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 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' 24128dc4bcSAndy ShevchenkoOUTFILE_PATH = 'test.log' 256ebf5866SFelix Guo 26f3ed003eSAndy Shevchenkodef get_file_path(build_dir, default): 27f3ed003eSAndy Shevchenko if build_dir: 28f3ed003eSAndy Shevchenko default = os.path.join(build_dir, default) 29f3ed003eSAndy Shevchenko return default 30f3ed003eSAndy Shevchenko 316ebf5866SFelix Guoclass ConfigError(Exception): 326ebf5866SFelix Guo """Represents an error trying to configure the Linux kernel.""" 336ebf5866SFelix Guo 346ebf5866SFelix Guo 356ebf5866SFelix Guoclass BuildError(Exception): 366ebf5866SFelix Guo """Represents an error trying to build the Linux kernel.""" 376ebf5866SFelix Guo 386ebf5866SFelix Guo 396ebf5866SFelix Guoclass LinuxSourceTreeOperations(object): 406ebf5866SFelix Guo """An abstraction over command line operations performed on a source tree.""" 416ebf5866SFelix Guo 426ebf5866SFelix Guo def make_mrproper(self): 436ebf5866SFelix Guo try: 445a9fcad7SWill Chen subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT) 456ebf5866SFelix Guo except OSError as e: 461abdd39fSDaniel Latypov raise ConfigError('Could not call make command: ' + str(e)) 476ebf5866SFelix Guo except subprocess.CalledProcessError as e: 481abdd39fSDaniel Latypov raise ConfigError(e.output.decode()) 496ebf5866SFelix Guo 500476e69fSGreg Thelen def make_olddefconfig(self, build_dir, make_options): 516ebf5866SFelix Guo command = ['make', 'ARCH=um', 'olddefconfig'] 520476e69fSGreg Thelen if make_options: 530476e69fSGreg Thelen command.extend(make_options) 546ebf5866SFelix Guo if build_dir: 556ebf5866SFelix Guo command += ['O=' + build_dir] 566ebf5866SFelix Guo try: 575a9fcad7SWill Chen subprocess.check_output(command, stderr=subprocess.STDOUT) 586ebf5866SFelix Guo except OSError as e: 591abdd39fSDaniel Latypov raise ConfigError('Could not call make command: ' + str(e)) 606ebf5866SFelix Guo except subprocess.CalledProcessError as e: 611abdd39fSDaniel Latypov raise ConfigError(e.output.decode()) 626ebf5866SFelix Guo 6367e2fae3SBrendan Higgins def make_allyesconfig(self, build_dir, make_options): 64021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 65021ed9f5SHeidi Fahim 'Enabling all CONFIGs for UML...') 6667e2fae3SBrendan Higgins command = ['make', 'ARCH=um', 'allyesconfig'] 6767e2fae3SBrendan Higgins if make_options: 6867e2fae3SBrendan Higgins command.extend(make_options) 6967e2fae3SBrendan Higgins if build_dir: 7067e2fae3SBrendan Higgins command += ['O=' + build_dir] 71021ed9f5SHeidi Fahim process = subprocess.Popen( 7267e2fae3SBrendan Higgins command, 73021ed9f5SHeidi Fahim stdout=subprocess.DEVNULL, 74021ed9f5SHeidi Fahim stderr=subprocess.STDOUT) 75021ed9f5SHeidi Fahim process.wait() 76021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 77021ed9f5SHeidi Fahim 'Disabling broken configs to run KUnit tests...') 78021ed9f5SHeidi Fahim with ExitStack() as es: 7967e2fae3SBrendan Higgins config = open(get_kconfig_path(build_dir), 'a') 80021ed9f5SHeidi Fahim disable = open(BROKEN_ALLCONFIG_PATH, 'r').read() 81021ed9f5SHeidi Fahim config.write(disable) 82021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 83021ed9f5SHeidi Fahim 'Starting Kernel with all configs takes a few minutes...') 84021ed9f5SHeidi Fahim 850476e69fSGreg Thelen def make(self, jobs, build_dir, make_options): 866ebf5866SFelix Guo command = ['make', 'ARCH=um', '--jobs=' + str(jobs)] 870476e69fSGreg Thelen if make_options: 880476e69fSGreg Thelen command.extend(make_options) 896ebf5866SFelix Guo if build_dir: 906ebf5866SFelix Guo command += ['O=' + build_dir] 916ebf5866SFelix Guo try: 9239088144SDaniel Latypov proc = subprocess.Popen(command, 9339088144SDaniel Latypov stderr=subprocess.PIPE, 9439088144SDaniel Latypov stdout=subprocess.DEVNULL) 956ebf5866SFelix Guo except OSError as e: 9639088144SDaniel Latypov raise BuildError('Could not call make command: ' + str(e)) 9739088144SDaniel Latypov _, stderr = proc.communicate() 9839088144SDaniel Latypov if proc.returncode != 0: 9939088144SDaniel Latypov raise BuildError(stderr.decode()) 10039088144SDaniel Latypov if stderr: # likely only due to build warnings 10139088144SDaniel Latypov print(stderr.decode()) 1026ebf5866SFelix Guo 103128dc4bcSAndy Shevchenko def linux_bin(self, params, timeout, build_dir): 1046ebf5866SFelix Guo """Runs the Linux UML binary. Must be named 'linux'.""" 105f3ed003eSAndy Shevchenko linux_bin = get_file_path(build_dir, 'linux') 106128dc4bcSAndy Shevchenko outfile = get_outfile_path(build_dir) 107021ed9f5SHeidi Fahim with open(outfile, 'w') as output: 108021ed9f5SHeidi Fahim process = subprocess.Popen([linux_bin] + params, 109021ed9f5SHeidi Fahim stdout=output, 110021ed9f5SHeidi Fahim stderr=subprocess.STDOUT) 111021ed9f5SHeidi Fahim process.wait(timeout) 1126ebf5866SFelix Guo 1136ebf5866SFelix Guodef get_kconfig_path(build_dir): 114f3ed003eSAndy Shevchenko return get_file_path(build_dir, KCONFIG_PATH) 1156ebf5866SFelix Guo 116fcdb0bc0SAndy Shevchenkodef get_kunitconfig_path(build_dir): 117f3ed003eSAndy Shevchenko return get_file_path(build_dir, KUNITCONFIG_PATH) 118fcdb0bc0SAndy Shevchenko 119128dc4bcSAndy Shevchenkodef get_outfile_path(build_dir): 120f3ed003eSAndy Shevchenko return get_file_path(build_dir, OUTFILE_PATH) 121128dc4bcSAndy Shevchenko 1226ebf5866SFelix Guoclass LinuxSourceTree(object): 1236ebf5866SFelix Guo """Represents a Linux kernel source tree with KUnit tests.""" 1246ebf5866SFelix Guo 1256ebf5866SFelix Guo def __init__(self): 1266ebf5866SFelix Guo self._ops = LinuxSourceTreeOperations() 127021ed9f5SHeidi Fahim signal.signal(signal.SIGINT, self.signal_handler) 1286ebf5866SFelix Guo 1296ebf5866SFelix Guo def clean(self): 1306ebf5866SFelix Guo try: 1316ebf5866SFelix Guo self._ops.make_mrproper() 1326ebf5866SFelix Guo except ConfigError as e: 1336ebf5866SFelix Guo logging.error(e) 1346ebf5866SFelix Guo return False 1356ebf5866SFelix Guo return True 1366ebf5866SFelix Guo 137fcdb0bc0SAndy Shevchenko def create_kunitconfig(self, build_dir, defconfig=DEFAULT_KUNITCONFIG_PATH): 138fcdb0bc0SAndy Shevchenko kunitconfig_path = get_kunitconfig_path(build_dir) 139fcdb0bc0SAndy Shevchenko if not os.path.exists(kunitconfig_path): 140fcdb0bc0SAndy Shevchenko shutil.copyfile(defconfig, kunitconfig_path) 141fcdb0bc0SAndy Shevchenko 142fcdb0bc0SAndy Shevchenko def read_kunitconfig(self, build_dir): 143fcdb0bc0SAndy Shevchenko kunitconfig_path = get_kunitconfig_path(build_dir) 144fcdb0bc0SAndy Shevchenko self._kconfig = kunit_config.Kconfig() 145fcdb0bc0SAndy Shevchenko self._kconfig.read_from_file(kunitconfig_path) 146fcdb0bc0SAndy Shevchenko 147dde54b94SHeidi Fahim def validate_config(self, build_dir): 148dde54b94SHeidi Fahim kconfig_path = get_kconfig_path(build_dir) 149dde54b94SHeidi Fahim validated_kconfig = kunit_config.Kconfig() 150dde54b94SHeidi Fahim validated_kconfig.read_from_file(kconfig_path) 151dde54b94SHeidi Fahim if not self._kconfig.is_subset_of(validated_kconfig): 152dde54b94SHeidi Fahim invalid = self._kconfig.entries() - validated_kconfig.entries() 153dde54b94SHeidi Fahim message = 'Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \ 154dde54b94SHeidi Fahim 'but not in .config: %s' % ( 155dde54b94SHeidi Fahim ', '.join([str(e) for e in invalid]) 156dde54b94SHeidi Fahim ) 157dde54b94SHeidi Fahim logging.error(message) 158dde54b94SHeidi Fahim return False 159dde54b94SHeidi Fahim return True 160dde54b94SHeidi Fahim 1610476e69fSGreg Thelen def build_config(self, build_dir, make_options): 1626ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 1636ebf5866SFelix Guo if build_dir and not os.path.exists(build_dir): 1646ebf5866SFelix Guo os.mkdir(build_dir) 1656ebf5866SFelix Guo self._kconfig.write_to_file(kconfig_path) 1666ebf5866SFelix Guo try: 1670476e69fSGreg Thelen self._ops.make_olddefconfig(build_dir, make_options) 1686ebf5866SFelix Guo except ConfigError as e: 1696ebf5866SFelix Guo logging.error(e) 1706ebf5866SFelix Guo return False 171dde54b94SHeidi Fahim return self.validate_config(build_dir) 1726ebf5866SFelix Guo 1730476e69fSGreg Thelen def build_reconfig(self, build_dir, make_options): 17414ee5cfdSSeongJae Park """Creates a new .config if it is not a subset of the .kunitconfig.""" 1756ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 1766ebf5866SFelix Guo if os.path.exists(kconfig_path): 1776ebf5866SFelix Guo existing_kconfig = kunit_config.Kconfig() 1786ebf5866SFelix Guo existing_kconfig.read_from_file(kconfig_path) 1796ebf5866SFelix Guo if not self._kconfig.is_subset_of(existing_kconfig): 1806ebf5866SFelix Guo print('Regenerating .config ...') 1816ebf5866SFelix Guo os.remove(kconfig_path) 1820476e69fSGreg Thelen return self.build_config(build_dir, make_options) 1836ebf5866SFelix Guo else: 1846ebf5866SFelix Guo return True 1856ebf5866SFelix Guo else: 1866ebf5866SFelix Guo print('Generating .config ...') 1870476e69fSGreg Thelen return self.build_config(build_dir, make_options) 1886ebf5866SFelix Guo 1890476e69fSGreg Thelen def build_um_kernel(self, alltests, jobs, build_dir, make_options): 1906ebf5866SFelix Guo try: 19167e2fae3SBrendan Higgins if alltests: 19267e2fae3SBrendan Higgins self._ops.make_allyesconfig(build_dir, make_options) 1930476e69fSGreg Thelen self._ops.make_olddefconfig(build_dir, make_options) 1940476e69fSGreg Thelen self._ops.make(jobs, build_dir, make_options) 1956ebf5866SFelix Guo except (ConfigError, BuildError) as e: 1966ebf5866SFelix Guo logging.error(e) 1976ebf5866SFelix Guo return False 198dde54b94SHeidi Fahim return self.validate_config(build_dir) 1996ebf5866SFelix Guo 200021ed9f5SHeidi Fahim def run_kernel(self, args=[], build_dir='', timeout=None): 201*65a4e529SDavid Gow args.extend(['mem=1G', 'console=tty']) 202128dc4bcSAndy Shevchenko self._ops.linux_bin(args, timeout, build_dir) 203128dc4bcSAndy Shevchenko outfile = get_outfile_path(build_dir) 204021ed9f5SHeidi Fahim subprocess.call(['stty', 'sane']) 205021ed9f5SHeidi Fahim with open(outfile, 'r') as file: 206021ed9f5SHeidi Fahim for line in file: 207021ed9f5SHeidi Fahim yield line 208021ed9f5SHeidi Fahim 209021ed9f5SHeidi Fahim def signal_handler(self, sig, frame): 210021ed9f5SHeidi Fahim logging.error('Build interruption occurred. Cleaning console.') 211021ed9f5SHeidi Fahim subprocess.call(['stty', 'sane']) 212