nose-devs / nose

nose is nicer testing for python
http://readthedocs.org/docs/nose/en/latest/
1.36k stars 395 forks source link

Nose output capture is incompatible with mox ExpectedMethodCallsError #529

Open jeblair opened 12 years ago

jeblair commented 12 years ago

When using mox, if the code under test fails to call an expected method, mox raises an ExpectedMethodCallsError, which produces a nicely formatted list of methods which were not called, like this:

ExpectedMethodCallsError: Verify: Expected methods never called:
  0.  foo() -> None
  1.  bar() -> None

However, when using the nose output capture plugin (the default behavior for nose), and output is sent to stdout, this is the result:

http://paste.openstack.org/show/19473/

That's the entire contents of the captured output printed one character per line. This doesn't happen if nothing is sent to stdout, and it also doesn't happen if output capture is disabled (--nocapture). It seems to be related to nose reconstructing the raised exception by passing formatted output back in as an argument. The constructor for ExpectedMethodCallsError expects a deque object, not a string, which is what nose supplies when reconstructing it. Here's a stack trace from nose's invocation of the constructor:

  File "/tmp/foo/bin/nosetests", line 9, in 
    load_entry_point('nose==1.1.2', 'console_scripts', 'nosetests')()
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/core.py", line 118, in __init__
    **extra_args)
  File "/usr/lib/python2.7/unittest/main.py", line 95, in __init__
    self.runTests()
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/core.py", line 197, in runTests
    result = self.testRunner.run(self.test)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/core.py", line 61, in run
    test(result)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/suite.py", line 176, in __call__
    return self.run(*arg, **kw)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/suite.py", line 223, in run
    test(orig)
  File "/usr/lib/python2.7/unittest/suite.py", line 70, in __call__
    return self.run(*args, **kwds)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/suite.py", line 74, in run
    test(result)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/suite.py", line 176, in __call__
    return self.run(*arg, **kw)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/suite.py", line 223, in run
    test(orig)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/suite.py", line 176, in __call__
    return self.run(*arg, **kw)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/suite.py", line 223, in run
    test(orig)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/case.py", line 45, in __call__
    return self.run(*arg, **kwarg)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/case.py", line 133, in run
    self.runTest(result)
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/case.py", line 151, in runTest
    test(result)
  File "/usr/lib/python2.7/unittest/case.py", line 391, in __call__
    return self.run(*args, **kwds)
  File "/usr/lib/python2.7/unittest/case.py", line 331, in run
    result.addFailure(self, sys.exc_info())
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/proxy.py", line 150, in addFailure
    self.result.addFailure(self.test, self._prepareErr(err))
  File "/tmp/foo/local/lib/python2.7/site-packages/nose/proxy.py", line 95, in _prepareErr
    value = err[0](err[1])
  File "/tmp/foo/local/lib/python2.7/site-packages/mox.py", line 104, in __init__
    traceback.print_stack()

Here is the simple test case I used to generate the output above:

import mox
import unittest

class MyTest(unittest.TestCase):
    def setUp(self):
        self.mox = mox.Mox()

    def tearDown(self):
        self.mox.UnsetStubs()

    def test_works(self):
        """This test fails, but it fails normally, without exhibiting the problem with the mox exception."""
        mock = self.mox.CreateMockAnything()
        mock.foo()
        mock.bar()
        self.mox.ReplayAll()
        self.mox.VerifyAll()

    def test_fails(self):
        """This fails and exhibits the problem with the mox exception."""
        mock = self.mox.CreateMockAnything()
        mock.foo()
        mock.bar()
        self.mox.ReplayAll()
        print "some output"
        self.mox.VerifyAll()
rdharrison2 commented 12 years ago

This also occurs when running twisted trial unit tests that leave the reactor dirty. In this case trial raises a twisted.trial.util.DirtyReactorAggregateError error which similarly takes a list as first parameter. Quite annoying...

russellb commented 12 years ago

workaround discussed here: http://stackoverflow.com/questions/6124180/python-nose-tests-actually-the-error-is-from-mox-print-out-errors-one-characte

wiseman commented 12 years ago

The workarounds are ugly. This seems like a very misguided assumption in ResultProxy._prepareError that one can recreate an exception instance by calling its class' constructor with its string representation as the (only) argument!