xref: /linux/tools/testing/kunit/kunit_kernel.py (revision 6ebf5866f2e870cf9451a2068c787bc2b30025e9)
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'
17
18class ConfigError(Exception):
19	"""Represents an error trying to configure the Linux kernel."""
20
21
22class BuildError(Exception):
23	"""Represents an error trying to build the Linux kernel."""
24
25
26class LinuxSourceTreeOperations(object):
27	"""An abstraction over command line operations performed on a source tree."""
28
29	def make_mrproper(self):
30		try:
31			subprocess.check_output(['make', 'mrproper'])
32		except OSError as e:
33			raise ConfigError('Could not call make command: ' + e)
34		except subprocess.CalledProcessError as e:
35			raise ConfigError(e.output)
36
37	def make_olddefconfig(self, build_dir):
38		command = ['make', 'ARCH=um', 'olddefconfig']
39		if build_dir:
40			command += ['O=' + build_dir]
41		try:
42			subprocess.check_output(command)
43		except OSError as e:
44			raise ConfigError('Could not call make command: ' + e)
45		except subprocess.CalledProcessError as e:
46			raise ConfigError(e.output)
47
48	def make(self, jobs, build_dir):
49		command = ['make', 'ARCH=um', '--jobs=' + str(jobs)]
50		if build_dir:
51			command += ['O=' + build_dir]
52		try:
53			subprocess.check_output(command)
54		except OSError as e:
55			raise BuildError('Could not call execute make: ' + e)
56		except subprocess.CalledProcessError as e:
57			raise BuildError(e.output)
58
59	def linux_bin(self, params, timeout, build_dir):
60		"""Runs the Linux UML binary. Must be named 'linux'."""
61		linux_bin = './linux'
62		if build_dir:
63			linux_bin = os.path.join(build_dir, 'linux')
64		process = subprocess.Popen(
65			[linux_bin] + params,
66			stdin=subprocess.PIPE,
67			stdout=subprocess.PIPE,
68			stderr=subprocess.PIPE)
69		process.wait(timeout=timeout)
70		return process
71
72
73def get_kconfig_path(build_dir):
74	kconfig_path = KCONFIG_PATH
75	if build_dir:
76		kconfig_path = os.path.join(build_dir, KCONFIG_PATH)
77	return kconfig_path
78
79class LinuxSourceTree(object):
80	"""Represents a Linux kernel source tree with KUnit tests."""
81
82	def __init__(self):
83		self._kconfig = kunit_config.Kconfig()
84		self._kconfig.read_from_file('kunitconfig')
85		self._ops = LinuxSourceTreeOperations()
86
87	def clean(self):
88		try:
89			self._ops.make_mrproper()
90		except ConfigError as e:
91			logging.error(e)
92			return False
93		return True
94
95	def build_config(self, build_dir):
96		kconfig_path = get_kconfig_path(build_dir)
97		if build_dir and not os.path.exists(build_dir):
98			os.mkdir(build_dir)
99		self._kconfig.write_to_file(kconfig_path)
100		try:
101			self._ops.make_olddefconfig(build_dir)
102		except ConfigError as e:
103			logging.error(e)
104			return False
105		validated_kconfig = kunit_config.Kconfig()
106		validated_kconfig.read_from_file(kconfig_path)
107		if not self._kconfig.is_subset_of(validated_kconfig):
108			logging.error('Provided Kconfig is not contained in validated .config!')
109			return False
110		return True
111
112	def build_reconfig(self, build_dir):
113		"""Creates a new .config if it is not a subset of the kunitconfig."""
114		kconfig_path = get_kconfig_path(build_dir)
115		if os.path.exists(kconfig_path):
116			existing_kconfig = kunit_config.Kconfig()
117			existing_kconfig.read_from_file(kconfig_path)
118			if not self._kconfig.is_subset_of(existing_kconfig):
119				print('Regenerating .config ...')
120				os.remove(kconfig_path)
121				return self.build_config(build_dir)
122			else:
123				return True
124		else:
125			print('Generating .config ...')
126			return self.build_config(build_dir)
127
128	def build_um_kernel(self, jobs, build_dir):
129		try:
130			self._ops.make_olddefconfig(build_dir)
131			self._ops.make(jobs, build_dir)
132		except (ConfigError, BuildError) as e:
133			logging.error(e)
134			return False
135		used_kconfig = kunit_config.Kconfig()
136		used_kconfig.read_from_file(get_kconfig_path(build_dir))
137		if not self._kconfig.is_subset_of(used_kconfig):
138			logging.error('Provided Kconfig is not contained in final config!')
139			return False
140		return True
141
142	def run_kernel(self, args=[], timeout=None, build_dir=None):
143		args.extend(['mem=256M'])
144		process = self._ops.linux_bin(args, timeout, build_dir)
145		with open('test.log', 'w') as f:
146			for line in process.stdout:
147				f.write(line.rstrip().decode('ascii') + '\n')
148				yield line.rstrip().decode('ascii')
149