master ohmu/fastfetch-openbsd / tests / gpu_pci.c
ohmu  ·  2026-10-15
  1// Tests for ffGPUParsePciIdsFile(), the pci.ids lookup that resolves a PCI GPU name on Linux and
  2// the BSDs.
  3//
  4// The parser takes the file content as a buffer instead of opening the file itself, which is
  5// exactly what makes it testable: a small hand written pci.ids excerpt is enough, and the
  6// expectations stay readable.
  7//
  8// The function is only compiled where libfastfetch ships the pci.ids based detection, so on other
  9// platforms this test has nothing to link against and only reports success.
 10
 11#include "common/textModifier.h"
 12#include "detection/gpu/gpu.h"
 13
 14#include <stdio.h>
 15#include <stdlib.h>
 16
 17#if FF_GPU_HAS_PCI_IDS
 18
 19static void verifyPciEx(
 20    const char* content, uint8_t subclass, uint16_t vendor, uint16_t device,
 21    const char* presetVendor, const char* presetName,
 22    const char* expectedVendor, const char* expectedName,
 23    int lineNo
 24) {
 25    FFGPUResult gpu = {};
 26    ffStrbufInit(&gpu.vendor);
 27    ffStrbufInit(&gpu.name);
 28
 29    // A caller may already know the vendor (for example from the PCI subsystem), in which case the
 30    // parser must not clobber it.
 31    if (presetVendor) {
 32        ffStrbufSetS(&gpu.vendor, presetVendor);
 33    }
 34    if (presetName) {
 35        ffStrbufSetS(&gpu.name, presetName);
 36    }
 37
 38    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateS(content);
 39    ffGPUParsePciIdsFile(&buffer, subclass, vendor, device, &gpu);
 40
 41    if (!ffStrbufEqualS(&gpu.vendor, expectedVendor) || !ffStrbufEqualS(&gpu.name, expectedName)) {
 42        fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffGPUParsePciIdsFile(vendor=0x%04x, device=0x%04x, subclass=%u): expected vendor=\"%s\" name=\"%s\", got vendor=\"%s\" name=\"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
 43            lineNo, vendor, device, (unsigned) subclass, expectedVendor, expectedName, gpu.vendor.chars, gpu.name.chars);
 44        exit(1);
 45    }
 46
 47    ffStrbufDestroy(&gpu.vendor);
 48    ffStrbufDestroy(&gpu.name);
 49}
 50
 51#define VERIFY_PCI(content, subclass, vendor, device, expectedVendor, expectedName) \
 52    verifyPciEx((content), (subclass), (vendor), (device), nullptr, nullptr, (expectedVendor), (expectedName), __LINE__)
 53
 54#define VERIFY_PCI_EX(content, subclass, vendor, device, presetVendor, presetName, expectedVendor, expectedName) \
 55    verifyPciEx((content), (subclass), (vendor), (device), (presetVendor), (presetName), (expectedVendor), (expectedName), __LINE__)
 56
 57// A miniature pci.ids. Vendors are ordered, device ids grow within a block, and the block is closed
 58// by a blank line -- the same shape the real file has.
 59static const char PCI_IDS[] =
 60    "# pci.ids excerpt used by the tests\n"
 61    "#\n"
 62    "1a2b  Vendor Alpha\n"
 63    "\t0001  Alpha One\n"
 64    "\t0005  Alpha Five\n"
 65    "\t\t0005 0abc  Subsystem that must be ignored\n"
 66    "\t00ff  Alpha FF [Alias]\n"
 67    "\t1000  Alpha Thousand\n"
 68    "\n"
 69    "1a2c  Vendor Beta\n"
 70    "\t0001  Beta One\n"
 71    "\t0002  Beta Two\n"
 72    "\t1234  Beta TwelveThirtyFour\n"
 73    "\n"
 74    "abcd  Vendor Delta\n"
 75    "\t0e1f  Delta Device\n"
 76    "\n"
 77    "ffff  Vendor NoDevices\n"
 78    "\n"
 79    "C 00  Unclassified device\n";
 80
 81// The last line has no terminating newline, so the parser has to fall back to the end of the
 82// buffer.
 83static const char PCI_IDS_NO_TRAILING_NL[] =
 84    "#\n"
 85    "1a2b  Vendor Alpha\n"
 86    "\t0001  Alpha One";
 87
 88// A vendor with no device lines at all, again without a terminating newline.
 89static const char PCI_IDS_VENDOR_ONLY[] =
 90    "#\n"
 91    "1a2b  Vendor Alpha";
 92
 93int main(void) {
 94    // An empty buffer is not an error: the name is synthesized from the vendor and device id.
 95    {
 96        VERIFY_PCI("", 0, 0x1a2b, 0x0001, "", "Unknown Device 0001 (VGA compatible)");
 97        VERIFY_PCI("", 2, 0x1a2b, 0x0001, "", "Unknown Device 0001 (3D)");
 98    }
 99
100    // The happy path: vendor and device are both present.
101    {
102        VERIFY_PCI(PCI_IDS, 2, 0x1a2b, 0x0001, "Vendor Alpha", "Alpha One");
103        VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0005, "Vendor Alpha", "Alpha Five");
104        VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x1000, "Vendor Alpha", "Alpha Thousand");
105    }
106
107    // Hex letters in the ids work on both sides.
108    {
109        VERIFY_PCI(PCI_IDS, 0, 0xabcd, 0x0e1f, "Vendor Delta", "Delta Device");
110    }
111
112    // A name of the form `... [Alias]` is reduced to the alias. Only the trailing bracket pair is
113    // considered, and a `[` without a matching `]` at the end is kept verbatim.
114    {
115        VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x00ff, "Vendor Alpha", "Alias");
116    }
117
118    // The lookup must stay inside the vendor block. `Beta` also has a 0x1234, but a lookup under
119    // `Alpha` must not find it -- `Alpha` has no 0x1234 of its own.
120    {
121        VERIFY_PCI(PCI_IDS, 0, 0x1a2c, 0x1234, "Vendor Beta", "Beta TwelveThirtyFour");
122        VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x1234, "Vendor Alpha", "Vendor Alpha Device 1234 (VGA compatible)");
123    }
124
125    // An unknown device under a known vendor synthesizes a name from the vendor.
126    {
127        VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002 (VGA compatible)");
128        VERIFY_PCI(PCI_IDS, 1, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002 (XGA compatible)");
129        VERIFY_PCI(PCI_IDS, 2, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002 (3D)");
130        VERIFY_PCI(PCI_IDS, 5, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002");
131    }
132
133    // The synthesized name uses the upper case form of the device id, while the lookup needle is
134    // lower case.
135    {
136        VERIFY_PCI(PCI_IDS, 0, 0xabcd, 0xbeef, "Vendor Delta", "Vendor Delta Device BEEF (VGA compatible)");
137    }
138
139    // A device id below the first entry, between two entries and above the last one all miss. The
140    // first case is also what the "stop once the ids grow past the target" shortcut relies on.
141    {
142        VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0000, "Vendor Alpha", "Vendor Alpha Device 0000 (VGA compatible)");
143        VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0020, "Vendor Alpha", "Vendor Alpha Device 0020 (VGA compatible)");
144        VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x9999, "Vendor Alpha", "Vendor Alpha Device 9999 (VGA compatible)");
145    }
146
147    // A subsystem id lives at twice the indentation and must never be mistaken for a device id,
148    // even when it is the value being searched for.
149    {
150        VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0abc, "Vendor Alpha", "Vendor Alpha Device 0ABC (VGA compatible)");
151    }
152
153    // A vendor with no device lines falls back to the synthesized name, and the vendor is still
154    // reported.
155    {
156        VERIFY_PCI(PCI_IDS, 0, 0xffff, 0x0001, "Vendor NoDevices", "Vendor NoDevices Device 0001 (VGA compatible)");
157    }
158
159    // An unknown vendor leaves the vendor buffer empty, so the synthesized name says "Unknown".
160    {
161        VERIFY_PCI(PCI_IDS, 0, 0x1234, 0x5678, "", "Unknown Device 5678 (VGA compatible)");
162    }
163
164    // Lines beyond the vendor section (the class list) are not reachable from a vendor block.
165    {
166        VERIFY_PCI(PCI_IDS, 0, 0x000c, 0x0000, "", "Unknown Device 0000 (VGA compatible)");
167    }
168
169    // A device line as the very last line of the buffer, with no trailing newline.
170    {
171        VERIFY_PCI(PCI_IDS_NO_TRAILING_NL, 0, 0x1a2b, 0x0001, "Vendor Alpha", "Alpha One");
172        VERIFY_PCI(PCI_IDS_NO_TRAILING_NL, 0, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002 (VGA compatible)");
173    }
174
175    // A vendor line as the very last line of the buffer, with no device lines after it.
176    {
177        VERIFY_PCI(PCI_IDS_VENDOR_ONLY, 0, 0x1a2b, 0x0001, "Vendor Alpha", "Vendor Alpha Device 0001 (VGA compatible)");
178    }
179
180    // A known vendor is not overwritten, but resolving the device name still happens.
181    {
182        VERIFY_PCI_EX(PCI_IDS, 0, 0x1a2b, 0x0001, "Custom Vendor", nullptr, "Custom Vendor", "Alpha One");
183    }
184
185    // Likewise a name the caller already resolved is kept when the entry is a plain one.
186    {
187        VERIFY_PCI_EX(PCI_IDS, 0, 0x1a2b, 0x0001, nullptr, "Custom Name", "Vendor Alpha", "Custom Name");
188    }
189
190    puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
191    return 0;
192}
193
194#else // FF_TEST_HAS_PCI_IDS
195
196int main(void) {
197    puts("\033[32mTests skipped. (pci.ids parser not available on this platform)" FASTFETCH_TEXT_MODIFIER_RESET);
198    return 0;
199}
200
201#endif