Merge tag 'xfs-5.10-merge-7' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux
[linux-2.6-microblaze.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 <felixguoxiuping@gmail.com>
7 # Author: Brendan Higgins <brendanhiggins@google.com>
8
9
10 import logging
11 import subprocess
12 import os
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 BROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config'
23
24 class ConfigError(Exception):
25         """Represents an error trying to configure the Linux kernel."""
26
27
28 class BuildError(Exception):
29         """Represents an error trying to build the Linux kernel."""
30
31
32 class LinuxSourceTreeOperations(object):
33         """An abstraction over command line operations performed on a source tree."""
34
35         def make_mrproper(self):
36                 try:
37                         subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT)
38                 except OSError as e:
39                         raise ConfigError('Could not call make command: ' + str(e))
40                 except subprocess.CalledProcessError as e:
41                         raise ConfigError(e.output.decode())
42
43         def make_olddefconfig(self, build_dir, make_options):
44                 command = ['make', 'ARCH=um', 'olddefconfig']
45                 if make_options:
46                         command.extend(make_options)
47                 if build_dir:
48                         command += ['O=' + build_dir]
49                 try:
50                         subprocess.check_output(command, stderr=subprocess.STDOUT)
51                 except OSError as e:
52                         raise ConfigError('Could not call make command: ' + str(e))
53                 except subprocess.CalledProcessError as e:
54                         raise ConfigError(e.output.decode())
55
56         def make_allyesconfig(self, build_dir, make_options):
57                 kunit_parser.print_with_timestamp(
58                         'Enabling all CONFIGs for UML...')
59                 command = ['make', 'ARCH=um', 'allyesconfig']
60                 if make_options:
61                         command.extend(make_options)
62                 if build_dir:
63                         command += ['O=' + build_dir]
64                 process = subprocess.Popen(
65                         command,
66                         stdout=subprocess.DEVNULL,
67                         stderr=subprocess.STDOUT)
68                 process.wait()
69                 kunit_parser.print_with_timestamp(
70                         'Disabling broken configs to run KUnit tests...')
71                 with ExitStack() as es:
72                         config = open(get_kconfig_path(build_dir), 'a')
73                         disable = open(BROKEN_ALLCONFIG_PATH, 'r').read()
74                         config.write(disable)
75                 kunit_parser.print_with_timestamp(
76                         'Starting Kernel with all configs takes a few minutes...')
77
78         def make(self, jobs, build_dir, make_options):
79                 command = ['make', 'ARCH=um', '--jobs=' + str(jobs)]
80                 if make_options:
81                         command.extend(make_options)
82                 if build_dir:
83                         command += ['O=' + build_dir]
84                 try:
85                         subprocess.check_output(command, stderr=subprocess.STDOUT)
86                 except OSError as e:
87                         raise BuildError('Could not call execute make: ' + str(e))
88                 except subprocess.CalledProcessError as e:
89                         raise BuildError(e.output.decode())
90
91         def linux_bin(self, params, timeout, build_dir, outfile):
92                 """Runs the Linux UML binary. Must be named 'linux'."""
93                 linux_bin = './linux'
94                 if build_dir:
95                         linux_bin = os.path.join(build_dir, 'linux')
96                 with open(outfile, 'w') as output:
97                         process = subprocess.Popen([linux_bin] + params,
98                                                    stdout=output,
99                                                    stderr=subprocess.STDOUT)
100                         process.wait(timeout)
101
102
103 def get_kconfig_path(build_dir):
104         kconfig_path = KCONFIG_PATH
105         if build_dir:
106                 kconfig_path = os.path.join(build_dir, KCONFIG_PATH)
107         return kconfig_path
108
109 class LinuxSourceTree(object):
110         """Represents a Linux kernel source tree with KUnit tests."""
111
112         def __init__(self):
113                 self._kconfig = kunit_config.Kconfig()
114                 self._kconfig.read_from_file(kunitconfig_path)
115                 self._ops = LinuxSourceTreeOperations()
116                 signal.signal(signal.SIGINT, self.signal_handler)
117
118         def clean(self):
119                 try:
120                         self._ops.make_mrproper()
121                 except ConfigError as e:
122                         logging.error(e)
123                         return False
124                 return True
125
126         def validate_config(self, build_dir):
127                 kconfig_path = get_kconfig_path(build_dir)
128                 validated_kconfig = kunit_config.Kconfig()
129                 validated_kconfig.read_from_file(kconfig_path)
130                 if not self._kconfig.is_subset_of(validated_kconfig):
131                         invalid = self._kconfig.entries() - validated_kconfig.entries()
132                         message = 'Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \
133                                           'but not in .config: %s' % (
134                                         ', '.join([str(e) for e in invalid])
135                         )
136                         logging.error(message)
137                         return False
138                 return True
139
140         def build_config(self, build_dir, make_options):
141                 kconfig_path = get_kconfig_path(build_dir)
142                 if build_dir and not os.path.exists(build_dir):
143                         os.mkdir(build_dir)
144                 self._kconfig.write_to_file(kconfig_path)
145                 try:
146                         self._ops.make_olddefconfig(build_dir, make_options)
147                 except ConfigError as e:
148                         logging.error(e)
149                         return False
150                 return self.validate_config(build_dir)
151
152         def build_reconfig(self, build_dir, make_options):
153                 """Creates a new .config if it is not a subset of the .kunitconfig."""
154                 kconfig_path = get_kconfig_path(build_dir)
155                 if os.path.exists(kconfig_path):
156                         existing_kconfig = kunit_config.Kconfig()
157                         existing_kconfig.read_from_file(kconfig_path)
158                         if not self._kconfig.is_subset_of(existing_kconfig):
159                                 print('Regenerating .config ...')
160                                 os.remove(kconfig_path)
161                                 return self.build_config(build_dir, make_options)
162                         else:
163                                 return True
164                 else:
165                         print('Generating .config ...')
166                         return self.build_config(build_dir, make_options)
167
168         def build_um_kernel(self, alltests, jobs, build_dir, make_options):
169                 try:
170                         if alltests:
171                                 self._ops.make_allyesconfig(build_dir, make_options)
172                         self._ops.make_olddefconfig(build_dir, make_options)
173                         self._ops.make(jobs, build_dir, make_options)
174                 except (ConfigError, BuildError) as e:
175                         logging.error(e)
176                         return False
177                 return self.validate_config(build_dir)
178
179         def run_kernel(self, args=[], build_dir='', timeout=None):
180                 args.extend(['mem=1G'])
181                 outfile = 'test.log'
182                 self._ops.linux_bin(args, timeout, build_dir, outfile)
183                 subprocess.call(['stty', 'sane'])
184                 with open(outfile, 'r') as file:
185                         for line in file:
186                                 yield line
187
188         def signal_handler(self, sig, frame):
189                 logging.error('Build interruption occurred. Cleaning console.')
190                 subprocess.call(['stty', 'sane'])