Mercurial > piecrust2
comparison tests/conftest.py @ 673:d6403c21bdea
tests: Improve failure reporting.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 06 Mar 2016 23:41:35 -0800 |
parents | 81d9c3a3a0b5 |
children | f987b29d6fab |
comparison
equal
deleted
inserted
replaced
672:783ff8bc4e03 | 673:d6403c21bdea |
---|---|
20 def pytest_addoption(parser): | 20 def pytest_addoption(parser): |
21 parser.addoption( | 21 parser.addoption( |
22 '--log-debug', | 22 '--log-debug', |
23 action='store_true', | 23 action='store_true', |
24 help="Sets the PieCrust logger to output debug info to stdout.") | 24 help="Sets the PieCrust logger to output debug info to stdout.") |
25 parser.addoption( | |
26 '--mock-debug', | |
27 action='store_true', | |
28 help="Prints contents of the mock file-system.") | |
25 | 29 |
26 | 30 |
27 def pytest_configure(config): | 31 def pytest_configure(config): |
28 if config.getoption('--log-debug'): | 32 if config.getoption('--log-debug'): |
29 hdl = logging.StreamHandler(stream=sys.stdout) | 33 hdl = logging.StreamHandler(stream=sys.stdout) |
42 return ChefTestFile(path, parent) | 46 return ChefTestFile(path, parent) |
43 elif category == 'servings': | 47 elif category == 'servings': |
44 return ServeTestFile(path, parent) | 48 return ServeTestFile(path, parent) |
45 | 49 |
46 | 50 |
51 def repr_nested_failure(excinfo): | |
52 # PyTest sadly doesn't show nested exceptions so we have to do it | |
53 # ourselves... it's not pretty, but at least it's more useful. | |
54 if excinfo.value.__cause__: | |
55 import traceback | |
56 ex = excinfo.value | |
57 return '\n'.join( | |
58 traceback.format_exception( | |
59 type(ex), ex, ex.__traceback__)) | |
60 return '' | |
61 | |
62 | |
47 class YamlTestFileBase(pytest.File): | 63 class YamlTestFileBase(pytest.File): |
48 def collect(self): | 64 def collect(self): |
49 spec = yaml.load_all(self.fspath.open(encoding='utf8')) | 65 spec = yaml.load_all(self.fspath.open(encoding='utf8')) |
50 for i, item in enumerate(spec): | 66 for i, item in enumerate(spec): |
51 name = '%s_%d' % (self.fspath.basename, i) | 67 name = '%s_%d' % (self.fspath.basename, i) |
56 | 72 |
57 class YamlTestItemBase(pytest.Item): | 73 class YamlTestItemBase(pytest.Item): |
58 def __init__(self, name, parent, spec): | 74 def __init__(self, name, parent, spec): |
59 super(YamlTestItemBase, self).__init__(name, parent) | 75 super(YamlTestItemBase, self).__init__(name, parent) |
60 self.spec = spec | 76 self.spec = spec |
77 | |
78 @property | |
79 def mock_debug(self): | |
80 return bool(self.config.getoption('--mock-debug')) | |
61 | 81 |
62 def _prepareMockFs(self): | 82 def _prepareMockFs(self): |
63 fs = mock_fs() | 83 fs = mock_fs() |
64 | 84 |
65 # Website config. | 85 # Website config. |
77 # Input file-system. | 97 # Input file-system. |
78 input_files = self.spec.get('in') | 98 input_files = self.spec.get('in') |
79 if input_files is not None: | 99 if input_files is not None: |
80 _add_mock_files(fs, '/kitchen', input_files) | 100 _add_mock_files(fs, '/kitchen', input_files) |
81 | 101 |
102 if self.mock_debug: | |
103 res = '\nMock File-System:\n' | |
104 res += 'At: %s\n' % fs.path('') | |
105 res += '\n'.join(print_fs_tree(fs.path(''))) | |
106 res += '\n' | |
107 print(res) | |
108 | |
82 return fs | 109 return fs |
110 | |
111 def repr_failure(self, excinfo): | |
112 res = super(YamlTestItemBase, self).repr_failure(excinfo) | |
113 nested_res = repr_nested_failure(excinfo) | |
114 if nested_res: | |
115 res = str(res) + '\n' + nested_res | |
116 return res | |
83 | 117 |
84 | 118 |
85 def check_expected_outputs(spec, fs, error_type): | 119 def check_expected_outputs(spec, fs, error_type): |
86 cctx = CompareContext() | 120 cctx = CompareContext() |
87 expected_output_files = spec.get('out') | 121 expected_output_files = spec.get('out') |
149 argv = argv.split(' ') | 183 argv = argv.split(' ') |
150 | 184 |
151 expected_code = self.spec.get('code', 0) | 185 expected_code = self.spec.get('code', 0) |
152 expected_out = self.spec.get('out', None) | 186 expected_out = self.spec.get('out', None) |
153 | 187 |
154 with mock_fs_scope(fs): | 188 with mock_fs_scope(fs, keep=self.mock_debug): |
155 memstream = io.StringIO() | 189 memstream = io.StringIO() |
156 hdl = logging.StreamHandler(stream=memstream) | 190 hdl = logging.StreamHandler(stream=memstream) |
157 logging.getLogger().addHandler(hdl) | 191 logging.getLogger().addHandler(hdl) |
158 try: | 192 try: |
159 from piecrust.main import _pre_parse_chef_args, _run_chef | 193 from piecrust.main import _pre_parse_chef_args, _run_chef |
194 class BakeTestItem(YamlTestItemBase): | 228 class BakeTestItem(YamlTestItemBase): |
195 def runtest(self): | 229 def runtest(self): |
196 fs = self._prepareMockFs() | 230 fs = self._prepareMockFs() |
197 | 231 |
198 from piecrust.baking.baker import Baker | 232 from piecrust.baking.baker import Baker |
199 with mock_fs_scope(fs): | 233 with mock_fs_scope(fs, keep=self.mock_debug): |
200 out_dir = fs.path('kitchen/_counter') | 234 out_dir = fs.path('kitchen/_counter') |
201 app = fs.getApp() | 235 app = fs.getApp() |
202 | 236 |
203 variant = self.spec.get('config_variant') | 237 variant = self.spec.get('config_variant') |
204 values = self.spec.get('config_values') | 238 values = self.spec.get('config_values') |
227 return ('\n'.join( | 261 return ('\n'.join( |
228 ['Unexpected bake output. Left is expected output, ' | 262 ['Unexpected bake output. Left is expected output, ' |
229 'right is actual output'] + | 263 'right is actual output'] + |
230 excinfo.value.args[0])) | 264 excinfo.value.args[0])) |
231 elif isinstance(excinfo.value, BakeError): | 265 elif isinstance(excinfo.value, BakeError): |
232 return ('\n'.join( | 266 res = ('\n'.join( |
233 ['Errors occured during bake:'] + | 267 ['Errors occured during bake:'] + |
234 excinfo.value.args[0])) | 268 excinfo.value.args[0])) |
269 res += repr_nested_failure(excinfo) | |
270 return res | |
235 return super(BakeTestItem, self).repr_failure(excinfo) | 271 return super(BakeTestItem, self).repr_failure(excinfo) |
236 | 272 |
237 | 273 |
238 class BakeError(Exception): | 274 class BakeError(Exception): |
239 pass | 275 pass |
250 class PipelineTestItem(YamlTestItemBase): | 286 class PipelineTestItem(YamlTestItemBase): |
251 def runtest(self): | 287 def runtest(self): |
252 fs = self._prepareMockFs() | 288 fs = self._prepareMockFs() |
253 | 289 |
254 from piecrust.processing.pipeline import ProcessorPipeline | 290 from piecrust.processing.pipeline import ProcessorPipeline |
255 with mock_fs_scope(fs): | 291 with mock_fs_scope(fs, keep=self.mock_debug): |
256 out_dir = fs.path('kitchen/_counter') | 292 out_dir = fs.path('kitchen/_counter') |
257 app = fs.getApp() | 293 app = fs.getApp() |
258 pipeline = ProcessorPipeline(app, out_dir) | 294 pipeline = ProcessorPipeline(app, out_dir) |
259 | 295 |
260 proc_names = self.spec.get('processors') | 296 proc_names = self.spec.get('processors') |
279 return ('\n'.join( | 315 return ('\n'.join( |
280 ['Unexpected pipeline output. Left is expected output, ' | 316 ['Unexpected pipeline output. Left is expected output, ' |
281 'right is actual output'] + | 317 'right is actual output'] + |
282 excinfo.value.args[0])) | 318 excinfo.value.args[0])) |
283 elif isinstance(excinfo.value, PipelineError): | 319 elif isinstance(excinfo.value, PipelineError): |
284 return ('\n'.join( | 320 res = ('\n'.join( |
285 ['Errors occured during processing:'] + | 321 ['Errors occured during processing:'] + |
286 excinfo.value.args[0])) | 322 excinfo.value.args[0])) |
323 res += repr_nested_failure(excinfo) | |
324 return res | |
287 return super(PipelineTestItem, self).repr_failure(excinfo) | 325 return super(PipelineTestItem, self).repr_failure(excinfo) |
288 | 326 |
289 | 327 |
290 class PipelineError(Exception): | 328 class PipelineError(Exception): |
291 pass | 329 pass |
322 | 360 |
323 from werkzeug.test import Client | 361 from werkzeug.test import Client |
324 from werkzeug.wrappers import BaseResponse | 362 from werkzeug.wrappers import BaseResponse |
325 from piecrust.app import PieCrustFactory | 363 from piecrust.app import PieCrustFactory |
326 from piecrust.serving.server import Server | 364 from piecrust.serving.server import Server |
327 with mock_fs_scope(fs): | 365 with mock_fs_scope(fs, keep=self.mock_debug): |
328 appfactory = PieCrustFactory(fs.path('/kitchen')) | 366 appfactory = PieCrustFactory(fs.path('/kitchen')) |
329 server = Server(appfactory) | 367 server = Server(appfactory) |
330 test_app = self._TestApp(server) | 368 test_app = self._TestApp(server) |
331 client = Client(test_app, BaseResponse) | 369 client = Client(test_app, BaseResponse) |
332 resp = client.get(url) | 370 resp = client.get(url) |
347 return self.fspath, 0, "serve: %s" % self.name | 385 return self.fspath, 0, "serve: %s" % self.name |
348 | 386 |
349 def repr_failure(self, excinfo): | 387 def repr_failure(self, excinfo): |
350 from piecrust.serving.server import MultipleNotFound | 388 from piecrust.serving.server import MultipleNotFound |
351 if isinstance(excinfo.value, MultipleNotFound): | 389 if isinstance(excinfo.value, MultipleNotFound): |
352 return '\n'.join( | 390 res = '\n'.join( |
353 ["HTTP error 404 returned:", | 391 ["HTTP error 404 returned:", |
354 excinfo.value.description] + | 392 excinfo.value.description] + |
355 [e.description for e in excinfo.value._nfes]) | 393 [e.description for e in excinfo.value._nfes]) |
394 res += repr_nested_failure(excinfo) | |
395 return res | |
356 elif isinstance(excinfo.value, HTTPException): | 396 elif isinstance(excinfo.value, HTTPException): |
357 return '\n'.join( | 397 res = '\n'.join( |
358 ["HTTP error %s returned:" % excinfo.value.code, | 398 ["HTTP error %s returned:" % excinfo.value.code, |
359 excinfo.value.description]) | 399 excinfo.value.description]) |
400 res += repr_nested_failure(excinfo) | |
401 return res | |
360 return super(ServeTestItem, self).repr_failure(excinfo) | 402 return super(ServeTestItem, self).repr_failure(excinfo) |
361 | 403 |
362 | 404 |
363 class ServeTestFile(YamlTestFileBase): | 405 class ServeTestFile(YamlTestFileBase): |
364 __item_class__ = ServeTestItem | 406 __item_class__ = ServeTestItem |