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:
commit
6f016a2f79
@ -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)
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user