1 #!/usr/bin/env python3 2 # 3 # Configure environment and run group of tests in it. 4 # 5 # Copyright (c) 2020-2021 Virtuozzo International GmbH 6 # 7 # This program is free software; you can redistribute it and/or 8 # modify it under the terms of the GNU General Public License as 9 # published by the Free Software Foundation. 10 # 11 # This program is distributed in the hope that it would be useful, 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 # GNU General Public License for more details. 15 # 16 # You should have received a copy of the GNU General Public License 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 19 import os 20 import sys 21 import argparse 22 import shutil 23 from pathlib import Path 24 25 from findtests import TestFinder 26 from testenv import TestEnv 27 from testrunner import TestRunner 28 29 def get_default_path(follow_link=False): 30 """ 31 Try to automagically figure out the path we are running from. 32 """ 33 # called from the build tree? 34 if os.path.islink(sys.argv[0]): 35 if follow_link: 36 return os.path.dirname(os.readlink(sys.argv[0])) 37 else: 38 return os.path.dirname(os.path.abspath(sys.argv[0])) 39 else: # or source tree? 40 return os.getcwd() 41 42 def make_argparser() -> argparse.ArgumentParser: 43 p = argparse.ArgumentParser( 44 description="Test run options", 45 formatter_class=argparse.ArgumentDefaultsHelpFormatter) 46 47 p.add_argument('-n', '--dry-run', action='store_true', 48 help='show me, do not run tests') 49 p.add_argument('-j', dest='jobs', type=int, default=1, 50 help='run tests in multiple parallel jobs') 51 52 p.add_argument('-d', dest='debug', action='store_true', help='debug') 53 p.add_argument('-p', dest='print', action='store_true', 54 help='redirects qemu\'s stdout and stderr to ' 55 'the test output') 56 p.add_argument('-gdb', action='store_true', 57 help="start gdbserver with $GDB_OPTIONS options " 58 "('localhost:12345' if $GDB_OPTIONS is empty)") 59 p.add_argument('-valgrind', action='store_true', 60 help='use valgrind, sets VALGRIND_QEMU environment ' 61 'variable') 62 63 p.add_argument('-misalign', action='store_true', 64 help='misalign memory allocations') 65 p.add_argument('--color', choices=['on', 'off', 'auto'], 66 default='auto', help="use terminal colors. The default " 67 "'auto' value means use colors if terminal stdout detected") 68 p.add_argument('-tap', action='store_true', 69 help='produce TAP output') 70 71 g_env = p.add_argument_group('test environment options') 72 mg = g_env.add_mutually_exclusive_group() 73 # We don't set default for cachemode, as we need to distinguish default 74 # from user input later. 75 mg.add_argument('-nocache', dest='cachemode', action='store_const', 76 const='none', help='set cache mode "none" (O_DIRECT), ' 77 'sets CACHEMODE environment variable') 78 mg.add_argument('-c', dest='cachemode', 79 help='sets CACHEMODE environment variable') 80 81 g_env.add_argument('-i', dest='aiomode', default='threads', 82 help='sets AIOMODE environment variable') 83 84 p.set_defaults(imgfmt='raw', imgproto='file') 85 86 format_list = ['raw', 'bochs', 'cloop', 'parallels', 'qcow', 'qcow2', 87 'qed', 'vdi', 'vpc', 'vhdx', 'vmdk', 'luks', 'dmg', 'vvfat'] 88 g_fmt = p.add_argument_group( 89 ' image format options', 90 'The following options set the IMGFMT environment variable. ' 91 'At most one choice is allowed, default is "raw"') 92 mg = g_fmt.add_mutually_exclusive_group() 93 for fmt in format_list: 94 mg.add_argument('-' + fmt, dest='imgfmt', action='store_const', 95 const=fmt, help=f'test {fmt}') 96 97 protocol_list = ['file', 'rbd', 'nbd', 'ssh', 'nfs', 'fuse'] 98 g_prt = p.add_argument_group( 99 ' image protocol options', 100 'The following options set the IMGPROTO environment variable. ' 101 'At most one choice is allowed, default is "file"') 102 mg = g_prt.add_mutually_exclusive_group() 103 for prt in protocol_list: 104 mg.add_argument('-' + prt, dest='imgproto', action='store_const', 105 const=prt, help=f'test {prt}') 106 107 g_bash = p.add_argument_group('bash tests options', 108 'The following options are ignored by ' 109 'python tests.') 110 # TODO: make support for the following options in iotests.py 111 g_bash.add_argument('-o', dest='imgopts', 112 help='options to pass to qemu-img create/convert, ' 113 'sets IMGOPTS environment variable') 114 115 g_sel = p.add_argument_group('test selecting options', 116 'The following options specify test set ' 117 'to run.') 118 g_sel.add_argument('-g', '--groups', metavar='group1,...', 119 help='include tests from these groups') 120 g_sel.add_argument('-x', '--exclude-groups', metavar='group1,...', 121 help='exclude tests from these groups') 122 g_sel.add_argument('--start-from', metavar='TEST', 123 help='Start from specified test: make sorted sequence ' 124 'of tests as usual and then drop tests from the first ' 125 'one to TEST (not inclusive). This may be used to ' 126 'rerun failed ./check command, starting from the ' 127 'middle of the process.') 128 g_sel.add_argument('tests', metavar='TEST_FILES', nargs='*', 129 help='tests to run, or "--" followed by a command') 130 g_sel.add_argument('--build-dir', default=get_default_path(), 131 help='Path to iotests build directory') 132 g_sel.add_argument('--source-dir', 133 default=get_default_path(follow_link=True), 134 help='Path to iotests build directory') 135 136 return p 137 138 139 if __name__ == '__main__': 140 args = make_argparser().parse_args() 141 142 env = TestEnv(source_dir=args.source_dir, 143 build_dir=args.build_dir, 144 imgfmt=args.imgfmt, imgproto=args.imgproto, 145 aiomode=args.aiomode, cachemode=args.cachemode, 146 imgopts=args.imgopts, misalign=args.misalign, 147 debug=args.debug, valgrind=args.valgrind, 148 gdb=args.gdb, qprint=args.print, 149 dry_run=args.dry_run) 150 151 if len(sys.argv) > 1 and sys.argv[-len(args.tests)-1] == '--': 152 if not args.tests: 153 sys.exit("missing command after '--'") 154 cmd = args.tests 155 env.print_env() 156 exec_pathstr = shutil.which(cmd[0]) 157 if exec_pathstr is None: 158 sys.exit('command not found: ' + cmd[0]) 159 exec_path = Path(exec_pathstr).resolve() 160 cmd[0] = str(exec_path) 161 full_env = env.prepare_subprocess(cmd) 162 os.chdir(exec_path.parent) 163 os.execve(cmd[0], cmd, full_env) 164 165 testfinder = TestFinder(test_dir=env.source_iotests) 166 167 groups = args.groups.split(',') if args.groups else None 168 x_groups = args.exclude_groups.split(',') if args.exclude_groups else None 169 170 group_local = os.path.join(env.source_iotests, 'group.local') 171 if os.path.isfile(group_local): 172 try: 173 testfinder.add_group_file(group_local) 174 except ValueError as e: 175 sys.exit(f"Failed to parse group file '{group_local}': {e}") 176 177 try: 178 tests = testfinder.find_tests(groups=groups, exclude_groups=x_groups, 179 tests=args.tests, 180 start_from=args.start_from) 181 if not tests: 182 raise ValueError('No tests selected') 183 except ValueError as e: 184 sys.exit(str(e)) 185 186 if args.dry_run: 187 with env: 188 print('\n'.join([os.path.basename(t) for t in tests])) 189 else: 190 with TestRunner(env, tap=args.tap, 191 color=args.color) as tr: 192 paths = [os.path.join(env.source_iotests, t) for t in tests] 193 ok = tr.run_tests(paths, args.jobs) 194 if not ok: 195 sys.exit(1) 196