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 <felixguoxiuping@gmail.com>
76ebf5866SFelix Guo# Author: Brendan Higgins <brendanhiggins@google.com>
86ebf5866SFelix Guo
96ebf5866SFelix Guoimport logging
106ebf5866SFelix Guoimport subprocess
116ebf5866SFelix Guoimport os
12fcdb0bc0SAndy Shevchenkoimport shutil
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'
21fcdb0bc0SAndy ShevchenkoKUNITCONFIG_PATH = '.kunitconfig'
22fcdb0bc0SAndy ShevchenkoDEFAULT_KUNITCONFIG_PATH = 'arch/um/configs/kunit_defconfig'
23021ed9f5SHeidi FahimBROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config'
246ebf5866SFelix Guo
256ebf5866SFelix Guoclass ConfigError(Exception):
266ebf5866SFelix Guo	"""Represents an error trying to configure the Linux kernel."""
276ebf5866SFelix Guo
286ebf5866SFelix Guo
296ebf5866SFelix Guoclass BuildError(Exception):
306ebf5866SFelix Guo	"""Represents an error trying to build the Linux kernel."""
316ebf5866SFelix Guo
326ebf5866SFelix Guo
336ebf5866SFelix Guoclass LinuxSourceTreeOperations(object):
346ebf5866SFelix Guo	"""An abstraction over command line operations performed on a source tree."""
356ebf5866SFelix Guo
366ebf5866SFelix Guo	def make_mrproper(self):
376ebf5866SFelix Guo		try:
385a9fcad7SWill Chen			subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT)
396ebf5866SFelix Guo		except OSError as e:
401abdd39fSDaniel Latypov			raise ConfigError('Could not call make command: ' + str(e))
416ebf5866SFelix Guo		except subprocess.CalledProcessError as e:
421abdd39fSDaniel Latypov			raise ConfigError(e.output.decode())
436ebf5866SFelix Guo
440476e69fSGreg Thelen	def make_olddefconfig(self, build_dir, make_options):
456ebf5866SFelix Guo		command = ['make', 'ARCH=um', 'olddefconfig']
460476e69fSGreg Thelen		if make_options:
470476e69fSGreg Thelen			command.extend(make_options)
486ebf5866SFelix Guo		if build_dir:
496ebf5866SFelix Guo			command += ['O=' + build_dir]
506ebf5866SFelix Guo		try:
515a9fcad7SWill Chen			subprocess.check_output(command, stderr=subprocess.STDOUT)
526ebf5866SFelix Guo		except OSError as e:
531abdd39fSDaniel Latypov			raise ConfigError('Could not call make command: ' + str(e))
546ebf5866SFelix Guo		except subprocess.CalledProcessError as e:
551abdd39fSDaniel Latypov			raise ConfigError(e.output.decode())
566ebf5866SFelix Guo
5767e2fae3SBrendan Higgins	def make_allyesconfig(self, build_dir, make_options):
58021ed9f5SHeidi Fahim		kunit_parser.print_with_timestamp(
59021ed9f5SHeidi Fahim			'Enabling all CONFIGs for UML...')
6067e2fae3SBrendan Higgins		command = ['make', 'ARCH=um', 'allyesconfig']
6167e2fae3SBrendan Higgins		if make_options:
6267e2fae3SBrendan Higgins			command.extend(make_options)
6367e2fae3SBrendan Higgins		if build_dir:
6467e2fae3SBrendan Higgins			command += ['O=' + build_dir]
65021ed9f5SHeidi Fahim		process = subprocess.Popen(
6667e2fae3SBrendan Higgins			command,
67021ed9f5SHeidi Fahim			stdout=subprocess.DEVNULL,
68021ed9f5SHeidi Fahim			stderr=subprocess.STDOUT)
69021ed9f5SHeidi Fahim		process.wait()
70021ed9f5SHeidi Fahim		kunit_parser.print_with_timestamp(
71021ed9f5SHeidi Fahim			'Disabling broken configs to run KUnit tests...')
72021ed9f5SHeidi Fahim		with ExitStack() as es:
7367e2fae3SBrendan Higgins			config = open(get_kconfig_path(build_dir), 'a')
74021ed9f5SHeidi Fahim			disable = open(BROKEN_ALLCONFIG_PATH, 'r').read()
75021ed9f5SHeidi Fahim			config.write(disable)
76021ed9f5SHeidi Fahim		kunit_parser.print_with_timestamp(
77021ed9f5SHeidi Fahim			'Starting Kernel with all configs takes a few minutes...')
78021ed9f5SHeidi Fahim
790476e69fSGreg Thelen	def make(self, jobs, build_dir, make_options):
806ebf5866SFelix Guo		command = ['make', 'ARCH=um', '--jobs=' + str(jobs)]
810476e69fSGreg Thelen		if make_options:
820476e69fSGreg Thelen			command.extend(make_options)
836ebf5866SFelix Guo		if build_dir:
846ebf5866SFelix Guo			command += ['O=' + build_dir]
856ebf5866SFelix Guo		try:
865a9fcad7SWill Chen			subprocess.check_output(command, stderr=subprocess.STDOUT)
876ebf5866SFelix Guo		except OSError as e:
881abdd39fSDaniel Latypov			raise BuildError('Could not call execute make: ' + str(e))
896ebf5866SFelix Guo		except subprocess.CalledProcessError as e:
901abdd39fSDaniel Latypov			raise BuildError(e.output.decode())
916ebf5866SFelix Guo
92021ed9f5SHeidi Fahim	def linux_bin(self, params, timeout, build_dir, outfile):
936ebf5866SFelix Guo		"""Runs the Linux UML binary. Must be named 'linux'."""
946ebf5866SFelix Guo		linux_bin = './linux'
956ebf5866SFelix Guo		if build_dir:
966ebf5866SFelix Guo			linux_bin = os.path.join(build_dir, 'linux')
97021ed9f5SHeidi Fahim		with open(outfile, 'w') as output:
98021ed9f5SHeidi Fahim			process = subprocess.Popen([linux_bin] + params,
99021ed9f5SHeidi Fahim						   stdout=output,
100021ed9f5SHeidi Fahim						   stderr=subprocess.STDOUT)
101021ed9f5SHeidi Fahim			process.wait(timeout)
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
109fcdb0bc0SAndy Shevchenkodef get_kunitconfig_path(build_dir):
110fcdb0bc0SAndy Shevchenko	kunitconfig_path = KUNITCONFIG_PATH
111fcdb0bc0SAndy Shevchenko	if build_dir:
112fcdb0bc0SAndy Shevchenko		kunitconfig_path = os.path.join(build_dir, KUNITCONFIG_PATH)
113fcdb0bc0SAndy Shevchenko	return kunitconfig_path
114fcdb0bc0SAndy Shevchenko
1156ebf5866SFelix Guoclass LinuxSourceTree(object):
1166ebf5866SFelix Guo	"""Represents a Linux kernel source tree with KUnit tests."""
1176ebf5866SFelix Guo
1186ebf5866SFelix Guo	def __init__(self):
1196ebf5866SFelix Guo		self._ops = LinuxSourceTreeOperations()
120021ed9f5SHeidi Fahim		signal.signal(signal.SIGINT, self.signal_handler)
1216ebf5866SFelix Guo
1226ebf5866SFelix Guo	def clean(self):
1236ebf5866SFelix Guo		try:
1246ebf5866SFelix Guo			self._ops.make_mrproper()
1256ebf5866SFelix Guo		except ConfigError as e:
1266ebf5866SFelix Guo			logging.error(e)
1276ebf5866SFelix Guo			return False
1286ebf5866SFelix Guo		return True
1296ebf5866SFelix Guo
130fcdb0bc0SAndy Shevchenko	def create_kunitconfig(self, build_dir, defconfig=DEFAULT_KUNITCONFIG_PATH):
131fcdb0bc0SAndy Shevchenko		kunitconfig_path = get_kunitconfig_path(build_dir)
132fcdb0bc0SAndy Shevchenko		if not os.path.exists(kunitconfig_path):
133fcdb0bc0SAndy Shevchenko			shutil.copyfile(defconfig, kunitconfig_path)
134fcdb0bc0SAndy Shevchenko
135fcdb0bc0SAndy Shevchenko	def read_kunitconfig(self, build_dir):
136fcdb0bc0SAndy Shevchenko		kunitconfig_path = get_kunitconfig_path(build_dir)
137fcdb0bc0SAndy Shevchenko		self._kconfig = kunit_config.Kconfig()
138fcdb0bc0SAndy Shevchenko		self._kconfig.read_from_file(kunitconfig_path)
139fcdb0bc0SAndy Shevchenko
140dde54b94SHeidi Fahim	def validate_config(self, build_dir):
141dde54b94SHeidi Fahim		kconfig_path = get_kconfig_path(build_dir)
142dde54b94SHeidi Fahim		validated_kconfig = kunit_config.Kconfig()
143dde54b94SHeidi Fahim		validated_kconfig.read_from_file(kconfig_path)
144dde54b94SHeidi Fahim		if not self._kconfig.is_subset_of(validated_kconfig):
145dde54b94SHeidi Fahim			invalid = self._kconfig.entries() - validated_kconfig.entries()
146dde54b94SHeidi Fahim			message = 'Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \
147dde54b94SHeidi Fahim					  'but not in .config: %s' % (
148dde54b94SHeidi Fahim					', '.join([str(e) for e in invalid])
149dde54b94SHeidi Fahim			)
150dde54b94SHeidi Fahim			logging.error(message)
151dde54b94SHeidi Fahim			return False
152dde54b94SHeidi Fahim		return True
153dde54b94SHeidi Fahim
1540476e69fSGreg Thelen	def build_config(self, build_dir, make_options):
1556ebf5866SFelix Guo		kconfig_path = get_kconfig_path(build_dir)
1566ebf5866SFelix Guo		if build_dir and not os.path.exists(build_dir):
1576ebf5866SFelix Guo			os.mkdir(build_dir)
1586ebf5866SFelix Guo		self._kconfig.write_to_file(kconfig_path)
1596ebf5866SFelix Guo		try:
1600476e69fSGreg Thelen			self._ops.make_olddefconfig(build_dir, make_options)
1616ebf5866SFelix Guo		except ConfigError as e:
1626ebf5866SFelix Guo			logging.error(e)
1636ebf5866SFelix Guo			return False
164dde54b94SHeidi Fahim		return self.validate_config(build_dir)
1656ebf5866SFelix Guo
1660476e69fSGreg Thelen	def build_reconfig(self, build_dir, make_options):
16714ee5cfdSSeongJae Park		"""Creates a new .config if it is not a subset of the .kunitconfig."""
1686ebf5866SFelix Guo		kconfig_path = get_kconfig_path(build_dir)
1696ebf5866SFelix Guo		if os.path.exists(kconfig_path):
1706ebf5866SFelix Guo			existing_kconfig = kunit_config.Kconfig()
1716ebf5866SFelix Guo			existing_kconfig.read_from_file(kconfig_path)
1726ebf5866SFelix Guo			if not self._kconfig.is_subset_of(existing_kconfig):
1736ebf5866SFelix Guo				print('Regenerating .config ...')
1746ebf5866SFelix Guo				os.remove(kconfig_path)
1750476e69fSGreg Thelen				return self.build_config(build_dir, make_options)
1766ebf5866SFelix Guo			else:
1776ebf5866SFelix Guo				return True
1786ebf5866SFelix Guo		else:
1796ebf5866SFelix Guo			print('Generating .config ...')
1800476e69fSGreg Thelen			return self.build_config(build_dir, make_options)
1816ebf5866SFelix Guo
1820476e69fSGreg Thelen	def build_um_kernel(self, alltests, jobs, build_dir, make_options):
1836ebf5866SFelix Guo		try:
18467e2fae3SBrendan Higgins			if alltests:
18567e2fae3SBrendan Higgins				self._ops.make_allyesconfig(build_dir, make_options)
1860476e69fSGreg Thelen			self._ops.make_olddefconfig(build_dir, make_options)
1870476e69fSGreg Thelen			self._ops.make(jobs, build_dir, make_options)
1886ebf5866SFelix Guo		except (ConfigError, BuildError) as e:
1896ebf5866SFelix Guo			logging.error(e)
1906ebf5866SFelix Guo			return False
191dde54b94SHeidi Fahim		return self.validate_config(build_dir)
1926ebf5866SFelix Guo
193021ed9f5SHeidi Fahim	def run_kernel(self, args=[], build_dir='', timeout=None):
194021ed9f5SHeidi Fahim		args.extend(['mem=1G'])
195021ed9f5SHeidi Fahim		outfile = 'test.log'
196021ed9f5SHeidi Fahim		self._ops.linux_bin(args, timeout, build_dir, outfile)
197021ed9f5SHeidi Fahim		subprocess.call(['stty', 'sane'])
198021ed9f5SHeidi Fahim		with open(outfile, 'r') as file:
199021ed9f5SHeidi Fahim			for line in file:
200021ed9f5SHeidi Fahim				yield line
201021ed9f5SHeidi Fahim
202021ed9f5SHeidi Fahim	def signal_handler(self, sig, frame):
203021ed9f5SHeidi Fahim		logging.error('Build interruption occurred. Cleaning console.')
204021ed9f5SHeidi Fahim		subprocess.call(['stty', 'sane'])
205