changeset 673:d6403c21bdea

tests: Improve failure reporting.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 06 Mar 2016 23:41:35 -0800
parents 783ff8bc4e03
children f987b29d6fab
files tests/conftest.py tests/tmpfs.py
diffstat 2 files changed, 54 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/tests/conftest.py	Fri Mar 04 22:37:07 2016 -0800
+++ b/tests/conftest.py	Sun Mar 06 23:41:35 2016 -0800
@@ -22,6 +22,10 @@
             '--log-debug',
             action='store_true',
             help="Sets the PieCrust logger to output debug info to stdout.")
+    parser.addoption(
+            '--mock-debug',
+            action='store_true',
+            help="Prints contents of the mock file-system.")
 
 
 def pytest_configure(config):
@@ -44,6 +48,18 @@
             return ServeTestFile(path, parent)
 
 
+def repr_nested_failure(excinfo):
+    # PyTest sadly doesn't show nested exceptions so we have to do it
+    # ourselves... it's not pretty, but at least it's more useful.
+    if excinfo.value.__cause__:
+        import traceback
+        ex = excinfo.value
+        return '\n'.join(
+                traceback.format_exception(
+                    type(ex), ex, ex.__traceback__))
+    return ''
+
+
 class YamlTestFileBase(pytest.File):
     def collect(self):
         spec = yaml.load_all(self.fspath.open(encoding='utf8'))
@@ -59,6 +75,10 @@
         super(YamlTestItemBase, self).__init__(name, parent)
         self.spec = spec
 
+    @property
+    def mock_debug(self):
+        return bool(self.config.getoption('--mock-debug'))
+
     def _prepareMockFs(self):
         fs = mock_fs()
 
@@ -79,8 +99,22 @@
         if input_files is not None:
             _add_mock_files(fs, '/kitchen', input_files)
 
+        if self.mock_debug:
+            res = '\nMock File-System:\n'
+            res += 'At: %s\n' % fs.path('')
+            res += '\n'.join(print_fs_tree(fs.path('')))
+            res += '\n'
+            print(res)
+
         return fs
 
+    def repr_failure(self, excinfo):
+        res = super(YamlTestItemBase, self).repr_failure(excinfo)
+        nested_res = repr_nested_failure(excinfo)
+        if nested_res:
+            res = str(res) + '\n' + nested_res
+        return res
+
 
 def check_expected_outputs(spec, fs, error_type):
     cctx = CompareContext()
@@ -151,7 +185,7 @@
         expected_code = self.spec.get('code', 0)
         expected_out = self.spec.get('out', None)
 
-        with mock_fs_scope(fs):
+        with mock_fs_scope(fs, keep=self.mock_debug):
             memstream = io.StringIO()
             hdl = logging.StreamHandler(stream=memstream)
             logging.getLogger().addHandler(hdl)
@@ -196,7 +230,7 @@
         fs = self._prepareMockFs()
 
         from piecrust.baking.baker import Baker
-        with mock_fs_scope(fs):
+        with mock_fs_scope(fs, keep=self.mock_debug):
             out_dir = fs.path('kitchen/_counter')
             app = fs.getApp()
 
@@ -229,9 +263,11 @@
                     'right is actual output'] +
                 excinfo.value.args[0]))
         elif isinstance(excinfo.value, BakeError):
-            return ('\n'.join(
+            res = ('\n'.join(
                 ['Errors occured during bake:'] +
                 excinfo.value.args[0]))
+            res += repr_nested_failure(excinfo)
+            return res
         return super(BakeTestItem, self).repr_failure(excinfo)
 
 
@@ -252,7 +288,7 @@
         fs = self._prepareMockFs()
 
         from piecrust.processing.pipeline import ProcessorPipeline
-        with mock_fs_scope(fs):
+        with mock_fs_scope(fs, keep=self.mock_debug):
             out_dir = fs.path('kitchen/_counter')
             app = fs.getApp()
             pipeline = ProcessorPipeline(app, out_dir)
@@ -281,9 +317,11 @@
                     'right is actual output'] +
                 excinfo.value.args[0]))
         elif isinstance(excinfo.value, PipelineError):
-            return ('\n'.join(
+            res = ('\n'.join(
                 ['Errors occured during processing:'] +
                 excinfo.value.args[0]))
+            res += repr_nested_failure(excinfo)
+            return res
         return super(PipelineTestItem, self).repr_failure(excinfo)
 
 
@@ -324,7 +362,7 @@
         from werkzeug.wrappers import BaseResponse
         from piecrust.app import PieCrustFactory
         from piecrust.serving.server import Server
-        with mock_fs_scope(fs):
+        with mock_fs_scope(fs, keep=self.mock_debug):
             appfactory = PieCrustFactory(fs.path('/kitchen'))
             server = Server(appfactory)
             test_app = self._TestApp(server)
@@ -349,14 +387,18 @@
     def repr_failure(self, excinfo):
         from piecrust.serving.server import MultipleNotFound
         if isinstance(excinfo.value, MultipleNotFound):
-            return '\n'.join(
+            res = '\n'.join(
                     ["HTTP error 404 returned:",
                      excinfo.value.description] +
                     [e.description for e in excinfo.value._nfes])
+            res += repr_nested_failure(excinfo)
+            return res
         elif isinstance(excinfo.value, HTTPException):
-            return '\n'.join(
+            res = '\n'.join(
                     ["HTTP error %s returned:" % excinfo.value.code,
                      excinfo.value.description])
+            res += repr_nested_failure(excinfo)
+            return res
         return super(ServeTestItem, self).repr_failure(excinfo)
 
 
--- a/tests/tmpfs.py	Fri Mar 04 22:37:07 2016 -0800
+++ b/tests/tmpfs.py	Sun Mar 06 23:41:35 2016 -0800
@@ -68,9 +68,10 @@
 
 
 class TempDirScope(object):
-    def __init__(self, fs, open_patches=None):
+    def __init__(self, fs, open_patches=None, keep=False):
         self._fs = fs
         self._open = open
+        self._keep = keep
 
     @property
     def root(self):
@@ -80,5 +81,6 @@
         return self
 
     def __exit__(self, type, value, traceback):
-        shutil.rmtree(self.root)
+        if not self._keep:
+            shutil.rmtree(self.root)