The combined qemu-system qtests check the linked machine list and
arch-prefixed -M help and errors. -readconfig [machine] type uses
arch:name, and a later bare -M keeps that prefix. The expected
arches come from single_binary_targets, plus none and the none
machine. Single-target binaries keep the historic -M cases.

Signed-off-by: Yonggang Luo <[email protected]>
---
 meson.build                                   |   2 +
 .../qtest/machine-list-combined-targets.h.in  |   4 +
 tests/qtest/machine-list-combined-test.c      | 104 +++
 tests/qtest/machine-option-test.c             | 714 ++++++++++++++++++
 tests/qtest/meson.build                       |  38 +
 5 files changed, 862 insertions(+)
 create mode 100644 tests/qtest/machine-list-combined-targets.h.in
 create mode 100644 tests/qtest/machine-list-combined-test.c
 create mode 100644 tests/qtest/machine-option-test.c

diff --git a/meson.build b/meson.build
index 96f493cb746..085151d65f8 100644
--- a/meson.build
+++ b/meson.build
@@ -4294,6 +4294,7 @@ single_binary_bases = {}
 single_binary_deps = []
 single_binary_crates = []
 single_binary_available = false
+single_binary_targets = []
 
 single_binary_supported_targets = [
   'aarch64-softmmu',
@@ -4496,6 +4497,7 @@ foreach target : target_dirs
       single_binary_deps += target_stubs
       single_binary_crates += main_rs
       single_binary_available = true
+      single_binary_targets += target_name
     endif
     execs = [{
       'name': 'qemu-system-' + target_name,
diff --git a/tests/qtest/machine-list-combined-targets.h.in 
b/tests/qtest/machine-list-combined-targets.h.in
new file mode 100644
index 00000000000..ede99c53762
--- /dev/null
+++ b/tests/qtest/machine-list-combined-targets.h.in
@@ -0,0 +1,4 @@
+/* Generated from configured single_binary_supported_targets. */
+static const char * const combined_targets[] = {
+@COMBINED_TARGETS@
+};
diff --git a/tests/qtest/machine-list-combined-test.c 
b/tests/qtest/machine-list-combined-test.c
new file mode 100644
index 00000000000..90c4ae7b377
--- /dev/null
+++ b/tests/qtest/machine-list-combined-test.c
@@ -0,0 +1,104 @@
+/*
+ * Combined qemu-system machine list tests.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest.h"
+#include "qobject/qdict.h"
+#include "qobject/qlist.h"
+#include "qobject/qstring.h"
+
+#include "machine-list-test.inc.h"
+
+#include "machine-list-combined-targets.h"
+
+static bool qemu_binary_is_combined(void)
+{
+    const char *bin = qtest_qemu_binary(NULL);
+
+    return g_str_has_suffix(bin, "qemu-system") ||
+           g_str_has_suffix(bin, "qemu-system.exe");
+}
+
+static QList *query_targets(QTestState *qts)
+{
+    QDict *resp;
+    QList *list;
+
+    resp = qtest_qmp(qts, "{'execute': 'query-targets'}");
+    g_assert(qdict_haskey(resp, "return"));
+    list = qdict_get_qlist(resp, "return");
+    qobject_ref(list);
+    qobject_unref(resp);
+    return list;
+}
+
+static QDict *target_entry(QList *targets, const char *arch,
+                           const char *endianness)
+{
+    const QListEntry *e;
+
+    for (e = qlist_first(targets); e; e = qlist_next(e)) {
+        QDict *t = qobject_to(QDict, qlist_entry_obj(e));
+
+        if (!strcmp(qdict_get_str(t, "arch"), arch) &&
+            !strcmp(qdict_get_str(t, "endianness"), endianness)) {
+            return t;
+        }
+    }
+    return NULL;
+}
+
+static void test_combined_machine_list(void)
+{
+    QTestState *qts;
+    g_autoptr(QList) targets = NULL;
+    size_t i;
+
+    qts = qtest_init("-nodefaults -M none");
+    read_accel_flags(qts);
+    targets = query_targets(qts);
+    qtest_quit(qts);
+
+    g_assert_cmpuint(qlist_size(targets), ==,
+                     G_N_ELEMENTS(combined_targets) + 1);
+    for (i = 0; i < G_N_ELEMENTS(combined_targets); i++) {
+        const struct ShippedTarget *t = 
lookup_shipped_target(combined_targets[i]);
+        QDict *tgt;
+
+        g_assert_nonnull(t);
+        tgt = target_entry(targets, t->arch, t->endianness);
+        g_assert_nonnull(tgt);
+        assert_name_list(qdict_get_qlist(tgt, "machines"), t->name,
+                         t->machines, t->n_machines);
+    }
+    {
+        QDict *none = target_entry(targets, "none", "little");
+        QList *names;
+        const QListEntry *e;
+        bool saw_none = false;
+
+        g_assert_nonnull(none);
+        names = qdict_get_qlist(none, "machines");
+        for (e = qlist_first(names); e; e = qlist_next(e)) {
+            const char *name = qstring_get_str(qobject_to(QString,
+                                                          qlist_entry_obj(e)));
+
+            if (!strcmp(name, "none")) {
+                saw_none = true;
+            }
+        }
+        g_assert_true(saw_none);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+    g_assert_true(qemu_binary_is_combined());
+    g_test_add_func("/machine-list/combined", test_combined_machine_list);
+    return g_test_run();
+}
diff --git a/tests/qtest/machine-option-test.c 
b/tests/qtest/machine-option-test.c
new file mode 100644
index 00000000000..4ccc854fba0
--- /dev/null
+++ b/tests/qtest/machine-option-test.c
@@ -0,0 +1,714 @@
+/*
+ * Combined and historic -M / -machine option tests.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest.h"
+#include "qobject/qdict.h"
+#include "qobject/qlist.h"
+#include "qobject/qstring.h"
+
+#include "machine-list-test.inc.h"
+
+static bool test_target_exists(const char *target_info);
+static bool spawn_args_targets_exist(const char *const *args);
+static bool cli_targets_exist(const char *cli);
+
+static bool qemu_binary_is_combined(void)
+{
+    const char *bin = qtest_qemu_binary(NULL);
+
+    return g_str_has_suffix(bin, "qemu-system") ||
+           g_str_has_suffix(bin, "qemu-system.exe");
+}
+
+static void spawn_qemu(gchar **out, gchar **err, int *code,
+                       const char *const *argv_tail)
+{
+    g_autoptr(GPtrArray) argv = g_ptr_array_new();
+    g_autofree gchar *stdout_buf = NULL;
+    g_autofree gchar *stderr_buf = NULL;
+    gint status = 0;
+    GError *gerr = NULL;
+    const char *const *p;
+
+    g_ptr_array_add(argv, (gpointer)qtest_qemu_binary(NULL));
+    for (p = argv_tail; *p; p++) {
+        g_ptr_array_add(argv, (gpointer)*p);
+    }
+    g_ptr_array_add(argv, NULL);
+
+    g_assert(g_spawn_sync(NULL, (char **)argv->pdata, NULL,
+                          G_SPAWN_DEFAULT, NULL, NULL,
+                          &stdout_buf, &stderr_buf, &status, &gerr));
+    g_assert_no_error(gerr);
+#ifdef _WIN32
+    *code = status;
+#else
+    g_assert(WIFEXITED(status));
+    *code = WEXITSTATUS(status);
+#endif
+    if (out) {
+        *out = g_steal_pointer(&stdout_buf);
+    }
+    if (err) {
+        *err = g_steal_pointer(&stderr_buf);
+    }
+}
+
+static void assert_spawn(const char *target_info, const char *const *args,
+                         int expect_code, const char *out_need,
+                         const char *err_need)
+{
+    g_autofree gchar *out = NULL;
+    g_autofree gchar *err = NULL;
+    int code;
+
+    if (!test_target_exists(target_info) ||
+        (target_info && !spawn_args_targets_exist(args))) {
+        return;
+    }
+    spawn_qemu(&out, &err, &code, args);
+    g_assert_cmpint(code, ==, expect_code);
+    if (out_need) {
+        g_assert_nonnull(strstr(out ? out : "", out_need));
+    }
+    if (err_need) {
+        g_assert_nonnull(strstr(err ? err : "", err_need));
+    }
+}
+
+static void assert_spawn_not(const char *target_info, const char *const *args,
+                             int expect_code, const char *out_need,
+                             const char *out_forbid)
+{
+    g_autofree gchar *out = NULL;
+    g_autofree gchar *err = NULL;
+    int code;
+
+    if (!test_target_exists(target_info) ||
+        (target_info && !spawn_args_targets_exist(args))) {
+        return;
+    }
+    spawn_qemu(&out, &err, &code, args);
+    g_assert_cmpint(code, ==, expect_code);
+    g_assert_nonnull(strstr(out ? out : "", out_need));
+    g_assert_null(strstr(out ? out : "", out_forbid));
+}
+
+static char *qom_machine_type(QTestState *qts)
+{
+    QDict *resp;
+    char *type;
+
+    resp = qtest_qmp(qts,
+                     "{'execute': 'qom-get', 'arguments':"
+                     " {'path': '/machine', 'property': 'type'}}");
+    g_assert(qdict_haskey(resp, "return"));
+    type = g_strdup(qdict_get_str(resp, "return"));
+    qobject_unref(resp);
+    return type;
+}
+
+static char *query_target_arch(QTestState *qts)
+{
+    QDict *resp, *ret;
+    char *arch;
+
+    resp = qtest_qmp(qts, "{'execute': 'query-target'}");
+    g_assert(qdict_haskey(resp, "return"));
+    ret = qdict_get_qdict(resp, "return");
+    arch = g_strdup(qdict_get_str(ret, "arch"));
+    qobject_unref(resp);
+    return arch;
+}
+
+/* query-target "arch" is SysEmuTarget, not always TARGET_NAME. */
+static const char *query_arch_for_target(const char *target_info)
+{
+    static const struct {
+        const char *target_name;
+        const char *arch;
+    } map[] = {
+        { "armeb", "arm" },
+        { "microblazeel", "microblaze" },
+        { "mips64el", "mips64" },
+        { "mipsel", "mips" },
+        { "ppc64le", "ppc64" },
+        { "sh4eb", "sh4" },
+        { "xtensaeb", "xtensa" },
+    };
+    size_t i;
+
+    for (i = 0; i < G_N_ELEMENTS(map); i++) {
+        if (!strcmp(target_info, map[i].target_name)) {
+            return map[i].arch;
+        }
+    }
+    return target_info;
+}
+
+static const struct ShippedTarget *lookup_shipped_arch(const char *arch,
+                                                       const char *endianness)
+{
+    size_t i;
+
+    for (i = 0; i < G_N_ELEMENTS(shipped_targets); i++) {
+        if (!strcmp(shipped_targets[i].arch, arch) &&
+            !strcmp(shipped_targets[i].endianness, endianness)) {
+            return &shipped_targets[i];
+        }
+    }
+    return NULL;
+}
+
+static const char *preferred_machine(const struct ShippedTarget *t)
+{
+    size_t i;
+
+    for (i = 0; i < t->n_machines; i++) {
+        if (!strcmp(t->machines[i], "virt")) {
+            return t->machines[i];
+        }
+    }
+    return t->n_machines ? t->machines[0] : NULL;
+}
+
+static bool help_section_header(const char *line, const char **name,
+                                size_t *name_len)
+{
+    size_t n;
+    size_t i;
+
+    if (!line[0]) {
+        return false;
+    }
+    n = strlen(line);
+    if (line[n - 1] != ':') {
+        return false;
+    }
+    for (i = 0; i < n - 1; i++) {
+        char c = line[i];
+
+        if (!g_ascii_isalnum(c) && c != '_' && c != '-' && c != '.') {
+            return false;
+        }
+    }
+    *name = line;
+    *name_len = n - 1;
+    return true;
+}
+
+/* First-column machine names. Aliases are omitted.
+ * only_section NULL keeps every section (historic help).
+ */
+static QList *names_from_help(const char *text, const char *only_section)
+{
+    g_auto(GStrv) lines = g_strsplit(text ? text : "", "\n", 0);
+    g_autofree char *section = NULL;
+    QList *names = qlist_new();
+    int i;
+
+    for (i = 0; lines[i]; i++) {
+        const char *line = lines[i];
+        const char *header;
+        size_t header_len;
+        const char *sp;
+        g_autofree char *tok = NULL;
+
+        if (help_section_header(line, &header, &header_len)) {
+            g_free(section);
+            section = g_strndup(header, header_len);
+            continue;
+        }
+        if (only_section &&
+            (!section || strcmp(section, only_section))) {
+            continue;
+        }
+        if (!line[0] || g_str_has_prefix(line, "Supported machines")) {
+            continue;
+        }
+        if (strstr(line, "(alias of ")) {
+            continue;
+        }
+        sp = line;
+        while (*sp && !g_ascii_isspace(*sp)) {
+            sp++;
+        }
+        if (sp == line) {
+            continue;
+        }
+        tok = g_strndup(line, sp - line);
+        qlist_append_str(names, tok);
+    }
+    return names;
+}
+
+static QList *query_targets(QTestState *qts)
+{
+    QDict *resp;
+    QList *list;
+
+    resp = qtest_qmp(qts, "{'execute': 'query-targets'}");
+    g_assert(qdict_haskey(resp, "return"));
+    list = qdict_get_qlist(resp, "return");
+    qobject_ref(list);
+    qobject_unref(resp);
+    return list;
+}
+
+static void read_accels(void)
+{
+    QTestState *qts = qtest_init("-nodefaults -M none");
+
+    read_accel_flags(qts);
+    qtest_quit(qts);
+}
+
+static bool combined_has_target(const char *target_info)
+{
+    g_autofree char *opt = g_strdup_printf("%s:help", target_info);
+    const char *args[] = { "-M", opt, NULL };
+    g_autofree gchar *err = NULL;
+    int code = 1;
+
+    spawn_qemu(NULL, &err, &code, args);
+    return code == 0;
+}
+
+static bool test_target_exists(const char *target_info)
+{
+    static GHashTable *cache;
+    gpointer cached;
+    gboolean have;
+
+    if (!target_info) {
+        return true;
+    }
+    if (!strcmp(target_info, "none")) {
+        return true;
+    }
+    if (!qemu_binary_is_combined()) {
+        return strcmp(target_info, qtest_get_arch()) == 0;
+    }
+    if (!cache) {
+        cache = g_hash_table_new(g_str_hash, g_str_equal);
+    }
+    if (g_hash_table_lookup_extended(cache, target_info, NULL, &cached)) {
+        return GPOINTER_TO_INT(cached);
+    }
+    have = combined_has_target(target_info);
+    g_hash_table_insert(cache, (gpointer)target_info, GINT_TO_POINTER(have));
+    return have;
+}
+
+static bool machine_optarg_targets_exist(const char *optarg)
+{
+    const char *type = optarg;
+    const char *colon;
+    g_autofree char *prefix = NULL;
+
+    if (!optarg) {
+        return true;
+    }
+    if (g_str_has_prefix(type, "type=")) {
+        type += 5;
+    }
+    colon = strchr(type, ':');
+    if (!colon || colon == type) {
+        return true;
+    }
+    prefix = g_strndup(type, colon - type);
+    return test_target_exists(prefix);
+}
+
+static bool spawn_args_targets_exist(const char *const *args)
+{
+    const char *const *p;
+
+    for (p = args; *p; p++) {
+        if ((!strcmp(*p, "-M") || !strcmp(*p, "-machine")) && p[1]) {
+            if (!machine_optarg_targets_exist(p[1])) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+static bool cli_targets_exist(const char *cli)
+{
+    g_auto(GStrv) tokens = g_strsplit(cli ? cli : "", " ", 0);
+    int i;
+
+    for (i = 0; tokens[i]; i++) {
+        if ((!strcmp(tokens[i], "-M") || !strcmp(tokens[i], "-machine")) &&
+            tokens[i + 1] && !machine_optarg_targets_exist(tokens[i + 1])) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static void assert_run(const char *target_info, const char *cli,
+                       const char *type_need)
+{
+    QTestState *qts;
+    g_autofree char *got_arch = NULL;
+    g_autofree char *got_type = NULL;
+
+    if (!test_target_exists(target_info) || !cli_targets_exist(cli)) {
+        return;
+    }
+    qts = qtest_initf("-nodefaults %s", cli);
+    got_arch = query_target_arch(qts);
+    g_assert_cmpstr(got_arch, ==, query_arch_for_target(target_info));
+    if (type_need) {
+        got_type = qom_machine_type(qts);
+        g_assert_nonnull(strstr(got_type, type_need));
+    }
+    qtest_quit(qts);
+}
+
+static void test_combined_help(void)
+{
+    const char *help[] = { "-M", "help", NULL };
+    const char *qmark[] = { "-M", "?", NULL };
+    const char *aarch64_help[] = { "-M", "aarch64:help", NULL };
+    const char *aarch64_q[] = { "-M", "aarch64:?", NULL };
+    const char *riscv64_help[] = { "-M", "riscv64:help", NULL };
+    const char *none_help[] = { "-M", "none:help", NULL };
+
+    assert_spawn("none", help, 0, "none:\n", NULL);
+    assert_spawn("aarch64", help, 0, "aarch64:\n", NULL);
+    assert_spawn("none", qmark, 0, "none:\n", NULL);
+    assert_spawn("aarch64", aarch64_help, 0, "virt", NULL);
+    assert_spawn_not("aarch64", aarch64_help, 0, "virt", "q35");
+    assert_spawn_not("aarch64", aarch64_help, 0, "virt", "none:\n");
+    assert_spawn("aarch64", aarch64_q, 0, "virt", NULL);
+    assert_spawn("riscv64", riscv64_help, 0, "virt", NULL);
+    assert_spawn("none", none_help, 0, "none", NULL);
+    assert_spawn_not("none", none_help, 0, "empty machine", "aarch64:\n");
+}
+
+static void test_combined_props(void)
+{
+    const char *virt_help[] = { "-M", "aarch64:virt,help", NULL };
+    const char *def_help[] = { "-M", "aarch64:,help", NULL };
+    const char *bare_help[] = { "-M", "virt,help", NULL };
+    const char *none_help[] = { "-M", "none,help", NULL };
+
+    assert_spawn("aarch64", virt_help, 0, "gic-version", NULL);
+    assert_spawn("aarch64", def_help, 1, NULL,
+                 "No machine specified, and there is no default");
+    assert_spawn(NULL, bare_help, 1, NULL, "needs an arch: prefix");
+    assert_spawn("none", none_help, 0, "none-machine", NULL);
+}
+
+static void test_combined_errors(void)
+{
+    const char *virt[] = { "-M", "virt", NULL };
+    const char *riscv64[] = { "-M", "riscv64", NULL };
+    const char *aarch64[] = { "-M", "aarch64", NULL };
+    const char *q35[] = { "-M", "aarch64:q35", NULL };
+    const char *sbsa[] = { "-M", "arm:sbsa-ref", NULL };
+    const char *nosuch_m[] = { "-M", "aarch64:no-such", NULL };
+    const char *nosuch_t[] = { "-M", "no-such:virt", NULL };
+    const char *upper[] = { "-M", "AARCH64:virt", NULL };
+    const char *none_virt[] = { "-M", "none:virt", NULL };
+    const char *lead[] = { "-M", ":virt", NULL };
+    const char *colon[] = { "-M", ":", NULL };
+    const char *extra[] = { "-M", "aarch64:virt:extra", NULL };
+
+    assert_spawn(NULL, virt, 1, NULL, "needs an arch: prefix");
+    assert_spawn(NULL, riscv64, 1, NULL, "needs an arch: prefix");
+    assert_spawn(NULL, aarch64, 1, NULL, "needs an arch: prefix");
+    assert_spawn("aarch64", q35, 1, NULL,
+                 "unsupported machine type: \"aarch64:q35\"");
+    assert_spawn("arm", sbsa, 1, NULL,
+                 "unsupported machine type: \"arm:sbsa-ref\"");
+    assert_spawn("aarch64", nosuch_m, 1, NULL,
+                 "unsupported machine type: \"aarch64:no-such\"");
+    assert_spawn(NULL, nosuch_t, 1, NULL, "target 'no-such' is not available");
+    assert_spawn(NULL, upper, 1, NULL, "target 'AARCH64' is not available");
+    assert_spawn("none", none_virt, 1, NULL,
+                 "machine type \"none:virt\" is not available for target 
none");
+    assert_spawn(NULL, lead, 1, NULL, "needs an arch: prefix");
+    assert_spawn(NULL, colon, 1, NULL, "needs an arch: prefix");
+    assert_spawn("aarch64", extra, 1, NULL,
+                 "unsupported machine type: \"aarch64:virt:extra\"");
+}
+
+static void test_combined_last_help(void)
+{
+    const char *run_then_help[] = { "-M", "aarch64:virt", "-M", "help", NULL };
+
+    assert_spawn("aarch64", run_then_help, 0, "virt", NULL);
+    assert_spawn_not("aarch64", run_then_help, 0, "virt", "q35");
+}
+
+static void test_combined_run(void)
+{
+    const char *aarch64_def[] = { "-M", "aarch64:", NULL };
+    const char *riscv64_def[] = { "-M", "riscv64:", NULL };
+    const char *last_riscv_def[] = {
+        "-M", "aarch64:virt", "-M", "riscv64:", NULL
+    };
+
+    assert_run("aarch64", "-M aarch64:virt", "virt");
+    assert_run("riscv64", "-M riscv64:virt", "virt");
+    assert_run("aarch64", "-M type=aarch64:virt", "virt");
+    assert_run("aarch64", "-machine aarch64:virt", "virt");
+    assert_spawn("aarch64", aarch64_def, 1, NULL,
+                 "No machine specified, and there is no default");
+    assert_spawn("riscv64", riscv64_def, 1, NULL,
+                 "No machine specified, and there is no default");
+    assert_run("x86_64", "-M x86_64:q35", "q35");
+    assert_run("x86_64", "-M x86_64:", NULL);
+    assert_run("x86_64", "-M aarch64:virt -M x86_64:q35", "q35");
+    assert_run("i386", "-M i386:", NULL);
+    assert_run("none", "-M none:", "none");
+    assert_run("none", "", "none");
+    assert_run("none", "-M none", "none");
+    assert_run("none", "-M none:none", "none");
+    assert_run("aarch64", "-M aarch64:none", "none");
+    assert_run("aarch64", "-M aarch64:virt -M none", "none");
+    assert_run("riscv64", "-M aarch64:virt -M riscv64:virt", "virt");
+    assert_run("aarch64", "-M aarch64:virt -M virt", "virt");
+    assert_run("aarch64", "-M virt -M aarch64:virt", "virt");
+    assert_run("aarch64", "-M aarch64: -M virt", "virt");
+    assert_spawn("riscv64", last_riscv_def, 1, NULL,
+                 "No machine specified, and there is no default");
+    assert_run("aarch64", "-M help -M aarch64:virt", "virt");
+}
+
+static void test_historic_help(void)
+{
+    const char *target_info = qtest_get_arch();
+    const struct ShippedTarget *t = lookup_shipped_target(target_info);
+    const char *help[] = { "-M", "help", NULL };
+    g_autofree gchar *out = NULL;
+    g_autofree gchar *err = NULL;
+    g_autoptr(QList) names = NULL;
+    int code;
+
+    spawn_qemu(&out, &err, &code, help);
+    g_assert_cmpint(code, ==, 0);
+    g_assert_nonnull(strstr(out ? out : "", "Supported machines are:"));
+    g_assert_null(strstr(out ? out : "", "none:\n"));
+    if (!t) {
+        return;
+    }
+    read_accels();
+    names = names_from_help(out, NULL);
+    assert_name_list(names, t->name, t->machines, t->n_machines);
+}
+
+static void test_historic_machine_help(void)
+{
+    const char *target_info = qtest_get_arch();
+    const struct ShippedTarget *t = lookup_shipped_target(target_info);
+    const char *machine;
+    g_autofree char *opt = NULL;
+    const char *args[] = { "-M", NULL, NULL };
+    size_t i;
+    bool have_virt = false;
+
+    if (!t) {
+        return;
+    }
+    machine = preferred_machine(t);
+    if (!machine) {
+        return;
+    }
+    opt = g_strdup_printf("%s,help", machine);
+    args[1] = opt;
+    assert_spawn(target_info, args, 0, machine, NULL);
+    for (i = 0; i < t->n_machines; i++) {
+        if (!strcmp(t->machines[i], "virt")) {
+            have_virt = true;
+            break;
+        }
+    }
+    if (have_virt &&
+        (!strcmp(target_info, "arm") || !strcmp(target_info, "aarch64"))) {
+        const char *virt_args[] = { "-M", "virt,help", NULL };
+
+        assert_spawn(target_info, virt_args, 0, "gic-version", NULL);
+    }
+}
+
+static void test_historic_run(void)
+{
+    const char *target_info = qtest_get_arch();
+    g_autofree char *match = g_strdup_printf("-M %s:none", target_info);
+    const char *mismatch[] = { "-M", "aarch64:virt", NULL };
+
+    assert_run(target_info, "-M none", "none");
+    assert_run(target_info, match, "none");
+    if (strcmp(target_info, "aarch64")) {
+        assert_spawn(target_info, mismatch, 1, NULL,
+                     "is not available for target");
+    } else {
+        assert_run(target_info, "-M virt", "virt");
+        assert_run(target_info, "-M aarch64:virt", "virt");
+    }
+}
+
+static void test_combined_help_lists(void)
+{
+    const char *help[] = { "-M", "help", NULL };
+    g_autofree gchar *out = NULL;
+    g_autofree gchar *err = NULL;
+    g_autoptr(QList) targets = NULL;
+    const QListEntry *e;
+    QTestState *qts;
+    int code;
+
+    spawn_qemu(&out, &err, &code, help);
+    g_assert_cmpint(code, ==, 0);
+    qts = qtest_init("-nodefaults -M none");
+    read_accel_flags(qts);
+    targets = query_targets(qts);
+    qtest_quit(qts);
+    g_assert_cmpuint(qlist_size(targets), >, 0);
+    for (e = qlist_first(targets); e; e = qlist_next(e)) {
+        QDict *tgt = qobject_to(QDict, qlist_entry_obj(e));
+        const struct ShippedTarget *t;
+        g_autoptr(QList) names = NULL;
+
+        g_assert_nonnull(tgt);
+        if (!strcmp(qdict_get_str(tgt, "arch"), "none")) {
+            QList *qmp_names = qdict_get_qlist(tgt, "machines");
+            g_autoptr(QList) help_names = names_from_help(out, "none");
+            const QListEntry *ne;
+            bool saw_none = false;
+
+            g_assert_nonnull(strstr(out, "none:\n"));
+            g_assert_cmpuint(qlist_size(help_names), ==, 
qlist_size(qmp_names));
+            for (ne = qlist_first(qmp_names); ne; ne = qlist_next(ne)) {
+                const char *name = qstring_get_str(qobject_to(QString,
+                                                               
qlist_entry_obj(ne)));
+                const QListEntry *he;
+                bool found = false;
+
+                if (!strcmp(name, "none")) {
+                    saw_none = true;
+                }
+                for (he = qlist_first(help_names); he; he = qlist_next(he)) {
+                    const char *hn = qstring_get_str(
+                        qobject_to(QString, qlist_entry_obj(he)));
+
+                    if (!strcmp(name, hn)) {
+                        found = true;
+                        break;
+                    }
+                }
+                g_assert_true(found);
+            }
+            g_assert_true(saw_none);
+            continue;
+        }
+        t = lookup_shipped_arch(qdict_get_str(tgt, "arch"),
+                                qdict_get_str(tgt, "endianness"));
+        g_assert_nonnull(t);
+        names = names_from_help(out, t->name);
+        assert_name_list(names, t->name, t->machines, t->n_machines);
+    }
+}
+
+static char *write_machine_config(const char *type)
+{
+    g_autofree char *text = g_strdup_printf("[machine]\ntype = \"%s\"\n", 
type);
+    g_autofree char *path = NULL;
+    GError *err = NULL;
+    int fd;
+
+    fd = g_file_open_tmp("qemu-m-XXXXXX", &path, &err);
+    g_assert_no_error(err);
+    g_assert_cmpint(fd, >=, 0);
+    close(fd);
+    g_assert_true(g_file_set_contents(path, text, -1, &err));
+    g_assert_no_error(err);
+    return g_steal_pointer(&path);
+}
+
+static void test_combined_readconfig(void)
+{
+    g_autofree char *aarch64_cfg = write_machine_config("aarch64:virt");
+    g_autofree char *none_cfg = write_machine_config("none:");
+    g_autofree char *riscv_cfg = write_machine_config("riscv64:virt");
+    g_autofree char *from_cfg = NULL;
+    g_autofree char *then_none = NULL;
+    g_autofree char *cfg_none = NULL;
+    g_autofree char *then_riscv = NULL;
+
+    from_cfg = g_strdup_printf("-readconfig \"%s\"", aarch64_cfg);
+    assert_run("aarch64", from_cfg, "virt");
+    then_none = g_strdup_printf("-readconfig \"%s\" -M none", aarch64_cfg);
+    assert_run("aarch64", then_none, "none");
+    cfg_none = g_strdup_printf("-readconfig \"%s\"", none_cfg);
+    assert_run("none", cfg_none, "none");
+    then_riscv = g_strdup_printf("-M aarch64:virt -readconfig \"%s\"",
+                                 riscv_cfg);
+    assert_run("riscv64", then_riscv, "virt");
+
+    unlink(aarch64_cfg);
+    unlink(none_cfg);
+    unlink(riscv_cfg);
+}
+
+static void test_combined_run_listed(void)
+{
+    g_autoptr(QList) targets = NULL;
+    const QListEntry *e;
+    QTestState *qts;
+
+    qts = qtest_init("-nodefaults -M none");
+    targets = query_targets(qts);
+    qtest_quit(qts);
+    for (e = qlist_first(targets); e; e = qlist_next(e)) {
+        QDict *tgt = qobject_to(QDict, qlist_entry_obj(e));
+        const struct ShippedTarget *t;
+        const char *machine;
+        g_autofree char *cli = NULL;
+
+        g_assert_nonnull(tgt);
+        if (!strcmp(qdict_get_str(tgt, "arch"), "none")) {
+            assert_run("none", "-M none:none", "none");
+            continue;
+        }
+        t = lookup_shipped_arch(qdict_get_str(tgt, "arch"),
+                                qdict_get_str(tgt, "endianness"));
+        g_assert_nonnull(t);
+        machine = preferred_machine(t);
+        g_assert_nonnull(machine);
+        cli = g_strdup_printf("-M %s:%s", t->name, machine);
+        assert_run(t->name, cli, machine);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    if (qemu_binary_is_combined()) {
+        g_test_add_func("/machine-option/help", test_combined_help);
+        g_test_add_func("/machine-option/help-lists", 
test_combined_help_lists);
+        g_test_add_func("/machine-option/props", test_combined_props);
+        g_test_add_func("/machine-option/errors", test_combined_errors);
+        g_test_add_func("/machine-option/last-help", test_combined_last_help);
+        g_test_add_func("/machine-option/run", test_combined_run);
+        g_test_add_func("/machine-option/readconfig", 
test_combined_readconfig);
+        g_test_add_func("/machine-option/run-listed", 
test_combined_run_listed);
+    } else {
+        qtest_add_func("machine-option/help", test_historic_help);
+        qtest_add_func("machine-option/machine-help", 
test_historic_machine_help);
+        qtest_add_func("machine-option/run", test_historic_run);
+    }
+
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index aef99d2bf52..99b7b19283b 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -19,6 +19,11 @@ slow_qtests = {
   'vmgenid-test': 610,
 }
 
+qtests_single_binary = [
+  'machine-option-test',
+  'machine-list-combined-test',
+]
+
 qtests_generic = [
   'cdrom-test',
   'device-introspect-test',
@@ -527,3 +532,36 @@ foreach dir : target_dirs
                 (slow_qtests.has_key(test) ? ['slow'] : []))
   endforeach
 endforeach
+
+if is_variable('qemu_system')
+  qtest_env_sb = environment()
+  qtest_env_sb.set('G_TEST_DBUS_DAEMON',
+                   meson.project_source_root() / 'tests/dbus-daemon.sh')
+  qtest_env_sb.set('QTEST_QEMU_BINARY', './qemu-system')
+  qtest_env_sb.set('PYTHON', python.full_path())
+  combined_targets_text = []
+  foreach name : single_binary_targets
+    combined_targets_text += '    "@0@",'.format(name)
+  endforeach
+  combined_targets_conf = configuration_data()
+  combined_targets_conf.set('COMBINED_TARGETS', 
'\n'.join(combined_targets_text))
+  combined_targets_h = configure_file(
+    input: 'machine-list-combined-targets.h.in',
+    output: 'machine-list-combined-targets.h',
+    configuration: combined_targets_conf,
+  )
+  foreach test : qtests_single_binary
+    qtest_executables += {
+      test: executable(test, [test + '.c', combined_targets_h],
+                       dependencies: [qemuutil, qos])
+    }
+    test('qtest-single-binary/' + test,
+         qtest_executables[test],
+         depends: [qemu_system, emulator_modules],
+         env: qtest_env_sb,
+         args: ['--tap', '-k'],
+         protocol: 'tap',
+         timeout: test == 'machine-option-test' ? 180 : 60,
+         suite: ['qtest', 'qtest-single-binary'])
+  endforeach
+endif
-- 
2.52.0.windows.1


Reply via email to