From cd6b296b39313d298d50f290ca0276d7ade57011 Mon Sep 17 00:00:00 2001 From: nocturnalastro Date: Thu, 5 Oct 2023 13:26:51 +0100 Subject: [PATCH] Allow render plot even when analysis errors If there is an issue with the analysis output then make that a literal --- src/testdrive/asciidoc.py | 85 ++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/src/testdrive/asciidoc.py b/src/testdrive/asciidoc.py index 3a7658c..748f32f 100644 --- a/src/testdrive/asciidoc.py +++ b/src/testdrive/asciidoc.py @@ -149,7 +149,7 @@ def _use_timing_from_stdout(self): try: dct = json.loads(self.stdout) timestamp = dct.get('timestamp') - except (TypeError, JSONDecodeError, AttributeError): + except (TypeError, json.JSONDecodeError, AttributeError): return if timestamp: self._timestamp = timestamp @@ -211,6 +211,30 @@ def xref_spec(self): """Return a cross-reference to this test case specification.""" return f'<<{self.uuid}_spec>>' + +class Table: + def __init__(self, title, dct): + self._title = title + self._dct = dct + + def to_asciidoc(self): + yield '' + yield f'.{self._title}' + yield '[cols="1,4"]' + yield '|===' + yield '' + yield from (row(f'*{k}*', self._dct[k]) for k in sorted(self._dct)) + yield '' + yield '|===' + +class LiteralBlock: + def __init__(self, block): + self._block = block + + def to_asciidoc(self): + yield literal_block(self._block) + + class TestDetail: """Test detail for a test case.""" def __init__(self, images=(), tables=()): @@ -227,15 +251,10 @@ def to_asciidoc(self, objdir): yield '' yield f'.{title or os.path.basename(path)}' yield f'image::{filename}[]' - for (title, dct) in self._tables: - yield '' - yield f'.{title}' - yield '[cols="1,4"]' - yield '|===' - yield '' - yield from (row(f'*{k}*', dct[k]) for k in sorted(dct)) - yield '' - yield '|===' + + for tbl in self._tables: + yield from tbl.to_asciidoc() + @staticmethod def _image_item(item): """Return (title, path) for the image specified by `item`. @@ -249,6 +268,28 @@ def _image_item(item): if not isinstance(item, str): return None return (None, item) + + @staticmethod + def _tables_from_output(dct): + tables = [] + if dct is not None: + if not isinstance(dct, dict): + return None + analysis = {} + for (key, val) in dct.items(): + if isinstance(val, dict): + tables.append(Table(key, val)) + elif isinstance(val, list): + try: + analysis[key] = '\n'.join(val) + except TypeError: + return None + else: + analysis[key] = val + if analysis: + tables.insert(0, Table('analysis', analysis)) + return tables + @classmethod def from_output(cls, output): """Return an instance of `cls` if `output` is JSON-encoded test detail. @@ -270,24 +311,12 @@ def from_output(cls, output): except ValueError: return None images.append((title, path)) - tables = [] - dct = obj.get('analysis') - if dct is not None: - if not isinstance(dct, dict): - return None - analysis = {} - for (key, val) in dct.items(): - if isinstance(val, dict): - tables.append((key, val)) - elif isinstance(val, list): - try: - analysis[key] = '\n'.join(val) - except TypeError: - return None - else: - analysis[key] = val - if analysis: - tables.insert(0, ('analysis', analysis)) + + analysis = obj.get('analysis') + tables = cls._tables_from_output(analysis) + if tables is None and analysis is not None: + tables = [LiteralBlock(json.dumps(analysis, indent=2))] + return cls(images, tables) class TestSuite(OrderedDict):