Run go tests in parallel
This commit is contained in:
parent
249df69ac9
commit
85b6053380
@ -3,12 +3,16 @@
|
|||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
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]:
|
def contents(package: str) -> Iterator[str]:
|
||||||
@ -83,27 +87,33 @@ def type_check() -> NoReturn:
|
|||||||
os.execlp(py, py, '-m', 'mypy', '--pretty')
|
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 = unittest.TextTestRunner
|
||||||
r.resultclass = unittest.TextTestResult
|
r.resultclass = unittest.TextTestResult
|
||||||
runner = r(verbosity=verbosity)
|
runner = r(verbosity=verbosity)
|
||||||
runner.tb_locals = True # type: ignore
|
runner.tb_locals = True # type: ignore
|
||||||
result = runner.run(suite)
|
result = runner.run(suite)
|
||||||
if not result.wasSuccessful():
|
return result.wasSuccessful()
|
||||||
raise SystemExit(1)
|
|
||||||
|
|
||||||
|
|
||||||
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()
|
ans = set()
|
||||||
base = os.getcwd()
|
base = os.getcwd()
|
||||||
|
pat = re.compile(r'^func Test([A-Z]\w+)', re.MULTILINE)
|
||||||
for (dirpath, dirnames, filenames) in os.walk(base):
|
for (dirpath, dirnames, filenames) in os.walk(base):
|
||||||
for f in filenames:
|
for f in filenames:
|
||||||
if f.endswith('_test.go'):
|
if f.endswith('_test.go'):
|
||||||
q = os.path.relpath(dirpath, base)
|
q = os.path.relpath(dirpath, base)
|
||||||
ans.add(q)
|
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:
|
def go_exe() -> str:
|
||||||
return shutil.which('go') or ''
|
return shutil.which('go') or ''
|
||||||
|
|
||||||
@ -124,20 +134,30 @@ def create_go_filter(packages: List[str], *names: str) -> str:
|
|||||||
return '|'.join(tests)
|
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()
|
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)
|
print('Skipping Go tests as go exe not found', file=sys.stderr)
|
||||||
return
|
return
|
||||||
if not packages:
|
go_packages, go_functions = find_testable_go_packages()
|
||||||
print('Skipping Go tests as go source files not availabe', file=sys.stderr)
|
if module:
|
||||||
return
|
go_packages &= {module}
|
||||||
cmd = [go, 'test', '-v']
|
|
||||||
if names:
|
if names:
|
||||||
cmd.extend(('-run', names))
|
pkgs = set()
|
||||||
cmd += packages
|
for name in names:
|
||||||
print(shlex.join(cmd), flush=True)
|
pkgs |= set(go_functions.get(name, []))
|
||||||
os.execl(go, *cmd)
|
go_packages &= pkgs
|
||||||
|
return go_packages
|
||||||
|
|
||||||
|
|
||||||
def run_tests() -> None:
|
def run_tests() -> None:
|
||||||
@ -145,30 +165,40 @@ def run_tests() -> None:
|
|||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'name', nargs='*', default=[],
|
'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('--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.'
|
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')
|
' For Go tests this is the name of a package, for example: tools/cli')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.name and args.name[0] in ('type-check', 'type_check', 'mypy'):
|
if args.name and args.name[0] in ('type-check', 'type_check', 'mypy'):
|
||||||
type_check()
|
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()
|
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:
|
if args.module:
|
||||||
tests = filter_tests_by_module(tests, args.module)
|
tests = filter_tests_by_module(tests, args.module)
|
||||||
go_packages &= {args.module}
|
if not tests._tests:
|
||||||
if not tests._tests and not go_packages:
|
if go_pkgs:
|
||||||
|
print_go()
|
||||||
|
raise SystemExit(go_proc.returncode)
|
||||||
raise SystemExit('No test module named %s found' % args.module)
|
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:
|
if args.name:
|
||||||
tests = filter_tests_by_name(tests, *args.name)
|
tests = filter_tests_by_name(tests, *args.name)
|
||||||
go_filter_spec = create_go_filter(go_pkg_args, *args.name)
|
if not tests._tests:
|
||||||
skip_go = not go_filter_spec
|
|
||||||
if not tests._tests and not go_filter_spec:
|
|
||||||
raise SystemExit('No test named %s found' % args.name)
|
raise SystemExit('No test named %s found' % args.name)
|
||||||
run_cli(tests, args.verbosity)
|
python_tests_ok = run_cli(tests, args.verbosity)
|
||||||
if not skip_go:
|
exit_code = 0 if python_tests_ok else 1
|
||||||
run_go(go_pkg_args, go_filter_spec)
|
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