2019-09-23 12:02:43 +03:00
# SPDX-License-Identifier: GPL-2.0
#
# Runs UML kernel, collects output, and handles errors.
#
# Copyright (C) 2019, Google LLC.
# Author: Felix Guo <felixguoxiuping@gmail.com>
# Author: Brendan Higgins <brendanhiggins@google.com>
import logging
import subprocess
import os
2020-10-26 19:59:25 +03:00
import shutil
2020-03-16 23:21:25 +03:00
import signal
2021-01-15 03:39:11 +03:00
from typing import Iterator
2020-03-16 23:21:25 +03:00
from contextlib import ExitStack
2019-09-23 12:02:43 +03:00
import kunit_config
2020-03-16 23:21:25 +03:00
import kunit_parser
2019-09-23 12:02:43 +03:00
KCONFIG_PATH = ' .config '
2020-10-26 19:59:25 +03:00
KUNITCONFIG_PATH = ' .kunitconfig '
DEFAULT_KUNITCONFIG_PATH = ' arch/um/configs/kunit_defconfig '
2020-03-16 23:21:25 +03:00
BROKEN_ALLCONFIG_PATH = ' tools/testing/kunit/configs/broken_on_uml.config '
2020-10-26 19:59:26 +03:00
OUTFILE_PATH = ' test.log '
2019-09-23 12:02:43 +03:00
2020-10-26 19:59:27 +03:00
def get_file_path ( build_dir , default ) :
if build_dir :
default = os . path . join ( build_dir , default )
return default
2019-09-23 12:02:43 +03:00
class ConfigError ( Exception ) :
""" Represents an error trying to configure the Linux kernel. """
class BuildError ( Exception ) :
""" Represents an error trying to build the Linux kernel. """
class LinuxSourceTreeOperations ( object ) :
""" An abstraction over command line operations performed on a source tree. """
2021-01-15 03:39:11 +03:00
def make_mrproper ( self ) - > None :
2019-09-23 12:02:43 +03:00
try :
2020-07-09 00:35:43 +03:00
subprocess . check_output ( [ ' make ' , ' mrproper ' ] , stderr = subprocess . STDOUT )
2019-09-23 12:02:43 +03:00
except OSError as e :
2020-09-30 21:31:51 +03:00
raise ConfigError ( ' Could not call make command: ' + str ( e ) )
2019-09-23 12:02:43 +03:00
except subprocess . CalledProcessError as e :
2020-09-30 21:31:51 +03:00
raise ConfigError ( e . output . decode ( ) )
2019-09-23 12:02:43 +03:00
2021-01-15 03:39:11 +03:00
def make_olddefconfig ( self , build_dir , make_options ) - > None :
2019-09-23 12:02:43 +03:00
command = [ ' make ' , ' ARCH=um ' , ' olddefconfig ' ]
2020-03-23 22:04:59 +03:00
if make_options :
command . extend ( make_options )
2019-09-23 12:02:43 +03:00
if build_dir :
command + = [ ' O= ' + build_dir ]
try :
2020-07-09 00:35:43 +03:00
subprocess . check_output ( command , stderr = subprocess . STDOUT )
2019-09-23 12:02:43 +03:00
except OSError as e :
2020-09-30 21:31:51 +03:00
raise ConfigError ( ' Could not call make command: ' + str ( e ) )
2019-09-23 12:02:43 +03:00
except subprocess . CalledProcessError as e :
2020-09-30 21:31:51 +03:00
raise ConfigError ( e . output . decode ( ) )
2019-09-23 12:02:43 +03:00
2021-01-15 03:39:11 +03:00
def make_allyesconfig ( self , build_dir , make_options ) - > None :
2020-03-16 23:21:25 +03:00
kunit_parser . print_with_timestamp (
' Enabling all CONFIGs for UML... ' )
2020-09-24 00:19:38 +03:00
command = [ ' make ' , ' ARCH=um ' , ' allyesconfig ' ]
if make_options :
command . extend ( make_options )
if build_dir :
command + = [ ' O= ' + build_dir ]
2020-03-16 23:21:25 +03:00
process = subprocess . Popen (
2020-09-24 00:19:38 +03:00
command ,
2020-03-16 23:21:25 +03:00
stdout = subprocess . DEVNULL ,
stderr = subprocess . STDOUT )
process . wait ( )
kunit_parser . print_with_timestamp (
' Disabling broken configs to run KUnit tests... ' )
with ExitStack ( ) as es :
2020-09-24 00:19:38 +03:00
config = open ( get_kconfig_path ( build_dir ) , ' a ' )
2020-03-16 23:21:25 +03:00
disable = open ( BROKEN_ALLCONFIG_PATH , ' r ' ) . read ( )
config . write ( disable )
kunit_parser . print_with_timestamp (
' Starting Kernel with all configs takes a few minutes... ' )
2021-01-15 03:39:11 +03:00
def make ( self , jobs , build_dir , make_options ) - > None :
2019-09-23 12:02:43 +03:00
command = [ ' make ' , ' ARCH=um ' , ' --jobs= ' + str ( jobs ) ]
2020-03-23 22:04:59 +03:00
if make_options :
command . extend ( make_options )
2019-09-23 12:02:43 +03:00
if build_dir :
command + = [ ' O= ' + build_dir ]
try :
2020-10-30 01:09:29 +03:00
proc = subprocess . Popen ( command ,
stderr = subprocess . PIPE ,
stdout = subprocess . DEVNULL )
2019-09-23 12:02:43 +03:00
except OSError as e :
2020-10-30 01:09:29 +03:00
raise BuildError ( ' Could not call make command: ' + str ( e ) )
_ , stderr = proc . communicate ( )
if proc . returncode != 0 :
raise BuildError ( stderr . decode ( ) )
if stderr : # likely only due to build warnings
print ( stderr . decode ( ) )
2019-09-23 12:02:43 +03:00
2021-01-15 03:39:11 +03:00
def linux_bin ( self , params , timeout , build_dir ) - > None :
2019-09-23 12:02:43 +03:00
""" Runs the Linux UML binary. Must be named ' linux ' . """
2020-10-26 19:59:27 +03:00
linux_bin = get_file_path ( build_dir , ' linux ' )
2020-10-26 19:59:26 +03:00
outfile = get_outfile_path ( build_dir )
2020-03-16 23:21:25 +03:00
with open ( outfile , ' w ' ) as output :
process = subprocess . Popen ( [ linux_bin ] + params ,
stdout = output ,
stderr = subprocess . STDOUT )
process . wait ( timeout )
2019-09-23 12:02:43 +03:00
2021-01-15 03:39:11 +03:00
def get_kconfig_path ( build_dir ) - > str :
2020-10-26 19:59:27 +03:00
return get_file_path ( build_dir , KCONFIG_PATH )
2019-09-23 12:02:43 +03:00
2021-01-15 03:39:11 +03:00
def get_kunitconfig_path ( build_dir ) - > str :
2020-10-26 19:59:27 +03:00
return get_file_path ( build_dir , KUNITCONFIG_PATH )
2020-10-26 19:59:25 +03:00
2021-01-15 03:39:11 +03:00
def get_outfile_path ( build_dir ) - > str :
2020-10-26 19:59:27 +03:00
return get_file_path ( build_dir , OUTFILE_PATH )
2020-10-26 19:59:26 +03:00
2019-09-23 12:02:43 +03:00
class LinuxSourceTree ( object ) :
""" Represents a Linux kernel source tree with KUnit tests. """
2021-01-15 03:39:11 +03:00
def __init__ ( self ) - > None :
2019-09-23 12:02:43 +03:00
self . _ops = LinuxSourceTreeOperations ( )
2020-03-16 23:21:25 +03:00
signal . signal ( signal . SIGINT , self . signal_handler )
2019-09-23 12:02:43 +03:00
2021-01-15 03:39:11 +03:00
def clean ( self ) - > bool :
2019-09-23 12:02:43 +03:00
try :
self . _ops . make_mrproper ( )
except ConfigError as e :
logging . error ( e )
return False
return True
2021-01-15 03:39:11 +03:00
def create_kunitconfig ( self , build_dir , defconfig = DEFAULT_KUNITCONFIG_PATH ) - > None :
2020-10-26 19:59:25 +03:00
kunitconfig_path = get_kunitconfig_path ( build_dir )
if not os . path . exists ( kunitconfig_path ) :
shutil . copyfile ( defconfig , kunitconfig_path )
2021-01-15 03:39:11 +03:00
def read_kunitconfig ( self , build_dir ) - > None :
2020-10-26 19:59:25 +03:00
kunitconfig_path = get_kunitconfig_path ( build_dir )
self . _kconfig = kunit_config . Kconfig ( )
self . _kconfig . read_from_file ( kunitconfig_path )
2021-01-15 03:39:11 +03:00
def validate_config ( self , build_dir ) - > bool :
2019-11-27 01:36:16 +03:00
kconfig_path = get_kconfig_path ( build_dir )
validated_kconfig = kunit_config . Kconfig ( )
validated_kconfig . read_from_file ( kconfig_path )
if not self . _kconfig . is_subset_of ( validated_kconfig ) :
invalid = self . _kconfig . entries ( ) - validated_kconfig . entries ( )
message = ' Provided Kconfig is not contained in validated .config. Following fields found in kunitconfig, ' \
' but not in .config: %s ' % (
' , ' . join ( [ str ( e ) for e in invalid ] )
)
logging . error ( message )
return False
return True
2021-01-15 03:39:11 +03:00
def build_config ( self , build_dir , make_options ) - > bool :
2019-09-23 12:02:43 +03:00
kconfig_path = get_kconfig_path ( build_dir )
if build_dir and not os . path . exists ( build_dir ) :
os . mkdir ( build_dir )
self . _kconfig . write_to_file ( kconfig_path )
try :
2020-03-23 22:04:59 +03:00
self . _ops . make_olddefconfig ( build_dir , make_options )
2019-09-23 12:02:43 +03:00
except ConfigError as e :
logging . error ( e )
return False
2019-11-27 01:36:16 +03:00
return self . validate_config ( build_dir )
2019-09-23 12:02:43 +03:00
2021-01-15 03:39:11 +03:00
def build_reconfig ( self , build_dir , make_options ) - > bool :
2019-12-20 08:14:07 +03:00
""" Creates a new .config if it is not a subset of the .kunitconfig. """
2019-09-23 12:02:43 +03:00
kconfig_path = get_kconfig_path ( build_dir )
if os . path . exists ( kconfig_path ) :
existing_kconfig = kunit_config . Kconfig ( )
existing_kconfig . read_from_file ( kconfig_path )
if not self . _kconfig . is_subset_of ( existing_kconfig ) :
print ( ' Regenerating .config ... ' )
os . remove ( kconfig_path )
2020-03-23 22:04:59 +03:00
return self . build_config ( build_dir , make_options )
2019-09-23 12:02:43 +03:00
else :
return True
else :
print ( ' Generating .config ... ' )
2020-03-23 22:04:59 +03:00
return self . build_config ( build_dir , make_options )
2019-09-23 12:02:43 +03:00
2021-01-15 03:39:11 +03:00
def build_um_kernel ( self , alltests , jobs , build_dir , make_options ) - > bool :
2019-09-23 12:02:43 +03:00
try :
2020-09-24 00:19:38 +03:00
if alltests :
self . _ops . make_allyesconfig ( build_dir , make_options )
2020-03-23 22:04:59 +03:00
self . _ops . make_olddefconfig ( build_dir , make_options )
self . _ops . make ( jobs , build_dir , make_options )
2019-09-23 12:02:43 +03:00
except ( ConfigError , BuildError ) as e :
logging . error ( e )
return False
2019-11-27 01:36:16 +03:00
return self . validate_config ( build_dir )
2019-09-23 12:02:43 +03:00
2021-01-15 03:39:11 +03:00
def run_kernel ( self , args = [ ] , build_dir = ' ' , timeout = None ) - > Iterator [ str ] :
2020-12-22 10:39:00 +03:00
args . extend ( [ ' mem=1G ' , ' console=tty ' ] )
2020-10-26 19:59:26 +03:00
self . _ops . linux_bin ( args , timeout , build_dir )
outfile = get_outfile_path ( build_dir )
2020-03-16 23:21:25 +03:00
subprocess . call ( [ ' stty ' , ' sane ' ] )
with open ( outfile , ' r ' ) as file :
for line in file :
yield line
2021-01-15 03:39:11 +03:00
def signal_handler ( self , sig , frame ) - > None :
2020-03-16 23:21:25 +03:00
logging . error ( ' Build interruption occurred. Cleaning console. ' )
subprocess . call ( [ ' stty ' , ' sane ' ] )