| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 |
|
| 6 | |
| 7 | Kconfig unit testing framework.
|
| 8 |
|
| 9 | This provides fixture functions commonly used from test files.
|
| 10 |
|
| 11 |
|
| 12 | import os
|
| 13 | import pytest
|
| 14 | import shutil
|
| 15 | import subprocess
|
| 16 | import tempfile
|
| 17 |
|
| 18 | CONF_PATH = os.path.abspath(os.path.join('scripts', 'kconfig', 'conf'))
|
| 19 |
|
| 20 |
|
| 21 | class Conf:
|
| 22 | |
| 23 |
|
| 24 | This class provides methods to run text-based interface of Kconfig
|
| 25 | (scripts/kconfig/conf) and retrieve the resulted configuration,
|
| 26 | stdout, and stderr. It also provides methods to compare those
|
| 27 | results with expectations.
|
| 28 |
|
| 29 |
|
| 30 | def __init__(self, request):
|
| 31 | |
| 32 |
|
| 33 | request: object to introspect the requesting test module
|
| 34 |
|
| 35 | |
| 36 | self._test_dir = os.path.dirname(str(request.fspath))
|
| 37 |
|
| 38 | |
| 39 | def _run_conf(self, mode, dot_config=None, out_file='.config',
|
| 40 | interactive=False, in_keys=None, extra_env={},
|
| 41 | silent=False):
|
| 42 | |
| 43 |
|
| 44 | mode: input mode option (--oldaskconfig, --defconfig=<file> etc.)
|
| 45 | dot_config: .config file to use for configuration base
|
| 46 | out_file: file name to contain the output config data
|
| 47 | interactive: flag to specify the interactive mode
|
| 48 | in_keys: key inputs for interactive modes
|
| 49 | extra_env: additional environments
|
| 50 | returncode: exit status of the Kconfig executable
|
| 51 |
|
| 52 | command = [CONF_PATH]
|
| 53 | if silent:
|
| 54 | command.append('-s')
|
| 55 | command += [mode, 'Kconfig']
|
| 56 |
|
| 57 | |
| 58 | extra_env['srctree'] = self._test_dir
|
| 59 |
|
| 60 | |
| 61 | |
| 62 | extra_env['KCONFIG_DEFCONFIG_LIST'] = ''
|
| 63 |
|
| 64 | |
| 65 | |
| 66 | with tempfile.TemporaryDirectory() as temp_dir:
|
| 67 |
|
| 68 | |
| 69 | if dot_config:
|
| 70 | shutil.copyfile(os.path.join(self._test_dir, dot_config),
|
| 71 | os.path.join(temp_dir, '.config'))
|
| 72 |
|
| 73 | ps = subprocess.Popen(command,
|
| 74 | stdin=subprocess.PIPE,
|
| 75 | stdout=subprocess.PIPE,
|
| 76 | stderr=subprocess.PIPE,
|
| 77 | cwd=temp_dir,
|
| 78 | env=dict(os.environ, **extra_env))
|
| 79 |
|
| 80 | |
| 81 | if in_keys:
|
| 82 | ps.stdin.write(in_keys.encode('utf-8'))
|
| 83 |
|
| 84 | while ps.poll() is None:
|
| 85 | |
| 86 | |
| 87 | if interactive:
|
| 88 | try:
|
| 89 | ps.stdin.write(b'\n')
|
| 90 | ps.stdin.flush()
|
| 91 | except (BrokenPipeError, OSError):
|
| 92 | |
| 93 | break
|
| 94 |
|
| 95 | |
| 96 | try:
|
| 97 | ps.stdin.close()
|
| 98 | except (BrokenPipeError, OSError):
|
| 99 | |
| 100 | pass
|
| 101 |
|
| 102 | |
| 103 | ps.wait()
|
| 104 |
|
| 105 | self.retcode = ps.returncode
|
| 106 | self.stdout = ps.stdout.read().decode()
|
| 107 | self.stderr = ps.stderr.read().decode()
|
| 108 |
|
| 109 | |
| 110 | |
| 111 | |
| 112 | if self.retcode == 0 and out_file:
|
| 113 | with open(os.path.join(temp_dir, out_file)) as f:
|
| 114 | self.config = f.read()
|
| 115 | else:
|
| 116 | self.config = None
|
| 117 |
|
| 118 | |
| 119 | |
| 120 | |
| 121 | |
| 122 |
|
| 123 | print("[command]\n{}\n".format(' '.join(command)))
|
| 124 |
|
| 125 | print("[retcode]\n{}\n".format(self.retcode))
|
| 126 |
|
| 127 | print("[stdout]")
|
| 128 | print(self.stdout)
|
| 129 |
|
| 130 | print("[stderr]")
|
| 131 | print(self.stderr)
|
| 132 |
|
| 133 | if self.config is not None:
|
| 134 | print("[output for '{}']".format(out_file))
|
| 135 | print(self.config)
|
| 136 |
|
| 137 | return self.retcode
|
| 138 |
|
| 139 | def oldaskconfig(self, dot_config=None, in_keys=None):
|
| 140 | |
| 141 |
|
| 142 | dot_config: .config file to use for configuration base (optional)
|
| 143 | in_key: key inputs (optional)
|
| 144 | returncode: exit status of the Kconfig executable
|
| 145 |
|
| 146 | return self._run_conf('--oldaskconfig', dot_config=dot_config,
|
| 147 | interactive=True, in_keys=in_keys)
|
| 148 |
|
| 149 | def oldconfig(self, dot_config=None, in_keys=None):
|
| 150 | |
| 151 |
|
| 152 | dot_config: .config file to use for configuration base (optional)
|
| 153 | in_key: key inputs (optional)
|
| 154 | returncode: exit status of the Kconfig executable
|
| 155 |
|
| 156 | return self._run_conf('--oldconfig', dot_config=dot_config,
|
| 157 | interactive=True, in_keys=in_keys)
|
| 158 |
|
| 159 | def olddefconfig(self, dot_config=None):
|
| 160 | |
| 161 |
|
| 162 | dot_config: .config file to use for configuration base (optional)
|
| 163 | returncode: exit status of the Kconfig executable
|
| 164 |
|
| 165 | return self._run_conf('--olddefconfig', dot_config=dot_config)
|
| 166 |
|
| 167 | def defconfig(self, defconfig):
|
| 168 | |
| 169 |
|
| 170 | defconfig: defconfig file for input
|
| 171 | returncode: exit status of the Kconfig executable
|
| 172 |
|
| 173 | defconfig_path = os.path.join(self._test_dir, defconfig)
|
| 174 | return self._run_conf('--defconfig={}'.format(defconfig_path))
|
| 175 |
|
| 176 | def _allconfig(self, mode, all_config, extra_env={}):
|
| 177 | if all_config:
|
| 178 | all_config_path = os.path.join(self._test_dir, all_config)
|
| 179 | extra_env['KCONFIG_ALLCONFIG'] = all_config_path
|
| 180 |
|
| 181 | return self._run_conf('--{}config'.format(mode), extra_env=extra_env)
|
| 182 |
|
| 183 | def allyesconfig(self, all_config=None):
|
| 184 | |
| 185 |
|
| 186 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
|
| 187 | returncode: exit status of the Kconfig executable
|
| 188 |
|
| 189 | return self._allconfig('allyes', all_config)
|
| 190 |
|
| 191 | def allmodconfig(self, all_config=None):
|
| 192 | |
| 193 |
|
| 194 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
|
| 195 | returncode: exit status of the Kconfig executable
|
| 196 |
|
| 197 | return self._allconfig('allmod', all_config)
|
| 198 |
|
| 199 | def allnoconfig(self, all_config=None):
|
| 200 | |
| 201 |
|
| 202 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
|
| 203 | returncode: exit status of the Kconfig executable
|
| 204 |
|
| 205 | return self._allconfig('allno', all_config)
|
| 206 |
|
| 207 | def alldefconfig(self, all_config=None):
|
| 208 | |
| 209 |
|
| 210 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
|
| 211 | returncode: exit status of the Kconfig executable
|
| 212 |
|
| 213 | return self._allconfig('alldef', all_config)
|
| 214 |
|
| 215 | def randconfig(self, all_config=None, seed=None):
|
| 216 | |
| 217 |
|
| 218 | all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
|
| 219 | seed: the seed for randconfig (optional)
|
| 220 | returncode: exit status of the Kconfig executable
|
| 221 |
|
| 222 | if seed is not None:
|
| 223 | extra_env = {'KCONFIG_SEED': hex(seed)}
|
| 224 | else:
|
| 225 | extra_env = {}
|
| 226 |
|
| 227 | return self._allconfig('rand', all_config, extra_env=extra_env)
|
| 228 |
|
| 229 | def savedefconfig(self, dot_config):
|
| 230 | |
| 231 |
|
| 232 | dot_config: .config file for input
|
| 233 | returncode: exit status of the Kconfig executable
|
| 234 |
|
| 235 | return self._run_conf('--savedefconfig', out_file='defconfig')
|
| 236 |
|
| 237 | def listnewconfig(self, dot_config=None):
|
| 238 | |
| 239 |
|
| 240 | dot_config: .config file to use for configuration base (optional)
|
| 241 | returncode: exit status of the Kconfig executable
|
| 242 |
|
| 243 | return self._run_conf('--listnewconfig', dot_config=dot_config,
|
| 244 | out_file=None)
|
| 245 |
|
| 246 | |
| 247 | def _read_and_compare(self, compare, expected):
|
| 248 | |
| 249 |
|
| 250 | compare: function to compare the result with expectation
|
| 251 | expected: file that contains the expected data
|
| 252 |
|
| 253 | with open(os.path.join(self._test_dir, expected)) as f:
|
| 254 | expected_data = f.read()
|
| 255 | return compare(self, expected_data)
|
| 256 |
|
| 257 | def _contains(self, attr, expected):
|
| 258 | return self._read_and_compare(
|
| 259 | lambda s, e: getattr(s, attr).find(e) >= 0,
|
| 260 | expected)
|
| 261 |
|
| 262 | def _matches(self, attr, expected):
|
| 263 | return self._read_and_compare(lambda s, e: getattr(s, attr) == e,
|
| 264 | expected)
|
| 265 |
|
| 266 | def config_contains(self, expected):
|
| 267 | |
| 268 |
|
| 269 | expected: file that contains the expected data
|
| 270 | returncode: True if result contains the expected data, False otherwise
|
| 271 |
|
| 272 | return self._contains('config', expected)
|
| 273 |
|
| 274 | def config_matches(self, expected):
|
| 275 | |
| 276 |
|
| 277 | expected: file that contains the expected data
|
| 278 | returncode: True if result matches the expected data, False otherwise
|
| 279 |
|
| 280 | return self._matches('config', expected)
|
| 281 |
|
| 282 | def stdout_contains(self, expected):
|
| 283 | |
| 284 |
|
| 285 | expected: file that contains the expected data
|
| 286 | returncode: True if result contains the expected data, False otherwise
|
| 287 |
|
| 288 | return self._contains('stdout', expected)
|
| 289 |
|
| 290 | def stdout_matches(self, expected):
|
| 291 | |
| 292 |
|
| 293 | expected: file that contains the expected data
|
| 294 | returncode: True if result matches the expected data, False otherwise
|
| 295 |
|
| 296 | return self._matches('stdout', expected)
|
| 297 |
|
| 298 | def stderr_contains(self, expected):
|
| 299 | |
| 300 |
|
| 301 | expected: file that contains the expected data
|
| 302 | returncode: True if result contains the expected data, False otherwise
|
| 303 |
|
| 304 | return self._contains('stderr', expected)
|
| 305 |
|
| 306 | def stderr_matches(self, expected):
|
| 307 | |
| 308 |
|
| 309 | expected: file that contains the expected data
|
| 310 | returncode: True if result matches the expected data, False otherwise
|
| 311 |
|
| 312 | return self._matches('stderr', expected)
|
| 313 |
|
| 314 |
|
| 315 | @pytest.fixture(scope="module")
|
| 316 | def conf(request):
|
| 317 |
|
| 318 | return Conf(request)
|