diff --git a/tests/meson.build b/tests/meson.build
index f75c248720..4792220b48 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -452,6 +452,7 @@ if conf.has('WITH_QEMU')
{ 'name': 'qemuagenttest', 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] },
{ 'name': 'qemublocktest', 'include': [ storage_file_inc_dir ], 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] },
{ 'name': 'qemucapabilitiestest', 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] },
+ { 'name': 'qemucapabilitiesnumbering', 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] },
{ 'name': 'qemucaps2xmltest', 'link_with': [ test_qemu_driver_lib ], 'link_whole': [ test_utils_qemu_lib ] },
{ 'name': 'qemucommandutiltest', 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] },
{ 'name': 'qemudomaincheckpointxml2xmltest', 'link_with': [ test_qemu_driver_lib ], 'link_whole': [ test_utils_qemu_lib ] },
diff --git a/tests/qemucapabilitiesnumbering.c b/tests/qemucapabilitiesnumbering.c
new file mode 100644
index 0000000000..eff7dbe2a1
--- /dev/null
+++ b/tests/qemucapabilitiesnumbering.c
@@ -0,0 +1,242 @@
+/*
+ * qemucapabilitiesnumbering.c: swiss-army knife for qemu capability data manipulation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * .
+ */
+#include
+
+#include "testutils.h"
+#include "testutilsqemu.h"
+#include "qemumonitortestutils.h"
+
+struct qmpTuple {
+ virJSONValue *command;
+ virJSONValue *reply;
+};
+
+struct qmpCommandList {
+ struct qmpTuple *items;
+ size_t nitems;
+};
+
+typedef struct qmpCommandList qmpCommandList;
+
+
+static int
+modify(struct qmpCommandList *list G_GNUC_UNUSED)
+{
+ /* in case you want to programmatically modify the replies file enable the
+ * following block and modify it to your needs. After compiling run this
+ * with:
+ *
+ * VIR_TEST_REGENERATE_OUTPUT=1 tests/qemucapabilitiesnumbering
+ *
+ * This applies the modification along with updating the files. Use git to
+ * your advantage to roll back mistakes.
+ */
+#if 0
+ struct qmpTuple tmptuple = { NULL, NULL };
+ size_t found = 0;
+ size_t i;
+
+ for (i = 0; i < list->nitems; i++) {
+ struct qmpTuple *item = list->items + i;
+ const char *cmdname = virJSONValueObjectGetString(item->command, "execute");
+
+ if (STREQ_NULLABLE(cmdname, "qom-list-properties")) {
+ found = i;
+ // break; /* uncomment if you want to find the first occurence */
+ }
+ }
+
+ if (found == 0) {
+ fprintf(stderr, "entry not found!!!\n");
+ return -1;
+ }
+
+ tmptuple.command = virJSONValueFromString("{\"execute\":\"dummy\"}");
+ tmptuple.reply = virJSONValueFromString("{\"return\":{}}");
+ // tmptuple.reply = virTestLoadFileJSON("path/", "to/", "file.json");
+
+ ignore_value(VIR_INSERT_ELEMENT(list->items, found + 1, list->nitems, tmptuple));
+#endif
+
+ return 0;
+}
+
+
+static void
+qmpCommandListFree(qmpCommandList* list)
+{
+ size_t i;
+
+ if (!list)
+ return;
+
+ for (i = 0; i < list->nitems; i++) {
+ struct qmpTuple *item = list->items + i;
+
+ virJSONValueFree(item->command);
+ virJSONValueFree(item->reply);
+ }
+
+ g_free(list->items);
+ g_free(list);
+}
+
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(qmpCommandList, qmpCommandListFree);
+
+
+static qmpCommandList *
+loadReplies(const char *filename)
+{
+ g_autofree char *replies = NULL;
+ g_autofree struct qemuMonitorTestCommandReplyTuple *items = NULL;
+ size_t nitems = 0;
+ g_autoptr(qmpCommandList) list = NULL;
+ size_t i;
+
+ if (virTestLoadFile(filename, &replies) < 0) {
+ fprintf(stderr, "Failed to load '%s'\n", filename);
+ return NULL;
+ }
+
+ if (qemuMonitorTestProcessFileEntries(replies, filename, &items, &nitems) < 0)
+ return NULL;
+
+ list = g_new0(qmpCommandList, 1);
+ list->items = g_new0(struct qmpTuple, nitems);
+
+ for (i = 0; i < nitems; i++) {
+ struct qemuMonitorTestCommandReplyTuple *item = items + i;
+
+ if (!(list->items[list->nitems].command = virJSONValueFromString(item->command)) ||
+ !(list->items[list->nitems++].reply = virJSONValueFromString(item->reply)))
+ return NULL;
+ }
+
+ return g_steal_pointer(&list);
+}
+
+/* see printLineSkipEmpty in tests/qemucapsprobemock.c */
+static void
+printLineSkipEmpty(const char *p,
+ virBuffer *buf)
+{
+ for (; *p; p++) {
+ if (p[0] == '\n' && p[1] == '\n')
+ continue;
+
+ virBufferAddChar(buf, *p);
+ }
+}
+
+
+static void
+renumberItem(virJSONValue *val,
+ size_t num)
+{
+ g_autoptr(virJSONValue) label = virJSONValueNewString(g_strdup_printf("libvirt-%zu", num));
+
+ virJSONValueObjectReplaceValue(val, "id", &label);
+}
+
+
+static int
+output(virBuffer *buf,
+ qmpCommandList *list)
+{
+ size_t commandindex = 1;
+ size_t i;
+
+ for (i = 0; i < list->nitems; i++) {
+ struct qmpTuple *item = list->items + i;
+ g_autofree char *jsoncommand = NULL;
+ g_autofree char *jsonreply = NULL;
+
+ if (STREQ_NULLABLE(virJSONValueObjectGetString(item->command, "execute"), "qmp_capabilities"))
+ commandindex = 1;
+
+ /* fix numbering */
+ renumberItem(item->command, commandindex);
+ renumberItem(item->reply, commandindex);
+ commandindex++;
+
+ /* output formatting */
+ if (!(jsoncommand = virJSONValueToString(item->command, true)) ||
+ !(jsonreply = virJSONValueToString(item->reply, true)))
+ return -1;
+
+ printLineSkipEmpty(jsoncommand, buf);
+ virBufferAddLit(buf, "\n");
+ printLineSkipEmpty(jsonreply, buf);
+ virBufferAddLit(buf, "\n");
+ }
+
+ virBufferTrim(buf, "\n");
+
+ return 0;
+}
+
+
+static int
+testCapsFile(const void *opaque)
+{
+ const char *repliesFile = opaque;
+ g_autoptr(qmpCommandList) list = NULL;
+ g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
+
+ if (!(list = loadReplies(repliesFile)))
+ return -1;
+
+ if (virTestGetRegenerate() > 0) {
+ if (modify(list) < 0)
+ return -1;
+ }
+
+ output(&buf, list);
+
+ if (virTestCompareToFile(virBufferCurrentContent(&buf), repliesFile) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+static int
+iterateCapsFile(const char *inputDir,
+ const char *prefix,
+ const char *version,
+ const char *archName,
+ const char *suffix,
+ void *opaque G_GNUC_UNUSED)
+{
+ g_autofree char *repliesFile = g_strdup_printf("%s/%s_%s.%s.%s", inputDir, prefix, version, archName, suffix);
+
+ return virTestRun(repliesFile, testCapsFile, repliesFile);
+}
+
+
+static int
+testmain(void)
+{
+ if (testQemuCapsIterate(".replies", iterateCapsFile, NULL) < 0)
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+}
+
+VIR_TEST_MAIN(testmain)