0001
0002
0003
0004
0005
0006 """
0007 Kconfig unit testing framework.
0008
0009 This provides fixture functions commonly used from test files.
0010 """
0011
0012 import os
0013 import pytest
0014 import shutil
0015 import subprocess
0016 import tempfile
0017
0018 CONF_PATH = os.path.abspath(os.path.join('scripts', 'kconfig', 'conf'))
0019
0020
0021 class Conf:
0022 """Kconfig runner and result checker.
0023
0024 This class provides methods to run text-based interface of Kconfig
0025 (scripts/kconfig/conf) and retrieve the resulted configuration,
0026 stdout, and stderr. It also provides methods to compare those
0027 results with expectations.
0028 """
0029
0030 def __init__(self, request):
0031 """Create a new Conf instance.
0032
0033 request: object to introspect the requesting test module
0034 """
0035
0036 self._test_dir = os.path.dirname(str(request.fspath))
0037
0038
0039 def _run_conf(self, mode, dot_config=None, out_file='.config',
0040 interactive=False, in_keys=None, extra_env={}):
0041 """Run text-based Kconfig executable and save the result.
0042
0043 mode: input mode option (--oldaskconfig, --defconfig=<file> etc.)
0044 dot_config: .config file to use for configuration base
0045 out_file: file name to contain the output config data
0046 interactive: flag to specify the interactive mode
0047 in_keys: key inputs for interactive modes
0048 extra_env: additional environments
0049 returncode: exit status of the Kconfig executable
0050 """
0051 command = [CONF_PATH, mode, 'Kconfig']
0052
0053
0054 extra_env['srctree'] = self._test_dir
0055
0056
0057
0058 extra_env['KCONFIG_DEFCONFIG_LIST'] = ''
0059
0060
0061
0062 with tempfile.TemporaryDirectory() as temp_dir:
0063
0064
0065 if dot_config:
0066 shutil.copyfile(os.path.join(self._test_dir, dot_config),
0067 os.path.join(temp_dir, '.config'))
0068
0069 ps = subprocess.Popen(command,
0070 stdin=subprocess.PIPE,
0071 stdout=subprocess.PIPE,
0072 stderr=subprocess.PIPE,
0073 cwd=temp_dir,
0074 env=dict(os.environ, **extra_env))
0075
0076
0077 if in_keys:
0078 ps.stdin.write(in_keys.encode('utf-8'))
0079
0080 while ps.poll() is None:
0081
0082
0083 if interactive:
0084 ps.stdin.write(b'\n')
0085
0086 self.retcode = ps.returncode
0087 self.stdout = ps.stdout.read().decode()
0088 self.stderr = ps.stderr.read().decode()
0089
0090
0091
0092
0093 if self.retcode == 0 and out_file:
0094 with open(os.path.join(temp_dir, out_file)) as f:
0095 self.config = f.read()
0096 else:
0097 self.config = None
0098
0099
0100
0101
0102
0103
0104 print("[command]\n{}\n".format(' '.join(command)))
0105
0106 print("[retcode]\n{}\n".format(self.retcode))
0107
0108 print("[stdout]")
0109 print(self.stdout)
0110
0111 print("[stderr]")
0112 print(self.stderr)
0113
0114 if self.config is not None:
0115 print("[output for '{}']".format(out_file))
0116 print(self.config)
0117
0118 return self.retcode
0119
0120 def oldaskconfig(self, dot_config=None, in_keys=None):
0121 """Run oldaskconfig.
0122
0123 dot_config: .config file to use for configuration base (optional)
0124 in_key: key inputs (optional)
0125 returncode: exit status of the Kconfig executable
0126 """
0127 return self._run_conf('--oldaskconfig', dot_config=dot_config,
0128 interactive=True, in_keys=in_keys)
0129
0130 def oldconfig(self, dot_config=None, in_keys=None):
0131 """Run oldconfig.
0132
0133 dot_config: .config file to use for configuration base (optional)
0134 in_key: key inputs (optional)
0135 returncode: exit status of the Kconfig executable
0136 """
0137 return self._run_conf('--oldconfig', dot_config=dot_config,
0138 interactive=True, in_keys=in_keys)
0139
0140 def olddefconfig(self, dot_config=None):
0141 """Run olddefconfig.
0142
0143 dot_config: .config file to use for configuration base (optional)
0144 returncode: exit status of the Kconfig executable
0145 """
0146 return self._run_conf('--olddefconfig', dot_config=dot_config)
0147
0148 def defconfig(self, defconfig):
0149 """Run defconfig.
0150
0151 defconfig: defconfig file for input
0152 returncode: exit status of the Kconfig executable
0153 """
0154 defconfig_path = os.path.join(self._test_dir, defconfig)
0155 return self._run_conf('--defconfig={}'.format(defconfig_path))
0156
0157 def _allconfig(self, mode, all_config):
0158 if all_config:
0159 all_config_path = os.path.join(self._test_dir, all_config)
0160 extra_env = {'KCONFIG_ALLCONFIG': all_config_path}
0161 else:
0162 extra_env = {}
0163
0164 return self._run_conf('--{}config'.format(mode), extra_env=extra_env)
0165
0166 def allyesconfig(self, all_config=None):
0167 """Run allyesconfig.
0168
0169 all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0170 returncode: exit status of the Kconfig executable
0171 """
0172 return self._allconfig('allyes', all_config)
0173
0174 def allmodconfig(self, all_config=None):
0175 """Run allmodconfig.
0176
0177 all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0178 returncode: exit status of the Kconfig executable
0179 """
0180 return self._allconfig('allmod', all_config)
0181
0182 def allnoconfig(self, all_config=None):
0183 """Run allnoconfig.
0184
0185 all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0186 returncode: exit status of the Kconfig executable
0187 """
0188 return self._allconfig('allno', all_config)
0189
0190 def alldefconfig(self, all_config=None):
0191 """Run alldefconfig.
0192
0193 all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0194 returncode: exit status of the Kconfig executable
0195 """
0196 return self._allconfig('alldef', all_config)
0197
0198 def randconfig(self, all_config=None):
0199 """Run randconfig.
0200
0201 all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
0202 returncode: exit status of the Kconfig executable
0203 """
0204 return self._allconfig('rand', all_config)
0205
0206 def savedefconfig(self, dot_config):
0207 """Run savedefconfig.
0208
0209 dot_config: .config file for input
0210 returncode: exit status of the Kconfig executable
0211 """
0212 return self._run_conf('--savedefconfig', out_file='defconfig')
0213
0214 def listnewconfig(self, dot_config=None):
0215 """Run listnewconfig.
0216
0217 dot_config: .config file to use for configuration base (optional)
0218 returncode: exit status of the Kconfig executable
0219 """
0220 return self._run_conf('--listnewconfig', dot_config=dot_config,
0221 out_file=None)
0222
0223
0224 def _read_and_compare(self, compare, expected):
0225 """Compare the result with expectation.
0226
0227 compare: function to compare the result with expectation
0228 expected: file that contains the expected data
0229 """
0230 with open(os.path.join(self._test_dir, expected)) as f:
0231 expected_data = f.read()
0232 return compare(self, expected_data)
0233
0234 def _contains(self, attr, expected):
0235 return self._read_and_compare(
0236 lambda s, e: getattr(s, attr).find(e) >= 0,
0237 expected)
0238
0239 def _matches(self, attr, expected):
0240 return self._read_and_compare(lambda s, e: getattr(s, attr) == e,
0241 expected)
0242
0243 def config_contains(self, expected):
0244 """Check if resulted configuration contains expected data.
0245
0246 expected: file that contains the expected data
0247 returncode: True if result contains the expected data, False otherwise
0248 """
0249 return self._contains('config', expected)
0250
0251 def config_matches(self, expected):
0252 """Check if resulted configuration exactly matches expected data.
0253
0254 expected: file that contains the expected data
0255 returncode: True if result matches the expected data, False otherwise
0256 """
0257 return self._matches('config', expected)
0258
0259 def stdout_contains(self, expected):
0260 """Check if resulted stdout contains expected data.
0261
0262 expected: file that contains the expected data
0263 returncode: True if result contains the expected data, False otherwise
0264 """
0265 return self._contains('stdout', expected)
0266
0267 def stdout_matches(self, expected):
0268 """Check if resulted stdout exactly matches expected data.
0269
0270 expected: file that contains the expected data
0271 returncode: True if result matches the expected data, False otherwise
0272 """
0273 return self._matches('stdout', expected)
0274
0275 def stderr_contains(self, expected):
0276 """Check if resulted stderr contains expected data.
0277
0278 expected: file that contains the expected data
0279 returncode: True if result contains the expected data, False otherwise
0280 """
0281 return self._contains('stderr', expected)
0282
0283 def stderr_matches(self, expected):
0284 """Check if resulted stderr exactly matches expected data.
0285
0286 expected: file that contains the expected data
0287 returncode: True if result matches the expected data, False otherwise
0288 """
0289 return self._matches('stderr', expected)
0290
0291
0292 @pytest.fixture(scope="module")
0293 def conf(request):
0294 """Create a Conf instance and provide it to test functions."""
0295 return Conf(request)