Bug 776035 part 2 - Add an enhanced MockedOpen facility to mozunit.py. r=ted
authorMike Hommey <mh+mozilla@glandium.org>
Wed, 25 Jul 2012 07:44:08 +0200
changeset 100436 c94f0e69113c9c533d013cd742baf282268902f0
parent 100435 284ee7c9fb336aff59b3e6756725696bd3bf4540
child 100437 4c78a8cc97bcc1ee0e7c9061a356285bb444ff64
push id23182
push useremorley@mozilla.com
push dateThu, 26 Jul 2012 12:04:56 +0000
treeherdermozilla-central@20db7c6d82cc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersted
bugs776035
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 776035 part 2 - Add an enhanced MockedOpen facility to mozunit.py. r=ted
config/Makefile.in
config/mozunit.py
config/tests/unit-mozunit.py
--- a/config/Makefile.in
+++ b/config/Makefile.in
@@ -156,16 +156,17 @@ PYUNITS := \
   unit-Expression.py \
   unit-Preprocessor.py \
   unit-nsinstall.py \
   unit-printprereleasesuffix.py \
   unit-JarMaker.py \
   unit-buildlist.py \
   unit-expandlibs.py \
   unit-writemozinfo.py \
+  unit-mozunit.py \
   $(NULL)
 
 check-preqs = \
   check-python-modules \
   check-jar-mn \
   check-makefiles \
   $(NULL)
 
--- a/config/mozunit.py
+++ b/config/mozunit.py
@@ -1,15 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from unittest import TextTestRunner as _TestRunner, TestResult as _TestResult
 import unittest
 import inspect
+from StringIO import StringIO
+import os
 
 '''Helper to make python unit tests report the way that the Mozilla
 unit test infrastructure expects tests to report.
 
 Usage:
 
 import unittest
 import mozunit
@@ -65,10 +67,73 @@ class MozTestRunner(_TestRunner):
     def _makeResult(self):
         return _MozTestResult(self.stream, self.descriptions)
     def run(self, test):
         result = self._makeResult()
         test(result)
         result.printErrorList()
         return result
 
+class MockedFile(StringIO):
+    def __init__(self, context, filename, content = ''):
+        self.context = context
+        self.name = filename
+        StringIO.__init__(self, content)
+
+    def close(self):
+        self.context.files[self.name] = self.getvalue()
+        StringIO.close(self)
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        self.close()
+
+class MockedOpen(object):
+    '''
+    Context manager diverting the open builtin such that opening files
+    can open "virtual" file instances given when creating a MockedOpen.
+
+    with MockedOpen({'foo': 'foo', 'bar': 'bar'}):
+        f = open('foo', 'r')
+
+    will thus open the virtual file instance for the file 'foo' to f.
+
+    MockedOpen also masks writes, so that creating or replacing files
+    doesn't touch the file system, while subsequently opening the file
+    will return the recorded content.
+
+    with MockedOpen():
+        f = open('foo', 'w')
+        f.write('foo')
+    self.assertRaises(Exception,f.open('foo', 'r'))
+    '''
+    def __init__(self, files = {}):
+        self.files = {}
+        for name, content in files.iteritems():
+            self.files[os.path.abspath(name)] = content
+
+    def __call__(self, name, mode = 'r'):
+        absname = os.path.abspath(name)
+        if 'w' in mode:
+            file = MockedFile(self, absname)
+        elif absname in self.files:
+            file = MockedFile(self, absname, self.files[absname])
+        elif 'a' in mode:
+            file = MockedFile(self, absname, self.open(name, 'r').read())
+        else:
+            file = self.open(name, mode)
+        if 'a' in mode:
+            file.seek(0, os.SEEK_END)
+        return file
+
+    def __enter__(self):
+        import __builtin__
+        self.open = __builtin__.open
+        __builtin__.open = self
+
+    def __exit__(self, type, value, traceback):
+        import __builtin__
+        __builtin__.open = self.open
+
 def main(*args):
     unittest.main(testRunner=MozTestRunner(),*args)
new file mode 100644
--- /dev/null
+++ b/config/tests/unit-mozunit.py
@@ -0,0 +1,75 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import with_statement
+import sys
+import os
+from mozunit import main, MockedOpen
+import unittest
+from tempfile import mkstemp
+
+class TestMozUnit(unittest.TestCase):
+    def test_mocked_open(self):
+        # Create a temporary file on the file system.
+        (fd, path) = mkstemp()
+        with os.fdopen(fd, 'w') as file:
+            file.write('foobar');
+
+        with MockedOpen({'file1': 'content1',
+                         'file2': 'content2'}):
+            # Check the contents of the files given at MockedOpen creation.
+            self.assertEqual(open('file1', 'r').read(), 'content1')
+            self.assertEqual(open('file2', 'r').read(), 'content2')
+
+            # Check that overwriting these files alters their content.
+            with open('file1', 'w') as file:
+                file.write('foo')
+            self.assertEqual(open('file1', 'r').read(), 'foo')
+
+            # ... but not until the file is closed.
+            file = open('file2', 'w')
+            file.write('bar')
+            self.assertEqual(open('file2', 'r').read(), 'content2')
+            file.close()
+            self.assertEqual(open('file2', 'r').read(), 'bar')
+
+            # Check that appending to a file does append
+            with open('file1', 'a') as file:
+                file.write('bar')
+            self.assertEqual(open('file1', 'r').read(), 'foobar')
+
+            # Opening a non-existing file ought to fail.
+            self.assertRaises(IOError, open, 'file3', 'r')
+
+            # Check that writing a new file does create the file.
+            with open('file3', 'w') as file:
+                file.write('baz')
+            self.assertEqual(open('file3', 'r').read(), 'baz')
+
+            # Check the content of the file created outside MockedOpen.
+            self.assertEqual(open(path, 'r').read(), 'foobar')
+
+            # Check that overwriting a file existing on the file system
+            # does modify its content.
+            with open(path, 'w') as file:
+                file.write('bazqux')
+            self.assertEqual(open(path, 'r').read(), 'bazqux')
+
+        with MockedOpen():
+            # Check that appending to a file existing on the file system
+            # does modify its content.
+            with open(path, 'a') as file:
+                file.write('bazqux')
+            self.assertEqual(open(path, 'r').read(), 'foobarbazqux')
+
+        # Check that the file was not actually modified on the file system.
+        self.assertEqual(open(path, 'r').read(), 'foobar')
+        os.remove(path)
+
+        # Check that the file created inside MockedOpen wasn't actually
+        # created.
+        self.assertRaises(IOError, open, 'file3', 'r')
+
+if __name__ == "__main__":
+    main()