Commits

allisonrandal  committed a029eb5

Migrate the bootstrapping test harness over to SubUnit. Integrates better
with the standard CPython test suite.

  • Participants
  • Parent commits c0ea97b

Comments (0)

Files changed (20)

File Lib/test/parrot/01-literals.py

 
 # check literals
 
-print '1..7'
-
+print 'test: integer literals'
 0
 1
 12
 
 #123l
 #1234L
+print 'success: integer literals'
 
 
 
-print 'ok 1'
-
-
+print 'test: octal literals'
 01
 02
 03
 05
 06
 07
+print 'success: octal literals'
 
-print 'ok 2'
 
+print 'test: hex literals'
 0x1
 0x2
 0x3
 0xf
 0xF
 0xdeadbeef
+print 'success: hex literals'
 
-print 'ok 3'
-
+print 'test: floating point literals'
 #3.14
 #10.
 #.001
 #0e0
 #0E0
 #
+print 'success: floating point literals'
 
-print 'ok',
 
+print 'test:',
+print 4
+print 'success:',
 print 4
 
+
+print 'test: imaginary literals'
 #3.14j
 #10.j
 #10j
 #1e100j
 #3.14e-10j
 #1J
-
-print 'ok 5'
-
-# prefix:+
-print 'ok', +6
-x = +7
-print 'ok', x
+print 'success: imaginary literals'

File Lib/test/parrot/02-op-math.py

 #!./parrot pynie.pbc
 
-print '1..14'
-
 # basic math operators
 
-print 'ok', 0 + 1
-print 'ok', 1 + 1
-print 'ok', 4 - 1
-print 'ok', 2 * 2
+print 'test: 1'
+print 'success:', 0 + 1
+print 'test: 2'
+print 'success:', 1 + 1
+print 'test: 3'
+print 'success:', 4 - 1
+print 'test: 4'
+print 'success:', 2 * 2
 
 # //, << and >>  operators
-print 'ok', 10 // 2
-print 'ok', 3<<1
-print 'ok', 14>>1
-print 'ok', 0 // 1 + 8
+print 'test: 5'
+print 'success:', 10 // 2
+print 'test: 6'
+print 'success:', 3<<1
+print 'test: 7'
+print 'success:', 14>>1
+print 'test: 8'
+print 'success:', 0 // 1 + 8
 
 # ** operator
-print 'ok', 3**2
-print 'ok', 0**0+9
-print 'ok', 0**1+11
-print 'ok', 1**0+11
+print 'test: 9'
+print 'success:', 3**2
+print 'test: 10'
+print 'success:', 0**0+9
+print 'test: 11'
+print 'success:', 0**1+11
+print 'test: 12'
+print 'success:', 1**0+11
 
 # unary + and -
-print 'ok', +13
-print 'ok', -10 + 24
+print 'test: 13'
+print 'success:', +13
+print 'test: 14'
+print 'success:', -10 + 24
+print 'test: -15'
+print 'success:', -15

File Lib/test/parrot/03-op-logic.py

 #!./parrot pynie.pbc
 
-print '1..8'
-
 # &
 
-print 'ok', (0 & 0) + 1
-print 'ok', (1 & 0) + 2
-print 'ok', (0 & 1) + 3
-print 'ok', (1 & 1) + 3
+print 'test: 1'
+print 'success:', (0 & 0) + 1
+print 'test: 2'
+print 'success:', (1 & 0) + 2
+print 'test: 3'
+print 'success:', (0 & 1) + 3
+print 'test: 4'
+print 'success:', (1 & 1) + 3
 
 # |
 
-print 'ok', (0 | 0) + 5
-print 'ok', (1 | 0) + 5
-print 'ok', (0 | 1) + 6
-print 'ok', (1 | 1) + 7
+print 'test: 5'
+print 'success:', (0 | 0) + 5
+print 'test: 6'
+print 'success:', (1 | 0) + 5
+print 'test: 7'
+print 'success:', (0 | 1) + 6
+print 'test: 8'
+print 'success:', (1 | 1) + 7

File Lib/test/parrot/05-vars.py

 #!./parrot pynie.pbc
 
-print '1..4'
+print 'test: 1'
+i=1
+print 'success:', i
 
-i=1
-print 'ok', i
+print 'test: 2'
 i=i+1
-print 'ok', i
+print 'success:', i
+
+print 'test: 3'
 s='3'
-print 'ok', s
-s='ok 4'
-print s
+print 'success:', s
+
+print 'test: string 4'
+s='string 4'
+print 'success:', s

File Lib/test/parrot/06-stmts.py

 #!./parrot pynie.pbc
 
-print '1..13'
-
 # if
 
-if 1: print 'ok 1'
-if 0: print 'nok 2'
-else: print 'ok 2'
-if 0: print 'nok 3'
-elif 1: print 'ok 3'
-if 0: print 'nok 4'
-elif 0: print 'nok 4'
-else: print 'ok 4'
+print 'test: if true'
+if 1: print 'success: if true'
+else: print 'failure: if true'
+
+print 'test: if false'
+if 0: print 'failure: if false'
+else: print 'success: if false'
+
+print 'test: elif true'
+if 0: print 'failure: elif true'
+elif 1: print 'success: elif true'
+
+print 'test: elif false'
+if 0: print 'failure: elif false'
+elif 0: print 'failure: elif false'
+else: print 'success: elif false'
 
 # while
 
 i=5
 while i < 7:
-    print 'ok',i
+    print 'test:', i
+    print 'success:', i
     i=i+1
+
+print 'test: while count'
+if i == 7: print 'success: while count'
+else: print 'failure: while count'
+
+print 'test: while false'
 while 0:
-    print 'nok 7'
+    print 'failure: while false'
 else:
-    print 'ok 7'
+    print 'success: while false'
 
 # for
 
+print 'test: for list literal'
+n = 0
+compare = 1
+for i in [ 1,2,3,4 ]:
+    n += 1
+    if i != n: compare = 0
+if compare: print 'success: for list literal'
+else: print 'failure: for list literal'
+
+print 'test: for list assign'
 lst = [ 1,2,3,4 ]
 n = 0
-ok = 1
+compare = 1
 for i in lst:
     n += 1
-    if i != n: ok = 0
-if ok: print 'ok 8'
-else: print 'nok 8'
+    if i != n: compare = 0
+if compare: print 'success: for list assign'
+else: print 'failure: for list assign'
 
+print 'test: for list bare'
 n = 0
-ok = 1
-for i in [ 1,2,3,4 ]:
-    n += 1
-    if i != n: ok = 0
-if ok: print 'ok 9'
-else: print 'nok 9'
-
-n = 0
-ok = 1
+compare = 1
 for i in 1,2,3,4:
     n += 1
-    if i != n: ok = 0
-if ok: print 'ok 10'
-else: print 'nok 10'
+    if i != n: compare = 0
+if compare: print 'success: for list bare'
+else: print 'failure: for list bare'
 
-# nested fors
+print 'test: nested fors'
 lst = [ ]
 for i in range(3):
     # XXX ResizablePMCArray has a 'push' method, use list.append
     for j in range(3):
         lst.push((i + 1) * (j + 1))
-ok = 1
+compare = 1
 lst2 = [ 1, 2, 3,  2, 4, 6,  3, 6, 9 ]
 for i in range(9):
-    if lst[i] != lst2[i]: ok = 0
-if ok: print 'ok 11'
-else: print 'nok 11'
+    if lst[i] != lst2[i]: compare = 0
+if compare: print 'success: nested fors'
+else: print 'failure: nested fors'
 
-# multiple iterators
-#ok = 0
+print 'test: multiple iterators'
+#compare = 0
 #for i, j in ( (0, 0), (1, 2), (2, 4) ):
-#    if j == i * 2: ok += 1
-#if ok == 3: print 'ok 12'
-#else: print 'nok 12'
-print 'not ok 12  # TODO implement multiple iterators'
+#    if j == i * 2: compare += 1
+#if compare == 3: print 'success: multiple iterators'
+#else: print 'failure: multiple iterators'
+print 'xfail: multiple iterators ['
+print 'multiple iterators not implemented yet'
+print ']'
 
-# nested scopes
+print 'test: nested scopes'
 i = 4
 if 0:
     if 0: pass
     i = 2
-if i == 2: print 'nok 13'
-else: print 'ok 13'
+if i == 2: print 'failure: nested scopes'
+else: print 'success: nested scopes'

File Lib/test/parrot/07-op-cmp.py

 #!./parrot pynie.pbc
 
-print '1..8'
+print 'test: 0 less than 1'
+if 0 < 1: print 'success: 0 less than 1'
 
-if 0 < 1: print 'ok 1'
-if 1 > 0: print 'ok 2'
-if 0 <= 1: print 'ok 3'
-if 0 <= 0: print 'ok 4'
-if 1 >= 0: print 'ok 5'
-if 0 >= 0: print 'ok 6'
-if 0 < 1 < 2: print 'ok 7'
-if 2 > 1 > 0: print 'ok 8'
+print 'test: 1 greater than 0'
+if 1 > 0: print 'success: 1 greater than 0'
+
+print 'test: 0 less than/equal 1'
+if 0 <= 1: print 'success: 0 less than/equal 1'
+
+print 'test: 0 less than/equal itself'
+if 0 <= 0: print 'success: 0 less than/equal itself'
+
+print 'test: 1 greater than/equal 0'
+if 1 >= 0: print 'success: 1 greater than/equal 0'
+
+print 'test: 0 greater than/equal itself'
+if 0 >= 0: print 'success: 0 greater than/equal itself'
+
+print 'test: 0 less than 1, less than 2'
+if 0 < 1 < 2: print 'success: 0 less than 1, less than 2'
+
+print 'test: 2 greater than 1, greater than 0'
+if 2 > 1 > 0: print 'success: 2 greater than 1, greater than 0'

File Lib/test/parrot/08-func.py

 #!./parrot pynie.pbc
 
-print '1..2'
+print 'test: define and call a function'
 
 def x():
- print "The wow starts now!\n"
+ print "success: define and call a function"
 
 x()
 
-print 'ok 1'
+print 'test: a function with arguments'
 
 def y(a,b,c):
  pass
 
 y(1,2,3)
 
-print 'ok 2'
+print 'success: a function with arguments'

File Lib/test/parrot/09-list.py

 #!./parrot
 
-print '1..5'
 
+print 'test: one element list'
 a = ["parrot"]
-if a: print 'ok', a
-else: print 'nok 1'
-b = ["ok 2", ["ok 3"]]
+if a: print 'success: one element list'
+else: print 'failure: one element list'
+
+print 'test: two element list'
+b = ["success: two element list", ["success: subelement"]]
 print b[0]
+print 'test: subelement'
 print b[1][0]
 
-c = ("ok 4")
-d = ("ok 5",)
+print 'test: single item in parens'
+c = ("success: single item in parens")
 print c
+
+print 'test: single item with comma'
+d = ("success: single item with comma",)
 print d[0]

File Lib/test/parrot/10-augop.py

 #!./parrot pynie.pbc
 
 # check augmented assignment ops
-print '1..11'
 
+print 'test: plus assign'
 i = 0
 i += 10
-if i == 10: print 'ok 1'
-else: print 'not ok 1'
+if i == 10: print 'success: plus assign'
+else: print 'failure: plus assign'
 
+print 'test: minus assign'
 i = 10
 i -= 5
-if i == 5: print 'ok 2'
-else: print 'not ok 2'
+if i == 5: print 'success: minus assign'
+else: print 'failure: minus assign'
 
+print 'test: multiply assign'
 i = -5
 i *= 2
-if i == -10: print 'ok 3'
-else: print 'not ok 3'
+if i == -10: print 'success: multiply assign'
+else: print 'failure: multiply assign'
 
+print 'test: divide assign'
 i = 10
 i /= 2
-if i == 5: print 'ok 4'
-else: print 'not ok 4'
+if i == 5: print 'success: divide assign'
+else: print 'failure: divide assign'
 
+print 'test: modulus assign'
 i = 10
 i %= 4
-if i == 2: print 'ok 5'
-else: print 'not ok 5'
+if i == 2: print 'success: modulus assign'
+else: print 'failure: modulus assign'
 
+print 'test: exponent assign'
 #i = 3
 #i **= 3
-#if i == 27: print 'ok 6'
-#else: print 'not ok 6'
-print 'not ok 6   # TODO implement **='
+#if i == 27: print 'success: exponent assign'
+#else: print 'failure exponent assign'
+print 'xfail: exponent assign ['
+print 'implement **='
+print ']'
 
+print 'test: right shift assign'
 i = 128
 i >>= 2
-if i == 32: print 'ok 7'
-else: print 'not ok 7'
+if i == 32: print 'success: right shift assign'
+else: print 'failure: right shift assign'
 
+print 'test: left shift assign'
 i = 1
 i <<= 10
-if i == 1024: print 'ok 8'
-else: print 'not ok 8'
+if i == 1024: print 'success: left shift assign'
+else: print 'failure: left shift assign'
 
+print 'test: bitwise AND assign'
 i = 0x55
 i &= 0x1f
-if i == 0x15: print 'ok 9'
-else: print 'not ok 9'
+if i == 0x15: print 'success: bitwise AND assign'
+else: print 'failure: bitwise AND assign'
 
+print 'test: bitwise XOR assign'
 i = 0x55
 i ^= 0x1f
-if i == 0x4a: print 'ok 10'
-else: print 'not ok 10'
+if i == 0x4a: print 'success: bitwise XOR assign'
+else: print 'failure: bitwise XOR assign'
 
+print 'test: bitwise OR assign'
 i = 0x55
 i |= 0x1f
-if i == 0x5f: print 'ok 11'
-else: print 'not ok 11'
+if i == 0x5f: print 'success: bitwise OR assign'
+else: print 'failure: bitwise OR assign'

File Lib/test/parrot/11-boolop.py

 #!./parrot pynie.pbc
 
-print '1..9'
+print 'test: logical AND true'
+if 1 and 2 and 3:
+    print 'success: logical AND true'
+else:
+    print 'failure: logical AND true'
 
-if 1 and 2 and 3:
-    print 'ok 1'
+print 'test: logical AND false'
+if 0 and 1 and 2:
+    print 'failure: logical AND false'
 else:
-    print 'not ok 1'
+    print 'success: logical AND false'
 
-if 0 and 1 and 2:
-    print 'not ok 2'
+print 'test: logical OR true'
+if 0 or 1 or 2:
+    print 'success: logical OR true'
 else:
-    print 'ok 2'
+    print 'failure: logical OR true'
 
-if 0 or 1 or 2:
-    print 'ok 3'
+print 'test: logical OR false'
+if 0 or 0 or 0:
+    print 'failure: logical OR false'
 else:
-    print 'not ok 3'
+    print 'success: logical OR false'
 
-if 0 or 0 or 0:
-    print 'not ok 4'
+print 'test: logical NOT true'
+if not 0:
+    print 'success: logical NOT true'
 else:
-    print 'ok 4'
+    print 'failure: logical NOT true'
 
-if not 0:
-    print 'ok 5'
+print 'test: logical NOT false'
+if not 1:
+    print 'failure: logical NOT false'
 else:
-    print 'not ok 5'
+    print 'success: logical NOT false'
 
-if not 1:
-    print 'not ok 6'
+print 'test: logical OR/AND precedence'
+if 1 or 0 and 0:   # and should be tighter than or
+    print 'success: logical OR/AND precedence'
 else:
-    print 'ok 6'
+    print 'failure: logical OR/AND precedence'
 
-if 1 or 0 and 0:   # and should be tighter than or
-    print 'ok 7'
+print 'test: logical ops parentheses'
+if (1 or 0) and 0:   # test parentheses
+    print 'failure: logical ops parentheses'
 else:
-    print 'not ok 7'
+    print 'success: logical ops parentheses'
 
-if (1 or 0) and 0:   # test parentheses
-    print 'not ok 8'
-else:
-    print 'ok 8'
-
+print 'test: double NOT'
 #if not not 1:
-#   print 'ok 9'
+#   print 'success: double NOT'
 #else:
-#   print 'not ok 9'
-print 'not ok 9   # TODO fix double nots'
+#   print 'failure: double NOT'
+print 'xfail: double NOT ['
+print 'fix double nots'
+print ']'

File Lib/test/parrot/12-try.py

 #!./parrot pynie.pbc
 
-print '1..5'
-
+print 'test: catch exception'
 try:
     x = 1
     raise Exception
 except:
     x = 2
-if x == 2: print 'ok 1'
-else: print 'not ok 1'
+if x == 2: print 'success: catch exception'
+else: print 'failure: catch exception'
 
+print 'test: no exception'
 try:
     x = 1
 except:
     x = 2
-if x == 1: print 'ok 2'
-else: print 'not ok 2'
+if x == 1: print 'success: no exception'
+else: print 'failure: no exception'
 
 # make sure asserts are on
 # XXX in real Python, you can't set __debug__
+# fix this test so it doesn't either
+
 __debug__ = 1
 
+print 'test: assert false'
 try:
     x = 1
     assert 0
 except:
     x = 2
-if x == 2: print 'ok 3'
-else: print 'not ok 3'
+if x == 2: print 'success: assert false'
+else: print 'failure: assert false'
 
+print 'test: assert true'
 try:
     x = 1
     assert 1
 except:
     x = 2
-if x == 1: print 'ok 4'
-else: print 'not ok 4'
+if x == 1: print 'success: assert true'
+else: print 'failure: assert true'
 
+print 'test: assert false, no debug'
 __debug__ = 0
 try:
     x = 1
     assert 0
 except:
     x = 2
-if x == 1: print 'ok 5'
-else: print 'not ok 5'
+if x == 1: print 'success: assert false, no debug'
+else: print 'failure: assert false, no debug'

File Lib/test/parrot/13-builtins.py

 #!./parrot pynie.pbc
 
-print '1..5'
-
 #len
+print 'test: len'
 n = len([1,2,3,4])
-if n == 4: print 'ok 1'
-else: print 'not ok 1'
+if n == 4: print 'success: len'
+else: print 'failure: len'
 
 #list
+print 'test: list'
 lst1 = [ 1,2,3,4 ]
 lst2 = list(lst1)
 lst1.pop()
 lst1.pop()
 lst1.pop()
 lst1.pop()
-if lst2[2] == 3: print 'ok 2'
-else: print 'not ok 2'
+if lst2[2] == 3: print 'success: list'
+else: print 'failure: list'
 
 #range
+print 'test: range'
 lst = range(4)
 ok = 0
 n = -1
 for i in lst:
     n += 1
     if n == i: ok += 1
-if ok == 4: print 'ok 3'
-else: print 'not ok 3'
+if ok == 4: print 'success: range'
+else: print 'failure: range'
 
 #str
-if str(5) == '5': print 'ok 4'
-else: print 'not ok 4'
+print 'test: str'
+if str(5) == '5': print 'success: str'
+else: print 'failure: str'
 
-if len(str(861)) == 3: print 'ok 5'
-else: print 'not ok 5'
+print 'test: len/str'
+if len(str(861)) == 3: print 'success: len/str'
+else: print 'failure: len/str'

File Lib/test/parrot/grammar.py

+#!./parrot pynie.pbc
+
+print 'test: backslash for line continuation'
+x = 1 \
++ 1
+if x != 2:
+    print 'failure: backslash for line continuation ['
+    print "test_grammar.py:9:FAIL test expected 2, not",
+    print x
+    print "]"
+else: print 'success: backslash for line continuation'
+
+print 'test: backslash ending comment'
+# Backslash in comments :\
+x = 0
+if x != 0:
+    print 'failure: backslash ending comment ['
+    print "test_grammar.py:16:FAIL test expected 0, not",
+    print x
+    print "]"
+else: print 'success: backslash ending comment'
+
+
+print 'test: backslash ending comment after space'
+# Backslash in comments \
+x = 2
+if x != 2:
+    print 'failure: backslash ending comment after space ['
+    print "test_grammar.py:27:FAIL test expected 2, not",
+    print x
+    print "]"
+else: print 'success: backslash ending comment after space'
+
+
+print 'test: hex int'
+if 0xff != 255:
+    print 'failure: hex int ['
+    print "test_grammar.py:36:FAIL test expected 255, not",
+    print 0xff
+    print "]"
+else: print 'success: hex int'
+
+print 'test: octal int'
+if 0377 != 255:
+    print 'failure: octal int ['
+    print "test_grammar.py:44:FAIL test expected 255, not",
+    print 0377
+    print "]"
+else: print 'success: octal int'

File Lib/test/parrot/harness

-#! perl
-
-# $Id$
-
-# note: Due to a limitation in Getopt::Long options that should be passed
-# through to fudgeall have to come after all other options
-
-use strict;
-use warnings;
-
-use FindBin;
-use File::Spec;
-use Getopt::Long qw(:config pass_through);
-
-$ENV{'HARNESS_PERL'} = 'parrot pynie.pbc';
-use Test::Harness;
-$Test::Harness::switches = '';
-
-GetOptions(
-    'tests-from-file=s' => \my $list_file,
-    'verbosity=i'       => \$Test::Harness::verbose,
-    'jobs:3'            => \my $jobs,
-);
-my @pass_through_options = grep m/^--?[^-]/, @ARGV;
-my @files = grep m/^[^-]/, @ARGV;
-
-my $slash = $^O eq 'MSWin32' ? '\\' : '/';
-
-if ($list_file) {
-    open(my $f, '<', $list_file)
-        or die "Can't open file '$list_file' for reading: $!";
-    while (<$f>) {
-        next if m/^\s*#/;
-        next unless m/\S/;
-        chomp;
-        my ($fn, $fudgespec) = split m/\s+#\s*/;
-        $fn = "t/spec/$fn" unless $fn =~ m/^t\Q$slash\Espec\Q$slash\E/;
-        $fn =~ s{/}{$slash}g;
-        if ( -r $fn ) {
-            push @files, $fn;
-        } else {
-            warn "Missing test file: $fn\n";
-        }
-    }
-    close $f or die $!;
-}
-
-my @tfiles = map { all_in($_) } sort @files;
-
-runtests(@tfiles);
-
-# adapted to return only files ending in '.py'
-sub all_in {
-    my $start = shift;
-
-    return $start unless -d $start;
-
-    my @skip = ( File::Spec->updir, File::Spec->curdir, qw( .svn CVS .git ) );
-    my %skip = map {($_,1)} @skip;
-
-    my @hits = ();
-
-    if ( opendir( my $dh, $start ) ) {
-        my @files = sort readdir $dh;
-        closedir $dh or die $!;
-        for my $file ( @files ) {
-            next if $skip{$file};
-
-            my $currfile = File::Spec->catfile( $start, $file );
-            if ( -d $currfile ) {
-                push( @hits, all_in( $currfile ) );
-            } else {
-                push( @hits, $currfile ) if $currfile =~ /\.py$/;
-            }
-        }
-    } else {
-        warn "$start: $!\n";
-    }
-
-    return @hits;
-}

File Lib/test/parrot/op-logic-compare.py

+#!./parrot pynie.pbc
+
+# &
+
+print 'test: boolean logic 0 AND 0'
+x = 0 & 0
+if x == 0: print 'success: boolean logic 0 AND 0'
+else:
+    print 'failure: boolean logic 0 AND 0 ['
+    print '03-op-logic.py:6:FAIL test expected 0, not',
+    print x
+    print ']'
+
+print 'test: boolean logic 0 AND 1'
+x = 0 & 1
+if x == 0: print 'success: boolean logic 0 AND 1'
+else:
+    print 'failure: boolean logic 0 AND 1 ['
+    print '03-op-logic.py:15:FAIL test expected 0, not',
+    print x
+    print ']'
+
+print 'test: boolean logic 1 AND 0'
+x = 1 & 0
+if x == 0: print 'success: boolean logic 1 AND 0'
+else:
+    print 'failure: boolean logic 1 AND 0 ['
+    print '03-op-logic.py:24:FAIL test expected 0, not',
+    print x
+    print ']'
+
+print 'test: boolean logic 1 AND 1'
+x = 1 & 1
+if x == 1: print 'success: boolean logic 1 AND 1'
+else:
+    print 'failure: boolean logic 1 AND 1 ['
+    print '03-op-logic.py:33:FAIL test expected 1, not',
+    print x
+    print ']'
+
+# |
+
+print 'test: boolean logic 0 OR 0'
+x = 0 | 0
+if x == 0: print 'success: boolean logic 0 OR 0'
+else:
+    print 'failure: boolean logic 0 OR 0 ['
+    print '03-op-logic.py:6:FAIL test expected 0, not',
+    print x
+    print ']'
+
+print 'test: boolean logic 0 OR 1'
+x = 0 | 1
+if x == 1: print 'success: boolean logic 0 OR 1'
+else:
+    print 'failure: boolean logic 0 OR 1 ['
+    print '03-op-logic.py:15:FAIL test expected 1, not',
+    print x
+    print ']'
+
+print 'test: boolean logic 1 OR 0'
+x = 1 | 0
+if x == 1: print 'success: boolean logic 1 OR 0'
+else:
+    print 'failure: boolean logic 1 OR 0 ['
+    print '03-op-logic.py:24:FAIL test expected 1, not',
+    print x
+    print ']'
+
+print 'test: boolean logic 1 OR 1'
+x = 1 | 1
+if x == 1: print 'success: boolean logic 1 OR 1'
+else:
+    print 'failure: boolean logic 1 OR 1 ['
+    print '03-op-logic.py:33:FAIL test expected 1, not',
+    print x
+    print ']'

File Lib/test/parrot/op-math-compare.py

+#!./parrot pynie.pbc
+
+# unary operators
+
+print 'test: prefix +'
+x = +7
+if x == 7: print 'success: prefix +'
+else:
+    print 'failure: prefix + ['
+    print "op-math-compare.py:92:FAIL test expected 7, not",
+    print x
+    print "]"
+
+print 'test: prefix -'
+x = -8
+if x == -8: print 'success: prefix -'
+else:
+    print 'failure: prefix - ['
+    print "op-math-compare.py:92:FAIL test expected -8, not",
+    print x
+    print "]"
+
+# basic math operators
+
+print 'test: addition'
+x = 1 + 1
+if x == 2: print 'success: addition'
+else:
+    print 'failure: addition ['
+    print '02-op-math.py:7:FAIL test expected 2, not',
+    print x
+    print ']'
+
+print 'test: addition with zero'
+x = 0 + 1
+if x == 1: print 'success: addition with zero'
+else:
+    print 'failure: addition with zero ['
+    print '02-op-math.py:16:FAIL test expected 1, not',
+    print x
+    print ']'
+
+print 'test: subtraction'
+x = 4 - 1
+if x == 3: print 'success: subtraction'
+else:
+    print 'failure: subtraction ['
+    print '02-op-math.py:24:FAIL test expected 3, not',
+    print x
+    print ']'
+
+print 'test: multiplication'
+x = 2 * 2
+if x == 4: print 'success: multiplication'
+else:
+    print 'failure: multiplication ['
+    print '02-op-math.py:33:FAIL test expected 4, not',
+    print x
+    print ']'
+
+print 'test: division'
+x = 10 / 5
+if x == 2: print 'success: division'
+else:
+    print 'failure: division ['
+    print '02-op-math.py:42:FAIL test expected 2, not',
+    print x
+    print ']'
+
+# //, << and >>  operators
+
+print 'test: floor division'
+x = 10 // 2
+if x == 5: print 'success: floor division'
+else:
+    print 'failure: floor division ['
+    print '02-op-math.py:53:FAIL test expected 5, not',
+    print x
+    print ']'
+
+print 'test: floor division and addition'
+x = 0 // 1 + 8
+if x == 8: print 'success: floor division and addition'
+else:
+    print 'failure: floor division and addition ['
+    print '02-op-math.py:62:FAIL test expected 8, not',
+    print x
+    print ']'
+
+print 'test: left shift'
+x = 3 << 1
+if x == 6: print 'success: left shift'
+else:
+    print 'failure: left shift ['
+    print '02-op-math.py:71:FAIL test expected 6, not',
+    print x
+    print ']'
+
+print 'test: right shift'
+x = 14 >> 1
+if x == 7: print 'success: right shift'
+else:
+    print 'failure: right shift ['
+    print '02-op-math.py:80:FAIL test expected 7, not',
+    print x
+    print ']'
+
+# ** operator
+
+print 'test: power'
+x = 3 ** 2
+if x == 9: print 'success: power'
+else:
+    print 'failure: power ['
+    print '02-op-math.py:91:FAIL test expected 9, not',
+    print x
+    print ']'
+
+print 'test: 0 to the power of 0'
+x = 0 ** 0
+if x == 1: print 'success: 0 to the power of 0'
+else:
+    print 'failure: 0 to the power of 0 ['
+    print '02-op-math.py:104:FAIL test expected 1, not',
+    print x
+    print ']'
+
+print 'test: 0 to the power of 1'
+x = 0 ** 1
+if x == 0: print 'success: 0 to the power of 1'
+else:
+    print 'failure: 0 to the power of 1 ['
+    print '02-op-math.py:109:FAIL test expected 0, not',
+    print x
+    print ']'
+
+print 'test: 1 to the power of 0'
+x = 1 ** 0
+if x == 1: print 'success: 1 to the power of 0'
+else:
+    print 'failure: 1 to the power of 0 ['
+    print '02-op-math.py:118:FAIL test expected 1, not',
+    print x
+    print ']'
+
+# unary + and -
+print 'test: unary plus'
+x = +13
+if x == 13: print 'success: unary plus'
+else:
+    print 'failure: unary plus ['
+    print '02-op-math.py:128:FAIL test expected 13, not',
+    print x
+    print ']'
+
+# unary + and -
+print 'test: unary minus'
+x = -10 + 24
+if x == 14: print 'success: unary minus'
+else:
+    print 'failure: unary minus ['
+    print '02-op-math.py:128:FAIL test expected 14, not',
+    print x
+    print ']'

File Lib/test/parrot/simplest.py

+#!./parrot pynie.pbc
+
+print 'test: able to print'
+print 'success: able to print'
 
 ##  testing targets
 test    : build
-	$(PERL) Lib/test/parrot/harness Lib/test/parrot
+	python runtests.py
 
 # basic run for missing libs
 test-installable: installable
+#!/usr/bin/python
+
+import subunit
+import unittest
+
+class PynieParrotTests(subunit.ExecTestCase):
+
+    def test_01_simplest(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/simplest.py"""
+
+    def test_02_literals(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/01-literals.py"""
+
+    def test_03_math_ops(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/02-op-math.py"""
+
+    def test_04_bitwise_ops(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/03-op-logic.py"""
+
+    def test_05_variables(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/05-vars.py"""
+
+    def test_06_statements(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/06-stmts.py"""
+
+    def test_07_compare_ops(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/07-op-cmp.py"""
+
+    def test_08_lists(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/09-list.py"""
+
+    def test_09_assign_ops(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/10-augop.py"""
+
+    def test_10_logic_ops(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/11-boolop.py"""
+
+    def test_11_functions(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/08-func.py"""
+
+    def test_12_builtins(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/13-builtins.py"""
+
+    def test_13_exceptions(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/12-try.py"""
+
+    def test_math_ops_compare(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/op-math-compare.py"""
+
+    def test_logic_ops_compare(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/op-logic-compare.py"""
+
+    def test_grammar(self):
+        """/usr/bin/parrot pynie.pbc Lib/test/parrot/grammar.py"""
+
+
+if __name__ == '__main__':
+     unittest.main()

File subunit/__init__.py

+#
+#  subunit: extensions to Python unittest to get test results from subprocesses.
+#  Copyright (C) 2005  Robert Collins <robertc@robertcollins.net>
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+import os
+from StringIO import StringIO
+import subprocess
+import sys
+import re
+import unittest
+
+def test_suite():
+    import subunit.tests
+    return subunit.tests.test_suite()
+
+
+def join_dir(base_path, path):
+    """
+    Returns an absolute path to C{path}, calculated relative to the parent
+    of C{base_path}.
+
+    @param base_path: A path to a file or directory.
+    @param path: An absolute path, or a path relative to the containing
+    directory of C{base_path}.
+
+    @return: An absolute path to C{path}.
+    """
+    return os.path.join(os.path.dirname(os.path.abspath(base_path)), path)
+
+
+def tags_to_new_gone(tags):
+    """Split a list of tags into a new_set and a gone_set."""
+    new_tags = set()
+    gone_tags = set()
+    for tag in tags:
+        if tag[0] == '-':
+            gone_tags.add(tag[1:])
+        else:
+            new_tags.add(tag)
+    return new_tags, gone_tags
+
+
+class TestProtocolServer(object):
+    """A class for receiving results from a TestProtocol client.
+    
+    :ivar tags: The current tags associated with the protocol stream.
+    """
+
+    OUTSIDE_TEST = 0
+    TEST_STARTED = 1
+    READING_FAILURE = 2
+    READING_ERROR = 3
+    READING_SKIP = 4
+    READING_XFAIL = 5
+    READING_SUCCESS = 6
+
+    def __init__(self, client, stream=sys.stdout):
+        """Create a TestProtocol server instance.
+
+        client should be an object that provides
+         - startTest
+         - addSuccess
+         - addFailure
+         - addError
+         - stopTest
+        methods, i.e. a TestResult.
+        """
+        self.state = TestProtocolServer.OUTSIDE_TEST
+        self.client = client
+        self._stream = stream
+        self.tags = set()
+
+    def _addError(self, offset, line):
+        if (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description == line[offset:-1]):
+            self.state = TestProtocolServer.OUTSIDE_TEST
+            self.current_test_description = None
+            self.client.addError(self._current_test, RemoteError(""))
+            self.client.stopTest(self._current_test)
+            self._current_test = None
+        elif (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description + " [" == line[offset:-1]):
+            self.state = TestProtocolServer.READING_ERROR
+            self._message = ""
+        else:
+            self.stdOutLineReceived(line)
+
+    def _addExpectedFail(self, offset, line):
+        if (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description == line[offset:-1]):
+            self.state = TestProtocolServer.OUTSIDE_TEST
+            self.current_test_description = None
+            self.client.addSuccess(self._current_test)
+            self.client.stopTest(self._current_test)
+        elif (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description + " [" == line[offset:-1]):
+            self.state = TestProtocolServer.READING_XFAIL
+            self._message = ""
+        else:
+            self.stdOutLineReceived(line)
+
+    def _addFailure(self, offset, line):
+        if (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description == line[offset:-1]):
+            self.state = TestProtocolServer.OUTSIDE_TEST
+            self.current_test_description = None
+            self.client.addFailure(self._current_test, RemoteError())
+            self.client.stopTest(self._current_test)
+        elif (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description + " [" == line[offset:-1]):
+            self.state = TestProtocolServer.READING_FAILURE
+            self._message = ""
+        else:
+            self.stdOutLineReceived(line)
+
+    def _addSkip(self, offset, line):
+        if (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description == line[offset:-1]):
+            self.state = TestProtocolServer.OUTSIDE_TEST
+            self.current_test_description = None
+            self._skip_or_error()
+            self.client.stopTest(self._current_test)
+        elif (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description + " [" == line[offset:-1]):
+            self.state = TestProtocolServer.READING_SKIP
+            self._message = ""
+        else:
+            self.stdOutLineReceived(line)
+
+    def _skip_or_error(self, message=None):
+        """Report the current test as a skip if possible, or else an error."""
+        addSkip = getattr(self.client, 'addSkip', None)
+        if not callable(addSkip):
+            self.client.addError(self._current_test, RemoteError(message))
+        else:
+            if not message:
+                message = "No reason given"
+            addSkip(self._current_test, message)
+
+    def _addSuccess(self, offset, line):
+        if (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description == line[offset:-1]):
+            self._succeedTest()
+        elif (self.state == TestProtocolServer.TEST_STARTED and
+            self.current_test_description + " [" == line[offset:-1]):
+            self.state = TestProtocolServer.READING_SUCCESS
+            self._message = ""
+        else:
+            self.stdOutLineReceived(line)
+
+    def _appendMessage(self, line):
+        if line[0:2] == " ]":
+            # quoted ] start
+            self._message += line[1:]
+        else:
+            self._message += line
+
+    def endQuote(self, line):
+        if self.state == TestProtocolServer.READING_FAILURE:
+            self.state = TestProtocolServer.OUTSIDE_TEST
+            self.current_test_description = None
+            self.client.addFailure(self._current_test,
+                                   RemoteError(self._message))
+            self.client.stopTest(self._current_test)
+        elif self.state == TestProtocolServer.READING_ERROR:
+            self.state = TestProtocolServer.OUTSIDE_TEST
+            self.current_test_description = None
+            self.client.addError(self._current_test,
+                                 RemoteError(self._message))
+            self.client.stopTest(self._current_test)
+        elif self.state == TestProtocolServer.READING_SKIP:
+            self.state = TestProtocolServer.OUTSIDE_TEST
+            self.current_test_description = None
+            self._skip_or_error(self._message)
+            self.client.stopTest(self._current_test)
+        elif self.state in (
+            TestProtocolServer.READING_SUCCESS,
+            TestProtocolServer.READING_XFAIL,
+            ):
+            self._succeedTest()
+        else:
+            self.stdOutLineReceived(line)
+
+    def _handleTags(self, offset, line):
+        """Process a tags command."""
+        tags = line[offset:].split()
+        new_tags, gone_tags = tags_to_new_gone(tags)
+        if self.state == TestProtocolServer.OUTSIDE_TEST:
+            update_tags = self.tags
+        else:
+            update_tags = self._current_test.tags
+        update_tags.update(new_tags)
+        update_tags.difference_update(gone_tags)
+
+    def lineReceived(self, line):
+        """Call the appropriate local method for the received line."""
+        if line == "]\n":
+            self.endQuote(line)
+        elif self.state in (TestProtocolServer.READING_FAILURE,
+            TestProtocolServer.READING_ERROR, TestProtocolServer.READING_SKIP,
+            TestProtocolServer.READING_SUCCESS,
+            TestProtocolServer.READING_XFAIL
+            ):
+            self._appendMessage(line)
+        else:
+            parts = line.split(None, 1)
+            if len(parts) == 2:
+                cmd, rest = parts
+                offset = len(cmd) + 1
+                cmd = cmd.strip(':')
+                if cmd in ('test', 'testing'):
+                    self._startTest(offset, line)
+                elif cmd == 'error':
+                    self._addError(offset, line)
+                elif cmd == 'failure':
+                    self._addFailure(offset, line)
+                elif cmd == 'skip':
+                    self._addSkip(offset, line)
+                elif cmd in ('success', 'successful'):
+                    self._addSuccess(offset, line)
+                elif cmd in ('tags',):
+                    self._handleTags(offset, line)
+                elif cmd in ('time',):
+                    # Accept it, but do not do anything with it yet.
+                    pass
+                elif cmd == 'xfail':
+                    self._addExpectedFail(offset, line)
+                else:
+                    self.stdOutLineReceived(line)
+            else:
+                self.stdOutLineReceived(line)
+
+    def _lostConnectionInTest(self, state_string):
+        error_string = "lost connection during %stest '%s'" % (
+            state_string, self.current_test_description)
+        self.client.addError(self._current_test, RemoteError(error_string))
+        self.client.stopTest(self._current_test)
+
+    def lostConnection(self):
+        """The input connection has finished."""
+        if self.state == TestProtocolServer.OUTSIDE_TEST:
+            return
+        if self.state == TestProtocolServer.TEST_STARTED:
+            self._lostConnectionInTest('')
+        elif self.state == TestProtocolServer.READING_ERROR:
+            self._lostConnectionInTest('error report of ')
+        elif self.state == TestProtocolServer.READING_FAILURE:
+            self._lostConnectionInTest('failure report of ')
+        elif self.state == TestProtocolServer.READING_SUCCESS:
+            self._lostConnectionInTest('success report of ')
+        elif self.state == TestProtocolServer.READING_SKIP:
+            self._lostConnectionInTest('skip report of ')
+        elif self.state == TestProtocolServer.READING_XFAIL:
+            self._lostConnectionInTest('xfail report of ')
+        else:
+            self._lostConnectionInTest('unknown state of ')
+
+    def readFrom(self, pipe):
+        for line in pipe.readlines():
+            self.lineReceived(line)
+        self.lostConnection()
+
+    def _startTest(self, offset, line):
+        """Internal call to change state machine. Override startTest()."""
+        if self.state == TestProtocolServer.OUTSIDE_TEST:
+            self.state = TestProtocolServer.TEST_STARTED
+            self._current_test = RemotedTestCase(line[offset:-1])
+            self.current_test_description = line[offset:-1]
+            self.client.startTest(self._current_test)
+            self._current_test.tags = set(self.tags)
+        else:
+            self.stdOutLineReceived(line)
+
+    def stdOutLineReceived(self, line):
+        self._stream.write(line)
+
+    def _succeedTest(self):
+        self.client.addSuccess(self._current_test)
+        self.client.stopTest(self._current_test)
+        self.current_test_description = None
+        self._current_test = None
+        self.state = TestProtocolServer.OUTSIDE_TEST
+
+
+class RemoteException(Exception):
+    """An exception that occured remotely to Python."""
+
+    def __eq__(self, other):
+        try:
+            return self.args == other.args
+        except AttributeError:
+            return False
+
+
+class TestProtocolClient(unittest.TestResult):
+    """A class that looks like a TestResult and informs a TestProtocolServer."""
+
+    def __init__(self, stream):
+        unittest.TestResult.__init__(self)
+        self._stream = stream
+
+    def addError(self, test, error):
+        """Report an error in test test."""
+        self._stream.write("error: %s [\n" % test.id())
+        for line in self._exc_info_to_string(error, test).splitlines():
+            self._stream.write("%s\n" % line)
+        self._stream.write("]\n")
+
+    def addFailure(self, test, error):
+        """Report a failure in test test."""
+        self._stream.write("failure: %s [\n" % test.id())
+        for line in self._exc_info_to_string(error, test).splitlines():
+            self._stream.write("%s\n" % line)
+        self._stream.write("]\n")
+
+    def addSkip(self, test, reason):
+        """Report a skipped test."""
+        self._stream.write("skip: %s [\n" % test.id())
+        self._stream.write("%s\n" % reason)
+        self._stream.write("]\n")
+
+    def addSuccess(self, test):
+        """Report a success in a test."""
+        self._stream.write("successful: %s\n" % test.id())
+
+    def startTest(self, test):
+        """Mark a test as starting its test run."""
+        self._stream.write("test: %s\n" % test.id())
+
+
+def RemoteError(description=""):
+    if description == "":
+        description = "\n"
+    return (RemoteException, RemoteException(description), None)
+
+
+class RemotedTestCase(unittest.TestCase):
+    """A class to represent test cases run in child processes.
+    
+    Instances of this class are used to provide the Python test API a TestCase
+    that can be printed to the screen, introspected for metadata and so on.
+    However, as they are a simply a memoisation of a test that was actually
+    run in the past by a separate process, they cannot perform any interactive
+    actions.
+    """
+
+    def __eq__ (self, other):
+        try:
+            return self.__description == other.__description
+        except AttributeError:
+            return False
+
+    def __init__(self, description):
+        """Create a psuedo test case with description description."""
+        self.__description = description
+
+    def error(self, label):
+        raise NotImplementedError("%s on RemotedTestCases is not permitted." %
+            label)
+
+    def setUp(self):
+        self.error("setUp")
+
+    def tearDown(self):
+        self.error("tearDown")
+
+    def shortDescription(self):
+        return self.__description
+
+    def id(self):
+        return "%s" % (self.__description,)
+
+    def __str__(self):
+        return "%s (%s)" % (self.__description, self._strclass())
+
+    def __repr__(self):
+        return "<%s description='%s'>" % \
+               (self._strclass(), self.__description)
+
+    def run(self, result=None):
+        if result is None: result = self.defaultTestResult()
+        result.startTest(self)
+        result.addError(self, RemoteError("Cannot run RemotedTestCases.\n"))
+        result.stopTest(self)
+
+    def _strclass(self):
+        cls = self.__class__
+        return "%s.%s" % (cls.__module__, cls.__name__)
+
+
+class ExecTestCase(unittest.TestCase):
+    """A test case which runs external scripts for test fixtures."""
+
+    def __init__(self, methodName='runTest'):
+        """Create an instance of the class that will use the named test
+           method when executed. Raises a ValueError if the instance does
+           not have a method with the specified name.
+        """
+        unittest.TestCase.__init__(self, methodName)
+        testMethod = getattr(self, methodName)
+        self.script = join_dir(sys.modules[self.__class__.__module__].__file__,
+                               testMethod.__doc__)
+
+    def countTestCases(self):
+        return 1
+
+    def run(self, result=None):
+        if result is None: result = self.defaultTestResult()
+        self._run(result)
+
+    def debug(self):
+        """Run the test without collecting errors in a TestResult"""
+        self._run(unittest.TestResult())
+
+    def _run(self, result):
+        protocol = TestProtocolServer(result)
+        output = subprocess.Popen(self.script, shell=True,
+                                  stdout=subprocess.PIPE).communicate()[0]
+        protocol.readFrom(StringIO(output))
+
+
+class IsolatedTestCase(unittest.TestCase):
+    """A TestCase which runs its tests in a forked process."""
+
+    def run(self, result=None):
+        if result is None: result = self.defaultTestResult()
+        run_isolated(unittest.TestCase, self, result)
+
+
+class IsolatedTestSuite(unittest.TestSuite):
+    """A TestCase which runs its tests in a forked process."""
+
+    def run(self, result=None):
+        if result is None: result = unittest.TestResult()
+        run_isolated(unittest.TestSuite, self, result)
+
+
+def run_isolated(klass, self, result):
+    """Run a test suite or case in a subprocess, using the run method on klass.
+    """
+    c2pread, c2pwrite = os.pipe()
+    # fixme - error -> result
+    # now fork
+    pid = os.fork()
+    if pid == 0:
+        # Child
+        # Close parent's pipe ends
+        os.close(c2pread)
+        # Dup fds for child
+        os.dup2(c2pwrite, 1)
+        # Close pipe fds.
+        os.close(c2pwrite)
+
+        # at this point, sys.stdin is redirected, now we want
+        # to filter it to escape ]'s.
+        ### XXX: test and write that bit.
+
+        result = TestProtocolClient(sys.stdout)
+        klass.run(self, result)
+        sys.stdout.flush()
+        sys.stderr.flush()
+        # exit HARD, exit NOW.
+        os._exit(0)
+    else:
+        # Parent
+        # Close child pipe ends
+        os.close(c2pwrite)
+        # hookup a protocol engine
+        protocol = TestProtocolServer(result)
+        protocol.readFrom(os.fdopen(c2pread, 'rU'))
+        os.waitpid(pid, 0)
+        # TODO return code evaluation.
+    return result
+
+
+def TAP2SubUnit(tap, subunit):
+    """Filter a TAP pipe into a subunit pipe.
+    
+    :param tap: A tap pipe/stream/file object.
+    :param subunit: A pipe/stream/file object to write subunit results to.
+    :return: The exit code to exit with.
+    """
+    BEFORE_PLAN = 0
+    AFTER_PLAN = 1
+    SKIP_STREAM = 2
+    client = TestProtocolClient(subunit)
+    state = BEFORE_PLAN
+    plan_start = 1
+    plan_stop = 0
+    def _skipped_test(subunit, plan_start):
+        # Some tests were skipped.
+        subunit.write('test test %d\n' % plan_start)
+        subunit.write('error test %d [\n' % plan_start)
+        subunit.write('test missing from TAP output\n')
+        subunit.write(']\n')
+        return plan_start + 1
+    # Test data for the next test to emit
+    test_name = None
+    log = []
+    result = None
+    def _emit_test():
+        "write out a test"
+        if test_name is None:
+            return
+        subunit.write("test %s\n" % test_name)
+        if not log:
+            subunit.write("%s %s\n" % (result, test_name))
+        else:
+            subunit.write("%s %s [\n" % (result, test_name))
+        if log:
+            for line in log:
+                subunit.write("%s\n" % line)
+            subunit.write("]\n")
+        del log[:]
+    for line in tap:
+        if state == BEFORE_PLAN:
+            match = re.match("(\d+)\.\.(\d+)\s*(?:\#\s+(.*))?\n", line)
+            if match:
+                state = AFTER_PLAN
+                _, plan_stop, comment = match.groups()
+                plan_stop = int(plan_stop)
+                if plan_start > plan_stop and plan_stop == 0:
+                    # skipped file
+                    state = SKIP_STREAM
+                    subunit.write("test file skip\n")
+                    subunit.write("skip file skip [\n")
+                    subunit.write("%s\n" % comment)
+                    subunit.write("]\n")
+                continue
+        # not a plan line, or have seen one before
+        match = re.match("(ok|not ok)(?:\s+(\d+)?)?(?:\s+([^#]*[^#\s]+)\s*)?(?:\s+#\s+(TODO|SKIP)(?:\s+(.*))?)?\n", line)
+        if match:
+            # new test, emit current one.
+            _emit_test()
+            status, number, description, directive, directive_comment = match.groups()
+            if status == 'ok':
+                result = 'success'
+            else:
+                result = "failure"
+            if description is None:
+                description = ''
+            else:
+                description = ' ' + description
+            if directive is not None:
+                if directive == 'TODO':
+                    result = 'xfail'
+                elif directive == 'SKIP':
+                    result = 'skip'
+                if directive_comment is not None:
+                    log.append(directive_comment)
+            if number is not None:
+                number = int(number)
+                while plan_start < number:
+                    plan_start = _skipped_test(subunit, plan_start)
+            test_name = "test %d%s" % (plan_start, description)
+            plan_start += 1
+            continue
+        match = re.match("Bail out\!(?:\s*(.*))?\n", line)
+        if match:
+            reason, = match.groups()
+            if reason is None:
+                extra = ''
+            else:
+                extra = ' %s' % reason
+            _emit_test()
+            test_name = "Bail out!%s" % extra
+            result = "error"
+            state = SKIP_STREAM
+            continue
+        match = re.match("\#.*\n", line)
+        if match:
+            log.append(line[:-1])
+            continue
+        subunit.write(line)
+    _emit_test()
+    while plan_start <= plan_stop:
+        # record missed tests
+        plan_start = _skipped_test(subunit, plan_start)
+    return 0
+
+
+def tag_stream(original, filtered, tags):
+    """Alter tags on a stream.
+
+    :param original: The input stream.
+    :param filtered: The output stream.
+    :param tags: The tags to apply. As in a normal stream - a list of 'TAG' or
+        '-TAG' commands.
+
+        A 'TAG' command will add the tag to the output stream,
+        and override any existing '-TAG' command in that stream.
+        Specifically:
+         * A global 'tags: TAG' will be added to the start of the stream.
+         * Any tags commands with -TAG will have the -TAG removed.
+
+        A '-TAG' command will remove the TAG command from the stream.
+        Specifically:
+         * A 'tags: -TAG' command will be added to the start of the stream.
+         * Any 'tags: TAG' command will have 'TAG' removed from it.
+        Additionally, any redundant tagging commands (adding a tag globally
+        present, or removing a tag globally removed) are stripped as a
+        by-product of the filtering.
+    :return: 0
+    """
+    new_tags, gone_tags = tags_to_new_gone(tags)
+    def write_tags(new_tags, gone_tags):
+        if new_tags or gone_tags:
+            filtered.write("tags: " + ' '.join(new_tags))
+            if gone_tags:
+                for tag in gone_tags:
+                    filtered.write("-" + tag)
+            filtered.write("\n")
+    write_tags(new_tags, gone_tags)
+    # TODO: use the protocol parser and thus don't mangle test comments.
+    for line in original:
+        if line.startswith("tags:"):
+            line_tags = line[5:].split()
+            line_new, line_gone = tags_to_new_gone(line_tags)
+            line_new = line_new - gone_tags
+            line_gone = line_gone - new_tags
+            write_tags(line_new, line_gone)
+        else:
+            filtered.write(line)
+    return 0
+
+
+class ProtocolTestCase(object):
+    """A test case which reports a subunit stream."""
+
+    def __init__(self, stream):
+        self._stream = stream
+
+    def __call__(self, result=None):
+        return self.run(result)
+
+    def run(self, result=None):
+        if result is None:
+            result = self.defaultTestResult()
+        protocol = TestProtocolServer(result)
+        line = self._stream.readline()
+        while line:
+            protocol.lineReceived(line)
+            line = self._stream.readline()
+        protocol.lostConnection()
+
+
+class TestResultStats(unittest.TestResult):
+    """A pyunit TestResult interface implementation for making statistics.
+    
+    :ivar total_tests: The total tests seen.
+    :ivar passed_tests: The tests that passed.
+    :ivar failed_tests: The tests that failed.
+    :ivar tags: The tags seen across all tests.
+    """
+
+    def __init__(self, stream):
+        """Create a TestResultStats which outputs to stream."""
+        unittest.TestResult.__init__(self)
+        self._stream = stream
+        self.failed_tests = 0
+        self.skipped_tests = 0
+        self.tags = set()
+
+    @property
+    def total_tests(self):
+        return self.testsRun
+
+    def addError(self, test, err):
+        self.failed_tests += 1
+
+    def addFailure(self, test, err):
+        self.failed_tests += 1
+
+    def addSkip(self, test, reason):
+        self.skipped_tests += 1
+
+    def formatStats(self):
+        self._stream.write("Total tests:   %5d\n" % self.total_tests)
+        self._stream.write("Passed tests:  %5d\n" % self.passed_tests)
+        self._stream.write("Failed tests:  %5d\n" % self.failed_tests)
+        self._stream.write("Skipped tests: %5d\n" % self.skipped_tests)
+        tags = sorted(self.tags)
+        self._stream.write("Tags: %s\n" % (", ".join(tags)))
+
+    @property
+    def passed_tests(self):
+        return self.total_tests - self.failed_tests - self.skipped_tests
+
+    def stopTest(self, test):
+        unittest.TestResult.stopTest(self, test)
+        self.tags.update(test.tags)
+
+    def wasSuccessful(self):
+        """Tells whether or not this result was a success"""
+        return self.failed_tests == 0
+
+
+class TestResultFilter(unittest.TestResult):
+    """A pyunit TestResult interface implementation which filters tests.
+
+    Tests that pass the filter are handed on to another TestResult instance
+    for further processing/reporting. To obtain the filtered results, 
+    the other instance must be interrogated.
+
+    :ivar result: The result that tests are passed to after filtering.
+    """
+
+    def __init__(self, result, filter_error=False, filter_failure=False,
+        filter_success=True, filter_skip=False):
+        """Create a FilterResult object filtering to result.
+        
+        :param filter_error: Filter out errors.
+        :param filter_failure: Filter out failures.
+        :param filter_success: Filter out successful tests.
+        :param filter_skip: Filter out skipped tests.
+        """
+        unittest.TestResult.__init__(self)
+        self.result = result
+        self._filter_error = filter_error
+        self._filter_failure = filter_failure
+        self._filter_success = filter_success
+        self._filter_skip = filter_skip
+        
+    def addError(self, test, err):
+        if not self._filter_error:
+            self.result.startTest(test)
+            self.result.addError(test, err)
+            self.result.stopTest(test)
+
+    def addFailure(self, test, err):
+        if not self._filter_failure:
+            self.result.startTest(test)
+            self.result.addFailure(test, err)
+            self.result.stopTest(test)
+
+    def addSkip(self, test, reason):
+        if not self._filter_skip:
+            self.result.startTest(test)
+            # This is duplicated, it would be nice to have on a 'calls
+            # TestResults' mixin perhaps.
+            addSkip = getattr(self.result, 'addSkip', None)
+            if not callable(addSkip):
+                self.result.addError(test, RemoteError(reason))
+            else:
+                self.result.addSkip(test, reason)
+            self.result.stopTest(test)
+
+    def addSuccess(self, test):
+        if not self._filter_success:
+            self.result.startTest(test)
+            self.result.addSuccess(test)
+            self.result.stopTest(test)
+
+    def id_to_orig_id(self, id):
+        if id.startswith("subunit.RemotedTestCase."):
+            return id[len("subunit.RemotedTestCase."):]
+        return id
+