| # SPDX-License-Identifier: GPL-2.0 | |
| import logging | |
| import subprocess | |
| import os | |
| import kunit_config | |
| import kunit_parser | |
| KCONFIG_PATH = '.config' | |
| from collections import namedtuple | |
| ConfigResult = namedtuple('ConfigResult', ['status','info']) | |
| BuildResult = namedtuple('BuildResult', ['status','info']) | |
| class ConfigStatus(object): | |
| SUCCESS = 'SUCCESS' | |
| FAILURE = 'FAILURE' | |
| class BuildStatus(object): | |
| SUCCESS = 'SUCCESS' | |
| FAILURE = 'FAILURE' | |
| class ConfigError(Exception): | |
| """Represents an error trying to configure the Linux kernel.""" | |
| class BuildError(Exception): | |
| """Represents an error trying to build the Linux kernel.""" | |
| class LinuxSourceTreeOperations(object): | |
| """An abstraction over command line operations performed on a source tree.""" | |
| def make_mrproper(self): | |
| try: | |
| subprocess.check_output(['make', 'mrproper']) | |
| except OSError as e: | |
| raise ConfigError('Could not call make command: ' + e) | |
| except subprocess.CalledProcessError as e: | |
| raise ConfigError(e.output) | |
| def make_olddefconfig(self): | |
| try: | |
| subprocess.check_output(['make', 'ARCH=um', 'olddefconfig']) | |
| except OSError as e: | |
| raise ConfigError('Could not call make command: ' + e) | |
| except subprocess.CalledProcessError as e: | |
| raise ConfigError(e.output) | |
| def make(self): | |
| try: | |
| subprocess.check_output(['make', 'ARCH=um']) | |
| except OSError as e: | |
| raise BuildError('Could not call execute make: ' + e) | |
| except subprocess.CalledProcessError as e: | |
| raise BuildError(e.output) | |
| def linux_bin(self, params, timeout): | |
| """Runs the Linux UML binary. Must be named 'linux'.""" | |
| process = subprocess.Popen( | |
| ['./linux'] + params, | |
| stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE) | |
| timed_out = False | |
| try: | |
| process.wait(timeout=timeout) | |
| except subprocess.TimeoutExpired: | |
| process.terminate() | |
| timed_out = True | |
| output, _ = process.communicate() | |
| output = output.decode('ascii') | |
| if timed_out: | |
| output += kunit_parser.TIMED_OUT_LOG_ENTRY + '\n' | |
| return output | |
| class LinuxSourceTree(object): | |
| """Represents a Linux kernel source tree with KUnit tests.""" | |
| def __init__(self, | |
| kconfig_provider=kunit_config.KunitConfigProvider(), | |
| linux_build_operations=LinuxSourceTreeOperations()): | |
| self._kconfig = kconfig_provider.get_kconfig() | |
| self._ops = linux_build_operations | |
| def clean(self): | |
| try: | |
| self._ops.make_mrproper() | |
| except ConfigError as e: | |
| logging.error(e) | |
| return False | |
| return True | |
| def build_config(self): | |
| self._kconfig.write_to_file(KCONFIG_PATH) | |
| try: | |
| self._ops.make_olddefconfig() | |
| except ConfigError as e: | |
| logging.error(e) | |
| return ConfigResult(ConfigStatus.FAILURE, e.message) | |
| validated_kconfig = kunit_config.Kconfig() | |
| validated_kconfig.read_from_file(KCONFIG_PATH) | |
| if not self._kconfig.is_subset_of(validated_kconfig): | |
| missing = self._kconfig.entries() - validated_kconfig.entries() | |
| message = 'Provided Kconfig contains fields not in validated .config: %s' % ( | |
| ', '.join([str(e) for e in missing]), | |
| ) | |
| logging.error(message) | |
| return ConfigResult(ConfigStatus.FAILURE, message) | |
| return ConfigResult(ConfigStatus.SUCCESS, 'Build config!') | |
| def build_reconfig(self): | |
| """Creates a new .config if it is not a subset of the kunitconfig.""" | |
| if os.path.exists(KCONFIG_PATH): | |
| existing_kconfig = kunit_config.Kconfig() | |
| existing_kconfig.read_from_file(KCONFIG_PATH) | |
| if not self._kconfig.is_subset_of(existing_kconfig): | |
| print('Regenerating .config ...') | |
| os.remove(KCONFIG_PATH) | |
| return self.build_config() | |
| else: | |
| return ConfigResult(ConfigStatus.SUCCESS, 'Already built.') | |
| else: | |
| print('Generating .config ...') | |
| return self.build_config() | |
| def build_um_kernel(self): | |
| try: | |
| self._ops.make_olddefconfig() | |
| self._ops.make() | |
| except (ConfigError, BuildError) as e: | |
| logging.error(e) | |
| return BuildResult(BuildStatus.FAILURE, e.message) | |
| used_kconfig = kunit_config.Kconfig() | |
| used_kconfig.read_from_file(KCONFIG_PATH) | |
| if not self._kconfig.is_subset_of(used_kconfig): | |
| missing = self._kconfig.entries() - validated_kconfig.entries() | |
| message = 'Provided Kconfig contains fields not in final config: %s' % ( | |
| ', '.join([str(e) for e in missing]), | |
| ) | |
| logging.error(message) | |
| return BuildResult(BuildStatus.FAILURE, message) | |
| return BuildResult(BuildStatus.SUCCESS, 'Built kernel!') | |
| def run_kernel(self, args=[], timeout=None): | |
| args.extend(['mem=256M']) | |
| raw_log = self._ops.linux_bin(args, timeout) | |
| with open('test.log', 'w') as f: | |
| for line in raw_log.split('\n'): | |
| f.write(line.rstrip() + '\n') | |
| yield line.rstrip() |