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 << 57 # by the user's environment. << 58 extra_env['KCONFIG_DEFCONFIG_LIST'] = << 59 << 60 # Run Kconfig in a temporary directory 56 # Run Kconfig in a temporary directory. 61 # This directory is automatically remo 57 # This directory is automatically removed when done. 62 with tempfile.TemporaryDirectory() as 58 with tempfile.TemporaryDirectory() as temp_dir: 63 59 64 # if .config is given, copy it to 60 # if .config is given, copy it to the working directory 65 if dot_config: 61 if dot_config: 66 shutil.copyfile(os.path.join(s 62 shutil.copyfile(os.path.join(self._test_dir, dot_config), 67 os.path.join(t 63 os.path.join(temp_dir, '.config')) 68 64 69 ps = subprocess.Popen(command, 65 ps = subprocess.Popen(command, 70 stdin=subpro 66 stdin=subprocess.PIPE, 71 stdout=subpr 67 stdout=subprocess.PIPE, 72 stderr=subpr 68 stderr=subprocess.PIPE, 73 cwd=temp_dir 69 cwd=temp_dir, 74 env=dict(os. 70 env=dict(os.environ, **extra_env)) 75 71 76 # If input key sequence is given, 72 # If input key sequence is given, feed it to stdin. 77 if in_keys: 73 if in_keys: 78 ps.stdin.write(in_keys.encode( 74 ps.stdin.write(in_keys.encode('utf-8')) 79 75 80 while ps.poll() is None: 76 while ps.poll() is None: 81 # For interactive modes such a 77 # For interactive modes such as oldaskconfig, oldconfig, 82 # send 'Enter' key until the p 78 # send 'Enter' key until the program finishes. 83 if interactive: 79 if interactive: 84 ps.stdin.write(b'\n') 80 ps.stdin.write(b'\n') 85 81 86 self.retcode = ps.returncode 82 self.retcode = ps.returncode 87 self.stdout = ps.stdout.read().dec 83 self.stdout = ps.stdout.read().decode() 88 self.stderr = ps.stderr.read().dec 84 self.stderr = ps.stderr.read().decode() 89 85 90 # Retrieve the resulted config dat 86 # Retrieve the resulted config data only when .config is supposed 91 # to exist. If the command fails, 87 # to exist. If the command fails, the .config does not exist. 92 # 'listnewconfig' does not produce 88 # 'listnewconfig' does not produce .config in the first place. 93 if self.retcode == 0 and out_file: 89 if self.retcode == 0 and out_file: 94 with open(os.path.join(temp_di 90 with open(os.path.join(temp_dir, out_file)) as f: 95 self.config = f.read() 91 self.config = f.read() 96 else: 92 else: 97 self.config = None 93 self.config = None 98 94 99 # Logging: 95 # Logging: 100 # Pytest captures the following inform 96 # Pytest captures the following information by default. In failure 101 # of tests, the captured log will be d 97 # of tests, the captured log will be displayed. This will be useful to 102 # figure out what has happened. 98 # figure out what has happened. 103 99 104 print("[command]\n{}\n".format(' '.joi 100 print("[command]\n{}\n".format(' '.join(command))) 105 101 106 print("[retcode]\n{}\n".format(self.re 102 print("[retcode]\n{}\n".format(self.retcode)) 107 103 108 print("[stdout]") 104 print("[stdout]") 109 print(self.stdout) 105 print(self.stdout) 110 106 111 print("[stderr]") 107 print("[stderr]") 112 print(self.stderr) 108 print(self.stderr) 113 109 114 if self.config is not None: 110 if self.config is not None: 115 print("[output for '{}']".format(o 111 print("[output for '{}']".format(out_file)) 116 print(self.config) 112 print(self.config) 117 113 118 return self.retcode 114 return self.retcode 119 115 120 def oldaskconfig(self, dot_config=None, in 116 def oldaskconfig(self, dot_config=None, in_keys=None): 121 """Run oldaskconfig. 117 """Run oldaskconfig. 122 118 123 dot_config: .config file to use for co 119 dot_config: .config file to use for configuration base (optional) 124 in_key: key inputs (optional) 120 in_key: key inputs (optional) 125 returncode: exit status of the Kconfig 121 returncode: exit status of the Kconfig executable 126 """ 122 """ 127 return self._run_conf('--oldaskconfig' 123 return self._run_conf('--oldaskconfig', dot_config=dot_config, 128 interactive=True 124 interactive=True, in_keys=in_keys) 129 125 130 def oldconfig(self, dot_config=None, in_ke 126 def oldconfig(self, dot_config=None, in_keys=None): 131 """Run oldconfig. 127 """Run oldconfig. 132 128 133 dot_config: .config file to use for co 129 dot_config: .config file to use for configuration base (optional) 134 in_key: key inputs (optional) 130 in_key: key inputs (optional) 135 returncode: exit status of the Kconfig 131 returncode: exit status of the Kconfig executable 136 """ 132 """ 137 return self._run_conf('--oldconfig', d 133 return self._run_conf('--oldconfig', dot_config=dot_config, 138 interactive=True 134 interactive=True, in_keys=in_keys) 139 135 140 def olddefconfig(self, dot_config=None): 136 def olddefconfig(self, dot_config=None): 141 """Run olddefconfig. 137 """Run olddefconfig. 142 138 143 dot_config: .config file to use for co 139 dot_config: .config file to use for configuration base (optional) 144 returncode: exit status of the Kconfig 140 returncode: exit status of the Kconfig executable 145 """ 141 """ 146 return self._run_conf('--olddefconfig' 142 return self._run_conf('--olddefconfig', dot_config=dot_config) 147 143 148 def defconfig(self, defconfig): 144 def defconfig(self, defconfig): 149 """Run defconfig. 145 """Run defconfig. 150 146 151 defconfig: defconfig file for input 147 defconfig: defconfig file for input 152 returncode: exit status of the Kconfig 148 returncode: exit status of the Kconfig executable 153 """ 149 """ 154 defconfig_path = os.path.join(self._te 150 defconfig_path = os.path.join(self._test_dir, defconfig) 155 return self._run_conf('--defconfig={}' 151 return self._run_conf('--defconfig={}'.format(defconfig_path)) 156 152 157 def _allconfig(self, mode, all_config, ext !! 153 def _allconfig(self, mode, all_config): 158 if all_config: 154 if all_config: 159 all_config_path = os.path.join(sel 155 all_config_path = os.path.join(self._test_dir, all_config) 160 extra_env['KCONFIG_ALLCONFIG'] = a !! 156 extra_env = {'KCONFIG_ALLCONFIG': all_config_path} >> 157 else: >> 158 extra_env = {} 161 159 162 return self._run_conf('--{}config'.for 160 return self._run_conf('--{}config'.format(mode), extra_env=extra_env) 163 161 164 def allyesconfig(self, all_config=None): 162 def allyesconfig(self, all_config=None): 165 """Run allyesconfig. 163 """Run allyesconfig. 166 164 167 all_config: fragment config file for K 165 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 168 returncode: exit status of the Kconfig 166 returncode: exit status of the Kconfig executable 169 """ 167 """ 170 return self._allconfig('allyes', all_c 168 return self._allconfig('allyes', all_config) 171 169 172 def allmodconfig(self, all_config=None): 170 def allmodconfig(self, all_config=None): 173 """Run allmodconfig. 171 """Run allmodconfig. 174 172 175 all_config: fragment config file for K 173 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 176 returncode: exit status of the Kconfig 174 returncode: exit status of the Kconfig executable 177 """ 175 """ 178 return self._allconfig('allmod', all_c 176 return self._allconfig('allmod', all_config) 179 177 180 def allnoconfig(self, all_config=None): 178 def allnoconfig(self, all_config=None): 181 """Run allnoconfig. 179 """Run allnoconfig. 182 180 183 all_config: fragment config file for K 181 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 184 returncode: exit status of the Kconfig 182 returncode: exit status of the Kconfig executable 185 """ 183 """ 186 return self._allconfig('allno', all_co 184 return self._allconfig('allno', all_config) 187 185 188 def alldefconfig(self, all_config=None): 186 def alldefconfig(self, all_config=None): 189 """Run alldefconfig. 187 """Run alldefconfig. 190 188 191 all_config: fragment config file for K 189 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 192 returncode: exit status of the Kconfig 190 returncode: exit status of the Kconfig executable 193 """ 191 """ 194 return self._allconfig('alldef', all_c 192 return self._allconfig('alldef', all_config) 195 193 196 def randconfig(self, all_config=None, seed !! 194 def randconfig(self, all_config=None): 197 """Run randconfig. 195 """Run randconfig. 198 196 199 all_config: fragment config file for K 197 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 200 seed: the seed for randconfig (optiona << 201 returncode: exit status of the Kconfig 198 returncode: exit status of the Kconfig executable 202 """ 199 """ 203 if seed is not None: !! 200 return self._allconfig('rand', all_config) 204 extra_env = {'KCONFIG_SEED': hex(s << 205 else: << 206 extra_env = {} << 207 << 208 return self._allconfig('rand', all_con << 209 201 210 def savedefconfig(self, dot_config): 202 def savedefconfig(self, dot_config): 211 """Run savedefconfig. 203 """Run savedefconfig. 212 204 213 dot_config: .config file for input 205 dot_config: .config file for input 214 returncode: exit status of the Kconfig 206 returncode: exit status of the Kconfig executable 215 """ 207 """ 216 return self._run_conf('--savedefconfig 208 return self._run_conf('--savedefconfig', out_file='defconfig') 217 209 218 def listnewconfig(self, dot_config=None): 210 def listnewconfig(self, dot_config=None): 219 """Run listnewconfig. 211 """Run listnewconfig. 220 212 221 dot_config: .config file to use for co 213 dot_config: .config file to use for configuration base (optional) 222 returncode: exit status of the Kconfig 214 returncode: exit status of the Kconfig executable 223 """ 215 """ 224 return self._run_conf('--listnewconfig 216 return self._run_conf('--listnewconfig', dot_config=dot_config, 225 out_file=None) 217 out_file=None) 226 218 227 # checkers 219 # checkers 228 def _read_and_compare(self, compare, expec 220 def _read_and_compare(self, compare, expected): 229 """Compare the result with expectation 221 """Compare the result with expectation. 230 222 231 compare: function to compare the resul 223 compare: function to compare the result with expectation 232 expected: file that contains the expec 224 expected: file that contains the expected data 233 """ 225 """ 234 with open(os.path.join(self._test_dir, 226 with open(os.path.join(self._test_dir, expected)) as f: 235 expected_data = f.read() 227 expected_data = f.read() 236 return compare(self, expected_data) 228 return compare(self, expected_data) 237 229 238 def _contains(self, attr, expected): 230 def _contains(self, attr, expected): 239 return self._read_and_compare( 231 return self._read_and_compare( 240 lambda s, 232 lambda s, e: getattr(s, attr).find(e) >= 0, 241 expected) 233 expected) 242 234 243 def _matches(self, attr, expected): 235 def _matches(self, attr, expected): 244 return self._read_and_compare(lambda s 236 return self._read_and_compare(lambda s, e: getattr(s, attr) == e, 245 expected 237 expected) 246 238 247 def config_contains(self, expected): 239 def config_contains(self, expected): 248 """Check if resulted configuration con 240 """Check if resulted configuration contains expected data. 249 241 250 expected: file that contains the expec 242 expected: file that contains the expected data 251 returncode: True if result contains th 243 returncode: True if result contains the expected data, False otherwise 252 """ 244 """ 253 return self._contains('config', expect 245 return self._contains('config', expected) 254 246 255 def config_matches(self, expected): 247 def config_matches(self, expected): 256 """Check if resulted configuration exa 248 """Check if resulted configuration exactly matches expected data. 257 249 258 expected: file that contains the expec 250 expected: file that contains the expected data 259 returncode: True if result matches the 251 returncode: True if result matches the expected data, False otherwise 260 """ 252 """ 261 return self._matches('config', expecte 253 return self._matches('config', expected) 262 254 263 def stdout_contains(self, expected): 255 def stdout_contains(self, expected): 264 """Check if resulted stdout contains e 256 """Check if resulted stdout contains expected data. 265 257 266 expected: file that contains the expec 258 expected: file that contains the expected data 267 returncode: True if result contains th 259 returncode: True if result contains the expected data, False otherwise 268 """ 260 """ 269 return self._contains('stdout', expect 261 return self._contains('stdout', expected) 270 262 271 def stdout_matches(self, expected): 263 def stdout_matches(self, expected): 272 """Check if resulted stdout exactly ma 264 """Check if resulted stdout exactly matches expected data. 273 265 274 expected: file that contains the expec 266 expected: file that contains the expected data 275 returncode: True if result matches the 267 returncode: True if result matches the expected data, False otherwise 276 """ 268 """ 277 return self._matches('stdout', expecte 269 return self._matches('stdout', expected) 278 270 279 def stderr_contains(self, expected): 271 def stderr_contains(self, expected): 280 """Check if resulted stderr contains e 272 """Check if resulted stderr contains expected data. 281 273 282 expected: file that contains the expec 274 expected: file that contains the expected data 283 returncode: True if result contains th 275 returncode: True if result contains the expected data, False otherwise 284 """ 276 """ 285 return self._contains('stderr', expect 277 return self._contains('stderr', expected) 286 278 287 def stderr_matches(self, expected): 279 def stderr_matches(self, expected): 288 """Check if resulted stderr exactly ma 280 """Check if resulted stderr exactly matches expected data. 289 281 290 expected: file that contains the expec 282 expected: file that contains the expected data 291 returncode: True if result matches the 283 returncode: True if result matches the expected data, False otherwise 292 """ 284 """ 293 return self._matches('stderr', expecte 285 return self._matches('stderr', expected) 294 286 295 287 296 @pytest.fixture(scope="module") 288 @pytest.fixture(scope="module") 297 def conf(request): 289 def conf(request): 298 """Create a Conf instance and provide it t 290 """Create a Conf instance and provide it to test functions.""" 299 return Conf(request) 291 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.