#!/usr/bin/env python2 import argparse import collections import ConfigParser import os import shutil import sys from utils import shellcmd from utils import FindBaseNaCl def Match(desc, includes, excludes, default_match): """Determines whether desc is a match against includes and excludes. 'desc' is a set of attributes, and 'includes' and 'excludes' are lists of sets of attributes. If 'desc' matches any element from 'excludes', the result is False. Otherwise, if 'desc' matches any element from 'includes', the result is True. Otherwise, the 'default_match' value is returned. """ for exclude in excludes: if exclude <= desc: return False for include in includes: if include <= desc: return True return default_match def RunNativePrefix(toolchain_root, target, attr, run_cmd): """Returns a prefix for running an executable for the target. For example, we may be running an ARM or MIPS target executable on an x86 machine and need to use an emulator. """ arch_map = { 'x8632' : '', 'x8664' : '', 'arm32' : os.path.join(toolchain_root, 'arm_trusted', 'run_under_qemu_arm'), 'mips32': os.path.join(toolchain_root, 'mips_trusted', 'run_under_qemu_mips32'), } attr_map = collections.defaultdict(str, { 'arm32-neon': ' -cpu cortex-a9', 'arm32-hwdiv-arm': ' -cpu cortex-a15', 'mips32-base': ' -cpu mips32r5-generic'}) prefix = arch_map[target] + attr_map[target + '-' + attr] if target == 'mips32': prefix = 'QEMU_SET_ENV=LD_LIBRARY_PATH=/usr/mipsel-linux-gnu/lib/ ' + prefix return (prefix + ' ' + run_cmd) if prefix else run_cmd def NonsfiLoaderArch(target): """Returns the arch for the nonsfi_loader""" arch_map = { 'arm32' : 'arm', 'x8632' : 'x86-32', 'mips32' : 'mips32', } return arch_map[target] def main(): """Framework for cross test generation and execution. Builds and executes cross tests from the space of all possible attribute combinations. The space can be restricted by providing subsets of attributes to specifically include or exclude. """ # pypath is where to find other Subzero python scripts. pypath = os.path.abspath(os.path.dirname(sys.argv[0])) root = FindBaseNaCl() # The rest of the attribute sets. targets = [ 'x8632', 'x8664', 'arm32', 'mips32' ] sandboxing = [ 'native', 'sandbox', 'nonsfi' ] opt_levels = [ 'Om1', 'O2' ] arch_attrs = { 'x8632': [ 'sse2', 'sse4.1' ], 'x8664': [ 'sse2', 'sse4.1' ], 'arm32': [ 'neon', 'hwdiv-arm' ], 'mips32': [ 'base' ] } flat_attrs = [] for v in arch_attrs.values(): flat_attrs += v arch_flags = { 'x8632': [], 'x8664': [], 'arm32': [], 'mips32': [] } # all_keys is only used in the help text. all_keys = '; '.join([' '.join(targets), ' '.join(sandboxing), ' '.join(opt_levels), ' '.join(flat_attrs)]) argparser = argparse.ArgumentParser( description=' ' + main.__doc__ + 'The set of attributes is the set of tests plus the following:\n' + all_keys, formatter_class=argparse.RawTextHelpFormatter) argparser.add_argument('--config', default='crosstest.cfg', dest='config', metavar='FILE', help='Test configuration file') argparser.add_argument('--print-tests', default=False, action='store_true', help='Print the set of test names and exit') argparser.add_argument('--include', '-i', default=[], dest='include', action='append', metavar='ATTR_LIST', help='Attributes to include (comma-separated). ' + 'Can be used multiple times.') argparser.add_argument('--exclude', '-e', default=[], dest='exclude', action='append', metavar='ATTR_LIST', help='Attributes to include (comma-separated). ' + 'Can be used multiple times.') argparser.add_argument('--verbose', '-v', default=False, action='store_true', help='Use verbose output') argparser.add_argument('--defer', default=False, action='store_true', help='Defer execution until all executables are built') argparser.add_argument('--no-compile', '-n', default=False, action='store_true', help="Don't build; reuse binaries from the last run") argparser.add_argument('--dir', dest='dir', metavar='DIRECTORY', default=('{root}/toolchain_build/src/subzero/' + 'crosstest/Output').format(root=root), help='Output directory') argparser.add_argument('--lit', default=False, action='store_true', help='Generate files for lit testing') argparser.add_argument('--toolchain-root', dest='toolchain_root', default=( '{root}/toolchain/linux_x86/pnacl_newlib_raw/bin' ).format(root=root), help='Path to toolchain binaries.') argparser.add_argument('--filetype', default=None, dest='filetype', help='File type override, one of {asm, iasm, obj}.') args = argparser.parse_args() # Run from the crosstest directory to make it easy to grab inputs. crosstest_dir = '{root}/toolchain_build/src/subzero/crosstest'.format( root=root) os.chdir(crosstest_dir) tests = ConfigParser.RawConfigParser() tests.read('crosstest.cfg') if args.print_tests: print 'Test name attributes: ' + ' '.join(sorted(tests.sections())) sys.exit(0) # includes and excludes are both lists of sets. includes = [ set(item.split(',')) for item in args.include ] excludes = [ set(item.split(',')) for item in args.exclude ] # If any --include args are provided, the default is to not match. default_match = not args.include # Delete and recreate the output directory, unless --no-compile was specified. if not args.no_compile: if os.path.exists(args.dir): if os.path.isdir(args.dir): shutil.rmtree(args.dir) else: os.remove(args.dir) if not os.path.exists(args.dir): os.makedirs(args.dir) # If --defer is specified, collect the run commands into deferred_cmds for # later execution. deferred_cmds = [] for test in sorted(tests.sections()): for target in targets: for sb in sandboxing: for opt in opt_levels: for attr in arch_attrs[target]: desc = [ test, target, sb, opt, attr ] if Match(set(desc), includes, excludes, default_match): exe = '{test}_{target}_{sb}_{opt}_{attr}'.format( test=test, target=target, sb=sb, opt=opt, attr=attr) extra = (tests.get(test, 'flags').split(' ') if tests.has_option(test, 'flags') else []) if args.filetype: extra += ['--filetype={ftype}'.format(ftype=args.filetype)] # Generate the compile command. cmp_cmd = ( ['{path}/crosstest.py'.format(path=pypath), '-{opt}'.format(opt=opt), '--mattr={attr}'.format(attr=attr), '--prefix=Subzero_', '--target={target}'.format(target=target), '--nonsfi={nsfi}'.format(nsfi='1' if sb=='nonsfi' else '0'), '--sandbox={sb}'.format(sb='1' if sb=='sandbox' else '0'), '--dir={dir}'.format(dir=args.dir), '--output={exe}'.format(exe=exe), '--driver={drv}'.format(drv=tests.get(test, 'driver'))] + extra + ['--test=' + t for t in tests.get(test, 'test').split(' ')] + arch_flags[target]) run_cmd_base = os.path.join(args.dir, exe) # Generate the run command. run_cmd = run_cmd_base if sb == 'sandbox': run_cmd = '{root}/run.py -q '.format(root=root) + run_cmd elif sb == 'nonsfi': run_cmd = ( '{root}/scons-out/opt-linux-{arch}/obj/src/nonsfi/' + 'loader/nonsfi_loader ').format( root=root, arch=NonsfiLoaderArch(target)) + run_cmd run_cmd = RunNativePrefix(args.toolchain_root, target, attr, run_cmd) else: run_cmd = RunNativePrefix(args.toolchain_root, target, attr, run_cmd) if args.lit: # Create a file to drive the lit test. with open(run_cmd_base + '.xtest', 'w') as f: f.write('# RUN: sh %s | FileCheck %s\n') f.write('cd ' + crosstest_dir + ' && \\\n') f.write(' '.join(cmp_cmd) + ' && \\\n') f.write(run_cmd + '\n') f.write('echo Recreate a failure using ' + __file__ + ' --toolchain-root=' + args.toolchain_root + (' --filetype=' + args.filetype if args.filetype else '') + ' --include=' + ','.join(desc) + '\n') f.write('# CHECK: Failures=0\n') else: if not args.no_compile: shellcmd(cmp_cmd, echo=args.verbose) if (args.defer): deferred_cmds.append(run_cmd) else: shellcmd(run_cmd, echo=True) for run_cmd in deferred_cmds: shellcmd(run_cmd, echo=True) if __name__ == '__main__': main()