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>
2022-01-13 02:36:57 +03:00
import importlib . abc
2021-05-27 00:24:06 +03:00
import importlib . util
2019-09-23 12:02:43 +03:00
import logging
import subprocess
import os
2022-05-12 17:25:55 +03:00
import shlex
2020-10-26 19:59:25 +03:00
import shutil
2020-03-16 23:21:25 +03:00
import signal
2021-10-05 04:13:40 +03:00
import threading
from typing import Iterator , List , Optional , Tuple
2023-03-17 01:06:38 +03:00
from types import FrameType
2020-03-16 23:21:25 +03:00
2019-09-23 12:02:43 +03:00
import kunit_config
2021-05-27 00:24:06 +03:00
import qemu_config
2019-09-23 12:02:43 +03:00
KCONFIG_PATH = ' .config '
2020-10-26 19:59:25 +03:00
KUNITCONFIG_PATH = ' .kunitconfig '
2021-11-20 02:23:16 +03:00
OLD_KUNITCONFIG_PATH = ' last_used_kunitconfig '
2021-05-22 07:42:40 +03:00
DEFAULT_KUNITCONFIG_PATH = ' tools/testing/kunit/configs/default.config '
2022-09-02 23:22:48 +03:00
ALL_TESTS_CONFIG_PATH = ' tools/testing/kunit/configs/all_tests.config '
2022-07-08 19:27:11 +03:00
UML_KCONFIG_PATH = ' tools/testing/kunit/configs/arch_uml.config '
2020-10-26 19:59:26 +03:00
OUTFILE_PATH = ' test.log '
2021-05-27 00:24:06 +03:00
ABS_TOOL_PATH = os . path . abspath ( os . path . dirname ( __file__ ) )
QEMU_CONFIGS_DIR = os . path . join ( ABS_TOOL_PATH , ' qemu_configs ' )
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. """
2022-05-09 23:49:09 +03:00
class LinuxSourceTreeOperations :
2019-09-23 12:02:43 +03:00
""" An abstraction over command line operations performed on a source tree. """
2021-05-27 00:24:06 +03:00
def __init__ ( self , linux_arch : str , cross_compile : Optional [ str ] ) :
self . _linux_arch = linux_arch
self . _cross_compile = cross_compile
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
2022-07-08 19:27:11 +03:00
def make_arch_config ( self , base_kunitconfig : kunit_config . Kconfig ) - > kunit_config . Kconfig :
2022-06-28 01:14:44 +03:00
return base_kunitconfig
2021-05-27 00:24:06 +03:00
2023-03-17 01:06:38 +03:00
def make_olddefconfig ( self , build_dir : str , make_options : Optional [ List [ str ] ] ) - > None :
2022-01-18 22:09:22 +03:00
command = [ ' make ' , ' ARCH= ' + self . _linux_arch , ' O= ' + build_dir , ' olddefconfig ' ]
2021-05-27 00:24:06 +03:00
if self . _cross_compile :
command + = [ ' CROSS_COMPILE= ' + self . _cross_compile ]
2020-03-23 22:04:59 +03:00
if make_options :
command . extend ( make_options )
2021-05-27 00:24:06 +03:00
print ( ' Populating config with: \n $ ' , ' ' . join ( command ) )
2019-09-23 12:02:43 +03:00
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
2023-03-17 01:06:38 +03:00
def make ( self , jobs : int , build_dir : str , make_options : Optional [ List [ str ] ] ) - > None :
2022-01-18 22:09:22 +03:00
command = [ ' make ' , ' ARCH= ' + self . _linux_arch , ' O= ' + build_dir , ' --jobs= ' + str ( jobs ) ]
2021-05-27 00:24:06 +03:00
if make_options :
command . extend ( make_options )
if self . _cross_compile :
command + = [ ' CROSS_COMPILE= ' + self . _cross_compile ]
print ( ' Building with: \n $ ' , ' ' . join ( command ) )
try :
proc = subprocess . Popen ( command ,
stderr = subprocess . PIPE ,
stdout = subprocess . DEVNULL )
except OSError as e :
raise BuildError ( ' Could not call execute make: ' + str ( e ) )
except subprocess . CalledProcessError as e :
raise BuildError ( e . output )
_ , stderr = proc . communicate ( )
if proc . returncode != 0 :
raise BuildError ( stderr . decode ( ) )
if stderr : # likely only due to build warnings
print ( stderr . decode ( ) )
2023-05-01 21:16:10 +03:00
def start ( self , params : List [ str ] , build_dir : str ) - > subprocess . Popen :
2021-10-05 04:13:40 +03:00
raise RuntimeError ( ' not implemented! ' )
2021-05-27 00:24:06 +03:00
class LinuxSourceTreeOperationsQemu ( LinuxSourceTreeOperations ) :
def __init__ ( self , qemu_arch_params : qemu_config . QemuArchParams , cross_compile : Optional [ str ] ) :
super ( ) . __init__ ( linux_arch = qemu_arch_params . linux_arch ,
cross_compile = cross_compile )
self . _kconfig = qemu_arch_params . kconfig
self . _qemu_arch = qemu_arch_params . qemu_arch
self . _kernel_path = qemu_arch_params . kernel_path
self . _kernel_command_line = qemu_arch_params . kernel_command_line + ' kunit_shutdown=reboot '
self . _extra_qemu_params = qemu_arch_params . extra_qemu_params
2023-02-28 13:31:02 +03:00
self . _serial = qemu_arch_params . serial
2021-05-27 00:24:06 +03:00
2022-07-08 19:27:11 +03:00
def make_arch_config ( self , base_kunitconfig : kunit_config . Kconfig ) - > kunit_config . Kconfig :
2021-11-06 04:30:57 +03:00
kconfig = kunit_config . parse_from_string ( self . _kconfig )
2022-06-28 01:14:44 +03:00
kconfig . merge_in_entries ( base_kunitconfig )
return kconfig
2021-05-27 00:24:06 +03:00
2023-05-01 21:16:10 +03:00
def start ( self , params : List [ str ] , build_dir : str ) - > subprocess . Popen :
2021-05-27 00:24:06 +03:00
kernel_path = os . path . join ( build_dir , self . _kernel_path )
qemu_command = [ ' qemu-system- ' + self . _qemu_arch ,
' -nodefaults ' ,
' -m ' , ' 1024 ' ,
' -kernel ' , kernel_path ,
2022-05-12 17:25:55 +03:00
' -append ' , ' ' . join ( params + [ self . _kernel_command_line ] ) ,
2021-05-27 00:24:06 +03:00
' -no-reboot ' ,
' -nographic ' ,
2023-02-28 13:31:02 +03:00
' -serial ' , self . _serial ] + self . _extra_qemu_params
2022-05-12 17:25:55 +03:00
# Note: shlex.join() does what we want, but requires python 3.8+.
print ( ' Running tests with: \n $ ' , ' ' . join ( shlex . quote ( arg ) for arg in qemu_command ) )
return subprocess . Popen ( qemu_command ,
stdin = subprocess . PIPE ,
stdout = subprocess . PIPE ,
stderr = subprocess . STDOUT ,
text = True , errors = ' backslashreplace ' )
2021-05-27 00:24:06 +03:00
class LinuxSourceTreeOperationsUml ( LinuxSourceTreeOperations ) :
""" An abstraction over command line operations performed on a source tree. """
2023-03-17 01:06:38 +03:00
def __init__ ( self , cross_compile : Optional [ str ] = None ) :
2021-05-27 00:24:06 +03:00
super ( ) . __init__ ( linux_arch = ' um ' , cross_compile = cross_compile )
2022-07-08 19:27:11 +03:00
def make_arch_config ( self , base_kunitconfig : kunit_config . Kconfig ) - > kunit_config . Kconfig :
kconfig = kunit_config . parse_file ( UML_KCONFIG_PATH )
kconfig . merge_in_entries ( base_kunitconfig )
return kconfig
2023-05-01 21:16:10 +03:00
def start ( self , params : List [ str ] , build_dir : str ) - > subprocess . Popen :
2019-09-23 12:02:43 +03:00
""" Runs the Linux UML binary. Must be named ' linux ' . """
2022-01-18 22:09:22 +03:00
linux_bin = os . path . join ( build_dir , ' linux ' )
2022-05-13 21:10:32 +03:00
params . extend ( [ ' mem=1G ' , ' console=tty ' , ' kunit_shutdown=halt ' ] )
2021-10-05 04:13:40 +03:00
return subprocess . Popen ( [ linux_bin ] + params ,
stdin = subprocess . PIPE ,
stdout = subprocess . PIPE ,
stderr = subprocess . STDOUT ,
2021-10-21 02:21:21 +03:00
text = True , errors = ' backslashreplace ' )
2019-09-23 12:02:43 +03:00
2022-01-18 22:09:22 +03:00
def get_kconfig_path ( build_dir : str ) - > str :
return os . path . join ( build_dir , KCONFIG_PATH )
2019-09-23 12:02:43 +03:00
2022-01-18 22:09:22 +03:00
def get_kunitconfig_path ( build_dir : str ) - > str :
return os . path . join ( build_dir , KUNITCONFIG_PATH )
2020-10-26 19:59:25 +03:00
2022-01-18 22:09:22 +03:00
def get_old_kunitconfig_path ( build_dir : str ) - > str :
return os . path . join ( build_dir , OLD_KUNITCONFIG_PATH )
2021-11-20 02:23:16 +03:00
kunit: tool: make --kunitconfig repeatable, blindly concat
It's come up a few times that it would be useful to have --kunitconfig
be repeatable [1][2].
This could be done before with a bit of shell-fu, e.g.
$ find fs/ -name '.kunitconfig' -exec cat {} + | \
./tools/testing/kunit/kunit.py run --kunitconfig=/dev/stdin
or equivalently:
$ cat fs/ext4/.kunitconfig fs/fat/.kunitconfig | \
./tools/testing/kunit/kunit.py run --kunitconfig=/dev/stdin
But this can be fairly clunky to use in practice.
And having explicit support in kunit.py opens the door to having more
config fragments of interest, e.g. options for PCI on UML [1], UML
coverage [2], variants of tests [3].
There's another argument to be made that users can just use multiple
--kconfig_add's, but this gets very clunky very fast (e.g. [2]).
Note: there's a big caveat here that some kconfig options might be
incompatible. We try to give a clearish error message in the simple case
where the same option appears multiple times with conflicting values,
but more subtle ones (e.g. mutually exclusive options) will be
potentially very confusing for the user. I don't know we can do better.
Note 2: if you want to combine a --kunitconfig with the default, you
either have to do to specify the current build_dir
> --kunitconfig=.kunit --kunitconfig=additional.config
or
> --kunitconfig=tools/testing/kunit/configs/default.config --kunitconifg=additional.config
each of which have their downsides (former depends on --build_dir,
doesn't work if you don't have a .kunitconfig yet), etc.
Example with conflicting values:
> $ ./tools/testing/kunit/kunit.py config --kunitconfig=lib/kunit --kunitconfig=/dev/stdin <<EOF
> CONFIG_KUNIT_TEST=n
> CONFIG_KUNIT=m
> EOF
> ...
> kunit_kernel.ConfigError: Multiple values specified for 2 options in kunitconfig:
> CONFIG_KUNIT=y
> vs from /dev/stdin
> CONFIG_KUNIT=m
>
> CONFIG_KUNIT_TEST=y
> vs from /dev/stdin
> # CONFIG_KUNIT_TEST is not set
[1] https://lists.freedesktop.org/archives/dri-devel/2022-June/357616.html
[2] https://lore.kernel.org/linux-kselftest/CAFd5g45f3X3xF2vz2BkTHRqOC4uW6GZxtUUMaP5mwwbK8uNVtA@mail.gmail.com/
[3] https://lore.kernel.org/linux-kselftest/CANpmjNOdSy6DuO6CYZ4UxhGxqhjzx4tn0sJMbRqo2xRFv9kX6Q@mail.gmail.com/
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2022-07-08 04:36:32 +03:00
def get_parsed_kunitconfig ( build_dir : str ,
kunitconfig_paths : Optional [ List [ str ] ] = None ) - > kunit_config . Kconfig :
if not kunitconfig_paths :
path = get_kunitconfig_path ( build_dir )
if not os . path . exists ( path ) :
shutil . copyfile ( DEFAULT_KUNITCONFIG_PATH , path )
return kunit_config . parse_file ( path )
merged = kunit_config . Kconfig ( )
for path in kunitconfig_paths :
if os . path . isdir ( path ) :
path = os . path . join ( path , KUNITCONFIG_PATH )
if not os . path . exists ( path ) :
raise ConfigError ( f ' Specified kunitconfig ( { path } ) does not exist ' )
partial = kunit_config . parse_file ( path )
diff = merged . conflicting_options ( partial )
if diff :
diff_str = ' \n \n ' . join ( f ' { a } \n vs from { path } \n { b } ' for a , b in diff )
raise ConfigError ( f ' Multiple values specified for { len ( diff ) } options in kunitconfig: \n { diff_str } ' )
merged . merge_in_entries ( partial )
return merged
2022-01-18 22:09:22 +03:00
def get_outfile_path ( build_dir : str ) - > str :
return os . path . join ( build_dir , OUTFILE_PATH )
2020-10-26 19:59:26 +03:00
2022-05-18 20:01:23 +03:00
def _default_qemu_config_path ( arch : str ) - > str :
2021-05-27 00:24:06 +03:00
config_path = os . path . join ( QEMU_CONFIGS_DIR , arch + ' .py ' )
2022-05-09 23:49:09 +03:00
if os . path . isfile ( config_path ) :
2022-05-18 20:01:23 +03:00
return config_path
kunit: tool: show list of valid --arch options when invalid
Consider this attempt to run KUnit in QEMU:
$ ./tools/testing/kunit/kunit.py run --arch=x86
Before you'd get this error message:
kunit_kernel.ConfigError: x86 is not a valid arch
After:
kunit_kernel.ConfigError: x86 is not a valid arch, options are ['alpha', 'arm', 'arm64', 'i386', 'powerpc', 'riscv', 's390', 'sparc', 'x86_64']
This should make it a bit easier for people to notice when they make
typos, etc. Currently, one would have to dive into the python code to
figure out what the valid set is.
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: David Gow <davidgow@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2021-09-30 02:25:34 +03:00
options = [ f [ : - 3 ] for f in os . listdir ( QEMU_CONFIGS_DIR ) if f . endswith ( ' .py ' ) ]
raise ConfigError ( arch + ' is not a valid arch, options are ' + str ( sorted ( options ) ) )
2021-05-27 00:24:06 +03:00
2022-05-18 20:01:23 +03:00
def _get_qemu_ops ( config_path : str ,
2022-05-18 20:01:24 +03:00
extra_qemu_args : Optional [ List [ str ] ] ,
2022-05-18 20:01:23 +03:00
cross_compile : Optional [ str ] ) - > Tuple [ str , LinuxSourceTreeOperations ] :
2021-05-27 00:24:06 +03:00
# The module name/path has very little to do with where the actual file
# exists (I learned this through experimentation and could not find it
# anywhere in the Python documentation).
#
# Bascially, we completely ignore the actual file location of the config
# we are loading and just tell Python that the module lives in the
# QEMU_CONFIGS_DIR for import purposes regardless of where it actually
# exists as a file.
module_path = ' . ' + os . path . join ( os . path . basename ( QEMU_CONFIGS_DIR ) , os . path . basename ( config_path ) )
spec = importlib . util . spec_from_file_location ( module_path , config_path )
2021-12-15 23:39:23 +03:00
assert spec is not None
2021-05-27 00:24:06 +03:00
config = importlib . util . module_from_spec ( spec )
2021-10-22 03:49:36 +03:00
# See https://github.com/python/typeshed/pull/2626 for context.
assert isinstance ( spec . loader , importlib . abc . Loader )
spec . loader . exec_module ( config )
if not hasattr ( config , ' QEMU_ARCH ' ) :
raise ValueError ( ' qemu_config module missing " QEMU_ARCH " : ' + config_path )
2023-03-17 01:06:38 +03:00
params : qemu_config . QemuArchParams = config . QEMU_ARCH
2022-05-18 20:01:24 +03:00
if extra_qemu_args :
params . extra_qemu_params . extend ( extra_qemu_args )
2021-10-22 03:49:36 +03:00
return params . linux_arch , LinuxSourceTreeOperationsQemu (
params , cross_compile = cross_compile )
2021-05-27 00:24:06 +03:00
2022-05-09 23:49:09 +03:00
class LinuxSourceTree :
2019-09-23 12:02:43 +03:00
""" Represents a Linux kernel source tree with KUnit tests. """
2021-05-27 00:24:06 +03:00
def __init__ (
self ,
build_dir : str ,
kunit: tool: make --kunitconfig repeatable, blindly concat
It's come up a few times that it would be useful to have --kunitconfig
be repeatable [1][2].
This could be done before with a bit of shell-fu, e.g.
$ find fs/ -name '.kunitconfig' -exec cat {} + | \
./tools/testing/kunit/kunit.py run --kunitconfig=/dev/stdin
or equivalently:
$ cat fs/ext4/.kunitconfig fs/fat/.kunitconfig | \
./tools/testing/kunit/kunit.py run --kunitconfig=/dev/stdin
But this can be fairly clunky to use in practice.
And having explicit support in kunit.py opens the door to having more
config fragments of interest, e.g. options for PCI on UML [1], UML
coverage [2], variants of tests [3].
There's another argument to be made that users can just use multiple
--kconfig_add's, but this gets very clunky very fast (e.g. [2]).
Note: there's a big caveat here that some kconfig options might be
incompatible. We try to give a clearish error message in the simple case
where the same option appears multiple times with conflicting values,
but more subtle ones (e.g. mutually exclusive options) will be
potentially very confusing for the user. I don't know we can do better.
Note 2: if you want to combine a --kunitconfig with the default, you
either have to do to specify the current build_dir
> --kunitconfig=.kunit --kunitconfig=additional.config
or
> --kunitconfig=tools/testing/kunit/configs/default.config --kunitconifg=additional.config
each of which have their downsides (former depends on --build_dir,
doesn't work if you don't have a .kunitconfig yet), etc.
Example with conflicting values:
> $ ./tools/testing/kunit/kunit.py config --kunitconfig=lib/kunit --kunitconfig=/dev/stdin <<EOF
> CONFIG_KUNIT_TEST=n
> CONFIG_KUNIT=m
> EOF
> ...
> kunit_kernel.ConfigError: Multiple values specified for 2 options in kunitconfig:
> CONFIG_KUNIT=y
> vs from /dev/stdin
> CONFIG_KUNIT=m
>
> CONFIG_KUNIT_TEST=y
> vs from /dev/stdin
> # CONFIG_KUNIT_TEST is not set
[1] https://lists.freedesktop.org/archives/dri-devel/2022-June/357616.html
[2] https://lore.kernel.org/linux-kselftest/CAFd5g45f3X3xF2vz2BkTHRqOC4uW6GZxtUUMaP5mwwbK8uNVtA@mail.gmail.com/
[3] https://lore.kernel.org/linux-kselftest/CANpmjNOdSy6DuO6CYZ4UxhGxqhjzx4tn0sJMbRqo2xRFv9kX6Q@mail.gmail.com/
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2022-07-08 04:36:32 +03:00
kunitconfig_paths : Optional [ List [ str ] ] = None ,
2021-11-06 04:30:58 +03:00
kconfig_add : Optional [ List [ str ] ] = None ,
2023-03-17 01:06:38 +03:00
arch : Optional [ str ] = None ,
cross_compile : Optional [ str ] = None ,
qemu_config_path : Optional [ str ] = None ,
extra_qemu_args : Optional [ List [ str ] ] = None ) - > None :
2020-03-16 23:21:25 +03:00
signal . signal ( signal . SIGINT , self . signal_handler )
2021-05-27 00:24:06 +03:00
if qemu_config_path :
2022-05-18 20:01:24 +03:00
self . _arch , self . _ops = _get_qemu_ops ( qemu_config_path , extra_qemu_args , cross_compile )
2021-05-27 00:24:06 +03:00
else :
self . _arch = ' um ' if arch is None else arch
2022-05-18 20:01:23 +03:00
if self . _arch == ' um ' :
self . _ops = LinuxSourceTreeOperationsUml ( cross_compile = cross_compile )
else :
qemu_config_path = _default_qemu_config_path ( self . _arch )
2022-05-18 20:01:24 +03:00
_ , self . _ops = _get_qemu_ops ( qemu_config_path , extra_qemu_args , cross_compile )
2021-01-15 03:39:13 +03:00
kunit: tool: make --kunitconfig repeatable, blindly concat
It's come up a few times that it would be useful to have --kunitconfig
be repeatable [1][2].
This could be done before with a bit of shell-fu, e.g.
$ find fs/ -name '.kunitconfig' -exec cat {} + | \
./tools/testing/kunit/kunit.py run --kunitconfig=/dev/stdin
or equivalently:
$ cat fs/ext4/.kunitconfig fs/fat/.kunitconfig | \
./tools/testing/kunit/kunit.py run --kunitconfig=/dev/stdin
But this can be fairly clunky to use in practice.
And having explicit support in kunit.py opens the door to having more
config fragments of interest, e.g. options for PCI on UML [1], UML
coverage [2], variants of tests [3].
There's another argument to be made that users can just use multiple
--kconfig_add's, but this gets very clunky very fast (e.g. [2]).
Note: there's a big caveat here that some kconfig options might be
incompatible. We try to give a clearish error message in the simple case
where the same option appears multiple times with conflicting values,
but more subtle ones (e.g. mutually exclusive options) will be
potentially very confusing for the user. I don't know we can do better.
Note 2: if you want to combine a --kunitconfig with the default, you
either have to do to specify the current build_dir
> --kunitconfig=.kunit --kunitconfig=additional.config
or
> --kunitconfig=tools/testing/kunit/configs/default.config --kunitconifg=additional.config
each of which have their downsides (former depends on --build_dir,
doesn't work if you don't have a .kunitconfig yet), etc.
Example with conflicting values:
> $ ./tools/testing/kunit/kunit.py config --kunitconfig=lib/kunit --kunitconfig=/dev/stdin <<EOF
> CONFIG_KUNIT_TEST=n
> CONFIG_KUNIT=m
> EOF
> ...
> kunit_kernel.ConfigError: Multiple values specified for 2 options in kunitconfig:
> CONFIG_KUNIT=y
> vs from /dev/stdin
> CONFIG_KUNIT=m
>
> CONFIG_KUNIT_TEST=y
> vs from /dev/stdin
> # CONFIG_KUNIT_TEST is not set
[1] https://lists.freedesktop.org/archives/dri-devel/2022-June/357616.html
[2] https://lore.kernel.org/linux-kselftest/CAFd5g45f3X3xF2vz2BkTHRqOC4uW6GZxtUUMaP5mwwbK8uNVtA@mail.gmail.com/
[3] https://lore.kernel.org/linux-kselftest/CANpmjNOdSy6DuO6CYZ4UxhGxqhjzx4tn0sJMbRqo2xRFv9kX6Q@mail.gmail.com/
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2022-07-08 04:36:32 +03:00
self . _kconfig = get_parsed_kunitconfig ( build_dir , kunitconfig_paths )
2021-11-06 04:30:58 +03:00
if kconfig_add :
kconfig = kunit_config . parse_from_string ( ' \n ' . join ( kconfig_add ) )
self . _kconfig . merge_in_entries ( kconfig )
kunit: tool: properly report the used arch for --json, or '' if not known
Before, kunit.py always printed "arch": "UM" in its json output, but...
1. With `kunit.py parse`, we could be parsing output from anywhere, so
we can't say that.
2. Capitalizing it is probably wrong, as it's `ARCH=um`
3. Commit 87c9c1631788 ("kunit: tool: add support for QEMU") made it so
kunit.py could knowingly run a different arch, yet we'd still always
claim "UM".
This patch addresses all of those. E.g.
1.
$ ./tools/testing/kunit/kunit.py parse .kunit/test.log --json | grep -o '"arch.*' | sort -u
"arch": "",
2.
$ ./tools/testing/kunit/kunit.py run --json | ...
"arch": "um",
3.
$ ./tools/testing/kunit/kunit.py run --json --arch=x86_64 | ...
"arch": "x86_64",
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: David Gow <davidgow@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2022-02-24 22:20:36 +03:00
def arch ( self ) - > str :
return self . _arch
2020-10-26 19:59:25 +03:00
2021-01-15 03:39:13 +03:00
def clean ( self ) - > bool :
try :
self . _ops . make_mrproper ( )
except ConfigError as e :
logging . error ( e )
return False
return True
2022-01-18 22:09:22 +03:00
def validate_config ( self , build_dir : str ) - > bool :
2019-11-27 01:36:16 +03:00
kconfig_path = get_kconfig_path ( build_dir )
2021-11-06 04:30:57 +03:00
validated_kconfig = kunit_config . parse_file ( kconfig_path )
2021-11-11 21:33:56 +03:00
if self . _kconfig . is_subset_of ( validated_kconfig ) :
return True
2022-06-28 01:14:44 +03:00
missing = set ( self . _kconfig . as_entries ( ) ) - set ( validated_kconfig . as_entries ( ) )
2021-11-11 21:33:56 +03:00
message = ' Not all Kconfig options selected in kunitconfig were in the generated .config. \n ' \
' This is probably due to unsatisfied dependencies. \n ' \
2022-06-28 01:14:44 +03:00
' Missing: ' + ' , ' . join ( str ( e ) for e in missing )
2021-11-11 21:33:56 +03:00
if self . _arch == ' um ' :
message + = ' \n Note: many Kconfig options aren \' t available on UML. You can try running ' \
' on a different architecture with something like " --arch=x86_64 " . '
logging . error ( message )
return False
2019-11-27 01:36:16 +03:00
2023-03-17 01:06:38 +03:00
def build_config ( self , build_dir : str , make_options : Optional [ List [ str ] ] ) - > 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 )
try :
2022-07-08 19:27:11 +03:00
self . _kconfig = self . _ops . make_arch_config ( self . _kconfig )
2021-05-27 00:24:06 +03:00
self . _kconfig . write_to_file ( kconfig_path )
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
2021-11-20 02:23:16 +03:00
if not self . validate_config ( build_dir ) :
return False
old_path = get_old_kunitconfig_path ( build_dir )
if os . path . exists ( old_path ) :
os . remove ( old_path ) # write_to_file appends to the file
self . _kconfig . write_to_file ( old_path )
return True
def _kunitconfig_changed ( self , build_dir : str ) - > bool :
old_path = get_old_kunitconfig_path ( build_dir )
if not os . path . exists ( old_path ) :
return True
old_kconfig = kunit_config . parse_file ( old_path )
2022-06-28 01:14:44 +03:00
return old_kconfig != self . _kconfig
2019-09-23 12:02:43 +03:00
2023-03-17 01:06:38 +03:00
def build_reconfig ( self , build_dir : str , make_options : Optional [ List [ str ] ] ) - > 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 )
2021-11-20 02:23:16 +03:00
if not os . path . exists ( kconfig_path ) :
2019-09-23 12:02:43 +03:00
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-11-20 02:23:16 +03:00
existing_kconfig = kunit_config . parse_file ( kconfig_path )
2022-07-08 19:27:11 +03:00
self . _kconfig = self . _ops . make_arch_config ( self . _kconfig )
2022-06-28 01:14:44 +03:00
2021-11-20 02:23:16 +03:00
if self . _kconfig . is_subset_of ( existing_kconfig ) and not self . _kunitconfig_changed ( build_dir ) :
return True
print ( ' Regenerating .config ... ' )
os . remove ( kconfig_path )
return self . build_config ( build_dir , make_options )
2023-03-17 01:06:38 +03:00
def build_kernel ( self , jobs : int , build_dir : str , make_options : Optional [ List [ str ] ] ) - > bool :
2019-09-23 12:02:43 +03:00
try :
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
kunit: tool: Add command line interface to filter and report attributes
Add ability to kunit.py to filter attributes and report a list of tests
including attributes without running tests.
Add flag "--filter" to input filters on test attributes. Tests will be
filtered out if they do not match all inputted filters.
Example: --filter speed=slow (This filter would run only the tests that are
marked as slow)
Filters have operations: <, >, <=, >=, !=, and =. But note that the
characters < and > are often interpreted by the shell, so they may need to
be quoted or escaped.
Example: --filter "speed>slow" or --filter speed\>slow (This filter would
run only the tests that have the speed faster than slow.
Additionally, multiple filters can be used.
Example: --filter "speed=slow, module!=example" (This filter would run
only the tests that have the speed slow and are not in the "example"
module)
Note if the user wants to skip filtered tests instead of not
running/showing them use the "--filter_action=skip" flag instead.
Expose the output of kunit.action=list option with flag "--list_tests" to
output a list of tests. Additionally, add flag "--list_tests_attr" to
output a list of tests and their attributes. These flags are useful to see
tests and test attributes without needing to run tests.
Example of the output of "--list_tests_attr":
example
example.test_1
example.test_2
# example.test_2.speed: slow
This output includes a suite, example, with two test cases, test_1 and
test_2. And in this instance test_2 has been marked as slow.
Reviewed-by: David Gow <davidgow@google.com>
Signed-off-by: Rae Moar <rmoar@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2023-07-26 00:25:16 +03:00
def run_kernel ( self , args : Optional [ List [ str ] ] = None , build_dir : str = ' ' , filter_glob : str = ' ' , filter : str = ' ' , filter_action : Optional [ str ] = None , timeout : Optional [ int ] = None ) - > Iterator [ str ] :
kunit: tool: fix unintentional statefulness in run_kernel()
This is a bug that has been present since the first version of this
code.
Using [] as a default parameter is dangerous, since it's mutable.
Example using the REPL:
>>> def bad(param = []):
... param.append(len(param))
... print(param)
...
>>> bad()
[0]
>>> bad()
[0, 1]
This wasn't a concern in the past since it would just keep appending the
same values to it.
E.g. before, `args` would just grow in size like:
[mem=1G', 'console=tty']
[mem=1G', 'console=tty', mem=1G', 'console=tty']
But with now filter_glob, this is more dangerous, e.g.
run_kernel(filter_glob='my-test*') # default modified here
run_kernel() # filter_glob still applies here!
That earlier `filter_glob` will affect all subsequent calls that don't
specify `args`.
Note: currently the kunit tool only calls run_kernel() at most once, so
it's not possible to trigger any negative side-effects right now.
Fixes: 6ebf5866f2e8 ("kunit: tool: add Python wrappers for running KUnit tests")
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2021-02-06 03:08:54 +03:00
if not args :
args = [ ]
2021-02-06 03:08:53 +03:00
if filter_glob :
kunit: tool: Add command line interface to filter and report attributes
Add ability to kunit.py to filter attributes and report a list of tests
including attributes without running tests.
Add flag "--filter" to input filters on test attributes. Tests will be
filtered out if they do not match all inputted filters.
Example: --filter speed=slow (This filter would run only the tests that are
marked as slow)
Filters have operations: <, >, <=, >=, !=, and =. But note that the
characters < and > are often interpreted by the shell, so they may need to
be quoted or escaped.
Example: --filter "speed>slow" or --filter speed\>slow (This filter would
run only the tests that have the speed faster than slow.
Additionally, multiple filters can be used.
Example: --filter "speed=slow, module!=example" (This filter would run
only the tests that have the speed slow and are not in the "example"
module)
Note if the user wants to skip filtered tests instead of not
running/showing them use the "--filter_action=skip" flag instead.
Expose the output of kunit.action=list option with flag "--list_tests" to
output a list of tests. Additionally, add flag "--list_tests_attr" to
output a list of tests and their attributes. These flags are useful to see
tests and test attributes without needing to run tests.
Example of the output of "--list_tests_attr":
example
example.test_1
example.test_2
# example.test_2.speed: slow
This output includes a suite, example, with two test cases, test_1 and
test_2. And in this instance test_2 has been marked as slow.
Reviewed-by: David Gow <davidgow@google.com>
Signed-off-by: Rae Moar <rmoar@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2023-07-26 00:25:16 +03:00
args . append ( ' kunit.filter_glob= ' + filter_glob )
if filter :
args . append ( ' kunit.filter= " ' + filter + ' " ' )
if filter_action :
args . append ( ' kunit.filter_action= ' + filter_action )
2022-08-23 17:24:54 +03:00
args . append ( ' kunit.enable=1 ' )
2021-10-05 04:13:40 +03:00
process = self . _ops . start ( args , build_dir )
assert process . stdout is not None # tell mypy it's set
# Enforce the timeout in a background thread.
2023-03-17 01:06:38 +03:00
def _wait_proc ( ) - > None :
2021-10-05 04:13:40 +03:00
try :
process . wait ( timeout = timeout )
except Exception as e :
print ( e )
process . terminate ( )
process . wait ( )
waiter = threading . Thread ( target = _wait_proc )
waiter . start ( )
output = open ( get_outfile_path ( build_dir ) , ' w ' )
try :
# Tee the output to the file and to our caller in real time.
for line in process . stdout :
output . write ( line )
2020-03-16 23:21:25 +03:00
yield line
2021-10-05 04:13:40 +03:00
# This runs even if our caller doesn't consume every line.
finally :
# Flush any leftover output to the file
output . write ( process . stdout . read ( ) )
output . close ( )
process . stdout . close ( )
waiter . join ( )
subprocess . call ( [ ' stty ' , ' sane ' ] )
2020-03-16 23:21:25 +03:00
2023-03-17 01:06:38 +03:00
def signal_handler ( self , unused_sig : int , unused_frame : Optional [ FrameType ] ) - > None :
2020-03-16 23:21:25 +03:00
logging . error ( ' Build interruption occurred. Cleaning console. ' )
subprocess . call ( [ ' stty ' , ' sane ' ] )