| Index: expect_tests/type_definitions.py
|
| diff --git a/expect_tests/type_definitions.py b/expect_tests/type_definitions.py
|
| index cd5c89ca2ae2ef08be506cfe86ce197777a221a6..6ac2fdd622416160ff39675158e43bb600271186 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,10 @@ 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 Test(object):
|
| + TEST_COVERS_MATCH = re.compile('.*/test/([^/]+)_test\.py$')
|
|
|
| -TestInfo = namedtuple(
|
| - 'TestInfo', 'name expect_dir expect_base ext')
|
| -
|
| -
|
| -class Test(_Test):
|
| - TEST_COVERS_MATCH = re.compile('.*/test/([^/]*)_test\.py$')
|
| -
|
| - def __new__(cls, name, func_call, expect_dir=None, expect_base=None,
|
| + def __init__(self, name, func_call, expect_dir=None, expect_base=None,
|
| ext='json', covers=None, breakpoints=None, break_funcs=()):
|
| """Create a new test.
|
|
|
| @@ -183,87 +177,125 @@ class Test(_Test):
|
| mode. See |break_funcs| for an easier way to set this.
|
| @param break_funcs: A list of functions for which to set breakpoints.
|
| """
|
| - breakpoints = breakpoints or []
|
| - if not breakpoints or break_funcs:
|
| - for f in (break_funcs or (func_call.func,)):
|
| - if hasattr(f, 'im_func'):
|
| - f = f.im_func
|
| - breakpoints.append((f.func_code.co_filename,
|
| - f.func_code.co_firstlineno,
|
| - f.func_code.co_name))
|
| -
|
| + self._func_call = func_call
|
| + self._name = name
|
| if expect_dir:
|
| expect_dir = expect_dir.rstrip('/')
|
| - return super(Test, cls).__new__(cls, name, func_call, expect_dir,
|
| - expect_base, ext, covers, breakpoints)
|
| + self._expect_dir = expect_dir
|
| + self._expect_base = expect_base
|
| + self._ext = ext
|
| + self._covers = covers
|
| + self._breakpoints = copy.copy(breakpoints) if breakpoints else []
|
| +
|
| + if self._func_call:
|
| + if not self._breakpoints or break_funcs:
|
| + for f in (break_funcs or (func_call.func,)):
|
| + if hasattr(f, 'im_func'):
|
| + f = f.im_func
|
| + self._breakpoints.append((f.func_code.co_filename,
|
| + f.func_code.co_firstlineno,
|
| + f.func_code.co_name))
|
|
|
| + @property
|
| + def name(self):
|
| + return self._name
|
|
|
| - @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
|
| + @property
|
| + def func_call(self):
|
| + return self._func_call
|
|
|
| - @staticmethod
|
| - def expect_dir_obj(obj):
|
| - test_file = inspect.getabsfile(obj)
|
| - return os.path.splitext(test_file)[0] + '.expected'
|
| + @property
|
| + def expect_dir(self):
|
| + return self._expect_dir
|
|
|
| - def coverage_includes(self):
|
| - if self.covers is not None:
|
| - return self.covers
|
| - return self.covers_obj(self.func_call.func)
|
| + @property
|
| + def expect_base(self):
|
| + return self._expect_base
|
|
|
| - 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 ext(self):
|
| + return self._ext
|
| +
|
| + @property
|
| + def covers(self):
|
| + return self._covers
|
| +
|
| + @property
|
| + def breakpoints(self):
|
| + return self._breakpoints
|
|
|
| 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))
|
| + 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 restrict(self, tests):
|
| assert tests[0] is self
|
| return self
|
|
|
| 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 Test(self.name,
|
| + None,
|
| + expect_dir=self._expect_dir,
|
| + expect_base=self._expect_base,
|
| + ext=self._ext,
|
| + covers=self._covers,
|
| + breakpoints=self._breakpoints)
|
|
|
| + def coverage_includes(self):
|
| + if self._covers is not None:
|
| + return self._covers
|
| + return self.covers_obj(self._func_call.func)
|
| +
|
| + @classmethod
|
| + def covers_obj(cls, obj):
|
| + test_file = inspect.getabsfile(obj)
|
| + covers = [test_file]
|
| + match = cls.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
|
|
|
| -_MultiTest = namedtuple(
|
| - 'MultiTest', 'name make_ctx_call destroy_ctx_call tests atomic')
|
| + 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,)))
|
|
|
| -MultiTestInfo = namedtuple('MultiTestInfo', 'name tests atomic')
|
| + @staticmethod
|
| + def expect_dir_obj(obj):
|
| + test_file = inspect.getabsfile(obj)
|
| + return os.path.splitext(test_file)[0] + '.expected'
|
|
|
|
|
| -class MultiTest(_MultiTest):
|
| +class MultiTest(object):
|
| """A wrapper around one or more Test instances.
|
|
|
| Allows the entire group to have common pre- and post- actions and an optional
|
| @@ -285,18 +317,32 @@ 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._name = name
|
| + self._make_ctx_call = make_ctx_call
|
| + self._destroy_ctx_call = destroy_ctx_call
|
| + self._tests = tests
|
| + self._atomic = atomic
|
|
|
| - All fields will be identical except for tests. If this MultiTest is atomic,
|
| - then this method returns |self|.
|
| + @property
|
| + def name(self):
|
| + return self._name
|
|
|
| - 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 make_ctx_call(self):
|
| + return self._make_ctx_call
|
| +
|
| + @property
|
| + def destroy_ctx_call(self):
|
| + return self._destroy_ctx_call
|
| +
|
| + @property
|
| + def tests(self):
|
| + return self._tests
|
| +
|
| + @property
|
| + def atomic(self):
|
| + return self._atomic
|
|
|
| def process(self, func=lambda test: test.run()):
|
| """Applies |func| to each sub-test, with properly bound context.
|
| @@ -316,13 +362,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
|
| @@ -330,12 +386,14 @@ class MultiTest(_MultiTest):
|
| Returns a MultiTestInfo instance.
|
| """
|
| all_tests = [test.get_info() for test in self.tests]
|
| - test = MultiTestInfo(name=self.name,
|
| - tests=all_tests,
|
| - atomic=self.atomic
|
| - )
|
| + test = MultiTest(self._name, None, None, all_tests, self._atomic)
|
| return test
|
|
|
| + @staticmethod
|
| + def expect_path(_ext=None):
|
| + return None
|
| +
|
| +
|
|
|
| class Handler(object):
|
| """Handler object.
|
|
|