]> Git Repo - linux.git/blob - tools/testing/kunit/kunit_kernel.py
scsi: ibmvfc: Complete commands outside the host/queue lock
[linux.git] / tools / testing / kunit / kunit_kernel.py
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 <[email protected]>
7 # Author: Brendan Higgins <[email protected]>
8
9 import logging
10 import subprocess
11 import os
12 import shutil
13 import signal
14
15 from contextlib import ExitStack
16
17 import kunit_config
18 import kunit_parser
19
20 KCONFIG_PATH = '.config'
21 KUNITCONFIG_PATH = '.kunitconfig'
22 DEFAULT_KUNITCONFIG_PATH = 'arch/um/configs/kunit_defconfig'
23 BROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config'
24 OUTFILE_PATH = 'test.log'
25
26 def get_file_path(build_dir, default):
27         if build_dir:
28                 default = os.path.join(build_dir, default)
29         return default
30
31 class ConfigError(Exception):
32         """Represents an error trying to configure the Linux kernel."""
33
34
35 class BuildError(Exception):
36         """Represents an error trying to build the Linux kernel."""
37
38
39 class LinuxSourceTreeOperations(object):
40         """An abstraction over command line operations performed on a source tree."""
41
42         def make_mrproper(self):
43                 try:
44                         subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT)
45                 except OSError as e:
46                         raise ConfigError('Could not call make command: ' + str(e))
47                 except subprocess.CalledProcessError as e:
48                         raise ConfigError(e.output.decode())
49
50         def make_olddefconfig(self, build_dir, make_options):
51                 command = ['make', 'ARCH=um', 'olddefconfig']
52                 if make_options:
53                         command.extend(make_options)
54                 if build_dir:
55                         command += ['O=' + build_dir]
56                 try:
57                         subprocess.check_output(command, stderr=subprocess.STDOUT)
58                 except OSError as e:
59                         raise ConfigError('Could not call make command: ' + str(e))
60                 except subprocess.CalledProcessError as e:
61                         raise ConfigError(e.output.decode())
62
63         def make_allyesconfig(self, build_dir, make_options):
64                 kunit_parser.print_with_timestamp(
65                         'Enabling all CONFIGs for UML...')
66                 command = ['make', 'ARCH=um', 'allyesconfig']
67                 if make_options:
68                         command.extend(make_options)
69                 if build_dir:
70                         command += ['O=' + build_dir]
71                 process = subprocess.Popen(
72                         command,
73                         stdout=subprocess.DEVNULL,
74                         stderr=subprocess.STDOUT)
75                 process.wait()
76                 kunit_parser.print_with_timestamp(
77                         'Disabling broken configs to run KUnit tests...')
78                 with ExitStack() as es:
79                         config = open(get_kconfig_path(build_dir), 'a')
80                         disable = open(BROKEN_ALLCONFIG_PATH, 'r').read()
81                         config.write(disable)
82                 kunit_parser.print_with_timestamp(
83                         'Starting Kernel with all configs takes a few minutes...')
84
85         def make(self, jobs, build_dir, make_options):
86                 command = ['make', 'ARCH=um', '--jobs=' + str(jobs)]
87                 if make_options:
88                         command.extend(make_options)
89                 if build_dir:
90                         command += ['O=' + build_dir]
91                 try:
92                         proc = subprocess.Popen(command,
93                                                 stderr=subprocess.PIPE,
94                                                 stdout=subprocess.DEVNULL)
95                 except OSError as e:
96                         raise BuildError('Could not call make command: ' + str(e))
97                 _, stderr = proc.communicate()
98                 if proc.returncode != 0:
99                         raise BuildError(stderr.decode())
100                 if stderr:  # likely only due to build warnings
101                         print(stderr.decode())
102
103         def linux_bin(self, params, timeout, build_dir):
104                 """Runs the Linux UML binary. Must be named 'linux'."""
105                 linux_bin = get_file_path(build_dir, 'linux')
106                 outfile = get_outfile_path(build_dir)
107                 with open(outfile, 'w') as output:
108                         process = subprocess.Popen([linux_bin] + params,
109                                                    stdout=output,
110                                                    stderr=subprocess.STDOUT)
111                         process.wait(timeout)
112
113 def get_kconfig_path(build_dir):
114         return get_file_path(build_dir, KCONFIG_PATH)
115
116 def get_kunitconfig_path(build_dir):
117         return get_file_path(build_dir, KUNITCONFIG_PATH)
118
119 def get_outfile_path(build_dir):
120         return get_file_path(build_dir, OUTFILE_PATH)
121
122 class LinuxSourceTree(object):
123         """Represents a Linux kernel source tree with KUnit tests."""
124
125         def __init__(self):
126                 self._ops = LinuxSourceTreeOperations()
127                 signal.signal(signal.SIGINT, self.signal_handler)
128
129         def clean(self):
130                 try:
131                         self._ops.make_mrproper()
132                 except ConfigError as e:
133                         logging.error(e)
134                         return False
135                 return True
136
137         def create_kunitconfig(self, build_dir, defconfig=DEFAULT_KUNITCONFIG_PATH):
138                 kunitconfig_path = get_kunitconfig_path(build_dir)
139                 if not os.path.exists(kunitconfig_path):
140                         shutil.copyfile(defconfig, kunitconfig_path)
141
142         def read_kunitconfig(self, build_dir):
143                 kunitconfig_path = get_kunitconfig_path(build_dir)
144                 self._kconfig = kunit_config.Kconfig()
145                 self._kconfig.read_from_file(kunitconfig_path)
146
147         def validate_config(self, build_dir):
148                 kconfig_path = get_kconfig_path(build_dir)
149                 validated_kconfig = kunit_config.Kconfig()
150                 validated_kconfig.read_from_file(kconfig_path)
151                 if not self._kconfig.is_subset_of(validated_kconfig):
152                         invalid = self._kconfig.entries() - validated_kconfig.entries()
153                         message = 'Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \
154                                           'but not in .config: %s' % (
155                                         ', '.join([str(e) for e in invalid])
156                         )
157                         logging.error(message)
158                         return False
159                 return True
160
161         def build_config(self, build_dir, make_options):
162                 kconfig_path = get_kconfig_path(build_dir)
163                 if build_dir and not os.path.exists(build_dir):
164                         os.mkdir(build_dir)
165                 self._kconfig.write_to_file(kconfig_path)
166                 try:
167                         self._ops.make_olddefconfig(build_dir, make_options)
168                 except ConfigError as e:
169                         logging.error(e)
170                         return False
171                 return self.validate_config(build_dir)
172
173         def build_reconfig(self, build_dir, make_options):
174                 """Creates a new .config if it is not a subset of the .kunitconfig."""
175                 kconfig_path = get_kconfig_path(build_dir)
176                 if os.path.exists(kconfig_path):
177                         existing_kconfig = kunit_config.Kconfig()
178                         existing_kconfig.read_from_file(kconfig_path)
179                         if not self._kconfig.is_subset_of(existing_kconfig):
180                                 print('Regenerating .config ...')
181                                 os.remove(kconfig_path)
182                                 return self.build_config(build_dir, make_options)
183                         else:
184                                 return True
185                 else:
186                         print('Generating .config ...')
187                         return self.build_config(build_dir, make_options)
188
189         def build_um_kernel(self, alltests, jobs, build_dir, make_options):
190                 try:
191                         if alltests:
192                                 self._ops.make_allyesconfig(build_dir, make_options)
193                         self._ops.make_olddefconfig(build_dir, make_options)
194                         self._ops.make(jobs, build_dir, make_options)
195                 except (ConfigError, BuildError) as e:
196                         logging.error(e)
197                         return False
198                 return self.validate_config(build_dir)
199
200         def run_kernel(self, args=[], build_dir='', timeout=None):
201                 args.extend(['mem=1G'])
202                 self._ops.linux_bin(args, timeout, build_dir)
203                 outfile = get_outfile_path(build_dir)
204                 subprocess.call(['stty', 'sane'])
205                 with open(outfile, 'r') as file:
206                         for line in file:
207                                 yield line
208
209         def signal_handler(self, sig, frame):
210                 logging.error('Build interruption occurred. Cleaning console.')
211                 subprocess.call(['stty', 'sane'])
This page took 0.044156 seconds and 4 git commands to generate.