Mercurial > piecrust2
comparison tests/conftest.py @ 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 | a9929e0b8f66 |
children | 3e4bb57d8506 |
comparison
equal
deleted
inserted
replaced
384:d241585412ad | 385:2d5f2289885a |
---|---|
1 import io | |
1 import sys | 2 import sys |
2 import pprint | 3 import pprint |
3 import os.path | 4 import os.path |
4 import logging | 5 import logging |
5 import pytest | 6 import pytest |
6 import yaml | 7 import yaml |
8 import colorama | |
7 from piecrust.configuration import merge_dicts | 9 from piecrust.configuration import merge_dicts |
8 from .mockutil import mock_fs, mock_fs_scope | 10 from .mockutil import mock_fs, mock_fs_scope |
9 | 11 |
10 | 12 |
11 def pytest_runtest_setup(item): | 13 def pytest_runtest_setup(item): |
27 | 29 |
28 | 30 |
29 def pytest_collect_file(parent, path): | 31 def pytest_collect_file(parent, path): |
30 if path.ext == ".bake" and path.basename.startswith("test"): | 32 if path.ext == ".bake" and path.basename.startswith("test"): |
31 return BakeTestFile(path, parent) | 33 return BakeTestFile(path, parent) |
32 | 34 elif path.ext == ".chef" and path.basename.startswith("test"): |
33 | 35 return ChefTestFile(path, parent) |
34 class BakeTestFile(pytest.File): | 36 |
37 | |
38 class YamlTestFileBase(pytest.File): | |
35 def collect(self): | 39 def collect(self): |
36 spec = yaml.load_all(self.fspath.open()) | 40 spec = yaml.load_all(self.fspath.open()) |
37 for i, item in enumerate(spec): | 41 for i, item in enumerate(spec): |
38 name = '%s_%d' % (self.fspath.basename, i) | 42 name = '%s_%d' % (self.fspath.basename, i) |
39 if 'test_name' in item: | 43 if 'test_name' in item: |
40 name += '_%s' % item['test_name'] | 44 name += '_%s' % item['test_name'] |
41 yield BakeTestItem(name, self, item) | 45 yield self.__item_class__(name, self, item) |
42 | 46 |
43 | 47 |
44 class BakeTestItem(pytest.Item): | 48 class YamlTestItemBase(pytest.Item): |
45 def __init__(self, name, parent, spec): | 49 def __init__(self, name, parent, spec): |
46 super(BakeTestItem, self).__init__(name, parent) | 50 super(YamlTestItemBase, self).__init__(name, parent) |
47 self.spec = spec | 51 self.spec = spec |
48 | 52 |
49 def runtest(self): | 53 def _prepareMockFs(self): |
50 fs = mock_fs() | 54 fs = mock_fs() |
51 | 55 |
52 # Website config. | 56 # Website config. |
53 config = { | 57 config = { |
54 'site': { | 58 'site': { |
64 # Input file-system. | 68 # Input file-system. |
65 input_files = self.spec.get('in') | 69 input_files = self.spec.get('in') |
66 if input_files is not None: | 70 if input_files is not None: |
67 _add_mock_files(fs, '/kitchen', input_files) | 71 _add_mock_files(fs, '/kitchen', input_files) |
68 | 72 |
73 return fs | |
74 | |
75 | |
76 class ChefTestItem(YamlTestItemBase): | |
77 __initialized_logging__ = False | |
78 | |
79 def runtest(self): | |
80 if not ChefTestItem.__initialized_logging__: | |
81 colorama.init() | |
82 hdl = logging.StreamHandler(stream=sys.stdout) | |
83 logging.getLogger().addHandler(hdl) | |
84 logging.getLogger().setLevel(logging.INFO) | |
85 ChefTestItem.__initialized_logging__ = True | |
86 | |
87 fs = self._prepareMockFs() | |
88 | |
89 argv = self.spec['args'] | |
90 if isinstance(argv, str): | |
91 argv = argv.split(' ') | |
92 | |
93 expected_code = self.spec.get('code', 0) | |
94 expected_out = self.spec.get('out', '') | |
95 | |
96 with mock_fs_scope(fs): | |
97 memstream = io.StringIO() | |
98 hdl = logging.StreamHandler(stream=memstream) | |
99 logging.getLogger().addHandler(hdl) | |
100 try: | |
101 from piecrust.main import PreParsedChefArgs, _run_chef | |
102 pre_args = PreParsedChefArgs( | |
103 root=fs.path('/kitchen')) | |
104 exit_code = _run_chef(pre_args, argv) | |
105 finally: | |
106 logging.getLogger().removeHandler(hdl) | |
107 | |
108 assert expected_code == exit_code | |
109 assert expected_out == memstream.getvalue() | |
110 | |
111 def reportinfo(self): | |
112 return self.fspath, 0, "bake: %s" % self.name | |
113 | |
114 def repr_failure(self, excinfo): | |
115 if isinstance(excinfo.value, ExpectedChefOutputError): | |
116 return ('\n'.join( | |
117 ['Unexpected command output. Left is expected output, ' | |
118 'right is actual output'] + | |
119 excinfo.value.args[0])) | |
120 return super(ChefTestItem, self).repr_failure(excinfo) | |
121 | |
122 | |
123 class ExpectedChefOutputError(Exception): | |
124 pass | |
125 | |
126 | |
127 class ChefTestFile(YamlTestFileBase): | |
128 __item_class__ = ChefTestItem | |
129 | |
130 | |
131 class BakeTestItem(YamlTestItemBase): | |
132 def runtest(self): | |
133 fs = self._prepareMockFs() | |
134 | |
69 # Output file-system. | 135 # Output file-system. |
70 expected_output_files = self.spec.get('out') | 136 expected_output_files = self.spec.get('out') |
71 expected_partial_files = self.spec.get('outfiles') | 137 expected_partial_files = self.spec.get('outfiles') |
72 | 138 |
73 # Bake! | 139 # Bake! |
115 return super(BakeTestItem, self).repr_failure(excinfo) | 181 return super(BakeTestItem, self).repr_failure(excinfo) |
116 | 182 |
117 | 183 |
118 class ExpectedBakeOutputError(Exception): | 184 class ExpectedBakeOutputError(Exception): |
119 pass | 185 pass |
186 | |
187 | |
188 class BakeTestFile(YamlTestFileBase): | |
189 __item_class__ = BakeTestItem | |
120 | 190 |
121 | 191 |
122 def _add_mock_files(fs, parent_path, spec): | 192 def _add_mock_files(fs, parent_path, spec): |
123 for name, subspec in spec.items(): | 193 for name, subspec in spec.items(): |
124 path = os.path.join(parent_path, name) | 194 path = os.path.join(parent_path, name) |