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