162306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci# 362306a36Sopenharmony_ci# Runs UML kernel, collects output, and handles errors. 462306a36Sopenharmony_ci# 562306a36Sopenharmony_ci# Copyright (C) 2019, Google LLC. 662306a36Sopenharmony_ci# Author: Felix Guo <felixguoxiuping@gmail.com> 762306a36Sopenharmony_ci# Author: Brendan Higgins <brendanhiggins@google.com> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ciimport importlib.abc 1062306a36Sopenharmony_ciimport importlib.util 1162306a36Sopenharmony_ciimport logging 1262306a36Sopenharmony_ciimport subprocess 1362306a36Sopenharmony_ciimport os 1462306a36Sopenharmony_ciimport shlex 1562306a36Sopenharmony_ciimport shutil 1662306a36Sopenharmony_ciimport signal 1762306a36Sopenharmony_ciimport threading 1862306a36Sopenharmony_cifrom typing import Iterator, List, Optional, Tuple 1962306a36Sopenharmony_cifrom types import FrameType 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ciimport kunit_config 2262306a36Sopenharmony_ciimport qemu_config 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ciKCONFIG_PATH = '.config' 2562306a36Sopenharmony_ciKUNITCONFIG_PATH = '.kunitconfig' 2662306a36Sopenharmony_ciOLD_KUNITCONFIG_PATH = 'last_used_kunitconfig' 2762306a36Sopenharmony_ciDEFAULT_KUNITCONFIG_PATH = 'tools/testing/kunit/configs/default.config' 2862306a36Sopenharmony_ciALL_TESTS_CONFIG_PATH = 'tools/testing/kunit/configs/all_tests.config' 2962306a36Sopenharmony_ciUML_KCONFIG_PATH = 'tools/testing/kunit/configs/arch_uml.config' 3062306a36Sopenharmony_ciOUTFILE_PATH = 'test.log' 3162306a36Sopenharmony_ciABS_TOOL_PATH = os.path.abspath(os.path.dirname(__file__)) 3262306a36Sopenharmony_ciQEMU_CONFIGS_DIR = os.path.join(ABS_TOOL_PATH, 'qemu_configs') 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ciclass ConfigError(Exception): 3562306a36Sopenharmony_ci """Represents an error trying to configure the Linux kernel.""" 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ciclass BuildError(Exception): 3962306a36Sopenharmony_ci """Represents an error trying to build the Linux kernel.""" 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ciclass LinuxSourceTreeOperations: 4362306a36Sopenharmony_ci """An abstraction over command line operations performed on a source tree.""" 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci def __init__(self, linux_arch: str, cross_compile: Optional[str]): 4662306a36Sopenharmony_ci self._linux_arch = linux_arch 4762306a36Sopenharmony_ci self._cross_compile = cross_compile 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci def make_mrproper(self) -> None: 5062306a36Sopenharmony_ci try: 5162306a36Sopenharmony_ci subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT) 5262306a36Sopenharmony_ci except OSError as e: 5362306a36Sopenharmony_ci raise ConfigError('Could not call make command: ' + str(e)) 5462306a36Sopenharmony_ci except subprocess.CalledProcessError as e: 5562306a36Sopenharmony_ci raise ConfigError(e.output.decode()) 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 5862306a36Sopenharmony_ci return base_kunitconfig 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci def make_olddefconfig(self, build_dir: str, make_options: Optional[List[str]]) -> None: 6162306a36Sopenharmony_ci command = ['make', 'ARCH=' + self._linux_arch, 'O=' + build_dir, 'olddefconfig'] 6262306a36Sopenharmony_ci if self._cross_compile: 6362306a36Sopenharmony_ci command += ['CROSS_COMPILE=' + self._cross_compile] 6462306a36Sopenharmony_ci if make_options: 6562306a36Sopenharmony_ci command.extend(make_options) 6662306a36Sopenharmony_ci print('Populating config with:\n$', ' '.join(command)) 6762306a36Sopenharmony_ci try: 6862306a36Sopenharmony_ci subprocess.check_output(command, stderr=subprocess.STDOUT) 6962306a36Sopenharmony_ci except OSError as e: 7062306a36Sopenharmony_ci raise ConfigError('Could not call make command: ' + str(e)) 7162306a36Sopenharmony_ci except subprocess.CalledProcessError as e: 7262306a36Sopenharmony_ci raise ConfigError(e.output.decode()) 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci def make(self, jobs: int, build_dir: str, make_options: Optional[List[str]]) -> None: 7562306a36Sopenharmony_ci command = ['make', 'ARCH=' + self._linux_arch, 'O=' + build_dir, '--jobs=' + str(jobs)] 7662306a36Sopenharmony_ci if make_options: 7762306a36Sopenharmony_ci command.extend(make_options) 7862306a36Sopenharmony_ci if self._cross_compile: 7962306a36Sopenharmony_ci command += ['CROSS_COMPILE=' + self._cross_compile] 8062306a36Sopenharmony_ci print('Building with:\n$', ' '.join(command)) 8162306a36Sopenharmony_ci try: 8262306a36Sopenharmony_ci proc = subprocess.Popen(command, 8362306a36Sopenharmony_ci stderr=subprocess.PIPE, 8462306a36Sopenharmony_ci stdout=subprocess.DEVNULL) 8562306a36Sopenharmony_ci except OSError as e: 8662306a36Sopenharmony_ci raise BuildError('Could not call execute make: ' + str(e)) 8762306a36Sopenharmony_ci except subprocess.CalledProcessError as e: 8862306a36Sopenharmony_ci raise BuildError(e.output) 8962306a36Sopenharmony_ci _, stderr = proc.communicate() 9062306a36Sopenharmony_ci if proc.returncode != 0: 9162306a36Sopenharmony_ci raise BuildError(stderr.decode()) 9262306a36Sopenharmony_ci if stderr: # likely only due to build warnings 9362306a36Sopenharmony_ci print(stderr.decode()) 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 9662306a36Sopenharmony_ci raise RuntimeError('not implemented!') 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ciclass LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci def __init__(self, qemu_arch_params: qemu_config.QemuArchParams, cross_compile: Optional[str]): 10262306a36Sopenharmony_ci super().__init__(linux_arch=qemu_arch_params.linux_arch, 10362306a36Sopenharmony_ci cross_compile=cross_compile) 10462306a36Sopenharmony_ci self._kconfig = qemu_arch_params.kconfig 10562306a36Sopenharmony_ci self._qemu_arch = qemu_arch_params.qemu_arch 10662306a36Sopenharmony_ci self._kernel_path = qemu_arch_params.kernel_path 10762306a36Sopenharmony_ci self._kernel_command_line = qemu_arch_params.kernel_command_line + ' kunit_shutdown=reboot' 10862306a36Sopenharmony_ci self._extra_qemu_params = qemu_arch_params.extra_qemu_params 10962306a36Sopenharmony_ci self._serial = qemu_arch_params.serial 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 11262306a36Sopenharmony_ci kconfig = kunit_config.parse_from_string(self._kconfig) 11362306a36Sopenharmony_ci kconfig.merge_in_entries(base_kunitconfig) 11462306a36Sopenharmony_ci return kconfig 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 11762306a36Sopenharmony_ci kernel_path = os.path.join(build_dir, self._kernel_path) 11862306a36Sopenharmony_ci qemu_command = ['qemu-system-' + self._qemu_arch, 11962306a36Sopenharmony_ci '-nodefaults', 12062306a36Sopenharmony_ci '-m', '1024', 12162306a36Sopenharmony_ci '-kernel', kernel_path, 12262306a36Sopenharmony_ci '-append', ' '.join(params + [self._kernel_command_line]), 12362306a36Sopenharmony_ci '-no-reboot', 12462306a36Sopenharmony_ci '-nographic', 12562306a36Sopenharmony_ci '-serial', self._serial] + self._extra_qemu_params 12662306a36Sopenharmony_ci # Note: shlex.join() does what we want, but requires python 3.8+. 12762306a36Sopenharmony_ci print('Running tests with:\n$', ' '.join(shlex.quote(arg) for arg in qemu_command)) 12862306a36Sopenharmony_ci return subprocess.Popen(qemu_command, 12962306a36Sopenharmony_ci stdin=subprocess.PIPE, 13062306a36Sopenharmony_ci stdout=subprocess.PIPE, 13162306a36Sopenharmony_ci stderr=subprocess.STDOUT, 13262306a36Sopenharmony_ci text=True, errors='backslashreplace') 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ciclass LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): 13562306a36Sopenharmony_ci """An abstraction over command line operations performed on a source tree.""" 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci def __init__(self, cross_compile: Optional[str]=None): 13862306a36Sopenharmony_ci super().__init__(linux_arch='um', cross_compile=cross_compile) 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 14162306a36Sopenharmony_ci kconfig = kunit_config.parse_file(UML_KCONFIG_PATH) 14262306a36Sopenharmony_ci kconfig.merge_in_entries(base_kunitconfig) 14362306a36Sopenharmony_ci return kconfig 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 14662306a36Sopenharmony_ci """Runs the Linux UML binary. Must be named 'linux'.""" 14762306a36Sopenharmony_ci linux_bin = os.path.join(build_dir, 'linux') 14862306a36Sopenharmony_ci params.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) 14962306a36Sopenharmony_ci return subprocess.Popen([linux_bin] + params, 15062306a36Sopenharmony_ci stdin=subprocess.PIPE, 15162306a36Sopenharmony_ci stdout=subprocess.PIPE, 15262306a36Sopenharmony_ci stderr=subprocess.STDOUT, 15362306a36Sopenharmony_ci text=True, errors='backslashreplace') 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cidef get_kconfig_path(build_dir: str) -> str: 15662306a36Sopenharmony_ci return os.path.join(build_dir, KCONFIG_PATH) 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cidef get_kunitconfig_path(build_dir: str) -> str: 15962306a36Sopenharmony_ci return os.path.join(build_dir, KUNITCONFIG_PATH) 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cidef get_old_kunitconfig_path(build_dir: str) -> str: 16262306a36Sopenharmony_ci return os.path.join(build_dir, OLD_KUNITCONFIG_PATH) 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cidef get_parsed_kunitconfig(build_dir: str, 16562306a36Sopenharmony_ci kunitconfig_paths: Optional[List[str]]=None) -> kunit_config.Kconfig: 16662306a36Sopenharmony_ci if not kunitconfig_paths: 16762306a36Sopenharmony_ci path = get_kunitconfig_path(build_dir) 16862306a36Sopenharmony_ci if not os.path.exists(path): 16962306a36Sopenharmony_ci shutil.copyfile(DEFAULT_KUNITCONFIG_PATH, path) 17062306a36Sopenharmony_ci return kunit_config.parse_file(path) 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci merged = kunit_config.Kconfig() 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci for path in kunitconfig_paths: 17562306a36Sopenharmony_ci if os.path.isdir(path): 17662306a36Sopenharmony_ci path = os.path.join(path, KUNITCONFIG_PATH) 17762306a36Sopenharmony_ci if not os.path.exists(path): 17862306a36Sopenharmony_ci raise ConfigError(f'Specified kunitconfig ({path}) does not exist') 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci partial = kunit_config.parse_file(path) 18162306a36Sopenharmony_ci diff = merged.conflicting_options(partial) 18262306a36Sopenharmony_ci if diff: 18362306a36Sopenharmony_ci diff_str = '\n\n'.join(f'{a}\n vs from {path}\n{b}' for a, b in diff) 18462306a36Sopenharmony_ci raise ConfigError(f'Multiple values specified for {len(diff)} options in kunitconfig:\n{diff_str}') 18562306a36Sopenharmony_ci merged.merge_in_entries(partial) 18662306a36Sopenharmony_ci return merged 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cidef get_outfile_path(build_dir: str) -> str: 18962306a36Sopenharmony_ci return os.path.join(build_dir, OUTFILE_PATH) 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cidef _default_qemu_config_path(arch: str) -> str: 19262306a36Sopenharmony_ci config_path = os.path.join(QEMU_CONFIGS_DIR, arch + '.py') 19362306a36Sopenharmony_ci if os.path.isfile(config_path): 19462306a36Sopenharmony_ci return config_path 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci options = [f[:-3] for f in os.listdir(QEMU_CONFIGS_DIR) if f.endswith('.py')] 19762306a36Sopenharmony_ci raise ConfigError(arch + ' is not a valid arch, options are ' + str(sorted(options))) 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cidef _get_qemu_ops(config_path: str, 20062306a36Sopenharmony_ci extra_qemu_args: Optional[List[str]], 20162306a36Sopenharmony_ci cross_compile: Optional[str]) -> Tuple[str, LinuxSourceTreeOperations]: 20262306a36Sopenharmony_ci # The module name/path has very little to do with where the actual file 20362306a36Sopenharmony_ci # exists (I learned this through experimentation and could not find it 20462306a36Sopenharmony_ci # anywhere in the Python documentation). 20562306a36Sopenharmony_ci # 20662306a36Sopenharmony_ci # Bascially, we completely ignore the actual file location of the config 20762306a36Sopenharmony_ci # we are loading and just tell Python that the module lives in the 20862306a36Sopenharmony_ci # QEMU_CONFIGS_DIR for import purposes regardless of where it actually 20962306a36Sopenharmony_ci # exists as a file. 21062306a36Sopenharmony_ci module_path = '.' + os.path.join(os.path.basename(QEMU_CONFIGS_DIR), os.path.basename(config_path)) 21162306a36Sopenharmony_ci spec = importlib.util.spec_from_file_location(module_path, config_path) 21262306a36Sopenharmony_ci assert spec is not None 21362306a36Sopenharmony_ci config = importlib.util.module_from_spec(spec) 21462306a36Sopenharmony_ci # See https://github.com/python/typeshed/pull/2626 for context. 21562306a36Sopenharmony_ci assert isinstance(spec.loader, importlib.abc.Loader) 21662306a36Sopenharmony_ci spec.loader.exec_module(config) 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci if not hasattr(config, 'QEMU_ARCH'): 21962306a36Sopenharmony_ci raise ValueError('qemu_config module missing "QEMU_ARCH": ' + config_path) 22062306a36Sopenharmony_ci params: qemu_config.QemuArchParams = config.QEMU_ARCH 22162306a36Sopenharmony_ci if extra_qemu_args: 22262306a36Sopenharmony_ci params.extra_qemu_params.extend(extra_qemu_args) 22362306a36Sopenharmony_ci return params.linux_arch, LinuxSourceTreeOperationsQemu( 22462306a36Sopenharmony_ci params, cross_compile=cross_compile) 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ciclass LinuxSourceTree: 22762306a36Sopenharmony_ci """Represents a Linux kernel source tree with KUnit tests.""" 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci def __init__( 23062306a36Sopenharmony_ci self, 23162306a36Sopenharmony_ci build_dir: str, 23262306a36Sopenharmony_ci kunitconfig_paths: Optional[List[str]]=None, 23362306a36Sopenharmony_ci kconfig_add: Optional[List[str]]=None, 23462306a36Sopenharmony_ci arch: Optional[str]=None, 23562306a36Sopenharmony_ci cross_compile: Optional[str]=None, 23662306a36Sopenharmony_ci qemu_config_path: Optional[str]=None, 23762306a36Sopenharmony_ci extra_qemu_args: Optional[List[str]]=None) -> None: 23862306a36Sopenharmony_ci signal.signal(signal.SIGINT, self.signal_handler) 23962306a36Sopenharmony_ci if qemu_config_path: 24062306a36Sopenharmony_ci self._arch, self._ops = _get_qemu_ops(qemu_config_path, extra_qemu_args, cross_compile) 24162306a36Sopenharmony_ci else: 24262306a36Sopenharmony_ci self._arch = 'um' if arch is None else arch 24362306a36Sopenharmony_ci if self._arch == 'um': 24462306a36Sopenharmony_ci self._ops = LinuxSourceTreeOperationsUml(cross_compile=cross_compile) 24562306a36Sopenharmony_ci else: 24662306a36Sopenharmony_ci qemu_config_path = _default_qemu_config_path(self._arch) 24762306a36Sopenharmony_ci _, self._ops = _get_qemu_ops(qemu_config_path, extra_qemu_args, cross_compile) 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci self._kconfig = get_parsed_kunitconfig(build_dir, kunitconfig_paths) 25062306a36Sopenharmony_ci if kconfig_add: 25162306a36Sopenharmony_ci kconfig = kunit_config.parse_from_string('\n'.join(kconfig_add)) 25262306a36Sopenharmony_ci self._kconfig.merge_in_entries(kconfig) 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci def arch(self) -> str: 25562306a36Sopenharmony_ci return self._arch 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci def clean(self) -> bool: 25862306a36Sopenharmony_ci try: 25962306a36Sopenharmony_ci self._ops.make_mrproper() 26062306a36Sopenharmony_ci except ConfigError as e: 26162306a36Sopenharmony_ci logging.error(e) 26262306a36Sopenharmony_ci return False 26362306a36Sopenharmony_ci return True 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci def validate_config(self, build_dir: str) -> bool: 26662306a36Sopenharmony_ci kconfig_path = get_kconfig_path(build_dir) 26762306a36Sopenharmony_ci validated_kconfig = kunit_config.parse_file(kconfig_path) 26862306a36Sopenharmony_ci if self._kconfig.is_subset_of(validated_kconfig): 26962306a36Sopenharmony_ci return True 27062306a36Sopenharmony_ci missing = set(self._kconfig.as_entries()) - set(validated_kconfig.as_entries()) 27162306a36Sopenharmony_ci message = 'Not all Kconfig options selected in kunitconfig were in the generated .config.\n' \ 27262306a36Sopenharmony_ci 'This is probably due to unsatisfied dependencies.\n' \ 27362306a36Sopenharmony_ci 'Missing: ' + ', '.join(str(e) for e in missing) 27462306a36Sopenharmony_ci if self._arch == 'um': 27562306a36Sopenharmony_ci message += '\nNote: many Kconfig options aren\'t available on UML. You can try running ' \ 27662306a36Sopenharmony_ci 'on a different architecture with something like "--arch=x86_64".' 27762306a36Sopenharmony_ci logging.error(message) 27862306a36Sopenharmony_ci return False 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci def build_config(self, build_dir: str, make_options: Optional[List[str]]) -> bool: 28162306a36Sopenharmony_ci kconfig_path = get_kconfig_path(build_dir) 28262306a36Sopenharmony_ci if build_dir and not os.path.exists(build_dir): 28362306a36Sopenharmony_ci os.mkdir(build_dir) 28462306a36Sopenharmony_ci try: 28562306a36Sopenharmony_ci self._kconfig = self._ops.make_arch_config(self._kconfig) 28662306a36Sopenharmony_ci self._kconfig.write_to_file(kconfig_path) 28762306a36Sopenharmony_ci self._ops.make_olddefconfig(build_dir, make_options) 28862306a36Sopenharmony_ci except ConfigError as e: 28962306a36Sopenharmony_ci logging.error(e) 29062306a36Sopenharmony_ci return False 29162306a36Sopenharmony_ci if not self.validate_config(build_dir): 29262306a36Sopenharmony_ci return False 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci old_path = get_old_kunitconfig_path(build_dir) 29562306a36Sopenharmony_ci if os.path.exists(old_path): 29662306a36Sopenharmony_ci os.remove(old_path) # write_to_file appends to the file 29762306a36Sopenharmony_ci self._kconfig.write_to_file(old_path) 29862306a36Sopenharmony_ci return True 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci def _kunitconfig_changed(self, build_dir: str) -> bool: 30162306a36Sopenharmony_ci old_path = get_old_kunitconfig_path(build_dir) 30262306a36Sopenharmony_ci if not os.path.exists(old_path): 30362306a36Sopenharmony_ci return True 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci old_kconfig = kunit_config.parse_file(old_path) 30662306a36Sopenharmony_ci return old_kconfig != self._kconfig 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci def build_reconfig(self, build_dir: str, make_options: Optional[List[str]]) -> bool: 30962306a36Sopenharmony_ci """Creates a new .config if it is not a subset of the .kunitconfig.""" 31062306a36Sopenharmony_ci kconfig_path = get_kconfig_path(build_dir) 31162306a36Sopenharmony_ci if not os.path.exists(kconfig_path): 31262306a36Sopenharmony_ci print('Generating .config ...') 31362306a36Sopenharmony_ci return self.build_config(build_dir, make_options) 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci existing_kconfig = kunit_config.parse_file(kconfig_path) 31662306a36Sopenharmony_ci self._kconfig = self._ops.make_arch_config(self._kconfig) 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci if self._kconfig.is_subset_of(existing_kconfig) and not self._kunitconfig_changed(build_dir): 31962306a36Sopenharmony_ci return True 32062306a36Sopenharmony_ci print('Regenerating .config ...') 32162306a36Sopenharmony_ci os.remove(kconfig_path) 32262306a36Sopenharmony_ci return self.build_config(build_dir, make_options) 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci def build_kernel(self, jobs: int, build_dir: str, make_options: Optional[List[str]]) -> bool: 32562306a36Sopenharmony_ci try: 32662306a36Sopenharmony_ci self._ops.make_olddefconfig(build_dir, make_options) 32762306a36Sopenharmony_ci self._ops.make(jobs, build_dir, make_options) 32862306a36Sopenharmony_ci except (ConfigError, BuildError) as e: 32962306a36Sopenharmony_ci logging.error(e) 33062306a36Sopenharmony_ci return False 33162306a36Sopenharmony_ci return self.validate_config(build_dir) 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci 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]: 33462306a36Sopenharmony_ci if not args: 33562306a36Sopenharmony_ci args = [] 33662306a36Sopenharmony_ci if filter_glob: 33762306a36Sopenharmony_ci args.append('kunit.filter_glob=' + filter_glob) 33862306a36Sopenharmony_ci if filter: 33962306a36Sopenharmony_ci args.append('kunit.filter="' + filter + '"') 34062306a36Sopenharmony_ci if filter_action: 34162306a36Sopenharmony_ci args.append('kunit.filter_action=' + filter_action) 34262306a36Sopenharmony_ci args.append('kunit.enable=1') 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci process = self._ops.start(args, build_dir) 34562306a36Sopenharmony_ci assert process.stdout is not None # tell mypy it's set 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci # Enforce the timeout in a background thread. 34862306a36Sopenharmony_ci def _wait_proc() -> None: 34962306a36Sopenharmony_ci try: 35062306a36Sopenharmony_ci process.wait(timeout=timeout) 35162306a36Sopenharmony_ci except Exception as e: 35262306a36Sopenharmony_ci print(e) 35362306a36Sopenharmony_ci process.terminate() 35462306a36Sopenharmony_ci process.wait() 35562306a36Sopenharmony_ci waiter = threading.Thread(target=_wait_proc) 35662306a36Sopenharmony_ci waiter.start() 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci output = open(get_outfile_path(build_dir), 'w') 35962306a36Sopenharmony_ci try: 36062306a36Sopenharmony_ci # Tee the output to the file and to our caller in real time. 36162306a36Sopenharmony_ci for line in process.stdout: 36262306a36Sopenharmony_ci output.write(line) 36362306a36Sopenharmony_ci yield line 36462306a36Sopenharmony_ci # This runs even if our caller doesn't consume every line. 36562306a36Sopenharmony_ci finally: 36662306a36Sopenharmony_ci # Flush any leftover output to the file 36762306a36Sopenharmony_ci output.write(process.stdout.read()) 36862306a36Sopenharmony_ci output.close() 36962306a36Sopenharmony_ci process.stdout.close() 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci waiter.join() 37262306a36Sopenharmony_ci subprocess.call(['stty', 'sane']) 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None: 37562306a36Sopenharmony_ci logging.error('Build interruption occurred. Cleaning console.') 37662306a36Sopenharmony_ci subprocess.call(['stty', 'sane']) 377