Index: third_party/typ/typ/runner.py |
diff --git a/third_party/typ/typ/runner.py b/third_party/typ/typ/runner.py |
index 67cfd26de4cc6ac9c447eb31fd05fe5b24cc630c..3a8dc8212c172a7f9f70a9ba868f7e10aac30c59 100644 |
--- a/third_party/typ/typ/runner.py |
+++ b/third_party/typ/typ/runner.py |
@@ -16,11 +16,24 @@ import fnmatch |
import importlib |
import inspect |
import json |
+import os |
import pdb |
+import sys |
import unittest |
from collections import OrderedDict |
+# This ensures that absolute imports of typ modules will work when |
+# running typ/runner.py as a script even if typ is not installed. |
+# We need this entry in addition to the one in __main__.py to ensure |
+# that typ/runner.py works when invoked via subprocess on windows in |
+# _spawn_main(). |
+path_to_file = os.path.realpath(__file__) |
+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) |
+ |
+ |
from typ import json_results |
from typ.arg_parser import ArgumentParser |
from typ.host import Host |
@@ -36,6 +49,19 @@ ResultSet = json_results.ResultSet |
ResultType = json_results.ResultType |
+def main(argv=None, host=None, stdout=None, stderr=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) |
+ |
+ |
class TestInput(object): |
def __init__(self, name, msg='', timeout=None, expected=None): |
@@ -64,6 +90,15 @@ class TestSet(object): |
self.teardown_fn = teardown_fn |
+class WinMultiprocessing(object): |
+ force = 'force' |
+ ignore = 'ignore' |
+ run_serially = 'run_serially' |
+ spawn = 'spawn' |
+ |
+ values = [force, ignore, run_serially, spawn] |
+ |
+ |
class _AddTestsError(Exception): |
pass |
@@ -84,14 +119,16 @@ class Runner(object): |
parser = ArgumentParser(self.host) |
self.parse_args(parser, []) |
- def main(self, argv=None, **defaults): |
+ def main(self, argv=None, win_multiprocessing=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.run() |
+ ret = self._handle_win_multiprocessing('main', win_multiprocessing) |
+ if ret is None: |
+ ret, _, _ = self.run(win_multiprocessing=win_multiprocessing) |
return ret |
except KeyboardInterrupt: |
self.print_("interrupted, exiting", stream=self.host.stderr) |
@@ -108,11 +145,66 @@ 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): |
+ context=None, setup_fn=None, teardown_fn=None, |
+ win_multiprocessing=None): |
+ |
ret = 0 |
h = self.host |
@@ -120,6 +212,9 @@ class Runner(object): |
self.print_(VERSION) |
return ret, None, None |
+ self._handle_win_multiprocessing('Runner.run', win_multiprocessing, |
+ allow_spawn=False) |
+ |
ret = self._set_up_runner() |
if ret: # pragma: no cover |
return ret, None, None |
@@ -799,3 +894,7 @@ def _load_via_load_tests(child, test_name): |
def _sort_inputs(inps): |
return sorted(inps, key=lambda inp: inp.name) |
+ |
+ |
+if __name__ == '__main__': # pragma: no cover |
+ sys.exit(main(win_multiprocessing='spawn')) |