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"
-