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' 17kunitconfig_path = '.kunitconfig' 18 19class ConfigError(Exception): 20 """Represents an error trying to configure the Linux kernel.""" 21 22 23class BuildError(Exception): 24 """Represents an error trying to build the Linux kernel.""" 25 26 27class 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 74def 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 80class 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 validate_config(self, build_dir): 97 kconfig_path = get_kconfig_path(build_dir) 98 validated_kconfig = kunit_config.Kconfig() 99 validated_kconfig.read_from_file(kconfig_path) 100 if not self._kconfig.is_subset_of(validated_kconfig): 101 invalid = self._kconfig.entries() - validated_kconfig.entries() 102 message = 'Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \ 103 'but not in .config: %s' % ( 104 ', '.join([str(e) for e in invalid]) 105 ) 106 logging.error(message) 107 return False 108 return True 109 110 def build_config(self, build_dir): 111 kconfig_path = get_kconfig_path(build_dir) 112 if build_dir and not os.path.exists(build_dir): 113 os.mkdir(build_dir) 114 self._kconfig.write_to_file(kconfig_path) 115 try: 116 self._ops.make_olddefconfig(build_dir) 117 except ConfigError as e: 118 logging.error(e) 119 return False 120 return self.validate_config(build_dir) 121 122 def build_reconfig(self, build_dir): 123 """Creates a new .config if it is not a subset of the .kunitconfig.""" 124 kconfig_path = get_kconfig_path(build_dir) 125 if os.path.exists(kconfig_path): 126 existing_kconfig = kunit_config.Kconfig() 127 existing_kconfig.read_from_file(kconfig_path) 128 if not self._kconfig.is_subset_of(existing_kconfig): 129 print('Regenerating .config ...') 130 os.remove(kconfig_path) 131 return self.build_config(build_dir) 132 else: 133 return True 134 else: 135 print('Generating .config ...') 136 return self.build_config(build_dir) 137 138 def build_um_kernel(self, jobs, build_dir): 139 try: 140 self._ops.make_olddefconfig(build_dir) 141 self._ops.make(jobs, build_dir) 142 except (ConfigError, BuildError) as e: 143 logging.error(e) 144 return False 145 return self.validate_config(build_dir) 146 147 def run_kernel(self, args=[], timeout=None, build_dir=''): 148 args.extend(['mem=256M']) 149 process = self._ops.linux_bin(args, timeout, build_dir) 150 with open(os.path.join(build_dir, 'test.log'), 'w') as f: 151 for line in process.stdout: 152 f.write(line.rstrip().decode('ascii') + '\n') 153 yield line.rstrip().decode('ascii') 154