16ebf5866SFelix Guo# SPDX-License-Identifier: GPL-2.0 26ebf5866SFelix Guo# 36ebf5866SFelix Guo# Runs UML kernel, collects output, and handles errors. 46ebf5866SFelix Guo# 56ebf5866SFelix Guo# Copyright (C) 2019, Google LLC. 66ebf5866SFelix Guo# Author: Felix Guo <felixguoxiuping@gmail.com> 76ebf5866SFelix Guo# Author: Brendan Higgins <brendanhiggins@google.com> 86ebf5866SFelix Guo 923552807SMichał Winiarskiimport importlib.abc 1087c9c163SBrendan Higginsimport importlib.util 116ebf5866SFelix Guoimport logging 126ebf5866SFelix Guoimport subprocess 136ebf5866SFelix Guoimport os 143f0a50f3SDaniel Latypovimport shlex 15fcdb0bc0SAndy Shevchenkoimport shutil 16021ed9f5SHeidi Fahimimport signal 177d7c48dfSDaniel Latypovimport threading 187d7c48dfSDaniel Latypovfrom typing import Iterator, List, Optional, Tuple 19021ed9f5SHeidi Fahim 206ebf5866SFelix Guoimport kunit_config 21e756dbebSDaniel Latypovfrom kunit_printer import stdout 2287c9c163SBrendan Higginsimport qemu_config 236ebf5866SFelix Guo 246ebf5866SFelix GuoKCONFIG_PATH = '.config' 25fcdb0bc0SAndy ShevchenkoKUNITCONFIG_PATH = '.kunitconfig' 264c2911f1SDaniel LatypovOLD_KUNITCONFIG_PATH = 'last_used_kunitconfig' 27d9d6b822SDavid GowDEFAULT_KUNITCONFIG_PATH = 'tools/testing/kunit/configs/default.config' 28*980ac3adSDaniel LatypovALL_TESTS_CONFIG_PATH = 'tools/testing/kunit/configs/all_tests.config' 296fc3a863SDavid GowUML_KCONFIG_PATH = 'tools/testing/kunit/configs/arch_uml.config' 30128dc4bcSAndy ShevchenkoOUTFILE_PATH = 'test.log' 3187c9c163SBrendan HigginsABS_TOOL_PATH = os.path.abspath(os.path.dirname(__file__)) 3287c9c163SBrendan HigginsQEMU_CONFIGS_DIR = os.path.join(ABS_TOOL_PATH, 'qemu_configs') 336ebf5866SFelix Guo 346ebf5866SFelix Guoclass ConfigError(Exception): 356ebf5866SFelix Guo """Represents an error trying to configure the Linux kernel.""" 366ebf5866SFelix Guo 376ebf5866SFelix Guo 386ebf5866SFelix Guoclass BuildError(Exception): 396ebf5866SFelix Guo """Represents an error trying to build the Linux kernel.""" 406ebf5866SFelix Guo 416ebf5866SFelix Guo 420453f984SDaniel Latypovclass LinuxSourceTreeOperations: 436ebf5866SFelix Guo """An abstraction over command line operations performed on a source tree.""" 446ebf5866SFelix Guo 4587c9c163SBrendan Higgins def __init__(self, linux_arch: str, cross_compile: Optional[str]): 4687c9c163SBrendan Higgins self._linux_arch = linux_arch 4787c9c163SBrendan Higgins self._cross_compile = cross_compile 4887c9c163SBrendan Higgins 4909641f7cSDaniel Latypov def make_mrproper(self) -> None: 506ebf5866SFelix Guo try: 515a9fcad7SWill Chen subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT) 526ebf5866SFelix Guo except OSError as e: 531abdd39fSDaniel Latypov raise ConfigError('Could not call make command: ' + str(e)) 546ebf5866SFelix Guo except subprocess.CalledProcessError as e: 551abdd39fSDaniel Latypov raise ConfigError(e.output.decode()) 566ebf5866SFelix Guo 576fc3a863SDavid Gow def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 588a7c6f85SDaniel Latypov return base_kunitconfig 5987c9c163SBrendan Higgins 60aa1c0555SDaniel Latypov def make_olddefconfig(self, build_dir: str, make_options) -> None: 61aa1c0555SDaniel Latypov command = ['make', 'ARCH=' + self._linux_arch, 'O=' + build_dir, 'olddefconfig'] 6287c9c163SBrendan Higgins if self._cross_compile: 6387c9c163SBrendan Higgins command += ['CROSS_COMPILE=' + self._cross_compile] 640476e69fSGreg Thelen if make_options: 650476e69fSGreg Thelen command.extend(make_options) 6687c9c163SBrendan Higgins print('Populating config with:\n$', ' '.join(command)) 676ebf5866SFelix Guo try: 685a9fcad7SWill Chen subprocess.check_output(command, stderr=subprocess.STDOUT) 696ebf5866SFelix Guo except OSError as e: 701abdd39fSDaniel Latypov raise ConfigError('Could not call make command: ' + str(e)) 716ebf5866SFelix Guo except subprocess.CalledProcessError as e: 721abdd39fSDaniel Latypov raise ConfigError(e.output.decode()) 736ebf5866SFelix Guo 74aa1c0555SDaniel Latypov def make(self, jobs, build_dir: str, make_options) -> None: 75aa1c0555SDaniel Latypov command = ['make', 'ARCH=' + self._linux_arch, 'O=' + build_dir, '--jobs=' + str(jobs)] 7687c9c163SBrendan Higgins if make_options: 7787c9c163SBrendan Higgins command.extend(make_options) 7887c9c163SBrendan Higgins if self._cross_compile: 7987c9c163SBrendan Higgins command += ['CROSS_COMPILE=' + self._cross_compile] 8087c9c163SBrendan Higgins print('Building with:\n$', ' '.join(command)) 8187c9c163SBrendan Higgins try: 8287c9c163SBrendan Higgins proc = subprocess.Popen(command, 8387c9c163SBrendan Higgins stderr=subprocess.PIPE, 8487c9c163SBrendan Higgins stdout=subprocess.DEVNULL) 8587c9c163SBrendan Higgins except OSError as e: 8687c9c163SBrendan Higgins raise BuildError('Could not call execute make: ' + str(e)) 8787c9c163SBrendan Higgins except subprocess.CalledProcessError as e: 8887c9c163SBrendan Higgins raise BuildError(e.output) 8987c9c163SBrendan Higgins _, stderr = proc.communicate() 9087c9c163SBrendan Higgins if proc.returncode != 0: 9187c9c163SBrendan Higgins raise BuildError(stderr.decode()) 9287c9c163SBrendan Higgins if stderr: # likely only due to build warnings 9387c9c163SBrendan Higgins print(stderr.decode()) 9487c9c163SBrendan Higgins 957d7c48dfSDaniel Latypov def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 967d7c48dfSDaniel Latypov raise RuntimeError('not implemented!') 9787c9c163SBrendan Higgins 9887c9c163SBrendan Higgins 9987c9c163SBrendan Higginsclass LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): 10087c9c163SBrendan Higgins 10187c9c163SBrendan Higgins def __init__(self, qemu_arch_params: qemu_config.QemuArchParams, cross_compile: Optional[str]): 10287c9c163SBrendan Higgins super().__init__(linux_arch=qemu_arch_params.linux_arch, 10387c9c163SBrendan Higgins cross_compile=cross_compile) 10487c9c163SBrendan Higgins self._kconfig = qemu_arch_params.kconfig 10587c9c163SBrendan Higgins self._qemu_arch = qemu_arch_params.qemu_arch 10687c9c163SBrendan Higgins self._kernel_path = qemu_arch_params.kernel_path 10787c9c163SBrendan Higgins self._kernel_command_line = qemu_arch_params.kernel_command_line + ' kunit_shutdown=reboot' 10887c9c163SBrendan Higgins self._extra_qemu_params = qemu_arch_params.extra_qemu_params 10987c9c163SBrendan Higgins 1106fc3a863SDavid Gow def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 11198978490SDaniel Latypov kconfig = kunit_config.parse_from_string(self._kconfig) 1128a7c6f85SDaniel Latypov kconfig.merge_in_entries(base_kunitconfig) 1138a7c6f85SDaniel Latypov return kconfig 11487c9c163SBrendan Higgins 1157d7c48dfSDaniel Latypov def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 11687c9c163SBrendan Higgins kernel_path = os.path.join(build_dir, self._kernel_path) 11787c9c163SBrendan Higgins qemu_command = ['qemu-system-' + self._qemu_arch, 11887c9c163SBrendan Higgins '-nodefaults', 11987c9c163SBrendan Higgins '-m', '1024', 12087c9c163SBrendan Higgins '-kernel', kernel_path, 1213f0a50f3SDaniel Latypov '-append', ' '.join(params + [self._kernel_command_line]), 12287c9c163SBrendan Higgins '-no-reboot', 12387c9c163SBrendan Higgins '-nographic', 1243f0a50f3SDaniel Latypov '-serial', 'stdio'] + self._extra_qemu_params 1253f0a50f3SDaniel Latypov # Note: shlex.join() does what we want, but requires python 3.8+. 1263f0a50f3SDaniel Latypov print('Running tests with:\n$', ' '.join(shlex.quote(arg) for arg in qemu_command)) 1273f0a50f3SDaniel Latypov return subprocess.Popen(qemu_command, 12887c9c163SBrendan Higgins stdin=subprocess.PIPE, 1297d7c48dfSDaniel Latypov stdout=subprocess.PIPE, 13087c9c163SBrendan Higgins stderr=subprocess.STDOUT, 1313f0a50f3SDaniel Latypov text=True, errors='backslashreplace') 13287c9c163SBrendan Higgins 13387c9c163SBrendan Higginsclass LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): 13487c9c163SBrendan Higgins """An abstraction over command line operations performed on a source tree.""" 13587c9c163SBrendan Higgins 13687c9c163SBrendan Higgins def __init__(self, cross_compile=None): 13787c9c163SBrendan Higgins super().__init__(linux_arch='um', cross_compile=cross_compile) 13887c9c163SBrendan Higgins 1396fc3a863SDavid Gow def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 1406fc3a863SDavid Gow kconfig = kunit_config.parse_file(UML_KCONFIG_PATH) 1416fc3a863SDavid Gow kconfig.merge_in_entries(base_kunitconfig) 1426fc3a863SDavid Gow return kconfig 1436fc3a863SDavid Gow 1447d7c48dfSDaniel Latypov def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 1456ebf5866SFelix Guo """Runs the Linux UML binary. Must be named 'linux'.""" 146aa1c0555SDaniel Latypov linux_bin = os.path.join(build_dir, 'linux') 1479241bc81SDaniel Latypov params.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) 1487d7c48dfSDaniel Latypov return subprocess.Popen([linux_bin] + params, 14987c9c163SBrendan Higgins stdin=subprocess.PIPE, 1507d7c48dfSDaniel Latypov stdout=subprocess.PIPE, 15187c9c163SBrendan Higgins stderr=subprocess.STDOUT, 1522ab5d5e6SDaniel Latypov text=True, errors='backslashreplace') 1536ebf5866SFelix Guo 154aa1c0555SDaniel Latypovdef get_kconfig_path(build_dir: str) -> str: 155aa1c0555SDaniel Latypov return os.path.join(build_dir, KCONFIG_PATH) 1566ebf5866SFelix Guo 157aa1c0555SDaniel Latypovdef get_kunitconfig_path(build_dir: str) -> str: 158aa1c0555SDaniel Latypov return os.path.join(build_dir, KUNITCONFIG_PATH) 159fcdb0bc0SAndy Shevchenko 160aa1c0555SDaniel Latypovdef get_old_kunitconfig_path(build_dir: str) -> str: 161aa1c0555SDaniel Latypov return os.path.join(build_dir, OLD_KUNITCONFIG_PATH) 1624c2911f1SDaniel Latypov 16353b46621SDaniel Latypovdef get_parsed_kunitconfig(build_dir: str, 16453b46621SDaniel Latypov kunitconfig_paths: Optional[List[str]]=None) -> kunit_config.Kconfig: 16553b46621SDaniel Latypov if not kunitconfig_paths: 16653b46621SDaniel Latypov path = get_kunitconfig_path(build_dir) 16753b46621SDaniel Latypov if not os.path.exists(path): 16853b46621SDaniel Latypov shutil.copyfile(DEFAULT_KUNITCONFIG_PATH, path) 16953b46621SDaniel Latypov return kunit_config.parse_file(path) 17053b46621SDaniel Latypov 17153b46621SDaniel Latypov merged = kunit_config.Kconfig() 17253b46621SDaniel Latypov 17353b46621SDaniel Latypov for path in kunitconfig_paths: 17453b46621SDaniel Latypov if os.path.isdir(path): 17553b46621SDaniel Latypov path = os.path.join(path, KUNITCONFIG_PATH) 17653b46621SDaniel Latypov if not os.path.exists(path): 17753b46621SDaniel Latypov raise ConfigError(f'Specified kunitconfig ({path}) does not exist') 17853b46621SDaniel Latypov 17953b46621SDaniel Latypov partial = kunit_config.parse_file(path) 18053b46621SDaniel Latypov diff = merged.conflicting_options(partial) 18153b46621SDaniel Latypov if diff: 18253b46621SDaniel Latypov diff_str = '\n\n'.join(f'{a}\n vs from {path}\n{b}' for a, b in diff) 18353b46621SDaniel Latypov raise ConfigError(f'Multiple values specified for {len(diff)} options in kunitconfig:\n{diff_str}') 18453b46621SDaniel Latypov merged.merge_in_entries(partial) 18553b46621SDaniel Latypov return merged 18653b46621SDaniel Latypov 187aa1c0555SDaniel Latypovdef get_outfile_path(build_dir: str) -> str: 188aa1c0555SDaniel Latypov return os.path.join(build_dir, OUTFILE_PATH) 189128dc4bcSAndy Shevchenko 1908c278d97SDaniel Latypovdef _default_qemu_config_path(arch: str) -> str: 19187c9c163SBrendan Higgins config_path = os.path.join(QEMU_CONFIGS_DIR, arch + '.py') 1920453f984SDaniel Latypov if os.path.isfile(config_path): 1938c278d97SDaniel Latypov return config_path 194fe678fedSDaniel Latypov 195fe678fedSDaniel Latypov options = [f[:-3] for f in os.listdir(QEMU_CONFIGS_DIR) if f.endswith('.py')] 196fe678fedSDaniel Latypov raise ConfigError(arch + ' is not a valid arch, options are ' + str(sorted(options))) 19787c9c163SBrendan Higgins 1988c278d97SDaniel Latypovdef _get_qemu_ops(config_path: str, 199a9333bd3SDaniel Latypov extra_qemu_args: Optional[List[str]], 2008c278d97SDaniel Latypov cross_compile: Optional[str]) -> Tuple[str, LinuxSourceTreeOperations]: 20187c9c163SBrendan Higgins # The module name/path has very little to do with where the actual file 20287c9c163SBrendan Higgins # exists (I learned this through experimentation and could not find it 20387c9c163SBrendan Higgins # anywhere in the Python documentation). 20487c9c163SBrendan Higgins # 20587c9c163SBrendan Higgins # Bascially, we completely ignore the actual file location of the config 20687c9c163SBrendan Higgins # we are loading and just tell Python that the module lives in the 20787c9c163SBrendan Higgins # QEMU_CONFIGS_DIR for import purposes regardless of where it actually 20887c9c163SBrendan Higgins # exists as a file. 20987c9c163SBrendan Higgins module_path = '.' + os.path.join(os.path.basename(QEMU_CONFIGS_DIR), os.path.basename(config_path)) 21087c9c163SBrendan Higgins spec = importlib.util.spec_from_file_location(module_path, config_path) 21185310a62SDaniel Latypov assert spec is not None 21287c9c163SBrendan Higgins config = importlib.util.module_from_spec(spec) 21352a5d80aSDaniel Latypov # See https://github.com/python/typeshed/pull/2626 for context. 21452a5d80aSDaniel Latypov assert isinstance(spec.loader, importlib.abc.Loader) 21552a5d80aSDaniel Latypov spec.loader.exec_module(config) 21652a5d80aSDaniel Latypov 21752a5d80aSDaniel Latypov if not hasattr(config, 'QEMU_ARCH'): 21852a5d80aSDaniel Latypov raise ValueError('qemu_config module missing "QEMU_ARCH": ' + config_path) 21952a5d80aSDaniel Latypov params: qemu_config.QemuArchParams = config.QEMU_ARCH # type: ignore 220a9333bd3SDaniel Latypov if extra_qemu_args: 221a9333bd3SDaniel Latypov params.extra_qemu_params.extend(extra_qemu_args) 22252a5d80aSDaniel Latypov return params.linux_arch, LinuxSourceTreeOperationsQemu( 22352a5d80aSDaniel Latypov params, cross_compile=cross_compile) 22487c9c163SBrendan Higgins 2250453f984SDaniel Latypovclass LinuxSourceTree: 2266ebf5866SFelix Guo """Represents a Linux kernel source tree with KUnit tests.""" 2276ebf5866SFelix Guo 22887c9c163SBrendan Higgins def __init__( 22987c9c163SBrendan Higgins self, 23087c9c163SBrendan Higgins build_dir: str, 23153b46621SDaniel Latypov kunitconfig_paths: Optional[List[str]]=None, 2329f57cc76SDaniel Latypov kconfig_add: Optional[List[str]]=None, 23387c9c163SBrendan Higgins arch=None, 23487c9c163SBrendan Higgins cross_compile=None, 235a9333bd3SDaniel Latypov qemu_config_path=None, 236a9333bd3SDaniel Latypov extra_qemu_args=None) -> None: 237021ed9f5SHeidi Fahim signal.signal(signal.SIGINT, self.signal_handler) 23887c9c163SBrendan Higgins if qemu_config_path: 239a9333bd3SDaniel Latypov self._arch, self._ops = _get_qemu_ops(qemu_config_path, extra_qemu_args, cross_compile) 24087c9c163SBrendan Higgins else: 24187c9c163SBrendan Higgins self._arch = 'um' if arch is None else arch 2428c278d97SDaniel Latypov if self._arch == 'um': 2438c278d97SDaniel Latypov self._ops = LinuxSourceTreeOperationsUml(cross_compile=cross_compile) 2448c278d97SDaniel Latypov else: 2458c278d97SDaniel Latypov qemu_config_path = _default_qemu_config_path(self._arch) 246a9333bd3SDaniel Latypov _, self._ops = _get_qemu_ops(qemu_config_path, extra_qemu_args, cross_compile) 2472b8fdbbfSDaniel Latypov 24853b46621SDaniel Latypov self._kconfig = get_parsed_kunitconfig(build_dir, kunitconfig_paths) 2499f57cc76SDaniel Latypov if kconfig_add: 2509f57cc76SDaniel Latypov kconfig = kunit_config.parse_from_string('\n'.join(kconfig_add)) 2519f57cc76SDaniel Latypov self._kconfig.merge_in_entries(kconfig) 2529f57cc76SDaniel Latypov 253885210d3SDaniel Latypov def arch(self) -> str: 254885210d3SDaniel Latypov return self._arch 2552b8fdbbfSDaniel Latypov 25609641f7cSDaniel Latypov def clean(self) -> bool: 2576ebf5866SFelix Guo try: 2586ebf5866SFelix Guo self._ops.make_mrproper() 2596ebf5866SFelix Guo except ConfigError as e: 2606ebf5866SFelix Guo logging.error(e) 2616ebf5866SFelix Guo return False 2626ebf5866SFelix Guo return True 2636ebf5866SFelix Guo 264aa1c0555SDaniel Latypov def validate_config(self, build_dir: str) -> bool: 265dde54b94SHeidi Fahim kconfig_path = get_kconfig_path(build_dir) 26698978490SDaniel Latypov validated_kconfig = kunit_config.parse_file(kconfig_path) 267c44895b6SDaniel Latypov if self._kconfig.is_subset_of(validated_kconfig): 268c44895b6SDaniel Latypov return True 2698a7c6f85SDaniel Latypov missing = set(self._kconfig.as_entries()) - set(validated_kconfig.as_entries()) 270c44895b6SDaniel Latypov message = 'Not all Kconfig options selected in kunitconfig were in the generated .config.\n' \ 271c44895b6SDaniel Latypov 'This is probably due to unsatisfied dependencies.\n' \ 2728a7c6f85SDaniel Latypov 'Missing: ' + ', '.join(str(e) for e in missing) 273c44895b6SDaniel Latypov if self._arch == 'um': 274c44895b6SDaniel Latypov message += '\nNote: many Kconfig options aren\'t available on UML. You can try running ' \ 275c44895b6SDaniel Latypov 'on a different architecture with something like "--arch=x86_64".' 276dde54b94SHeidi Fahim logging.error(message) 277dde54b94SHeidi Fahim return False 278dde54b94SHeidi Fahim 279aa1c0555SDaniel Latypov def build_config(self, build_dir: str, make_options) -> bool: 2806ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 2816ebf5866SFelix Guo if build_dir and not os.path.exists(build_dir): 2826ebf5866SFelix Guo os.mkdir(build_dir) 2836ebf5866SFelix Guo try: 2846fc3a863SDavid Gow self._kconfig = self._ops.make_arch_config(self._kconfig) 28587c9c163SBrendan Higgins self._kconfig.write_to_file(kconfig_path) 2860476e69fSGreg Thelen self._ops.make_olddefconfig(build_dir, make_options) 2876ebf5866SFelix Guo except ConfigError as e: 2886ebf5866SFelix Guo logging.error(e) 2896ebf5866SFelix Guo return False 2904c2911f1SDaniel Latypov if not self.validate_config(build_dir): 2914c2911f1SDaniel Latypov return False 2924c2911f1SDaniel Latypov 2934c2911f1SDaniel Latypov old_path = get_old_kunitconfig_path(build_dir) 2944c2911f1SDaniel Latypov if os.path.exists(old_path): 2954c2911f1SDaniel Latypov os.remove(old_path) # write_to_file appends to the file 2964c2911f1SDaniel Latypov self._kconfig.write_to_file(old_path) 2974c2911f1SDaniel Latypov return True 2984c2911f1SDaniel Latypov 2994c2911f1SDaniel Latypov def _kunitconfig_changed(self, build_dir: str) -> bool: 3004c2911f1SDaniel Latypov old_path = get_old_kunitconfig_path(build_dir) 3014c2911f1SDaniel Latypov if not os.path.exists(old_path): 3024c2911f1SDaniel Latypov return True 3034c2911f1SDaniel Latypov 3044c2911f1SDaniel Latypov old_kconfig = kunit_config.parse_file(old_path) 3058a7c6f85SDaniel Latypov return old_kconfig != self._kconfig 3066ebf5866SFelix Guo 307aa1c0555SDaniel Latypov def build_reconfig(self, build_dir: str, make_options) -> bool: 30814ee5cfdSSeongJae Park """Creates a new .config if it is not a subset of the .kunitconfig.""" 3096ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 3104c2911f1SDaniel Latypov if not os.path.exists(kconfig_path): 3114c2911f1SDaniel Latypov print('Generating .config ...') 3124c2911f1SDaniel Latypov return self.build_config(build_dir, make_options) 3134c2911f1SDaniel Latypov 31498978490SDaniel Latypov existing_kconfig = kunit_config.parse_file(kconfig_path) 3156fc3a863SDavid Gow self._kconfig = self._ops.make_arch_config(self._kconfig) 3168a7c6f85SDaniel Latypov 3174c2911f1SDaniel Latypov if self._kconfig.is_subset_of(existing_kconfig) and not self._kunitconfig_changed(build_dir): 3184c2911f1SDaniel Latypov return True 3196ebf5866SFelix Guo print('Regenerating .config ...') 3206ebf5866SFelix Guo os.remove(kconfig_path) 3210476e69fSGreg Thelen return self.build_config(build_dir, make_options) 3226ebf5866SFelix Guo 323*980ac3adSDaniel Latypov def build_kernel(self, jobs, build_dir: str, make_options) -> bool: 3246ebf5866SFelix Guo try: 3250476e69fSGreg Thelen self._ops.make_olddefconfig(build_dir, make_options) 3260476e69fSGreg Thelen self._ops.make(jobs, build_dir, make_options) 3276ebf5866SFelix Guo except (ConfigError, BuildError) as e: 3286ebf5866SFelix Guo logging.error(e) 3296ebf5866SFelix Guo return False 330dde54b94SHeidi Fahim return self.validate_config(build_dir) 3316ebf5866SFelix Guo 3327af29141SDaniel Latypov def run_kernel(self, args=None, build_dir='', filter_glob='', timeout=None) -> Iterator[str]: 3337af29141SDaniel Latypov if not args: 3347af29141SDaniel Latypov args = [] 335d992880bSDaniel Latypov if filter_glob: 336d992880bSDaniel Latypov args.append('kunit.filter_glob='+filter_glob) 337d20a6ba5SJoe Fradley args.append('kunit.enable=1') 3387d7c48dfSDaniel Latypov 3397d7c48dfSDaniel Latypov process = self._ops.start(args, build_dir) 3407d7c48dfSDaniel Latypov assert process.stdout is not None # tell mypy it's set 3417d7c48dfSDaniel Latypov 3427d7c48dfSDaniel Latypov # Enforce the timeout in a background thread. 3437d7c48dfSDaniel Latypov def _wait_proc(): 3447d7c48dfSDaniel Latypov try: 3457d7c48dfSDaniel Latypov process.wait(timeout=timeout) 3467d7c48dfSDaniel Latypov except Exception as e: 3477d7c48dfSDaniel Latypov print(e) 3487d7c48dfSDaniel Latypov process.terminate() 3497d7c48dfSDaniel Latypov process.wait() 3507d7c48dfSDaniel Latypov waiter = threading.Thread(target=_wait_proc) 3517d7c48dfSDaniel Latypov waiter.start() 3527d7c48dfSDaniel Latypov 3537d7c48dfSDaniel Latypov output = open(get_outfile_path(build_dir), 'w') 3547d7c48dfSDaniel Latypov try: 3557d7c48dfSDaniel Latypov # Tee the output to the file and to our caller in real time. 3567d7c48dfSDaniel Latypov for line in process.stdout: 3577d7c48dfSDaniel Latypov output.write(line) 358021ed9f5SHeidi Fahim yield line 3597d7c48dfSDaniel Latypov # This runs even if our caller doesn't consume every line. 3607d7c48dfSDaniel Latypov finally: 3617d7c48dfSDaniel Latypov # Flush any leftover output to the file 3627d7c48dfSDaniel Latypov output.write(process.stdout.read()) 3637d7c48dfSDaniel Latypov output.close() 3647d7c48dfSDaniel Latypov process.stdout.close() 3657d7c48dfSDaniel Latypov 3667d7c48dfSDaniel Latypov waiter.join() 3677d7c48dfSDaniel Latypov subprocess.call(['stty', 'sane']) 368021ed9f5SHeidi Fahim 3690453f984SDaniel Latypov def signal_handler(self, unused_sig, unused_frame) -> None: 370021ed9f5SHeidi Fahim logging.error('Build interruption occurred. Cleaning console.') 371021ed9f5SHeidi Fahim subprocess.call(['stty', 'sane']) 372