16ebf5866SFelix Guo# SPDX-License-Identifier: GPL-2.0
26ebf5866SFelix Guo#
36ebf5866SFelix Guo# Runs UML kernel, collects output, and handles errors.
46ebf5866SFelix Guo#
56ebf5866SFelix Guo# Copyright (C) 2019, Google LLC.
66ebf5866SFelix Guo# Author: Felix Guo <[email protected]>
76ebf5866SFelix Guo# Author: Brendan Higgins <[email protected]>
86ebf5866SFelix Guo
96ebf5866SFelix Guo
106ebf5866SFelix Guoimport logging
116ebf5866SFelix Guoimport subprocess
126ebf5866SFelix Guoimport os
13021ed9f5SHeidi Fahimimport signal
14021ed9f5SHeidi Fahim
15021ed9f5SHeidi Fahimfrom contextlib import ExitStack
166ebf5866SFelix Guo
176ebf5866SFelix Guoimport kunit_config
18021ed9f5SHeidi Fahimimport kunit_parser
196ebf5866SFelix Guo
206ebf5866SFelix GuoKCONFIG_PATH = '.config'
2114ee5cfdSSeongJae Parkkunitconfig_path = '.kunitconfig'
22021ed9f5SHeidi FahimBROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config'
236ebf5866SFelix Guo
246ebf5866SFelix Guoclass ConfigError(Exception):
256ebf5866SFelix Guo	"""Represents an error trying to configure the Linux kernel."""
266ebf5866SFelix Guo
276ebf5866SFelix Guo
286ebf5866SFelix Guoclass BuildError(Exception):
296ebf5866SFelix Guo	"""Represents an error trying to build the Linux kernel."""
306ebf5866SFelix Guo
316ebf5866SFelix Guo
326ebf5866SFelix Guoclass LinuxSourceTreeOperations(object):
336ebf5866SFelix Guo	"""An abstraction over command line operations performed on a source tree."""
346ebf5866SFelix Guo
356ebf5866SFelix Guo	def make_mrproper(self):
366ebf5866SFelix Guo		try:
375a9fcad7SWill Chen			subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT)
386ebf5866SFelix Guo		except OSError as e:
396ebf5866SFelix Guo			raise ConfigError('Could not call make command: ' + e)
406ebf5866SFelix Guo		except subprocess.CalledProcessError as e:
416ebf5866SFelix Guo			raise ConfigError(e.output)
426ebf5866SFelix Guo
430476e69fSGreg Thelen	def make_olddefconfig(self, build_dir, make_options):
446ebf5866SFelix Guo		command = ['make', 'ARCH=um', 'olddefconfig']
450476e69fSGreg Thelen		if make_options:
460476e69fSGreg Thelen			command.extend(make_options)
476ebf5866SFelix Guo		if build_dir:
486ebf5866SFelix Guo			command += ['O=' + build_dir]
496ebf5866SFelix Guo		try:
505a9fcad7SWill Chen			subprocess.check_output(command, stderr=subprocess.STDOUT)
516ebf5866SFelix Guo		except OSError as e:
526ebf5866SFelix Guo			raise ConfigError('Could not call make command: ' + e)
536ebf5866SFelix Guo		except subprocess.CalledProcessError as e:
546ebf5866SFelix Guo			raise ConfigError(e.output)
556ebf5866SFelix Guo
56*67e2fae3SBrendan Higgins	def make_allyesconfig(self, build_dir, make_options):
57021ed9f5SHeidi Fahim		kunit_parser.print_with_timestamp(
58021ed9f5SHeidi Fahim			'Enabling all CONFIGs for UML...')
59*67e2fae3SBrendan Higgins		command = ['make', 'ARCH=um', 'allyesconfig']
60*67e2fae3SBrendan Higgins		if make_options:
61*67e2fae3SBrendan Higgins			command.extend(make_options)
62*67e2fae3SBrendan Higgins		if build_dir:
63*67e2fae3SBrendan Higgins			command += ['O=' + build_dir]
64021ed9f5SHeidi Fahim		process = subprocess.Popen(
65*67e2fae3SBrendan Higgins			command,
66021ed9f5SHeidi Fahim			stdout=subprocess.DEVNULL,
67021ed9f5SHeidi Fahim			stderr=subprocess.STDOUT)
68021ed9f5SHeidi Fahim		process.wait()
69021ed9f5SHeidi Fahim		kunit_parser.print_with_timestamp(
70021ed9f5SHeidi Fahim			'Disabling broken configs to run KUnit tests...')
71021ed9f5SHeidi Fahim		with ExitStack() as es:
72*67e2fae3SBrendan Higgins			config = open(get_kconfig_path(build_dir), 'a')
73021ed9f5SHeidi Fahim			disable = open(BROKEN_ALLCONFIG_PATH, 'r').read()
74021ed9f5SHeidi Fahim			config.write(disable)
75021ed9f5SHeidi Fahim		kunit_parser.print_with_timestamp(
76021ed9f5SHeidi Fahim			'Starting Kernel with all configs takes a few minutes...')
77021ed9f5SHeidi Fahim
780476e69fSGreg Thelen	def make(self, jobs, build_dir, make_options):
796ebf5866SFelix Guo		command = ['make', 'ARCH=um', '--jobs=' + str(jobs)]
800476e69fSGreg Thelen		if make_options:
810476e69fSGreg Thelen			command.extend(make_options)
826ebf5866SFelix Guo		if build_dir:
836ebf5866SFelix Guo			command += ['O=' + build_dir]
846ebf5866SFelix Guo		try:
855a9fcad7SWill Chen			subprocess.check_output(command, stderr=subprocess.STDOUT)
866ebf5866SFelix Guo		except OSError as e:
876ebf5866SFelix Guo			raise BuildError('Could not call execute make: ' + e)
886ebf5866SFelix Guo		except subprocess.CalledProcessError as e:
896ebf5866SFelix Guo			raise BuildError(e.output)
906ebf5866SFelix Guo
91021ed9f5SHeidi Fahim	def linux_bin(self, params, timeout, build_dir, outfile):
926ebf5866SFelix Guo		"""Runs the Linux UML binary. Must be named 'linux'."""
936ebf5866SFelix Guo		linux_bin = './linux'
946ebf5866SFelix Guo		if build_dir:
956ebf5866SFelix Guo			linux_bin = os.path.join(build_dir, 'linux')
96021ed9f5SHeidi Fahim		with open(outfile, 'w') as output:
97021ed9f5SHeidi Fahim			process = subprocess.Popen([linux_bin] + params,
98021ed9f5SHeidi Fahim						   stdout=output,
99021ed9f5SHeidi Fahim						   stderr=subprocess.STDOUT)
100021ed9f5SHeidi Fahim			process.wait(timeout)
1016ebf5866SFelix Guo
1026ebf5866SFelix Guo
1036ebf5866SFelix Guodef get_kconfig_path(build_dir):
1046ebf5866SFelix Guo	kconfig_path = KCONFIG_PATH
1056ebf5866SFelix Guo	if build_dir:
1066ebf5866SFelix Guo		kconfig_path = os.path.join(build_dir, KCONFIG_PATH)
1076ebf5866SFelix Guo	return kconfig_path
1086ebf5866SFelix Guo
1096ebf5866SFelix Guoclass LinuxSourceTree(object):
1106ebf5866SFelix Guo	"""Represents a Linux kernel source tree with KUnit tests."""
1116ebf5866SFelix Guo
1126ebf5866SFelix Guo	def __init__(self):
1136ebf5866SFelix Guo		self._kconfig = kunit_config.Kconfig()
114e3212513SSeongJae Park		self._kconfig.read_from_file(kunitconfig_path)
1156ebf5866SFelix Guo		self._ops = LinuxSourceTreeOperations()
116021ed9f5SHeidi Fahim		signal.signal(signal.SIGINT, self.signal_handler)
1176ebf5866SFelix Guo
1186ebf5866SFelix Guo	def clean(self):
1196ebf5866SFelix Guo		try:
1206ebf5866SFelix Guo			self._ops.make_mrproper()
1216ebf5866SFelix Guo		except ConfigError as e:
1226ebf5866SFelix Guo			logging.error(e)
1236ebf5866SFelix Guo			return False
1246ebf5866SFelix Guo		return True
1256ebf5866SFelix Guo
126dde54b94SHeidi Fahim	def validate_config(self, build_dir):
127dde54b94SHeidi Fahim		kconfig_path = get_kconfig_path(build_dir)
128dde54b94SHeidi Fahim		validated_kconfig = kunit_config.Kconfig()
129dde54b94SHeidi Fahim		validated_kconfig.read_from_file(kconfig_path)
130dde54b94SHeidi Fahim		if not self._kconfig.is_subset_of(validated_kconfig):
131dde54b94SHeidi Fahim			invalid = self._kconfig.entries() - validated_kconfig.entries()
132dde54b94SHeidi Fahim			message = 'Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \
133dde54b94SHeidi Fahim					  'but not in .config: %s' % (
134dde54b94SHeidi Fahim					', '.join([str(e) for e in invalid])
135dde54b94SHeidi Fahim			)
136dde54b94SHeidi Fahim			logging.error(message)
137dde54b94SHeidi Fahim			return False
138dde54b94SHeidi Fahim		return True
139dde54b94SHeidi Fahim
1400476e69fSGreg Thelen	def build_config(self, build_dir, make_options):
1416ebf5866SFelix Guo		kconfig_path = get_kconfig_path(build_dir)
1426ebf5866SFelix Guo		if build_dir and not os.path.exists(build_dir):
1436ebf5866SFelix Guo			os.mkdir(build_dir)
1446ebf5866SFelix Guo		self._kconfig.write_to_file(kconfig_path)
1456ebf5866SFelix Guo		try:
1460476e69fSGreg Thelen			self._ops.make_olddefconfig(build_dir, make_options)
1476ebf5866SFelix Guo		except ConfigError as e:
1486ebf5866SFelix Guo			logging.error(e)
1496ebf5866SFelix Guo			return False
150dde54b94SHeidi Fahim		return self.validate_config(build_dir)
1516ebf5866SFelix Guo
1520476e69fSGreg Thelen	def build_reconfig(self, build_dir, make_options):
15314ee5cfdSSeongJae Park		"""Creates a new .config if it is not a subset of the .kunitconfig."""
1546ebf5866SFelix Guo		kconfig_path = get_kconfig_path(build_dir)
1556ebf5866SFelix Guo		if os.path.exists(kconfig_path):
1566ebf5866SFelix Guo			existing_kconfig = kunit_config.Kconfig()
1576ebf5866SFelix Guo			existing_kconfig.read_from_file(kconfig_path)
1586ebf5866SFelix Guo			if not self._kconfig.is_subset_of(existing_kconfig):
1596ebf5866SFelix Guo				print('Regenerating .config ...')
1606ebf5866SFelix Guo				os.remove(kconfig_path)
1610476e69fSGreg Thelen				return self.build_config(build_dir, make_options)
1626ebf5866SFelix Guo			else:
1636ebf5866SFelix Guo				return True
1646ebf5866SFelix Guo		else:
1656ebf5866SFelix Guo			print('Generating .config ...')
1660476e69fSGreg Thelen			return self.build_config(build_dir, make_options)
1676ebf5866SFelix Guo
1680476e69fSGreg Thelen	def build_um_kernel(self, alltests, jobs, build_dir, make_options):
1696ebf5866SFelix Guo		try:
170*67e2fae3SBrendan Higgins			if alltests:
171*67e2fae3SBrendan Higgins				self._ops.make_allyesconfig(build_dir, make_options)
1720476e69fSGreg Thelen			self._ops.make_olddefconfig(build_dir, make_options)
1730476e69fSGreg Thelen			self._ops.make(jobs, build_dir, make_options)
1746ebf5866SFelix Guo		except (ConfigError, BuildError) as e:
1756ebf5866SFelix Guo			logging.error(e)
1766ebf5866SFelix Guo			return False
177dde54b94SHeidi Fahim		return self.validate_config(build_dir)
1786ebf5866SFelix Guo
179021ed9f5SHeidi Fahim	def run_kernel(self, args=[], build_dir='', timeout=None):
180021ed9f5SHeidi Fahim		args.extend(['mem=1G'])
181021ed9f5SHeidi Fahim		outfile = 'test.log'
182021ed9f5SHeidi Fahim		self._ops.linux_bin(args, timeout, build_dir, outfile)
183021ed9f5SHeidi Fahim		subprocess.call(['stty', 'sane'])
184021ed9f5SHeidi Fahim		with open(outfile, 'r') as file:
185021ed9f5SHeidi Fahim			for line in file:
186021ed9f5SHeidi Fahim				yield line
187021ed9f5SHeidi Fahim
188021ed9f5SHeidi Fahim	def signal_handler(self, sig, frame):
189021ed9f5SHeidi Fahim		logging.error('Build interruption occurred. Cleaning console.')
190021ed9f5SHeidi Fahim		subprocess.call(['stty', 'sane'])
191