1022a4bf6SMasahiro Yamada# SPDX-License-Identifier: GPL-2.0 2022a4bf6SMasahiro Yamada# 3022a4bf6SMasahiro Yamada# Copyright (C) 2018 Masahiro Yamada <[email protected]> 4022a4bf6SMasahiro Yamada# 5022a4bf6SMasahiro Yamada 6022a4bf6SMasahiro Yamada""" 7022a4bf6SMasahiro YamadaKconfig unit testing framework. 8022a4bf6SMasahiro Yamada 9022a4bf6SMasahiro YamadaThis provides fixture functions commonly used from test files. 10022a4bf6SMasahiro Yamada""" 11022a4bf6SMasahiro Yamada 12022a4bf6SMasahiro Yamadaimport os 13022a4bf6SMasahiro Yamadaimport pytest 14022a4bf6SMasahiro Yamadaimport shutil 15022a4bf6SMasahiro Yamadaimport subprocess 16022a4bf6SMasahiro Yamadaimport tempfile 17022a4bf6SMasahiro Yamada 18022a4bf6SMasahiro YamadaCONF_PATH = os.path.abspath(os.path.join('scripts', 'kconfig', 'conf')) 19022a4bf6SMasahiro Yamada 20022a4bf6SMasahiro Yamada 21022a4bf6SMasahiro Yamadaclass Conf: 22022a4bf6SMasahiro Yamada """Kconfig runner and result checker. 23022a4bf6SMasahiro Yamada 24022a4bf6SMasahiro Yamada This class provides methods to run text-based interface of Kconfig 25022a4bf6SMasahiro Yamada (scripts/kconfig/conf) and retrieve the resulted configuration, 26022a4bf6SMasahiro Yamada stdout, and stderr. It also provides methods to compare those 27022a4bf6SMasahiro Yamada results with expectations. 28022a4bf6SMasahiro Yamada """ 29022a4bf6SMasahiro Yamada 30022a4bf6SMasahiro Yamada def __init__(self, request): 31022a4bf6SMasahiro Yamada """Create a new Conf instance. 32022a4bf6SMasahiro Yamada 33022a4bf6SMasahiro Yamada request: object to introspect the requesting test module 34022a4bf6SMasahiro Yamada """ 35022a4bf6SMasahiro Yamada # the directory of the test being run 36022a4bf6SMasahiro Yamada self._test_dir = os.path.dirname(str(request.fspath)) 37022a4bf6SMasahiro Yamada 38022a4bf6SMasahiro Yamada # runners 39022a4bf6SMasahiro Yamada def _run_conf(self, mode, dot_config=None, out_file='.config', 40022a4bf6SMasahiro Yamada interactive=False, in_keys=None, extra_env={}): 41022a4bf6SMasahiro Yamada """Run text-based Kconfig executable and save the result. 42022a4bf6SMasahiro Yamada 43022a4bf6SMasahiro Yamada mode: input mode option (--oldaskconfig, --defconfig=<file> etc.) 44022a4bf6SMasahiro Yamada dot_config: .config file to use for configuration base 45022a4bf6SMasahiro Yamada out_file: file name to contain the output config data 46022a4bf6SMasahiro Yamada interactive: flag to specify the interactive mode 47022a4bf6SMasahiro Yamada in_keys: key inputs for interactive modes 48022a4bf6SMasahiro Yamada extra_env: additional environments 49022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 50022a4bf6SMasahiro Yamada """ 51022a4bf6SMasahiro Yamada command = [CONF_PATH, mode, 'Kconfig'] 52022a4bf6SMasahiro Yamada 53022a4bf6SMasahiro Yamada # Override 'srctree' environment to make the test as the top directory 54022a4bf6SMasahiro Yamada extra_env['srctree'] = self._test_dir 55022a4bf6SMasahiro Yamada 56b75b0a81SMasahiro Yamada # Clear KCONFIG_DEFCONFIG_LIST to keep unit tests from being affected 57b75b0a81SMasahiro Yamada # by the user's environment. 58b75b0a81SMasahiro Yamada extra_env['KCONFIG_DEFCONFIG_LIST'] = '' 59b75b0a81SMasahiro Yamada 60022a4bf6SMasahiro Yamada # Run Kconfig in a temporary directory. 61022a4bf6SMasahiro Yamada # This directory is automatically removed when done. 62022a4bf6SMasahiro Yamada with tempfile.TemporaryDirectory() as temp_dir: 63022a4bf6SMasahiro Yamada 64022a4bf6SMasahiro Yamada # if .config is given, copy it to the working directory 65022a4bf6SMasahiro Yamada if dot_config: 66022a4bf6SMasahiro Yamada shutil.copyfile(os.path.join(self._test_dir, dot_config), 67022a4bf6SMasahiro Yamada os.path.join(temp_dir, '.config')) 68022a4bf6SMasahiro Yamada 69022a4bf6SMasahiro Yamada ps = subprocess.Popen(command, 70022a4bf6SMasahiro Yamada stdin=subprocess.PIPE, 71022a4bf6SMasahiro Yamada stdout=subprocess.PIPE, 72022a4bf6SMasahiro Yamada stderr=subprocess.PIPE, 73022a4bf6SMasahiro Yamada cwd=temp_dir, 74022a4bf6SMasahiro Yamada env=dict(os.environ, **extra_env)) 75022a4bf6SMasahiro Yamada 76022a4bf6SMasahiro Yamada # If input key sequence is given, feed it to stdin. 77022a4bf6SMasahiro Yamada if in_keys: 78022a4bf6SMasahiro Yamada ps.stdin.write(in_keys.encode('utf-8')) 79022a4bf6SMasahiro Yamada 80022a4bf6SMasahiro Yamada while ps.poll() is None: 81022a4bf6SMasahiro Yamada # For interactive modes such as oldaskconfig, oldconfig, 82022a4bf6SMasahiro Yamada # send 'Enter' key until the program finishes. 83022a4bf6SMasahiro Yamada if interactive: 84022a4bf6SMasahiro Yamada ps.stdin.write(b'\n') 85022a4bf6SMasahiro Yamada 86022a4bf6SMasahiro Yamada self.retcode = ps.returncode 87022a4bf6SMasahiro Yamada self.stdout = ps.stdout.read().decode() 88022a4bf6SMasahiro Yamada self.stderr = ps.stderr.read().decode() 89022a4bf6SMasahiro Yamada 90022a4bf6SMasahiro Yamada # Retrieve the resulted config data only when .config is supposed 91022a4bf6SMasahiro Yamada # to exist. If the command fails, the .config does not exist. 92022a4bf6SMasahiro Yamada # 'listnewconfig' does not produce .config in the first place. 93022a4bf6SMasahiro Yamada if self.retcode == 0 and out_file: 94022a4bf6SMasahiro Yamada with open(os.path.join(temp_dir, out_file)) as f: 95022a4bf6SMasahiro Yamada self.config = f.read() 96022a4bf6SMasahiro Yamada else: 97022a4bf6SMasahiro Yamada self.config = None 98022a4bf6SMasahiro Yamada 99022a4bf6SMasahiro Yamada # Logging: 100022a4bf6SMasahiro Yamada # Pytest captures the following information by default. In failure 101022a4bf6SMasahiro Yamada # of tests, the captured log will be displayed. This will be useful to 102022a4bf6SMasahiro Yamada # figure out what has happened. 103022a4bf6SMasahiro Yamada 104022a4bf6SMasahiro Yamada print("[command]\n{}\n".format(' '.join(command))) 105022a4bf6SMasahiro Yamada 106022a4bf6SMasahiro Yamada print("[retcode]\n{}\n".format(self.retcode)) 107022a4bf6SMasahiro Yamada 108022a4bf6SMasahiro Yamada print("[stdout]") 109022a4bf6SMasahiro Yamada print(self.stdout) 110022a4bf6SMasahiro Yamada 111022a4bf6SMasahiro Yamada print("[stderr]") 112022a4bf6SMasahiro Yamada print(self.stderr) 113022a4bf6SMasahiro Yamada 114022a4bf6SMasahiro Yamada if self.config is not None: 115022a4bf6SMasahiro Yamada print("[output for '{}']".format(out_file)) 116022a4bf6SMasahiro Yamada print(self.config) 117022a4bf6SMasahiro Yamada 118022a4bf6SMasahiro Yamada return self.retcode 119022a4bf6SMasahiro Yamada 120022a4bf6SMasahiro Yamada def oldaskconfig(self, dot_config=None, in_keys=None): 121022a4bf6SMasahiro Yamada """Run oldaskconfig. 122022a4bf6SMasahiro Yamada 123022a4bf6SMasahiro Yamada dot_config: .config file to use for configuration base (optional) 124022a4bf6SMasahiro Yamada in_key: key inputs (optional) 125022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 126022a4bf6SMasahiro Yamada """ 127022a4bf6SMasahiro Yamada return self._run_conf('--oldaskconfig', dot_config=dot_config, 128022a4bf6SMasahiro Yamada interactive=True, in_keys=in_keys) 129022a4bf6SMasahiro Yamada 130022a4bf6SMasahiro Yamada def oldconfig(self, dot_config=None, in_keys=None): 131022a4bf6SMasahiro Yamada """Run oldconfig. 132022a4bf6SMasahiro Yamada 133022a4bf6SMasahiro Yamada dot_config: .config file to use for configuration base (optional) 134022a4bf6SMasahiro Yamada in_key: key inputs (optional) 135022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 136022a4bf6SMasahiro Yamada """ 137022a4bf6SMasahiro Yamada return self._run_conf('--oldconfig', dot_config=dot_config, 138022a4bf6SMasahiro Yamada interactive=True, in_keys=in_keys) 139022a4bf6SMasahiro Yamada 140022a4bf6SMasahiro Yamada def olddefconfig(self, dot_config=None): 141022a4bf6SMasahiro Yamada """Run olddefconfig. 142022a4bf6SMasahiro Yamada 143022a4bf6SMasahiro Yamada dot_config: .config file to use for configuration base (optional) 144022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 145022a4bf6SMasahiro Yamada """ 146022a4bf6SMasahiro Yamada return self._run_conf('--olddefconfig', dot_config=dot_config) 147022a4bf6SMasahiro Yamada 148022a4bf6SMasahiro Yamada def defconfig(self, defconfig): 149022a4bf6SMasahiro Yamada """Run defconfig. 150022a4bf6SMasahiro Yamada 151022a4bf6SMasahiro Yamada defconfig: defconfig file for input 152022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 153022a4bf6SMasahiro Yamada """ 154022a4bf6SMasahiro Yamada defconfig_path = os.path.join(self._test_dir, defconfig) 155022a4bf6SMasahiro Yamada return self._run_conf('--defconfig={}'.format(defconfig_path)) 156022a4bf6SMasahiro Yamada 157*c9aa7d86SMasahiro Yamada def _allconfig(self, mode, all_config, extra_env={}): 158022a4bf6SMasahiro Yamada if all_config: 159022a4bf6SMasahiro Yamada all_config_path = os.path.join(self._test_dir, all_config) 160*c9aa7d86SMasahiro Yamada extra_env['KCONFIG_ALLCONFIG'] = all_config_path 161022a4bf6SMasahiro Yamada 162022a4bf6SMasahiro Yamada return self._run_conf('--{}config'.format(mode), extra_env=extra_env) 163022a4bf6SMasahiro Yamada 164022a4bf6SMasahiro Yamada def allyesconfig(self, all_config=None): 165022a4bf6SMasahiro Yamada """Run allyesconfig. 166022a4bf6SMasahiro Yamada 167022a4bf6SMasahiro Yamada all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 168022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 169022a4bf6SMasahiro Yamada """ 170022a4bf6SMasahiro Yamada return self._allconfig('allyes', all_config) 171022a4bf6SMasahiro Yamada 172022a4bf6SMasahiro Yamada def allmodconfig(self, all_config=None): 173022a4bf6SMasahiro Yamada """Run allmodconfig. 174022a4bf6SMasahiro Yamada 175022a4bf6SMasahiro Yamada all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 176022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 177022a4bf6SMasahiro Yamada """ 178022a4bf6SMasahiro Yamada return self._allconfig('allmod', all_config) 179022a4bf6SMasahiro Yamada 180022a4bf6SMasahiro Yamada def allnoconfig(self, all_config=None): 181022a4bf6SMasahiro Yamada """Run allnoconfig. 182022a4bf6SMasahiro Yamada 183022a4bf6SMasahiro Yamada all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 184022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 185022a4bf6SMasahiro Yamada """ 186022a4bf6SMasahiro Yamada return self._allconfig('allno', all_config) 187022a4bf6SMasahiro Yamada 188022a4bf6SMasahiro Yamada def alldefconfig(self, all_config=None): 189022a4bf6SMasahiro Yamada """Run alldefconfig. 190022a4bf6SMasahiro Yamada 191022a4bf6SMasahiro Yamada all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 192022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 193022a4bf6SMasahiro Yamada """ 194022a4bf6SMasahiro Yamada return self._allconfig('alldef', all_config) 195022a4bf6SMasahiro Yamada 196*c9aa7d86SMasahiro Yamada def randconfig(self, all_config=None, seed=None): 197022a4bf6SMasahiro Yamada """Run randconfig. 198022a4bf6SMasahiro Yamada 199022a4bf6SMasahiro Yamada all_config: fragment config file for KCONFIG_ALLCONFIG (optional) 200*c9aa7d86SMasahiro Yamada seed: the seed for randconfig (optional) 201022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 202022a4bf6SMasahiro Yamada """ 203*c9aa7d86SMasahiro Yamada if seed is not None: 204*c9aa7d86SMasahiro Yamada extra_env = {'KCONFIG_SEED': hex(seed)} 205*c9aa7d86SMasahiro Yamada else: 206*c9aa7d86SMasahiro Yamada extra_env = {} 207*c9aa7d86SMasahiro Yamada 208*c9aa7d86SMasahiro Yamada return self._allconfig('rand', all_config, extra_env=extra_env) 209022a4bf6SMasahiro Yamada 210022a4bf6SMasahiro Yamada def savedefconfig(self, dot_config): 211022a4bf6SMasahiro Yamada """Run savedefconfig. 212022a4bf6SMasahiro Yamada 213022a4bf6SMasahiro Yamada dot_config: .config file for input 214022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 215022a4bf6SMasahiro Yamada """ 216022a4bf6SMasahiro Yamada return self._run_conf('--savedefconfig', out_file='defconfig') 217022a4bf6SMasahiro Yamada 218022a4bf6SMasahiro Yamada def listnewconfig(self, dot_config=None): 219022a4bf6SMasahiro Yamada """Run listnewconfig. 220022a4bf6SMasahiro Yamada 221022a4bf6SMasahiro Yamada dot_config: .config file to use for configuration base (optional) 222022a4bf6SMasahiro Yamada returncode: exit status of the Kconfig executable 223022a4bf6SMasahiro Yamada """ 224022a4bf6SMasahiro Yamada return self._run_conf('--listnewconfig', dot_config=dot_config, 225022a4bf6SMasahiro Yamada out_file=None) 226022a4bf6SMasahiro Yamada 227022a4bf6SMasahiro Yamada # checkers 228022a4bf6SMasahiro Yamada def _read_and_compare(self, compare, expected): 229022a4bf6SMasahiro Yamada """Compare the result with expectation. 230022a4bf6SMasahiro Yamada 231022a4bf6SMasahiro Yamada compare: function to compare the result with expectation 232022a4bf6SMasahiro Yamada expected: file that contains the expected data 233022a4bf6SMasahiro Yamada """ 234022a4bf6SMasahiro Yamada with open(os.path.join(self._test_dir, expected)) as f: 235022a4bf6SMasahiro Yamada expected_data = f.read() 236022a4bf6SMasahiro Yamada return compare(self, expected_data) 237022a4bf6SMasahiro Yamada 238022a4bf6SMasahiro Yamada def _contains(self, attr, expected): 239022a4bf6SMasahiro Yamada return self._read_and_compare( 240022a4bf6SMasahiro Yamada lambda s, e: getattr(s, attr).find(e) >= 0, 241022a4bf6SMasahiro Yamada expected) 242022a4bf6SMasahiro Yamada 243022a4bf6SMasahiro Yamada def _matches(self, attr, expected): 244022a4bf6SMasahiro Yamada return self._read_and_compare(lambda s, e: getattr(s, attr) == e, 245022a4bf6SMasahiro Yamada expected) 246022a4bf6SMasahiro Yamada 247022a4bf6SMasahiro Yamada def config_contains(self, expected): 248022a4bf6SMasahiro Yamada """Check if resulted configuration contains expected data. 249022a4bf6SMasahiro Yamada 250022a4bf6SMasahiro Yamada expected: file that contains the expected data 251022a4bf6SMasahiro Yamada returncode: True if result contains the expected data, False otherwise 252022a4bf6SMasahiro Yamada """ 253022a4bf6SMasahiro Yamada return self._contains('config', expected) 254022a4bf6SMasahiro Yamada 255022a4bf6SMasahiro Yamada def config_matches(self, expected): 256022a4bf6SMasahiro Yamada """Check if resulted configuration exactly matches expected data. 257022a4bf6SMasahiro Yamada 258022a4bf6SMasahiro Yamada expected: file that contains the expected data 259022a4bf6SMasahiro Yamada returncode: True if result matches the expected data, False otherwise 260022a4bf6SMasahiro Yamada """ 261022a4bf6SMasahiro Yamada return self._matches('config', expected) 262022a4bf6SMasahiro Yamada 263022a4bf6SMasahiro Yamada def stdout_contains(self, expected): 264022a4bf6SMasahiro Yamada """Check if resulted stdout contains expected data. 265022a4bf6SMasahiro Yamada 266022a4bf6SMasahiro Yamada expected: file that contains the expected data 267022a4bf6SMasahiro Yamada returncode: True if result contains the expected data, False otherwise 268022a4bf6SMasahiro Yamada """ 269022a4bf6SMasahiro Yamada return self._contains('stdout', expected) 270022a4bf6SMasahiro Yamada 271022a4bf6SMasahiro Yamada def stdout_matches(self, expected): 272022a4bf6SMasahiro Yamada """Check if resulted stdout exactly matches expected data. 273022a4bf6SMasahiro Yamada 274022a4bf6SMasahiro Yamada expected: file that contains the expected data 275022a4bf6SMasahiro Yamada returncode: True if result matches the expected data, False otherwise 276022a4bf6SMasahiro Yamada """ 277022a4bf6SMasahiro Yamada return self._matches('stdout', expected) 278022a4bf6SMasahiro Yamada 279022a4bf6SMasahiro Yamada def stderr_contains(self, expected): 280022a4bf6SMasahiro Yamada """Check if resulted stderr contains expected data. 281022a4bf6SMasahiro Yamada 282022a4bf6SMasahiro Yamada expected: file that contains the expected data 283022a4bf6SMasahiro Yamada returncode: True if result contains the expected data, False otherwise 284022a4bf6SMasahiro Yamada """ 285022a4bf6SMasahiro Yamada return self._contains('stderr', expected) 286022a4bf6SMasahiro Yamada 287022a4bf6SMasahiro Yamada def stderr_matches(self, expected): 288022a4bf6SMasahiro Yamada """Check if resulted stderr exactly matches expected data. 289022a4bf6SMasahiro Yamada 290022a4bf6SMasahiro Yamada expected: file that contains the expected data 291022a4bf6SMasahiro Yamada returncode: True if result matches the expected data, False otherwise 292022a4bf6SMasahiro Yamada """ 293022a4bf6SMasahiro Yamada return self._matches('stderr', expected) 294022a4bf6SMasahiro Yamada 295022a4bf6SMasahiro Yamada 296022a4bf6SMasahiro Yamada@pytest.fixture(scope="module") 297022a4bf6SMasahiro Yamadadef conf(request): 298022a4bf6SMasahiro Yamada """Create a Conf instance and provide it to test functions.""" 299022a4bf6SMasahiro Yamada return Conf(request) 300