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