diff options
Diffstat (limited to 'tests')
47 files changed, 1040 insertions, 481 deletions
diff --git a/tests/Makefile.include b/tests/Makefile.include index d34254fb29..799e47169c 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -92,7 +92,7 @@ TESTS_RESULTS_DIR=$(BUILD_DIR)/tests/results # Any number of command separated loggers are accepted. For more # information please refer to "avocado --help". AVOCADO_SHOW=app -AVOCADO_TAGS=$(patsubst %-softmmu,-t arch:%, $(filter %-softmmu,$(TARGET_DIRS))) +AVOCADO_TAGS=$(patsubst %-softmmu,-t arch:%, $(filter %-softmmu,$(TARGETS))) $(TESTS_VENV_DIR): $(TESTS_VENV_REQ) $(call quiet-command, \ @@ -109,7 +109,8 @@ $(TESTS_RESULTS_DIR): check-venv: $(TESTS_VENV_DIR) -FEDORA_31_ARCHES_CANDIDATES=$(patsubst ppc64,ppc64le,$(TARGETS)) +FEDORA_31_ARCHES_TARGETS=$(patsubst %-softmmu,%, $(filter %-softmmu,$(TARGETS))) +FEDORA_31_ARCHES_CANDIDATES=$(patsubst ppc64,ppc64le,$(FEDORA_31_ARCHES_TARGETS)) FEDORA_31_ARCHES := x86_64 aarch64 ppc64le s390x FEDORA_31_DOWNLOAD=$(filter $(FEDORA_31_ARCHES),$(FEDORA_31_ARCHES_CANDIDATES)) diff --git a/tests/acceptance/boot_xen.py b/tests/acceptance/boot_xen.py new file mode 100644 index 0000000000..75c2d44492 --- /dev/null +++ b/tests/acceptance/boot_xen.py @@ -0,0 +1,118 @@ +# Functional test that boots a Xen hypervisor with a domU kernel and +# checks the console output is vaguely sane . +# +# Copyright (c) 2020 Linaro +# +# Author: +# Alex Bennée <alex.bennee@linaro.org> +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. + +import os + +from avocado import skipIf +from avocado_qemu import wait_for_console_pattern +from boot_linux_console import LinuxKernelTest + + +class BootXenBase(LinuxKernelTest): + """ + Boots a Xen hypervisor with a Linux DomU kernel. + """ + + timeout = 90 + XEN_COMMON_COMMAND_LINE = 'dom0_mem=128M loglvl=all guest_loglvl=all' + + def fetch_guest_kernel(self): + # Using my own built kernel - which works + kernel_url = ('https://fileserver.linaro.org/' + 's/JSsewXGZ6mqxPr5/download?path=%2F&files=' + 'linux-5.9.9-arm64-ajb') + kernel_sha1 = '4f92bc4b9f88d5ab792fa7a43a68555d344e1b83' + kernel_path = self.fetch_asset(kernel_url, + asset_hash=kernel_sha1) + + return kernel_path + + def launch_xen(self, xen_path): + """ + Launch Xen with a dom0 guest kernel + """ + self.log.info("launch with xen_path: %s", xen_path) + kernel_path = self.fetch_guest_kernel() + + self.vm.set_console() + + xen_command_line = self.XEN_COMMON_COMMAND_LINE + self.vm.add_args('-machine', 'virtualization=on', + '-cpu', 'cortex-a57', + '-m', '768', + '-kernel', xen_path, + '-append', xen_command_line, + '-device', + 'guest-loader,addr=0x47000000,kernel=%s,bootargs=console=hvc0' + % (kernel_path)) + + self.vm.launch() + + console_pattern = 'VFS: Cannot open root device' + wait_for_console_pattern(self, console_pattern, "Panic on CPU 0:") + + +class BootXen(BootXenBase): + + def test_arm64_xen_411_and_dom0(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=accel:tcg + :avocado: tags=cpu:cortex-a57 + :avocado: tags=machine:virt + """ + + # archive of file from https://deb.debian.org/debian/pool/main/x/xen/ + xen_url = ('https://fileserver.linaro.org/s/JSsewXGZ6mqxPr5/' + 'download?path=%2F&files=' + 'xen-hypervisor-4.11-arm64_4.11.4%2B37-g3263f257ca-1_arm64.deb') + xen_sha1 = '034e634d4416adbad1212d59b62bccdcda63e62a' + xen_deb = self.fetch_asset(xen_url, asset_hash=xen_sha1) + xen_path = self.extract_from_deb(xen_deb, "/boot/xen-4.11-arm64") + + self.launch_xen(xen_path) + + def test_arm64_xen_414_and_dom0(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=accel:tcg + :avocado: tags=cpu:cortex-a57 + :avocado: tags=machine:virt + """ + + # archive of file from https://deb.debian.org/debian/pool/main/x/xen/ + xen_url = ('https://fileserver.linaro.org/s/JSsewXGZ6mqxPr5/' + 'download?path=%2F&files=' + 'xen-hypervisor-4.14-arm64_4.14.0%2B80-gd101b417b7-1_arm64.deb') + xen_sha1 = 'b9d209dd689ed2b393e625303a225badefec1160' + xen_deb = self.fetch_asset(xen_url, asset_hash=xen_sha1) + xen_path = self.extract_from_deb(xen_deb, "/boot/xen-4.14-arm64") + + self.launch_xen(xen_path) + + def test_arm64_xen_415_and_dom0(self): + """ + :avocado: tags=arch:aarch64 + :avocado: tags=accel:tcg + :avocado: tags=cpu:cortex-a57 + :avocado: tags=machine:virt + """ + + xen_url = ('https://fileserver.linaro.org/' + 's/JSsewXGZ6mqxPr5/download' + '?path=%2F&files=xen-upstream-4.15-unstable.deb') + xen_sha1 = 'fc191172b85cf355abb95d275a24cc0f6d6579d8' + xen_deb = self.fetch_asset(xen_url, asset_hash=xen_sha1) + xen_path = self.extract_from_deb(xen_deb, "/boot/xen-4.15-unstable") + + self.launch_xen(xen_path) diff --git a/tests/docker/dockerfiles/debian10.docker b/tests/docker/dockerfiles/debian10.docker index 9d42b5a4b8..d034acbd25 100644 --- a/tests/docker/dockerfiles/debian10.docker +++ b/tests/docker/dockerfiles/debian10.docker @@ -32,6 +32,6 @@ RUN apt update && \ psmisc \ python3 \ python3-sphinx \ - $(apt-get -s build-dep qemu | egrep ^Inst | fgrep '[all]' | cut -d\ -f2) + $(apt-get -s build-dep --arch-only qemu | egrep ^Inst | fgrep '[all]' | cut -d\ -f2) ENV FEATURES docs diff --git a/tests/docker/test-tcg b/tests/docker/test-tcg new file mode 100755 index 0000000000..00993b73ba --- /dev/null +++ b/tests/docker/test-tcg @@ -0,0 +1,22 @@ +#!/bin/bash -e +# +# Build and run the TCG tests +# +# Copyright (c) 2021 Linaro Ltd. +# +# Authors: +# Alex Bennée <alex.bennee@linaro.org> +# +# This work is licensed under the terms of the GNU GPL, version 2 +# or (at your option) any later version. See the COPYING file in +# the top-level directory. + +. common.rc + +cd "$BUILD_DIR" + +# although we are not building QEMU itself we still need a configured +# build for the unit tests to be built and run +TARGET_LIST=${TARGET_LIST:-$DEF_TARGET_LIST} \ +build_qemu +check_qemu check-tcg diff --git a/tests/qemu-iotests/030 b/tests/qemu-iotests/030 index 12aa9ed37e..5fb65b4bef 100755 --- a/tests/qemu-iotests/030 +++ b/tests/qemu-iotests/030 @@ -153,7 +153,7 @@ class TestSingleDrive(iotests.QMPTestCase): def test_device_not_found(self): result = self.vm.qmp('block-stream', device='nonexistent') self.assert_qmp(result, 'error/desc', - 'Cannot find device=nonexistent nor node_name=nonexistent') + 'Cannot find device=\'nonexistent\' nor node-name=\'nonexistent\'') def test_job_id_missing(self): result = self.vm.qmp('block-stream', device='mid') @@ -507,7 +507,7 @@ class TestParallelOps(iotests.QMPTestCase): # Error: the base node does not exist result = self.vm.qmp('block-stream', device='node4', base_node='none', job_id='stream') self.assert_qmp(result, 'error/desc', - 'Cannot find device= nor node_name=none') + 'Cannot find device=\'\' nor node-name=\'none\'') # Error: the base node is not a backing file of the top node result = self.vm.qmp('block-stream', device='node4', base_node='node6', job_id='stream') diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040 index 7ebc9ed825..336ff7c4f2 100755 --- a/tests/qemu-iotests/040 +++ b/tests/qemu-iotests/040 @@ -175,13 +175,13 @@ class TestSingleDrive(ImageCommitTestCase): self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top_node='badfile', base_node='base') self.assert_qmp(result, 'error/class', 'GenericError') - self.assert_qmp(result, 'error/desc', "Cannot find device= nor node_name=badfile") + self.assert_qmp(result, 'error/desc', "Cannot find device='' nor node-name='badfile'") def test_base_node_invalid(self): self.assert_no_active_block_jobs() result = self.vm.qmp('block-commit', device='drive0', top_node='mid', base_node='badfile') self.assert_qmp(result, 'error/class', 'GenericError') - self.assert_qmp(result, 'error/desc', "Cannot find device= nor node_name=badfile") + self.assert_qmp(result, 'error/desc', "Cannot find device='' nor node-name='badfile'") def test_top_path_and_node(self): self.assert_no_active_block_jobs() diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out index b1d8fd9107..01f7b1f240 100644 --- a/tests/qemu-iotests/049.out +++ b/tests/qemu-iotests/049.out @@ -92,16 +92,22 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off comp == 3. Invalid sizes == qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1024 -qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807. +qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for +qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. qemu-img create -f qcow2 -o size=-1024 TEST_DIR/t.qcow2 -qemu-img: TEST_DIR/t.qcow2: Value '-1024' is out of range for parameter 'size' +qemu-img: TEST_DIR/t.qcow2: Parameter 'size' expects a non-negative number below 2^64 +Optional suffix k, M, G, T, P or E means kilo-, mega-, giga-, tera-, peta- +and exabytes, respectively. qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1k -qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807. +qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for +qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. qemu-img create -f qcow2 -o size=-1k TEST_DIR/t.qcow2 -qemu-img: TEST_DIR/t.qcow2: Value '-1k' is out of range for parameter 'size' +qemu-img: TEST_DIR/t.qcow2: Parameter 'size' expects a non-negative number below 2^64 +Optional suffix k, M, G, T, P or E means kilo-, mega-, giga-, tera-, peta- +and exabytes, respectively. qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- 1kilobyte qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for diff --git a/tests/qemu-iotests/051.pc.out b/tests/qemu-iotests/051.pc.out index f707471fb0..f570610f64 100644 --- a/tests/qemu-iotests/051.pc.out +++ b/tests/qemu-iotests/051.pc.out @@ -61,13 +61,13 @@ QEMU X.Y.Z monitor - type 'help' for more information (qemu) quit Testing: -drive file=TEST_DIR/t.qcow2,node-name=123foo -QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=123foo: Invalid node name +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=123foo: Invalid node-name: '123foo' Testing: -drive file=TEST_DIR/t.qcow2,node-name=_foo -QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=_foo: Invalid node name +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=_foo: Invalid node-name: '_foo' Testing: -drive file=TEST_DIR/t.qcow2,node-name=foo#12 -QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=foo#12: Invalid node name +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,node-name=foo#12: Invalid node-name: 'foo#12' === Device without drive === diff --git a/tests/qemu-iotests/081.out b/tests/qemu-iotests/081.out index 1974262fac..615c083549 100644 --- a/tests/qemu-iotests/081.out +++ b/tests/qemu-iotests/081.out @@ -140,7 +140,7 @@ Testing: QMP_VERSION {"return": {}} {"error": {"class": "GenericError", "desc": "blkverify=on can only be set if there are exactly two files and vote-threshold is 2"}} -{"error": {"class": "GenericError", "desc": "Cannot find device=drive0-quorum nor node_name=drive0-quorum"}} +{"error": {"class": "GenericError", "desc": "Cannot find device='drive0-quorum' nor node-name='drive0-quorum'"}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} diff --git a/tests/qemu-iotests/085.out b/tests/qemu-iotests/085.out index 32a193f2c2..1d4c565b6d 100644 --- a/tests/qemu-iotests/085.out +++ b/tests/qemu-iotests/085.out @@ -24,7 +24,7 @@ Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 extended { 'execute': 'blockdev-snapshot-sync', 'arguments': { 'snapshot-file':'TEST_DIR/1-snapshot-v0.IMGFMT', 'format': 'IMGFMT' } } -{"error": {"class": "GenericError", "desc": "Cannot find device= nor node_name="}} +{"error": {"class": "GenericError", "desc": "Cannot find device='' nor node-name=''"}} === Invalid command - missing snapshot-file === @@ -222,10 +222,10 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/ { 'execute': 'blockdev-snapshot', 'arguments': { 'node': 'virtio0', 'overlay':'snap_14' } } -{"error": {"class": "GenericError", "desc": "Cannot find device=snap_14 nor node_name=snap_14"}} +{"error": {"class": "GenericError", "desc": "Cannot find device='snap_14' nor node-name='snap_14'"}} { 'execute': 'blockdev-snapshot', 'arguments': { 'node':'nodevice', 'overlay':'snap_13' } } -{"error": {"class": "GenericError", "desc": "Cannot find device=nodevice nor node_name=nodevice"}} +{"error": {"class": "GenericError", "desc": "Cannot find device='nodevice' nor node-name='nodevice'"}} *** done diff --git a/tests/qemu-iotests/087 b/tests/qemu-iotests/087 index edd43f1a28..d8e0e384cd 100755 --- a/tests/qemu-iotests/087 +++ b/tests/qemu-iotests/087 @@ -143,9 +143,7 @@ run_qemu <<EOF "arguments": { "qom-type": "secret", "id": "sec0", - "props": { - "data": "123456" - } + "data": "123456" } } { "execute": "blockdev-add", @@ -176,9 +174,7 @@ run_qemu <<EOF "arguments": { "qom-type": "secret", "id": "sec0", - "props": { - "data": "123456" - } + "data": "123456" } } { "execute": "blockdev-add", diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out index b61ba638af..e1c23a6983 100644 --- a/tests/qemu-iotests/087.out +++ b/tests/qemu-iotests/087.out @@ -17,7 +17,7 @@ Testing: -drive driver=IMGFMT,id=disk,node-name=test-node,file=TEST_DIR/t.IMGFMT QMP_VERSION {"return": {}} {"error": {"class": "GenericError", "desc": "node-name=disk is conflicting with a device id"}} -{"error": {"class": "GenericError", "desc": "Duplicate node name"}} +{"error": {"class": "GenericError", "desc": "Duplicate nodes with node-name='test-node'"}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} diff --git a/tests/qemu-iotests/178.out.qcow2 b/tests/qemu-iotests/178.out.qcow2 index fe193fd5f4..0d51fe401e 100644 --- a/tests/qemu-iotests/178.out.qcow2 +++ b/tests/qemu-iotests/178.out.qcow2 @@ -13,7 +13,8 @@ qemu-img: Invalid option list: , qemu-img: Invalid parameter 'snapshot.foo' qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar' qemu-img: --output must be used with human or json as argument. -qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807. +qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for +qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. qemu-img: Unknown file format 'foo' == Size calculation for a new file (human) == diff --git a/tests/qemu-iotests/178.out.raw b/tests/qemu-iotests/178.out.raw index 445e460fad..116241ddef 100644 --- a/tests/qemu-iotests/178.out.raw +++ b/tests/qemu-iotests/178.out.raw @@ -13,7 +13,8 @@ qemu-img: Invalid option list: , qemu-img: Invalid parameter 'snapshot.foo' qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar' qemu-img: --output must be used with human or json as argument. -qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807. +qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for +qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. qemu-img: Unknown file format 'foo' == Size calculation for a new file (human) == diff --git a/tests/qemu-iotests/184 b/tests/qemu-iotests/184 index 513d167098..e4cbcd8634 100755 --- a/tests/qemu-iotests/184 +++ b/tests/qemu-iotests/184 @@ -67,10 +67,8 @@ run_qemu <<EOF "arguments": { "qom-type": "throttle-group", "id": "group0", - "props": { - "limits" : { - "iops-total": 1000 - } + "limits" : { + "iops-total": 1000 } } } @@ -96,10 +94,8 @@ run_qemu <<EOF "arguments": { "qom-type": "throttle-group", "id": "group0", - "props" : { - "limits": { - "iops-total": 1000 - } + "limits": { + "iops-total": 1000 } } } @@ -136,10 +132,8 @@ run_qemu <<EOF "arguments": { "qom-type": "throttle-group", "id": "group0", - "props" : { - "limits": { - "iops-total": 1000 - } + "limits": { + "iops-total": 1000 } } } diff --git a/tests/qemu-iotests/206.out b/tests/qemu-iotests/206.out index 5dd589d14e..b68c443867 100644 --- a/tests/qemu-iotests/206.out +++ b/tests/qemu-iotests/206.out @@ -155,7 +155,7 @@ Format specific information: {"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "qcow2", "file": "this doesn't exist", "size": 33554432}}} {"return": {}} -Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist +Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist' {"execute": "job-dismiss", "arguments": {"id": "job0"}} {"return": {}} diff --git a/tests/qemu-iotests/210.out b/tests/qemu-iotests/210.out index 2e9fc596eb..55c0844370 100644 --- a/tests/qemu-iotests/210.out +++ b/tests/qemu-iotests/210.out @@ -108,7 +108,7 @@ Format specific information: {"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "luks", "file": "this doesn't exist", "size": 67108864}}} {"return": {}} -Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist +Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist' {"execute": "job-dismiss", "arguments": {"id": "job0"}} {"return": {}} diff --git a/tests/qemu-iotests/211.out b/tests/qemu-iotests/211.out index b83384deea..3bc092a8a8 100644 --- a/tests/qemu-iotests/211.out +++ b/tests/qemu-iotests/211.out @@ -62,7 +62,7 @@ cluster_size: 1048576 {"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "vdi", "file": "this doesn't exist", "size": 33554432}}} {"return": {}} -Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist +Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist' {"execute": "job-dismiss", "arguments": {"id": "job0"}} {"return": {}} diff --git a/tests/qemu-iotests/212.out b/tests/qemu-iotests/212.out index 1538d679be..8102033488 100644 --- a/tests/qemu-iotests/212.out +++ b/tests/qemu-iotests/212.out @@ -52,7 +52,7 @@ virtual size: 32 MiB (33554432 bytes) {"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "parallels", "file": "this doesn't exist", "size": 33554432}}} {"return": {}} -Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist +Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist' {"execute": "job-dismiss", "arguments": {"id": "job0"}} {"return": {}} diff --git a/tests/qemu-iotests/213.out b/tests/qemu-iotests/213.out index be4ae85180..3cdce4d790 100644 --- a/tests/qemu-iotests/213.out +++ b/tests/qemu-iotests/213.out @@ -55,7 +55,7 @@ cluster_size: 268435456 {"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "vhdx", "file": "this doesn't exist", "size": 33554432}}} {"return": {}} -Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist +Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist' {"execute": "job-dismiss", "arguments": {"id": "job0"}} {"return": {}} diff --git a/tests/qemu-iotests/218 b/tests/qemu-iotests/218 index ae7c4fb187..325d8244fb 100755 --- a/tests/qemu-iotests/218 +++ b/tests/qemu-iotests/218 @@ -152,7 +152,7 @@ with iotests.VM() as vm, \ vm.launch() ret = vm.qmp('object-add', qom_type='throttle-group', id='tg', - props={'x-bps-read': 4096}) + limits={'bps-read': 4096}) assert ret['return'] == {} ret = vm.qmp('blockdev-add', diff --git a/tests/qemu-iotests/223.out b/tests/qemu-iotests/223.out index bbc85289e3..083b62d053 100644 --- a/tests/qemu-iotests/223.out +++ b/tests/qemu-iotests/223.out @@ -53,7 +53,7 @@ exports available: 0 {"return": {}} {"execute":"nbd-server-add", "arguments":{"device":"nosuch"}} -{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}} +{"error": {"class": "GenericError", "desc": "Cannot find device='nosuch' nor node-name='nosuch'"}} {"execute":"nbd-server-add", "arguments":{"device":"n"}} {"error": {"class": "GenericError", "desc": "Block export id 'n' is already in use"}} @@ -154,7 +154,7 @@ exports available: 0 {"return": {}} {"execute":"nbd-server-add", "arguments":{"device":"nosuch"}} -{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}} +{"error": {"class": "GenericError", "desc": "Cannot find device='nosuch' nor node-name='nosuch'"}} {"execute":"nbd-server-add", "arguments":{"device":"n"}} {"error": {"class": "GenericError", "desc": "Block export id 'n' is already in use"}} diff --git a/tests/qemu-iotests/235 b/tests/qemu-iotests/235 index 20d16dbf38..8aed45f9a7 100755 --- a/tests/qemu-iotests/235 +++ b/tests/qemu-iotests/235 @@ -57,7 +57,7 @@ vm.add_args('-drive', 'id=src,file=' + disk) vm.launch() log(vm.qmp('object-add', qom_type='throttle-group', id='tg0', - props={ 'x-bps-total': size })) + limits={'bps-total': size})) log(vm.qmp('blockdev-add', **{ 'node-name': 'target', diff --git a/tests/qemu-iotests/237.out b/tests/qemu-iotests/237.out index a8c800bfad..aa94986803 100644 --- a/tests/qemu-iotests/237.out +++ b/tests/qemu-iotests/237.out @@ -85,7 +85,7 @@ Format specific information: {"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "vmdk", "file": "this doesn't exist", "size": 33554432}}} {"return": {}} -Job failed: Cannot find device=this doesn't exist nor node_name=this doesn't exist +Job failed: Cannot find device='this doesn't exist' nor node-name='this doesn't exist' {"execute": "job-dismiss", "arguments": {"id": "job0"}} {"return": {}} diff --git a/tests/qemu-iotests/241.out b/tests/qemu-iotests/241.out index 75f9f465e5..3f8c173cc8 100644 --- a/tests/qemu-iotests/241.out +++ b/tests/qemu-iotests/241.out @@ -5,7 +5,7 @@ QA output created by 241 size: 1024 min block: 1 [{ "start": 0, "length": 1000, "depth": 0, "zero": false, "data": true, "offset": OFFSET}, -{ "start": 1000, "length": 24, "depth": 0, "zero": true, "data": true, "offset": OFFSET}] +{ "start": 1000, "length": 24, "depth": 0, "zero": true, "data": false, "offset": OFFSET}] 1 KiB (0x400) bytes allocated at offset 0 bytes (0x0) === Exporting unaligned raw image, forced server sector alignment === @@ -23,6 +23,6 @@ WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed size: 1024 min block: 1 [{ "start": 0, "length": 1000, "depth": 0, "zero": false, "data": true, "offset": OFFSET}, -{ "start": 1000, "length": 24, "depth": 0, "zero": true, "data": true, "offset": OFFSET}] +{ "start": 1000, "length": 24, "depth": 0, "zero": true, "data": false, "offset": OFFSET}] 1 KiB (0x400) bytes allocated at offset 0 bytes (0x0) *** done diff --git a/tests/qemu-iotests/245 b/tests/qemu-iotests/245 index cfdeb902be..11104b9208 100755 --- a/tests/qemu-iotests/245 +++ b/tests/qemu-iotests/245 @@ -140,8 +140,8 @@ class TestBlockdevReopen(iotests.QMPTestCase): self.reopen(opts, {'file': 'hd0-file'}) # We cannot change any of these - self.reopen(opts, {'node-name': 'not-found'}, "Cannot find node named 'not-found'") - self.reopen(opts, {'node-name': ''}, "Cannot find node named ''") + self.reopen(opts, {'node-name': 'not-found'}, "Failed to find node with node-name='not-found'") + self.reopen(opts, {'node-name': ''}, "Failed to find node with node-name=''") self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'node-name', expected: string") self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'") self.reopen(opts, {'driver': ''}, "Invalid parameter ''") @@ -158,7 +158,7 @@ class TestBlockdevReopen(iotests.QMPTestCase): # node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it del opts['node-name'] - self.reopen(opts, {}, "Node name not specified") + self.reopen(opts, {}, "node-name not specified") # Check that nothing has changed self.check_node_graph(original_graph) @@ -187,8 +187,8 @@ class TestBlockdevReopen(iotests.QMPTestCase): self.reopen(opts, {'backing': backing_node_name}) # We can't use a non-existing or empty (non-NULL) node as the backing image - self.reopen(opts, {'backing': 'not-found'}, "Cannot find device= nor node_name=not-found") - self.reopen(opts, {'backing': ''}, "Cannot find device= nor node_name=") + self.reopen(opts, {'backing': 'not-found'}, "Cannot find device=\'\' nor node-name=\'not-found\'") + self.reopen(opts, {'backing': ''}, "Cannot find device=\'\' nor node-name=\'\'") # We can reopen the image just fine if we specify the backing options opts['backing'] = {'driver': iotests.imgfmt, @@ -644,12 +644,12 @@ class TestBlockdevReopen(iotests.QMPTestCase): ###### throttle ###### ###################### opts = { 'qom-type': 'throttle-group', 'id': 'group0', - 'props': { 'limits': { 'iops-total': 1000 } } } + 'limits': { 'iops-total': 1000 } } result = self.vm.qmp('object-add', conv_keys = False, **opts) self.assert_qmp(result, 'return', {}) opts = { 'qom-type': 'throttle-group', 'id': 'group1', - 'props': { 'limits': { 'iops-total': 2000 } } } + 'limits': { 'iops-total': 2000 } } result = self.vm.qmp('object-add', conv_keys = False, **opts) self.assert_qmp(result, 'return', {}) diff --git a/tests/qemu-iotests/249.out b/tests/qemu-iotests/249.out index 92ec81db03..d2bf9be85e 100644 --- a/tests/qemu-iotests/249.out +++ b/tests/qemu-iotests/249.out @@ -18,7 +18,7 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t. 'filter-node-name': '1234'}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}} -{"error": {"class": "GenericError", "desc": "Invalid node name"}} +{"error": {"class": "GenericError", "desc": "Invalid node-name: '1234'"}} === Send a write command to a drive opened in read-only mode (2) diff --git a/tests/qemu-iotests/258 b/tests/qemu-iotests/258 index 9a2d33ae5e..a6618208a8 100755 --- a/tests/qemu-iotests/258 +++ b/tests/qemu-iotests/258 @@ -103,9 +103,9 @@ def test_concurrent_finish(write_to_stream_node): vm.qmp_log('object-add', qom_type='throttle-group', id='tg', - props={ - 'x-iops-write': 1, - 'x-iops-write-max': 1 + limits={ + 'iops-write': 1, + 'iops-write-max': 1 }) vm.qmp_log('blockdev-add', diff --git a/tests/qemu-iotests/258.out b/tests/qemu-iotests/258.out index ce6e9ba3e5..c3a003d3e3 100644 --- a/tests/qemu-iotests/258.out +++ b/tests/qemu-iotests/258.out @@ -2,7 +2,7 @@ Running tests: === Commit and stream finish concurrently (letting stream write) === -{"execute": "object-add", "arguments": {"id": "tg", "props": {"x-iops-write": 1, "x-iops-write-max": 1}, "qom-type": "throttle-group"}} +{"execute": "object-add", "arguments": {"id": "tg", "limits": {"iops-write": 1, "iops-write-max": 1}, "qom-type": "throttle-group"}} {"return": {}} {"execute": "blockdev-add", "arguments": {"backing": {"backing": {"backing": {"backing": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-node0.img"}, "node-name": "node0"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node1.img"}, "node-name": "node1"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node2.img"}, "node-name": "node2"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node3.img"}, "node-name": "node3"}, "driver": "IMGFMT", "file": {"driver": "throttle", "file": {"driver": "file", "filename": "TEST_DIR/PID-node4.img"}, "throttle-group": "tg"}, "node-name": "node4"}} {"return": {}} @@ -18,7 +18,7 @@ Running tests: === Commit and stream finish concurrently (letting commit write) === -{"execute": "object-add", "arguments": {"id": "tg", "props": {"x-iops-write": 1, "x-iops-write-max": 1}, "qom-type": "throttle-group"}} +{"execute": "object-add", "arguments": {"id": "tg", "limits": {"iops-write": 1, "iops-write-max": 1}, "qom-type": "throttle-group"}} {"return": {}} {"execute": "blockdev-add", "arguments": {"backing": {"backing": {"backing": {"backing": {"driver": "raw", "file": {"driver": "throttle", "file": {"driver": "file", "filename": "TEST_DIR/PID-node0.img"}, "throttle-group": "tg"}, "node-name": "node0"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node1.img"}, "node-name": "node1"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node2.img"}, "node-name": "node2"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node3.img"}, "node-name": "node3"}, "driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/PID-node4.img"}, "node-name": "node4"}} {"return": {}} diff --git a/tests/qemu-iotests/283 b/tests/qemu-iotests/283 index 79643e375b..010c22f0a2 100755 --- a/tests/qemu-iotests/283 +++ b/tests/qemu-iotests/283 @@ -97,3 +97,56 @@ vm.qmp_log('blockdev-add', **{ vm.qmp_log('blockdev-backup', sync='full', device='source', target='target') vm.shutdown() + + +print('\n=== backup-top should be gone after job-finalize ===\n') + +# Check that the backup-top node is gone after job-finalize. +# +# During finalization, the node becomes inactive and can no longer +# function. If it is still present, new parents might be attached, and +# there would be no meaningful way to handle their I/O requests. + +vm = iotests.VM() +vm.launch() + +vm.qmp_log('blockdev-add', **{ + 'node-name': 'source', + 'driver': 'null-co', +}) + +vm.qmp_log('blockdev-add', **{ + 'node-name': 'target', + 'driver': 'null-co', +}) + +vm.qmp_log('blockdev-backup', + job_id='backup', + device='source', + target='target', + sync='full', + filter_node_name='backup-filter', + auto_finalize=False, + auto_dismiss=False) + +vm.event_wait('BLOCK_JOB_PENDING', 5.0) + +# The backup-top filter should still be present prior to finalization +assert vm.node_info('backup-filter') is not None + +vm.qmp_log('job-finalize', id='backup') +vm.event_wait('BLOCK_JOB_COMPLETED', 5.0) + +# The filter should be gone now. Check that by trying to access it +# with qemu-io (which will most likely crash qemu if it is still +# there.). +vm.qmp_log('human-monitor-command', + command_line='qemu-io backup-filter "write 0 1M"') + +# (Also, do an explicit check.) +assert vm.node_info('backup-filter') is None + +vm.qmp_log('job-dismiss', id='backup') +vm.event_wait('JOB_STATUS_CHANGE', 5.0, {'data': {'status': 'null'}}) + +vm.shutdown() diff --git a/tests/qemu-iotests/283.out b/tests/qemu-iotests/283.out index d8cff22cc1..37c35058ae 100644 --- a/tests/qemu-iotests/283.out +++ b/tests/qemu-iotests/283.out @@ -6,3 +6,18 @@ {"return": {}} {"execute": "blockdev-backup", "arguments": {"device": "source", "sync": "full", "target": "target"}} {"error": {"class": "GenericError", "desc": "Cannot set permissions for backup-top filter: Conflicts with use by other as 'image', which uses 'write' on base"}} + +=== backup-top should be gone after job-finalize === + +{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "source"}} +{"return": {}} +{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "target"}} +{"return": {}} +{"execute": "blockdev-backup", "arguments": {"auto-dismiss": false, "auto-finalize": false, "device": "source", "filter-node-name": "backup-filter", "job-id": "backup", "sync": "full", "target": "target"}} +{"return": {}} +{"execute": "job-finalize", "arguments": {"id": "backup"}} +{"return": {}} +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io backup-filter \"write 0 1M\""}} +{"return": "Error: Cannot find device='' nor node-name='backup-filter'\r\n"} +{"execute": "job-dismiss", "arguments": {"id": "backup"}} +{"return": {}} diff --git a/tests/qemu-iotests/295 b/tests/qemu-iotests/295 index 01a6c0b31f..270ad3999f 100755 --- a/tests/qemu-iotests/295 +++ b/tests/qemu-iotests/295 @@ -43,7 +43,7 @@ class Secret: def to_qmp_object(self): return { "qom_type" : "secret", "id": self.id(), - "props": { "data": self.secret() } } + "data": self.secret() } ################################################################################ class EncryptionSetupTestCase(iotests.QMPTestCase): diff --git a/tests/qemu-iotests/296 b/tests/qemu-iotests/296 index 0bc3c6c7d7..7c65e987a1 100755 --- a/tests/qemu-iotests/296 +++ b/tests/qemu-iotests/296 @@ -43,7 +43,7 @@ class Secret: def to_qmp_object(self): return { "qom_type" : "secret", "id": self.id(), - "props": { "data": self.secret() } } + "data": self.secret() } ################################################################################ diff --git a/tests/qemu-iotests/300 b/tests/qemu-iotests/300 index 63036f6a6e..b475a92c47 100755 --- a/tests/qemu-iotests/300 +++ b/tests/qemu-iotests/300 @@ -22,7 +22,7 @@ import os import random import re -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional import iotests @@ -30,7 +30,7 @@ import iotests # pylint: disable=wrong-import-order import qemu -BlockBitmapMapping = List[Dict[str, Union[str, List[Dict[str, str]]]]] +BlockBitmapMapping = List[Dict[str, object]] mig_sock = os.path.join(iotests.sock_dir, 'mig_sock') @@ -189,8 +189,8 @@ class TestAliasMigration(TestDirtyBitmapMigration): # Check for error message on the destination if self.src_node_name != self.dst_node_name: self.verify_dest_error(f"Cannot find " - f"device={self.src_node_name} nor " - f"node_name={self.src_node_name}") + f"device='{self.src_node_name}' nor " + f"node-name='{self.src_node_name}'") else: self.verify_dest_error(None) @@ -602,7 +602,8 @@ class TestCrossAliasMigration(TestDirtyBitmapMigration): class TestAliasTransformMigration(TestDirtyBitmapMigration): """ - Tests the 'transform' option which modifies bitmap persistence on migration. + Tests the 'transform' option which modifies bitmap persistence on + migration. """ src_node_name = 'node-a' @@ -674,7 +675,8 @@ class TestAliasTransformMigration(TestDirtyBitmapMigration): bitmaps = self.vm_b.query_bitmaps() for node in bitmaps: - bitmaps[node] = sorted(((bmap['name'], bmap['persistent']) for bmap in bitmaps[node])) + bitmaps[node] = sorted(((bmap['name'], bmap['persistent']) + for bmap in bitmaps[node])) self.assertEqual(bitmaps, {'node-a': [('bmap-a', True), ('bmap-b', False)], diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 4e758308f2..90d0b62523 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -17,6 +17,7 @@ # import atexit +import bz2 from collections import OrderedDict import faulthandler import io @@ -24,6 +25,7 @@ import json import logging import os import re +import shutil import signal import struct import subprocess @@ -96,6 +98,14 @@ luks_default_secret_object = 'secret,id=keysec0,data=' + \ os.environ.get('IMGKEYSECRET', '') luks_default_key_secret_opt = 'key-secret=keysec0' +sample_img_dir = os.environ['SAMPLE_IMG_DIR'] + + +def unarchive_sample_image(sample, fname): + sample_fname = os.path.join(sample_img_dir, sample + '.bz2') + with bz2.open(sample_fname) as f_in, open(fname, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + def qemu_tool_pipe_and_status(tool: str, args: Sequence[str], connect_stderr: bool = True) -> Tuple[str, int]: diff --git a/tests/qemu-iotests/sample_images/parallels-with-bitmap.bz2 b/tests/qemu-iotests/sample_images/parallels-with-bitmap.bz2 Binary files differnew file mode 100644 index 0000000000..54892fd4d0 --- /dev/null +++ b/tests/qemu-iotests/sample_images/parallels-with-bitmap.bz2 diff --git a/tests/qemu-iotests/sample_images/parallels-with-bitmap.sh b/tests/qemu-iotests/sample_images/parallels-with-bitmap.sh new file mode 100755 index 0000000000..30615aa6bd --- /dev/null +++ b/tests/qemu-iotests/sample_images/parallels-with-bitmap.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# +# Test parallels load bitmap +# +# Copyright (c) 2021 Virtuozzo International GmbH. +# +# 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/>. +# + +CT=parallels-with-bitmap-ct +DIR=$PWD/parallels-with-bitmap-dir +IMG=$DIR/root.hds +XML=$DIR/DiskDescriptor.xml +TARGET=parallels-with-bitmap.bz2 + +rm -rf $DIR + +prlctl create $CT --vmtype ct +prlctl set $CT --device-add hdd --image $DIR --recreate --size 2G + +# cleanup the image +qemu-img create -f parallels $IMG 64G + +# create bitmap +prlctl backup $CT + +prlctl set $CT --device-del hdd1 +prlctl destroy $CT + +dev=$(ploop mount $XML | sed -n 's/^Adding delta dev=\(\/dev\/ploop[0-9]\+\).*/\1/p') +dd if=/dev/zero of=$dev bs=64K seek=5 count=2 oflag=direct +dd if=/dev/zero of=$dev bs=64K seek=30 count=1 oflag=direct +dd if=/dev/zero of=$dev bs=64K seek=10 count=3 oflag=direct +ploop umount $XML # bitmap name will be in the output + +bzip2 -z $IMG + +mv $IMG.bz2 $TARGET + +rm -rf $DIR diff --git a/tests/qemu-iotests/tests/parallels-read-bitmap b/tests/qemu-iotests/tests/parallels-read-bitmap new file mode 100755 index 0000000000..af6b9c5db3 --- /dev/null +++ b/tests/qemu-iotests/tests/parallels-read-bitmap @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# +# Test parallels load bitmap +# +# Copyright (c) 2021 Virtuozzo International GmbH. +# +# 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 json +import iotests +from iotests import qemu_nbd_popen, qemu_img_pipe, log, file_path + +iotests.script_initialize(supported_fmts=['parallels']) + +nbd_sock = file_path('nbd-sock', base_dir=iotests.sock_dir) +disk = iotests.file_path('disk') +bitmap = 'e4f2eed0-37fe-4539-b50b-85d2e7fd235f' +nbd_opts = f'driver=nbd,server.type=unix,server.path={nbd_sock}' \ + f',x-dirty-bitmap=qemu:dirty-bitmap:{bitmap}' + + +iotests.unarchive_sample_image('parallels-with-bitmap', disk) + + +with qemu_nbd_popen('--read-only', f'--socket={nbd_sock}', + f'--bitmap={bitmap}', '-f', iotests.imgfmt, disk): + out = qemu_img_pipe('map', '--output=json', '--image-opts', nbd_opts) + chunks = json.loads(out) + cluster = 64 * 1024 + + log('dirty clusters (cluster size is 64K):') + for c in chunks: + assert c['start'] % cluster == 0 + assert c['length'] % cluster == 0 + if c['data']: + continue + + a = c['start'] // cluster + b = (c['start'] + c['length']) // cluster + if b - a > 1: + log(f'{a}-{b-1}') + else: + log(a) diff --git a/tests/qemu-iotests/tests/parallels-read-bitmap.out b/tests/qemu-iotests/tests/parallels-read-bitmap.out new file mode 100644 index 0000000000..e8f6bc9e96 --- /dev/null +++ b/tests/qemu-iotests/tests/parallels-read-bitmap.out @@ -0,0 +1,6 @@ +Start NBD server +dirty clusters (cluster size is 64K): +5-6 +10-12 +30 +Kill NBD server diff --git a/tests/qtest/libqos/libqtest.h b/tests/qtest/libqos/libqtest.h index 724f65aa94..a68dcd79d4 100644 --- a/tests/qtest/libqos/libqtest.h +++ b/tests/qtest/libqos/libqtest.h @@ -75,6 +75,17 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args); QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd); /** + * qtest_kill_qemu: + * @s: #QTestState instance to operate on. + * + * Kill the QEMU process and wait for it to terminate. It is safe to call this + * function multiple times. Normally qtest_quit() is used instead because it + * also frees QTestState. Use qtest_kill_qemu() when you just want to kill QEMU + * and qtest_quit() will be called later. + */ +void qtest_kill_qemu(QTestState *s); + +/** * qtest_quit: * @s: #QTestState instance to operate on. * @@ -133,6 +144,14 @@ void qtest_qmp_send_raw(QTestState *s, const char *fmt, ...) GCC_FMT_ATTR(2, 3); /** + * qtest_socket_server: + * @socket_path: the UNIX domain socket path + * + * Create and return a listen socket file descriptor, or abort on failure. + */ +int qtest_socket_server(const char *socket_path); + +/** * qtest_vqmp_fds: * @s: #QTestState instance to operate on. * @fds: array of file descriptors @@ -630,9 +649,27 @@ void qtest_add_data_func_full(const char *str, void *data, g_free(path); \ } while (0) +/** + * qtest_add_abrt_handler: + * @fn: Handler function + * @data: Argument that is passed to the handler + * + * Add a handler function that is invoked on SIGABRT. This can be used to + * terminate processes and perform other cleanup. The handler can be removed + * with qtest_remove_abrt_handler(). + */ void qtest_add_abrt_handler(GHookFunc fn, const void *data); /** + * qtest_remove_abrt_handler: + * @data: Argument previously passed to qtest_add_abrt_handler() + * + * Remove an abrt handler that was previously added with + * qtest_add_abrt_handler(). + */ +void qtest_remove_abrt_handler(void *data); + +/** * qtest_qmp_assert_success: * @qts: QTestState instance to operate on * @fmt: QMP message to send to qemu, formatted like diff --git a/tests/qtest/libqos/qgraph.h b/tests/qtest/libqos/qgraph.h index 07a32535f1..54672350c8 100644 --- a/tests/qtest/libqos/qgraph.h +++ b/tests/qtest/libqos/qgraph.h @@ -29,7 +29,6 @@ typedef struct QOSGraphObject QOSGraphObject; typedef struct QOSGraphNode QOSGraphNode; typedef struct QOSGraphEdge QOSGraphEdge; -typedef struct QOSGraphNodeOptions QOSGraphNodeOptions; typedef struct QOSGraphEdgeOptions QOSGraphEdgeOptions; typedef struct QOSGraphTestOptions QOSGraphTestOptions; @@ -49,340 +48,94 @@ typedef void (*QOSStartFunct) (QOSGraphObject *object); typedef void *(*QOSBeforeTest) (GString *cmd_line, void *arg); /** - * SECTION: qgraph.h - * @title: Qtest Driver Framework - * @short_description: interfaces to organize drivers and tests - * as nodes in a graph - * - * This Qgraph API provides all basic functions to create a graph - * and instantiate nodes representing machines, drivers and tests - * representing their relations with CONSUMES, PRODUCES, and CONTAINS - * edges. - * - * The idea is to have a framework where each test asks for a specific - * driver, and the framework takes care of allocating the proper devices - * required and passing the correct command line arguments to QEMU. - * - * A node can be of four types: - * - QNODE_MACHINE: for example "arm/raspi2" - * - QNODE_DRIVER: for example "generic-sdhci" - * - QNODE_INTERFACE: for example "sdhci" (interface for all "-sdhci" drivers) - * an interface is not explicitly created, it will be auto- - * matically instantiated when a node consumes or produces - * it. - * - QNODE_TEST: for example "sdhci-test", consumes an interface and tests - * the functions provided - * - * Notes for the nodes: - * - QNODE_MACHINE: each machine struct must have a QGuestAllocator and - * implement get_driver to return the allocator passing - * "memory". The function can also return NULL if the - * allocator is not set. - * - QNODE_DRIVER: driver names must be unique, and machines and nodes - * planned to be "consumed" by other nodes must match QEMU - * drivers name, otherwise they won't be discovered - * - * An edge relation between two nodes (drivers or machines) X and Y can be: - * - X CONSUMES Y: Y can be plugged into X - * - X PRODUCES Y: X provides the interface Y - * - X CONTAINS Y: Y is part of X component - * - * Basic framework steps are the following: - * - All nodes and edges are created in their respective - * machine/driver/test files - * - The framework starts QEMU and asks for a list of available devices - * and machines (note that only machines and "consumed" nodes are mapped - * 1:1 with QEMU devices) - * - The framework walks the graph starting from the available machines and - * performs a Depth First Search for tests - * - Once a test is found, the path is walked again and all drivers are - * allocated accordingly and the final interface is passed to the test - * - The test is executed - * - Unused objects are cleaned and the path discovery is continued - * - * Depending on the QEMU binary used, only some drivers/machines will be - * available and only test that are reached by them will be executed. - * - * <example> - * <title>Creating new driver an its interface</title> - * <programlisting> - #include "qgraph.h" - - struct My_driver { - QOSGraphObject obj; - Node_produced prod; - Node_contained cont; - } - - static void my_destructor(QOSGraphObject *obj) - { - g_free(obj); - } - - static void my_get_driver(void *object, const char *interface) { - My_driver *dev = object; - if (!g_strcmp0(interface, "my_interface")) { - return &dev->prod; - } - abort(); - } - - static void my_get_device(void *object, const char *device) { - My_driver *dev = object; - if (!g_strcmp0(device, "my_driver_contained")) { - return &dev->cont; - } - abort(); - } - - static void *my_driver_constructor(void *node_consumed, - QOSGraphObject *alloc) - { - My_driver dev = g_new(My_driver, 1); - // get the node pointed by the produce edge - dev->obj.get_driver = my_get_driver; - // get the node pointed by the contains - dev->obj.get_device = my_get_device; - // free the object - dev->obj.destructor = my_destructor; - do_something_with_node_consumed(node_consumed); - // set all fields of contained device - init_contained_device(&dev->cont); - return &dev->obj; - } - - static void register_my_driver(void) - { - qos_node_create_driver("my_driver", my_driver_constructor); - // contained drivers don't need a constructor, - // they will be init by the parent. - qos_node_create_driver("my_driver_contained", NULL); - - // For the sake of this example, assume machine x86_64/pc contains - // "other_node". - // This relation, along with the machine and "other_node" creation, - // should be defined in the x86_64_pc-machine.c file. - // "my_driver" will then consume "other_node" - qos_node_contains("my_driver", "my_driver_contained"); - qos_node_produces("my_driver", "my_interface"); - qos_node_consumes("my_driver", "other_node"); - } - * </programlisting> - * </example> - * - * In the above example, all possible types of relations are created: - * node "my_driver" consumes, contains and produces other nodes. - * more specifically: - * x86_64/pc -->contains--> other_node <--consumes-- my_driver - * | - * my_driver_contained <--contains--+ - * | - * my_interface <--produces--+ - * - * or inverting the consumes edge in consumed_by: - * - * x86_64/pc -->contains--> other_node --consumed_by--> my_driver - * | - * my_driver_contained <--contains--+ - * | - * my_interface <--produces--+ - * - * <example> - * <title>Creating new test</title> - * <programlisting> - * #include "qgraph.h" - * - * static void my_test_function(void *obj, void *data) - * { - * Node_produced *interface_to_test = obj; - * // test interface_to_test - * } - * - * static void register_my_test(void) - * { - * qos_add_test("my_interface", "my_test", my_test_function); - * } - * - * libqos_init(register_my_test); - * - * </programlisting> - * </example> - * - * Here a new test is created, consuming "my_interface" node - * and creating a valid path from a machine to a test. - * Final graph will be like this: - * x86_64/pc -->contains--> other_node <--consumes-- my_driver - * | - * my_driver_contained <--contains--+ - * | - * my_test --consumes--> my_interface <--produces--+ - * - * or inverting the consumes edge in consumed_by: - * - * x86_64/pc -->contains--> other_node --consumed_by--> my_driver - * | - * my_driver_contained <--contains--+ - * | - * my_test <--consumed_by-- my_interface <--produces--+ - * - * Assuming there the binary is - * QTEST_QEMU_BINARY=./qemu-system-x86_64 - * a valid test path will be: - * "/x86_64/pc/other_node/my_driver/my_interface/my_test". - * - * Additional examples are also in test-qgraph.c - * - * Command line: - * Command line is built by using node names and optional arguments - * passed by the user when building the edges. - * - * There are three types of command line arguments: - * - in node : created from the node name. For example, machines will - * have "-M <machine>" to its command line, while devices - * "-device <device>". It is automatically done by the - * framework. - * - after node : added as additional argument to the node name. - * This argument is added optionally when creating edges, - * by setting the parameter @after_cmd_line and - * @extra_edge_opts in #QOSGraphEdgeOptions. - * The framework automatically adds - * a comma before @extra_edge_opts, - * because it is going to add attributes - * after the destination node pointed by - * the edge containing these options, and automatically - * adds a space before @after_cmd_line, because it - * adds an additional device, not an attribute. - * - before node : added as additional argument to the node name. - * This argument is added optionally when creating edges, - * by setting the parameter @before_cmd_line in - * #QOSGraphEdgeOptions. This attribute - * is going to add attributes before the destination node - * pointed by the edge containing these options. It is - * helpful to commands that are not node-representable, - * such as "-fdsev" or "-netdev". - * - * While adding command line in edges is always used, not all nodes names are - * used in every path walk: this is because the contained or produced ones - * are already added by QEMU, so only nodes that "consumes" will be used to - * build the command line. Also, nodes that will have { "abstract" : true } - * as QMP attribute will loose their command line, since they are not proper - * devices to be added in QEMU. - * - * Example: - * - QOSGraphEdgeOptions opts = { - .arg = NULL, - .size_arg = 0, - .after_cmd_line = "-device other", - .before_cmd_line = "-netdev something", - .extra_edge_opts = "addr=04.0", - }; - QOSGraphNode * node = qos_node_create_driver("my_node", constructor); - qos_node_consumes_args("my_node", "interface", &opts); - * - * Will produce the following command line: - * "-netdev something -device my_node,addr=04.0 -device other" - */ - -/** - * Edge options to be passed to the contains/consumes *_args function. + * struct QOSGraphEdgeOptions: + * Edge options to be passed to the contains/consumes \*_args function. + * @arg: optional arg that will be used by dest edge + * @size_arg: @arg size that will be used by dest edge + * @extra_device_opts: optional additional command line for dest + * edge, used to add additional attributes + * *after* the node command line, the + * framework automatically prepends "," + * to this argument. + * @before_cmd_line: optional additional command line for dest + * edge, used to add additional attributes + * *before* the node command line, usually + * other non-node represented commands, + * like "-fdsev synt" + * @after_cmd_line: optional extra command line to be added + * after the device command. This option + * is used to add other devices + * command line that depend on current node. + * Automatically prepends " " to this argument + * @edge_name: optional edge to differentiate multiple + * devices with same node name */ struct QOSGraphEdgeOptions { - void *arg; /* - * optional arg that will be used by - * dest edge - */ - uint32_t size_arg; /* - * optional arg size that will be used by - * dest edge - */ - const char *extra_device_opts;/* - *optional additional command line for dest - * edge, used to add additional attributes - * *after* the node command line, the - * framework automatically prepends "," - * to this argument. - */ - const char *before_cmd_line; /* - * optional additional command line for dest - * edge, used to add additional attributes - * *before* the node command line, usually - * other non-node represented commands, - * like "-fdsev synt" - */ - const char *after_cmd_line; /* - * optional extra command line to be added - * after the device command. This option - * is used to add other devices - * command line that depend on current node. - * Automatically prepends " " to this - * argument - */ - const char *edge_name; /* - * optional edge to differentiate multiple - * devices with same node name - */ + void *arg; + uint32_t size_arg; + const char *extra_device_opts; + const char *before_cmd_line; + const char *after_cmd_line; + const char *edge_name; }; /** + * struct QOSGraphTestOptions: * Test options to be passed to the test functions. + * @edge: edge arguments that will be used by test. + * Note that test *does not* use edge_name, + * and uses instead arg and size_arg as + * data arg for its test function. + * @arg: if @before is non-NULL, pass @arg there. + * Otherwise pass it to the test function. + * @before: executed before the test. Used to add + * additional parameters to the command line + * and modify the argument to the test function. + * @subprocess: run the test in a subprocess. */ struct QOSGraphTestOptions { - QOSGraphEdgeOptions edge; /* edge arguments that will be used by test. - * Note that test *does not* use edge_name, - * and uses instead arg and size_arg as - * data arg for its test function. - */ - void *arg; /* passed to the .before function, or to the - * test function if there is no .before - * function - */ - QOSBeforeTest before; /* executed before the test. Can add - * additional parameters to the command line - * and modify the argument to the test function. - */ - bool subprocess; /* run the test in a subprocess */ + QOSGraphEdgeOptions edge; + void *arg; + QOSBeforeTest before; + bool subprocess; }; /** + * struct QOSGraphObject: * Each driver, test or machine of this framework will have a * QOSGraphObject as first field. * * This set of functions offered by QOSGraphObject are executed * in different stages of the framework: - * - get_driver / get_device : Once a machine-to-test path has been - * found, the framework traverses it again and allocates all the - * nodes, using the provided constructor. To satisfy their relations, - * i.e. for produces or contains, where a struct constructor needs - * an external parameter represented by the previous node, - * the framework will call get_device (for contains) or - * get_driver (for produces), depending on the edge type, passing - * them the name of the next node to be taken and getting from them - * the corresponding pointer to the actual structure of the next node to - * be used in the path. - * - * - start_hw: This function is executed after all the path objects - * have been allocated, but before the test is run. It starts the hw, setting - * the initial configurations (*_device_enable) and making it ready for the - * test. - * - * - destructor: Opposite to the node constructor, destroys the object. - * This function is called after the test has been executed, and performs - * a complete cleanup of each node allocated field. In case no constructor - * is provided, no destructor will be called. - * + * @get_driver: see @get_device + * @get_device: Once a machine-to-test path has been + * found, the framework traverses it again and allocates all the + * nodes, using the provided constructor. To satisfy their + * relations, i.e. for produces or contains, where a struct + * constructor needs an external parameter represented by the + * previous node, the framework will call + * @get_device (for contains) or @get_driver (for produces), + * depending on the edge type, passing them the name of the next + * node to be taken and getting from them the corresponding + * pointer to the actual structure of the next node to + * be used in the path. + * @start_hw: This function is executed after all the path objects + * have been allocated, but before the test is run. It starts the + * hw, setting the initial configurations (\*_device_enable) and + * making it ready for the test. + * @destructor: Opposite to the node constructor, destroys the object. + * This function is called after the test has been executed, and + * performs a complete cleanup of each node allocated field. + * In case no constructor is provided, no destructor will be + * called. + * @free: free the memory associated to the QOSGraphObject and its contained + * children */ struct QOSGraphObject { - /* for produces edges, returns void * */ QOSGetDriver get_driver; - /* for contains edges, returns a QOSGraphObject * */ QOSGetDevice get_device; - /* start the hw, get ready for the test */ QOSStartFunct start_hw; - /* destroy this QOSGraphObject */ QOSDestructorFunc destructor; - /* free the memory associated to the QOSGraphObject and its contained - * children */ GDestroyNotify free; }; @@ -399,24 +152,30 @@ void qos_graph_init(void); void qos_graph_destroy(void); /** - * qos_node_destroy(): removes and frees a node from the, + * qos_node_destroy(): removes and frees a node from the * nodes hash table. + * @key: Name of the node */ void qos_node_destroy(void *key); /** - * qos_edge_destroy(): removes and frees an edge from the, + * qos_edge_destroy(): removes and frees an edge from the * edges hash table. + * @key: Name of the node */ void qos_edge_destroy(void *key); /** * qos_add_test(): adds a test node @name to the nodes hash table. + * @name: Name of the test + * @interface: Name of the interface node it consumes + * @test_func: Actual test to perform + * @opts: Facultative options (see %QOSGraphTestOptions) * * The test will consume a @interface node, and once the * graph walking algorithm has found it, the @test_func will be * executed. It also has the possibility to - * add an optional @opts (see %QOSGraphNodeOptions). + * add an optional @opts (see %QOSGraphTestOptions). * * For tests, opts->edge.arg and size_arg represent the arg to pass * to @test_func @@ -428,6 +187,8 @@ void qos_add_test(const char *name, const char *interface, /** * qos_node_create_machine(): creates the machine @name and * adds it to the node hash table. + * @name: Name of the machine + * @function: Machine constructor * * This node will be of type QNODE_MACHINE and have @function * as constructor @@ -438,6 +199,9 @@ void qos_node_create_machine(const char *name, QOSCreateMachineFunc function); * qos_node_create_machine_args(): same as qos_node_create_machine, * but with the possibility to add an optional ", @opts" after -M machine * command line. + * @name: Name of the machine + * @function: Machine constructor + * @opts: Optional additional command line */ void qos_node_create_machine_args(const char *name, QOSCreateMachineFunc function, @@ -446,6 +210,8 @@ void qos_node_create_machine_args(const char *name, /** * qos_node_create_driver(): creates the driver @name and * adds it to the node hash table. + * @name: Name of the driver + * @function: Driver constructor * * This node will be of type QNODE_DRIVER and have @function * as constructor @@ -453,17 +219,17 @@ void qos_node_create_machine_args(const char *name, void qos_node_create_driver(const char *name, QOSCreateDriverFunc function); /** - * Behaves as qos_node_create_driver() with the extension of allowing to - * specify a different node name vs. associated QEMU device name. + * qos_node_create_driver_named(): behaves as qos_node_create_driver() with the + * extension of allowing to specify a different node name vs. associated QEMU + * device name. + * @name: Custom, unique name of the node to be created + * @qemu_name: Actual (official) QEMU driver name the node shall be + * associated with + * @function: Driver constructor * * Use this function instead of qos_node_create_driver() if you need to create * several instances of the same QEMU device. You are free to choose a custom * node name, however the chosen node name must always be unique. - * - * @param name: custom, unique name of the node to be created - * @param qemu_name: actual (official) QEMU driver name the node shall be - * associated with - * @param function: driver constructor */ void qos_node_create_driver_named(const char *name, const char *qemu_name, QOSCreateDriverFunc function); @@ -472,6 +238,9 @@ void qos_node_create_driver_named(const char *name, const char *qemu_name, * qos_node_contains(): creates one or more edges of type QEDGE_CONTAINS * and adds them to the edge list mapped to @container in the * edge hash table. + * @container: Source node that "contains" + * @contained: Destination node that "is contained" + * @opts: Facultative options (see %QOSGraphEdgeOptions) * * The edges will have @container as source and @contained as destination. * @@ -483,14 +252,17 @@ void qos_node_create_driver_named(const char *name, const char *qemu_name, * This function can be useful when there are multiple devices * with the same node name contained in a machine/other node * - * For example, if "arm/raspi2" contains 2 "generic-sdhci" + * For example, if ``arm/raspi2`` contains 2 ``generic-sdhci`` * devices, the right commands will be: - * qos_node_create_machine("arm/raspi2"); - * qos_node_create_driver("generic-sdhci", constructor); - * //assume rest of the fields are set NULL - * QOSGraphEdgeOptions op1 = { .edge_name = "emmc" }; - * QOSGraphEdgeOptions op2 = { .edge_name = "sdcard" }; - * qos_node_contains("arm/raspi2", "generic-sdhci", &op1, &op2, NULL); + * + * .. code:: + * + * qos_node_create_machine("arm/raspi2"); + * qos_node_create_driver("generic-sdhci", constructor); + * // assume rest of the fields are set NULL + * QOSGraphEdgeOptions op1 = { .edge_name = "emmc" }; + * QOSGraphEdgeOptions op2 = { .edge_name = "sdcard" }; + * qos_node_contains("arm/raspi2", "generic-sdhci", &op1, &op2, NULL); * * Of course this also requires that the @container's get_device function * should implement a case for "emmc" and "sdcard". @@ -505,6 +277,8 @@ void qos_node_contains(const char *container, const char *contained, * qos_node_produces(): creates an edge of type QEDGE_PRODUCES and * adds it to the edge list mapped to @producer in the * edge hash table. + * @producer: Source node that "produces" + * @interface: Interface node that "is produced" * * This edge will have @producer as source and @interface as destination. */ @@ -514,6 +288,9 @@ void qos_node_produces(const char *producer, const char *interface); * qos_node_consumes(): creates an edge of type QEDGE_CONSUMED_BY and * adds it to the edge list mapped to @interface in the * edge hash table. + * @consumer: Node that "consumes" + * @interface: Interface node that "is consumed by" + * @opts: Facultative options (see %QOSGraphEdgeOptions) * * This edge will have @interface as source and @consumer as destination. * It also has the possibility to add an optional @opts @@ -539,7 +316,7 @@ const char *qos_get_current_command_line(void); /** * qos_allocate_objects(): * @qts: The #QTestState that will be referred to by the machine object. - * @alloc: Where to store the allocator for the machine object, or %NULL. + * @p_alloc: Where to store the allocator for the machine object, or %NULL. * * Allocate driver objects for the current test * path, but relative to the QTestState @qts. @@ -551,24 +328,27 @@ void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc); /** * qos_object_destroy(): calls the destructor for @obj + * @obj: A #QOSGraphObject to destroy */ void qos_object_destroy(QOSGraphObject *obj); /** * qos_object_queue_destroy(): queue the destructor for @obj so that it is * called at the end of the test + * @obj: A #QOSGraphObject to destroy */ void qos_object_queue_destroy(QOSGraphObject *obj); /** * qos_object_start_hw(): calls the start_hw function for @obj + * @obj: A #QOSGraphObject containing the start_hw function */ void qos_object_start_hw(QOSGraphObject *obj); /** * qos_machine_new(): instantiate a new machine node - * @node: A machine node to be instantiated - * @qts: The #QTestState that will be referred to by the machine object. + * @node: Machine node to be instantiated + * @qts: A #QTestState that will be referred to by the machine object. * * Returns a machine object. */ @@ -587,8 +367,8 @@ QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent, QGuestAllocator *alloc, void *arg); /** - * Just for debugging purpose: prints all currently existing nodes and - * edges to stdout. + * qos_dump_graph(): prints all currently existing nodes and + * edges to stdout. Just for debugging purposes. * * All qtests add themselves to the overall qos graph by calling qgraph * functions that add device nodes and edges between the individual graph diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c index fd043b0570..71e359efcd 100644 --- a/tests/qtest/libqtest.c +++ b/tests/qtest/libqtest.c @@ -81,24 +81,8 @@ static void qtest_client_set_rx_handler(QTestState *s, QTestRecvFn recv); static int init_socket(const char *socket_path) { - struct sockaddr_un addr; - int sock; - int ret; - - sock = socket(PF_UNIX, SOCK_STREAM, 0); - g_assert_cmpint(sock, !=, -1); - - addr.sun_family = AF_UNIX; - snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path); + int sock = qtest_socket_server(socket_path); qemu_set_cloexec(sock); - - do { - ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); - } while (ret == -1 && errno == EINTR); - g_assert_cmpint(ret, !=, -1); - ret = listen(sock, 1); - g_assert_cmpint(ret, !=, -1); - return sock; } @@ -149,7 +133,7 @@ void qtest_set_expected_status(QTestState *s, int status) s->expected_status = status; } -static void kill_qemu(QTestState *s) +void qtest_kill_qemu(QTestState *s) { pid_t pid = s->qemu_pid; int wstatus; @@ -159,6 +143,7 @@ static void kill_qemu(QTestState *s) kill(pid, SIGTERM); TFR(pid = waitpid(s->qemu_pid, &s->wstatus, 0)); assert(pid == s->qemu_pid); + s->qemu_pid = -1; } /* @@ -185,7 +170,7 @@ static void kill_qemu(QTestState *s) static void kill_qemu_hook_func(void *s) { - kill_qemu(s); + qtest_kill_qemu(s); } static void sigabrt_handler(int signo) @@ -211,15 +196,30 @@ static void cleanup_sigabrt_handler(void) sigaction(SIGABRT, &sigact_old, NULL); } +static bool hook_list_is_empty(GHookList *hook_list) +{ + GHook *hook = g_hook_first_valid(hook_list, TRUE); + + if (!hook) { + return false; + } + + g_hook_unref(hook_list, hook); + return true; +} + void qtest_add_abrt_handler(GHookFunc fn, const void *data) { GHook *hook; - /* Only install SIGABRT handler once */ if (!abrt_hooks.is_setup) { g_hook_list_init(&abrt_hooks, sizeof(GHook)); } - setup_sigabrt_handler(); + + /* Only install SIGABRT handler once */ + if (hook_list_is_empty(&abrt_hooks)) { + setup_sigabrt_handler(); + } hook = g_hook_alloc(&abrt_hooks); hook->func = fn; @@ -228,6 +228,17 @@ void qtest_add_abrt_handler(GHookFunc fn, const void *data) g_hook_prepend(&abrt_hooks, hook); } +void qtest_remove_abrt_handler(void *data) +{ + GHook *hook = g_hook_find_data(&abrt_hooks, TRUE, data); + g_hook_destroy_link(&abrt_hooks, hook); + + /* Uninstall SIGABRT handler on last instance */ + if (hook_list_is_empty(&abrt_hooks)) { + cleanup_sigabrt_handler(); + } +} + static const char *qtest_qemu_binary(void) { const char *qemu_bin; @@ -384,12 +395,9 @@ QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd) void qtest_quit(QTestState *s) { - g_hook_destroy_link(&abrt_hooks, g_hook_find_data(&abrt_hooks, TRUE, s)); - - /* Uninstall SIGABRT handler on last instance */ - cleanup_sigabrt_handler(); + qtest_remove_abrt_handler(s); - kill_qemu(s); + qtest_kill_qemu(s); close(s->fd); close(s->qmp_fd); g_string_free(s->rx, true); @@ -638,6 +646,28 @@ QDict *qtest_qmp_receive_dict(QTestState *s) return qmp_fd_receive(s->qmp_fd); } +int qtest_socket_server(const char *socket_path) +{ + struct sockaddr_un addr; + int sock; + int ret; + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + g_assert_cmpint(sock, !=, -1); + + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path); + + do { + ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); + } while (ret == -1 && errno == EINTR); + g_assert_cmpint(ret, !=, -1); + ret = listen(sock, 1); + g_assert_cmpint(ret, !=, -1); + + return sock; +} + /** * Allow users to send a message without waiting for the reply, * in the case that they choose to discard all replies up until diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 58efc46144..2688e1bfad 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -157,6 +157,7 @@ qtests_npcm7xx = \ 'npcm7xx_watchdog_timer-test'] + \ (slirp.found() ? ['npcm7xx_emc-test'] : []) qtests_arm = \ + (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \ (config_all_devices.has_key('CONFIG_CMSDK_APB_DUALTIMER') ? ['cmsdk-apb-dualtimer-test'] : []) + \ (config_all_devices.has_key('CONFIG_CMSDK_APB_TIMER') ? ['cmsdk-apb-timer-test'] : []) + \ (config_all_devices.has_key('CONFIG_CMSDK_APB_WATCHDOG') ? ['cmsdk-apb-watchdog-test'] : []) + \ diff --git a/tests/qtest/sse-timer-test.c b/tests/qtest/sse-timer-test.c new file mode 100644 index 0000000000..a65d7542d5 --- /dev/null +++ b/tests/qtest/sse-timer-test.c @@ -0,0 +1,240 @@ +/* + * QTest testcase for the SSE timer device + * + * Copyright (c) 2021 Linaro Limited + * + * 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. + */ + +#include "qemu/osdep.h" +#include "libqtest-single.h" + +/* + * SSE-123/SSE-300 timer in the mps3-an547 board, where it is driven + * at 32MHz, so 31.25ns per tick. + */ +#define TIMER_BASE 0x48000000 + +/* PERIPHNSPPC0 register in the SSE-300 Secure Access Configuration block */ +#define PERIPHNSPPC0 (0x50080000 + 0x70) + +/* Base of the System Counter control frame */ +#define COUNTER_BASE 0x58100000 + +/* SSE counter register offsets in the control frame */ +#define CNTCR 0 +#define CNTSR 0x4 +#define CNTCV_LO 0x8 +#define CNTCV_HI 0xc +#define CNTSCR 0x10 + +/* SSE timer register offsets */ +#define CNTPCT_LO 0 +#define CNTPCT_HI 4 +#define CNTFRQ 0x10 +#define CNTP_CVAL_LO 0x20 +#define CNTP_CVAL_HI 0x24 +#define CNTP_TVAL 0x28 +#define CNTP_CTL 0x2c +#define CNTP_AIVAL_LO 0x40 +#define CNTP_AIVAL_HI 0x44 +#define CNTP_AIVAL_RELOAD 0x48 +#define CNTP_AIVAL_CTL 0x4c + +/* 4 ticks in nanoseconds (so we can work in integers) */ +#define FOUR_TICKS 125 + +static void clock_step_ticks(uint64_t ticks) +{ + /* + * Advance the qtest clock by however many nanoseconds we + * need to move the timer forward the specified number of ticks. + * ticks must be a multiple of 4, so we get a whole number of ns. + */ + assert(!(ticks & 3)); + clock_step(FOUR_TICKS * (ticks >> 2)); +} + +static void reset_counter_and_timer(void) +{ + /* + * Reset the system counter and the timer between tests. This + * isn't a full reset, but it's sufficient for what the tests check. + */ + writel(COUNTER_BASE + CNTCR, 0); + writel(TIMER_BASE + CNTP_CTL, 0); + writel(TIMER_BASE + CNTP_AIVAL_CTL, 0); + writel(COUNTER_BASE + CNTCV_LO, 0); + writel(COUNTER_BASE + CNTCV_HI, 0); +} + +static void test_counter(void) +{ + /* Basic counter functionality test */ + + reset_counter_and_timer(); + /* The counter should start disabled: check that it doesn't move */ + clock_step_ticks(100); + g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_LO), ==, 0); + g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_HI), ==, 0); + /* Now enable it and check that it does count */ + writel(COUNTER_BASE + CNTCR, 1); + clock_step_ticks(100); + g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_LO), ==, 100); + g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_HI), ==, 0); + /* Check the counter scaling functionality */ + writel(COUNTER_BASE + CNTCR, 0); + writel(COUNTER_BASE + CNTSCR, 0x00100000); /* 1/16th normal speed */ + writel(COUNTER_BASE + CNTCR, 5); /* EN, SCEN */ + clock_step_ticks(160); + g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_LO), ==, 110); + g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_HI), ==, 0); +} + +static void test_timer(void) +{ + /* Basic timer functionality test */ + + reset_counter_and_timer(); + /* + * The timer is behind a Peripheral Protection Controller, and + * qtest accesses are always non-secure (no memory attributes), + * so we must program the PPC to accept NS transactions. TIMER0 + * is on port 0 of PPC0, controlled by bit 0 of this register. + */ + writel(PERIPHNSPPC0, 1); + /* We must enable the System Counter or the timer won't run. */ + writel(COUNTER_BASE + CNTCR, 1); + + /* Timer starts disabled and with a counter of 0 */ + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 0); + g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_LO), ==, 0); + g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_HI), ==, 0); + + /* Turn it on */ + writel(TIMER_BASE + CNTP_CTL, 1); + + /* Is the timer ticking? */ + clock_step_ticks(100); + g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_LO), ==, 100); + g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_HI), ==, 0); + + /* Set the CompareValue to 4000 ticks */ + writel(TIMER_BASE + CNTP_CVAL_LO, 4000); + writel(TIMER_BASE + CNTP_CVAL_HI, 0); + + /* Check TVAL view of the counter */ + g_assert_cmpuint(readl(TIMER_BASE + CNTP_TVAL), ==, 3900); + + /* Advance to the CompareValue mark and check ISTATUS is set */ + clock_step_ticks(3900); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_TVAL), ==, 0); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5); + + /* Now exercise the auto-reload part of the timer */ + writel(TIMER_BASE + CNTP_AIVAL_RELOAD, 200); + writel(TIMER_BASE + CNTP_AIVAL_CTL, 1); + + /* Check AIVAL was reloaded and that ISTATUS is now clear */ + g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_LO), ==, 4200); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_HI), ==, 0); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1); + + /* + * Check that when we advance forward to the reload time the interrupt + * fires and the value reloads + */ + clock_step_ticks(100); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1); + clock_step_ticks(100); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_LO), ==, 4400); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_HI), ==, 0); + + clock_step_ticks(100); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5); + /* Check that writing 0 to CLR clears the interrupt */ + writel(TIMER_BASE + CNTP_AIVAL_CTL, 1); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1); + /* Check that when we move forward to the reload time it fires again */ + clock_step_ticks(100); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_LO), ==, 4600); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_HI), ==, 0); + + /* + * Step the clock far enough that we overflow the low half of the + * CNTPCT and AIVAL registers, and check that their high halves + * give the right values. We do the forward movement in + * non-autoinc mode because otherwise it takes forever as the + * timer has to emulate all the 'reload at t + N, t + 2N, etc' + * steps. + */ + writel(TIMER_BASE + CNTP_AIVAL_CTL, 0); + clock_step_ticks(0x42ULL << 32); + g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_LO), ==, 4400); + g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_HI), ==, 0x42); + + /* Turn on the autoinc again to check AIVAL_HI */ + writel(TIMER_BASE + CNTP_AIVAL_CTL, 1); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_LO), ==, 4600); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_HI), ==, 0x42); +} + +static void test_timer_scale_change(void) +{ + /* + * Test that the timer responds correctly to counter + * scaling changes while it has an active timer. + */ + reset_counter_and_timer(); + /* Give ourselves access to the timer, and enable the counter and timer */ + writel(PERIPHNSPPC0, 1); + writel(COUNTER_BASE + CNTCR, 1); + writel(TIMER_BASE + CNTP_CTL, 1); + /* Set the CompareValue to 4000 ticks */ + writel(TIMER_BASE + CNTP_CVAL_LO, 4000); + writel(TIMER_BASE + CNTP_CVAL_HI, 0); + /* Advance halfway and check ISTATUS is not set */ + clock_step_ticks(2000); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1); + /* Reprogram the counter to run at 1/16th speed */ + writel(COUNTER_BASE + CNTCR, 0); + writel(COUNTER_BASE + CNTSCR, 0x00100000); /* 1/16th normal speed */ + writel(COUNTER_BASE + CNTCR, 5); /* EN, SCEN */ + /* Advance to where the timer would have fired and check it has not */ + clock_step_ticks(2000); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1); + /* Advance to where the timer must fire at the new clock rate */ + clock_step_ticks(29996); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1); + clock_step_ticks(4); + g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5); +} + +int main(int argc, char **argv) +{ + int r; + + g_test_init(&argc, &argv, NULL); + + qtest_start("-machine mps3-an547"); + + qtest_add_func("/sse-timer/counter", test_counter); + qtest_add_func("/sse-timer/timer", test_timer); + qtest_add_func("/sse-timer/timer-scale-change", test_timer_scale_change); + + r = g_test_run(); + + qtest_end(); + + return r; +} diff --git a/tests/test-cutils.c b/tests/test-cutils.c index 1aa8351520..bad3a60993 100644 --- a/tests/test-cutils.c +++ b/tests/test-cutils.c @@ -1960,18 +1960,24 @@ static void test_qemu_strtosz_simple(void) g_assert_cmpint(res, ==, 0); g_assert(endptr == str + 1); - str = "12345"; + /* Leading 0 gives decimal results, not octal */ + str = "08"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert_cmpint(res, ==, 8); + g_assert(endptr == str + 2); + + /* Leading space is ignored */ + str = " 12345"; err = qemu_strtosz(str, &endptr, &res); g_assert_cmpint(err, ==, 0); g_assert_cmpint(res, ==, 12345); - g_assert(endptr == str + 5); + g_assert(endptr == str + 6); err = qemu_strtosz(str, NULL, &res); g_assert_cmpint(err, ==, 0); g_assert_cmpint(res, ==, 12345); - /* Note: precision is 53 bits since we're parsing with strtod() */ - str = "9007199254740991"; /* 2^53-1 */ err = qemu_strtosz(str, &endptr, &res); g_assert_cmpint(err, ==, 0); @@ -1987,7 +1993,7 @@ static void test_qemu_strtosz_simple(void) str = "9007199254740993"; /* 2^53+1 */ err = qemu_strtosz(str, &endptr, &res); g_assert_cmpint(err, ==, 0); - g_assert_cmpint(res, ==, 0x20000000000000); /* rounded to 53 bits */ + g_assert_cmpint(res, ==, 0x20000000000001); g_assert(endptr == str + 16); str = "18446744073709549568"; /* 0xfffffffffffff800 (53 msbs set) */ @@ -1999,11 +2005,40 @@ static void test_qemu_strtosz_simple(void) str = "18446744073709550591"; /* 0xfffffffffffffbff */ err = qemu_strtosz(str, &endptr, &res); g_assert_cmpint(err, ==, 0); - g_assert_cmpint(res, ==, 0xfffffffffffff800); /* rounded to 53 bits */ + g_assert_cmpint(res, ==, 0xfffffffffffffbff); g_assert(endptr == str + 20); - /* 0x7ffffffffffffe00..0x7fffffffffffffff get rounded to - * 0x8000000000000000, thus -ERANGE; see test_qemu_strtosz_erange() */ + str = "18446744073709551615"; /* 0xffffffffffffffff */ + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert_cmpint(res, ==, 0xffffffffffffffff); + g_assert(endptr == str + 20); +} + +static void test_qemu_strtosz_hex(void) +{ + const char *str; + const char *endptr; + int err; + uint64_t res = 0xbaadf00d; + + str = "0x0"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert_cmpint(res, ==, 0); + g_assert(endptr == str + 3); + + str = "0xab"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert_cmpint(res, ==, 171); + g_assert(endptr == str + 4); + + str = "0xae"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert_cmpint(res, ==, 174); + g_assert(endptr == str + 4); } static void test_qemu_strtosz_units(void) @@ -2064,14 +2099,36 @@ static void test_qemu_strtosz_units(void) static void test_qemu_strtosz_float(void) { - const char *str = "12.345M"; + const char *str; int err; const char *endptr; uint64_t res = 0xbaadf00d; + str = "0.5E"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert_cmpint(res, ==, EiB / 2); + g_assert(endptr == str + 4); + + /* For convenience, a fraction of 0 is tolerated even on bytes */ + str = "1.0B"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert_cmpint(res, ==, 1); + g_assert(endptr == str + 4); + + /* An empty fraction is tolerated */ + str = "1.k"; err = qemu_strtosz(str, &endptr, &res); g_assert_cmpint(err, ==, 0); - g_assert_cmpint(res, ==, 12.345 * MiB); + g_assert_cmpint(res, ==, 1024); + g_assert(endptr == str + 3); + + /* For convenience, we permit values that are not byte-exact */ + str = "12.345M"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert_cmpint(res, ==, (uint64_t) (12.345 * MiB)); g_assert(endptr == str + 7); } @@ -2106,6 +2163,45 @@ static void test_qemu_strtosz_invalid(void) err = qemu_strtosz(str, &endptr, &res); g_assert_cmpint(err, ==, -EINVAL); g_assert(endptr == str); + + /* Fractional values require scale larger than bytes */ + str = "1.1B"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, -EINVAL); + g_assert(endptr == str); + + str = "1.1"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, -EINVAL); + g_assert(endptr == str); + + /* No floating point exponents */ + str = "1.5e1k"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, -EINVAL); + g_assert(endptr == str); + + str = "1.5E+0k"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, -EINVAL); + g_assert(endptr == str); + + /* No hex fractions */ + str = "0x1.8k"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, -EINVAL); + g_assert(endptr == str); + + /* No negative values */ + str = "-0"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, -EINVAL); + g_assert(endptr == str); + + str = "-1"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, -EINVAL); + g_assert(endptr == str); } static void test_qemu_strtosz_trailing(void) @@ -2131,6 +2227,30 @@ static void test_qemu_strtosz_trailing(void) err = qemu_strtosz(str, NULL, &res); g_assert_cmpint(err, ==, -EINVAL); + + str = "0x"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(res, ==, 0); + g_assert(endptr == str + 1); + + err = qemu_strtosz(str, NULL, &res); + g_assert_cmpint(err, ==, -EINVAL); + + str = "0.NaN"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert(endptr == str + 2); + + err = qemu_strtosz(str, NULL, &res); + g_assert_cmpint(err, ==, -EINVAL); + + str = "123-45"; + err = qemu_strtosz(str, &endptr, &res); + g_assert_cmpint(res, ==, 123); + g_assert(endptr == str + 3); + + err = qemu_strtosz(str, NULL, &res); + g_assert_cmpint(err, ==, -EINVAL); } static void test_qemu_strtosz_erange(void) @@ -2140,22 +2260,7 @@ static void test_qemu_strtosz_erange(void) int err; uint64_t res = 0xbaadf00d; - str = "-1"; - err = qemu_strtosz(str, &endptr, &res); - g_assert_cmpint(err, ==, -ERANGE); - g_assert(endptr == str + 2); - - str = "18446744073709550592"; /* 0xfffffffffffffc00 */ - err = qemu_strtosz(str, &endptr, &res); - g_assert_cmpint(err, ==, -ERANGE); - g_assert(endptr == str + 20); - - str = "18446744073709551615"; /* 2^64-1 */ - err = qemu_strtosz(str, &endptr, &res); - g_assert_cmpint(err, ==, -ERANGE); - g_assert(endptr == str + 20); - - str = "18446744073709551616"; /* 2^64 */ + str = "18446744073709551616"; /* 2^64; see strtosz_simple for 2^64-1 */ err = qemu_strtosz(str, &endptr, &res); g_assert_cmpint(err, ==, -ERANGE); g_assert(endptr == str + 20); @@ -2168,15 +2273,22 @@ static void test_qemu_strtosz_erange(void) static void test_qemu_strtosz_metric(void) { - const char *str = "12345k"; + const char *str; int err; const char *endptr; uint64_t res = 0xbaadf00d; + str = "12345k"; err = qemu_strtosz_metric(str, &endptr, &res); g_assert_cmpint(err, ==, 0); g_assert_cmpint(res, ==, 12345000); g_assert(endptr == str + 6); + + str = "12.345M"; + err = qemu_strtosz_metric(str, &endptr, &res); + g_assert_cmpint(err, ==, 0); + g_assert_cmpint(res, ==, 12345000); + g_assert(endptr == str + 7); } int main(int argc, char **argv) @@ -2443,6 +2555,8 @@ int main(int argc, char **argv) g_test_add_func("/cutils/strtosz/simple", test_qemu_strtosz_simple); + g_test_add_func("/cutils/strtosz/hex", + test_qemu_strtosz_hex); g_test_add_func("/cutils/strtosz/units", test_qemu_strtosz_units); g_test_add_func("/cutils/strtosz/float", diff --git a/tests/test-keyval.c b/tests/test-keyval.c index ee927fe4e4..e20c07cf3e 100644 --- a/tests/test-keyval.c +++ b/tests/test-keyval.c @@ -445,9 +445,9 @@ static void test_keyval_visit_size(void) visit_end_struct(v, NULL); visit_free(v); - /* Note: precision is 53 bits since we're parsing with strtod() */ + /* Note: full 64 bits of precision */ - /* Around limit of precision: 2^53-1, 2^53, 2^53+1 */ + /* Around double limit of precision: 2^53-1, 2^53, 2^53+1 */ qdict = keyval_parse("sz1=9007199254740991," "sz2=9007199254740992," "sz3=9007199254740993", @@ -460,22 +460,25 @@ static void test_keyval_visit_size(void) visit_type_size(v, "sz2", &sz, &error_abort); g_assert_cmphex(sz, ==, 0x20000000000000); visit_type_size(v, "sz3", &sz, &error_abort); - g_assert_cmphex(sz, ==, 0x20000000000000); + g_assert_cmphex(sz, ==, 0x20000000000001); visit_check_struct(v, &error_abort); visit_end_struct(v, NULL); visit_free(v); - /* Close to signed upper limit 0x7ffffffffffffc00 (53 msbs set) */ - qdict = keyval_parse("sz1=9223372036854774784," /* 7ffffffffffffc00 */ - "sz2=9223372036854775295", /* 7ffffffffffffdff */ + /* Close to signed integer limit 2^63 */ + qdict = keyval_parse("sz1=9223372036854775807," /* 7fffffffffffffff */ + "sz2=9223372036854775808," /* 8000000000000000 */ + "sz3=9223372036854775809", /* 8000000000000001 */ NULL, NULL, &error_abort); v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); qobject_unref(qdict); visit_start_struct(v, NULL, NULL, 0, &error_abort); visit_type_size(v, "sz1", &sz, &error_abort); - g_assert_cmphex(sz, ==, 0x7ffffffffffffc00); + g_assert_cmphex(sz, ==, 0x7fffffffffffffff); visit_type_size(v, "sz2", &sz, &error_abort); - g_assert_cmphex(sz, ==, 0x7ffffffffffffc00); + g_assert_cmphex(sz, ==, 0x8000000000000000); + visit_type_size(v, "sz3", &sz, &error_abort); + g_assert_cmphex(sz, ==, 0x8000000000000001); visit_check_struct(v, &error_abort); visit_end_struct(v, NULL); visit_free(v); @@ -490,14 +493,26 @@ static void test_keyval_visit_size(void) visit_type_size(v, "sz1", &sz, &error_abort); g_assert_cmphex(sz, ==, 0xfffffffffffff800); visit_type_size(v, "sz2", &sz, &error_abort); - g_assert_cmphex(sz, ==, 0xfffffffffffff800); + g_assert_cmphex(sz, ==, 0xfffffffffffffbff); + visit_check_struct(v, &error_abort); + visit_end_struct(v, NULL); + visit_free(v); + + /* Actual limit 2^64-1*/ + qdict = keyval_parse("sz1=18446744073709551615", /* ffffffffffffffff */ + NULL, NULL, &error_abort); + v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); + qobject_unref(qdict); + visit_start_struct(v, NULL, NULL, 0, &error_abort); + visit_type_size(v, "sz1", &sz, &error_abort); + g_assert_cmphex(sz, ==, 0xffffffffffffffff); visit_check_struct(v, &error_abort); visit_end_struct(v, NULL); visit_free(v); /* Beyond limits */ qdict = keyval_parse("sz1=-1," - "sz2=18446744073709550592", /* fffffffffffffc00 */ + "sz2=18446744073709551616", /* 2^64 */ NULL, NULL, &error_abort); v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); qobject_unref(qdict); diff --git a/tests/test-qemu-opts.c b/tests/test-qemu-opts.c index 8bbb17b1c7..6568e31a72 100644 --- a/tests/test-qemu-opts.c +++ b/tests/test-qemu-opts.c @@ -654,9 +654,9 @@ static void test_opts_parse_size(void) g_assert_cmpuint(opts_count(opts), ==, 1); g_assert_cmpuint(qemu_opt_get_size(opts, "size1", 1), ==, 0); - /* Note: precision is 53 bits since we're parsing with strtod() */ + /* Note: full 64 bits of precision */ - /* Around limit of precision: 2^53-1, 2^53, 2^54 */ + /* Around double limit of precision: 2^53-1, 2^53, 2^53+1 */ opts = qemu_opts_parse(&opts_list_02, "size1=9007199254740991," "size2=9007199254740992," @@ -668,18 +668,21 @@ static void test_opts_parse_size(void) g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1), ==, 0x20000000000000); g_assert_cmphex(qemu_opt_get_size(opts, "size3", 1), - ==, 0x20000000000000); + ==, 0x20000000000001); - /* Close to signed upper limit 0x7ffffffffffffc00 (53 msbs set) */ + /* Close to signed int limit: 2^63-1, 2^63, 2^63+1 */ opts = qemu_opts_parse(&opts_list_02, - "size1=9223372036854774784," /* 7ffffffffffffc00 */ - "size2=9223372036854775295", /* 7ffffffffffffdff */ + "size1=9223372036854775807," /* 7fffffffffffffff */ + "size2=9223372036854775808," /* 8000000000000000 */ + "size3=9223372036854775809", /* 8000000000000001 */ false, &error_abort); - g_assert_cmpuint(opts_count(opts), ==, 2); + g_assert_cmpuint(opts_count(opts), ==, 3); g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1), - ==, 0x7ffffffffffffc00); + ==, 0x7fffffffffffffff); g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1), - ==, 0x7ffffffffffffc00); + ==, 0x8000000000000000); + g_assert_cmphex(qemu_opt_get_size(opts, "size3", 1), + ==, 0x8000000000000001); /* Close to actual upper limit 0xfffffffffffff800 (53 msbs set) */ opts = qemu_opts_parse(&opts_list_02, @@ -690,14 +693,22 @@ static void test_opts_parse_size(void) g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1), ==, 0xfffffffffffff800); g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1), - ==, 0xfffffffffffff800); + ==, 0xfffffffffffffbff); + + /* Actual limit, 2^64-1 */ + opts = qemu_opts_parse(&opts_list_02, + "size1=18446744073709551615", /* ffffffffffffffff */ + false, &error_abort); + g_assert_cmpuint(opts_count(opts), ==, 1); + g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1), + ==, 0xffffffffffffffff); /* Beyond limits */ opts = qemu_opts_parse(&opts_list_02, "size1=-1", false, &err); error_free_or_abort(&err); g_assert(!opts); opts = qemu_opts_parse(&opts_list_02, - "size1=18446744073709550592", /* fffffffffffffc00 */ + "size1=18446744073709551616", /* 2^64 */ false, &err); error_free_or_abort(&err); g_assert(!opts); |