diff options
| author | dhuth <derickhuth@gmail.com> | 2014-11-21 13:35:20 -0700 | 
|---|---|---|
| committer | dhuth <derickhuth@gmail.com> | 2014-11-21 13:35:20 -0700 | 
| commit | a1834b22c43c282442b0cb164767e6c877cf0e5b (patch) | |
| tree | bedc5be7d1bdb8d32c1868caa496a8a1530d8d8a /test-chill/testchill | |
| parent | ded84bb4aec7461738e7b7033d782a518e2c606b (diff) | |
| parent | eb9236c5353785472ae132f27e1cfb9f1e4264a5 (diff) | |
| download | chill-a1834b22c43c282442b0cb164767e6c877cf0e5b.tar.gz chill-a1834b22c43c282442b0cb164767e6c877cf0e5b.tar.bz2 chill-a1834b22c43c282442b0cb164767e6c877cf0e5b.zip | |
Merge branch 'master' into doe
Diffstat (limited to 'test-chill/testchill')
| -rw-r--r-- | test-chill/testchill/__init__.py | 0 | ||||
| -rw-r--r-- | test-chill/testchill/__main__.py | 381 | ||||
| -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 | 224 | ||||
| -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, 2615 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..3e03e11 --- /dev/null +++ b/test-chill/testchill/__main__.py @@ -0,0 +1,381 @@ +#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) +    argsns.chill_build_coverage = argsns.coverage_set is not None #TODO: make arg passed to local. +    argsns.chill_test_coverage = argsns.coverage_set is not None +     +    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, options={'coverage': argsns.chill_build_coverage}, 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', default='../') +    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.joinpath(os.getcwd(), '../omega'), help='Omega home directory. (Defaults to ../omega)', 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..b881ef4 --- /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 -Wuninitialized'] +        elif link and cc == 'nvcc': +            compile_args = ['-L/usr/local/cuda/lib64/lib', '-lcuda', '-lcudart', '-lstdc++', '-lrt', '-Wuninitialized'] +        else: +            compile_args = ['-lstdc++', '-lrt', '-Wuninitialized'] +         +        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..668c00e --- /dev/null +++ b/test-chill/testchill/gcov.py @@ -0,0 +1,224 @@ +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) +     +    def _get_coverage_by_file(self): +        return functools.reduce(lambda a,b: a|b, self.coverage_by_program.values()).files +     +    def _get_filenames(self): +        return self.coverage_by_file.keys() +     +    coverage_by_file = property(_get_coverage_by_file) +    filenames = property(_get_filenames) + + 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..266a94d --- /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('{} was invoked multiple times.'.format(func.__name___)) +    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) + | 
