Block patches:

- Added support to the iotests for running tests in several parallel
   jobs (using the new -j parameter)
 -----BEGIN PGP SIGNATURE-----
 
 iQJGBAABCAAwFiEEy2LXoO44KeRfAE00ofpA0JgBnN8FAmHDVJ8SHGhyZWl0ekBy
 ZWRoYXQuY29tAAoJEKH6QNCYAZzfMh4QALXFA9BT+YGQPsMbx6HCnVjbV3PuRdX+
 8m5vx5JUdfiMKOxe6h9yI/dmyv2ihcwdDOOrFqu+sBvRW8wsEj5qgvwnLdf7QZws
 Q/mNcKm4GUTl3QWtn9PBf4hMPQCSrkhI6SbT+B93EbHz9ugM1Y6VigEeedt67WwU
 M3WJu+/X/4UW5XNk1B4IGbPGB8xDbs9R4phw3i4pC9rjzVdRn/U+vQA9pGXZ6I+9
 fYQ0MTuUKzvUTSb/4tDXn6obkukdm+qlF+q3v3SNAXIDc/7cuX4vynlVCK3k1cFc
 mCisfTOueaihuqAxFW8S5uTmiEerJwEc/RPX6bMu9JalRShAvdvMYRphSPk9ZODJ
 TVgydIlZNBUkpPvp2Ar2ZqDDLVlW0Pu/cfgYGATVyGpVY8PpdrFmETZ9tCE6xMNz
 hZCX9BOkDK4AJudOum2+hHItw93Kkt5RyGnNx/BcqaSU2kJW2NHr9oxaUwMX9urC
 qyn62xCktwFXH4HfB3i4UQAsTMphgbmj53KI78U40jQXskpAnK3KITPe5hTw/N/E
 jPoqs0fp2mNWtOKNmR13gEKQ4zd6iO5GfW0dZYIuDydBVnpyapNPm7meXnd7p6Ni
 jAXqYWg/KI0GRxufuna3zD6fjP7nFRBgg2/dQxqmftY2+iiFiAGR8JjRAyYYFJAO
 c/wsvFregD9k
 =etR3
 -----END PGP SIGNATURE-----

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>
This commit is contained in:
Richard Henderson 2021-12-22 10:01:11 -08:00
commit 6f016a2f79
2 changed files with 80 additions and 10 deletions

View File

@ -34,6 +34,8 @@ def make_argparser() -> argparse.ArgumentParser:
help='show me, do not run tests') help='show me, do not run tests')
p.add_argument('-makecheck', action='store_true', p.add_argument('-makecheck', action='store_true',
help='pretty print output for make check') 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('-d', dest='debug', action='store_true', help='debug')
p.add_argument('-p', dest='print', action='store_true', p.add_argument('-p', dest='print', action='store_true',
@ -165,6 +167,6 @@ if __name__ == '__main__':
with TestRunner(env, makecheck=args.makecheck, with TestRunner(env, makecheck=args.makecheck,
color=args.color) as tr: color=args.color) as tr:
paths = [os.path.join(env.source_iotests, t) for t in tests] 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: if not ok:
sys.exit(1) sys.exit(1)

View File

@ -26,6 +26,7 @@ import contextlib
import json import json
import termios import termios
import sys import sys
from multiprocessing import Pool
from contextlib import contextmanager from contextlib import contextmanager
from typing import List, Optional, Iterator, Any, Sequence, Dict, \ from typing import List, Optional, Iterator, Any, Sequence, Dict, \
ContextManager ContextManager
@ -126,6 +127,31 @@ class TestResult:
class TestRunner(ContextManager['TestRunner']): 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, def __init__(self, env: TestEnv, makecheck: bool = False,
color: str = 'auto') -> None: color: str = 'auto') -> None:
self.env = env self.env = env
@ -219,7 +245,18 @@ class TestRunner(ContextManager['TestRunner']):
return f'{test}.out' 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_test = Path(test)
f_bad = Path(f_test.name + '.out.bad') f_bad = Path(f_test.name + '.out.bad')
f_notrun = Path(f_test.name + '.notrun') f_notrun = Path(f_test.name + '.notrun')
@ -243,6 +280,12 @@ class TestRunner(ContextManager['TestRunner']):
args = [str(f_test.resolve())] args = [str(f_test.resolve())]
env = self.env.prepare_subprocess(args) 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() t0 = time.time()
with f_bad.open('w', encoding="utf-8") as f: with f_bad.open('w', encoding="utf-8") as f:
@ -281,21 +324,36 @@ class TestRunner(ContextManager['TestRunner']):
diff=diff, casenotrun=casenotrun) diff=diff, casenotrun=casenotrun)
else: else:
f_bad.unlink() f_bad.unlink()
self.last_elapsed.update(test, elapsed)
return TestResult(status='pass', elapsed=elapsed, return TestResult(status='pass', elapsed=elapsed,
casenotrun=casenotrun) casenotrun=casenotrun)
def run_test(self, test: str, 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) last_el = self.last_elapsed.get(test)
start = datetime.datetime.now().strftime('%H:%M:%S') start = datetime.datetime.now().strftime('%H:%M:%S')
if not self.makecheck: if not self.makecheck:
self.test_print_one_line(test=test, starttime=start, self.test_print_one_line(test=test,
lasttime=last_el, end='\r', status = 'started' if mp else '...',
starttime=start,
lasttime=last_el,
end = '\n' if mp else '\r',
test_field_width=test_field_width) 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') end = datetime.datetime.now().strftime('%H:%M:%S')
self.test_print_one_line(test=test, status=res.status, self.test_print_one_line(test=test, status=res.status,
@ -309,7 +367,7 @@ class TestRunner(ContextManager['TestRunner']):
return res return res
def run_tests(self, tests: List[str]) -> bool: def run_tests(self, tests: List[str], jobs: int = 1) -> bool:
n_run = 0 n_run = 0
failed = [] failed = []
notrun = [] notrun = []
@ -320,9 +378,16 @@ class TestRunner(ContextManager['TestRunner']):
test_field_width = max(len(os.path.basename(t)) for t in tests) + 2 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) 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') assert res.status in ('pass', 'fail', 'not run')
@ -340,6 +405,9 @@ class TestRunner(ContextManager['TestRunner']):
print('\n'.join(res.diff)) print('\n'.join(res.diff))
elif res.status == 'not run': elif res.status == 'not run':
notrun.append(name) notrun.append(name)
elif res.status == 'pass':
assert res.elapsed is not None
self.last_elapsed.update(t, res.elapsed)
sys.stdout.flush() sys.stdout.flush()
if res.interrupted: if res.interrupted: