Chromium Code Reviews| Index: expect_tests/type_definitions.py |
| diff --git a/expect_tests/type_definitions.py b/expect_tests/type_definitions.py |
| index cd5c89ca2ae2ef08be506cfe86ce197777a221a6..9a3f99fd201184ce83b68709a2730dea1bbc6c10 100644 |
| --- a/expect_tests/type_definitions.py |
| +++ b/expect_tests/type_definitions.py |
| @@ -2,6 +2,7 @@ |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| +import copy |
| import inspect |
| import os |
| import re |
| @@ -148,17 +149,82 @@ class FuncCall(object): |
| return 'FuncCall(%r, *%r, **%r)' % (self.func, self.args, self.kwargs) |
| -_Test = namedtuple( |
| - 'Test', 'name func_call expect_dir expect_base ext covers breakpoints') |
| +class TestInfo(object): |
| + TEST_COVERS_MATCH = re.compile('.*/test/([^/]*)_test\.py$') |
|
dnj
2014/09/16 23:13:26
I know this is kind of outside of the immediate sc
|
| -TestInfo = namedtuple( |
| - 'TestInfo', 'name expect_dir expect_base ext') |
| + def __init__(self, name, expect_dir=None, expect_base=None, |
| + ext='json', covers=None, breakpoints=None): |
| + self._name = name |
| + self._expect_dir = expect_dir |
| + self._expect_base = expect_base |
| + self._ext = ext |
| + self._covers = covers |
| + self._breakpoints = breakpoints |
| + @property |
| + def name(self): |
| + return self._name |
| -class Test(_Test): |
| - TEST_COVERS_MATCH = re.compile('.*/test/([^/]*)_test\.py$') |
| + @property |
| + def expect_dir(self): |
| + return self._expect_dir |
| + |
| + @property |
| + def expect_base(self): |
| + return self._expect_base |
| + |
| + @property |
| + def ext(self): |
| + return self._ext |
| + |
| + @property |
| + def covers(self): |
| + return self._covers |
| + |
| + @property |
| + def breakpoints(self): |
| + return self._breakpoints |
| + |
| + @staticmethod |
| + def covers_obj(obj): |
| + test_file = inspect.getabsfile(obj) |
| + covers = [test_file] |
| + match = Test.TEST_COVERS_MATCH.match(test_file) |
|
dnj
2014/09/16 23:13:26
Make this a "classmethod" and use "cls.TEST_COVERS
|
| + if match: |
| + covers.append(os.path.join( |
| + os.path.dirname(os.path.dirname(test_file)), |
| + match.group(1) + '.py' |
| + )) |
| + return covers |
| + |
| + @staticmethod |
| + def expect_dir_obj(obj): |
| + test_file = inspect.getabsfile(obj) |
| + return os.path.splitext(test_file)[0] + '.expected' |
| + |
| + def coverage_includes(self): |
| + if self.covers is not None: |
| + return self.covers |
| + return self.covers_obj(self.func_call.func) |
| - def __new__(cls, name, func_call, expect_dir=None, expect_base=None, |
| + def expect_path(self, ext=None): |
| + expect_dir = self.expect_dir |
| + if expect_dir is None: |
| + expect_dir = self.expect_dir_obj(self.func_call.func) |
| + name = self.expect_base or self.name |
| + name = ''.join('_' if c in '<>:"\\/|?*\0' else c for c in name) |
| + return os.path.join(expect_dir, name + ('.%s' % (ext or self.ext))) |
| + |
| + def restrict(self, tests): |
| + assert tests[0] is self |
| + return self |
| + |
| + def get_info(self): |
| + return self |
| + |
| + |
| +class Test(TestInfo): |
| + def __init__(self, name, func_call, expect_dir=None, expect_base=None, |
| ext='json', covers=None, breakpoints=None, break_funcs=()): |
| """Create a new test. |
| @@ -184,7 +250,10 @@ class Test(_Test): |
| @param break_funcs: A list of functions for which to set breakpoints. |
| """ |
| breakpoints = breakpoints or [] |
| + self._func_call = func_call |
| + |
| if not breakpoints or break_funcs: |
| + breakpoints = copy.copy(breakpoints) |
| for f in (break_funcs or (func_call.func,)): |
| if hasattr(f, 'im_func'): |
| f = f.im_func |
| @@ -194,76 +263,89 @@ class Test(_Test): |
| if expect_dir: |
| expect_dir = expect_dir.rstrip('/') |
| - return super(Test, cls).__new__(cls, name, func_call, expect_dir, |
| - expect_base, ext, covers, breakpoints) |
| + super(Test, self).__init__(name, expect_dir, expect_base, |
| + ext, covers, breakpoints) |
| - |
| - @staticmethod |
| - def covers_obj(obj): |
| - test_file = inspect.getabsfile(obj) |
| - covers = [test_file] |
| - match = Test.TEST_COVERS_MATCH.match(test_file) |
| - if match: |
| - covers.append(os.path.join( |
| - os.path.dirname(os.path.dirname(test_file)), |
| - match.group(1) + '.py' |
| - )) |
| - return covers |
| - |
| - @staticmethod |
| - def expect_dir_obj(obj): |
| - test_file = inspect.getabsfile(obj) |
| - return os.path.splitext(test_file)[0] + '.expected' |
| - |
| - def coverage_includes(self): |
| - if self.covers is not None: |
| - return self.covers |
| - return self.covers_obj(self.func_call.func) |
| - |
| - def expect_path(self, ext=None): |
| - expect_dir = self.expect_dir |
| - if expect_dir is None: |
| - expect_dir = self.expect_dir_obj(self.func_call.func) |
| - name = self.expect_base or self.name |
| - name = ''.join('_' if c in '<>:"\\/|?*\0' else c for c in name) |
| - return os.path.join(expect_dir, name + ('.%s' % (ext or self.ext))) |
| + @property |
| + def func_call(self): |
| + return self._func_call |
| def run(self, context=None): |
| return self.func_call(context=context) |
| def process(self, func=lambda test: test.run()): |
| - """Applies |func| to the test, and yields (self, func(self)). |
| + """Applies |func| to the test, and yields (self.get_info(), func(self)). |
| For duck-typing compatibility with MultiTest. |
| Bind(name='context') if used by your test function, is bound to None. |
| - Used interally by expect_tests, you're not expected to call this yourself. |
| + Used internally by expect_tests, you're not expected to call this yourself. |
| """ |
| - yield self, func(self.bind(context=None)) |
| + yield self.get_info(), func(self.bind(context=None)) |
| def bind(self, *args, **kwargs): |
| - return self._replace(func_call=self.func_call.bind(*args, **kwargs)) |
| - |
| - def restrict(self, tests): |
| - assert tests[0] is self |
| - return self |
| + return Test(self._name, |
| + self._func_call.bind(*args, **kwargs), |
| + expect_dir=self.expect_dir, |
| + expect_base=self.expect_base, |
| + ext=self._ext, |
| + covers=self._covers, |
| + breakpoints=self._breakpoints) |
| def get_info(self): |
| - """Strips test instance of hard-to-pickle stuff |
| + """Strips test instance of information required for running test. |
| Returns a TestInfo instance. |
| """ |
| - return TestInfo(self.name, self.expect_dir, self.expect_base, self.ext) |
| + return TestInfo(self.name, |
| + expect_dir=self._expect_dir, |
| + expect_base=self._expect_base, |
| + ext=self._ext, |
| + covers=self._covers, |
| + breakpoints=self._breakpoints) |
| -_MultiTest = namedtuple( |
| - 'MultiTest', 'name make_ctx_call destroy_ctx_call tests atomic') |
| +class MultiTestInfo(object): |
| + def __init__(self, name, tests, atomic): |
| + self._name = name |
| + self._tests = tests |
| + self._atomic = atomic |
| -MultiTestInfo = namedtuple('MultiTestInfo', 'name tests atomic') |
| + @property |
| + def name(self): |
| + return self._name |
| + @property |
| + def tests(self): |
| + return self._tests |
| -class MultiTest(_MultiTest): |
| + @property |
| + def atomic(self): |
| + return self._atomic |
| + |
| + @staticmethod |
| + def expect_path(_ext=None): |
| + return None |
| + |
| + def restrict(self, tests): |
| + """A helper method to re-cast the MultiTest with fewer subtests. |
| + |
| + All fields will be identical except for tests. If this MultiTest is atomic, |
| + then this method returns |self|. |
| + |
| + Used interally by expect_tests, you're not expected to call this yourself. |
| + """ |
| + if self.atomic: |
| + return self |
| + assert all(t in self.tests for t in tests) |
| + return MultiTestInfo(self._name, tests, self._atomic) |
| + |
| + def get_info(self): |
| + return self |
| + |
| + |
| +class MultiTest(MultiTestInfo): |
| """A wrapper around one or more Test instances. |
| Allows the entire group to have common pre- and post- actions and an optional |
| @@ -285,18 +367,18 @@ class MultiTest(_MultiTest): |
| either all at once, or not at all (i.e., subtests may not be filtered). |
| """ |
| - def restrict(self, tests): |
| - """A helper method to re-cast the MultiTest with fewer subtests. |
| + def __init__(self, name, make_ctx_call, destroy_ctx_call, tests, atomic): |
| + self._make_ctx_call = make_ctx_call |
| + self._destroy_ctx_call = destroy_ctx_call |
| + super(MultiTest, self).__init__(name, tests, atomic) |
| - All fields will be identical except for tests. If this MultiTest is atomic, |
| - then this method returns |self|. |
| + @property |
| + def make_ctx_call(self): |
| + return self._make_ctx_call |
| - Used interally by expect_tests, you're not expected to call this yourself. |
| - """ |
| - if self.atomic: |
| - return self |
| - assert all(t in self.tests for t in tests) |
| - return self._replace(tests=tests) |
| + @property |
| + def destroy_ctx_call(self): |
| + return self._destroy_ctx_call |
| def process(self, func=lambda test: test.run()): |
| """Applies |func| to each sub-test, with properly bound context. |
| @@ -316,13 +398,23 @@ class MultiTest(_MultiTest): |
| ctx_object = self.make_ctx_call() |
| try: |
| for test in self.tests: |
| - yield test, func(test.bind(context=ctx_object)) |
| + yield test.get_info(), func(test.bind(context=ctx_object)) |
| finally: |
| self.destroy_ctx_call.bind(context=ctx_object)() |
| - @staticmethod |
| - def expect_path(_ext=None): |
| - return None |
| + def restrict(self, tests): |
| + """A helper method to re-cast the MultiTest with fewer subtests. |
| + |
| + All fields will be identical except for tests. If this MultiTest is atomic, |
| + then this method returns |self|. |
| + |
| + Used internally by expect_tests, you're not expected to call this yourself. |
| + """ |
| + if self.atomic: |
| + return self |
| + assert all(t in self.tests for t in tests) |
| + return MultiTest(self._name, self._make_ctx_call, self._destroy_ctx_call, |
| + tests, self._atomic) |
| def get_info(self): |
| """Strips MultiTest instance of hard-to-pickle stuff |