| # 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): |
| message = 'Provided Kconfig is not contained in validated .config!' |
| 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): |
| message = 'Provided Kconfig is not contained in final config!' |
| 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() |