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 <[email protected]> 76ebf5866SFelix Guo# Author: Brendan Higgins <[email protected]> 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 191da2e622SDaniel Latypovfrom types import FrameType 20021ed9f5SHeidi Fahim 216ebf5866SFelix Guoimport kunit_config 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' 28980ac3adSDaniel 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 601da2e622SDaniel Latypov def make_olddefconfig(self, build_dir: str, make_options: Optional[List[str]]) -> 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 741da2e622SDaniel Latypov def make(self, jobs: int, build_dir: str, make_options: Optional[List[str]]) -> None: 75*43ebec94SBrendan Jackman command = ['make', 'all', 'compile_commands.json', 'scripts_gdb', 76*43ebec94SBrendan Jackman '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 96e30f65c4SDaniel 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 1080a1111d4SThomas Weißschuh self._kernel_command_line = qemu_arch_params.kernel_command_line 1090a1111d4SThomas Weißschuh if 'kunit_shutdown=' not in self._kernel_command_line: 1100a1111d4SThomas Weißschuh self._kernel_command_line += ' kunit_shutdown=reboot' 11187c9c163SBrendan Higgins self._extra_qemu_params = qemu_arch_params.extra_qemu_params 1125ffb8629SGeert Uytterhoeven self._serial = qemu_arch_params.serial 11387c9c163SBrendan Higgins 1146fc3a863SDavid Gow def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 11598978490SDaniel Latypov kconfig = kunit_config.parse_from_string(self._kconfig) 1168a7c6f85SDaniel Latypov kconfig.merge_in_entries(base_kunitconfig) 1178a7c6f85SDaniel Latypov return kconfig 11887c9c163SBrendan Higgins 119e30f65c4SDaniel Latypov def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 12087c9c163SBrendan Higgins kernel_path = os.path.join(build_dir, self._kernel_path) 12187c9c163SBrendan Higgins qemu_command = ['qemu-system-' + self._qemu_arch, 12287c9c163SBrendan Higgins '-nodefaults', 12387c9c163SBrendan Higgins '-m', '1024', 12487c9c163SBrendan Higgins '-kernel', kernel_path, 1253f0a50f3SDaniel Latypov '-append', ' '.join(params + [self._kernel_command_line]), 12687c9c163SBrendan Higgins '-no-reboot', 12787c9c163SBrendan Higgins '-nographic', 128220374e7STamir Duberstein '-accel', 'kvm', 129220374e7STamir Duberstein '-accel', 'hvf', 130220374e7STamir Duberstein '-accel', 'tcg', 1315ffb8629SGeert Uytterhoeven '-serial', self._serial] + self._extra_qemu_params 1323f0a50f3SDaniel Latypov # Note: shlex.join() does what we want, but requires python 3.8+. 1333f0a50f3SDaniel Latypov print('Running tests with:\n$', ' '.join(shlex.quote(arg) for arg in qemu_command)) 1343f0a50f3SDaniel Latypov return subprocess.Popen(qemu_command, 13587c9c163SBrendan Higgins stdin=subprocess.PIPE, 1367d7c48dfSDaniel Latypov stdout=subprocess.PIPE, 13787c9c163SBrendan Higgins stderr=subprocess.STDOUT, 1383f0a50f3SDaniel Latypov text=True, errors='backslashreplace') 13987c9c163SBrendan Higgins 14087c9c163SBrendan Higginsclass LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): 14187c9c163SBrendan Higgins """An abstraction over command line operations performed on a source tree.""" 14287c9c163SBrendan Higgins 1431da2e622SDaniel Latypov def __init__(self, cross_compile: Optional[str]=None): 14487c9c163SBrendan Higgins super().__init__(linux_arch='um', cross_compile=cross_compile) 14587c9c163SBrendan Higgins 1466fc3a863SDavid Gow def make_arch_config(self, base_kunitconfig: kunit_config.Kconfig) -> kunit_config.Kconfig: 1476fc3a863SDavid Gow kconfig = kunit_config.parse_file(UML_KCONFIG_PATH) 1486fc3a863SDavid Gow kconfig.merge_in_entries(base_kunitconfig) 1496fc3a863SDavid Gow return kconfig 1506fc3a863SDavid Gow 151e30f65c4SDaniel Latypov def start(self, params: List[str], build_dir: str) -> subprocess.Popen: 1526ebf5866SFelix Guo """Runs the Linux UML binary. Must be named 'linux'.""" 153aa1c0555SDaniel Latypov linux_bin = os.path.join(build_dir, 'linux') 1549241bc81SDaniel Latypov params.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) 155ee8bd4a4SMickaël Salaün print('Running tests with:\n$', linux_bin, ' '.join(shlex.quote(arg) for arg in params)) 1567d7c48dfSDaniel Latypov return subprocess.Popen([linux_bin] + params, 15787c9c163SBrendan Higgins stdin=subprocess.PIPE, 1587d7c48dfSDaniel Latypov stdout=subprocess.PIPE, 15987c9c163SBrendan Higgins stderr=subprocess.STDOUT, 1602ab5d5e6SDaniel Latypov text=True, errors='backslashreplace') 1616ebf5866SFelix Guo 162aa1c0555SDaniel Latypovdef get_kconfig_path(build_dir: str) -> str: 163aa1c0555SDaniel Latypov return os.path.join(build_dir, KCONFIG_PATH) 1646ebf5866SFelix Guo 165aa1c0555SDaniel Latypovdef get_kunitconfig_path(build_dir: str) -> str: 166aa1c0555SDaniel Latypov return os.path.join(build_dir, KUNITCONFIG_PATH) 167fcdb0bc0SAndy Shevchenko 168aa1c0555SDaniel Latypovdef get_old_kunitconfig_path(build_dir: str) -> str: 169aa1c0555SDaniel Latypov return os.path.join(build_dir, OLD_KUNITCONFIG_PATH) 1704c2911f1SDaniel Latypov 17153b46621SDaniel Latypovdef get_parsed_kunitconfig(build_dir: str, 17253b46621SDaniel Latypov kunitconfig_paths: Optional[List[str]]=None) -> kunit_config.Kconfig: 17353b46621SDaniel Latypov if not kunitconfig_paths: 17453b46621SDaniel Latypov path = get_kunitconfig_path(build_dir) 17553b46621SDaniel Latypov if not os.path.exists(path): 17653b46621SDaniel Latypov shutil.copyfile(DEFAULT_KUNITCONFIG_PATH, path) 17753b46621SDaniel Latypov return kunit_config.parse_file(path) 17853b46621SDaniel Latypov 17953b46621SDaniel Latypov merged = kunit_config.Kconfig() 18053b46621SDaniel Latypov 18153b46621SDaniel Latypov for path in kunitconfig_paths: 18253b46621SDaniel Latypov if os.path.isdir(path): 18353b46621SDaniel Latypov path = os.path.join(path, KUNITCONFIG_PATH) 18453b46621SDaniel Latypov if not os.path.exists(path): 18553b46621SDaniel Latypov raise ConfigError(f'Specified kunitconfig ({path}) does not exist') 18653b46621SDaniel Latypov 18753b46621SDaniel Latypov partial = kunit_config.parse_file(path) 18853b46621SDaniel Latypov diff = merged.conflicting_options(partial) 18953b46621SDaniel Latypov if diff: 19053b46621SDaniel Latypov diff_str = '\n\n'.join(f'{a}\n vs from {path}\n{b}' for a, b in diff) 19153b46621SDaniel Latypov raise ConfigError(f'Multiple values specified for {len(diff)} options in kunitconfig:\n{diff_str}') 19253b46621SDaniel Latypov merged.merge_in_entries(partial) 19353b46621SDaniel Latypov return merged 19453b46621SDaniel Latypov 195aa1c0555SDaniel Latypovdef get_outfile_path(build_dir: str) -> str: 196aa1c0555SDaniel Latypov return os.path.join(build_dir, OUTFILE_PATH) 197128dc4bcSAndy Shevchenko 1988c278d97SDaniel Latypovdef _default_qemu_config_path(arch: str) -> str: 19987c9c163SBrendan Higgins config_path = os.path.join(QEMU_CONFIGS_DIR, arch + '.py') 2000453f984SDaniel Latypov if os.path.isfile(config_path): 2018c278d97SDaniel Latypov return config_path 202fe678fedSDaniel Latypov 203fe678fedSDaniel Latypov options = [f[:-3] for f in os.listdir(QEMU_CONFIGS_DIR) if f.endswith('.py')] 204fe678fedSDaniel Latypov raise ConfigError(arch + ' is not a valid arch, options are ' + str(sorted(options))) 20587c9c163SBrendan Higgins 2068c278d97SDaniel Latypovdef _get_qemu_ops(config_path: str, 207a9333bd3SDaniel Latypov extra_qemu_args: Optional[List[str]], 2088c278d97SDaniel Latypov cross_compile: Optional[str]) -> Tuple[str, LinuxSourceTreeOperations]: 20987c9c163SBrendan Higgins # The module name/path has very little to do with where the actual file 21087c9c163SBrendan Higgins # exists (I learned this through experimentation and could not find it 21187c9c163SBrendan Higgins # anywhere in the Python documentation). 21287c9c163SBrendan Higgins # 21387c9c163SBrendan Higgins # Bascially, we completely ignore the actual file location of the config 21487c9c163SBrendan Higgins # we are loading and just tell Python that the module lives in the 21587c9c163SBrendan Higgins # QEMU_CONFIGS_DIR for import purposes regardless of where it actually 21687c9c163SBrendan Higgins # exists as a file. 21787c9c163SBrendan Higgins module_path = '.' + os.path.join(os.path.basename(QEMU_CONFIGS_DIR), os.path.basename(config_path)) 21887c9c163SBrendan Higgins spec = importlib.util.spec_from_file_location(module_path, config_path) 21985310a62SDaniel Latypov assert spec is not None 22087c9c163SBrendan Higgins config = importlib.util.module_from_spec(spec) 22152a5d80aSDaniel Latypov # See https://github.com/python/typeshed/pull/2626 for context. 22252a5d80aSDaniel Latypov assert isinstance(spec.loader, importlib.abc.Loader) 22352a5d80aSDaniel Latypov spec.loader.exec_module(config) 22452a5d80aSDaniel Latypov 22552a5d80aSDaniel Latypov if not hasattr(config, 'QEMU_ARCH'): 22652a5d80aSDaniel Latypov raise ValueError('qemu_config module missing "QEMU_ARCH": ' + config_path) 2271da2e622SDaniel Latypov params: qemu_config.QemuArchParams = config.QEMU_ARCH 228a9333bd3SDaniel Latypov if extra_qemu_args: 229a9333bd3SDaniel Latypov params.extra_qemu_params.extend(extra_qemu_args) 23052a5d80aSDaniel Latypov return params.linux_arch, LinuxSourceTreeOperationsQemu( 23152a5d80aSDaniel Latypov params, cross_compile=cross_compile) 23287c9c163SBrendan Higgins 2330453f984SDaniel Latypovclass LinuxSourceTree: 2346ebf5866SFelix Guo """Represents a Linux kernel source tree with KUnit tests.""" 2356ebf5866SFelix Guo 23687c9c163SBrendan Higgins def __init__( 23787c9c163SBrendan Higgins self, 23887c9c163SBrendan Higgins build_dir: str, 23953b46621SDaniel Latypov kunitconfig_paths: Optional[List[str]]=None, 2409f57cc76SDaniel Latypov kconfig_add: Optional[List[str]]=None, 2411da2e622SDaniel Latypov arch: Optional[str]=None, 2421da2e622SDaniel Latypov cross_compile: Optional[str]=None, 2431da2e622SDaniel Latypov qemu_config_path: Optional[str]=None, 2441da2e622SDaniel Latypov extra_qemu_args: Optional[List[str]]=None) -> None: 245021ed9f5SHeidi Fahim signal.signal(signal.SIGINT, self.signal_handler) 24687c9c163SBrendan Higgins if qemu_config_path: 247a9333bd3SDaniel Latypov self._arch, self._ops = _get_qemu_ops(qemu_config_path, extra_qemu_args, cross_compile) 24887c9c163SBrendan Higgins else: 24987c9c163SBrendan Higgins self._arch = 'um' if arch is None else arch 2508c278d97SDaniel Latypov if self._arch == 'um': 2518c278d97SDaniel Latypov self._ops = LinuxSourceTreeOperationsUml(cross_compile=cross_compile) 2528c278d97SDaniel Latypov else: 2538c278d97SDaniel Latypov qemu_config_path = _default_qemu_config_path(self._arch) 254a9333bd3SDaniel Latypov _, self._ops = _get_qemu_ops(qemu_config_path, extra_qemu_args, cross_compile) 2552b8fdbbfSDaniel Latypov 25653b46621SDaniel Latypov self._kconfig = get_parsed_kunitconfig(build_dir, kunitconfig_paths) 2579f57cc76SDaniel Latypov if kconfig_add: 2589f57cc76SDaniel Latypov kconfig = kunit_config.parse_from_string('\n'.join(kconfig_add)) 2599f57cc76SDaniel Latypov self._kconfig.merge_in_entries(kconfig) 2609f57cc76SDaniel Latypov 261885210d3SDaniel Latypov def arch(self) -> str: 262885210d3SDaniel Latypov return self._arch 2632b8fdbbfSDaniel Latypov 26409641f7cSDaniel Latypov def clean(self) -> bool: 2656ebf5866SFelix Guo try: 2666ebf5866SFelix Guo self._ops.make_mrproper() 2676ebf5866SFelix Guo except ConfigError as e: 2686ebf5866SFelix Guo logging.error(e) 2696ebf5866SFelix Guo return False 2706ebf5866SFelix Guo return True 2716ebf5866SFelix Guo 272aa1c0555SDaniel Latypov def validate_config(self, build_dir: str) -> bool: 273dde54b94SHeidi Fahim kconfig_path = get_kconfig_path(build_dir) 27498978490SDaniel Latypov validated_kconfig = kunit_config.parse_file(kconfig_path) 275c44895b6SDaniel Latypov if self._kconfig.is_subset_of(validated_kconfig): 276c44895b6SDaniel Latypov return True 2778a7c6f85SDaniel Latypov missing = set(self._kconfig.as_entries()) - set(validated_kconfig.as_entries()) 278c44895b6SDaniel Latypov message = 'Not all Kconfig options selected in kunitconfig were in the generated .config.\n' \ 279c44895b6SDaniel Latypov 'This is probably due to unsatisfied dependencies.\n' \ 2808a7c6f85SDaniel Latypov 'Missing: ' + ', '.join(str(e) for e in missing) 281c44895b6SDaniel Latypov if self._arch == 'um': 282c44895b6SDaniel Latypov message += '\nNote: many Kconfig options aren\'t available on UML. You can try running ' \ 283c44895b6SDaniel Latypov 'on a different architecture with something like "--arch=x86_64".' 284dde54b94SHeidi Fahim logging.error(message) 285dde54b94SHeidi Fahim return False 286dde54b94SHeidi Fahim 2871da2e622SDaniel Latypov def build_config(self, build_dir: str, make_options: Optional[List[str]]) -> bool: 2886ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 2896ebf5866SFelix Guo if build_dir and not os.path.exists(build_dir): 2906ebf5866SFelix Guo os.mkdir(build_dir) 2916ebf5866SFelix Guo try: 2926fc3a863SDavid Gow self._kconfig = self._ops.make_arch_config(self._kconfig) 29387c9c163SBrendan Higgins self._kconfig.write_to_file(kconfig_path) 2940476e69fSGreg Thelen self._ops.make_olddefconfig(build_dir, make_options) 2956ebf5866SFelix Guo except ConfigError as e: 2966ebf5866SFelix Guo logging.error(e) 2976ebf5866SFelix Guo return False 2984c2911f1SDaniel Latypov if not self.validate_config(build_dir): 2994c2911f1SDaniel Latypov return False 3004c2911f1SDaniel Latypov 3014c2911f1SDaniel Latypov old_path = get_old_kunitconfig_path(build_dir) 3024c2911f1SDaniel Latypov if os.path.exists(old_path): 3034c2911f1SDaniel Latypov os.remove(old_path) # write_to_file appends to the file 3044c2911f1SDaniel Latypov self._kconfig.write_to_file(old_path) 3054c2911f1SDaniel Latypov return True 3064c2911f1SDaniel Latypov 3074c2911f1SDaniel Latypov def _kunitconfig_changed(self, build_dir: str) -> bool: 3084c2911f1SDaniel Latypov old_path = get_old_kunitconfig_path(build_dir) 3094c2911f1SDaniel Latypov if not os.path.exists(old_path): 3104c2911f1SDaniel Latypov return True 3114c2911f1SDaniel Latypov 3124c2911f1SDaniel Latypov old_kconfig = kunit_config.parse_file(old_path) 3138a7c6f85SDaniel Latypov return old_kconfig != self._kconfig 3146ebf5866SFelix Guo 3151da2e622SDaniel Latypov def build_reconfig(self, build_dir: str, make_options: Optional[List[str]]) -> bool: 31614ee5cfdSSeongJae Park """Creates a new .config if it is not a subset of the .kunitconfig.""" 3176ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 3184c2911f1SDaniel Latypov if not os.path.exists(kconfig_path): 3194c2911f1SDaniel Latypov print('Generating .config ...') 3204c2911f1SDaniel Latypov return self.build_config(build_dir, make_options) 3214c2911f1SDaniel Latypov 32298978490SDaniel Latypov existing_kconfig = kunit_config.parse_file(kconfig_path) 3236fc3a863SDavid Gow self._kconfig = self._ops.make_arch_config(self._kconfig) 3248a7c6f85SDaniel Latypov 3254c2911f1SDaniel Latypov if self._kconfig.is_subset_of(existing_kconfig) and not self._kunitconfig_changed(build_dir): 3264c2911f1SDaniel Latypov return True 3276ebf5866SFelix Guo print('Regenerating .config ...') 3286ebf5866SFelix Guo os.remove(kconfig_path) 3290476e69fSGreg Thelen return self.build_config(build_dir, make_options) 3306ebf5866SFelix Guo 3311da2e622SDaniel Latypov def build_kernel(self, jobs: int, build_dir: str, make_options: Optional[List[str]]) -> bool: 3326ebf5866SFelix Guo try: 3330476e69fSGreg Thelen self._ops.make_olddefconfig(build_dir, make_options) 3340476e69fSGreg Thelen self._ops.make(jobs, build_dir, make_options) 3356ebf5866SFelix Guo except (ConfigError, BuildError) as e: 3366ebf5866SFelix Guo logging.error(e) 3376ebf5866SFelix Guo return False 338dde54b94SHeidi Fahim return self.validate_config(build_dir) 3396ebf5866SFelix Guo 340723c8258SRae Moar 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]: 3417af29141SDaniel Latypov if not args: 3427af29141SDaniel Latypov args = [] 343d992880bSDaniel Latypov if filter_glob: 344d992880bSDaniel Latypov args.append('kunit.filter_glob=' + filter_glob) 345723c8258SRae Moar if filter: 346723c8258SRae Moar args.append('kunit.filter="' + filter + '"') 347723c8258SRae Moar if filter_action: 348723c8258SRae Moar args.append('kunit.filter_action=' + filter_action) 349d20a6ba5SJoe Fradley args.append('kunit.enable=1') 3507d7c48dfSDaniel Latypov 3517d7c48dfSDaniel Latypov process = self._ops.start(args, build_dir) 3527d7c48dfSDaniel Latypov assert process.stdout is not None # tell mypy it's set 3537d7c48dfSDaniel Latypov 3547d7c48dfSDaniel Latypov # Enforce the timeout in a background thread. 3551da2e622SDaniel Latypov def _wait_proc() -> None: 3567d7c48dfSDaniel Latypov try: 3577d7c48dfSDaniel Latypov process.wait(timeout=timeout) 3587d7c48dfSDaniel Latypov except Exception as e: 3597d7c48dfSDaniel Latypov print(e) 3607d7c48dfSDaniel Latypov process.terminate() 3617d7c48dfSDaniel Latypov process.wait() 3627d7c48dfSDaniel Latypov waiter = threading.Thread(target=_wait_proc) 3637d7c48dfSDaniel Latypov waiter.start() 3647d7c48dfSDaniel Latypov 3657d7c48dfSDaniel Latypov output = open(get_outfile_path(build_dir), 'w') 3667d7c48dfSDaniel Latypov try: 3677d7c48dfSDaniel Latypov # Tee the output to the file and to our caller in real time. 3687d7c48dfSDaniel Latypov for line in process.stdout: 3697d7c48dfSDaniel Latypov output.write(line) 370021ed9f5SHeidi Fahim yield line 3717d7c48dfSDaniel Latypov # This runs even if our caller doesn't consume every line. 3727d7c48dfSDaniel Latypov finally: 3737d7c48dfSDaniel Latypov # Flush any leftover output to the file 3747d7c48dfSDaniel Latypov output.write(process.stdout.read()) 3757d7c48dfSDaniel Latypov output.close() 3767d7c48dfSDaniel Latypov process.stdout.close() 3777d7c48dfSDaniel Latypov 3787d7c48dfSDaniel Latypov waiter.join() 3797d7c48dfSDaniel Latypov subprocess.call(['stty', 'sane']) 380021ed9f5SHeidi Fahim 3811da2e622SDaniel Latypov def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None: 382021ed9f5SHeidi Fahim logging.error('Build interruption occurred. Cleaning console.') 383021ed9f5SHeidi Fahim subprocess.call(['stty', 'sane']) 384