示例#1
0
文件: _diff.py 项目: maxeler/cram
def esc(el, l):
    """Apply an escape match to a line annotated with '(esc)'"""
    ann = b(' (esc)\n')
    if el.endswith(ann):
        el = codecs.escape_decode(el[:-len(ann)])[0] + b('\n')
    if l.endswith(ann):
        l = codecs.escape_decode(l[:-len(ann)])[0] + b('\n')
    return el == l
示例#2
0
        def testwrapper():
            """Test function that adds CLI output"""
            total[0] += 1
            _log(None, path + b(': '), verbose)

            refout, postout, diff = test()
            if refout is None:
                skipped[0] += 1
                _log('s', 'empty\n', verbose)
                return refout, postout, diff

            abspath = os.path.abspath(path)
            if error_dir is not None:
                errpath = os.path.join(error_dir.encode(),
                                       os.path.basename(path) + b'.err')
            else:
                errpath = abspath + b('.err')

            if postout is None:
                skipped[0] += 1
                _log('s', 'skipped\n', verbose)
            elif not diff:
                _log('.', 'passed\n', verbose)
                if os.path.exists(errpath):
                    os.remove(errpath)
            else:
                failed[0] += 1
                _log('!', 'failed\n', verbose)
                if not quiet:
                    _log('\n', None, verbose)

                errfile = open(errpath, 'wb')
                try:
                    for line in postout:
                        errfile.write(line)
                finally:
                    errfile.close()

                if not quiet:
                    origdiff = diff
                    diff = []
                    for line in origdiff:
                        stdoutb.write(line)
                        diff.append(line)

                    if (patchcmd and
                        _prompt('Accept this change?', 'yN', answer) == 'y'):
                        if _patch(patchcmd, diff):
                            _log(None, path + b(': merged output\n'), verbose)
                            os.remove(errpath)
                        else:
                            _log(path + b(': merge failed\n'))

            return refout, postout, diff
示例#3
0
文件: _run.py 项目: maxeler/cram
def _findtests(paths):
    """Yield tests in paths in sorted order"""
    for p in paths:
        if os.path.isdir(p):
            for root, dirs, files in os.walk(p):
                if os.path.basename(root).startswith(b('.')):
                    continue
                for f in sorted(files):
                    if not f.startswith(b('.')) and f.endswith(b('.t')):
                        yield os.path.normpath(os.path.join(root, f))
        else:
            yield os.path.normpath(p)
示例#4
0
def _findtests(paths):
    """Yield tests in paths in sorted order"""
    for p in paths:
        if os.path.isdir(p):
            for root, dirs, files in _walk(p):
                if os.path.basename(root).startswith(b('.')):
                    continue
                for f in sorted(files):
                    if not f.startswith(b('.')) and f.endswith(b('.t')):
                        yield os.path.normpath(os.path.join(root, f))
        else:
            yield os.path.normpath(p)
示例#5
0
文件: _diff.py 项目: tchaikov/cram
def esc(el, line):
    """Apply an escape match to a line annotated with '(esc)'"""
    ann = b(' (esc)\n')

    if el.endswith(ann):
        el = codecs.escape_decode(el[:-len(ann)])[0] + b('\n')
    if el == line:
        return True

    if line.endswith(ann):
        line = codecs.escape_decode(line[:-len(ann)])[0] + b('\n')
    return el == line
示例#6
0
文件: _cli.py 项目: fipar/sysbench
        def testwrapper():
            """Test function that adds CLI output"""
            total[0] += 1
            _log(None, path + b(': '), verbose)

            refout, postout, diff = test()
            if refout is None:
                skipped[0] += 1
                _log('s', 'empty\n', verbose)
                return refout, postout, diff

            abspath = os.path.abspath(path)
            errpath = abspath + b('.err')

            if postout is None:
                skipped[0] += 1
                _log('s', 'skipped\n', verbose)
            elif not diff:
                _log('.', 'passed\n', verbose)
                if os.path.exists(errpath):
                    os.remove(errpath)
            else:
                failed[0] += 1
                _log('!', 'failed\n', verbose)
                if not quiet:
                    _log('\n', None, verbose)

                if not noerrfiles:
                    errfile = open(errpath, 'wb')
                    try:
                        for line in postout:
                            errfile.write(line)
                    finally:
                        errfile.close()

                if not quiet:
                    origdiff = diff
                    diff = []
                    for line in origdiff:
                        stdoutb.write(line)
                        diff.append(line)

                    if (patchcmd and
                        _prompt('Accept this change?', 'yN', answer) == 'y'):
                        if _patch(patchcmd, diff):
                            _log(None, path + b(': merged output\n'), verbose)
                            if not noerrfiles:
                                os.remove(errpath)
                        else:
                            _log(path + b(': merge failed\n'))

            return refout, postout, diff
示例#7
0
文件: _diff.py 项目: tchaikov/cram
def _glob(el, l):
    r"""Match a glob-like pattern.

    The only supported special characters are * and ?. Escaping is
    supported.

    >>> from cram._encoding import b
    >>> bool(_glob(b(r'\* \\ \? fo?b*'), b('* \\ ? foobar')))
    True
    """
    i, n = 0, len(el)
    res = b('')
    while i < n:
        c = el[i:i + 1]
        i += 1
        if c == b('\\') and el[i] in b('*?\\'):
            res += el[i - 1:i + 1]
            i += 1
        elif c == b('*'):
            res += b('.*')
        elif c == b('?'):
            res += b('.')
        else:
            res += re.escape(c)
    return _regex(res, l)
示例#8
0
def _glob(el, l):
    r"""Match a glob-like pattern.

    The only supported special characters are * and ?. Escaping is
    supported.

    >>> from cram._encoding import b
    >>> bool(_glob(b(r'\* \\ \? fo?b*'), b('* \\ ? foobar')))
    True
    """
    i, n = 0, len(el)
    res = b('')
    while i < n:
        c = el[i:i + 1]
        i += 1
        if c == b('\\') and el[i] in b('*?\\'):
            res += el[i - 1:i + 1]
            i += 1
        elif c == b('*'):
            res += b('.*')
        elif c == b('?'):
            res += b('.')
        else:
            res += re.escape(c)
    return _regex(res, l)
示例#9
0
文件: _run.py 项目: rsp9u/cram
def runtests(paths,
             tmpdir,
             shell,
             indent=2,
             cleanenv=True,
             debug=False,
             debug_script=False,
             noescape=False):
    """Run tests and yield results.

    This yields a sequence of 2-tuples containing the following:

        (test path, test function)

    The test function, when called, runs the test in a temporary directory
    and returns a 3-tuple:

        (list of lines in the test, same list with actual output, diff)
    """
    cwd = os.getcwd()
    seen = set()
    basenames = set()
    for i, path in enumerate(_findtests(paths)):
        abspath = os.path.abspath(path)
        if abspath in seen:
            continue
        seen.add(abspath)

        if not os.stat(path).st_size:
            yield (path, lambda: (None, None, None))
            continue

        basename = os.path.basename(path)
        if basename in basenames:
            basename = basename + b('-%s' % i)
        else:
            basenames.add(basename)

        def test():
            """Run test file"""
            testdir = os.path.join(tmpdir, basename)
            os.mkdir(testdir)
            try:
                os.chdir(testdir)
                return testfile(abspath,
                                shell,
                                indent=indent,
                                cleanenv=cleanenv,
                                debug=debug,
                                debug_script=debug_script,
                                testname=path,
                                noescape=noescape)
            finally:
                os.chdir(cwd)

        yield (path, test)
示例#10
0
文件: _diff.py 项目: tchaikov/cram
def _regex(pattern, s):
    """Match a regular expression or return False if invalid.

    >>> from cram._encoding import b
    >>> [bool(_regex(r, b('foobar'))) for r in (b('foo.*'), b('***'))]
    [True, False]
    """
    try:
        return re.match(pattern + b(r'\Z'), s)
    except re.error:
        return False
示例#11
0
def _regex(pattern, s):
    """Match a regular expression or return False if invalid.

    >>> from cram._encoding import b
    >>> [bool(_regex(r, b('foobar'))) for r in (b('foo.*'), b('***'))]
    [True, False]
    """
    try:
        return re.match(pattern + b(r'\Z'), s)
    except re.error:
        return False
示例#12
0
def runtests(paths, tmpdir, shell, indent=2, cleanenv=True, debug=False):
    """Run tests and yield results.

    This yields a sequence of 2-tuples containing the following:

        (test path, test function)

    The test function, when called, runs the test in a temporary directory
    and returns a 3-tuple:

        (list of lines in the test, same list with actual output, diff)
    """
    cwd = os.getcwd()
    seen = set()
    basenames = set()
    for i, path in enumerate(_findtests(paths)):
        abspath = os.path.abspath(path)
        if abspath in seen:
            continue
        seen.add(abspath)

        if not os.stat(path).st_size:
            yield (path, lambda: (None, None, None))
            continue

        basename = os.path.basename(path)
        if basename in basenames:
            basename = basename + b('-%s' % i)
        else:
            basenames.add(basename)

        def test():
            """Run test file"""
            testdir = os.path.join(tmpdir, basename)
            os.mkdir(testdir)
            try:
                os.chdir(testdir)
                return testfile(abspath, shell, indent=indent,
                                cleanenv=cleanenv, debug=debug,
                                testname=path)
            finally:
                os.chdir(cwd)

        yield (path, test)
示例#13
0
def main(args):
    """Main entry point.

    If you're thinking of using Cram in other Python code (e.g., unit tests),
    consider using the test() or testfile() functions instead.

    :param args: Script arguments (excluding script name)
    :type args: str
    :return: Exit code (non-zero on failure)
    :rtype: int
    """
    opts, paths, getusage = _parseopts(args)
    if opts.version:
        sys.stdout.write("""Cram CLI testing framework (version 0.7)

Copyright (C) 2010-2016 Brodie Rao <*****@*****.**> and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
""")
        return

    conflicts = [('--yes', opts.yes, '--no', opts.no),
                 ('--quiet', opts.quiet, '--interactive', opts.interactive),
                 ('--debug', opts.debug, '--quiet', opts.quiet),
                 ('--debug', opts.debug, '--interactive', opts.interactive),
                 ('--debug', opts.debug, '--verbose', opts.verbose),
                 ('--debug', opts.debug, '--xunit-file', opts.xunit_file)]
    for s1, o1, s2, o2 in conflicts:
        if o1 and o2:
            sys.stderr.write('options %s and %s are mutually exclusive\n'
                             % (s1, s2))
            return 2

    shellcmd = _which(opts.shell)
    if not shellcmd:
        stderrb.write(b('shell not found: ') + fsencode(opts.shell) + b('\n'))
        return 2
    shell = [shellcmd]
    if opts.shell_opts:
        shell += shlex.split(opts.shell_opts)

    patchcmd = None
    if opts.interactive:
        patchcmd = _which('patch')
        if not patchcmd:
            sys.stderr.write('patch(1) required for -i\n')
            return 2

    if not paths:
        sys.stdout.write(getusage())
        return 2

    badpaths = [path for path in paths if not os.path.exists(path)]
    if badpaths:
        stderrb.write(b('no such file: ') + badpaths[0] + b('\n'))
        return 2

    if opts.yes:
        answer = 'y'
    elif opts.no:
        answer = 'n'
    else:
        answer = None

    tmpdir = os.environ['CRAMTMP'] = tempfile.mkdtemp('', 'cramtests-')
    tmpdirb = fsencode(tmpdir)
    proctmp = os.path.join(tmpdir, 'tmp')
    for s in ('TMPDIR', 'TEMP', 'TMP'):
        os.environ[s] = proctmp

    os.mkdir(proctmp)
    try:
        tests = runtests(paths, tmpdirb, shell, indent=opts.indent,
                         cleanenv=not opts.preserve_env, debug=opts.debug)
        if not opts.debug:
            tests = runcli(tests, quiet=opts.quiet, verbose=opts.verbose,
                           patchcmd=patchcmd, answer=answer)
            if opts.xunit_file is not None:
                tests = runxunit(tests, opts.xunit_file)

        hastests = False
        failed = False
        for path, test in tests:
            hastests = True
            refout, postout, diff = test()
            if diff:
                failed = True

        if not hastests:
            sys.stderr.write('no tests found\n')
            return 2

        return int(failed)
    finally:
        if opts.keep_tmpdir:
            stdoutb.write(b('# Kept temporary directory: ') + tmpdirb +
                          b('\n'))
        else:
            shutil.rmtree(tmpdir)
示例#14
0
def test(lines,
         shell='/bin/sh',
         indent=2,
         testname=None,
         env=None,
         cleanenv=True,
         debug=False):
    r"""Run test lines and return input, output, and diff.

    This returns a 3-tuple containing the following:

        (list of lines in test, same list with actual output, diff)

    diff is a generator that yields the diff between the two lists.

    If a test exits with return code 80, the actual output is set to
    None and diff is set to [].

    Note that the TESTSHELL environment variable is available in the
    test (set to the specified shell). However, the TESTDIR and
    TESTFILE environment variables are not available. To run actual
    test files, see testfile().

    Example usage:

    >>> from cram._encoding import b
    >>> refout, postout, diff = test([b('  $ echo hi\n'),
    ...                               b('  [a-z]{2} (re)\n')])
    >>> refout == [b('  $ echo hi\n'), b('  [a-z]{2} (re)\n')]
    True
    >>> postout == [b('  $ echo hi\n'), b('  hi\n')]
    True
    >>> bool(diff)
    False

    lines may also be a single bytes string:

    >>> refout, postout, diff = test(b('  $ echo hi\n  bye\n'))
    >>> refout == [b('  $ echo hi\n'), b('  bye\n')]
    True
    >>> postout == [b('  $ echo hi\n'), b('  hi\n')]
    True
    >>> bool(diff)
    True
    >>> (b('').join(diff) ==
    ...  b('--- \n+++ \n@@ -1,2 +1,2 @@\n   $ echo hi\n-  bye\n+  hi\n'))
    True

    Note that the b() function is internal to Cram. If you're using Python 2,
    use normal string literals instead. If you're using Python 3, use bytes
    literals.

    :param lines: Test input
    :type lines: bytes or collections.Iterable[bytes]
    :param shell: Shell to run test in
    :type shell: bytes or str or list[bytes] or list[str]
    :param indent: Amount of indentation to use for shell commands
    :type indent: int
    :param testname: Optional test file name (used in diff output)
    :type testname: bytes or None
    :param env: Optional environment variables for the test shell
    :type env: dict or None
    :param cleanenv: Whether or not to sanitize the environment
    :type cleanenv: bool
    :param debug: Whether or not to run in debug mode (don't capture stdout)
    :type debug: bool
    :return: Input, output, and diff iterables
    :rtype: (list[bytes], list[bytes], collections.Iterable[bytes])
    """
    indent = b(' ') * indent
    cmdline = indent + b('$ ')
    conline = indent + b('> ')
    usalt = 'CRAM%s' % time.time()
    salt = b(usalt)

    if env is None:
        env = os.environ.copy()

    if cleanenv:
        for s in ('LANG', 'LC_ALL', 'LANGUAGE'):
            env[s] = 'C'
        env['TZ'] = 'GMT'
        env['CDPATH'] = ''
        env['COLUMNS'] = '80'
        env['GREP_OPTIONS'] = ''

    if isinstance(lines, bytestype):
        lines = lines.splitlines(True)

    if isinstance(shell, (bytestype, unicodetype)):
        shell = [shell]
    env['TESTSHELL'] = shell[0]

    if debug:
        stdin = []
        for line in lines:
            if not line.endswith(b('\n')):
                line += b('\n')
            if line.startswith(cmdline):
                stdin.append(line[len(cmdline):])
            elif line.startswith(conline):
                stdin.append(line[len(conline):])

        execute(shell + ['-'], stdin=b('').join(stdin), env=env)
        return ([], [], [])

    after = {}
    refout, postout = [], []
    i = pos = prepos = -1
    stdin = []
    for i, line in enumerate(lines):
        if not line.endswith(b('\n')):
            line += b('\n')
        refout.append(line)
        if line.startswith(cmdline):
            after.setdefault(pos, []).append(line)
            prepos = pos
            pos = i
            stdin.append(b('echo %s %s $?\n' % (usalt, i)))
            stdin.append(line[len(cmdline):])
        elif line.startswith(conline):
            after.setdefault(prepos, []).append(line)
            stdin.append(line[len(conline):])
        elif not line.startswith(indent):
            after.setdefault(pos, []).append(line)
    stdin.append(b('echo %s %s $?\n' % (usalt, i + 1)))

    output, retcode = execute(shell + ['-'],
                              stdin=b('').join(stdin),
                              stdout=PIPE,
                              stderr=STDOUT,
                              env=env)
    if retcode == 80:
        return (refout, None, [])

    pos = -1
    ret = 0
    for i, line in enumerate(output[:-1].splitlines(True)):
        out, cmd = line, None
        if salt in line:
            out, cmd = line.split(salt, 1)

        if out:
            if not out.endswith(b('\n')):
                out += b(' (no-eol)\n')

            if _needescape(out):
                out = _escape(out)
            postout.append(indent + out)

        if cmd:
            ret = int(cmd.split()[1])
            if ret != 0:
                postout.append(indent + b('[%s]\n' % (ret)))
            postout += after.pop(pos, [])
            pos = int(cmd.split()[0])

    postout += after.pop(pos, [])

    if testname:
        diffpath = testname
        errpath = diffpath + b('.err')
    else:
        diffpath = errpath = b('')
    diff = unified_diff(refout,
                        postout,
                        diffpath,
                        errpath,
                        matchers=[esc, glob, regex])
    for firstline in diff:
        return refout, postout, itertools.chain([firstline], diff)
    return refout, postout, []
示例#15
0
def _escape(s):
    """Like the string-escape codec, but doesn't escape quotes"""
    return (_escapesub(lambda m: _escapemap[m.group(0)], s[:-1]) +
            b(' (esc)\n'))
示例#16
0
"""Utilities for running individual tests"""

import itertools
import os
import re
import time

from cram._encoding import b, bchr, bytestype, envencode, unicodetype
from cram._diff import esc, glob, regex, unified_diff
from cram._process import PIPE, STDOUT, execute

__all__ = ['test', 'testfile']

_needescape = re.compile(b(r'[\x00-\x09\x0b-\x1f\x7f-\xff]')).search
_escapesub = re.compile(b(r'[\x00-\x09\x0b-\x1f\\\x7f-\xff]')).sub
_escapemap = dict((bchr(i), b(r'\x%02x' % i)) for i in range(256))
_escapemap.update({b('\\'): b('\\\\'), b('\r'): b(r'\r'), b('\t'): b(r'\t')})


def _escape(s):
    """Like the string-escape codec, but doesn't escape quotes"""
    return (_escapesub(lambda m: _escapemap[m.group(0)], s[:-1]) +
            b(' (esc)\n'))


def test(lines,
         shell='/bin/sh',
         indent=2,
         testname=None,
         env=None,
         cleanenv=True,
示例#17
0
文件: _cli.py 项目: maxeler/cram
def _patch(cmd, diff, path):
    """Run echo [lines from diff] | cmd -p0"""
    out, retcode = execute([cmd, '-p0'], stdin=b('').join(diff), cwd=path)
    return retcode == 0
示例#18
0
def _matchannotation(keyword, matchfunc, el, l):
    """Apply match function based on annotation keyword"""
    ann = b(' (%s)\n' % keyword)
    return el.endswith(ann) and matchfunc(el[:-len(ann)], l[:-1])
示例#19
0
文件: _diff.py 项目: tchaikov/cram
def unified_diff(l1,
                 l2,
                 fromfile=b(''),
                 tofile=b(''),
                 fromfiledate=b(''),
                 tofiledate=b(''),
                 n=3,
                 lineterm=b('\n'),
                 matchers=None):
    r"""Compare two sequences of lines; generate the delta as a unified diff.

    This is like difflib.unified_diff(), but allows custom matchers.

    >>> from cram._encoding import b
    >>> l1 = [b('a\n'), b('? (glob)\n')]
    >>> l2 = [b('a\n'), b('b\n')]
    >>> (list(unified_diff(l1, l2, b('f1'), b('f2'), b('1970-01-01'),
    ...                    b('1970-01-02'))) ==
    ...  [b('--- f1\t1970-01-01\n'), b('+++ f2\t1970-01-02\n'),
    ...   b('@@ -1,2 +1,2 @@\n'), b(' a\n'), b('-? (glob)\n'), b('+b\n')])
    True

    >>> from cram._diff import glob
    >>> list(unified_diff(l1, l2, matchers=[glob]))
    []
    """
    if matchers is None:
        matchers = []
    started = False
    matcher = _SequenceMatcher(None, l1, l2, matchers=matchers)
    for group in matcher.get_grouped_opcodes(n):
        if not started:
            if fromfiledate:
                fromdate = b('\t') + fromfiledate
            else:
                fromdate = b('')
            if tofiledate:
                todate = b('\t') + tofiledate
            else:
                todate = b('')
            yield b('--- ') + fromfile + fromdate + lineterm
            yield b('+++ ') + tofile + todate + lineterm
            started = True
        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
        yield (b("@@ -%d,%d +%d,%d @@" % (i1 + 1, i2 - i1, j1 + 1, j2 - j1)) +
               lineterm)
        for tag, i1, i2, j1, j2 in group:
            if tag == 'equal':
                for line in l1[i1:i2]:
                    yield b(' ') + line
                continue
            if tag == 'replace' or tag == 'delete':
                for line in l1[i1:i2]:
                    yield b('-') + line
            if tag == 'replace' or tag == 'insert':
                for line in l2[j1:j2]:
                    yield b('+') + line
示例#20
0
def unified_diff(l1, l2, fromfile=b(''), tofile=b(''), fromfiledate=b(''),
                 tofiledate=b(''), n=3, lineterm=b('\n'), matchers=None):
    r"""Compare two sequences of lines; generate the delta as a unified diff.

    This is like difflib.unified_diff(), but allows custom matchers.

    >>> from cram._encoding import b
    >>> l1 = [b('a\n'), b('? (glob)\n')]
    >>> l2 = [b('a\n'), b('b\n')]
    >>> (list(unified_diff(l1, l2, b('f1'), b('f2'), b('1970-01-01'),
    ...                    b('1970-01-02'))) ==
    ...  [b('--- f1\t1970-01-01\n'), b('+++ f2\t1970-01-02\n'),
    ...   b('@@ -1,2 +1,2 @@\n'), b(' a\n'), b('-? (glob)\n'), b('+b\n')])
    True

    >>> from cram._diff import glob
    >>> list(unified_diff(l1, l2, matchers=[glob]))
    []
    """
    if matchers is None:
        matchers = []
    started = False
    matcher = _SequenceMatcher(None, l1, l2, matchers=matchers)
    for group in matcher.get_grouped_opcodes(n):
        if not started:
            if fromfiledate:
                fromdate = b('\t') + fromfiledate
            else:
                fromdate = b('')
            if tofiledate:
                todate = b('\t') + tofiledate
            else:
                todate = b('')
            yield b('--- ') + fromfile + fromdate + lineterm
            yield b('+++ ') + tofile + todate + lineterm
            started = True
        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
        yield (b("@@ -%d,%d +%d,%d @@" % (i1 + 1, i2 - i1, j1 + 1, j2 - j1)) +
               lineterm)
        for tag, i1, i2, j1, j2 in group:
            if tag == 'equal':
                for line in l1[i1:i2]:
                    yield b(' ') + line
                continue
            if tag == 'replace' or tag == 'delete':
                for line in l1[i1:i2]:
                    yield b('-') + line
            if tag == 'replace' or tag == 'insert':
                for line in l2[j1:j2]:
                    yield b('+') + line
示例#21
0
文件: _diff.py 项目: tchaikov/cram
def _matchannotation(keyword, matchfunc, el, l):
    """Apply match function based on annotation keyword"""
    ann = b(' (%s)\n' % keyword)
    return el.endswith(ann) and matchfunc(el[:-len(ann)], l[:-1])
示例#22
0
文件: _test.py 项目: fipar/sysbench
def _escape(s):
    """Like the string-escape codec, but doesn't escape quotes"""
    return (_escapesub(lambda m: _escapemap[m.group(0)], s[:-1]) +
            b(' (esc)\n'))
示例#23
0
def _patch(cmd, diff):
    """Run echo [lines from diff] | cmd -p0"""
    out, retcode = execute([cmd, '-p0'], stdin=b('').join(diff))
    return retcode == 0
示例#24
0
文件: _test.py 项目: fipar/sysbench
"""Utilities for running individual tests"""

import itertools
import os
import re
import time

from cram._encoding import b, bchr, bytestype, envencode, unicodetype
from cram._diff import esc, glob, regex, unified_diff
from cram._process import PIPE, STDOUT, execute

__all__ = ['test', 'testfile']

_needescape = re.compile(b(r'[\x00-\x09\x0b-\x1f\x7f-\xff]')).search
_escapesub = re.compile(b(r'[\x00-\x09\x0b-\x1f\\\x7f-\xff]')).sub
_escapemap = dict((bchr(i), b(r'\x%02x' % i)) for i in range(256))
_escapemap.update({b('\\'): b('\\\\'), b('\r'): b(r'\r'), b('\t'): b(r'\t')})

def _escape(s):
    """Like the string-escape codec, but doesn't escape quotes"""
    return (_escapesub(lambda m: _escapemap[m.group(0)], s[:-1]) +
            b(' (esc)\n'))

def test(lines, shell='/bin/sh', indent=2, testname=None, env=None,
         cleanenv=True, debug=False, noerrfile=False):
    r"""Run test lines and return input, output, and diff.

    This returns a 3-tuple containing the following:

        (list of lines in test, same list with actual output, diff)
示例#25
0
def main(args):
    """Main entry point.

    If you're thinking of using Cram in other Python code (e.g., unit tests),
    consider using the test() or testfile() functions instead.

    :param args: Script arguments (excluding script name)
    :type args: str
    :return: Exit code (non-zero on failure)
    :rtype: int
    """
    opts, paths, getusage = _parseopts(args)
    if opts.version:
        sys.stdout.write("""Cram CLI testing framework (version 0.7)

Copyright (C) 2010-2016 Brodie Rao <*****@*****.**> and others
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
""")
        return

    conflicts = [('--yes', opts.yes, '--no', opts.no),
                 ('--quiet', opts.quiet, '--interactive', opts.interactive),
                 ('--debug', opts.debug, '--quiet', opts.quiet),
                 ('--debug', opts.debug, '--interactive', opts.interactive),
                 ('--debug', opts.debug, '--verbose', opts.verbose),
                 ('--debug', opts.debug, '--xunit-file', opts.xunit_file)]
    for s1, o1, s2, o2 in conflicts:
        if o1 and o2:
            sys.stderr.write('options %s and %s are mutually exclusive\n' %
                             (s1, s2))
            return 2

    shellcmd = _which(opts.shell)
    if not shellcmd:
        stderrb.write(b('shell not found: ') + fsencode(opts.shell) + b('\n'))
        return 2
    shell = [shellcmd]
    if opts.shell_opts:
        shell += shlex.split(opts.shell_opts)

    patchcmd = None
    if opts.interactive:
        patchcmd = _which('patch')
        if not patchcmd:
            sys.stderr.write('patch(1) required for -i\n')
            return 2

    if not paths:
        sys.stdout.write(getusage())
        return 2

    badpaths = [path for path in paths if not os.path.exists(path)]
    if badpaths:
        stderrb.write(b('no such file: ') + badpaths[0] + b('\n'))
        return 2

    if opts.yes:
        answer = 'y'
    elif opts.no:
        answer = 'n'
    else:
        answer = None

    tmpdir = os.environ['CRAMTMP'] = tempfile.mkdtemp('', 'cramtests-')
    tmpdirb = fsencode(tmpdir)
    proctmp = os.path.join(tmpdir, 'tmp')
    for s in ('TMPDIR', 'TEMP', 'TMP'):
        os.environ[s] = proctmp

    os.mkdir(proctmp)
    try:
        tests = runtests(paths,
                         tmpdirb,
                         shell,
                         indent=opts.indent,
                         cleanenv=not opts.preserve_env,
                         debug=opts.debug,
                         noerrfiles=opts.no_err_files)
        if not opts.debug:
            tests = runcli(tests,
                           quiet=opts.quiet,
                           verbose=opts.verbose,
                           patchcmd=patchcmd,
                           answer=answer,
                           noerrfiles=opts.no_err_files)
            if opts.xunit_file is not None:
                tests = runxunit(tests, opts.xunit_file)

        hastests = False
        failed = False
        for path, test in tests:
            hastests = True
            refout, postout, diff = test()
            if diff:
                failed = True

        if not hastests:
            sys.stderr.write('no tests found\n')
            return 2

        return int(failed)
    finally:
        if opts.keep_tmpdir:
            stdoutb.write(
                b('# Kept temporary directory: ') + tmpdirb + b('\n'))
        else:
            shutil.rmtree(tmpdir)
示例#26
0
文件: _test.py 项目: fipar/sysbench
def test(lines, shell='/bin/sh', indent=2, testname=None, env=None,
         cleanenv=True, debug=False, noerrfile=False):
    r"""Run test lines and return input, output, and diff.

    This returns a 3-tuple containing the following:

        (list of lines in test, same list with actual output, diff)

    diff is a generator that yields the diff between the two lists.

    If a test exits with return code 80, the actual output is set to
    None and diff is set to [].

    Note that the TESTSHELL environment variable is available in the
    test (set to the specified shell). However, the TESTDIR and
    TESTFILE environment variables are not available. To run actual
    test files, see testfile().

    Example usage:

    >>> from cram._encoding import b
    >>> refout, postout, diff = test([b('  $ echo hi\n'),
    ...                               b('  [a-z]{2} (re)\n')])
    >>> refout == [b('  $ echo hi\n'), b('  [a-z]{2} (re)\n')]
    True
    >>> postout == [b('  $ echo hi\n'), b('  hi\n')]
    True
    >>> bool(diff)
    False

    lines may also be a single bytes string:

    >>> refout, postout, diff = test(b('  $ echo hi\n  bye\n'))
    >>> refout == [b('  $ echo hi\n'), b('  bye\n')]
    True
    >>> postout == [b('  $ echo hi\n'), b('  hi\n')]
    True
    >>> bool(diff)
    True
    >>> (b('').join(diff) ==
    ...  b('--- \n+++ \n@@ -1,2 +1,2 @@\n   $ echo hi\n-  bye\n+  hi\n'))
    True

    Note that the b() function is internal to Cram. If you're using Python 2,
    use normal string literals instead. If you're using Python 3, use bytes
    literals.

    :param lines: Test input
    :type lines: bytes or collections.Iterable[bytes]
    :param shell: Shell to run test in
    :type shell: bytes or str or list[bytes] or list[str]
    :param indent: Amount of indentation to use for shell commands
    :type indent: int
    :param testname: Optional test file name (used in diff output)
    :type testname: bytes or None
    :param env: Optional environment variables for the test shell
    :type env: dict or None
    :param cleanenv: Whether or not to sanitize the environment
    :type cleanenv: bool
    :param debug: Whether or not to run in debug mode (don't capture stdout)
    :type debug: bool
    :return: Input, output, and diff iterables
    :rtype: (list[bytes], list[bytes], collections.Iterable[bytes])
    """
    indent = b(' ') * indent
    cmdline = indent + b('$ ')
    conline = indent + b('> ')
    usalt = 'CRAM%s' % time.time()
    salt = b(usalt)

    if env is None:
        env = os.environ.copy()

    if cleanenv:
        for s in ('LANG', 'LC_ALL', 'LANGUAGE'):
            env[s] = 'C'
        env['TZ'] = 'GMT'
        env['CDPATH'] = ''
        env['COLUMNS'] = '80'
        env['GREP_OPTIONS'] = ''

    if isinstance(lines, bytestype):
        lines = lines.splitlines(True)

    if isinstance(shell, (bytestype, unicodetype)):
        shell = [shell]
    env['TESTSHELL'] = shell[0]

    if debug:
        stdin = []
        for line in lines:
            if not line.endswith(b('\n')):
                line += b('\n')
            if line.startswith(cmdline):
                stdin.append(line[len(cmdline):])
            elif line.startswith(conline):
                stdin.append(line[len(conline):])

        execute(shell + ['-'], stdin=b('').join(stdin), env=env)
        return ([], [], [])

    after = {}
    refout, postout = [], []
    i = pos = prepos = -1
    stdin = []
    for i, line in enumerate(lines):
        if not line.endswith(b('\n')):
            line += b('\n')
        refout.append(line)
        if line.startswith(cmdline):
            after.setdefault(pos, []).append(line)
            prepos = pos
            pos = i
            stdin.append(b('echo %s %s $?\n' % (usalt, i)))
            stdin.append(line[len(cmdline):])
        elif line.startswith(conline):
            after.setdefault(prepos, []).append(line)
            stdin.append(line[len(conline):])
        elif not line.startswith(indent):
            after.setdefault(pos, []).append(line)
    stdin.append(b('echo %s %s $?\n' % (usalt, i + 1)))

    output, retcode = execute(shell + ['-'], stdin=b('').join(stdin),
                              stdout=PIPE, stderr=STDOUT, env=env)
    if retcode == 80:
        return (refout, None, [])

    pos = -1
    ret = 0
    for i, line in enumerate(output[:-1].splitlines(True)):
        out, cmd = line, None
        if salt in line:
            out, cmd = line.split(salt, 1)

        if out:
            if not out.endswith(b('\n')):
                out += b(' (no-eol)\n')

            if _needescape(out):
                out = _escape(out)
            postout.append(indent + out)

        if cmd:
            ret = int(cmd.split()[1])
            if ret != 0:
                postout.append(indent + b('[%s]\n' % (ret)))
            postout += after.pop(pos, [])
            pos = int(cmd.split()[0])

    postout += after.pop(pos, [])

    if testname:
        diffpath = testname
        errpath = diffpath + b('.err')
    else:
        diffpath = errpath = b('')
    diff = unified_diff(refout, postout, diffpath, errpath,
                        matchers=[esc, glob, regex])
    for firstline in diff:
        return refout, postout, itertools.chain([firstline], diff)
    return refout, postout, []
示例#27
0
文件: _diff.py 项目: maxeler/cram
def unified_diff(l1, l2, fromfile=b(''), tofile=b(''), fromfiledate=b(''),
                 tofiledate=b(''), n=3, lineterm=b('\n'), matchers=None):
    """Compare two sequences of lines; generate the delta as a unified diff.

    This is like difflib.unified_diff(), but allows custom matchers.
    """
    if matchers is None:
        matchers = []
    started = False
    matcher = _SequenceMatcher(None, l1, l2, matchers=matchers)
    for group in matcher.get_grouped_opcodes(n):
        if not started:
            if fromfiledate:
                fromdate = b('\t') + fromfiledate
            else:
                fromdate = b('')
            if tofiledate:
                todate = b('\t') + tofiledate
            else:
                todate = b('')
            yield b('--- ') + fromfile + fromdate + lineterm
            yield b('+++ ') + tofile + todate + lineterm
            started = True
        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
        yield (b("@@ -%d,%d +%d,%d @@" % (i1 + 1, i2 - i1, j1 + 1, j2 - j1)) +
               lineterm)
        for tag, i1, i2, j1, j2 in group:
            if tag == 'equal':
                for line in l1[i1:i2]:
                    yield b(' ') + line
                continue
            if tag == 'replace' or tag == 'delete':
                for line in l1[i1:i2]:
                    yield b('-') + line
            if tag == 'replace' or tag == 'insert':
                for line in l2[j1:j2]:
                    yield b('+') + line