diff options
Diffstat (limited to 'scripts/kconfig/tests/conftest.py')
-rw-r--r-- | scripts/kconfig/tests/conftest.py | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py new file mode 100644 index 000000000000..0345ef6e3273 --- /dev/null +++ b/scripts/kconfig/tests/conftest.py | |||
@@ -0,0 +1,291 @@ | |||
1 | # SPDX-License-Identifier: GPL-2.0 | ||
2 | # | ||
3 | # Copyright (C) 2018 Masahiro Yamada <yamada.masahiro@socionext.com> | ||
4 | # | ||
5 | |||
6 | """ | ||
7 | Kconfig unit testing framework. | ||
8 | |||
9 | This provides fixture functions commonly used from test files. | ||
10 | """ | ||
11 | |||
12 | import os | ||
13 | import pytest | ||
14 | import shutil | ||
15 | import subprocess | ||
16 | import tempfile | ||
17 | |||
18 | CONF_PATH = os.path.abspath(os.path.join('scripts', 'kconfig', 'conf')) | ||
19 | |||
20 | |||
21 | class Conf: | ||
22 | """Kconfig runner and result checker. | ||
23 | |||
24 | This class provides methods to run text-based interface of Kconfig | ||
25 | (scripts/kconfig/conf) and retrieve the resulted configuration, | ||
26 | stdout, and stderr. It also provides methods to compare those | ||
27 | results with expectations. | ||
28 | """ | ||
29 | |||
30 | def __init__(self, request): | ||
31 | """Create a new Conf instance. | ||
32 | |||
33 | request: object to introspect the requesting test module | ||
34 | """ | ||
35 | # the directory of the test being run | ||
36 | self._test_dir = os.path.dirname(str(request.fspath)) | ||
37 | |||
38 | # runners | ||
39 | def _run_conf(self, mode, dot_config=None, out_file='.config', | ||
40 | interactive=False, in_keys=None, extra_env={}): | ||
41 | """Run text-based Kconfig executable and save the result. | ||
42 | |||
43 | mode: input mode option (--oldaskconfig, --defconfig=<file> etc.) | ||
44 | dot_config: .config file to use for configuration base | ||
45 | out_file: file name to contain the output config data | ||
46 | interactive: flag to specify the interactive mode | ||
47 | in_keys: key inputs for interactive modes | ||
48 | extra_env: additional environments | ||
49 | returncode: exit status of the Kconfig executable | ||
50 | """ | ||
51 | command = [CONF_PATH, mode, 'Kconfig'] | ||
52 | |||
53 | # Override 'srctree' environment to make the test as the top directory | ||
54 | extra_env['srctree'] = self._test_dir | ||
55 | |||
56 | # Run Kconfig in a temporary directory. | ||
57 | # This directory is automatically removed when done. | ||
58 | with tempfile.TemporaryDirectory() as temp_dir: | ||
59 | |||
60 | # if .config is given, copy it to the working directory | ||
61 | if dot_config: | ||
62 | shutil.copyfile(os.path.join(self._test_dir, dot_config), | ||
63 | os.path.join(temp_dir, '.config')) | ||
64 | |||
65 | ps = subprocess.Popen(command, | ||
66 | stdin=subprocess.PIPE, | ||
67 | stdout=subprocess.PIPE, | ||
68 | stderr=subprocess.PIPE, | ||
69 | cwd=temp_dir, | ||
70 | env=dict(os.environ, **extra_env)) | ||
71 | |||
72 | # If input key sequence is given, feed it to stdin. | ||
73 | if in_keys: | ||
74 | ps.stdin.write(in_keys.encode('utf-8')) | ||
75 | |||
76 | while ps.poll() is None: | ||
77 | # For interactive modes such as oldaskconfig, oldconfig, | ||
78 | # send 'Enter' key until the program finishes. | ||
79 | if interactive: | ||
80 | ps.stdin.write(b'\n') | ||
81 | |||
82 | self.retcode = ps.returncode | ||
83 | self.stdout = ps.stdout.read().decode() | ||
84 | self.stderr = ps.stderr.read().decode() | ||
85 | |||
86 | # Retrieve the resulted config data only when .config is supposed | ||
87 | # to exist. If the command fails, the .config does not exist. | ||
88 | # 'listnewconfig' does not produce .config in the first place. | ||
89 | if self.retcode == 0 and out_file: | ||
90 | with open(os.path.join(temp_dir, out_file)) as f: | ||
91 | self.config = f.read() | ||
92 | else: | ||
93 | self.config = None | ||
94 | |||
95 | # Logging: | ||
96 | # Pytest captures the following information by default. In failure | ||
97 | # of tests, the captured log will be displayed. This will be useful to | ||
98 | # figure out what has happened. | ||
99 | |||
100 | print("[command]\n{}\n".format(' '.join(command))) | ||
101 | |||
102 | print("[retcode]\n{}\n".format(self.retcode)) | ||
103 | |||
104 | print("[stdout]") | ||
105 | print(self.stdout) | ||
106 | |||
107 | print("[stderr]") | ||
108 | print(self.stderr) | ||
109 | |||
110 | if self.config is not None: | ||
111 | print("[output for '{}']".format(out_file)) | ||
112 | print(self.config) | ||
113 | |||
114 | return self.retcode | ||
115 | |||
116 | def oldaskconfig(self, dot_config=None, in_keys=None): | ||
117 | """Run oldaskconfig. | ||
118 | |||
119 | dot_config: .config file to use for configuration base (optional) | ||
120 | in_key: key inputs (optional) | ||
121 | returncode: exit status of the Kconfig executable | ||
122 | """ | ||
123 | return self._run_conf('--oldaskconfig', dot_config=dot_config, | ||
124 | interactive=True, in_keys=in_keys) | ||
125 | |||
126 | def oldconfig(self, dot_config=None, in_keys=None): | ||
127 | """Run oldconfig. | ||
128 | |||
129 | dot_config: .config file to use for configuration base (optional) | ||
130 | in_key: key inputs (optional) | ||
131 | returncode: exit status of the Kconfig executable | ||
132 | """ | ||
133 | return self._run_conf('--oldconfig', dot_config=dot_config, | ||
134 | interactive=True, in_keys=in_keys) | ||
135 | |||
136 | def olddefconfig(self, dot_config=None): | ||
137 | """Run olddefconfig. | ||
138 | |||
139 | dot_config: .config file to use for configuration base (optional) | ||
140 | returncode: exit status of the Kconfig executable | ||
141 | """ | ||
142 | return self._run_conf('--olddefconfig', dot_config=dot_config) | ||
143 | |||
144 | def defconfig(self, defconfig): | ||
145 | """Run defconfig. | ||
146 | |||
147 | defconfig: defconfig file for input | ||
148 | returncode: exit status of the Kconfig executable | ||
149 | """ | ||
150 | defconfig_path = os.path.join(self._test_dir, defconfig) | ||
151 | return self._run_conf('--defconfig={}'.format(defconfig_path)) | ||
152 | |||
153 | def _allconfig(self, mode, all_config): | ||
154 | if all_config: | ||
155 | all_config_path = os.path.join(self._test_dir, all_config) | ||
156 | extra_env = {'KCONFIG_ALLCONFIG': all_config_path} | ||
157 | else: | ||
158 | extra_env = {} | ||
159 | |||
160 | return self._run_conf('--{}config'.format(mode), extra_env=extra_env) | ||
161 | |||
162 | def allyesconfig(self, all_config=None): | ||
163 | """Run allyesconfig. | ||
164 | |||
165 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional) | ||
166 | returncode: exit status of the Kconfig executable | ||
167 | """ | ||
168 | return self._allconfig('allyes', all_config) | ||
169 | |||
170 | def allmodconfig(self, all_config=None): | ||
171 | """Run allmodconfig. | ||
172 | |||
173 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional) | ||
174 | returncode: exit status of the Kconfig executable | ||
175 | """ | ||
176 | return self._allconfig('allmod', all_config) | ||
177 | |||
178 | def allnoconfig(self, all_config=None): | ||
179 | """Run allnoconfig. | ||
180 | |||
181 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional) | ||
182 | returncode: exit status of the Kconfig executable | ||
183 | """ | ||
184 | return self._allconfig('allno', all_config) | ||
185 | |||
186 | def alldefconfig(self, all_config=None): | ||
187 | """Run alldefconfig. | ||
188 | |||
189 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional) | ||
190 | returncode: exit status of the Kconfig executable | ||
191 | """ | ||
192 | return self._allconfig('alldef', all_config) | ||
193 | |||
194 | def randconfig(self, all_config=None): | ||
195 | """Run randconfig. | ||
196 | |||
197 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional) | ||
198 | returncode: exit status of the Kconfig executable | ||
199 | """ | ||
200 | return self._allconfig('rand', all_config) | ||
201 | |||
202 | def savedefconfig(self, dot_config): | ||
203 | """Run savedefconfig. | ||
204 | |||
205 | dot_config: .config file for input | ||
206 | returncode: exit status of the Kconfig executable | ||
207 | """ | ||
208 | return self._run_conf('--savedefconfig', out_file='defconfig') | ||
209 | |||
210 | def listnewconfig(self, dot_config=None): | ||
211 | """Run listnewconfig. | ||
212 | |||
213 | dot_config: .config file to use for configuration base (optional) | ||
214 | returncode: exit status of the Kconfig executable | ||
215 | """ | ||
216 | return self._run_conf('--listnewconfig', dot_config=dot_config, | ||
217 | out_file=None) | ||
218 | |||
219 | # checkers | ||
220 | def _read_and_compare(self, compare, expected): | ||
221 | """Compare the result with expectation. | ||
222 | |||
223 | compare: function to compare the result with expectation | ||
224 | expected: file that contains the expected data | ||
225 | """ | ||
226 | with open(os.path.join(self._test_dir, expected)) as f: | ||
227 | expected_data = f.read() | ||
228 | return compare(self, expected_data) | ||
229 | |||
230 | def _contains(self, attr, expected): | ||
231 | return self._read_and_compare( | ||
232 | lambda s, e: getattr(s, attr).find(e) >= 0, | ||
233 | expected) | ||
234 | |||
235 | def _matches(self, attr, expected): | ||
236 | return self._read_and_compare(lambda s, e: getattr(s, attr) == e, | ||
237 | expected) | ||
238 | |||
239 | def config_contains(self, expected): | ||
240 | """Check if resulted configuration contains expected data. | ||
241 | |||
242 | expected: file that contains the expected data | ||
243 | returncode: True if result contains the expected data, False otherwise | ||
244 | """ | ||
245 | return self._contains('config', expected) | ||
246 | |||
247 | def config_matches(self, expected): | ||
248 | """Check if resulted configuration exactly matches expected data. | ||
249 | |||
250 | expected: file that contains the expected data | ||
251 | returncode: True if result matches the expected data, False otherwise | ||
252 | """ | ||
253 | return self._matches('config', expected) | ||
254 | |||
255 | def stdout_contains(self, expected): | ||
256 | """Check if resulted stdout contains expected data. | ||
257 | |||
258 | expected: file that contains the expected data | ||
259 | returncode: True if result contains the expected data, False otherwise | ||
260 | """ | ||
261 | return self._contains('stdout', expected) | ||
262 | |||
263 | def stdout_matches(self, expected): | ||
264 | """Check if resulted stdout exactly matches expected data. | ||
265 | |||
266 | expected: file that contains the expected data | ||
267 | returncode: True if result matches the expected data, False otherwise | ||
268 | """ | ||
269 | return self._matches('stdout', expected) | ||
270 | |||
271 | def stderr_contains(self, expected): | ||
272 | """Check if resulted stderr contains expected data. | ||
273 | |||
274 | expected: file that contains the expected data | ||
275 | returncode: True if result contains the expected data, False otherwise | ||
276 | """ | ||
277 | return self._contains('stderr', expected) | ||
278 | |||
279 | def stderr_matches(self, expected): | ||
280 | """Check if resulted stderr exactly matches expected data. | ||
281 | |||
282 | expected: file that contains the expected data | ||
283 | returncode: True if result matches the expected data, False otherwise | ||
284 | """ | ||
285 | return self._matches('stderr', expected) | ||
286 | |||
287 | |||
288 | @pytest.fixture(scope="module") | ||
289 | def conf(request): | ||
290 | """Create a Conf instance and provide it to test functions.""" | ||
291 | return Conf(request) | ||