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 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: 376ebf5866SFelix Guo subprocess.check_output(['make', 'mrproper']) 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 436ebf5866SFelix Guo def make_olddefconfig(self, build_dir): 446ebf5866SFelix Guo command = ['make', 'ARCH=um', 'olddefconfig'] 456ebf5866SFelix Guo if build_dir: 466ebf5866SFelix Guo command += ['O=' + build_dir] 476ebf5866SFelix Guo try: 48021ed9f5SHeidi Fahim subprocess.check_output(command, stderr=subprocess.PIPE) 496ebf5866SFelix Guo except OSError as e: 506ebf5866SFelix Guo raise ConfigError('Could not call make command: ' + e) 516ebf5866SFelix Guo except subprocess.CalledProcessError as e: 526ebf5866SFelix Guo raise ConfigError(e.output) 536ebf5866SFelix Guo 54021ed9f5SHeidi Fahim def make_allyesconfig(self): 55021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 56021ed9f5SHeidi Fahim 'Enabling all CONFIGs for UML...') 57021ed9f5SHeidi Fahim process = subprocess.Popen( 58021ed9f5SHeidi Fahim ['make', 'ARCH=um', 'allyesconfig'], 59021ed9f5SHeidi Fahim stdout=subprocess.DEVNULL, 60021ed9f5SHeidi Fahim stderr=subprocess.STDOUT) 61021ed9f5SHeidi Fahim process.wait() 62021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 63021ed9f5SHeidi Fahim 'Disabling broken configs to run KUnit tests...') 64021ed9f5SHeidi Fahim with ExitStack() as es: 65021ed9f5SHeidi Fahim config = open(KCONFIG_PATH, 'a') 66021ed9f5SHeidi Fahim disable = open(BROKEN_ALLCONFIG_PATH, 'r').read() 67021ed9f5SHeidi Fahim config.write(disable) 68021ed9f5SHeidi Fahim kunit_parser.print_with_timestamp( 69021ed9f5SHeidi Fahim 'Starting Kernel with all configs takes a few minutes...') 70021ed9f5SHeidi Fahim 716ebf5866SFelix Guo def make(self, jobs, build_dir): 726ebf5866SFelix Guo command = ['make', 'ARCH=um', '--jobs=' + str(jobs)] 736ebf5866SFelix Guo if build_dir: 746ebf5866SFelix Guo command += ['O=' + build_dir] 756ebf5866SFelix Guo try: 766ebf5866SFelix Guo subprocess.check_output(command) 776ebf5866SFelix Guo except OSError as e: 786ebf5866SFelix Guo raise BuildError('Could not call execute make: ' + e) 796ebf5866SFelix Guo except subprocess.CalledProcessError as e: 806ebf5866SFelix Guo raise BuildError(e.output) 816ebf5866SFelix Guo 82021ed9f5SHeidi Fahim def linux_bin(self, params, timeout, build_dir, outfile): 836ebf5866SFelix Guo """Runs the Linux UML binary. Must be named 'linux'.""" 846ebf5866SFelix Guo linux_bin = './linux' 856ebf5866SFelix Guo if build_dir: 866ebf5866SFelix Guo linux_bin = os.path.join(build_dir, 'linux') 87021ed9f5SHeidi Fahim with open(outfile, 'w') as output: 88021ed9f5SHeidi Fahim process = subprocess.Popen([linux_bin] + params, 89021ed9f5SHeidi Fahim stdout=output, 90021ed9f5SHeidi Fahim stderr=subprocess.STDOUT) 91021ed9f5SHeidi Fahim process.wait(timeout) 926ebf5866SFelix Guo 936ebf5866SFelix Guo 946ebf5866SFelix Guodef get_kconfig_path(build_dir): 956ebf5866SFelix Guo kconfig_path = KCONFIG_PATH 966ebf5866SFelix Guo if build_dir: 976ebf5866SFelix Guo kconfig_path = os.path.join(build_dir, KCONFIG_PATH) 986ebf5866SFelix Guo return kconfig_path 996ebf5866SFelix Guo 1006ebf5866SFelix Guoclass LinuxSourceTree(object): 1016ebf5866SFelix Guo """Represents a Linux kernel source tree with KUnit tests.""" 1026ebf5866SFelix Guo 1036ebf5866SFelix Guo def __init__(self): 1046ebf5866SFelix Guo self._kconfig = kunit_config.Kconfig() 105e3212513SSeongJae Park self._kconfig.read_from_file(kunitconfig_path) 1066ebf5866SFelix Guo self._ops = LinuxSourceTreeOperations() 107021ed9f5SHeidi Fahim signal.signal(signal.SIGINT, self.signal_handler) 1086ebf5866SFelix Guo 1096ebf5866SFelix Guo def clean(self): 1106ebf5866SFelix Guo try: 1116ebf5866SFelix Guo self._ops.make_mrproper() 1126ebf5866SFelix Guo except ConfigError as e: 1136ebf5866SFelix Guo logging.error(e) 1146ebf5866SFelix Guo return False 1156ebf5866SFelix Guo return True 1166ebf5866SFelix Guo 117dde54b94SHeidi Fahim def validate_config(self, build_dir): 118dde54b94SHeidi Fahim kconfig_path = get_kconfig_path(build_dir) 119dde54b94SHeidi Fahim validated_kconfig = kunit_config.Kconfig() 120dde54b94SHeidi Fahim validated_kconfig.read_from_file(kconfig_path) 121dde54b94SHeidi Fahim if not self._kconfig.is_subset_of(validated_kconfig): 122dde54b94SHeidi Fahim invalid = self._kconfig.entries() - validated_kconfig.entries() 123dde54b94SHeidi Fahim message = 'Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \ 124dde54b94SHeidi Fahim 'but not in .config: %s' % ( 125dde54b94SHeidi Fahim ', '.join([str(e) for e in invalid]) 126dde54b94SHeidi Fahim ) 127dde54b94SHeidi Fahim logging.error(message) 128dde54b94SHeidi Fahim return False 129dde54b94SHeidi Fahim return True 130dde54b94SHeidi Fahim 1316ebf5866SFelix Guo def build_config(self, build_dir): 1326ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 1336ebf5866SFelix Guo if build_dir and not os.path.exists(build_dir): 1346ebf5866SFelix Guo os.mkdir(build_dir) 1356ebf5866SFelix Guo self._kconfig.write_to_file(kconfig_path) 1366ebf5866SFelix Guo try: 1376ebf5866SFelix Guo self._ops.make_olddefconfig(build_dir) 1386ebf5866SFelix Guo except ConfigError as e: 1396ebf5866SFelix Guo logging.error(e) 1406ebf5866SFelix Guo return False 141dde54b94SHeidi Fahim return self.validate_config(build_dir) 1426ebf5866SFelix Guo 1436ebf5866SFelix Guo def build_reconfig(self, build_dir): 14414ee5cfdSSeongJae Park """Creates a new .config if it is not a subset of the .kunitconfig.""" 1456ebf5866SFelix Guo kconfig_path = get_kconfig_path(build_dir) 1466ebf5866SFelix Guo if os.path.exists(kconfig_path): 1476ebf5866SFelix Guo existing_kconfig = kunit_config.Kconfig() 1486ebf5866SFelix Guo existing_kconfig.read_from_file(kconfig_path) 1496ebf5866SFelix Guo if not self._kconfig.is_subset_of(existing_kconfig): 1506ebf5866SFelix Guo print('Regenerating .config ...') 1516ebf5866SFelix Guo os.remove(kconfig_path) 1526ebf5866SFelix Guo return self.build_config(build_dir) 1536ebf5866SFelix Guo else: 1546ebf5866SFelix Guo return True 1556ebf5866SFelix Guo else: 1566ebf5866SFelix Guo print('Generating .config ...') 1576ebf5866SFelix Guo return self.build_config(build_dir) 1586ebf5866SFelix Guo 159021ed9f5SHeidi Fahim def build_um_kernel(self, alltests, jobs, build_dir): 160021ed9f5SHeidi Fahim if alltests: 161021ed9f5SHeidi Fahim self._ops.make_allyesconfig() 1626ebf5866SFelix Guo try: 1636ebf5866SFelix Guo self._ops.make_olddefconfig(build_dir) 1646ebf5866SFelix Guo self._ops.make(jobs, build_dir) 1656ebf5866SFelix Guo except (ConfigError, BuildError) as e: 1666ebf5866SFelix Guo logging.error(e) 1676ebf5866SFelix Guo return False 168dde54b94SHeidi Fahim return self.validate_config(build_dir) 1696ebf5866SFelix Guo 170021ed9f5SHeidi Fahim def run_kernel(self, args=[], build_dir='', timeout=None): 171021ed9f5SHeidi Fahim args.extend(['mem=1G']) 172021ed9f5SHeidi Fahim outfile = 'test.log' 173021ed9f5SHeidi Fahim self._ops.linux_bin(args, timeout, build_dir, outfile) 174021ed9f5SHeidi Fahim subprocess.call(['stty', 'sane']) 175021ed9f5SHeidi Fahim with open(outfile, 'r') as file: 176021ed9f5SHeidi Fahim for line in file: 177021ed9f5SHeidi Fahim yield line 178021ed9f5SHeidi Fahim 179021ed9f5SHeidi Fahim def signal_handler(self, sig, frame): 180021ed9f5SHeidi Fahim logging.error('Build interruption occurred. Cleaning console.') 181021ed9f5SHeidi Fahim subprocess.call(['stty', 'sane']) 182