changeset 385:2d5f2289885a

tests: Add support for "Chef tests", which are direct CLI tests. Since Chef tests are written as YAML tests and also involve creating a mock file-system, this change includes a refactor of the bake tests to put some functionality in a common base class.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 11 May 2015 22:25:19 -0700
parents d241585412ad
children 5f0e5276c7cb
files tests/conftest.py
diffstat 1 files changed, 75 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/tests/conftest.py	Mon May 11 22:24:05 2015 -0700
+++ b/tests/conftest.py	Mon May 11 22:25:19 2015 -0700
@@ -1,9 +1,11 @@
+import io
 import sys
 import pprint
 import os.path
 import logging
 import pytest
 import yaml
+import colorama
 from piecrust.configuration import merge_dicts
 from .mockutil import mock_fs, mock_fs_scope
 
@@ -29,24 +31,26 @@
 def pytest_collect_file(parent, path):
     if path.ext == ".bake" and path.basename.startswith("test"):
         return BakeTestFile(path, parent)
+    elif path.ext == ".chef" and path.basename.startswith("test"):
+        return ChefTestFile(path, parent)
 
 
-class BakeTestFile(pytest.File):
+class YamlTestFileBase(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)
+            yield self.__item_class__(name, self, item)
 
 
-class BakeTestItem(pytest.Item):
+class YamlTestItemBase(pytest.Item):
     def __init__(self, name, parent, spec):
-        super(BakeTestItem, self).__init__(name, parent)
+        super(YamlTestItemBase, self).__init__(name, parent)
         self.spec = spec
 
-    def runtest(self):
+    def _prepareMockFs(self):
         fs = mock_fs()
 
         # Website config.
@@ -66,6 +70,68 @@
         if input_files is not None:
             _add_mock_files(fs, '/kitchen', input_files)
 
+        return fs
+
+
+class ChefTestItem(YamlTestItemBase):
+    __initialized_logging__ = False
+
+    def runtest(self):
+        if not ChefTestItem.__initialized_logging__:
+            colorama.init()
+            hdl = logging.StreamHandler(stream=sys.stdout)
+            logging.getLogger().addHandler(hdl)
+            logging.getLogger().setLevel(logging.INFO)
+            ChefTestItem.__initialized_logging__ = True
+
+        fs = self._prepareMockFs()
+
+        argv = self.spec['args']
+        if isinstance(argv, str):
+            argv = argv.split(' ')
+
+        expected_code = self.spec.get('code', 0)
+        expected_out = self.spec.get('out', '')
+
+        with mock_fs_scope(fs):
+            memstream = io.StringIO()
+            hdl = logging.StreamHandler(stream=memstream)
+            logging.getLogger().addHandler(hdl)
+            try:
+                from piecrust.main import PreParsedChefArgs, _run_chef
+                pre_args = PreParsedChefArgs(
+                        root=fs.path('/kitchen'))
+                exit_code = _run_chef(pre_args, argv)
+            finally:
+                logging.getLogger().removeHandler(hdl)
+
+            assert expected_code == exit_code
+            assert expected_out == memstream.getvalue()
+
+    def reportinfo(self):
+        return self.fspath, 0, "bake: %s" % self.name
+
+    def repr_failure(self, excinfo):
+        if isinstance(excinfo.value, ExpectedChefOutputError):
+            return ('\n'.join(
+                ['Unexpected command output. Left is expected output, '
+                    'right is actual output'] +
+                excinfo.value.args[0]))
+        return super(ChefTestItem, self).repr_failure(excinfo)
+
+
+class ExpectedChefOutputError(Exception):
+    pass
+
+
+class ChefTestFile(YamlTestFileBase):
+    __item_class__ = ChefTestItem
+
+
+class BakeTestItem(YamlTestItemBase):
+    def runtest(self):
+        fs = self._prepareMockFs()
+
         # Output file-system.
         expected_output_files = self.spec.get('out')
         expected_partial_files = self.spec.get('outfiles')
@@ -119,6 +185,10 @@
     pass
 
 
+class BakeTestFile(YamlTestFileBase):
+    __item_class__ = BakeTestItem
+
+
 def _add_mock_files(fs, parent_path, spec):
     for name, subspec in spec.items():
         path = os.path.join(parent_path, name)