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 14fcdb0bc0SAndy Shevchenkoimport shutil 15021ed9f5SHeidi Fahimimport signal 167d7c48dfSDaniel Latypovimport threading 177d7c48dfSDaniel Latypovfrom typing import Iterator, List, Optional, Tuple 18021ed9f5SHeidi Fahim 196ebf5866SFelix Guoimport kunit_config 20021ed9f5SHeidi Fahimimport kunit_parser 2187c9c163SBrendan Higginsimport qemu_config 226ebf5866SFelix Guo 236ebf5866SFelix GuoKCONFIG_PATH = '.config' 24fcdb0bc0SAndy ShevchenkoKUNITCONFIG_PATH = '.kunitconfig' 254c2911f1SDaniel LatypovOLD_KUNITCONFIG_PATH = 'last_used_kunitconfig' 26d9d6b822SDavid GowDEFAULT_KUNITCONFIG_PATH = 'tools/testing/kunit/configs/default.config' 27021ed9f5SHeidi FahimBROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config' 28128dc4bcSAndy ShevchenkoOUTFILE_PATH = 'test.log' 2987c9c163SBrendan HigginsABS_TOOL_PATH = os.path.abspath(os.path.dirname(__file__)) 3087c9c163SBrendan HigginsQEMU_CONFIGS_DIR = os.path.join(ABS_TOOL_PATH, 'qemu_configs') 316ebf5866SFelix Guo 326ebf5866SFelix Guoclass ConfigError(Exception): 336ebf5866SFelix Guo """Represents an error trying to configure the Linux kernel.""" 346ebf5866SFelix Guo 356ebf5866SFelix Guo 366ebf5866SFelix Guoclass BuildError(Exception): 376ebf5866SFelix Guo """Represents an error trying to build the Linux kernel.""" 386ebf5866SFelix Guo 396ebf5866SFelix Guo 406ebf5866SFelix Guoclass LinuxSourceTreeOperations(object): 416ebf5866SFelix Guo """An abstraction over command line operations performed on a source tree.""" 426ebf5866SFelix Guo 4387c9c163SBrendan Higgins def __init__(self, linux_arch: str, cross_compile: Optional[str]): 4487c9c163SBrendan Higgins self._linux_arch = linux_arch 4587c9c163SBrendan Higgins self._cross_compile = cross_compile 4687c9c163SBrendan Higgins 4709641f7cSDaniel Latypov def make_mrproper(self) -> None: 486ebf5866SFelix Guo try: 495a9fcad7SWill Chen subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT) 506ebf5866SFelix Guo except OSError as e: 511abdd39fSDaniel Latypov raise ConfigError('Could not call make command: ' + str(e)) 526ebf5866SFelix Guo except subprocess.CalledProcessError as e: 531abdd39fSDaniel Latypov raise ConfigError(e.output.decode()) 546ebf5866SFelix Guo 5587c9c163SBrendan Higgins def make_arch_qemuconfig(self, kconfig: kunit_config.Kconfig) -> None: 5687c9c163SBrendan Higgins pass 5787c9c163SBrendan Higgins 58aa1c0555SDaniel Latypov def make_allyesconfig(self, build_dir: str, make_options) -> None: 5987c9c163SBrendan Higgins raise ConfigError('Only the "um" arch is supported for alltests') 6087c9c163SBrendan Higgins 61aa1c0555SDaniel Latypov def make_olddefconfig(self, build_dir: str, make_options) -> None: 62aa1c0555SDaniel Latypov command = ['make', 'ARCH=' + self._linux_arch, 'O=' + build_dir, 'olddefconfig'] 6387c9c163SBrendan Higgins if self._cross_compile: 6487c9c163SBrendan Higgins command += ['CROSS_COMPILE=' + self._cross_compile] 650476e69fSGreg Thelen if make_options: 660476e69fSGreg Thelen command.extend(make_options) 6787c9c163SBrendan Higgins print('Populating config with:\n$', ' '.join(command)) 686ebf5866SFelix Guo try: 695a9fcad7SWill Chen subprocess.check_output(command, stderr=subprocess.STDOUT) 706ebf5866SFelix Guo except OSError as e: 711abdd39fSDaniel Latypov raise ConfigError('Could not call make command: ' + str(e)) 726ebf5866SFelix Guo except subprocess.CalledProcessError as e: 731abdd39fSDaniel Latypov raise ConfigError(e.output.decode()) 746ebf5866SFelix Guo 75aa1c0555SDaniel Latypov def make(self, jobs, build_dir: str, make_options) -> None: 76aa1c0555SDaniel Latypov command = ['make', 'ARCH=' + self._linux_arch, 'O=' + build_dir, '--jobs=' + str(jobs)] 7787c9c163SBrendan Higgins if make_options: 7887c9c163SBrendan Higgins command.extend(make_options) 7987c9c163SBrendan Higgins if self._cross_compile: 8087c9c163SBrendan Higgins command += ['CROSS_COMPILE=' + self._cross_compile] 8187c9c163SBrendan Higgins print('Building with:\n$', ' '.join(command)) 8287c9c163SBrendan Higgins try: 8387c9c163SBrendan Higgins proc = subprocess.Popen(command, 8487c9c163SBrendan Higgins stderr=subprocess.PIPE, 8587c9c163SBrendan Higgins stdout=subprocess.DEVNULL) 8687c9c163SBrendan Higgins except OSError as e: 8787c9c163SBrendan Higgins raise BuildError('Could not call execute make: ' + str(e)) 8887c9c163SBrendan Higgins except subprocess.CalledProcessError as e: 8987c9c163SBrendan Higgins raise BuildError(e.output) 9087c9c163SBrendan Higgins _, stderr = proc.communicate() 9187c9c163SBrendan Higgins if proc.returncode != 0: 9287c9c163SBrendan Higgins raise BuildError(stderr.decode()) 9387c9c163SBrendan Higgins if stderr: # likely only due to build warnings 9487c9c163SBrendan Higgins print(stderr.decode()) 9587c9c163SBrendan Higgins 967d7c48dfSDaniel Latypov def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 977d7c48dfSDaniel Latypov raise RuntimeError('not implemented!') 9887c9c163SBrendan Higgins 9987c9c163SBrendan Higgins 10087c9c163SBrendan Higginsclass LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): 10187c9c163SBrendan Higgins 10287c9c163SBrendan Higgins def __init__(self, qemu_arch_params: qemu_config.QemuArchParams, cross_compile: Optional[str]): 10387c9c163SBrendan Higgins super().__init__(linux_arch=qemu_arch_params.linux_arch, 10487c9c163SBrendan Higgins cross_compile=cross_compile) 10587c9c163SBrendan Higgins self._kconfig = qemu_arch_params.kconfig 10687c9c163SBrendan Higgins self._qemu_arch = qemu_arch_params.qemu_arch 10787c9c163SBrendan Higgins self._kernel_path = qemu_arch_params.kernel_path 10887c9c163SBrendan Higgins self._kernel_command_line = qemu_arch_params.kernel_command_line + ' kunit_shutdown=reboot' 10987c9c163SBrendan Higgins self._extra_qemu_params = qemu_arch_params.extra_qemu_params 11087c9c163SBrendan Higgins 11187c9c163SBrendan Higgins def make_arch_qemuconfig(self, base_kunitconfig: kunit_config.Kconfig) -> None: 11298978490SDaniel Latypov kconfig = kunit_config.parse_from_string(self._kconfig) 11387c9c163SBrendan Higgins base_kunitconfig.merge_in_entries(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, 12187c9c163SBrendan Higgins '-append', '\'' + ' '.join(params + [self._kernel_command_line]) + '\'', 12287c9c163SBrendan Higgins '-no-reboot', 12387c9c163SBrendan Higgins '-nographic', 12487c9c163SBrendan Higgins '-serial stdio'] + self._extra_qemu_params 12587c9c163SBrendan Higgins print('Running tests with:\n$', ' '.join(qemu_command)) 1267d7c48dfSDaniel Latypov return subprocess.Popen(' '.join(qemu_command), 12787c9c163SBrendan Higgins stdin=subprocess.PIPE, 1287d7c48dfSDaniel Latypov stdout=subprocess.PIPE, 12987c9c163SBrendan Higgins stderr=subprocess.STDOUT, 1302ab5d5e6SDaniel Latypov text=True, shell=True, errors='backslashreplace') 13187c9c163SBrendan Higgins 13287c9c163SBrendan Higginsclass LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): 13387c9c163SBrendan Higgins """An abstraction over command line operations performed on a source tree.""" 13487c9c163SBrendan Higgins 13587c9c163SBrendan Higgins def __init__(self, cross_compile=None): 13687c9c163SBrendan Higgins super().__init__(linux_arch='um', cross_compile=cross_compile) 13787c9c163SBrendan Higgins 138aa1c0555SDaniel Latypov def make_allyesconfig(self, build_dir: str, make_options) -> None: 139021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 140021ed9f5SHeidi Fahim 'Enabling all CONFIGs for UML...') 141aa1c0555SDaniel Latypov command = ['make', 'ARCH=um', 'O=' + build_dir, 'allyesconfig'] 14267e2fae3SBrendan Higgins if make_options: 14367e2fae3SBrendan Higgins command.extend(make_options) 144021ed9f5SHeidi Fahim process = subprocess.Popen( 14567e2fae3SBrendan Higgins command, 146021ed9f5SHeidi Fahim stdout=subprocess.DEVNULL, 147021ed9f5SHeidi Fahim stderr=subprocess.STDOUT) 148021ed9f5SHeidi Fahim process.wait() 149021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 150021ed9f5SHeidi Fahim 'Disabling broken configs to run KUnit tests...') 151a54ea2e0SDaniel Latypov 152a54ea2e0SDaniel Latypov with open(get_kconfig_path(build_dir), 'a') as config: 153a54ea2e0SDaniel Latypov with open(BROKEN_ALLCONFIG_PATH, 'r') as disable: 154a54ea2e0SDaniel Latypov config.write(disable.read()) 155021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 156021ed9f5SHeidi Fahim 'Starting Kernel with all configs takes a few minutes...') 157021ed9f5SHeidi Fahim 1587d7c48dfSDaniel Latypov def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 1596ebf5866SFelix Guo """Runs the Linux UML binary. Must be named 'linux'.""" 160aa1c0555SDaniel Latypov linux_bin = os.path.join(build_dir, 'linux') 1617d7c48dfSDaniel Latypov return subprocess.Popen([linux_bin] + params, 16287c9c163SBrendan Higgins stdin=subprocess.PIPE, 1637d7c48dfSDaniel Latypov stdout=subprocess.PIPE, 16487c9c163SBrendan Higgins stderr=subprocess.STDOUT, 1652ab5d5e6SDaniel Latypov text=True, errors='backslashreplace') 1666ebf5866SFelix Guo 167aa1c0555SDaniel Latypovdef get_kconfig_path(build_dir: str) -> str: 168aa1c0555SDaniel Latypov return os.path.join(build_dir, KCONFIG_PATH) 1696ebf5866SFelix Guo 170aa1c0555SDaniel Latypovdef get_kunitconfig_path(build_dir: str) -> str: 171aa1c0555SDaniel Latypov return os.path.join(build_dir, KUNITCONFIG_PATH) 172fcdb0bc0SAndy Shevchenko 173aa1c0555SDaniel Latypovdef get_old_kunitconfig_path(build_dir: str) -> str: 174aa1c0555SDaniel Latypov return os.path.join(build_dir, OLD_KUNITCONFIG_PATH) 1754c2911f1SDaniel Latypov 176aa1c0555SDaniel Latypovdef get_outfile_path(build_dir: str) -> str: 177aa1c0555SDaniel Latypov return os.path.join(build_dir, OUTFILE_PATH) 178128dc4bcSAndy Shevchenko 17987c9c163SBrendan Higginsdef get_source_tree_ops(arch: str, cross_compile: Optional[str]) -> LinuxSourceTreeOperations: 18087c9c163SBrendan Higgins config_path = os.path.join(QEMU_CONFIGS_DIR, arch + '.py') 18187c9c163SBrendan Higgins if arch == 'um': 18287c9c163SBrendan Higgins return LinuxSourceTreeOperationsUml(cross_compile=cross_compile) 18387c9c163SBrendan Higgins elif os.path.isfile(config_path): 18487c9c163SBrendan Higgins return get_source_tree_ops_from_qemu_config(config_path, cross_compile)[1] 185fe678fedSDaniel Latypov 186fe678fedSDaniel Latypov options = [f[:-3] for f in os.listdir(QEMU_CONFIGS_DIR) if f.endswith('.py')] 187fe678fedSDaniel Latypov raise ConfigError(arch + ' is not a valid arch, options are ' + str(sorted(options))) 18887c9c163SBrendan Higgins 18987c9c163SBrendan Higginsdef get_source_tree_ops_from_qemu_config(config_path: str, 19058c965d8SDaniel Latypov cross_compile: Optional[str]) -> Tuple[ 19187c9c163SBrendan Higgins str, LinuxSourceTreeOperations]: 19287c9c163SBrendan Higgins # The module name/path has very little to do with where the actual file 19387c9c163SBrendan Higgins # exists (I learned this through experimentation and could not find it 19487c9c163SBrendan Higgins # anywhere in the Python documentation). 19587c9c163SBrendan Higgins # 19687c9c163SBrendan Higgins # Bascially, we completely ignore the actual file location of the config 19787c9c163SBrendan Higgins # we are loading and just tell Python that the module lives in the 19887c9c163SBrendan Higgins # QEMU_CONFIGS_DIR for import purposes regardless of where it actually 19987c9c163SBrendan Higgins # exists as a file. 20087c9c163SBrendan Higgins module_path = '.' + os.path.join(os.path.basename(QEMU_CONFIGS_DIR), os.path.basename(config_path)) 20187c9c163SBrendan Higgins spec = importlib.util.spec_from_file_location(module_path, config_path) 20285310a62SDaniel Latypov assert spec is not None 20387c9c163SBrendan Higgins config = importlib.util.module_from_spec(spec) 20452a5d80aSDaniel Latypov # See https://github.com/python/typeshed/pull/2626 for context. 20552a5d80aSDaniel Latypov assert isinstance(spec.loader, importlib.abc.Loader) 20652a5d80aSDaniel Latypov spec.loader.exec_module(config) 20752a5d80aSDaniel Latypov 20852a5d80aSDaniel Latypov if not hasattr(config, 'QEMU_ARCH'): 20952a5d80aSDaniel Latypov raise ValueError('qemu_config module missing "QEMU_ARCH": ' + config_path) 21052a5d80aSDaniel Latypov params: qemu_config.QemuArchParams = config.QEMU_ARCH # type: ignore 21152a5d80aSDaniel Latypov return params.linux_arch, LinuxSourceTreeOperationsQemu( 21252a5d80aSDaniel Latypov params, cross_compile=cross_compile) 21387c9c163SBrendan Higgins 2146ebf5866SFelix Guoclass LinuxSourceTree(object): 2156ebf5866SFelix Guo """Represents a Linux kernel source tree with KUnit tests.""" 2166ebf5866SFelix Guo 21787c9c163SBrendan Higgins def __init__( 21887c9c163SBrendan Higgins self, 21987c9c163SBrendan Higgins build_dir: str, 22087c9c163SBrendan Higgins load_config=True, 22187c9c163SBrendan Higgins kunitconfig_path='', 2229f57cc76SDaniel Latypov kconfig_add: Optional[List[str]]=None, 22387c9c163SBrendan Higgins arch=None, 22487c9c163SBrendan Higgins cross_compile=None, 22587c9c163SBrendan Higgins qemu_config_path=None) -> None: 226021ed9f5SHeidi Fahim signal.signal(signal.SIGINT, self.signal_handler) 22787c9c163SBrendan Higgins if qemu_config_path: 22887c9c163SBrendan Higgins self._arch, self._ops = get_source_tree_ops_from_qemu_config( 22987c9c163SBrendan Higgins qemu_config_path, cross_compile) 23087c9c163SBrendan Higgins else: 23187c9c163SBrendan Higgins self._arch = 'um' if arch is None else arch 23287c9c163SBrendan Higgins self._ops = get_source_tree_ops(self._arch, cross_compile) 2332b8fdbbfSDaniel Latypov 2342b8fdbbfSDaniel Latypov if not load_config: 2352b8fdbbfSDaniel Latypov return 2362b8fdbbfSDaniel Latypov 237243180f5SDaniel Latypov if kunitconfig_path: 2389854781dSDaniel Latypov if os.path.isdir(kunitconfig_path): 2399854781dSDaniel Latypov kunitconfig_path = os.path.join(kunitconfig_path, KUNITCONFIG_PATH) 240243180f5SDaniel Latypov if not os.path.exists(kunitconfig_path): 241243180f5SDaniel Latypov raise ConfigError(f'Specified kunitconfig ({kunitconfig_path}) does not exist') 242243180f5SDaniel Latypov else: 2432b8fdbbfSDaniel Latypov kunitconfig_path = get_kunitconfig_path(build_dir) 2442b8fdbbfSDaniel Latypov if not os.path.exists(kunitconfig_path): 245243180f5SDaniel Latypov shutil.copyfile(DEFAULT_KUNITCONFIG_PATH, kunitconfig_path) 2462b8fdbbfSDaniel Latypov 24798978490SDaniel Latypov self._kconfig = kunit_config.parse_file(kunitconfig_path) 2489f57cc76SDaniel Latypov if kconfig_add: 2499f57cc76SDaniel Latypov kconfig = kunit_config.parse_from_string('\n'.join(kconfig_add)) 2509f57cc76SDaniel Latypov self._kconfig.merge_in_entries(kconfig) 2519f57cc76SDaniel Latypov 252*885210d3SDaniel Latypov def arch(self) -> str: 253*885210d3SDaniel Latypov return self._arch 2542b8fdbbfSDaniel Latypov 25509641f7cSDaniel Latypov def clean(self) -> bool: 2566ebf5866SFelix Guo try: 2576ebf5866SFelix Guo self._ops.make_mrproper() 2586ebf5866SFelix Guo except ConfigError as e: 2596ebf5866SFelix Guo logging.error(e) 2606ebf5866SFelix Guo return False 2616ebf5866SFelix Guo return True 2626ebf5866SFelix Guo 263aa1c0555SDaniel Latypov def validate_config(self, build_dir: str) -> bool: 264dde54b94SHeidi Fahim kconfig_path = get_kconfig_path(build_dir) 26598978490SDaniel Latypov validated_kconfig = kunit_config.parse_file(kconfig_path) 266c44895b6SDaniel Latypov if self._kconfig.is_subset_of(validated_kconfig): 267c44895b6SDaniel Latypov return True 268dde54b94SHeidi Fahim invalid = self._kconfig.entries() - validated_kconfig.entries() 269c44895b6SDaniel Latypov message = 'Not all Kconfig options selected in kunitconfig were in the generated .config.\n' \ 270c44895b6SDaniel Latypov 'This is probably due to unsatisfied dependencies.\n' \ 271c44895b6SDaniel Latypov 'Missing: ' + ', '.join([str(e) for e in invalid]) 272c44895b6SDaniel Latypov if self._arch == 'um': 273c44895b6SDaniel Latypov message += '\nNote: many Kconfig options aren\'t available on UML. You can try running ' \ 274c44895b6SDaniel Latypov 'on a different architecture with something like "--arch=x86_64".' 275dde54b94SHeidi Fahim logging.error(message) 276dde54b94SHeidi Fahim return False 277dde54b94SHeidi Fahim 278aa1c0555SDaniel Latypov def build_config(self, build_dir: str, make_options) -> bool: 2796ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 2806ebf5866SFelix Guo if build_dir and not os.path.exists(build_dir): 2816ebf5866SFelix Guo os.mkdir(build_dir) 2826ebf5866SFelix Guo try: 28387c9c163SBrendan Higgins self._ops.make_arch_qemuconfig(self._kconfig) 28487c9c163SBrendan Higgins self._kconfig.write_to_file(kconfig_path) 2850476e69fSGreg Thelen self._ops.make_olddefconfig(build_dir, make_options) 2866ebf5866SFelix Guo except ConfigError as e: 2876ebf5866SFelix Guo logging.error(e) 2886ebf5866SFelix Guo return False 2894c2911f1SDaniel Latypov if not self.validate_config(build_dir): 2904c2911f1SDaniel Latypov return False 2914c2911f1SDaniel Latypov 2924c2911f1SDaniel Latypov old_path = get_old_kunitconfig_path(build_dir) 2934c2911f1SDaniel Latypov if os.path.exists(old_path): 2944c2911f1SDaniel Latypov os.remove(old_path) # write_to_file appends to the file 2954c2911f1SDaniel Latypov self._kconfig.write_to_file(old_path) 2964c2911f1SDaniel Latypov return True 2974c2911f1SDaniel Latypov 2984c2911f1SDaniel Latypov def _kunitconfig_changed(self, build_dir: str) -> bool: 2994c2911f1SDaniel Latypov old_path = get_old_kunitconfig_path(build_dir) 3004c2911f1SDaniel Latypov if not os.path.exists(old_path): 3014c2911f1SDaniel Latypov return True 3024c2911f1SDaniel Latypov 3034c2911f1SDaniel Latypov old_kconfig = kunit_config.parse_file(old_path) 3044c2911f1SDaniel Latypov return old_kconfig.entries() != self._kconfig.entries() 3056ebf5866SFelix Guo 306aa1c0555SDaniel Latypov def build_reconfig(self, build_dir: str, make_options) -> bool: 30714ee5cfdSSeongJae Park """Creates a new .config if it is not a subset of the .kunitconfig.""" 3086ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 3094c2911f1SDaniel Latypov if not os.path.exists(kconfig_path): 3104c2911f1SDaniel Latypov print('Generating .config ...') 3114c2911f1SDaniel Latypov return self.build_config(build_dir, make_options) 3124c2911f1SDaniel Latypov 31398978490SDaniel Latypov existing_kconfig = kunit_config.parse_file(kconfig_path) 31487c9c163SBrendan Higgins self._ops.make_arch_qemuconfig(self._kconfig) 3154c2911f1SDaniel Latypov if self._kconfig.is_subset_of(existing_kconfig) and not self._kunitconfig_changed(build_dir): 3164c2911f1SDaniel Latypov return True 3176ebf5866SFelix Guo print('Regenerating .config ...') 3186ebf5866SFelix Guo os.remove(kconfig_path) 3190476e69fSGreg Thelen return self.build_config(build_dir, make_options) 3206ebf5866SFelix Guo 321aa1c0555SDaniel Latypov def build_kernel(self, alltests, jobs, build_dir: str, make_options) -> bool: 3226ebf5866SFelix Guo try: 32367e2fae3SBrendan Higgins if alltests: 32467e2fae3SBrendan Higgins self._ops.make_allyesconfig(build_dir, make_options) 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 = [] 335b6d5799bSDavid Gow args.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) 336d992880bSDaniel Latypov if filter_glob: 337d992880bSDaniel Latypov args.append('kunit.filter_glob='+filter_glob) 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 36909641f7cSDaniel Latypov def signal_handler(self, sig, frame) -> None: 370021ed9f5SHeidi Fahim logging.error('Build interruption occurred. Cleaning console.') 371021ed9f5SHeidi Fahim subprocess.call(['stty', 'sane']) 372