| 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 "qobject/qdict.h" |
| 17 | #include "qobject/qlist.h" |
| 18 | #include "qapi/qobject-input-visitor.h" |
| 19 | #include "qobject/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)); |
| 36 | close(cfgfd); |
| 37 | if (ret < 0) { |
| 38 | unlink(cfgpath); |
| 39 | } |
| 40 | g_assert_cmpint(ret, ==, strlen(cfgdata)); |
| 41 | |
| 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 | |
| 51 | static void test_x86_memdev_resp(QObject *res, const char *mem_id, int size) |
| 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; |
| 66 | g_assert_cmpstr(memdev->id, ==, mem_id); |
| 67 | g_assert_cmpint(memdev->size, ==, size * MiB); |
| 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); |
| 81 | /* Test valid command */ |
| 82 | resp = qtest_qmp(qts, "{ 'execute': 'query-memdev' }"); |
| 83 | test_x86_memdev_resp(qdict_get(resp, "return"), "ram", 200); |
| 84 | qobject_unref(resp); |
| 85 | |
| 86 | qtest_quit(qts); |
| 87 | } |
| 88 | |
| 89 | /* FIXME: The test is currently broken on FreeBSD */ |
| 90 | #if defined(CONFIG_SPICE) && !defined(__FreeBSD__) |
| 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); |
| 98 | visit_type_SpiceInfo(v, "spice", &spice, &error_abort); |
| 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" |
| 112 | #ifndef WIN32 |
| 113 | "unix = \"on\"\n" |
| 114 | #endif |
| 115 | "disable-ticketing = \"on\"\n"; |
| 116 | |
| 117 | qts = qtest_init_with_config(cfgdata); |
| 118 | /* Test valid command */ |
| 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 | |
| 127 | static void test_object_available(QObject *res, const char *name, |
| 128 | const char *type) |
| 129 | { |
| 130 | Visitor *v; |
| 131 | g_autoptr(ObjectPropertyInfoList) objs = NULL; |
| 132 | ObjectPropertyInfoList *tmp; |
| 133 | ObjectPropertyInfo *obj; |
| 134 | bool object_available = false; |
| 135 | g_autofree char *childtype = g_strdup_printf("child<%s>", type); |
| 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; |
| 147 | if (g_str_equal(obj->name, name) && g_str_equal(obj->type, childtype)) { |
| 148 | object_available = true; |
| 149 | break; |
| 150 | } |
| 151 | |
| 152 | tmp = tmp->next; |
| 153 | } |
| 154 | |
| 155 | g_assert(object_available); |
| 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); |
| 170 | /* Test valid command */ |
| 171 | resp = qtest_qmp(qts, |
| 172 | "{ 'execute': 'qom-list'," |
| 173 | " 'arguments': {'path': '/objects' }}"); |
| 174 | test_object_available(qdict_get(resp, "return"), "rng0", "rng-builtin"); |
| 175 | qobject_unref(resp); |
| 176 | |
| 177 | qtest_quit(qts); |
| 178 | } |
| 179 | |
| 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 | |
| 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 | |
| 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); |
| 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 | } |
| 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 |
| 410 | } |
| 411 | #if defined(CONFIG_SPICE) && !defined(__FreeBSD__) |
| 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 | } |