#include #include #include #include #include "testutils.h" #include "internal.h" #include "viralloc.h" #include "viridentity.h" #include "qemu/qemu_block.h" #include "qemu/qemu_capabilities.h" #include "qemu/qemu_domain.h" #include "qemu/qemu_migration.h" #include "qemu/qemu_passt.h" #include "qemu/qemu_process.h" #include "qemu/qemu_slirp.h" #include "qemu/qemu_virtiofs.h" #include "qemu/qemu_vhost_user.h" #include "datatypes.h" #include "conf/storage_conf.h" #include "virfilewrapper.h" #include "configmake.h" #include "testutilsqemuschema.h" #include "qemufakedrivers.h" #define LIBVIRT_QEMU_CAPSPRIV_H_ALLOW #include "qemu/qemu_capspriv.h" #define LIBVIRT_QEMU_PROCESSPRIV_H_ALLOW #include "qemu/qemu_processpriv.h" #define LIBVIRT_VIRCOMMANDPRIV_H_ALLOW #include "util/vircommandpriv.h" #include "testutilsqemu.h" #define VIR_FROM_THIS VIR_FROM_QEMU static virQEMUDriver driver; /* this test case uses virTestDummyFDContextNew so we want to mock dup() to * track FD hints properly */ #include "virmock.h" VIR_TEST_MAKE_DUMMY_FD_INSTALL_DUP_MOCK static void testQemuPrepareHostdevPCI(virDomainHostdevDef *hostdev) { qemuDomainHostdevPrivate *hostdevPriv = QEMU_DOMAIN_HOSTDEV_PRIVATE(hostdev); if (virHostdevIsPCIDeviceWithIOMMUFD(hostdev)) { g_autofree char *name = g_strdup_printf("hostdev-%s-fd", hostdev->info->alias); int vfioDeviceFD = virTestMakeDummyFD(g_strdup_printf("@hostdev-%s-fd@", hostdev->info->alias)); hostdevPriv->vfioDeviceFd = qemuFDPassDirectNew(name, &vfioDeviceFD); } } static void testQemuPrepareHostdevUSB(virDomainHostdevDef *hostdev) { virDomainHostdevSubsysUSB *usb = &hostdev->source.subsys.u.usb; if (!usb->device && !usb->bus) { if (usb->vendor == 0x1234 && usb->product == 0x4321) { usb->bus = 42; usb->device = 0x1234; } else { g_assert_not_reached(); } } else if (!usb->device && !usb->vendor && !usb->product) { if (usb->bus == 2 && STREQ(usb->port, "3")) { usb->device = 4; } else { g_assert_not_reached(); } } } static void testQemuPrepareHostdev(virDomainObj *vm) { qemuDomainObjPrivate *priv = QEMU_DOMAIN_PRIVATE(vm); size_t i; for (i = 0; i < vm->def->nhostdevs; i++) { virDomainHostdevDef *hostdev = vm->def->hostdevs[i]; if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) continue; switch (hostdev->source.subsys.type) { case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: testQemuPrepareHostdevUSB(hostdev); break; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: testQemuPrepareHostdevPCI(hostdev); break; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI: case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST: case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST: break; } } if (vm->def->iommufd_fdgroup) { ignore_value(qemuProcessGetPassedIommuFd(vm)); } else if (virDomainDefHasPCIHostdevWithIOMMUFD(vm->def)) { int iommufd = virTestMakeDummyFD(g_strdup("@iommufd-fd@")); priv->iommufd = qemuFDPassDirectNew("iommufd", &iommufd); } } static void testUpdateQEMUCapsHostCPUModel(virQEMUCaps *qemuCaps, virArch hostArch) { virQEMUCapsUpdateHostCPUModel(qemuCaps, hostArch, VIR_DOMAIN_VIRT_KVM); virQEMUCapsUpdateHostCPUModel(qemuCaps, hostArch, VIR_DOMAIN_VIRT_QEMU); } static int testCheckExclusiveFlags(int flags) { virCheckFlags(FLAG_EXPECT_FAILURE | FLAG_EXPECT_PARSE_ERROR | FLAG_REAL_CAPS | FLAG_SLIRP_HELPER | FLAG_ALLOW_DUPLICATE_OUTPUT | FLAG_ALLOW_MISSING_INPUT | 0, -1); return 0; } static virCommand * testCompareXMLToArgvCreateArgs(virQEMUDriver *drv, virDomainObj *vm, const char *migrateURI, testQemuInfo *info, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; size_t i; if (qemuProcessCreatePretendCmdPrepare(drv, vm, migrateURI, VIR_QEMU_PROCESS_START_COLD) < 0) return NULL; if (qemuDomainDeviceBackendChardevForeach(vm->def, testQemuPrepareHostBackendChardevOne, vm) < 0) return NULL; if (testQemuPrepareHostBackendChardevOne(NULL, priv->monConfig, vm) < 0) return NULL; for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; virStorageSource *src; /* host cdrom requires special treatment in qemu, mock it */ if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM && disk->src->format == VIR_STORAGE_FILE_RAW && virStorageSourceIsBlockLocal(disk->src) && STREQ(disk->src->path, "/dev/cdrom")) disk->src->hostcdrom = true; for (src = disk->src; virStorageSourceIsBacking(src); src = src->backingStore) { if (src->type == VIR_STORAGE_TYPE_VHOST_VDPA) { qemuDomainStorageSourcePrivate *srcpriv = qemuDomainStorageSourcePrivateFetch(src); int fd = virTestMakeDummyFD(g_strdup_printf("@vdpa-%s-fd@", qemuBlockStorageSourceGetStorageNodename(src))); srcpriv->fdpass = qemuFDPassNew(qemuBlockStorageSourceGetStorageNodename(src), priv); qemuFDPassAddFD(srcpriv->fdpass, &fd, "-vdpa"); } } } if (vm->def->vsock) { virDomainVsockDef *vsock = vm->def->vsock; qemuDomainVsockPrivate *vsockPriv = (qemuDomainVsockPrivate *)vsock->privateData; if (vsock->auto_cid == VIR_TRISTATE_BOOL_YES) vsock->guest_cid = 42; vsockPriv->vhostfd = virTestMakeDummyFD(g_strdup("@vsock-vhost-fd@")); } for (i = 0; i < vm->def->nfss; i++) { unsigned long long ver = 0; virDomainFSDef *fs = vm->def->fss[i]; virStringParseVersion(&ver, info->args.capsver, false); if (fs->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS && !fs->sock) { /* QEMU 8.0.0 was the first release without virtiofsd included. Assume that from that version, the Rust version of virtiofsd which supports separate options is used. */ if (ver != 0 && ver < 8 * 1000 * 1000) continue; virBitmapSetBitExpand(fs->caps, QEMU_VHOST_USER_FS_FEATURE_SEPARATE_OPTIONS); } } for (i = 0; i < vm->def->ntpms; i++) { if (vm->def->tpms[i]->type != VIR_DOMAIN_TPM_TYPE_EMULATOR) continue; VIR_FREE(vm->def->tpms[i]->data.emulator.source->data.nix.path); vm->def->tpms[i]->data.emulator.source->type = VIR_DOMAIN_CHR_TYPE_UNIX; vm->def->tpms[i]->data.emulator.source->data.nix.path = g_strdup("/dev/test"); } for (i = 0; i < vm->def->nvideos; i++) { virDomainVideoDef *video = vm->def->videos[i]; if (video->backend == VIR_DOMAIN_VIDEO_BACKEND_TYPE_VHOSTUSER) { qemuDomainVideoPrivate *vpriv = QEMU_DOMAIN_VIDEO_PRIVATE(video); vpriv->vhost_user_fd = virTestMakeDummyFD(g_strdup("@vhost-user-video-fd@")); } } testQemuPrepareHostdev(vm); if (flags & FLAG_SLIRP_HELPER) { for (i = 0; i < vm->def->nnets; i++) { virDomainNetDef *net = vm->def->nets[i]; if (net->type == VIR_DOMAIN_NET_TYPE_USER && net->backend.type == VIR_DOMAIN_NET_BACKEND_DEFAULT && virQEMUCapsGet(info->qemuCaps, QEMU_CAPS_DBUS_VMSTATE)) { qemuSlirp *slirp = qemuSlirpNew(); slirp->fd[0] = 42; QEMU_DOMAIN_NETWORK_PRIVATE(net)->slirp = slirp; } } } return qemuProcessCreatePretendCmdBuild(vm, migrateURI); } struct testValidateSchemaCommandData { const char *name; const char *schema; bool allowIncomplete; /* relax validator for commands with incomplete schema */ }; static const struct testValidateSchemaCommandData commands[] = { { "-blockdev", "blockdev-add", false }, { "-netdev", "netdev_add", false }, { "-object", "object-add", false }, { "-device", "device_add", true }, { "-chardev", "chardev-add", false }, }; static int testCompareXMLToArgvValidateSchema(virCommand *cmd, testQemuInfo *info) { char **args; size_t nargs; size_t a; if (!info->qmpSchema) return 0; virCommandArgListAccess(cmd, &args, &nargs); for (a = 0; a < nargs; a++) { const char *curcommand = args[a]; const char *curargs = NULL; size_t i; if (a + 1 < nargs) curargs = args[a + 1]; for (i = 0; i < G_N_ELEMENTS(commands); i++) { const struct testValidateSchemaCommandData *command = commands + i; g_auto(virBuffer) debug = VIR_BUFFER_INITIALIZER; g_autoptr(virJSONValue) jsonargs = NULL; if (STRNEQ(curcommand, command->name)) continue; if (!curargs) { VIR_TEST_VERBOSE("expected arguments for command '%s'", command->name); return -1; } if (*curargs != '{') { a++; break; } if (!(jsonargs = virJSONValueFromString(curargs))) return -1; if (testQEMUSchemaValidateCommand(command->schema, jsonargs, info->qmpSchema, false, false, command->allowIncomplete, &debug) < 0) { VIR_TEST_VERBOSE("failed to validate '%s %s' against QAPI schema: %s", command->name, curargs, virBufferCurrentContent(&debug)); return -1; } a++; } } return 0; } struct testCompareXMLToArgvStabilizeOneFindJSONObjectIterData { const char *field; bool recurse; virJSONValue *ret; }; static int testCompareXMLToArgvStabilizeOneFindJSONObjectIter(const char *key, virJSONValue *value, void *opaque) { struct testCompareXMLToArgvStabilizeOneFindJSONObjectIterData *data = opaque; /* negative value breaks iteration and returns specific return value */ if (STREQ(data->field, key)) return -1; if (!data->recurse) return 0; if (!virJSONValueIsObject(value)) return 0; if (virJSONValueObjectForeachKeyValue(value, testCompareXMLToArgvStabilizeOneFindJSONObjectIter, data) == -2) { /* -2 means that the callback found something; now if it was the above * call that found the key we need to fill the pointer to what we've * called it with */ if (data->ret == NULL) data->ret = value; return -1; } return 0; } /** * testCompareXMLToArgvStabilizeOne: * @arg: argument to modify (may be replaced by other memory) * @field: field to replace * @substitutions: optional hash-table to look up value of @field and replace * it with the stored string * * Takes one qemu argument @arg and replaces the value of '@field' by a stable * string, either 'XXXXXXX' if @substitutions is NULL or the value of @field * wasn't found in @substitutions, or with the string from @substitutions if * found. * * Works both on JSON and normal arguments. */ static void testCompareXMLToArgvStabilizeOne(char **arg, const char *field, GHashTable *substitutions, bool recurseJSON) { g_autofree char *oldarg = g_steal_pointer(arg); if (*oldarg == '{') { g_autoptr(virJSONValue) j = virJSONValueFromString(oldarg); struct testCompareXMLToArgvStabilizeOneFindJSONObjectIterData data = { .field = field, .recurse = recurseJSON, }; const char *curstr = NULL; /* Any JSON args will be validated so this ought not to happen */ if (!j) { *arg = g_steal_pointer(&oldarg); return; } if (virJSONValueObjectForeachKeyValue(j, testCompareXMLToArgvStabilizeOneFindJSONObjectIter, &data) == -2) { /* -2 means that the callback found something; now if it was the above * call that found the key we need to fill the pointer to what we've * called it with */ if (data.ret == NULL) data.ret = j; } if (data.ret) { virJSONValue *cur = virJSONValueObjectGet(data.ret, field); if (cur) { switch (virJSONValueGetType(cur)) { case VIR_JSON_TYPE_STRING: curstr = virJSONValueGetString(cur); break; case VIR_JSON_TYPE_NUMBER: curstr = virJSONValueGetNumberString(cur); break; case VIR_JSON_TYPE_OBJECT: case VIR_JSON_TYPE_ARRAY: case VIR_JSON_TYPE_BOOLEAN: case VIR_JSON_TYPE_NULL: break; } } } if (curstr) { g_autoptr(virJSONValue) newval = NULL; const char *subst = NULL; if (substitutions) subst = g_hash_table_lookup(substitutions, curstr); if (!subst) subst = "XXXXXXX"; newval = virJSONValueNewString(g_strdup(subst)); ignore_value(virJSONValueObjectReplaceKey(data.ret, field, &newval)); *arg = virJSONValueToString(j, false); return; } else { *arg = g_steal_pointer(&oldarg); return; } } else { g_autofree char *fieldmatch = g_strdup_printf("%s=", field); char *match; char *val; char *rest; const char *subst = NULL; if (!(match = strstr(oldarg, fieldmatch))) { *arg = g_steal_pointer(&oldarg); return; } if ((rest = strchr(match, ','))) *rest = '\0'; *match = '\0'; val = match + strlen(fieldmatch); if (substitutions) subst = g_hash_table_lookup(substitutions, val); if (!subst) subst = "XXXXXXX"; if (rest) *rest = ','; *arg = g_strdup_printf("%s%s%s%s", oldarg, fieldmatch, subst, NULLSTR_EMPTY(rest)); } } static void testCompareXMLToArgvStabilizeArgs(virCommand *cmd, GHashTable *fdsubsts) { char **args; size_t nargs; size_t a; virCommandArgListAccess(cmd, &args, &nargs); for (a = 0; a < nargs; a++) { /* Any following replacements want also an argument for an option so * so we guarantee that there's at least one extra arg */ if (a + 1 >= nargs) break; if (STREQ(args[a], "-add-fd") || STREQ(args[a], "-chardev") || (STREQ(args[a], "-object") && STRPREFIX(args[a + 1], "{\"qom-type\":\"iommufd\""))) { testCompareXMLToArgvStabilizeOne(&args[a + 1], "fd", fdsubsts, false); /* qemuxmlconftest also tests the JSON syntax which isn't supported via * -chardev upstream. The syntax is weird so we need to make sure to * apply the substitution only on JSON with -chardev */ if (STREQ(args[a], "-chardev") && STRPREFIX(args[a + 1], "{")) { testCompareXMLToArgvStabilizeOne(&args[a + 1], "str", fdsubsts, true); } a++; } else if (STREQ(args[a], "-device")) { if (STRPREFIX(args[a + 1], "{\"driver\":\"vhost-vsock-")) { testCompareXMLToArgvStabilizeOne(&args[a + 1], "vhostfd", fdsubsts, false); } else if (STRPREFIX(args[a + 1], "{\"driver\":\"vfio-pci\"")) { testCompareXMLToArgvStabilizeOne(&args[a + 1], "fd", fdsubsts, false); } a++; } else if (STREQ(args[a], "-netdev")) { /* Stabiilize both the singular and plural forms. For the plural * forms we don't bother splitting the colon separated lists into * individual FDs so they will stay censored */ testCompareXMLToArgvStabilizeOne(&args[a + 1], "fd", fdsubsts, false); testCompareXMLToArgvStabilizeOne(&args[a + 1], "fds", fdsubsts, false); testCompareXMLToArgvStabilizeOne(&args[a + 1], "vhostfd", fdsubsts, false); testCompareXMLToArgvStabilizeOne(&args[a + 1], "vhostfds", fdsubsts, false); a++; } } } static int testInfoCheckDuplicate(testQemuInfo *info) { const char *path = info->outfile; if (info->flags & FLAG_ALLOW_DUPLICATE_OUTPUT) return 0; if (!path) path = info->errfile; if (g_hash_table_contains(info->conf->duplicateTests, path)) { fprintf(stderr, "\nduplicate invocation of test case: %s\n'", path); return -1; } g_hash_table_insert(info->conf->duplicateTests, g_strdup(path), NULL); return 0; } /** * testQemuConfXMLCommon: Prepare common test data (e.g. parse input XML) * for a test case. * * @info: test info struct to prepare * @rv: return value that the caller is supposed to return (see below) * * Since some of the prepared data is reused by multiple tests, which can be * potentially skipped via 'VIR_TEST_RANGE', this function is designed to be * callable multiple times but processes data just once. * * Returns 'true' if subsequent tests are expected to run (input XML was parsed * properly and test is not expected to fail on parse error). Otherwise 'false' * is returned and rv is populated according to the following logic: * 1) expected failure of parsing (FLAG_EXPECT_PARSE_ERROR) * - first invocation sets @rv to 0 * - other invocations set rv to EXIT_AM_SKIP * 2) unexpected error * - all invocations return -1 * - first invocation reports actual error * - other invocations report replacement error */ static bool testQemuConfXMLCommon(testQemuInfo *info, int *rv) { g_autoptr(virDomainDef) def = NULL; /* initialize a test just once */ if (info->prepared) goto cleanup; virTestCaseMarkUsed(info->conf->existingTestCases, info->infile); virTestCaseMarkUsed(info->conf->existingTestCases, info->outfile); virTestCaseMarkUsed(info->conf->existingTestCases, info->errfile); virTestCaseMarkUsed(info->conf->existingTestCases, info->out_xml_inactive); if (testQemuInfoInitArgs((testQemuInfo *) info) < 0) goto cleanup; if (testInfoCheckDuplicate(info) < 0) goto cleanup; #if !WITH_NBDKIT /* when compiled without nbdkit support we want to skip the test after * marking it as used */ if (info->args.fakeNbdkitCaps) { info->prep_skip = true; info->prepared = true; goto cleanup; } #endif /* !WITH_NBDKIT */ if (info->arch != VIR_ARCH_NONE && info->arch != VIR_ARCH_X86_64) qemuTestSetHostArch(&driver, info->arch); if (info->args.capsHostCPUModel) { virCPUDef *hostCPUModel = qemuTestGetCPUDef(info->args.capsHostCPUModel); qemuTestSetHostCPU(&driver, driver.hostarch, hostCPUModel); testUpdateQEMUCapsHostCPUModel(info->qemuCaps, driver.hostarch); } if (testCheckExclusiveFlags(info->flags) < 0) goto cleanup; virFileCacheClear(driver.qemuCapsCache); if (qemuTestCapsCacheInsert(driver.qemuCapsCache, info->qemuCaps) < 0) goto cleanup; if (!(info->flags & FLAG_ALLOW_MISSING_INPUT) && !virFileExists(info->infile)) { virReportError(VIR_ERR_INTERNAL_ERROR, "Input file '%s' not found", info->infile); goto cleanup; } if (!(def = virDomainDefParseFile(info->infile, driver.xmlopt, NULL, info->parseFlags | VIR_DOMAIN_DEF_PARSE_INACTIVE))) { virError *err = virGetLastError(); if (!err) { VIR_TEST_DEBUG("no error was reported for expected parse error"); goto cleanup; } if (info->flags & FLAG_EXPECT_PARSE_ERROR) { g_autoptr(GString) errstr = g_string_new(NULLSTR(err->message)); g_string_replace(errstr, abs_srcdir, "ABS_SRCDIR", 0); g_string_append_c(errstr, '\n'); if (virTestCompareToFile(errstr->str, info->errfile) >= 0) { info->prep_skip = true; } } goto cleanup; } if (info->flags & FLAG_EXPECT_PARSE_ERROR) { VIR_TEST_DEBUG("passed instead of expected parse error"); goto cleanup; } info->def = g_steal_pointer(&def); cleanup: /* definition is present and correct, return true to signal that caller can continue */ if (info->def) { info->prepared = true; return true; } /* definition is not present, but failure was expected */ if (info->prep_skip) { /* first time we report success, any subsequent time EXIT_AM_SKIP */ if (!info->prepared) *rv = 0; else *rv = EXIT_AM_SKIP; } else { /* any other failure always results in error in all invocations * so that the user will see all the failures in the final error * message which is suggesting a VIR_TEST_RANGE-limited run for * debugging. */ *rv = -1; /* report replacement error on subsequent runs */ if (!info->prepared) VIR_TEST_VERBOSE("error from testQemuConfXMLCommon() was reported in the first invocation"); } info->prepared = true; /* caller is not expected to run tests */ return false; } static int testCompareDef2XML(const void *data) { testQemuInfo *info = (void *) data; g_autofree char *actual = NULL; unsigned int format_flags = VIR_DOMAIN_DEF_FORMAT_SECURE; int rc = 0; if (!testQemuConfXMLCommon(info, &rc)) return rc; /* we deliberately format the XML as live to catch potential test regressions * as virDomainDefFormatInternalSetRootName implies _INACTIVE if 'def->id' * is -1, thus VM is inactive. */ if (!(actual = virDomainDefFormat(info->def, driver.xmlopt, format_flags))) return -1; if (virTestCompareToFile(actual, info->out_xml_inactive) < 0) return -1; return 0; } static int testCompareOutXML2XML(const void *data) { testQemuInfo *info = (void *) data; g_autofree char *actual = NULL; g_autoptr(virDomainDef) outdef = NULL; unsigned int format_flags = VIR_DOMAIN_DEF_FORMAT_SECURE; int rc = 0; if (!testQemuConfXMLCommon(info, &rc)) return rc; /* parsing of the file produced by libvirt MUST succeed */ if (!(outdef = virDomainDefParseFile(info->out_xml_inactive, driver.xmlopt, NULL, info->parseFlags | VIR_DOMAIN_DEF_PARSE_INACTIVE))) { VIR_TEST_VERBOSE("failed to parse '%s'", info->out_xml_inactive); return -1; } /* we deliberately format the XML as live to catch potential test regressions * as virDomainDefFormatInternalSetRootName implies _INACTIVE if 'def->id' * is -1, thus VM is inactive. */ if (!(actual = virDomainDefFormat(outdef, driver.xmlopt, format_flags))) return -1; if (virTestCompareToFile(actual, info->out_xml_inactive) < 0) return -1; return 0; } static int testExtDeviceArgv(testQemuInfo *info, virCommand *cmd, const char *helper, size_t idx) { g_auto(virBuffer) actualBuf = VIR_BUFFER_INITIALIZER; g_autofree char *actualargv = NULL; g_autofree char *outfile = NULL; virError *err = NULL; outfile = g_strdup_printf("%s/qemuxmlconfdata/%s%s%s.%s%zu.args", abs_srcdir, info->name, info->suffix, info->args.capsvariant, helper, idx); virTestCaseMarkUsed(info->conf->existingTestCases, outfile); if (!cmd) { err = virGetLastError(); if (!err) { VIR_TEST_DEBUG("no error was reported for expected failure"); return -1; } return -1; } if (virCommandToStringBuf(cmd, &actualBuf, true, false) < 0) return -1; virBufferAddLit(&actualBuf, "\n"); actualargv = virBufferContentAndReset(&actualBuf); if (virTestCompareToFileFull(actualargv, outfile, false) < 0) return -1; return 0; } static int testExtDevicesArgv(testQemuInfo *info, virDomainObj *vm) { g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(QEMU_DOMAIN_PRIVATE(vm)->driver); size_t i = 42; int ret = 0; int fd; for (i = 0; i < vm->def->nnets; i++) { virDomainNetDef *net = vm->def->nets[i]; if (net->type != VIR_DOMAIN_NET_TYPE_USER && net->type != VIR_DOMAIN_NET_TYPE_VHOSTUSER) { continue; } if (net->backend.type == VIR_DOMAIN_NET_BACKEND_PASST) { g_autoptr(virCommand) cmd = NULL; cmd = qemuPasstBuildCommand(NULL, NULL, vm, net); if (testExtDeviceArgv(info, cmd, "passt", i) < 0) ret = -1; } } for (i = 0; i < vm->def->nfss; i++) { virDomainFSDef *fs = vm->def->fss[i]; if (fs->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS && !fs->sock) { g_autoptr(virCommand) cmd = NULL; fd = 1730 + i; cmd = qemuVirtioFSBuildCommandLine(cfg, fs, &fd); if (testExtDeviceArgv(info, cmd, "virtiofsd", i) < 0) ret = -1; } } return ret; } static int testCompareXMLToArgv(const void *data) { testQemuInfo *info = (void *) data; g_autofree char *migrateURI = NULL; g_auto(virBuffer) actualBuf = VIR_BUFFER_INITIALIZER; g_autofree char *actualargv = NULL; unsigned int flags = info->flags; int ret = -1; virDomainObj *vm = NULL; virError *err = NULL; g_autofree char *log = NULL; g_autoptr(virCommand) cmd = NULL; qemuDomainObjPrivate *priv = NULL; g_autoptr(virIdentity) sysident = virIdentityGetSystem(); int rc = 0; if (!testQemuConfXMLCommon(info, &rc)) return rc; if (virIdentitySetCurrent(sysident) < 0) goto cleanup; if (info->nbdkitCaps) { if (virFileCacheInsertData(driver.nbdkitCapsCache, TEST_NBDKIT_PATH, g_object_ref(info->nbdkitCaps)) < 0) { g_object_unref(info->nbdkitCaps); goto cleanup; } } if (info->migrateFrom && !(migrateURI = qemuMigrationDstGetURI(info->migrateFrom, info->migrateFd))) goto cleanup; if (!(vm = virDomainObjNew(driver.xmlopt))) goto cleanup; vm->def = info->def; priv = vm->privateData; if (info->args.fds) { g_clear_pointer(&priv->fds, g_hash_table_unref); priv->fds = g_steal_pointer(&info->args.fds); } if (virBitmapParse("0-3", &priv->autoNodeset, 4) < 0) goto cleanup; if (!virDomainDefCheckABIStability(vm->def, vm->def, driver.xmlopt)) { VIR_TEST_DEBUG("ABI stability check failed on %s", info->infile); goto cleanup; } vm->def->id = -1; virResetLastError(); if (!(cmd = testCompareXMLToArgvCreateArgs(&driver, vm, migrateURI, info, flags))) { err = virGetLastError(); if (!err) { VIR_TEST_DEBUG("no error was reported for expected failure"); goto cleanup; } if (flags & FLAG_EXPECT_FAILURE) { g_autofree char *tmperr = g_strdup_printf("%s\n", NULLSTR(err->message)); if (virTestCompareToFile(tmperr, info->errfile) >= 0) goto ok; } goto cleanup; } if (flags & FLAG_EXPECT_FAILURE) { VIR_TEST_DEBUG("passed instead of expected failure"); goto cleanup; } if (info->fdsubsts->errors) { GSList *n; for (n = info->fdsubsts->errors; n; n = n->next) { VIR_TEST_VERBOSE("%s", (char *) n->data); } goto cleanup; } if (testCompareXMLToArgvValidateSchema(cmd, info) < 0) goto cleanup; testCompareXMLToArgvStabilizeArgs(cmd, info->fdsubsts->hints); if (virCommandToStringBuf(cmd, &actualBuf, true, false) < 0) goto cleanup; virBufferAddLit(&actualBuf, "\n"); actualargv = virBufferContentAndReset(&actualBuf); if (virTestCompareToFileFull(actualargv, info->outfile, false) < 0) goto cleanup; if (testExtDevicesArgv(info, vm) < 0) goto cleanup; ret = 0; ok: if (ret == 0 && flags & FLAG_EXPECT_FAILURE) { ret = -1; VIR_TEST_DEBUG("Error expected but there wasn't any."); goto cleanup; } if (flags & FLAG_EXPECT_FAILURE) { if ((log = virTestLogContentAndReset())) VIR_TEST_DEBUG("Got expected error: \n%s", log); } virResetLastError(); ret = 0; cleanup: /* clear overriden host cpu */ if (info->args.capsHostCPUModel) qemuTestSetHostCPU(&driver, driver.hostarch, NULL); if (vm) { vm->def = NULL; virObjectUnref(vm); } virIdentitySetCurrent(NULL); if (info->arch != VIR_ARCH_NONE && info->arch != VIR_ARCH_X86_64) qemuTestSetHostArch(&driver, VIR_ARCH_NONE); return ret; } static bool testConfXMLEnumerate(struct dirent *ent) { return virStringHasSuffix(ent->d_name, ".xml") || virStringHasSuffix(ent->d_name, ".args") || virStringHasSuffix(ent->d_name, ".err"); } static int testXMLParse(const void *data) { testQemuInfo *info = (void *) data; int rc = 0; testQemuConfXMLCommon(info, &rc); return rc; } static void testRun(const char *name, const char *suffix, int *ret, struct testQemuConf *testConf, ...) { g_autofree char *name_parse = g_strdup_printf("QEMU XML def parse %s%s", name, suffix); g_autofree char *name_xml = g_strdup_printf("QEMU XML def -> XML %s%s", name, suffix); g_autofree char *name_outxml = g_strdup_printf("QEMU XML OUT -> XML %s%s", name, suffix); g_autofree char *name_argv = g_strdup_printf("QEMU XML def -> ARGV %s%s", name, suffix); g_autoptr(testQemuInfo) info = g_new0(testQemuInfo, 1); va_list ap; g_autoptr(virTestDummyFDContext) fdsubsts = virTestDummyFDContextNew(); info->name = name; info->suffix = suffix; info->conf = testConf; info->fdsubsts = fdsubsts; va_start(ap, testConf); testQemuInfoSetArgs(info, ap); va_end(ap); info->infile = g_strdup_printf("%s/qemuxmlconfdata/%s.xml", abs_srcdir, info->name); if (info->flags & (FLAG_EXPECT_FAILURE | FLAG_EXPECT_PARSE_ERROR)) { info->errfile = g_strdup_printf("%s/qemuxmlconfdata/%s%s%s.err", abs_srcdir, info->name, suffix, info->args.capsvariant); } else { info->outfile = g_strdup_printf("%s/qemuxmlconfdata/%s%s%s.args", abs_srcdir, info->name, suffix, info->args.capsvariant); } if (!(info->flags & FLAG_EXPECT_PARSE_ERROR)) info->out_xml_inactive = g_strdup_printf("%s/qemuxmlconfdata/%s%s%s.xml", abs_srcdir, info->name, suffix, info->args.capsvariant); virTestRunLog(ret, name_parse, testXMLParse, info); virTestRunLog(ret, name_xml, testCompareDef2XML, info); virTestRunLog(ret, name_outxml, testCompareOutXML2XML, info); virTestRunLog(ret, name_argv, testCompareXMLToArgv, info); /* clear overriden host cpu */ if (info->args.capsHostCPUModel) qemuTestSetHostCPU(&driver, driver.hostarch, NULL); if (info->arch != VIR_ARCH_NONE && info->arch != VIR_ARCH_X86_64) qemuTestSetHostArch(&driver, VIR_ARCH_NONE); } static int mymain(void) { int ret = 0; g_autoptr(virConnect) conn = NULL; g_autoptr(GHashTable) duplicateTests = virHashNew(NULL); g_autoptr(GHashTable) existingTestCases = NULL; g_autoptr(GHashTable) capslatest = testQemuGetLatestCaps(); g_autoptr(GHashTable) qapiSchemaCache = virHashNew((GDestroyNotify) g_hash_table_unref); g_autoptr(GHashTable) capscache = virHashNew(virObjectUnref); struct testQemuConf testConf = { .capslatest = capslatest, .capscache = capscache, .qapiSchemaCache = qapiSchemaCache, .duplicateTests = duplicateTests }; if (!capslatest) return EXIT_FAILURE; /* enumerate and store all available test cases to verify at the end that * all of them were invoked */ if (virTestEnumerateTestCases(abs_srcdir "/qemuxmlconfdata", testConfXMLEnumerate, &existingTestCases) < 0) { return EXIT_FAILURE; } testConf.existingTestCases = existingTestCases; /* Set the timezone because we are mocking the time() function. * If we don't do that, then localtime() may return unpredictable * results. In order to detect things that just work by a blind * chance, we need to set an virtual timezone that no libvirt * developer resides in. */ if (g_setenv("TZ", "VIR00:30", TRUE) == FALSE) { perror("g_setenv"); return EXIT_FAILURE; } if (qemuTestDriverInit(&driver) < 0) return EXIT_FAILURE; virFileWrapperAddPrefix("/sys/devices/system", abs_srcdir "/vircaps2xmldata/linux-basic/system"); virFileWrapperAddPrefix(SYSCONFDIR "/qemu/firmware", abs_srcdir "/qemufirmwaredata/etc/qemu/firmware"); virFileWrapperAddPrefix(PREFIX "/share/qemu/firmware", abs_srcdir "/qemufirmwaredata/usr/share/qemu/firmware"); virFileWrapperAddPrefix("/home/user/.config/qemu/firmware", abs_srcdir "/qemufirmwaredata/home/user/.config/qemu/firmware"); virFileWrapperAddPrefix(SYSCONFDIR "/qemu/vhost-user", abs_srcdir "/qemuvhostuserdata/etc/qemu/vhost-user"); virFileWrapperAddPrefix(PREFIX "/share/qemu/vhost-user", abs_srcdir "/qemuvhostuserdata/usr/share/qemu/vhost-user"); virFileWrapperAddPrefix("/home/user/.config/qemu/vhost-user", abs_srcdir "/qemuvhostuserdata/home/user/.config/qemu/vhost-user"); virFileWrapperAddPrefix("/usr/libexec/qemu/vhost-user", abs_srcdir "/qemuvhostuserdata/usr/libexec/qemu/vhost-user"); virFileWrapperAddPrefix("/usr/libexec/virtiofsd", abs_srcdir "/qemuvhostuserdata/usr/libexec/qemu/vhost-user/test-virtiofsd"); if (!(conn = virGetConnect())) return EXIT_FAILURE; conn->secretDriver = testQemuGetFakeSecretDriver(); conn->storageDriver = testQemuGetFakeStorageDriver(); conn->nwfilterDriver = testQemuGetFakeNWFilterDriver(); conn->networkDriver = testQemuGetFakeNetworkDriver(); virSetConnectInterface(conn); virSetConnectNetwork(conn); virSetConnectNWFilter(conn); virSetConnectNodeDev(conn); virSetConnectSecret(conn); virSetConnectStorage(conn); /** * The following set of macros allows testing of XML -> argv conversion with a * real set of capabilities gathered from a real qemu copy. It is desired to use * these for positive test cases as it provides combinations of flags which * can be met in real life. * * The capabilities are taken from the real capabilities stored in * tests/qemucapabilitiesdata. * * It is suggested to use the DO_TEST_CAPS_LATEST macro which always takes the * most recent capability set. In cases when the new code would change behaviour * the test cases should be forked using DO_TEST_CAPS_VER with the appropriate * version. */ #define DO_TEST_FULL(_name, _suffix, ...) \ testRun(_name, _suffix, &ret, &testConf, __VA_ARGS__); #define DO_TEST_CAPS_INTERNAL(name, arch, ver, ...) \ DO_TEST_FULL(name, "." arch "-" ver, \ ARG_CAPS_ARCH, arch, \ ARG_CAPS_VER, ver, \ __VA_ARGS__, \ ARG_END) #define DO_TEST_CAPS_ARCH_LATEST_FULL(name, arch, ...) \ DO_TEST_CAPS_INTERNAL(name, arch, "latest", __VA_ARGS__) #define DO_TEST_CAPS_ARCH_VER_FULL(name, arch, ver, ...) \ DO_TEST_CAPS_INTERNAL(name, arch, ver, __VA_ARGS__) #define DO_TEST_CAPS_ARCH_LATEST(name, arch) \ DO_TEST_CAPS_ARCH_LATEST_FULL(name, arch, ARG_END) #define DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE(name, arch) \ DO_TEST_FULL(name, "." arch "-latest.abi-update", \ ARG_CAPS_ARCH, arch, \ ARG_CAPS_VER, "latest", \ ARG_PARSEFLAGS, VIR_DOMAIN_DEF_PARSE_ABI_UPDATE, \ ARG_END) #define DO_TEST_CAPS_ARCH_VER(name, arch, ver) \ DO_TEST_CAPS_ARCH_VER_FULL(name, arch, ver, ARG_END) #define DO_TEST_CAPS_LATEST_NBDKIT(name, ...) \ DO_TEST_CAPS_ARCH_LATEST_FULL(name, "x86_64", ARG_NBDKIT_CAPS, __VA_ARGS__, QEMU_NBDKIT_CAPS_LAST, ARG_END) #define DO_TEST_CAPS_LATEST(name) \ DO_TEST_CAPS_ARCH_LATEST(name, "x86_64") #define DO_TEST_CAPS_LATEST_ABI_UPDATE(name) \ DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE(name, "x86_64") #define DO_TEST_CAPS_VER(name, ver) \ DO_TEST_CAPS_ARCH_VER(name, "x86_64", ver) #define DO_TEST_CAPS_LATEST_PPC64(name) \ DO_TEST_CAPS_ARCH_LATEST(name, "ppc64") #define DO_TEST_CAPS_LATEST_PPC64_HOSTCPU(name, hostcpu) \ DO_TEST_CAPS_ARCH_LATEST_FULL(name, "ppc64", \ ARG_CAPS_HOST_CPU_MODEL, hostcpu) #define DO_TEST_CAPS_LATEST_PPC64_HOSTCPU_FAILURE(name, hostcpu) \ DO_TEST_CAPS_ARCH_LATEST_FULL(name, "ppc64", \ ARG_CAPS_HOST_CPU_MODEL, hostcpu, \ ARG_FLAGS, FLAG_EXPECT_FAILURE) #define DO_TEST_CAPS_ARCH_LATEST_FAILURE(name, arch) \ DO_TEST_CAPS_ARCH_LATEST_FULL(name, arch, \ ARG_FLAGS, FLAG_EXPECT_FAILURE) #define DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE_FAILURE(name, arch) \ DO_TEST_FULL(name, "." arch "-latest.abi-update", \ ARG_CAPS_ARCH, arch, \ ARG_CAPS_VER, "latest", \ ARG_PARSEFLAGS, VIR_DOMAIN_DEF_PARSE_ABI_UPDATE, \ ARG_FLAGS, FLAG_EXPECT_FAILURE, \ ARG_END) #define DO_TEST_CAPS_ARCH_VER_FAILURE(name, arch, ver) \ DO_TEST_CAPS_ARCH_VER_FULL(name, arch, ver, \ ARG_FLAGS, FLAG_EXPECT_FAILURE) #define DO_TEST_CAPS_LATEST_FAILURE(name) \ DO_TEST_CAPS_ARCH_LATEST_FAILURE(name, "x86_64") #define DO_TEST_CAPS_LATEST_ABI_UPDATE_FAILURE(name) \ DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE_FAILURE(name, "x86_64") #define DO_TEST_CAPS_VER_FAILURE(name, ver) \ DO_TEST_CAPS_ARCH_VER_FAILURE(name, "x86_64", ver) #define DO_TEST_CAPS_ARCH_LATEST_PARSE_ERROR(name, arch) \ DO_TEST_CAPS_ARCH_LATEST_FULL(name, arch, \ ARG_FLAGS, FLAG_EXPECT_PARSE_ERROR) #define DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE_PARSE_ERROR(name, arch) \ DO_TEST_FULL(name, "." arch "-latest.abi-update", \ ARG_CAPS_ARCH, arch, \ ARG_CAPS_VER, "latest", \ ARG_PARSEFLAGS, VIR_DOMAIN_DEF_PARSE_ABI_UPDATE, \ ARG_FLAGS, FLAG_EXPECT_PARSE_ERROR, \ ARG_END) #define DO_TEST_CAPS_ARCH_VER_PARSE_ERROR(name, arch, ver) \ DO_TEST_CAPS_ARCH_VER_FULL(name, arch, ver, \ ARG_FLAGS, FLAG_EXPECT_PARSE_ERROR) #define DO_TEST_CAPS_LATEST_PARSE_ERROR(name) \ DO_TEST_CAPS_ARCH_LATEST_PARSE_ERROR(name, "x86_64") #define DO_TEST_CAPS_LATEST_ABI_UPDATE_PARSE_ERROR(name) \ DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE_PARSE_ERROR(name, "x86_64") #define DO_TEST_CAPS_VER_PARSE_ERROR(name, ver) \ DO_TEST_CAPS_ARCH_VER_PARSE_ERROR(name, "x86_64", ver) #define DO_TEST_GIC(name, ver, gic) \ DO_TEST_CAPS_ARCH_VER_FULL(name, "aarch64", ver, ARG_GIC, gic, ARG_FLAGS, FLAG_ALLOW_DUPLICATE_OUTPUT, ARG_END) /* Unset or set all envvars here that are copied in qemudBuildCommandLine * using ADD_ENV_COPY, otherwise these tests may fail due to unexpected * values for these envvars */ g_setenv("PATH", "/bin", TRUE); g_setenv("USER", "test", TRUE); g_setenv("LOGNAME", "test", TRUE); g_setenv("HOME", "/home/test", TRUE); g_setenv("LC_ALL", "C", TRUE); g_unsetenv("TMPDIR"); g_unsetenv("LD_PRELOAD"); g_unsetenv("LD_LIBRARY_PATH"); g_unsetenv("DYLD_INSERT_LIBRARIES"); g_unsetenv("DYLD_FORCE_FLAT_NAMESPACE"); g_unsetenv("QEMU_AUDIO_DRV"); g_unsetenv("SDL_AUDIODRIVER"); g_unsetenv("PIPEWIRE_CORE"); g_unsetenv("PIPEWIRE_REMOTE"); g_unsetenv("PIPEWIRE_RUNTIME_DIR"); DO_TEST_CAPS_ARCH_LATEST_FULL("nonexistent-file", "x86_64", ARG_FLAGS, FLAG_EXPECT_PARSE_ERROR | FLAG_ALLOW_MISSING_INPUT); DO_TEST_CAPS_LATEST_PARSE_ERROR("broken-xml-invalid"); DO_TEST_CAPS_LATEST("x86_64-pc-minimal"); DO_TEST_CAPS_LATEST_ABI_UPDATE("x86_64-pc-minimal"); DO_TEST_CAPS_LATEST("x86_64-q35-minimal"); DO_TEST_CAPS_LATEST_ABI_UPDATE("x86_64-q35-minimal"); DO_TEST_CAPS_ARCH_LATEST("aarch64-virt-minimal", "aarch64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("aarch64-virt-minimal", "aarch64"); DO_TEST_CAPS_ARCH_LATEST("armv7l-versatilepb-minimal", "armv7l"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("armv7l-versatilepb-minimal", "armv7l"); DO_TEST_CAPS_ARCH_LATEST("aarch64-realview-minimal", "aarch64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("aarch64-realview-minimal", "aarch64"); /* The '-nousb' test case tests machine without a built-in USB controller */ DO_TEST_CAPS_ARCH_LATEST("aarch64-nousb-minimal", "aarch64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("aarch64-nousb-minimal", "aarch64"); DO_TEST_CAPS_ARCH_LATEST("loongarch64-virt-minimal", "loongarch64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("loongarch64-virt-minimal", "loongarch64"); DO_TEST_CAPS_ARCH_LATEST("riscv64-virt-minimal", "riscv64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("riscv64-virt-minimal", "riscv64"); DO_TEST_CAPS_ARCH_LATEST("ppc64-pseries-minimal", "ppc64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("ppc64-pseries-minimal", "ppc64"); DO_TEST_CAPS_ARCH_LATEST("ppc64-g3beige-minimal", "ppc64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("ppc64-g3beige-minimal", "ppc64"); DO_TEST_CAPS_ARCH_LATEST("ppc64-mac99-minimal", "ppc64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("ppc64-mac99-minimal", "ppc64"); DO_TEST_CAPS_ARCH_LATEST("ppc-mac99-minimal", "ppc"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("ppc-mac99-minimal", "ppc"); DO_TEST_CAPS_ARCH_LATEST("ppc64-powernv9-minimal", "ppc64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("ppc64-powernv9-minimal", "ppc64"); DO_TEST_CAPS_ARCH_LATEST("s390x-ccw-minimal", "s390x"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("s390x-ccw-minimal", "s390x"); DO_TEST_CAPS_LATEST("x86_64-pc-default-models"); DO_TEST_CAPS_LATEST_ABI_UPDATE("x86_64-pc-default-models"); DO_TEST_CAPS_LATEST("x86_64-q35-default-models"); DO_TEST_CAPS_LATEST_ABI_UPDATE("x86_64-q35-default-models"); DO_TEST_CAPS_ARCH_LATEST("aarch64-virt-default-models", "aarch64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("aarch64-virt-default-models", "aarch64"); DO_TEST_CAPS_ARCH_LATEST("loongarch64-virt-default-models", "loongarch64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("loongarch64-virt-default-models", "loongarch64"); DO_TEST_CAPS_ARCH_LATEST("riscv64-virt-default-models", "riscv64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("riscv64-virt-default-models", "riscv64"); DO_TEST_CAPS_ARCH_LATEST("ppc64-pseries-default-models", "ppc64"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("ppc64-pseries-default-models", "ppc64"); DO_TEST_CAPS_ARCH_LATEST("s390x-ccw-default-models", "s390x"); DO_TEST_CAPS_ARCH_LATEST_ABI_UPDATE("s390x-ccw-default-models", "s390x"); DO_TEST_CAPS_LATEST_PARSE_ERROR("no-memory"); DO_TEST_CAPS_LATEST_PARSE_ERROR("isapc-pci"); DO_TEST_CAPS_LATEST_PARSE_ERROR("microvm-pci"); DO_TEST_CAPS_LATEST("genid"); DO_TEST_CAPS_LATEST("genid-auto"); DO_TEST_CAPS_LATEST("machine-aliases1"); DO_TEST_CAPS_LATEST("machine-aliases2"); DO_TEST_CAPS_LATEST("machine-core-on"); driver.config->dumpGuestCore = true; DO_TEST_CAPS_LATEST("machine-core-off"); driver.config->dumpGuestCore = false; DO_TEST_CAPS_LATEST("machine-smm-on"); DO_TEST_CAPS_LATEST("machine-smm-off"); DO_TEST_CAPS_LATEST("machine-vmport-opt"); DO_TEST_CAPS_LATEST("machine-i8042-on"); DO_TEST_CAPS_LATEST("machine-i8042-off"); DO_TEST_CAPS_LATEST_PARSE_ERROR("machine-i8042-off-vmport-on"); DO_TEST_CAPS_LATEST_PARSE_ERROR("machine-i8042-off-explicit-ps2-inputs"); DO_TEST_CAPS_LATEST("default-kvm-host-arch"); DO_TEST_CAPS_LATEST("default-qemu-host-arch"); DO_TEST_CAPS_LATEST("x86-kvm-32-on-64"); DO_TEST_CAPS_LATEST("boot-cdrom"); DO_TEST_CAPS_LATEST("boot-network"); DO_TEST_CAPS_LATEST("boot-floppy"); DO_TEST_CAPS_LATEST("boot-floppy-q35"); DO_TEST_CAPS_LATEST("boot-multi"); DO_TEST_CAPS_LATEST("boot-menu-enable"); DO_TEST_CAPS_LATEST("boot-menu-enable-with-timeout"); DO_TEST_CAPS_LATEST_PARSE_ERROR("boot-menu-enable-with-timeout-invalid"); DO_TEST_CAPS_LATEST("boot-menu-disable"); DO_TEST_CAPS_LATEST("boot-menu-disable-drive"); DO_TEST_CAPS_LATEST_PARSE_ERROR("boot-dev+order"); DO_TEST_CAPS_LATEST("boot-order"); DO_TEST_CAPS_LATEST("boot-complex"); DO_TEST_CAPS_LATEST("audio-none-minimal"); DO_TEST_CAPS_LATEST("audio-alsa-minimal"); DO_TEST_CAPS_LATEST("audio-coreaudio-minimal"); DO_TEST_CAPS_LATEST("audio-jack-minimal"); DO_TEST_CAPS_LATEST("audio-oss-minimal"); DO_TEST_CAPS_LATEST("audio-pulseaudio-minimal"); g_setenv("PIPEWIRE_RUNTIME_DIR", "/run/user/1000", TRUE); DO_TEST_CAPS_LATEST("audio-pipewire-minimal"); g_unsetenv("PIPEWIRE_RUNTIME_DIR"); DO_TEST_CAPS_LATEST("audio-sdl-minimal"); DO_TEST_CAPS_LATEST("audio-spice-minimal"); DO_TEST_CAPS_LATEST("audio-file-minimal"); DO_TEST_CAPS_LATEST("audio-none-best"); DO_TEST_CAPS_LATEST("audio-alsa-best"); DO_TEST_CAPS_LATEST("audio-coreaudio-best"); DO_TEST_CAPS_LATEST("audio-oss-best"); DO_TEST_CAPS_LATEST("audio-pulseaudio-best"); DO_TEST_CAPS_LATEST("audio-pipewire-best"); DO_TEST_CAPS_LATEST("audio-sdl-best"); DO_TEST_CAPS_LATEST("audio-spice-best"); DO_TEST_CAPS_LATEST("audio-file-best"); DO_TEST_CAPS_LATEST("audio-none-full"); DO_TEST_CAPS_LATEST("audio-alsa-full"); DO_TEST_CAPS_LATEST("audio-coreaudio-full"); DO_TEST_CAPS_LATEST("audio-jack-full"); DO_TEST_CAPS_LATEST("audio-oss-full"); DO_TEST_CAPS_LATEST("audio-pulseaudio-full"); DO_TEST_CAPS_LATEST("audio-pipewire-full"); DO_TEST_CAPS_LATEST("audio-sdl-full"); DO_TEST_CAPS_LATEST("audio-spice-full"); DO_TEST_CAPS_LATEST("audio-file-full"); g_setenv("PIPEWIRE_RUNTIME_DIR", "/run/user/1000", TRUE); DO_TEST_CAPS_LATEST("audio-many-backends"); g_unsetenv("PIPEWIRE_RUNTIME_DIR"); /* Validate auto-creation of