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)