comparison 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
comparison
equal deleted inserted replaced
5:474c9882decf 6:f5ca5c5bed85
1 import io
2 import time
3 import random
4 import codecs
5 import os.path
6 import functools
1 import mock 7 import mock
8 import yaml
2 from piecrust.app import PieCrust, PieCrustConfiguration 9 from piecrust.app import PieCrust, PieCrustConfiguration
10
11
12 resources_path = os.path.abspath(
13 os.path.join(
14 os.path.dirname(__file__),
15 '..', 'piecrust', 'resources'))
3 16
4 17
5 def get_mock_app(config=None): 18 def get_mock_app(config=None):
6 app = mock.MagicMock(spec=PieCrust) 19 app = mock.MagicMock(spec=PieCrust)
7 app.config = PieCrustConfiguration() 20 app.config = PieCrustConfiguration()
8 return app 21 return app
9 22
23
24 def with_mock_fs_app(f):
25 @functools.wraps(f)
26 def wrapper(app, *args, **kwargs):
27 with mock_fs_scope(app):
28 real_app = app.getApp()
29 return f(real_app, *args, **kwargs)
30 return wrapper
31
32
33 class mock_fs(object):
34 def __init__(self, default_spec=True):
35 self._root = 'root_%d' % random.randrange(1000)
36 self._fs = {self._root: {}}
37 if default_spec:
38 self.withDir('counter')
39 self.withFile('kitchen/_content/config.yml',
40 "site:\n title: Mock Website\n")
41
42 def path(self, p):
43 if p in ['/', '', None]:
44 return '/%s' % self._root
45 return '/%s/%s' % (self._root, p.lstrip('/'))
46
47 def getApp(self):
48 root_dir = self.path('/kitchen')
49 return PieCrust(root_dir, cache=False)
50
51 def withDir(self, path):
52 cur = self._fs[self._root]
53 for b in path.split('/'):
54 if b not in cur:
55 cur[b] = {}
56 cur = cur[b]
57 return self
58
59 def withFile(self, path, contents):
60 cur = self._fs[self._root]
61 bits = path.split('/')
62 for b in bits[:-1]:
63 if b not in cur:
64 cur[b] = {}
65 cur = cur[b]
66 cur[bits[-1]] = (contents, {'mtime': time.time()})
67 return self
68
69 def withAsset(self, path, contents):
70 return self.withFile('kitchen/' + path, contents)
71
72 def withAssetDir(self, path):
73 return self.withDir('kitchen/' + path)
74
75 def withConfig(self, config):
76 return self.withFile('kitchen/_content/config.yml',
77 yaml.dump(config))
78
79 def withThemeConfig(self, config):
80 return self.withFile('kitchen/_content/theme/_content/theme_config.yml',
81 yaml.dump(config))
82
83 def withPage(self, url, config=None, contents=None):
84 config = config or {}
85 contents = contents or "A test page."
86 text = "---\n"
87 text += yaml.dump(config)
88 text += "---\n"
89 text += contents
90
91 name, ext = os.path.splitext(url)
92 if not ext:
93 url += '.md'
94 url = url.lstrip('/')
95 return self.withAsset('_content/pages/' + url, text)
96
97 def withPageAsset(self, page_url, name, contents=None):
98 contents = contents or "A test asset."
99 url_base, ext = os.path.splitext(page_url)
100 dirname = url_base + '-assets'
101 return self.withAsset('_content/pages/%s/%s' % (dirname, name),
102 contents)
103
104
105 class mock_fs_scope(object):
106 def __init__(self, fs):
107 self._fs = fs
108 self._root = None
109 self._patchers = []
110 self._originals = {}
111 if isinstance(fs, mock_fs):
112 self._fs = fs._fs
113 self._root = fs._root
114
115 def __enter__(self):
116 self._startMock()
117 return self
118
119 def __exit__(self, type, value, traceback):
120 self._endMock()
121
122 def _startMock(self):
123 self._createMock('__main__.open', open, self._open, create=True)
124 self._createMock('codecs.open', codecs.open, self._codecsOpen)
125 self._createMock('os.listdir', os.listdir, self._listdir)
126 self._createMock('os.path.isdir', os.path.isdir, self._isdir)
127 self._createMock('os.path.islink', os.path.islink, self._islink)
128 self._createMock('os.path.getmtime', os.path.getmtime, self._getmtime)
129 for p in self._patchers:
130 p.start()
131
132 def _endMock(self):
133 for p in self._patchers:
134 p.stop()
135
136 def _createMock(self, name, orig, func, **kwargs):
137 self._originals[name] = orig
138 self._patchers.append(mock.patch(name, func, **kwargs))
139
140 def _open(self, path, *args, **kwargs):
141 path = os.path.abspath(path)
142 if path.startswith(resources_path):
143 return self._originals['__main__.open'](path, **kwargs)
144 e = self._getFsEntry(path)
145 return io.StringIO(e[0])
146
147 def _codecsOpen(self, path, *args, **kwargs):
148 path = os.path.abspath(path)
149 if path.startswith(resources_path):
150 return self._originals['codecs.open'](path, *args, **kwargs)
151 e = self._getFsEntry(path)
152 return io.StringIO(e[0])
153
154 def _listdir(self, path):
155 if not path.startswith('/' + self._root):
156 return self._originals['os.listdir'](path)
157 e = self._getFsEntry(path)
158 if not isinstance(e, dict):
159 raise Exception("'%s' is not a directory." % path)
160 return list(e.keys())
161
162 def _isdir(self, path):
163 if not path.startswith('/' + self._root):
164 return self._originals['os.path.isdir'](path)
165 e = self._getFsEntry(path)
166 return e is not None and isinstance(e, dict)
167
168 def _islink(self, path):
169 if not path.startswith('/' + self._root):
170 return self._originals['os.path.islink'](path)
171 return False
172
173 def _getmtime(self, path):
174 if not path.startswith('/' + self._root):
175 return self._originals['os.path.getmtime'](path)
176 e = self._getFsEntry(path)
177 if e is None:
178 raise OSError()
179 return e[1]['mtime']
180
181 def _getFsEntry(self, path):
182 cur = self._fs
183 bits = path.lstrip('/').split('/')
184 for p in bits:
185 try:
186 cur = cur[p]
187 except KeyError:
188 return None
189 return cur
190