diff options
Diffstat (limited to 'test-chill/testchill')
-rw-r--r-- | test-chill/testchill/__init__.py | 0 | ||||
-rw-r--r-- | test-chill/testchill/__main__.py | 379 | ||||
-rw-r--r-- | test-chill/testchill/_cpp_validate_env.py | 654 | ||||
-rw-r--r-- | test-chill/testchill/_extract.py | 98 | ||||
-rw-r--r-- | test-chill/testchill/chill.py | 345 | ||||
-rw-r--r-- | test-chill/testchill/cpp_validate.py | 165 | ||||
-rw-r--r-- | test-chill/testchill/cpp_validate/grammar.txt | 124 | ||||
-rw-r--r-- | test-chill/testchill/cpp_validate/src/validate.cpp | 29 | ||||
-rw-r--r-- | test-chill/testchill/gcov.py | 213 | ||||
-rw-r--r-- | test-chill/testchill/omega.py | 29 | ||||
-rw-r--r-- | test-chill/testchill/test.py | 381 | ||||
-rw-r--r-- | test-chill/testchill/util.py | 185 |
12 files changed, 2602 insertions, 0 deletions
diff --git a/test-chill/testchill/__init__.py b/test-chill/testchill/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test-chill/testchill/__init__.py diff --git a/test-chill/testchill/__main__.py b/test-chill/testchill/__main__.py new file mode 100644 index 0000000..ae9f29e --- /dev/null +++ b/test-chill/testchill/__main__.py @@ -0,0 +1,379 @@ +#TODO: setup and cleanup mechanism + +import argparse +import logging +import os +import pickle +import sys +import textwrap + +from . import chill +from . import gcov +from . import omega +from . import test +from . import util + + + +def make_local(argsns, arg_parser): + """ + Make the local test case list. A convinience function for testing a local copy of chill. + @params argsns Command line arguments + @params arg_parser The ArgumentParser object + """ + util.mkdir_p(os.path.join(os.getcwd(), '.staging'), temp=True) + argsns.wd = os.path.join(os.getcwd(), '.staging/wd') + argsns.bin_dir = os.path.join(os.getcwd(), '.staging/bin') + argsns.chill_tc_dir = os.path.join(os.getcwd(), 'test-cases') # formally from the commandline + argsns.chill_dir = os.path.abspath(argsns.chill_dir) + argsns.omega_dir = os.path.abspath(argsns.omega_dir) + + util.mkdir_p(argsns.wd) + util.mkdir_p(argsns.bin_dir) + util.shell('cp', [os.path.join(argsns.chill_dir, 'examples/cuda-chill/cudaize.lua'), argsns.wd]) + util.shell('cp', [os.path.join(argsns.chill_dir, 'examples/cuda-chill/cudaize.py'), argsns.wd]) + + chill_version = argsns.chill_version + for config in chill.ChillConfig.configs(argsns.omega_dir, argsns.chill_dir, argsns.bin_dir, version=chill_version): + build_testcase = chill.BuildChillTestCase(config, coverage_set=argsns.coverage_set) + yield build_testcase + batch_file = os.path.join(argsns.chill_tc_dir, config.name() + '.tclist') + for tc in make_batch_testcaselist(argsns, arg_parser, batch_file): + yield tc + +def make_repo(argsns, arg_parser): + """ + Make the repo test case list. A convinience function for testing chill from the repsitory. + @params argsns Command line arguments + @params arg_parser The ArgumentParser object + """ + util.mkdir_p(os.path.join(os.getcwd(), '.staging'), temp=True) + argsns.bin_dir = os.path.join(os.getcwd(), '.staging/bin') + argsns.repo_dir = os.path.join(os.getcwd(), '.staging/repo') + argsns.chill_tc_dir = os.path.join(os.getcwd(), 'test-cases') # formally from the commandline + argsns.wd = os.path.join(os.getcwd(), '.staging/wd') + + util.mkdir_p(argsns.bin_dir) + util.mkdir_p(argsns.repo_dir) + util.mkdir_p(argsns.wd) + + #TODO: Should these be hard coded? + repo_root = 'shell.cs.utah.edu/uusoc/facility/res/hallresearch/svn_repo/resRepo/projects' + for version in ['release', 'dev']: + new_args = util.copy(argsns) + if version == 'dev': + chill_repo = 'svn+ssh://{}@{}/chill/branches/cuda-chill-rose'.format(new_args.svnuser, repo_root) + chill_repo_name = 'chill' + omega_repo = 'svn+ssh://{}@{}/omega/branches/cuda-omega-rose'.format(new_args.svnuser, repo_root) + omega_repo_name = 'omega' + elif version == 'release': + chill_repo = 'svn+ssh://{}@{}/chill/release'.format(new_args.svnuser, repo_root) + chill_repo_name = 'chill-release' + omega_repo = 'svn+ssh://{}@{}/omega/release'.format(new_args.svnuser, repo_root) + omega_repo_name = 'omega-release' + new_args.omega_dir = os.path.join(new_args.repo_dir, omega_repo_name) + new_args.chill_dir = os.path.join(new_args.repo_dir, chill_repo_name) + util.shell('svn', ['export', '--force', omega_repo, new_args.omega_dir]) + util.shell('svn', ['export', '--force', chill_repo, new_args.chill_dir]) + util.shell('cp', [os.path.join(new_args.chill_dir, 'examples/cuda-chill/cudaize.lua'), new_args.wd]) + if version == 'dev': + util.shell('cp', [os.path.join(new_args.chill_dir, 'examples/cuda-chill/cudaize.py'), new_args.wd]) + # do omega: (just build it for now) + yield omega.BuildOmegaTestCase(new_args.omega_dir ,version) + # do chill + for config in chill.ChillConfig.configs(new_args.omega_dir, new_args.chill_dir, new_args.bin_dir, version=version): + yield chill.BuildChillTestCase(config, coverage_set=argsns.coverage_set) + batch_file = os.path.join(argsns.chill_tc_dir, config.name() + '.tclist') + if os.path.exists(batch_file): + for tc in make_batch_testcaselist(new_args, arg_parser, batch_file): + yield tc + +def make_runchill_testcase(argsns): + """ + Make a RunChillTestCase from the given argument namespace + @param argsns Command line arguments + """ + assert (argsns.chill_dir != None) or (argsns.bin_dir != None) + + ### Required parameters ### + wd = os.path.abspath(argsns.wd) + chill_script = os.path.abspath(argsns.chill_script) + chill_src = os.path.abspath(argsns.chill_src) + coverage_set = argsns.coverage_set + + ### Options to pass to the chill test case ### + options = dict() + options['compile-src'] = argsns.chill_test_compile_src + options['run-script'] = argsns.chill_test_run_script + options['compile-gensrc'] = argsns.chill_test_compile_gensrc + options['check-run-script-stdout'] = argsns.chill_test_check_run_script + options['coverage'] = argsns.chill_test_coverage + + ### choose interface language from script extension if none is given ### + if argsns.chill_script_lang is None: + argsns.chill_script_lang = chill.ChillConfig.ext_to_script_lang(chill_script.split('.')[-1]) + + config = chill.ChillConfig( + omega_dir = os.path.abspath(argsns.omega_dir) if argsns.omega_dir != None else None, + chill_dir = os.path.abspath(argsns.chill_dir) if argsns.chill_dir != None else None, + bin_dir = os.path.abspath(argsns.bin_dir) if argsns.bin_dir != None else None, + build_cuda = argsns.build_cuda, + script_lang = argsns.chill_script_lang, + version = argsns.chill_version) + + return chill.RunChillTestCase(config, chill_script, chill_src, wd=wd, options=options, coverage_set=coverage_set) + +def make_buildchill_testcase(argsns): + """ + Make a BuilChillTestCase from the given argument namespace + @param argsns Command line arguments + """ + assert argsns.chill_dir != None + assert argsns.omega_dir != None + + coverage_set = argsns.coverage_set + + options = dict() + options['coverage'] = argsns.chill_build_coverage + + config = chill.ChillConfig( + omega_dir = os.path.abspath(argsns.omega_dir) if argsns.omega_dir != None else None, + chill_dir = os.path.abspath(argsns.chill_dir) if argsns.chill_dir != None else None, + bin_dir = os.path.abspath(argsns.bin_dir) if argsns.bin_dir != None else None, + build_cuda = argsns.build_cuda, + script_lang = argsns.chill_script_lang, + version = argsns.chill_version) + + return chill.BuildChillTestCase(config, options=options, coverage_set=coverage_set) + +def make_batch_testcaselist(argsns, arg_parser, batch_file=None): + """ + Make a list of test cases from a file. + @param argsns The parent argument namespace + @param arg_parser The argument parser. Used to parse lines from the batch file. + @param batch_file The batch file name + """ + if batch_file is None: + batch_file = argsns.batch_file + with open(batch_file, 'r') as f: + for txt_line in f.readlines(): + if len(txt_line.strip()) == 0: continue # skip empty lines + if txt_line.strip().startswith('#'): continue # skip comment lines + args = util.applyenv(txt_line.strip()) # replace environment variables with thier values + args = args.split() # split by whitespace + for tc in args_to_tclist(args, arg_parser, argsns): + yield tc + +@util.callonce +def add_local_args(arg_parser): + """ + Command line arguments for the local command + @param arg_parser The local ArgumentParser object + """ + arg_parser.add_argument('chill_dir', metavar='chill-home') + arg_parser.add_argument('-v', '--chill-branch', dest='chill_version', default='dev', choices=['release','dev']) + # - Testing should consider all interface languages. Will uncomment if testing takes too long + # arg_parser.add_argument('-i', '--interface-lang', nargs=1, action='append', dest='chill_script_lang_list', choices=['script','lua','python']) + # arg_parser.add_argument('-t', '--testcase-dir', dest='chill_tc_dir', default=os.path.join(os.getcwd(), 'test-cases/')) + arg_parser.set_defaults(wd=os.path.join(os.getcwd(), '.staging/wd')) # - These don't seem to work + arg_parser.set_defaults(bin_dir=os.path.join(os.getcwd(), '.staging/bin')) # - + +@util.callonce +def add_repo_args(arg_parser): + """ + Command line arguments for the repo command + @param arg_parser The local ArgumentParser object + """ + arg_parser.add_argument('svnuser', metavar='svn-user-name') + +def add_boolean_option(arg_parser, name, dest, default=True, help_on=None, help_off=None): + """ + Add a boolean option. + @param parg_parser The ArgumentParser object + @param name The name of the parameter + @param dest The dest parameter passed to the ArgumentParser + @param default The default value + @param help_on The help parameter for the true option + @param help_off The help parameter for the false option + """ + group = arg_parser.add_mutually_exclusive_group() + group.add_argument('--' + name, action='store_true', dest=dest, default=default, help=help_on) + group.add_argument('--no-' + name, action='store_false', dest=dest, default=default, help=help_off) + +def add_chill_common_args(arg_parser): + """ + Common chill command line arguments. + @param arg_parser The ArgumentParser object + """ + arg_parser.add_argument('-v', '--chill-branch', dest='chill_version', default='dev', choices=['release','dev']) + cuda_group = arg_parser.add_mutually_exclusive_group() + cuda_group.add_argument('-u', '--target-cuda', action='store_const', const=True, dest='build_cuda', default=False, help='Test cuda-chill. (Default is chill)') + cuda_group.add_argument('-c', '--target-c', action='store_const', const=False, dest='build_cuda', default=False, help='Test chill. (Default is chill)') + arg_parser.add_argument('-i', '--interface-lang', dest='chill_script_lang', choices=['script','lua','python'], default=None, help='Chill interface language. If an interface language is not specified, it will be determined by the script file name.') + +@util.callonce +def add_chill_run_args(arg_parser): + """ + Command line arguments specific to running a chill test case + @param arg_parser The ArgumentParser object + """ + arg_parser.add_argument('chill_script', help='Chill script file.', metavar='chill-script') + arg_parser.add_argument('chill_src', help='Chill source file.', metavar='chill-src') + add_boolean_option(arg_parser, 'compile-src', dest='chill_test_compile_src', default=True, help_on='Compile source file.', help_off='Do not compile source file.') + add_boolean_option(arg_parser, 'run-script', dest='chill_test_run_script', default=True, help_on='Run chill script.', help_off='Do not run chill script.') + add_boolean_option(arg_parser, 'compile-gensrc', dest='chill_test_compile_gensrc', default=True, help_on='Compile generated source file', help_off='Do not compile generated source file.') + add_boolean_option(arg_parser, 'check-run-script', dest='chill_test_check_run_script', default=False, help_on='Diff stdout from chill script against a benchmark.') + add_boolean_option(arg_parser, 'test-coverage', 'chill_test_coverage', default=True, help_on='Run chill and record code coverage (default).', help_off='Run chill normally without recording code coverage.') + +@util.callonce +def add_chill_build_args(arg_parser): + """ + Command line arguments specific to building chill and testing the build process + @params arg_parser The ArgumentParser object + """ + add_boolean_option(arg_parser, 'build-coverage', 'chill_build_coverage', default=True, help_on='Build chill for code coverage flags (default).', help_off='Build chill normally without code coverage flags.') + +@util.callonce +def add_local_command(command_group): + """ + Add local to the subcommand group + @param command_group the subparser group object + """ + local_arg_parser = command_group.add_parser('local') + add_local_args(local_arg_parser) + local_arg_parser.set_defaults(func=lambda a, ap: make_local(a, ap)) + +@util.callonce +def add_repo_command(command_group): + """ + Add repo to the subcommand group + @param command_group the subparser group object + """ + repo_arg_parser = command_group.add_parser('repo') + add_repo_args(repo_arg_parser) + repo_arg_parser.set_defaults(func=lambda a, ap: make_repo(a, ap)) + +@util.callonce +def add_chill_command(command_group): + """ + Add chill-testcase to the subcommand group + @param command_group The subparser group object + """ + chill_arg_parser = command_group.add_parser('chill-testcase') + add_chill_run_args(chill_arg_parser) + add_chill_common_args(chill_arg_parser) + chill_arg_parser.set_defaults(func=lambda a, ap: [make_runchill_testcase(a)]) + +@util.callonce +def add_buildchill_command(command_group): + """ + Add build-chill-testcase to the subcommand group + @param command_group The subparser group object + """ + buildchill_arg_parser = command_group.add_parser('build-chill-testcase') + add_chill_common_args(buildchill_arg_parser) + add_chill_build_args(buildchill_arg_parser) + buildchill_arg_parser.set_defaults(func=lambda a, ap: [make_buildchill_testcase(a)]) + +@util.callonce +def add_batch_args(arg_parser): + """ + Command line arguments for the batch file command + @param arg_parser The ArgumentParser object + """ + arg_parser.add_argument('batch_file', help='Batch file', metavar='batch-filename') + +@util.callonce +def add_batch_command(command_group): + """ + Add batch command to the subcommand group + @param command_group The subparser group object + """ + batch_arg_parser = command_group.add_parser('batch') + add_batch_args(batch_arg_parser) + batch_arg_parser.set_defaults(func=make_batch_testcaselist) + +@util.callonce +def add_commands(arg_parser): + """ + Add the subcommand group + @param arg_parser The ArgumentParser object + """ + command_group = arg_parser.add_subparsers(title='commands') + add_local_command(command_group) + add_repo_command(command_group) + add_chill_command(command_group) + add_buildchill_command(command_group) + add_batch_command(command_group) + +@util.callonce +def add_global_args(arg_parser): + """ + Add arguments that are used for most subcommands + @param arg_parser The ArgumentParser object + """ + arg_parser.add_argument('-w', '--working-dir', dest='wd', default=os.getcwd(), help='The working directory. (Defaults to the current directory)', metavar='working-directory') + arg_parser.add_argument('-R', '--rose-home', dest='rose_dir', default=os.getenv('ROSEHOME'), help='Rose home directory. (Defaults to ROSEHOME)', metavar='rose-home') + arg_parser.add_argument('-C', '--chill-home', dest='chill_dir', default=os.getenv('CHILLHOME'), help='Chill home directory. (Defaults to CHILLHOME)', metavar='chill-home') + arg_parser.add_argument('-O', '--omega-home', dest='omega_dir', default=os.getenv('OMEGAHOME'), help='Omega home directory. (Defaults to OMEGAHOME)', metavar='omega-home') + arg_parser.add_argument('-b', '--binary-dir', dest='bin_dir', default=None, help='Binary directory.', metavar='bin-dir') + +@util.callonce +def make_argparser(): + """ + Create the argument parser. + """ + arg_parser = argparse.ArgumentParser( + prog='python -m testchill', + description=textwrap.dedent('''\ + + To test a local working copy of chill (from the development branch): + -------------------------------------------------------------------- + - Set $OMEGAHOME and compile omega. + - Run `python -m testchill local <path-to-chill>` + + '''), + epilog='EPILOG', + formatter_class=argparse.RawDescriptionHelpFormatter) + + add_global_args(arg_parser) + add_commands(arg_parser) + + # ... + + return arg_parser + +def args_to_tclist(args=sys.argv[1:], arg_parser=make_argparser(), argsns=None, **kwargs): + """ + Parse one line and return a list of test cases. + @params args Raw arguments to be passed to the ArgumentParser object (defaults to sys.args[1:]) + @params arg_parser The ArgumentParser object (defaults to an ArgumentParser returned by make_argparser()) + @params argsns The top level argument namespace (defaults to None) + """ + if not argsns is None: # if an argsns is given, + argsns = util.copy(argsns, exclude=['func']) # make a shallow copy, (excluding func) + argsns = arg_parser.parse_args(args, namespace=argsns) + for k,v in kwargs.items(): + setattr(argsns, k, v) + return list(argsns.func(argsns, arg_parser)) + +@util.callonce +def main(): + #coverage = gcov.GcovSet() + coverage=None + results = list(test.run(args_to_tclist(coverage_set=coverage))) + test.pretty_print_results(results) + #util.rmtemp() + #coverage.pretty_print() + + with open('coverage.pickle', 'wb') as f: + pickle.dump(coverage, f, 2) + with open('testresults.pickle', 'wb') as f: + pickle.dump(results, f, 2) + + if any(s.failed() or s.errored() for s in results): + sys.exit(1) + +if __name__ == '__main__': + main() + diff --git a/test-chill/testchill/_cpp_validate_env.py b/test-chill/testchill/_cpp_validate_env.py new file mode 100644 index 0000000..9ef5a71 --- /dev/null +++ b/test-chill/testchill/_cpp_validate_env.py @@ -0,0 +1,654 @@ +import ast as _pyast +import collections as _pycollections +import functools as _pyfunctools +import itertools as _pyitertools +import random as _pyrandom +import struct as _pystruct +import types as _pytypes + +from . import util as _chill_util + +_pylambdatype = _pycollections.namedtuple('LambdaType', ['paramtypes','exprtype']) +_pyarraytype = _pycollections.namedtuple('ArrayType', ['dimensions','basetype']) + +_runtime_globals = dict({ + '_pyitertools':_pyitertools, + '_pyrandom':_pyrandom + }) + +def _evalexpr(expr, target_type, bindings): + glbls = dict(bindings) + glbls.update(_runtime_globals) + if target_type is None: + pytype = None + else: + pytype = target_type.getpytype() + expr = _pyast.Expression(expr.compile_expr(pytype)) + expr = _pyast.fix_missing_locations(expr) + return eval(compile(expr, '<string>', 'eval'), glbls) + +def _addbindings(expr, binding_frame): + if hasattr(expr, 'binding_stack'): + expr.binding_stack = [binding_frame] + expr.binding_stack + return expr + + +class _TreeNode(object): + def print_tree(self, stream=None, indent=0): + strname = type(self).__name__ + stream.write(strname + ':\n') + indent += 2 + for k,v in vars(self).items(): + if isinstance(v, _TreeNode): + stream.write(('{}{}:'.format(' '*indent, k))) + v.print_tree(stream, indent + len(k)) + elif isinstance(v, list): + stream.write(('{}{}: [\n'.format(' '*indent, k))) + for itm in v: + if isinstance(itm, _TreeNode): + stream.write(' '*indent) + itm.print_tree(stream, indent + len(k) + 1) + else: + stream.write('{}{}\n'.format(' '*(indent + 1), str(itm))) + else: + stream.write(('{}{}: {}\n'.format(' '*indent, k, str(v)))) + +class _CppType(_TreeNode): + def __init__(self): + pass + + def __repr__(self): + return "{}".format(str(self)) + + def statictype(self, bindings): + return self + + def formatdata(self, data): + raise NotImplementedError + + def get_cdecl_stmt(self, param_name): + raise NotImplementedError + + def get_cread_stmt(self, param_name, istream_name, dims): + raise NotImplementedError + + def get_cwrite_stmt(self, param_name, ostream_name, dims): + raise NotImplementedError + + def getfreevars(self, glbls): + raise NotImplementedError + + +class _CppPrimitiveType(_CppType): + _bycppname = { + 'char': ('char', 'c', 1, False, False, True, False), + 'signed char': ('signed char', 'b', 1, True, False, False, False), + 'unsigned char': ('unsigned char', 'B', 1, True, False, False, False), + 'short': ('short', 'h', 2, True, False, False, True), + 'unsigned short': ('unsigned short', 'H', 2, True, False, False, False), + 'int': ('int', 'i', 4, True, False, False, True), + 'unsigned int': ('unsigned int', 'I', 4, True, False, False, False), + 'long': ('long', 'l', 4, True, False, False, True), + 'unsigned long': ('unsigned long', 'L', 4, True, False, False, False), + 'long long': ('long long', 'q', 8, True, False, False, True), + 'unsigned long long': ('unsigned long long', 'Q', 8, True, False, False, False), + 'float': ('float', 'f', 4, False, True, False, True), + 'double': ('double', 'd', 8, False, True, False, True) + } + def __init__(self, cppname, structfmt, size, isint, isfloat, ischar, issigned): + _CppType.__init__(self) + self.cppname = cppname + self.size = size + self.size_expr = 'sizeof(' + cppname + ')' + self.structfmt = structfmt + self.isint = isint + self.isfloat = isfloat + self.ischar = ischar + self.issigned = issigned + + @staticmethod + def get_from_cppname(cppname): + return _CppPrimitiveType(*_CppPrimitiveType._bycppname[cppname]) + + def getfreevars(self, glbls): + return set() + + def getpytype(self): + if self.ischar: + return str + elif self.isint: + return int + elif self.isfloat: + return float + + def __str__(self): + return self.cppname + + def formatdata(self, data): + return [1], _pystruct.pack(self.structfmt, data) + + def get_cdecl_stmt(self, param_name): + return '{} {};'.format(self.cppname, param_name) + + def get_cread_stmt(self, param_name, istream_name, dims): + return '{}.read((const char*)&{}, {});'.format(istream_name, param_name, self.size_expr) + + def get_cwrite_stmt(self, param_name, ostream_name, dims): + return '{}.write((const char*)&{}, {});'.format(ostream_name, param_name, self.size_expr) + + +class _CppVoidType(_CppType): + def __init__(self): + self.cppname = 'void' + + def getfreevars(self, glbls): + return set() + + def getpytype(self): + return type(None) + + def __str__(self): + return 'void' + + +class _CppArrayType(_CppType): + def __init__(self, basetype, dims=[None]): + _CppType.__init__(self) + self.basetype = basetype + self.dimensions = dims + + def getfreevars(self, glbls): + freevars = self.basetype.getfreevars(glbls) + for fv in iter(d.getfreevars(glbls) for d in self.dimensions if hasattr(d, 'getfreevars')): + freevars = freevars | fv + return freevars + + def getpytype(self): + return _pyarraytype(self.dimensions, self.basetype.getpytype()) + + def __str__(self): + return '{}[{}]'.format(str(self.basetype), ']['.join(map(str,self.dimensions))) + + def statictype(self, bindings): + dim_list = list() + for dim in self.dimensions: + if dim is None: + dim_list.append(None) + else: + dim_list.append(_evalexpr(dim, _CppPrimitiveType.get_from_cppname('int'), bindings)) + return _CppArrayType(self.basetype.statictype(bindings), dim_list) + + def _formatdata_array(self, unit_length, data): + read_length = 0 + if _chill_util.python_version_major == 2: + read_data = '' + else: + read_data = bytes() + while read_length < len(data): + for i in range(unit_length): + _, b = self.basetype.formatdata(data[read_length+i]) + read_data += b + read_length += unit_length + return read_data + + def formatdata(self, data): + prod = lambda l: _pyfunctools.reduce(lambda a,v: a*v, l, 1) + if self.dimensions[0] is None: + return self.dimensions, self._formatdata_array(prod(self.dimensions[1:]), data) + else: + return self.dimensions, self._formatdata_array(prod(self.dimensions), data) + + def get_cdecl_stmt(self, param_name): + return '{} {}[{}];'.format(str(self.basetype), param_name, ']['.join(map(str,self.dimensions))) + + def get_cread_stmt(self, param_name, istream_name, dims): + length = _pyfunctools.reduce(lambda a,v: a*v, self.dimensions) + #TODO: use dims + if isinstance(self.basetype, _CppPrimitiveType): + size_expr = '{}*{}'.format(length, self.basetype.size_expr) + return '{}.read((char*){}, {});'.format(istream_name, param_name, size_expr) + else: + raise NotImplementedError + + def get_cwrite_stmt(self, param_name, ostream_name, dims): + length = _pyfunctools.reduce(lambda a,v: a*v, self.dimensions) + #TODO: use dims + if isinstance(self.basetype, _CppPrimitiveType): + size_expr = '{}*{}'.format(length, self.basetype.size_expr) + return '{}.write((char*){}, {});'.format(ostream_name, param_name, size_expr) + else: + raise NotImplementedError + + +class _CppPointerType(_CppType): + def __init__(self, basetype): + _CppType.__init__(self) + self.basetype = basetype + + def getfreevars(self, glbls): + return self.basetype.getfreevars(glbls) + + def getpytype(self): + return self.basetype.getpytype() + + def __str__(self): + return '{}*'.format(str(self.basetype)) + + def statictype(self, bindings): + return _CppPointerType(self.basetype.statictype(bindings)) + + def formatdata(self, data): + if isinstance(data, list): + if _chill_util.python_version_major == 2: + read_data = '' + else: + read_data = bytes() + for data_item in data: + next_dims, b = self.basetype.formatdata(data_item) + read_data += b + return [len(data)] + next_dims, read_data + else: + dims, fmt_data = self.basetype.formatdata(data) + return [1] + dims, fmt_data + + +class _CppReferenceType(_CppType): + def __init__(self, basetype): + _CppType.__init__(self) + self.basetype = basetype + + def getfreevars(self, glbls): + return self.basetype.getfreevars(glbls) + + def getpytype(self): + return self.basetype.getpytype() + + def __str__(self): + return '{}&'.format(str(self.basetype)) + + def statictype(self, bindings): + return _CppReferenceType(self.basetype.statictype(bindings)) + + def formatdata(self, data): + dims, fmt_data = self.basetype.formatdata(data) + return dims, fmt_data + + +class _Parameter(_TreeNode): + def __init__(self, name, cpptype, direction, init_expr=None): + self.name = name + self.direction = direction + self.cpptype = cpptype + self.init_expr = init_expr + self._generated = None + + @staticmethod + def order_by_freevars(param_list, glbls=set()): + defined_names = set() + parameter_names = set(p.name for p in param_list) + param_queue = _pycollections.deque(param_list) + while len(param_queue): + param = param_queue.popleft() + freevars = (parameter_names & param.getfreevars(glbls)) - defined_names + if not len(freevars): + defined_names.add(param.name) + yield param + else: + param_queue.append(param) + + def getfreevars(self, glbls=set()): + freevars = set() + if self.init_expr is not None: + freevars = freevars | self.init_expr.getfreevars(glbls) + freevars = freevars | self.cpptype.getfreevars(glbls) + return freevars + + def generatedata(self, bindings=dict()): + if self._generated is None: + if self.init_expr is None: + py_data = None + else: + py_data = _evalexpr(self.init_expr, self.cpptype, bindings) + static_type = self.cpptype.statictype(bindings) + dims, data = static_type.formatdata(py_data) + self._generated = (self.name, static_type, dims, data) + return self.name, static_type, dims, data + else: + return self._generated + + +class _Procedure(_TreeNode): + def __init__(self, name, rtype, parameters): + self.name = name + self.rtype = rtype + self.parameters = parameters + self.binding_stack = [] + self._bindings = None + self._params_orderd = None + self._invoke_str = '{}({});'.format(self.name, ','.join([p.name for p in parameters])) + + def _order_params(self): + if not self._params_orderd: + self._params_orderd = list(_Parameter.order_by_freevars(self.parameters)) + + def _compute_bindings(self, global_bindings): + local_bindings = dict(global_bindings) + if self._bindings is None: + new_bindings = dict() + for binding_frame in self.binding_stack: + for name, (ctype, expr) in binding_frame.items(): + value = _evalexpr(expr, ctype, local_bindings) + new_bindings[name] = value + local_bindings[name] = value + self._bindings = new_bindings + local_bindings.update(self._bindings) + return local_bindings + + def generatedata(self, direction_list, global_bindings=None): + self._order_params() + if global_bindings is None: + global_bindings = dict() + bindings = self._compute_bindings(global_bindings) + for param in (p for p in self._params_orderd if p.direction in direction_list): + p_name, p_statictype, p_dims, p_data = param.generatedata(bindings) + #TODO: add binding + yield p_name, p_statictype, p_dims, p_data + + def generatedecls(self, bindings): + for p_name, p_statictype, p_dims, p_data in self.generatedata(['in','out','inout'], bindings): + yield p_statictype.get_cdecl_stmt(p_name) + #for p_name, p_statictype, p_dims, p_data in self.generatedata('out', bindings): + # yield p_statictype.get_cdecl_stmt(p_name) + + def generatereads(self, direction_list, stream, bindings): + for p_name, p_statictype, p_dims, p_data in self.generatedata(direction_list, bindings): + yield p_statictype.get_cread_stmt(p_name, stream, p_dims) + + def generatewrites(self, stream, bindings): + for p_name, p_statictype, p_dims, p_data in self.generatedata(['inout', 'out'], bindings): + yield p_statictype.get_cwrite_stmt(p_name, stream, p_dims) + + def getinvokestr(self): + return self._invoke_str + + +class _Expr(_TreeNode): + def __init__(self): + pass + + def getfreevars(self, glbls): + raise NotImplementedError + + def compile_to_lambda(self, glbls, target_type): + args = _pyast.arguments(list(_pyast.Name(n, _pyast.Param()) for n in self.getfreevars(self, glbls)), None, None, []) + expr = _pyast.Expression(_pyast.Lambda(args, self.compile_expr(target_type))) + expr = _pyast.fix_missing_locations(expr) + return eval(compile(expr, '<string>', 'eval')) + + def compile_expr(self, target_type): + raise NotImplementedError + + +class _ConstantExpr(_Expr): + def __init__(self, value): + self.value = value + + def compile_expr(self, target_type): + if target_type is None: + return _pyast.parse(self.value, '<string>', 'eval').body + elif target_type == chr: + return _pyast.Str(chr(self.value)) + elif target_type == int: + return _pyast.Num(int(self.value)) + elif target_type == str: + return _pyast.Str(str(self.value)) + elif target_type == float: + return _pyast.Num(float(self.value)) + + def getfreevars(self, glbls): + return set() + + def __str__(self): + return self.value + + +class _NameExpr(_Expr): + def __init__(self, name): + self.name = name + + def compile_expr(self, target_type): + return _pyast.Name(self.name, _pyast.Load()) + + def getfreevars(self, glbls): + if self.name not in glbls: + return set([self.name]) + else: + return set() + + def __str__(self): + return self.name + + +class _AttributeExpr(_Expr): + def __init__(self, expr, name): + self.expr = expr + self.name = name + + def compile_expr(self, target_type): + return _pyast.Attribute( + self.expr.compile_expr(None), + self.name, + _pyast.Load()) + + def getfreevars(self, glbls): + return self.expr.getfreevars(glbls) + + def __str__(self): + return '{}.{}'.format(str(self.expr), self.name) + + +class _BinExpr(_Expr): + _optypes = { + '+': _pyast.Add, + '-': _pyast.Sub, + '*': _pyast.Mult, + '**': _pyast.Pow, + '/': _pyast.Div + } + def __init__(self, left, op, right): + self.left = left + self.right = right + self.op = op + + def compile_expr(self, target_type): + return _pyast.BinOp( + self.left.compile_expr(target_type), + _BinExpr._optypes[self.op](), + self.right.compile_expr(target_type)) + + def getfreevars(self, glbls): + return self.left.getfreevars(glbls) | self.right.getfreevars(glbls) + + def __str__(self): + return '({}{}{})'.format(str(self.left),self.op,str(self.right)) + + +class _UnaryExpr(_Expr): + _optypes = { + '-': _pyast.USub + } + def __init__(self, op, expr): + self.op = op + self.expr = expr + + def compile_expr(self, target_type): + return _pyast.UnaryOp( + _UnaryExpr._optypes[self.op](), + self.expr.compile_expr(target_type)) + + def getfreevars(self, glbls): + return self.expr.getfreevars(glbls) + + def __str__(self): + return '({}{})'.format(self.op, str(self.expr)) + + +class _LambdaExpr(_Expr): + def __init__(self, params, expr): + self.params = params + self.expr = expr + + def compile_expr(self, target_type): + if target_type is None: + exprtype = None + else: + assert hasattr(target_type, 'paramtypes') + assert hasattr(target_type, 'exprtype') + exprtype = target_type.exprtype + if _chill_util.python_version_major == 2: + return _pyast.Lambda( + _pyast.arguments([_pyast.Name(p, _pyast.Param()) for p in self.params], None, None, []), + self.expr.compile_expr(exprtype)) + else: + return _pyast.Lambda( + _pyast.arguments([_pyast.arg(p, None) for p in self.params], None, None, [], None, None, [], []), + self.expr.compile_expr(exprtype)) + + def getfreevars(self, glbls): + new_glbls = set(glbls) + new_glbls = new_glbls | set(self.params) + return self.expr.getfreevars(new_glbls) + + def __str__(self): + return 'lambda {}:{}'.format(','.join(map(str,self.params)), str(self.expr)) + + +class _InvokeExpr(_Expr): + def __init__(self, func, parameters): + self.func = func + self.parameters = parameters + + def compile_expr(self, target_type): + if target_type is None: + lt = None + else: + lt = _pylambdatype([None for p in self.parameters], target_type) + return _pyast.Call( + self.func.compile_expr(lt), + [p.compile_expr(None) for p in self.parameters], + [], + None, + None) + + def getfreevars(self, glbls): + return set( + self.func.getfreevars(glbls) | + _pyfunctools.reduce(lambda a,v: a | v.getfreevars(glbls), self.parameters, set())) + + def __str__(self): + return '{}({})'.format(str(self.func),','.join(map(str,self.parameters))) + + +class _Generator(_Expr): + def __init__(self): + _Expr.__init__(self) + + +class _MatrixGenerator(_Generator): + def __init__(self, dims, genexpr): + self.dimensions = dims + self.genexpr = genexpr + + def _compile_dims(self, target_type): + if hasattr(target_type, 'dimensions'): + dim_exprs = list() + assert len(target_type.dimensions) == len(self.dimensions) + for i, d in enumerate(target_type.dimensions): + if d is None: + d = self.dimensions[i] + dim_exprs += [d.compile_expr(int)] + else: + dim_exprs = [d.compile_expr(int) for d in self.dimensions] + return _pyast.List(dim_exprs, _pyast.Load()) + + def _lambda_type(self, target_type): + if hasattr(target_type, 'dimensions'): + return _pylambdatype([int for d in target_type.dimensions], target_type.basetype) + else: + return _pylambdatype([int for d in self.dimensions], target_type) + + def compile_expr(self, target_type): + assert target_type is not None + dims = self._compile_dims(target_type) + ltype = self._lambda_type(target_type) + + #def array(func,dims): + # return [func(*d) for d in itertools.product(*(map(range,dims))] + elt_expr = _pyast.Call(self.genexpr.compile_expr(ltype), [], [], _pyast.Name('_d', _pyast.Load()), None) # func(*d) + # elt_expr = _pyast.Call(_pyast.Name('tuple', _pyast.Load()), [_pyast.Name('_d', _pyast.Load()), elt_expr], [], None, None) # tuple(d, func(*d)) + pdt_expr = _pyast.Attribute(_pyast.Name('_pyitertools', _pyast.Load()), 'product', _pyast.Load()) # itertools.product + itr_expr = _pyast.Call(_pyast.Name('map', _pyast.Load()), [_pyast.Name('range', _pyast.Load()), dims], [], None, None) # map(range,dims) + itr_expr = _pyast.Call(pdt_expr, [], [], itr_expr, None) # itertools.product(*(map(range,dims))) + return _pyast.ListComp( + elt_expr, + [_pyast.comprehension(_pyast.Name('_d', _pyast.Store()), itr_expr, [])]) + + def getfreevars(self, glbls): + return set( + self.genexpr.getfreevars(glbls) | + _pyfunctools.reduce(lambda a,v: a | v.getfreevars(glbls), filter(lambda x: x is not None, self.dimensions), set())) + + def __str__(self): + return 'matrix([{}],{})'.format(','.join(map(str,self.dimensions)),str(self.genexpr)) + + +class _RandomExpr(_Expr): + def __init__(self, minexpr, maxexpr): + self.minexpr = minexpr + self.maxexpr = maxexpr + self.expr = _BinExpr( + _BinExpr( + _InvokeExpr(_AttributeExpr(_NameExpr('_pyrandom'),'random'),[]), + '*', + _BinExpr(maxexpr, '-', minexpr)), + '+', + minexpr) + + def getfreevars(self, glbls): + return self.minexpr.getfreevars(glbls) | self.maxexpr.getfreevars(glbls) + + def compile_expr(self, target_type): + if target_type == int: + return _pyast.Call(_pyast.Name('int', _pyast.Load()),[self.expr.compile_expr(float)],[],None,None) + elif target_type == float: + return self.expr.compile_expr(target_type) + elif target_type is None: + return self.expr.compile_expr(None) + assert False + + def __str__(self): + return 'random({},{})'.format(str(self.minexpr),str(self.maxexpr)) + + +### What to import from * ### +addbindings = _addbindings + +CppType = _CppType +CppPrimitiveType = _CppPrimitiveType +CppVoidType = _CppVoidType +CppArrayType = _CppArrayType +CppPointerType = _CppPointerType + +ConstantExpr = _ConstantExpr +NameExpr = _NameExpr +AttributeExpr = _AttributeExpr +BinExpr = _BinExpr +UnaryExpr = _UnaryExpr +LambdaExpr = _LambdaExpr +InvokeExpr = _InvokeExpr +MatrixGenerator = _MatrixGenerator +RandomExpr = _RandomExpr + +Procedure = _Procedure +Parameter = _Parameter + diff --git a/test-chill/testchill/_extract.py b/test-chill/testchill/_extract.py new file mode 100644 index 0000000..f6984ac --- /dev/null +++ b/test-chill/testchill/_extract.py @@ -0,0 +1,98 @@ +import collections +import os +import os.path +import itertools +import re + +from . import util + +if util.python_version_major == 2: + from HTMLParser import HTMLParser +else: + from html.parser import HTMLParser + +class _TagExtractor(HTMLParser): + _comment_style_expr = { + 'c': [('/(/)+',r'[\n]'),(r'/\*',r'\*/')], + 'cc': [('/(/)+',r'[\n]'),(r'/\*',r'\*/')], + 'cpp': [('/(/)+',r'[\n]'),(r'/\*',r'\*/')], + 'h': [('/(/)+',r'[\n]'),(r'/\*',r'\*/')], + 'hh': [('/(/)+',r'[\n]'),(r'/\*',r'\*/')], + 'hpp': [('/(/)+',r'[\n]'),(r'/\*',r'\*/')], + 'py': [('#+',r'[\n]'),('\'\'\'',),('"""',)], + 'script': [('#+',r'[\n]')], + 'lua': [(r'--\[\[',r'\]\]--')] + } + + def __init__(self, tagname): + HTMLParser.__init__(self) + self.tagname = tagname + self._readin = False + self._value = '' + + def handle_starttag(self, tag, attrs): + if tag == self.tagname: + self._readin = True + self._attrs = dict(attrs) + + def handle_endtag(self, tag): + if tag == self.tagname: + self._readin = False + self._tag_list.append((self._value, self._attrs)) + self._value = '' + + def handle_data(self, txt): + if self._readin: + self._value += txt + + @classmethod + def _parse(cls, tagname, txt): + reader = cls(tagname) + reader._readin = False + reader._value = '' + reader._tag_list = [] + reader.feed(txt) + return reader._tag_list + + @classmethod + def _get_commentstyles(cls, ext): + for comment_style in cls._comment_style_expr[ext]: + if len(comment_style) == 1: + start_expr = comment_style[0] + end_expr = comment_style[0] + elif len(comment_style) == 2: + start_expr = comment_style[0] + end_expr = comment_style[1] + yield start_expr, end_expr + + @classmethod + def _commented(cls, txt, ext): + comment_spans = list() + for start_expr, end_expr in cls._get_commentstyles(ext): + pos = 0 + while pos < len(txt): + start_match = re.search(start_expr, txt[pos:]) + if start_match: + start_pos = pos + start_match.end() + end_match = re.search(end_expr, txt[start_pos:]) + if end_match: + end_pos = start_pos + end_match.start() + pos = start_pos + end_match.end() + else: + end_pos = len(txt) + pos = end_pos + comment_spans.append((start_pos, end_pos)) + else: + break + for span in sorted(comment_spans, key=lambda s: s[0]): + yield txt[span[0]:span[1]] + + @classmethod + def extract_tag(cls, tagname, filename, wd=os.getcwd()): + with open(os.path.join(wd, filename), 'r') as f: + txt = f.read() + ext = filename.split('.')[-1] + return cls._parse(tagname, '\n'.join(cls._commented(txt, ext))) + +extract_tag = _TagExtractor.extract_tag + diff --git a/test-chill/testchill/chill.py b/test-chill/testchill/chill.py new file mode 100644 index 0000000..bfd9c81 --- /dev/null +++ b/test-chill/testchill/chill.py @@ -0,0 +1,345 @@ +#TODO: Re-Document +#TODO: highlight test implementation hooks + +import os +import os.path + +from . import gcov +from . import test +from . import util +from . import cpp_validate + + +class ChillConfig(object): + _config_map = dict(('-'.join(map(str,k)),v) for k,v in [ + (('dev',False,'script'), ('chill', 'depend-chill', 'chill', '')), + (('dev',False,'lua'), ('chill-lua', 'depend-chill', 'chill', 'SCRIPT_LANG=lua')), + (('dev',False,'python'), ('chill-python', 'depend-chill', 'chill', 'SCRIPT_LANG=python')), + (('dev',True,'lua'), ('cuda-chill', 'depend-cuda-chill', 'cuda-chill', '')), + (('dev',True,'python'), ('cuda-chill-python', 'depend-cuda-chill', 'cuda-chill', 'SCRIPT_LANG=python')), + (('release',False,'script'), ('chill-release', 'depend', 'chill', '')), + (('release',True,'lua'), ('cuda-chill-release', 'depend-cuda-chill', 'cuda-chill', '')) + ]) + + def __init__(self, omega_dir=None, chill_dir=None, bin_dir=None, build_cuda=False, script_lang=None, version='dev'): + self.version = version + self.build_cuda = build_cuda + self.script_lang = script_lang + self.omega_dir = omega_dir + self.chill_dir = chill_dir + self.bin_dir = bin_dir + if self.script_lang is None: + self.script_lang = self.default_script_lang() + + def _get(self, index): + return ChillConfig._config_map[self.version + '-' + str(self.build_cuda) + '-' + self.script_lang][index] + + def default_script_lang(self): + if self.build_cuda: + return 'lua' + else: + return 'script' + + def name(self): + return self._get(0) + + def make_depend_target(self): + return self._get(1) + + def make_target(self): + return self._get(2) + + def make_args(self): + return self._get(3) + + def _buildfunc(self, cc, link=True): + if not link: + compile_args = ['-c'] + elif link and cc == 'nvcc': + compile_args = ['-L/usr/local/cuda/lib64/lib', '-lcuda', '-lcudart', '-lstdc++', '-lrt'] + else: + compile_args = ['-lstdc++', '-lrt'] + + def build(src, dest, args=[], defines={}, wd=None): + if wd is None: + wd = os.path.dirname(src) + args += ['-D{}={}'.format(k,v) for k, v in defines.items()] + dest = os.path.join(wd, dest) + stdout = util.shell(cc, args + [src, '-o', dest] + compile_args, wd=wd) + return dest, stdout + return build + + def compile_src_func(self): + return self._buildfunc('gcc', False) + + def compile_gensrc_func(self): + if self.build_cuda: + return self._buildfunc('nvcc', False) + else: + return self._buildfunc('gcc', False) + + def build_src_func(self): + return self._buildfunc('gcc') + + def build_gensrc_func(self): + if self.build_cuda: + return self._buildfunc('nvcc') + else: + return self._buildfunc('gcc') + + def env(self): + chill_env = {'OMEGAHOME':self.omega_dir} + if self.version == 'release' and self.build_cuda: + chill_env['CUDACHILL']='true' + return chill_env + + @staticmethod + def ext_to_script_lang(ext): + return {'script':'script', 'lua':'lua', 'py':'python'}[ext] + + @staticmethod + def configs(omega_dir, chill_dir, bin_dir, build_cuda=None, script_lang=None, version=None): + all_configs = [ + (False, 'script', 'dev'), + (False, 'script', 'release'), + (False, 'lua', 'dev'), + (False, 'python', 'dev'), + (True, 'lua', 'dev'), + (True, 'lua', 'release'), + (True, 'python', 'dev')] + + pred_list = [lambda x: True] + if not build_cuda is None: + pred_list += [lambda x: x[0] == build_cuda] + if not script_lang is None: + pred_list += [lambda x: x[1] == script_lang] + if not version is None: + pred_list += [lambda x: x[2] == version] + + cond = lambda x: all(p(x) for p in pred_list) + + return iter(ChillConfig(omega_dir, chill_dir, bin_dir, *conf) for conf in filter(cond, all_configs)) + + +# - - # +# - Test case for building chill - # +# - - # +class BuildChillTestCase(test.TestCase): + """ + Test case for building chill. + """ + + default_options = { + 'coverage': False # compile for coverage + } + + def __init__(self, config, options={}, coverage_set=None): + """ + @param config chill configuration object + @param options options for building chill and testing the build process + @param coverage_set GcovSet object to record coverage + """ + assert isinstance(config, ChillConfig) + if config.script_lang == None: + config.script_lang = config.default_script_lang() + self.config = config + super(BuildChillTestCase,self).__init__(self.config.name()) + self._set_options(options, coverage_set) + + def _set_options(self, options, coverage_set): + self.options = dict(BuildChillTestCase.default_options) + self.options.update(options) + + self.build_env = self.config.env() + self.build_args = self.config.make_args() + if self.options['coverage']: + self.build_args += ' "TEST_COVERAGE=1"' + coverage_set.addprogram(self.config.name(), self.config.chill_dir) + + def setUp(self): + """ + Called before run, outside of the context of a test case + """ + # clean up any coverage files from a previous build + util.shell('rm', ['-f', '*.gcno'], wd=self.config.chill_dir) + util.shell('rm', ['-f', '*.gcov'], wd=self.config.chill_dir) + util.shell('rm', ['-f', '*.gcda'], wd=self.config.chill_dir) + + util.shell('make clean', wd=self.config.chill_dir) + util.shell('make veryclean', wd=self.config.chill_dir) + + def run(self): + """ + Build chill + """ + depend_target = self.config.make_depend_target() + target = self.config.make_target() + util.shell('make', ['clean'], wd=self.config.chill_dir) + util.shell('make', ['veryclean'], wd=self.config.chill_dir) + util.shell('make', [depend_target] + [self.build_args], env=self.build_env, wd=self.config.chill_dir) + util.shell('make', [target] + [self.build_args], env=self.build_env, wd=self.config.chill_dir) + return self.make_pass() + + def tearDown(self): + """ + Called after run, outside of the context of a test case. + If a binary directory is specified, rename and move the executable there, otherwise, just rename it. + """ + if self.test_result.passed(): + if self.config.bin_dir: + util.shell('mv', [os.path.join(self.config.chill_dir, self.config.make_target()), os.path.join(self.config.bin_dir, self.config.name())]) + else: + util.shell('mv', [os.path.join(self.config.chill_dir, self.config.make_target()), os.path.join(self.config.chill_dir, self.config.name())]) + + +# - - # +# - Test case for running chill - # +# - - # +class RunChillTestCase(test.SequencialTestCase): + """ + Test case for running and testing chill. + """ + + default_options={ + 'compile-src':True, # Compile original source file + 'run-script':True, # Run chill script + 'compile-gensrc':True, # Compile generated source file + 'check-run-script-stdout':False, # Diff stdout from run_script() against an expected value (from a .stdout file) + 'coverage':False, # Record coverage + + 'fail-compile-src':False, # Expect compile_src to fail (TODO: not implemented) + 'fail-run-script':False, # Expect run_script to fail (TODO: not implemented) + } + + def __init__(self, config, chill_script, chill_src, wd=None, options={}, coverage_set=None): + """ + @param config Chill configuration object + @param chill_script The path to the chill script. + @param chill_src The path to the source file that the script uses. + @param wd The working directory. Where the script will be executed, compiled, and tested. + @param options Additional testing options. + @param coverage_set GcovSet object to record coverage + """ + if config.script_lang == None: + config.script_lang = ChillConfig.ext_to_script_lang(chill_script.split('.')[-1]) + + assert isinstance(config, ChillConfig) + + super(RunChillTestCase,self).__init__(config.name() + ':' + os.path.basename(chill_script)) + + self.config = config + self.wd = wd if (wd != None) else os.getcwd() + + self.chill_src_path = os.path.abspath(chill_src) + self.chill_script_path = os.path.abspath(chill_script) + self.chill_bin = os.path.join(self.config.bin_dir, self.config.name()) + self.chill_src = os.path.basename(self.chill_src_path) + self.chill_script = os.path.basename(self.chill_script_path) + self.chill_gensrc = self._get_gensrc(self.chill_src) + self.chill_gensrc_path = os.path.join(self.wd, self.chill_gensrc) + + self.compile_src_func = self.config.compile_src_func() + self.compile_gensrc_func = self.config.compile_gensrc_func() + self.build_src_func = self.config.build_src_func() + self.build_gensrc_func = self.config.build_gensrc_func() + + self._set_options(options, coverage_set) + + def _set_options(self, options, coverage_set=None): + self.options = dict(RunChillTestCase.default_options) + self.options.update(options) + + self.out = dict() + self.expected = dict() + + if self.options['compile-src']: + self.add_subtest('compile-src', self.compile_src) + if self.options['run-script']: + self.add_subtest('run-script', self.run_script) + if self.options['compile-gensrc']: + self.add_subtest('compile-generated-src', self.compile_gensrc) + self.add_subtest('check-run-script-validate', self.check_run_script_validate) + if self.options['check-run-script-stdout']: + self.add_subtest('check-run-script-stdout', self.check_run_script_stdout) + with open('.'.join(self.chill_script_path.split('.')[0:-1] + ['stdout']), 'r') as f: + self.expected['run_script.stdout'] = f.read() + self.coverage_set = coverage_set + + def _get_gensrc(self, src): + """ + The name of the generated source file. + """ + if not self.config.build_cuda: + return 'rose_' + src + else: + return 'rose_' + '.'.join(src.split('.')[0:-1]) + '.cu' + + def setUp(self): + """ + Called before any tests are performed. Moves source and script files into the working directory + and removes any gcov data files + """ + util.shell('cp', [self.chill_src_path, self.chill_src], wd=self.wd) + util.shell('cp', [self.chill_script_path, self.chill_script], wd=self.wd) + #TODO: check for chill binary + + def tearDown(self): + """ + Called when the test is complete + """ + util.shell('rm', ['-f', self.chill_src], wd=self.wd) + util.shell('rm', ['-f', self.chill_script], wd=self.wd) + util.shell('rm', ['-f', self.chill_gensrc], wd=self.wd) + if self.options['coverage'] and self.coverage_set is not None: + self.coverage_set.addcoverage(self.config.name(), self.name) + + # - - # + # - Chill Tests - # + # - - # + + def compile_src(self, tc): + """ + Attempts to compile the source file before any transformation is performed. Fails if gcc fails. + """ + #self.out['compile_src.stdout'] = util.shell('gcc', ['-c', self.chill_src], wd=self.wd) + _, self.out['compile_src.stdout'] = self.compile_src_func(self.chill_src, util.mktemp(), wd=self.wd) + return tc.make_pass() + + def run_script(self, tc): + """ + Attempts to run the script file. Fails if chill exits with a non-zero result. + """ + # look for cudaize.lua for cuda-chill + if self.config.build_cuda and not os.path.exists(os.path.join(self.wd, 'cudaize.lua')): + return test.TestResult.make_error(test.FailedTestResult, tc, reason='cudaize.lua was missing from the working directory.') + self.out['run_script.stdout'] = util.shell(self.chill_bin, [self.chill_script], wd=self.wd) + return tc.make_pass() + + def compile_gensrc(self, tc): + """ + Attempts to compile the generated source file. Fails if gcc fails. + """ + #self.out['compile_gensrc.stdout'] = util.shell('gcc', ['-c', self.chill_gensrc], wd=self.wd) + _, self.out['compile_gensrc.stdout'] = self.compile_gensrc_func(self.chill_gensrc_path, util.mktemp(), wd=self.wd) + return tc.make_pass() + + def check_run_script_validate(self, tc): + """ + Generate test data and run both the original source and generated source against it. + Fail if any test procedure generates different output. + """ + for name, (is_valid, is_faster) in cpp_validate.run_from_src(self.chill_src, self.chill_gensrc, self.build_src_func, self.build_gensrc_func, wd=self.wd): + self.out['check_run_script_validate.{}'.format(name)] = (is_valid, is_faster) + if not is_valid: + return tc.make_fail('test procedure {} returned invalid results.'.format(name)) + return tc.make_pass() + + def check_run_script_stdout(self, tc): + """ + Diff stdout from run_script against an expected stdout + """ + isdiff, diff = util.isdiff(self.out['run_script.stdout'], self.expected['run_script.stdout']) + if isdiff: + return test.TestResult.make_fail(test.FailedTestResult, tc, reason='Diff:\n' + diff) + return tc.make_pass() + diff --git a/test-chill/testchill/cpp_validate.py b/test-chill/testchill/cpp_validate.py new file mode 100644 index 0000000..5f19a12 --- /dev/null +++ b/test-chill/testchill/cpp_validate.py @@ -0,0 +1,165 @@ +import collections +import os +import pickle +import re + +from . import util + +_script_parser = None +def _get_script_parser(): + """ + Retrieve the test code generator language parser. + """ + global _script_parser + if _script_parser is None: + with open('testchill/cpp_validate/parser.pickle','rb') as f: + _script_parser = pickle.load(f) + return _script_parser + +def _parse_testproc_python(txt, glbls=None): + """ + Parse text as a python testchill._cpp_validate_env.Procedure object" + @param txt Python code to be parsed. + @param glbls A python global dict. + """ + if glbls is None: + glbls = dict() + exec('import testchill._cpp_validate_env\nfrom testchill._cpp_validate_env import *', None, glbls) + return eval(txt, glbls) + +def _parse_testproc_script(txt, glbls=None): + """ + Parse text as test code generator language. + @param txt Code to be parsed. + @param glbls A python global dict. + """ + parser = _get_script_parser() + proc = list(parser.parse(util.textstream(txt)))[0] + if glbls is None: + from . import _cpp_validate_env + glbls = dict() + return _cpp_validate_env.addbindings(proc, glbls) + else: + return proc + +def _parse_testproc_iter(srcfile, wd=os.getcwd()): + """ + Parse all test procedures from a file. + @param srcfile File path to parse. + @param wd Working directory. + """ + default_attrs = {'lang':'script', 'define':'dict()'} + for txt, parsed_attrs in util.extract_tag('test', srcfile, wd): + attrs = collections.defaultdict(lambda: None) + attrs.update(default_attrs) + attrs.update(parsed_attrs) + if attrs['lang'] == 'python': + yield _parse_testproc_python(txt), attrs + if attrs['lang'] == 'script': + yield _parse_testproc_script(txt), attrs + +#def _compile_gpp(src, dest): +# """ +# Compile a signle C++ source file into an executable object. +# @param src Source file path. +# @param dest Object file path. +# """ +# util.shell('g++', ['-o', dest, src, '-lrt']) + +def _test_time(control_time, test_time): + """ + Determine if test ran faster than control. + @param control_time Time taken by control. + @param test_time Time taken by test. + """ + return control_time > test_time + +def _test_validate(control_dataout_path, test_dataout_path): + """ + Determine if control and test computed the same values. + @param control_dataout_path Path to the file writen by control. + @param test_dataout_path Path to the file writen by test. + """ + with open(control_dataout_path, 'rb') as controlfile: + with open(test_dataout_path, 'rb') as testfile: + return controlfile.read() == testfile.read() + +def _run_test_validate_time(control_obj_path, test_obj_path, datain_path): + control_dataout_path = util.mktemp() + test_dataout_path = util.mktemp() + control_time, = eval(util.shell(os.path.abspath(control_obj_path), [datain_path, control_dataout_path])) + test_time, = eval(util.shell(os.path.abspath(test_obj_path), [datain_path, test_dataout_path])) + return _test_validate(control_dataout_path, test_dataout_path), _test_time(control_time, test_time) + +#def _run_test_validate_time(control_obj_path, test_obj_path, datain_path, wd): + #control_obj_path = '.'.join(control_src_path.split('.')[:-1]) + #test_obj_path = '.'.join(test_src_path.split('.')[:-1]) + + + + #util.set_tempfile(control_obj_path) + #util.set_tempfile(test_obj_path) + #_compile_gpp(control_src_path, control_obj_path) + #_compile_gpp(test_src_path, test_obj_path) + + #test_validate, test_time = _run_test_validate_time(control_obj_path, test_obj_path, datain_path) + #return test_validate, test_time + +def _generate_initial_data(test_proc, srcfile, defines, wd=os.getcwd()): + filename = os.path.join(wd, os.path.basename(srcfile)) + '.data' + with open(filename, 'wb') as f: + for p_name, p_type, p_dims, p_data in test_proc.generatedata(['in', 'inout'], defines): + f.write(p_data) + for p_name, p_type, p_dims, p_data in test_proc.generatedata(['out'], defines): + f.write(p_data) + return filename + +def _format_insertion_dict(test_proc, src_path, defines): + with open(src_path, 'r') as src_file: + return { + 'defines' : '\n'.join(['#define {} {}'.format(k,v) for k,v in defines.items()]), + 'test-proc' : src_file.read(), + 'declarations' : '\n'.join(test_proc.generatedecls(defines)), + 'read-in' : '\n'.join(test_proc.generatereads(['in','inout'], 'datafile_initialize', defines)), + 'read-out' : '\n'.join(test_proc.generatereads(['out'], 'datafile_initialize', defines)), + 'run' : test_proc.getinvokestr(), + 'write-out' : '\n'.join(test_proc.generatewrites('datafile_out', defines)), + } + +def _write_generated_code(test_proc, src_path, defines, dest_filename, wd): + insertion_dict = _format_insertion_dict(test_proc, src_path, defines) + dest_file_path = os.path.join(wd, dest_filename) + with open('testchill/cpp_validate/src/validate.cpp', 'r') as template_file: + with open(dest_file_path, 'w') as destfile: + template_text = template_file.read() + desttext = template_text + for match in re.finditer(r'(?P<indent>[ \t]*)//# (?P<name>[^\s]+)', template_text): + destlines = insertion_dict[match.group('name')].splitlines() + indent = match.group('indent') + match_text = match.group() + repl_text = '\n'.join([indent + line for line in destlines]) + desttext = desttext.replace(match_text, repl_text) + destfile.write(desttext) + return dest_file_path + +def run_from_src(control_src, test_src, build_control_func, build_test_func, wd=os.getcwd()): + control_src_path = os.path.join(wd, control_src) + test_src_path = os.path.join(wd, test_src) + gen_control_obj_path = os.path.join(wd, 'control_obj') + gen_test_obj_path = os.path.join(wd, 'test_obj') + for test_proc, attrs in _parse_testproc_iter(control_src, wd): + defines = eval(attrs['define']) + datafile = _generate_initial_data(test_proc, control_src_path, defines, wd=wd) + gen_control_src = _write_generated_code(test_proc, control_src_path, defines, 'gen_control.cc', wd) + gen_test_src = _write_generated_code(test_proc, test_src_path, defines, 'gen_test.cc', wd) + gen_control_obj, _ = build_control_func(gen_control_src, gen_control_obj_path) + gen_test_obj, _ = build_test_func(gen_test_src, gen_test_obj_path) + util.set_tempfile(gen_control_obj) + util.set_tempfile(gen_test_obj) + yield attrs['name'], _run_test_validate_time(gen_control_obj, gen_test_obj, datafile) + +def parse_defines_iter(src, wd=os.getcwd()): + for txt, attrs in util.extract_tag('test', src, wd): + if 'define' in attrs.keys(): + yield eval(attrs['define']) + diff --git a/test-chill/testchill/cpp_validate/grammar.txt b/test-chill/testchill/cpp_validate/grammar.txt new file mode 100644 index 0000000..fdb8c00 --- /dev/null +++ b/test-chill/testchill/cpp_validate/grammar.txt @@ -0,0 +1,124 @@ +terminals: + Identifier '[a-zA-Z_][a-zA-Z_0-9]*' + NumericLiteral '[0-9]+(\.[0-9]+)?' + Comment '\#([^\x0a])*' + WS '\s+' +ignore: WS, <NL>, Comment +rules: +<proc-unit> ::= + <with-stmt>:w => w + <proc>:p => p +<with-stmt> ::= + 'with' '{' <with-decl-list-opt>:decls '}' <proc-unit>:p => addbindings(p, dict(decls)) +<with-decl-list-opt> ::= + eps => [] + <with-decl-list>:l => l +<with-decl-list> ::= + <with-decl-list>:l ',' <with-decl>:decl => l + [decl] + <with-decl>:decl => [decl] +<with-decl> ::= + Identifier:name ':' <expr>:e => (name, (None, e)) + <c-type>:ctype Identifier:name ':' <expr>:e => (name, (ctype, e)) + +<proc> ::= + 'procedure' <c-type>:rtype Identifier:name '(' <param-list-opt>:plist ')' + => Procedure(name, rtype, plist) +<c-type> ::= + <c-type>:bt '*' => CppPointerType(bt) + <c-type>:bt <c-array-dim-list>:dims => CppArrayType(bt, dims) + 'void' => CppVoidType() + 'char' => CppPrimitiveType.get_from_cppname('char') + 'signed' 'char' => CppPrimitiveType.get_from_cppname('signed char') + 'unsigned' 'char' => CppPrimitiveType.get_from_cppname('unsigned char') + 'short' => CppPrimitiveType.get_from_cppname('short') + 'unsigned' 'short' => CppPrimitiveType.get_from_cppname('unsigned short') + 'int' => CppPrimitiveType.get_from_cppname('int') + 'unsigned' 'int' => CppPrimitiveType.get_from_cppname('unsigned int') + 'long' => CppPrimitiveType.get_from_cppname('long') + 'unsigned' 'long' => CppPrimitiveType.get_from_cppname('unsigned long') + 'long' 'long' => CppPrimitiveType.get_from_cppname('long long') + 'unsigned' 'long' 'long' => CppPrimitiveType.get_from_cppname('unsigned long long') + 'float' => CppPrimitiveType.get_from_cppname('float') + 'double' => CppPrimitiveType.get_from_cppname('double') +<c-array-dim-list> ::= + <c-array-dim-list>:dlist '[' <expr>:e ']' => dlist + [e] + <c-array-dim-list>:dlist '[' ']' => dlist + [None] + '[' ']' => [None] + '[' <expr>:e ']' => [e] +<param-list-opt> ::= + eps => [] + <param-list>:l => l +<param-list> ::= + <param-list>:l ',' <param>:p => l + [p] + <param>:p => [p] +<param> ::= + <direction>:d <c-type>:t Identifier:name '=' <expr>:e => Parameter(name, t, d, e) + <direction>:d <c-type>:t Identifier:name => Parameter(name, t, d, None) +<direction> ::= + 'in' => 'in' + 'out' => 'out' + 'in' 'out' => 'inout' + 'out' 'in' => 'inout' + eps => 'inout' + + +<expr> ::= + <add-expr>:e => e + 'lambda' <id-list-opt>:params ':' <expr>:e => LambdaExpr(params, e) + 'matrix' '(' <dim-list-expr>:d ',' <expr>:e ')' => MatrixGenerator(d, e) + 'matrix' <named-dim-list-expr>:dims <expr>:e => MatrixGenerator([d[1] for d in dims], LambdaExpr([d[0] for d in dims], e)) +<add-expr> ::= + <add-expr>:l '+' <mul-expr>:r => BinExpr(l, '+', r) + <add-expr>:l '-' <mul-expr>:r => BinExpr(l, '-', r) + <mul-expr>:e => e +<mul-expr> ::= + <mul-expr>:l '*' <prefix-expr>:r => BinExpr(l, '*', r) + <mul-expr>:l '/' <prefix-expr>:r => BinExpr(l, '/', r) + <prefix-expr>:e => e +<prefix-expr> ::= + '-' <prefix-expr>:e => UnaryExpr('-', e) + <postfix-expr>:e => e +<postfix-expr> ::= + <pow-expr>:e => e +<pow-expr> ::= + <term-expr>:l '**' <pow-expr>:r => BinExpr(l, '**', r) + <term-expr>:e => e +<term-expr> ::= + '(' <expr>:e ')' => e + '[' <expr-list-opt>:l ']' => l + Identifier:name => NameExpr(name) + NumericLiteral:num => ConstantExpr(num) + 'random' '(' <expr>:mn ',' <expr>:mx ')' => RandomExpr(mn, mx) + <term-expr>:f '(' <expr-list-opt>:l ')' => InvokeExpr(f, l) + <term-expr>:n '.' Identifier:attr => AttributeExpr(n, attr) +<expr-list-opt> ::= + eps => [] + <expr-list>:l => l +<expr-list> ::= + <expr-list>:l ',' <expr>:e => l + [e] + <expr>:e => [e] +<dim-list-expr> ::= + '[' <dim-expr-list>:l ']' => l +<dim-expr-list> ::= + <dim-expr-list>:l ',' <dim-expr>:e => l + [e] + <dim-expr>:e => [e] +<dim-expr> ::= + eps => None + '*' => None + <expr>:e => e +<id-list-opt> ::= + eps => [] + <id-list>:l => l +<id-list> ::= + <id-list>:l ',' Identifier:ident => l + [ident] + Identifier:ident => [ident] +<named-dim-list-expr> ::= + '[' <named-dim-expr-list>:l ']' => l +<named-dim-expr-list> ::= + <named-dim-expr-list>:l ',' <named-dim-expr>:e => l + [e] + <named-dim-expr>:e => [e] +<named-dim-expr> ::= + Identifier:name => (name, None) + Identifier:name ':' <expr>:e => (name, e) + + diff --git a/test-chill/testchill/cpp_validate/src/validate.cpp b/test-chill/testchill/cpp_validate/src/validate.cpp new file mode 100644 index 0000000..f09009d --- /dev/null +++ b/test-chill/testchill/cpp_validate/src/validate.cpp @@ -0,0 +1,29 @@ +#include <time.h> +#include <fstream> +#include <cstdio> + +//# defines +//# test-proc + +int main(int argc, char** argv) { + //# declarations + timespec start_time; + timespec end_time; + + std::ifstream datafile_initialize(argv[1]); + //# read-in + //# read-out + datafile_initialize.close(); + + clock_gettime(CLOCK_REALTIME, &start_time); + //# run + clock_gettime(CLOCK_REALTIME, &end_time); + + std::ofstream datafile_out(argv[2]); + //# write-out + datafile_out.close(); + + double time_diff = (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_nsec - start_time.tv_nsec)/1000000000.0; + std::printf("(%f,)", time_diff); + return 0; +} diff --git a/test-chill/testchill/gcov.py b/test-chill/testchill/gcov.py new file mode 100644 index 0000000..caeb849 --- /dev/null +++ b/test-chill/testchill/gcov.py @@ -0,0 +1,213 @@ +from __future__ import print_function +import functools +import itertools +import os +import os.path +import sys + +from . import util + +class GcovFile(object): + def __init__(self, src_file_name, cov_file_path, lines, properties): + """ + @param src_file_name Name of the source file. + @param cov_file_path Full path to the coverage file. + @param lines List of GcovLine objects. + @param properties Properties from the coverage file. + """ + self.src_file_name = src_file_name + self.cov_file_path = cov_file_path + self.lines = lines + self.properties = properties + + @staticmethod + def parse_file(gcov, fname, process=None): + """ + Parse a file into a GcovFile object. + @param gcov Gcov object that tis file is a part of. + @param gname File name. + @param process Process name + """ + util.shell('gcov', [fname], wd=gcov.srcdir) + cov_file_path = os.path.join(gcov.srcdir, fname + '.gcov') + src_file_name = fname + if os.path.exists(cov_file_path): + with open(cov_file_path, 'r') as f: + lines, properties = GcovFile.parse_lines(f.readlines(), process) + return GcovFile(src_file_name, cov_file_path, lines, properties) + else: + return None + + @staticmethod + def parse_lines(str_lines, process): + """ + Parse a string from a coverage file into a list of GcovLine objects. + @param str_lines Full text of a coverage file. + @param process Name of the process that executed the code. + """ + properties = dict() + lines = [] + for line in str_lines: + if line[-1] == '\n': + line = line[0:-1] + pline = line.split(':') + pline = list(map(str.strip, pline[0:2])) + pline[2:] + if pline[1] == '0': + properties[pline[2]] = pline[3].strip() + elif pline[0][0] == '-': + lines.append(GcovLine(int(pline[1]), dict(), ':'.join(pline[2:]))) + elif pline[0][0] == '#': + lines.append(GcovLine(int(pline[1]), {process : 0}, ':'.join(pline[2:]))) + else: + lines.append(GcovLine(int(pline[1]), {process : int(pline[0])}, ':'.join(pline[2:]))) + return lines, properties + + @staticmethod + def union(left, right): + """ + Merge two different coverages of the same file into a single coverage object. + """ + return left | right + + def __or__(self, right): + """ + Merge two different coverages of the same file into a single coverage object. + """ + new_file = self.clone() + new_file.merge(right) + return new_file + + def __ior__(self, right): + """ + Merge two different coverages of the same file into a single coverage object. + """ + self.merge(right) + return self + + def merge(self, other): + """ + Merge another coeverage into self. + """ + assert self.src_file_name == other.src_file_name + GcovLine.merge_lines(self.lines, other.lines) + self.properties.update(other.properties) + + def clone(self): + """ + Create a shallow clone. + """ + return GcovFile(self.src_file_name, self.cov_file_path, list(self.lines), dict(self.properties)) + + +class GcovLine(object): + def __init__(self, lineno, count_by_process, code): + """ + @param lineno Line number. + @param count_by_prcess A dictionary of execution counts by name of the process that executed them. + @param code Source code from this line. + """ + self.lineno = lineno + self.count_by_process = count_by_process + self.code = code + + @staticmethod + def merge_lines(lines, other_lines): + """ + Merge lines from other_line into lines. + """ + for line, other_line in zip(lines, other_lines): + assert line.lineno == other_line.lineno + assert line.code == other_line.code + line.count_by_process.update(other_line.count_by_process) + + def count(self): + """ + The total number of times this line was executed. + """ + runable_list = [l for l in self.count_by_process.values() if l is not None] + if len(runable_list) == 0: + return None + else: + return sum(runable_list) + + def __repr__(self): + return str((self.lineno, self.count_by_process, self.code)) + + +class Gcov(object): + def __init__(self, srcdir): + self.srcdir = srcdir + self.files = dict() + + @staticmethod + def parse(srcdir, process=None): + gcov = Gcov(srcdir) + gcov._append(filter(lambda f: f is not None, map(functools.partial(GcovFile.parse_file, gcov, process=process), + util.filterext(['cc','c','cpp','h','hh'], os.listdir(srcdir))))) + return gcov + + def _append(self, files): + for f in files: + if f.src_file_name in self.files: + self.files[f.src_file_name].merge(f) + else: + self.files[f.src_file_name] = f + + def __or__(self, right): + new_cov = self.clone() + new_cov.merge(right) + return new_cov + + def __ior__(self, right): + self.merge(right) + return self + + @staticmethod + def union(left, right): + return left | right + + def merge(self, other): + self._append(other.files.values()) + + def clone(self): + new_cov = Gcov(self.srcdir) + new_cov._append(iter(f.clone() for f in self.files.values())) + return new_cov + + +class GcovSet(object): + def __init__(self): + self.coverage_by_program = dict() + + def addprogram(self, prog_name, src_dir): + self.coverage_by_program[prog_name] = Gcov(src_dir) + + def addcoverage(self, prog_name, process_name): + cov = self.coverage_by_program[prog_name] + cov.merge(Gcov.parse(cov.srcdir, process_name)) + + def unexecuted_lines(self): + covlist = sorted(self.coverage_by_program.values(), key=lambda c: c.srcdir) + for src, grp in itertools.groupby(covlist, lambda c: c.srcdir): + files = functools.reduce(lambda a, c: a | c, grp).files.values() + file_lines = iter((f.src_file_name, iter(l for l in f.lines if l.count() == 0)) for f in files) + yield src, file_lines + + def pretty_print(self, outfile=sys.stdout, width=60, stats=['unexecuted', 'unexecuted.bysrc']): + print('='*width, file=outfile) + print(' CODE COVERAGE', file=outfile) + + if 'unexecuted' in stats: + print('='*width, file=outfile) + print(' unexecuted lines', file=outfile) + if 'unexecuted.bysrc' in stats: + for src, file_lines in self.unexecuted_lines(): + print((src + ':'), file=outfile) + print('-'*width, file=outfile) + for src_file_name, lines in file_lines: + print(' ' + src_file_name + ':', file=outfile) + for line in lines: + print("{}:{}".format(str(line.lineno).rjust(5), line.code), file=outfile) + #print('='*width, file=outfile) + #print(prog, file=outfile) + #print('-'*width, file=outfile) diff --git a/test-chill/testchill/omega.py b/test-chill/testchill/omega.py new file mode 100644 index 0000000..962333a --- /dev/null +++ b/test-chill/testchill/omega.py @@ -0,0 +1,29 @@ +from . import test +from . import util + + + +class BuildOmegaTestCase(test.TestCase): + def __init__(self, omega_dir, version='dev'): + super(BuildOmegaTestCase, self).__init__(BuildOmegaTestCase.getname(version)) + self.omega_dir = omega_dir + self.version = version + + @staticmethod + def getname(version): + if version == 'release': + return 'omega-release' + else: + return 'omega' + + def setUp(self): + util.shell('make clean', wd=self.omega_dir) + + def tearDown(self): + pass + + def run(self): + util.shell('make depend', wd=self.omega_dir) + util.shell('make', wd=self.omega_dir) + + diff --git a/test-chill/testchill/test.py b/test-chill/testchill/test.py new file mode 100644 index 0000000..c38b98a --- /dev/null +++ b/test-chill/testchill/test.py @@ -0,0 +1,381 @@ +from __future__ import print_function +#TODO: test dependencies +#TODO: expected failures +import itertools +import io +import logging +import pprint +import sys +import traceback + +from . import util + + +class TestResult(object): + """ + The base class for all test results. + """ + _pass = 'pass' + _error = 'error' + _fail = 'fail' + _skipped = 'skipped' + + def __init__(self, testcase, status): + self.testcase_name = testcase.name + self.status = status + testcase.setresult(self) + + @staticmethod + def make_pass(result_type, testcase, *args, **kwargs): + """ + Create and return a passing test result of type result_type. + @param result_type A class that extends TestResult + @param testcase The test case that generated the result + @param *args Additional positional arguments to be passed to result_type.__init__ + @param *kwargs Keyword arguments to be passed to result_type.__init__ + """ + return result_type(testcase, TestResult._pass, *args, **kwargs) + + @staticmethod + def make_error(result_type, testcase, *args, **kwargs): + """ + Create and return a errored test result of type result_type. + @param result_type A class that extends TestResult + @param testcase The test case that generated the result + @param *args Additional positional arguments to be passed to result_type.__init__ + @param *kwargs Keyword arguments to be passed to result_type.__init__ + """ + return result_type(testcase, TestResult._error, *args, **kwargs) + + @staticmethod + def make_fail(result_type, testcase, *args, **kwargs): + """ + Create and return a failed test result of type result_type. + @param result_type A class that extends TestResult + @param testcase The test case that generated the result + @param *args Additional positional arguments to be passed to result_type.__init__ + @param *kwargs Keyword arguments to be passed to result_type.__init__ + """ + return result_type(testcase, TestResult._fail, *args, **kwargs) + + @staticmethod + def make_skipped(result_type, testcase, *args, **kwargs): + """ + Create and return a skipped test result of type result_type. + @param result_type A class that extends TestResult + @param testcase The test case that generated the result + @param *args Additional positional arguments to be passed to result_type.__init__ + @param *kwargs Keyword arguments to be passed to result_type.__init__ + """ + return result_type(testcase, TestResult._skipped, *args, **kwargs) + + def passed(self): + """ Return true iff the testcase passed. """ + return self.status == TestResult._pass + + def errored(self): + """ Return true iff the testcase passed. """ + return self.status == TestResult._error + + def failed(self): + """ Return true iff the testcase passed. """ + return self.status == TestResult._fail + + def skipped(self): + """ Return true iff the testcase was skipped """ + return self.status == TestResult._skipped + + def pprint_dict(self): + """ + Return a dict that is ideal for passing to pprint. + """ + return {'testcase_name': self.testcase_name, 'status':self.status} + + def pretty_print(self, width=60, outfile=sys.stdout): + """ + Print result to a file in a human readable way. + """ + print('='*width, end='\n', file=outfile) + print("{}: {}".format(self.status, self.testcase_name), end='\n', file=outfile) + print('-'*width, end='\n', file=outfile) + print(self.pretty_message(), end='\n', file=outfile) + print('-'*width, end='\n', file=outfile) + + def pretty_message(self): + """ Return a message to be printed by pretty_print. Returns an empyt string if not overriden. """ + return '' + + + +class FailedTestResult(TestResult): + """ + A basic implementation of TestResult for failed tests. + """ + def __init__(self, testcase, status=TestResult._fail, reason=None): + super(FailedTestResult, self).__init__(testcase, status) + self.reason = reason + + def pprint_dict(self): + """ + Return a dict that is ideal for passing to pprint. + """ + ppdict = super(FailedTestResult, self).pprint_dict() + ppdict['reason'] = self.reason + return ppdict + + def pretty_message(self): + return self.reason + + +class CompoundTestResult(TestResult): + """ + A TestResult returned by running a sequencial test case + """ + def __init__(self, testcase, results): + super(CompoundTestResult, self).__init__(testcase, None) + self.sub_results = results + status_list = [r.status for r in results] + if TestResult._fail in status_list: + self.status = TestResult._fail + elif TestResult._error in status_list: + self.status = TestResult._error + elif TestResult._pass in status_list: + self.status = TestResult._pass + else: + self.status = TestResult._skipped + + def pprint_dict(self): + """ + Returns a dict that is ideal for passing to pprint. + """ + ppdict = super(CompoundTestResult, self).pprint_dict() + ppdict['sub_results'] = list(s.pprint_dict() for s in self.sub_results) + return ppdict + + def pretty_message(self): + return '\n'.join( + "{}: {}{}".format( + st.status, + st.testcase_name, + '\n' + st.pretty_message() if st.status in [TestResult._fail, TestResult._error] else '') + for st in self.sub_results) + + +class SubTestResult(TestResult): + """ + A TestResult for a subtest in a sequencial test case. + """ + def __init__(self, subtest_name, inner_result): + """ + @param subtest_name The name of the subtest. + @param inner_result The result returned from running the subtest. + """ + super(SubTestResult, self).__init__(inner_result.testcase, inner_result.status) + self.inner_result = inner_result + + def pprint_dict(self): + """ + Return a dict that is ideal for passing to pprint. + """ + ppdict = super(CompoundTestResult, self).pprint_dict() + ppdict['inner_result'] = self.inner_result.pprint_dict() + return ppdict + + +class UnhandledExceptionTestResult(TestResult): + """ + A TestResult returned for exceptions that the test case failed to handle. + """ + def __init__(self, testcase, status, exc_type, exc_value, exc_traceback): + super(UnhandledExceptionTestResult, self).__init__(testcase, status) + self.exception_type = exc_type + self.exception_value = exc_value + if not exc_traceback is None: + sio = util.StringIO() + traceback.print_exception(self.exception_type, self.exception_value, exc_traceback, file=sio) + self.exception_message = sio.getvalue() + else: + self.exception_message = "{}: {}".format(str(exc_type), str(exc_value)) + + def pprint_dict(self): + """ + Return a dict that is ideal for passing to pprint. + """ + ppdict = super(UnhandledExceptionTestResult, self).pprint_dict() + ppdict['exception_type'] = self.exception_type + ppdict['exception_value'] = self.exception_value + ppdict['exception_message'] = self.exception_message + return ppdict + + def pretty_message(self): + return self.exception_message + + +class TestCase(object): + """ + Base class for all test cases + """ + def __init__(self, name=None): + """ + @param name A unique test case name. + """ + self.name = name + + def setUp(self): + """ + Called imediately before a testcase is executed. + """ + pass + + def run(self): + """ + Run the test case, and return its result. + """ + raise NotImplementedError + + def tearDown(self): + """ + Called imediately after a testcase is executed. + """ + pass + + def catch(self, exc): + """ + Called when run raises an exception. If the test case + knows how to handle it, it should return it's own result or None. + Otherwise, return the original exception. + """ + return exc + + def setresult(self, test_result): + """ + Called after a test issues a result and before tearDown is called. + """ + self.test_result = test_result + + def make_pass(self, result_type=TestResult, *args, **kwargs): + """ + Make a passed result for this testcase. + """ + return TestResult.make_pass(result_type, self, *args, **kwargs) + + def make_fail(self, result_type=FailedTestResult, *args, **kwargs): + """ + Make a failed result for this testcase. + """ + return TestResult.make_fail(result_type, self, *args, **kwargs) + + +class SequencialTestCase(TestCase): + """ + A test case that executes a sequence of subtests until + one fails. + """ + def __init__(self, name): + super(SequencialTestCase, self).__init__(name) + self.tests = [] + + def add_subtest(self, subtest_name, subtest_func): + """ + Add a subtest. + """ + self.tests.append((subtest_name, subtest_func)) + + def run(self): + return CompoundTestResult(self, list(self._runall())) + + def _runall(self): + return _rungen([SubTestCase(name, func) for name, func in self.tests], failfast=True) + + +class SubTestCase(TestCase): + """ + A subtest of a sequncial test. + """ + def __init__(self, name, func): + super(SubTestCase, self).__init__(name) + self.run = lambda: func(self) + + +def run(tclist, failfast=False): + """ + Run all test cases in tclist and return a list of thier results. + """ + return list(_rungen(tclist, failfast)) + +def _rungen(tclist, failfast=False): + """ + A generator for running tests internally. + """ + for tc in tclist: + result = None + tc.setUp() + try: + result = _result(tc.run(), tc) + except Exception as ex: + result = _result(tc.catch(ex), tc) + tc.tearDown() + yield result + if failfast and (result.failed() or result.errored()): + break + +def _result(res, tc): + """ + Convert res to a TestResult object. + If res is a TestResult object, give it back. + If res is an Exception, return an UnandledExceptionTestResult. + If res is something else, discard it and return a passed TestResult. + """ + if isinstance(res, TestResult): + return res + elif isinstance(res, Exception): + logging.info('uncaught exception: {}'.format(str(res))) + return TestResult.make_error(UnhandledExceptionTestResult, tc, *(sys.exc_info())) + else: + return TestResult.make_pass(TestResult, tc) + +def pprint_results(result_iter, outfile=sys.stdout): + """ + Print pprint version of test results to a file-like object. + @param result_iter An iterator of results to print. + @param outfile An opened file-like object to print to (defaults to stdout). + """ + status_func = lambda r: r.status + result_iter = sorted(result_iter, key=status_func) + status_dict = dict(iter((k, list(map(lambda tc: tc.pprint_dict(), g))) for k, g in itertools.groupby(result_iter, status_func))) + pprint.pprint(status_dict, stream=outfile) + +def pretty_print_results( + result_iter, + count_by_status=True, exclude_passed=True, exclude_skipped=True, exclude_failed=False, + exclude_errored=False, sort_by_status=True, width=60, outfile=sys.stdout): + """ + Print iterator of TestResults in a human readable format to a file-like object. + @param result_iter An iterator of TestResult objects to print. + @param count_by_status Print the number of tests for each status (defaults to True). + @param exclude_passed Exclude passed test results from printing (defaults to True). + @param exclude_skipped Exclude skipped test results from printing (defaults to True). + @param exclude_failed Exclude failed test results from printing (defaults to False). + @param exclude_errored Exclude errored test results from printing (defaults to False). + @param sort_by_status Print test results in order of status: passed, errored, failed, then skipped (defaults to True). + @param width Printing width (defaults to 60). + @param outfile A file-like object to print to (defaults to stdout). + """ + result_list = list(result_iter) + status_func = lambda r: r.status + if sort_by_status: + #TODO: printing order + result_iter = sorted(result_iter, key=status_func) + + if count_by_status: + print('Passed: {}'.format(len([tr for tr in result_list if tr.passed()])), file=outfile) + print('Errors: {}'.format(len([tr for tr in result_list if tr.errored()])), file=outfile) + print('Failed: {}'.format(len([tr for tr in result_list if tr.failed()])), file=outfile) + print('Skipped: {}'.format(len([tr for tr in result_list if tr.skipped()])), file=outfile) + #TODO: something that doesn't expose TestResult._* + print_status = set(itertools.compress([TestResult._pass, TestResult._error, TestResult._fail, TestResult._skipped], + map(lambda n: not n, [exclude_passed, exclude_errored, exclude_failed, exclude_skipped]))) + for tr in (r for r in result_list if r.status in print_status): + tr.pretty_print(width=width, outfile=outfile) + + diff --git a/test-chill/testchill/util.py b/test-chill/testchill/util.py new file mode 100644 index 0000000..cfb61dd --- /dev/null +++ b/test-chill/testchill/util.py @@ -0,0 +1,185 @@ +import difflib +import functools +import itertools +import logging +import os +import re +import sysconfig +import subprocess +import tempfile + + + +logging.basicConfig(filename='testchill.log', level=logging.DEBUG, filemode='w') +#logging.basicConfig(level=logging.INFO) + +python_version = sysconfig.get_python_version() +python_version_major = int(sysconfig.get_python_version().split('.')[0]) +python_version_minor = int(sysconfig.get_python_version().split('.')[1]) + +if python_version_major == 2: + from StringIO import StringIO +else: + from io import StringIO + +_temp_dirs = [] +_temp_files = [] + +### Errors ### +### Shell Util ### + +def shell(cmd, args=[], stdout=None, stderr=None, env={}, wd=os.getcwd()): + """ + Execute a shell command. + @params cmd The command name + @params args A list of command line arguments (defaults to []) + @params stdout A file like object or file number that reads input written to stdout. + stdout will be returned as a string if this is None or not given. + @params stderr A file like object or file number that reads input written to stderr. + @params env A dict of environment variables. Before the command is executed, these will be exported + @params wd The working directory. Before the command is executed, the working directory will be changed to wd. (wd defaults to the current working directory) + """ + fullcmd = ' '.join(['export {}={};'.format(k,str(v)) for k,v in env.items()] + ['cd {};'.format(wd)] + [cmd] + args) + logging.info('shell: '+fullcmd) + if stdout == None: + outp = subprocess.check_output(fullcmd, stderr=stderr, shell=True) + if python_version_major == 2: + return outp + elif python_version_major == 3: + return outp.decode() + else: + subprocess.check_call(fullcmd, stdout=stdout, stderr=stderr, shell=True) + +def mkdir_p(directory, temp=False, **kwargs): + """ + Make directory (equivelent to shell('mkdir', ['-p', directory])) + """ + if not os.path.exists(directory): + if temp and (directory not in _temp_dirs): + _temp_dirs.append(directory) + shell('mkdir', ['-p', directory], **kwargs) + +def set_tempfile(filename): + """ + Add a file to a list of temp files + @param filename The full path to a temparary file. + """ + _temp_files.append(filename) + +def withtmp(wtfunc, rdfunc): + """ + Perform some operation using a temporary file. + @param wtfunc A function that writes to the temparary file + @param rdfybc A function that reads from the temparary file + """ + with tempfile.TemporaryFile() as f: + wtfunc(f) + f.seek(0) + return rdfunc(f) + +def rmtemp(): + """ + Clean temp files and directories + """ + for temp_file in list(_temp_files): + if os.path.exists(temp_file): + shell('rm', [temp_file]) + _temp_files.remove(temp_file) + + for temp_dir in list(_temp_dirs): + if os.path.exists(temp_dir): + shell('rm', ['-rf', temp_dir]) + _temp_dirs.remove(temp_dir) + +def mktemp(mode=None): + """ + Create a temparary file. Returns a two-tuple with an open file object and the filename. + """ + fd, name = tempfile.mkstemp() + _temp_files.append(name) + if mode is None: + os.close(fd) + return name + else: + return os.fdopen(fd, mode), name + + +### Misc Util ### + +def copy(obj, exclude=[]): + """ + Make a shallow copy of a python object with __dict__, excluding any attribute in exclude + @param obj The object to copy + @param exclude A list of attributes to ignore + """ + nobj = type(obj)() + for k, v in vars(obj).items(): + if k in exclude: continue + setattr(nobj, k, v) + return nobj + +def applyenv(line): + """ + Apply bash style environment variables to a string + @param line The input string + """ + return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)\b',lambda m: str(os.getenv(m.group(1), '')), line) + +def callonce(func): + """ + Assert that a function is only ever called once. + @param func Function to only be run once. + """ + pred_name = '__' + func.__module__.replace('.','__') + '_' + func.__name__ + '_called' + globals()[pred_name] = False + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not globals()[pred_name]: + globals()[pred_name] = True + return func(*args, **kwargs) + else: + raise Exception + return wrapper + +def isdiff(strone, strtwo): + """ + Diff two strings. Returns a two element tuple. The first is True if the the two files are different, and the + next is a textual representation of the diff. + @param strone First string. + @param strtwo Second string. + """ + diff = list(difflib.ndiff(strone.splitlines(), strtwo.splitlines())) + return len(list(line for line in diff if line[0] in ['-','+'])) != 0, '\n'.join(diff) + +def filterext(ext_list, filenames): + """ + Filter file names by extension. + @param ext_list A list of extensions. + @param filenames An iterable object of file names. + """ + return iter(s for s in filenames if any(s.strip().endswith(e) for e in ext_list)) + +def extract_tag(tagname, filename, wd=os.getcwd()): + """ + Extract commented out text in each html tag '<tagname>'. Returns a list of tuples for each tag. + Each tuple has two elements, the first is the text found in the tag, the second contains a dict + of attributes given in the tag. + @param tagname The name of the tag to search for. + @param filename A filename to search for comments in. + @param wd The working directory. + """ + from . import _extract + return _extract.extract_tag(tagname, filename, wd) + +def textstream(txt): + """ + Creates a stream from text. Intended to hide version differences between 2 and 3. + @param txt A string to use as the default data in a stream. + """ + if python_version_major == 2: + import StringIO + return StringIO.StringIO(txt) + elif python_version_major == 3: + import io + return io.StringIO(txt) + |