b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | # SPDX-License-Identifier: GPL-2.0 |
| 2 | # |
| 3 | # Runs UML kernel, collects output, and handles errors. |
| 4 | # |
| 5 | # Copyright (C) 2019, Google LLC. |
| 6 | # Author: Felix Guo <felixguoxiuping@gmail.com> |
| 7 | # Author: Brendan Higgins <brendanhiggins@google.com> |
| 8 | |
| 9 | |
| 10 | import logging |
| 11 | import subprocess |
| 12 | import os |
| 13 | |
| 14 | import kunit_config |
| 15 | |
| 16 | KCONFIG_PATH = '.config' |
| 17 | KUNITCONFIG_PATH = 'kunitconfig' |
| 18 | |
| 19 | class ConfigError(Exception): |
| 20 | """Represents an error trying to configure the Linux kernel.""" |
| 21 | |
| 22 | |
| 23 | class BuildError(Exception): |
| 24 | """Represents an error trying to build the Linux kernel.""" |
| 25 | |
| 26 | |
| 27 | class LinuxSourceTreeOperations(object): |
| 28 | """An abstraction over command line operations performed on a source tree.""" |
| 29 | |
| 30 | def make_mrproper(self): |
| 31 | try: |
| 32 | subprocess.check_output(['make', 'mrproper']) |
| 33 | except OSError as e: |
| 34 | raise ConfigError('Could not call make command: ' + e) |
| 35 | except subprocess.CalledProcessError as e: |
| 36 | raise ConfigError(e.output) |
| 37 | |
| 38 | def make_olddefconfig(self, build_dir): |
| 39 | command = ['make', 'ARCH=um', 'olddefconfig'] |
| 40 | if build_dir: |
| 41 | command += ['O=' + build_dir] |
| 42 | try: |
| 43 | subprocess.check_output(command) |
| 44 | except OSError as e: |
| 45 | raise ConfigError('Could not call make command: ' + e) |
| 46 | except subprocess.CalledProcessError as e: |
| 47 | raise ConfigError(e.output) |
| 48 | |
| 49 | def make(self, jobs, build_dir): |
| 50 | command = ['make', 'ARCH=um', '--jobs=' + str(jobs)] |
| 51 | if build_dir: |
| 52 | command += ['O=' + build_dir] |
| 53 | try: |
| 54 | subprocess.check_output(command) |
| 55 | except OSError as e: |
| 56 | raise BuildError('Could not call execute make: ' + e) |
| 57 | except subprocess.CalledProcessError as e: |
| 58 | raise BuildError(e.output) |
| 59 | |
| 60 | def linux_bin(self, params, timeout, build_dir): |
| 61 | """Runs the Linux UML binary. Must be named 'linux'.""" |
| 62 | linux_bin = './linux' |
| 63 | if build_dir: |
| 64 | linux_bin = os.path.join(build_dir, 'linux') |
| 65 | process = subprocess.Popen( |
| 66 | [linux_bin] + params, |
| 67 | stdin=subprocess.PIPE, |
| 68 | stdout=subprocess.PIPE, |
| 69 | stderr=subprocess.PIPE) |
| 70 | process.wait(timeout=timeout) |
| 71 | return process |
| 72 | |
| 73 | |
| 74 | def get_kconfig_path(build_dir): |
| 75 | kconfig_path = KCONFIG_PATH |
| 76 | if build_dir: |
| 77 | kconfig_path = os.path.join(build_dir, KCONFIG_PATH) |
| 78 | return kconfig_path |
| 79 | |
| 80 | class LinuxSourceTree(object): |
| 81 | """Represents a Linux kernel source tree with KUnit tests.""" |
| 82 | |
| 83 | def __init__(self): |
| 84 | self._kconfig = kunit_config.Kconfig() |
| 85 | self._kconfig.read_from_file(KUNITCONFIG_PATH) |
| 86 | self._ops = LinuxSourceTreeOperations() |
| 87 | |
| 88 | def clean(self): |
| 89 | try: |
| 90 | self._ops.make_mrproper() |
| 91 | except ConfigError as e: |
| 92 | logging.error(e) |
| 93 | return False |
| 94 | return True |
| 95 | |
| 96 | def build_config(self, build_dir): |
| 97 | kconfig_path = get_kconfig_path(build_dir) |
| 98 | if build_dir and not os.path.exists(build_dir): |
| 99 | os.mkdir(build_dir) |
| 100 | self._kconfig.write_to_file(kconfig_path) |
| 101 | try: |
| 102 | self._ops.make_olddefconfig(build_dir) |
| 103 | except ConfigError as e: |
| 104 | logging.error(e) |
| 105 | return False |
| 106 | validated_kconfig = kunit_config.Kconfig() |
| 107 | validated_kconfig.read_from_file(kconfig_path) |
| 108 | if not self._kconfig.is_subset_of(validated_kconfig): |
| 109 | logging.error('Provided Kconfig is not contained in validated .config!') |
| 110 | return False |
| 111 | return True |
| 112 | |
| 113 | def build_reconfig(self, build_dir): |
| 114 | """Creates a new .config if it is not a subset of the kunitconfig.""" |
| 115 | kconfig_path = get_kconfig_path(build_dir) |
| 116 | if os.path.exists(kconfig_path): |
| 117 | existing_kconfig = kunit_config.Kconfig() |
| 118 | existing_kconfig.read_from_file(kconfig_path) |
| 119 | if not self._kconfig.is_subset_of(existing_kconfig): |
| 120 | print('Regenerating .config ...') |
| 121 | os.remove(kconfig_path) |
| 122 | return self.build_config(build_dir) |
| 123 | else: |
| 124 | return True |
| 125 | else: |
| 126 | print('Generating .config ...') |
| 127 | return self.build_config(build_dir) |
| 128 | |
| 129 | def build_um_kernel(self, jobs, build_dir): |
| 130 | try: |
| 131 | self._ops.make_olddefconfig(build_dir) |
| 132 | self._ops.make(jobs, build_dir) |
| 133 | except (ConfigError, BuildError) as e: |
| 134 | logging.error(e) |
| 135 | return False |
| 136 | used_kconfig = kunit_config.Kconfig() |
| 137 | used_kconfig.read_from_file(get_kconfig_path(build_dir)) |
| 138 | if not self._kconfig.is_subset_of(used_kconfig): |
| 139 | logging.error('Provided Kconfig is not contained in final config!') |
| 140 | return False |
| 141 | return True |
| 142 | |
| 143 | def run_kernel(self, args=None, timeout=None, build_dir=None): |
| 144 | if not args: |
| 145 | args = [] |
| 146 | args.extend(['mem=256M']) |
| 147 | process = self._ops.linux_bin(args, timeout, build_dir) |
| 148 | with open('test.log', 'w') as f: |
| 149 | for line in process.stdout: |
| 150 | f.write(line.rstrip().decode('ascii') + '\n') |
| 151 | yield line.rstrip().decode('ascii') |