Mercurial > piecrust2
changeset 347:76c838453dbe
tests: Support for YAML-based baking tests. Convert old code-based ones.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Wed, 15 Apr 2015 16:39:35 -0700 |
parents | 89cc71928f6a |
children | f43e911cecac |
files | tests/bakes/test_empty.bake tests/bakes/test_simple.bake tests/bakes/test_simple_categories.bake tests/bakes/test_simple_tags.bake tests/conftest.py tests/test_baking_baker.py |
diffstat | 6 files changed, 291 insertions(+), 101 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/bakes/test_empty.bake Wed Apr 15 16:39:35 2015 -0700 @@ -0,0 +1,6 @@ +--- +in: + pages/_index.md: '' +out: + index.html: '' +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/bakes/test_simple.bake Wed Apr 15 16:39:35 2015 -0700 @@ -0,0 +1,28 @@ +--- +in: + posts/2010-01-01_post1.md: 'post one' + pages/about.md: 'URL: {{page.url}}' + pages/_index.md: 'something' +out: + '2010': + '01': + '01': + post1.html: 'post one' + about.html: 'URL: /about.html' + index.html: 'something' +--- +config: + site: + root: /whatever +in: + posts/2010-01-01_post1.md: 'post one' + pages/about.md: 'URL: {{page.url}}' + pages/_index.md: 'something' +out: + '2010': + '01': + '01': + post1.html: 'post one' + about.html: 'URL: /whatever/about.html' + index.html: 'something' +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/bakes/test_simple_categories.bake Wed Apr 15 16:39:35 2015 -0700 @@ -0,0 +1,45 @@ +--- +config: + site: + category_url: cat/%category% +in: + posts/2015-03-01_post01.md: | + --- + title: Post 01 + category: foo + --- + posts/2015-03-02_post02.md: | + --- + title: Post 02 + category: bar + --- + posts/2015-03-03_post03.md: | + --- + title: Post 03 + category: foo + --- + pages/_category.md: | + Pages in {{category}} + {% for p in pagination.posts -%} + {{p.title}} + {% endfor %} + pages/_index.md: '' +out: + index.html: '' + '2015': + '03': + '01': + post01.html: '' + '02': + post02.html: '' + '03': + post03.html: '' + cat: + foo.html: | + Pages in foo + Post 03 + Post 01 + bar.html: | + Pages in bar + Post 02 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/bakes/test_simple_tags.bake Wed Apr 15 16:39:35 2015 -0700 @@ -0,0 +1,46 @@ +--- +in: + posts/2015-03-01_post01.md: | + --- + title: Post 01 + tags: [foo] + --- + posts/2015-03-02_post02.md: | + --- + title: Post 02 + tags: [bar, whatever] + --- + posts/2015-03-03_post03.md: | + --- + title: Post 03 + tags: [foo, bar] + --- + pages/_tag.md: | + Pages in {{tag}} + {% for p in pagination.posts -%} + {{p.title}} + {% endfor %} + pages/_index.md: '' +out: + index.html: '' + '2015': + '03': + '01': + post01.html: '' + '02': + post02.html: '' + '03': + post03.html: '' + tag: + foo.html: | + Pages in foo + Post 03 + Post 01 + bar.html: | + Pages in bar + Post 03 + Post 02 + whatever.html: | + Pages in whatever + Post 02 +
--- a/tests/conftest.py Wed Apr 15 16:38:55 2015 -0700 +++ b/tests/conftest.py Wed Apr 15 16:39:35 2015 -0700 @@ -1,5 +1,11 @@ import sys +import pprint +import os.path import logging +import pytest +import yaml +from piecrust.configuration import merge_dicts +from .mockutil import mock_fs, mock_fs_scope def pytest_runtest_setup(item): @@ -7,7 +13,9 @@ def pytest_addoption(parser): - parser.addoption('--log-debug', action='store_true', + parser.addoption( + '--log-debug', + action='store_true', help="Sets the PieCrust logger to output debug info to stdout.") @@ -17,3 +25,160 @@ logging.getLogger('piecrust').addHandler(hdl) logging.getLogger('piecrust').setLevel(logging.DEBUG) + +def pytest_collect_file(parent, path): + if path.ext == ".bake" and path.basename.startswith("test"): + return BakeTestFile(path, parent) + + +class BakeTestFile(pytest.File): + def collect(self): + spec = yaml.load_all(self.fspath.open()) + for i, item in enumerate(spec): + name = '%s_%d' % (self.fspath.basename, i) + if 'test_name' in item: + name += '_%s' % item['test_name'] + yield BakeTestItem(name, self, item) + + +class BakeTestItem(pytest.Item): + def __init__(self, name, parent, spec): + super(BakeTestItem, self).__init__(name, parent) + self.spec = spec + + def runtest(self): + fs = mock_fs() + + # Website config. + config = { + 'site': { + 'default_format': 'none', + 'default_page_layout': 'none', + 'default_post_layout': 'none'} + } + test_config = self.spec.get('config') + if test_config is not None: + merge_dicts(config, test_config) + fs.withConfig(config) + + # Input file-system. + input_files = self.spec.get('in') + if input_files is not None: + _add_mock_files(fs, '/kitchen', input_files) + + # Output file-system. + expected_output_files = self.spec.get('out') + expected_partial_files = self.spec.get('outfiles') + + # Bake! + from piecrust.baking.baker import Baker + with mock_fs_scope(fs): + out_dir = fs.path('kitchen/_counter') + app = fs.getApp() + baker = Baker(app, out_dir) + baker.bake() + + if expected_output_files: + actual = fs.getStructure('kitchen/_counter') + error = _compare_dicts(actual, expected_output_files) + if error: + raise ExpectedBakeOutputError(error) + + if expected_partial_files: + for key, content in expected_partial_files.items(): + try: + actual = fs.getStructure('kitchen/_counter/' + + key.lstrip('/')) + except Exception: + raise ExpectedBakeOutputError([ + "Missing expected output file: %s" % key]) + if not isinstance(actual, str): + raise ExpectedBakeOutputError([ + "Expected output file is a directory: %s" % key]) + if actual != content: + raise ExpectedBakeOutputError([ + "Unexpected output file contents:", + "%s: %s" % (key, content), + "%s: %s" % (key, actual)]) + + def reportinfo(self): + return self.fspath, 0, "bake: %s" % self.name + + def repr_failure(self, excinfo): + if isinstance(excinfo.value, ExpectedBakeOutputError): + return ('\n'.join(excinfo.value.args[0])) + return super(BakeTestItem, self).repr_failure(excinfo) + + +class ExpectedBakeOutputError(Exception): + pass + + +def _add_mock_files(fs, parent_path, spec): + for name, subspec in spec.items(): + path = os.path.join(parent_path, name) + if isinstance(subspec, str): + fs.withFile(path, subspec) + elif isinstance(subspec, dict): + _add_mock_files(fs, path, subspec) + + +def _compare_dicts(left, right, basepath=''): + key_diff = set(left.keys()) ^ set(right.keys()) + if key_diff: + extra_left = set(left.keys()) - set(right.keys()) + if extra_left: + return (["Left contains more items: "] + + ['- %s/%s' % (basepath, k) for k in extra_left]) + extra_right = set(right.keys()) - set(left.keys()) + if extra_right: + return (["Right contains more items: "] + + ['- %s/%s' % (basepath, k) for k in extra_right]) + return ["Unknown difference"] + + for key in left.keys(): + lv = left[key] + rv = right[key] + childpath = basepath + '/' + key + if type(lv) != type(rv): + return (["Different items: ", + "%s/%s: %s" % (basepath, key, pprint.pformat(lv)), + "%s/%s: %s" % (basepath, key, pprint.pformat(rv))]) + + if isinstance(lv, dict): + r = _compare_dicts(lv, rv, childpath) + if r: + return r + elif isinstance(lv, list): + r = _compare_lists(lv, rv, childpath) + if r: + return r + elif lv != rv: + return (["Different items: ", + "%s/%s: %s" % (basepath, key, pprint.pformat(lv)), + "%s/%s: %s" % (basepath, key, pprint.pformat(rv))]) + return None + + +def _compare_lists(left, right): + for i in range(min(len(left), len(right))): + l = left[i] + r = right[i] + if type(l) != type(r): + return ['Different items at index %d:' % i, + pprint.pformat(l), + pprint.pformat(r)] + if isinstance(l, dict): + r = _compare_dicts(l, r) + if r: + return r + elif isinstance(l, list): + r = _compare_lists(l, r) + if r: + return r + elif l != r: + return ['Different items at index %d:' % i, + pprint.pformat(l), + pprint.pformat(r)] + return None +
--- a/tests/test_baking_baker.py Wed Apr 15 16:38:55 2015 -0700 +++ b/tests/test_baking_baker.py Wed Apr 15 16:39:35 2015 -0700 @@ -49,44 +49,6 @@ assert expected == path -def test_empty_bake(): - fs = mock_fs() - with mock_fs_scope(fs): - out_dir = fs.path('kitchen/_counter') - assert not os.path.isdir(out_dir) - app = fs.getApp() - baker = Baker(app, out_dir) - baker.bake() - assert os.path.isdir(out_dir) - structure = fs.getStructure('kitchen/_counter') - assert list(structure.keys()) == ['index.html'] - - -@pytest.mark.parametrize( - 'site_root', - [ - ('/'), ('/whatever') - ]) -def test_simple_bake(site_root): - pconf = {'layout': 'none', 'format': 'none'} - fs = (mock_fs() - .withConfig({'site': {'root': site_root}}) - .withPage('posts/2010-01-01_post1.md', pconf, 'post one') - .withPage('pages/about.md', pconf, 'URL: {{page.url}}') - .withPage('pages/_index.md', pconf, "something")) - with mock_fs_scope(fs): - out_dir = fs.path('kitchen/_counter') - app = fs.getApp() - baker = Baker(app, out_dir) - baker.bake() - structure = fs.getStructure('kitchen/_counter') - assert structure == { - '2010': {'01': {'01': {'post1.html': 'post one'}}}, - 'about.html': 'URL: %s' % ( - site_root.rstrip('/') + '/about.html'), - 'index.html': 'something'} - - def test_removed(): fs = (mock_fs() .withPage('pages/foo.md', {'layout': 'none', 'format': 'none'}, 'a foo page') @@ -135,65 +97,3 @@ finally: BakeRecord.RECORD_VERSION -= 1 - -def test_bake_tags(): - tags = [ - ['foo'], - ['bar', 'whatever'], - ['foo', 'bar']] - - def config_factory(i): - c = {'title': 'Post %d' % (i + 1)} - c['tags'] = tags[i] - return c - - fs = (mock_fs() - .withPages(3, 'posts/2015-03-{idx1:02}_post{idx1:02}.md', - config_factory) - .withPage('pages/_tag.md', {'layout': 'none', 'format': 'none'}, - "Pages in {{tag}}\n" - "{%for p in pagination.posts -%}\n" - "{{p.title}}\n" - "{%endfor%}")) - with mock_fs_scope(fs): - out_dir = fs.path('kitchen/_counter') - app = fs.getApp() - baker = Baker(app, out_dir) - r = baker.bake() - assert r.success is True - - s = fs.getStructure('kitchen/_counter/tag') - assert s['foo.html'] == "Pages in foo\nPost 3\nPost 1\n" - assert s['bar.html'] == "Pages in bar\nPost 3\nPost 2\n" - assert s['whatever.html'] == "Pages in whatever\nPost 2\n" - - -def test_bake_categories(): - categories = [ - 'foo', 'bar', 'foo'] - - def config_factory(i): - c = {'title': 'Post %d' % (i + 1)} - c['category'] = categories[i] - return c - - fs = (mock_fs() - .withConfig({'site': {'category_url': 'cat/%category%'}}) - .withPages(3, 'posts/2015-03-{idx1:02}_post{idx1:02}.md', - config_factory) - .withPage('pages/_category.md', {'layout': 'none', 'format': 'none'}, - "Pages in {{category}}\n" - "{%for p in pagination.posts -%}\n" - "{{p.title}}\n" - "{%endfor%}")) - with mock_fs_scope(fs): - out_dir = fs.path('kitchen/_counter') - app = fs.getApp() - baker = Baker(app, out_dir) - baker.bake() - - print(fs.getStructure('kitchen/_counter').keys()) - s = fs.getStructure('kitchen/_counter/cat') - assert s['foo.html'] == "Pages in foo\nPost 3\nPost 1\n" - assert s['bar.html'] == "Pages in bar\nPost 2\n" -