diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/Makefile | 1 | ||||
| -rw-r--r-- | tests/ahci-test.c | 221 | ||||
| -rw-r--r-- | tests/ide-test.c | 34 | ||||
| -rw-r--r-- | tests/libqos/ahci.c | 10 | ||||
| -rw-r--r-- | tests/libqos/ahci.h | 4 | ||||
| -rw-r--r-- | tests/libqos/libqos-pc.c | 5 | ||||
| -rw-r--r-- | tests/libqos/libqos-pc.h | 1 | ||||
| -rw-r--r-- | tests/libqos/libqos.c | 66 | ||||
| -rw-r--r-- | tests/libqos/libqos.h | 3 | ||||
| -rw-r--r-- | tests/libqtest.c | 47 | ||||
| -rw-r--r-- | tests/libqtest.h | 47 | ||||
| -rwxr-xr-x | tests/qemu-iotests/122 | 223 | ||||
| -rw-r--r-- | tests/qemu-iotests/122.out | 209 | ||||
| -rw-r--r-- | tests/qemu-iotests/124 | 363 | ||||
| -rw-r--r-- | tests/qemu-iotests/124.out | 5 | ||||
| -rw-r--r-- | tests/qemu-iotests/129 | 86 | ||||
| -rw-r--r-- | tests/qemu-iotests/129.out | 5 | ||||
| -rw-r--r-- | tests/qemu-iotests/group | 3 | ||||
| -rw-r--r-- | tests/qemu-iotests/iotests.py | 38 | ||||
| -rw-r--r-- | tests/test-aio.c | 19 | ||||
| -rw-r--r-- | tests/test-hbitmap.c | 255 |
21 files changed, 1557 insertions, 88 deletions
diff --git a/tests/Makefile b/tests/Makefile index 55aa7452b4..309e8697fd 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -415,6 +415,7 @@ GCOV_OPTIONS = -n $(if $(V),-f,) $(patsubst %, check-qtest-%, $(QTEST_TARGETS)): check-qtest-%: $(check-qtest-y) $(if $(CONFIG_GCOV),@rm -f *.gcda */*.gcda */*/*.gcda */*/*/*.gcda,) $(call quiet-command,QTEST_QEMU_BINARY=$*-softmmu/qemu-system-$* \ + QTEST_QEMU_IMG=qemu-img$(EXESUF) \ MALLOC_PERTURB_=$${MALLOC_PERTURB_:-$$((RANDOM % 255 + 1))} \ gtester $(GTESTER_OPTIONS) -m=$(SPEED) $(check-qtest-$*-y),"GTESTER $@") $(if $(CONFIG_GCOV),@for f in $(gcov-files-$*-y); do \ diff --git a/tests/ahci-test.c b/tests/ahci-test.c index ea62e249f5..7c23bb2180 100644 --- a/tests/ahci-test.c +++ b/tests/ahci-test.c @@ -39,11 +39,14 @@ #include "hw/pci/pci_ids.h" #include "hw/pci/pci_regs.h" -/* Test-specific defines. */ -#define TEST_IMAGE_SIZE (64 * 1024 * 1024) +/* 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) /*** Globals ***/ static char tmp_path[] = "/tmp/qtest.XXXXXX"; +static char debug_path[] = "/tmp/qtest-blkdebug.XXXXXX"; static bool ahci_pedantic; /*** Function Declarations ***/ @@ -99,19 +102,12 @@ static void generate_pattern(void *buffer, size_t len, size_t cycle_len) /** * Start a Q35 machine and bookmark a handle to the AHCI device. */ -static AHCIQState *ahci_boot(void) +static AHCIQState *ahci_vboot(const char *cli, va_list ap) { AHCIQState *s; - const char *cli; s = g_malloc0(sizeof(AHCIQState)); - - cli = "-drive if=none,id=drive0,file=%s,cache=writeback,serial=%s" - ",format=raw" - " -M q35 " - "-device ide-hd,drive=drive0 " - "-global ide-hd.ver=%s"; - s->parent = qtest_pc_boot(cli, tmp_path, "testdisk", "version"); + s->parent = qtest_pc_vboot(cli, ap); alloc_set_flags(s->parent->alloc, ALLOC_LEAK_ASSERT); /* Verify that we have an AHCI device present. */ @@ -121,12 +117,35 @@ static AHCIQState *ahci_boot(void) } /** + * Start a Q35 machine and bookmark a handle to the AHCI device. + */ +static AHCIQState *ahci_boot(const char *cli, ...) +{ + AHCIQState *s; + va_list ap; + + if (cli) { + va_start(ap, cli); + s = ahci_vboot(cli, ap); + va_end(ap); + } else { + cli = "-drive if=none,id=drive0,file=%s,cache=writeback,serial=%s" + ",format=qcow2" + " -M q35 " + "-device ide-hd,drive=drive0 " + "-global ide-hd.ver=%s"; + s = ahci_boot(cli, tmp_path, "testdisk", "version"); + } + + return s; +} + +/** * Clean up the PCI device, then terminate the QEMU instance. */ static void ahci_shutdown(AHCIQState *ahci) { QOSState *qs = ahci->parent; - ahci_clean_mem(ahci); free_ahci_device(ahci->dev); g_free(ahci); @@ -137,10 +156,18 @@ static void ahci_shutdown(AHCIQState *ahci) * Boot and fully enable the HBA device. * @see ahci_boot, ahci_pci_enable and ahci_hba_enable. */ -static AHCIQState *ahci_boot_and_enable(void) +static AHCIQState *ahci_boot_and_enable(const char *cli, ...) { AHCIQState *ahci; - ahci = ahci_boot(); + va_list ap; + + if (cli) { + va_start(ap, cli); + ahci = ahci_vboot(cli, ap); + va_end(ap); + } else { + ahci = ahci_boot(NULL); + } ahci_pci_enable(ahci); ahci_hba_enable(ahci); @@ -738,7 +765,7 @@ static void ahci_test_identify(AHCIQState *ahci) ahci_port_clear(ahci, px); /* "Read" 512 bytes using CMD_IDENTIFY into the host buffer. */ - ahci_io(ahci, px, CMD_IDENTIFY, &buff, buffsize); + ahci_io(ahci, px, CMD_IDENTIFY, &buff, buffsize, 0); /* Check serial number/version in the buffer */ /* NB: IDENTIFY strings are packed in 16bit little endian chunks. @@ -754,11 +781,12 @@ static void ahci_test_identify(AHCIQState *ahci) g_assert_cmphex(rc, ==, 0); sect_size = le16_to_cpu(*((uint16_t *)(&buff[5]))); - g_assert_cmphex(sect_size, ==, 0x200); + g_assert_cmphex(sect_size, ==, AHCI_SECTOR_SIZE); } static void ahci_test_io_rw_simple(AHCIQState *ahci, unsigned bufsize, - uint8_t read_cmd, uint8_t write_cmd) + uint64_t sector, uint8_t read_cmd, + uint8_t write_cmd) { uint64_t ptr; uint8_t port; @@ -781,9 +809,9 @@ static void ahci_test_io_rw_simple(AHCIQState *ahci, unsigned bufsize, memwrite(ptr, tx, bufsize); /* Write this buffer to disk, then read it back to the DMA buffer. */ - ahci_guest_io(ahci, port, write_cmd, ptr, bufsize); + ahci_guest_io(ahci, port, write_cmd, ptr, bufsize, sector); qmemset(ptr, 0x00, bufsize); - ahci_guest_io(ahci, port, read_cmd, ptr, bufsize); + ahci_guest_io(ahci, port, read_cmd, ptr, bufsize, sector); /*** Read back the Data ***/ memread(ptr, rx, bufsize); @@ -794,6 +822,29 @@ static void ahci_test_io_rw_simple(AHCIQState *ahci, unsigned bufsize, g_free(rx); } +static void ahci_test_nondata(AHCIQState *ahci, uint8_t ide_cmd) +{ + uint8_t px; + AHCICommand *cmd; + + /* Sanitize */ + px = ahci_port_select(ahci); + ahci_port_clear(ahci, px); + + /* Issue Command */ + cmd = ahci_command_create(ide_cmd); + ahci_command_commit(ahci, cmd, px); + ahci_command_issue(ahci, cmd); + ahci_command_verify(ahci, cmd); + ahci_command_free(cmd); +} + +static void ahci_test_flush(AHCIQState *ahci) +{ + ahci_test_nondata(ahci, CMD_FLUSH_CACHE); +} + + /******************************************************************************/ /* Test Interfaces */ /******************************************************************************/ @@ -804,7 +855,7 @@ static void ahci_test_io_rw_simple(AHCIQState *ahci, unsigned bufsize, static void test_sanity(void) { AHCIQState *ahci; - ahci = ahci_boot(); + ahci = ahci_boot(NULL); ahci_shutdown(ahci); } @@ -815,7 +866,7 @@ static void test_sanity(void) static void test_pci_spec(void) { AHCIQState *ahci; - ahci = ahci_boot(); + ahci = ahci_boot(NULL); ahci_test_pci_spec(ahci); ahci_shutdown(ahci); } @@ -827,8 +878,7 @@ static void test_pci_spec(void) static void test_pci_enable(void) { AHCIQState *ahci; - - ahci = ahci_boot(); + ahci = ahci_boot(NULL); ahci_pci_enable(ahci); ahci_shutdown(ahci); } @@ -841,7 +891,7 @@ static void test_hba_spec(void) { AHCIQState *ahci; - ahci = ahci_boot(); + ahci = ahci_boot(NULL); ahci_pci_enable(ahci); ahci_test_hba_spec(ahci); ahci_shutdown(ahci); @@ -855,7 +905,7 @@ static void test_hba_enable(void) { AHCIQState *ahci; - ahci = ahci_boot(); + ahci = ahci_boot(NULL); ahci_pci_enable(ahci); ahci_hba_enable(ahci); ahci_shutdown(ahci); @@ -869,7 +919,7 @@ static void test_identify(void) { AHCIQState *ahci; - ahci = ahci_boot_and_enable(); + ahci = ahci_boot_and_enable(NULL); ahci_test_identify(ahci); ahci_shutdown(ahci); } @@ -890,7 +940,7 @@ static void test_dma_fragmented(void) unsigned char *rx = g_malloc0(bufsize); uint64_t ptr; - ahci = ahci_boot_and_enable(); + ahci = ahci_boot_and_enable(NULL); px = ahci_port_select(ahci); ahci_port_clear(ahci, px); @@ -928,6 +978,50 @@ static void test_dma_fragmented(void) g_free(tx); } +static void test_flush(void) +{ + AHCIQState *ahci; + + ahci = ahci_boot_and_enable(NULL); + ahci_test_flush(ahci); + ahci_shutdown(ahci); +} + +static void test_flush_retry(void) +{ + AHCIQState *ahci; + AHCICommand *cmd; + uint8_t port; + const char *s; + + 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," + "rerror=stop,werror=stop " + "-M q35 " + "-device ide-hd,drive=drive0 ", + debug_path, + tmp_path); + + /* Issue Flush Command */ + port = ahci_port_select(ahci); + ahci_port_clear(ahci, port); + cmd = ahci_command_create(CMD_FLUSH_CACHE); + ahci_command_commit(ahci, cmd, port); + ahci_command_issue_async(ahci, cmd); + qmp_eventwait("STOP"); + + /* Complete the command */ + s = "{'execute':'cont' }"; + qmp_async(s); + qmp_eventwait("RESUME"); + ahci_command_wait(ahci, cmd); + ahci_command_verify(ahci, cmd); + + ahci_command_free(cmd); + ahci_shutdown(ahci); +} + /******************************************************************************/ /* AHCI I/O Test Matrix Definitions */ @@ -968,12 +1062,45 @@ enum IOOps { NUM_IO_OPS }; +enum OffsetType { + OFFSET_BEGIN = 0, + OFFSET_ZERO = OFFSET_BEGIN, + OFFSET_LOW, + OFFSET_HIGH, + NUM_OFFSETS +}; + +static const char *offset_str[NUM_OFFSETS] = { "zero", "low", "high" }; + typedef struct AHCIIOTestOptions { enum BuffLen length; enum AddrMode address_type; enum IOMode io_type; + enum OffsetType offset; } AHCIIOTestOptions; +static uint64_t offset_sector(enum OffsetType ofst, + enum AddrMode addr_type, + uint64_t buffsize) +{ + uint64_t ceil; + uint64_t nsectors; + + switch (ofst) { + case OFFSET_ZERO: + return 0; + case OFFSET_LOW: + return 1; + case OFFSET_HIGH: + ceil = (addr_type == ADDR_MODE_LBA28) ? 0xfffffff : 0xffffffffffff; + ceil = MIN(ceil, TEST_IMAGE_SECTORS - 1); + nsectors = buffsize / AHCI_SECTOR_SIZE; + return ceil - nsectors + 1; + default: + g_assert_not_reached(); + } +} + /** * Table of possible I/O ATA commands given a set of enumerations. */ @@ -1001,12 +1128,12 @@ static const uint8_t io_cmds[NUM_MODES][NUM_ADDR_MODES][NUM_IO_OPS] = { * transfer modes, and buffer sizes. */ static void test_io_rw_interface(enum AddrMode lba48, enum IOMode dma, - unsigned bufsize) + unsigned bufsize, uint64_t sector) { AHCIQState *ahci; - ahci = ahci_boot_and_enable(); - ahci_test_io_rw_simple(ahci, bufsize, + ahci = ahci_boot_and_enable(NULL); + ahci_test_io_rw_simple(ahci, bufsize, sector, io_cmds[dma][lba48][IO_READ], io_cmds[dma][lba48][IO_WRITE]); ahci_shutdown(ahci); @@ -1019,6 +1146,7 @@ static void test_io_interface(gconstpointer opaque) { AHCIIOTestOptions *opts = (AHCIIOTestOptions *)opaque; unsigned bufsize; + uint64_t sector; switch (opts->length) { case LEN_SIMPLE: @@ -1037,13 +1165,14 @@ static void test_io_interface(gconstpointer opaque) g_assert_not_reached(); } - test_io_rw_interface(opts->address_type, opts->io_type, bufsize); + sector = offset_sector(opts->offset, opts->address_type, bufsize); + test_io_rw_interface(opts->address_type, opts->io_type, bufsize, sector); g_free(opts); return; } static void create_ahci_io_test(enum IOMode type, enum AddrMode addr, - enum BuffLen len) + enum BuffLen len, enum OffsetType offset) { static const char *arch; char *name; @@ -1052,15 +1181,17 @@ static void create_ahci_io_test(enum IOMode type, enum AddrMode addr, opts->length = len; opts->address_type = addr; opts->io_type = type; + opts->offset = offset; if (!arch) { arch = qtest_get_arch(); } - name = g_strdup_printf("/%s/ahci/io/%s/%s/%s", arch, + name = g_strdup_printf("/%s/ahci/io/%s/%s/%s/%s", arch, io_mode_str[type], addr_mode_str[addr], - buff_len_str[len]); + buff_len_str[len], + offset_str[offset]); g_test_add_data_func(name, opts, test_io_interface); g_free(name); @@ -1071,10 +1202,10 @@ static void create_ahci_io_test(enum IOMode type, enum AddrMode addr, int main(int argc, char **argv) { const char *arch; - int fd; int ret; + int fd; int c; - int i, j, k; + int i, j, k, m; static struct option long_options[] = { {"pedantic", no_argument, 0, 'p' }, @@ -1108,11 +1239,13 @@ int main(int argc, char **argv) return 0; } - /* Create a temporary raw image */ - fd = mkstemp(tmp_path); + /* Create a temporary qcow2 image */ + close(mkstemp(tmp_path)); + mkqcow2(tmp_path, TEST_IMAGE_SIZE_MB); + + /* Create temporary blkdebug instructions */ + fd = mkstemp(debug_path); g_assert(fd >= 0); - ret = ftruncate(fd, TEST_IMAGE_SIZE); - g_assert(ret == 0); close(fd); /* Run the tests */ @@ -1126,17 +1259,23 @@ int main(int argc, char **argv) for (i = MODE_BEGIN; i < NUM_MODES; i++) { for (j = ADDR_MODE_BEGIN; j < NUM_ADDR_MODES; j++) { for (k = LEN_BEGIN; k < NUM_LENGTHS; k++) { - create_ahci_io_test(i, j, k); + for (m = OFFSET_BEGIN; m < NUM_OFFSETS; m++) { + create_ahci_io_test(i, j, k, m); + } } } } qtest_add_func("/ahci/io/dma/lba28/fragmented", test_dma_fragmented); + qtest_add_func("/ahci/flush/simple", test_flush); + qtest_add_func("/ahci/flush/retry", test_flush_retry); + ret = g_test_run(); /* Cleanup */ unlink(tmp_path); + unlink(debug_path); return ret; } diff --git a/tests/ide-test.c b/tests/ide-test.c index b28a3023c2..78382e9c75 100644 --- a/tests/ide-test.c +++ b/tests/ide-test.c @@ -29,6 +29,7 @@ #include <glib.h> #include "libqtest.h" +#include "libqos/libqos.h" #include "libqos/pci-pc.h" #include "libqos/malloc-pc.h" @@ -494,33 +495,10 @@ static void test_flush(void) ide_test_quit(); } -static void prepare_blkdebug_script(const char *debug_fn, const char *event) -{ - FILE *debug_file = fopen(debug_fn, "w"); - int ret; - - fprintf(debug_file, "[inject-error]\n"); - fprintf(debug_file, "event = \"%s\"\n", event); - fprintf(debug_file, "errno = \"5\"\n"); - fprintf(debug_file, "state = \"1\"\n"); - fprintf(debug_file, "immediately = \"off\"\n"); - fprintf(debug_file, "once = \"on\"\n"); - - fprintf(debug_file, "[set-state]\n"); - fprintf(debug_file, "event = \"%s\"\n", event); - fprintf(debug_file, "new_state = \"2\"\n"); - fflush(debug_file); - g_assert(!ferror(debug_file)); - - ret = fclose(debug_file); - g_assert(ret == 0); -} - static void test_retry_flush(const char *machine) { uint8_t data; const char *s; - QDict *response; prepare_blkdebug_script(debug_path, "flush_to_disk"); @@ -539,15 +517,7 @@ static void test_retry_flush(const char *machine) assert_bit_set(data, BSY | DRDY); assert_bit_clear(data, DF | ERR | DRQ); - for (;; response = NULL) { - response = qmp_receive(); - if ((qdict_haskey(response, "event")) && - (strcmp(qdict_get_str(response, "event"), "STOP") == 0)) { - QDECREF(response); - break; - } - QDECREF(response); - } + qmp_eventwait("STOP"); /* Complete the command */ s = "{'execute':'cont' }"; diff --git a/tests/libqos/ahci.c b/tests/libqos/ahci.c index b0f39a5e32..a18c12bcc1 100644 --- a/tests/libqos/ahci.c +++ b/tests/libqos/ahci.c @@ -568,13 +568,15 @@ inline unsigned size_to_prdtl(unsigned bytes, unsigned bytes_per_prd) /* Given a guest buffer address, perform an IO operation */ void ahci_guest_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, - uint64_t buffer, size_t bufsize) + uint64_t buffer, size_t bufsize, uint64_t sector) { AHCICommand *cmd; - cmd = ahci_command_create(ide_cmd); ahci_command_set_buffer(cmd, buffer); ahci_command_set_size(cmd, bufsize); + if (sector) { + ahci_command_set_offset(cmd, sector); + } ahci_command_commit(ahci, cmd, port); ahci_command_issue(ahci, cmd); ahci_command_verify(ahci, cmd); @@ -612,7 +614,7 @@ static AHCICommandProp *ahci_command_find(uint8_t command_name) /* Given a HOST buffer, create a buffer address and perform an IO operation. */ void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, - void *buffer, size_t bufsize) + void *buffer, size_t bufsize, uint64_t sector) { uint64_t ptr; AHCICommandProp *props; @@ -626,7 +628,7 @@ void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, memwrite(ptr, buffer, bufsize); } - ahci_guest_io(ahci, port, ide_cmd, ptr, bufsize); + ahci_guest_io(ahci, port, ide_cmd, ptr, bufsize, sector); if (props->read) { memread(ptr, buffer, bufsize); diff --git a/tests/libqos/ahci.h b/tests/libqos/ahci.h index 888545d5a2..40e8ca48ba 100644 --- a/tests/libqos/ahci.h +++ b/tests/libqos/ahci.h @@ -523,9 +523,9 @@ void ahci_write_fis(AHCIQState *ahci, RegH2DFIS *fis, uint64_t addr); unsigned ahci_pick_cmd(AHCIQState *ahci, uint8_t port); unsigned size_to_prdtl(unsigned bytes, unsigned bytes_per_prd); void ahci_guest_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, - uint64_t gbuffer, size_t size); + uint64_t gbuffer, size_t size, uint64_t sector); void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, - void *buffer, size_t bufsize); + void *buffer, size_t bufsize, uint64_t sector); /* Command Lifecycle */ AHCICommand *ahci_command_create(uint8_t command_name); diff --git a/tests/libqos/libqos-pc.c b/tests/libqos/libqos-pc.c index bbace893fb..1403699377 100644 --- a/tests/libqos/libqos-pc.c +++ b/tests/libqos/libqos-pc.c @@ -6,6 +6,11 @@ static QOSOps qos_ops = { .uninit_allocator = pc_alloc_uninit }; +QOSState *qtest_pc_vboot(const char *cmdline_fmt, va_list ap) +{ + return qtest_vboot(&qos_ops, cmdline_fmt, ap); +} + QOSState *qtest_pc_boot(const char *cmdline_fmt, ...) { QOSState *qs; diff --git a/tests/libqos/libqos-pc.h b/tests/libqos/libqos-pc.h index 316857d32f..b1820c5739 100644 --- a/tests/libqos/libqos-pc.h +++ b/tests/libqos/libqos-pc.h @@ -3,6 +3,7 @@ #include "libqos/libqos.h" +QOSState *qtest_pc_vboot(const char *cmdline_fmt, va_list ap); QOSState *qtest_pc_boot(const char *cmdline_fmt, ...); void qtest_pc_shutdown(QOSState *qs); diff --git a/tests/libqos/libqos.c b/tests/libqos/libqos.c index bc8beb281f..7e7207856e 100644 --- a/tests/libqos/libqos.c +++ b/tests/libqos/libqos.c @@ -61,3 +61,69 @@ void qtest_shutdown(QOSState *qs) qtest_quit(qs->qts); g_free(qs); } + +void mkimg(const char *file, const char *fmt, unsigned size_mb) +{ + gchar *cli; + bool ret; + int rc; + GError *err = NULL; + char *qemu_img_path; + gchar *out, *out2; + char *abs_path; + + qemu_img_path = getenv("QTEST_QEMU_IMG"); + abs_path = realpath(qemu_img_path, NULL); + assert(qemu_img_path); + + cli = g_strdup_printf("%s create -f %s %s %uM", abs_path, + fmt, file, size_mb); + ret = g_spawn_command_line_sync(cli, &out, &out2, &rc, &err); + if (err) { + fprintf(stderr, "%s\n", err->message); + g_error_free(err); + } + g_assert(ret && !err); + + /* In glib 2.34, we have g_spawn_check_exit_status. in 2.12, we don't. + * glib 2.43.91 implementation assumes that any non-zero is an error for + * windows, but uses extra precautions for Linux. However, + * 0 is only possible if the program exited normally, so that should be + * sufficient for our purposes on all platforms, here. */ + if (rc) { + fprintf(stderr, "qemu-img returned status code %d\n", rc); + } + g_assert(!rc); + + g_free(out); + g_free(out2); + g_free(cli); + free(abs_path); +} + +void mkqcow2(const char *file, unsigned size_mb) +{ + return mkimg(file, "qcow2", size_mb); +} + +void prepare_blkdebug_script(const char *debug_fn, const char *event) +{ + FILE *debug_file = fopen(debug_fn, "w"); + int ret; + + fprintf(debug_file, "[inject-error]\n"); + fprintf(debug_file, "event = \"%s\"\n", event); + fprintf(debug_file, "errno = \"5\"\n"); + fprintf(debug_file, "state = \"1\"\n"); + fprintf(debug_file, "immediately = \"off\"\n"); + fprintf(debug_file, "once = \"on\"\n"); + + fprintf(debug_file, "[set-state]\n"); + fprintf(debug_file, "event = \"%s\"\n", event); + fprintf(debug_file, "new_state = \"2\"\n"); + fflush(debug_file); + g_assert(!ferror(debug_file)); + + ret = fclose(debug_file); + g_assert(ret == 0); +} diff --git a/tests/libqos/libqos.h b/tests/libqos/libqos.h index 612d41e5e9..f57362b688 100644 --- a/tests/libqos/libqos.h +++ b/tests/libqos/libqos.h @@ -19,6 +19,9 @@ 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); +void mkimg(const char *file, const char *fmt, unsigned size_mb); +void mkqcow2(const char *file, unsigned size_mb); +void prepare_blkdebug_script(const char *debug_fn, const char *event); static inline uint64_t qmalloc(QOSState *q, size_t bytes) { diff --git a/tests/libqtest.c b/tests/libqtest.c index 12d65bd1e6..a525dc532c 100644 --- a/tests/libqtest.c +++ b/tests/libqtest.c @@ -388,7 +388,12 @@ QDict *qtest_qmp_receive(QTestState *s) return qmp.response; } -QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap) +/** + * Allow users to send a message without waiting for the reply, + * in the case that they choose to discard all replies up until + * a particular EVENT is received. + */ +void qtest_async_qmpv(QTestState *s, const char *fmt, va_list ap) { va_list ap_copy; QObject *qobj; @@ -417,6 +422,11 @@ QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap) QDECREF(qstr); qobject_decref(qobj); } +} + +QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap) +{ + qtest_async_qmpv(s, fmt, ap); /* Receive reply */ return qtest_qmp_receive(s); @@ -433,6 +443,15 @@ QDict *qtest_qmp(QTestState *s, const char *fmt, ...) return response; } +void qtest_async_qmp(QTestState *s, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + qtest_async_qmpv(s, fmt, ap); + va_end(ap); +} + void qtest_qmpv_discard_response(QTestState *s, const char *fmt, va_list ap) { QDict *response = qtest_qmpv(s, fmt, ap); @@ -450,9 +469,26 @@ void qtest_qmp_discard_response(QTestState *s, const char *fmt, ...) QDECREF(response); } +void qtest_qmp_eventwait(QTestState *s, const char *event) +{ + QDict *response; + + for (;;) { + response = qtest_qmp_receive(s); + if ((qdict_haskey(response, "event")) && + (strcmp(qdict_get_str(response, "event"), event) == 0)) { + QDECREF(response); + break; + } + QDECREF(response); + } +} + + const char *qtest_get_arch(void) { const char *qemu = getenv("QTEST_QEMU_BINARY"); + g_assert(qemu != NULL); const char *end = strrchr(qemu, '/'); return end + strlen("/qemu-system-"); @@ -695,6 +731,15 @@ QDict *qmp(const char *fmt, ...) return response; } +void qmp_async(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + qtest_async_qmpv(global_qtest, fmt, ap); + va_end(ap); +} + void qmp_discard_response(const char *fmt, ...) { va_list ap; diff --git a/tests/libqtest.h b/tests/libqtest.h index 03469b8781..4b54b5da9e 100644 --- a/tests/libqtest.h +++ b/tests/libqtest.h @@ -64,6 +64,15 @@ void qtest_qmp_discard_response(QTestState *s, const char *fmt, ...); QDict *qtest_qmp(QTestState *s, const char *fmt, ...); /** + * qtest_async_qmp: + * @s: #QTestState instance to operate on. + * @fmt...: QMP message to send to qemu + * + * Sends a QMP message to QEMU and leaves the response in the stream. + */ +void qtest_async_qmp(QTestState *s, const char *fmt, ...); + +/** * qtest_qmpv_discard_response: * @s: #QTestState instance to operate on. * @fmt: QMP message to send to QEMU @@ -84,6 +93,16 @@ void qtest_qmpv_discard_response(QTestState *s, const char *fmt, va_list ap); QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap); /** + * qtest_async_qmpv: + * @s: #QTestState instance to operate on. + * @fmt: QMP message to send to QEMU + * @ap: QMP message arguments + * + * Sends a QMP message to QEMU and leaves the response in the stream. + */ +void qtest_async_qmpv(QTestState *s, const char *fmt, va_list ap); + +/** * qtest_receive: * @s: #QTestState instance to operate on. * @@ -92,6 +111,15 @@ QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap); QDict *qtest_qmp_receive(QTestState *s); /** + * qtest_qmp_eventwait: + * @s: #QTestState instance to operate on. + * @s: #event event to wait for. + * + * Continuosly polls for QMP responses until it receives the desired event. + */ +void qtest_qmp_eventwait(QTestState *s, const char *event); + +/** * qtest_get_irq: * @s: #QTestState instance to operate on. * @num: Interrupt to observe. @@ -411,6 +439,14 @@ static inline void qtest_end(void) QDict *qmp(const char *fmt, ...); /** + * qmp_async: + * @fmt...: QMP message to send to qemu + * + * Sends a QMP message to QEMU and leaves the response in the stream. + */ +void qmp_async(const char *fmt, ...); + +/** * qmp_discard_response: * @fmt...: QMP message to send to qemu * @@ -429,6 +465,17 @@ static inline QDict *qmp_receive(void) } /** + * qmp_eventwait: + * @s: #event event to wait for. + * + * Continuosly polls for QMP responses until it receives the desired event. + */ +static inline void qmp_eventwait(const char *event) +{ + return qtest_qmp_eventwait(global_qtest, event); +} + +/** * get_irq: * @num: Interrupt to observe. * diff --git a/tests/qemu-iotests/122 b/tests/qemu-iotests/122 new file mode 100755 index 0000000000..350ca9c466 --- /dev/null +++ b/tests/qemu-iotests/122 @@ -0,0 +1,223 @@ +#!/bin/bash +# +# Test some qemu-img convert cases +# +# 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/>. +# + +# creator +owner=kwolf@redhat.com + +seq="$(basename $0)" +echo "QA output created by $seq" + +here="$PWD" +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + rm -f "$TEST_IMG".[123] + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt qcow2 +_supported_proto file +_supported_os Linux + + +TEST_IMG="$TEST_IMG".base _make_test_img 64M +$QEMU_IO -c "write -P 0x11 0 64M" "$TEST_IMG".base 2>&1 | _filter_qemu_io | _filter_testdir + + +echo +echo "=== Check allocation status regression with -B ===" +echo + +_make_test_img -b "$TEST_IMG".base +$QEMU_IO -c "write -P 0x22 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IMG map "$TEST_IMG".orig | _filter_qemu_img_map + + +echo +echo "=== Check that zero clusters are kept in overlay ===" +echo + +_make_test_img -b "$TEST_IMG".base + +$QEMU_IO -c "write -P 0 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG convert -O $IMGFMT -c -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir + +$QEMU_IO -c "write -z 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG convert -O $IMGFMT -c -B "$TEST_IMG".base "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir + + +echo +echo "=== Concatenate multiple source images ===" +echo + +TEST_IMG="$TEST_IMG".1 _make_test_img 4M +TEST_IMG="$TEST_IMG".2 _make_test_img 4M +TEST_IMG="$TEST_IMG".3 _make_test_img 4M + +$QEMU_IO -c "write -P 0x11 0 64k" "$TEST_IMG".1 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "write -P 0x22 0 64k" "$TEST_IMG".2 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "write -P 0x33 0 64k" "$TEST_IMG".3 2>&1 | _filter_qemu_io | _filter_testdir + +$QEMU_IMG convert -O $IMGFMT "$TEST_IMG".[123] "$TEST_IMG" +$QEMU_IMG map "$TEST_IMG" | _filter_qemu_img_map +$QEMU_IO -c "read -P 0x11 0 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x22 4M 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x33 8M 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir + +$QEMU_IMG convert -c -O $IMGFMT "$TEST_IMG".[123] "$TEST_IMG" +$QEMU_IMG map --output=json "$TEST_IMG" | _filter_qemu_img_map +$QEMU_IO -c "read -P 0x11 0 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x22 4M 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x33 8M 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir + +# -B can't be combined with concatenation +$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base "$TEST_IMG".[123] "$TEST_IMG" +$QEMU_IMG convert -O $IMGFMT -c -B "$TEST_IMG".base "$TEST_IMG".[123] "$TEST_IMG" + + +echo +echo "=== Compression with misaligned allocations and image sizes ===" +echo + +TEST_IMG="$TEST_IMG".1 _make_test_img 1023k -o cluster_size=1024 +TEST_IMG="$TEST_IMG".2 _make_test_img 1023k -o cluster_size=1024 + +$QEMU_IO -c "write -P 0x11 16k 16k" "$TEST_IMG".1 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "write -P 0x22 130k 130k" "$TEST_IMG".1 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "write -P 0x33 1022k 1k" "$TEST_IMG".1 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "write -P 0x44 0k 1k" "$TEST_IMG".2 2>&1 | _filter_qemu_io | _filter_testdir + +$QEMU_IMG convert -c -O $IMGFMT "$TEST_IMG".[12] "$TEST_IMG" +$QEMU_IMG map --output=json "$TEST_IMG" | _filter_qemu_img_map +$QEMU_IO -c "read -P 0 0k 16k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x11 16k 16k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0 32k 98k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x22 130k 130k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0 260k 762k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x33 1022k 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x44 1023k 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0 1024k 1022k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir + + +echo +echo "=== Full allocation with -S 0 ===" +echo + +# Standalone image +_make_test_img 64M +$QEMU_IO -c "write -P 0x22 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "write -P 0 3M 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir + +echo +echo convert -S 0: +$QEMU_IMG convert -O $IMGFMT -S 0 "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0 3M 61M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map + +echo +echo convert -c -S 0: +$QEMU_IMG convert -O $IMGFMT -c -S 0 "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0 3M 61M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map + +# With backing file +TEST_IMG="$TEST_IMG".base _make_test_img 64M +$QEMU_IO -c "write -P 0x11 0 32M" "$TEST_IMG".base 2>&1 | _filter_qemu_io | _filter_testdir + +_make_test_img -b "$TEST_IMG".base 64M +$QEMU_IO -c "write -P 0x22 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir + +echo +echo convert -S 0 with source backing file: +$QEMU_IMG convert -O $IMGFMT -S 0 "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x11 3M 29M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0 32M 32M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map + +echo +echo convert -c -S 0 with source backing file: +$QEMU_IMG convert -O $IMGFMT -c -S 0 "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x11 3M 29M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0 32M 32M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map + +# With keeping the backing file +echo +echo convert -S 0 -B ... +$QEMU_IMG convert -O $IMGFMT -S 0 "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x11 3M 29M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0 32M 32M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map + +echo +echo convert -c -S 0 -B ... +$QEMU_IMG convert -O $IMGFMT -c -S 0 "$TEST_IMG" "$TEST_IMG".orig +$QEMU_IO -c "read -P 0x22 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0x11 3M 29M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "read -P 0 32M 32M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map + + +echo +echo "=== Non-zero -S ===" +echo + +_make_test_img 64M -o cluster_size=1k +$QEMU_IO -c "write -P 0 0 64k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "write 0 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "write 8k 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir +$QEMU_IO -c "write 17k 1k" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir + +for min_sparse in 4k 8k; do + echo + echo convert -S $min_sparse + $QEMU_IMG convert -O $IMGFMT -o cluster_size=1k -S $min_sparse "$TEST_IMG" "$TEST_IMG".orig + $QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map + + echo + echo convert -c -S $min_sparse + # For compressed images, -S values other than 0 are ignored + $QEMU_IMG convert -O $IMGFMT -o cluster_size=1k -c -S $min_sparse "$TEST_IMG" "$TEST_IMG".orig + $QEMU_IMG map --output=json "$TEST_IMG".orig | _filter_qemu_img_map +done + +# success, all done +echo '*** done' +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/122.out b/tests/qemu-iotests/122.out new file mode 100644 index 0000000000..1f853b9e93 --- /dev/null +++ b/tests/qemu-iotests/122.out @@ -0,0 +1,209 @@ +QA output created by 122 +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +wrote 67108864/67108864 bytes at offset 0 +64 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +=== Check allocation status regression with -B === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +wrote 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Offset Length File +0 0x300000 TEST_DIR/t.IMGFMT.orig +0x300000 0x3d00000 TEST_DIR/t.IMGFMT.base + +=== Check that zero clusters are kept in overlay === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +wrote 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +=== Concatenate multiple source images === + +Formatting 'TEST_DIR/t.IMGFMT.1', fmt=IMGFMT size=4194304 +Formatting 'TEST_DIR/t.IMGFMT.2', fmt=IMGFMT size=4194304 +Formatting 'TEST_DIR/t.IMGFMT.3', fmt=IMGFMT size=4194304 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Offset Length File +0 0x10000 TEST_DIR/t.IMGFMT +0x400000 0x10000 TEST_DIR/t.IMGFMT +0x800000 0x10000 TEST_DIR/t.IMGFMT +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 4194304 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 8388608 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +[{ "start": 0, "length": 65536, "depth": 0, "zero": false, "data": true}, +{ "start": 65536, "length": 4128768, "depth": 0, "zero": true, "data": false}, +{ "start": 4194304, "length": 65536, "depth": 0, "zero": false, "data": true}, +{ "start": 4259840, "length": 4128768, "depth": 0, "zero": true, "data": false}, +{ "start": 8388608, "length": 65536, "depth": 0, "zero": false, "data": true}, +{ "start": 8454144, "length": 4128768, "depth": 0, "zero": true, "data": false}] +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 4194304 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 8388608 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-img: -B makes no sense when concatenating multiple input images +qemu-img: -B makes no sense when concatenating multiple input images + +=== Compression with misaligned allocations and image sizes === + +Formatting 'TEST_DIR/t.IMGFMT.1', fmt=IMGFMT size=1047552 +Formatting 'TEST_DIR/t.IMGFMT.2', fmt=IMGFMT size=1047552 +wrote 16384/16384 bytes at offset 16384 +16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 133120/133120 bytes at offset 133120 +130 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 1024/1024 bytes at offset 1046528 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 1024/1024 bytes at offset 0 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +[{ "start": 0, "length": 65536, "depth": 0, "zero": false, "data": true}, +{ "start": 65536, "length": 65536, "depth": 0, "zero": true, "data": false}, +{ "start": 131072, "length": 196608, "depth": 0, "zero": false, "data": true}, +{ "start": 327680, "length": 655360, "depth": 0, "zero": true, "data": false}, +{ "start": 983040, "length": 65536, "depth": 0, "zero": false, "data": true}, +{ "start": 1048576, "length": 1046528, "depth": 0, "zero": true, "data": false}] +read 16384/16384 bytes at offset 0 +16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 16384/16384 bytes at offset 16384 +16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 100352/100352 bytes at offset 32768 +98 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 133120/133120 bytes at offset 133120 +130 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 780288/780288 bytes at offset 266240 +762 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 1024/1024 bytes at offset 1046528 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 1024/1024 bytes at offset 1047552 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 1046528/1046528 bytes at offset 1048576 +1022 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +=== Full allocation with -S 0 === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 3145728/3145728 bytes at offset 3145728 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +convert -S 0: +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 63963136/63963136 bytes at offset 3145728 +61 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +[{ "start": 0, "length": 6291456, "depth": 0, "zero": false, "data": true, "offset": 327680}, +{ "start": 6291456, "length": 60817408, "depth": 0, "zero": true, "data": false}] + +convert -c -S 0: +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 63963136/63963136 bytes at offset 3145728 +61 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +[{ "start": 0, "length": 6291456, "depth": 0, "zero": false, "data": true}, +{ "start": 6291456, "length": 60817408, "depth": 0, "zero": true, "data": false}] +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +wrote 33554432/33554432 bytes at offset 0 +32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +wrote 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +convert -S 0 with source backing file: +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 30408704/30408704 bytes at offset 3145728 +29 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 33554432/33554432 bytes at offset 33554432 +32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +[{ "start": 0, "length": 67108864, "depth": 0, "zero": false, "data": true, "offset": 327680}] + +convert -c -S 0 with source backing file: +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 30408704/30408704 bytes at offset 3145728 +29 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 33554432/33554432 bytes at offset 33554432 +32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +[{ "start": 0, "length": 67108864, "depth": 0, "zero": false, "data": true}] + +convert -S 0 -B ... +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 30408704/30408704 bytes at offset 3145728 +29 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 33554432/33554432 bytes at offset 33554432 +32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +[{ "start": 0, "length": 67108864, "depth": 0, "zero": false, "data": true, "offset": 327680}] + +convert -c -S 0 -B ... +read 3145728/3145728 bytes at offset 0 +3 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 30408704/30408704 bytes at offset 3145728 +29 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 33554432/33554432 bytes at offset 33554432 +32 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +[{ "start": 0, "length": 67108864, "depth": 0, "zero": false, "data": true}] + +=== Non-zero -S === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 1024/1024 bytes at offset 0 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 1024/1024 bytes at offset 8192 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 1024/1024 bytes at offset 17408 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +convert -S 4k +[{ "start": 0, "length": 1024, "depth": 0, "zero": false, "data": true, "offset": 8192}, +{ "start": 1024, "length": 7168, "depth": 0, "zero": true, "data": false}, +{ "start": 8192, "length": 1024, "depth": 0, "zero": false, "data": true, "offset": 9216}, +{ "start": 9216, "length": 8192, "depth": 0, "zero": true, "data": false}, +{ "start": 17408, "length": 1024, "depth": 0, "zero": false, "data": true, "offset": 10240}, +{ "start": 18432, "length": 67090432, "depth": 0, "zero": true, "data": false}] + +convert -c -S 4k +[{ "start": 0, "length": 1024, "depth": 0, "zero": false, "data": true}, +{ "start": 1024, "length": 7168, "depth": 0, "zero": true, "data": false}, +{ "start": 8192, "length": 1024, "depth": 0, "zero": false, "data": true}, +{ "start": 9216, "length": 8192, "depth": 0, "zero": true, "data": false}, +{ "start": 17408, "length": 1024, "depth": 0, "zero": false, "data": true}, +{ "start": 18432, "length": 67090432, "depth": 0, "zero": true, "data": false}] + +convert -S 8k +[{ "start": 0, "length": 9216, "depth": 0, "zero": false, "data": true, "offset": 8192}, +{ "start": 9216, "length": 8192, "depth": 0, "zero": true, "data": false}, +{ "start": 17408, "length": 1024, "depth": 0, "zero": false, "data": true, "offset": 17408}, +{ "start": 18432, "length": 67090432, "depth": 0, "zero": true, "data": false}] + +convert -c -S 8k +[{ "start": 0, "length": 1024, "depth": 0, "zero": false, "data": true}, +{ "start": 1024, "length": 7168, "depth": 0, "zero": true, "data": false}, +{ "start": 8192, "length": 1024, "depth": 0, "zero": false, "data": true}, +{ "start": 9216, "length": 8192, "depth": 0, "zero": true, "data": false}, +{ "start": 17408, "length": 1024, "depth": 0, "zero": false, "data": true}, +{ "start": 18432, "length": 67090432, "depth": 0, "zero": true, "data": false}] +*** done diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124 new file mode 100644 index 0000000000..3ee78cd1f1 --- /dev/null +++ b/tests/qemu-iotests/124 @@ -0,0 +1,363 @@ +#!/usr/bin/env python +# +# Tests for incremental drive-backup +# +# Copyright (C) 2015 John Snow for Red Hat, Inc. +# +# Based on 056. +# +# 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 + + +def io_write_patterns(img, patterns): + for pattern in patterns: + iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img) + + +def try_remove(img): + try: + os.remove(img) + except OSError: + pass + + +class Bitmap: + def __init__(self, name, drive): + self.name = name + self.drive = drive + self.num = 0 + self.backups = list() + + def base_target(self): + return (self.drive['backup'], None) + + def new_target(self, num=None): + if num is None: + num = self.num + self.num = num + 1 + base = os.path.join(iotests.test_dir, + "%s.%s." % (self.drive['id'], self.name)) + suff = "%i.%s" % (num, self.drive['fmt']) + target = base + "inc" + suff + reference = base + "ref" + suff + self.backups.append((target, reference)) + return (target, reference) + + def last_target(self): + if self.backups: + return self.backups[-1] + return self.base_target() + + def del_target(self): + for image in self.backups.pop(): + try_remove(image) + self.num -= 1 + + def cleanup(self): + for backup in self.backups: + for image in backup: + try_remove(image) + + +class TestIncrementalBackup(iotests.QMPTestCase): + def setUp(self): + self.bitmaps = list() + self.files = list() + self.drives = list() + self.vm = iotests.VM() + self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt) + + # Create a base image with a distinctive patterning + drive0 = self.add_node('drive0') + self.img_create(drive0['file'], drive0['fmt']) + self.vm.add_drive(drive0['file']) + io_write_patterns(drive0['file'], (('0x41', 0, 512), + ('0xd5', '1M', '32k'), + ('0xdc', '32M', '124k'))) + self.vm.launch() + + + def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None): + if path is None: + path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt)) + if backup is None: + backup = os.path.join(iotests.test_dir, + '%s.full.backup.%s' % (node_id, fmt)) + + self.drives.append({ + 'id': node_id, + 'file': path, + 'backup': backup, + 'fmt': fmt }) + return self.drives[-1] + + + def img_create(self, img, fmt=iotests.imgfmt, size='64M', + parent=None, parentFormat=None): + if parent: + if parentFormat is None: + parentFormat = fmt + iotests.qemu_img('create', '-f', fmt, img, size, + '-b', parent, '-F', parentFormat) + else: + iotests.qemu_img('create', '-f', fmt, img, size) + self.files.append(img) + + + def do_qmp_backup(self, error='Input/output error', **kwargs): + res = self.vm.qmp('drive-backup', **kwargs) + self.assert_qmp(res, 'return', {}) + + event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", + match={'data': {'device': kwargs['device']}}) + self.assertIsNotNone(event) + + try: + failure = self.dictpath(event, 'data/error') + except AssertionError: + # Backup succeeded. + self.assert_qmp(event, 'data/offset', event['data']['len']) + return True + else: + # Backup failed. + self.assert_qmp(event, 'data/error', error) + return False + + + def create_anchor_backup(self, drive=None): + if drive is None: + drive = self.drives[-1] + res = self.do_qmp_backup(device=drive['id'], sync='full', + format=drive['fmt'], target=drive['backup']) + self.assertTrue(res) + self.files.append(drive['backup']) + return drive['backup'] + + + def make_reference_backup(self, bitmap=None): + if bitmap is None: + bitmap = self.bitmaps[-1] + _, reference = bitmap.last_target() + res = self.do_qmp_backup(device=bitmap.drive['id'], sync='full', + format=bitmap.drive['fmt'], target=reference) + self.assertTrue(res) + + + def add_bitmap(self, name, drive, **kwargs): + bitmap = Bitmap(name, drive) + self.bitmaps.append(bitmap) + result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'], + name=bitmap.name, **kwargs) + self.assert_qmp(result, 'return', {}) + return bitmap + + + def prepare_backup(self, bitmap=None, parent=None): + if bitmap is None: + bitmap = self.bitmaps[-1] + if parent is None: + parent, _ = bitmap.last_target() + + target, _ = bitmap.new_target() + self.img_create(target, bitmap.drive['fmt'], parent=parent) + return target + + + def create_incremental(self, bitmap=None, parent=None, + parentFormat=None, validate=True): + if bitmap is None: + bitmap = self.bitmaps[-1] + if parent is None: + parent, _ = bitmap.last_target() + + target = self.prepare_backup(bitmap, parent) + res = self.do_qmp_backup(device=bitmap.drive['id'], + sync='dirty-bitmap', bitmap=bitmap.name, + format=bitmap.drive['fmt'], target=target, + mode='existing') + if not res: + bitmap.del_target(); + self.assertFalse(validate) + else: + self.make_reference_backup(bitmap) + return res + + + def check_backups(self): + for bitmap in self.bitmaps: + for incremental, reference in bitmap.backups: + self.assertTrue(iotests.compare_images(incremental, reference)) + last = bitmap.last_target()[0] + self.assertTrue(iotests.compare_images(last, bitmap.drive['file'])) + + + def hmp_io_writes(self, drive, patterns): + for pattern in patterns: + self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern) + self.vm.hmp_qemu_io(drive, 'flush') + + + def do_incremental_simple(self, **kwargs): + self.create_anchor_backup() + self.add_bitmap('bitmap0', self.drives[0], **kwargs) + + # Sanity: Create a "hollow" incremental backup + self.create_incremental() + # Three writes: One complete overwrite, one new segment, + # and one partial overlap. + self.hmp_io_writes(self.drives[0]['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + self.create_incremental() + # Three more writes, one of each kind, like above + self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) + self.create_incremental() + self.vm.shutdown() + self.check_backups() + + + def test_incremental_simple(self): + ''' + Test: Create and verify three incremental backups. + + Create a bitmap and a full backup before VM execution begins, + then create a series of three incremental backups "during execution," + i.e.; after IO requests begin modifying the drive. + ''' + return self.do_incremental_simple() + + + def test_small_granularity(self): + ''' + Test: Create and verify backups made with a small granularity bitmap. + + Perform the same test as test_incremental_simple, but with a granularity + of only 32KiB instead of the present default of 64KiB. + ''' + return self.do_incremental_simple(granularity=32768) + + + def test_large_granularity(self): + ''' + Test: Create and verify backups made with a large granularity bitmap. + + Perform the same test as test_incremental_simple, but with a granularity + of 128KiB instead of the present default of 64KiB. + ''' + return self.do_incremental_simple(granularity=131072) + + + def test_incremental_failure(self): + '''Test: Verify backups made after a failure are correct. + + Simulate a failure during an incremental backup block job, + emulate additional writes, then create another incremental backup + afterwards and verify that the backup created is correct. + ''' + + # Create a blkdebug interface to this img as 'drive1', + # but don't actually create a new image. + drive1 = self.add_node('drive1', self.drives[0]['fmt'], + path=self.drives[0]['file'], + backup=self.drives[0]['backup']) + 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', {}) + + self.create_anchor_backup(self.drives[0]) + self.add_bitmap('bitmap0', drive1) + # Note: at this point, during a normal execution, + # Assume that the VM resumes and begins issuing IO requests here. + + self.hmp_io_writes(drive1['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + + result = self.create_incremental(validate=False) + self.assertFalse(result) + self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) + self.create_incremental() + 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) + result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], + sync='dirty-bitmap', format=self.drives[0]['fmt'], + target=self.err_img) + self.assert_qmp(result, 'error/class', 'GenericError') + + + def test_sync_dirty_bitmap_not_found(self): + self.assert_no_active_block_jobs() + self.files.append(self.err_img) + result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], + sync='dirty-bitmap', bitmap='unknown', + format=self.drives[0]['fmt'], target=self.err_img) + self.assert_qmp(result, 'error/class', 'GenericError') + + + def test_sync_dirty_bitmap_bad_granularity(self): + ''' + Test: Test what happens if we provide an improper granularity. + + The granularity must always be a power of 2. + ''' + self.assert_no_active_block_jobs() + self.assertRaises(AssertionError, self.add_bitmap, + 'bitmap0', self.drives[0], + granularity=64000) + + + def tearDown(self): + self.vm.shutdown() + for bitmap in self.bitmaps: + bitmap.cleanup() + for filename in self.files: + try_remove(filename) + + +if __name__ == '__main__': + iotests.main(supported_fmts=['qcow2']) diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out new file mode 100644 index 0000000000..2f7d3902f2 --- /dev/null +++ b/tests/qemu-iotests/124.out @@ -0,0 +1,5 @@ +....... +---------------------------------------------------------------------- +Ran 7 tests + +OK diff --git a/tests/qemu-iotests/129 b/tests/qemu-iotests/129 new file mode 100644 index 0000000000..9e87e1c8d9 --- /dev/null +++ b/tests/qemu-iotests/129 @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# +# Tests that "bdrv_drain_all" doesn't drain block jobs +# +# 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 iotests +import time + +class TestStopWithBlockJob(iotests.QMPTestCase): + test_img = os.path.join(iotests.test_dir, 'test.img') + target_img = os.path.join(iotests.test_dir, 'target.img') + base_img = os.path.join(iotests.test_dir, 'base.img') + + def setUp(self): + iotests.qemu_img('create', '-f', iotests.imgfmt, self.base_img, "1G") + iotests.qemu_img('create', '-f', iotests.imgfmt, self.test_img, "-b", self.base_img) + iotests.qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x5d 1M 128M', self.test_img) + self.vm = iotests.VM().add_drive(self.test_img) + self.vm.launch() + + def tearDown(self): + params = {"device": "drive0", + "bps": 0, + "bps_rd": 0, + "bps_wr": 0, + "iops": 0, + "iops_rd": 0, + "iops_wr": 0, + } + result = self.vm.qmp("block_set_io_throttle", conv_keys=False, + **params) + self.vm.shutdown() + + def do_test_stop(self, cmd, **args): + """Test 'stop' while block job is running on a throttled drive. + The 'stop' command shouldn't drain the job""" + params = {"device": "drive0", + "bps": 1024, + "bps_rd": 0, + "bps_wr": 0, + "iops": 0, + "iops_rd": 0, + "iops_wr": 0, + } + result = self.vm.qmp("block_set_io_throttle", conv_keys=False, + **params) + self.assert_qmp(result, 'return', {}) + result = self.vm.qmp(cmd, **args) + self.assert_qmp(result, 'return', {}) + result = self.vm.qmp("stop") + self.assert_qmp(result, 'return', {}) + result = self.vm.qmp("query-block-jobs") + self.assert_qmp(result, 'return[0]/busy', True) + self.assert_qmp(result, 'return[0]/ready', False) + + def test_drive_mirror(self): + self.do_test_stop("drive-mirror", device="drive0", + target=self.target_img, + sync="full") + + def test_drive_backup(self): + self.do_test_stop("drive-backup", device="drive0", + target=self.target_img, + sync="full") + + def test_block_commit(self): + self.do_test_stop("block-commit", device="drive0") + +if __name__ == '__main__': + iotests.main(supported_fmts=["qcow2"]) diff --git a/tests/qemu-iotests/129.out b/tests/qemu-iotests/129.out new file mode 100644 index 0000000000..8d7e996700 --- /dev/null +++ b/tests/qemu-iotests/129.out @@ -0,0 +1,5 @@ +... +---------------------------------------------------------------------- +Ran 3 tests + +OK diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index bcf25786ab..6ca3466ec5 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -122,6 +122,9 @@ 115 rw auto 116 rw auto quick 121 rw auto +122 rw auto 123 rw auto quick +124 rw auto backing 128 rw auto quick +129 rw auto quick 130 rw auto quick diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 14028540b3..e93e62387b 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -78,6 +78,23 @@ def create_image(name, size): i = i + 512 file.close() +# Test if 'match' is a recursive subset of 'event' +def event_match(event, match=None): + if match is None: + return True + + for key in match: + if key in event: + if isinstance(event[key], dict): + if not event_match(event[key], match[key]): + return False + elif event[key] != match[key]: + return False + else: + return False + + return True + class VM(object): '''A QEMU VM''' @@ -92,6 +109,7 @@ class VM(object): '-machine', 'accel=qtest', '-display', 'none', '-vga', 'none'] self._num_drives = 0 + self._events = [] # This can be used to add an unused monitor instance. def add_monitor_telnet(self, ip, port): @@ -202,14 +220,34 @@ class VM(object): def get_qmp_event(self, wait=False): '''Poll for one queued QMP events and return it''' + if len(self._events) > 0: + return self._events.pop(0) return self._qmp.pull_event(wait=wait) def get_qmp_events(self, wait=False): '''Poll for queued QMP events and return a list of dicts''' events = self._qmp.get_events(wait=wait) + events.extend(self._events) + del self._events[:] self._qmp.clear_events() return events + def event_wait(self, name='BLOCK_JOB_COMPLETED', timeout=60.0, match=None): + # Search cached events + for event in self._events: + if (event['event'] == name) and event_match(event, match): + self._events.remove(event) + return event + + # Poll for new events + while True: + event = self._qmp.pull_event(wait=timeout) + if (event['event'] == name) and event_match(event, match): + return event + self._events.append(event) + + return None + index_re = re.compile(r'([^\[]+)\[([^\]]+)\]') class QMPTestCase(unittest.TestCase): diff --git a/tests/test-aio.c b/tests/test-aio.c index a7cb5c9915..4b0cb45d31 100644 --- a/tests/test-aio.c +++ b/tests/test-aio.c @@ -107,6 +107,7 @@ static void test_notify(void) typedef struct { QemuMutex start_lock; + EventNotifier notifier; bool thread_acquired; } AcquireTestData; @@ -118,6 +119,8 @@ static void *test_acquire_thread(void *opaque) qemu_mutex_lock(&data->start_lock); qemu_mutex_unlock(&data->start_lock); + g_usleep(500000); + event_notifier_set(&data->notifier); aio_context_acquire(ctx); aio_context_release(ctx); @@ -126,20 +129,19 @@ static void *test_acquire_thread(void *opaque) return NULL; } -static void dummy_notifier_read(EventNotifier *unused) +static void dummy_notifier_read(EventNotifier *n) { - g_assert(false); /* should never be invoked */ + event_notifier_test_and_clear(n); } static void test_acquire(void) { QemuThread thread; - EventNotifier notifier; AcquireTestData data; /* Dummy event notifier ensures aio_poll() will block */ - event_notifier_init(¬ifier, false); - aio_set_event_notifier(ctx, ¬ifier, dummy_notifier_read); + event_notifier_init(&data.notifier, false); + aio_set_event_notifier(ctx, &data.notifier, dummy_notifier_read); g_assert(!aio_poll(ctx, false)); /* consume aio_notify() */ qemu_mutex_init(&data.start_lock); @@ -153,12 +155,13 @@ static void test_acquire(void) /* Block in aio_poll(), let other thread kick us and acquire context */ aio_context_acquire(ctx); qemu_mutex_unlock(&data.start_lock); /* let the thread run */ - g_assert(!aio_poll(ctx, true)); + g_assert(aio_poll(ctx, true)); + g_assert(!data.thread_acquired); aio_context_release(ctx); qemu_thread_join(&thread); - aio_set_event_notifier(ctx, ¬ifier, NULL); - event_notifier_cleanup(¬ifier); + aio_set_event_notifier(ctx, &data.notifier, NULL); + event_notifier_cleanup(&data.notifier); g_assert(data.thread_acquired); } diff --git a/tests/test-hbitmap.c b/tests/test-hbitmap.c index 8c902f2055..9f41b5fd2e 100644 --- a/tests/test-hbitmap.c +++ b/tests/test-hbitmap.c @@ -11,6 +11,8 @@ #include <glib.h> #include <stdarg.h> +#include <string.h> +#include <sys/types.h> #include "qemu/hbitmap.h" #define LOG_BITS_PER_LONG (BITS_PER_LONG == 32 ? 5 : 6) @@ -23,6 +25,7 @@ typedef struct TestHBitmapData { HBitmap *hb; unsigned long *bits; size_t size; + size_t old_size; int granularity; } TestHBitmapData; @@ -91,6 +94,44 @@ static void hbitmap_test_init(TestHBitmapData *data, } } +static inline size_t hbitmap_test_array_size(size_t bits) +{ + size_t n = (bits + BITS_PER_LONG - 1) / BITS_PER_LONG; + return n ? n : 1; +} + +static void hbitmap_test_truncate_impl(TestHBitmapData *data, + size_t size) +{ + size_t n; + size_t m; + data->old_size = data->size; + data->size = size; + + if (data->size == data->old_size) { + return; + } + + n = hbitmap_test_array_size(size); + m = hbitmap_test_array_size(data->old_size); + data->bits = g_realloc(data->bits, sizeof(unsigned long) * n); + if (n > m) { + memset(&data->bits[m], 0x00, sizeof(unsigned long) * (n - m)); + } + + /* If we shrink to an uneven multiple of sizeof(unsigned long), + * scrub the leftover memory. */ + if (data->size < data->old_size) { + m = size % (sizeof(unsigned long) * 8); + if (m) { + unsigned long mask = (1ULL << m) - 1; + data->bits[n-1] &= mask; + } + } + + hbitmap_truncate(data->hb, size); +} + static void hbitmap_test_teardown(TestHBitmapData *data, const void *unused) { @@ -369,6 +410,198 @@ static void test_hbitmap_iter_granularity(TestHBitmapData *data, g_assert_cmpint(hbitmap_iter_next(&hbi), <, 0); } +static void hbitmap_test_set_boundary_bits(TestHBitmapData *data, ssize_t diff) +{ + size_t size = data->size; + + /* First bit */ + hbitmap_test_set(data, 0, 1); + if (diff < 0) { + /* Last bit in new, shortened map */ + hbitmap_test_set(data, size + diff - 1, 1); + + /* First bit to be truncated away */ + hbitmap_test_set(data, size + diff, 1); + } + /* Last bit */ + hbitmap_test_set(data, size - 1, 1); + if (data->granularity == 0) { + hbitmap_test_check_get(data); + } +} + +static void hbitmap_test_check_boundary_bits(TestHBitmapData *data) +{ + size_t size = MIN(data->size, data->old_size); + + if (data->granularity == 0) { + hbitmap_test_check_get(data); + hbitmap_test_check(data, 0); + } else { + /* If a granularity was set, note that every distinct + * (bit >> granularity) value that was set will increase + * the bit pop count by 2^granularity, not just 1. + * + * The hbitmap_test_check facility does not currently tolerate + * non-zero granularities, so test the boundaries and the population + * count manually. + */ + g_assert(hbitmap_get(data->hb, 0)); + g_assert(hbitmap_get(data->hb, size - 1)); + g_assert_cmpint(2 << data->granularity, ==, hbitmap_count(data->hb)); + } +} + +/* Generic truncate test. */ +static void hbitmap_test_truncate(TestHBitmapData *data, + size_t size, + ssize_t diff, + int granularity) +{ + hbitmap_test_init(data, size, granularity); + hbitmap_test_set_boundary_bits(data, diff); + hbitmap_test_truncate_impl(data, size + diff); + hbitmap_test_check_boundary_bits(data); +} + +static void test_hbitmap_truncate_nop(TestHBitmapData *data, + const void *unused) +{ + hbitmap_test_truncate(data, L2, 0, 0); +} + +/** + * Grow by an amount smaller than the granularity, without crossing + * a granularity alignment boundary. Effectively a NOP. + */ +static void test_hbitmap_truncate_grow_negligible(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2 - 1; + size_t diff = 1; + int granularity = 1; + + hbitmap_test_truncate(data, size, diff, granularity); +} + +/** + * Shrink by an amount smaller than the granularity, without crossing + * a granularity alignment boundary. Effectively a NOP. + */ +static void test_hbitmap_truncate_shrink_negligible(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2; + ssize_t diff = -1; + int granularity = 1; + + hbitmap_test_truncate(data, size, diff, granularity); +} + +/** + * Grow by an amount smaller than the granularity, but crossing over + * a granularity alignment boundary. + */ +static void test_hbitmap_truncate_grow_tiny(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2 - 2; + ssize_t diff = 1; + int granularity = 1; + + hbitmap_test_truncate(data, size, diff, granularity); +} + +/** + * Shrink by an amount smaller than the granularity, but crossing over + * a granularity alignment boundary. + */ +static void test_hbitmap_truncate_shrink_tiny(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2 - 1; + ssize_t diff = -1; + int granularity = 1; + + hbitmap_test_truncate(data, size, diff, granularity); +} + +/** + * Grow by an amount smaller than sizeof(long), and not crossing over + * a sizeof(long) alignment boundary. + */ +static void test_hbitmap_truncate_grow_small(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2 + 1; + size_t diff = sizeof(long) / 2; + + hbitmap_test_truncate(data, size, diff, 0); +} + +/** + * Shrink by an amount smaller than sizeof(long), and not crossing over + * a sizeof(long) alignment boundary. + */ +static void test_hbitmap_truncate_shrink_small(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2; + size_t diff = sizeof(long) / 2; + + hbitmap_test_truncate(data, size, -diff, 0); +} + +/** + * Grow by an amount smaller than sizeof(long), while crossing over + * a sizeof(long) alignment boundary. + */ +static void test_hbitmap_truncate_grow_medium(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2 - 1; + size_t diff = sizeof(long) / 2; + + hbitmap_test_truncate(data, size, diff, 0); +} + +/** + * Shrink by an amount smaller than sizeof(long), while crossing over + * a sizeof(long) alignment boundary. + */ +static void test_hbitmap_truncate_shrink_medium(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2 + 1; + size_t diff = sizeof(long) / 2; + + hbitmap_test_truncate(data, size, -diff, 0); +} + +/** + * Grow by an amount larger than sizeof(long). + */ +static void test_hbitmap_truncate_grow_large(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2; + size_t diff = 8 * sizeof(long); + + hbitmap_test_truncate(data, size, diff, 0); +} + +/** + * Shrink by an amount larger than sizeof(long). + */ +static void test_hbitmap_truncate_shrink_large(TestHBitmapData *data, + const void *unused) +{ + size_t size = L2; + size_t diff = 8 * sizeof(long); + + hbitmap_test_truncate(data, size, -diff, 0); +} + static void hbitmap_test_add(const char *testpath, void (*test_func)(TestHBitmapData *data, const void *user_data)) { @@ -395,6 +628,28 @@ int main(int argc, char **argv) hbitmap_test_add("/hbitmap/reset/empty", test_hbitmap_reset_empty); hbitmap_test_add("/hbitmap/reset/general", test_hbitmap_reset); hbitmap_test_add("/hbitmap/granularity", test_hbitmap_granularity); + + hbitmap_test_add("/hbitmap/truncate/nop", test_hbitmap_truncate_nop); + hbitmap_test_add("/hbitmap/truncate/grow/negligible", + test_hbitmap_truncate_grow_negligible); + hbitmap_test_add("/hbitmap/truncate/shrink/negligible", + test_hbitmap_truncate_shrink_negligible); + hbitmap_test_add("/hbitmap/truncate/grow/tiny", + test_hbitmap_truncate_grow_tiny); + hbitmap_test_add("/hbitmap/truncate/shrink/tiny", + test_hbitmap_truncate_shrink_tiny); + hbitmap_test_add("/hbitmap/truncate/grow/small", + test_hbitmap_truncate_grow_small); + hbitmap_test_add("/hbitmap/truncate/shrink/small", + test_hbitmap_truncate_shrink_small); + hbitmap_test_add("/hbitmap/truncate/grow/medium", + test_hbitmap_truncate_grow_medium); + hbitmap_test_add("/hbitmap/truncate/shrink/medium", + test_hbitmap_truncate_shrink_medium); + hbitmap_test_add("/hbitmap/truncate/grow/large", + test_hbitmap_truncate_grow_large); + hbitmap_test_add("/hbitmap/truncate/shrink/large", + test_hbitmap_truncate_shrink_large); g_test_run(); return 0; |