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