diff options
author | dpranke <dpranke@chromium.org> | 2014-10-16 20:16:31 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-10-17 03:16:45 +0000 |
commit | 2bcc3a44e028d4b1e11a3131fafd5fdaa5a35eb0 (patch) | |
tree | 2d5cf26779074b332300c38eaf9091ede9fffcf2 /third_party/typ | |
parent | 8cd8c9e87bfea3fa8de580578e45a1d6148d6b68 (diff) | |
download | chromium_src-2bcc3a44e028d4b1e11a3131fafd5fdaa5a35eb0.zip chromium_src-2bcc3a44e028d4b1e11a3131fafd5fdaa5a35eb0.tar.gz chromium_src-2bcc3a44e028d4b1e11a3131fafd5fdaa5a35eb0.tar.bz2 |
Roll typ to v0.8.5
From typ commit df1f462ec978d47c7f092e86f4be3fafc7cc446f:
Rework typ to handle unimportable modules better.
multiprocessing on windows requires that the __main__ module
be importable in order to work properly. If developers are mostly
working and testing on unix, it's possible for them to not realize this
and attempt to call typ from a wrapper script that may not be
importable, and then get unexpected errors.
This change makes the typ entry points check if it is being called
from an unimportable __main__, and error out if so. Developers
must then specify several different ways to work around this by passing
the 'win_multiprocessing' flag to typ.main() or typ.Runner.main().
The flag takes one of the values specified in the typ.WinMultiprocessing
class:
ignore - The tests are not expected to run on Windows, so ignore
the fact that __main__ is not importable. (If the tests
are actually run on Windows, it will err out).
run_serially - Run serially (one at a time) on Windows, regardless
of how many cores are available (hence multiprocessing is
not used).
spawn - Typ will convert the args it was passed back into a command
line, and spawn off an invocation of itself on Windows to actually
execute the tests in parallel (Since typ itself is importable,
this works fine). This mode cannot be used in conjunction
with contexts or setup and teardown functions at this time.
force - This is like 'spawn', except that it forces the spawn even
on non-Windows platforms. This mostly exists for testing.
As part of this change, we also no longer expose spawn_main() as a public
entry point (main() handles spawning instead), and merge the cmdline.py
code directly into runner, as it didn't make sense to split things
across the two modules once runner had to do the importability checking.
We also add a host.call_inline() wrapper that is a dumb wrapper
around subprocess.call(); it does not do the output capturing that
host.call() does, and is used when spawning the typ subprocess if needed.
TBR=dtu@chromium.org
BUG=402172
Review URL: https://codereview.chromium.org/660133004
Cr-Commit-Position: refs/heads/master@{#300052}
Diffstat (limited to 'third_party/typ')
-rw-r--r-- | third_party/typ/README.chromium | 4 | ||||
-rwxr-xr-x | third_party/typ/run | 4 | ||||
-rw-r--r-- | third_party/typ/setup.py | 2 | ||||
-rw-r--r-- | third_party/typ/typ/__init__.py | 5 | ||||
-rw-r--r-- | third_party/typ/typ/__main__.py | 7 | ||||
-rw-r--r-- | third_party/typ/typ/arg_parser.py | 47 | ||||
-rw-r--r-- | third_party/typ/typ/cmdline.py | 56 | ||||
-rw-r--r-- | third_party/typ/typ/fakes/host_fake.py | 4 | ||||
-rw-r--r-- | third_party/typ/typ/fakes/tests/host_fake_test.py | 5 | ||||
-rw-r--r-- | third_party/typ/typ/host.py | 5 | ||||
-rw-r--r-- | third_party/typ/typ/runner.py | 105 | ||||
-rw-r--r-- | third_party/typ/typ/tests/arg_parser_test.py | 14 | ||||
-rw-r--r-- | third_party/typ/typ/tests/cmdline_test.py | 45 | ||||
-rw-r--r-- | third_party/typ/typ/tests/host_test.py | 12 | ||||
-rw-r--r-- | third_party/typ/typ/tests/main_test.py | 2 | ||||
-rw-r--r-- | third_party/typ/typ/tests/runner_test.py | 160 | ||||
-rw-r--r-- | third_party/typ/typ/version.py | 2 |
17 files changed, 358 insertions, 121 deletions
diff --git a/third_party/typ/README.chromium b/third_party/typ/README.chromium index a6fcca5..a49eb39 100644 --- a/third_party/typ/README.chromium +++ b/third_party/typ/README.chromium @@ -1,7 +1,7 @@ Name: typ URL: https://github.com/dpranke/typ.git -Version: 0.8.4 -Revision: 5e73c13993ecb44b00f93ca2fe97a7c18158f7ff +Version: 0.8.5 +Revision: aba6b5defd08d74dc1e52d25838b04a38088072d Security Critical: no License: Apache 2.0 License File: NOT_SHIPPED diff --git a/third_party/typ/run b/third_party/typ/run index c825055..9fb0bd2 100755 --- a/third_party/typ/run +++ b/third_party/typ/run @@ -139,13 +139,13 @@ def run_lint(args): def run_tests(args): # Tests that we can run the command line directly if typ is in sys.path. - call(['python', os.path.join('typ', 'cmdline.py'), + call(['python', os.path.join('typ', 'runner.py'), 'typ.tests.main_test.TestMain.test_basic']) # Test that we can run the command line directly if typ is not in sys.path. repo_dir = os.path.abspath(os.path.dirname(__file__)) home_dir = os.environ['HOME'] - call(['python', os.path.join(repo_dir, 'typ', 'cmdline.py'), + call(['python', os.path.join(repo_dir, 'typ', 'runner.py'), 'typ.tests.main_test.TestMain.test_basic'], cwd=home_dir) # Now run all the tests under Python2 and Python3. diff --git a/third_party/typ/setup.py b/third_party/typ/setup.py index ebc740a..170398f 100644 --- a/third_party/typ/setup.py +++ b/third_party/typ/setup.py @@ -34,7 +34,7 @@ setup( package_data={'': ['../README.rst']}, entry_points={ 'console_scripts': [ - 'typ=typ.cmdline:main', + 'typ=typ.runner:main', ] }, install_requires=[ diff --git a/third_party/typ/typ/__init__.py b/third_party/typ/typ/__init__.py index 6013a3a..ab04414 100644 --- a/third_party/typ/typ/__init__.py +++ b/third_party/typ/typ/__init__.py @@ -59,11 +59,10 @@ functionality: from typ.arg_parser import ArgumentParser from typ.fakes.host_fake import FakeHost from typ.host import Host -from typ.cmdline import main, spawn_main from typ.json_results import exit_code_from_full_results from typ.json_results import make_full_results, make_upload_request from typ.json_results import Result, ResultSet, ResultType -from typ.runner import Runner, TestInput, TestSet +from typ.runner import Runner, TestInput, TestSet, WinMultiprocessing, main from typ.stats import Stats from typ.printer import Printer from typ.test_case import convert_newlines, TestCase, MainTestCase @@ -85,10 +84,10 @@ __all__ = [ 'TestInput', 'TestSet', 'VERSION', + 'WinMultiprocessing', 'convert_newlines', 'exit_code_from_full_results', 'main', 'make_full_results', 'make_upload_request', - 'spawn_main', ] diff --git a/third_party/typ/typ/__main__.py b/third_party/typ/typ/__main__.py index 3b8b541..375e3e2 100644 --- a/third_party/typ/typ/__main__.py +++ b/third_party/typ/typ/__main__.py @@ -14,11 +14,8 @@ import sys -from typ import main, spawn_main +from typ import main if __name__ == '__main__': - if sys.platform == 'win32': # pragma: win32 - sys.exit(spawn_main(sys.argv[1:], sys.stdout, sys.stderr)) - else: # pragma: no win32 - sys.exit(main()) + sys.exit(main(win_multiprocessing='spawn')) diff --git a/third_party/typ/typ/arg_parser.py b/third_party/typ/typ/arg_parser.py index ac2d33e..b55dee8 100644 --- a/third_party/typ/typ/arg_parser.py +++ b/third_party/typ/typ/arg_parser.py @@ -246,6 +246,49 @@ class ArgumentParser(argparse.ArgumentParser): options.append(optparse.make_option(*args, **kwargs)) return options + def argv_from_args(self, args): + default_parser = ArgumentParser(host=self._host) + default_args = default_parser.parse_args([]) + argv = [] + tests = [] + d = vars(args) + for k in sorted(d.keys()): + v = d[k] + argname = _argname_from_key(k) + action = self._action_for_key(k) + action_str = _action_str(action) + if k == 'tests': + tests = v + continue + if getattr(default_args, k) == v: + # this arg has the default value, so skip it. + continue + + assert action_str in ['append', 'count', 'store', 'store_true'] + if action_str == 'append': + for el in v: + argv.append(argname) + argv.append(el) + elif action_str == 'count': + for _ in range(v): + argv.append(argname) + elif action_str == 'store': + argv.append(argname) + argv.append(str(v)) + else: + # action_str == 'store_true' + argv.append(argname) + + return argv + tests + + def _action_for_key(self, key): + for action in self._actions: + if action.dest == key: + return action + + assert False, ('Could not find an action for %s' # pragma: no cover + % key) + def _action_str(action): # Access to a protected member pylint: disable=W0212 @@ -264,3 +307,7 @@ def _action_str(action): return 'store' if isinstance(action, argparse._StoreTrueAction): return 'store_true' + + +def _argname_from_key(key): + return '--' + key.replace('_', '-') diff --git a/third_party/typ/typ/cmdline.py b/third_party/typ/typ/cmdline.py deleted file mode 100644 index 4bb718f..0000000 --- a/third_party/typ/typ/cmdline.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2014 Google Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import subprocess -import sys - - -# This ensures that absolute imports of typ modules will work when -# running typ/cmdline.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/cmdline.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.runner import Runner - - -def main(argv=None, host=None, **defaults): - runner = Runner(host=host) - return runner.main(argv, **defaults) - - -def spawn_main(argv, stdout, stderr): - # This function is called from __main__.py when running 'python -m typ' on - # windows. - # - # In order to use multiprocessing on windows, the initial module needs - # to be importable, and __main__.py isn't. - # - # This code instead spawns a subprocess that invokes main.py directly, - # getting around the problem. - # - # We don't want to always spawn a subprocess, because doing so is more - # heavyweight than it needs to be on other platforms (and can make - # debugging a bit more annoying). - return subprocess.call([sys.executable, path_to_file] + argv, - stdout=stdout, stderr=stderr) - - -if __name__ == '__main__': # pragma: no cover - sys.exit(main()) diff --git a/third_party/typ/typ/fakes/host_fake.py b/third_party/typ/typ/fakes/host_fake.py index cb74d27..3e8643c 100644 --- a/third_party/typ/typ/fakes/host_fake.py +++ b/third_party/typ/typ/fakes/host_fake.py @@ -40,6 +40,7 @@ class FakeHost(object): self.stdin = io.StringIO() self.stdout = io.StringIO() self.stderr = io.StringIO() + self.platform = 'linux2' self.env = {} self.sep = '/' self.dirs = set([]) @@ -89,6 +90,9 @@ class FakeHost(object): self.cmds.append(argv) return 0, '', '' + def call_inline(self, argv): + return self.call(argv)[0] + def chdir(self, *comps): path = self.join(*comps) if not path.startswith('/'): diff --git a/third_party/typ/typ/fakes/tests/host_fake_test.py b/third_party/typ/typ/fakes/tests/host_fake_test.py index 02f74e7..a16b7ba 100644 --- a/third_party/typ/typ/fakes/tests/host_fake_test.py +++ b/third_party/typ/typ/fakes/tests/host_fake_test.py @@ -43,6 +43,11 @@ class TestFakeHost(host_test.TestHost): self.assertEqual(err, '') self.assertEqual(h.cmds, [['echo', 'hello, world']]) + def test_call_inline(self): + h = self.host() + ret = h.call_inline(['echo', 'hello, world']) + self.assertEqual(ret, 0) + def test_capture_output(self): h = self.host() self.host = lambda: h diff --git a/third_party/typ/typ/host.py b/third_party/typ/typ/host.py index 4859d9c..c5f6ac8 100644 --- a/third_party/typ/typ/host.py +++ b/third_party/typ/typ/host.py @@ -48,6 +48,7 @@ class Host(object): self.stderr = sys.stderr self.stdin = sys.stdin self.env = os.environ + self.platform = sys.platform def abspath(self, *comps): return os.path.abspath(self.join(*comps)) @@ -75,6 +76,10 @@ class Host(object): # pylint type checking bug - pylint: disable=E1103 return proc.returncode, stdout.decode('utf-8'), stderr.decode('utf-8') + def call_inline(self, argv, env=None): + return subprocess.call(argv, stdin=self.stdin, stdout=self.stdout, + stderr=self.stderr, env=env) + def chdir(self, *comps): return os.chdir(self.join(*comps)) diff --git a/third_party/typ/typ/runner.py b/third_party/typ/typ/runner.py index 67cfd26..3a8dc82 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')) diff --git a/third_party/typ/typ/tests/arg_parser_test.py b/third_party/typ/typ/tests/arg_parser_test.py index caaf373..aabaebe 100644 --- a/third_party/typ/typ/tests/arg_parser_test.py +++ b/third_party/typ/typ/tests/arg_parser_test.py @@ -29,3 +29,17 @@ class ArgumentParserTest(unittest.TestCase): skip='[-d]') options, _ = parser.parse_args(['-j', '1']) self.assertEqual(options.jobs, 1) + + def test_argv_from_args(self): + + def check(argv, expected=None): + parser = ArgumentParser() + args = parser.parse_args(argv) + actual_argv = parser.argv_from_args(args) + expected = expected or argv + self.assertEqual(expected, actual_argv) + + check(['--version']) + check(['--coverage', '--coverage-omit', 'foo']) + check(['--jobs', '4']) + check(['-vv'], ['--verbose', '--verbose']) diff --git a/third_party/typ/typ/tests/cmdline_test.py b/third_party/typ/typ/tests/cmdline_test.py deleted file mode 100644 index fbb908c..0000000 --- a/third_party/typ/typ/tests/cmdline_test.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2014 Dirk Pranke. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import tempfile - -from typ import spawn_main -from typ import test_case -from typ import VERSION - - -class TestSpawnMain(test_case.MainTestCase): - def call(self, host, argv, stdin, env): - out = err = None - out_str = err_str = '' - try: - out = tempfile.NamedTemporaryFile(delete=False) - err = tempfile.NamedTemporaryFile(delete=False) - ret = spawn_main(argv, stdout=out, stderr=err) - out.close() - out_str = open(out.name).read() - err.close() - err_str = open(err.name).read() - finally: - if out: - out.close() - os.remove(out.name) - if err: - err.close() - os.remove(err.name) - return ret, out_str, err_str - - def test_version(self): - self.check(['--version'], ret=0, out=VERSION + '\n') diff --git a/third_party/typ/typ/tests/host_test.py b/third_party/typ/typ/tests/host_test.py index 8bb94f8..50db37f 100644 --- a/third_party/typ/typ/tests/host_test.py +++ b/third_party/typ/typ/tests/host_test.py @@ -184,6 +184,14 @@ class TestHost(unittest.TestCase): self.assertEqual(out, '') self.assertIn(err, ('err\n', 'err\r\n')) + def test_call_inline(self): + h = self.host() + h.stdout = None + h.stderr = None + ret = h.call_inline([h.python_interpreter, + '-c', 'import sys; sys.exit(0)']) + self.assertEqual(ret, 0) + def test_add_to_path(self): orig_sys_path = sys.path[:] try: @@ -196,3 +204,7 @@ class TestHost(unittest.TestCase): self.assertNotEqual(sys.path, orig_sys_path) finally: sys.path = orig_sys_path + + def test_platform(self): + h = self.host() + self.assertNotEqual(h.platform, None) diff --git a/third_party/typ/typ/tests/main_test.py b/third_party/typ/typ/tests/main_test.py index ff9b975..cc690d5 100644 --- a/third_party/typ/typ/tests/main_test.py +++ b/third_party/typ/typ/tests/main_test.py @@ -165,7 +165,7 @@ LOAD_TEST_FILES = {'load_test.py': LOAD_TEST_PY} path_to_main = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), - 'cmdline.py') + 'runner.py') class TestCli(test_case.MainTestCase): diff --git a/third_party/typ/typ/tests/runner_test.py b/third_party/typ/typ/tests/runner_test.py index 3409879..1d3b0cc 100644 --- a/third_party/typ/typ/tests/runner_test.py +++ b/third_party/typ/typ/tests/runner_test.py @@ -12,10 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import tempfile + from textwrap import dedent as d from typ import Host, Runner, TestCase, TestSet, TestInput +from typ import WinMultiprocessing, main def _setup_process(child, context): # pylint: disable=W0613 @@ -36,12 +39,12 @@ class RunnerTests(TestCase): def test_bad_default(self): r = Runner() - ret = r.main(foo='bar') + ret = r.main([], foo='bar') self.assertEqual(ret, 2) def test_good_default(self): r = Runner() - ret = r.main(tests=['typ.tests.runner_test.ContextTests']) + ret = r.main([], tests=['typ.tests.runner_test.ContextTests']) self.assertEqual(ret, 0) @@ -80,6 +83,159 @@ class TestSetTests(TestCase): h.rmtree(tmpdir) +class TestWinMultiprocessing(TestCase): + def make_host(self): + return Host() + + def call(self, argv, platform=None, importable=None, **kwargs): + h = self.make_host() + orig_wd = h.getcwd() + tmpdir = None + try: + tmpdir = h.mkdtemp() + h.chdir(tmpdir) + h.capture_output() + if platform is not None: + h.platform = platform + r = Runner(h) + if importable is not None: + r._main_is_importable = lambda: importable + ret = r.main(argv, **kwargs) + finally: + out, err = h.restore_output() + h.chdir(orig_wd) + if tmpdir: + h.rmtree(tmpdir) + + return ret, out, err + + def test_bad_value(self): + self.assertRaises(ValueError, self.call, [], win_multiprocessing='foo') + + def test_force(self): + h = self.make_host() + tmpdir = None + orig_wd = h.getcwd() + out = err = None + out_str = err_str = '' + try: + tmpdir = h.mkdtemp() + h.chdir(tmpdir) + out = tempfile.NamedTemporaryFile(delete=False) + err = tempfile.NamedTemporaryFile(delete=False) + ret = main([], stdout=out, stderr=err, + win_multiprocessing=WinMultiprocessing.force) + finally: + h.chdir(orig_wd) + if tmpdir: + h.rmtree(tmpdir) + if out: + out.close() + out = open(out.name) + out_str = out.read() + out.close() + h.remove(out.name) + if err: + err.close() + err = open(err.name) + err_str = err.read() + err.close() + h.remove(err.name) + + self.assertEqual(ret, 1) + self.assertEqual(out_str, 'No tests to run.\n') + self.assertEqual(err_str, '') + + def test_ignore(self): + h = self.make_host() + if h.platform == 'win32': # pragma: win32 + self.assertRaises(ValueError, self.call, [], + win_multiprocessing=WinMultiprocessing.ignore) + else: + result = self.call([], platform=None, importable=False, + win_multiprocessing=WinMultiprocessing.ignore) + ret, out, err = result + self.assertEqual(ret, 1) + self.assertEqual(out, 'No tests to run.\n') + self.assertEqual(err, '') + + def test_multiple_jobs(self): + self.assertRaises(ValueError, self.call, ['-j', '2'], + platform='win32', importable=False) + + def test_normal(self): + # This tests that typ itself is importable ... + ret, out, err = self.call([]) + self.assertEqual(ret, 1) + self.assertEqual(out, 'No tests to run.\n') + self.assertEqual(err, '') + + def test_real_unimportable_main(self): + h = self.make_host() + tmpdir = None + orig_wd = h.getcwd() + out = err = None + out_str = err_str = '' + try: + tmpdir = h.mkdtemp() + h.chdir(tmpdir) + out = tempfile.NamedTemporaryFile(delete=False) + err = tempfile.NamedTemporaryFile(delete=False) + path_above_typ = h.realpath(h.dirname(__file__), '..', '..') + env = {'PYTHONPATH': path_above_typ} + h.write_text_file('test', d(""" + import sys + import typ + sys.exit(typ.main()) + """)) + h.stdout = out + h.stderr = err + ret = h.call_inline([h.python_interpreter, h.join(tmpdir, 'test')], + env=env) + finally: + h.chdir(orig_wd) + if tmpdir: + h.rmtree(tmpdir) + if out: + out.close() + out = open(out.name) + out_str = out.read() + out.close() + h.remove(out.name) + if err: + err.close() + err = open(err.name) + err_str = err.read() + err.close() + h.remove(err.name) + + self.assertEqual(ret, 1) + self.assertEqual(out_str, '') + self.assertIn('ValueError: The __main__ module is not importable', + err_str) + + def test_run_serially(self): + ret, out, err = self.call([], importable=False, + win_multiprocessing=WinMultiprocessing.spawn) + self.assertEqual(ret, 1) + self.assertEqual(out, 'No tests to run.\n') + self.assertEqual(err, '') + + def test_single_job(self): + ret, out, err = self.call(['-j', '1'], platform='win32', + importable=False) + self.assertEqual(ret, 1) + self.assertEqual(out, 'No tests to run.\n') + self.assertEqual(err, '') + + def test_spawn(self): + ret, out, err = self.call([], importable=False, + win_multiprocessing=WinMultiprocessing.spawn) + self.assertEqual(ret, 1) + self.assertEqual(out, 'No tests to run.\n') + self.assertEqual(err, '') + + class ContextTests(TestCase): def test_context(self): # This test is mostly intended to be called by diff --git a/third_party/typ/typ/version.py b/third_party/typ/typ/version.py index 58b2468..96ebc7a 100644 --- a/third_party/typ/typ/version.py +++ b/third_party/typ/typ/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -VERSION = '0.8.4' +VERSION = '0.8.5' |