]>
Commit | Line | Data |
---|---|---|
f6a5f380 DB |
1 | /* |
2 | * Validate -readconfig | |
3 | * | |
4 | * Copyright (c) 2022 Red Hat, Inc. | |
5 | * | |
6 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
7 | * See the COPYING file in the top-level directory. | |
8 | */ | |
9 | ||
10 | #include "qemu/osdep.h" | |
11 | #include "libqtest.h" | |
12 | #include "qapi/error.h" | |
13 | #include "qapi/qapi-visit-machine.h" | |
14 | #include "qapi/qapi-visit-qom.h" | |
15 | #include "qapi/qapi-visit-ui.h" | |
16 | #include "qapi/qmp/qdict.h" | |
17 | #include "qapi/qmp/qlist.h" | |
18 | #include "qapi/qobject-input-visitor.h" | |
19 | #include "qapi/qmp/qstring.h" | |
20 | #include "qemu/units.h" | |
21 | ||
22 | static QTestState *qtest_init_with_config(const char *cfgdata) | |
23 | { | |
24 | GError *error = NULL; | |
25 | g_autofree char *args = NULL; | |
26 | int cfgfd = -1; | |
27 | g_autofree char *cfgpath = NULL; | |
28 | QTestState *qts; | |
29 | ssize_t ret; | |
30 | ||
31 | cfgfd = g_file_open_tmp("readconfig-test-XXXXXX", &cfgpath, &error); | |
32 | g_assert_no_error(error); | |
33 | g_assert_cmpint(cfgfd, >=, 0); | |
34 | ||
35 | ret = qemu_write_full(cfgfd, cfgdata, strlen(cfgdata)); | |
9c23d719 | 36 | close(cfgfd); |
f6a5f380 DB |
37 | if (ret < 0) { |
38 | unlink(cfgpath); | |
39 | } | |
40 | g_assert_cmpint(ret, ==, strlen(cfgdata)); | |
41 | ||
f6a5f380 DB |
42 | args = g_strdup_printf("-nodefaults -machine none -readconfig %s", cfgpath); |
43 | ||
44 | qts = qtest_init(args); | |
45 | ||
46 | unlink(cfgpath); | |
47 | ||
48 | return qts; | |
49 | } | |
50 | ||
5a7d4dc9 | 51 | static void test_x86_memdev_resp(QObject *res, const char *mem_id, int size) |
f6a5f380 DB |
52 | { |
53 | Visitor *v; | |
54 | g_autoptr(MemdevList) memdevs = NULL; | |
55 | Memdev *memdev; | |
56 | ||
57 | g_assert(res); | |
58 | v = qobject_input_visitor_new(res); | |
59 | visit_type_MemdevList(v, NULL, &memdevs, &error_abort); | |
60 | ||
61 | g_assert(memdevs); | |
62 | g_assert(memdevs->value); | |
63 | g_assert(!memdevs->next); | |
64 | ||
65 | memdev = memdevs->value; | |
5a7d4dc9 TH |
66 | g_assert_cmpstr(memdev->id, ==, mem_id); |
67 | g_assert_cmpint(memdev->size, ==, size * MiB); | |
f6a5f380 DB |
68 | |
69 | visit_free(v); | |
70 | } | |
71 | ||
72 | static void test_x86_memdev(void) | |
73 | { | |
74 | QDict *resp; | |
75 | QTestState *qts; | |
76 | const char *cfgdata = | |
77 | "[memory]\n" | |
78 | "size = \"200\""; | |
79 | ||
80 | qts = qtest_init_with_config(cfgdata); | |
9c23d719 | 81 | /* Test valid command */ |
f6a5f380 | 82 | resp = qtest_qmp(qts, "{ 'execute': 'query-memdev' }"); |
5a7d4dc9 | 83 | test_x86_memdev_resp(qdict_get(resp, "return"), "ram", 200); |
f6a5f380 DB |
84 | qobject_unref(resp); |
85 | ||
86 | qtest_quit(qts); | |
87 | } | |
88 | ||
01013d2c TH |
89 | /* FIXME: The test is currently broken on FreeBSD */ |
90 | #if defined(CONFIG_SPICE) && !defined(__FreeBSD__) | |
f6a5f380 DB |
91 | static void test_spice_resp(QObject *res) |
92 | { | |
93 | Visitor *v; | |
94 | g_autoptr(SpiceInfo) spice = NULL; | |
95 | ||
96 | g_assert(res); | |
97 | v = qobject_input_visitor_new(res); | |
9c23d719 | 98 | visit_type_SpiceInfo(v, "spice", &spice, &error_abort); |
f6a5f380 DB |
99 | |
100 | g_assert(spice); | |
101 | g_assert(spice->enabled); | |
102 | ||
103 | visit_free(v); | |
104 | } | |
105 | ||
106 | static void test_spice(void) | |
107 | { | |
108 | QDict *resp; | |
109 | QTestState *qts; | |
110 | const char *cfgdata = | |
111 | "[spice]\n" | |
beecc4b7 MAL |
112 | #ifndef WIN32 |
113 | "unix = \"on\"\n" | |
114 | #endif | |
115 | "disable-ticketing = \"on\"\n"; | |
f6a5f380 DB |
116 | |
117 | qts = qtest_init_with_config(cfgdata); | |
9c23d719 | 118 | /* Test valid command */ |
f6a5f380 DB |
119 | resp = qtest_qmp(qts, "{ 'execute': 'query-spice' }"); |
120 | test_spice_resp(qdict_get(resp, "return")); | |
121 | qobject_unref(resp); | |
122 | ||
123 | qtest_quit(qts); | |
124 | } | |
125 | #endif | |
126 | ||
79571e7f TH |
127 | static void test_object_available(QObject *res, const char *name, |
128 | const char *type) | |
f6a5f380 DB |
129 | { |
130 | Visitor *v; | |
131 | g_autoptr(ObjectPropertyInfoList) objs = NULL; | |
132 | ObjectPropertyInfoList *tmp; | |
133 | ObjectPropertyInfo *obj; | |
79571e7f TH |
134 | bool object_available = false; |
135 | g_autofree char *childtype = g_strdup_printf("child<%s>", type); | |
f6a5f380 DB |
136 | |
137 | g_assert(res); | |
138 | v = qobject_input_visitor_new(res); | |
139 | visit_type_ObjectPropertyInfoList(v, NULL, &objs, &error_abort); | |
140 | ||
141 | g_assert(objs); | |
142 | tmp = objs; | |
143 | while (tmp) { | |
144 | g_assert(tmp->value); | |
145 | ||
146 | obj = tmp->value; | |
79571e7f TH |
147 | if (g_str_equal(obj->name, name) && g_str_equal(obj->type, childtype)) { |
148 | object_available = true; | |
9c23d719 | 149 | break; |
f6a5f380 DB |
150 | } |
151 | ||
152 | tmp = tmp->next; | |
153 | } | |
154 | ||
79571e7f | 155 | g_assert(object_available); |
f6a5f380 DB |
156 | |
157 | visit_free(v); | |
158 | } | |
159 | ||
160 | static void test_object_rng(void) | |
161 | { | |
162 | QDict *resp; | |
163 | QTestState *qts; | |
164 | const char *cfgdata = | |
165 | "[object]\n" | |
166 | "qom-type = \"rng-builtin\"\n" | |
167 | "id = \"rng0\"\n"; | |
168 | ||
169 | qts = qtest_init_with_config(cfgdata); | |
9c23d719 | 170 | /* Test valid command */ |
f6a5f380 DB |
171 | resp = qtest_qmp(qts, |
172 | "{ 'execute': 'qom-list'," | |
173 | " 'arguments': {'path': '/objects' }}"); | |
79571e7f | 174 | test_object_available(qdict_get(resp, "return"), "rng0", "rng-builtin"); |
f6a5f380 DB |
175 | qobject_unref(resp); |
176 | ||
177 | qtest_quit(qts); | |
178 | } | |
179 | ||
201aa17e TH |
180 | static void test_docs_config_ich9(void) |
181 | { | |
182 | QTestState *qts; | |
183 | QDict *resp; | |
184 | QObject *qobj; | |
185 | ||
186 | qts = qtest_initf("-nodefaults -readconfig docs/config/ich9-ehci-uhci.cfg"); | |
187 | ||
188 | resp = qtest_qmp(qts, "{ 'execute': 'qom-list'," | |
189 | " 'arguments': {'path': '/machine/peripheral' }}"); | |
190 | qobj = qdict_get(resp, "return"); | |
191 | test_object_available(qobj, "ehci", "ich9-usb-ehci1"); | |
192 | test_object_available(qobj, "uhci-1", "ich9-usb-uhci1"); | |
193 | test_object_available(qobj, "uhci-2", "ich9-usb-uhci2"); | |
194 | test_object_available(qobj, "uhci-3", "ich9-usb-uhci3"); | |
195 | qobject_unref(resp); | |
196 | ||
197 | qtest_quit(qts); | |
198 | } | |
199 | ||
bc55e2ea TH |
200 | #if defined(CONFIG_POSIX) && defined(CONFIG_SLIRP) |
201 | ||
202 | static char *make_temp_img(const char *template, const char *format, int size) | |
203 | { | |
204 | GError *error = NULL; | |
205 | char *temp_name; | |
206 | int fd; | |
207 | ||
208 | /* Create a temporary image names */ | |
209 | fd = g_file_open_tmp(template, &temp_name, &error); | |
210 | if (fd == -1) { | |
211 | fprintf(stderr, "unable to create file: %s\n", error->message); | |
212 | g_error_free(error); | |
213 | return NULL; | |
214 | } | |
215 | close(fd); | |
216 | ||
217 | if (!mkimg(temp_name, format, size)) { | |
218 | fprintf(stderr, "qemu-img failed to create %s\n", temp_name); | |
219 | g_free(temp_name); | |
220 | return NULL; | |
221 | } | |
222 | ||
223 | return temp_name; | |
224 | } | |
225 | ||
226 | struct device { | |
227 | const char *name; | |
228 | const char *type; | |
229 | }; | |
230 | ||
231 | static void test_docs_q35(const char *input_file, struct device *devices) | |
232 | { | |
233 | QTestState *qts; | |
234 | QDict *resp; | |
235 | QObject *qobj; | |
236 | int ret, i; | |
237 | g_autofree char *cfg_file = NULL, *sedcmd = NULL; | |
238 | g_autofree char *hd_file = NULL, *cd_file = NULL; | |
239 | ||
240 | /* Check that all the devices are available in the QEMU binary */ | |
241 | for (i = 0; devices[i].name; i++) { | |
242 | if (!qtest_has_device(devices[i].type)) { | |
243 | g_test_skip("one of the required devices is not available"); | |
244 | return; | |
245 | } | |
246 | } | |
247 | ||
248 | hd_file = make_temp_img("qtest_disk_XXXXXX.qcow2", "qcow2", 1); | |
249 | cd_file = make_temp_img("qtest_cdrom_XXXXXX.iso", "raw", 1); | |
250 | if (!hd_file || !cd_file) { | |
251 | g_test_skip("could not create disk images"); | |
252 | goto cleanup; | |
253 | } | |
254 | ||
255 | /* Create a temporary config file where we replace the disk image names */ | |
256 | ret = g_file_open_tmp("q35-emulated-XXXXXX.cfg", &cfg_file, NULL); | |
257 | if (ret == -1) { | |
258 | g_test_skip("could not create temporary config file"); | |
259 | goto cleanup; | |
260 | } | |
261 | close(ret); | |
262 | ||
263 | sedcmd = g_strdup_printf("sed -e 's,guest.qcow2,%s,' -e 's,install.iso,%s,'" | |
264 | " %s %s > '%s'", | |
265 | hd_file, cd_file, | |
266 | !qtest_has_accel("kvm") ? "-e '/accel/d'" : "", | |
267 | input_file, cfg_file); | |
268 | ret = system(sedcmd); | |
269 | if (ret) { | |
270 | g_test_skip("could not modify temporary config file"); | |
271 | goto cleanup; | |
272 | } | |
273 | ||
274 | qts = qtest_initf("-machine none -nodefaults -readconfig %s", cfg_file); | |
275 | ||
276 | /* Check memory size */ | |
277 | resp = qtest_qmp(qts, "{ 'execute': 'query-memdev' }"); | |
278 | test_x86_memdev_resp(qdict_get(resp, "return"), "pc.ram", 1024); | |
279 | qobject_unref(resp); | |
280 | ||
281 | resp = qtest_qmp(qts, "{ 'execute': 'qom-list'," | |
282 | " 'arguments': {'path': '/machine/peripheral' }}"); | |
283 | qobj = qdict_get(resp, "return"); | |
284 | ||
285 | /* Check that all the devices have been created */ | |
286 | for (i = 0; devices[i].name; i++) { | |
287 | test_object_available(qobj, devices[i].name, devices[i].type); | |
288 | } | |
289 | ||
290 | qobject_unref(resp); | |
291 | ||
292 | qtest_quit(qts); | |
293 | ||
294 | cleanup: | |
295 | if (hd_file) { | |
296 | unlink(hd_file); | |
297 | } | |
298 | if (cd_file) { | |
299 | unlink(cd_file); | |
300 | } | |
301 | if (cfg_file) { | |
302 | unlink(cfg_file); | |
303 | } | |
304 | } | |
305 | ||
306 | static void test_docs_q35_emulated(void) | |
307 | { | |
308 | struct device devices[] = { | |
309 | { "ich9-pcie-port-1", "ioh3420" }, | |
310 | { "ich9-pcie-port-2", "ioh3420" }, | |
311 | { "ich9-pcie-port-3", "ioh3420" }, | |
312 | { "ich9-pcie-port-4", "ioh3420" }, | |
313 | { "ich9-pci-bridge", "i82801b11-bridge" }, | |
314 | { "ich9-ehci-1", "ich9-usb-ehci1" }, | |
315 | { "ich9-ehci-2", "ich9-usb-ehci2" }, | |
316 | { "ich9-uhci-1", "ich9-usb-uhci1" }, | |
317 | { "ich9-uhci-2", "ich9-usb-uhci2" }, | |
318 | { "ich9-uhci-3", "ich9-usb-uhci3" }, | |
319 | { "ich9-uhci-4", "ich9-usb-uhci4" }, | |
320 | { "ich9-uhci-5", "ich9-usb-uhci5" }, | |
321 | { "ich9-uhci-6", "ich9-usb-uhci6" }, | |
322 | { "sata-disk", "ide-hd" }, | |
323 | { "sata-optical-disk", "ide-cd" }, | |
324 | { "net", "e1000" }, | |
325 | { "video", "VGA" }, | |
326 | { "ich9-hda-audio", "ich9-intel-hda" }, | |
327 | { "ich9-hda-duplex", "hda-duplex" }, | |
328 | { NULL, NULL } | |
329 | }; | |
330 | ||
331 | test_docs_q35("docs/config/q35-emulated.cfg", devices); | |
332 | } | |
333 | ||
334 | static void test_docs_q35_virtio_graphical(void) | |
335 | { | |
336 | struct device devices[] = { | |
337 | { "pcie.1", "pcie-root-port" }, | |
338 | { "pcie.2", "pcie-root-port" }, | |
339 | { "pcie.3", "pcie-root-port" }, | |
340 | { "pcie.4", "pcie-root-port" }, | |
341 | { "pcie.5", "pcie-root-port" }, | |
342 | { "pcie.6", "pcie-root-port" }, | |
343 | { "pcie.7", "pcie-root-port" }, | |
344 | { "pcie.8", "pcie-root-port" }, | |
345 | { "scsi", "virtio-scsi-pci" }, | |
346 | { "scsi-disk", "scsi-hd" }, | |
347 | { "scsi-optical-disk", "scsi-cd" }, | |
348 | { "net", "virtio-net-pci" }, | |
349 | { "usb", "nec-usb-xhci" }, | |
350 | { "tablet", "usb-tablet" }, | |
351 | { "video", "qxl-vga" }, | |
352 | { "sound", "ich9-intel-hda" }, | |
353 | { "duplex", "hda-duplex" }, | |
354 | { NULL, NULL } | |
355 | }; | |
356 | ||
357 | test_docs_q35("docs/config/q35-virtio-graphical.cfg", devices); | |
358 | } | |
359 | ||
360 | static void test_docs_q35_virtio_serial(void) | |
361 | { | |
362 | struct device devices[] = { | |
363 | { "pcie.1", "pcie-root-port" }, | |
364 | { "pcie.2", "pcie-root-port" }, | |
365 | { "pcie.3", "pcie-root-port" }, | |
366 | { "pcie.4", "pcie-root-port" }, | |
367 | { "pcie.5", "pcie-root-port" }, | |
368 | { "pcie.6", "pcie-root-port" }, | |
369 | { "pcie.7", "pcie-root-port" }, | |
370 | { "pcie.8", "pcie-root-port" }, | |
371 | { "scsi", "virtio-scsi-pci" }, | |
372 | { "scsi-disk", "scsi-hd" }, | |
373 | { "scsi-optical-disk", "scsi-cd" }, | |
374 | { "net", "virtio-net-pci" }, | |
375 | { NULL, NULL } | |
376 | }; | |
377 | ||
378 | test_docs_q35("docs/config/q35-virtio-serial.cfg", devices); | |
379 | } | |
380 | ||
381 | #endif /* CONFIG_LINUX */ | |
382 | ||
f6a5f380 DB |
383 | int main(int argc, char *argv[]) |
384 | { | |
385 | const char *arch; | |
386 | g_test_init(&argc, &argv, NULL); | |
387 | ||
388 | arch = qtest_get_arch(); | |
389 | ||
390 | if (g_str_equal(arch, "i386") || | |
391 | g_str_equal(arch, "x86_64")) { | |
392 | qtest_add_func("readconfig/x86/memdev", test_x86_memdev); | |
335da811 TH |
393 | if (qtest_has_device("ich9-usb-ehci1") && |
394 | qtest_has_device("ich9-usb-uhci1")) { | |
395 | qtest_add_func("readconfig/x86/ich9-ehci-uhci", test_docs_config_ich9); | |
396 | } | |
bc55e2ea TH |
397 | #if defined(CONFIG_POSIX) && defined(CONFIG_SLIRP) |
398 | qtest_add_func("readconfig/x86/q35-emulated", test_docs_q35_emulated); | |
399 | qtest_add_func("readconfig/x86/q35-virtio-graphical", | |
400 | test_docs_q35_virtio_graphical); | |
401 | if (g_test_slow()) { | |
402 | /* | |
403 | * q35-virtio-serial.cfg is a subset of q35-virtio-graphical.cfg, | |
404 | * so we can skip the test in quick mode | |
405 | */ | |
406 | qtest_add_func("readconfig/x86/q35-virtio-serial", | |
407 | test_docs_q35_virtio_serial); | |
408 | } | |
409 | #endif | |
f6a5f380 | 410 | } |
01013d2c | 411 | #if defined(CONFIG_SPICE) && !defined(__FreeBSD__) |
f6a5f380 DB |
412 | qtest_add_func("readconfig/spice", test_spice); |
413 | #endif | |
414 | ||
415 | qtest_add_func("readconfig/object-rng", test_object_rng); | |
416 | ||
417 | return g_test_run(); | |
418 | } |