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): 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 else: >> 162 extra_env = {} 161 163 162 return self._run_conf('--{}config'.for 164 return self._run_conf('--{}config'.format(mode), extra_env=extra_env) 163 165 164 def allyesconfig(self, all_config=None): 166 def allyesconfig(self, all_config=None): 165 """Run allyesconfig. 167 """Run allyesconfig. 166 168 167 all_config: fragment config file for K 169 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 168 returncode: exit status of the Kconfig 170 returncode: exit status of the Kconfig executable 169 """ 171 """ 170 return self._allconfig('allyes', all_c 172 return self._allconfig('allyes', all_config) 171 173 172 def allmodconfig(self, all_config=None): 174 def allmodconfig(self, all_config=None): 173 """Run allmodconfig. 175 """Run allmodconfig. 174 176 175 all_config: fragment config file for K 177 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 176 returncode: exit status of the Kconfig 178 returncode: exit status of the Kconfig executable 177 """ 179 """ 178 return self._allconfig('allmod', all_c 180 return self._allconfig('allmod', all_config) 179 181 180 def allnoconfig(self, all_config=None): 182 def allnoconfig(self, all_config=None): 181 """Run allnoconfig. 183 """Run allnoconfig. 182 184 183 all_config: fragment config file for K 185 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 184 returncode: exit status of the Kconfig 186 returncode: exit status of the Kconfig executable 185 """ 187 """ 186 return self._allconfig('allno', all_co 188 return self._allconfig('allno', all_config) 187 189 188 def alldefconfig(self, all_config=None): 190 def alldefconfig(self, all_config=None): 189 """Run alldefconfig. 191 """Run alldefconfig. 190 192 191 all_config: fragment config file for K 193 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 192 returncode: exit status of the Kconfig 194 returncode: exit status of the Kconfig executable 193 """ 195 """ 194 return self._allconfig('alldef', all_c 196 return self._allconfig('alldef', all_config) 195 197 196 def randconfig(self, all_config=None, seed !! 198 def randconfig(self, all_config=None): 197 """Run randconfig. 199 """Run randconfig. 198 200 199 all_config: fragment config file for K 201 all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 200 seed: the seed for randconfig (optiona << 201 returncode: exit status of the Kconfig 202 returncode: exit status of the Kconfig executable 202 """ 203 """ 203 if seed is not None: !! 204 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 205 210 def savedefconfig(self, dot_config): 206 def savedefconfig(self, dot_config): 211 """Run savedefconfig. 207 """Run savedefconfig. 212 208 213 dot_config: .config file for input 209 dot_config: .config file for input 214 returncode: exit status of the Kconfig 210 returncode: exit status of the Kconfig executable 215 """ 211 """ 216 return self._run_conf('--savedefconfig 212 return self._run_conf('--savedefconfig', out_file='defconfig') 217 213 218 def listnewconfig(self, dot_config=None): 214 def listnewconfig(self, dot_config=None): 219 """Run listnewconfig. 215 """Run listnewconfig. 220 216 221 dot_config: .config file to use for co 217 dot_config: .config file to use for configuration base (optional) 222 returncode: exit status of the Kconfig 218 returncode: exit status of the Kconfig executable 223 """ 219 """ 224 return self._run_conf('--listnewconfig 220 return self._run_conf('--listnewconfig', dot_config=dot_config, 225 out_file=None) 221 out_file=None) 226 222 227 # checkers 223 # checkers 228 def _read_and_compare(self, compare, expec 224 def _read_and_compare(self, compare, expected): 229 """Compare the result with expectation 225 """Compare the result with expectation. 230 226 231 compare: function to compare the resul 227 compare: function to compare the result with expectation 232 expected: file that contains the expec 228 expected: file that contains the expected data 233 """ 229 """ 234 with open(os.path.join(self._test_dir, 230 with open(os.path.join(self._test_dir, expected)) as f: 235 expected_data = f.read() 231 expected_data = f.read() 236 return compare(self, expected_data) 232 return compare(self, expected_data) 237 233 238 def _contains(self, attr, expected): 234 def _contains(self, attr, expected): 239 return self._read_and_compare( 235 return self._read_and_compare( 240 lambda s, 236 lambda s, e: getattr(s, attr).find(e) >= 0, 241 expected) 237 expected) 242 238 243 def _matches(self, attr, expected): 239 def _matches(self, attr, expected): 244 return self._read_and_compare(lambda s 240 return self._read_and_compare(lambda s, e: getattr(s, attr) == e, 245 expected 241 expected) 246 242 247 def config_contains(self, expected): 243 def config_contains(self, expected): 248 """Check if resulted configuration con 244 """Check if resulted configuration contains expected data. 249 245 250 expected: file that contains the expec 246 expected: file that contains the expected data 251 returncode: True if result contains th 247 returncode: True if result contains the expected data, False otherwise 252 """ 248 """ 253 return self._contains('config', expect 249 return self._contains('config', expected) 254 250 255 def config_matches(self, expected): 251 def config_matches(self, expected): 256 """Check if resulted configuration exa 252 """Check if resulted configuration exactly matches expected data. 257 253 258 expected: file that contains the expec 254 expected: file that contains the expected data 259 returncode: True if result matches the 255 returncode: True if result matches the expected data, False otherwise 260 """ 256 """ 261 return self._matches('config', expecte 257 return self._matches('config', expected) 262 258 263 def stdout_contains(self, expected): 259 def stdout_contains(self, expected): 264 """Check if resulted stdout contains e 260 """Check if resulted stdout contains expected data. 265 261 266 expected: file that contains the expec 262 expected: file that contains the expected data 267 returncode: True if result contains th 263 returncode: True if result contains the expected data, False otherwise 268 """ 264 """ 269 return self._contains('stdout', expect 265 return self._contains('stdout', expected) 270 266 271 def stdout_matches(self, expected): 267 def stdout_matches(self, expected): 272 """Check if resulted stdout exactly ma 268 """Check if resulted stdout exactly matches expected data. 273 269 274 expected: file that contains the expec 270 expected: file that contains the expected data 275 returncode: True if result matches the 271 returncode: True if result matches the expected data, False otherwise 276 """ 272 """ 277 return self._matches('stdout', expecte 273 return self._matches('stdout', expected) 278 274 279 def stderr_contains(self, expected): 275 def stderr_contains(self, expected): 280 """Check if resulted stderr contains e 276 """Check if resulted stderr contains expected data. 281 277 282 expected: file that contains the expec 278 expected: file that contains the expected data 283 returncode: True if result contains th 279 returncode: True if result contains the expected data, False otherwise 284 """ 280 """ 285 return self._contains('stderr', expect 281 return self._contains('stderr', expected) 286 282 287 def stderr_matches(self, expected): 283 def stderr_matches(self, expected): 288 """Check if resulted stderr exactly ma 284 """Check if resulted stderr exactly matches expected data. 289 285 290 expected: file that contains the expec 286 expected: file that contains the expected data 291 returncode: True if result matches the 287 returncode: True if result matches the expected data, False otherwise 292 """ 288 """ 293 return self._matches('stderr', expecte 289 return self._matches('stderr', expected) 294 290 295 291 296 @pytest.fixture(scope="module") 292 @pytest.fixture(scope="module") 297 def conf(request): 293 def conf(request): 298 """Create a Conf instance and provide it t 294 """Create a Conf instance and provide it to test functions.""" 299 return Conf(request) 295 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.