Mercurial > piecrust2
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 |