Commit | Line | Data |
---|---|---|
88004776 PP |
1 | # The MIT License (MIT) |
2 | # | |
3 | # Copyright (c) 2020 Philippe Proulx <pproulx@efficios.com> | |
4 | # | |
5 | # Permission is hereby granted, free of charge, to any person obtaining | |
6 | # a copy of this software and associated documentation files (the | |
7 | # "Software"), to deal in the Software without restriction, including | |
8 | # without limitation the rights to use, copy, modify, merge, publish, | |
9 | # distribute, sublicense, and/or sell copies of the Software, and to | |
10 | # permit persons to whom the Software is furnished to do so, subject to | |
11 | # the following conditions: | |
12 | # | |
13 | # The above copyright notice and this permission notice shall be | |
14 | # included in all copies or substantial portions of the Software. | |
15 | # | |
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
19 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
20 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
21 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
22 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
23 | ||
24 | import pytest | |
25 | import os | |
26 | import os.path | |
27 | import barectf | |
28 | import shutil | |
29 | import subprocess | |
816fefd3 PP |
30 | import tempfile |
31 | ||
32 | ||
33 | def pytest_collect_file(parent, path): | |
34 | yaml_ext = '.yaml' | |
35 | ||
36 | if path.ext != yaml_ext: | |
37 | # not a YAML file: cancel | |
38 | return | |
39 | ||
7fdaeefa | 40 | # If `path` is |
101c7c7d PP |
41 | # `/home/jo/barectf/tests/tracing/configs/basic/static-array/of-str.yaml`, |
42 | # for example, then: | |
816fefd3 | 43 | # |
101c7c7d PP |
44 | # `cat`: |
45 | # `basic` | |
46 | # | |
47 | # `subcat`: | |
48 | # `static-array` | |
49 | # | |
50 | # `file_name`: | |
51 | # `of-str.yaml` | |
816fefd3 | 52 | path_str = str(path) |
101c7c7d PP |
53 | file_name = os.path.basename(path_str) |
54 | subcat_dir = os.path.dirname(path_str) | |
55 | subcat = os.path.basename(subcat_dir) | |
56 | cat_dir = os.path.dirname(subcat_dir) | |
57 | cat = os.path.basename(cat_dir) | |
58 | configs_dir = os.path.dirname(cat_dir) | |
5b906cae PP |
59 | valid_cats = { |
60 | 'basic', | |
61 | 'counter-clock', | |
62 | 'basic-extra-pc-ft-members', | |
8d813789 | 63 | 'packet-set-buf', |
5b906cae | 64 | } |
7fdaeefa | 65 | |
5b906cae | 66 | if cat not in valid_cats or os.path.basename(configs_dir) != 'configs': |
7fdaeefa PP |
67 | # not a YAML configuration test |
68 | return | |
816fefd3 | 69 | |
816fefd3 | 70 | # create C source, expectation file, and support directory paths |
7fdaeefa | 71 | base_dir = os.path.dirname(configs_dir) |
101c7c7d PP |
72 | base_name = file_name.replace(yaml_ext, '') |
73 | subcat_rel_dir = os.path.join(cat, subcat) | |
74 | src_path = os.path.join(base_dir, 'src', subcat_rel_dir, f'{base_name}.c') | |
75 | data_expect_path = os.path.join(base_dir, 'expect', subcat_rel_dir, f'{base_name}.data.expect') | |
76 | metadata_expect_path = os.path.join(base_dir, 'expect', subcat_rel_dir, | |
77 | f'{base_name}.metadata.expect') | |
78 | support_dir_path = os.path.join(base_dir, 'support', cat) | |
816fefd3 PP |
79 | |
80 | # create the file node | |
81 | return _YamlFile.from_parent(parent, fspath=path, src_path=src_path, | |
82 | data_expect_path=data_expect_path, | |
83 | metadata_expect_path=metadata_expect_path, | |
101c7c7d PP |
84 | support_dir_path=support_dir_path, |
85 | name=f'test-{cat}-{subcat}-{base_name}') | |
816fefd3 PP |
86 | |
87 | ||
88 | class _YamlFile(pytest.File): | |
89 | def __init__(self, parent, fspath, src_path, data_expect_path, metadata_expect_path, | |
90 | support_dir_path, name): | |
91 | super().__init__(parent=parent, fspath=fspath) | |
92 | self._name = name | |
93 | self._src_path = src_path | |
94 | self._data_expect_path = data_expect_path | |
95 | self._metadata_expect_path = metadata_expect_path | |
96 | self._support_dir_path = support_dir_path | |
97 | ||
98 | def collect(self): | |
99 | # yield a single item | |
100 | yield _YamlItem.from_parent(self, name=self._name, src_path=self._src_path, | |
101 | data_expect_path=self._data_expect_path, | |
102 | metadata_expect_path=self._metadata_expect_path, | |
103 | support_dir_path=self._support_dir_path) | |
104 | ||
105 | ||
106 | class _YamlItem(pytest.Item): | |
107 | def __init__(self, parent, name, src_path, data_expect_path, metadata_expect_path, | |
108 | support_dir_path): | |
109 | super().__init__(parent=parent, name=name) | |
110 | self._src_path = src_path | |
111 | self._data_expect_path = data_expect_path | |
112 | self._metadata_expect_path = metadata_expect_path | |
113 | self._support_dir_path = support_dir_path | |
114 | ||
115 | def runtest(self): | |
116 | # create a temporary directory | |
117 | tmpdir = tempfile.TemporaryDirectory(prefix='pytest-barectf') | |
88004776 PP |
118 | |
119 | # create barectf configuration | |
816fefd3 | 120 | with open(self.fspath) as f: |
7fdaeefa | 121 | cfg = barectf.configuration_from_file(f, inclusion_directories=[self._support_dir_path]) |
88004776 PP |
122 | |
123 | # generate and write C code files | |
124 | cg = barectf.CodeGenerator(cfg) | |
125 | files = cg.generate_c_headers() | |
126 | files += cg.generate_c_sources() | |
127 | ||
128 | for file in files: | |
816fefd3 | 129 | with open(os.path.join(tmpdir.name, file.name), 'w') as f: |
88004776 PP |
130 | f.write(file.contents) |
131 | ||
132 | # generate metadata stream, stripping the version and date | |
133 | file = cg.generate_metadata_stream() | |
134 | lines = file.contents.split('\n') | |
135 | new_lines = [] | |
136 | discard_patterns = [ | |
137 | 'Copyright (c)', | |
138 | 'The following code was generated', | |
139 | '* on ', | |
140 | 'barectf_gen_date =', | |
141 | 'tracer_major =', | |
142 | 'tracer_minor =', | |
143 | 'tracer_patch =', | |
1d3b354e | 144 | 'tracer_pre =', |
88004776 PP |
145 | ] |
146 | ||
147 | for line in lines: | |
148 | skip = False | |
149 | ||
150 | for pattern in discard_patterns: | |
151 | if pattern in line: | |
152 | skip = True | |
153 | ||
154 | if skip: | |
155 | continue | |
156 | ||
157 | new_lines.append(line) | |
158 | ||
159 | actual_metadata = '\n'.join(new_lines) | |
160 | ||
161 | # copy Makefile to build directory | |
816fefd3 | 162 | shutil.copy(os.path.join(self._support_dir_path, 'Makefile'), tmpdir.name) |
88004776 PP |
163 | |
164 | # copy platform files to build directory | |
816fefd3 PP |
165 | shutil.copy(os.path.join(self._support_dir_path, 'test-platform.c'), tmpdir.name) |
166 | shutil.copy(os.path.join(self._support_dir_path, 'test-platform.h'), tmpdir.name) | |
88004776 PP |
167 | |
168 | # copy specific source code file to build directory | |
816fefd3 | 169 | shutil.copy(self._src_path, os.path.join(tmpdir.name, 'test.c')) |
88004776 PP |
170 | |
171 | # build the test | |
816fefd3 | 172 | subprocess.check_output(['make'], cwd=tmpdir.name) |
88004776 PP |
173 | |
174 | # run the test (produce the data stream) | |
816fefd3 | 175 | subprocess.check_output(['./test'], cwd=tmpdir.name) |
88004776 PP |
176 | |
177 | # read actual stream | |
816fefd3 | 178 | with open(os.path.join(tmpdir.name, 'stream'), 'rb') as f: |
88004776 PP |
179 | actual_stream = f.read() |
180 | ||
181 | # read data stream expectation file | |
816fefd3 | 182 | with open(self._data_expect_path, 'rb') as f: |
88004776 PP |
183 | expected_stream = f.read() |
184 | ||
185 | # read metadata stream expectation file | |
816fefd3 | 186 | with open(self._metadata_expect_path, 'r') as f: |
88004776 PP |
187 | expected_metadata = f.read() |
188 | ||
189 | # validate streams | |
190 | assert actual_metadata == expected_metadata | |
191 | assert actual_stream == expected_stream | |
192 | ||
816fefd3 PP |
193 | # delete temporary directory |
194 | tmpdir.cleanup() | |
195 | ||
196 | def repr_failure(self, excinfo, style=None): | |
197 | return f'`{self.fspath}` failed: {excinfo}.' | |
198 | ||
199 | def reportinfo(self): | |
200 | return self.fspath, None, self.name |