Mocking __builtins__.open() in tests breaks coverage

Issue #452 invalid
Krzysztof Pawlik created an issue

Mocking builtins.open() in tests breaks coverage: coverage will fail to write the .coverage file (mock intercepts the open() call). To work around this it would be ok to either isolate_module() builtins or use os.fdopen()/os.open() pair like in snippet https://bitbucket.org/snippets/nelchael/rLd6y.

Comments (5)

  1. Ned Batchelder repo owner

    @nelchael Thanks for the report. Can you provide a small test case to reproduce the issue?

  2. Krzysztof Pawlik reporter

    Sure, this is simple case that shows the error:

    #!/usr/bin/python2
    
    import __builtin__
    import mox
    import StringIO
    import unittest
    
    class TestedClass(object):
            def doIt(self):
                    f = open('/dev/shm/foo', 'w')
                    f.write('Something')
                    f.close()
                    return True
    
    class TestForTestedClass(unittest.TestCase):
            def setUp(self):
                    self.mox = mox.Mox()
    
            def tearDown(self):
                    self.mox.UnsetStubs()
    
            def test(self):
                    self.mox.StubOutWithMock(__builtins__, 'open')
    
                    __builtin__.open('/dev/shm/foo', 'w').AndReturn(StringIO.StringIO())
    
                    self.mox.ReplayAll()
    
                    testedClass = TestedClass()
                    self.assertFalse(testedClass.doIt())
    
                    self.mox.VerifyAll()
    
    if __name__ == '__main__':
            unittest.main()
    

    And the error:

    Traceback (most recent call last):
      File "/home/nelchael/.local/bin/coverage", line 9, in <module>
        load_entry_point('coverage==4.0.3', 'console_scripts', 'coverage')()
      File "/home/nelchael/.local/lib/python2.7/site-packages/coverage/cmdline.py", line 741, in main
        status = CoverageScript().command_line(argv)
      File "/home/nelchael/.local/lib/python2.7/site-packages/coverage/cmdline.py", line 481, in command_line
        return self.do_run(options, args)
      File "/home/nelchael/.local/lib/python2.7/site-packages/coverage/cmdline.py", line 631, in do_run
        self.coverage.save()
      File "/home/nelchael/.local/lib/python2.7/site-packages/coverage/control.py", line 765, in save
        self.data_files.write(self.data, suffix=self.data_suffix)
      File "/home/nelchael/.local/lib/python2.7/site-packages/coverage/data.py", line 665, in write
        data.write_file(filename)
      File "/home/nelchael/.local/lib/python2.7/site-packages/coverage/data.py", line 458, in write_file
        with open(filename, 'w') as fdata:
      File "/usr/lib/python2.7/site-packages/mox.py", line 1000, in __call__
        expected_method = self._VerifyMethodCall()
      File "/usr/lib/python2.7/site-packages/mox.py", line 1047, in _VerifyMethodCall
        expected = self._PopNextMethod()
      File "/usr/lib/python2.7/site-packages/mox.py", line 1033, in _PopNextMethod
        raise UnexpectedMethodCallError(self, None)
    mox.UnexpectedMethodCallError: Unexpected method call Stub for <built-in function open>.__call__('/home/nelchael/.coverage', 'w') -> None
    

    I've changed data.py/write_file() to this:

        def write_file(self, filename):
            """Write the coverage data to `filename`."""
            if self._debug and self._debug.should('dataio'):
                self._debug.write("Writing data to %r" % (filename,))
            with os.fdopen(os.open(filename, os.O_WRONLY|os.O_CREAT), 'w') as fdata:
                self.write_fileobj(fdata)
    
  3. Ned Batchelder repo owner

    A better approach would be to narrow your mock. If you are using open in product.py, instead of mocking "builtin.open", mock "product.open". That way, only the use of open in your file will be affected. Would that work?

  4. Ned Batchelder repo owner

    I'm going to close this issue. I don't think it's reasonable for coverage to try to avoid the standard library so that overly aggressive mocks will continue to work. Maybe I'm wrong. Let me know.

  5. Log in to comment