]>
Commit | Line | Data |
---|---|---|
6ebf5866 FG |
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' | |
14ee5cfd | 17 | kunitconfig_path = '.kunitconfig' |
6ebf5866 FG |
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() | |
e3212513 | 85 | self._kconfig.read_from_file(kunitconfig_path) |
6ebf5866 FG |
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 | ||
dde54b94 HF |
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 | ||
6ebf5866 FG |
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 | |
dde54b94 | 120 | return self.validate_config(build_dir) |
6ebf5866 FG |
121 | |
122 | def build_reconfig(self, build_dir): | |
14ee5cfd | 123 | """Creates a new .config if it is not a subset of the .kunitconfig.""" |
6ebf5866 FG |
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 | |
dde54b94 | 145 | return self.validate_config(build_dir) |
6ebf5866 | 146 | |
609952c2 | 147 | def run_kernel(self, args=[], timeout=None, build_dir=''): |
6ebf5866 FG |
148 | args.extend(['mem=256M']) |
149 | process = self._ops.linux_bin(args, timeout, build_dir) | |
609952c2 | 150 | with open(os.path.join(build_dir, 'test.log'), 'w') as f: |
6ebf5866 FG |
151 | for line in process.stdout: |
152 | f.write(line.rstrip().decode('ascii') + '\n') | |
153 | yield line.rstrip().decode('ascii') |