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 14*09641f7cSDaniel Latypovfrom typing import Iterator 15021ed9f5SHeidi Fahim 16021ed9f5SHeidi Fahimfrom contextlib import ExitStack 176ebf5866SFelix Guo 186ebf5866SFelix Guoimport kunit_config 19021ed9f5SHeidi Fahimimport kunit_parser 206ebf5866SFelix Guo 216ebf5866SFelix GuoKCONFIG_PATH = '.config' 22fcdb0bc0SAndy ShevchenkoKUNITCONFIG_PATH = '.kunitconfig' 23fcdb0bc0SAndy ShevchenkoDEFAULT_KUNITCONFIG_PATH = 'arch/um/configs/kunit_defconfig' 24021ed9f5SHeidi FahimBROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config' 25128dc4bcSAndy ShevchenkoOUTFILE_PATH = 'test.log' 266ebf5866SFelix Guo 27f3ed003eSAndy Shevchenkodef get_file_path(build_dir, default): 28f3ed003eSAndy Shevchenko if build_dir: 29f3ed003eSAndy Shevchenko default = os.path.join(build_dir, default) 30f3ed003eSAndy Shevchenko return default 31f3ed003eSAndy Shevchenko 326ebf5866SFelix Guoclass ConfigError(Exception): 336ebf5866SFelix Guo """Represents an error trying to configure the Linux kernel.""" 346ebf5866SFelix Guo 356ebf5866SFelix Guo 366ebf5866SFelix Guoclass BuildError(Exception): 376ebf5866SFelix Guo """Represents an error trying to build the Linux kernel.""" 386ebf5866SFelix Guo 396ebf5866SFelix Guo 406ebf5866SFelix Guoclass LinuxSourceTreeOperations(object): 416ebf5866SFelix Guo """An abstraction over command line operations performed on a source tree.""" 426ebf5866SFelix Guo 43*09641f7cSDaniel Latypov def make_mrproper(self) -> None: 446ebf5866SFelix Guo try: 455a9fcad7SWill Chen subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT) 466ebf5866SFelix Guo except OSError as e: 471abdd39fSDaniel Latypov raise ConfigError('Could not call make command: ' + str(e)) 486ebf5866SFelix Guo except subprocess.CalledProcessError as e: 491abdd39fSDaniel Latypov raise ConfigError(e.output.decode()) 506ebf5866SFelix Guo 51*09641f7cSDaniel Latypov def make_olddefconfig(self, build_dir, make_options) -> None: 526ebf5866SFelix Guo command = ['make', 'ARCH=um', 'olddefconfig'] 530476e69fSGreg Thelen if make_options: 540476e69fSGreg Thelen command.extend(make_options) 556ebf5866SFelix Guo if build_dir: 566ebf5866SFelix Guo command += ['O=' + build_dir] 576ebf5866SFelix Guo try: 585a9fcad7SWill Chen subprocess.check_output(command, stderr=subprocess.STDOUT) 596ebf5866SFelix Guo except OSError as e: 601abdd39fSDaniel Latypov raise ConfigError('Could not call make command: ' + str(e)) 616ebf5866SFelix Guo except subprocess.CalledProcessError as e: 621abdd39fSDaniel Latypov raise ConfigError(e.output.decode()) 636ebf5866SFelix Guo 64*09641f7cSDaniel Latypov def make_allyesconfig(self, build_dir, make_options) -> None: 65021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 66021ed9f5SHeidi Fahim 'Enabling all CONFIGs for UML...') 6767e2fae3SBrendan Higgins command = ['make', 'ARCH=um', 'allyesconfig'] 6867e2fae3SBrendan Higgins if make_options: 6967e2fae3SBrendan Higgins command.extend(make_options) 7067e2fae3SBrendan Higgins if build_dir: 7167e2fae3SBrendan Higgins command += ['O=' + build_dir] 72021ed9f5SHeidi Fahim process = subprocess.Popen( 7367e2fae3SBrendan Higgins command, 74021ed9f5SHeidi Fahim stdout=subprocess.DEVNULL, 75021ed9f5SHeidi Fahim stderr=subprocess.STDOUT) 76021ed9f5SHeidi Fahim process.wait() 77021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 78021ed9f5SHeidi Fahim 'Disabling broken configs to run KUnit tests...') 79021ed9f5SHeidi Fahim with ExitStack() as es: 8067e2fae3SBrendan Higgins config = open(get_kconfig_path(build_dir), 'a') 81021ed9f5SHeidi Fahim disable = open(BROKEN_ALLCONFIG_PATH, 'r').read() 82021ed9f5SHeidi Fahim config.write(disable) 83021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 84021ed9f5SHeidi Fahim 'Starting Kernel with all configs takes a few minutes...') 85021ed9f5SHeidi Fahim 86*09641f7cSDaniel Latypov def make(self, jobs, build_dir, make_options) -> None: 876ebf5866SFelix Guo command = ['make', 'ARCH=um', '--jobs=' + str(jobs)] 880476e69fSGreg Thelen if make_options: 890476e69fSGreg Thelen command.extend(make_options) 906ebf5866SFelix Guo if build_dir: 916ebf5866SFelix Guo command += ['O=' + build_dir] 926ebf5866SFelix Guo try: 9339088144SDaniel Latypov proc = subprocess.Popen(command, 9439088144SDaniel Latypov stderr=subprocess.PIPE, 9539088144SDaniel Latypov stdout=subprocess.DEVNULL) 966ebf5866SFelix Guo except OSError as e: 9739088144SDaniel Latypov raise BuildError('Could not call make command: ' + str(e)) 9839088144SDaniel Latypov _, stderr = proc.communicate() 9939088144SDaniel Latypov if proc.returncode != 0: 10039088144SDaniel Latypov raise BuildError(stderr.decode()) 10139088144SDaniel Latypov if stderr: # likely only due to build warnings 10239088144SDaniel Latypov print(stderr.decode()) 1036ebf5866SFelix Guo 104*09641f7cSDaniel Latypov def linux_bin(self, params, timeout, build_dir) -> None: 1056ebf5866SFelix Guo """Runs the Linux UML binary. Must be named 'linux'.""" 106f3ed003eSAndy Shevchenko linux_bin = get_file_path(build_dir, 'linux') 107128dc4bcSAndy Shevchenko outfile = get_outfile_path(build_dir) 108021ed9f5SHeidi Fahim with open(outfile, 'w') as output: 109021ed9f5SHeidi Fahim process = subprocess.Popen([linux_bin] + params, 110021ed9f5SHeidi Fahim stdout=output, 111021ed9f5SHeidi Fahim stderr=subprocess.STDOUT) 112021ed9f5SHeidi Fahim process.wait(timeout) 1136ebf5866SFelix Guo 114*09641f7cSDaniel Latypovdef get_kconfig_path(build_dir) -> str: 115f3ed003eSAndy Shevchenko return get_file_path(build_dir, KCONFIG_PATH) 1166ebf5866SFelix Guo 117*09641f7cSDaniel Latypovdef get_kunitconfig_path(build_dir) -> str: 118f3ed003eSAndy Shevchenko return get_file_path(build_dir, KUNITCONFIG_PATH) 119fcdb0bc0SAndy Shevchenko 120*09641f7cSDaniel Latypovdef get_outfile_path(build_dir) -> str: 121f3ed003eSAndy Shevchenko return get_file_path(build_dir, OUTFILE_PATH) 122128dc4bcSAndy Shevchenko 1236ebf5866SFelix Guoclass LinuxSourceTree(object): 1246ebf5866SFelix Guo """Represents a Linux kernel source tree with KUnit tests.""" 1256ebf5866SFelix Guo 126*09641f7cSDaniel Latypov def __init__(self) -> None: 1276ebf5866SFelix Guo self._ops = LinuxSourceTreeOperations() 128021ed9f5SHeidi Fahim signal.signal(signal.SIGINT, self.signal_handler) 1296ebf5866SFelix Guo 130*09641f7cSDaniel Latypov def clean(self) -> bool: 1316ebf5866SFelix Guo try: 1326ebf5866SFelix Guo self._ops.make_mrproper() 1336ebf5866SFelix Guo except ConfigError as e: 1346ebf5866SFelix Guo logging.error(e) 1356ebf5866SFelix Guo return False 1366ebf5866SFelix Guo return True 1376ebf5866SFelix Guo 138*09641f7cSDaniel Latypov def create_kunitconfig(self, build_dir, defconfig=DEFAULT_KUNITCONFIG_PATH) -> None: 139fcdb0bc0SAndy Shevchenko kunitconfig_path = get_kunitconfig_path(build_dir) 140fcdb0bc0SAndy Shevchenko if not os.path.exists(kunitconfig_path): 141fcdb0bc0SAndy Shevchenko shutil.copyfile(defconfig, kunitconfig_path) 142fcdb0bc0SAndy Shevchenko 143*09641f7cSDaniel Latypov def read_kunitconfig(self, build_dir) -> None: 144fcdb0bc0SAndy Shevchenko kunitconfig_path = get_kunitconfig_path(build_dir) 145fcdb0bc0SAndy Shevchenko self._kconfig = kunit_config.Kconfig() 146fcdb0bc0SAndy Shevchenko self._kconfig.read_from_file(kunitconfig_path) 147fcdb0bc0SAndy Shevchenko 148*09641f7cSDaniel Latypov def validate_config(self, build_dir) -> bool: 149dde54b94SHeidi Fahim kconfig_path = get_kconfig_path(build_dir) 150dde54b94SHeidi Fahim validated_kconfig = kunit_config.Kconfig() 151dde54b94SHeidi Fahim validated_kconfig.read_from_file(kconfig_path) 152dde54b94SHeidi Fahim if not self._kconfig.is_subset_of(validated_kconfig): 153dde54b94SHeidi Fahim invalid = self._kconfig.entries() - validated_kconfig.entries() 154dde54b94SHeidi Fahim message = 'Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \ 155dde54b94SHeidi Fahim 'but not in .config: %s' % ( 156dde54b94SHeidi Fahim ', '.join([str(e) for e in invalid]) 157dde54b94SHeidi Fahim ) 158dde54b94SHeidi Fahim logging.error(message) 159dde54b94SHeidi Fahim return False 160dde54b94SHeidi Fahim return True 161dde54b94SHeidi Fahim 162*09641f7cSDaniel Latypov def build_config(self, build_dir, make_options) -> bool: 1636ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 1646ebf5866SFelix Guo if build_dir and not os.path.exists(build_dir): 1656ebf5866SFelix Guo os.mkdir(build_dir) 1666ebf5866SFelix Guo self._kconfig.write_to_file(kconfig_path) 1676ebf5866SFelix Guo try: 1680476e69fSGreg Thelen self._ops.make_olddefconfig(build_dir, make_options) 1696ebf5866SFelix Guo except ConfigError as e: 1706ebf5866SFelix Guo logging.error(e) 1716ebf5866SFelix Guo return False 172dde54b94SHeidi Fahim return self.validate_config(build_dir) 1736ebf5866SFelix Guo 174*09641f7cSDaniel Latypov def build_reconfig(self, build_dir, make_options) -> bool: 17514ee5cfdSSeongJae Park """Creates a new .config if it is not a subset of the .kunitconfig.""" 1766ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 1776ebf5866SFelix Guo if os.path.exists(kconfig_path): 1786ebf5866SFelix Guo existing_kconfig = kunit_config.Kconfig() 1796ebf5866SFelix Guo existing_kconfig.read_from_file(kconfig_path) 1806ebf5866SFelix Guo if not self._kconfig.is_subset_of(existing_kconfig): 1816ebf5866SFelix Guo print('Regenerating .config ...') 1826ebf5866SFelix Guo os.remove(kconfig_path) 1830476e69fSGreg Thelen return self.build_config(build_dir, make_options) 1846ebf5866SFelix Guo else: 1856ebf5866SFelix Guo return True 1866ebf5866SFelix Guo else: 1876ebf5866SFelix Guo print('Generating .config ...') 1880476e69fSGreg Thelen return self.build_config(build_dir, make_options) 1896ebf5866SFelix Guo 190*09641f7cSDaniel Latypov def build_um_kernel(self, alltests, jobs, build_dir, make_options) -> bool: 1916ebf5866SFelix Guo try: 19267e2fae3SBrendan Higgins if alltests: 19367e2fae3SBrendan Higgins self._ops.make_allyesconfig(build_dir, make_options) 1940476e69fSGreg Thelen self._ops.make_olddefconfig(build_dir, make_options) 1950476e69fSGreg Thelen self._ops.make(jobs, build_dir, make_options) 1966ebf5866SFelix Guo except (ConfigError, BuildError) as e: 1976ebf5866SFelix Guo logging.error(e) 1986ebf5866SFelix Guo return False 199dde54b94SHeidi Fahim return self.validate_config(build_dir) 2006ebf5866SFelix Guo 201*09641f7cSDaniel Latypov def run_kernel(self, args=[], build_dir='', timeout=None) -> Iterator[str]: 20265a4e529SDavid Gow args.extend(['mem=1G', 'console=tty']) 203128dc4bcSAndy Shevchenko self._ops.linux_bin(args, timeout, build_dir) 204128dc4bcSAndy Shevchenko outfile = get_outfile_path(build_dir) 205021ed9f5SHeidi Fahim subprocess.call(['stty', 'sane']) 206021ed9f5SHeidi Fahim with open(outfile, 'r') as file: 207021ed9f5SHeidi Fahim for line in file: 208021ed9f5SHeidi Fahim yield line 209021ed9f5SHeidi Fahim 210*09641f7cSDaniel Latypov def signal_handler(self, sig, frame) -> None: 211021ed9f5SHeidi Fahim logging.error('Build interruption occurred. Cleaning console.') 212021ed9f5SHeidi Fahim subprocess.call(['stty', 'sane']) 213