blob: e20e2056cb380bfdb858c9300c36b3b8cd26c21d [file] [log] [blame]
Felix Guo6ebf5862019-09-23 02:02:43 -07001# 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
Heidi Fahim021ed9f2020-03-16 13:21:25 -070013import signal
14
15from contextlib import ExitStack
Felix Guo6ebf5862019-09-23 02:02:43 -070016
17import kunit_config
Heidi Fahim021ed9f2020-03-16 13:21:25 -070018import kunit_parser
Felix Guo6ebf5862019-09-23 02:02:43 -070019
20KCONFIG_PATH = '.config'
SeongJae Park14ee5cfd2019-12-20 05:14:07 +000021kunitconfig_path = '.kunitconfig'
Heidi Fahim021ed9f2020-03-16 13:21:25 -070022BROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config'
Felix Guo6ebf5862019-09-23 02:02:43 -070023
24class ConfigError(Exception):
25 """Represents an error trying to configure the Linux kernel."""
26
27
28class BuildError(Exception):
29 """Represents an error trying to build the Linux kernel."""
30
31
32class LinuxSourceTreeOperations(object):
33 """An abstraction over command line operations performed on a source tree."""
34
35 def make_mrproper(self):
36 try:
Will Chen5a9fcad2020-07-08 14:35:43 -070037 subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT)
Felix Guo6ebf5862019-09-23 02:02:43 -070038 except OSError as e:
39 raise ConfigError('Could not call make command: ' + e)
40 except subprocess.CalledProcessError as e:
41 raise ConfigError(e.output)
42
Greg Thelen0476e692020-03-23 12:04:59 -070043 def make_olddefconfig(self, build_dir, make_options):
Felix Guo6ebf5862019-09-23 02:02:43 -070044 command = ['make', 'ARCH=um', 'olddefconfig']
Greg Thelen0476e692020-03-23 12:04:59 -070045 if make_options:
46 command.extend(make_options)
Felix Guo6ebf5862019-09-23 02:02:43 -070047 if build_dir:
48 command += ['O=' + build_dir]
49 try:
Will Chen5a9fcad2020-07-08 14:35:43 -070050 subprocess.check_output(command, stderr=subprocess.STDOUT)
Felix Guo6ebf5862019-09-23 02:02:43 -070051 except OSError as e:
52 raise ConfigError('Could not call make command: ' + e)
53 except subprocess.CalledProcessError as e:
54 raise ConfigError(e.output)
55
Heidi Fahim021ed9f2020-03-16 13:21:25 -070056 def make_allyesconfig(self):
57 kunit_parser.print_with_timestamp(
58 'Enabling all CONFIGs for UML...')
59 process = subprocess.Popen(
60 ['make', 'ARCH=um', 'allyesconfig'],
61 stdout=subprocess.DEVNULL,
62 stderr=subprocess.STDOUT)
63 process.wait()
64 kunit_parser.print_with_timestamp(
65 'Disabling broken configs to run KUnit tests...')
66 with ExitStack() as es:
67 config = open(KCONFIG_PATH, 'a')
68 disable = open(BROKEN_ALLCONFIG_PATH, 'r').read()
69 config.write(disable)
70 kunit_parser.print_with_timestamp(
71 'Starting Kernel with all configs takes a few minutes...')
72
Greg Thelen0476e692020-03-23 12:04:59 -070073 def make(self, jobs, build_dir, make_options):
Felix Guo6ebf5862019-09-23 02:02:43 -070074 command = ['make', 'ARCH=um', '--jobs=' + str(jobs)]
Greg Thelen0476e692020-03-23 12:04:59 -070075 if make_options:
76 command.extend(make_options)
Felix Guo6ebf5862019-09-23 02:02:43 -070077 if build_dir:
78 command += ['O=' + build_dir]
79 try:
Will Chen5a9fcad2020-07-08 14:35:43 -070080 subprocess.check_output(command, stderr=subprocess.STDOUT)
Felix Guo6ebf5862019-09-23 02:02:43 -070081 except OSError as e:
82 raise BuildError('Could not call execute make: ' + e)
83 except subprocess.CalledProcessError as e:
84 raise BuildError(e.output)
85
Heidi Fahim021ed9f2020-03-16 13:21:25 -070086 def linux_bin(self, params, timeout, build_dir, outfile):
Felix Guo6ebf5862019-09-23 02:02:43 -070087 """Runs the Linux UML binary. Must be named 'linux'."""
88 linux_bin = './linux'
89 if build_dir:
90 linux_bin = os.path.join(build_dir, 'linux')
Heidi Fahim021ed9f2020-03-16 13:21:25 -070091 with open(outfile, 'w') as output:
92 process = subprocess.Popen([linux_bin] + params,
93 stdout=output,
94 stderr=subprocess.STDOUT)
95 process.wait(timeout)
Felix Guo6ebf5862019-09-23 02:02:43 -070096
97
98def get_kconfig_path(build_dir):
99 kconfig_path = KCONFIG_PATH
100 if build_dir:
101 kconfig_path = os.path.join(build_dir, KCONFIG_PATH)
102 return kconfig_path
103
104class LinuxSourceTree(object):
105 """Represents a Linux kernel source tree with KUnit tests."""
106
107 def __init__(self):
108 self._kconfig = kunit_config.Kconfig()
SeongJae Parke3212512019-12-20 05:14:05 +0000109 self._kconfig.read_from_file(kunitconfig_path)
Felix Guo6ebf5862019-09-23 02:02:43 -0700110 self._ops = LinuxSourceTreeOperations()
Heidi Fahim021ed9f2020-03-16 13:21:25 -0700111 signal.signal(signal.SIGINT, self.signal_handler)
Felix Guo6ebf5862019-09-23 02:02:43 -0700112
113 def clean(self):
114 try:
115 self._ops.make_mrproper()
116 except ConfigError as e:
117 logging.error(e)
118 return False
119 return True
120
Heidi Fahimdde54b92019-11-26 14:36:16 -0800121 def validate_config(self, build_dir):
122 kconfig_path = get_kconfig_path(build_dir)
123 validated_kconfig = kunit_config.Kconfig()
124 validated_kconfig.read_from_file(kconfig_path)
125 if not self._kconfig.is_subset_of(validated_kconfig):
126 invalid = self._kconfig.entries() - validated_kconfig.entries()
127 message = 'Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \
128 'but not in .config: %s' % (
129 ', '.join([str(e) for e in invalid])
130 )
131 logging.error(message)
132 return False
133 return True
134
Greg Thelen0476e692020-03-23 12:04:59 -0700135 def build_config(self, build_dir, make_options):
Felix Guo6ebf5862019-09-23 02:02:43 -0700136 kconfig_path = get_kconfig_path(build_dir)
137 if build_dir and not os.path.exists(build_dir):
138 os.mkdir(build_dir)
139 self._kconfig.write_to_file(kconfig_path)
140 try:
Greg Thelen0476e692020-03-23 12:04:59 -0700141 self._ops.make_olddefconfig(build_dir, make_options)
Felix Guo6ebf5862019-09-23 02:02:43 -0700142 except ConfigError as e:
143 logging.error(e)
144 return False
Heidi Fahimdde54b92019-11-26 14:36:16 -0800145 return self.validate_config(build_dir)
Felix Guo6ebf5862019-09-23 02:02:43 -0700146
Greg Thelen0476e692020-03-23 12:04:59 -0700147 def build_reconfig(self, build_dir, make_options):
SeongJae Park14ee5cfd2019-12-20 05:14:07 +0000148 """Creates a new .config if it is not a subset of the .kunitconfig."""
Felix Guo6ebf5862019-09-23 02:02:43 -0700149 kconfig_path = get_kconfig_path(build_dir)
150 if os.path.exists(kconfig_path):
151 existing_kconfig = kunit_config.Kconfig()
152 existing_kconfig.read_from_file(kconfig_path)
153 if not self._kconfig.is_subset_of(existing_kconfig):
154 print('Regenerating .config ...')
155 os.remove(kconfig_path)
Greg Thelen0476e692020-03-23 12:04:59 -0700156 return self.build_config(build_dir, make_options)
Felix Guo6ebf5862019-09-23 02:02:43 -0700157 else:
158 return True
159 else:
160 print('Generating .config ...')
Greg Thelen0476e692020-03-23 12:04:59 -0700161 return self.build_config(build_dir, make_options)
Felix Guo6ebf5862019-09-23 02:02:43 -0700162
Greg Thelen0476e692020-03-23 12:04:59 -0700163 def build_um_kernel(self, alltests, jobs, build_dir, make_options):
Heidi Fahim021ed9f2020-03-16 13:21:25 -0700164 if alltests:
165 self._ops.make_allyesconfig()
Felix Guo6ebf5862019-09-23 02:02:43 -0700166 try:
Greg Thelen0476e692020-03-23 12:04:59 -0700167 self._ops.make_olddefconfig(build_dir, make_options)
168 self._ops.make(jobs, build_dir, make_options)
Felix Guo6ebf5862019-09-23 02:02:43 -0700169 except (ConfigError, BuildError) as e:
170 logging.error(e)
171 return False
Heidi Fahimdde54b92019-11-26 14:36:16 -0800172 return self.validate_config(build_dir)
Felix Guo6ebf5862019-09-23 02:02:43 -0700173
Heidi Fahim021ed9f2020-03-16 13:21:25 -0700174 def run_kernel(self, args=[], build_dir='', timeout=None):
175 args.extend(['mem=1G'])
176 outfile = 'test.log'
177 self._ops.linux_bin(args, timeout, build_dir, outfile)
178 subprocess.call(['stty', 'sane'])
179 with open(outfile, 'r') as file:
180 for line in file:
181 yield line
182
183 def signal_handler(self, sig, frame):
184 logging.error('Build interruption occurred. Cleaning console.')
185 subprocess.call(['stty', 'sane'])