Skip to content

Commit

Permalink
Merge pull request #951 from LewisHaley/fix-test-repr-with-mutable-args
Browse files Browse the repository at this point in the history
Fix test repr with mutable args
  • Loading branch information
jszakmeister committed Nov 28, 2015
2 parents 7c8f005 + c37ce12 commit ad63d9a
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 29 deletions.
50 changes: 21 additions & 29 deletions nose/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def runTest(self):
def shortDescription(self):
if hasattr(self.test, 'description'):
return self.test.description
func, arg = self._descriptors()
func = self._descriptor()
doc = getattr(func, '__doc__', None)
if not doc:
doc = str(self)
Expand Down Expand Up @@ -239,6 +239,7 @@ def __init__(self, test, setUp=None, tearDown=None, arg=tuple(),
self.setUpFunc = setUp
self.tearDownFunc = tearDown
self.arg = arg
self.arg_repr = repr(self.arg)
self.descriptor = descriptor
TestBase.__init__(self)

Expand Down Expand Up @@ -276,32 +277,27 @@ def tearDown(self):
try_run(self.test, names)

def __str__(self):
func, arg = self._descriptors()
func = self._descriptor()
if hasattr(func, 'compat_func_name'):
name = func.compat_func_name
else:
name = func.__name__
name = "%s.%s" % (func.__module__, name)
if arg:
name = "%s%s" % (name, arg)
if not self.arg_repr == '()':
name = "%s%s" % (name, self.arg_repr)
# FIXME need to include the full dir path to disambiguate
# in cases where test module of the same name was seen in
# another directory (old fromDirectory)
return name
__repr__ = __str__

def _descriptors(self):
"""Get the descriptors of the test function: the function and
arguments that will be used to construct the test name. In
most cases, this is the function itself and no arguments. For
tests generated by generator functions, the original
(generator) function and args passed to the generated function
are returned.
def _descriptor(self):
"""Get the descriptor of the test method.
If a `descriptor` was specified for __init__, then it is returned, else
the test method (callable object) is returned.
"""
if self.descriptor:
return self.descriptor, self.arg
else:
return self.test, self.arg
return self.descriptor if self.descriptor is not None else self.test


class MethodTestCase(TestBase):
Expand Down Expand Up @@ -338,6 +334,7 @@ def __init__(self, method, test=None, arg=tuple(), descriptor=None):
self.method = method
self.test = test
self.arg = arg
self.arg_repr = repr(self.arg)
self.descriptor = descriptor
if isfunction(method):
raise ValueError("Unbound methods must be wrapped using pyversion.unbound_method before passing to MethodTestCase")
Expand All @@ -349,16 +346,16 @@ def __init__(self, method, test=None, arg=tuple(), descriptor=None):
TestBase.__init__(self)

def __str__(self):
func, arg = self._descriptors()
func = self._descriptor()
if hasattr(func, 'compat_func_name'):
name = func.compat_func_name
else:
name = func.__name__
name = "%s.%s.%s" % (self.cls.__module__,
self.cls.__name__,
name)
if arg:
name = "%s%s" % (name, arg)
if not self.arg_repr == '()':
name = "%s%s" % (name, self.arg_repr)
return name
__repr__ = __str__

Expand All @@ -383,15 +380,10 @@ def setUp(self):
def tearDown(self):
try_run(self.inst, ('teardown', 'tearDown'))

def _descriptors(self):
"""Get the descriptors of the test method: the method and
arguments that will be used to construct the test name. In
most cases, this is the method itself and no arguments. For
tests generated by generator methods, the original
(generator) method and args passed to the generated method
or function are returned.
def _descriptor(self):
"""Get the descriptor of the test method.
If a `descriptor` was specified for __init__, then it is returned, else
the test method (callable object) is returned.
"""
if self.descriptor:
return self.descriptor, self.arg
else:
return self.method, self.arg
return self.descriptor if self.descriptor is not None else self.method
40 changes: 40 additions & 0 deletions unit_tests/test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,46 @@ def test_failure_case(self):
f(res)
assert res.errors

def test_FunctionTestCase_repr_is_consistent_with_mutable_args(self):
class Foo(object):
def __init__(self):
self.bar = 'unmodified'
def __repr__(self):
return "Foo(%s)" % self.bar

def test_foo(foo):
pass

foo = Foo()
case = nose.case.FunctionTestCase(test_foo, arg=(foo,))
case_repr_before = case.__repr__()
foo.bar = "snafu'd!"
case_repr_after = case.__repr__()
assert case_repr_before == case_repr_after, (
"Modifying a mutable object arg during test case changed the test "
"case's __repr__")

def test_MethodTestCase_repr_is_consistent_with_mutable_args(self):
class Foo(object):
def __init__(self):
self.bar = 'unmodified'
def __repr__(self):
return "Foo(%s)" % self.bar

class FooTester(object):
def test_foo(self, foo):
pass

foo = Foo()
case = nose.case.FunctionTestCase(
unbound_method(FooTester, FooTester.test_foo), arg=(foo,))
case_repr_before = case.__repr__()
foo.bar = "snafu'd!"
case_repr_after = case.__repr__()
assert case_repr_before == case_repr_after, (
"Modifying a mutable object arg during test case changed the test "
"case's __repr__")


class TestNoseTestWrapper(unittest.TestCase):
def test_case_fixtures_called(self):
Expand Down

0 comments on commit ad63d9a

Please # to comment.