|  | #!/usr/bin/env python3 | 
|  | # | 
|  | # Configure environment and run group of tests in it. | 
|  | # | 
|  | # Copyright (c) 2020-2021 Virtuozzo International GmbH | 
|  | # | 
|  | # This program is free software; you can redistribute it and/or | 
|  | # modify it under the terms of the GNU General Public License as | 
|  | # published by the Free Software Foundation. | 
|  | # | 
|  | # This program is distributed in the hope that it would be useful, | 
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | # GNU General Public License for more details. | 
|  | # | 
|  | # You should have received a copy of the GNU General Public License | 
|  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
|  |  | 
|  | import os | 
|  | import sys | 
|  | import argparse | 
|  | import shutil | 
|  | from pathlib import Path | 
|  |  | 
|  | from findtests import TestFinder | 
|  | from testenv import TestEnv | 
|  | from testrunner import TestRunner | 
|  |  | 
|  | def get_default_path(follow_link=False): | 
|  | """ | 
|  | Try to automagically figure out the path we are running from. | 
|  | """ | 
|  | # called from the build tree? | 
|  | if os.path.islink(sys.argv[0]): | 
|  | if follow_link: | 
|  | return os.path.dirname(os.readlink(sys.argv[0])) | 
|  | else: | 
|  | return os.path.dirname(os.path.abspath(sys.argv[0])) | 
|  | else:  # or source tree? | 
|  | return os.getcwd() | 
|  |  | 
|  | def make_argparser() -> argparse.ArgumentParser: | 
|  | p = argparse.ArgumentParser( | 
|  | description="Test run options", | 
|  | formatter_class=argparse.ArgumentDefaultsHelpFormatter) | 
|  |  | 
|  | p.add_argument('-n', '--dry-run', action='store_true', | 
|  | help='show me, do not run tests') | 
|  | p.add_argument('-j', dest='jobs', type=int, default=1, | 
|  | help='run tests in multiple parallel jobs') | 
|  |  | 
|  | p.add_argument('-d', dest='debug', action='store_true', help='debug') | 
|  | p.add_argument('-p', dest='print', action='store_true', | 
|  | help='redirects qemu\'s stdout and stderr to ' | 
|  | 'the test output') | 
|  | p.add_argument('-gdb', action='store_true', | 
|  | help="start gdbserver with $GDB_OPTIONS options " | 
|  | "('localhost:12345' if $GDB_OPTIONS is empty)") | 
|  | p.add_argument('-valgrind', action='store_true', | 
|  | help='use valgrind, sets VALGRIND_QEMU environment ' | 
|  | 'variable') | 
|  |  | 
|  | p.add_argument('-misalign', action='store_true', | 
|  | help='misalign memory allocations') | 
|  | p.add_argument('--color', choices=['on', 'off', 'auto'], | 
|  | default='auto', help="use terminal colors. The default " | 
|  | "'auto' value means use colors if terminal stdout detected") | 
|  | p.add_argument('-tap', action='store_true', | 
|  | help='produce TAP output') | 
|  |  | 
|  | g_env = p.add_argument_group('test environment options') | 
|  | mg = g_env.add_mutually_exclusive_group() | 
|  | # We don't set default for cachemode, as we need to distinguish default | 
|  | # from user input later. | 
|  | mg.add_argument('-nocache', dest='cachemode', action='store_const', | 
|  | const='none', help='set cache mode "none" (O_DIRECT), ' | 
|  | 'sets CACHEMODE environment variable') | 
|  | mg.add_argument('-c', dest='cachemode', | 
|  | help='sets CACHEMODE environment variable') | 
|  |  | 
|  | g_env.add_argument('-i', dest='aiomode', default='threads', | 
|  | help='sets AIOMODE environment variable') | 
|  |  | 
|  | p.set_defaults(imgfmt='raw', imgproto='file') | 
|  |  | 
|  | format_list = ['raw', 'bochs', 'cloop', 'parallels', 'qcow', 'qcow2', | 
|  | 'qed', 'vdi', 'vpc', 'vhdx', 'vmdk', 'luks', 'dmg'] | 
|  | g_fmt = p.add_argument_group( | 
|  | '  image format options', | 
|  | 'The following options set the IMGFMT environment variable. ' | 
|  | 'At most one choice is allowed, default is "raw"') | 
|  | mg = g_fmt.add_mutually_exclusive_group() | 
|  | for fmt in format_list: | 
|  | mg.add_argument('-' + fmt, dest='imgfmt', action='store_const', | 
|  | const=fmt, help=f'test {fmt}') | 
|  |  | 
|  | protocol_list = ['file', 'rbd', 'nbd', 'ssh', 'nfs', 'fuse'] | 
|  | g_prt = p.add_argument_group( | 
|  | '  image protocol options', | 
|  | 'The following options set the IMGPROTO environment variable. ' | 
|  | 'At most one choice is allowed, default is "file"') | 
|  | mg = g_prt.add_mutually_exclusive_group() | 
|  | for prt in protocol_list: | 
|  | mg.add_argument('-' + prt, dest='imgproto', action='store_const', | 
|  | const=prt, help=f'test {prt}') | 
|  |  | 
|  | g_bash = p.add_argument_group('bash tests options', | 
|  | 'The following options are ignored by ' | 
|  | 'python tests.') | 
|  | # TODO: make support for the following options in iotests.py | 
|  | g_bash.add_argument('-o', dest='imgopts', | 
|  | help='options to pass to qemu-img create/convert, ' | 
|  | 'sets IMGOPTS environment variable') | 
|  |  | 
|  | g_sel = p.add_argument_group('test selecting options', | 
|  | 'The following options specify test set ' | 
|  | 'to run.') | 
|  | g_sel.add_argument('-g', '--groups', metavar='group1,...', | 
|  | help='include tests from these groups') | 
|  | g_sel.add_argument('-x', '--exclude-groups', metavar='group1,...', | 
|  | help='exclude tests from these groups') | 
|  | g_sel.add_argument('--start-from', metavar='TEST', | 
|  | help='Start from specified test: make sorted sequence ' | 
|  | 'of tests as usual and then drop tests from the first ' | 
|  | 'one to TEST (not inclusive). This may be used to ' | 
|  | 'rerun failed ./check command, starting from the ' | 
|  | 'middle of the process.') | 
|  | g_sel.add_argument('tests', metavar='TEST_FILES', nargs='*', | 
|  | help='tests to run, or "--" followed by a command') | 
|  | g_sel.add_argument('--build-dir', default=get_default_path(), | 
|  | help='Path to iotests build directory') | 
|  | g_sel.add_argument('--source-dir', | 
|  | default=get_default_path(follow_link=True), | 
|  | help='Path to iotests build directory') | 
|  |  | 
|  | return p | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | args = make_argparser().parse_args() | 
|  |  | 
|  | env = TestEnv(source_dir=args.source_dir, | 
|  | build_dir=args.build_dir, | 
|  | imgfmt=args.imgfmt, imgproto=args.imgproto, | 
|  | aiomode=args.aiomode, cachemode=args.cachemode, | 
|  | imgopts=args.imgopts, misalign=args.misalign, | 
|  | debug=args.debug, valgrind=args.valgrind, | 
|  | gdb=args.gdb, qprint=args.print, | 
|  | dry_run=args.dry_run) | 
|  |  | 
|  | if len(sys.argv) > 1 and sys.argv[-len(args.tests)-1] == '--': | 
|  | if not args.tests: | 
|  | sys.exit("missing command after '--'") | 
|  | cmd = args.tests | 
|  | env.print_env() | 
|  | exec_pathstr = shutil.which(cmd[0]) | 
|  | if exec_pathstr is None: | 
|  | sys.exit('command not found: ' + cmd[0]) | 
|  | exec_path = Path(exec_pathstr).resolve() | 
|  | cmd[0] = str(exec_path) | 
|  | full_env = env.prepare_subprocess(cmd) | 
|  | os.chdir(exec_path.parent) | 
|  | os.execve(cmd[0], cmd, full_env) | 
|  |  | 
|  | testfinder = TestFinder(test_dir=env.source_iotests) | 
|  |  | 
|  | groups = args.groups.split(',') if args.groups else None | 
|  | x_groups = args.exclude_groups.split(',') if args.exclude_groups else None | 
|  |  | 
|  | group_local = os.path.join(env.source_iotests, 'group.local') | 
|  | if os.path.isfile(group_local): | 
|  | try: | 
|  | testfinder.add_group_file(group_local) | 
|  | except ValueError as e: | 
|  | sys.exit(f"Failed to parse group file '{group_local}': {e}") | 
|  |  | 
|  | try: | 
|  | tests = testfinder.find_tests(groups=groups, exclude_groups=x_groups, | 
|  | tests=args.tests, | 
|  | start_from=args.start_from) | 
|  | if not tests: | 
|  | raise ValueError('No tests selected') | 
|  | except ValueError as e: | 
|  | sys.exit(str(e)) | 
|  |  | 
|  | if args.dry_run: | 
|  | with env: | 
|  | print('\n'.join([os.path.basename(t) for t in tests])) | 
|  | else: | 
|  | with TestRunner(env, tap=args.tap, | 
|  | color=args.color) as tr: | 
|  | paths = [os.path.join(env.source_iotests, t) for t in tests] | 
|  | ok = tr.run_tests(paths, args.jobs) | 
|  | if not ok: | 
|  | sys.exit(1) |