aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2021-12-22 10:01:11 -0800
committerRichard Henderson <richard.henderson@linaro.org>2021-12-22 10:01:11 -0800
commit6f016a2f7909eb6d595436a2f56cdf7cdf3e3c68 (patch)
treeaf5b29b78b17b87ca564bb7bbd50c966640e821d
parent8d29feca838bce06473616168b6d5c0b35b96c94 (diff)
parent722f87df2545b308aec49b459b028f0802b4fd9e (diff)
Merge tag 'pull-block-2021-12-22' of https://gitlab.com/hreitz/qemu into staging
Block patches: - Added support to the iotests for running tests in several parallel jobs (using the new -j parameter) # gpg: Signature made Wed 22 Dec 2021 08:38:55 AM PST # gpg: using RSA key CB62D7A0EE3829E45F004D34A1FA40D098019CDF # gpg: issuer "hreitz@redhat.com" # gpg: Good signature from "Hanna Reitz <hreitz@redhat.com>" [unknown] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: CB62 D7A0 EE38 29E4 5F00 4D34 A1FA 40D0 9801 9CDF * tag 'pull-block-2021-12-22' of https://gitlab.com/hreitz/qemu: iotests: check: multiprocessing support iotests/testrunner.py: move updating last_elapsed to run_tests iotests/testrunner.py: add doc string for run_test() Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
-rwxr-xr-xtests/qemu-iotests/check4
-rw-r--r--tests/qemu-iotests/testrunner.py86
2 files changed, 80 insertions, 10 deletions
diff --git a/tests/qemu-iotests/check b/tests/qemu-iotests/check
index 43a4b694cc..0c27721a41 100755
--- a/tests/qemu-iotests/check
+++ b/tests/qemu-iotests/check
@@ -34,6 +34,8 @@ def make_argparser() -> argparse.ArgumentParser:
help='show me, do not run tests')
p.add_argument('-makecheck', action='store_true',
help='pretty print output for make check')
+ p.add_argument('-j', dest='jobs', type=int, default=1,
+ help='run tests in multiple parallel jobs')
p.add_argument('-d', dest='debug', action='store_true', help='debug')
p.add_argument('-p', dest='print', action='store_true',
@@ -165,6 +167,6 @@ if __name__ == '__main__':
with TestRunner(env, makecheck=args.makecheck,
color=args.color) as tr:
paths = [os.path.join(env.source_iotests, t) for t in tests]
- ok = tr.run_tests(paths)
+ ok = tr.run_tests(paths, args.jobs)
if not ok:
sys.exit(1)
diff --git a/tests/qemu-iotests/testrunner.py b/tests/qemu-iotests/testrunner.py
index 0e29c2fddd..0feaa396d0 100644
--- a/tests/qemu-iotests/testrunner.py
+++ b/tests/qemu-iotests/testrunner.py
@@ -26,6 +26,7 @@ import contextlib
import json
import termios
import sys
+from multiprocessing import Pool
from contextlib import contextmanager
from typing import List, Optional, Iterator, Any, Sequence, Dict, \
ContextManager
@@ -126,6 +127,31 @@ class TestResult:
class TestRunner(ContextManager['TestRunner']):
+ shared_self = None
+
+ @staticmethod
+ def proc_run_test(test: str, test_field_width: int) -> TestResult:
+ # We are in a subprocess, we can't change the runner object!
+ runner = TestRunner.shared_self
+ assert runner is not None
+ return runner.run_test(test, test_field_width, mp=True)
+
+ def run_tests_pool(self, tests: List[str],
+ test_field_width: int, jobs: int) -> List[TestResult]:
+
+ # passing self directly to Pool.starmap() just doesn't work, because
+ # it's a context manager.
+ assert TestRunner.shared_self is None
+ TestRunner.shared_self = self
+
+ with Pool(jobs) as p:
+ results = p.starmap(self.proc_run_test,
+ zip(tests, [test_field_width] * len(tests)))
+
+ TestRunner.shared_self = None
+
+ return results
+
def __init__(self, env: TestEnv, makecheck: bool = False,
color: str = 'auto') -> None:
self.env = env
@@ -219,7 +245,18 @@ class TestRunner(ContextManager['TestRunner']):
return f'{test}.out'
- def do_run_test(self, test: str) -> TestResult:
+ def do_run_test(self, test: str, mp: bool) -> TestResult:
+ """
+ Run one test
+
+ :param test: test file path
+ :param mp: if true, we are in a multiprocessing environment, use
+ personal subdirectories for test run
+
+ Note: this method may be called from subprocess, so it does not
+ change ``self`` object in any way!
+ """
+
f_test = Path(test)
f_bad = Path(f_test.name + '.out.bad')
f_notrun = Path(f_test.name + '.notrun')
@@ -243,6 +280,12 @@ class TestRunner(ContextManager['TestRunner']):
args = [str(f_test.resolve())]
env = self.env.prepare_subprocess(args)
+ if mp:
+ # Split test directories, so that tests running in parallel don't
+ # break each other.
+ for d in ['TEST_DIR', 'SOCK_DIR']:
+ env[d] = os.path.join(env[d], f_test.name)
+ Path(env[d]).mkdir(parents=True, exist_ok=True)
t0 = time.time()
with f_bad.open('w', encoding="utf-8") as f:
@@ -281,21 +324,36 @@ class TestRunner(ContextManager['TestRunner']):
diff=diff, casenotrun=casenotrun)
else:
f_bad.unlink()
- self.last_elapsed.update(test, elapsed)
return TestResult(status='pass', elapsed=elapsed,
casenotrun=casenotrun)
def run_test(self, test: str,
- test_field_width: Optional[int] = None) -> TestResult:
+ test_field_width: Optional[int] = None,
+ mp: bool = False) -> TestResult:
+ """
+ Run one test and print short status
+
+ :param test: test file path
+ :param test_field_width: width for first field of status format
+ :param mp: if true, we are in a multiprocessing environment, don't try
+ to rewrite things in stdout
+
+ Note: this method may be called from subprocess, so it does not
+ change ``self`` object in any way!
+ """
+
last_el = self.last_elapsed.get(test)
start = datetime.datetime.now().strftime('%H:%M:%S')
if not self.makecheck:
- self.test_print_one_line(test=test, starttime=start,
- lasttime=last_el, end='\r',
+ self.test_print_one_line(test=test,
+ status = 'started' if mp else '...',
+ starttime=start,
+ lasttime=last_el,
+ end = '\n' if mp else '\r',
test_field_width=test_field_width)
- res = self.do_run_test(test)
+ res = self.do_run_test(test, mp)
end = datetime.datetime.now().strftime('%H:%M:%S')
self.test_print_one_line(test=test, status=res.status,
@@ -309,7 +367,7 @@ class TestRunner(ContextManager['TestRunner']):
return res
- def run_tests(self, tests: List[str]) -> bool:
+ def run_tests(self, tests: List[str], jobs: int = 1) -> bool:
n_run = 0
failed = []
notrun = []
@@ -320,9 +378,16 @@ class TestRunner(ContextManager['TestRunner']):
test_field_width = max(len(os.path.basename(t)) for t in tests) + 2
- for t in tests:
+ if jobs > 1:
+ results = self.run_tests_pool(tests, test_field_width, jobs)
+
+ for i, t in enumerate(tests):
name = os.path.basename(t)
- res = self.run_test(t, test_field_width=test_field_width)
+
+ if jobs > 1:
+ res = results[i]
+ else:
+ res = self.run_test(t, test_field_width)
assert res.status in ('pass', 'fail', 'not run')
@@ -340,6 +405,9 @@ class TestRunner(ContextManager['TestRunner']):
print('\n'.join(res.diff))
elif res.status == 'not run':
notrun.append(name)
+ elif res.status == 'pass':
+ assert res.elapsed is not None
+ self.last_elapsed.update(t, res.elapsed)
sys.stdout.flush()
if res.interrupted: