diff tests/mockutil.py @ 6:f5ca5c5bed85

More Python 3 fixes, modularization, and new unit tests.
author Ludovic Chabant <ludovic@chabant.com>
date Sat, 16 Aug 2014 08:15:30 -0700
parents f485ba500df3
children 617191dec18e
line wrap: on
line diff
--- a/tests/mockutil.py	Mon Aug 11 22:36:47 2014 -0700
+++ b/tests/mockutil.py	Sat Aug 16 08:15:30 2014 -0700
@@ -1,9 +1,190 @@
+import io
+import time
+import random
+import codecs
+import os.path
+import functools
 import mock
+import yaml
 from piecrust.app import PieCrust, PieCrustConfiguration
 
 
+resources_path = os.path.abspath(
+            os.path.join(
+            os.path.dirname(__file__),
+            '..', 'piecrust', 'resources'))
+
+
 def get_mock_app(config=None):
     app = mock.MagicMock(spec=PieCrust)
     app.config = PieCrustConfiguration()
     return app
 
+
+def with_mock_fs_app(f):
+    @functools.wraps(f)
+    def wrapper(app, *args, **kwargs):
+        with mock_fs_scope(app):
+            real_app = app.getApp()
+            return f(real_app, *args, **kwargs)
+    return wrapper
+
+
+class mock_fs(object):
+    def __init__(self, default_spec=True):
+        self._root = 'root_%d' % random.randrange(1000)
+        self._fs = {self._root: {}}
+        if default_spec:
+            self.withDir('counter')
+            self.withFile('kitchen/_content/config.yml',
+                    "site:\n  title: Mock Website\n")
+
+    def path(self, p):
+        if p in ['/', '', None]:
+            return '/%s' % self._root
+        return '/%s/%s' % (self._root, p.lstrip('/'))
+
+    def getApp(self):
+        root_dir = self.path('/kitchen')
+        return PieCrust(root_dir, cache=False)
+
+    def withDir(self, path):
+        cur = self._fs[self._root]
+        for b in path.split('/'):
+            if b not in cur:
+                cur[b] = {}
+            cur = cur[b]
+        return self
+
+    def withFile(self, path, contents):
+        cur = self._fs[self._root]
+        bits = path.split('/')
+        for b in bits[:-1]:
+            if b not in cur:
+                cur[b] = {}
+            cur = cur[b]
+        cur[bits[-1]] = (contents, {'mtime': time.time()})
+        return self
+
+    def withAsset(self, path, contents):
+        return self.withFile('kitchen/' + path, contents)
+
+    def withAssetDir(self, path):
+        return self.withDir('kitchen/' + path)
+
+    def withConfig(self, config):
+        return self.withFile('kitchen/_content/config.yml',
+                yaml.dump(config))
+
+    def withThemeConfig(self, config):
+        return self.withFile('kitchen/_content/theme/_content/theme_config.yml',
+                yaml.dump(config))
+
+    def withPage(self, url, config=None, contents=None):
+        config = config or {}
+        contents = contents or "A test page."
+        text = "---\n"
+        text += yaml.dump(config)
+        text += "---\n"
+        text += contents
+
+        name, ext = os.path.splitext(url)
+        if not ext:
+            url += '.md'
+        url = url.lstrip('/')
+        return self.withAsset('_content/pages/' + url, text)
+
+    def withPageAsset(self, page_url, name, contents=None):
+        contents = contents or "A test asset."
+        url_base, ext = os.path.splitext(page_url)
+        dirname = url_base + '-assets'
+        return self.withAsset('_content/pages/%s/%s' % (dirname, name),
+                contents)
+
+
+class mock_fs_scope(object):
+    def __init__(self, fs):
+        self._fs = fs
+        self._root = None
+        self._patchers = []
+        self._originals = {}
+        if isinstance(fs, mock_fs):
+            self._fs = fs._fs
+            self._root = fs._root
+
+    def __enter__(self):
+        self._startMock()
+        return self
+
+    def __exit__(self, type, value, traceback):
+        self._endMock()
+
+    def _startMock(self):
+        self._createMock('__main__.open', open, self._open, create=True)
+        self._createMock('codecs.open', codecs.open, self._codecsOpen)
+        self._createMock('os.listdir', os.listdir, self._listdir)
+        self._createMock('os.path.isdir', os.path.isdir, self._isdir)
+        self._createMock('os.path.islink', os.path.islink, self._islink)
+        self._createMock('os.path.getmtime', os.path.getmtime, self._getmtime)
+        for p in self._patchers:
+            p.start()
+
+    def _endMock(self):
+        for p in self._patchers:
+            p.stop()
+
+    def _createMock(self, name, orig, func, **kwargs):
+        self._originals[name] = orig
+        self._patchers.append(mock.patch(name, func, **kwargs))
+
+    def _open(self, path, *args, **kwargs):
+        path = os.path.abspath(path)
+        if path.startswith(resources_path):
+            return self._originals['__main__.open'](path, **kwargs)
+        e = self._getFsEntry(path)
+        return io.StringIO(e[0])
+
+    def _codecsOpen(self, path, *args, **kwargs):
+        path = os.path.abspath(path)
+        if path.startswith(resources_path):
+            return self._originals['codecs.open'](path, *args, **kwargs)
+        e = self._getFsEntry(path)
+        return io.StringIO(e[0])
+
+    def _listdir(self, path):
+        if not path.startswith('/' + self._root):
+            return self._originals['os.listdir'](path)
+        e = self._getFsEntry(path)
+        if not isinstance(e, dict):
+            raise Exception("'%s' is not a directory." % path)
+        return list(e.keys())
+
+    def _isdir(self, path):
+        if not path.startswith('/' + self._root):
+            return self._originals['os.path.isdir'](path)
+        e = self._getFsEntry(path)
+        return e is not None and isinstance(e, dict)
+
+    def _islink(self, path):
+        if not path.startswith('/' + self._root):
+            return self._originals['os.path.islink'](path)
+        return False
+
+    def _getmtime(self, path):
+        if not path.startswith('/' + self._root):
+            return self._originals['os.path.getmtime'](path)
+        e = self._getFsEntry(path)
+        if e is None:
+            raise OSError()
+        return e[1]['mtime']
+
+    def _getFsEntry(self, path):
+        cur = self._fs
+        bits = path.lstrip('/').split('/')
+        for p in bits:
+            try:
+                cur = cur[p]
+            except KeyError:
+                return None
+        return cur
+