| Index: third_party/typ/typ/runner.py
|
| diff --git a/third_party/typ/typ/runner.py b/third_party/typ/typ/runner.py
|
| index 3a8dc8212c172a7f9f70a9ba868f7e10aac30c59..61aeb9d9e39df6362d15e5766c408fc1137312d4 100644
|
| --- a/third_party/typ/typ/runner.py
|
| +++ b/third_party/typ/typ/runner.py
|
| @@ -29,6 +29,8 @@ from collections import OrderedDict
|
| # that typ/runner.py works when invoked via subprocess on windows in
|
| # _spawn_main().
|
| path_to_file = os.path.realpath(__file__)
|
| +if path_to_file.endswith('.pyc'): # pragma: no cover
|
| + path_to_file = path_to_file[:-1]
|
| dir_above_typ = os.path.dirname(os.path.dirname(path_to_file))
|
| if dir_above_typ not in sys.path: # pragma: no cover
|
| sys.path.append(dir_above_typ)
|
| @@ -49,17 +51,12 @@ ResultSet = json_results.ResultSet
|
| ResultType = json_results.ResultType
|
|
|
|
|
| -def main(argv=None, host=None, stdout=None, stderr=None,
|
| - win_multiprocessing=None, **defaults):
|
| +def main(argv=None, host=None, win_multiprocessing=None, **defaults):
|
| host = host or Host()
|
| - if stdout:
|
| - host.stdout = stdout
|
| - if stderr:
|
| - host.stderr = stderr
|
| runner = Runner(host=host)
|
| -
|
| - return runner.main(argv, win_multiprocessing=win_multiprocessing,
|
| - **defaults)
|
| + if win_multiprocessing is not None:
|
| + runner.win_multiprocessing = win_multiprocessing
|
| + return runner.main(argv, **defaults)
|
|
|
|
|
| class TestInput(object):
|
| @@ -74,8 +71,7 @@ class TestInput(object):
|
| class TestSet(object):
|
|
|
| def __init__(self, parallel_tests=None, isolated_tests=None,
|
| - tests_to_skip=None, context=None, setup_fn=None,
|
| - teardown_fn=None):
|
| + tests_to_skip=None):
|
|
|
| def promote(tests):
|
| tests = tests or []
|
| @@ -85,18 +81,14 @@ class TestSet(object):
|
| self.parallel_tests = promote(parallel_tests)
|
| self.isolated_tests = promote(isolated_tests)
|
| self.tests_to_skip = promote(tests_to_skip)
|
| - self.context = context
|
| - self.setup_fn = setup_fn
|
| - self.teardown_fn = teardown_fn
|
|
|
|
|
| class WinMultiprocessing(object):
|
| - force = 'force'
|
| ignore = 'ignore'
|
| - run_serially = 'run_serially'
|
| + importable = 'importable'
|
| spawn = 'spawn'
|
|
|
| - values = [force, ignore, run_serially, spawn]
|
| + values = [ignore, importable, spawn]
|
|
|
|
|
| class _AddTestsError(Exception):
|
| @@ -106,29 +98,32 @@ class _AddTestsError(Exception):
|
| class Runner(object):
|
|
|
| def __init__(self, host=None):
|
| + self.args = None
|
| + self.classifier = None
|
| + self.cov = None
|
| + self.context = None
|
| + self.coverage_source = None
|
| self.host = host or Host()
|
| self.loader = unittest.loader.TestLoader()
|
| self.printer = None
|
| + self.setup_fn = None
|
| self.stats = None
|
| - self.cov = None
|
| - self.coverage_source = None
|
| + self.teardown_fn = None
|
| self.top_level_dir = None
|
| - self.args = None
|
| + self.win_multiprocessing = WinMultiprocessing.spawn
|
|
|
| # initialize self.args to the defaults.
|
| parser = ArgumentParser(self.host)
|
| self.parse_args(parser, [])
|
|
|
| - def main(self, argv=None, win_multiprocessing=None, **defaults):
|
| + def main(self, argv=None, **defaults):
|
| parser = ArgumentParser(self.host)
|
| self.parse_args(parser, argv, **defaults)
|
| if parser.exit_status is not None:
|
| return parser.exit_status
|
|
|
| try:
|
| - ret = self._handle_win_multiprocessing('main', win_multiprocessing)
|
| - if ret is None:
|
| - ret, _, _ = self.run(win_multiprocessing=win_multiprocessing)
|
| + ret, _, _ = self.run()
|
| return ret
|
| except KeyboardInterrupt:
|
| self.print_("interrupted, exiting", stream=self.host.stderr)
|
| @@ -145,65 +140,10 @@ class Runner(object):
|
| if parser.exit_status is not None:
|
| return
|
|
|
| - def _handle_win_multiprocessing(self, entry_point, win_multiprocessing,
|
| - allow_spawn=True):
|
| - wmp = win_multiprocessing
|
| - force, ignore, run_serially, spawn = WinMultiprocessing.values
|
| -
|
| - if (wmp is not None and wmp not in WinMultiprocessing.values):
|
| - raise ValueError('illegal value %s for win_multiprocessing' %
|
| - wmp)
|
| -
|
| - # First, check if __main__ is importable; if it is, we're fine.
|
| - if (self._main_is_importable() and wmp != force):
|
| - return None
|
| -
|
| - if wmp is None and self.args.jobs == 1:
|
| - return None
|
| -
|
| - if wmp is None:
|
| - raise ValueError(
|
| - 'The __main__ module is not importable; The caller '
|
| - 'must pass a valid WinMultiprocessing value (one of %s) '
|
| - 'to %s to tell typ how to handle Windows.' %
|
| - (WinMultiprocessing.values, entry_point))
|
| -
|
| - h = self.host
|
| -
|
| - if (h.platform != 'win32' and wmp != force):
|
| - return
|
| -
|
| - if wmp == ignore: # pragma: win32
|
| - raise ValueError('Cannot use WinMultiprocessing.ignore for '
|
| - 'win_multiprocessing when actually running '
|
| - 'on Windows.')
|
| -
|
| - if wmp == run_serially: # pragma: win32
|
| - self.args.jobs = 1
|
| - return None
|
| -
|
| - assert allow_spawn, ('Cannot use WinMultiprocessing.spawn '
|
| - 'in %s' % entry_point)
|
| - assert wmp in (force, spawn)
|
| - argv = ArgumentParser(h).argv_from_args(self.args)
|
| - return h.call_inline([h.python_interpreter, path_to_file] + argv)
|
| -
|
| - def _main_is_importable(self):
|
| - path = self.host.realpath(sys.modules['__main__'].__file__)
|
| - if not path or not path.endswith('.py'): # pragma: no cover
|
| - return False
|
| -
|
| - for d in sys.path:
|
| - if path.startswith(self.host.realpath(d)):
|
| - return True
|
| - return False # pragma: no cover
|
| -
|
| def print_(self, msg='', end='\n', stream=None):
|
| self.host.print_(msg, end, stream=stream)
|
|
|
| - def run(self, test_set=None, classifier=None,
|
| - context=None, setup_fn=None, teardown_fn=None,
|
| - win_multiprocessing=None):
|
| + def run(self, test_set=None):
|
|
|
| ret = 0
|
| h = self.host
|
| @@ -212,8 +152,9 @@ class Runner(object):
|
| self.print_(VERSION)
|
| return ret, None, None
|
|
|
| - self._handle_win_multiprocessing('Runner.run', win_multiprocessing,
|
| - allow_spawn=False)
|
| + should_spawn = self._check_win_multiprocessing()
|
| + if should_spawn:
|
| + return self._spawn(test_set)
|
|
|
| ret = self._set_up_runner()
|
| if ret: # pragma: no cover
|
| @@ -228,8 +169,7 @@ class Runner(object):
|
| result_set = ResultSet()
|
|
|
| if not test_set:
|
| - ret, test_set = self.find_tests(self.args, classifier, context,
|
| - setup_fn, teardown_fn)
|
| + ret, test_set = self.find_tests(self.args)
|
| find_end = h.time()
|
|
|
| if not ret:
|
| @@ -243,8 +183,8 @@ class Runner(object):
|
| trace = self._trace_from_results(result_set)
|
| if full_results:
|
| self._summarize(full_results)
|
| - self.write_results(full_results)
|
| - upload_ret = self.upload_results(full_results)
|
| + self._write(self.args.write_full_results_to, full_results)
|
| + upload_ret = self._upload(full_results)
|
| if not ret:
|
| ret = upload_ret
|
| reporting_end = h.time()
|
| @@ -252,13 +192,93 @@ class Runner(object):
|
| self._add_trace_event(trace, 'discovery', find_start, find_end)
|
| self._add_trace_event(trace, 'testing', find_end, test_end)
|
| self._add_trace_event(trace, 'reporting', test_end, reporting_end)
|
| - self.write_trace(trace)
|
| + self._write(self.args.write_trace_to, trace)
|
| self.report_coverage()
|
| else:
|
| upload_ret = 0
|
|
|
| return ret, full_results, trace
|
|
|
| + def _check_win_multiprocessing(self):
|
| + wmp = self.win_multiprocessing
|
| +
|
| + ignore, importable, spawn = WinMultiprocessing.values
|
| +
|
| + if wmp not in WinMultiprocessing.values:
|
| + raise ValueError('illegal value %s for win_multiprocessing' %
|
| + wmp)
|
| +
|
| + h = self.host
|
| + if wmp == ignore and h.platform == 'win32': # pragma: win32
|
| + raise ValueError('Cannot use WinMultiprocessing.ignore for '
|
| + 'win_multiprocessing when actually running '
|
| + 'on Windows.')
|
| +
|
| + if wmp == ignore or self.args.jobs == 1:
|
| + return False
|
| +
|
| + if wmp == importable:
|
| + if self._main_is_importable():
|
| + return False
|
| + raise ValueError('The __main__ module (%s) ' # pragma: no cover
|
| + 'may not be importable' %
|
| + sys.modules['__main__'].__file__)
|
| +
|
| + assert wmp == spawn
|
| + return True
|
| +
|
| + def _main_is_importable(self): # pragma: untested
|
| + path = sys.modules['__main__'].__file__
|
| + if not path:
|
| + return False
|
| + if path.endswith('.pyc'):
|
| + path = path[:-1]
|
| + if not path.endswith('.py'):
|
| + return False
|
| + if path.endswith('__main__.py'):
|
| + # main modules are not directly importable.
|
| + return False
|
| +
|
| + path = self.host.realpath(path)
|
| + for d in sys.path:
|
| + if path.startswith(self.host.realpath(d)):
|
| + return True
|
| + return False # pragma: no cover
|
| +
|
| + def _spawn(self, test_set):
|
| + # TODO: Handle picklable hooks, rather than requiring them to be None.
|
| + assert self.classifier is None
|
| + assert self.context is None
|
| + assert self.setup_fn is None
|
| + assert self.teardown_fn is None
|
| + assert test_set is None
|
| + h = self.host
|
| +
|
| + if self.args.write_trace_to: # pragma: untested
|
| + should_delete_trace = False
|
| + else:
|
| + should_delete_trace = True
|
| + fp = h.mktempfile(delete=False)
|
| + fp.close()
|
| + self.args.write_trace_to = fp.name
|
| +
|
| + if self.args.write_full_results_to: # pragma: untested
|
| + should_delete_results = False
|
| + else:
|
| + should_delete_results = True
|
| + fp = h.mktempfile(delete=False)
|
| + fp.close()
|
| + self.args.write_full_results_to = fp.name
|
| +
|
| + argv = ArgumentParser(h).argv_from_args(self.args)
|
| + ret = h.call_inline([h.python_interpreter, path_to_file] + argv)
|
| +
|
| + trace = self._read_and_delete(self.args.write_trace_to,
|
| + should_delete_trace)
|
| + full_results = self._read_and_delete(self.args.write_full_results_to,
|
| + should_delete_results)
|
| + return ret, full_results, trace
|
| +
|
| def _set_up_runner(self):
|
| h = self.host
|
| args = self.args
|
| @@ -303,11 +323,8 @@ class Runner(object):
|
| self.cov.erase()
|
| return 0
|
|
|
| - def find_tests(self, args, classifier=None,
|
| - context=None, setup_fn=None, teardown_fn=None):
|
| - test_set = self._make_test_set(context=context,
|
| - setup_fn=setup_fn,
|
| - teardown_fn=teardown_fn)
|
| + def find_tests(self, args):
|
| + test_set = TestSet()
|
|
|
| orig_skip = unittest.skip
|
| orig_skip_if = unittest.skipIf
|
| @@ -317,7 +334,7 @@ class Runner(object):
|
|
|
| try:
|
| names = self._name_list_from_args(args)
|
| - classifier = classifier or _default_classifier(args)
|
| + classifier = self.classifier or _default_classifier(args)
|
|
|
| for name in names:
|
| try:
|
| @@ -396,7 +413,7 @@ class Runner(object):
|
|
|
| self._run_one_set(self.stats, result_set, test_set)
|
|
|
| - failed_tests = json_results.failed_test_names(result_set)
|
| + failed_tests = sorted(json_results.failed_test_names(result_set))
|
| retry_limit = self.args.retry_limit
|
|
|
| while retry_limit and failed_tests:
|
| @@ -414,11 +431,7 @@ class Runner(object):
|
|
|
| stats = Stats(self.args.status_format, h.time, 1)
|
| stats.total = len(failed_tests)
|
| - tests_to_retry = self._make_test_set(
|
| - isolated_tests=[TestInput(name) for name in failed_tests],
|
| - context=test_set.context,
|
| - setup_fn=test_set.setup_fn,
|
| - teardown_fn=test_set.teardown_fn)
|
| + tests_to_retry = TestSet(isolated_tests=list(failed_tests))
|
| retry_set = ResultSet()
|
| self._run_one_set(stats, retry_set, tests_to_retry)
|
| result_set.results.extend(retry_set.results)
|
| @@ -435,26 +448,14 @@ class Runner(object):
|
| return (json_results.exit_code_from_full_results(full_results),
|
| full_results)
|
|
|
| - def _make_test_set(self, parallel_tests=None, isolated_tests=None,
|
| - tests_to_skip=None, context=None, setup_fn=None,
|
| - teardown_fn=None):
|
| - parallel_tests = parallel_tests or []
|
| - isolated_tests = isolated_tests or []
|
| - tests_to_skip = tests_to_skip or []
|
| - return TestSet(_sort_inputs(parallel_tests),
|
| - _sort_inputs(isolated_tests),
|
| - _sort_inputs(tests_to_skip),
|
| - context, setup_fn, teardown_fn)
|
| -
|
| def _run_one_set(self, stats, result_set, test_set):
|
| stats.total = (len(test_set.parallel_tests) +
|
| len(test_set.isolated_tests) +
|
| len(test_set.tests_to_skip))
|
| self._skip_tests(stats, result_set, test_set.tests_to_skip)
|
| - self._run_list(stats, result_set, test_set,
|
| - test_set.parallel_tests, self.args.jobs)
|
| - self._run_list(stats, result_set, test_set,
|
| - test_set.isolated_tests, 1)
|
| + self._run_list(stats, result_set, test_set.parallel_tests,
|
| + self.args.jobs)
|
| + self._run_list(stats, result_set, test_set.isolated_tests, 1)
|
|
|
| def _skip_tests(self, stats, result_set, tests_to_skip):
|
| for test_input in tests_to_skip:
|
| @@ -470,7 +471,7 @@ class Runner(object):
|
| stats.finished += 1
|
| self._print_test_finished(stats, result)
|
|
|
| - def _run_list(self, stats, result_set, test_set, test_inputs, jobs):
|
| + def _run_list(self, stats, result_set, test_inputs, jobs):
|
| h = self.host
|
| running_jobs = set()
|
|
|
| @@ -478,7 +479,7 @@ class Runner(object):
|
| if not jobs:
|
| return
|
|
|
| - child = _Child(self, self.loader, test_set)
|
| + child = _Child(self)
|
| pool = make_pool(h, jobs, _run_one_test, child,
|
| _setup_process, _teardown_process)
|
| try:
|
| @@ -572,19 +573,22 @@ class Runner(object):
|
| '' if num_failures == 1 else 's'), elide=False)
|
| self.print_()
|
|
|
| - def write_trace(self, trace):
|
| - if self.args.write_trace_to:
|
| - self.host.write_text_file(
|
| - self.args.write_trace_to,
|
| - json.dumps(trace, indent=2) + '\n')
|
| -
|
| - def write_results(self, full_results):
|
| - if self.args.write_full_results_to:
|
| - self.host.write_text_file(
|
| - self.args.write_full_results_to,
|
| - json.dumps(full_results, indent=2) + '\n')
|
| -
|
| - def upload_results(self, full_results):
|
| + def _read_and_delete(self, path, delete):
|
| + h = self.host
|
| + obj = None
|
| + if h.exists(path):
|
| + contents = h.read_text_file(path)
|
| + if contents:
|
| + obj = json.loads(contents)
|
| + if delete:
|
| + h.remove(path)
|
| + return obj
|
| +
|
| + def _write(self, path, obj):
|
| + if path:
|
| + self.host.write_text_file(path, json.dumps(obj, indent=2) + '\n')
|
| +
|
| + def _upload(self, full_results):
|
| h = self.host
|
| if not self.args.test_results_server:
|
| return 0
|
| @@ -697,7 +701,7 @@ def _test_adder(test_set, classifier):
|
|
|
| class _Child(object):
|
|
|
| - def __init__(self, parent, loader, test_set):
|
| + def __init__(self, parent):
|
| self.host = None
|
| self.worker_num = None
|
| self.all = parent.args.all
|
| @@ -705,11 +709,11 @@ class _Child(object):
|
| self.coverage = parent.args.coverage and parent.args.jobs > 1
|
| self.coverage_source = parent.coverage_source
|
| self.dry_run = parent.args.dry_run
|
| - self.loader = loader
|
| + self.loader = parent.loader
|
| self.passthrough = parent.args.passthrough
|
| - self.context = test_set.context
|
| - self.setup_fn = test_set.setup_fn
|
| - self.teardown_fn = test_set.teardown_fn
|
| + self.context = parent.context
|
| + self.setup_fn = parent.setup_fn
|
| + self.teardown_fn = parent.teardown_fn
|
| self.context_after_setup = None
|
| self.top_level_dir = parent.top_level_dir
|
| self.loaded_suites = {}
|
| @@ -897,4 +901,5 @@ def _sort_inputs(inps):
|
|
|
|
|
| if __name__ == '__main__': # pragma: no cover
|
| - sys.exit(main(win_multiprocessing='spawn'))
|
| + sys.modules['__main__'].__file__ = path_to_file
|
| + sys.exit(main(win_multiprocessing=WinMultiprocessing.importable))
|
|
|