|  | #!/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() |