Skip to content

Commit

Permalink
Import wvtestrun and wvtest.py from the wvtest.git project.
Browse files Browse the repository at this point in the history
Corresponding wvtest commit is db65ff5907571a5004bb3c500efd19421cb06d1a.
  • Loading branch information
apenwarr committed Dec 31, 2009
1 parent ae32766 commit 01d3e50
Show file tree
Hide file tree
Showing 2 changed files with 317 additions and 0 deletions.
131 changes: 131 additions & 0 deletions wvtest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env python
import traceback
import os
import re
import sys

if __name__ != "__main__": # we're imported as a module
_registered = []
_tests = 0
_fails = 0

def wvtest(func):
""" Use this decorator (@wvtest) in front of any function you want to run
as part of the unit test suite. Then run:
python wvtest.py path/to/yourtest.py
to run all the @wvtest functions in that file.
"""
_registered.append(func)
return func


def _result(msg, tb, code):
global _tests, _fails
_tests += 1
if code != 'ok':
_fails += 1
(filename, line, func, text) = tb
filename = os.path.basename(filename)
msg = re.sub(r'\s+', ' ', str(msg))
sys.stderr.flush()
print '! %-70s %s' % ('%s:%-4d %s' % (filename, line, msg),
code)
sys.stdout.flush()


def _check(cond, msg = 'unknown', tb = None):
if tb == None: tb = traceback.extract_stack()[-3]
if cond:
_result(msg, tb, 'ok')
else:
_result(msg, tb, 'FAILED')
return cond


def _code():
(filename, line, func, text) = traceback.extract_stack()[-3]
text = re.sub(r'^\w+\((.*)\)$', r'\1', text);
return text


def WVPASS(cond = True):
''' Throws an exception unless cond is true. '''
return _check(cond, _code())

def WVFAIL(cond = True):
''' Throws an exception unless cond is false. '''
return _check(not cond, 'NOT(%s)' % _code())

def WVPASSEQ(a, b):
''' Throws an exception unless a == b. '''
return _check(a == b, '%s == %s' % (repr(a), repr(b)))

def WVPASSNE(a, b):
''' Throws an exception unless a != b. '''
return _check(a != b, '%s != %s' % (repr(a), repr(b)))

def WVPASSLT(a, b):
''' Throws an exception unless a < b. '''
return _check(a < b, '%s < %s' % (repr(a), repr(b)))

def WVPASSLE(a, b):
''' Throws an exception unless a <= b. '''
return _check(a <= b, '%s <= %s' % (repr(a), repr(b)))

def WVPASSGT(a, b):
''' Throws an exception unless a > b. '''
return _check(a > b, '%s > %s' % (repr(a), repr(b)))

def WVPASSGE(a, b):
''' Throws an exception unless a >= b. '''
return _check(a >= b, '%s >= %s' % (repr(a), repr(b)))

else: # we're the main program
# NOTE
# Why do we do this in such convoluted way? Because if you run
# wvtest.py as a main program and it imports your test files, then
# those test files will try to import the wvtest module recursively.
# That actually *works* fine, because we don't run this main program
# when we're imported as a module. But you end up with two separate
# wvtest modules, the one that gets imported, and the one that's the
# main program. Each of them would have duplicated global variables
# (most importantly, wvtest._registered), and so screwy things could
# happen. Thus, we make the main program module *totally* different
# from the imported module. Then we import wvtest (the module) into
# wvtest (the main program) here and make sure to refer to the right
# versions of global variables.
#
# All this is done just so that wvtest.py can be a single file that's
# easy to import into your own applications.
import wvtest

def _runtest(modname, fname, f):
print
print 'Testing "%s" in %s.py:' % (fname, modname)
sys.stdout.flush()
try:
f()
except Exception, e:
print
print traceback.format_exc()
tb = sys.exc_info()[2]
wvtest._result(e, traceback.extract_tb(tb)[-1],
'EXCEPTION')

# main code
for modname in sys.argv[1:]:
if not os.path.exists(modname):
print 'Skipping: %s' % modname
continue
if modname.endswith('.py'):
modname = modname[:-3]
print 'Importing: %s' % modname
wvtest._registered = []
mod = __import__(modname.replace('/', '.'), None, None, [])

for t in wvtest._registered:
_runtest(modname, t.func_name, t)
print

print
print 'WvTest: %d tests, %d failures.' % (wvtest._tests, wvtest._fails)
186 changes: 186 additions & 0 deletions wvtestrun
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#!/usr/bin/perl -w
#
# WvTest:
# Copyright (C)2007-2009 Versabanq Innovations Inc. and contributors.
# Licensed under the GNU Library General Public License, version 2.
# See the included file named LICENSE for license information.
#
use strict;
use Time::HiRes qw(time);

# always flush
$| = 1;

if (@ARGV < 1) {
print STDERR "Usage: $0 <command line...>\n";
exit 127;
}

print STDERR "Testing \"all\" in @ARGV:\n";

my $pid = open(my $fh, "-|");
if (!$pid) {
# child
setpgrp();
open STDERR, '>&STDOUT' or die("Can't dup stdout: $!\n");
exec(@ARGV);
exit 126; # just in case
}

my $istty = -t STDOUT;
my @log = ();
my ($gpasses, $gfails) = (0,0);

sub bigkill($)
{
my $pid = shift;

if (@log) {
print "\n" . join("\n", @log) . "\n";
}

print STDERR "\n! Killed by signal FAILED\n";

($pid > 0) || die("pid is '$pid'?!\n");

local $SIG{CHLD} = sub { }; # this will wake us from sleep() faster
kill 15, $pid;
sleep(2);

if ($pid > 1) {
kill 9, -$pid;
}
kill 9, $pid;

exit(125);
}

# parent
local $SIG{INT} = sub { bigkill($pid); };
local $SIG{TERM} = sub { bigkill($pid); };
local $SIG{ALRM} = sub {
print STDERR "Alarm timed out! No test results for too long.\n";
bigkill($pid);
};

sub colourize($)
{
my $result = shift;
my $pass = ($result eq "ok");

if ($istty) {
my $colour = $pass ? "\e[32;1m" : "\e[31;1m";
return "$colour$result\e[0m";
} else {
return $result;
}
}

sub mstime($$$)
{
my ($floatsec, $warntime, $badtime) = @_;
my $ms = int($floatsec * 1000);
my $str = sprintf("%d.%03ds", $ms/1000, $ms % 1000);

if ($istty && $ms > $badtime) {
return "\e[31;1m$str\e[0m";
} elsif ($istty && $ms > $warntime) {
return "\e[33;1m$str\e[0m";
} else {
return "$str";
}
}

sub resultline($$)
{
my ($name, $result) = @_;
return sprintf("! %-65s %s", $name, colourize($result));
}

my $allstart = time();
my ($start, $stop);

sub endsect()
{
$stop = time();
if ($start) {
printf " %s %s\n", mstime($stop - $start, 500, 1000), colourize("ok");
}
}

while (<$fh>)
{
chomp;
s/\r//g;

if (/^\s*Testing "(.*)" in (.*):\s*$/)
{
alarm(120);
my ($sect, $file) = ($1, $2);

endsect();

printf("! %s %s: ", $file, $sect);
@log = ();
$start = $stop;
}
elsif (/^!\s*(.*?)\s+(\S+)\s*$/)
{
alarm(120);

my ($name, $result) = ($1, $2);
my $pass = ($result eq "ok");

if (!$start) {
printf("\n! Startup: ");
$start = time();
}

push @log, resultline($name, $result);

if (!$pass) {
$gfails++;
if (@log) {
print "\n" . join("\n", @log) . "\n";
@log = ();
}
} else {
$gpasses++;
print ".";
}
}
else
{
push @log, $_;
}
}

endsect();

my $newpid = waitpid($pid, 0);
if ($newpid != $pid) {
die("waitpid returned '$newpid', expected '$pid'\n");
}

my $code = $?;
my $ret = ($code >> 8);

# return death-from-signal exits as >128. This is what bash does if you ran
# the program directly.
if ($code && !$ret) { $ret = $code | 128; }

if ($ret && @log) {
print "\n" . join("\n", @log) . "\n";
}

if ($code != 0) {
print resultline("Program returned non-zero exit code ($ret)", "FAILED");
}

my $gtotal = $gpasses+$gfails;
printf("\nWvTest: %d test%s, %d failure%s, total time %s.\n",
$gtotal, $gtotal==1 ? "" : "s",
$gfails, $gfails==1 ? "" : "s",
mstime(time() - $allstart, 2000, 5000));
print STDERR "\nWvTest result code: $ret\n";
exit( $ret ? $ret : ($gfails ? 125 : 0) );

0 comments on commit 01d3e50

Please sign in to comment.