Linux-libre 5.7.3-gnu
[librecmc/linux-libre.git] / scripts / kconfig / tests / conftest.py
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)