summaryrefslogtreecommitdiff
path: root/test-chill/testchill
diff options
context:
space:
mode:
Diffstat (limited to 'test-chill/testchill')
-rw-r--r--test-chill/testchill/__init__.py0
-rw-r--r--test-chill/testchill/__main__.py379
-rw-r--r--test-chill/testchill/_cpp_validate_env.py654
-rw-r--r--test-chill/testchill/_extract.py98
-rw-r--r--test-chill/testchill/chill.py345
-rw-r--r--test-chill/testchill/cpp_validate.py165
-rw-r--r--test-chill/testchill/cpp_validate/grammar.txt124
-rw-r--r--test-chill/testchill/cpp_validate/src/validate.cpp29
-rw-r--r--test-chill/testchill/gcov.py213
-rw-r--r--test-chill/testchill/omega.py29
-rw-r--r--test-chill/testchill/test.py381
-rw-r--r--test-chill/testchill/util.py185
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)
+