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 idunknown
push userunknown
push dateunknown
reviewersted
bugs776035
milestone17.0a1
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()