diff options
Diffstat (limited to 'tests')
83 files changed, 4676 insertions, 709 deletions
diff --git a/tests/.gitignore b/tests/.gitignore index 65496aa5a6..1e55722b6a 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -9,6 +9,7 @@ check-qom-proplist rcutorture test-aio test-bitops +test-blockjob-txn test-coroutine test-crypto-cipher test-crypto-hash @@ -30,6 +31,7 @@ test-qapi-types.[ch] test-qapi-visit.[ch] test-qdev-global-props test-qemu-opts +test-qga test-qmp-commands test-qmp-commands.h test-qmp-event @@ -44,6 +46,7 @@ test-string-input-visitor test-string-output-visitor test-thread-pool test-throttle +test-timed-average test-visitor-serialization test-vmstate test-write-threshold diff --git a/tests/Makefile b/tests/Makefile index 1c57e39c53..a1d03b457d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -47,6 +47,8 @@ check-unit-y += tests/test-thread-pool$(EXESUF) gcov-files-test-thread-pool-y = thread-pool.c gcov-files-test-hbitmap-y = util/hbitmap.c check-unit-y += tests/test-hbitmap$(EXESUF) +gcov-files-test-hbitmap-y = blockjob.c +check-unit-y += tests/test-blockjob-txn$(EXESUF) check-unit-y += tests/test-x86-cpuid$(EXESUF) # all code tested by test-x86-cpuid is inside topology.h gcov-files-test-x86-cpuid-y = @@ -81,6 +83,7 @@ check-unit-y += tests/test-crypto-cipher$(EXESUF) check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF) check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF) check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF) +check-unit-y += tests/test-timed-average$(EXESUF) check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh @@ -194,8 +197,9 @@ gcov-files-i386-y += hw/usb/hcd-xhci.c check-qtest-i386-y += tests/pc-cpu-test$(EXESUF) check-qtest-i386-y += tests/q35-test$(EXESUF) gcov-files-i386-y += hw/pci-host/q35.c -ifeq ($(CONFIG_VHOST_NET),y) -check-qtest-i386-$(CONFIG_LINUX) += tests/vhost-user-test$(EXESUF) +check-qtest-i386-$(CONFIG_VHOST_NET_TEST_i386) += tests/vhost-user-test$(EXESUF) +ifeq ($(CONFIG_VHOST_NET_TEST_i386),) +check-qtest-x86_64-$(CONFIG_VHOST_NET_TEST_x86_64) += tests/vhost-user-test$(EXESUF) endif check-qtest-i386-y += tests/test-netfilter$(EXESUF) check-qtest-x86_64-y = $(check-qtest-i386-y) @@ -265,7 +269,6 @@ qapi-schema += enum-dict-member.json qapi-schema += enum-int-member.json qapi-schema += enum-max-member.json qapi-schema += enum-missing-data.json -qapi-schema += enum-union-clash.json qapi-schema += enum-wrong-data.json qapi-schema += escape-outside-string.json qapi-schema += escape-too-big.json @@ -316,12 +319,17 @@ qapi-schema += redefined-builtin.json qapi-schema += redefined-command.json qapi-schema += redefined-event.json qapi-schema += redefined-type.json +qapi-schema += reserved-command-q.json +qapi-schema += reserved-member-has.json +qapi-schema += reserved-member-q.json +qapi-schema += reserved-member-u.json +qapi-schema += reserved-type-kind.json +qapi-schema += reserved-type-list.json qapi-schema += returns-alternate.json qapi-schema += returns-array-bad.json qapi-schema += returns-dict.json qapi-schema += returns-unknown.json qapi-schema += returns-whitelist.json -qapi-schema += struct-base-clash-base.json qapi-schema += struct-base-clash-deep.json qapi-schema += struct-base-clash.json qapi-schema += struct-data-invalid.json @@ -386,6 +394,7 @@ tests/test-coroutine$(EXESUF): tests/test-coroutine.o $(test-block-obj-y) tests/test-aio$(EXESUF): tests/test-aio.o $(test-block-obj-y) tests/test-rfifolock$(EXESUF): tests/test-rfifolock.o $(test-util-obj-y) tests/test-throttle$(EXESUF): tests/test-throttle.o $(test-block-obj-y) +tests/test-blockjob-txn$(EXESUF): tests/test-blockjob-txn.o $(test-block-obj-y) $(test-util-obj-y) tests/test-thread-pool$(EXESUF): tests/test-thread-pool.o $(test-block-obj-y) tests/test-iov$(EXESUF): tests/test-iov.o $(test-util-obj-y) tests/test-hbitmap$(EXESUF): tests/test-hbitmap.o $(test-util-obj-y) @@ -405,6 +414,8 @@ tests/test-vmstate$(EXESUF): tests/test-vmstate.o \ migration/vmstate.o migration/qemu-file.o migration/qemu-file-buf.o \ migration/qemu-file-unix.o qjson.o \ $(test-qom-obj-y) +tests/test-timed-average$(EXESUF): tests/test-timed-average.o qemu-timer.o \ + $(test-util-obj-y) tests/test-qapi-types.c tests/test-qapi-types.h :\ $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py) @@ -525,6 +536,7 @@ tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o $(test-util-obj-y) tests/test-write-threshold$(EXESUF): tests/test-write-threshold.o $(test-block-obj-y) tests/test-netfilter$(EXESUF): tests/test-netfilter.o $(qtest-obj-y) tests/ivshmem-test$(EXESUF): tests/ivshmem-test.o contrib/ivshmem-server/ivshmem-server.o $(libqos-pc-obj-y) +tests/vhost-user-bridge$(EXESUF): tests/vhost-user-bridge.o ifeq ($(CONFIG_POSIX),y) LIBS += -lutil @@ -600,7 +612,7 @@ $(patsubst %, check-%, $(check-unit-y)): check-%: % $(patsubst %, check-report-qtest-%.xml, $(QTEST_TARGETS)): check-report-qtest-%.xml: $(check-qtest-y) $(call quiet-command,QTEST_QEMU_BINARY=$*-softmmu/qemu-system-$* \ QTEST_QEMU_IMG=qemu-img$(EXESUF) \ - gtester -q $(GTESTER_OPTIONS) -o $@ -m=$(SPEED) $(check-qtest-$*-y),"GTESTER $@") + gtester -q $(GTESTER_OPTIONS) -o $@ -m=$(SPEED) $(check-qtest-$*-y) $(check-qtest-generic-y),"GTESTER $@") check-report-unit.xml: $(check-unit-y) $(call quiet-command,gtester -q $(GTESTER_OPTIONS) -o $@ -m=$(SPEED) $^, "GTESTER $@") @@ -648,7 +660,7 @@ check: check-qapi-schema check-unit check-qtest check-clean: $(MAKE) -C tests/tcg clean rm -rf $(check-unit-y) tests/*.o $(QEMU_IOTESTS_HELPERS-y) - rm -rf $(sort $(foreach target,$(SYSEMU_TARGET_LIST), $(check-qtest-$(target)-y))) + rm -rf $(sort $(foreach target,$(SYSEMU_TARGET_LIST), $(check-qtest-$(target)-y)) $(check-qtest-generic-y)) clean: check-clean diff --git a/tests/ahci-test.c b/tests/ahci-test.c index 59d387c6d0..088850642e 100644 --- a/tests/ahci-test.c +++ b/tests/ahci-test.c @@ -39,15 +39,17 @@ #include "hw/pci/pci_ids.h" #include "hw/pci/pci_regs.h" -/* Test-specific defines -- in MiB */ -#define TEST_IMAGE_SIZE_MB (200 * 1024) -#define TEST_IMAGE_SECTORS ((TEST_IMAGE_SIZE_MB / AHCI_SECTOR_SIZE) \ - * 1024 * 1024) +/* Test images sizes in MB */ +#define TEST_IMAGE_SIZE_MB_LARGE (200 * 1024) +#define TEST_IMAGE_SIZE_MB_SMALL 64 /*** Globals ***/ static char tmp_path[] = "/tmp/qtest.XXXXXX"; static char debug_path[] = "/tmp/qtest-blkdebug.XXXXXX"; +static char mig_socket[] = "/tmp/qtest-migration.XXXXXX"; static bool ahci_pedantic; +static const char *imgfmt; +static unsigned test_image_size_mb; /*** Function Declarations ***/ static void ahci_test_port_spec(AHCIQState *ahci, uint8_t port); @@ -60,6 +62,11 @@ static void ahci_test_pmcap(AHCIQState *ahci, uint8_t offset); /*** Utilities ***/ +static uint64_t mb_to_sectors(uint64_t image_size_mb) +{ + return (image_size_mb * 1024 * 1024) / AHCI_SECTOR_SIZE; +} + static void string_bswap16(uint16_t *s, size_t bytes) { g_assert_cmphex((bytes & 1), ==, 0); @@ -114,8 +121,11 @@ static void ahci_migrate(AHCIQState *from, AHCIQState *to, const char *uri) { QOSState *tmp = to->parent; QPCIDevice *dev = to->dev; + char *uri_local = NULL; + if (uri == NULL) { - uri = "tcp:127.0.0.1:1234"; + uri_local = g_strdup_printf("%s%s", "unix:", mig_socket); + uri = uri_local; } /* context will be 'to' after completion. */ @@ -135,6 +145,7 @@ static void ahci_migrate(AHCIQState *from, AHCIQState *to, const char *uri) from->dev = dev; verify_state(to); + g_free(uri_local); } /*** Test Setup & Teardown ***/ @@ -170,11 +181,11 @@ static AHCIQState *ahci_boot(const char *cli, ...) va_end(ap); } else { cli = "-drive if=none,id=drive0,file=%s,cache=writeback,serial=%s" - ",format=qcow2" + ",format=%s" " -M q35 " "-device ide-hd,drive=drive0 " "-global ide-hd.ver=%s"; - s = ahci_boot(cli, tmp_path, "testdisk", "version"); + s = ahci_boot(cli, tmp_path, "testdisk", imgfmt, "version"); } return s; @@ -900,7 +911,7 @@ static void ahci_test_max(AHCIQState *ahci) uint64_t nsect; uint8_t port; uint8_t cmd; - uint64_t config_sect = TEST_IMAGE_SECTORS - 1; + uint64_t config_sect = mb_to_sectors(test_image_size_mb) - 1; if (config_sect > 0xFFFFFF) { cmd = CMD_READ_MAX_EXT; @@ -1073,12 +1084,12 @@ static void test_flush_retry(void) prepare_blkdebug_script(debug_path, "flush_to_disk"); ahci = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0," - "format=qcow2,cache=writeback," + "format=%s,cache=writeback," "rerror=stop,werror=stop " "-M q35 " "-device ide-hd,drive=drive0 ", debug_path, - tmp_path); + tmp_path, imgfmt); /* Issue Flush Command and wait for error */ port = ahci_port_select(ahci); @@ -1105,18 +1116,19 @@ static void test_flush_retry(void) static void test_migrate_sanity(void) { AHCIQState *src, *dst; - const char *uri = "tcp:127.0.0.1:1234"; + char *uri = g_strdup_printf("unix:%s", mig_socket); src = ahci_boot("-m 1024 -M q35 " - "-hda %s ", tmp_path); + "-drive if=ide,file=%s,format=%s ", tmp_path, imgfmt); dst = ahci_boot("-m 1024 -M q35 " - "-hda %s " - "-incoming %s", tmp_path, uri); + "-drive if=ide,file=%s,format=%s " + "-incoming %s", tmp_path, imgfmt, uri); ahci_migrate(src, dst, uri); ahci_shutdown(src); ahci_shutdown(dst); + g_free(uri); } /** @@ -1129,13 +1141,14 @@ static void ahci_migrate_simple(uint8_t cmd_read, uint8_t cmd_write) size_t bufsize = 4096; unsigned char *tx = g_malloc(bufsize); unsigned char *rx = g_malloc0(bufsize); - const char *uri = "tcp:127.0.0.1:1234"; + char *uri = g_strdup_printf("unix:%s", mig_socket); src = ahci_boot_and_enable("-m 1024 -M q35 " - "-hda %s ", tmp_path); + "-drive if=ide,format=%s,file=%s ", + imgfmt, tmp_path); dst = ahci_boot("-m 1024 -M q35 " - "-hda %s " - "-incoming %s", tmp_path, uri); + "-drive if=ide,format=%s,file=%s " + "-incoming %s", imgfmt, tmp_path, uri); set_context(src->parent); @@ -1158,6 +1171,7 @@ static void ahci_migrate_simple(uint8_t cmd_read, uint8_t cmd_write) ahci_shutdown(dst); g_free(rx); g_free(tx); + g_free(uri); } static void test_migrate_dma(void) @@ -1190,12 +1204,12 @@ static void ahci_halted_io_test(uint8_t cmd_read, uint8_t cmd_write) prepare_blkdebug_script(debug_path, "write_aio"); ahci = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0," - "format=qcow2,cache=writeback," + "format=%s,cache=writeback," "rerror=stop,werror=stop " "-M q35 " "-device ide-hd,drive=drive0 ", debug_path, - tmp_path); + tmp_path, imgfmt); /* Initialize and prepare */ port = ahci_port_select(ahci); @@ -1251,25 +1265,25 @@ static void ahci_migrate_halted_io(uint8_t cmd_read, uint8_t cmd_write) unsigned char *rx = g_malloc0(bufsize); uint64_t ptr; AHCICommand *cmd; - const char *uri = "tcp:127.0.0.1:1234"; + char *uri = g_strdup_printf("unix:%s", mig_socket); prepare_blkdebug_script(debug_path, "write_aio"); src = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0," - "format=qcow2,cache=writeback," + "format=%s,cache=writeback," "rerror=stop,werror=stop " "-M q35 " "-device ide-hd,drive=drive0 ", debug_path, - tmp_path); + tmp_path, imgfmt); dst = ahci_boot("-drive file=%s,if=none,id=drive0," - "format=qcow2,cache=writeback," + "format=%s,cache=writeback," "rerror=stop,werror=stop " "-M q35 " "-device ide-hd,drive=drive0 " "-incoming %s", - tmp_path, uri); + tmp_path, imgfmt, uri); set_context(src->parent); @@ -1301,6 +1315,7 @@ static void ahci_migrate_halted_io(uint8_t cmd_read, uint8_t cmd_write) ahci_shutdown(dst); g_free(rx); g_free(tx); + g_free(uri); } static void test_migrate_halted_dma(void) @@ -1322,20 +1337,22 @@ static void test_flush_migrate(void) AHCICommand *cmd; uint8_t px; const char *s; - const char *uri = "tcp:127.0.0.1:1234"; + char *uri = g_strdup_printf("unix:%s", mig_socket); prepare_blkdebug_script(debug_path, "flush_to_disk"); src = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0," - "cache=writeback,rerror=stop,werror=stop " + "cache=writeback,rerror=stop,werror=stop," + "format=%s " "-M q35 " "-device ide-hd,drive=drive0 ", - debug_path, tmp_path); + debug_path, tmp_path, imgfmt); dst = ahci_boot("-drive file=%s,if=none,id=drive0," - "cache=writeback,rerror=stop,werror=stop " + "cache=writeback,rerror=stop,werror=stop," + "format=%s " "-M q35 " "-device ide-hd,drive=drive0 " - "-incoming %s", tmp_path, uri); + "-incoming %s", tmp_path, imgfmt, uri); set_context(src->parent); @@ -1360,6 +1377,7 @@ static void test_flush_migrate(void) ahci_command_free(cmd); ahci_shutdown(src); ahci_shutdown(dst); + g_free(uri); } static void test_max(void) @@ -1476,7 +1494,7 @@ static uint64_t offset_sector(enum OffsetType ofst, return 1; case OFFSET_HIGH: ceil = (addr_type == ADDR_MODE_LBA28) ? 0xfffffff : 0xffffffffffff; - ceil = MIN(ceil, TEST_IMAGE_SECTORS - 1); + ceil = MIN(ceil, mb_to_sectors(test_image_size_mb) - 1); nsectors = buffsize / AHCI_SECTOR_SIZE; return ceil - nsectors + 1; default: @@ -1558,8 +1576,9 @@ static void create_ahci_io_test(enum IOMode type, enum AddrMode addr, enum BuffLen len, enum OffsetType offset) { char *name; - AHCIIOTestOptions *opts = g_malloc(sizeof(AHCIIOTestOptions)); + AHCIIOTestOptions *opts; + opts = g_malloc(sizeof(AHCIIOTestOptions)); opts->length = len; opts->address_type = addr; opts->io_type = type; @@ -1571,6 +1590,13 @@ static void create_ahci_io_test(enum IOMode type, enum AddrMode addr, buff_len_str[len], offset_str[offset]); + if ((addr == ADDR_MODE_LBA48) && (offset == OFFSET_HIGH) && + (mb_to_sectors(test_image_size_mb) <= 0xFFFFFFF)) { + g_test_message("%s: skipped; test image too small", name); + g_free(name); + return; + } + qtest_add_data_func(name, opts, test_io_interface); g_free(name); } @@ -1617,15 +1643,33 @@ int main(int argc, char **argv) return 0; } - /* Create a temporary qcow2 image */ - close(mkstemp(tmp_path)); - mkqcow2(tmp_path, TEST_IMAGE_SIZE_MB); + /* Create a temporary image */ + fd = mkstemp(tmp_path); + g_assert(fd >= 0); + if (have_qemu_img()) { + imgfmt = "qcow2"; + test_image_size_mb = TEST_IMAGE_SIZE_MB_LARGE; + mkqcow2(tmp_path, TEST_IMAGE_SIZE_MB_LARGE); + } else { + g_test_message("QTEST_QEMU_IMG not set or qemu-img missing; " + "skipping LBA48 high-sector tests"); + imgfmt = "raw"; + test_image_size_mb = TEST_IMAGE_SIZE_MB_SMALL; + ret = ftruncate(fd, test_image_size_mb * 1024 * 1024); + g_assert(ret == 0); + } + close(fd); /* Create temporary blkdebug instructions */ fd = mkstemp(debug_path); g_assert(fd >= 0); close(fd); + /* Reserve a hollow file to use as a socket for migration tests */ + fd = mkstemp(mig_socket); + g_assert(fd >= 0); + close(fd); + /* Run the tests */ qtest_add_func("/ahci/sanity", test_sanity); qtest_add_func("/ahci/pci_spec", test_pci_spec); @@ -1668,6 +1712,7 @@ int main(int argc, char **argv) /* Cleanup */ unlink(tmp_path); unlink(debug_path); + unlink(mig_socket); return ret; } diff --git a/tests/check-qjson.c b/tests/check-qjson.c index 1cfffa5934..61e9bfbabf 100644 --- a/tests/check-qjson.c +++ b/tests/check-qjson.c @@ -1484,6 +1484,30 @@ static void unterminated_literal(void) g_assert(obj == NULL); } +static char *make_nest(char *buf, size_t cnt) +{ + memset(buf, '[', cnt - 1); + buf[cnt - 1] = '{'; + buf[cnt] = '}'; + memset(buf + cnt + 1, ']', cnt - 1); + buf[2 * cnt] = 0; + return buf; +} + +static void limits_nesting(void) +{ + enum { max_nesting = 1024 }; /* see qobject/json-streamer.c */ + char buf[2 * (max_nesting + 1) + 1]; + QObject *obj; + + obj = qobject_from_json(make_nest(buf, max_nesting)); + g_assert(obj != NULL); + qobject_decref(obj); + + obj = qobject_from_json(make_nest(buf, max_nesting + 1)); + g_assert(obj == NULL); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); @@ -1519,6 +1543,7 @@ int main(int argc, char **argv) g_test_add_func("/errors/invalid_array_comma", invalid_array_comma); g_test_add_func("/errors/invalid_dict_comma", invalid_dict_comma); g_test_add_func("/errors/unterminated/literal", unterminated_literal); + g_test_add_func("/errors/limits/nesting", limits_nesting); return g_test_run(); } diff --git a/tests/check-qom-proplist.c b/tests/check-qom-proplist.c index 7400b1fce9..e674c0fa89 100644 --- a/tests/check-qom-proplist.c +++ b/tests/check-qom-proplist.c @@ -152,6 +152,148 @@ static const TypeInfo dummy_info = { .class_size = sizeof(DummyObjectClass), }; + +/* + * The following 3 object classes are used to + * simulate the kind of relationships seen in + * qdev, which result in complex object + * property destruction ordering. + * + * DummyDev has a 'bus' child to a DummyBus + * DummyBus has a 'backend' child to a DummyBackend + * DummyDev has a 'backend' link to DummyBackend + * + * When DummyDev is finalized, it unparents the + * DummyBackend, which unparents the DummyDev + * which deletes the 'backend' link from DummyDev + * to DummyBackend. This illustrates that the + * object_property_del_all() method needs to + * cope with the list of properties being changed + * while it iterates over them. + */ +typedef struct DummyDev DummyDev; +typedef struct DummyDevClass DummyDevClass; +typedef struct DummyBus DummyBus; +typedef struct DummyBusClass DummyBusClass; +typedef struct DummyBackend DummyBackend; +typedef struct DummyBackendClass DummyBackendClass; + +#define TYPE_DUMMY_DEV "qemu-dummy-dev" +#define TYPE_DUMMY_BUS "qemu-dummy-bus" +#define TYPE_DUMMY_BACKEND "qemu-dummy-backend" + +#define DUMMY_DEV(obj) \ + OBJECT_CHECK(DummyDev, (obj), TYPE_DUMMY_DEV) +#define DUMMY_BUS(obj) \ + OBJECT_CHECK(DummyBus, (obj), TYPE_DUMMY_BUS) +#define DUMMY_BACKEND(obj) \ + OBJECT_CHECK(DummyBackend, (obj), TYPE_DUMMY_BACKEND) + +struct DummyDev { + Object parent_obj; + + DummyBus *bus; +}; + +struct DummyDevClass { + ObjectClass parent_class; +}; + +struct DummyBus { + Object parent_obj; + + DummyBackend *backend; +}; + +struct DummyBusClass { + ObjectClass parent_class; +}; + +struct DummyBackend { + Object parent_obj; +}; + +struct DummyBackendClass { + ObjectClass parent_class; +}; + + +static void dummy_dev_init(Object *obj) +{ + DummyDev *dev = DUMMY_DEV(obj); + DummyBus *bus = DUMMY_BUS(object_new(TYPE_DUMMY_BUS)); + DummyBackend *backend = DUMMY_BACKEND(object_new(TYPE_DUMMY_BACKEND)); + + object_property_add_child(obj, "bus", OBJECT(bus), NULL); + dev->bus = bus; + object_property_add_child(OBJECT(bus), "backend", OBJECT(backend), NULL); + bus->backend = backend; + + object_property_add_link(obj, "backend", TYPE_DUMMY_BACKEND, + (Object **)&bus->backend, NULL, 0, NULL); +} + +static void dummy_dev_unparent(Object *obj) +{ + DummyDev *dev = DUMMY_DEV(obj); + object_unparent(OBJECT(dev->bus)); +} + +static void dummy_dev_class_init(ObjectClass *klass, void *opaque) +{ + klass->unparent = dummy_dev_unparent; +} + + +static void dummy_bus_init(Object *obj) +{ +} + +static void dummy_bus_unparent(Object *obj) +{ + DummyBus *bus = DUMMY_BUS(obj); + object_property_del(obj->parent, "backend", NULL); + object_unparent(OBJECT(bus->backend)); +} + +static void dummy_bus_class_init(ObjectClass *klass, void *opaque) +{ + klass->unparent = dummy_bus_unparent; +} + +static void dummy_backend_init(Object *obj) +{ +} + + +static const TypeInfo dummy_dev_info = { + .name = TYPE_DUMMY_DEV, + .parent = TYPE_OBJECT, + .instance_size = sizeof(DummyDev), + .instance_init = dummy_dev_init, + .class_size = sizeof(DummyDevClass), + .class_init = dummy_dev_class_init, +}; + +static const TypeInfo dummy_bus_info = { + .name = TYPE_DUMMY_BUS, + .parent = TYPE_OBJECT, + .instance_size = sizeof(DummyBus), + .instance_init = dummy_bus_init, + .class_size = sizeof(DummyBusClass), + .class_init = dummy_bus_class_init, +}; + +static const TypeInfo dummy_backend_info = { + .name = TYPE_DUMMY_BACKEND, + .parent = TYPE_OBJECT, + .instance_size = sizeof(DummyBackend), + .instance_init = dummy_backend_init, + .class_size = sizeof(DummyBackendClass), +}; + + + static void test_dummy_createv(void) { Error *err = NULL; @@ -283,20 +425,83 @@ static void test_dummy_getenum(void) &err); g_assert(err != NULL); error_free(err); + + object_unparent(OBJECT(dobj)); } +static void test_dummy_iterator(void) +{ + Object *parent = object_get_objects_root(); + DummyObject *dobj = DUMMY_OBJECT( + object_new_with_props(TYPE_DUMMY, + parent, + "dummy0", + &error_abort, + "bv", "yes", + "sv", "Hiss hiss hiss", + "av", "platypus", + NULL)); + + ObjectProperty *prop; + ObjectPropertyIterator *iter; + bool seenbv = false, seensv = false, seenav = false, seentype; + + iter = object_property_iter_init(OBJECT(dobj)); + while ((prop = object_property_iter_next(iter))) { + if (g_str_equal(prop->name, "bv")) { + seenbv = true; + } else if (g_str_equal(prop->name, "sv")) { + seensv = true; + } else if (g_str_equal(prop->name, "av")) { + seenav = true; + } else if (g_str_equal(prop->name, "type")) { + /* This prop comes from the base Object class */ + seentype = true; + } else { + g_printerr("Found prop '%s'\n", prop->name); + g_assert_not_reached(); + } + } + object_property_iter_free(iter); + g_assert(seenbv); + g_assert(seenav); + g_assert(seensv); + g_assert(seentype); + + object_unparent(OBJECT(dobj)); +} + + +static void test_dummy_delchild(void) +{ + Object *parent = object_get_objects_root(); + DummyDev *dev = DUMMY_DEV( + object_new_with_props(TYPE_DUMMY_DEV, + parent, + "dev0", + &error_abort, + NULL)); + + object_unparent(OBJECT(dev)); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); module_call_init(MODULE_INIT_QOM); type_register_static(&dummy_info); + type_register_static(&dummy_dev_info); + type_register_static(&dummy_bus_info); + type_register_static(&dummy_backend_info); g_test_add_func("/qom/proplist/createlist", test_dummy_createlist); g_test_add_func("/qom/proplist/createv", test_dummy_createv); g_test_add_func("/qom/proplist/badenum", test_dummy_badenum); g_test_add_func("/qom/proplist/getenum", test_dummy_getenum); + g_test_add_func("/qom/proplist/iterator", test_dummy_iterator); + g_test_add_func("/qom/proplist/delchild", test_dummy_delchild); return g_test_run(); } diff --git a/tests/crypto-tls-x509-helpers.c b/tests/crypto-tls-x509-helpers.c index c5de67baaf..47b4c7ba53 100644 --- a/tests/crypto-tls-x509-helpers.c +++ b/tests/crypto-tls-x509-helpers.c @@ -153,6 +153,7 @@ test_tls_get_ipaddr(const char *addrstr, *datalen = res->ai_addrlen; *data = g_new(char, *datalen); memcpy(*data, res->ai_addr, *datalen); + freeaddrinfo(res); } /* @@ -465,6 +466,7 @@ void test_tls_write_cert_chain(const char *filename, if (!g_file_set_contents(filename, buffer, offset, NULL)) { abort(); } + g_free(buffer); } diff --git a/tests/i440fx-test.c b/tests/i440fx-test.c index d0bc8de25a..7fa170990f 100644 --- a/tests/i440fx-test.c +++ b/tests/i440fx-test.c @@ -191,7 +191,7 @@ static void write_area(uint32_t start, uint32_t end, uint8_t value) uint32_t size = end - start + 1; uint8_t *data; - data = g_malloc0(size); + data = g_malloc(size); memset(data, value, size); memwrite(start, data, size); diff --git a/tests/ide-test.c b/tests/ide-test.c index d1014bbc46..b864701356 100644 --- a/tests/ide-test.c +++ b/tests/ide-test.c @@ -593,12 +593,12 @@ static void test_flush_nodev(void) ide_test_quit(); } -static void test_pci_retry_flush(const char *machine) +static void test_pci_retry_flush(void) { test_retry_flush("pc"); } -static void test_isa_retry_flush(const char *machine) +static void test_isa_retry_flush(void) { test_retry_flush("isapc"); } @@ -642,15 +642,19 @@ static void nsleep(int64_t nsecs) static uint8_t ide_wait_clear(uint8_t flag) { - int i; uint8_t data; + time_t st; /* Wait with a 5 second timeout */ - for (i = 0; i <= 12500000; i++) { + time(&st); + while (true) { data = inb(IDE_BASE + reg_status); if (!(data & flag)) { return data; } + if (difftime(time(NULL), st) > 5.0) { + break; + } nsleep(400); } g_assert_not_reached(); @@ -658,14 +662,18 @@ static uint8_t ide_wait_clear(uint8_t flag) static void ide_wait_intr(int irq) { - int i; + time_t st; bool intr; - for (i = 0; i <= 12500000; i++) { + time(&st); + while (true) { intr = get_irq(irq); if (intr) { return; } + if (difftime(time(NULL), st) > 5.0) { + break; + } nsleep(400); } @@ -699,24 +707,16 @@ static void cdrom_pio_impl(int nblocks) outb(IDE_BASE + reg_lba_middle, BYTE_COUNT_LIMIT & 0xFF); outb(IDE_BASE + reg_lba_high, (BYTE_COUNT_LIMIT >> 8 & 0xFF)); outb(IDE_BASE + reg_command, CMD_PACKET); - /* HPD0: Check_Status_A State */ + /* HP0: Check_Status_A State */ nsleep(400); data = ide_wait_clear(BSY); - /* HPD1: Send_Packet State */ + /* HP1: Send_Packet State */ assert_bit_set(data, DRQ | DRDY); assert_bit_clear(data, ERR | DF | BSY); /* SCSI CDB (READ10) -- read n*2048 bytes from block 0 */ send_scsi_cdb_read10(0, nblocks); - /* HPD3: INTRQ_Wait */ - ide_wait_intr(IDE_PRIMARY_IRQ); - - /* HPD2: Check_Status_B */ - data = ide_wait_clear(BSY); - assert_bit_set(data, DRQ | DRDY); - assert_bit_clear(data, ERR | DF | BSY); - /* Read data back: occurs in bursts of 'BYTE_COUNT_LIMIT' bytes. * If BYTE_COUNT_LIMIT is odd, we transfer BYTE_COUNT_LIMIT - 1 bytes. * We allow an odd limit only when the remaining transfer size is @@ -728,11 +728,25 @@ static void cdrom_pio_impl(int nblocks) for (i = 0; i < DIV_ROUND_UP(rxsize, limit); i++) { size_t offset = i * (limit / 2); size_t rem = (rxsize / 2) - offset; + + /* HP3: INTRQ_Wait */ + ide_wait_intr(IDE_PRIMARY_IRQ); + + /* HP2: Check_Status_B (and clear IRQ) */ + data = ide_wait_clear(BSY); + assert_bit_set(data, DRQ | DRDY); + assert_bit_clear(data, ERR | DF | BSY); + + /* HP4: Transfer_Data */ for (j = 0; j < MIN((limit / 2), rem); j++) { rx[offset + j] = le16_to_cpu(inw(IDE_BASE + reg_data)); } - ide_wait_intr(IDE_PRIMARY_IRQ); } + + /* Check for final completion IRQ */ + ide_wait_intr(IDE_PRIMARY_IRQ); + + /* Sanity check final state */ data = ide_wait_clear(DRQ); assert_bit_set(data, DRDY); assert_bit_clear(data, DRQ | ERR | DF | BSY); diff --git a/tests/ivshmem-test.c b/tests/ivshmem-test.c index c8f0cf0f70..03c7b962a3 100644 --- a/tests/ivshmem-test.c +++ b/tests/ivshmem-test.c @@ -40,6 +40,7 @@ static QPCIDevice *get_device(void) QPCIBus *pcibus; pcibus = qpci_init_pc(); + dev = NULL; qpci_device_foreach(pcibus, 0x1af4, 0x1110, save_fn, &dev); g_assert(dev != NULL); @@ -392,7 +393,7 @@ static void test_ivshmem_memdev(void) /* just for the sake of checking memory-backend property */ setup_vm_cmd(&state, "-object memory-backend-ram,size=1M,id=mb1" - " -device ivshmem,memdev=mb1", false); + " -device ivshmem,x-memdev=mb1", false); qtest_quit(state.qtest); } @@ -478,10 +479,12 @@ int main(int argc, char **argv) tmpserver = g_strconcat(tmpdir, "/server", NULL); qtest_add_func("/ivshmem/single", test_ivshmem_single); - qtest_add_func("/ivshmem/pair", test_ivshmem_pair); - qtest_add_func("/ivshmem/server", test_ivshmem_server); qtest_add_func("/ivshmem/hotplug", test_ivshmem_hotplug); qtest_add_func("/ivshmem/memdev", test_ivshmem_memdev); + if (g_test_slow()) { + qtest_add_func("/ivshmem/pair", test_ivshmem_pair); + qtest_add_func("/ivshmem/server", test_ivshmem_server); + } ret = g_test_run(); diff --git a/tests/libqos/libqos.c b/tests/libqos/libqos.c index 8d7c5a9db8..2d1a802dbe 100644 --- a/tests/libqos/libqos.c +++ b/tests/libqos/libqos.c @@ -147,6 +147,23 @@ void migrate(QOSState *from, QOSState *to, const char *uri) set_context(to); } +bool have_qemu_img(void) +{ + char *rpath; + const char *path = getenv("QTEST_QEMU_IMG"); + if (!path) { + return false; + } + + rpath = realpath(path, NULL); + if (!rpath) { + return false; + } else { + free(rpath); + return true; + } +} + void mkimg(const char *file, const char *fmt, unsigned size_mb) { gchar *cli; @@ -155,13 +172,14 @@ void mkimg(const char *file, const char *fmt, unsigned size_mb) GError *err = NULL; char *qemu_img_path; gchar *out, *out2; - char *abs_path; + char *qemu_img_abs_path; qemu_img_path = getenv("QTEST_QEMU_IMG"); - abs_path = realpath(qemu_img_path, NULL); - assert(qemu_img_path); + g_assert(qemu_img_path); + qemu_img_abs_path = realpath(qemu_img_path, NULL); + g_assert(qemu_img_abs_path); - cli = g_strdup_printf("%s create -f %s %s %uM", abs_path, + cli = g_strdup_printf("%s create -f %s %s %uM", qemu_img_abs_path, fmt, file, size_mb); ret = g_spawn_command_line_sync(cli, &out, &out2, &rc, &err); if (err) { @@ -183,7 +201,7 @@ void mkimg(const char *file, const char *fmt, unsigned size_mb) g_free(out); g_free(out2); g_free(cli); - free(abs_path); + free(qemu_img_abs_path); } void mkqcow2(const char *file, unsigned size_mb) diff --git a/tests/libqos/libqos.h b/tests/libqos/libqos.h index 492a651f5b..ca14d2e9fe 100644 --- a/tests/libqos/libqos.h +++ b/tests/libqos/libqos.h @@ -19,6 +19,7 @@ typedef struct QOSState { QOSState *qtest_vboot(QOSOps *ops, const char *cmdline_fmt, va_list ap); QOSState *qtest_boot(QOSOps *ops, const char *cmdline_fmt, ...); void qtest_shutdown(QOSState *qs); +bool have_qemu_img(void); void mkimg(const char *file, const char *fmt, unsigned size_mb); void mkqcow2(const char *file, unsigned size_mb); void set_context(QOSState *s); diff --git a/tests/libqtest.c b/tests/libqtest.c index f6f3d7a950..fa314e1ee7 100644 --- a/tests/libqtest.c +++ b/tests/libqtest.c @@ -110,6 +110,11 @@ static void kill_qemu(QTestState *s) } } +static void kill_qemu_hook_func(void *s) +{ + kill_qemu(s); +} + static void sigabrt_handler(int signo) { g_hook_list_invoke(&abrt_hooks, FALSE); @@ -133,7 +138,7 @@ static void cleanup_sigabrt_handler(void) sigaction(SIGABRT, &sigact_old, NULL); } -void qtest_add_abrt_handler(void (*fn), const void *data) +void qtest_add_abrt_handler(GHookFunc fn, const void *data) { GHook *hook; @@ -170,7 +175,7 @@ QTestState *qtest_init(const char *extra_args) sock = init_socket(socket_path); qmpsock = init_socket(qmp_socket_path); - qtest_add_abrt_handler(kill_qemu, s); + qtest_add_abrt_handler(kill_qemu_hook_func, s); s->qemu_pid = fork(); if (s->qemu_pid == 0) { @@ -351,7 +356,7 @@ typedef struct { QDict *response; } QMPResponseParser; -static void qmp_response(JSONMessageParser *parser, QList *tokens) +static void qmp_response(JSONMessageParser *parser, GQueue *tokens) { QMPResponseParser *qmp = container_of(parser, QMPResponseParser, parser); QObject *obj; @@ -755,14 +760,15 @@ void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size) g_strfreev(args); } -void qtest_add_func(const char *str, void (*fn)) +void qtest_add_func(const char *str, void (*fn)(void)) { gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str); g_test_add_func(path, fn); g_free(path); } -void qtest_add_data_func(const char *str, const void *data, void (*fn)) +void qtest_add_data_func(const char *str, const void *data, + void (*fn)(const void *)) { gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str); g_test_add_data_func(path, data, fn); diff --git a/tests/libqtest.h b/tests/libqtest.h index df087452ee..ebdd5bbe53 100644 --- a/tests/libqtest.h +++ b/tests/libqtest.h @@ -416,7 +416,7 @@ const char *qtest_get_arch(void); * The path is prefixed with the architecture under test, as * returned by qtest_get_arch(). */ -void qtest_add_func(const char *str, void (*fn)); +void qtest_add_func(const char *str, void (*fn)(void)); /** * qtest_add_data_func: @@ -428,7 +428,8 @@ void qtest_add_func(const char *str, void (*fn)); * The path is prefixed with the architecture under test, as * returned by qtest_get_arch(). */ -void qtest_add_data_func(const char *str, const void *data, void (*fn)); +void qtest_add_data_func(const char *str, const void *data, + void (*fn)(const void *)); /** * qtest_add: @@ -450,7 +451,7 @@ void qtest_add_data_func(const char *str, const void *data, void (*fn)); g_free(path); \ } while (0) -void qtest_add_abrt_handler(void (*fn), const void *data); +void qtest_add_abrt_handler(GHookFunc fn, const void *data); /** * qtest_start: diff --git a/tests/qapi-schema/enum-union-clash.err b/tests/qapi-schema/enum-union-clash.err deleted file mode 100644 index c04e1a8064..0000000000 --- a/tests/qapi-schema/enum-union-clash.err +++ /dev/null @@ -1 +0,0 @@ -tests/qapi-schema/enum-union-clash.json:2: enum 'UnionKind' should not end in 'Kind' diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 4e2d7c2063..44638da948 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -3,6 +3,9 @@ # This file is a stress test of supported qapi constructs that must # parse and compile correctly. +{ 'struct': 'TestStruct', + 'data': { 'integer': 'int', 'boolean': 'bool', 'string': 'str' } } + # for testing enums { 'struct': 'NestedEnumsOne', 'data': { 'enum1': 'EnumOne', # Intentional forward reference @@ -11,6 +14,10 @@ # An empty enum, although unusual, is currently acceptable { 'enum': 'MyEnum', 'data': [ ] } +# Likewise for an empty struct, including an empty base +{ 'struct': 'Empty1', 'data': { } } +{ 'struct': 'Empty2', 'base': 'Empty1', 'data': { } } + # for testing override of default naming heuristic { 'enum': 'QEnumTwo', 'prefix': 'QENUM_TWO', @@ -42,7 +49,8 @@ # dummy struct to force generation of array types not otherwise mentioned { 'struct': 'ForceArrays', - 'data': { 'unused1':['UserDefOne'], 'unused2':['UserDefTwo'] } } + 'data': { 'unused1':['UserDefOne'], 'unused2':['UserDefTwo'], + 'unused3':['TestStruct'] } } # for testing unions # Among other things, test that a name collision between branches does diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index a6c80e04d7..e20a8239ad 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -81,6 +81,9 @@ event EVENT_A None event EVENT_B None event EVENT_C :obj-EVENT_C-arg event EVENT_D :obj-EVENT_D-arg +object Empty1 +object Empty2 + base Empty1 enum EnumOne ['value1', 'value2', 'value3'] object EventStructOne member struct1: UserDefOne optional=False @@ -89,6 +92,7 @@ object EventStructOne object ForceArrays member unused1: UserDefOneList optional=False member unused2: UserDefTwoList optional=False + member unused3: TestStructList optional=False enum MyEnum [] object NestedEnumsOne member enum1: EnumOne optional=False @@ -97,6 +101,10 @@ object NestedEnumsOne member enum4: EnumOne optional=True enum QEnumTwo ['value1', 'value2'] prefix QENUM_TWO +object TestStruct + member integer: int optional=False + member boolean: bool optional=False + member string: str optional=False object UserDefA member boolean: bool optional=False member a_b: int optional=True diff --git a/tests/qapi-schema/reserved-command-q.err b/tests/qapi-schema/reserved-command-q.err new file mode 100644 index 0000000000..f939e044eb --- /dev/null +++ b/tests/qapi-schema/reserved-command-q.err @@ -0,0 +1 @@ +tests/qapi-schema/reserved-command-q.json:5: 'command' uses invalid name 'q-unix' diff --git a/tests/qapi-schema/enum-union-clash.exit b/tests/qapi-schema/reserved-command-q.exit index d00491fd7e..d00491fd7e 100644 --- a/tests/qapi-schema/enum-union-clash.exit +++ b/tests/qapi-schema/reserved-command-q.exit diff --git a/tests/qapi-schema/reserved-command-q.json b/tests/qapi-schema/reserved-command-q.json new file mode 100644 index 0000000000..99f8aae314 --- /dev/null +++ b/tests/qapi-schema/reserved-command-q.json @@ -0,0 +1,5 @@ +# C entity name collision +# We reject names like 'q-unix', because they can collide with the mangled +# name for 'unix' in generated C. +{ 'command': 'unix' } +{ 'command': 'q-unix' } diff --git a/tests/qapi-schema/enum-union-clash.out b/tests/qapi-schema/reserved-command-q.out index e69de29bb2..e69de29bb2 100644 --- a/tests/qapi-schema/enum-union-clash.out +++ b/tests/qapi-schema/reserved-command-q.out diff --git a/tests/qapi-schema/reserved-member-has.err b/tests/qapi-schema/reserved-member-has.err new file mode 100644 index 0000000000..e755771446 --- /dev/null +++ b/tests/qapi-schema/reserved-member-has.err @@ -0,0 +1 @@ +tests/qapi-schema/reserved-member-has.json:5: Member of 'data' for command 'oops' uses reserved name 'has-a' diff --git a/tests/qapi-schema/reserved-member-has.exit b/tests/qapi-schema/reserved-member-has.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/reserved-member-has.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/reserved-member-has.json b/tests/qapi-schema/reserved-member-has.json new file mode 100644 index 0000000000..45b9109bdc --- /dev/null +++ b/tests/qapi-schema/reserved-member-has.json @@ -0,0 +1,5 @@ +# C member name collision +# We reject names like 'has-a', because they can collide with the flag +# for an optional 'a' in generated C. +# TODO we could munge the optional flag name to avoid the collision. +{ 'command': 'oops', 'data': { '*a': 'str', 'has-a': 'str' } } diff --git a/tests/qapi-schema/struct-base-clash-base.err b/tests/qapi-schema/reserved-member-has.out index e69de29bb2..e69de29bb2 100644 --- a/tests/qapi-schema/struct-base-clash-base.err +++ b/tests/qapi-schema/reserved-member-has.out diff --git a/tests/qapi-schema/reserved-member-q.err b/tests/qapi-schema/reserved-member-q.err new file mode 100644 index 0000000000..f3d5dd7818 --- /dev/null +++ b/tests/qapi-schema/reserved-member-q.err @@ -0,0 +1 @@ +tests/qapi-schema/reserved-member-q.json:4: Member of 'data' for struct 'Foo' uses invalid name 'q-unix' diff --git a/tests/qapi-schema/reserved-member-q.exit b/tests/qapi-schema/reserved-member-q.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/reserved-member-q.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/reserved-member-q.json b/tests/qapi-schema/reserved-member-q.json new file mode 100644 index 0000000000..62fed8fddf --- /dev/null +++ b/tests/qapi-schema/reserved-member-q.json @@ -0,0 +1,4 @@ +# C member name collision +# We reject names like 'q-unix', because they can collide with the mangled +# name for 'unix' in generated C. +{ 'struct': 'Foo', 'data': { 'unix':'int', 'q-unix':'bool' } } diff --git a/tests/qapi-schema/reserved-member-q.out b/tests/qapi-schema/reserved-member-q.out new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/qapi-schema/reserved-member-q.out diff --git a/tests/qapi-schema/reserved-member-u.err b/tests/qapi-schema/reserved-member-u.err new file mode 100644 index 0000000000..87d42296cc --- /dev/null +++ b/tests/qapi-schema/reserved-member-u.err @@ -0,0 +1 @@ +tests/qapi-schema/reserved-member-u.json:7: Member of 'data' for struct 'Oops' uses reserved name 'u' diff --git a/tests/qapi-schema/reserved-member-u.exit b/tests/qapi-schema/reserved-member-u.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/reserved-member-u.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/reserved-member-u.json b/tests/qapi-schema/reserved-member-u.json new file mode 100644 index 0000000000..1eaf0f301c --- /dev/null +++ b/tests/qapi-schema/reserved-member-u.json @@ -0,0 +1,7 @@ +# Potential C member name collision +# We reject use of 'u' as a member name, to allow it for internal use in +# putting union branch members in a separate namespace from QMP members. +# This is true even for non-unions, because it is possible to convert a +# struct to flat union while remaining backwards compatible in QMP. +# TODO - we could munge the member name to 'q_u' to avoid the collision +{ 'struct': 'Oops', 'data': { 'u': 'str' } } diff --git a/tests/qapi-schema/reserved-member-u.out b/tests/qapi-schema/reserved-member-u.out new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/qapi-schema/reserved-member-u.out diff --git a/tests/qapi-schema/reserved-type-kind.err b/tests/qapi-schema/reserved-type-kind.err new file mode 100644 index 0000000000..0a38efaad8 --- /dev/null +++ b/tests/qapi-schema/reserved-type-kind.err @@ -0,0 +1 @@ +tests/qapi-schema/reserved-type-kind.json:2: enum 'UnionKind' should not end in 'Kind' diff --git a/tests/qapi-schema/reserved-type-kind.exit b/tests/qapi-schema/reserved-type-kind.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/reserved-type-kind.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/enum-union-clash.json b/tests/qapi-schema/reserved-type-kind.json index 593282b6cf..9ecaba12bc 100644 --- a/tests/qapi-schema/enum-union-clash.json +++ b/tests/qapi-schema/reserved-type-kind.json @@ -1,4 +1,2 @@ # we reject types that would conflict with implicit union enum { 'enum': 'UnionKind', 'data': [ 'oops' ] } -{ 'union': 'Union', - 'data': { 'a': 'int' } } diff --git a/tests/qapi-schema/reserved-type-kind.out b/tests/qapi-schema/reserved-type-kind.out new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/qapi-schema/reserved-type-kind.out diff --git a/tests/qapi-schema/reserved-type-list.err b/tests/qapi-schema/reserved-type-list.err new file mode 100644 index 0000000000..4510fa6d90 --- /dev/null +++ b/tests/qapi-schema/reserved-type-list.err @@ -0,0 +1 @@ +tests/qapi-schema/reserved-type-list.json:5: struct 'FooList' should not end in 'List' diff --git a/tests/qapi-schema/reserved-type-list.exit b/tests/qapi-schema/reserved-type-list.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/reserved-type-list.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/reserved-type-list.json b/tests/qapi-schema/reserved-type-list.json new file mode 100644 index 0000000000..98d53bf808 --- /dev/null +++ b/tests/qapi-schema/reserved-type-list.json @@ -0,0 +1,5 @@ +# Potential C name collision +# We reserve names ending in 'List' for use by array types. +# TODO - we could choose array names to avoid collision with user types, +# in order to let this compile +{ 'struct': 'FooList', 'data': { 's': 'str' } } diff --git a/tests/qapi-schema/reserved-type-list.out b/tests/qapi-schema/reserved-type-list.out new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/qapi-schema/reserved-type-list.out diff --git a/tests/qapi-schema/struct-base-clash-base.exit b/tests/qapi-schema/struct-base-clash-base.exit deleted file mode 100644 index 573541ac97..0000000000 --- a/tests/qapi-schema/struct-base-clash-base.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/tests/qapi-schema/struct-base-clash-base.json b/tests/qapi-schema/struct-base-clash-base.json deleted file mode 100644 index 0c840258c9..0000000000 --- a/tests/qapi-schema/struct-base-clash-base.json +++ /dev/null @@ -1,9 +0,0 @@ -# Struct member 'base' -# FIXME: this parses, but then fails to compile due to a duplicate 'base' -# (one explicit in QMP, the other used to box the base class members). -# We should either reject the collision at parse time, or change the -# generated struct to allow this to compile. -{ 'struct': 'Base', 'data': {} } -{ 'struct': 'Sub', - 'base': 'Base', - 'data': { 'base': 'str' } } diff --git a/tests/qapi-schema/struct-base-clash-base.out b/tests/qapi-schema/struct-base-clash-base.out deleted file mode 100644 index e69a416560..0000000000 --- a/tests/qapi-schema/struct-base-clash-base.out +++ /dev/null @@ -1,5 +0,0 @@ -object :empty -object Base -object Sub - base Base - member base: str optional=False diff --git a/tests/qemu-iotests/030 b/tests/qemu-iotests/030 index 952a524ec7..32469efd76 100755 --- a/tests/qemu-iotests/030 +++ b/tests/qemu-iotests/030 @@ -245,6 +245,7 @@ class TestEIO(TestErrors): while not completed: for event in self.vm.get_qmp_events(wait=True): if event['event'] == 'BLOCK_JOB_ERROR': + error = True self.assert_qmp(event, 'data/device', 'drive0') self.assert_qmp(event, 'data/operation', 'read') @@ -257,9 +258,11 @@ class TestEIO(TestErrors): self.assert_qmp(result, 'return', {}) result = self.vm.qmp('query-block-jobs') + if result == {'return': []}: + # Race; likely already finished. Check. + continue self.assert_qmp(result, 'return[0]/paused', False) self.assert_qmp(result, 'return[0]/io-status', 'ok') - error = True elif event['event'] == 'BLOCK_JOB_COMPLETED': self.assertTrue(error, 'job completed unexpectedly') self.assert_qmp(event, 'data/type', 'stream') diff --git a/tests/qemu-iotests/039.out b/tests/qemu-iotests/039.out index 03a31c5943..32c884694c 100644 --- a/tests/qemu-iotests/039.out +++ b/tests/qemu-iotests/039.out @@ -11,7 +11,11 @@ No errors were found on the image. Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 wrote 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) -./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" ) +./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then + exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +else + exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +fi ) incompatible_features 0x1 ERROR cluster 5 refcount=0 reference=1 ERROR OFLAG_COPIED data cluster: l2_entry=8000000000050000 refcount=0 @@ -46,7 +50,11 @@ read 512/512 bytes at offset 0 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 wrote 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) -./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" ) +./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then + exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +else + exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +fi ) incompatible_features 0x1 ERROR cluster 5 refcount=0 reference=1 Rebuilding refcount structure @@ -60,7 +68,11 @@ incompatible_features 0x0 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 wrote 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) -./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" ) +./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then + exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +else + exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +fi ) incompatible_features 0x0 No errors were found on the image. @@ -79,7 +91,11 @@ No errors were found on the image. Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 wrote 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) -./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" ) +./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then + exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +else + exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +fi ) incompatible_features 0x1 ERROR cluster 5 refcount=0 reference=1 ERROR OFLAG_COPIED data cluster: l2_entry=8000000000050000 refcount=0 @@ -89,7 +105,11 @@ Data may be corrupted, or further writes to the image may corrupt it. Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 wrote 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) -./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" ) +./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then + exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +else + exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +fi ) incompatible_features 0x0 No errors were found on the image. *** done diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040 index ea2f98e51d..5bdaf3d48d 100755 --- a/tests/qemu-iotests/040 +++ b/tests/qemu-iotests/040 @@ -41,6 +41,7 @@ class ImageCommitTestCase(iotests.QMPTestCase): while not completed: for event in self.vm.get_qmp_events(wait=True): if event['event'] == 'BLOCK_JOB_COMPLETED': + self.assert_qmp_absent(event, 'data/error') self.assert_qmp(event, 'data/type', 'commit') self.assert_qmp(event, 'data/device', 'drive0') self.assert_qmp(event, 'data/offset', event['data']['len']) @@ -251,5 +252,34 @@ class TestSetSpeed(ImageCommitTestCase): class TestActiveZeroLengthImage(TestSingleDrive): image_len = 0 +class TestReopenOverlay(ImageCommitTestCase): + image_len = 1024 * 1024 + img0 = os.path.join(iotests.test_dir, '0.img') + img1 = os.path.join(iotests.test_dir, '1.img') + img2 = os.path.join(iotests.test_dir, '2.img') + img3 = os.path.join(iotests.test_dir, '3.img') + + def setUp(self): + iotests.create_image(self.img0, self.image_len) + qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img0, self.img1) + qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img1, self.img2) + qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % self.img2, self.img3) + qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xab 0 128K', self.img1) + self.vm = iotests.VM().add_drive(self.img3) + self.vm.launch() + + def tearDown(self): + self.vm.shutdown() + os.remove(self.img0) + os.remove(self.img1) + os.remove(self.img2) + os.remove(self.img3) + + # This tests what happens when the overlay image of the 'top' node + # needs to be reopened in read-write mode in order to update the + # backing image string. + def test_reopen_overlay(self): + self.run_commit_test(self.img1, self.img0) + if __name__ == '__main__': iotests.main(supported_fmts=['qcow2', 'qed']) diff --git a/tests/qemu-iotests/040.out b/tests/qemu-iotests/040.out index 42314e9c00..4fd1c2dcd2 100644 --- a/tests/qemu-iotests/040.out +++ b/tests/qemu-iotests/040.out @@ -1,5 +1,5 @@ -........................ +......................... ---------------------------------------------------------------------- -Ran 24 tests +Ran 25 tests OK diff --git a/tests/qemu-iotests/056 b/tests/qemu-iotests/056 index 54e4bd0692..04f2c3c841 100755 --- a/tests/qemu-iotests/056 +++ b/tests/qemu-iotests/056 @@ -82,6 +82,31 @@ class TestSyncModesNoneAndTop(iotests.QMPTestCase): time.sleep(1) self.assertEqual(-1, qemu_io('-c', 'read -P0x41 0 512', target_img).find("verification failed")) +class TestBeforeWriteNotifier(iotests.QMPTestCase): + def setUp(self): + self.vm = iotests.VM().add_drive_raw("file=blkdebug::null-co://,id=drive0,align=65536,driver=blkdebug") + self.vm.launch() + + def tearDown(self): + self.vm.shutdown() + os.remove(target_img) + + def test_before_write_notifier(self): + self.vm.pause_drive("drive0") + result = self.vm.qmp('drive-backup', device='drive0', + sync='full', target=target_img, + format="file", speed=1) + self.assert_qmp(result, 'return', {}) + result = self.vm.qmp('block-job-pause', device="drive0") + self.assert_qmp(result, 'return', {}) + # Speed is low enough that this must be an uncopied range, which will + # trigger the before write notifier + self.vm.hmp_qemu_io('drive0', 'aio_write -P 1 512512 512') + self.vm.resume_drive("drive0") + result = self.vm.qmp('block-job-resume', device="drive0") + self.assert_qmp(result, 'return', {}) + event = self.cancel_and_wait() + self.assert_qmp(event, 'data/type', 'backup') if __name__ == '__main__': iotests.main(supported_fmts=['qcow2', 'qed']) diff --git a/tests/qemu-iotests/056.out b/tests/qemu-iotests/056.out index fbc63e62f8..8d7e996700 100644 --- a/tests/qemu-iotests/056.out +++ b/tests/qemu-iotests/056.out @@ -1,5 +1,5 @@ -.. +... ---------------------------------------------------------------------- -Ran 2 tests +Ran 3 tests OK diff --git a/tests/qemu-iotests/058 b/tests/qemu-iotests/058 index f2bdd0bffc..63a6598784 100755 --- a/tests/qemu-iotests/058 +++ b/tests/qemu-iotests/058 @@ -32,11 +32,17 @@ status=1 # failure is the default! nbd_unix_socket=$TEST_DIR/test_qemu_nbd_socket nbd_snapshot_img="nbd:unix:$nbd_unix_socket" +rm -f "${TEST_DIR}/qemu-nbd.pid" _cleanup_nbd() { - if [ -n "$NBD_SNAPSHOT_PID" ]; then - kill "$NBD_SNAPSHOT_PID" + local NBD_SNAPSHOT_PID + if [ -f "${TEST_DIR}/qemu-nbd.pid" ]; then + read NBD_SNAPSHOT_PID < "${TEST_DIR}/qemu-nbd.pid" + rm -f "${TEST_DIR}/qemu-nbd.pid" + if [ -n "$NBD_SNAPSHOT_PID" ]; then + kill "$NBD_SNAPSHOT_PID" + fi fi rm -f "$nbd_unix_socket" } @@ -60,7 +66,6 @@ _export_nbd_snapshot() { _cleanup_nbd $QEMU_NBD -v -t -k "$nbd_unix_socket" "$TEST_IMG" -l $1 & - NBD_SNAPSHOT_PID=$! _wait_for_nbd } @@ -68,7 +73,6 @@ _export_nbd_snapshot1() { _cleanup_nbd $QEMU_NBD -v -t -k "$nbd_unix_socket" "$TEST_IMG" -l snapshot.name=$1 & - NBD_SNAPSHOT_PID=$! _wait_for_nbd } diff --git a/tests/qemu-iotests/061.out b/tests/qemu-iotests/061.out index b16bea95d2..f2598a8f9d 100644 --- a/tests/qemu-iotests/061.out +++ b/tests/qemu-iotests/061.out @@ -57,7 +57,11 @@ No errors were found on the image. Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 wrote 131072/131072 bytes at offset 0 128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) -./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" ) +./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then + exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +else + exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +fi ) magic 0x514649fb version 3 backing_file_offset 0x0 @@ -215,7 +219,11 @@ No errors were found on the image. Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 wrote 131072/131072 bytes at offset 0 128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) -./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" ) +./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then + exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +else + exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +fi ) magic 0x514649fb version 3 backing_file_offset 0x0 diff --git a/tests/qemu-iotests/085 b/tests/qemu-iotests/085 index 56cd6f89b7..aa77eca77d 100755 --- a/tests/qemu-iotests/085 +++ b/tests/qemu-iotests/085 @@ -7,6 +7,7 @@ # snapshots are performed. # # Copyright (C) 2014 Red Hat, Inc. +# Copyright (C) 2015 Igalia, S.L. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,17 +35,17 @@ status=1 # failure is the default! snapshot_virt0="snapshot-v0.qcow2" snapshot_virt1="snapshot-v1.qcow2" -MAX_SNAPSHOTS=10 +SNAPSHOTS=10 _cleanup() { _cleanup_qemu - for i in $(seq 1 ${MAX_SNAPSHOTS}) + for i in $(seq 1 ${SNAPSHOTS}) do rm -f "${TEST_DIR}/${i}-${snapshot_virt0}" rm -f "${TEST_DIR}/${i}-${snapshot_virt1}" done - _cleanup_test_img + rm -f "${TEST_IMG}.1" "${TEST_IMG}.2" } trap "_cleanup; exit \$status" 0 1 2 3 15 @@ -64,7 +65,7 @@ function create_single_snapshot() { cmd="{ 'execute': 'blockdev-snapshot-sync', 'arguments': { 'device': 'virtio0', - 'snapshot-file':'"${TEST_DIR}/${1}-${snapshot_virt0}"', + 'snapshot-file':'${TEST_DIR}/${1}-${snapshot_virt0}', 'format': 'qcow2' } }" _send_qemu_cmd $h "${cmd}" "return" } @@ -76,27 +77,60 @@ function create_group_snapshot() {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', - 'snapshot-file': '"${TEST_DIR}/${1}-${snapshot_virt0}"' } }, + 'snapshot-file': '${TEST_DIR}/${1}-${snapshot_virt0}' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', - 'snapshot-file': '"${TEST_DIR}/${1}-${snapshot_virt1}"' } } ] + 'snapshot-file': '${TEST_DIR}/${1}-${snapshot_virt1}' } } ] } }" _send_qemu_cmd $h "${cmd}" "return" } +# ${1}: unique identifier for the snapshot filename +# ${2}: true: open backing images; false: don't open them (default) +function add_snapshot_image() +{ + if [ "${2}" = "true" ]; then + extra_params="" + else + extra_params="'backing': '', " + fi + base_image="${TEST_DIR}/$((${1}-1))-${snapshot_virt0}" + snapshot_file="${TEST_DIR}/${1}-${snapshot_virt0}" + _make_test_img -b "${base_image}" "$size" + mv "${TEST_IMG}" "${snapshot_file}" + cmd="{ 'execute': 'blockdev-add', 'arguments': + { 'options': + { 'driver': 'qcow2', 'node-name': 'snap_${1}', ${extra_params} + 'file': + { 'driver': 'file', 'filename': '${snapshot_file}', + 'node-name': 'file_${1}' } } } }" + _send_qemu_cmd $h "${cmd}" "return" +} + +# ${1}: unique identifier for the snapshot filename +# ${2}: expected response, defaults to 'return' +function blockdev_snapshot() +{ + cmd="{ 'execute': 'blockdev-snapshot', + 'arguments': { 'node': 'virtio0', + 'overlay':'snap_${1}' } }" + _send_qemu_cmd $h "${cmd}" "${2:-return}" +} + size=128M _make_test_img $size -mv "${TEST_IMG}" "${TEST_IMG}.orig" +mv "${TEST_IMG}" "${TEST_IMG}.1" _make_test_img $size +mv "${TEST_IMG}" "${TEST_IMG}.2" echo echo === Running QEMU === echo qemu_comm_method="qmp" -_launch_qemu -drive file="${TEST_IMG}.orig",if=virtio -drive file="${TEST_IMG}",if=virtio +_launch_qemu -drive file="${TEST_IMG}.1",if=virtio -drive file="${TEST_IMG}.2",if=virtio h=$QEMU_HANDLE echo @@ -105,6 +139,8 @@ echo _send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" "return" +# Tests for the blockdev-snapshot-sync command + echo echo === Create a single snapshot on virtio0 === echo @@ -117,7 +153,7 @@ echo === Invalid command - missing device and nodename === echo _send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot-sync', - 'arguments': { 'snapshot-file':'"${TEST_DIR}/1-${snapshot_virt0}"', + 'arguments': { 'snapshot-file':'${TEST_DIR}/1-${snapshot_virt0}', 'format': 'qcow2' } }" "error" echo @@ -132,11 +168,75 @@ echo echo === Create several transactional group snapshots === echo -for i in $(seq 2 ${MAX_SNAPSHOTS}) +for i in $(seq 2 ${SNAPSHOTS}) do create_group_snapshot ${i} done +# Tests for the blockdev-snapshot command + +echo +echo === Create a couple of snapshots using blockdev-snapshot === +echo + +SNAPSHOTS=$((${SNAPSHOTS}+1)) +add_snapshot_image ${SNAPSHOTS} +blockdev_snapshot ${SNAPSHOTS} + +SNAPSHOTS=$((${SNAPSHOTS}+1)) +add_snapshot_image ${SNAPSHOTS} +blockdev_snapshot ${SNAPSHOTS} + +echo +echo === Invalid command - cannot create a snapshot using a file BDS === +echo + +_send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot', + 'arguments': { 'node':'virtio0', + 'overlay':'file_${SNAPSHOTS}' } + }" "error" + +echo +echo === Invalid command - snapshot node used as active layer === +echo + +blockdev_snapshot ${SNAPSHOTS} error + +_send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot', + 'arguments': { 'node':'virtio0', + 'overlay':'virtio0' } + }" "error" + +_send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot', + 'arguments': { 'node':'virtio0', + 'overlay':'virtio1' } + }" "error" + +echo +echo === Invalid command - snapshot node used as backing hd === +echo + +blockdev_snapshot $((${SNAPSHOTS}-1)) error + +echo +echo === Invalid command - snapshot node has a backing image === +echo + +SNAPSHOTS=$((${SNAPSHOTS}+1)) +add_snapshot_image ${SNAPSHOTS} true +blockdev_snapshot ${SNAPSHOTS} error + +echo +echo === Invalid command - The node does not exist === +echo + +blockdev_snapshot $((${SNAPSHOTS}+1)) error + +_send_qemu_cmd $h "{ 'execute': 'blockdev-snapshot', + 'arguments': { 'node':'nodevice', + 'overlay':'snap_${SNAPSHOTS}' } + }" "error" + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/085.out b/tests/qemu-iotests/085.out index a6cf19e57a..01c78d6894 100644 --- a/tests/qemu-iotests/085.out +++ b/tests/qemu-iotests/085.out @@ -11,7 +11,7 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 === Create a single snapshot on virtio0 === -Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2.orig backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16 +Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2.1 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16 {"return": {}} === Invalid command - missing device and nodename === @@ -26,7 +26,7 @@ Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file === Create several transactional group snapshots === Formatting 'TEST_DIR/2-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/1-snapshot-v0.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16 -Formatting 'TEST_DIR/2-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16 +Formatting 'TEST_DIR/2-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2.2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16 {"return": {}} Formatting 'TEST_DIR/3-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/2-snapshot-v0.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16 Formatting 'TEST_DIR/3-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/2-snapshot-v1.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16 @@ -52,4 +52,38 @@ Formatting 'TEST_DIR/9-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file Formatting 'TEST_DIR/10-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/9-snapshot-v0.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16 Formatting 'TEST_DIR/10-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/9-snapshot-v1.qcow2 backing_fmt=qcow2 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16 {"return": {}} + +=== Create a couple of snapshots using blockdev-snapshot === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/10-snapshot-v0.IMGFMT +{"return": {}} +{"return": {}} +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/11-snapshot-v0.IMGFMT +{"return": {}} +{"return": {}} + +=== Invalid command - cannot create a snapshot using a file BDS === + +{"error": {"class": "GenericError", "desc": "The snapshot does not support backing images"}} + +=== Invalid command - snapshot node used as active layer === + +{"error": {"class": "GenericError", "desc": "The snapshot is already in use by virtio0"}} +{"error": {"class": "GenericError", "desc": "The snapshot is already in use by virtio0"}} +{"error": {"class": "GenericError", "desc": "The snapshot is already in use by virtio1"}} + +=== Invalid command - snapshot node used as backing hd === + +{"error": {"class": "GenericError", "desc": "Node 'snap_11' is busy: node is used as backing hd of 'virtio0'"}} + +=== Invalid command - snapshot node has a backing image === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/12-snapshot-v0.IMGFMT +{"return": {}} +{"error": {"class": "GenericError", "desc": "The snapshot already has a backing image"}} + +=== Invalid command - The node does not exist === + +{"error": {"class": "GenericError", "desc": "Cannot find device=snap_14 nor node_name=snap_14"}} +{"error": {"class": "GenericError", "desc": "Cannot find device=nodevice nor node_name=nodevice"}} *** done diff --git a/tests/qemu-iotests/118 b/tests/qemu-iotests/118 new file mode 100755 index 0000000000..a2bcd548c1 --- /dev/null +++ b/tests/qemu-iotests/118 @@ -0,0 +1,720 @@ +#!/usr/bin/env python +# +# Test case for the QMP 'change' command and all other associated +# commands +# +# Copyright (C) 2015 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import os +import stat +import time +import iotests +from iotests import qemu_img + +old_img = os.path.join(iotests.test_dir, 'test0.img') +new_img = os.path.join(iotests.test_dir, 'test1.img') + +class ChangeBaseClass(iotests.QMPTestCase): + has_opened = False + has_closed = False + + def process_events(self): + for event in self.vm.get_qmp_events(wait=False): + if (event['event'] == 'DEVICE_TRAY_MOVED' and + event['data']['device'] == 'drive0'): + if event['data']['tray-open'] == False: + self.has_closed = True + else: + self.has_opened = True + + def wait_for_open(self): + timeout = time.clock() + 3 + while not self.has_opened and time.clock() < timeout: + self.process_events() + if not self.has_opened: + self.fail('Timeout while waiting for the tray to open') + + def wait_for_close(self): + timeout = time.clock() + 3 + while not self.has_closed and time.clock() < timeout: + self.process_events() + if not self.has_opened: + self.fail('Timeout while waiting for the tray to close') + +class GeneralChangeTestsBaseClass(ChangeBaseClass): + def test_change(self): + result = self.vm.qmp('change', device='drive0', target=new_img, + arg=iotests.imgfmt) + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_blockdev_change_medium(self): + result = self.vm.qmp('blockdev-change-medium', device='drive0', + filename=new_img, + format=iotests.imgfmt) + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_eject(self): + result = self.vm.qmp('eject', device='drive0', force=True) + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + self.assert_qmp_absent(result, 'return[0]/inserted') + + def test_tray_eject_change(self): + result = self.vm.qmp('eject', device='drive0', force=True) + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + self.assert_qmp_absent(result, 'return[0]/inserted') + + result = self.vm.qmp('blockdev-change-medium', device='drive0', + filename=new_img, + format=iotests.imgfmt) + self.assert_qmp(result, 'return', {}) + + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_tray_open_close(self): + result = self.vm.qmp('blockdev-open-tray', device='drive0', force=True) + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + if self.was_empty == True: + self.assert_qmp_absent(result, 'return[0]/inserted') + else: + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-close-tray', device='drive0') + self.assert_qmp(result, 'return', {}) + + if self.has_real_tray or not self.was_empty: + self.wait_for_close() + + result = self.vm.qmp('query-block') + if self.has_real_tray or not self.was_empty: + self.assert_qmp(result, 'return[0]/tray_open', False) + else: + self.assert_qmp(result, 'return[0]/tray_open', True) + if self.was_empty == True: + self.assert_qmp_absent(result, 'return[0]/inserted') + else: + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + def test_tray_eject_close(self): + result = self.vm.qmp('eject', device='drive0', force=True) + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + self.assert_qmp_absent(result, 'return[0]/inserted') + + result = self.vm.qmp('blockdev-close-tray', device='drive0') + self.assert_qmp(result, 'return', {}) + + if self.has_real_tray: + self.wait_for_close() + + result = self.vm.qmp('query-block') + if self.has_real_tray: + self.assert_qmp(result, 'return[0]/tray_open', False) + else: + self.assert_qmp(result, 'return[0]/tray_open', True) + self.assert_qmp_absent(result, 'return[0]/inserted') + + def test_tray_open_change(self): + result = self.vm.qmp('blockdev-open-tray', device='drive0', force=True) + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + if self.was_empty == True: + self.assert_qmp_absent(result, 'return[0]/inserted') + else: + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', device='drive0', + filename=new_img, + format=iotests.imgfmt) + self.assert_qmp(result, 'return', {}) + + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_cycle(self): + result = self.vm.qmp('blockdev-add', + options={'node-name': 'new', + 'driver': iotests.imgfmt, + 'file': {'filename': new_img, + 'driver': 'file'}}) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('blockdev-open-tray', device='drive0', force=True) + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + if self.was_empty == True: + self.assert_qmp_absent(result, 'return[0]/inserted') + else: + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-remove-medium', device='drive0') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + self.assert_qmp_absent(result, 'return[0]/inserted') + + result = self.vm.qmp('blockdev-insert-medium', device='drive0', + node_name='new') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + result = self.vm.qmp('blockdev-close-tray', device='drive0') + self.assert_qmp(result, 'return', {}) + + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_close_on_closed(self): + result = self.vm.qmp('blockdev-close-tray', device='drive0') + # Should be a no-op + self.assert_qmp(result, 'return', {}) + self.assertEquals(self.vm.get_qmp_events(wait=False), []) + + def test_remove_on_closed(self): + if self.has_opened: + # Empty floppy drive + return + + result = self.vm.qmp('blockdev-remove-medium', device='drive0') + self.assert_qmp(result, 'error/class', 'GenericError') + + def test_insert_on_closed(self): + if self.has_opened: + # Empty floppy drive + return + + result = self.vm.qmp('blockdev-add', + options={'node-name': 'new', + 'driver': iotests.imgfmt, + 'file': {'filename': new_img, + 'driver': 'file'}}) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('blockdev-insert-medium', device='drive0', + node_name='new') + self.assert_qmp(result, 'error/class', 'GenericError') + +class TestInitiallyFilled(GeneralChangeTestsBaseClass): + was_empty = False + + def setUp(self, media, interface): + qemu_img('create', '-f', iotests.imgfmt, old_img, '1440k') + qemu_img('create', '-f', iotests.imgfmt, new_img, '1440k') + self.vm = iotests.VM().add_drive(old_img, 'media=%s' % media, interface) + self.vm.launch() + + def tearDown(self): + self.vm.shutdown() + os.remove(old_img) + os.remove(new_img) + + def test_insert_on_filled(self): + result = self.vm.qmp('blockdev-add', + options={'node-name': 'new', + 'driver': iotests.imgfmt, + 'file': {'filename': new_img, + 'driver': 'file'}}) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('blockdev-open-tray', device='drive0') + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + + result = self.vm.qmp('blockdev-insert-medium', device='drive0', + node_name='new') + self.assert_qmp(result, 'error/class', 'GenericError') + +class TestInitiallyEmpty(GeneralChangeTestsBaseClass): + was_empty = True + + def setUp(self, media, interface): + qemu_img('create', '-f', iotests.imgfmt, new_img, '1440k') + self.vm = iotests.VM().add_drive(None, 'media=%s' % media, interface) + self.vm.launch() + + def tearDown(self): + self.vm.shutdown() + os.remove(new_img) + + def test_remove_on_empty(self): + result = self.vm.qmp('blockdev-open-tray', device='drive0') + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + + result = self.vm.qmp('blockdev-remove-medium', device='drive0') + # Should be a no-op + self.assert_qmp(result, 'return', {}) + +class TestCDInitiallyFilled(TestInitiallyFilled): + TestInitiallyFilled = TestInitiallyFilled + has_real_tray = True + + def setUp(self): + self.TestInitiallyFilled.setUp(self, 'cdrom', 'ide') + +class TestCDInitiallyEmpty(TestInitiallyEmpty): + TestInitiallyEmpty = TestInitiallyEmpty + has_real_tray = True + + def setUp(self): + self.TestInitiallyEmpty.setUp(self, 'cdrom', 'ide') + +class TestFloppyInitiallyFilled(TestInitiallyFilled): + TestInitiallyFilled = TestInitiallyFilled + has_real_tray = False + + def setUp(self): + self.TestInitiallyFilled.setUp(self, 'disk', 'floppy') + +class TestFloppyInitiallyEmpty(TestInitiallyEmpty): + TestInitiallyEmpty = TestInitiallyEmpty + has_real_tray = False + + def setUp(self): + self.TestInitiallyEmpty.setUp(self, 'disk', 'floppy') + # FDDs not having a real tray and there not being a medium inside the + # tray at startup means the tray will be considered open + self.has_opened = True + +class TestChangeReadOnly(ChangeBaseClass): + def setUp(self): + qemu_img('create', '-f', iotests.imgfmt, old_img, '1440k') + qemu_img('create', '-f', iotests.imgfmt, new_img, '1440k') + self.vm = iotests.VM() + + def tearDown(self): + self.vm.shutdown() + os.chmod(old_img, 0666) + os.chmod(new_img, 0666) + os.remove(old_img) + os.remove(new_img) + + def test_ro_ro_retain(self): + os.chmod(old_img, 0444) + os.chmod(new_img, 0444) + self.vm.add_drive(old_img, 'media=disk,read-only=on', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', device='drive0', + filename=new_img, + format=iotests.imgfmt, + read_only_mode='retain') + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_ro_rw_retain(self): + os.chmod(old_img, 0444) + self.vm.add_drive(old_img, 'media=disk,read-only=on', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', device='drive0', + filename=new_img, + format=iotests.imgfmt, + read_only_mode='retain') + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_rw_ro_retain(self): + os.chmod(new_img, 0444) + self.vm.add_drive(old_img, 'media=disk', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', device='drive0', + filename=new_img, + format=iotests.imgfmt, + read_only_mode='retain') + self.assert_qmp(result, 'error/class', 'GenericError') + + self.assertEquals(self.vm.get_qmp_events(wait=False), []) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + def test_ro_rw(self): + os.chmod(old_img, 0444) + self.vm.add_drive(old_img, 'media=disk,read-only=on', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', + device='drive0', + filename=new_img, + format=iotests.imgfmt, + read_only_mode='read-write') + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_rw_ro(self): + os.chmod(new_img, 0444) + self.vm.add_drive(old_img, 'media=disk', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', + device='drive0', + filename=new_img, + format=iotests.imgfmt, + read_only_mode='read-only') + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_make_rw_ro(self): + self.vm.add_drive(old_img, 'media=disk', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', + device='drive0', + filename=new_img, + format=iotests.imgfmt, + read_only_mode='read-only') + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_make_ro_rw(self): + os.chmod(new_img, 0444) + self.vm.add_drive(old_img, 'media=disk', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', + device='drive0', + filename=new_img, + format=iotests.imgfmt, + read_only_mode='read-write') + self.assert_qmp(result, 'error/class', 'GenericError') + + self.assertEquals(self.vm.get_qmp_events(wait=False), []) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + def test_make_rw_ro_by_retain(self): + os.chmod(old_img, 0444) + self.vm.add_drive(old_img, 'media=disk,read-only=on', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', device='drive0', + filename=new_img, + format=iotests.imgfmt, + read_only_mode='retain') + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + def test_make_ro_rw_by_retain(self): + os.chmod(new_img, 0444) + self.vm.add_drive(old_img, 'media=disk', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-change-medium', device='drive0', + filename=new_img, + format=iotests.imgfmt, + read_only_mode='retain') + self.assert_qmp(result, 'error/class', 'GenericError') + + self.assertEquals(self.vm.get_qmp_events(wait=False), []) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + def test_rw_ro_cycle(self): + os.chmod(new_img, 0444) + self.vm.add_drive(old_img, 'media=disk', 'floppy') + self.vm.launch() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-add', + options={'node-name': 'new', + 'driver': iotests.imgfmt, + 'read-only': True, + 'file': {'filename': new_img, + 'driver': 'file'}}) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('blockdev-open-tray', device='drive0', force=True) + self.assert_qmp(result, 'return', {}) + + self.wait_for_open() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + self.assert_qmp(result, 'return[0]/inserted/ro', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + result = self.vm.qmp('blockdev-remove-medium', device='drive0') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + self.assert_qmp_absent(result, 'return[0]/inserted') + + result = self.vm.qmp('blockdev-insert-medium', device='drive0', + node_name='new') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', True) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + + result = self.vm.qmp('blockdev-close-tray', device='drive0') + self.assert_qmp(result, 'return', {}) + + self.wait_for_close() + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/ro', True) + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + +GeneralChangeTestsBaseClass = None +TestInitiallyFilled = None +TestInitiallyEmpty = None + + +class TestBlockJobsAfterCycle(ChangeBaseClass): + def setUp(self): + qemu_img('create', '-f', iotests.imgfmt, old_img, '1M') + + self.vm = iotests.VM() + self.vm.launch() + + result = self.vm.qmp('blockdev-add', + options={'id': 'drive0', + 'driver': 'null-co'}) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/image/format', 'null-co') + + # For device-less BBs, calling blockdev-open-tray or blockdev-close-tray + # is not necessary + result = self.vm.qmp('blockdev-remove-medium', device='drive0') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('query-block') + self.assert_qmp_absent(result, 'return[0]/inserted') + + result = self.vm.qmp('blockdev-add', + options={'node-name': 'node0', + 'driver': iotests.imgfmt, + 'file': {'filename': old_img, + 'driver': 'file'}}) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('blockdev-insert-medium', device='drive0', + node_name='node0') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/tray_open', False) + self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img) + + def tearDown(self): + self.vm.shutdown() + os.remove(old_img) + try: + os.remove(new_img) + except OSError: + pass + + def test_snapshot_and_commit(self): + # We need backing file support + if iotests.imgfmt != 'qcow2' and iotests.imgfmt != 'qed': + return + + result = self.vm.qmp('blockdev-snapshot-sync', device='drive0', + snapshot_file=new_img, + format=iotests.imgfmt) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('query-block') + self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img) + self.assert_qmp(result, + 'return[0]/inserted/image/backing-image/filename', + old_img) + + result = self.vm.qmp('block-commit', device='drive0') + self.assert_qmp(result, 'return', {}) + + self.vm.event_wait(name='BLOCK_JOB_READY') + + result = self.vm.qmp('query-block-jobs') + self.assert_qmp(result, 'return[0]/device', 'drive0') + + result = self.vm.qmp('block-job-complete', device='drive0') + self.assert_qmp(result, 'return', {}) + + self.vm.event_wait(name='BLOCK_JOB_COMPLETED') + + +if __name__ == '__main__': + if iotests.qemu_default_machine != 'pc': + # We need floppy and IDE CD-ROM + iotests.notrun('not suitable for this machine type: %s' % + iotests.qemu_default_machine) + iotests.main() diff --git a/tests/qemu-iotests/118.out b/tests/qemu-iotests/118.out new file mode 100644 index 0000000000..6a917130b6 --- /dev/null +++ b/tests/qemu-iotests/118.out @@ -0,0 +1,5 @@ +........................................................... +---------------------------------------------------------------------- +Ran 59 tests + +OK diff --git a/tests/qemu-iotests/119 b/tests/qemu-iotests/119 index 9a11f1b921..cc6ec07705 100755 --- a/tests/qemu-iotests/119 +++ b/tests/qemu-iotests/119 @@ -49,7 +49,7 @@ echo "{'execute': 'qmp_capabilities'} {'execute': 'human-monitor-command', 'arguments': {'command-line': 'qemu-io drv \"read -P 0 0 64k\"'}} {'execute': 'quit'}" \ - | $QEMU -drive id=drv,if=none,file="$TEST_IMG",driver=nbd \ + | $QEMU -nographic -drive id=drv,if=none,file="$TEST_IMG",driver=nbd \ -qmp stdio -nodefaults \ | _filter_qmp | _filter_qemu_io diff --git a/tests/qemu-iotests/120 b/tests/qemu-iotests/120 index 9f13078764..d899a3f527 100755 --- a/tests/qemu-iotests/120 +++ b/tests/qemu-iotests/120 @@ -49,7 +49,7 @@ echo "{'execute': 'qmp_capabilities'} {'execute': 'human-monitor-command', 'arguments': {'command-line': 'qemu-io drv \"write -P 42 0 64k\"'}} {'execute': 'quit'}" \ - | $QEMU -qmp stdio -nodefaults \ + | $QEMU -qmp stdio -nographic -nodefaults \ -drive id=drv,if=none,file="$TEST_IMG",driver=raw,file.driver=$IMGFMT \ | _filter_qmp | _filter_qemu_io $QEMU_IO -c 'read -P 42 0 64k' "$TEST_IMG" | _filter_qemu_io diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124 index 9ccd11809f..c928f0101b 100644 --- a/tests/qemu-iotests/124 +++ b/tests/qemu-iotests/124 @@ -36,6 +36,23 @@ def try_remove(img): pass +def transaction_action(action, **kwargs): + return { + 'type': action, + 'data': dict((k.replace('_', '-'), v) for k, v in kwargs.iteritems()) + } + + +def transaction_bitmap_clear(node, name, **kwargs): + return transaction_action('block-dirty-bitmap-clear', + node=node, name=name, **kwargs) + + +def transaction_drive_backup(device, target, **kwargs): + return transaction_action('drive-backup', device=device, target=target, + **kwargs) + + class Bitmap: def __init__(self, name, drive): self.name = name @@ -122,9 +139,12 @@ class TestIncrementalBackup(iotests.QMPTestCase): def do_qmp_backup(self, error='Input/output error', **kwargs): res = self.vm.qmp('drive-backup', **kwargs) self.assert_qmp(res, 'return', {}) + return self.wait_qmp_backup(kwargs['device'], error) + + def wait_qmp_backup(self, device, error='Input/output error'): event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", - match={'data': {'device': kwargs['device']}}) + match={'data': {'device': device}}) self.assertNotEqual(event, None) try: @@ -139,6 +159,12 @@ class TestIncrementalBackup(iotests.QMPTestCase): return False + def wait_qmp_backup_cancelled(self, device): + event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED', + match={'data': {'device': device}}) + self.assertNotEqual(event, None) + + def create_anchor_backup(self, drive=None): if drive is None: drive = self.drives[-1] @@ -264,6 +290,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): return self.do_incremental_simple(granularity=131072) + def test_incremental_transaction(self): + '''Test: Verify backups made from transactionally created bitmaps. + + Create a bitmap "before" VM execution begins, then create a second + bitmap AFTER writes have already occurred. Use transactions to create + a full backup and synchronize both bitmaps to this backup. + Create an incremental backup through both bitmaps and verify that + both backups match the current drive0 image. + ''' + + drive0 = self.drives[0] + bitmap0 = self.add_bitmap('bitmap0', drive0) + self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + bitmap1 = self.add_bitmap('bitmap1', drive0) + + result = self.vm.qmp('transaction', actions=[ + transaction_bitmap_clear(bitmap0.drive['id'], bitmap0.name), + transaction_bitmap_clear(bitmap1.drive['id'], bitmap1.name), + transaction_drive_backup(drive0['id'], drive0['backup'], + sync='full', format=drive0['fmt']) + ]) + self.assert_qmp(result, 'return', {}) + self.wait_until_completed(drive0['id']) + self.files.append(drive0['backup']) + + self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) + # Both bitmaps should be correctly in sync. + self.create_incremental(bitmap0) + self.create_incremental(bitmap1) + self.vm.shutdown() + self.check_backups() + + def test_incremental_failure(self): '''Test: Verify backups made after a failure are correct. @@ -321,6 +384,123 @@ class TestIncrementalBackup(iotests.QMPTestCase): self.check_backups() + def test_transaction_failure(self): + '''Test: Verify backups made from a transaction that partially fails. + + Add a second drive with its own unique pattern, and add a bitmap to each + drive. Use blkdebug to interfere with the backup on just one drive and + attempt to create a coherent incremental backup across both drives. + + verify a failure in one but not both, then delete the failed stubs and + re-run the same transaction. + + verify that both incrementals are created successfully. + ''' + + # Create a second drive, with pattern: + drive1 = self.add_node('drive1') + self.img_create(drive1['file'], drive1['fmt']) + io_write_patterns(drive1['file'], (('0x14', 0, 512), + ('0x5d', '1M', '32k'), + ('0xcd', '32M', '124k'))) + + # Create a blkdebug interface to this img as 'drive1' + result = self.vm.qmp('blockdev-add', options={ + 'id': drive1['id'], + 'driver': drive1['fmt'], + 'file': { + 'driver': 'blkdebug', + 'image': { + 'driver': 'file', + 'filename': drive1['file'] + }, + 'set-state': [{ + 'event': 'flush_to_disk', + 'state': 1, + 'new_state': 2 + }], + 'inject-error': [{ + 'event': 'read_aio', + 'errno': 5, + 'state': 2, + 'immediately': False, + 'once': True + }], + } + }) + self.assert_qmp(result, 'return', {}) + + # Create bitmaps and full backups for both drives + drive0 = self.drives[0] + dr0bm0 = self.add_bitmap('bitmap0', drive0) + dr1bm0 = self.add_bitmap('bitmap0', drive1) + self.create_anchor_backup(drive0) + self.create_anchor_backup(drive1) + self.assert_no_active_block_jobs() + self.assertFalse(self.vm.get_qmp_events(wait=False)) + + # Emulate some writes + self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + self.hmp_io_writes(drive1['id'], (('0xba', 0, 512), + ('0xef', '16M', '256k'), + ('0x46', '32736k', '64k'))) + + # Create incremental backup targets + target0 = self.prepare_backup(dr0bm0) + target1 = self.prepare_backup(dr1bm0) + + # Ask for a new incremental backup per-each drive, + # expecting drive1's backup to fail: + transaction = [ + transaction_drive_backup(drive0['id'], target0, sync='incremental', + format=drive0['fmt'], mode='existing', + bitmap=dr0bm0.name), + transaction_drive_backup(drive1['id'], target1, sync='incremental', + format=drive1['fmt'], mode='existing', + bitmap=dr1bm0.name) + ] + result = self.vm.qmp('transaction', actions=transaction, + properties={'completion-mode': 'grouped'} ) + self.assert_qmp(result, 'return', {}) + + # Observe that drive0's backup is cancelled and drive1 completes with + # an error. + self.wait_qmp_backup_cancelled(drive0['id']) + self.assertFalse(self.wait_qmp_backup(drive1['id'])) + error = self.vm.event_wait('BLOCK_JOB_ERROR') + self.assert_qmp(error, 'data', {'device': drive1['id'], + 'action': 'report', + 'operation': 'read'}) + self.assertFalse(self.vm.get_qmp_events(wait=False)) + self.assert_no_active_block_jobs() + + # Delete drive0's successful target and eliminate our record of the + # unsuccessful drive1 target. Then re-run the same transaction. + dr0bm0.del_target() + dr1bm0.del_target() + target0 = self.prepare_backup(dr0bm0) + target1 = self.prepare_backup(dr1bm0) + + # Re-run the exact same transaction. + result = self.vm.qmp('transaction', actions=transaction, + properties={'completion-mode':'grouped'}) + self.assert_qmp(result, 'return', {}) + + # Both should complete successfully this time. + self.assertTrue(self.wait_qmp_backup(drive0['id'])) + self.assertTrue(self.wait_qmp_backup(drive1['id'])) + self.make_reference_backup(dr0bm0) + self.make_reference_backup(dr1bm0) + self.assertFalse(self.vm.get_qmp_events(wait=False)) + self.assert_no_active_block_jobs() + + # And the images should of course validate. + self.vm.shutdown() + self.check_backups() + + def test_sync_dirty_bitmap_missing(self): self.assert_no_active_block_jobs() self.files.append(self.err_img) diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out index 2f7d3902f2..dae404e278 100644 --- a/tests/qemu-iotests/124.out +++ b/tests/qemu-iotests/124.out @@ -1,5 +1,5 @@ -....... +......... ---------------------------------------------------------------------- -Ran 7 tests +Ran 9 tests OK diff --git a/tests/qemu-iotests/136 b/tests/qemu-iotests/136 new file mode 100644 index 0000000000..e8c6937fc9 --- /dev/null +++ b/tests/qemu-iotests/136 @@ -0,0 +1,349 @@ +#!/usr/bin/env python +# +# Tests for block device statistics +# +# Copyright (C) 2015 Igalia, S.L. +# Author: Alberto Garcia <berto@igalia.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import iotests +import os + +interval_length = 10 +nsec_per_sec = 1000000000 +op_latency = nsec_per_sec / 1000 # See qtest_latency_ns in accounting.c +bad_sector = 8192 +bad_offset = bad_sector * 512 +blkdebug_file = os.path.join(iotests.test_dir, 'blkdebug.conf') + +class BlockDeviceStatsTestCase(iotests.QMPTestCase): + test_img = "null-aio://" + total_rd_bytes = 0 + total_rd_ops = 0 + total_wr_bytes = 0 + total_wr_ops = 0 + total_wr_merged = 0 + total_flush_ops = 0 + failed_rd_ops = 0 + failed_wr_ops = 0 + invalid_rd_ops = 0 + invalid_wr_ops = 0 + wr_highest_offset = 0 + account_invalid = False + account_failed = False + + def blockstats(self, device): + result = self.vm.qmp("query-blockstats") + for r in result['return']: + if r['device'] == device: + return r['stats'] + raise Exception("Device not found for blockstats: %s" % device) + + def create_blkdebug_file(self): + file = open(blkdebug_file, 'w') + file.write(''' +[inject-error] +event = "read_aio" +errno = "5" +sector = "%d" + +[inject-error] +event = "write_aio" +errno = "5" +sector = "%d" +''' % (bad_sector, bad_sector)) + file.close() + + def setUp(self): + drive_args = [] + drive_args.append("stats-intervals.0=%d" % interval_length) + drive_args.append("stats-account-invalid=%s" % + (self.account_invalid and "on" or "off")) + drive_args.append("stats-account-failed=%s" % + (self.account_failed and "on" or "off")) + self.create_blkdebug_file() + self.vm = iotests.VM().add_drive('blkdebug:%s:%s ' % + (blkdebug_file, self.test_img), + ','.join(drive_args)) + self.vm.launch() + # Set an initial value for the clock + self.vm.qtest("clock_step %d" % nsec_per_sec) + + def tearDown(self): + self.vm.shutdown() + os.remove(blkdebug_file) + + def accounted_ops(self, read = False, write = False, flush = False): + ops = 0 + if write: + ops += self.total_wr_ops + if self.account_failed: + ops += self.failed_wr_ops + if self.account_invalid: + ops += self.invalid_wr_ops + if read: + ops += self.total_rd_ops + if self.account_failed: + ops += self.failed_rd_ops + if self.account_invalid: + ops += self.invalid_rd_ops + if flush: + ops += self.total_flush_ops + return ops + + def accounted_latency(self, read = False, write = False, flush = False): + latency = 0 + if write: + latency += self.total_wr_ops * op_latency + if self.account_failed: + latency += self.failed_wr_ops * op_latency + if read: + latency += self.total_rd_ops * op_latency + if self.account_failed: + latency += self.failed_rd_ops * op_latency + if flush: + latency += self.total_flush_ops * op_latency + return latency + + def check_values(self): + stats = self.blockstats('drive0') + + # Check that the totals match with what we have calculated + self.assertEqual(self.total_rd_bytes, stats['rd_bytes']) + self.assertEqual(self.total_wr_bytes, stats['wr_bytes']) + self.assertEqual(self.total_rd_ops, stats['rd_operations']) + self.assertEqual(self.total_wr_ops, stats['wr_operations']) + self.assertEqual(self.total_flush_ops, stats['flush_operations']) + self.assertEqual(self.wr_highest_offset, stats['wr_highest_offset']) + self.assertEqual(self.failed_rd_ops, stats['failed_rd_operations']) + self.assertEqual(self.failed_wr_ops, stats['failed_wr_operations']) + self.assertEqual(self.invalid_rd_ops, stats['invalid_rd_operations']) + self.assertEqual(self.invalid_wr_ops, stats['invalid_wr_operations']) + self.assertEqual(self.account_invalid, stats['account_invalid']) + self.assertEqual(self.account_failed, stats['account_failed']) + self.assertEqual(self.total_wr_merged, stats['wr_merged']) + + # Check that there's exactly one interval with the length we defined + self.assertEqual(1, len(stats['timed_stats'])) + timed_stats = stats['timed_stats'][0] + self.assertEqual(interval_length, timed_stats['interval_length']) + + total_rd_latency = self.accounted_latency(read = True) + if (total_rd_latency != 0): + self.assertEqual(total_rd_latency, stats['rd_total_time_ns']) + self.assertEqual(op_latency, timed_stats['min_rd_latency_ns']) + self.assertEqual(op_latency, timed_stats['max_rd_latency_ns']) + self.assertEqual(op_latency, timed_stats['avg_rd_latency_ns']) + self.assertLess(0, timed_stats['avg_rd_queue_depth']) + else: + self.assertEqual(0, stats['rd_total_time_ns']) + self.assertEqual(0, timed_stats['min_rd_latency_ns']) + self.assertEqual(0, timed_stats['max_rd_latency_ns']) + self.assertEqual(0, timed_stats['avg_rd_latency_ns']) + self.assertEqual(0, timed_stats['avg_rd_queue_depth']) + + # min read latency <= avg read latency <= max read latency + self.assertLessEqual(timed_stats['min_rd_latency_ns'], + timed_stats['avg_rd_latency_ns']) + self.assertLessEqual(timed_stats['avg_rd_latency_ns'], + timed_stats['max_rd_latency_ns']) + + total_wr_latency = self.accounted_latency(write = True) + if (total_wr_latency != 0): + self.assertEqual(total_wr_latency, stats['wr_total_time_ns']) + self.assertEqual(op_latency, timed_stats['min_wr_latency_ns']) + self.assertEqual(op_latency, timed_stats['max_wr_latency_ns']) + self.assertEqual(op_latency, timed_stats['avg_wr_latency_ns']) + self.assertLess(0, timed_stats['avg_wr_queue_depth']) + else: + self.assertEqual(0, stats['wr_total_time_ns']) + self.assertEqual(0, timed_stats['min_wr_latency_ns']) + self.assertEqual(0, timed_stats['max_wr_latency_ns']) + self.assertEqual(0, timed_stats['avg_wr_latency_ns']) + self.assertEqual(0, timed_stats['avg_wr_queue_depth']) + + # min write latency <= avg write latency <= max write latency + self.assertLessEqual(timed_stats['min_wr_latency_ns'], + timed_stats['avg_wr_latency_ns']) + self.assertLessEqual(timed_stats['avg_wr_latency_ns'], + timed_stats['max_wr_latency_ns']) + + total_flush_latency = self.accounted_latency(flush = True) + if (total_flush_latency != 0): + self.assertEqual(total_flush_latency, stats['flush_total_time_ns']) + self.assertEqual(op_latency, timed_stats['min_flush_latency_ns']) + self.assertEqual(op_latency, timed_stats['max_flush_latency_ns']) + self.assertEqual(op_latency, timed_stats['avg_flush_latency_ns']) + else: + self.assertEqual(0, stats['flush_total_time_ns']) + self.assertEqual(0, timed_stats['min_flush_latency_ns']) + self.assertEqual(0, timed_stats['max_flush_latency_ns']) + self.assertEqual(0, timed_stats['avg_flush_latency_ns']) + + # min flush latency <= avg flush latency <= max flush latency + self.assertLessEqual(timed_stats['min_flush_latency_ns'], + timed_stats['avg_flush_latency_ns']) + self.assertLessEqual(timed_stats['avg_flush_latency_ns'], + timed_stats['max_flush_latency_ns']) + + # idle_time_ns must be > 0 if we have performed any operation + if (self.accounted_ops(read = True, write = True, flush = True) != 0): + self.assertLess(0, stats['idle_time_ns']) + else: + self.assertFalse(stats.has_key('idle_time_ns')) + + # This test does not alter these, so they must be all 0 + self.assertEqual(0, stats['rd_merged']) + self.assertEqual(0, stats['failed_flush_operations']) + self.assertEqual(0, stats['invalid_flush_operations']) + + def do_test_stats(self, rd_size = 0, rd_ops = 0, wr_size = 0, wr_ops = 0, + flush_ops = 0, invalid_rd_ops = 0, invalid_wr_ops = 0, + failed_rd_ops = 0, failed_wr_ops = 0, wr_merged = 0): + # The 'ops' list will contain all the requested I/O operations + ops = [] + for i in range(rd_ops): + ops.append("aio_read %d %d" % (i * rd_size, rd_size)) + + for i in range(wr_ops): + ops.append("aio_write %d %d" % (i * wr_size, wr_size)) + + for i in range(flush_ops): + ops.append("aio_flush") + + highest_offset = wr_ops * wr_size + + # Two types of invalid operations: unaligned length and unaligned offset + for i in range(invalid_rd_ops / 2): + ops.append("aio_read 0 511") + + for i in range(invalid_rd_ops / 2, invalid_rd_ops): + ops.append("aio_read 13 512") + + for i in range(invalid_wr_ops / 2): + ops.append("aio_write 0 511") + + for i in range(invalid_wr_ops / 2, invalid_wr_ops): + ops.append("aio_write 13 512") + + for i in range(failed_rd_ops): + ops.append("aio_read %d 512" % bad_offset) + + for i in range(failed_wr_ops): + ops.append("aio_write %d 512" % bad_offset) + + if failed_wr_ops > 0: + highest_offset = max(highest_offset, bad_offset + 512) + + for i in range(wr_merged): + first = i * wr_size * 2 + second = first + wr_size + ops.append("multiwrite %d %d ; %d %d" % + (first, wr_size, second, wr_size)) + + highest_offset = max(highest_offset, wr_merged * wr_size * 2) + + # Now perform all operations + for op in ops: + self.vm.hmp_qemu_io("drive0", op) + + # Update the expected totals + self.total_rd_bytes += rd_ops * rd_size + self.total_rd_ops += rd_ops + self.total_wr_bytes += wr_ops * wr_size + self.total_wr_ops += wr_ops + self.total_wr_merged += wr_merged + self.total_flush_ops += flush_ops + self.invalid_rd_ops += invalid_rd_ops + self.invalid_wr_ops += invalid_wr_ops + self.failed_rd_ops += failed_rd_ops + self.failed_wr_ops += failed_wr_ops + + self.wr_highest_offset = max(self.wr_highest_offset, highest_offset) + + # Advance the clock so idle_time_ns has a meaningful value + self.vm.qtest("clock_step %d" % nsec_per_sec) + + # And check that the actual statistics match the expected ones + self.check_values() + + def test_read_only(self): + test_values = [[512, 1], + [65536, 1], + [512, 12], + [65536, 12]] + for i in test_values: + self.do_test_stats(rd_size = i[0], rd_ops = i[1]) + + def test_write_only(self): + test_values = [[512, 1], + [65536, 1], + [512, 12], + [65536, 12]] + for i in test_values: + self.do_test_stats(wr_size = i[0], wr_ops = i[1]) + + def test_invalid(self): + self.do_test_stats(invalid_rd_ops = 7) + self.do_test_stats(invalid_wr_ops = 3) + self.do_test_stats(invalid_rd_ops = 4, invalid_wr_ops = 5) + + def test_failed(self): + self.do_test_stats(failed_rd_ops = 8) + self.do_test_stats(failed_wr_ops = 6) + self.do_test_stats(failed_rd_ops = 5, failed_wr_ops = 12) + + def test_flush(self): + self.do_test_stats(flush_ops = 8) + + def test_merged(self): + for i in range(5): + self.do_test_stats(wr_merged = i * 3) + + def test_all(self): + # rd_size, rd_ops, wr_size, wr_ops, flush_ops + # invalid_rd_ops, invalid_wr_ops, + # failed_rd_ops, failed_wr_ops + # wr_merged + test_values = [[512, 1, 512, 1, 1, 4, 7, 5, 2, 1], + [65536, 1, 2048, 12, 7, 7, 5, 2, 5, 5], + [32768, 9, 8192, 1, 4, 3, 2, 4, 6, 4], + [16384, 11, 3584, 16, 9, 8, 6, 7, 3, 4]] + for i in test_values: + self.do_test_stats(*i) + + def test_no_op(self): + # All values must be sane before doing any I/O + self.check_values() + + +class BlockDeviceStatsTestAccountInvalid(BlockDeviceStatsTestCase): + account_invalid = True + account_failed = False + +class BlockDeviceStatsTestAccountFailed(BlockDeviceStatsTestCase): + account_invalid = False + account_failed = True + +class BlockDeviceStatsTestAccountBoth(BlockDeviceStatsTestCase): + account_invalid = True + account_failed = True + +class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase): + test_img = "null-co://" + +if __name__ == '__main__': + iotests.main(supported_fmts=["raw"]) diff --git a/tests/qemu-iotests/136.out b/tests/qemu-iotests/136.out new file mode 100644 index 0000000000..0a5e9583a4 --- /dev/null +++ b/tests/qemu-iotests/136.out @@ -0,0 +1,5 @@ +........................................ +---------------------------------------------------------------------- +Ran 40 tests + +OK diff --git a/tests/qemu-iotests/137.out b/tests/qemu-iotests/137.out index cf55a41d8a..88c702cf77 100644 --- a/tests/qemu-iotests/137.out +++ b/tests/qemu-iotests/137.out @@ -31,7 +31,11 @@ Cache clean interval too big Unsupported value 'blubb' for qcow2 option 'overlap-check'. Allowed are any of the following: none, constant, cached, all wrote 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) -./common.config: Killed ( exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" ) +./common.config: Killed ( if [ "${VALGRIND_QEMU}" == "y" ]; then + exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +else + exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@"; +fi ) incompatible_features 0x0 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 wrote 65536/65536 bytes at offset 0 diff --git a/tests/qemu-iotests/139 b/tests/qemu-iotests/139 new file mode 100644 index 0000000000..42f78c7baa --- /dev/null +++ b/tests/qemu-iotests/139 @@ -0,0 +1,416 @@ +#!/usr/bin/env python +# +# Test cases for the QMP 'x-blockdev-del' command +# +# Copyright (C) 2015 Igalia, S.L. +# Author: Alberto Garcia <berto@igalia.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import os +import iotests +import time + +base_img = os.path.join(iotests.test_dir, 'base.img') +new_img = os.path.join(iotests.test_dir, 'new.img') + +class TestBlockdevDel(iotests.QMPTestCase): + + def setUp(self): + iotests.qemu_img('create', '-f', iotests.imgfmt, base_img, '1M') + self.vm = iotests.VM() + self.vm.launch() + + def tearDown(self): + self.vm.shutdown() + os.remove(base_img) + if os.path.isfile(new_img): + os.remove(new_img) + + # Check whether a BlockBackend exists + def checkBlockBackend(self, backend, node, must_exist = True): + result = self.vm.qmp('query-block') + backends = filter(lambda x: x['device'] == backend, result['return']) + self.assertLessEqual(len(backends), 1) + self.assertEqual(must_exist, len(backends) == 1) + if must_exist: + if node: + self.assertEqual(backends[0]['inserted']['node-name'], node) + else: + self.assertFalse(backends[0].has_key('inserted')) + + # Check whether a BlockDriverState exists + def checkBlockDriverState(self, node, must_exist = True): + result = self.vm.qmp('query-named-block-nodes') + nodes = filter(lambda x: x['node-name'] == node, result['return']) + self.assertLessEqual(len(nodes), 1) + self.assertEqual(must_exist, len(nodes) == 1) + + # Add a new BlockBackend (with its attached BlockDriverState) + def addBlockBackend(self, backend, node): + file_node = '%s_file' % node + self.checkBlockBackend(backend, node, False) + self.checkBlockDriverState(node, False) + self.checkBlockDriverState(file_node, False) + opts = {'driver': iotests.imgfmt, + 'id': backend, + 'node-name': node, + 'file': {'driver': 'file', + 'node-name': file_node, + 'filename': base_img}} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(node) + self.checkBlockDriverState(file_node) + + # Add a BlockDriverState without a BlockBackend + def addBlockDriverState(self, node): + file_node = '%s_file' % node + self.checkBlockDriverState(node, False) + self.checkBlockDriverState(file_node, False) + opts = {'driver': iotests.imgfmt, + 'node-name': node, + 'file': {'driver': 'file', + 'node-name': file_node, + 'filename': base_img}} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + self.checkBlockDriverState(file_node) + + # Add a BlockDriverState that will be used as overlay for the base_img BDS + def addBlockDriverStateOverlay(self, node): + self.checkBlockDriverState(node, False) + iotests.qemu_img('create', '-f', iotests.imgfmt, + '-b', base_img, new_img, '1M') + opts = {'driver': iotests.imgfmt, + 'node-name': node, + 'backing': '', + 'file': {'driver': 'file', + 'filename': new_img}} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + + # Delete a BlockBackend + def delBlockBackend(self, backend, node, expect_error = False, + destroys_media = True): + self.checkBlockBackend(backend, node) + if node: + self.checkBlockDriverState(node) + result = self.vm.qmp('x-blockdev-del', id = backend) + if expect_error: + self.assert_qmp(result, 'error/class', 'GenericError') + if node: + self.checkBlockDriverState(node) + else: + self.assert_qmp(result, 'return', {}) + if node: + self.checkBlockDriverState(node, not destroys_media) + self.checkBlockBackend(backend, node, must_exist = expect_error) + + # Delete a BlockDriverState + def delBlockDriverState(self, node, expect_error = False): + self.checkBlockDriverState(node) + result = self.vm.qmp('x-blockdev-del', node_name = node) + if expect_error: + self.assert_qmp(result, 'error/class', 'GenericError') + else: + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node, expect_error) + + # Add a device model + def addDeviceModel(self, device, backend): + result = self.vm.qmp('device_add', id = device, + driver = 'virtio-blk-pci', drive = backend) + self.assert_qmp(result, 'return', {}) + + # Delete a device model + def delDeviceModel(self, device): + result = self.vm.qmp('device_del', id = device) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('system_reset') + self.assert_qmp(result, 'return', {}) + + device_path = '/machine/peripheral/%s/virtio-backend' % device + event = self.vm.event_wait(name="DEVICE_DELETED", + match={'data': {'path': device_path}}) + self.assertNotEqual(event, None) + + event = self.vm.event_wait(name="DEVICE_DELETED", + match={'data': {'device': device}}) + self.assertNotEqual(event, None) + + # Remove a BlockDriverState + def ejectDrive(self, backend, node, expect_error = False, + destroys_media = True): + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(node) + result = self.vm.qmp('eject', device = backend) + if expect_error: + self.assert_qmp(result, 'error/class', 'GenericError') + self.checkBlockDriverState(node) + self.checkBlockBackend(backend, node) + else: + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node, not destroys_media) + self.checkBlockBackend(backend, None) + + # Insert a BlockDriverState + def insertDrive(self, backend, node): + self.checkBlockBackend(backend, None) + self.checkBlockDriverState(node) + result = self.vm.qmp('blockdev-insert-medium', + device = backend, node_name = node) + self.assert_qmp(result, 'return', {}) + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(node) + + # Create a snapshot using 'blockdev-snapshot-sync' + def createSnapshotSync(self, node, overlay): + self.checkBlockDriverState(node) + self.checkBlockDriverState(overlay, False) + opts = {'node-name': node, + 'snapshot-file': new_img, + 'snapshot-node-name': overlay, + 'format': iotests.imgfmt} + result = self.vm.qmp('blockdev-snapshot-sync', conv_keys=False, **opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + self.checkBlockDriverState(overlay) + + # Create a snapshot using 'blockdev-snapshot' + def createSnapshot(self, node, overlay): + self.checkBlockDriverState(node) + self.checkBlockDriverState(overlay) + result = self.vm.qmp('blockdev-snapshot', + node = node, overlay = overlay) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + self.checkBlockDriverState(overlay) + + # Create a mirror + def createMirror(self, backend, node, new_node): + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(new_node, False) + opts = {'device': backend, + 'target': new_img, + 'node-name': new_node, + 'sync': 'top', + 'format': iotests.imgfmt} + result = self.vm.qmp('drive-mirror', conv_keys=False, **opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockBackend(backend, node) + self.checkBlockDriverState(new_node) + + # Complete an existing block job + def completeBlockJob(self, backend, node_before, node_after): + self.checkBlockBackend(backend, node_before) + result = self.vm.qmp('block-job-complete', device=backend) + self.assert_qmp(result, 'return', {}) + self.wait_until_completed(backend) + self.checkBlockBackend(backend, node_after) + + # Add a BlkDebug node + # Note that the purpose of this is to test the x-blockdev-del + # sanity checks, not to create a usable blkdebug drive + def addBlkDebug(self, debug, node): + self.checkBlockDriverState(node, False) + self.checkBlockDriverState(debug, False) + image = {'driver': iotests.imgfmt, + 'node-name': node, + 'file': {'driver': 'file', + 'filename': base_img}} + opts = {'driver': 'blkdebug', + 'node-name': debug, + 'image': image} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(node) + self.checkBlockDriverState(debug) + + # Add a BlkVerify node + # Note that the purpose of this is to test the x-blockdev-del + # sanity checks, not to create a usable blkverify drive + def addBlkVerify(self, blkverify, test, raw): + self.checkBlockDriverState(test, False) + self.checkBlockDriverState(raw, False) + self.checkBlockDriverState(blkverify, False) + iotests.qemu_img('create', '-f', iotests.imgfmt, new_img, '1M') + node_0 = {'driver': iotests.imgfmt, + 'node-name': test, + 'file': {'driver': 'file', + 'filename': base_img}} + node_1 = {'driver': iotests.imgfmt, + 'node-name': raw, + 'file': {'driver': 'file', + 'filename': new_img}} + opts = {'driver': 'blkverify', + 'node-name': blkverify, + 'test': node_0, + 'raw': node_1} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(test) + self.checkBlockDriverState(raw) + self.checkBlockDriverState(blkverify) + + # Add a Quorum node + def addQuorum(self, quorum, child0, child1): + self.checkBlockDriverState(child0, False) + self.checkBlockDriverState(child1, False) + self.checkBlockDriverState(quorum, False) + iotests.qemu_img('create', '-f', iotests.imgfmt, new_img, '1M') + child_0 = {'driver': iotests.imgfmt, + 'node-name': child0, + 'file': {'driver': 'file', + 'filename': base_img}} + child_1 = {'driver': iotests.imgfmt, + 'node-name': child1, + 'file': {'driver': 'file', + 'filename': new_img}} + opts = {'driver': 'quorum', + 'node-name': quorum, + 'vote-threshold': 1, + 'children': [ child_0, child_1 ]} + result = self.vm.qmp('blockdev-add', conv_keys = False, options = opts) + self.assert_qmp(result, 'return', {}) + self.checkBlockDriverState(child0) + self.checkBlockDriverState(child1) + self.checkBlockDriverState(quorum) + + ######################## + # The tests start here # + ######################## + + def testWrongParameters(self): + self.addBlockBackend('drive0', 'node0') + result = self.vm.qmp('x-blockdev-del') + self.assert_qmp(result, 'error/class', 'GenericError') + result = self.vm.qmp('x-blockdev-del', id='drive0', node_name='node0') + self.assert_qmp(result, 'error/class', 'GenericError') + self.delBlockBackend('drive0', 'node0') + + def testBlockBackend(self): + self.addBlockBackend('drive0', 'node0') + # You cannot delete a BDS that is attached to a backend + self.delBlockDriverState('node0', expect_error = True) + self.delBlockBackend('drive0', 'node0') + + def testBlockDriverState(self): + self.addBlockDriverState('node0') + # You cannot delete a file BDS directly + self.delBlockDriverState('node0_file', expect_error = True) + self.delBlockDriverState('node0') + + def testEject(self): + self.addBlockBackend('drive0', 'node0') + self.ejectDrive('drive0', 'node0') + self.delBlockBackend('drive0', None) + + def testDeviceModel(self): + self.addBlockBackend('drive0', 'node0') + self.addDeviceModel('device0', 'drive0') + self.ejectDrive('drive0', 'node0', expect_error = True) + self.delBlockBackend('drive0', 'node0', expect_error = True) + self.delDeviceModel('device0') + self.delBlockBackend('drive0', 'node0') + + def testAttachMedia(self): + # This creates a BlockBackend and removes its media + self.addBlockBackend('drive0', 'node0') + self.ejectDrive('drive0', 'node0') + # This creates a new BlockDriverState and inserts it into the backend + self.addBlockDriverState('node1') + self.insertDrive('drive0', 'node1') + # The backend can't be removed: the new BDS has an extra reference + self.delBlockBackend('drive0', 'node1', expect_error = True) + self.delBlockDriverState('node1', expect_error = True) + # The BDS still exists after being ejected, but now it can be removed + self.ejectDrive('drive0', 'node1', destroys_media = False) + self.delBlockDriverState('node1') + self.delBlockBackend('drive0', None) + + def testSnapshotSync(self): + self.addBlockBackend('drive0', 'node0') + self.createSnapshotSync('node0', 'overlay0') + # This fails because node0 is now being used as a backing image + self.delBlockDriverState('node0', expect_error = True) + # This succeeds because overlay0 only has the backend reference + self.delBlockBackend('drive0', 'overlay0') + self.checkBlockDriverState('node0', False) + + def testSnapshot(self): + self.addBlockBackend('drive0', 'node0') + self.addBlockDriverStateOverlay('overlay0') + self.createSnapshot('node0', 'overlay0') + self.delBlockBackend('drive0', 'overlay0', expect_error = True) + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('overlay0', expect_error = True) + self.ejectDrive('drive0', 'overlay0', destroys_media = False) + self.delBlockBackend('drive0', None) + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('overlay0') + self.checkBlockDriverState('node0', False) + + def testMirror(self): + self.addBlockBackend('drive0', 'node0') + self.createMirror('drive0', 'node0', 'mirror0') + # The block job prevents removing the device + self.delBlockBackend('drive0', 'node0', expect_error = True) + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('mirror0', expect_error = True) + self.wait_ready('drive0') + self.completeBlockJob('drive0', 'node0', 'mirror0') + self.assert_no_active_block_jobs() + self.checkBlockDriverState('node0', False) + # This succeeds because the backend now points to mirror0 + self.delBlockBackend('drive0', 'mirror0') + + def testBlkDebug(self): + self.addBlkDebug('debug0', 'node0') + # 'node0' is used by the blkdebug node + self.delBlockDriverState('node0', expect_error = True) + # But we can remove the blkdebug node directly + self.delBlockDriverState('debug0') + self.checkBlockDriverState('node0', False) + + def testBlkVerify(self): + self.addBlkVerify('verify0', 'node0', 'node1') + # We cannot remove the children of a blkverify device + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('node1', expect_error = True) + # But we can remove the blkverify node directly + self.delBlockDriverState('verify0') + self.checkBlockDriverState('node0', False) + self.checkBlockDriverState('node1', False) + + def testQuorum(self): + if not 'quorum' in iotests.qemu_img_pipe('--help'): + return + self.addQuorum('quorum0', 'node0', 'node1') + # We cannot remove the children of a Quorum device + self.delBlockDriverState('node0', expect_error = True) + self.delBlockDriverState('node1', expect_error = True) + # But we can remove the Quorum node directly + self.delBlockDriverState('quorum0') + self.checkBlockDriverState('node0', False) + self.checkBlockDriverState('node1', False) + + +if __name__ == '__main__': + iotests.main(supported_fmts=["qcow2"]) diff --git a/tests/qemu-iotests/139.out b/tests/qemu-iotests/139.out new file mode 100644 index 0000000000..281b69efea --- /dev/null +++ b/tests/qemu-iotests/139.out @@ -0,0 +1,5 @@ +............ +---------------------------------------------------------------------- +Ran 12 tests + +OK diff --git a/tests/qemu-iotests/common b/tests/qemu-iotests/common index 25c351bbd1..ff84f4b0d6 100644 --- a/tests/qemu-iotests/common +++ b/tests/qemu-iotests/common @@ -41,7 +41,6 @@ sortme=false expunge=true have_test_arg=false randomize=false -valgrind=false cachemode=false rm -f $tmp.list $tmp.tmp $tmp.sed @@ -53,6 +52,7 @@ export CACHEMODE="writeback" export QEMU_IO_OPTIONS="" export CACHEMODE_IS_DEFAULT=true export QEMU_OPTIONS="-nodefaults" +export VALGRIND_QEMU= for r do @@ -278,7 +278,7 @@ testlist options ;; -valgrind) - valgrind=true + VALGRIND_QEMU='y' xpand=false ;; @@ -436,8 +436,3 @@ fi if [ "$IMGPROTO" = "nbd" ] ; then [ "$QEMU_NBD" = "" ] && _fatal "qemu-nbd not found" fi - -if $valgrind; then - export REAL_QEMU_IO="$QEMU_IO_PROG" - export QEMU_IO_PROG=valgrind_qemu_io -fi diff --git a/tests/qemu-iotests/common.config b/tests/qemu-iotests/common.config index 596bb2b1e5..3ed51b8baa 100644 --- a/tests/qemu-iotests/common.config +++ b/tests/qemu-iotests/common.config @@ -44,6 +44,8 @@ export HOST_OPTIONS=${HOST_OPTIONS:=local.config} export CHECK_OPTIONS=${CHECK_OPTIONS:="-g auto"} export PWD=`pwd` +export _QEMU_HANDLE=0 + # $1 = prog to look for, $2* = default pathnames if not found in $PATH set_prog_path() { @@ -105,7 +107,12 @@ fi _qemu_wrapper() { - (exec "$QEMU_PROG" $QEMU_OPTIONS "$@") + ( + if [ -n "${QEMU_NEED_PID}" ]; then + echo $BASHPID > "${TEST_DIR}/qemu-${_QEMU_HANDLE}.pid" + fi + exec "$QEMU_PROG" $QEMU_OPTIONS "$@" + ) } _qemu_img_wrapper() @@ -115,12 +122,31 @@ _qemu_img_wrapper() _qemu_io_wrapper() { - (exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@") + local VALGRIND_LOGFILE=/tmp/$$.valgrind + local RETVAL + ( + if [ "${VALGRIND_QEMU}" == "y" ]; then + exec valgrind --log-file="${VALGRIND_LOGFILE}" --error-exitcode=99 "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" + else + exec "$QEMU_IO_PROG" $QEMU_IO_OPTIONS "$@" + fi + ) + RETVAL=$? + if [ "${VALGRIND_QEMU}" == "y" ]; then + if [ $RETVAL == 99 ]; then + cat "${VALGRIND_LOGFILE}" + fi + rm -f "${VALGRIND_LOGFILE}" + fi + (exit $RETVAL) } _qemu_nbd_wrapper() { - (exec "$QEMU_NBD_PROG" $QEMU_NBD_OPTIONS "$@") + ( + echo $BASHPID > "${TEST_DIR}/qemu-nbd.pid" + exec "$QEMU_NBD_PROG" $QEMU_NBD_OPTIONS "$@" + ) } export QEMU=_qemu_wrapper diff --git a/tests/qemu-iotests/common.qemu b/tests/qemu-iotests/common.qemu index e3faa53d22..8bf3969418 100644 --- a/tests/qemu-iotests/common.qemu +++ b/tests/qemu-iotests/common.qemu @@ -30,8 +30,6 @@ QEMU_COMM_TIMEOUT=10 QEMU_FIFO_IN="${TEST_DIR}/qmp-in-$$" QEMU_FIFO_OUT="${TEST_DIR}/qmp-out-$$" -QEMU_PID= -_QEMU_HANDLE=0 QEMU_HANDLE=0 # If bash version is >= 4.1, these will be overwritten and dynamic @@ -153,11 +151,11 @@ function _launch_qemu() mkfifo "${fifo_out}" mkfifo "${fifo_in}" + QEMU_NEED_PID='y'\ ${QEMU} -nographic -serial none ${comm} -machine accel=qtest "${@}" \ >"${fifo_out}" \ 2>&1 \ <"${fifo_in}" & - QEMU_PID[${_QEMU_HANDLE}]=$! if [[ "${BASH_VERSINFO[0]}" -ge "5" || ("${BASH_VERSINFO[0]}" -ge "4" && "${BASH_VERSINFO[1]}" -ge "1") ]] @@ -196,10 +194,18 @@ function _cleanup_qemu() # QEMU_PID[], QEMU_IN[], QEMU_OUT[] all use same indices for i in "${!QEMU_OUT[@]}" do - if [ -z "${wait}" ]; then - kill -KILL ${QEMU_PID[$i]} 2>/dev/null + local QEMU_PID + if [ -f "${TEST_DIR}/qemu-${i}.pid" ]; then + read QEMU_PID < "${TEST_DIR}/qemu-${i}.pid" + rm -f "${TEST_DIR}/qemu-${i}.pid" + if [ -z "${wait}" ] && [ -n "${QEMU_PID}" ]; then + kill -KILL ${QEMU_PID} 2>/dev/null + fi + if [ -n "${QEMU_PID}" ]; then + wait ${QEMU_PID} 2>/dev/null # silent kill + fi fi - wait ${QEMU_PID[$i]} 2>/dev/null # silent kill + if [ -n "${wait}" ]; then cat <&${QEMU_OUT[$i]} | _filter_testdir | _filter_qemu \ | _filter_qemu_io | _filter_qmp diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc index 28e4beac15..d9913f8496 100644 --- a/tests/qemu-iotests/common.rc +++ b/tests/qemu-iotests/common.rc @@ -70,16 +70,6 @@ else TEST_IMG=$IMGPROTO:$TEST_DIR/t.$IMGFMT fi -function valgrind_qemu_io() -{ - valgrind --log-file=/tmp/$$.valgrind --error-exitcode=99 $REAL_QEMU_IO "$@" - if [ $? != 0 ]; then - cat /tmp/$$.valgrind - fi - rm -f /tmp/$$.valgrind -} - - _optstr_add() { if [ -n "$1" ]; then @@ -154,7 +144,6 @@ _make_test_img() # Start an NBD server on the image file, which is what we'll be talking to if [ $IMGPROTO = "nbd" ]; then eval "$QEMU_NBD -v -t -b 127.0.0.1 -p 10810 -f $IMGFMT $TEST_IMG_FILE &" - QEMU_NBD_PID=$! sleep 1 # FIXME: qemu-nbd needs to be listening before we continue fi } @@ -175,8 +164,11 @@ _cleanup_test_img() case "$IMGPROTO" in nbd) - if [ -n "$QEMU_NBD_PID" ]; then - kill $QEMU_NBD_PID + if [ -f "${TEST_DIR}/qemu-nbd.pid" ]; then + local QEMU_NBD_PID + read QEMU_NBD_PID < "${TEST_DIR}/qemu-nbd.pid" + kill ${QEMU_NBD_PID} + rm -f "${TEST_DIR}/qemu-nbd.pid" fi rm -f "$TEST_IMG_FILE" ;; diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index 30c784e940..5a0880893a 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -122,6 +122,7 @@ 114 rw auto quick 115 rw auto 116 rw auto quick +118 rw auto 119 rw auto quick 120 rw auto quick 121 rw auto @@ -135,5 +136,7 @@ 132 rw auto quick 134 rw auto quick 135 rw auto +136 rw auto 137 rw auto 138 rw auto quick +139 rw auto quick diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index ff5905f379..e02245ed07 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -140,6 +140,11 @@ class VM(object): self._args.append('-monitor') self._args.append(args) + def add_drive_raw(self, opts): + self._args.append('-drive') + self._args.append(opts) + return self + def add_drive(self, path, opts='', interface='virtio'): '''Add a virtio-blk drive to the VM''' options = ['if=%s' % interface, diff --git a/tests/qom-test.c b/tests/qom-test.c index fde04e7a19..3e5e8730e7 100644 --- a/tests/qom-test.c +++ b/tests/qom-test.c @@ -47,7 +47,7 @@ static bool is_blacklisted(const char *arch, const char *mach) static void test_properties(const char *path, bool recurse) { char *child_path; - QDict *response, *tuple; + QDict *response, *tuple, *tmp; QList *list; QListEntry *entry; @@ -57,6 +57,7 @@ static void test_properties(const char *path, bool recurse) g_assert(response); if (!recurse) { + QDECREF(response); return; } @@ -75,14 +76,16 @@ static void test_properties(const char *path, bool recurse) } else { const char *prop = qdict_get_str(tuple, "name"); g_test_message("Testing property %s.%s", path, prop); - response = qmp("{ 'execute': 'qom-get'," - " 'arguments': { 'path': %s," - " 'property': %s } }", - path, prop); + tmp = qmp("{ 'execute': 'qom-get'," + " 'arguments': { 'path': %s," + " 'property': %s } }", + path, prop); /* qom-get may fail but should not, e.g., segfault. */ - g_assert(response); + g_assert(tmp); + QDECREF(tmp); } } + QDECREF(response); } static void test_machine(gconstpointer data) @@ -98,9 +101,11 @@ static void test_machine(gconstpointer data) response = qmp("{ 'execute': 'quit' }"); g_assert(qdict_haskey(response, "return")); + QDECREF(response); qtest_end(); g_free(args); + g_free((void *)machine); } static void add_machine_test_cases(void) @@ -129,10 +134,12 @@ static void add_machine_test_cases(void) mname = qstring_get_str(qstr); if (!is_blacklisted(arch, mname)) { path = g_strdup_printf("qom/%s", mname); - qtest_add_data_func(path, mname, test_machine); + qtest_add_data_func(path, g_strdup(mname), test_machine); } } + qtest_end(); + QDECREF(response); } int main(int argc, char **argv) diff --git a/tests/test-aio.c b/tests/test-aio.c index 1623803e8c..e188d8c13d 100644 --- a/tests/test-aio.c +++ b/tests/test-aio.c @@ -393,6 +393,7 @@ static void test_aio_external_client(void) aio_enable_external(ctx); } assert(aio_poll(ctx, false)); + set_event_notifier(ctx, &data.e, NULL); event_notifier_cleanup(&data.e); } } diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c new file mode 100644 index 0000000000..34747e924d --- /dev/null +++ b/tests/test-blockjob-txn.c @@ -0,0 +1,250 @@ +/* + * Blockjob transactions tests + * + * Copyright Red Hat, Inc. 2015 + * + * Authors: + * Stefan Hajnoczi <stefanha@redhat.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include <glib.h> +#include "qapi/error.h" +#include "qemu/main-loop.h" +#include "block/blockjob.h" + +typedef struct { + BlockJob common; + unsigned int iterations; + bool use_timer; + int rc; + int *result; +} TestBlockJob; + +static const BlockJobDriver test_block_job_driver = { + .instance_size = sizeof(TestBlockJob), +}; + +static void test_block_job_complete(BlockJob *job, void *opaque) +{ + BlockDriverState *bs = job->bs; + int rc = (intptr_t)opaque; + + if (block_job_is_cancelled(job)) { + rc = -ECANCELED; + } + + block_job_completed(job, rc); + bdrv_unref(bs); +} + +static void coroutine_fn test_block_job_run(void *opaque) +{ + TestBlockJob *s = opaque; + BlockJob *job = &s->common; + + while (s->iterations--) { + if (s->use_timer) { + block_job_sleep_ns(job, QEMU_CLOCK_REALTIME, 0); + } else { + block_job_yield(job); + } + + if (block_job_is_cancelled(job)) { + break; + } + } + + block_job_defer_to_main_loop(job, test_block_job_complete, + (void *)(intptr_t)s->rc); +} + +typedef struct { + TestBlockJob *job; + int *result; +} TestBlockJobCBData; + +static void test_block_job_cb(void *opaque, int ret) +{ + TestBlockJobCBData *data = opaque; + if (!ret && block_job_is_cancelled(&data->job->common)) { + ret = -ECANCELED; + } + *data->result = ret; + g_free(data); +} + +/* Create a block job that completes with a given return code after a given + * number of event loop iterations. The return code is stored in the given + * result pointer. + * + * The event loop iterations can either be handled automatically with a 0 delay + * timer, or they can be stepped manually by entering the coroutine. + */ +static BlockJob *test_block_job_start(unsigned int iterations, + bool use_timer, + int rc, int *result) +{ + BlockDriverState *bs; + TestBlockJob *s; + TestBlockJobCBData *data; + + data = g_new0(TestBlockJobCBData, 1); + bs = bdrv_new(); + s = block_job_create(&test_block_job_driver, bs, 0, test_block_job_cb, + data, &error_abort); + s->iterations = iterations; + s->use_timer = use_timer; + s->rc = rc; + s->result = result; + s->common.co = qemu_coroutine_create(test_block_job_run); + data->job = s; + data->result = result; + qemu_coroutine_enter(s->common.co, s); + return &s->common; +} + +static void test_single_job(int expected) +{ + BlockJob *job; + BlockJobTxn *txn; + int result = -EINPROGRESS; + + txn = block_job_txn_new(); + job = test_block_job_start(1, true, expected, &result); + block_job_txn_add_job(txn, job); + + if (expected == -ECANCELED) { + block_job_cancel(job); + } + + while (result == -EINPROGRESS) { + aio_poll(qemu_get_aio_context(), true); + } + g_assert_cmpint(result, ==, expected); + + block_job_txn_unref(txn); +} + +static void test_single_job_success(void) +{ + test_single_job(0); +} + +static void test_single_job_failure(void) +{ + test_single_job(-EIO); +} + +static void test_single_job_cancel(void) +{ + test_single_job(-ECANCELED); +} + +static void test_pair_jobs(int expected1, int expected2) +{ + BlockJob *job1; + BlockJob *job2; + BlockJobTxn *txn; + int result1 = -EINPROGRESS; + int result2 = -EINPROGRESS; + + txn = block_job_txn_new(); + job1 = test_block_job_start(1, true, expected1, &result1); + block_job_txn_add_job(txn, job1); + job2 = test_block_job_start(2, true, expected2, &result2); + block_job_txn_add_job(txn, job2); + + if (expected1 == -ECANCELED) { + block_job_cancel(job1); + } + if (expected2 == -ECANCELED) { + block_job_cancel(job2); + } + + while (result1 == -EINPROGRESS || result2 == -EINPROGRESS) { + aio_poll(qemu_get_aio_context(), true); + } + + /* Failure or cancellation of one job cancels the other job */ + if (expected1 != 0) { + expected2 = -ECANCELED; + } else if (expected2 != 0) { + expected1 = -ECANCELED; + } + + g_assert_cmpint(result1, ==, expected1); + g_assert_cmpint(result2, ==, expected2); + + block_job_txn_unref(txn); +} + +static void test_pair_jobs_success(void) +{ + test_pair_jobs(0, 0); +} + +static void test_pair_jobs_failure(void) +{ + /* Test both orderings. The two jobs run for a different number of + * iterations so the code path is different depending on which job fails + * first. + */ + test_pair_jobs(-EIO, 0); + test_pair_jobs(0, -EIO); +} + +static void test_pair_jobs_cancel(void) +{ + test_pair_jobs(-ECANCELED, 0); + test_pair_jobs(0, -ECANCELED); +} + +static void test_pair_jobs_fail_cancel_race(void) +{ + BlockJob *job1; + BlockJob *job2; + BlockJobTxn *txn; + int result1 = -EINPROGRESS; + int result2 = -EINPROGRESS; + + txn = block_job_txn_new(); + job1 = test_block_job_start(1, true, -ECANCELED, &result1); + block_job_txn_add_job(txn, job1); + job2 = test_block_job_start(2, false, 0, &result2); + block_job_txn_add_job(txn, job2); + + block_job_cancel(job1); + + /* Now make job2 finish before the main loop kicks jobs. This simulates + * the race between a pending kick and another job completing. + */ + block_job_enter(job2); + block_job_enter(job2); + + while (result1 == -EINPROGRESS || result2 == -EINPROGRESS) { + aio_poll(qemu_get_aio_context(), true); + } + + g_assert_cmpint(result1, ==, -ECANCELED); + g_assert_cmpint(result2, ==, -ECANCELED); + + block_job_txn_unref(txn); +} + +int main(int argc, char **argv) +{ + qemu_init_main_loop(&error_abort); + + g_test_init(&argc, &argv, NULL); + g_test_add_func("/single/success", test_single_job_success); + g_test_add_func("/single/failure", test_single_job_failure); + g_test_add_func("/single/cancel", test_single_job_cancel); + g_test_add_func("/pair/success", test_pair_jobs_success); + g_test_add_func("/pair/failure", test_pair_jobs_failure); + g_test_add_func("/pair/cancel", test_pair_jobs_cancel); + g_test_add_func("/pair/fail-cancel-race", test_pair_jobs_fail_cancel_race); + return g_test_run(); +} diff --git a/tests/test-qga.c b/tests/test-qga.c index 64738465c5..e6a84d17f0 100644 --- a/tests/test-qga.c +++ b/tests/test-qga.c @@ -13,6 +13,7 @@ #include "libqtest.h" #include "config-host.h" +#include "qga/guest-agent-core.h" typedef struct { char *test_dir; @@ -352,10 +353,10 @@ static void test_qga_network_get_interfaces(gconstpointer fix) static void test_qga_file_ops(gconstpointer fix) { const TestFixture *fixture = fix; - const guchar helloworld[] = "Hello World!\n"; + const unsigned char helloworld[] = "Hello World!\n"; const char *b64; gchar *cmd, *path, *enc; - guchar *dec; + unsigned char *dec; QDict *ret, *val; int64_t id, eof; gsize count; @@ -457,7 +458,7 @@ static void test_qga_file_ops(gconstpointer fix) cmd = g_strdup_printf("{'execute': 'guest-file-seek'," " 'arguments': { 'handle': %" PRId64 ", " " 'offset': %d, 'whence': %d } }", - id, 6, SEEK_SET); + id, 6, QGA_SEEK_SET); ret = qmp_fd(fixture->fd, cmd); qmp_assert_no_error(ret); val = qdict_get_qdict(ret, "return"); @@ -496,6 +497,96 @@ static void test_qga_file_ops(gconstpointer fix) g_free(cmd); } +static void test_qga_file_write_read(gconstpointer fix) +{ + const TestFixture *fixture = fix; + const unsigned char helloworld[] = "Hello World!\n"; + const char *b64; + gchar *cmd, *enc; + QDict *ret, *val; + int64_t id, eof; + gsize count; + + /* open */ + ret = qmp_fd(fixture->fd, "{'execute': 'guest-file-open'," + " 'arguments': { 'path': 'foo', 'mode': 'w+' } }"); + g_assert_nonnull(ret); + qmp_assert_no_error(ret); + id = qdict_get_int(ret, "return"); + QDECREF(ret); + + enc = g_base64_encode(helloworld, sizeof(helloworld)); + /* write */ + cmd = g_strdup_printf("{'execute': 'guest-file-write'," + " 'arguments': { 'handle': %" PRId64 "," + " 'buf-b64': '%s' } }", id, enc); + ret = qmp_fd(fixture->fd, cmd); + g_assert_nonnull(ret); + qmp_assert_no_error(ret); + + val = qdict_get_qdict(ret, "return"); + count = qdict_get_int(val, "count"); + eof = qdict_get_bool(val, "eof"); + g_assert_cmpint(count, ==, sizeof(helloworld)); + g_assert_cmpint(eof, ==, 0); + QDECREF(ret); + g_free(cmd); + + /* read (check implicit flush) */ + cmd = g_strdup_printf("{'execute': 'guest-file-read'," + " 'arguments': { 'handle': %" PRId64 "} }", + id); + ret = qmp_fd(fixture->fd, cmd); + val = qdict_get_qdict(ret, "return"); + count = qdict_get_int(val, "count"); + eof = qdict_get_bool(val, "eof"); + b64 = qdict_get_str(val, "buf-b64"); + g_assert_cmpint(count, ==, 0); + g_assert(eof); + g_assert_cmpstr(b64, ==, ""); + QDECREF(ret); + g_free(cmd); + + /* seek to 0 */ + cmd = g_strdup_printf("{'execute': 'guest-file-seek'," + " 'arguments': { 'handle': %" PRId64 ", " + " 'offset': %d, 'whence': %d } }", + id, 0, QGA_SEEK_SET); + ret = qmp_fd(fixture->fd, cmd); + qmp_assert_no_error(ret); + val = qdict_get_qdict(ret, "return"); + count = qdict_get_int(val, "position"); + eof = qdict_get_bool(val, "eof"); + g_assert_cmpint(count, ==, 0); + g_assert(!eof); + QDECREF(ret); + g_free(cmd); + + /* read */ + cmd = g_strdup_printf("{'execute': 'guest-file-read'," + " 'arguments': { 'handle': %" PRId64 "} }", + id); + ret = qmp_fd(fixture->fd, cmd); + val = qdict_get_qdict(ret, "return"); + count = qdict_get_int(val, "count"); + eof = qdict_get_bool(val, "eof"); + b64 = qdict_get_str(val, "buf-b64"); + g_assert_cmpint(count, ==, sizeof(helloworld)); + g_assert(eof); + g_assert_cmpstr(b64, ==, enc); + QDECREF(ret); + g_free(cmd); + g_free(enc); + + /* close */ + cmd = g_strdup_printf("{'execute': 'guest-file-close'," + " 'arguments': {'handle': %" PRId64 "} }", + id); + ret = qmp_fd(fixture->fd, cmd); + QDECREF(ret); + g_free(cmd); +} + static void test_qga_get_time(gconstpointer fix) { const TestFixture *fixture = fix; @@ -762,6 +853,7 @@ int main(int argc, char **argv) g_test_add_data_func("/qga/get-memory-blocks", &fix, test_qga_get_memory_blocks); g_test_add_data_func("/qga/file-ops", &fix, test_qga_file_ops); + g_test_add_data_func("/qga/file-write-read", &fix, test_qga_file_write_read); g_test_add_data_func("/qga/get-time", &fix, test_qga_get_time); g_test_add_data_func("/qga/invalid-cmd", &fix, test_qga_invalid_cmd); g_test_add_data_func("/qga/fsfreeze-status", &fix, diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c index bc59835835..888fb5ffec 100644 --- a/tests/test-qmp-commands.c +++ b/tests/test-qmp-commands.c @@ -25,11 +25,9 @@ UserDefTwo *qmp_user_def_cmd2(UserDefOne *ud1a, UserDefOne *ud1d = g_malloc0(sizeof(UserDefOne)); ud1c->string = strdup(ud1a->string); - ud1c->base = g_new0(UserDefZero, 1); - ud1c->base->integer = ud1a->base->integer; + ud1c->integer = ud1a->integer; ud1d->string = strdup(has_udb1 ? ud1b->string : "blah0"); - ud1d->base = g_new0(UserDefZero, 1); - ud1d->base->integer = has_udb1 ? ud1b->base->integer : 0; + ud1d->integer = has_udb1 ? ud1b->integer : 0; ret = g_new0(UserDefTwo, 1); ret->string0 = strdup("blah1"); @@ -64,8 +62,8 @@ __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a, { __org_qemu_x_Union1 *ret = g_new0(__org_qemu_x_Union1, 1); - ret->kind = ORG_QEMU_X_UNION1_KIND___ORG_QEMU_X_BRANCH; - ret->__org_qemu_x_branch = strdup("blah1"); + ret->type = ORG_QEMU_X_UNION1_KIND___ORG_QEMU_X_BRANCH; + ret->u.__org_qemu_x_branch = strdup("blah1"); return ret; } @@ -176,20 +174,17 @@ static void test_dealloc_types(void) UserDefOneList *ud1list; ud1test = g_malloc0(sizeof(UserDefOne)); - ud1test->base = g_new0(UserDefZero, 1); - ud1test->base->integer = 42; + ud1test->integer = 42; ud1test->string = g_strdup("hi there 42"); qapi_free_UserDefOne(ud1test); ud1a = g_malloc0(sizeof(UserDefOne)); - ud1a->base = g_new0(UserDefZero, 1); - ud1a->base->integer = 43; + ud1a->integer = 43; ud1a->string = g_strdup("hi there 43"); ud1b = g_malloc0(sizeof(UserDefOne)); - ud1b->base = g_new0(UserDefZero, 1); - ud1b->base->integer = 44; + ud1b->integer = 44; ud1b->string = g_strdup("hi there 44"); ud1list = g_malloc0(sizeof(UserDefOneList)); @@ -230,8 +225,7 @@ static void test_dealloc_partial(void) assert(ud2->dict1 == NULL); /* confirm & release construction error */ - assert(err != NULL); - error_free(err); + error_free_or_abort(&err); /* tear down partial object */ qapi_free_UserDefTwo(ud2); diff --git a/tests/test-qmp-event.c b/tests/test-qmp-event.c index 28f146d4b7..035c65cfdf 100644 --- a/tests/test-qmp-event.c +++ b/tests/test-qmp-event.c @@ -179,9 +179,7 @@ static void test_event_c(TestEventData *data, QDict *d, *d_data, *d_b; UserDefOne b; - UserDefZero z; - z.integer = 2; - b.base = &z; + b.integer = 2; b.string = g_strdup("test1"); b.has_enum1 = false; @@ -209,11 +207,9 @@ static void test_event_d(TestEventData *data, { UserDefOne struct1; EventStructOne a; - UserDefZero z; QDict *d, *d_data, *d_a, *d_struct1; - z.integer = 2; - struct1.base = &z; + struct1.integer = 2; struct1.string = g_strdup("test1"); struct1.has_enum1 = true; struct1.enum1 = ENUM_ONE_VALUE1; diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c index 53a769388c..f1c2e3ba67 100644 --- a/tests/test-qmp-input-strict.c +++ b/tests/test-qmp-input-strict.c @@ -40,31 +40,42 @@ static void validate_teardown(TestInputVisitorData *data, } } -/* This is provided instead of a test setup function so that the JSON - string used by the tests are kept in the test functions (and not - int main()) */ -static GCC_FMT_ATTR(2, 3) -Visitor *validate_test_init(TestInputVisitorData *data, - const char *json_string, ...) +/* The various test_init functions are provided instead of a test setup + function so that the JSON string used by the tests are kept in the test + functions (and not in main()). */ +static Visitor *validate_test_init_internal(TestInputVisitorData *data, + const char *json_string, + va_list *ap) { Visitor *v; - va_list ap; - va_start(ap, json_string); - data->obj = qobject_from_jsonv(json_string, &ap); - va_end(ap); + validate_teardown(data, NULL); - g_assert(data->obj != NULL); + data->obj = qobject_from_jsonv(json_string, ap); + g_assert(data->obj); data->qiv = qmp_input_visitor_new_strict(data->obj); - g_assert(data->qiv != NULL); + g_assert(data->qiv); v = qmp_input_get_visitor(data->qiv); - g_assert(v != NULL); + g_assert(v); return v; } +static GCC_FMT_ATTR(2, 3) +Visitor *validate_test_init(TestInputVisitorData *data, + const char *json_string, ...) +{ + Visitor *v; + va_list ap; + + va_start(ap, json_string); + v = validate_test_init_internal(data, json_string, &ap); + va_end(ap); + return v; +} + /* similar to validate_test_init(), but does not expect a string * literal/format json_string argument and so can be used for * programatically generated strings (and we can't pass in programatically @@ -75,67 +86,19 @@ Visitor *validate_test_init(TestInputVisitorData *data, static Visitor *validate_test_init_raw(TestInputVisitorData *data, const char *json_string) { - Visitor *v; - - data->obj = qobject_from_json(json_string); - g_assert(data->obj != NULL); - - data->qiv = qmp_input_visitor_new_strict(data->obj); - g_assert(data->qiv != NULL); - - v = qmp_input_get_visitor(data->qiv); - g_assert(v != NULL); - - return v; + return validate_test_init_internal(data, json_string, NULL); } -typedef struct TestStruct -{ - int64_t integer; - bool boolean; - char *string; -} TestStruct; - -static void visit_type_TestStruct(Visitor *v, TestStruct **obj, - const char *name, Error **errp) -{ - Error *err = NULL; - - visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct), - &err); - if (err) { - goto out; - } - - visit_type_int(v, &(*obj)->integer, "integer", &err); - if (err) { - goto out_end; - } - visit_type_bool(v, &(*obj)->boolean, "boolean", &err); - if (err) { - goto out_end; - } - visit_type_str(v, &(*obj)->string, "string", &err); - -out_end: - error_propagate(errp, err); - err = NULL; - visit_end_struct(v, &err); -out: - error_propagate(errp, err); -} static void test_validate_struct(TestInputVisitorData *data, const void *unused) { TestStruct *p = NULL; - Error *err = NULL; Visitor *v; v = validate_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo' }"); - visit_type_TestStruct(v, &p, NULL, &err); - g_assert(!err); + visit_type_TestStruct(v, &p, NULL, &error_abort); g_free(p->string); g_free(p); } @@ -144,7 +107,6 @@ static void test_validate_struct_nested(TestInputVisitorData *data, const void *unused) { UserDefTwo *udp = NULL; - Error *err = NULL; Visitor *v; v = validate_test_init(data, "{ 'string0': 'string0', " @@ -152,8 +114,7 @@ static void test_validate_struct_nested(TestInputVisitorData *data, "'dict2': { 'userdef': { 'integer': 42, " "'string': 'string' }, 'string': 'string2'}}}"); - visit_type_UserDefTwo(v, &udp, NULL, &err); - g_assert(!err); + visit_type_UserDefTwo(v, &udp, NULL, &error_abort); qapi_free_UserDefTwo(udp); } @@ -161,13 +122,11 @@ static void test_validate_list(TestInputVisitorData *data, const void *unused) { UserDefOneList *head = NULL; - Error *err = NULL; Visitor *v; v = validate_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44 } ]"); - visit_type_UserDefOneList(v, &head, NULL, &err); - g_assert(!err); + visit_type_UserDefOneList(v, &head, NULL, &error_abort); qapi_free_UserDefOneList(head); } @@ -176,12 +135,10 @@ static void test_validate_union_native_list(TestInputVisitorData *data, { UserDefNativeListUnion *tmp = NULL; Visitor *v; - Error *err = NULL; v = validate_test_init(data, "{ 'type': 'integer', 'data' : [ 1, 2 ] }"); - visit_type_UserDefNativeListUnion(v, &tmp, NULL, &err); - g_assert(!err); + visit_type_UserDefNativeListUnion(v, &tmp, NULL, &error_abort); qapi_free_UserDefNativeListUnion(tmp); } @@ -190,7 +147,6 @@ static void test_validate_union_flat(TestInputVisitorData *data, { UserDefFlatUnion *tmp = NULL; Visitor *v; - Error *err = NULL; v = validate_test_init(data, "{ 'enum1': 'value1', " @@ -198,8 +154,7 @@ static void test_validate_union_flat(TestInputVisitorData *data, "'string': 'str', " "'boolean': true }"); - visit_type_UserDefFlatUnion(v, &tmp, NULL, &err); - g_assert(!err); + visit_type_UserDefFlatUnion(v, &tmp, NULL, &error_abort); qapi_free_UserDefFlatUnion(tmp); } @@ -208,12 +163,10 @@ static void test_validate_alternate(TestInputVisitorData *data, { UserDefAlternate *tmp = NULL; Visitor *v; - Error *err = NULL; v = validate_test_init(data, "42"); - visit_type_UserDefAlternate(v, &tmp, NULL, &err); - g_assert(!err); + visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort); qapi_free_UserDefAlternate(tmp); } @@ -227,7 +180,7 @@ static void test_validate_fail_struct(TestInputVisitorData *data, v = validate_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo', 'extra': 42 }"); visit_type_TestStruct(v, &p, NULL, &err); - g_assert(err); + error_free_or_abort(&err); if (p) { g_free(p->string); } @@ -244,7 +197,7 @@ static void test_validate_fail_struct_nested(TestInputVisitorData *data, v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string', 'extra': [42, 23, {'foo':'bar'}] }, 'string2': 'string2'}}}"); visit_type_UserDefTwo(v, &udp, NULL, &err); - g_assert(err); + error_free_or_abort(&err); qapi_free_UserDefTwo(udp); } @@ -258,7 +211,7 @@ static void test_validate_fail_list(TestInputVisitorData *data, v = validate_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44, 'extra': 'ggg' } ]"); visit_type_UserDefOneList(v, &head, NULL, &err); - g_assert(err); + error_free_or_abort(&err); qapi_free_UserDefOneList(head); } @@ -273,7 +226,7 @@ static void test_validate_fail_union_native_list(TestInputVisitorData *data, "{ 'type': 'integer', 'data' : [ 'string' ] }"); visit_type_UserDefNativeListUnion(v, &tmp, NULL, &err); - g_assert(err); + error_free_or_abort(&err); qapi_free_UserDefNativeListUnion(tmp); } @@ -287,7 +240,7 @@ static void test_validate_fail_union_flat(TestInputVisitorData *data, v = validate_test_init(data, "{ 'string': 'c', 'integer': 41, 'boolean': true }"); visit_type_UserDefFlatUnion(v, &tmp, NULL, &err); - g_assert(err); + error_free_or_abort(&err); qapi_free_UserDefFlatUnion(tmp); } @@ -302,7 +255,7 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data, v = validate_test_init(data, "{ 'integer': 42, 'string': 'c', 'string1': 'd', 'string2': 'e' }"); visit_type_UserDefFlatUnion2(v, &tmp, NULL, &err); - g_assert(err); + error_free_or_abort(&err); qapi_free_UserDefFlatUnion2(tmp); } @@ -316,7 +269,7 @@ static void test_validate_fail_alternate(TestInputVisitorData *data, v = validate_test_init(data, "3.14"); visit_type_UserDefAlternate(v, &tmp, NULL, &err); - g_assert(err); + error_free_or_abort(&err); qapi_free_UserDefAlternate(tmp); } @@ -324,16 +277,11 @@ static void do_test_validate_qmp_introspect(TestInputVisitorData *data, const char *schema_json) { SchemaInfoList *schema = NULL; - Error *err = NULL; Visitor *v; v = validate_test_init_raw(data, schema_json); - visit_type_SchemaInfoList(v, &schema, NULL, &err); - if (err) { - fprintf(stderr, "%s", error_get_pretty(err)); - } - g_assert(!err); + visit_type_SchemaInfoList(v, &schema, NULL, &error_abort); g_assert(schema); qapi_free_SchemaInfoList(schema); diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c index 8941963c8d..d48ebdd62f 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -36,31 +36,42 @@ static void visitor_input_teardown(TestInputVisitorData *data, } } -/* This is provided instead of a test setup function so that the JSON - string used by the tests are kept in the test functions (and not - int main()) */ -static GCC_FMT_ATTR(2, 3) -Visitor *visitor_input_test_init(TestInputVisitorData *data, - const char *json_string, ...) +/* The various test_init functions are provided instead of a test setup + function so that the JSON string used by the tests are kept in the test + functions (and not in main()). */ +static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data, + const char *json_string, + va_list *ap) { Visitor *v; - va_list ap; - va_start(ap, json_string); - data->obj = qobject_from_jsonv(json_string, &ap); - va_end(ap); + visitor_input_teardown(data, NULL); - g_assert(data->obj != NULL); + data->obj = qobject_from_jsonv(json_string, ap); + g_assert(data->obj); data->qiv = qmp_input_visitor_new(data->obj); - g_assert(data->qiv != NULL); + g_assert(data->qiv); v = qmp_input_get_visitor(data->qiv); - g_assert(v != NULL); + g_assert(v); return v; } +static GCC_FMT_ATTR(2, 3) +Visitor *visitor_input_test_init(TestInputVisitorData *data, + const char *json_string, ...) +{ + Visitor *v; + va_list ap; + + va_start(ap, json_string); + v = visitor_input_test_init_internal(data, json_string, &ap); + va_end(ap); + return v; +} + /* similar to visitor_input_test_init(), but does not expect a string * literal/format json_string argument and so can be used for * programatically generated strings (and we can't pass in programatically @@ -71,32 +82,18 @@ Visitor *visitor_input_test_init(TestInputVisitorData *data, static Visitor *visitor_input_test_init_raw(TestInputVisitorData *data, const char *json_string) { - Visitor *v; - - data->obj = qobject_from_json(json_string); - - g_assert(data->obj != NULL); - - data->qiv = qmp_input_visitor_new(data->obj); - g_assert(data->qiv != NULL); - - v = qmp_input_get_visitor(data->qiv); - g_assert(v != NULL); - - return v; + return visitor_input_test_init_internal(data, json_string, NULL); } static void test_visitor_in_int(TestInputVisitorData *data, const void *unused) { int64_t res = 0, value = -42; - Error *err = NULL; Visitor *v; v = visitor_input_test_init(data, "%" PRId64, value); - visit_type_int(v, &res, NULL, &err); - g_assert(!err); + visit_type_int(v, &res, NULL, &error_abort); g_assert_cmpint(res, ==, value); } @@ -114,21 +111,18 @@ static void test_visitor_in_int_overflow(TestInputVisitorData *data, v = visitor_input_test_init(data, "%f", DBL_MAX); visit_type_int(v, &res, NULL, &err); - g_assert(err); - error_free(err); + error_free_or_abort(&err); } static void test_visitor_in_bool(TestInputVisitorData *data, const void *unused) { - Error *err = NULL; bool res = false; Visitor *v; v = visitor_input_test_init(data, "true"); - visit_type_bool(v, &res, NULL, &err); - g_assert(!err); + visit_type_bool(v, &res, NULL, &error_abort); g_assert_cmpint(res, ==, true); } @@ -136,13 +130,11 @@ static void test_visitor_in_number(TestInputVisitorData *data, const void *unused) { double res = 0, value = 3.14; - Error *err = NULL; Visitor *v; v = visitor_input_test_init(data, "%f", value); - visit_type_number(v, &res, NULL, &err); - g_assert(!err); + visit_type_number(v, &res, NULL, &error_abort); g_assert_cmpfloat(res, ==, value); } @@ -150,13 +142,11 @@ static void test_visitor_in_string(TestInputVisitorData *data, const void *unused) { char *res = NULL, *value = (char *) "Q E M U"; - Error *err = NULL; Visitor *v; v = visitor_input_test_init(data, "%s", value); - visit_type_str(v, &res, NULL, &err); - g_assert(!err); + visit_type_str(v, &res, NULL, &error_abort); g_assert_cmpstr(res, ==, value); g_free(res); @@ -165,7 +155,6 @@ static void test_visitor_in_string(TestInputVisitorData *data, static void test_visitor_in_enum(TestInputVisitorData *data, const void *unused) { - Error *err = NULL; Visitor *v; EnumOne i; @@ -174,63 +163,21 @@ static void test_visitor_in_enum(TestInputVisitorData *data, v = visitor_input_test_init(data, "%s", EnumOne_lookup[i]); - visit_type_EnumOne(v, &res, NULL, &err); - g_assert(!err); + visit_type_EnumOne(v, &res, NULL, &error_abort); g_assert_cmpint(i, ==, res); - - visitor_input_teardown(data, NULL); } - - data->obj = NULL; - data->qiv = NULL; } -typedef struct TestStruct -{ - int64_t integer; - bool boolean; - char *string; -} TestStruct; - -static void visit_type_TestStruct(Visitor *v, TestStruct **obj, - const char *name, Error **errp) -{ - Error *err = NULL; - - visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct), - &err); - if (err) { - goto out; - } - visit_type_int(v, &(*obj)->integer, "integer", &err); - if (err) { - goto out_end; - } - visit_type_bool(v, &(*obj)->boolean, "boolean", &err); - if (err) { - goto out_end; - } - visit_type_str(v, &(*obj)->string, "string", &err); - -out_end: - error_propagate(errp, err); - err = NULL; - visit_end_struct(v, &err); -out: - error_propagate(errp, err); -} static void test_visitor_in_struct(TestInputVisitorData *data, const void *unused) { TestStruct *p = NULL; - Error *err = NULL; Visitor *v; v = visitor_input_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo' }"); - visit_type_TestStruct(v, &p, NULL, &err); - g_assert(!err); + visit_type_TestStruct(v, &p, NULL, &error_abort); g_assert_cmpint(p->integer, ==, -42); g_assert(p->boolean == true); g_assert_cmpstr(p->string, ==, "foo"); @@ -239,17 +186,10 @@ static void test_visitor_in_struct(TestInputVisitorData *data, g_free(p); } -static void check_and_free_str(char *str, const char *cmp) -{ - g_assert_cmpstr(str, ==, cmp); - g_free(str); -} - static void test_visitor_in_struct_nested(TestInputVisitorData *data, const void *unused) { UserDefTwo *udp = NULL; - Error *err = NULL; Visitor *v; v = visitor_input_test_init(data, "{ 'string0': 'string0', " @@ -257,34 +197,28 @@ static void test_visitor_in_struct_nested(TestInputVisitorData *data, "'dict2': { 'userdef': { 'integer': 42, " "'string': 'string' }, 'string': 'string2'}}}"); - visit_type_UserDefTwo(v, &udp, NULL, &err); - g_assert(!err); + visit_type_UserDefTwo(v, &udp, NULL, &error_abort); - check_and_free_str(udp->string0, "string0"); - check_and_free_str(udp->dict1->string1, "string1"); - g_assert_cmpint(udp->dict1->dict2->userdef->base->integer, ==, 42); - check_and_free_str(udp->dict1->dict2->userdef->string, "string"); - check_and_free_str(udp->dict1->dict2->string, "string2"); + g_assert_cmpstr(udp->string0, ==, "string0"); + g_assert_cmpstr(udp->dict1->string1, ==, "string1"); + g_assert_cmpint(udp->dict1->dict2->userdef->integer, ==, 42); + g_assert_cmpstr(udp->dict1->dict2->userdef->string, ==, "string"); + g_assert_cmpstr(udp->dict1->dict2->string, ==, "string2"); g_assert(udp->dict1->has_dict3 == false); - g_free(udp->dict1->dict2->userdef); - g_free(udp->dict1->dict2); - g_free(udp->dict1); - g_free(udp); + qapi_free_UserDefTwo(udp); } static void test_visitor_in_list(TestInputVisitorData *data, const void *unused) { UserDefOneList *item, *head = NULL; - Error *err = NULL; Visitor *v; int i; v = visitor_input_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44 } ]"); - visit_type_UserDefOneList(v, &head, NULL, &err); - g_assert(!err); + visit_type_UserDefOneList(v, &head, NULL, &error_abort); g_assert(head != NULL); for (i = 0, item = head; item; item = item->next, i++) { @@ -292,17 +226,22 @@ static void test_visitor_in_list(TestInputVisitorData *data, snprintf(string, sizeof(string), "string%d", i); g_assert_cmpstr(item->value->string, ==, string); - g_assert_cmpint(item->value->base->integer, ==, 42 + i); + g_assert_cmpint(item->value->integer, ==, 42 + i); } qapi_free_UserDefOneList(head); + head = NULL; + + /* An empty list is valid */ + v = visitor_input_test_init(data, "[]"); + visit_type_UserDefOneList(v, &head, NULL, &error_abort); + g_assert(!head); } static void test_visitor_in_any(TestInputVisitorData *data, const void *unused) { QObject *res = NULL; - Error *err = NULL; Visitor *v; QInt *qint; QBool *qbool; @@ -311,16 +250,14 @@ static void test_visitor_in_any(TestInputVisitorData *data, QObject *qobj; v = visitor_input_test_init(data, "-42"); - visit_type_any(v, &res, NULL, &err); - g_assert(!err); + visit_type_any(v, &res, NULL, &error_abort); qint = qobject_to_qint(res); g_assert(qint); g_assert_cmpint(qint_get_int(qint), ==, -42); qobject_decref(res); v = visitor_input_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo' }"); - visit_type_any(v, &res, NULL, &err); - g_assert(!err); + visit_type_any(v, &res, NULL, &error_abort); qdict = qobject_to_qdict(res); g_assert(qdict && qdict_size(qdict) == 3); qobj = qdict_get(qdict, "integer"); @@ -345,8 +282,8 @@ static void test_visitor_in_union_flat(TestInputVisitorData *data, const void *unused) { Visitor *v; - Error *err = NULL; UserDefFlatUnion *tmp; + UserDefUnionBase *base; v = visitor_input_test_init(data, "{ 'enum1': 'value1', " @@ -354,12 +291,15 @@ static void test_visitor_in_union_flat(TestInputVisitorData *data, "'string': 'str', " "'boolean': true }"); - visit_type_UserDefFlatUnion(v, &tmp, NULL, &err); - g_assert(err == NULL); + visit_type_UserDefFlatUnion(v, &tmp, NULL, &error_abort); g_assert_cmpint(tmp->enum1, ==, ENUM_ONE_VALUE1); g_assert_cmpstr(tmp->string, ==, "str"); g_assert_cmpint(tmp->integer, ==, 41); - g_assert_cmpint(tmp->value1->boolean, ==, true); + g_assert_cmpint(tmp->u.value1->boolean, ==, true); + + base = qapi_UserDefFlatUnion_base(tmp); + g_assert(&base->enum1 == &tmp->enum1); + qapi_free_UserDefFlatUnion(tmp); } @@ -372,25 +312,20 @@ static void test_visitor_in_alternate(TestInputVisitorData *data, v = visitor_input_test_init(data, "42"); visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort); - g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_I); - g_assert_cmpint(tmp->i, ==, 42); + g_assert_cmpint(tmp->type, ==, USER_DEF_ALTERNATE_KIND_I); + g_assert_cmpint(tmp->u.i, ==, 42); qapi_free_UserDefAlternate(tmp); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "'string'"); visit_type_UserDefAlternate(v, &tmp, NULL, &error_abort); - g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_S); - g_assert_cmpstr(tmp->s, ==, "string"); + g_assert_cmpint(tmp->type, ==, USER_DEF_ALTERNATE_KIND_S); + g_assert_cmpstr(tmp->u.s, ==, "string"); qapi_free_UserDefAlternate(tmp); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "false"); visit_type_UserDefAlternate(v, &tmp, NULL, &err); - g_assert(err); - error_free(err); - err = NULL; + error_free_or_abort(&err); qapi_free_UserDefAlternate(tmp); - visitor_input_teardown(data, NULL); } static void test_visitor_in_alternate_number(TestInputVisitorData *data, @@ -409,97 +344,77 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data, v = visitor_input_test_init(data, "42"); visit_type_AltStrBool(v, &asb, NULL, &err); - g_assert(err); - error_free(err); - err = NULL; + error_free_or_abort(&err); qapi_free_AltStrBool(asb); - visitor_input_teardown(data, NULL); /* FIXME: Order of alternate should not affect semantics; asn should * parse the same as ans */ v = visitor_input_test_init(data, "42"); visit_type_AltStrNum(v, &asn, NULL, &err); - /* FIXME g_assert_cmpint(asn->kind, == ALT_STR_NUM_KIND_N); */ - /* FIXME g_assert_cmpfloat(asn->n, ==, 42); */ - g_assert(err); - error_free(err); - err = NULL; + /* FIXME g_assert_cmpint(asn->type, == ALT_STR_NUM_KIND_N); */ + /* FIXME g_assert_cmpfloat(asn->u.n, ==, 42); */ + error_free_or_abort(&err); qapi_free_AltStrNum(asn); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "42"); visit_type_AltNumStr(v, &ans, NULL, &error_abort); - g_assert_cmpint(ans->kind, ==, ALT_NUM_STR_KIND_N); - g_assert_cmpfloat(ans->n, ==, 42); + g_assert_cmpint(ans->type, ==, ALT_NUM_STR_KIND_N); + g_assert_cmpfloat(ans->u.n, ==, 42); qapi_free_AltNumStr(ans); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "42"); visit_type_AltStrInt(v, &asi, NULL, &error_abort); - g_assert_cmpint(asi->kind, ==, ALT_STR_INT_KIND_I); - g_assert_cmpint(asi->i, ==, 42); + g_assert_cmpint(asi->type, ==, ALT_STR_INT_KIND_I); + g_assert_cmpint(asi->u.i, ==, 42); qapi_free_AltStrInt(asi); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "42"); visit_type_AltIntNum(v, &ain, NULL, &error_abort); - g_assert_cmpint(ain->kind, ==, ALT_INT_NUM_KIND_I); - g_assert_cmpint(ain->i, ==, 42); + g_assert_cmpint(ain->type, ==, ALT_INT_NUM_KIND_I); + g_assert_cmpint(ain->u.i, ==, 42); qapi_free_AltIntNum(ain); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "42"); visit_type_AltNumInt(v, &ani, NULL, &error_abort); - g_assert_cmpint(ani->kind, ==, ALT_NUM_INT_KIND_I); - g_assert_cmpint(ani->i, ==, 42); + g_assert_cmpint(ani->type, ==, ALT_NUM_INT_KIND_I); + g_assert_cmpint(ani->u.i, ==, 42); qapi_free_AltNumInt(ani); - visitor_input_teardown(data, NULL); /* Parsing a double */ v = visitor_input_test_init(data, "42.5"); visit_type_AltStrBool(v, &asb, NULL, &err); - g_assert(err); - error_free(err); - err = NULL; + error_free_or_abort(&err); qapi_free_AltStrBool(asb); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "42.5"); visit_type_AltStrNum(v, &asn, NULL, &error_abort); - g_assert_cmpint(asn->kind, ==, ALT_STR_NUM_KIND_N); - g_assert_cmpfloat(asn->n, ==, 42.5); + g_assert_cmpint(asn->type, ==, ALT_STR_NUM_KIND_N); + g_assert_cmpfloat(asn->u.n, ==, 42.5); qapi_free_AltStrNum(asn); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "42.5"); visit_type_AltNumStr(v, &ans, NULL, &error_abort); - g_assert_cmpint(ans->kind, ==, ALT_NUM_STR_KIND_N); - g_assert_cmpfloat(ans->n, ==, 42.5); + g_assert_cmpint(ans->type, ==, ALT_NUM_STR_KIND_N); + g_assert_cmpfloat(ans->u.n, ==, 42.5); qapi_free_AltNumStr(ans); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "42.5"); visit_type_AltStrInt(v, &asi, NULL, &err); - g_assert(err); - error_free(err); - err = NULL; + error_free_or_abort(&err); qapi_free_AltStrInt(asi); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "42.5"); visit_type_AltIntNum(v, &ain, NULL, &error_abort); - g_assert_cmpint(ain->kind, ==, ALT_INT_NUM_KIND_N); - g_assert_cmpfloat(ain->n, ==, 42.5); + g_assert_cmpint(ain->type, ==, ALT_INT_NUM_KIND_N); + g_assert_cmpfloat(ain->u.n, ==, 42.5); qapi_free_AltIntNum(ain); - visitor_input_teardown(data, NULL); v = visitor_input_test_init(data, "42.5"); visit_type_AltNumInt(v, &ani, NULL, &error_abort); - g_assert_cmpint(ani->kind, ==, ALT_NUM_INT_KIND_N); - g_assert_cmpfloat(ani->n, ==, 42.5); + g_assert_cmpint(ani->type, ==, ALT_NUM_INT_KIND_N); + g_assert_cmpfloat(ani->u.n, ==, 42.5); qapi_free_AltNumInt(ani); - visitor_input_teardown(data, NULL); } static void test_native_list_integer_helper(TestInputVisitorData *data, @@ -507,7 +422,6 @@ static void test_native_list_integer_helper(TestInputVisitorData *data, UserDefNativeListUnionKind kind) { UserDefNativeListUnion *cvalue = NULL; - Error *err = NULL; Visitor *v; GString *gstr_list = g_string_new(""); GString *gstr_union = g_string_new(""); @@ -524,71 +438,70 @@ static void test_native_list_integer_helper(TestInputVisitorData *data, gstr_list->str); v = visitor_input_test_init_raw(data, gstr_union->str); - visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &err); - g_assert(err == NULL); + visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &error_abort); g_assert(cvalue != NULL); - g_assert_cmpint(cvalue->kind, ==, kind); + g_assert_cmpint(cvalue->type, ==, kind); switch (kind) { case USER_DEF_NATIVE_LIST_UNION_KIND_INTEGER: { intList *elem = NULL; - for (i = 0, elem = cvalue->integer; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.integer; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, i); } break; } case USER_DEF_NATIVE_LIST_UNION_KIND_S8: { int8List *elem = NULL; - for (i = 0, elem = cvalue->s8; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.s8; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, i); } break; } case USER_DEF_NATIVE_LIST_UNION_KIND_S16: { int16List *elem = NULL; - for (i = 0, elem = cvalue->s16; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.s16; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, i); } break; } case USER_DEF_NATIVE_LIST_UNION_KIND_S32: { int32List *elem = NULL; - for (i = 0, elem = cvalue->s32; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.s32; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, i); } break; } case USER_DEF_NATIVE_LIST_UNION_KIND_S64: { int64List *elem = NULL; - for (i = 0, elem = cvalue->s64; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.s64; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, i); } break; } case USER_DEF_NATIVE_LIST_UNION_KIND_U8: { uint8List *elem = NULL; - for (i = 0, elem = cvalue->u8; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.u8; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, i); } break; } case USER_DEF_NATIVE_LIST_UNION_KIND_U16: { uint16List *elem = NULL; - for (i = 0, elem = cvalue->u16; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.u16; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, i); } break; } case USER_DEF_NATIVE_LIST_UNION_KIND_U32: { uint32List *elem = NULL; - for (i = 0, elem = cvalue->u32; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.u32; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, i); } break; } case USER_DEF_NATIVE_LIST_UNION_KIND_U64: { uint64List *elem = NULL; - for (i = 0, elem = cvalue->u64; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.u64; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, i); } break; @@ -670,7 +583,6 @@ static void test_visitor_in_native_list_bool(TestInputVisitorData *data, { UserDefNativeListUnion *cvalue = NULL; boolList *elem = NULL; - Error *err = NULL; Visitor *v; GString *gstr_list = g_string_new(""); GString *gstr_union = g_string_new(""); @@ -687,12 +599,11 @@ static void test_visitor_in_native_list_bool(TestInputVisitorData *data, gstr_list->str); v = visitor_input_test_init_raw(data, gstr_union->str); - visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &err); - g_assert(err == NULL); + visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &error_abort); g_assert(cvalue != NULL); - g_assert_cmpint(cvalue->kind, ==, USER_DEF_NATIVE_LIST_UNION_KIND_BOOLEAN); + g_assert_cmpint(cvalue->type, ==, USER_DEF_NATIVE_LIST_UNION_KIND_BOOLEAN); - for (i = 0, elem = cvalue->boolean; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.boolean; elem; elem = elem->next, i++) { g_assert_cmpint(elem->value, ==, (i % 3 == 0) ? 1 : 0); } @@ -706,7 +617,6 @@ static void test_visitor_in_native_list_string(TestInputVisitorData *data, { UserDefNativeListUnion *cvalue = NULL; strList *elem = NULL; - Error *err = NULL; Visitor *v; GString *gstr_list = g_string_new(""); GString *gstr_union = g_string_new(""); @@ -722,12 +632,11 @@ static void test_visitor_in_native_list_string(TestInputVisitorData *data, gstr_list->str); v = visitor_input_test_init_raw(data, gstr_union->str); - visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &err); - g_assert(err == NULL); + visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &error_abort); g_assert(cvalue != NULL); - g_assert_cmpint(cvalue->kind, ==, USER_DEF_NATIVE_LIST_UNION_KIND_STRING); + g_assert_cmpint(cvalue->type, ==, USER_DEF_NATIVE_LIST_UNION_KIND_STRING); - for (i = 0, elem = cvalue->string; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.string; elem; elem = elem->next, i++) { gchar str[8]; sprintf(str, "%d", i); g_assert_cmpstr(elem->value, ==, str); @@ -745,7 +654,6 @@ static void test_visitor_in_native_list_number(TestInputVisitorData *data, { UserDefNativeListUnion *cvalue = NULL; numberList *elem = NULL; - Error *err = NULL; Visitor *v; GString *gstr_list = g_string_new(""); GString *gstr_union = g_string_new(""); @@ -761,12 +669,11 @@ static void test_visitor_in_native_list_number(TestInputVisitorData *data, gstr_list->str); v = visitor_input_test_init_raw(data, gstr_union->str); - visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &err); - g_assert(err == NULL); + visit_type_UserDefNativeListUnion(v, &cvalue, NULL, &error_abort); g_assert(cvalue != NULL); - g_assert_cmpint(cvalue->kind, ==, USER_DEF_NATIVE_LIST_UNION_KIND_NUMBER); + g_assert_cmpint(cvalue->type, ==, USER_DEF_NATIVE_LIST_UNION_KIND_NUMBER); - for (i = 0, elem = cvalue->number; elem; elem = elem->next, i++) { + for (i = 0, elem = cvalue->u.number; elem; elem = elem->next, i++) { GString *double_expected = g_string_new(""); GString *double_actual = g_string_new(""); @@ -797,18 +704,69 @@ static void test_visitor_in_errors(TestInputVisitorData *data, TestStruct *p = NULL; Error *err = NULL; Visitor *v; + strList *q = NULL; - v = visitor_input_test_init(data, "{ 'integer': false, 'boolean': 'foo', 'string': -42 }"); + v = visitor_input_test_init(data, "{ 'integer': false, 'boolean': 'foo', " + "'string': -42 }"); visit_type_TestStruct(v, &p, NULL, &err); - g_assert(err); + error_free_or_abort(&err); /* FIXME - a failed parse should not leave a partially-allocated p * for us to clean up; this could cause callers to leak memory. */ g_assert(p->string == NULL); - error_free(err); g_free(p->string); g_free(p); + + v = visitor_input_test_init(data, "[ '1', '2', false, '3' ]"); + visit_type_strList(v, &q, NULL, &err); + error_free_or_abort(&err); + assert(q); + qapi_free_strList(q); +} + +static void test_visitor_in_wrong_type(TestInputVisitorData *data, + const void *unused) +{ + TestStruct *p = NULL; + Visitor *v; + strList *q = NULL; + int64_t i; + Error *err = NULL; + + /* Make sure arrays and structs cannot be confused */ + + v = visitor_input_test_init(data, "[]"); + visit_type_TestStruct(v, &p, NULL, &err); + error_free_or_abort(&err); + g_assert(!p); + + v = visitor_input_test_init(data, "{}"); + visit_type_strList(v, &q, NULL, &err); + error_free_or_abort(&err); + assert(!q); + + /* Make sure primitives and struct cannot be confused */ + + v = visitor_input_test_init(data, "1"); + visit_type_TestStruct(v, &p, NULL, &err); + error_free_or_abort(&err); + g_assert(!p); + + v = visitor_input_test_init(data, "{}"); + visit_type_int(v, &i, NULL, &err); + error_free_or_abort(&err); + + /* Make sure primitives and arrays cannot be confused */ + + v = visitor_input_test_init(data, "1"); + visit_type_strList(v, &q, NULL, &err); + error_free_or_abort(&err); + assert(!q); + + v = visitor_input_test_init(data, "[]"); + visit_type_int(v, &i, NULL, &err); + error_free_or_abort(&err); } int main(int argc, char **argv) @@ -843,6 +801,8 @@ int main(int argc, char **argv) &in_visitor_data, test_visitor_in_alternate); input_visitor_test_add("/visitor/input/errors", &in_visitor_data, test_visitor_in_errors); + input_visitor_test_add("/visitor/input/wrong-type", + &in_visitor_data, test_visitor_in_wrong_type); input_visitor_test_add("/visitor/input/alternate-number", &in_visitor_data, test_visitor_in_alternate_number); input_visitor_test_add("/visitor/input/native_list/int", diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c index c84002e2f2..0d0c85989a 100644 --- a/tests/test-qmp-output-visitor.c +++ b/tests/test-qmp-output-visitor.c @@ -45,11 +45,9 @@ static void test_visitor_out_int(TestOutputVisitorData *data, const void *unused) { int64_t value = -42; - Error *err = NULL; QObject *obj; - visit_type_int(data->ov, &value, NULL, &err); - g_assert(!err); + visit_type_int(data->ov, &value, NULL, &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); @@ -62,12 +60,10 @@ static void test_visitor_out_int(TestOutputVisitorData *data, static void test_visitor_out_bool(TestOutputVisitorData *data, const void *unused) { - Error *err = NULL; bool value = true; QObject *obj; - visit_type_bool(data->ov, &value, NULL, &err); - g_assert(!err); + visit_type_bool(data->ov, &value, NULL, &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); @@ -81,11 +77,9 @@ static void test_visitor_out_number(TestOutputVisitorData *data, const void *unused) { double value = 3.14; - Error *err = NULL; QObject *obj; - visit_type_number(data->ov, &value, NULL, &err); - g_assert(!err); + visit_type_number(data->ov, &value, NULL, &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); @@ -99,11 +93,9 @@ static void test_visitor_out_string(TestOutputVisitorData *data, const void *unused) { char *string = (char *) "Q E M U"; - Error *err = NULL; QObject *obj; - visit_type_str(data->ov, &string, NULL, &err); - g_assert(!err); + visit_type_str(data->ov, &string, NULL, &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); @@ -117,12 +109,10 @@ static void test_visitor_out_no_string(TestOutputVisitorData *data, const void *unused) { char *string = NULL; - Error *err = NULL; QObject *obj; /* A null string should return "" */ - visit_type_str(data->ov, &string, NULL, &err); - g_assert(!err); + visit_type_str(data->ov, &string, NULL, &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); @@ -135,13 +125,11 @@ static void test_visitor_out_no_string(TestOutputVisitorData *data, static void test_visitor_out_enum(TestOutputVisitorData *data, const void *unused) { - Error *err = NULL; QObject *obj; EnumOne i; for (i = 0; i < ENUM_ONE_MAX; i++) { - visit_type_EnumOne(data->ov, &i, "unused", &err); - g_assert(!err); + visit_type_EnumOne(data->ov, &i, "unused", &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); @@ -166,41 +154,6 @@ static void test_visitor_out_enum_errors(TestOutputVisitorData *data, } } -typedef struct TestStruct -{ - int64_t integer; - bool boolean; - char *string; -} TestStruct; - -static void visit_type_TestStruct(Visitor *v, TestStruct **obj, - const char *name, Error **errp) -{ - Error *err = NULL; - - visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct), - &err); - if (err) { - goto out; - } - - visit_type_int(v, &(*obj)->integer, "integer", &err); - if (err) { - goto out_end; - } - visit_type_bool(v, &(*obj)->boolean, "boolean", &err); - if (err) { - goto out_end; - } - visit_type_str(v, &(*obj)->string, "string", &err); - -out_end: - error_propagate(errp, err); - err = NULL; - visit_end_struct(v, &err); -out: - error_propagate(errp, err); -} static void test_visitor_out_struct(TestOutputVisitorData *data, const void *unused) @@ -209,12 +162,10 @@ static void test_visitor_out_struct(TestOutputVisitorData *data, .boolean = false, .string = (char *) "foo"}; TestStruct *p = &test_struct; - Error *err = NULL; QObject *obj; QDict *qdict; - visit_type_TestStruct(data->ov, &p, NULL, &err); - g_assert(!err); + visit_type_TestStruct(data->ov, &p, NULL, &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); @@ -233,7 +184,6 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data, const void *unused) { int64_t value = 42; - Error *err = NULL; UserDefTwo *ud2; QObject *obj; QDict *qdict, *dict1, *dict2, *dict3, *userdef; @@ -250,20 +200,17 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data, ud2->dict1->dict2 = g_malloc0(sizeof(*ud2->dict1->dict2)); ud2->dict1->dict2->userdef = g_new0(UserDefOne, 1); ud2->dict1->dict2->userdef->string = g_strdup(string); - ud2->dict1->dict2->userdef->base = g_new0(UserDefZero, 1); - ud2->dict1->dict2->userdef->base->integer = value; + ud2->dict1->dict2->userdef->integer = value; ud2->dict1->dict2->string = g_strdup(strings[2]); ud2->dict1->dict3 = g_malloc0(sizeof(*ud2->dict1->dict3)); ud2->dict1->has_dict3 = true; ud2->dict1->dict3->userdef = g_new0(UserDefOne, 1); ud2->dict1->dict3->userdef->string = g_strdup(string); - ud2->dict1->dict3->userdef->base = g_new0(UserDefZero, 1); - ud2->dict1->dict3->userdef->base->integer = value; + ud2->dict1->dict3->userdef->integer = value; ud2->dict1->dict3->string = g_strdup(strings[3]); - visit_type_UserDefTwo(data->ov, &ud2, "unused", &err); - g_assert(!err); + visit_type_UserDefTwo(data->ov, &ud2, "unused", &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); @@ -301,8 +248,8 @@ static void test_visitor_out_struct_errors(TestOutputVisitorData *data, const void *unused) { EnumOne bad_values[] = { ENUM_ONE_MAX, -1 }; - UserDefZero b; - UserDefOne u = { .base = &b }, *pu = &u; + UserDefOne u = {0}; + UserDefOne *pu = &u; Error *err; int i; @@ -316,57 +263,33 @@ static void test_visitor_out_struct_errors(TestOutputVisitorData *data, } } -typedef struct TestStructList -{ - union { - TestStruct *value; - uint64_t padding; - }; - struct TestStructList *next; -} TestStructList; - -static void visit_type_TestStructList(Visitor *v, TestStructList **obj, - const char *name, Error **errp) -{ - GenericList *i, **head = (GenericList **)obj; - - visit_start_list(v, name, errp); - - for (*head = i = visit_next_list(v, head, errp); i; i = visit_next_list(v, &i, errp)) { - TestStructList *native_i = (TestStructList *)i; - visit_type_TestStruct(v, &native_i->value, NULL, errp); - } - - visit_end_list(v, errp); -} static void test_visitor_out_list(TestOutputVisitorData *data, const void *unused) { - char *value_str = (char *) "list value"; + const char *value_str = "list value"; TestStructList *p, *head = NULL; const int max_items = 10; bool value_bool = true; int value_int = 10; - Error *err = NULL; QListEntry *entry; QObject *obj; QList *qlist; int i; + /* Build the list in reverse order... */ for (i = 0; i < max_items; i++) { p = g_malloc0(sizeof(*p)); p->value = g_malloc0(sizeof(*p->value)); - p->value->integer = value_int; + p->value->integer = value_int + (max_items - i - 1); p->value->boolean = value_bool; - p->value->string = value_str; + p->value->string = g_strdup(value_str); p->next = head; head = p; } - visit_type_TestStructList(data->ov, &head, NULL, &err); - g_assert(!err); + visit_type_TestStructList(data->ov, &head, NULL, &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); @@ -375,6 +298,7 @@ static void test_visitor_out_list(TestOutputVisitorData *data, qlist = qobject_to_qlist(obj); g_assert(!qlist_empty(qlist)); + /* ...and ensure that the visitor sees it in order */ i = 0; QLIST_FOREACH_ENTRY(qlist, entry) { QDict *qdict; @@ -382,7 +306,7 @@ static void test_visitor_out_list(TestOutputVisitorData *data, g_assert(qobject_type(entry->value) == QTYPE_QDICT); qdict = qobject_to_qdict(entry->value); g_assert_cmpint(qdict_size(qdict), ==, 3); - g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, value_int); + g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, value_int + i); g_assert_cmpint(qdict_get_bool(qdict, "boolean"), ==, value_bool); g_assert_cmpstr(qdict_get_str(qdict, "string"), ==, value_str); i++; @@ -390,13 +314,7 @@ static void test_visitor_out_list(TestOutputVisitorData *data, g_assert_cmpint(i, ==, max_items); QDECREF(qlist); - - for (p = head; p;) { - TestStructList *tmp = p->next; - g_free(p->value); - g_free(p); - p = tmp; - } + qapi_free_TestStructList(head); } static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data, @@ -416,8 +334,7 @@ static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data, p->value->dict1->dict2 = g_new0(UserDefTwoDictDict, 1); p->value->dict1->dict2->userdef = g_new0(UserDefOne, 1); p->value->dict1->dict2->userdef->string = g_strdup(string); - p->value->dict1->dict2->userdef->base = g_new0(UserDefZero, 1); - p->value->dict1->dict2->userdef->base->integer = 42; + p->value->dict1->dict2->userdef->integer = 42; p->value->dict1->dict2->string = g_strdup(string); p->value->dict1->has_dict3 = false; @@ -432,7 +349,6 @@ static void test_visitor_out_any(TestOutputVisitorData *data, const void *unused) { QObject *qobj; - Error *err = NULL; QInt *qint; QBool *qbool; QString *qstring; @@ -440,8 +356,7 @@ static void test_visitor_out_any(TestOutputVisitorData *data, QObject *obj; qobj = QOBJECT(qint_from_int(-42)); - visit_type_any(data->ov, &qobj, NULL, &err); - g_assert(!err); + visit_type_any(data->ov, &qobj, NULL, &error_abort); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); g_assert(qobject_type(obj) == QTYPE_QINT); @@ -454,8 +369,8 @@ static void test_visitor_out_any(TestOutputVisitorData *data, qdict_put(qdict, "boolean", qbool_from_bool(true)); qdict_put(qdict, "string", qstring_from_str("foo")); qobj = QOBJECT(qdict); - visit_type_any(data->ov, &qobj, NULL, &err); - g_assert(!err); + visit_type_any(data->ov, &qobj, NULL, &error_abort); + qobject_decref(qobj); obj = qmp_output_get_qobject(data->qov); g_assert(obj != NULL); qdict = qobject_to_qdict(obj); @@ -476,7 +391,6 @@ static void test_visitor_out_any(TestOutputVisitorData *data, g_assert(qstring); g_assert_cmpstr(qstring_get_str(qstring), ==, "foo"); qobject_decref(obj); - qobject_decref(qobj); } static void test_visitor_out_union_flat(TestOutputVisitorData *data, @@ -485,17 +399,14 @@ static void test_visitor_out_union_flat(TestOutputVisitorData *data, QObject *arg; QDict *qdict; - Error *err = NULL; - UserDefFlatUnion *tmp = g_malloc0(sizeof(UserDefFlatUnion)); tmp->enum1 = ENUM_ONE_VALUE1; tmp->string = g_strdup("str"); - tmp->value1 = g_malloc0(sizeof(UserDefA)); - /* TODO when generator bug is fixed: tmp->integer = 41; */ - tmp->value1->boolean = true; + tmp->u.value1 = g_malloc0(sizeof(UserDefA)); + tmp->integer = 41; + tmp->u.value1->boolean = true; - visit_type_UserDefFlatUnion(data->ov, &tmp, NULL, &err); - g_assert(err == NULL); + visit_type_UserDefFlatUnion(data->ov, &tmp, NULL, &error_abort); arg = qmp_output_get_qobject(data->qov); g_assert(qobject_type(arg) == QTYPE_QDICT); @@ -503,7 +414,7 @@ static void test_visitor_out_union_flat(TestOutputVisitorData *data, g_assert_cmpstr(qdict_get_str(qdict, "enum1"), ==, "value1"); g_assert_cmpstr(qdict_get_str(qdict, "string"), ==, "str"); - /* TODO g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 41); */ + g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 41); g_assert_cmpint(qdict_get_bool(qdict, "boolean"), ==, true); qapi_free_UserDefFlatUnion(tmp); @@ -514,20 +425,33 @@ static void test_visitor_out_alternate(TestOutputVisitorData *data, const void *unused) { QObject *arg; - Error *err = NULL; + UserDefAlternate *tmp; - UserDefAlternate *tmp = g_malloc0(sizeof(UserDefAlternate)); - tmp->kind = USER_DEF_ALTERNATE_KIND_I; - tmp->i = 42; + tmp = g_new0(UserDefAlternate, 1); + tmp->type = USER_DEF_ALTERNATE_KIND_I; + tmp->u.i = 42; - visit_type_UserDefAlternate(data->ov, &tmp, NULL, &err); - g_assert(err == NULL); + visit_type_UserDefAlternate(data->ov, &tmp, NULL, &error_abort); arg = qmp_output_get_qobject(data->qov); g_assert(qobject_type(arg) == QTYPE_QINT); g_assert_cmpint(qint_get_int(qobject_to_qint(arg)), ==, 42); qapi_free_UserDefAlternate(tmp); + qobject_decref(arg); + + tmp = g_new0(UserDefAlternate, 1); + tmp->type = USER_DEF_ALTERNATE_KIND_S; + tmp->u.s = g_strdup("hello"); + + visit_type_UserDefAlternate(data->ov, &tmp, NULL, &error_abort); + arg = qmp_output_get_qobject(data->qov); + + g_assert(qobject_type(arg) == QTYPE_QSTRING); + g_assert_cmpstr(qstring_get_str(qobject_to_qstring(arg)), ==, "hello"); + + qapi_free_UserDefAlternate(tmp); + qobject_decref(arg); } static void test_visitor_out_empty(TestOutputVisitorData *data, @@ -543,9 +467,9 @@ static void test_visitor_out_empty(TestOutputVisitorData *data, static void init_native_list(UserDefNativeListUnion *cvalue) { int i; - switch (cvalue->kind) { + switch (cvalue->type) { case USER_DEF_NATIVE_LIST_UNION_KIND_INTEGER: { - intList **list = &cvalue->integer; + intList **list = &cvalue->u.integer; for (i = 0; i < 32; i++) { *list = g_new0(intList, 1); (*list)->value = i; @@ -555,7 +479,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_S8: { - int8List **list = &cvalue->s8; + int8List **list = &cvalue->u.s8; for (i = 0; i < 32; i++) { *list = g_new0(int8List, 1); (*list)->value = i; @@ -565,7 +489,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_S16: { - int16List **list = &cvalue->s16; + int16List **list = &cvalue->u.s16; for (i = 0; i < 32; i++) { *list = g_new0(int16List, 1); (*list)->value = i; @@ -575,7 +499,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_S32: { - int32List **list = &cvalue->s32; + int32List **list = &cvalue->u.s32; for (i = 0; i < 32; i++) { *list = g_new0(int32List, 1); (*list)->value = i; @@ -585,7 +509,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_S64: { - int64List **list = &cvalue->s64; + int64List **list = &cvalue->u.s64; for (i = 0; i < 32; i++) { *list = g_new0(int64List, 1); (*list)->value = i; @@ -595,7 +519,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_U8: { - uint8List **list = &cvalue->u8; + uint8List **list = &cvalue->u.u8; for (i = 0; i < 32; i++) { *list = g_new0(uint8List, 1); (*list)->value = i; @@ -605,7 +529,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_U16: { - uint16List **list = &cvalue->u16; + uint16List **list = &cvalue->u.u16; for (i = 0; i < 32; i++) { *list = g_new0(uint16List, 1); (*list)->value = i; @@ -615,7 +539,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_U32: { - uint32List **list = &cvalue->u32; + uint32List **list = &cvalue->u.u32; for (i = 0; i < 32; i++) { *list = g_new0(uint32List, 1); (*list)->value = i; @@ -625,7 +549,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_U64: { - uint64List **list = &cvalue->u64; + uint64List **list = &cvalue->u.u64; for (i = 0; i < 32; i++) { *list = g_new0(uint64List, 1); (*list)->value = i; @@ -635,7 +559,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_BOOLEAN: { - boolList **list = &cvalue->boolean; + boolList **list = &cvalue->u.boolean; for (i = 0; i < 32; i++) { *list = g_new0(boolList, 1); (*list)->value = (i % 3 == 0); @@ -645,7 +569,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_STRING: { - strList **list = &cvalue->string; + strList **list = &cvalue->u.string; for (i = 0; i < 32; i++) { *list = g_new0(strList, 1); (*list)->value = g_strdup_printf("%d", i); @@ -655,7 +579,7 @@ static void init_native_list(UserDefNativeListUnion *cvalue) break; } case USER_DEF_NATIVE_LIST_UNION_KIND_NUMBER: { - numberList **list = &cvalue->number; + numberList **list = &cvalue->u.number; for (i = 0; i < 32; i++) { *list = g_new0(numberList, 1); (*list)->value = (double)i / 3; @@ -761,17 +685,15 @@ static void test_native_list(TestOutputVisitorData *data, UserDefNativeListUnionKind kind) { UserDefNativeListUnion *cvalue = g_new0(UserDefNativeListUnion, 1); - Error *err = NULL; QObject *obj; - cvalue->kind = kind; + cvalue->type = kind; init_native_list(cvalue); - visit_type_UserDefNativeListUnion(data->ov, &cvalue, NULL, &err); - g_assert(err == NULL); + visit_type_UserDefNativeListUnion(data->ov, &cvalue, NULL, &error_abort); obj = qmp_output_get_qobject(data->qov); - check_native_list(obj, cvalue->kind); + check_native_list(obj, cvalue->type); qapi_free_UserDefNativeListUnion(cvalue); qobject_decref(obj); } diff --git a/tests/test-timed-average.c b/tests/test-timed-average.c new file mode 100644 index 0000000000..a049799b80 --- /dev/null +++ b/tests/test-timed-average.c @@ -0,0 +1,90 @@ +/* + * Timed average computation tests + * + * Copyright Nodalink, EURL. 2014 + * + * Authors: + * Benoît Canet <benoit.canet@nodalink.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include <glib.h> +#include <unistd.h> + +#include "qemu/timed-average.h" + +/* This is the clock for QEMU_CLOCK_VIRTUAL */ +static int64_t my_clock_value; + +int64_t cpu_get_clock(void) +{ + return my_clock_value; +} + +static void account(TimedAverage *ta) +{ + timed_average_account(ta, 1); + timed_average_account(ta, 5); + timed_average_account(ta, 2); + timed_average_account(ta, 4); + timed_average_account(ta, 3); +} + +static void test_average(void) +{ + TimedAverage ta; + uint64_t result; + int i; + + /* we will compute some average on a period of 1 second */ + timed_average_init(&ta, QEMU_CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND); + + result = timed_average_min(&ta); + g_assert(result == 0); + result = timed_average_avg(&ta); + g_assert(result == 0); + result = timed_average_max(&ta); + g_assert(result == 0); + + for (i = 0; i < 100; i++) { + account(&ta); + result = timed_average_min(&ta); + g_assert(result == 1); + result = timed_average_avg(&ta); + g_assert(result == 3); + result = timed_average_max(&ta); + g_assert(result == 5); + my_clock_value += NANOSECONDS_PER_SECOND / 10; + } + + my_clock_value += NANOSECONDS_PER_SECOND * 100; + + result = timed_average_min(&ta); + g_assert(result == 0); + result = timed_average_avg(&ta); + g_assert(result == 0); + result = timed_average_max(&ta); + g_assert(result == 0); + + for (i = 0; i < 100; i++) { + account(&ta); + result = timed_average_min(&ta); + g_assert(result == 1); + result = timed_average_avg(&ta); + g_assert(result == 3); + result = timed_average_max(&ta); + g_assert(result == 5); + my_clock_value += NANOSECONDS_PER_SECOND / 10; + } +} + +int main(int argc, char **argv) +{ + /* tests in the same order as the header function declarations */ + g_test_init(&argc, &argv, NULL); + g_test_add_func("/timed-average/average", test_average); + return g_test_run(); +} + diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c index fa86cae88a..9f67f9e003 100644 --- a/tests/test-visitor-serialization.c +++ b/tests/test-visitor-serialization.c @@ -186,40 +186,6 @@ static void visit_primitive_list(Visitor *v, void **native, Error **errp) } } -typedef struct TestStruct -{ - int64_t integer; - bool boolean; - char *string; -} TestStruct; - -static void visit_type_TestStruct(Visitor *v, TestStruct **obj, - const char *name, Error **errp) -{ - Error *err = NULL; - - visit_start_struct(v, (void **)obj, NULL, name, sizeof(TestStruct), &err); - if (err) { - goto out; - } - - visit_type_int(v, &(*obj)->integer, "integer", &err); - if (err) { - goto out_end; - } - visit_type_bool(v, &(*obj)->boolean, "boolean", &err); - if (err) { - goto out_end; - } - visit_type_str(v, &(*obj)->string, "string", &err); - -out_end: - error_propagate(errp, err); - err = NULL; - visit_end_struct(v, &err); -out: - error_propagate(errp, err); -} static TestStruct *struct_create(void) { @@ -258,15 +224,13 @@ static UserDefTwo *nested_struct_create(void) udnp->dict1->string1 = strdup("test_string1"); udnp->dict1->dict2 = g_malloc0(sizeof(*udnp->dict1->dict2)); udnp->dict1->dict2->userdef = g_new0(UserDefOne, 1); - udnp->dict1->dict2->userdef->base = g_new0(UserDefZero, 1); - udnp->dict1->dict2->userdef->base->integer = 42; + udnp->dict1->dict2->userdef->integer = 42; udnp->dict1->dict2->userdef->string = strdup("test_string"); udnp->dict1->dict2->string = strdup("test_string2"); udnp->dict1->dict3 = g_malloc0(sizeof(*udnp->dict1->dict3)); udnp->dict1->has_dict3 = true; udnp->dict1->dict3->userdef = g_new0(UserDefOne, 1); - udnp->dict1->dict3->userdef->base = g_new0(UserDefZero, 1); - udnp->dict1->dict3->userdef->base->integer = 43; + udnp->dict1->dict3->userdef->integer = 43; udnp->dict1->dict3->userdef->string = strdup("test_string"); udnp->dict1->dict3->string = strdup("test_string3"); return udnp; @@ -278,15 +242,15 @@ static void nested_struct_compare(UserDefTwo *udnp1, UserDefTwo *udnp2) g_assert(udnp2); g_assert_cmpstr(udnp1->string0, ==, udnp2->string0); g_assert_cmpstr(udnp1->dict1->string1, ==, udnp2->dict1->string1); - g_assert_cmpint(udnp1->dict1->dict2->userdef->base->integer, ==, - udnp2->dict1->dict2->userdef->base->integer); + g_assert_cmpint(udnp1->dict1->dict2->userdef->integer, ==, + udnp2->dict1->dict2->userdef->integer); g_assert_cmpstr(udnp1->dict1->dict2->userdef->string, ==, udnp2->dict1->dict2->userdef->string); g_assert_cmpstr(udnp1->dict1->dict2->string, ==, udnp2->dict1->dict2->string); g_assert(udnp1->dict1->has_dict3 == udnp2->dict1->has_dict3); - g_assert_cmpint(udnp1->dict1->dict3->userdef->base->integer, ==, - udnp2->dict1->dict3->userdef->base->integer); + g_assert_cmpint(udnp1->dict1->dict3->userdef->integer, ==, + udnp2->dict1->dict3->userdef->integer); g_assert_cmpstr(udnp1->dict1->dict3->userdef->string, ==, udnp2->dict1->dict3->userdef->string); g_assert_cmpstr(udnp1->dict1->dict3->string, ==, @@ -338,14 +302,13 @@ static void test_primitives(gconstpointer opaque) const SerializeOps *ops = args->ops; PrimitiveType *pt = args->test_data; PrimitiveType *pt_copy = g_malloc0(sizeof(*pt_copy)); - Error *err = NULL; void *serialize_data; pt_copy->type = pt->type; - ops->serialize(pt, &serialize_data, visit_primitive_type, &err); - ops->deserialize((void **)&pt_copy, serialize_data, visit_primitive_type, &err); + ops->serialize(pt, &serialize_data, visit_primitive_type, &error_abort); + ops->deserialize((void **)&pt_copy, serialize_data, visit_primitive_type, + &error_abort); - g_assert(err == NULL); g_assert(pt_copy != NULL); if (pt->type == PTYPE_STRING) { g_assert_cmpstr(pt->value.string, ==, pt_copy->value.string); @@ -381,7 +344,6 @@ static void test_primitive_lists(gconstpointer opaque) PrimitiveList pl = { .value = { NULL } }; PrimitiveList pl_copy = { .value = { NULL } }; PrimitiveList *pl_copy_ptr = &pl_copy; - Error *err = NULL; void *serialize_data; void *cur_head = NULL; int i; @@ -528,10 +490,11 @@ static void test_primitive_lists(gconstpointer opaque) } } - ops->serialize((void **)&pl, &serialize_data, visit_primitive_list, &err); - ops->deserialize((void **)&pl_copy_ptr, serialize_data, visit_primitive_list, &err); + ops->serialize((void **)&pl, &serialize_data, visit_primitive_list, + &error_abort); + ops->deserialize((void **)&pl_copy_ptr, serialize_data, + visit_primitive_list, &error_abort); - g_assert(err == NULL); i = 0; /* compare our deserialized list of primitives to the original */ @@ -688,10 +651,8 @@ static void test_primitive_lists(gconstpointer opaque) g_assert_cmpint(i, ==, 33); ops->cleanup(serialize_data); - dealloc_helper(&pl, visit_primitive_list, &err); - g_assert(!err); - dealloc_helper(&pl_copy, visit_primitive_list, &err); - g_assert(!err); + dealloc_helper(&pl, visit_primitive_list, &error_abort); + dealloc_helper(&pl_copy, visit_primitive_list, &error_abort); g_free(args); } @@ -701,13 +662,12 @@ static void test_struct(gconstpointer opaque) const SerializeOps *ops = args->ops; TestStruct *ts = struct_create(); TestStruct *ts_copy = NULL; - Error *err = NULL; void *serialize_data; - ops->serialize(ts, &serialize_data, visit_struct, &err); - ops->deserialize((void **)&ts_copy, serialize_data, visit_struct, &err); + ops->serialize(ts, &serialize_data, visit_struct, &error_abort); + ops->deserialize((void **)&ts_copy, serialize_data, visit_struct, + &error_abort); - g_assert(err == NULL); struct_compare(ts, ts_copy); struct_cleanup(ts); @@ -723,14 +683,12 @@ static void test_nested_struct(gconstpointer opaque) const SerializeOps *ops = args->ops; UserDefTwo *udnp = nested_struct_create(); UserDefTwo *udnp_copy = NULL; - Error *err = NULL; void *serialize_data; - ops->serialize(udnp, &serialize_data, visit_nested_struct, &err); + ops->serialize(udnp, &serialize_data, visit_nested_struct, &error_abort); ops->deserialize((void **)&udnp_copy, serialize_data, visit_nested_struct, - &err); + &error_abort); - g_assert(err == NULL); nested_struct_compare(udnp, udnp_copy); nested_struct_cleanup(udnp); @@ -745,7 +703,6 @@ static void test_nested_struct_list(gconstpointer opaque) TestArgs *args = (TestArgs *) opaque; const SerializeOps *ops = args->ops; UserDefTwoList *listp = NULL, *tmp, *tmp_copy, *listp_copy = NULL; - Error *err = NULL; void *serialize_data; int i = 0; @@ -756,11 +713,10 @@ static void test_nested_struct_list(gconstpointer opaque) listp = tmp; } - ops->serialize(listp, &serialize_data, visit_nested_struct_list, &err); + ops->serialize(listp, &serialize_data, visit_nested_struct_list, + &error_abort); ops->deserialize((void **)&listp_copy, serialize_data, - visit_nested_struct_list, &err); - - g_assert(err == NULL); + visit_nested_struct_list, &error_abort); tmp = listp; tmp_copy = listp_copy; diff --git a/tests/vhost-user-bridge.c b/tests/vhost-user-bridge.c new file mode 100644 index 0000000000..9fb09f1df4 --- /dev/null +++ b/tests/vhost-user-bridge.c @@ -0,0 +1,1398 @@ +/* + * Vhost User Bridge + * + * Copyright (c) 2015 Red Hat, Inc. + * + * Authors: + * Victor Kaplansky <victork@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * later. See the COPYING file in the top-level directory. + */ + +/* + * TODO: + * - main should get parameters from the command line. + * - implement all request handlers. Still not implemented: + * vubr_get_queue_num_exec() + * vubr_send_rarp_exec() + * - test for broken requests and virtqueue. + * - implement features defined by Virtio 1.0 spec. + * - support mergeable buffers and indirect descriptors. + * - implement clean shutdown. + * - implement non-blocking writes to UDP backend. + * - implement polling strategy. + * - implement clean starting/stopping of vq processing + * - implement clean starting/stopping of used and buffers + * dirty page logging. + */ + +#define _FILE_OFFSET_BITS 64 + +#include <stddef.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/unistd.h> +#include <sys/mman.h> +#include <sys/eventfd.h> +#include <arpa/inet.h> +#include <ctype.h> +#include <netdb.h> + +#include <linux/vhost.h> + +#include "qemu/atomic.h" +#include "standard-headers/linux/virtio_net.h" +#include "standard-headers/linux/virtio_ring.h" + +#define VHOST_USER_BRIDGE_DEBUG 1 + +#define DPRINT(...) \ + do { \ + if (VHOST_USER_BRIDGE_DEBUG) { \ + printf(__VA_ARGS__); \ + } \ + } while (0) + +typedef void (*CallbackFunc)(int sock, void *ctx); + +typedef struct Event { + void *ctx; + CallbackFunc callback; +} Event; + +typedef struct Dispatcher { + int max_sock; + fd_set fdset; + Event events[FD_SETSIZE]; +} Dispatcher; + +static void +vubr_die(const char *s) +{ + perror(s); + exit(1); +} + +static int +dispatcher_init(Dispatcher *dispr) +{ + FD_ZERO(&dispr->fdset); + dispr->max_sock = -1; + return 0; +} + +static int +dispatcher_add(Dispatcher *dispr, int sock, void *ctx, CallbackFunc cb) +{ + if (sock >= FD_SETSIZE) { + fprintf(stderr, + "Error: Failed to add new event. sock %d should be less than %d\n", + sock, FD_SETSIZE); + return -1; + } + + dispr->events[sock].ctx = ctx; + dispr->events[sock].callback = cb; + + FD_SET(sock, &dispr->fdset); + if (sock > dispr->max_sock) { + dispr->max_sock = sock; + } + DPRINT("Added sock %d for watching. max_sock: %d\n", + sock, dispr->max_sock); + return 0; +} + +/* dispatcher_remove() is not currently in use but may be useful + * in the future. */ +static int +dispatcher_remove(Dispatcher *dispr, int sock) +{ + if (sock >= FD_SETSIZE) { + fprintf(stderr, + "Error: Failed to remove event. sock %d should be less than %d\n", + sock, FD_SETSIZE); + return -1; + } + + FD_CLR(sock, &dispr->fdset); + DPRINT("Sock %d removed from dispatcher watch.\n", sock); + return 0; +} + +/* timeout in us */ +static int +dispatcher_wait(Dispatcher *dispr, uint32_t timeout) +{ + struct timeval tv; + tv.tv_sec = timeout / 1000000; + tv.tv_usec = timeout % 1000000; + + fd_set fdset = dispr->fdset; + + /* wait until some of sockets become readable. */ + int rc = select(dispr->max_sock + 1, &fdset, 0, 0, &tv); + + if (rc == -1) { + vubr_die("select"); + } + + /* Timeout */ + if (rc == 0) { + return 0; + } + + /* Now call callback for every ready socket. */ + + int sock; + for (sock = 0; sock < dispr->max_sock + 1; sock++) { + /* The callback on a socket can remove other sockets from the + * dispatcher, thus we have to check that the socket is + * still not removed from dispatcher's list + */ + if (FD_ISSET(sock, &fdset) && FD_ISSET(sock, &dispr->fdset)) { + Event *e = &dispr->events[sock]; + e->callback(sock, e->ctx); + } + } + + return 0; +} + +typedef struct VubrVirtq { + int call_fd; + int kick_fd; + uint32_t size; + uint16_t last_avail_index; + uint16_t last_used_index; + struct vring_desc *desc; + struct vring_avail *avail; + struct vring_used *used; + uint64_t log_guest_addr; + int enable; +} VubrVirtq; + +/* Based on qemu/hw/virtio/vhost-user.c */ + +#define VHOST_MEMORY_MAX_NREGIONS 8 +#define VHOST_USER_F_PROTOCOL_FEATURES 30 + +#define VHOST_LOG_PAGE 4096 + +enum VhostUserProtocolFeature { + VHOST_USER_PROTOCOL_F_MQ = 0, + VHOST_USER_PROTOCOL_F_LOG_SHMFD = 1, + VHOST_USER_PROTOCOL_F_RARP = 2, + + VHOST_USER_PROTOCOL_F_MAX +}; + +#define VHOST_USER_PROTOCOL_FEATURE_MASK ((1 << VHOST_USER_PROTOCOL_F_MAX) - 1) + +typedef enum VhostUserRequest { + VHOST_USER_NONE = 0, + VHOST_USER_GET_FEATURES = 1, + VHOST_USER_SET_FEATURES = 2, + VHOST_USER_SET_OWNER = 3, + VHOST_USER_RESET_OWNER = 4, + VHOST_USER_SET_MEM_TABLE = 5, + VHOST_USER_SET_LOG_BASE = 6, + VHOST_USER_SET_LOG_FD = 7, + VHOST_USER_SET_VRING_NUM = 8, + VHOST_USER_SET_VRING_ADDR = 9, + VHOST_USER_SET_VRING_BASE = 10, + VHOST_USER_GET_VRING_BASE = 11, + VHOST_USER_SET_VRING_KICK = 12, + VHOST_USER_SET_VRING_CALL = 13, + VHOST_USER_SET_VRING_ERR = 14, + VHOST_USER_GET_PROTOCOL_FEATURES = 15, + VHOST_USER_SET_PROTOCOL_FEATURES = 16, + VHOST_USER_GET_QUEUE_NUM = 17, + VHOST_USER_SET_VRING_ENABLE = 18, + VHOST_USER_SEND_RARP = 19, + VHOST_USER_MAX +} VhostUserRequest; + +typedef struct VhostUserMemoryRegion { + uint64_t guest_phys_addr; + uint64_t memory_size; + uint64_t userspace_addr; + uint64_t mmap_offset; +} VhostUserMemoryRegion; + +typedef struct VhostUserMemory { + uint32_t nregions; + uint32_t padding; + VhostUserMemoryRegion regions[VHOST_MEMORY_MAX_NREGIONS]; +} VhostUserMemory; + +typedef struct VhostUserLog { + uint64_t mmap_size; + uint64_t mmap_offset; +} VhostUserLog; + +typedef struct VhostUserMsg { + VhostUserRequest request; + +#define VHOST_USER_VERSION_MASK (0x3) +#define VHOST_USER_REPLY_MASK (0x1<<2) + uint32_t flags; + uint32_t size; /* the following payload size */ + union { +#define VHOST_USER_VRING_IDX_MASK (0xff) +#define VHOST_USER_VRING_NOFD_MASK (0x1<<8) + uint64_t u64; + struct vhost_vring_state state; + struct vhost_vring_addr addr; + VhostUserMemory memory; + VhostUserLog log; + } payload; + int fds[VHOST_MEMORY_MAX_NREGIONS]; + int fd_num; +} QEMU_PACKED VhostUserMsg; + +#define VHOST_USER_HDR_SIZE offsetof(VhostUserMsg, payload.u64) + +/* The version of the protocol we support */ +#define VHOST_USER_VERSION (0x1) + +#define MAX_NR_VIRTQUEUE (8) + +typedef struct VubrDevRegion { + /* Guest Physical address. */ + uint64_t gpa; + /* Memory region size. */ + uint64_t size; + /* QEMU virtual address (userspace). */ + uint64_t qva; + /* Starting offset in our mmaped space. */ + uint64_t mmap_offset; + /* Start address of mmaped space. */ + uint64_t mmap_addr; +} VubrDevRegion; + +typedef struct VubrDev { + int sock; + Dispatcher dispatcher; + uint32_t nregions; + VubrDevRegion regions[VHOST_MEMORY_MAX_NREGIONS]; + VubrVirtq vq[MAX_NR_VIRTQUEUE]; + int log_call_fd; + uint64_t log_size; + uint8_t *log_table; + int backend_udp_sock; + struct sockaddr_in backend_udp_dest; + int ready; + uint64_t features; +} VubrDev; + +static const char *vubr_request_str[] = { + [VHOST_USER_NONE] = "VHOST_USER_NONE", + [VHOST_USER_GET_FEATURES] = "VHOST_USER_GET_FEATURES", + [VHOST_USER_SET_FEATURES] = "VHOST_USER_SET_FEATURES", + [VHOST_USER_SET_OWNER] = "VHOST_USER_SET_OWNER", + [VHOST_USER_RESET_OWNER] = "VHOST_USER_RESET_OWNER", + [VHOST_USER_SET_MEM_TABLE] = "VHOST_USER_SET_MEM_TABLE", + [VHOST_USER_SET_LOG_BASE] = "VHOST_USER_SET_LOG_BASE", + [VHOST_USER_SET_LOG_FD] = "VHOST_USER_SET_LOG_FD", + [VHOST_USER_SET_VRING_NUM] = "VHOST_USER_SET_VRING_NUM", + [VHOST_USER_SET_VRING_ADDR] = "VHOST_USER_SET_VRING_ADDR", + [VHOST_USER_SET_VRING_BASE] = "VHOST_USER_SET_VRING_BASE", + [VHOST_USER_GET_VRING_BASE] = "VHOST_USER_GET_VRING_BASE", + [VHOST_USER_SET_VRING_KICK] = "VHOST_USER_SET_VRING_KICK", + [VHOST_USER_SET_VRING_CALL] = "VHOST_USER_SET_VRING_CALL", + [VHOST_USER_SET_VRING_ERR] = "VHOST_USER_SET_VRING_ERR", + [VHOST_USER_GET_PROTOCOL_FEATURES] = "VHOST_USER_GET_PROTOCOL_FEATURES", + [VHOST_USER_SET_PROTOCOL_FEATURES] = "VHOST_USER_SET_PROTOCOL_FEATURES", + [VHOST_USER_GET_QUEUE_NUM] = "VHOST_USER_GET_QUEUE_NUM", + [VHOST_USER_SET_VRING_ENABLE] = "VHOST_USER_SET_VRING_ENABLE", + [VHOST_USER_SEND_RARP] = "VHOST_USER_SEND_RARP", + [VHOST_USER_MAX] = "VHOST_USER_MAX", +}; + +static void +print_buffer(uint8_t *buf, size_t len) +{ + int i; + printf("Raw buffer:\n"); + for (i = 0; i < len; i++) { + if (i % 16 == 0) { + printf("\n"); + } + if (i % 4 == 0) { + printf(" "); + } + printf("%02x ", buf[i]); + } + printf("\n............................................................\n"); +} + +/* Translate guest physical address to our virtual address. */ +static uint64_t +gpa_to_va(VubrDev *dev, uint64_t guest_addr) +{ + int i; + + /* Find matching memory region. */ + for (i = 0; i < dev->nregions; i++) { + VubrDevRegion *r = &dev->regions[i]; + + if ((guest_addr >= r->gpa) && (guest_addr < (r->gpa + r->size))) { + return guest_addr - r->gpa + r->mmap_addr + r->mmap_offset; + } + } + + assert(!"address not found in regions"); + return 0; +} + +/* Translate qemu virtual address to our virtual address. */ +static uint64_t +qva_to_va(VubrDev *dev, uint64_t qemu_addr) +{ + int i; + + /* Find matching memory region. */ + for (i = 0; i < dev->nregions; i++) { + VubrDevRegion *r = &dev->regions[i]; + + if ((qemu_addr >= r->qva) && (qemu_addr < (r->qva + r->size))) { + return qemu_addr - r->qva + r->mmap_addr + r->mmap_offset; + } + } + + assert(!"address not found in regions"); + return 0; +} + +static void +vubr_message_read(int conn_fd, VhostUserMsg *vmsg) +{ + char control[CMSG_SPACE(VHOST_MEMORY_MAX_NREGIONS * sizeof(int))] = { }; + struct iovec iov = { + .iov_base = (char *)vmsg, + .iov_len = VHOST_USER_HDR_SIZE, + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = control, + .msg_controllen = sizeof(control), + }; + size_t fd_size; + struct cmsghdr *cmsg; + int rc; + + rc = recvmsg(conn_fd, &msg, 0); + + if (rc == 0) { + vubr_die("recvmsg"); + fprintf(stderr, "Peer disconnected.\n"); + exit(1); + } + if (rc < 0) { + vubr_die("recvmsg"); + } + + vmsg->fd_num = 0; + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + fd_size = cmsg->cmsg_len - CMSG_LEN(0); + vmsg->fd_num = fd_size / sizeof(int); + memcpy(vmsg->fds, CMSG_DATA(cmsg), fd_size); + break; + } + } + + if (vmsg->size > sizeof(vmsg->payload)) { + fprintf(stderr, + "Error: too big message request: %d, size: vmsg->size: %u, " + "while sizeof(vmsg->payload) = %lu\n", + vmsg->request, vmsg->size, sizeof(vmsg->payload)); + exit(1); + } + + if (vmsg->size) { + rc = read(conn_fd, &vmsg->payload, vmsg->size); + if (rc == 0) { + vubr_die("recvmsg"); + fprintf(stderr, "Peer disconnected.\n"); + exit(1); + } + if (rc < 0) { + vubr_die("recvmsg"); + } + + assert(rc == vmsg->size); + } +} + +static void +vubr_message_write(int conn_fd, VhostUserMsg *vmsg) +{ + int rc; + + do { + rc = write(conn_fd, vmsg, VHOST_USER_HDR_SIZE + vmsg->size); + } while (rc < 0 && errno == EINTR); + + if (rc < 0) { + vubr_die("write"); + } +} + +static void +vubr_backend_udp_sendbuf(VubrDev *dev, uint8_t *buf, size_t len) +{ + int slen = sizeof(struct sockaddr_in); + + if (sendto(dev->backend_udp_sock, buf, len, 0, + (struct sockaddr *) &dev->backend_udp_dest, slen) == -1) { + vubr_die("sendto()"); + } +} + +static int +vubr_backend_udp_recvbuf(VubrDev *dev, uint8_t *buf, size_t buflen) +{ + int slen = sizeof(struct sockaddr_in); + int rc; + + rc = recvfrom(dev->backend_udp_sock, buf, buflen, 0, + (struct sockaddr *) &dev->backend_udp_dest, + (socklen_t *)&slen); + if (rc == -1) { + vubr_die("recvfrom()"); + } + + return rc; +} + +static void +vubr_consume_raw_packet(VubrDev *dev, uint8_t *buf, uint32_t len) +{ + int hdrlen = sizeof(struct virtio_net_hdr_v1); + + if (VHOST_USER_BRIDGE_DEBUG) { + print_buffer(buf, len); + } + vubr_backend_udp_sendbuf(dev, buf + hdrlen, len - hdrlen); +} + +/* Kick the log_call_fd if required. */ +static void +vubr_log_kick(VubrDev *dev) +{ + if (dev->log_call_fd != -1) { + DPRINT("Kicking the QEMU's log...\n"); + eventfd_write(dev->log_call_fd, 1); + } +} + +/* Kick the guest if necessary. */ +static void +vubr_virtqueue_kick(VubrVirtq *vq) +{ + if (!(vq->avail->flags & VRING_AVAIL_F_NO_INTERRUPT)) { + DPRINT("Kicking the guest...\n"); + eventfd_write(vq->call_fd, 1); + } +} + +static void +vubr_log_page(uint8_t *log_table, uint64_t page) +{ + DPRINT("Logged dirty guest page: %"PRId64"\n", page); + atomic_or(&log_table[page / 8], 1 << (page % 8)); +} + +static void +vubr_log_write(VubrDev *dev, uint64_t address, uint64_t length) +{ + uint64_t page; + + if (!(dev->features & (1ULL << VHOST_F_LOG_ALL)) || + !dev->log_table || !length) { + return; + } + + assert(dev->log_size > ((address + length - 1) / VHOST_LOG_PAGE / 8)); + + page = address / VHOST_LOG_PAGE; + while (page * VHOST_LOG_PAGE < address + length) { + vubr_log_page(dev->log_table, page); + page += VHOST_LOG_PAGE; + } + vubr_log_kick(dev); +} + +static void +vubr_post_buffer(VubrDev *dev, VubrVirtq *vq, uint8_t *buf, int32_t len) +{ + struct vring_desc *desc = vq->desc; + struct vring_avail *avail = vq->avail; + struct vring_used *used = vq->used; + uint64_t log_guest_addr = vq->log_guest_addr; + + unsigned int size = vq->size; + + uint16_t avail_index = atomic_mb_read(&avail->idx); + + /* We check the available descriptors before posting the + * buffer, so here we assume that enough available + * descriptors. */ + assert(vq->last_avail_index != avail_index); + uint16_t a_index = vq->last_avail_index % size; + uint16_t u_index = vq->last_used_index % size; + uint16_t d_index = avail->ring[a_index]; + + int i = d_index; + + DPRINT("Post packet to guest on vq:\n"); + DPRINT(" size = %d\n", vq->size); + DPRINT(" last_avail_index = %d\n", vq->last_avail_index); + DPRINT(" last_used_index = %d\n", vq->last_used_index); + DPRINT(" a_index = %d\n", a_index); + DPRINT(" u_index = %d\n", u_index); + DPRINT(" d_index = %d\n", d_index); + DPRINT(" desc[%d].addr = 0x%016"PRIx64"\n", i, desc[i].addr); + DPRINT(" desc[%d].len = %d\n", i, desc[i].len); + DPRINT(" desc[%d].flags = %d\n", i, desc[i].flags); + DPRINT(" avail->idx = %d\n", avail_index); + DPRINT(" used->idx = %d\n", used->idx); + + if (!(desc[i].flags & VRING_DESC_F_WRITE)) { + /* FIXME: we should find writable descriptor. */ + fprintf(stderr, "Error: descriptor is not writable. Exiting.\n"); + exit(1); + } + + void *chunk_start = (void *)gpa_to_va(dev, desc[i].addr); + uint32_t chunk_len = desc[i].len; + + if (len <= chunk_len) { + memcpy(chunk_start, buf, len); + vubr_log_write(dev, desc[i].addr, len); + } else { + fprintf(stderr, + "Received too long packet from the backend. Dropping...\n"); + return; + } + + /* Add descriptor to the used ring. */ + used->ring[u_index].id = d_index; + used->ring[u_index].len = len; + vubr_log_write(dev, + log_guest_addr + offsetof(struct vring_used, ring[u_index]), + sizeof(used->ring[u_index])); + + vq->last_avail_index++; + vq->last_used_index++; + + atomic_mb_set(&used->idx, vq->last_used_index); + vubr_log_write(dev, + log_guest_addr + offsetof(struct vring_used, idx), + sizeof(used->idx)); + + /* Kick the guest if necessary. */ + vubr_virtqueue_kick(vq); +} + +static int +vubr_process_desc(VubrDev *dev, VubrVirtq *vq) +{ + struct vring_desc *desc = vq->desc; + struct vring_avail *avail = vq->avail; + struct vring_used *used = vq->used; + uint64_t log_guest_addr = vq->log_guest_addr; + + unsigned int size = vq->size; + + uint16_t a_index = vq->last_avail_index % size; + uint16_t u_index = vq->last_used_index % size; + uint16_t d_index = avail->ring[a_index]; + + uint32_t i, len = 0; + size_t buf_size = 4096; + uint8_t buf[4096]; + + DPRINT("Chunks: "); + i = d_index; + do { + void *chunk_start = (void *)gpa_to_va(dev, desc[i].addr); + uint32_t chunk_len = desc[i].len; + + assert(!(desc[i].flags & VRING_DESC_F_WRITE)); + + if (len + chunk_len < buf_size) { + memcpy(buf + len, chunk_start, chunk_len); + DPRINT("%d ", chunk_len); + } else { + fprintf(stderr, "Error: too long packet. Dropping...\n"); + break; + } + + len += chunk_len; + + if (!(desc[i].flags & VRING_DESC_F_NEXT)) { + break; + } + + i = desc[i].next; + } while (1); + DPRINT("\n"); + + if (!len) { + return -1; + } + + /* Add descriptor to the used ring. */ + used->ring[u_index].id = d_index; + used->ring[u_index].len = len; + vubr_log_write(dev, + log_guest_addr + offsetof(struct vring_used, ring[u_index]), + sizeof(used->ring[u_index])); + + vubr_consume_raw_packet(dev, buf, len); + + return 0; +} + +static void +vubr_process_avail(VubrDev *dev, VubrVirtq *vq) +{ + struct vring_avail *avail = vq->avail; + struct vring_used *used = vq->used; + uint64_t log_guest_addr = vq->log_guest_addr; + + while (vq->last_avail_index != atomic_mb_read(&avail->idx)) { + vubr_process_desc(dev, vq); + vq->last_avail_index++; + vq->last_used_index++; + } + + atomic_mb_set(&used->idx, vq->last_used_index); + vubr_log_write(dev, + log_guest_addr + offsetof(struct vring_used, idx), + sizeof(used->idx)); +} + +static void +vubr_backend_recv_cb(int sock, void *ctx) +{ + VubrDev *dev = (VubrDev *) ctx; + VubrVirtq *rx_vq = &dev->vq[0]; + uint8_t buf[4096]; + struct virtio_net_hdr_v1 *hdr = (struct virtio_net_hdr_v1 *)buf; + int hdrlen = sizeof(struct virtio_net_hdr_v1); + int buflen = sizeof(buf); + int len; + + if (!dev->ready) { + return; + } + + DPRINT("\n\n *** IN UDP RECEIVE CALLBACK ***\n\n"); + + uint16_t avail_index = atomic_mb_read(&rx_vq->avail->idx); + + /* If there is no available descriptors, just do nothing. + * The buffer will be handled by next arrived UDP packet, + * or next kick on receive virtq. */ + if (rx_vq->last_avail_index == avail_index) { + DPRINT("Got UDP packet, but no available descriptors on RX virtq.\n"); + return; + } + + len = vubr_backend_udp_recvbuf(dev, buf + hdrlen, buflen - hdrlen); + + *hdr = (struct virtio_net_hdr_v1) { }; + hdr->num_buffers = 1; + vubr_post_buffer(dev, rx_vq, buf, len + hdrlen); +} + +static void +vubr_kick_cb(int sock, void *ctx) +{ + VubrDev *dev = (VubrDev *) ctx; + eventfd_t kick_data; + ssize_t rc; + + rc = eventfd_read(sock, &kick_data); + if (rc == -1) { + vubr_die("eventfd_read()"); + } else { + DPRINT("Got kick_data: %016"PRIx64"\n", kick_data); + vubr_process_avail(dev, &dev->vq[1]); + } +} + +static int +vubr_none_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + DPRINT("Function %s() not implemented yet.\n", __func__); + return 0; +} + +static int +vubr_get_features_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + vmsg->payload.u64 = + ((1ULL << VIRTIO_NET_F_MRG_RXBUF) | + (1ULL << VHOST_F_LOG_ALL) | + (1ULL << VIRTIO_NET_F_GUEST_ANNOUNCE) | + (1ULL << VHOST_USER_F_PROTOCOL_FEATURES)); + + vmsg->size = sizeof(vmsg->payload.u64); + + DPRINT("Sending back to guest u64: 0x%016"PRIx64"\n", vmsg->payload.u64); + + /* Reply */ + return 1; +} + +static int +vubr_set_features_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + DPRINT("u64: 0x%016"PRIx64"\n", vmsg->payload.u64); + dev->features = vmsg->payload.u64; + return 0; +} + +static int +vubr_set_owner_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + return 0; +} + +static void +vubr_close_log(VubrDev *dev) +{ + if (dev->log_table) { + if (munmap(dev->log_table, dev->log_size) != 0) { + vubr_die("munmap()"); + } + + dev->log_table = 0; + } + if (dev->log_call_fd != -1) { + close(dev->log_call_fd); + dev->log_call_fd = -1; + } +} + +static int +vubr_reset_device_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + vubr_close_log(dev); + dev->ready = 0; + dev->features = 0; + return 0; +} + +static int +vubr_set_mem_table_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + int i; + VhostUserMemory *memory = &vmsg->payload.memory; + dev->nregions = memory->nregions; + + DPRINT("Nregions: %d\n", memory->nregions); + for (i = 0; i < dev->nregions; i++) { + void *mmap_addr; + VhostUserMemoryRegion *msg_region = &memory->regions[i]; + VubrDevRegion *dev_region = &dev->regions[i]; + + DPRINT("Region %d\n", i); + DPRINT(" guest_phys_addr: 0x%016"PRIx64"\n", + msg_region->guest_phys_addr); + DPRINT(" memory_size: 0x%016"PRIx64"\n", + msg_region->memory_size); + DPRINT(" userspace_addr 0x%016"PRIx64"\n", + msg_region->userspace_addr); + DPRINT(" mmap_offset 0x%016"PRIx64"\n", + msg_region->mmap_offset); + + dev_region->gpa = msg_region->guest_phys_addr; + dev_region->size = msg_region->memory_size; + dev_region->qva = msg_region->userspace_addr; + dev_region->mmap_offset = msg_region->mmap_offset; + + /* We don't use offset argument of mmap() since the + * mapped address has to be page aligned, and we use huge + * pages. */ + mmap_addr = mmap(0, dev_region->size + dev_region->mmap_offset, + PROT_READ | PROT_WRITE, MAP_SHARED, + vmsg->fds[i], 0); + + if (mmap_addr == MAP_FAILED) { + vubr_die("mmap"); + } + dev_region->mmap_addr = (uint64_t) mmap_addr; + DPRINT(" mmap_addr: 0x%016"PRIx64"\n", dev_region->mmap_addr); + + close(vmsg->fds[i]); + } + + return 0; +} + +static int +vubr_set_log_base_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + int fd; + uint64_t log_mmap_size, log_mmap_offset; + void *rc; + + assert(vmsg->fd_num == 1); + fd = vmsg->fds[0]; + + assert(vmsg->size == sizeof(vmsg->payload.log)); + log_mmap_offset = vmsg->payload.log.mmap_offset; + log_mmap_size = vmsg->payload.log.mmap_size; + DPRINT("Log mmap_offset: %"PRId64"\n", log_mmap_offset); + DPRINT("Log mmap_size: %"PRId64"\n", log_mmap_size); + + rc = mmap(0, log_mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, + log_mmap_offset); + if (rc == MAP_FAILED) { + vubr_die("mmap"); + } + dev->log_table = rc; + dev->log_size = log_mmap_size; + + vmsg->size = sizeof(vmsg->payload.u64); + /* Reply */ + return 1; +} + +static int +vubr_set_log_fd_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + assert(vmsg->fd_num == 1); + dev->log_call_fd = vmsg->fds[0]; + DPRINT("Got log_call_fd: %d\n", vmsg->fds[0]); + return 0; +} + +static int +vubr_set_vring_num_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + unsigned int index = vmsg->payload.state.index; + unsigned int num = vmsg->payload.state.num; + + DPRINT("State.index: %d\n", index); + DPRINT("State.num: %d\n", num); + dev->vq[index].size = num; + return 0; +} + +static int +vubr_set_vring_addr_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + struct vhost_vring_addr *vra = &vmsg->payload.addr; + unsigned int index = vra->index; + VubrVirtq *vq = &dev->vq[index]; + + DPRINT("vhost_vring_addr:\n"); + DPRINT(" index: %d\n", vra->index); + DPRINT(" flags: %d\n", vra->flags); + DPRINT(" desc_user_addr: 0x%016llx\n", vra->desc_user_addr); + DPRINT(" used_user_addr: 0x%016llx\n", vra->used_user_addr); + DPRINT(" avail_user_addr: 0x%016llx\n", vra->avail_user_addr); + DPRINT(" log_guest_addr: 0x%016llx\n", vra->log_guest_addr); + + vq->desc = (struct vring_desc *)qva_to_va(dev, vra->desc_user_addr); + vq->used = (struct vring_used *)qva_to_va(dev, vra->used_user_addr); + vq->avail = (struct vring_avail *)qva_to_va(dev, vra->avail_user_addr); + vq->log_guest_addr = vra->log_guest_addr; + + DPRINT("Setting virtq addresses:\n"); + DPRINT(" vring_desc at %p\n", vq->desc); + DPRINT(" vring_used at %p\n", vq->used); + DPRINT(" vring_avail at %p\n", vq->avail); + + vq->last_used_index = vq->used->idx; + return 0; +} + +static int +vubr_set_vring_base_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + unsigned int index = vmsg->payload.state.index; + unsigned int num = vmsg->payload.state.num; + + DPRINT("State.index: %d\n", index); + DPRINT("State.num: %d\n", num); + dev->vq[index].last_avail_index = num; + + return 0; +} + +static int +vubr_get_vring_base_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + unsigned int index = vmsg->payload.state.index; + + DPRINT("State.index: %d\n", index); + vmsg->payload.state.num = dev->vq[index].last_avail_index; + vmsg->size = sizeof(vmsg->payload.state); + /* FIXME: this is a work-around for a bug in QEMU enabling + * too early vrings. When protocol features are enabled, + * we have to respect * VHOST_USER_SET_VRING_ENABLE request. */ + dev->ready = 0; + + if (dev->vq[index].call_fd != -1) { + close(dev->vq[index].call_fd); + dispatcher_remove(&dev->dispatcher, dev->vq[index].call_fd); + dev->vq[index].call_fd = -1; + } + if (dev->vq[index].kick_fd != -1) { + close(dev->vq[index].kick_fd); + dispatcher_remove(&dev->dispatcher, dev->vq[index].kick_fd); + dev->vq[index].kick_fd = -1; + } + + /* Reply */ + return 1; +} + +static int +vubr_set_vring_kick_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + uint64_t u64_arg = vmsg->payload.u64; + int index = u64_arg & VHOST_USER_VRING_IDX_MASK; + + DPRINT("u64: 0x%016"PRIx64"\n", vmsg->payload.u64); + + assert((u64_arg & VHOST_USER_VRING_NOFD_MASK) == 0); + assert(vmsg->fd_num == 1); + + if (dev->vq[index].kick_fd != -1) { + close(dev->vq[index].kick_fd); + dispatcher_remove(&dev->dispatcher, dev->vq[index].kick_fd); + } + dev->vq[index].kick_fd = vmsg->fds[0]; + DPRINT("Got kick_fd: %d for vq: %d\n", vmsg->fds[0], index); + + if (index % 2 == 1) { + /* TX queue. */ + dispatcher_add(&dev->dispatcher, dev->vq[index].kick_fd, + dev, vubr_kick_cb); + + DPRINT("Waiting for kicks on fd: %d for vq: %d\n", + dev->vq[index].kick_fd, index); + } + /* We temporarily use this hack to determine that both TX and RX + * queues are set up and ready for processing. + * FIXME: we need to rely in VHOST_USER_SET_VRING_ENABLE and + * actual kicks. */ + if (dev->vq[0].kick_fd != -1 && + dev->vq[1].kick_fd != -1) { + dev->ready = 1; + DPRINT("vhost-user-bridge is ready for processing queues.\n"); + } + return 0; + +} + +static int +vubr_set_vring_call_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + uint64_t u64_arg = vmsg->payload.u64; + int index = u64_arg & VHOST_USER_VRING_IDX_MASK; + + DPRINT("u64: 0x%016"PRIx64"\n", vmsg->payload.u64); + assert((u64_arg & VHOST_USER_VRING_NOFD_MASK) == 0); + assert(vmsg->fd_num == 1); + + if (dev->vq[index].call_fd != -1) { + close(dev->vq[index].call_fd); + dispatcher_remove(&dev->dispatcher, dev->vq[index].call_fd); + } + dev->vq[index].call_fd = vmsg->fds[0]; + DPRINT("Got call_fd: %d for vq: %d\n", vmsg->fds[0], index); + + return 0; +} + +static int +vubr_set_vring_err_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + DPRINT("u64: 0x%016"PRIx64"\n", vmsg->payload.u64); + return 0; +} + +static int +vubr_get_protocol_features_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + vmsg->payload.u64 = 1ULL << VHOST_USER_PROTOCOL_F_LOG_SHMFD; + DPRINT("u64: 0x%016"PRIx64"\n", vmsg->payload.u64); + vmsg->size = sizeof(vmsg->payload.u64); + + /* Reply */ + return 1; +} + +static int +vubr_set_protocol_features_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + /* FIXME: unimplented */ + DPRINT("u64: 0x%016"PRIx64"\n", vmsg->payload.u64); + return 0; +} + +static int +vubr_get_queue_num_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + DPRINT("Function %s() not implemented yet.\n", __func__); + return 0; +} + +static int +vubr_set_vring_enable_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + unsigned int index = vmsg->payload.state.index; + unsigned int enable = vmsg->payload.state.num; + + DPRINT("State.index: %d\n", index); + DPRINT("State.enable: %d\n", enable); + dev->vq[index].enable = enable; + return 0; +} + +static int +vubr_send_rarp_exec(VubrDev *dev, VhostUserMsg *vmsg) +{ + DPRINT("Function %s() not implemented yet.\n", __func__); + return 0; +} + +static int +vubr_execute_request(VubrDev *dev, VhostUserMsg *vmsg) +{ + /* Print out generic part of the request. */ + DPRINT( + "================== Vhost user message from QEMU ==================\n"); + DPRINT("Request: %s (%d)\n", vubr_request_str[vmsg->request], + vmsg->request); + DPRINT("Flags: 0x%x\n", vmsg->flags); + DPRINT("Size: %d\n", vmsg->size); + + if (vmsg->fd_num) { + int i; + DPRINT("Fds:"); + for (i = 0; i < vmsg->fd_num; i++) { + DPRINT(" %d", vmsg->fds[i]); + } + DPRINT("\n"); + } + + switch (vmsg->request) { + case VHOST_USER_NONE: + return vubr_none_exec(dev, vmsg); + case VHOST_USER_GET_FEATURES: + return vubr_get_features_exec(dev, vmsg); + case VHOST_USER_SET_FEATURES: + return vubr_set_features_exec(dev, vmsg); + case VHOST_USER_SET_OWNER: + return vubr_set_owner_exec(dev, vmsg); + case VHOST_USER_RESET_OWNER: + return vubr_reset_device_exec(dev, vmsg); + case VHOST_USER_SET_MEM_TABLE: + return vubr_set_mem_table_exec(dev, vmsg); + case VHOST_USER_SET_LOG_BASE: + return vubr_set_log_base_exec(dev, vmsg); + case VHOST_USER_SET_LOG_FD: + return vubr_set_log_fd_exec(dev, vmsg); + case VHOST_USER_SET_VRING_NUM: + return vubr_set_vring_num_exec(dev, vmsg); + case VHOST_USER_SET_VRING_ADDR: + return vubr_set_vring_addr_exec(dev, vmsg); + case VHOST_USER_SET_VRING_BASE: + return vubr_set_vring_base_exec(dev, vmsg); + case VHOST_USER_GET_VRING_BASE: + return vubr_get_vring_base_exec(dev, vmsg); + case VHOST_USER_SET_VRING_KICK: + return vubr_set_vring_kick_exec(dev, vmsg); + case VHOST_USER_SET_VRING_CALL: + return vubr_set_vring_call_exec(dev, vmsg); + case VHOST_USER_SET_VRING_ERR: + return vubr_set_vring_err_exec(dev, vmsg); + case VHOST_USER_GET_PROTOCOL_FEATURES: + return vubr_get_protocol_features_exec(dev, vmsg); + case VHOST_USER_SET_PROTOCOL_FEATURES: + return vubr_set_protocol_features_exec(dev, vmsg); + case VHOST_USER_GET_QUEUE_NUM: + return vubr_get_queue_num_exec(dev, vmsg); + case VHOST_USER_SET_VRING_ENABLE: + return vubr_set_vring_enable_exec(dev, vmsg); + case VHOST_USER_SEND_RARP: + return vubr_send_rarp_exec(dev, vmsg); + + case VHOST_USER_MAX: + assert(vmsg->request != VHOST_USER_MAX); + } + return 0; +} + +static void +vubr_receive_cb(int sock, void *ctx) +{ + VubrDev *dev = (VubrDev *) ctx; + VhostUserMsg vmsg; + int reply_requested; + + vubr_message_read(sock, &vmsg); + reply_requested = vubr_execute_request(dev, &vmsg); + if (reply_requested) { + /* Set the version in the flags when sending the reply */ + vmsg.flags &= ~VHOST_USER_VERSION_MASK; + vmsg.flags |= VHOST_USER_VERSION; + vmsg.flags |= VHOST_USER_REPLY_MASK; + vubr_message_write(sock, &vmsg); + } +} + +static void +vubr_accept_cb(int sock, void *ctx) +{ + VubrDev *dev = (VubrDev *)ctx; + int conn_fd; + struct sockaddr_un un; + socklen_t len = sizeof(un); + + conn_fd = accept(sock, (struct sockaddr *) &un, &len); + if (conn_fd == -1) { + vubr_die("accept()"); + } + DPRINT("Got connection from remote peer on sock %d\n", conn_fd); + dispatcher_add(&dev->dispatcher, conn_fd, ctx, vubr_receive_cb); +} + +static VubrDev * +vubr_new(const char *path) +{ + VubrDev *dev = (VubrDev *) calloc(1, sizeof(VubrDev)); + dev->nregions = 0; + int i; + struct sockaddr_un un; + size_t len; + + for (i = 0; i < MAX_NR_VIRTQUEUE; i++) { + dev->vq[i] = (VubrVirtq) { + .call_fd = -1, .kick_fd = -1, + .size = 0, + .last_avail_index = 0, .last_used_index = 0, + .desc = 0, .avail = 0, .used = 0, + .enable = 0, + }; + } + + /* Init log */ + dev->log_call_fd = -1; + dev->log_size = 0; + dev->log_table = 0; + dev->ready = 0; + dev->features = 0; + + /* Get a UNIX socket. */ + dev->sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (dev->sock == -1) { + vubr_die("socket"); + } + + un.sun_family = AF_UNIX; + strcpy(un.sun_path, path); + len = sizeof(un.sun_family) + strlen(path); + unlink(path); + + if (bind(dev->sock, (struct sockaddr *) &un, len) == -1) { + vubr_die("bind"); + } + + if (listen(dev->sock, 1) == -1) { + vubr_die("listen"); + } + + dispatcher_init(&dev->dispatcher); + dispatcher_add(&dev->dispatcher, dev->sock, (void *)dev, + vubr_accept_cb); + + DPRINT("Waiting for connections on UNIX socket %s ...\n", path); + return dev; +} + +static void +vubr_set_host(struct sockaddr_in *saddr, const char *host) +{ + if (isdigit(host[0])) { + if (!inet_aton(host, &saddr->sin_addr)) { + fprintf(stderr, "inet_aton() failed.\n"); + exit(1); + } + } else { + struct hostent *he = gethostbyname(host); + + if (!he) { + fprintf(stderr, "gethostbyname() failed.\n"); + exit(1); + } + saddr->sin_addr = *(struct in_addr *)he->h_addr; + } +} + +static void +vubr_backend_udp_setup(VubrDev *dev, + const char *local_host, + const char *local_port, + const char *remote_host, + const char *remote_port) +{ + int sock; + const char *r; + + int lport, rport; + + lport = strtol(local_port, (char **)&r, 0); + if (r == local_port) { + fprintf(stderr, "lport parsing failed.\n"); + exit(1); + } + + rport = strtol(remote_port, (char **)&r, 0); + if (r == remote_port) { + fprintf(stderr, "rport parsing failed.\n"); + exit(1); + } + + struct sockaddr_in si_local = { + .sin_family = AF_INET, + .sin_port = htons(lport), + }; + + vubr_set_host(&si_local, local_host); + + /* setup destination for sends */ + dev->backend_udp_dest = (struct sockaddr_in) { + .sin_family = AF_INET, + .sin_port = htons(rport), + }; + vubr_set_host(&dev->backend_udp_dest, remote_host); + + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == -1) { + vubr_die("socket"); + } + + if (bind(sock, (struct sockaddr *)&si_local, sizeof(si_local)) == -1) { + vubr_die("bind"); + } + + dev->backend_udp_sock = sock; + dispatcher_add(&dev->dispatcher, sock, dev, vubr_backend_recv_cb); + DPRINT("Waiting for data from udp backend on %s:%d...\n", + local_host, lport); +} + +static void +vubr_run(VubrDev *dev) +{ + while (1) { + /* timeout 200ms */ + dispatcher_wait(&dev->dispatcher, 200000); + /* Here one can try polling strategy. */ + } +} + +static int +vubr_parse_host_port(const char **host, const char **port, const char *buf) +{ + char *p = strchr(buf, ':'); + + if (!p) { + return -1; + } + *p = '\0'; + *host = strdup(buf); + *port = strdup(p + 1); + return 0; +} + +#define DEFAULT_UD_SOCKET "/tmp/vubr.sock" +#define DEFAULT_LHOST "127.0.0.1" +#define DEFAULT_LPORT "4444" +#define DEFAULT_RHOST "127.0.0.1" +#define DEFAULT_RPORT "5555" + +static const char *ud_socket_path = DEFAULT_UD_SOCKET; +static const char *lhost = DEFAULT_LHOST; +static const char *lport = DEFAULT_LPORT; +static const char *rhost = DEFAULT_RHOST; +static const char *rport = DEFAULT_RPORT; + +int +main(int argc, char *argv[]) +{ + VubrDev *dev; + int opt; + + while ((opt = getopt(argc, argv, "l:r:u:")) != -1) { + + switch (opt) { + case 'l': + if (vubr_parse_host_port(&lhost, &lport, optarg) < 0) { + goto out; + } + break; + case 'r': + if (vubr_parse_host_port(&rhost, &rport, optarg) < 0) { + goto out; + } + break; + case 'u': + ud_socket_path = strdup(optarg); + break; + default: + goto out; + } + } + + DPRINT("ud socket: %s\n", ud_socket_path); + DPRINT("local: %s:%s\n", lhost, lport); + DPRINT("remote: %s:%s\n", rhost, rport); + + dev = vubr_new(ud_socket_path); + if (!dev) { + return 1; + } + + vubr_backend_udp_setup(dev, lhost, lport, rhost, rport); + vubr_run(dev); + return 0; + +out: + fprintf(stderr, "Usage: %s ", argv[0]); + fprintf(stderr, "[-u ud_socket_path] [-l lhost:lport] [-r rhost:rport]\n"); + fprintf(stderr, "\t-u path to unix doman socket. default: %s\n", + DEFAULT_UD_SOCKET); + fprintf(stderr, "\t-l local host and port. default: %s:%s\n", + DEFAULT_LHOST, DEFAULT_LPORT); + fprintf(stderr, "\t-r remote host and port. default: %s:%s\n", + DEFAULT_RHOST, DEFAULT_RPORT); + + return 1; +} diff --git a/tests/vhost-user-test.c b/tests/vhost-user-test.c index a74c934cc0..991fd85c7c 100644 --- a/tests/vhost-user-test.c +++ b/tests/vhost-user-test.c @@ -57,7 +57,7 @@ typedef enum VhostUserRequest { VHOST_USER_GET_FEATURES = 1, VHOST_USER_SET_FEATURES = 2, VHOST_USER_SET_OWNER = 3, - VHOST_USER_RESET_DEVICE = 4, + VHOST_USER_RESET_OWNER = 4, VHOST_USER_SET_MEM_TABLE = 5, VHOST_USER_SET_LOG_BASE = 6, VHOST_USER_SET_LOG_FD = 7, @@ -70,6 +70,7 @@ typedef enum VhostUserRequest { VHOST_USER_SET_VRING_ERR = 14, VHOST_USER_GET_PROTOCOL_FEATURES = 15, VHOST_USER_SET_PROTOCOL_FEATURES = 16, + VHOST_USER_SET_VRING_ENABLE = 18, VHOST_USER_MAX } VhostUserRequest; @@ -86,6 +87,11 @@ typedef struct VhostUserMemory { VhostUserMemoryRegion regions[VHOST_MEMORY_MAX_NREGIONS]; } VhostUserMemory; +typedef struct VhostUserLog { + uint64_t mmap_size; + uint64_t mmap_offset; +} VhostUserLog; + typedef struct VhostUserMsg { VhostUserRequest request; @@ -94,11 +100,14 @@ typedef struct VhostUserMsg { uint32_t flags; uint32_t size; /* the following payload size */ union { +#define VHOST_USER_VRING_IDX_MASK (0xff) +#define VHOST_USER_VRING_NOFD_MASK (0x1<<8) uint64_t u64; struct vhost_vring_state state; struct vhost_vring_addr addr; VhostUserMemory memory; - }; + VhostUserLog log; + } payload; } QEMU_PACKED VhostUserMsg; static VhostUserMsg m __attribute__ ((unused)); @@ -114,6 +123,7 @@ static VhostUserMsg m __attribute__ ((unused)); typedef struct TestServer { gchar *socket_path; + gchar *mig_path; gchar *chr_name; CharDriverState *chr; int fds_num; @@ -122,6 +132,7 @@ typedef struct TestServer { GMutex data_mutex; GCond data_cond; int log_fd; + uint64_t rings; } TestServer; #if !GLIB_CHECK_VERSION(2, 32, 0) @@ -162,8 +173,9 @@ static void wait_for_fds(TestServer *s) g_mutex_unlock(&s->data_mutex); } -static void read_guest_mem(TestServer *s) +static void read_guest_mem(const void *data) { + TestServer *s = (void *)data; uint32_t *guest_mem; int i, j; size_t size; @@ -206,8 +218,7 @@ static void read_guest_mem(TestServer *s) static void *thread_function(void *data) { - GMainLoop *loop; - loop = g_main_loop_new(NULL, FALSE); + GMainLoop *loop = data; g_main_loop_run(loop); return NULL; } @@ -242,23 +253,23 @@ static void chr_read(void *opaque, const uint8_t *buf, int size) case VHOST_USER_GET_FEATURES: /* send back features to qemu */ msg.flags |= VHOST_USER_REPLY_MASK; - msg.size = sizeof(m.u64); - msg.u64 = 0x1ULL << VHOST_F_LOG_ALL | + msg.size = sizeof(m.payload.u64); + msg.payload.u64 = 0x1ULL << VHOST_F_LOG_ALL | 0x1ULL << VHOST_USER_F_PROTOCOL_FEATURES; p = (uint8_t *) &msg; qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size); break; case VHOST_USER_SET_FEATURES: - g_assert_cmpint(msg.u64 & (0x1ULL << VHOST_USER_F_PROTOCOL_FEATURES), + g_assert_cmpint(msg.payload.u64 & (0x1ULL << VHOST_USER_F_PROTOCOL_FEATURES), !=, 0ULL); break; case VHOST_USER_GET_PROTOCOL_FEATURES: /* send back features to qemu */ msg.flags |= VHOST_USER_REPLY_MASK; - msg.size = sizeof(m.u64); - msg.u64 = 1 << VHOST_USER_PROTOCOL_F_LOG_SHMFD; + msg.size = sizeof(m.payload.u64); + msg.payload.u64 = 1 << VHOST_USER_PROTOCOL_F_LOG_SHMFD; p = (uint8_t *) &msg; qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size); break; @@ -266,15 +277,18 @@ static void chr_read(void *opaque, const uint8_t *buf, int size) case VHOST_USER_GET_VRING_BASE: /* send back vring base to qemu */ msg.flags |= VHOST_USER_REPLY_MASK; - msg.size = sizeof(m.state); - msg.state.num = 0; + msg.size = sizeof(m.payload.state); + msg.payload.state.num = 0; p = (uint8_t *) &msg; qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size); + + assert(msg.payload.state.index < 2); + s->rings &= ~(0x1ULL << msg.payload.state.index); break; case VHOST_USER_SET_MEM_TABLE: /* received the mem table */ - memcpy(&s->memory, &msg.memory, sizeof(msg.memory)); + memcpy(&s->memory, &msg.payload.memory, sizeof(msg.payload.memory)); s->fds_num = qemu_chr_fe_get_msgfds(chr, s->fds, G_N_ELEMENTS(s->fds)); /* signal the test that it can continue */ @@ -307,8 +321,9 @@ static void chr_read(void *opaque, const uint8_t *buf, int size) g_cond_signal(&s->data_cond); break; - case VHOST_USER_RESET_DEVICE: - s->fds_num = 0; + case VHOST_USER_SET_VRING_BASE: + assert(msg.payload.state.index < 2); + s->rings |= 0x1ULL << msg.payload.state.index; break; default: @@ -351,6 +366,7 @@ static TestServer *test_server_new(const gchar *name) gchar *chr_path; server->socket_path = g_strdup_printf("%s/%s.sock", tmpfs, name); + server->mig_path = g_strdup_printf("%s/%s.mig", tmpfs, name); chr_path = g_strdup_printf("unix:%s,server,nowait", server->socket_path); server->chr_name = g_strdup_printf("chr-%s", name); @@ -375,7 +391,7 @@ static TestServer *test_server_new(const gchar *name) g_strdup_printf(QEMU_CMD extra, (mem), (mem), (root), (s)->chr_name, \ (s)->socket_path, (s)->chr_name, ##__VA_ARGS__) -static void test_server_free(TestServer *server) +static gboolean _test_server_free(TestServer *server) { int i; @@ -392,9 +408,18 @@ static void test_server_free(TestServer *server) unlink(server->socket_path); g_free(server->socket_path); + unlink(server->mig_path); + g_free(server->mig_path); g_free(server->chr_name); g_free(server); + + return FALSE; +} + +static void test_server_free(TestServer *server) +{ + g_idle_add((GSourceFunc)_test_server_free, server); } static void wait_for_log_fd(TestServer *s) @@ -475,25 +500,36 @@ static gboolean test_migrate_source_check(GSource *source) { TestMigrateSource *t = (TestMigrateSource *)source; - gboolean overlap = t->src->fds_num > 0 && t->dest->fds_num > 0; + gboolean overlap = t->src->rings && t->dest->rings; g_assert(!overlap); return FALSE; } +#if !GLIB_CHECK_VERSION(2,36,0) +/* this callback is unnecessary with glib >2.36, the default + * prepare for the source does the same */ +static gboolean +test_migrate_source_prepare(GSource *source, gint *timeout) +{ + *timeout = -1; + return FALSE; +} +#endif + GSourceFuncs test_migrate_source_funcs = { - NULL, - test_migrate_source_check, - NULL, - NULL +#if !GLIB_CHECK_VERSION(2,36,0) + .prepare = test_migrate_source_prepare, +#endif + .check = test_migrate_source_check, }; static void test_migrate(void) { TestServer *s = test_server_new("src"); TestServer *dest = test_server_new("dest"); - const char *uri = "tcp:127.0.0.1:1234"; + char *uri = g_strdup_printf("%s%s", "unix:", dest->mig_path); QTestState *global = global_qtest, *from, *to; GSource *source; gchar *cmd; @@ -564,6 +600,7 @@ static void test_migrate(void) test_server_free(dest); qtest_quit(from); test_server_free(s); + g_free(uri); global_qtest = global; } @@ -576,6 +613,8 @@ int main(int argc, char **argv) char *qemu_cmd = NULL; int ret; char template[] = "/tmp/vhost-test-XXXXXX"; + GMainLoop *loop; + GThread *thread; g_test_init(&argc, &argv, NULL); @@ -598,8 +637,9 @@ int main(int argc, char **argv) server = test_server_new("test"); + loop = g_main_loop_new(NULL, FALSE); /* run the main loop thread so the chardev may operate */ - g_thread_new(NULL, thread_function, NULL); + thread = g_thread_new(NULL, thread_function, loop); qemu_cmd = GET_QEMU_CMD(server); @@ -618,6 +658,14 @@ int main(int argc, char **argv) /* cleanup */ test_server_free(server); + /* finish the helper thread and dispatch pending sources */ + g_main_loop_quit(loop); + g_thread_join(thread); + while (g_main_context_pending(NULL)) { + g_main_context_iteration (NULL, TRUE); + } + g_main_loop_unref(loop); + ret = rmdir(tmpfs); if (ret != 0) { g_test_message("unable to rmdir: path (%s): %s\n", |