From 928b01f45883869627697ee995b7654b8615e86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciro=20Santilli=20=E5=85=AD=E5=9B=9B=E4=BA=8B=E4=BB=B6=20?= =?UTF-8?q?=E6=B3=95=E8=BD=AE=E5=8A=9F?= Date: Tue, 22 Jan 2019 00:00:00 +0000 Subject: [PATCH] add a --quiet flag test-gdb and test-userland produce beautiful output by default create def get_common_args to help forward common args to child calls... it is ugly, but I'm lazy for a perfect solution now --- README.adoc | 4 +- build | 41 ++++++++---------- build-qemu | 4 +- common.py | 107 +++++++++++++++++++++++++++++++++++++---------- run | 91 ++++++++++++++++++++-------------------- shell_helpers.py | 23 ++++++---- test-gdb | 35 ++++++++-------- test-userland | 32 +++++++------- 8 files changed, 200 insertions(+), 137 deletions(-) diff --git a/README.adoc b/README.adoc index fee60d0..bc30e05 100644 --- a/README.adoc +++ b/README.adoc @@ -2914,7 +2914,7 @@ The target Linux kernel of the executable is a GCC toolchain build-time configur First let's run a dynamically linked executable built with the Buildroot toolchain: .... -./build-qemu --arch aarch64 --userland +./build-qemu --arch aarch64 --user-mode ./build-userland --arch aarch64 ./build-buildroot --arch aarch64 ./run \ @@ -2931,7 +2931,7 @@ asdf qw er .... -This runs link:userland/print_argv.c[]. `--userland` path resolution is analogous to <>. +This runs link:userland/print_argv.c[]. `--user-mode` path resolution is analogous to <>. `./build-userland` is further documented at: <>. diff --git a/build b/build index e2786c8..da3b035 100755 --- a/build +++ b/build @@ -48,12 +48,11 @@ class _Component: (self.build_callback is not None) and (self.supported_archs is None or arch in self.supported_archs) ): - self.build_callback(arch) + self.build_callback() -class Main(cli_function.CliFunction): +class Main(common.LkmcCliFunction): def __init__(self): super().__init__( - config_file=common.consts['config_file'], description='''\ Build a component and all its dependencies. @@ -110,24 +109,24 @@ This is equivalent to: self.name_to_component_map = { # Leaves without dependencies. 'baremetal-qemu': _Component( - lambda arch: self._run_cmd(['build-baremetal', '--emulator', 'qemu'], arch), + lambda: self.import_path_main('build-baremetal')(archs=self.env['archs'], emulators=['qemu']), supported_archs=common.consts['crosstool_ng_supported_archs'], ), 'baremetal-gem5': _Component( - lambda arch: self._run_cmd(['build-baremetal', '--gem5'], arch), + lambda: self.import_path_main('build-baremetal')(archs=self.env['archs'], emulators=['gem5']), supported_archs=common.consts['crosstool_ng_supported_archs'], ), 'baremetal-gem5-pbx': _Component( - lambda arch: self._run_cmd(['build-baremetal', '--gem5', '--machine', 'RealViewPBX'], arch), + lambda: self.import_path_main('build-baremetal')(archs=self.env['archs'], emulators=['gem5'], machine='RealViewPBX'), supported_archs=common.consts['crosstool_ng_supported_archs'], ), 'buildroot': buildroot_component, 'buildroot-gcc': buildroot_component, 'copy-overlay': _Component( - lambda arch: self._run_cmd(['copy-overlay'], arch), + lambda: self.import_path_main('copy-overlay')(archs=self.env['archs']), ), 'crosstool-ng': _Component( - lambda arch: self._run_cmd(['build-crosstool-ng'], arch), + lambda: self.import_path_main('build-crosstool-ng')(archs=self.env['archs']), supported_archs=common.consts['crosstool_ng_supported_archs'], # http://crosstool-ng.github.io/docs/os-setup/ apt_get_pkgs={ @@ -147,7 +146,7 @@ This is equivalent to: submodules={'crosstool-ng'}, ), 'gem5': _Component( - lambda arch: self._run_cmd(['build-gem5'], arch), + lambda: self.import_path_main('build-gem5')(archs=self.env['archs']), # TODO test it out on Docker and answer that question properly: # https://askubuntu.com/questions/350475/how-can-i-install-gem5 apt_get_pkgs={ @@ -170,7 +169,7 @@ This is equivalent to: submodules={'gem5'}, ), 'gem5-debug': _Component( - lambda arch: self._run_cmd(['build-gem5', '--gem5-build-type', 'debug'], arch), + lambda: self.import_path_main('build-gem5')(archs=self.env['archs'], gem5_build_type='debug'), ), 'gem5-fast': _Component( lambda arch: self._run_cmd(['build-gem5', '--gem5-build-type', 'fast'], arch), @@ -309,18 +308,12 @@ Which components to build. Default: qemu-buildroot ''' ) - def _run_cmd(self, python_file, **kwargs): - python_file = os.path.join(common.consts['root_dir'], python_file) - run = common.import_path(python_file).Main() - run(**kwargs) - self.sh.run_cmd(cmd_abs) - - def main(self, **kwargs): - self.sh = shell_helpers.ShellHelpers(dry_run=kwargs['dry_run']) + def timed_main(self): + self.sh = shell_helpers.ShellHelpers(dry_run=self.env['dry_run']) # Decide components. - components = kwargs['components'] - if kwargs['all']: + components = self.env['components'] + if self.env['all']: components = ['all'] elif components == []: components = ['qemu-buildroot'] @@ -336,7 +329,7 @@ Which components to build. Default: qemu-buildroot selected_components.append(component) todo.extend(component.dependencies) - if kwargs['download_dependencies']: + if self.env['download_dependencies']: apt_get_pkgs = { # Core requirements for this repo. 'git', @@ -374,7 +367,7 @@ Which components to build. Default: qemu-buildroot python2_pkgs.update(component.python2_pkgs) python3_pkgs.update(component.python3_pkgs) if apt_get_pkgs or apt_build_deps: - if kwargs['travis']: + if self.env['travis']: interacive_pkgs = { 'libsdl2-dev', } @@ -392,7 +385,7 @@ Which components to build. Default: qemu-buildroot f.write(sources_txt) else: sudo = ['sudo'] - if common.consts['in_docker'] or kwargs['travis']: + if common.consts['in_docker'] or self.env['travis']: y = ['-y'] else: y = [] @@ -465,7 +458,7 @@ Which components to build. Default: qemu-buildroot # Do the build. for component in selected_components: - component.build() + component.build(self.env['arch']) if __name__ == '__main__': Main().cli() diff --git a/build-qemu b/build-qemu index 21a53ff..604159d 100755 --- a/build-qemu +++ b/build-qemu @@ -9,7 +9,7 @@ class Main(common.BuildCliFunction): def __init__(self): super().__init__() self.add_argument( - '--userland', + '--user-mode', default=False, help='Build QEMU user mode instead of system.', ) @@ -27,7 +27,7 @@ class Main(common.BuildCliFunction): verbose = ['V=1'] else: verbose = [] - if self.env['userland']: + if self.env['user_mode']: target_list = '{}-linux-user'.format(self.env['arch']) else: target_list = '{}-softmmu'.format(self.env['arch']) diff --git a/common.py b/common.py index a2836f2..add1f02 100644 --- a/common.py +++ b/common.py @@ -110,7 +110,7 @@ class LkmcCliFunction(cli_function.CliFunction): Common functionality shared across our CLI functions: * command timing - * some common flags, e.g.: --arch, --dry-run, --verbose + * some common flags, e.g.: --arch, --dry-run, --quiet, --verbose ''' def __init__(self, *args, defaults=None, supported_archs=None, **kwargs): ''' @@ -122,6 +122,8 @@ class LkmcCliFunction(cli_function.CliFunction): if defaults is None: defaults = {} self._defaults = defaults + self._is_common = True + self._common_args = set() super().__init__(*args, **kwargs) self.supported_archs = supported_archs @@ -165,7 +167,17 @@ mkdir are generally omitted since those are obvious ) self.add_argument( '--print-time', default=True, - help='Print how long it took to run the command at the end.' + help='''\ +Print how long it took to run the command at the end. +Implied by --quiet. +''' + ) + self.add_argument( + '-q', '--quiet', default=False, + help='''\ +Don't print anything to stdout, except if it is part of an interactive terminal. +TODO: implement fully, some stuff is escaping currently. +''' ) self.add_argument( '-v', '--verbose', default=False, @@ -284,7 +296,22 @@ See the documentation for other values known to work. # Userland. self.add_argument( - '--userland-build-id', default=None + '-u', '--userland', + help='''\ +Run the given userland executable in user mode instead of booting the Linux kernel +in full system mode. In gem5, user mode is called Syscall Emulation (SE) mode and +uses se.py. +Path resolution is similar to --baremetal. +''' + ) + self.add_argument( + '--userland-args', + help='''\ +CLI arguments to pass to the userland executable. +''' + ) + self.add_argument( + '--userland-build-id' ) # Run. @@ -336,16 +363,21 @@ Emulator to use. If given multiple times, semantics are similar to --arch. Valid emulators: {} '''.format(emulators_string) ) + self._is_common = False def __call__(self, **kwargs): ''' - For Python code calls, print the CLI equivalent of the call. + For Python code calls, in addition to base: + + - print the CLI equivalent of the call + - automatically forward common arguments ''' print_cmd = ['./' + self.extra_config_params, LF] for line in self.get_cli(**kwargs): print_cmd.extend(line) print_cmd.append(LF) - shell_helpers.ShellHelpers.print_cmd(print_cmd) + if not ('quiet' in kwargs and kwargs['quiet']): + shell_helpers.ShellHelpers().print_cmd(print_cmd) return super().__call__(**kwargs) def _init_env(self, env): @@ -606,24 +638,23 @@ Valid emulators: {} env['image'] = path def add_argument(self, *args, **kwargs): + ''' + Also handle: + + - modified defaults from child classes. + - common arguments to forward on Python calls + ''' shortname, longname, key, is_option = self.get_key(*args, **kwargs) if key in self._defaults: kwargs['default'] = self._defaults[key] + if self._is_common: + self._common_args.add(key) super().add_argument(*args, **kwargs) @staticmethod def base64_encode(string): return base64.b64encode(string.encode()).decode() - def gem5_list_checkpoint_dirs(self): - ''' - List checkpoint directory, oldest first. - ''' - prefix_re = re.compile(self.env['gem5_cpt_prefix']) - files = list(filter(lambda x: os.path.isdir(os.path.join(self.env['m5out_dir'], x)) and prefix_re.search(x), os.listdir(self.env['m5out_dir']))) - files.sort(key=lambda x: os.path.getmtime(os.path.join(self.env['m5out_dir'], x))) - return files - def get_elf_entry(self, elf_file_path): readelf_header = subprocess.check_output([ self.get_toolchain_tool('readelf'), @@ -637,6 +668,18 @@ Valid emulators: {} break return int(addr, 0) + def gem5_list_checkpoint_dirs(self): + ''' + List checkpoint directory, oldest first. + ''' + prefix_re = re.compile(self.env['gem5_cpt_prefix']) + files = list(filter(lambda x: os.path.isdir(os.path.join(self.env['m5out_dir'], x)) and prefix_re.search(x), os.listdir(self.env['m5out_dir']))) + files.sort(key=lambda x: os.path.getmtime(os.path.join(self.env['m5out_dir'], x))) + return files + + def get_common_args(self): + return {key:self.env[key] for key in self._common_args} + def get_stats(self, stat_re=None, stats_file=None): if stat_re is None: stat_re = '^system.cpu[0-9]*.numCycles$' @@ -737,29 +780,41 @@ Valid emulators: {} ''' env = kwargs.copy() env.update(consts) - if env['all_archs']: - env['archs'] = consts['all_long_archs'] + real_all_archs= env['all_archs'] + if real_all_archs: + real_archs = consts['all_long_archs'] + else: + real_archs = env['archs'] if env['all_emulators']: - env['emulators'] = consts['all_long_emulators'] - for emulator in env['emulators']: - for arch in env['archs']: + real_emulators = consts['all_long_emulators'] + else: + real_emulators = env['emulators'] + for emulator in real_emulators: + for arch in real_archs: if arch in env['arch_short_to_long_dict']: arch = env['arch_short_to_long_dict'][arch] if self.supported_archs is None or arch in self.supported_archs: if not env['dry_run']: start_time = time.time() env['arch'] = arch + env['archs'] = [arch] + env['all_archs'] = False env['emulator'] = emulator + env['emulators'] = [emulator] + env['all_emulators'] = False self.env = env.copy() self._init_env(self.env) - self.sh = shell_helpers.ShellHelpers(dry_run=self.env['dry_run']) + self.sh = shell_helpers.ShellHelpers( + dry_run=self.env['dry_run'], + quiet=self.env['quiet'], + ) ret = self.timed_main() if not env['dry_run']: end_time = time.time() self._print_time(end_time - start_time) if ret is not None and ret != 0: return ret - elif not env['all_archs']: + elif not real_all_archs: raise Exception('Unsupported arch for this action: ' + arch) return 0 @@ -787,7 +842,7 @@ Valid emulators: {} return False def _print_time(self, ellapsed_seconds): - if self.env['print_time']: + if self.env['print_time'] and not self.env['quiet']: hours, rem = divmod(ellapsed_seconds, 3600) minutes, seconds = divmod(rem, 60) print('time {:02}:{:02}:{:02}'.format(int(hours), int(minutes), int(seconds))) @@ -859,6 +914,14 @@ Valid emulators: {} self.env['userland_build_ext'], ) + def test_setup(self, test_env, source): + if not self.env['verbose']: + test_env['quiet'] = True + test_id_string = '{} {} {}'.format(self.env['emulator'], self.env['arch'], source) + if not self.env['quiet']: + print(test_id_string) + return test_id_string + def timed_main(self): ''' Main action of the derived class. diff --git a/run b/run index e9da024..172300c 100755 --- a/run +++ b/run @@ -178,21 +178,6 @@ to use this option: '--tmux-args', help='''\ Parameters to pass to the program running on the tmux split. Implies --tmux. -''' - ) - self.add_argument( - '-u', '--userland', - help='''\ -Run the given userland executable in user mode instead of booting the Linux kernel -in full system mode. In gem5, user mode is called Syscall Emulation (SE) mode and -uses se.py. -Path resolution is similar to --baremetal. -''' - ) - self.add_argument( - '--userland-args', - help='''\ -CLI arguments to pass to the userland executable. ''' ) self.add_argument( @@ -221,6 +206,8 @@ Run QEMU with VNC instead of the default SDL. Connect to it with: # * https://unix.stackexchange.com/questions/397939/turning-off-kaslr-to-debug-linux-kernel-using-qemu-and-gdb # * https://stackoverflow.com/questions/44612822/unable-to-debug-kernel-with-qemu-gdb/49840927#49840927 # Turned on by default since v4.12 + raise_on_failure = True + show_stdout = True kernel_cli = 'console_msg_format=syslog nokaslr norandmaps panic=-1 printk.devkmsg=on printk.time=y rw' if self.env['kernel_cli'] is not None: kernel_cli += ' {}'.format(self.env['kernel_cli']) @@ -303,6 +290,8 @@ Run QEMU with VNC instead of the default SDL. Connect to it with: raise Exception('Baremetal ELF file not found. Tried:\n' + '\n'.join(paths)) cmd = debug_vm.copy() if self.env['emulator'] == 'gem5': + if self.env['quiet']: + show_stdout = False if self.env['baremetal'] is None: if not os.path.exists(self.env['rootfs_raw_file']): if not os.path.exists(self.env['qcow2_file']): @@ -415,7 +404,7 @@ Run QEMU with VNC instead of the default SDL. Connect to it with: if self.env['wait_gdb']: # https://stackoverflow.com/questions/49296092/how-to-make-gem5-wait-for-gdb-to-connect-to-reliably-break-at-start-kernel-of-th cmd.extend(['--param', 'system.cpu[0].wait_for_remote_gdb = True', LF]) - else: + elif self.env['emulator'] == 'qemu': qemu_user_and_system_options = [ '-trace', 'enable={},file={}'.format(trace_type, self.env['qemu_trace_file']), LF, ] @@ -432,6 +421,8 @@ Run QEMU with VNC instead of the default SDL. Connect to it with: qemu_user_and_system_options + debug_args ) + show_stdout = False + raise_on_failure = False else: if not os.path.exists(self.env['image']): raise_image_not_found() @@ -452,6 +443,8 @@ Run QEMU with VNC instead of the default SDL. Connect to it with: else: if self.env['background']: serial_monitor = ['-serial', 'file:{}'.format(self.env['qemu_background_serial_file']), LF] + if self.env['quiet']: + show_stdout = False else: serial_monitor = ['-serial', 'mon:stdio', LF] if self.env['kvm']: @@ -592,36 +585,44 @@ Run QEMU with VNC instead of the default SDL. Connect to it with: out_file = None else: out_file = self.env['termout_file'] - self.sh.run_cmd(cmd, cmd_file=self.env['run_cmd_file'], out_file=out_file, extra_env=extra_env) - # Check if guest panicked. - if self.env['emulator'] == 'gem5': - # We have to do some parsing here because gem5 exits with status 0 even when panic happens. - # Grepping for '^panic: ' does not work because some errors don't show that message. - panic_msg = b'--- BEGIN LIBC BACKTRACE ---$' - else: - panic_msg = b'Kernel panic - not syncing' - panic_re = re.compile(panic_msg) - error_string_found = False - exit_status = 0 - if out_file is not None and not self.env['dry_run']: - with open(self.env['termout_file'], 'br') as logfile: - line = None - for line in logfile: - if panic_re.search(line): - exit_status = 1 - if line is not None: - last_line = line.rstrip() - match = re.search(b'Simulated exit code not 0! Exit code is (\d+)', last_line) - if match: - exit_status = int(match.group(1)) - if not self.env['userland']: - if os.path.exists(self.env['guest_terminal_file']): - with open(self.env['guest_terminal_file'], 'br') as logfile: - lines = logfile.readlines() - if lines and lines[-1].rstrip() == self.env['magic_fail_string']: + exit_status = self.sh.run_cmd( + cmd, + cmd_file=self.env['run_cmd_file'], + extra_env=extra_env, + out_file=out_file, + raise_on_failure=raise_on_failure, + show_stdout=show_stdout, + ) + if exit_status == 0: + # Check if guest panicked. + if self.env['emulator'] == 'gem5': + # We have to do some parsing here because gem5 exits with status 0 even when panic happens. + # Grepping for '^panic: ' does not work because some errors don't show that message. + panic_msg = b'--- BEGIN LIBC BACKTRACE ---$' + else: + panic_msg = b'Kernel panic - not syncing' + panic_re = re.compile(panic_msg) + error_string_found = False + exit_status = 0 + if out_file is not None and not self.env['dry_run']: + with open(self.env['termout_file'], 'br') as logfile: + line = None + for line in logfile: + if panic_re.search(line): exit_status = 1 - if exit_status != 0: - self.log_error('simulation error detected by parsing logs') + if line is not None: + last_line = line.rstrip() + match = re.search(b'Simulated exit code not 0! Exit code is (\d+)', last_line) + if match: + exit_status = int(match.group(1)) + if not self.env['userland']: + if os.path.exists(self.env['guest_terminal_file']): + with open(self.env['guest_terminal_file'], 'br') as logfile: + lines = logfile.readlines() + if lines and lines[-1].rstrip() == self.env['magic_fail_string']: + exit_status = 1 + if exit_status != 0: + self.log_error('simulation error detected by parsing logs') return exit_status if __name__ == '__main__': diff --git a/shell_helpers.py b/shell_helpers.py index 53eff69..41cd2bc 100644 --- a/shell_helpers.py +++ b/shell_helpers.py @@ -31,18 +31,25 @@ class ShellHelpers: _print_lock = threading.Lock() - def __init__(self, dry_run=False): + def __init__(self, dry_run=False, quiet=False): ''' :param dry_run: don't run the commands, just potentially print them. Debug aid. :type dry_run: Bool + + :param quiet: don't print the commands. + :type dry_run: Bool ''' self.dry_run = dry_run + self.quiet = quiet @classmethod def _print_thread_safe(cls, string): - # Python sucks: a naive print adds a bunch of random spaces to stdout, - # and then copy pasting the command fails. - # https://stackoverflow.com/questions/3029816/how-do-i-get-a-thread-safe-print-in-python-2-6 + ''' + Python sucks: a naive print adds a bunch of random spaces to stdout, + and then copy pasting the command fails. + https://stackoverflow.com/questions/3029816/how-do-i-get-a-thread-safe-print-in-python-2-6 + The initial use case was test-gdb which must create a thread for GDB to run the program in parallel. + ''' cls._print_lock.acquire() sys.stdout.write(string + '\n') sys.stdout.flush() @@ -107,8 +114,7 @@ class ShellHelpers: update=1, ) - @classmethod - def print_cmd(cls, cmd, cwd=None, cmd_file=None, extra_env=None, extra_paths=None): + def print_cmd(self, cmd, cwd=None, cmd_file=None, extra_env=None, extra_paths=None): ''' Print cmd_to_string to stdout. @@ -121,8 +127,9 @@ class ShellHelpers: if type(cmd) is str: cmd_string = cmd else: - cmd_string = cls.cmd_to_string(cmd, cwd=cwd, extra_env=extra_env, extra_paths=extra_paths) - cls._print_thread_safe('+ ' + cmd_string) + cmd_string = self.cmd_to_string(cmd, cwd=cwd, extra_env=extra_env, extra_paths=extra_paths) + if not self.quiet: + self._print_thread_safe('+ ' + cmd_string) if cmd_file is not None: with open(cmd_file, 'w') as f: f.write('#!/usr/bin/env bash\n') diff --git a/test-gdb b/test-gdb index 27ed093..876cb07 100755 --- a/test-gdb +++ b/test-gdb @@ -7,7 +7,11 @@ import common class Main(common.LkmcCliFunction): def __init__(self): - super().__init__() + super().__init__( + defaults={ + 'print_time': False, + }, + ) self.add_argument( 'tests', metavar='tests', @@ -19,8 +23,8 @@ found by searching for the Python test files. ) def timed_main(self): - run = self.import_path('run').Main() - run_gdb = self.import_path('run-gdb').Main() + run = self.import_path_main('run') + run_gdb = self.import_path_main('run-gdb') if self.env['arch'] in self.env['crosstool_ng_supported_archs']: if self.env['tests'] == []: test_scripts_noext = [] @@ -38,22 +42,17 @@ found by searching for the Python test files. else: test_scripts_noext = self.env['tests'] for test_script_noext in test_scripts_noext: - run_thread = threading.Thread(target=lambda: run( - archs=[self.env['arch']], - background=True, - baremetal=test_script_noext, - dry_run=self.env['dry_run'], - emulators=self.env['emulator'], - wait_gdb=True - )) + common_args = self.get_common_args() + common_args['baremetal'] = test_script_noext + test_id_string = self.test_setup(common_args, test_script_noext) + run_args = common_args.copy() + run_args['wait_gdb'] = True + run_args['background'] = True + run_thread = threading.Thread(target=lambda: run(**run_args)) run_thread.start() - run_gdb( - archs=[self.env['arch']], - baremetal=test_script_noext, - dry_run=self.env['dry_run'], - emulators=self.env['emulator'], - test=True, - ) + gdb_args = common_args.copy() + gdb_args['test'] = True + run_gdb(**gdb_args) run_thread.join() if __name__ == '__main__': diff --git a/test-userland b/test-userland index 4d7e002..d9e85b8 100755 --- a/test-userland +++ b/test-userland @@ -1,12 +1,17 @@ #!/usr/bin/env python3 import os +import sys import common class Main(common.LkmcCliFunction): def __init__(self): - super().__init__() + super().__init__( + defaults={ + 'print_time': False, + }, + ) self.add_argument( 'tests', metavar='tests', @@ -17,13 +22,10 @@ If given, run only the given tests. Otherwise, run all tests. ) def timed_main(self): - run = self.import_path('run').Main() + run = self.import_path_main('run') + run_args = self.get_common_args() if self.env['emulator'] == 'gem5': - extra_args = { - 'userland_build_id': 'static', - } - else: - extra_args = {} + run_args['userland_build_id'] = 'static' if self.env['tests'] == []: sources = [ 'add.c', @@ -46,21 +48,19 @@ If given, run only the given tests. Otherwise, run all tests. else: sources = self.env['tests'] for source in sources: - exit_status = run( - archs=[self.env['arch']], - dry_run=self.env['dry_run'], - userland=source, - emulators=[self.env['emulator']], - **extra_args, - ) + run_args['userland'] = source + test_id_string = self.test_setup(run_args, source) + run_args['background'] = True + exit_status = run(**run_args) # TODO forward all args attempt. In particular, --dry-run. #new_env = self.env.copy() #new_env['userland'] = source #new_env['emulator'] = emulator - #new_env.update(extra_args) + #new_env.update(run_args) #exit_status = run(**new_env) if exit_status != 0: - raise Exception('Test failed: {} {} {} {}'.format(emulator, arch, source, exit_status)) + self.log_error('test failed, program exit status: {} test id: {}'.format(exit_status, test_id_string)) + sys.exit(1) if __name__ == '__main__': Main().cli()