1 # SPDX-License-Identifier: GPL-2.0 1 # SPDX-License-Identifier: GPL-2.0 2 # 2 # 3 # Runs UML kernel, collects output, and handle 3 # Runs UML kernel, collects output, and handles errors. 4 # 4 # 5 # Copyright (C) 2019, Google LLC. 5 # Copyright (C) 2019, Google LLC. 6 # Author: Felix Guo <felixguoxiuping@gmail.com> 6 # Author: Felix Guo <felixguoxiuping@gmail.com> 7 # Author: Brendan Higgins <brendanhiggins@googl 7 # Author: Brendan Higgins <brendanhiggins@google.com> 8 8 9 import importlib.abc 9 import importlib.abc 10 import importlib.util 10 import importlib.util 11 import logging 11 import logging 12 import subprocess 12 import subprocess 13 import os 13 import os 14 import shlex 14 import shlex 15 import shutil 15 import shutil 16 import signal 16 import signal 17 import threading 17 import threading 18 from typing import Iterator, List, Optional, T 18 from typing import Iterator, List, Optional, Tuple 19 from types import FrameType 19 from types import FrameType 20 20 21 import kunit_config 21 import kunit_config 22 import qemu_config 22 import qemu_config 23 23 24 KCONFIG_PATH = '.config' 24 KCONFIG_PATH = '.config' 25 KUNITCONFIG_PATH = '.kunitconfig' 25 KUNITCONFIG_PATH = '.kunitconfig' 26 OLD_KUNITCONFIG_PATH = 'last_used_kunitconfig' 26 OLD_KUNITCONFIG_PATH = 'last_used_kunitconfig' 27 DEFAULT_KUNITCONFIG_PATH = 'tools/testing/kuni 27 DEFAULT_KUNITCONFIG_PATH = 'tools/testing/kunit/configs/default.config' 28 ALL_TESTS_CONFIG_PATH = 'tools/testing/kunit/c 28 ALL_TESTS_CONFIG_PATH = 'tools/testing/kunit/configs/all_tests.config' 29 UML_KCONFIG_PATH = 'tools/testing/kunit/config 29 UML_KCONFIG_PATH = 'tools/testing/kunit/configs/arch_uml.config' 30 OUTFILE_PATH = 'test.log' 30 OUTFILE_PATH = 'test.log' 31 ABS_TOOL_PATH = os.path.abspath(os.path.dirnam 31 ABS_TOOL_PATH = os.path.abspath(os.path.dirname(__file__)) 32 QEMU_CONFIGS_DIR = os.path.join(ABS_TOOL_PATH, 32 QEMU_CONFIGS_DIR = os.path.join(ABS_TOOL_PATH, 'qemu_configs') 33 33 34 class ConfigError(Exception): 34 class ConfigError(Exception): 35 """Represents an error trying to confi 35 """Represents an error trying to configure the Linux kernel.""" 36 36 37 37 38 class BuildError(Exception): 38 class BuildError(Exception): 39 """Represents an error trying to build 39 """Represents an error trying to build the Linux kernel.""" 40 40 41 41 42 class LinuxSourceTreeOperations: 42 class LinuxSourceTreeOperations: 43 """An abstraction over command line op 43 """An abstraction over command line operations performed on a source tree.""" 44 44 45 def __init__(self, linux_arch: str, cr 45 def __init__(self, linux_arch: str, cross_compile: Optional[str]): 46 self._linux_arch = linux_arch 46 self._linux_arch = linux_arch 47 self._cross_compile = cross_co 47 self._cross_compile = cross_compile 48 48 49 def make_mrproper(self) -> None: 49 def make_mrproper(self) -> None: 50 try: 50 try: 51 subprocess.check_outpu 51 subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT) 52 except OSError as e: 52 except OSError as e: 53 raise ConfigError('Cou 53 raise ConfigError('Could not call make command: ' + str(e)) 54 except subprocess.CalledProces 54 except subprocess.CalledProcessError as e: 55 raise ConfigError(e.ou 55 raise ConfigError(e.output.decode()) 56 56 57 def make_arch_config(self, base_kunitc 57 def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 58 return base_kunitconfig 58 return base_kunitconfig 59 59 60 def make_olddefconfig(self, build_dir: 60 def make_olddefconfig(self, build_dir: str, make_options: Optional[List[str]]) -> None: 61 command = ['make', 'ARCH=' + s 61 command = ['make', 'ARCH=' + self._linux_arch, 'O=' + build_dir, 'olddefconfig'] 62 if self._cross_compile: 62 if self._cross_compile: 63 command += ['CROSS_COM 63 command += ['CROSS_COMPILE=' + self._cross_compile] 64 if make_options: 64 if make_options: 65 command.extend(make_op 65 command.extend(make_options) 66 print('Populating config with: 66 print('Populating config with:\n$', ' '.join(command)) 67 try: 67 try: 68 subprocess.check_outpu 68 subprocess.check_output(command, stderr=subprocess.STDOUT) 69 except OSError as e: 69 except OSError as e: 70 raise ConfigError('Cou 70 raise ConfigError('Could not call make command: ' + str(e)) 71 except subprocess.CalledProces 71 except subprocess.CalledProcessError as e: 72 raise ConfigError(e.ou 72 raise ConfigError(e.output.decode()) 73 73 74 def make(self, jobs: int, build_dir: s 74 def make(self, jobs: int, build_dir: str, make_options: Optional[List[str]]) -> None: 75 command = ['make', 'all', 'com 75 command = ['make', 'all', 'compile_commands.json', 'ARCH=' + self._linux_arch, 76 'O=' + build_dir, ' 76 'O=' + build_dir, '--jobs=' + str(jobs)] 77 if make_options: 77 if make_options: 78 command.extend(make_op 78 command.extend(make_options) 79 if self._cross_compile: 79 if self._cross_compile: 80 command += ['CROSS_COM 80 command += ['CROSS_COMPILE=' + self._cross_compile] 81 print('Building with:\n$', ' ' 81 print('Building with:\n$', ' '.join(command)) 82 try: 82 try: 83 proc = subprocess.Pope 83 proc = subprocess.Popen(command, 84 84 stderr=subprocess.PIPE, 85 85 stdout=subprocess.DEVNULL) 86 except OSError as e: 86 except OSError as e: 87 raise BuildError('Coul 87 raise BuildError('Could not call execute make: ' + str(e)) 88 except subprocess.CalledProces 88 except subprocess.CalledProcessError as e: 89 raise BuildError(e.out 89 raise BuildError(e.output) 90 _, stderr = proc.communicate() 90 _, stderr = proc.communicate() 91 if proc.returncode != 0: 91 if proc.returncode != 0: 92 raise BuildError(stder 92 raise BuildError(stderr.decode()) 93 if stderr: # likely only due 93 if stderr: # likely only due to build warnings 94 print(stderr.decode()) 94 print(stderr.decode()) 95 95 96 def start(self, params: List[str], bui 96 def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 97 raise RuntimeError('not implem 97 raise RuntimeError('not implemented!') 98 98 99 99 100 class LinuxSourceTreeOperationsQemu(LinuxSourc 100 class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): 101 101 102 def __init__(self, qemu_arch_params: q 102 def __init__(self, qemu_arch_params: qemu_config.QemuArchParams, cross_compile: Optional[str]): 103 super().__init__(linux_arch=qe 103 super().__init__(linux_arch=qemu_arch_params.linux_arch, 104 cross_compile 104 cross_compile=cross_compile) 105 self._kconfig = qemu_arch_para 105 self._kconfig = qemu_arch_params.kconfig 106 self._qemu_arch = qemu_arch_pa 106 self._qemu_arch = qemu_arch_params.qemu_arch 107 self._kernel_path = qemu_arch_ 107 self._kernel_path = qemu_arch_params.kernel_path 108 self._kernel_command_line = qe 108 self._kernel_command_line = qemu_arch_params.kernel_command_line + ' kunit_shutdown=reboot' 109 self._extra_qemu_params = qemu 109 self._extra_qemu_params = qemu_arch_params.extra_qemu_params 110 self._serial = qemu_arch_param 110 self._serial = qemu_arch_params.serial 111 111 112 def make_arch_config(self, base_kunitc 112 def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 113 kconfig = kunit_config.parse_f 113 kconfig = kunit_config.parse_from_string(self._kconfig) 114 kconfig.merge_in_entries(base_ 114 kconfig.merge_in_entries(base_kunitconfig) 115 return kconfig 115 return kconfig 116 116 117 def start(self, params: List[str], bui 117 def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 118 kernel_path = os.path.join(bui 118 kernel_path = os.path.join(build_dir, self._kernel_path) 119 qemu_command = ['qemu-system-' 119 qemu_command = ['qemu-system-' + self._qemu_arch, 120 '-nodefaults', 120 '-nodefaults', 121 '-m', '1024', 121 '-m', '1024', 122 '-kernel', ker 122 '-kernel', kernel_path, 123 '-append', ' ' 123 '-append', ' '.join(params + [self._kernel_command_line]), 124 '-no-reboot', 124 '-no-reboot', 125 '-nographic', 125 '-nographic', 126 '-serial', sel 126 '-serial', self._serial] + self._extra_qemu_params 127 # Note: shlex.join() does what 127 # Note: shlex.join() does what we want, but requires python 3.8+. 128 print('Running tests with:\n$' 128 print('Running tests with:\n$', ' '.join(shlex.quote(arg) for arg in qemu_command)) 129 return subprocess.Popen(qemu_c 129 return subprocess.Popen(qemu_command, 130 stdin= 130 stdin=subprocess.PIPE, 131 stdout 131 stdout=subprocess.PIPE, 132 stderr 132 stderr=subprocess.STDOUT, 133 text=T 133 text=True, errors='backslashreplace') 134 134 135 class LinuxSourceTreeOperationsUml(LinuxSource 135 class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): 136 """An abstraction over command line op 136 """An abstraction over command line operations performed on a source tree.""" 137 137 138 def __init__(self, cross_compile: Opti 138 def __init__(self, cross_compile: Optional[str]=None): 139 super().__init__(linux_arch='u 139 super().__init__(linux_arch='um', cross_compile=cross_compile) 140 140 141 def make_arch_config(self, base_kunitc 141 def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 142 kconfig = kunit_config.parse_f 142 kconfig = kunit_config.parse_file(UML_KCONFIG_PATH) 143 kconfig.merge_in_entries(base_ 143 kconfig.merge_in_entries(base_kunitconfig) 144 return kconfig 144 return kconfig 145 145 146 def start(self, params: List[str], bui 146 def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 147 """Runs the Linux UML binary. 147 """Runs the Linux UML binary. Must be named 'linux'.""" 148 linux_bin = os.path.join(build 148 linux_bin = os.path.join(build_dir, 'linux') 149 params.extend(['mem=1G', 'cons 149 params.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) 150 print('Running tests with:\n$' 150 print('Running tests with:\n$', linux_bin, ' '.join(shlex.quote(arg) for arg in params)) 151 return subprocess.Popen([linux 151 return subprocess.Popen([linux_bin] + params, 152 std 152 stdin=subprocess.PIPE, 153 std 153 stdout=subprocess.PIPE, 154 std 154 stderr=subprocess.STDOUT, 155 tex 155 text=True, errors='backslashreplace') 156 156 157 def get_kconfig_path(build_dir: str) -> str: 157 def get_kconfig_path(build_dir: str) -> str: 158 return os.path.join(build_dir, KCONFIG 158 return os.path.join(build_dir, KCONFIG_PATH) 159 159 160 def get_kunitconfig_path(build_dir: str) -> st 160 def get_kunitconfig_path(build_dir: str) -> str: 161 return os.path.join(build_dir, KUNITCO 161 return os.path.join(build_dir, KUNITCONFIG_PATH) 162 162 163 def get_old_kunitconfig_path(build_dir: str) - 163 def get_old_kunitconfig_path(build_dir: str) -> str: 164 return os.path.join(build_dir, OLD_KUN 164 return os.path.join(build_dir, OLD_KUNITCONFIG_PATH) 165 165 166 def get_parsed_kunitconfig(build_dir: str, 166 def get_parsed_kunitconfig(build_dir: str, 167 kunitconfig_paths: 167 kunitconfig_paths: Optional[List[str]]=None) -> kunit_config.Kconfig: 168 if not kunitconfig_paths: 168 if not kunitconfig_paths: 169 path = get_kunitconfig_path(bu 169 path = get_kunitconfig_path(build_dir) 170 if not os.path.exists(path): 170 if not os.path.exists(path): 171 shutil.copyfile(DEFAUL 171 shutil.copyfile(DEFAULT_KUNITCONFIG_PATH, path) 172 return kunit_config.parse_file 172 return kunit_config.parse_file(path) 173 173 174 merged = kunit_config.Kconfig() 174 merged = kunit_config.Kconfig() 175 175 176 for path in kunitconfig_paths: 176 for path in kunitconfig_paths: 177 if os.path.isdir(path): 177 if os.path.isdir(path): 178 path = os.path.join(pa 178 path = os.path.join(path, KUNITCONFIG_PATH) 179 if not os.path.exists(path): 179 if not os.path.exists(path): 180 raise ConfigError(f'Sp 180 raise ConfigError(f'Specified kunitconfig ({path}) does not exist') 181 181 182 partial = kunit_config.parse_f 182 partial = kunit_config.parse_file(path) 183 diff = merged.conflicting_opti 183 diff = merged.conflicting_options(partial) 184 if diff: 184 if diff: 185 diff_str = '\n\n'.join 185 diff_str = '\n\n'.join(f'{a}\n vs from {path}\n{b}' for a, b in diff) 186 raise ConfigError(f'Mu 186 raise ConfigError(f'Multiple values specified for {len(diff)} options in kunitconfig:\n{diff_str}') 187 merged.merge_in_entries(partia 187 merged.merge_in_entries(partial) 188 return merged 188 return merged 189 189 190 def get_outfile_path(build_dir: str) -> str: 190 def get_outfile_path(build_dir: str) -> str: 191 return os.path.join(build_dir, OUTFILE 191 return os.path.join(build_dir, OUTFILE_PATH) 192 192 193 def _default_qemu_config_path(arch: str) -> st 193 def _default_qemu_config_path(arch: str) -> str: 194 config_path = os.path.join(QEMU_CONFIG 194 config_path = os.path.join(QEMU_CONFIGS_DIR, arch + '.py') 195 if os.path.isfile(config_path): 195 if os.path.isfile(config_path): 196 return config_path 196 return config_path 197 197 198 options = [f[:-3] for f in os.listdir( 198 options = [f[:-3] for f in os.listdir(QEMU_CONFIGS_DIR) if f.endswith('.py')] 199 raise ConfigError(arch + ' is not a va 199 raise ConfigError(arch + ' is not a valid arch, options are ' + str(sorted(options))) 200 200 201 def _get_qemu_ops(config_path: str, 201 def _get_qemu_ops(config_path: str, 202 extra_qemu_args: Optional[Li 202 extra_qemu_args: Optional[List[str]], 203 cross_compile: Optional[str] 203 cross_compile: Optional[str]) -> Tuple[str, LinuxSourceTreeOperations]: 204 # The module name/path has very little 204 # The module name/path has very little to do with where the actual file 205 # exists (I learned this through exper 205 # exists (I learned this through experimentation and could not find it 206 # anywhere in the Python documentation 206 # anywhere in the Python documentation). 207 # 207 # 208 # Bascially, we completely ignore the 208 # Bascially, we completely ignore the actual file location of the config 209 # we are loading and just tell Python 209 # we are loading and just tell Python that the module lives in the 210 # QEMU_CONFIGS_DIR for import purposes 210 # QEMU_CONFIGS_DIR for import purposes regardless of where it actually 211 # exists as a file. 211 # exists as a file. 212 module_path = '.' + os.path.join(os.pa 212 module_path = '.' + os.path.join(os.path.basename(QEMU_CONFIGS_DIR), os.path.basename(config_path)) 213 spec = importlib.util.spec_from_file_l 213 spec = importlib.util.spec_from_file_location(module_path, config_path) 214 assert spec is not None 214 assert spec is not None 215 config = importlib.util.module_from_sp 215 config = importlib.util.module_from_spec(spec) 216 # See https://github.com/python/typesh 216 # See https://github.com/python/typeshed/pull/2626 for context. 217 assert isinstance(spec.loader, importl 217 assert isinstance(spec.loader, importlib.abc.Loader) 218 spec.loader.exec_module(config) 218 spec.loader.exec_module(config) 219 219 220 if not hasattr(config, 'QEMU_ARCH'): 220 if not hasattr(config, 'QEMU_ARCH'): 221 raise ValueError('qemu_config 221 raise ValueError('qemu_config module missing "QEMU_ARCH": ' + config_path) 222 params: qemu_config.QemuArchParams = c 222 params: qemu_config.QemuArchParams = config.QEMU_ARCH 223 if extra_qemu_args: 223 if extra_qemu_args: 224 params.extra_qemu_params.exten 224 params.extra_qemu_params.extend(extra_qemu_args) 225 return params.linux_arch, LinuxSourceT 225 return params.linux_arch, LinuxSourceTreeOperationsQemu( 226 params, cross_compile= 226 params, cross_compile=cross_compile) 227 227 228 class LinuxSourceTree: 228 class LinuxSourceTree: 229 """Represents a Linux kernel source tr 229 """Represents a Linux kernel source tree with KUnit tests.""" 230 230 231 def __init__( 231 def __init__( 232 self, 232 self, 233 build_dir: str, 233 build_dir: str, 234 kunitconfig_paths: Optional[List 234 kunitconfig_paths: Optional[List[str]]=None, 235 kconfig_add: Optional[List[str]] 235 kconfig_add: Optional[List[str]]=None, 236 arch: Optional[str]=None, 236 arch: Optional[str]=None, 237 cross_compile: Optional[str]=Non 237 cross_compile: Optional[str]=None, 238 qemu_config_path: Optional[str]= 238 qemu_config_path: Optional[str]=None, 239 extra_qemu_args: Optional[List[s 239 extra_qemu_args: Optional[List[str]]=None) -> None: 240 signal.signal(signal.SIGINT, s 240 signal.signal(signal.SIGINT, self.signal_handler) 241 if qemu_config_path: 241 if qemu_config_path: 242 self._arch, self._ops 242 self._arch, self._ops = _get_qemu_ops(qemu_config_path, extra_qemu_args, cross_compile) 243 else: 243 else: 244 self._arch = 'um' if a 244 self._arch = 'um' if arch is None else arch 245 if self._arch == 'um': 245 if self._arch == 'um': 246 self._ops = Li 246 self._ops = LinuxSourceTreeOperationsUml(cross_compile=cross_compile) 247 else: 247 else: 248 qemu_config_pa 248 qemu_config_path = _default_qemu_config_path(self._arch) 249 _, self._ops = 249 _, self._ops = _get_qemu_ops(qemu_config_path, extra_qemu_args, cross_compile) 250 250 251 self._kconfig = get_parsed_kun 251 self._kconfig = get_parsed_kunitconfig(build_dir, kunitconfig_paths) 252 if kconfig_add: 252 if kconfig_add: 253 kconfig = kunit_config 253 kconfig = kunit_config.parse_from_string('\n'.join(kconfig_add)) 254 self._kconfig.merge_in 254 self._kconfig.merge_in_entries(kconfig) 255 255 256 def arch(self) -> str: 256 def arch(self) -> str: 257 return self._arch 257 return self._arch 258 258 259 def clean(self) -> bool: 259 def clean(self) -> bool: 260 try: 260 try: 261 self._ops.make_mrprope 261 self._ops.make_mrproper() 262 except ConfigError as e: 262 except ConfigError as e: 263 logging.error(e) 263 logging.error(e) 264 return False 264 return False 265 return True 265 return True 266 266 267 def validate_config(self, build_dir: s 267 def validate_config(self, build_dir: str) -> bool: 268 kconfig_path = get_kconfig_pat 268 kconfig_path = get_kconfig_path(build_dir) 269 validated_kconfig = kunit_conf 269 validated_kconfig = kunit_config.parse_file(kconfig_path) 270 if self._kconfig.is_subset_of( 270 if self._kconfig.is_subset_of(validated_kconfig): 271 return True 271 return True 272 missing = set(self._kconfig.as 272 missing = set(self._kconfig.as_entries()) - set(validated_kconfig.as_entries()) 273 message = 'Not all Kconfig opt 273 message = 'Not all Kconfig options selected in kunitconfig were in the generated .config.\n' \ 274 'This is probably du 274 'This is probably due to unsatisfied dependencies.\n' \ 275 'Missing: ' + ', '.j 275 'Missing: ' + ', '.join(str(e) for e in missing) 276 if self._arch == 'um': 276 if self._arch == 'um': 277 message += '\nNote: ma 277 message += '\nNote: many Kconfig options aren\'t available on UML. You can try running ' \ 278 'on a diffe 278 'on a different architecture with something like "--arch=x86_64".' 279 logging.error(message) 279 logging.error(message) 280 return False 280 return False 281 281 282 def build_config(self, build_dir: str, 282 def build_config(self, build_dir: str, make_options: Optional[List[str]]) -> bool: 283 kconfig_path = get_kconfig_pat 283 kconfig_path = get_kconfig_path(build_dir) 284 if build_dir and not os.path.e 284 if build_dir and not os.path.exists(build_dir): 285 os.mkdir(build_dir) 285 os.mkdir(build_dir) 286 try: 286 try: 287 self._kconfig = self._ 287 self._kconfig = self._ops.make_arch_config(self._kconfig) 288 self._kconfig.write_to 288 self._kconfig.write_to_file(kconfig_path) 289 self._ops.make_olddefc 289 self._ops.make_olddefconfig(build_dir, make_options) 290 except ConfigError as e: 290 except ConfigError as e: 291 logging.error(e) 291 logging.error(e) 292 return False 292 return False 293 if not self.validate_config(bu 293 if not self.validate_config(build_dir): 294 return False 294 return False 295 295 296 old_path = get_old_kunitconfig 296 old_path = get_old_kunitconfig_path(build_dir) 297 if os.path.exists(old_path): 297 if os.path.exists(old_path): 298 os.remove(old_path) # 298 os.remove(old_path) # write_to_file appends to the file 299 self._kconfig.write_to_file(ol 299 self._kconfig.write_to_file(old_path) 300 return True 300 return True 301 301 302 def _kunitconfig_changed(self, build_d 302 def _kunitconfig_changed(self, build_dir: str) -> bool: 303 old_path = get_old_kunitconfig 303 old_path = get_old_kunitconfig_path(build_dir) 304 if not os.path.exists(old_path 304 if not os.path.exists(old_path): 305 return True 305 return True 306 306 307 old_kconfig = kunit_config.par 307 old_kconfig = kunit_config.parse_file(old_path) 308 return old_kconfig != self._kc 308 return old_kconfig != self._kconfig 309 309 310 def build_reconfig(self, build_dir: st 310 def build_reconfig(self, build_dir: str, make_options: Optional[List[str]]) -> bool: 311 """Creates a new .config if it 311 """Creates a new .config if it is not a subset of the .kunitconfig.""" 312 kconfig_path = get_kconfig_pat 312 kconfig_path = get_kconfig_path(build_dir) 313 if not os.path.exists(kconfig_ 313 if not os.path.exists(kconfig_path): 314 print('Generating .con 314 print('Generating .config ...') 315 return self.build_conf 315 return self.build_config(build_dir, make_options) 316 316 317 existing_kconfig = kunit_confi 317 existing_kconfig = kunit_config.parse_file(kconfig_path) 318 self._kconfig = self._ops.make 318 self._kconfig = self._ops.make_arch_config(self._kconfig) 319 319 320 if self._kconfig.is_subset_of( 320 if self._kconfig.is_subset_of(existing_kconfig) and not self._kunitconfig_changed(build_dir): 321 return True 321 return True 322 print('Regenerating .config .. 322 print('Regenerating .config ...') 323 os.remove(kconfig_path) 323 os.remove(kconfig_path) 324 return self.build_config(build 324 return self.build_config(build_dir, make_options) 325 325 326 def build_kernel(self, jobs: int, buil 326 def build_kernel(self, jobs: int, build_dir: str, make_options: Optional[List[str]]) -> bool: 327 try: 327 try: 328 self._ops.make_olddefc 328 self._ops.make_olddefconfig(build_dir, make_options) 329 self._ops.make(jobs, b 329 self._ops.make(jobs, build_dir, make_options) 330 except (ConfigError, BuildErro 330 except (ConfigError, BuildError) as e: 331 logging.error(e) 331 logging.error(e) 332 return False 332 return False 333 return self.validate_config(bu 333 return self.validate_config(build_dir) 334 334 335 def run_kernel(self, args: Optional[Li 335 def run_kernel(self, args: Optional[List[str]]=None, build_dir: str='', filter_glob: str='', filter: str='', filter_action: Optional[str]=None, timeout: Optional[int]=None) -> Iterator[str]: 336 if not args: 336 if not args: 337 args = [] 337 args = [] 338 if filter_glob: 338 if filter_glob: 339 args.append('kunit.fil 339 args.append('kunit.filter_glob=' + filter_glob) 340 if filter: 340 if filter: 341 args.append('kunit.fil 341 args.append('kunit.filter="' + filter + '"') 342 if filter_action: 342 if filter_action: 343 args.append('kunit.fil 343 args.append('kunit.filter_action=' + filter_action) 344 args.append('kunit.enable=1') 344 args.append('kunit.enable=1') 345 345 346 process = self._ops.start(args 346 process = self._ops.start(args, build_dir) 347 assert process.stdout is not N 347 assert process.stdout is not None # tell mypy it's set 348 348 349 # Enforce the timeout in a bac 349 # Enforce the timeout in a background thread. 350 def _wait_proc() -> None: 350 def _wait_proc() -> None: 351 try: 351 try: 352 process.wait(t 352 process.wait(timeout=timeout) 353 except Exception as e: 353 except Exception as e: 354 print(e) 354 print(e) 355 process.termin 355 process.terminate() 356 process.wait() 356 process.wait() 357 waiter = threading.Thread(targ 357 waiter = threading.Thread(target=_wait_proc) 358 waiter.start() 358 waiter.start() 359 359 360 output = open(get_outfile_path 360 output = open(get_outfile_path(build_dir), 'w') 361 try: 361 try: 362 # Tee the output to th 362 # Tee the output to the file and to our caller in real time. 363 for line in process.st 363 for line in process.stdout: 364 output.write(l 364 output.write(line) 365 yield line 365 yield line 366 # This runs even if our caller 366 # This runs even if our caller doesn't consume every line. 367 finally: 367 finally: 368 # Flush any leftover o 368 # Flush any leftover output to the file 369 output.write(process.s 369 output.write(process.stdout.read()) 370 output.close() 370 output.close() 371 process.stdout.close() 371 process.stdout.close() 372 372 373 waiter.join() 373 waiter.join() 374 subprocess.call(['stty 374 subprocess.call(['stty', 'sane']) 375 375 376 def signal_handler(self, unused_sig: i 376 def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None: 377 logging.error('Build interrupt 377 logging.error('Build interruption occurred. Cleaning console.') 378 subprocess.call(['stty', 'sane 378 subprocess.call(['stty', 'sane'])
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.