qemu-iotests: do not buffer the test output
Instead of buffering the test output into a StringIO, patch it on the fly by wrapping sys.stdout's write method. This can be done unconditionally, even if using -d, which makes execute_unittest a bit simpler. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Tested-by: Emanuele Giuseppe Esposito <eesposit@redhat.com> Message-Id: <20210323181928.311862-2-pbonzini@redhat.com> Message-Id: <20210503110110.476887-2-pbonzini@redhat.com> Signed-off-by: Max Reitz <mreitz@redhat.com>
This commit is contained in:
		@@ -15,7 +15,7 @@
 | 
				
			|||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "blockdev-del", "arguments": {"node-name": "hd0"}}
 | 
					{"execute": "blockdev-del", "arguments": {"node-name": "hd0"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
==Attach two SCSI disks using the same block device and the same iothread==
 | 
					.==Attach two SCSI disks using the same block device and the same iothread==
 | 
				
			||||||
{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "hd0", "read-only": true, "read-zeroes": true}}
 | 
					{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "hd0", "read-only": true, "read-zeroes": true}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "object-add", "arguments": {"id": "iothread0", "qom-type": "iothread"}}
 | 
					{"execute": "object-add", "arguments": {"id": "iothread0", "qom-type": "iothread"}}
 | 
				
			||||||
@@ -32,7 +32,7 @@
 | 
				
			|||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "blockdev-del", "arguments": {"node-name": "hd0"}}
 | 
					{"execute": "blockdev-del", "arguments": {"node-name": "hd0"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
==Attach two SCSI disks using the same block device but different iothreads==
 | 
					.==Attach two SCSI disks using the same block device but different iothreads==
 | 
				
			||||||
{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "hd0", "read-only": true, "read-zeroes": true}}
 | 
					{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "hd0", "read-only": true, "read-zeroes": true}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "object-add", "arguments": {"id": "iothread0", "qom-type": "iothread"}}
 | 
					{"execute": "object-add", "arguments": {"id": "iothread0", "qom-type": "iothread"}}
 | 
				
			||||||
@@ -55,7 +55,7 @@
 | 
				
			|||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "blockdev-del", "arguments": {"node-name": "hd0"}}
 | 
					{"execute": "blockdev-del", "arguments": {"node-name": "hd0"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
==Attach a SCSI disks using the same block device as a NBD server==
 | 
					.==Attach a SCSI disks using the same block device as a NBD server==
 | 
				
			||||||
{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "hd0", "read-only": true, "read-zeroes": true}}
 | 
					{"execute": "blockdev-add", "arguments": {"driver": "null-co", "node-name": "hd0", "read-only": true, "read-zeroes": true}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "nbd-server-start", "arguments": {"addr": {"data": {"path": "SOCK_DIR/PID-nbd.sock"}, "type": "unix"}}}
 | 
					{"execute": "nbd-server-start", "arguments": {"addr": {"data": {"path": "SOCK_DIR/PID-nbd.sock"}, "type": "unix"}}}
 | 
				
			||||||
@@ -68,7 +68,7 @@
 | 
				
			|||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "device_add", "arguments": {"drive": "hd0", "driver": "scsi-hd", "id": "scsi-hd0"}}
 | 
					{"execute": "device_add", "arguments": {"drive": "hd0", "driver": "scsi-hd", "id": "scsi-hd0"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
....
 | 
					.
 | 
				
			||||||
----------------------------------------------------------------------
 | 
					----------------------------------------------------------------------
 | 
				
			||||||
Ran 4 tests
 | 
					Ran 4 tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,16 @@
 | 
				
			|||||||
{"execute": "job-finalize", "arguments": {"id": "commit0"}}
 | 
					..{"execute": "job-finalize", "arguments": {"id": "commit0"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"data": {"id": "commit0", "type": "commit"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
					{"data": {"id": "commit0", "type": "commit"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
				
			||||||
{"data": {"device": "commit0", "len": 3145728, "offset": 3145728, "speed": 0, "type": "commit"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
					{"data": {"device": "commit0", "len": 3145728, "offset": 3145728, "speed": 0, "type": "commit"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
				
			||||||
{"execute": "job-finalize", "arguments": {"id": "stream0"}}
 | 
					...{"execute": "job-finalize", "arguments": {"id": "stream0"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"data": {"id": "stream0", "type": "stream"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
					{"data": {"id": "stream0", "type": "stream"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
				
			||||||
{"data": {"device": "stream0", "len": 3145728, "offset": 3145728, "speed": 0, "type": "stream"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
					{"data": {"device": "stream0", "len": 3145728, "offset": 3145728, "speed": 0, "type": "stream"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
				
			||||||
{"execute": "job-finalize", "arguments": {"id": "stream0"}}
 | 
					.{"execute": "job-finalize", "arguments": {"id": "stream0"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"data": {"id": "stream0", "type": "stream"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
					{"data": {"id": "stream0", "type": "stream"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
				
			||||||
{"data": {"device": "stream0", "len": 3145728, "offset": 3145728, "speed": 0, "type": "stream"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
					{"data": {"device": "stream0", "len": 3145728, "offset": 3145728, "speed": 0, "type": "stream"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
 | 
				
			||||||
.....................
 | 
					...............
 | 
				
			||||||
----------------------------------------------------------------------
 | 
					----------------------------------------------------------------------
 | 
				
			||||||
Ran 21 tests
 | 
					Ran 21 tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
 | 
					{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
 | 
					.{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
 | 
					{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
@@ -13,7 +13,7 @@ Job failed: Invalid password, cannot unlock any keyslot
 | 
				
			|||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
 | 
					{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
 | 
					.{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
 | 
					{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
@@ -33,7 +33,7 @@ Job failed: All the active keyslots match the (old) password that was given and
 | 
				
			|||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
 | 
					{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
...
 | 
					.
 | 
				
			||||||
----------------------------------------------------------------------
 | 
					----------------------------------------------------------------------
 | 
				
			||||||
Ran 3 tests
 | 
					Ran 3 tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ Job failed: Failed to get shared "consistent read" lock
 | 
				
			|||||||
qemu-img: Failed to get shared "consistent read" lock
 | 
					qemu-img: Failed to get shared "consistent read" lock
 | 
				
			||||||
Is another process using the image [TEST_DIR/test.img]?
 | 
					Is another process using the image [TEST_DIR/test.img]?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
 | 
					.Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Job failed: Block node is read-only
 | 
					Job failed: Block node is read-only
 | 
				
			||||||
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
 | 
					{"execute": "job-dismiss", "arguments": {"id": "job0"}}
 | 
				
			||||||
@@ -26,15 +26,15 @@ Job failed: Failed to get shared "consistent read" lock
 | 
				
			|||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
 | 
					{"execute": "job-dismiss", "arguments": {"id": "job0"}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
 | 
					.Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"error": {"class": "GenericError", "desc": "Failed to get \"write\" lock"}}
 | 
					{"error": {"class": "GenericError", "desc": "Failed to get \"write\" lock"}}
 | 
				
			||||||
Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
 | 
					.Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
{"return": {}}
 | 
					{"return": {}}
 | 
				
			||||||
....
 | 
					.
 | 
				
			||||||
----------------------------------------------------------------------
 | 
					----------------------------------------------------------------------
 | 
				
			||||||
Ran 4 tests
 | 
					Ran 4 tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,6 @@ import atexit
 | 
				
			|||||||
import bz2
 | 
					import bz2
 | 
				
			||||||
from collections import OrderedDict
 | 
					from collections import OrderedDict
 | 
				
			||||||
import faulthandler
 | 
					import faulthandler
 | 
				
			||||||
import io
 | 
					 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
@@ -32,7 +31,7 @@ import subprocess
 | 
				
			|||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
from typing import (Any, Callable, Dict, Iterable,
 | 
					from typing import (Any, Callable, Dict, Iterable,
 | 
				
			||||||
                    List, Optional, Sequence, Tuple, TypeVar)
 | 
					                    List, Optional, Sequence, TextIO, Tuple, Type, TypeVar)
 | 
				
			||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from contextlib import contextmanager
 | 
					from contextlib import contextmanager
 | 
				
			||||||
@@ -1271,37 +1270,50 @@ def skip_if_user_is_root(func):
 | 
				
			|||||||
            return func(*args, **kwargs)
 | 
					            return func(*args, **kwargs)
 | 
				
			||||||
    return func_wrapper
 | 
					    return func_wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# We need to filter out the time taken from the output so that
 | 
				
			||||||
 | 
					# qemu-iotest can reliably diff the results against master output,
 | 
				
			||||||
 | 
					# and hide skipped tests from the reference output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReproducibleTestResult(unittest.TextTestResult):
 | 
				
			||||||
 | 
					    def addSkip(self, test, reason):
 | 
				
			||||||
 | 
					        # Same as TextTestResult, but print dot instead of "s"
 | 
				
			||||||
 | 
					        unittest.TestResult.addSkip(self, test, reason)
 | 
				
			||||||
 | 
					        if self.showAll:
 | 
				
			||||||
 | 
					            self.stream.writeln("skipped {0!r}".format(reason))
 | 
				
			||||||
 | 
					        elif self.dots:
 | 
				
			||||||
 | 
					            self.stream.write(".")
 | 
				
			||||||
 | 
					            self.stream.flush()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReproducibleStreamWrapper:
 | 
				
			||||||
 | 
					    def __init__(self, stream: TextIO):
 | 
				
			||||||
 | 
					        self.stream = stream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getattr__(self, attr):
 | 
				
			||||||
 | 
					        if attr in ('stream', '__getstate__'):
 | 
				
			||||||
 | 
					            raise AttributeError(attr)
 | 
				
			||||||
 | 
					        return getattr(self.stream, attr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, arg=None):
 | 
				
			||||||
 | 
					        arg = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', arg)
 | 
				
			||||||
 | 
					        arg = re.sub(r' \(skipped=\d+\)', r'', arg)
 | 
				
			||||||
 | 
					        self.stream.write(arg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReproducibleTestRunner(unittest.TextTestRunner):
 | 
				
			||||||
 | 
					    def __init__(self, stream: Optional[TextIO] = None,
 | 
				
			||||||
 | 
					             resultclass: Type[unittest.TestResult] = ReproducibleTestResult,
 | 
				
			||||||
 | 
					             **kwargs: Any) -> None:
 | 
				
			||||||
 | 
					        rstream = ReproducibleStreamWrapper(stream or sys.stdout)
 | 
				
			||||||
 | 
					        super().__init__(stream=rstream,           # type: ignore
 | 
				
			||||||
 | 
					                         descriptions=True,
 | 
				
			||||||
 | 
					                         resultclass=resultclass,
 | 
				
			||||||
 | 
					                         **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def execute_unittest(debug=False):
 | 
					def execute_unittest(debug=False):
 | 
				
			||||||
    """Executes unittests within the calling module."""
 | 
					    """Executes unittests within the calling module."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    verbosity = 2 if debug else 1
 | 
					    verbosity = 2 if debug else 1
 | 
				
			||||||
 | 
					    runner = ReproducibleTestRunner(verbosity=verbosity)
 | 
				
			||||||
    if debug:
 | 
					    unittest.main(testRunner=runner)
 | 
				
			||||||
        output = sys.stdout
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        # We need to filter out the time taken from the output so that
 | 
					 | 
				
			||||||
        # qemu-iotest can reliably diff the results against master output.
 | 
					 | 
				
			||||||
        output = io.StringIO()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    runner = unittest.TextTestRunner(stream=output, descriptions=True,
 | 
					 | 
				
			||||||
                                     verbosity=verbosity)
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        # unittest.main() will use sys.exit(); so expect a SystemExit
 | 
					 | 
				
			||||||
        # exception
 | 
					 | 
				
			||||||
        unittest.main(testRunner=runner)
 | 
					 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        # We need to filter out the time taken from the output so that
 | 
					 | 
				
			||||||
        # qemu-iotest can reliably diff the results against master output.
 | 
					 | 
				
			||||||
        if not debug:
 | 
					 | 
				
			||||||
            out = output.getvalue()
 | 
					 | 
				
			||||||
            out = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', out)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Hide skipped tests from the reference output
 | 
					 | 
				
			||||||
            out = re.sub(r'OK \(skipped=\d+\)', 'OK', out)
 | 
					 | 
				
			||||||
            out_first_line, out_rest = out.split('\n', 1)
 | 
					 | 
				
			||||||
            out = out_first_line.replace('s', '.') + '\n' + out_rest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            sys.stderr.write(out)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def execute_setup_common(supported_fmts: Sequence[str] = (),
 | 
					def execute_setup_common(supported_fmts: Sequence[str] = (),
 | 
				
			||||||
                         supported_platforms: Sequence[str] = (),
 | 
					                         supported_platforms: Sequence[str] = (),
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user