Run go tests in parallel
This commit is contained in:
parent
249df69ac9
commit
85b6053380
@ -3,12 +3,16 @@
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from typing import Callable, Generator, Iterator, List, NoReturn, Sequence, Set
|
||||
from functools import lru_cache
|
||||
from typing import (
|
||||
Callable, Dict, Generator, Iterator, List, NoReturn, Sequence, Set, Tuple,
|
||||
)
|
||||
|
||||
|
||||
def contents(package: str) -> Iterator[str]:
|
||||
@ -83,27 +87,33 @@ def type_check() -> NoReturn:
|
||||
os.execlp(py, py, '-m', 'mypy', '--pretty')
|
||||
|
||||
|
||||
def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None:
|
||||
def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> bool:
|
||||
r = unittest.TextTestRunner
|
||||
r.resultclass = unittest.TextTestResult
|
||||
runner = r(verbosity=verbosity)
|
||||
runner.tb_locals = True # type: ignore
|
||||
result = runner.run(suite)
|
||||
if not result.wasSuccessful():
|
||||
raise SystemExit(1)
|
||||
return result.wasSuccessful()
|
||||
|
||||
|
||||
def find_testable_go_packages() -> Set[str]:
|
||||
def find_testable_go_packages() -> Tuple[Set[str], Dict[str, List[str]]]:
|
||||
test_functions: Dict[str, List[str]] = {}
|
||||
ans = set()
|
||||
base = os.getcwd()
|
||||
pat = re.compile(r'^func Test([A-Z]\w+)', re.MULTILINE)
|
||||
for (dirpath, dirnames, filenames) in os.walk(base):
|
||||
for f in filenames:
|
||||
if f.endswith('_test.go'):
|
||||
q = os.path.relpath(dirpath, base)
|
||||
ans.add(q)
|
||||
return ans
|
||||
with open(os.path.join(dirpath, f)) as s:
|
||||
raw = s.read()
|
||||
for m in pat.finditer(raw):
|
||||
test_functions.setdefault(m.group(1), []).append(q)
|
||||
return ans, test_functions
|
||||
|
||||
|
||||
@lru_cache
|
||||
def go_exe() -> str:
|
||||
return shutil.which('go') or ''
|
||||
|
||||
@ -124,20 +134,30 @@ def create_go_filter(packages: List[str], *names: str) -> str:
|
||||
return '|'.join(tests)
|
||||
|
||||
|
||||
def run_go(packages: List[str], names: str) -> None:
|
||||
def run_go(packages: Set[str], names: str) -> 'subprocess.Popen[bytes]':
|
||||
go = go_exe()
|
||||
if not go:
|
||||
go_pkg_args = [f'kitty/{x}' for x in packages]
|
||||
cmd = [go, 'test', '-v']
|
||||
for name in names:
|
||||
cmd.extend(('-run', name))
|
||||
cmd += go_pkg_args
|
||||
print(shlex.join(cmd), flush=True)
|
||||
return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
|
||||
def reduce_go_pkgs(module: str, names: Sequence[str]) -> Set[str]:
|
||||
if not go_exe():
|
||||
print('Skipping Go tests as go exe not found', file=sys.stderr)
|
||||
return
|
||||
if not packages:
|
||||
print('Skipping Go tests as go source files not availabe', file=sys.stderr)
|
||||
return
|
||||
cmd = [go, 'test', '-v']
|
||||
go_packages, go_functions = find_testable_go_packages()
|
||||
if module:
|
||||
go_packages &= {module}
|
||||
if names:
|
||||
cmd.extend(('-run', names))
|
||||
cmd += packages
|
||||
print(shlex.join(cmd), flush=True)
|
||||
os.execl(go, *cmd)
|
||||
pkgs = set()
|
||||
for name in names:
|
||||
pkgs |= set(go_functions.get(name, []))
|
||||
go_packages &= pkgs
|
||||
return go_packages
|
||||
|
||||
|
||||
def run_tests() -> None:
|
||||
@ -145,30 +165,40 @@ def run_tests() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'name', nargs='*', default=[],
|
||||
help='The name of the test to run, for e.g. linebuf corresponds to test_linebuf. Can be specified multiple times.')
|
||||
help='The name of the test to run, for e.g. linebuf corresponds to test_linebuf.'
|
||||
' Can be specified multiple times. For go tests Something corresponds to TestSometing.')
|
||||
parser.add_argument('--verbosity', default=4, type=int, help='Test verbosity')
|
||||
parser.add_argument('--module', default='', help='Name of a test module to restrict to. For example: ssh.'
|
||||
' For Go tests this is the name of a package, for example: tools/cli')
|
||||
args = parser.parse_args()
|
||||
if args.name and args.name[0] in ('type-check', 'type_check', 'mypy'):
|
||||
type_check()
|
||||
go_pkgs = reduce_go_pkgs(args.module, args.name)
|
||||
if go_pkgs:
|
||||
go_proc = run_go(go_pkgs, args.name)
|
||||
tests = find_all_tests()
|
||||
go_packages = find_testable_go_packages()
|
||||
go_filter_spec = ''
|
||||
|
||||
def print_go() -> None:
|
||||
print(go_proc.stdout.read().decode(), end='', flush=True)
|
||||
go_proc.stdout.close()
|
||||
go_proc.wait()
|
||||
|
||||
if args.module:
|
||||
tests = filter_tests_by_module(tests, args.module)
|
||||
go_packages &= {args.module}
|
||||
if not tests._tests and not go_packages:
|
||||
if not tests._tests:
|
||||
if go_pkgs:
|
||||
print_go()
|
||||
raise SystemExit(go_proc.returncode)
|
||||
raise SystemExit('No test module named %s found' % args.module)
|
||||
go_pkg_args = [f'kitty/{x}' for x in go_packages]
|
||||
|
||||
skip_go = False
|
||||
if args.name:
|
||||
tests = filter_tests_by_name(tests, *args.name)
|
||||
go_filter_spec = create_go_filter(go_pkg_args, *args.name)
|
||||
skip_go = not go_filter_spec
|
||||
if not tests._tests and not go_filter_spec:
|
||||
if not tests._tests:
|
||||
raise SystemExit('No test named %s found' % args.name)
|
||||
run_cli(tests, args.verbosity)
|
||||
if not skip_go:
|
||||
run_go(go_pkg_args, go_filter_spec)
|
||||
python_tests_ok = run_cli(tests, args.verbosity)
|
||||
exit_code = 0 if python_tests_ok else 1
|
||||
if go_pkgs:
|
||||
print_go()
|
||||
if exit_code == 0:
|
||||
exit_code = go_proc.returncode
|
||||
raise SystemExit(exit_code)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user