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