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