diff options
Diffstat (limited to 'tests')
46 files changed, 2567 insertions, 291 deletions
diff --git a/tests/Makefile.include b/tests/Makefile.include index 2f1cafe..edcbd47 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -98,6 +98,7 @@ check-unit-y += tests/rcutorture$(EXESUF) check-unit-y += tests/test-rcu-list$(EXESUF) check-unit-y += tests/test-rcu-simpleq$(EXESUF) check-unit-y += tests/test-rcu-tailq$(EXESUF) +check-unit-y += tests/test-rcu-slist$(EXESUF) check-unit-y += tests/test-qdist$(EXESUF) check-unit-y += tests/test-qht$(EXESUF) check-unit-y += tests/test-qht-par$(EXESUF) @@ -415,6 +416,7 @@ tests/rcutorture$(EXESUF): tests/rcutorture.o $(test-util-obj-y) tests/test-rcu-list$(EXESUF): tests/test-rcu-list.o $(test-util-obj-y) tests/test-rcu-simpleq$(EXESUF): tests/test-rcu-simpleq.o $(test-util-obj-y) tests/test-rcu-tailq$(EXESUF): tests/test-rcu-tailq.o $(test-util-obj-y) +tests/test-rcu-slist$(EXESUF): tests/test-rcu-slist.o $(test-util-obj-y) tests/test-qdist$(EXESUF): tests/test-qdist.o $(test-util-obj-y) tests/test-qht$(EXESUF): tests/test-qht.o $(test-util-obj-y) tests/test-qht-par$(EXESUF): tests/test-qht-par.o tests/qht-bench$(EXESUF) $(test-util-obj-y) diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040 index 2e7ee0e..32c82b4 100755 --- a/tests/qemu-iotests/040 +++ b/tests/qemu-iotests/040 @@ -430,6 +430,289 @@ class TestReopenOverlay(ImageCommitTestCase): def test_reopen_overlay(self): self.run_commit_test(self.img1, self.img0) +class TestErrorHandling(iotests.QMPTestCase): + image_len = 2 * 1024 * 1024 + + def setUp(self): + iotests.create_image(backing_img, self.image_len) + qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img) + qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img) + + qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x11 0 512k', mid_img) + qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x22 0 512k', test_img) + + self.vm = iotests.VM() + self.vm.launch() + + self.blkdebug_file = iotests.file_path("blkdebug.conf") + + def tearDown(self): + self.vm.shutdown() + os.remove(test_img) + os.remove(mid_img) + os.remove(backing_img) + + def blockdev_add(self, **kwargs): + result = self.vm.qmp('blockdev-add', **kwargs) + self.assert_qmp(result, 'return', {}) + + def add_block_nodes(self, base_debug=None, mid_debug=None, top_debug=None): + self.blockdev_add(node_name='base-file', driver='file', + filename=backing_img) + self.blockdev_add(node_name='mid-file', driver='file', + filename=mid_img) + self.blockdev_add(node_name='top-file', driver='file', + filename=test_img) + + if base_debug: + self.blockdev_add(node_name='base-dbg', driver='blkdebug', + image='base-file', inject_error=base_debug) + if mid_debug: + self.blockdev_add(node_name='mid-dbg', driver='blkdebug', + image='mid-file', inject_error=mid_debug) + if top_debug: + self.blockdev_add(node_name='top-dbg', driver='blkdebug', + image='top-file', inject_error=top_debug) + + self.blockdev_add(node_name='base-fmt', driver='raw', + file=('base-dbg' if base_debug else 'base-file')) + self.blockdev_add(node_name='mid-fmt', driver=iotests.imgfmt, + file=('mid-dbg' if mid_debug else 'mid-file'), + backing='base-fmt') + self.blockdev_add(node_name='top-fmt', driver=iotests.imgfmt, + file=('top-dbg' if top_debug else 'top-file'), + backing='mid-fmt') + + def run_job(self, expected_events, error_pauses_job=False): + match_device = {'data': {'device': 'job0'}} + events = [ + ('BLOCK_JOB_COMPLETED', match_device), + ('BLOCK_JOB_CANCELLED', match_device), + ('BLOCK_JOB_ERROR', match_device), + ('BLOCK_JOB_READY', match_device), + ] + + completed = False + log = [] + while not completed: + ev = self.vm.events_wait(events, timeout=5.0) + if ev['event'] == 'BLOCK_JOB_COMPLETED': + completed = True + elif ev['event'] == 'BLOCK_JOB_ERROR': + if error_pauses_job: + result = self.vm.qmp('block-job-resume', device='job0') + self.assert_qmp(result, 'return', {}) + elif ev['event'] == 'BLOCK_JOB_READY': + result = self.vm.qmp('block-job-complete', device='job0') + self.assert_qmp(result, 'return', {}) + else: + self.fail("Unexpected event: %s" % ev) + log.append(iotests.filter_qmp_event(ev)) + + self.maxDiff = None + self.assertEqual(expected_events, log) + + def event_error(self, op, action): + return { + 'event': 'BLOCK_JOB_ERROR', + 'data': {'action': action, 'device': 'job0', 'operation': op}, + 'timestamp': {'microseconds': 'USECS', 'seconds': 'SECS'} + } + + def event_ready(self): + return { + 'event': 'BLOCK_JOB_READY', + 'data': {'device': 'job0', + 'len': 524288, + 'offset': 524288, + 'speed': 0, + 'type': 'commit'}, + 'timestamp': {'microseconds': 'USECS', 'seconds': 'SECS'}, + } + + def event_completed(self, errmsg=None, active=True): + max_len = 524288 if active else self.image_len + data = { + 'device': 'job0', + 'len': max_len, + 'offset': 0 if errmsg else max_len, + 'speed': 0, + 'type': 'commit' + } + if errmsg: + data['error'] = errmsg + + return { + 'event': 'BLOCK_JOB_COMPLETED', + 'data': data, + 'timestamp': {'microseconds': 'USECS', 'seconds': 'SECS'}, + } + + def blkdebug_event(self, event, is_raw=False): + if event: + return [{ + 'event': event, + 'sector': 512 if is_raw else 1024, + 'once': True, + }] + return None + + def prepare_and_start_job(self, on_error, active=True, + top_event=None, mid_event=None, base_event=None): + + top_debug = self.blkdebug_event(top_event) + mid_debug = self.blkdebug_event(mid_event) + base_debug = self.blkdebug_event(base_event, True) + + self.add_block_nodes(top_debug=top_debug, mid_debug=mid_debug, + base_debug=base_debug) + + result = self.vm.qmp('block-commit', job_id='job0', device='top-fmt', + top_node='top-fmt' if active else 'mid-fmt', + base_node='mid-fmt' if active else 'base-fmt', + on_error=on_error) + self.assert_qmp(result, 'return', {}) + + def testActiveReadErrorReport(self): + self.prepare_and_start_job('report', top_event='read_aio') + self.run_job([ + self.event_error('read', 'report'), + self.event_completed('Input/output error') + ]) + + self.vm.shutdown() + self.assertFalse(iotests.compare_images(test_img, mid_img), + 'target image matches source after error') + + def testActiveReadErrorStop(self): + self.prepare_and_start_job('stop', top_event='read_aio') + self.run_job([ + self.event_error('read', 'stop'), + self.event_ready(), + self.event_completed() + ], error_pauses_job=True) + + self.vm.shutdown() + self.assertTrue(iotests.compare_images(test_img, mid_img), + 'target image does not match source after commit') + + def testActiveReadErrorIgnore(self): + self.prepare_and_start_job('ignore', top_event='read_aio') + self.run_job([ + self.event_error('read', 'ignore'), + self.event_ready(), + self.event_completed() + ]) + + # For commit, 'ignore' actually means retry, so this will succeed + self.vm.shutdown() + self.assertTrue(iotests.compare_images(test_img, mid_img), + 'target image does not match source after commit') + + def testActiveWriteErrorReport(self): + self.prepare_and_start_job('report', mid_event='write_aio') + self.run_job([ + self.event_error('write', 'report'), + self.event_completed('Input/output error') + ]) + + self.vm.shutdown() + self.assertFalse(iotests.compare_images(test_img, mid_img), + 'target image matches source after error') + + def testActiveWriteErrorStop(self): + self.prepare_and_start_job('stop', mid_event='write_aio') + self.run_job([ + self.event_error('write', 'stop'), + self.event_ready(), + self.event_completed() + ], error_pauses_job=True) + + self.vm.shutdown() + self.assertTrue(iotests.compare_images(test_img, mid_img), + 'target image does not match source after commit') + + def testActiveWriteErrorIgnore(self): + self.prepare_and_start_job('ignore', mid_event='write_aio') + self.run_job([ + self.event_error('write', 'ignore'), + self.event_ready(), + self.event_completed() + ]) + + # For commit, 'ignore' actually means retry, so this will succeed + self.vm.shutdown() + self.assertTrue(iotests.compare_images(test_img, mid_img), + 'target image does not match source after commit') + + def testIntermediateReadErrorReport(self): + self.prepare_and_start_job('report', active=False, mid_event='read_aio') + self.run_job([ + self.event_error('read', 'report'), + self.event_completed('Input/output error', active=False) + ]) + + self.vm.shutdown() + self.assertFalse(iotests.compare_images(mid_img, backing_img, fmt2='raw'), + 'target image matches source after error') + + def testIntermediateReadErrorStop(self): + self.prepare_and_start_job('stop', active=False, mid_event='read_aio') + self.run_job([ + self.event_error('read', 'stop'), + self.event_completed(active=False) + ], error_pauses_job=True) + + self.vm.shutdown() + self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'), + 'target image does not match source after commit') + + def testIntermediateReadErrorIgnore(self): + self.prepare_and_start_job('ignore', active=False, mid_event='read_aio') + self.run_job([ + self.event_error('read', 'ignore'), + self.event_completed(active=False) + ]) + + # For commit, 'ignore' actually means retry, so this will succeed + self.vm.shutdown() + self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'), + 'target image does not match source after commit') + + def testIntermediateWriteErrorReport(self): + self.prepare_and_start_job('report', active=False, base_event='write_aio') + self.run_job([ + self.event_error('write', 'report'), + self.event_completed('Input/output error', active=False) + ]) + + self.vm.shutdown() + self.assertFalse(iotests.compare_images(mid_img, backing_img, fmt2='raw'), + 'target image matches source after error') + + def testIntermediateWriteErrorStop(self): + self.prepare_and_start_job('stop', active=False, base_event='write_aio') + self.run_job([ + self.event_error('write', 'stop'), + self.event_completed(active=False) + ], error_pauses_job=True) + + self.vm.shutdown() + self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'), + 'target image does not match source after commit') + + def testIntermediateWriteErrorIgnore(self): + self.prepare_and_start_job('ignore', active=False, base_event='write_aio') + self.run_job([ + self.event_error('write', 'ignore'), + self.event_completed(active=False) + ]) + + # For commit, 'ignore' actually means retry, so this will succeed + self.vm.shutdown() + self.assertTrue(iotests.compare_images(mid_img, backing_img, fmt2='raw'), + 'target image does not match source after commit') + if __name__ == '__main__': iotests.main(supported_fmts=['qcow2', 'qed'], supported_protocols=['file']) diff --git a/tests/qemu-iotests/040.out b/tests/qemu-iotests/040.out index 220a5fa..6a91713 100644 --- a/tests/qemu-iotests/040.out +++ b/tests/qemu-iotests/040.out @@ -1,5 +1,5 @@ -............................................... +........................................................... ---------------------------------------------------------------------- -Ran 47 tests +Ran 59 tests OK diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041 index 43556b9..5d67bf1 100755 --- a/tests/qemu-iotests/041 +++ b/tests/qemu-iotests/041 @@ -20,6 +20,7 @@ import time import os +import re import iotests from iotests import qemu_img, qemu_io @@ -34,6 +35,8 @@ quorum_img3 = os.path.join(iotests.test_dir, 'quorum3.img') quorum_repair_img = os.path.join(iotests.test_dir, 'quorum_repair.img') quorum_snapshot_file = os.path.join(iotests.test_dir, 'quorum_snapshot.img') +nbd_sock_path = os.path.join(iotests.test_dir, 'nbd.sock') + class TestSingleDrive(iotests.QMPTestCase): image_len = 1 * 1024 * 1024 # MB qmp_cmd = 'drive-mirror' @@ -80,7 +83,6 @@ class TestSingleDrive(iotests.QMPTestCase): self.cancel_and_wait(force=True) result = self.vm.qmp('query-block') self.assert_qmp(result, 'return[0]/inserted/file', test_img) - self.vm.shutdown() def test_cancel_after_ready(self): self.assert_no_active_block_jobs() @@ -201,8 +203,6 @@ class TestSingleDrive(iotests.QMPTestCase): self.assert_qmp(result, 'return[0]/node-name', 'top') self.assert_qmp(result, 'return[0]/backing/node-name', 'base') - self.vm.shutdown() - def test_medium_not_found(self): if iotests.qemu_default_machine != 'pc': return @@ -455,7 +455,6 @@ new_state = "1" self.assert_qmp(event, 'data/id', 'drive0') self.assert_no_active_block_jobs() - self.vm.shutdown() def test_ignore_read(self): self.assert_no_active_block_jobs() @@ -475,7 +474,6 @@ new_state = "1" result = self.vm.qmp('query-block-jobs') self.assert_qmp(result, 'return[0]/paused', False) self.complete_and_wait() - self.vm.shutdown() def test_large_cluster(self): self.assert_no_active_block_jobs() @@ -540,7 +538,6 @@ new_state = "1" self.complete_and_wait(wait_ready=False) self.assert_no_active_block_jobs() - self.vm.shutdown() class TestWriteErrors(iotests.QMPTestCase): image_len = 2 * 1024 * 1024 # MB @@ -614,7 +611,6 @@ new_state = "1" completed = True self.assert_no_active_block_jobs() - self.vm.shutdown() def test_ignore_write(self): self.assert_no_active_block_jobs() @@ -631,7 +627,6 @@ new_state = "1" result = self.vm.qmp('query-block-jobs') self.assert_qmp(result, 'return[0]/paused', False) self.complete_and_wait() - self.vm.shutdown() def test_stop_write(self): self.assert_no_active_block_jobs() @@ -667,7 +662,6 @@ new_state = "1" self.complete_and_wait(wait_ready=False) self.assert_no_active_block_jobs() - self.vm.shutdown() class TestSetSpeed(iotests.QMPTestCase): image_len = 80 * 1024 * 1024 # MB @@ -881,11 +875,14 @@ class TestRepairQuorum(iotests.QMPTestCase): # Add each individual quorum images for i in self.IMAGES: qemu_img('create', '-f', iotests.imgfmt, i, - str(TestSingleDrive.image_len)) + str(self.image_len)) # Assign a node name to each quorum image in order to manipulate # them opts = "node-name=img%i" % self.IMAGES.index(i) - self.vm = self.vm.add_drive(i, opts) + opts += ',driver=%s' % iotests.imgfmt + opts += ',file.driver=file' + opts += ',file.filename=%s' % i + self.vm = self.vm.add_blockdev(opts) self.vm.launch() @@ -898,7 +895,8 @@ class TestRepairQuorum(iotests.QMPTestCase): def tearDown(self): self.vm.shutdown() - for i in self.IMAGES + [ quorum_repair_img, quorum_snapshot_file ]: + for i in self.IMAGES + [ quorum_repair_img, quorum_snapshot_file, + nbd_sock_path ]: # Do a try/except because the test may have deleted some images try: os.remove(i) @@ -915,8 +913,7 @@ class TestRepairQuorum(iotests.QMPTestCase): self.complete_and_wait(drive="job0") self.assert_has_block_node("repair0", quorum_repair_img) - # TODO: a better test requiring some QEMU infrastructure will be added - # to check that this file is really driven by quorum + self.vm.assert_block_path('quorum0', '/children.1', 'repair0') self.vm.shutdown() self.assertTrue(iotests.compare_images(quorum_img2, quorum_repair_img), 'target image does not match source after mirroring') @@ -933,7 +930,6 @@ class TestRepairQuorum(iotests.QMPTestCase): # here we check that the last registered quorum file has not been # swapped out and unref self.assert_has_block_node(None, quorum_img3) - self.vm.shutdown() def test_cancel_after_ready(self): self.assert_no_active_block_jobs() @@ -1038,9 +1034,71 @@ class TestRepairQuorum(iotests.QMPTestCase): self.complete_and_wait('job0') self.assert_has_block_node("repair0", quorum_repair_img) - # TODO: a better test requiring some QEMU infrastructure will be added - # to check that this file is really driven by quorum + self.vm.assert_block_path('quorum0', '/children.1', 'repair0') + + def test_with_other_parent(self): + """ + Check that we cannot replace a Quorum child when it has other + parents. + """ + result = self.vm.qmp('nbd-server-start', + addr={ + 'type': 'unix', + 'data': {'path': nbd_sock_path} + }) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('nbd-server-add', device='img1') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0', + sync='full', node_name='repair0', replaces='img1', + target=quorum_repair_img, format=iotests.imgfmt) + self.assert_qmp(result, 'error/desc', + "Cannot replace 'img1' by a node mirrored from " + "'quorum0', because it cannot be guaranteed that doing " + "so would not lead to an abrupt change of visible data") + + def test_with_other_parents_after_mirror_start(self): + """ + The same as test_with_other_parent(), but add the NBD server + only when the mirror job is already running. + """ + result = self.vm.qmp('nbd-server-start', + addr={ + 'type': 'unix', + 'data': {'path': nbd_sock_path} + }) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('drive-mirror', job_id='mirror', device='quorum0', + sync='full', node_name='repair0', replaces='img1', + target=quorum_repair_img, format=iotests.imgfmt) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('nbd-server-add', device='img1') + self.assert_qmp(result, 'return', {}) + + # The full error message goes to stderr, we will check it later + self.complete_and_wait('mirror', + completion_error='Operation not permitted') + + # Should not have been replaced + self.vm.assert_block_path('quorum0', '/children.1', 'img1') + + # Check the full error message now self.vm.shutdown() + log = self.vm.get_log() + log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) + log = re.sub(r'^Formatting.*\n', '', log) + log = re.sub(r'\n\[I \+\d+\.\d+\] CLOSED\n?$', '', log) + log = re.sub(r'^%s: ' % os.path.basename(iotests.qemu_prog), '', log) + + self.assertEqual(log, + "Can no longer replace 'img1' by 'repair0', because " + + "it can no longer be guaranteed that doing so would " + + "not lead to an abrupt change of visible data") + # Test mirroring with a source that does not have any parents (not even a # BlockBackend) @@ -1132,6 +1190,52 @@ class TestOrphanedSource(iotests.QMPTestCase): self.assertFalse('mirror-filter' in nodes, 'Mirror filter node did not disappear') +# Test cases for @replaces that do not necessarily involve Quorum +class TestReplaces(iotests.QMPTestCase): + # Each of these test cases needs their own block graph, so do not + # create any nodes here + def setUp(self): + self.vm = iotests.VM() + self.vm.launch() + + def tearDown(self): + self.vm.shutdown() + for img in (test_img, target_img): + try: + os.remove(img) + except OSError: + pass + + @iotests.skip_if_unsupported(['copy-on-read']) + def test_replace_filter(self): + """ + Check that we can replace filter nodes. + """ + result = self.vm.qmp('blockdev-add', **{ + 'driver': 'copy-on-read', + 'node-name': 'filter0', + 'file': { + 'driver': 'copy-on-read', + 'node-name': 'filter1', + 'file': { + 'driver': 'null-co' + } + } + }) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('blockdev-add', + node_name='target', driver='null-co') + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('blockdev-mirror', job_id='mirror', device='filter0', + target='target', sync='full', replaces='filter1') + self.assert_qmp(result, 'return', {}) + + self.complete_and_wait('mirror') + + self.vm.assert_block_path('filter0', '/file', 'target') + if __name__ == '__main__': iotests.main(supported_fmts=['qcow2', 'qed'], supported_protocols=['file'], diff --git a/tests/qemu-iotests/041.out b/tests/qemu-iotests/041.out index f496be9..877b76f 100644 --- a/tests/qemu-iotests/041.out +++ b/tests/qemu-iotests/041.out @@ -1,5 +1,5 @@ -........................................................................................... +.............................................................................................. ---------------------------------------------------------------------- -Ran 91 tests +Ran 94 tests OK diff --git a/tests/qemu-iotests/122 b/tests/qemu-iotests/122 index dfa3509..f7a3ae6 100755 --- a/tests/qemu-iotests/122 +++ b/tests/qemu-iotests/122 @@ -276,6 +276,20 @@ $QEMU_IMG convert -O $IMGFMT -n "$TEST_IMG" "$TEST_IMG".orig $QEMU_IMG compare "$TEST_IMG" "$TEST_IMG".orig +echo +echo '=== -n -B to an image without a backing file ===' +echo + +# Base for the output +TEST_IMG="$TEST_IMG".base _make_test_img 64M + +# Output that does have $TEST_IMG.base set as its (implicit) backing file +TEST_IMG="$TEST_IMG".orig _make_test_img 64M + +# Convert with -n, which should not confuse -B with "target BDS has a +# backing file" +$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base -n "$TEST_IMG" "$TEST_IMG".orig + # success, all done echo '*** done' rm -f $seq.full diff --git a/tests/qemu-iotests/122.out b/tests/qemu-iotests/122.out index 849b6cc..1a35951 100644 --- a/tests/qemu-iotests/122.out +++ b/tests/qemu-iotests/122.out @@ -228,4 +228,9 @@ Formatting 'TEST_DIR/t.IMGFMT.orig', 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) Images are identical. + +=== -n -B to an image without a backing file === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +Formatting 'TEST_DIR/t.IMGFMT.orig', fmt=IMGFMT size=67108864 *** done diff --git a/tests/qemu-iotests/139 b/tests/qemu-iotests/139 index 6b1a444..7120d31 100755 --- a/tests/qemu-iotests/139 +++ b/tests/qemu-iotests/139 @@ -344,9 +344,6 @@ class TestBlockdevDel(iotests.QMPTestCase): @iotests.skip_if_unsupported(['quorum']) def testQuorum(self): - if not iotests.supports_quorum(): - return - self.addQuorum('quorum0', 'node0', 'node1') # We cannot remove the children of a Quorum device self.delBlockDriverState('node0', expect_error = True) diff --git a/tests/qemu-iotests/147 b/tests/qemu-iotests/147 index f4b0a11..d7a9f31 100755 --- a/tests/qemu-iotests/147 +++ b/tests/qemu-iotests/147 @@ -134,7 +134,7 @@ class BuiltinNBD(NBDBlockdevAddBase): self.server.add_drive_raw('if=none,id=nbd-export,' + 'file=%s,' % test_img + 'format=%s,' % imgfmt + - 'cache=%s' % cachemode + + 'cache=%s,' % cachemode + 'aio=%s' % aiomode) self.server.launch() diff --git a/tests/qemu-iotests/155 b/tests/qemu-iotests/155 index e35b1d5..f237868 100755 --- a/tests/qemu-iotests/155 +++ b/tests/qemu-iotests/155 @@ -163,12 +163,7 @@ class MirrorBaseClass(BaseClass): self.assert_qmp(result, 'return', {}) - self.vm.event_wait('BLOCK_JOB_READY') - - result = self.vm.qmp('block-job-complete', device='mirror-job') - self.assert_qmp(result, 'return', {}) - - self.vm.event_wait('BLOCK_JOB_COMPLETED') + self.complete_and_wait('mirror-job') def testFull(self): self.runMirror('full') diff --git a/tests/qemu-iotests/244 b/tests/qemu-iotests/244 index 0d1efee..2ec1815 100755 --- a/tests/qemu-iotests/244 +++ b/tests/qemu-iotests/244 @@ -197,6 +197,20 @@ $QEMU_IO -c 'read -P 0x11 0 1M' -f $IMGFMT "$TEST_IMG" | _filter_qemu_io $QEMU_IMG map --output=human "$TEST_IMG" | _filter_testdir $QEMU_IMG map --output=json "$TEST_IMG" +echo +echo "=== Copy offloading ===" +echo + +# Make use of copy offloading if the test host can provide it +_make_test_img -o "data_file=$TEST_IMG.data" 64M +$QEMU_IMG convert -f $IMGFMT -O $IMGFMT -n -C "$TEST_IMG.src" "$TEST_IMG" +$QEMU_IMG compare -f $IMGFMT -F $IMGFMT "$TEST_IMG.src" "$TEST_IMG" + +# blkdebug doesn't support copy offloading, so this tests the error path +$QEMU_IMG amend -f $IMGFMT -o "data_file=blkdebug::$TEST_IMG.data" "$TEST_IMG" +$QEMU_IMG convert -f $IMGFMT -O $IMGFMT -n -C "$TEST_IMG.src" "$TEST_IMG" +$QEMU_IMG compare -f $IMGFMT -F $IMGFMT "$TEST_IMG.src" "$TEST_IMG" + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/244.out b/tests/qemu-iotests/244.out index 6a3d006..e6f4dc7 100644 --- a/tests/qemu-iotests/244.out +++ b/tests/qemu-iotests/244.out @@ -122,4 +122,10 @@ Offset Length Mapped to File 0 0x100000 0 TEST_DIR/t.qcow2.data [{ "start": 0, "length": 1048576, "depth": 0, "zero": false, "data": true, "offset": 0}, { "start": 1048576, "length": 66060288, "depth": 0, "zero": true, "data": false}] + +=== Copy offloading === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 data_file=TEST_DIR/t.IMGFMT.data +Images are identical. +Images are identical. *** done diff --git a/tests/qemu-iotests/259 b/tests/qemu-iotests/259 new file mode 100755 index 0000000..62e29af --- /dev/null +++ b/tests/qemu-iotests/259 @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# +# Test generic image creation fallback (by using NBD) +# +# Copyright (C) 2019 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=mreitz@redhat.com + +seq=$(basename $0) +echo "QA output created by $seq" + +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt raw +_supported_proto nbd +_supported_os Linux + + +_make_test_img 64M + +echo +echo '--- Testing creation ---' + +$QEMU_IMG create -f qcow2 "$TEST_IMG" 64M | _filter_img_create +$QEMU_IMG info "$TEST_IMG" | _filter_img_info + +echo +echo '--- Testing creation for which the node would need to grow ---' + +# NBD does not support resizing, so this will fail +$QEMU_IMG create -f qcow2 -o preallocation=metadata "$TEST_IMG" 64M 2>&1 \ + | _filter_img_create + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/259.out b/tests/qemu-iotests/259.out new file mode 100644 index 0000000..ffed19c --- /dev/null +++ b/tests/qemu-iotests/259.out @@ -0,0 +1,14 @@ +QA output created by 259 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 + +--- Testing creation --- +Formatting 'TEST_DIR/t.IMGFMT', fmt=qcow2 size=67108864 +image: TEST_DIR/t.IMGFMT +file format: qcow2 +virtual size: 64 MiB (67108864 bytes) +disk size: unavailable + +--- Testing creation for which the node would need to grow --- +qemu-img: TEST_DIR/t.IMGFMT: Could not resize image: Image format driver does not support resize +Formatting 'TEST_DIR/t.IMGFMT', fmt=qcow2 size=67108864 preallocation=metadata +*** done diff --git a/tests/qemu-iotests/279 b/tests/qemu-iotests/279 index 6682376..30d29b1 100755 --- a/tests/qemu-iotests/279 +++ b/tests/qemu-iotests/279 @@ -38,6 +38,8 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 _supported_fmt qcow qcow2 vmdk qed _supported_proto file _supported_os Linux +_unsupported_imgopts "subformat=monolithicFlat" \ + "subformat=twoGbMaxExtentFlat" \ TEST_IMG="$TEST_IMG.base" _make_test_img 64M TEST_IMG="$TEST_IMG.mid" _make_test_img -b "$TEST_IMG.base" @@ -45,11 +47,12 @@ _make_test_img -b "$TEST_IMG.mid" echo echo '== qemu-img info --backing-chain ==' -_img_info --backing-chain | _filter_img_info +_img_info --backing-chain | _filter_img_info | grep -v 'backing file format' echo echo '== qemu-img info --backing-chain --image-opts ==' -TEST_IMG="driver=qcow2,file.driver=file,file.filename=$TEST_IMG" _img_info --backing-chain --image-opts | _filter_img_info +TEST_IMG="driver=$IMGFMT,file.driver=file,file.filename=$TEST_IMG" _img_info --backing-chain --image-opts \ + | _filter_img_info | grep -v 'backing file format' # success, all done echo "*** done" diff --git a/tests/qemu-iotests/284 b/tests/qemu-iotests/284 new file mode 100755 index 0000000..071e89b --- /dev/null +++ b/tests/qemu-iotests/284 @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# +# Test ref count checks on encrypted images +# +# Copyright (C) 2019 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=berrange@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +status=1 # failure is the default! + +_cleanup() +{ + _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 generic +_supported_os Linux + + +size=1M + +SECRET="secret,id=sec0,data=astrochicken" + +IMGSPEC="driver=$IMGFMT,file.filename=$TEST_IMG,encrypt.key-secret=sec0" +QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT + +_run_test() +{ + IMGOPTSSYNTAX=true + OLD_TEST_IMG="$TEST_IMG" + TEST_IMG="driver=$IMGFMT,file.filename=$TEST_IMG,encrypt.key-secret=sec0" + QEMU_IMG_EXTRA_ARGS="--image-opts --object $SECRET" + + echo + echo "== cluster size $csize" + echo "== checking image refcounts ==" + _check_test_img + + echo + echo "== writing some data ==" + $QEMU_IO -c "write -P 0x9 0 1" $QEMU_IMG_EXTRA_ARGS $TEST_IMG | _filter_qemu_io | _filter_testdir + echo + echo "== rechecking image refcounts ==" + _check_test_img + + echo + echo "== writing some more data ==" + $QEMU_IO -c "write -P 0x9 $csize 1" $QEMU_IMG_EXTRA_ARGS $TEST_IMG | _filter_qemu_io | _filter_testdir + echo + echo "== rechecking image refcounts ==" + _check_test_img + + TEST_IMG="$OLD_TEST_IMG" + QEMU_IMG_EXTRA_ARGS= + IMGOPTSSYNTAX= +} + + +echo +echo "testing LUKS qcow2 encryption" +echo + +for csize in 512 2048 32768 +do + _make_test_img --object $SECRET -o "encrypt.format=luks,encrypt.key-secret=sec0,encrypt.iter-time=10,cluster_size=$csize" $size + _run_test + _cleanup_test_img +done + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/284.out b/tests/qemu-iotests/284.out new file mode 100644 index 0000000..48216f5 --- /dev/null +++ b/tests/qemu-iotests/284.out @@ -0,0 +1,62 @@ +QA output created by 284 + +testing LUKS qcow2 encryption + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 + +== cluster size 512 +== checking image refcounts == +No errors were found on the image. + +== writing some data == +wrote 1/1 bytes at offset 0 +1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== rechecking image refcounts == +No errors were found on the image. + +== writing some more data == +wrote 1/1 bytes at offset 512 +1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== rechecking image refcounts == +No errors were found on the image. +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 + +== cluster size 2048 +== checking image refcounts == +No errors were found on the image. + +== writing some data == +wrote 1/1 bytes at offset 0 +1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== rechecking image refcounts == +No errors were found on the image. + +== writing some more data == +wrote 1/1 bytes at offset 2048 +1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== rechecking image refcounts == +No errors were found on the image. +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 + +== cluster size 32768 +== checking image refcounts == +No errors were found on the image. + +== writing some data == +wrote 1/1 bytes at offset 0 +1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== rechecking image refcounts == +No errors were found on the image. + +== writing some more data == +wrote 1/1 bytes at offset 32768 +1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== rechecking image refcounts == +No errors were found on the image. +*** done diff --git a/tests/qemu-iotests/286 b/tests/qemu-iotests/286 new file mode 100755 index 0000000..f14445b --- /dev/null +++ b/tests/qemu-iotests/286 @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# +# Test qemu-img snapshot -l +# +# Copyright (C) 2019 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/>. +# + +seq=$(basename "$0") +echo "QA output created by $seq" + +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter +. ./common.qemu + +_supported_fmt qcow2 +_supported_proto file +# Internal snapshots are (currently) impossible with refcount_bits=1, +# and generally impossible with external data files +_unsupported_imgopts 'refcount_bits=1[^0-9]' data_file + +_make_test_img 64M + +# Should be so long as to take up the whole field width +sn_name=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz + +# More memory will give us a larger VM state, i.e. one above 1 MB. +# This way, we get a number with a decimal point. +qemu_comm_method=monitor _launch_qemu -m 512 "$TEST_IMG" + +_send_qemu_cmd $QEMU_HANDLE "savevm $sn_name" '(qemu)' +_send_qemu_cmd $QEMU_HANDLE 'quit' '(qemu)' +wait=yes _cleanup_qemu + +# Check that all fields are separated by spaces. +# We first collapse all space sequences into one space each; +# then we turn every space-separated field into a '.'; +# and finally, we name the '.'s so the output is not just a confusing +# sequence of dots. + +echo 'Output structure:' +$QEMU_IMG snapshot -l "$TEST_IMG" | tail -n 1 | tr -s ' ' \ + | sed -e 's/\S\+/./g' \ + | sed -e 's/\./(snapshot ID)/' \ + -e 's/\./(snapshot name)/' \ + -e 's/\./(VM state size value)/' \ + -e 's/\./(VM state size unit)/' \ + -e 's/\./(snapshot date)/' \ + -e 's/\./(snapshot time)/' \ + -e 's/\./(VM clock)/' + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/286.out b/tests/qemu-iotests/286.out new file mode 100644 index 0000000..39ff07e --- /dev/null +++ b/tests/qemu-iotests/286.out @@ -0,0 +1,8 @@ +QA output created by 286 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +QEMU X.Y.Z monitor - type 'help' for more information +(qemu) savevm abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz +(qemu) quit +Output structure: +(snapshot ID) (snapshot name) (VM state size value) (VM state size unit) (snapshot date) (snapshot time) (VM clock) +*** done diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index 1904223..0317667 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -273,6 +273,7 @@ 256 rw auto quick 257 rw 258 rw quick +259 rw auto quick 260 rw quick 261 rw 262 rw quick migration @@ -290,3 +291,5 @@ 280 rw migration quick 281 rw quick 283 auto quick +284 rw +286 rw quick diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 0473e82..8815052 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -714,6 +714,65 @@ class VM(qtest.QEMUQtestMachine): return fields.items() <= ret.items() + def assert_block_path(self, root, path, expected_node, graph=None): + """ + Check whether the node under the given path in the block graph + is @expected_node. + + @root is the node name of the node where the @path is rooted. + + @path is a string that consists of child names separated by + slashes. It must begin with a slash. + + Examples for @root + @path: + - root="qcow2-node", path="/backing/file" + - root="quorum-node", path="/children.2/file" + + Hypothetically, @path could be empty, in which case it would + point to @root. However, in practice this case is not useful + and hence not allowed. + + @expected_node may be None. (All elements of the path but the + leaf must still exist.) + + @graph may be None or the result of an x-debug-query-block-graph + call that has already been performed. + """ + if graph is None: + graph = self.qmp('x-debug-query-block-graph')['return'] + + iter_path = iter(path.split('/')) + + # Must start with a / + assert next(iter_path) == '' + + node = next((node for node in graph['nodes'] if node['name'] == root), + None) + + # An empty @path is not allowed, so the root node must be present + assert node is not None, 'Root node %s not found' % root + + for child_name in iter_path: + assert node is not None, 'Cannot follow path %s%s' % (root, path) + + try: + node_id = next(edge['child'] for edge in graph['edges'] \ + if edge['parent'] == node['id'] and + edge['name'] == child_name) + + node = next(node for node in graph['nodes'] \ + if node['id'] == node_id) + except StopIteration: + node = None + + if node is None: + assert expected_node is None, \ + 'No node found under %s (but expected %s)' % \ + (path, expected_node) + else: + assert node['name'] == expected_node, \ + 'Found node %s under %s (but expected %s)' % \ + (node['name'], path, expected_node) index_re = re.compile(r'([^\[]+)\[([^\]]+)\]') diff --git a/tests/qtest/Makefile.include b/tests/qtest/Makefile.include index eb0f23b..e769c1a 100644 --- a/tests/qtest/Makefile.include +++ b/tests/qtest/Makefile.include @@ -157,52 +157,54 @@ check-qtest-s390x-y += migration-test # libqos / qgraph : libqgraph-obj-y = tests/qtest/libqos/qgraph.o -libqos-obj-y = $(libqgraph-obj-y) tests/qtest/libqos/pci.o tests/qtest/libqos/fw_cfg.o -libqos-obj-y += tests/qtest/libqos/malloc.o -libqos-obj-y += tests/qtest/libqos/libqos.o -libqos-spapr-obj-y = $(libqos-obj-y) tests/qtest/libqos/malloc-spapr.o +libqos-core-obj-y = $(libqgraph-obj-y) tests/qtest/libqos/pci.o tests/qtest/libqos/fw_cfg.o +libqos-core-obj-y += tests/qtest/libqos/malloc.o +libqos-core-obj-y += tests/qtest/libqos/libqos.o +libqos-spapr-obj-y = $(libqos-core-obj-y) tests/qtest/libqos/malloc-spapr.o libqos-spapr-obj-y += tests/qtest/libqos/libqos-spapr.o libqos-spapr-obj-y += tests/qtest/libqos/rtas.o libqos-spapr-obj-y += tests/qtest/libqos/pci-spapr.o -libqos-pc-obj-y = $(libqos-obj-y) tests/qtest/libqos/pci-pc.o +libqos-pc-obj-y = $(libqos-core-obj-y) tests/qtest/libqos/pci-pc.o libqos-pc-obj-y += tests/qtest/libqos/malloc-pc.o tests/qtest/libqos/libqos-pc.o libqos-pc-obj-y += tests/qtest/libqos/ahci.o libqos-usb-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/qtest/libqos/usb.o # qos devices: -qos-test-obj-y = tests/qtest/qos-test.o $(libqgraph-obj-y) -qos-test-obj-y += $(libqos-pc-obj-y) $(libqos-spapr-obj-y) -qos-test-obj-y += tests/qtest/libqos/e1000e.o -qos-test-obj-y += tests/qtest/libqos/i2c.o -qos-test-obj-y += tests/qtest/libqos/i2c-imx.o -qos-test-obj-y += tests/qtest/libqos/i2c-omap.o -qos-test-obj-y += tests/qtest/libqos/sdhci.o -qos-test-obj-y += tests/qtest/libqos/tpci200.o -qos-test-obj-y += tests/qtest/libqos/virtio.o -qos-test-obj-$(CONFIG_VIRTFS) += tests/qtest/libqos/virtio-9p.o -qos-test-obj-y += tests/qtest/libqos/virtio-balloon.o -qos-test-obj-y += tests/qtest/libqos/virtio-blk.o -qos-test-obj-y += tests/qtest/libqos/virtio-mmio.o -qos-test-obj-y += tests/qtest/libqos/virtio-net.o -qos-test-obj-y += tests/qtest/libqos/virtio-pci.o -qos-test-obj-y += tests/qtest/libqos/virtio-pci-modern.o -qos-test-obj-y += tests/qtest/libqos/virtio-rng.o -qos-test-obj-y += tests/qtest/libqos/virtio-scsi.o -qos-test-obj-y += tests/qtest/libqos/virtio-serial.o +libqos-obj-y = $(libqgraph-obj-y) +libqos-obj-y += $(libqos-pc-obj-y) $(libqos-spapr-obj-y) +libqos-obj-y += tests/qtest/libqos/qos_external.o +libqos-obj-y += tests/qtest/libqos/e1000e.o +libqos-obj-y += tests/qtest/libqos/i2c.o +libqos-obj-y += tests/qtest/libqos/i2c-imx.o +libqos-obj-y += tests/qtest/libqos/i2c-omap.o +libqos-obj-y += tests/qtest/libqos/sdhci.o +libqos-obj-y += tests/qtest/libqos/tpci200.o +libqos-obj-y += tests/qtest/libqos/virtio.o +libqos-obj-$(CONFIG_VIRTFS) += tests/qtest/libqos/virtio-9p.o +libqos-obj-y += tests/qtest/libqos/virtio-balloon.o +libqos-obj-y += tests/qtest/libqos/virtio-blk.o +libqos-obj-y += tests/qtest/libqos/virtio-mmio.o +libqos-obj-y += tests/qtest/libqos/virtio-net.o +libqos-obj-y += tests/qtest/libqos/virtio-pci.o +libqos-obj-y += tests/qtest/libqos/virtio-pci-modern.o +libqos-obj-y += tests/qtest/libqos/virtio-rng.o +libqos-obj-y += tests/qtest/libqos/virtio-scsi.o +libqos-obj-y += tests/qtest/libqos/virtio-serial.o # qos machines: -qos-test-obj-y += tests/qtest/libqos/aarch64-xlnx-zcu102-machine.o -qos-test-obj-y += tests/qtest/libqos/arm-imx25-pdk-machine.o -qos-test-obj-y += tests/qtest/libqos/arm-n800-machine.o -qos-test-obj-y += tests/qtest/libqos/arm-raspi2-machine.o -qos-test-obj-y += tests/qtest/libqos/arm-sabrelite-machine.o -qos-test-obj-y += tests/qtest/libqos/arm-smdkc210-machine.o -qos-test-obj-y += tests/qtest/libqos/arm-virt-machine.o -qos-test-obj-y += tests/qtest/libqos/arm-xilinx-zynq-a9-machine.o -qos-test-obj-y += tests/qtest/libqos/ppc64_pseries-machine.o -qos-test-obj-y += tests/qtest/libqos/x86_64_pc-machine.o +libqos-obj-y += tests/qtest/libqos/aarch64-xlnx-zcu102-machine.o +libqos-obj-y += tests/qtest/libqos/arm-imx25-pdk-machine.o +libqos-obj-y += tests/qtest/libqos/arm-n800-machine.o +libqos-obj-y += tests/qtest/libqos/arm-raspi2-machine.o +libqos-obj-y += tests/qtest/libqos/arm-sabrelite-machine.o +libqos-obj-y += tests/qtest/libqos/arm-smdkc210-machine.o +libqos-obj-y += tests/qtest/libqos/arm-virt-machine.o +libqos-obj-y += tests/qtest/libqos/arm-xilinx-zynq-a9-machine.o +libqos-obj-y += tests/qtest/libqos/ppc64_pseries-machine.o +libqos-obj-y += tests/qtest/libqos/x86_64_pc-machine.o # qos tests: +qos-test-obj-y += tests/qtest/qos-test.o qos-test-obj-y += tests/qtest/ac97-test.o qos-test-obj-y += tests/qtest/ds1338-test.o qos-test-obj-y += tests/qtest/e1000-test.o @@ -234,7 +236,7 @@ check-unit-y += tests/test-qgraph$(EXESUF) tests/test-qgraph$(EXESUF): tests/test-qgraph.o $(libqgraph-obj-y) check-qtest-generic-y += qos-test -tests/qtest/qos-test$(EXESUF): $(qos-test-obj-y) +tests/qtest/qos-test$(EXESUF): $(qos-test-obj-y) $(libqos-obj-y) # QTest dependencies: tests/qtest/qmp-test$(EXESUF): tests/qtest/qmp-test.o diff --git a/tests/qtest/fuzz/Makefile.include b/tests/qtest/fuzz/Makefile.include new file mode 100644 index 0000000..cde3e96 --- /dev/null +++ b/tests/qtest/fuzz/Makefile.include @@ -0,0 +1,18 @@ +QEMU_PROG_FUZZ=qemu-fuzz-$(TARGET_NAME)$(EXESUF) + +fuzz-obj-y += tests/qtest/libqtest.o +fuzz-obj-y += $(libqos-obj-y) +fuzz-obj-y += tests/qtest/fuzz/fuzz.o # Fuzzer skeleton +fuzz-obj-y += tests/qtest/fuzz/fork_fuzz.o +fuzz-obj-y += tests/qtest/fuzz/qos_fuzz.o + +# Targets +fuzz-obj-y += tests/qtest/fuzz/i440fx_fuzz.o +fuzz-obj-y += tests/qtest/fuzz/virtio_net_fuzz.o +fuzz-obj-y += tests/qtest/fuzz/virtio_scsi_fuzz.o + +FUZZ_CFLAGS += -I$(SRC_PATH)/tests -I$(SRC_PATH)/tests/qtest + +# Linker Script to force coverage-counters into known regions which we can mark +# shared +FUZZ_LDFLAGS += -Xlinker -T$(SRC_PATH)/tests/qtest/fuzz/fork_fuzz.ld diff --git a/tests/qtest/fuzz/fork_fuzz.c b/tests/qtest/fuzz/fork_fuzz.c new file mode 100644 index 0000000..2bd0851 --- /dev/null +++ b/tests/qtest/fuzz/fork_fuzz.c @@ -0,0 +1,55 @@ +/* + * Fork-based fuzzing helpers + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "fork_fuzz.h" + + +void counter_shm_init(void) +{ + char *shm_path = g_strdup_printf("/qemu-fuzz-cntrs.%d", getpid()); + int fd = shm_open(shm_path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + g_free(shm_path); + + if (fd == -1) { + perror("Error: "); + exit(1); + } + if (ftruncate(fd, &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START) == -1) { + perror("Error: "); + exit(1); + } + /* Copy what's in the counter region to the shm.. */ + void *rptr = mmap(NULL , + &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START, + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + memcpy(rptr, + &__FUZZ_COUNTERS_START, + &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START); + + munmap(rptr, &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START); + + /* And map the shm over the counter region */ + rptr = mmap(&__FUZZ_COUNTERS_START, + &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); + + close(fd); + + if (!rptr) { + perror("Error: "); + exit(1); + } +} + + diff --git a/tests/qtest/fuzz/fork_fuzz.h b/tests/qtest/fuzz/fork_fuzz.h new file mode 100644 index 0000000..9ecb8b5 --- /dev/null +++ b/tests/qtest/fuzz/fork_fuzz.h @@ -0,0 +1,23 @@ +/* + * Fork-based fuzzing helpers + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef FORK_FUZZ_H +#define FORK_FUZZ_H + +extern uint8_t __FUZZ_COUNTERS_START; +extern uint8_t __FUZZ_COUNTERS_END; + +void counter_shm_init(void); + +#endif + diff --git a/tests/qtest/fuzz/fork_fuzz.ld b/tests/qtest/fuzz/fork_fuzz.ld new file mode 100644 index 0000000..b23a59f --- /dev/null +++ b/tests/qtest/fuzz/fork_fuzz.ld @@ -0,0 +1,37 @@ +/* We adjust linker script modification to place all of the stuff that needs to + * persist across fuzzing runs into a contiguous seciton of memory. Then, it is + * easy to re-map the counter-related memory as shared. +*/ + +SECTIONS +{ + .data.fuzz_start : ALIGN(4K) + { + __FUZZ_COUNTERS_START = .; + __start___sancov_cntrs = .; + *(_*sancov_cntrs); + __stop___sancov_cntrs = .; + + /* Lowest stack counter */ + *(__sancov_lowest_stack); + } + .data.fuzz_ordered : + { + /* Coverage counters. They're not necessary for fuzzing, but are useful + * for analyzing the fuzzing performance + */ + __start___llvm_prf_cnts = .; + *(*llvm_prf_cnts); + __stop___llvm_prf_cnts = .; + + /* Internal Libfuzzer TracePC object which contains the ValueProfileMap */ + FuzzerTracePC*(.bss*); + } + .data.fuzz_end : ALIGN(4K) + { + __FUZZ_COUNTERS_END = .; + } +} +/* Dont overwrite the SECTIONS in the default linker script. Instead insert the + * above into the default script */ +INSERT AFTER .data; diff --git a/tests/qtest/fuzz/fuzz.c b/tests/qtest/fuzz/fuzz.c new file mode 100644 index 0000000..0d78ac8 --- /dev/null +++ b/tests/qtest/fuzz/fuzz.c @@ -0,0 +1,179 @@ +/* + * fuzzing driver + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" + +#include <wordexp.h> + +#include "sysemu/qtest.h" +#include "sysemu/runstate.h" +#include "sysemu/sysemu.h" +#include "qemu/main-loop.h" +#include "tests/qtest/libqtest.h" +#include "tests/qtest/libqos/qgraph.h" +#include "fuzz.h" + +#define MAX_EVENT_LOOPS 10 + +typedef struct FuzzTargetState { + FuzzTarget *target; + QSLIST_ENTRY(FuzzTargetState) target_list; +} FuzzTargetState; + +typedef QSLIST_HEAD(, FuzzTargetState) FuzzTargetList; + +static const char *fuzz_arch = TARGET_NAME; + +static FuzzTargetList *fuzz_target_list; +static FuzzTarget *fuzz_target; +static QTestState *fuzz_qts; + + + +void flush_events(QTestState *s) +{ + int i = MAX_EVENT_LOOPS; + while (g_main_context_pending(NULL) && i-- > 0) { + main_loop_wait(false); + } +} + +static QTestState *qtest_setup(void) +{ + qtest_server_set_send_handler(&qtest_client_inproc_recv, &fuzz_qts); + return qtest_inproc_init(&fuzz_qts, false, fuzz_arch, + &qtest_server_inproc_recv); +} + +void fuzz_add_target(const FuzzTarget *target) +{ + FuzzTargetState *tmp; + FuzzTargetState *target_state; + if (!fuzz_target_list) { + fuzz_target_list = g_new0(FuzzTargetList, 1); + } + + QSLIST_FOREACH(tmp, fuzz_target_list, target_list) { + if (g_strcmp0(tmp->target->name, target->name) == 0) { + fprintf(stderr, "Error: Fuzz target name %s already in use\n", + target->name); + abort(); + } + } + target_state = g_new0(FuzzTargetState, 1); + target_state->target = g_new0(FuzzTarget, 1); + *(target_state->target) = *target; + QSLIST_INSERT_HEAD(fuzz_target_list, target_state, target_list); +} + + + +static void usage(char *path) +{ + printf("Usage: %s --fuzz-target=FUZZ_TARGET [LIBFUZZER ARGUMENTS]\n", path); + printf("where FUZZ_TARGET is one of:\n"); + FuzzTargetState *tmp; + if (!fuzz_target_list) { + fprintf(stderr, "Fuzz target list not initialized\n"); + abort(); + } + QSLIST_FOREACH(tmp, fuzz_target_list, target_list) { + printf(" * %s : %s\n", tmp->target->name, + tmp->target->description); + } + exit(0); +} + +static FuzzTarget *fuzz_get_target(char* name) +{ + FuzzTargetState *tmp; + if (!fuzz_target_list) { + fprintf(stderr, "Fuzz target list not initialized\n"); + abort(); + } + + QSLIST_FOREACH(tmp, fuzz_target_list, target_list) { + if (strcmp(tmp->target->name, name) == 0) { + return tmp->target; + } + } + return NULL; +} + + +/* Executed for each fuzzing-input */ +int LLVMFuzzerTestOneInput(const unsigned char *Data, size_t Size) +{ + /* + * Do the pre-fuzz-initialization before the first fuzzing iteration, + * instead of before the actual fuzz loop. This is needed since libfuzzer + * may fork off additional workers, prior to the fuzzing loop, and if + * pre_fuzz() sets up e.g. shared memory, this should be done for the + * individual worker processes + */ + static int pre_fuzz_done; + if (!pre_fuzz_done && fuzz_target->pre_fuzz) { + fuzz_target->pre_fuzz(fuzz_qts); + pre_fuzz_done = true; + } + + fuzz_target->fuzz(fuzz_qts, Data, Size); + return 0; +} + +/* Executed once, prior to fuzzing */ +int LLVMFuzzerInitialize(int *argc, char ***argv, char ***envp) +{ + + char *target_name; + + /* Initialize qgraph and modules */ + qos_graph_init(); + module_call_init(MODULE_INIT_FUZZ_TARGET); + module_call_init(MODULE_INIT_QOM); + module_call_init(MODULE_INIT_LIBQOS); + + if (*argc <= 1) { + usage(**argv); + } + + /* Identify the fuzz target */ + target_name = (*argv)[1]; + if (!strstr(target_name, "--fuzz-target=")) { + usage(**argv); + } + + target_name += strlen("--fuzz-target="); + + fuzz_target = fuzz_get_target(target_name); + if (!fuzz_target) { + usage(**argv); + } + + fuzz_qts = qtest_setup(); + + if (fuzz_target->pre_vm_init) { + fuzz_target->pre_vm_init(); + } + + /* Run QEMU's softmmu main with the fuzz-target dependent arguments */ + const char *init_cmdline = fuzz_target->get_init_cmdline(fuzz_target); + + /* Split the runcmd into an argv and argc */ + wordexp_t result; + wordexp(init_cmdline, &result, 0); + + qemu_init(result.we_wordc, result.we_wordv, NULL); + + return 0; +} diff --git a/tests/qtest/fuzz/fuzz.h b/tests/qtest/fuzz/fuzz.h new file mode 100644 index 0000000..03901d4 --- /dev/null +++ b/tests/qtest/fuzz/fuzz.h @@ -0,0 +1,95 @@ +/* + * fuzzing driver + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef FUZZER_H_ +#define FUZZER_H_ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" + +#include "tests/qtest/libqtest.h" + +/** + * A libfuzzer fuzzing target + * + * The QEMU fuzzing binary is built with all available targets, each + * with a unique @name that can be specified on the command-line to + * select which target should run. + * + * A target must implement ->fuzz() to process a random input. If QEMU + * crashes in ->fuzz() then libfuzzer will record a failure. + * + * Fuzzing targets are registered with fuzz_add_target(): + * + * static const FuzzTarget fuzz_target = { + * .name = "my-device-fifo", + * .description = "Fuzz the FIFO buffer registers of my-device", + * ... + * }; + * + * static void register_fuzz_target(void) + * { + * fuzz_add_target(&fuzz_target); + * } + * fuzz_target_init(register_fuzz_target); + */ +typedef struct FuzzTarget { + const char *name; /* target identifier (passed to --fuzz-target=)*/ + const char *description; /* help text */ + + + /* + * returns the arg-list that is passed to qemu/softmmu init() + * Cannot be NULL + */ + const char* (*get_init_cmdline)(struct FuzzTarget *); + + /* + * will run once, prior to running qemu/softmmu init. + * eg: set up shared-memory for communication with the child-process + * Can be NULL + */ + void(*pre_vm_init)(void); + + /* + * will run once, after QEMU has been initialized, prior to the fuzz-loop. + * eg: detect the memory map + * Can be NULL + */ + void(*pre_fuzz)(QTestState *); + + /* + * accepts and executes an input from libfuzzer. this is repeatedly + * executed during the fuzzing loop. Its should handle setup, input + * execution and cleanup. + * Cannot be NULL + */ + void(*fuzz)(QTestState *, const unsigned char *, size_t); + +} FuzzTarget; + +void flush_events(QTestState *); +void reboot(QTestState *); + +/* + * makes a copy of *target and adds it to the target-list. + * i.e. fine to set up target on the caller's stack + */ +void fuzz_add_target(const FuzzTarget *target); + +int LLVMFuzzerTestOneInput(const unsigned char *Data, size_t Size); +int LLVMFuzzerInitialize(int *argc, char ***argv, char ***envp); + +#endif + diff --git a/tests/qtest/fuzz/i440fx_fuzz.c b/tests/qtest/fuzz/i440fx_fuzz.c new file mode 100644 index 0000000..ab5f112 --- /dev/null +++ b/tests/qtest/fuzz/i440fx_fuzz.c @@ -0,0 +1,193 @@ +/* + * I440FX Fuzzing Target + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" + +#include "qemu/main-loop.h" +#include "tests/qtest/libqtest.h" +#include "tests/qtest/libqos/pci.h" +#include "tests/qtest/libqos/pci-pc.h" +#include "fuzz.h" +#include "fuzz/qos_fuzz.h" +#include "fuzz/fork_fuzz.h" + + +#define I440FX_PCI_HOST_BRIDGE_CFG 0xcf8 +#define I440FX_PCI_HOST_BRIDGE_DATA 0xcfc + +/* + * the input to the fuzzing functions below is a buffer of random bytes. we + * want to convert these bytes into a sequence of qtest or qos calls. to do + * this we define some opcodes: + */ +enum action_id { + WRITEB, + WRITEW, + WRITEL, + READB, + READW, + READL, + ACTION_MAX +}; + +static void i440fx_fuzz_qtest(QTestState *s, + const unsigned char *Data, size_t Size) { + /* + * loop over the Data, breaking it up into actions. each action has an + * opcode, address offset and value + */ + typedef struct QTestFuzzAction { + uint8_t opcode; + uint8_t addr; + uint32_t value; + } QTestFuzzAction; + QTestFuzzAction a; + + while (Size >= sizeof(a)) { + /* make a copy of the action so we can normalize the values in-place */ + memcpy(&a, Data, sizeof(a)); + /* select between two i440fx Port IO addresses */ + uint16_t addr = a.addr % 2 ? I440FX_PCI_HOST_BRIDGE_CFG : + I440FX_PCI_HOST_BRIDGE_DATA; + switch (a.opcode % ACTION_MAX) { + case WRITEB: + qtest_outb(s, addr, (uint8_t)a.value); + break; + case WRITEW: + qtest_outw(s, addr, (uint16_t)a.value); + break; + case WRITEL: + qtest_outl(s, addr, (uint32_t)a.value); + break; + case READB: + qtest_inb(s, addr); + break; + case READW: + qtest_inw(s, addr); + break; + case READL: + qtest_inl(s, addr); + break; + } + /* Move to the next operation */ + Size -= sizeof(a); + Data += sizeof(a); + } + flush_events(s); +} + +static void i440fx_fuzz_qos(QTestState *s, + const unsigned char *Data, size_t Size) { + /* + * Same as i440fx_fuzz_qtest, but using QOS. devfn is incorporated into the + * value written over Port IO + */ + typedef struct QOSFuzzAction { + uint8_t opcode; + uint8_t offset; + int devfn; + uint32_t value; + } QOSFuzzAction; + + static QPCIBus *bus; + if (!bus) { + bus = qpci_new_pc(s, fuzz_qos_alloc); + } + + QOSFuzzAction a; + while (Size >= sizeof(a)) { + memcpy(&a, Data, sizeof(a)); + switch (a.opcode % ACTION_MAX) { + case WRITEB: + bus->config_writeb(bus, a.devfn, a.offset, (uint8_t)a.value); + break; + case WRITEW: + bus->config_writew(bus, a.devfn, a.offset, (uint16_t)a.value); + break; + case WRITEL: + bus->config_writel(bus, a.devfn, a.offset, (uint32_t)a.value); + break; + case READB: + bus->config_readb(bus, a.devfn, a.offset); + break; + case READW: + bus->config_readw(bus, a.devfn, a.offset); + break; + case READL: + bus->config_readl(bus, a.devfn, a.offset); + break; + } + Size -= sizeof(a); + Data += sizeof(a); + } + flush_events(s); +} + +static void i440fx_fuzz_qos_fork(QTestState *s, + const unsigned char *Data, size_t Size) { + if (fork() == 0) { + i440fx_fuzz_qos(s, Data, Size); + _Exit(0); + } else { + wait(NULL); + } +} + +static const char *i440fx_qtest_argv = TARGET_NAME " -machine accel=qtest" + "-m 0 -display none"; +static const char *i440fx_argv(FuzzTarget *t) +{ + return i440fx_qtest_argv; +} + +static void fork_init(void) +{ + counter_shm_init(); +} + +static void register_pci_fuzz_targets(void) +{ + /* Uses simple qtest commands and reboots to reset state */ + fuzz_add_target(&(FuzzTarget){ + .name = "i440fx-qtest-reboot-fuzz", + .description = "Fuzz the i440fx using raw qtest commands and" + "rebooting after each run", + .get_init_cmdline = i440fx_argv, + .fuzz = i440fx_fuzz_qtest}); + + /* Uses libqos and forks to prevent state leakage */ + fuzz_add_qos_target(&(FuzzTarget){ + .name = "i440fx-qos-fork-fuzz", + .description = "Fuzz the i440fx using raw qtest commands and" + "rebooting after each run", + .pre_vm_init = &fork_init, + .fuzz = i440fx_fuzz_qos_fork,}, + "i440FX-pcihost", + &(QOSGraphTestOptions){} + ); + + /* + * Uses libqos. Doesn't do anything to reset state. Note that if we were to + * reboot after each run, we would also have to redo the qos-related + * initialization (qos_init_path) + */ + fuzz_add_qos_target(&(FuzzTarget){ + .name = "i440fx-qos-noreset-fuzz", + .description = "Fuzz the i440fx using raw qtest commands and" + "rebooting after each run", + .fuzz = i440fx_fuzz_qos,}, + "i440FX-pcihost", + &(QOSGraphTestOptions){} + ); +} + +fuzz_target_init(register_pci_fuzz_targets); diff --git a/tests/qtest/fuzz/qos_fuzz.c b/tests/qtest/fuzz/qos_fuzz.c new file mode 100644 index 0000000..bbb1747 --- /dev/null +++ b/tests/qtest/fuzz/qos_fuzz.c @@ -0,0 +1,234 @@ +/* + * QOS-assisted fuzzing helpers + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/> + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "qemu-common.h" +#include "exec/memory.h" +#include "exec/address-spaces.h" +#include "sysemu/sysemu.h" +#include "qemu/main-loop.h" + +#include "tests/qtest/libqtest.h" +#include "tests/qtest/libqos/malloc.h" +#include "tests/qtest/libqos/qgraph.h" +#include "tests/qtest/libqos/qgraph_internal.h" +#include "tests/qtest/libqos/qos_external.h" + +#include "fuzz.h" +#include "qos_fuzz.h" + +#include "qapi/qapi-commands-machine.h" +#include "qapi/qapi-commands-qom.h" +#include "qapi/qmp/qlist.h" + + +void *fuzz_qos_obj; +QGuestAllocator *fuzz_qos_alloc; + +static const char *fuzz_target_name; +static char **fuzz_path_vec; + +/* + * Replaced the qmp commands with direct qmp_marshal calls. + * Probably there is a better way to do this + */ +static void qos_set_machines_devices_available(void) +{ + QDict *req = qdict_new(); + QObject *response; + QDict *args = qdict_new(); + QList *lst; + Error *err = NULL; + + qmp_marshal_query_machines(NULL, &response, &err); + assert(!err); + lst = qobject_to(QList, response); + apply_to_qlist(lst, true); + + qobject_unref(response); + + + qdict_put_str(req, "execute", "qom-list-types"); + qdict_put_str(args, "implements", "device"); + qdict_put_bool(args, "abstract", true); + qdict_put_obj(req, "arguments", (QObject *) args); + + qmp_marshal_qom_list_types(args, &response, &err); + assert(!err); + lst = qobject_to(QList, response); + apply_to_qlist(lst, false); + qobject_unref(response); + qobject_unref(req); +} + +static char **current_path; + +void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc) +{ + return allocate_objects(qts, current_path + 1, p_alloc); +} + +static const char *qos_build_main_args(void) +{ + char **path = fuzz_path_vec; + QOSGraphNode *test_node; + GString *cmd_line = g_string_new(path[0]); + void *test_arg; + + if (!path) { + fprintf(stderr, "QOS Path not found\n"); + abort(); + } + + /* Before test */ + current_path = path; + test_node = qos_graph_get_node(path[(g_strv_length(path) - 1)]); + test_arg = test_node->u.test.arg; + if (test_node->u.test.before) { + test_arg = test_node->u.test.before(cmd_line, test_arg); + } + /* Prepend the arguments that we need */ + g_string_prepend(cmd_line, + TARGET_NAME " -display none -machine accel=qtest -m 64 "); + return cmd_line->str; +} + +/* + * This function is largely a copy of qos-test.c:walk_path. Since walk_path + * is itself a callback, its a little annoying to add another argument/layer of + * indirection + */ +static void walk_path(QOSGraphNode *orig_path, int len) +{ + QOSGraphNode *path; + QOSGraphEdge *edge; + + /* etype set to QEDGE_CONSUMED_BY so that machine can add to the command line */ + QOSEdgeType etype = QEDGE_CONSUMED_BY; + + /* twice QOS_PATH_MAX_ELEMENT_SIZE since each edge can have its arg */ + char **path_vec = g_new0(char *, (QOS_PATH_MAX_ELEMENT_SIZE * 2)); + int path_vec_size = 0; + + char *after_cmd, *before_cmd, *after_device; + GString *after_device_str = g_string_new(""); + char *node_name = orig_path->name, *path_str; + + GString *cmd_line = g_string_new(""); + GString *cmd_line2 = g_string_new(""); + + path = qos_graph_get_node(node_name); /* root */ + node_name = qos_graph_edge_get_dest(path->path_edge); /* machine name */ + + path_vec[path_vec_size++] = node_name; + path_vec[path_vec_size++] = qos_get_machine_type(node_name); + + for (;;) { + path = qos_graph_get_node(node_name); + if (!path->path_edge) { + break; + } + + node_name = qos_graph_edge_get_dest(path->path_edge); + + /* append node command line + previous edge command line */ + if (path->command_line && etype == QEDGE_CONSUMED_BY) { + g_string_append(cmd_line, path->command_line); + g_string_append(cmd_line, after_device_str->str); + g_string_truncate(after_device_str, 0); + } + + path_vec[path_vec_size++] = qos_graph_edge_get_name(path->path_edge); + /* detect if edge has command line args */ + after_cmd = qos_graph_edge_get_after_cmd_line(path->path_edge); + after_device = qos_graph_edge_get_extra_device_opts(path->path_edge); + before_cmd = qos_graph_edge_get_before_cmd_line(path->path_edge); + edge = qos_graph_get_edge(path->name, node_name); + etype = qos_graph_edge_get_type(edge); + + if (before_cmd) { + g_string_append(cmd_line, before_cmd); + } + if (after_cmd) { + g_string_append(cmd_line2, after_cmd); + } + if (after_device) { + g_string_append(after_device_str, after_device); + } + } + + path_vec[path_vec_size++] = NULL; + g_string_append(cmd_line, after_device_str->str); + g_string_free(after_device_str, true); + + g_string_append(cmd_line, cmd_line2->str); + g_string_free(cmd_line2, true); + + /* + * here position 0 has <arch>/<machine>, position 1 has <machine>. + * The path must not have the <arch>, qtest_add_data_func adds it. + */ + path_str = g_strjoinv("/", path_vec + 1); + + /* Check that this is the test we care about: */ + char *test_name = strrchr(path_str, '/') + 1; + if (strcmp(test_name, fuzz_target_name) == 0) { + /* + * put arch/machine in position 1 so run_one_test can do its work + * and add the command line at position 0. + */ + path_vec[1] = path_vec[0]; + path_vec[0] = g_string_free(cmd_line, false); + + fuzz_path_vec = path_vec; + } else { + g_free(path_vec); + } + + g_free(path_str); +} + +static const char *qos_get_cmdline(FuzzTarget *t) +{ + /* + * Set a global variable that we use to identify the qos_path for our + * fuzz_target + */ + fuzz_target_name = t->name; + qos_set_machines_devices_available(); + qos_graph_foreach_test_path(walk_path); + return qos_build_main_args(); +} + +void fuzz_add_qos_target( + FuzzTarget *fuzz_opts, + const char *interface, + QOSGraphTestOptions *opts + ) +{ + qos_add_test(fuzz_opts->name, interface, NULL, opts); + fuzz_opts->get_init_cmdline = qos_get_cmdline; + fuzz_add_target(fuzz_opts); +} + +void qos_init_path(QTestState *s) +{ + fuzz_qos_obj = qos_allocate_objects(s , &fuzz_qos_alloc); +} diff --git a/tests/qtest/fuzz/qos_fuzz.h b/tests/qtest/fuzz/qos_fuzz.h new file mode 100644 index 0000000..477f11b --- /dev/null +++ b/tests/qtest/fuzz/qos_fuzz.h @@ -0,0 +1,33 @@ +/* + * QOS-assisted fuzzing helpers + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef _QOS_FUZZ_H_ +#define _QOS_FUZZ_H_ + +#include "tests/qtest/fuzz/fuzz.h" +#include "tests/qtest/libqos/qgraph.h" + +int qos_fuzz(const unsigned char *Data, size_t Size); +void qos_setup(void); + +extern void *fuzz_qos_obj; +extern QGuestAllocator *fuzz_qos_alloc; + +void fuzz_add_qos_target( + FuzzTarget *fuzz_opts, + const char *interface, + QOSGraphTestOptions *opts + ); + +void qos_init_path(QTestState *); + +#endif diff --git a/tests/qtest/fuzz/virtio_net_fuzz.c b/tests/qtest/fuzz/virtio_net_fuzz.c new file mode 100644 index 0000000..d08a47e --- /dev/null +++ b/tests/qtest/fuzz/virtio_net_fuzz.c @@ -0,0 +1,198 @@ +/* + * virtio-net Fuzzing Target + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" + +#include "standard-headers/linux/virtio_config.h" +#include "tests/qtest/libqtest.h" +#include "tests/qtest/libqos/virtio-net.h" +#include "fuzz.h" +#include "fork_fuzz.h" +#include "qos_fuzz.h" + + +#define QVIRTIO_NET_TIMEOUT_US (30 * 1000 * 1000) +#define QVIRTIO_RX_VQ 0 +#define QVIRTIO_TX_VQ 1 +#define QVIRTIO_CTRL_VQ 2 + +static int sockfds[2]; +static bool sockfds_initialized; + +static void virtio_net_fuzz_multi(QTestState *s, + const unsigned char *Data, size_t Size, bool check_used) +{ + typedef struct vq_action { + uint8_t queue; + uint8_t length; + uint8_t write; + uint8_t next; + uint8_t rx; + } vq_action; + + uint32_t free_head = 0; + + QGuestAllocator *t_alloc = fuzz_qos_alloc; + + QVirtioNet *net_if = fuzz_qos_obj; + QVirtioDevice *dev = net_if->vdev; + QVirtQueue *q; + vq_action vqa; + while (Size >= sizeof(vqa)) { + memcpy(&vqa, Data, sizeof(vqa)); + Data += sizeof(vqa); + Size -= sizeof(vqa); + + q = net_if->queues[vqa.queue % 3]; + + vqa.length = vqa.length >= Size ? Size : vqa.length; + + /* + * Only attempt to write incoming packets, when using the socket + * backend. Otherwise, always place the input on a virtqueue. + */ + if (vqa.rx && sockfds_initialized) { + write(sockfds[0], Data, vqa.length); + } else { + vqa.rx = 0; + uint64_t req_addr = guest_alloc(t_alloc, vqa.length); + /* + * If checking used ring, ensure that the fuzzer doesn't trigger + * trivial asserion failure on zero-zied buffer + */ + qtest_memwrite(s, req_addr, Data, vqa.length); + + + free_head = qvirtqueue_add(s, q, req_addr, vqa.length, + vqa.write, vqa.next); + qvirtqueue_add(s, q, req_addr, vqa.length, vqa.write , vqa.next); + qvirtqueue_kick(s, dev, q, free_head); + } + + /* Run the main loop */ + qtest_clock_step(s, 100); + flush_events(s); + + /* Wait on used descriptors */ + if (check_used && !vqa.rx) { + gint64 start_time = g_get_monotonic_time(); + /* + * normally, we could just use qvirtio_wait_used_elem, but since we + * must manually run the main-loop for all the bhs to run, we use + * this hack with flush_events(), to run the main_loop + */ + while (!vqa.rx && q != net_if->queues[QVIRTIO_RX_VQ]) { + uint32_t got_desc_idx; + /* Input led to a virtio_error */ + if (dev->bus->get_status(dev) & VIRTIO_CONFIG_S_NEEDS_RESET) { + break; + } + if (dev->bus->get_queue_isr_status(dev, q) && + qvirtqueue_get_buf(s, q, &got_desc_idx, NULL)) { + g_assert_cmpint(got_desc_idx, ==, free_head); + break; + } + g_assert(g_get_monotonic_time() - start_time + <= QVIRTIO_NET_TIMEOUT_US); + + /* Run the main loop */ + qtest_clock_step(s, 100); + flush_events(s); + } + } + Data += vqa.length; + Size -= vqa.length; + } +} + +static void virtio_net_fork_fuzz(QTestState *s, + const unsigned char *Data, size_t Size) +{ + if (fork() == 0) { + virtio_net_fuzz_multi(s, Data, Size, false); + flush_events(s); + _Exit(0); + } else { + wait(NULL); + } +} + +static void virtio_net_fork_fuzz_check_used(QTestState *s, + const unsigned char *Data, size_t Size) +{ + if (fork() == 0) { + virtio_net_fuzz_multi(s, Data, Size, true); + flush_events(s); + _Exit(0); + } else { + wait(NULL); + } +} + +static void virtio_net_pre_fuzz(QTestState *s) +{ + qos_init_path(s); + counter_shm_init(); +} + +static void *virtio_net_test_setup_socket(GString *cmd_line, void *arg) +{ + int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sockfds); + g_assert_cmpint(ret, !=, -1); + fcntl(sockfds[0], F_SETFL, O_NONBLOCK); + sockfds_initialized = true; + g_string_append_printf(cmd_line, " -netdev socket,fd=%d,id=hs0 ", + sockfds[1]); + return arg; +} + +static void *virtio_net_test_setup_user(GString *cmd_line, void *arg) +{ + g_string_append_printf(cmd_line, " -netdev user,id=hs0 "); + return arg; +} + +static void register_virtio_net_fuzz_targets(void) +{ + fuzz_add_qos_target(&(FuzzTarget){ + .name = "virtio-net-socket", + .description = "Fuzz the virtio-net virtual queues. Fuzz incoming " + "traffic using the socket backend", + .pre_fuzz = &virtio_net_pre_fuzz, + .fuzz = virtio_net_fork_fuzz,}, + "virtio-net", + &(QOSGraphTestOptions){.before = virtio_net_test_setup_socket} + ); + + fuzz_add_qos_target(&(FuzzTarget){ + .name = "virtio-net-socket-check-used", + .description = "Fuzz the virtio-net virtual queues. Wait for the " + "descriptors to be used. Timeout may indicate improperly handled " + "input", + .pre_fuzz = &virtio_net_pre_fuzz, + .fuzz = virtio_net_fork_fuzz_check_used,}, + "virtio-net", + &(QOSGraphTestOptions){.before = virtio_net_test_setup_socket} + ); + fuzz_add_qos_target(&(FuzzTarget){ + .name = "virtio-net-slirp", + .description = "Fuzz the virtio-net virtual queues with the slirp " + " backend. Warning: May result in network traffic emitted from the " + " process. Run in an isolated network environment.", + .pre_fuzz = &virtio_net_pre_fuzz, + .fuzz = virtio_net_fork_fuzz,}, + "virtio-net", + &(QOSGraphTestOptions){.before = virtio_net_test_setup_user} + ); +} + +fuzz_target_init(register_virtio_net_fuzz_targets); diff --git a/tests/qtest/fuzz/virtio_scsi_fuzz.c b/tests/qtest/fuzz/virtio_scsi_fuzz.c new file mode 100644 index 0000000..3b95247 --- /dev/null +++ b/tests/qtest/fuzz/virtio_scsi_fuzz.c @@ -0,0 +1,213 @@ +/* + * virtio-serial Fuzzing Target + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" + +#include "tests/qtest/libqtest.h" +#include "libqos/virtio-scsi.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" +#include "standard-headers/linux/virtio_ids.h" +#include "standard-headers/linux/virtio_pci.h" +#include "standard-headers/linux/virtio_scsi.h" +#include "fuzz.h" +#include "fork_fuzz.h" +#include "qos_fuzz.h" + +#define PCI_SLOT 0x02 +#define PCI_FN 0x00 +#define QVIRTIO_SCSI_TIMEOUT_US (1 * 1000 * 1000) + +#define MAX_NUM_QUEUES 64 + +/* Based on tests/virtio-scsi-test.c */ +typedef struct { + int num_queues; + QVirtQueue *vq[MAX_NUM_QUEUES + 2]; +} QVirtioSCSIQueues; + +static QVirtioSCSIQueues *qvirtio_scsi_init(QVirtioDevice *dev, uint64_t mask) +{ + QVirtioSCSIQueues *vs; + uint64_t feat; + int i; + + vs = g_new0(QVirtioSCSIQueues, 1); + + feat = qvirtio_get_features(dev); + if (mask) { + feat &= ~QVIRTIO_F_BAD_FEATURE | mask; + } else { + feat &= ~(QVIRTIO_F_BAD_FEATURE | (1ull << VIRTIO_RING_F_EVENT_IDX)); + } + qvirtio_set_features(dev, feat); + + vs->num_queues = qvirtio_config_readl(dev, 0); + + for (i = 0; i < vs->num_queues + 2; i++) { + vs->vq[i] = qvirtqueue_setup(dev, fuzz_qos_alloc, i); + } + + qvirtio_set_driver_ok(dev); + + return vs; +} + +static void virtio_scsi_fuzz(QTestState *s, QVirtioSCSIQueues* queues, + const unsigned char *Data, size_t Size) +{ + /* + * Data is a sequence of random bytes. We split them up into "actions", + * followed by data: + * [vqa][dddddddd][vqa][dddd][vqa][dddddddddddd] ... + * The length of the data is specified by the preceding vqa.length + */ + typedef struct vq_action { + uint8_t queue; + uint8_t length; + uint8_t write; + uint8_t next; + uint8_t kick; + } vq_action; + + /* Keep track of the free head for each queue we interact with */ + bool vq_touched[MAX_NUM_QUEUES + 2] = {0}; + uint32_t free_head[MAX_NUM_QUEUES + 2]; + + QGuestAllocator *t_alloc = fuzz_qos_alloc; + + QVirtioSCSI *scsi = fuzz_qos_obj; + QVirtioDevice *dev = scsi->vdev; + QVirtQueue *q; + vq_action vqa; + while (Size >= sizeof(vqa)) { + /* Copy the action, so we can normalize length, queue and flags */ + memcpy(&vqa, Data, sizeof(vqa)); + + Data += sizeof(vqa); + Size -= sizeof(vqa); + + vqa.queue = vqa.queue % queues->num_queues; + /* Cap length at the number of remaining bytes in data */ + vqa.length = vqa.length >= Size ? Size : vqa.length; + vqa.write = vqa.write & 1; + vqa.next = vqa.next & 1; + vqa.kick = vqa.kick & 1; + + + q = queues->vq[vqa.queue]; + + /* Copy the data into ram, and place it on the virtqueue */ + uint64_t req_addr = guest_alloc(t_alloc, vqa.length); + qtest_memwrite(s, req_addr, Data, vqa.length); + if (vq_touched[vqa.queue] == 0) { + vq_touched[vqa.queue] = 1; + free_head[vqa.queue] = qvirtqueue_add(s, q, req_addr, vqa.length, + vqa.write, vqa.next); + } else { + qvirtqueue_add(s, q, req_addr, vqa.length, vqa.write , vqa.next); + } + + if (vqa.kick) { + qvirtqueue_kick(s, dev, q, free_head[vqa.queue]); + free_head[vqa.queue] = 0; + } + Data += vqa.length; + Size -= vqa.length; + } + /* In the end, kick each queue we interacted with */ + for (int i = 0; i < MAX_NUM_QUEUES + 2; i++) { + if (vq_touched[i]) { + qvirtqueue_kick(s, dev, queues->vq[i], free_head[i]); + } + } +} + +static void virtio_scsi_fork_fuzz(QTestState *s, + const unsigned char *Data, size_t Size) +{ + QVirtioSCSI *scsi = fuzz_qos_obj; + static QVirtioSCSIQueues *queues; + if (!queues) { + queues = qvirtio_scsi_init(scsi->vdev, 0); + } + if (fork() == 0) { + virtio_scsi_fuzz(s, queues, Data, Size); + flush_events(s); + _Exit(0); + } else { + wait(NULL); + } +} + +static void virtio_scsi_with_flag_fuzz(QTestState *s, + const unsigned char *Data, size_t Size) +{ + QVirtioSCSI *scsi = fuzz_qos_obj; + static QVirtioSCSIQueues *queues; + + if (fork() == 0) { + if (Size >= sizeof(uint64_t)) { + queues = qvirtio_scsi_init(scsi->vdev, *(uint64_t *)Data); + virtio_scsi_fuzz(s, queues, + Data + sizeof(uint64_t), Size - sizeof(uint64_t)); + flush_events(s); + } + _Exit(0); + } else { + wait(NULL); + } +} + +static void virtio_scsi_pre_fuzz(QTestState *s) +{ + qos_init_path(s); + counter_shm_init(); +} + +static void *virtio_scsi_test_setup(GString *cmd_line, void *arg) +{ + g_string_append(cmd_line, + " -drive file=blkdebug::null-co://," + "file.image.read-zeroes=on," + "if=none,id=dr1,format=raw,file.align=4k " + "-device scsi-hd,drive=dr1,lun=0,scsi-id=1"); + return arg; +} + + +static void register_virtio_scsi_fuzz_targets(void) +{ + fuzz_add_qos_target(&(FuzzTarget){ + .name = "virtio-scsi-fuzz", + .description = "Fuzz the virtio-scsi virtual queues, forking" + "for each fuzz run", + .pre_vm_init = &counter_shm_init, + .pre_fuzz = &virtio_scsi_pre_fuzz, + .fuzz = virtio_scsi_fork_fuzz,}, + "virtio-scsi", + &(QOSGraphTestOptions){.before = virtio_scsi_test_setup} + ); + + fuzz_add_qos_target(&(FuzzTarget){ + .name = "virtio-scsi-flags-fuzz", + .description = "Fuzz the virtio-scsi virtual queues, forking" + "for each fuzz run (also fuzzes the virtio flags)", + .pre_vm_init = &counter_shm_init, + .pre_fuzz = &virtio_scsi_pre_fuzz, + .fuzz = virtio_scsi_with_flag_fuzz,}, + "virtio-scsi", + &(QOSGraphTestOptions){.before = virtio_scsi_test_setup} + ); +} + +fuzz_target_init(register_virtio_scsi_fuzz_targets); diff --git a/tests/qtest/libqos/i2c.c b/tests/qtest/libqos/i2c.c index 156114e..38f800d 100644 --- a/tests/qtest/libqos/i2c.c +++ b/tests/qtest/libqos/i2c.c @@ -10,12 +10,12 @@ #include "libqos/i2c.h" #include "libqtest.h" -void i2c_send(QI2CDevice *i2cdev, const uint8_t *buf, uint16_t len) +void qi2c_send(QI2CDevice *i2cdev, const uint8_t *buf, uint16_t len) { i2cdev->bus->send(i2cdev->bus, i2cdev->addr, buf, len); } -void i2c_recv(QI2CDevice *i2cdev, uint8_t *buf, uint16_t len) +void qi2c_recv(QI2CDevice *i2cdev, uint8_t *buf, uint16_t len) { i2cdev->bus->recv(i2cdev->bus, i2cdev->addr, buf, len); } @@ -23,8 +23,8 @@ void i2c_recv(QI2CDevice *i2cdev, uint8_t *buf, uint16_t len) void i2c_read_block(QI2CDevice *i2cdev, uint8_t reg, uint8_t *buf, uint16_t len) { - i2c_send(i2cdev, ®, 1); - i2c_recv(i2cdev, buf, len); + qi2c_send(i2cdev, ®, 1); + qi2c_recv(i2cdev, buf, len); } void i2c_write_block(QI2CDevice *i2cdev, uint8_t reg, @@ -33,7 +33,7 @@ void i2c_write_block(QI2CDevice *i2cdev, uint8_t reg, uint8_t *cmd = g_malloc(len + 1); cmd[0] = reg; memcpy(&cmd[1], buf, len); - i2c_send(i2cdev, cmd, len + 1); + qi2c_send(i2cdev, cmd, len + 1); g_free(cmd); } diff --git a/tests/qtest/libqos/i2c.h b/tests/qtest/libqos/i2c.h index 945b65b..c65f087 100644 --- a/tests/qtest/libqos/i2c.h +++ b/tests/qtest/libqos/i2c.h @@ -47,8 +47,8 @@ struct QI2CDevice { void *i2c_device_create(void *i2c_bus, QGuestAllocator *alloc, void *addr); void add_qi2c_address(QOSGraphEdgeOptions *opts, QI2CAddress *addr); -void i2c_send(QI2CDevice *dev, const uint8_t *buf, uint16_t len); -void i2c_recv(QI2CDevice *dev, uint8_t *buf, uint16_t len); +void qi2c_send(QI2CDevice *dev, const uint8_t *buf, uint16_t len); +void qi2c_recv(QI2CDevice *dev, uint8_t *buf, uint16_t len); void i2c_read_block(QI2CDevice *dev, uint8_t reg, uint8_t *buf, uint16_t len); diff --git a/tests/qtest/libqos/qgraph.c b/tests/qtest/libqos/qgraph.c index 7a7ae2a..ca01de0 100644 --- a/tests/qtest/libqos/qgraph.c +++ b/tests/qtest/libqos/qgraph.c @@ -474,7 +474,7 @@ QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge) if (!edge) { return -1; } - return edge->type;; + return edge->type; } char *qos_graph_edge_get_dest(QOSGraphEdge *edge) @@ -590,7 +590,7 @@ void qos_add_test(const char *name, const char *interface, QOSTestFunc test_func, QOSGraphTestOptions *opts) { QOSGraphNode *node; - char *test_name = g_strdup_printf("%s-tests/%s", interface, name);; + char *test_name = g_strdup_printf("%s-tests/%s", interface, name); QOSGraphTestOptions def_opts = { }; if (!opts) { diff --git a/tests/qtest/libqos/qos_external.c b/tests/qtest/libqos/qos_external.c new file mode 100644 index 0000000..398556d --- /dev/null +++ b/tests/qtest/libqos/qos_external.c @@ -0,0 +1,168 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/> + */ + +#include "qemu/osdep.h" +#include <getopt.h> +#include "libqtest.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qbool.h" +#include "qapi/qmp/qstring.h" +#include "qemu/module.h" +#include "qapi/qmp/qlist.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "libqos/qgraph_internal.h" +#include "libqos/qos_external.h" + + + +void apply_to_node(const char *name, bool is_machine, bool is_abstract) +{ + char *machine_name = NULL; + if (is_machine) { + const char *arch = qtest_get_arch(); + machine_name = g_strconcat(arch, "/", name, NULL); + name = machine_name; + } + qos_graph_node_set_availability(name, true); + if (is_abstract) { + qos_delete_cmd_line(name); + } + g_free(machine_name); +} + +/** + * apply_to_qlist(): using QMP queries QEMU for a list of + * machines and devices available, and sets the respective node + * as true. If a node is found, also all its produced and contained + * child are marked available. + * + * See qos_graph_node_set_availability() for more info + */ +void apply_to_qlist(QList *list, bool is_machine) +{ + const QListEntry *p; + const char *name; + bool abstract; + QDict *minfo; + QObject *qobj; + QString *qstr; + QBool *qbool; + + for (p = qlist_first(list); p; p = qlist_next(p)) { + minfo = qobject_to(QDict, qlist_entry_obj(p)); + qobj = qdict_get(minfo, "name"); + qstr = qobject_to(QString, qobj); + name = qstring_get_str(qstr); + + qobj = qdict_get(minfo, "abstract"); + if (qobj) { + qbool = qobject_to(QBool, qobj); + abstract = qbool_get_bool(qbool); + } else { + abstract = false; + } + + apply_to_node(name, is_machine, abstract); + qobj = qdict_get(minfo, "alias"); + if (qobj) { + qstr = qobject_to(QString, qobj); + name = qstring_get_str(qstr); + apply_to_node(name, is_machine, abstract); + } + } +} + +QGuestAllocator *get_machine_allocator(QOSGraphObject *obj) +{ + return obj->get_driver(obj, "memory"); +} + +/** + * allocate_objects(): given an array of nodes @arg, + * walks the path invoking all constructors and + * passing the corresponding parameter in order to + * continue the objects allocation. + * Once the test is reached, return the object it consumes. + * + * Since the machine and QEDGE_CONSUMED_BY nodes allocate + * memory in the constructor, g_test_queue_destroy is used so + * that after execution they can be safely free'd. (The test's + * ->before callback is also welcome to use g_test_queue_destroy). + * + * Note: as specified in walk_path() too, @arg is an array of + * char *, where arg[0] is a pointer to the command line + * string that will be used to properly start QEMU when executing + * the test, and the remaining elements represent the actual objects + * that will be allocated. + */ +void *allocate_objects(QTestState *qts, char **path, QGuestAllocator **p_alloc) +{ + int current = 0; + QGuestAllocator *alloc; + QOSGraphObject *parent = NULL; + QOSGraphEdge *edge; + QOSGraphNode *node; + void *edge_arg; + void *obj; + + node = qos_graph_get_node(path[current]); + g_assert(node->type == QNODE_MACHINE); + + obj = qos_machine_new(node, qts); + qos_object_queue_destroy(obj); + + alloc = get_machine_allocator(obj); + if (p_alloc) { + *p_alloc = alloc; + } + + for (;;) { + if (node->type != QNODE_INTERFACE) { + qos_object_start_hw(obj); + parent = obj; + } + + /* follow edge and get object for next node constructor */ + current++; + edge = qos_graph_get_edge(path[current - 1], path[current]); + node = qos_graph_get_node(path[current]); + + if (node->type == QNODE_TEST) { + g_assert(qos_graph_edge_get_type(edge) == QEDGE_CONSUMED_BY); + return obj; + } + + switch (qos_graph_edge_get_type(edge)) { + case QEDGE_PRODUCES: + obj = parent->get_driver(parent, path[current]); + break; + + case QEDGE_CONSUMED_BY: + edge_arg = qos_graph_edge_get_arg(edge); + obj = qos_driver_new(node, obj, alloc, edge_arg); + qos_object_queue_destroy(obj); + break; + + case QEDGE_CONTAINS: + obj = parent->get_device(parent, path[current]); + break; + } + } +} + diff --git a/tests/qtest/libqos/qos_external.h b/tests/qtest/libqos/qos_external.h new file mode 100644 index 0000000..7b44930 --- /dev/null +++ b/tests/qtest/libqos/qos_external.h @@ -0,0 +1,28 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/> + */ + +#ifndef QOS_EXTERNAL_H +#define QOS_EXTERNAL_H +#include "libqos/qgraph.h" + +void apply_to_node(const char *name, bool is_machine, bool is_abstract); +void apply_to_qlist(QList *list, bool is_machine); +QGuestAllocator *get_machine_allocator(QOSGraphObject *obj); +void *allocate_objects(QTestState *qts, char **path, QGuestAllocator **p_alloc); + +#endif diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c index 76c9f8e..49075b5 100644 --- a/tests/qtest/libqtest.c +++ b/tests/qtest/libqtest.c @@ -35,6 +35,23 @@ #define SOCKET_TIMEOUT 50 #define SOCKET_MAX_FDS 16 + +typedef void (*QTestSendFn)(QTestState *s, const char *buf); +typedef void (*ExternalSendFn)(void *s, const char *buf); +typedef GString* (*QTestRecvFn)(QTestState *); + +typedef struct QTestClientTransportOps { + QTestSendFn send; /* for sending qtest commands */ + + /* + * use external_send to send qtest command strings through functions which + * do not accept a QTestState as the first parameter. + */ + ExternalSendFn external_send; + + QTestRecvFn recv_line; /* for receiving qtest command responses */ +} QTestTransportOps; + struct QTestState { int fd; @@ -45,6 +62,7 @@ struct QTestState bool big_endian; bool irq_level[MAX_IRQ]; GString *rx; + QTestTransportOps ops; }; static GHookList abrt_hooks; @@ -52,6 +70,14 @@ static struct sigaction sigact_old; static int qtest_query_target_endianness(QTestState *s); +static void qtest_client_socket_send(QTestState*, const char *buf); +static void socket_send(int fd, const char *buf, size_t size); + +static GString *qtest_client_socket_recv_line(QTestState *); + +static void qtest_client_set_tx_handler(QTestState *s, QTestSendFn send); +static void qtest_client_set_rx_handler(QTestState *s, QTestRecvFn recv); + static int init_socket(const char *socket_path) { struct sockaddr_un addr; @@ -234,6 +260,9 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args) sock = init_socket(socket_path); qmpsock = init_socket(qmp_socket_path); + qtest_client_set_rx_handler(s, qtest_client_socket_recv_line); + qtest_client_set_tx_handler(s, qtest_client_socket_send); + qtest_add_abrt_handler(kill_qemu_hook_func, s); command = g_strdup_printf("exec %s " @@ -379,13 +408,9 @@ static void socket_send(int fd, const char *buf, size_t size) } } -static void socket_sendf(int fd, const char *fmt, va_list ap) +static void qtest_client_socket_send(QTestState *s, const char *buf) { - gchar *str = g_strdup_vprintf(fmt, ap); - size_t size = strlen(str); - - socket_send(fd, str, size); - g_free(str); + socket_send(s->fd, buf, strlen(buf)); } static void GCC_FMT_ATTR(2, 3) qtest_sendf(QTestState *s, const char *fmt, ...) @@ -393,8 +418,11 @@ static void GCC_FMT_ATTR(2, 3) qtest_sendf(QTestState *s, const char *fmt, ...) va_list ap; va_start(ap, fmt); - socket_sendf(s->fd, fmt, ap); + gchar *str = g_strdup_vprintf(fmt, ap); va_end(ap); + + s->ops.send(s, str); + g_free(str); } /* Sends a message and file descriptors to the socket. @@ -431,7 +459,7 @@ static void socket_send_fds(int socket_fd, int *fds, size_t fds_num, g_assert_cmpint(ret, >, 0); } -static GString *qtest_recv_line(QTestState *s) +static GString *qtest_client_socket_recv_line(QTestState *s) { GString *line; size_t offset; @@ -468,7 +496,7 @@ static gchar **qtest_rsp(QTestState *s, int expected_args) int i; redo: - line = qtest_recv_line(s); + line = s->ops.recv_line(s); words = g_strsplit(line->str, " ", 0); g_string_free(line, TRUE); @@ -1058,8 +1086,8 @@ void qtest_bufwrite(QTestState *s, uint64_t addr, const void *data, size_t size) bdata = g_base64_encode(data, size); qtest_sendf(s, "b64write 0x%" PRIx64 " 0x%zx ", addr, size); - socket_send(s->fd, bdata, strlen(bdata)); - socket_send(s->fd, "\n", 1); + s->ops.send(s, bdata); + s->ops.send(s, "\n"); qtest_rsp(s, 0); g_free(bdata); } @@ -1337,3 +1365,72 @@ void qmp_assert_error_class(QDict *rsp, const char *class) qobject_unref(rsp); } + +static void qtest_client_set_tx_handler(QTestState *s, + QTestSendFn send) +{ + s->ops.send = send; +} +static void qtest_client_set_rx_handler(QTestState *s, QTestRecvFn recv) +{ + s->ops.recv_line = recv; +} +/* A type-safe wrapper for s->send() */ +static void send_wrapper(QTestState *s, const char *buf) +{ + s->ops.external_send(s, buf); +} + +static GString *qtest_client_inproc_recv_line(QTestState *s) +{ + GString *line; + size_t offset; + char *eol; + + eol = strchr(s->rx->str, '\n'); + offset = eol - s->rx->str; + line = g_string_new_len(s->rx->str, offset); + g_string_erase(s->rx, 0, offset + 1); + return line; +} + +QTestState *qtest_inproc_init(QTestState **s, bool log, const char* arch, + void (*send)(void*, const char*)) +{ + QTestState *qts; + qts = g_new0(QTestState, 1); + *s = qts; /* Expose qts early on, since the query endianness relies on it */ + qts->wstatus = 0; + for (int i = 0; i < MAX_IRQ; i++) { + qts->irq_level[i] = false; + } + + qtest_client_set_rx_handler(qts, qtest_client_inproc_recv_line); + + /* send() may not have a matching protoype, so use a type-safe wrapper */ + qts->ops.external_send = send; + qtest_client_set_tx_handler(qts, send_wrapper); + + qts->big_endian = qtest_query_target_endianness(qts); + + /* + * Set a dummy path for QTEST_QEMU_BINARY. Doesn't need to exist, but this + * way, qtest_get_arch works for inproc qtest. + */ + gchar *bin_path = g_strconcat("/qemu-system-", arch, NULL); + setenv("QTEST_QEMU_BINARY", bin_path, 0); + g_free(bin_path); + + return qts; +} + +void qtest_client_inproc_recv(void *opaque, const char *str) +{ + QTestState *qts = *(QTestState **)opaque; + + if (!qts->rx) { + qts->rx = g_string_new(NULL); + } + g_string_append(qts->rx, str); + return; +} diff --git a/tests/qtest/libqtest.h b/tests/qtest/libqtest.h index c9e21e0..f5cf93c 100644 --- a/tests/qtest/libqtest.h +++ b/tests/qtest/libqtest.h @@ -729,4 +729,8 @@ bool qtest_probe_child(QTestState *s); */ void qtest_set_expected_status(QTestState *s, int status); +QTestState *qtest_inproc_init(QTestState **s, bool log, const char* arch, + void (*send)(void*, const char*)); + +void qtest_client_inproc_recv(void *opaque, const char *str); #endif diff --git a/tests/qtest/numa-test.c b/tests/qtest/numa-test.c index 17dd807..2f9b7f6 100644 --- a/tests/qtest/numa-test.c +++ b/tests/qtest/numa-test.c @@ -14,67 +14,60 @@ #include "qapi/qmp/qdict.h" #include "qapi/qmp/qlist.h" -static char *make_cli(const char *generic_cli, const char *test_cli) +static char *make_cli(const GString *generic_cli, const char *test_cli) { - return g_strdup_printf("%s %s", generic_cli ? generic_cli : "", test_cli); + return g_strdup_printf("%s %s", generic_cli->str, test_cli); } static void test_mon_explicit(const void *data) { - char *s; - char *cli; QTestState *qts; + g_autofree char *s = NULL; + g_autofree char *cli = NULL; - cli = make_cli(data, "-smp 8 " - "-numa node,nodeid=0,cpus=0-3 " - "-numa node,nodeid=1,cpus=4-7 "); + cli = make_cli(data, "-smp 8 -numa node,nodeid=0,memdev=ram,cpus=0-3 " + "-numa node,nodeid=1,cpus=4-7"); qts = qtest_init(cli); s = qtest_hmp(qts, "info numa"); g_assert(strstr(s, "node 0 cpus: 0 1 2 3")); g_assert(strstr(s, "node 1 cpus: 4 5 6 7")); - g_free(s); qtest_quit(qts); - g_free(cli); } -static void test_mon_default(const void *data) +static void test_def_cpu_split(const void *data) { - char *s; - char *cli; QTestState *qts; + g_autofree char *s = NULL; + g_autofree char *cli = NULL; - cli = make_cli(data, "-smp 8 -numa node -numa node"); + cli = make_cli(data, "-smp 8 -numa node,memdev=ram -numa node"); qts = qtest_init(cli); s = qtest_hmp(qts, "info numa"); g_assert(strstr(s, "node 0 cpus: 0 2 4 6")); g_assert(strstr(s, "node 1 cpus: 1 3 5 7")); - g_free(s); qtest_quit(qts); - g_free(cli); } static void test_mon_partial(const void *data) { - char *s; - char *cli; QTestState *qts; + g_autofree char *s = NULL; + g_autofree char *cli = NULL; cli = make_cli(data, "-smp 8 " - "-numa node,nodeid=0,cpus=0-1 " + "-numa node,nodeid=0,memdev=ram,cpus=0-1 " "-numa node,nodeid=1,cpus=4-5 "); qts = qtest_init(cli); s = qtest_hmp(qts, "info numa"); g_assert(strstr(s, "node 0 cpus: 0 1 2 3 6 7")); g_assert(strstr(s, "node 1 cpus: 4 5")); - g_free(s); qtest_quit(qts); - g_free(cli); } static QList *get_cpus(QTestState *qts, QDict **resp) @@ -87,13 +80,14 @@ static QList *get_cpus(QTestState *qts, QDict **resp) static void test_query_cpus(const void *data) { - char *cli; QDict *resp; QList *cpus; QObject *e; QTestState *qts; + g_autofree char *cli = NULL; - cli = make_cli(data, "-smp 8 -numa node,cpus=0-3 -numa node,cpus=4-7"); + cli = make_cli(data, "-smp 8 -numa node,memdev=ram,cpus=0-3 " + "-numa node,cpus=4-7"); qts = qtest_init(cli); cpus = get_cpus(qts, &resp); g_assert(cpus); @@ -120,19 +114,18 @@ static void test_query_cpus(const void *data) qobject_unref(resp); qtest_quit(qts); - g_free(cli); } static void pc_numa_cpu(const void *data) { - char *cli; QDict *resp; QList *cpus; QObject *e; QTestState *qts; + g_autofree char *cli = NULL; cli = make_cli(data, "-cpu pentium -smp 8,sockets=2,cores=2,threads=2 " - "-numa node,nodeid=0 -numa node,nodeid=1 " + "-numa node,nodeid=0,memdev=ram -numa node,nodeid=1 " "-numa cpu,node-id=1,socket-id=0 " "-numa cpu,node-id=0,socket-id=1,core-id=0 " "-numa cpu,node-id=0,socket-id=1,core-id=1,thread-id=0 " @@ -174,19 +167,18 @@ static void pc_numa_cpu(const void *data) qobject_unref(resp); qtest_quit(qts); - g_free(cli); } static void spapr_numa_cpu(const void *data) { - char *cli; QDict *resp; QList *cpus; QObject *e; QTestState *qts; + g_autofree char *cli = NULL; cli = make_cli(data, "-smp 4,cores=4 " - "-numa node,nodeid=0 -numa node,nodeid=1 " + "-numa node,nodeid=0,memdev=ram -numa node,nodeid=1 " "-numa cpu,node-id=0,core-id=0 " "-numa cpu,node-id=0,core-id=1 " "-numa cpu,node-id=0,core-id=2 " @@ -220,19 +212,18 @@ static void spapr_numa_cpu(const void *data) qobject_unref(resp); qtest_quit(qts); - g_free(cli); } static void aarch64_numa_cpu(const void *data) { - char *cli; QDict *resp; QList *cpus; QObject *e; QTestState *qts; + g_autofree char *cli = NULL; cli = make_cli(data, "-smp 2 " - "-numa node,nodeid=0 -numa node,nodeid=1 " + "-numa node,nodeid=0,memdev=ram -numa node,nodeid=1 " "-numa cpu,node-id=1,thread-id=0 " "-numa cpu,node-id=0,thread-id=1"); qts = qtest_init(cli); @@ -264,7 +255,6 @@ static void aarch64_numa_cpu(const void *data) qobject_unref(resp); qtest_quit(qts); - g_free(cli); } static void pc_dynamic_cpu_cfg(const void *data) @@ -273,13 +263,14 @@ static void pc_dynamic_cpu_cfg(const void *data) QDict *resp; QList *cpus; QTestState *qs; + g_autofree char *cli = NULL; - qs = qtest_initf("%s -nodefaults --preconfig -smp 2", - data ? (char *)data : ""); + cli = make_cli(data, "-nodefaults --preconfig -smp 2"); + qs = qtest_init(cli); /* create 2 numa nodes */ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node'," - " 'arguments': { 'type': 'node', 'nodeid': 0 } }"))); + " 'arguments': { 'type': 'node', 'nodeid': 0, 'memdev': 'ram' } }"))); g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node'," " 'arguments': { 'type': 'node', 'nodeid': 1 } }"))); @@ -329,16 +320,19 @@ static void pc_dynamic_cpu_cfg(const void *data) static void pc_hmat_build_cfg(const void *data) { - QTestState *qs = qtest_initf("%s -nodefaults --preconfig -machine hmat=on " - "-smp 2,sockets=2 " - "-m 128M,slots=2,maxmem=1G " - "-object memory-backend-ram,size=64M,id=m0 " - "-object memory-backend-ram,size=64M,id=m1 " - "-numa node,nodeid=0,memdev=m0 " - "-numa node,nodeid=1,memdev=m1,initiator=0 " - "-numa cpu,node-id=0,socket-id=0 " - "-numa cpu,node-id=0,socket-id=1", - data ? (char *)data : ""); + QTestState *qs; + g_autofree char *cli = NULL; + + cli = make_cli(data, "-nodefaults --preconfig -machine hmat=on " + "-smp 2,sockets=2 " + "-m 128M,slots=2,maxmem=1G " + "-object memory-backend-ram,size=64M,id=m0 " + "-object memory-backend-ram,size=64M,id=m1 " + "-numa node,nodeid=0,memdev=m0 " + "-numa node,nodeid=1,memdev=m1,initiator=0 " + "-numa cpu,node-id=0,socket-id=0 " + "-numa cpu,node-id=0,socket-id=1"); + qs = qtest_init(cli); /* Fail: Initiator should be less than the number of nodes */ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node'," @@ -455,13 +449,16 @@ static void pc_hmat_build_cfg(const void *data) static void pc_hmat_off_cfg(const void *data) { - QTestState *qs = qtest_initf("%s -nodefaults --preconfig " - "-smp 2,sockets=2 " - "-m 128M,slots=2,maxmem=1G " - "-object memory-backend-ram,size=64M,id=m0 " - "-object memory-backend-ram,size=64M,id=m1 " - "-numa node,nodeid=0,memdev=m0", - data ? (char *)data : ""); + QTestState *qs; + g_autofree char *cli = NULL; + + cli = make_cli(data, "-nodefaults --preconfig " + "-smp 2,sockets=2 " + "-m 128M,slots=2,maxmem=1G " + "-object memory-backend-ram,size=64M,id=m0 " + "-object memory-backend-ram,size=64M,id=m1 " + "-numa node,nodeid=0,memdev=m0"); + qs = qtest_init(cli); /* * Fail: Enable HMAT with -machine hmat=on @@ -491,16 +488,19 @@ static void pc_hmat_off_cfg(const void *data) static void pc_hmat_erange_cfg(const void *data) { - QTestState *qs = qtest_initf("%s -nodefaults --preconfig -machine hmat=on " - "-smp 2,sockets=2 " - "-m 128M,slots=2,maxmem=1G " - "-object memory-backend-ram,size=64M,id=m0 " - "-object memory-backend-ram,size=64M,id=m1 " - "-numa node,nodeid=0,memdev=m0 " - "-numa node,nodeid=1,memdev=m1,initiator=0 " - "-numa cpu,node-id=0,socket-id=0 " - "-numa cpu,node-id=0,socket-id=1", - data ? (char *)data : ""); + QTestState *qs; + g_autofree char *cli = NULL; + + cli = make_cli(data, "-nodefaults --preconfig -machine hmat=on " + "-smp 2,sockets=2 " + "-m 128M,slots=2,maxmem=1G " + "-object memory-backend-ram,size=64M,id=m0 " + "-object memory-backend-ram,size=64M,id=m1 " + "-numa node,nodeid=0,memdev=m0 " + "-numa node,nodeid=1,memdev=m1,initiator=0 " + "-numa cpu,node-id=0,socket-id=0 " + "-numa cpu,node-id=0,socket-id=1"); + qs = qtest_init(cli); /* Can't store the compressed latency */ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node'," @@ -539,16 +539,22 @@ static void pc_hmat_erange_cfg(const void *data) int main(int argc, char **argv) { - const char *args = NULL; + g_autoptr(GString) args = g_string_new(NULL); const char *arch = qtest_get_arch(); - if (strcmp(arch, "aarch64") == 0) { - args = "-machine virt"; + if (g_str_equal(arch, "ppc64")) { + g_string_append(args, " -object memory-backend-ram,id=ram,size=512M"); + } else { + g_string_append(args, " -object memory-backend-ram,id=ram,size=128M"); + } + + if (g_str_equal(arch, "aarch64")) { + g_string_append(args, " -machine virt"); } g_test_init(&argc, &argv, NULL); - qtest_add_data_func("/numa/mon/default", args, test_mon_default); + qtest_add_data_func("/numa/mon/cpus/default", args, test_def_cpu_split); qtest_add_data_func("/numa/mon/cpus/explicit", args, test_mon_explicit); qtest_add_data_func("/numa/mon/cpus/partial", args, test_mon_partial); qtest_add_data_func("/numa/qmp/cpus/query-cpus", args, test_query_cpus); diff --git a/tests/qtest/pca9552-test.c b/tests/qtest/pca9552-test.c index 4b800d3..d80ed93 100644 --- a/tests/qtest/pca9552-test.c +++ b/tests/qtest/pca9552-test.c @@ -32,22 +32,22 @@ static void receive_autoinc(void *obj, void *data, QGuestAllocator *alloc) pca9552_init(i2cdev); - i2c_send(i2cdev, ®, 1); + qi2c_send(i2cdev, ®, 1); /* PCA9552_LS0 */ - i2c_recv(i2cdev, &resp, 1); + qi2c_recv(i2cdev, &resp, 1); g_assert_cmphex(resp, ==, 0x54); /* PCA9552_LS1 */ - i2c_recv(i2cdev, &resp, 1); + qi2c_recv(i2cdev, &resp, 1); g_assert_cmphex(resp, ==, 0x55); /* PCA9552_LS2 */ - i2c_recv(i2cdev, &resp, 1); + qi2c_recv(i2cdev, &resp, 1); g_assert_cmphex(resp, ==, 0x55); /* PCA9552_LS3 */ - i2c_recv(i2cdev, &resp, 1); + qi2c_recv(i2cdev, &resp, 1); g_assert_cmphex(resp, ==, 0x54); } diff --git a/tests/qtest/qos-test.c b/tests/qtest/qos-test.c index fd70d73..ad193f4 100644 --- a/tests/qtest/qos-test.c +++ b/tests/qtest/qos-test.c @@ -27,65 +27,11 @@ #include "libqos/malloc.h" #include "libqos/qgraph.h" #include "libqos/qgraph_internal.h" +#include "libqos/qos_external.h" static char *old_path; -static void apply_to_node(const char *name, bool is_machine, bool is_abstract) -{ - char *machine_name = NULL; - if (is_machine) { - const char *arch = qtest_get_arch(); - machine_name = g_strconcat(arch, "/", name, NULL); - name = machine_name; - } - qos_graph_node_set_availability(name, true); - if (is_abstract) { - qos_delete_cmd_line(name); - } - g_free(machine_name); -} -/** - * apply_to_qlist(): using QMP queries QEMU for a list of - * machines and devices available, and sets the respective node - * as true. If a node is found, also all its produced and contained - * child are marked available. - * - * See qos_graph_node_set_availability() for more info - */ -static void apply_to_qlist(QList *list, bool is_machine) -{ - const QListEntry *p; - const char *name; - bool abstract; - QDict *minfo; - QObject *qobj; - QString *qstr; - QBool *qbool; - - for (p = qlist_first(list); p; p = qlist_next(p)) { - minfo = qobject_to(QDict, qlist_entry_obj(p)); - qobj = qdict_get(minfo, "name"); - qstr = qobject_to(QString, qobj); - name = qstring_get_str(qstr); - - qobj = qdict_get(minfo, "abstract"); - if (qobj) { - qbool = qobject_to(QBool, qobj); - abstract = qbool_get_bool(qbool); - } else { - abstract = false; - } - - apply_to_node(name, is_machine, abstract); - qobj = qdict_get(minfo, "alias"); - if (qobj) { - qstr = qobject_to(QString, qobj); - name = qstring_get_str(qstr); - apply_to_node(name, is_machine, abstract); - } - } -} /** * qos_set_machines_devices_available(): sets availability of qgraph @@ -129,10 +75,6 @@ static void qos_set_machines_devices_available(void) qobject_unref(response); } -static QGuestAllocator *get_machine_allocator(QOSGraphObject *obj) -{ - return obj->get_driver(obj, "memory"); -} static void restart_qemu_or_continue(char *path) { @@ -159,78 +101,6 @@ void qos_invalidate_command_line(void) old_path = NULL; } -/** - * allocate_objects(): given an array of nodes @arg, - * walks the path invoking all constructors and - * passing the corresponding parameter in order to - * continue the objects allocation. - * Once the test is reached, return the object it consumes. - * - * Since the machine and QEDGE_CONSUMED_BY nodes allocate - * memory in the constructor, g_test_queue_destroy is used so - * that after execution they can be safely free'd. (The test's - * ->before callback is also welcome to use g_test_queue_destroy). - * - * Note: as specified in walk_path() too, @arg is an array of - * char *, where arg[0] is a pointer to the command line - * string that will be used to properly start QEMU when executing - * the test, and the remaining elements represent the actual objects - * that will be allocated. - */ -static void *allocate_objects(QTestState *qts, char **path, QGuestAllocator **p_alloc) -{ - int current = 0; - QGuestAllocator *alloc; - QOSGraphObject *parent = NULL; - QOSGraphEdge *edge; - QOSGraphNode *node; - void *edge_arg; - void *obj; - - node = qos_graph_get_node(path[current]); - g_assert(node->type == QNODE_MACHINE); - - obj = qos_machine_new(node, qts); - qos_object_queue_destroy(obj); - - alloc = get_machine_allocator(obj); - if (p_alloc) { - *p_alloc = alloc; - } - - for (;;) { - if (node->type != QNODE_INTERFACE) { - qos_object_start_hw(obj); - parent = obj; - } - - /* follow edge and get object for next node constructor */ - current++; - edge = qos_graph_get_edge(path[current - 1], path[current]); - node = qos_graph_get_node(path[current]); - - if (node->type == QNODE_TEST) { - g_assert(qos_graph_edge_get_type(edge) == QEDGE_CONSUMED_BY); - return obj; - } - - switch (qos_graph_edge_get_type(edge)) { - case QEDGE_PRODUCES: - obj = parent->get_driver(parent, path[current]); - break; - - case QEDGE_CONSUMED_BY: - edge_arg = qos_graph_edge_get_arg(edge); - obj = qos_driver_new(node, obj, alloc, edge_arg); - qos_object_queue_destroy(obj); - break; - - case QEDGE_CONTAINS: - obj = parent->get_device(parent, path[current]); - break; - } - } -} /* The argument to run_one_test, which is the test function that is registered * with GTest, is a vector of strings. The first item is the initial command diff --git a/tests/test-aio.c b/tests/test-aio.c index 86fb73b..8a46078 100644 --- a/tests/test-aio.c +++ b/tests/test-aio.c @@ -615,7 +615,8 @@ static void test_source_bh_delete_from_cb(void) g_assert_cmpint(data1.n, ==, data1.max); g_assert(data1.bh == NULL); - g_assert(!g_main_context_iteration(NULL, false)); + assert(g_main_context_iteration(NULL, false)); + assert(!g_main_context_iteration(NULL, false)); } static void test_source_bh_delete_from_cb_many(void) diff --git a/tests/test-rcu-list.c b/tests/test-rcu-list.c index 6f07647..1442c0c 100644 --- a/tests/test-rcu-list.c +++ b/tests/test-rcu-list.c @@ -93,6 +93,8 @@ struct list_element { QSIMPLEQ_ENTRY(list_element) entry; #elif TEST_LIST_TYPE == 3 QTAILQ_ENTRY(list_element) entry; +#elif TEST_LIST_TYPE == 4 + QSLIST_ENTRY(list_element) entry; #else #error Invalid TEST_LIST_TYPE #endif @@ -144,6 +146,20 @@ static QTAILQ_HEAD(, list_element) Q_list_head; #define TEST_LIST_INSERT_HEAD_RCU QTAILQ_INSERT_HEAD_RCU #define TEST_LIST_FOREACH_RCU QTAILQ_FOREACH_RCU #define TEST_LIST_FOREACH_SAFE_RCU QTAILQ_FOREACH_SAFE_RCU + +#elif TEST_LIST_TYPE == 4 +static QSLIST_HEAD(, list_element) Q_list_head; + +#define TEST_NAME "qslist" +#define TEST_LIST_REMOVE_RCU(el, f) \ + QSLIST_REMOVE_RCU(&Q_list_head, el, list_element, f) + +#define TEST_LIST_INSERT_AFTER_RCU(list_el, el, f) \ + QSLIST_INSERT_AFTER_RCU(&Q_list_head, list_el, el, f) + +#define TEST_LIST_INSERT_HEAD_RCU QSLIST_INSERT_HEAD_RCU +#define TEST_LIST_FOREACH_RCU QSLIST_FOREACH_RCU +#define TEST_LIST_FOREACH_SAFE_RCU QSLIST_FOREACH_SAFE_RCU #else #error Invalid TEST_LIST_TYPE #endif diff --git a/tests/test-rcu-slist.c b/tests/test-rcu-slist.c new file mode 100644 index 0000000..868e1e4 --- /dev/null +++ b/tests/test-rcu-slist.c @@ -0,0 +1,2 @@ +#define TEST_LIST_TYPE 4 +#include "test-rcu-list.c" |