comparison tests/conftest.py @ 979:45ad976712ec

tests: Big push to get the tests to pass again. - Lots of fixes everywhere in the code. - Try to handle debug logging in the multiprocessing worker pool when running in pytest. Not perfect, but usable for now. - Replace all `.md` test files with `.html` since now a auto-format extension always sets the format. - Replace `out` with `outfiles` in most places since now blog archives are added to the bake output and I don't want to add expected outputs for blog archives everywhere.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 29 Oct 2017 22:51:57 -0700
parents 72f17534d58e
children 63f118c773ee
comparison
equal deleted inserted replaced
978:7e51d14097cb 979:45ad976712ec
27 help="Sets the PieCrust logger to write to a file.") 27 help="Sets the PieCrust logger to write to a file.")
28 parser.addoption( 28 parser.addoption(
29 '--mock-debug', 29 '--mock-debug',
30 action='store_true', 30 action='store_true',
31 help="Prints contents of the mock file-system.") 31 help="Prints contents of the mock file-system.")
32 parser.addoption(
33 '--leave-mockfs',
34 action='store_true',
35 help="Leave the contents of the mock file-system on disk.")
32 36
33 37
34 def pytest_configure(config): 38 def pytest_configure(config):
35 if config.getoption('--log-debug'): 39 if config.getoption('--log-debug'):
40 root_logger = logging.getLogger()
36 hdl = logging.StreamHandler(stream=sys.stdout) 41 hdl = logging.StreamHandler(stream=sys.stdout)
37 logging.getLogger('piecrust').addHandler(hdl) 42 root_logger.addHandler(hdl)
38 logging.getLogger('piecrust').setLevel(logging.DEBUG) 43 root_logger.setLevel(logging.DEBUG)
44
45 from .basefs import TestFileSystemBase
46 TestFileSystemBase._use_chef_debug = True
47 TestFileSystemBase._pytest_log_handler = hdl
39 48
40 log_file = config.getoption('--log-file') 49 log_file = config.getoption('--log-file')
41 if log_file: 50 if log_file:
42 hdl = logging.StreamHandler( 51 hdl = logging.StreamHandler(
43 stream=open(log_file, 'w', encoding='utf8')) 52 stream=open(log_file, 'w', encoding='utf8'))
44 logging.getLogger().addHandler(hdl) 53 logging.getLogger().addHandler(hdl)
45 54
55 if config.getoption('--leave-mockfs'):
56 from .basefs import TestFileSystemBase
57 TestFileSystemBase._leave_mockfs = True
58
46 59
47 def pytest_collect_file(parent, path): 60 def pytest_collect_file(parent, path):
48 if path.ext == '.yaml' and path.basename.startswith("test"): 61 if path.ext == '.yaml' and path.basename.startswith("test"):
49 category = os.path.basename(path.dirname) 62 category = os.path.basename(path.dirname)
50 if category == 'bakes': 63 if category == 'bakes':
51 return BakeTestFile(path, parent) 64 return BakeTestFile(path, parent)
52 elif category == 'procs':
53 return PipelineTestFile(path, parent)
54 elif category == 'cli': 65 elif category == 'cli':
55 return ChefTestFile(path, parent) 66 return ChefTestFile(path, parent)
56 elif category == 'servings': 67 elif category == 'servings':
57 return ServeTestFile(path, parent) 68 return ServeTestFile(path, parent)
58 69
262 273
263 values = self.spec.get('config_values') 274 values = self.spec.get('config_values')
264 if values is not None: 275 if values is not None:
265 values = list(values.items()) 276 values = list(values.items())
266 variants = self.spec.get('config_variants') 277 variants = self.spec.get('config_variants')
267 if variants is not None:
268 variants = list(variants.items())
269 apply_variants_and_values(app, variants, values) 278 apply_variants_and_values(app, variants, values)
270 279
271 appfactory = PieCrustFactory(app.root_dir, 280 appfactory = PieCrustFactory(app.root_dir,
281 theme_site=self.is_theme_site,
272 config_variants=variants, 282 config_variants=variants,
273 config_values=values) 283 config_values=values)
274 baker = Baker(appfactory, app, out_dir) 284 baker = Baker(appfactory, app, out_dir)
275 records = baker.bake() 285 records = baker.bake()
276 286
311 321
312 class BakeTestFile(YamlTestFileBase): 322 class BakeTestFile(YamlTestFileBase):
313 __item_class__ = BakeTestItem 323 __item_class__ = BakeTestItem
314 324
315 325
316 class PipelineTestItem(YamlTestItemBase):
317 def runtest(self):
318 fs = self._prepareMockFs()
319
320 from piecrust.processing.pipeline import ProcessorPipeline
321 with mock_fs_scope(fs, keep=self.mock_debug):
322 out_dir = fs.path('kitchen/_counter')
323 app = fs.getApp(theme_site=self.is_theme_site)
324 pipeline = ProcessorPipeline(app, out_dir)
325
326 proc_names = self.spec.get('processors')
327 if proc_names:
328 pipeline.enabled_processors = proc_names
329
330 record = pipeline.run()
331
332 if not record.success:
333 errors = []
334 for e in record.entries:
335 errors += e.errors
336 raise PipelineError(errors)
337
338 check_expected_outputs(self.spec, fs, ExpectedPipelineOutputError)
339
340 def reportinfo(self):
341 return self.fspath, 0, "pipeline: %s" % self.name
342
343 def repr_failure(self, excinfo):
344 if isinstance(excinfo.value, ExpectedPipelineOutputError):
345 return ('\n'.join(
346 ['Unexpected pipeline output. Left is expected output, '
347 'right is actual output'] +
348 excinfo.value.args[0]))
349 elif isinstance(excinfo.value, PipelineError):
350 res = ('\n'.join(
351 ['Errors occured during processing:'] +
352 excinfo.value.args[0]))
353 res += repr_nested_failure(excinfo)
354 return res
355 return super(PipelineTestItem, self).repr_failure(excinfo)
356
357
358 class PipelineError(Exception):
359 pass
360
361
362 class ExpectedPipelineOutputError(Exception):
363 pass
364
365
366 class PipelineTestFile(YamlTestFileBase):
367 __item_class__ = PipelineTestItem
368
369
370 class ServeTestItem(YamlTestItemBase): 326 class ServeTestItem(YamlTestItemBase):
371 class _TestApp(object):
372 def __init__(self, server):
373 self.server = server
374
375 def __call__(self, environ, start_response):
376 response = self.server._try_run_request(environ)
377 return response(environ, start_response)
378
379 def runtest(self): 327 def runtest(self):
380 fs = self._prepareMockFs() 328 fs = self._prepareMockFs()
381 329
382 url = self.spec.get('url') 330 url = self.spec.get('url')
383 if url is None: 331 if url is None:
385 333
386 expected_status = self.spec.get('status', 200) 334 expected_status = self.spec.get('status', 200)
387 expected_headers = self.spec.get('headers') 335 expected_headers = self.spec.get('headers')
388 expected_output = self.spec.get('out') 336 expected_output = self.spec.get('out')
389 expected_contains = self.spec.get('out_contains') 337 expected_contains = self.spec.get('out_contains')
390 is_admin_test = self.spec.get('admin') is True
391 338
392 from werkzeug.test import Client 339 from werkzeug.test import Client
393 from werkzeug.wrappers import BaseResponse 340 from werkzeug.wrappers import BaseResponse
341 from piecrust.app import PieCrustFactory
342 from piecrust.serving.server import PieCrustServer
343
394 with mock_fs_scope(fs, keep=self.mock_debug): 344 with mock_fs_scope(fs, keep=self.mock_debug):
395 if is_admin_test: 345 appfactory = PieCrustFactory(
396 from piecrust.admin.web import create_foodtruck_app 346 fs.path('/kitchen'),
397 s = { 347 theme_site=self.is_theme_site)
398 'FOODTRUCK_CMDLINE_MODE': True, 348 server = PieCrustServer(appfactory)
399 'FOODTRUCK_ROOT': fs.path('/kitchen') 349
400 } 350 client = Client(server, BaseResponse)
401 test_app = create_foodtruck_app(s)
402 else:
403 from piecrust.app import PieCrustFactory
404 from piecrust.serving.server import Server
405 appfactory = PieCrustFactory(
406 fs.path('/kitchen'),
407 theme_site=self.is_theme_site)
408 server = Server(appfactory)
409 test_app = self._TestApp(server)
410
411 client = Client(test_app, BaseResponse)
412 resp = client.get(url) 351 resp = client.get(url)
413 assert expected_status == resp.status_code 352 assert expected_status == resp.status_code
414 353
415 if expected_headers: 354 if expected_headers:
416 for k, v in expected_headers.items(): 355 for k, v in expected_headers.items():
558 # This is where the time starts. Let's compare that the time 497 # This is where the time starts. Let's compare that the time
559 # values are within a few seconds of each other (usually 0 or 1). 498 # values are within a few seconds of each other (usually 0 or 1).
560 right_time_str = right[i:i + len(test_time_iso8601)] 499 right_time_str = right[i:i + len(test_time_iso8601)]
561 right_time = time.strptime(right_time_str, '%Y-%m-%dT%H:%M:%SZ') 500 right_time = time.strptime(right_time_str, '%Y-%m-%dT%H:%M:%SZ')
562 left_time = time.gmtime(ctx.time) 501 left_time = time.gmtime(ctx.time)
502 # Need to patch the daylist-savings-time flag because it can
503 # mess up the computation of the time difference.
504 right_time = (right_time[0], right_time[1], right_time[2],
505 right_time[3], right_time[4], right_time[5],
506 right_time[6], right_time[7],
507 left_time.tm_isdst)
563 difference = time.mktime(left_time) - time.mktime(right_time) 508 difference = time.mktime(left_time) - time.mktime(right_time)
564 print("Got time difference: %d" % difference) 509 print("Got time difference: %d" % difference)
565 if abs(difference) <= 2: 510 if abs(difference) <= 1:
566 print("(good enough, moving to end of timestamp)") 511 print("(good enough, moving to end of timestamp)")
567 skip_for = len(test_time_iso8601) - 1 512 skip_for = len(test_time_iso8601) - 1
568 513
569 if left[i] != right[i]: 514 if left[i] != right[i]:
570 start = max(0, i - 15) 515 start = max(0, i - 15)