Merge lp:~stewart/percona-xtrabackup/1.6-testsuite-fixes into lp:percona-xtrabackup/1.6

Proposed by Stewart Smith
Status: Merged
Approved by: Stewart Smith
Approved revision: no longer in the source branch.
Merged at revision: 255
Proposed branch: lp:~stewart/percona-xtrabackup/1.6-testsuite-fixes
Merge into: lp:percona-xtrabackup/1.6
Diff against target: 18017 lines (+16942/-219)
86 files modified
test/bootstrap.sh (+88/-0)
test/experimental/xb_race_drop.sh (+0/-2)
test/inc/common.sh (+63/-114)
test/python/BytesIO.py (+136/-0)
test/python/iso8601/LICENSE (+20/-0)
test/python/iso8601/README (+26/-0)
test/python/iso8601/README.subunit (+5/-0)
test/python/iso8601/setup.py (+58/-0)
test/python/iso8601/test_iso8601.py (+111/-0)
test/python/junitxml/__init__.py (+221/-0)
test/python/junitxml/tests/__init__.py (+16/-0)
test/python/junitxml/tests/test_junitxml.py (+327/-0)
test/python/subunit/__init__.py (+1250/-0)
test/python/subunit/chunked.py (+185/-0)
test/python/subunit/details.py (+119/-0)
test/python/subunit/iso8601.py (+133/-0)
test/python/subunit/progress_model.py (+106/-0)
test/python/subunit/run.py (+73/-0)
test/python/subunit/test_results.py (+492/-0)
test/python/subunit/tests/TestUtil.py (+80/-0)
test/python/subunit/tests/__init__.py (+41/-0)
test/python/subunit/tests/sample-script.py (+21/-0)
test/python/subunit/tests/sample-two-script.py (+7/-0)
test/python/subunit/tests/test_chunked.py (+152/-0)
test/python/subunit/tests/test_details.py (+112/-0)
test/python/subunit/tests/test_progress_model.py (+118/-0)
test/python/subunit/tests/test_subunit_filter.py (+208/-0)
test/python/subunit/tests/test_subunit_stats.py (+84/-0)
test/python/subunit/tests/test_subunit_tags.py (+69/-0)
test/python/subunit/tests/test_tap2subunit.py (+445/-0)
test/python/subunit/tests/test_test_protocol.py (+1299/-0)
test/python/subunit/tests/test_test_results.py (+300/-0)
test/python/testtools/__init__.py (+80/-0)
test/python/testtools/_spinner.py (+316/-0)
test/python/testtools/compat.py (+286/-0)
test/python/testtools/content.py (+238/-0)
test/python/testtools/content_type.py (+33/-0)
test/python/testtools/deferredruntest.py (+335/-0)
test/python/testtools/distutilscmd.py (+62/-0)
test/python/testtools/helpers.py (+64/-0)
test/python/testtools/matchers.py (+785/-0)
test/python/testtools/monkey.py (+97/-0)
test/python/testtools/run.py (+332/-0)
test/python/testtools/runtest.py (+200/-0)
test/python/testtools/testcase.py (+724/-0)
test/python/testtools/testresult/__init__.py (+19/-0)
test/python/testtools/testresult/doubles.py (+111/-0)
test/python/testtools/testresult/real.py (+621/-0)
test/python/testtools/tests/__init__.py (+44/-0)
test/python/testtools/tests/helpers.py (+72/-0)
test/python/testtools/tests/test_compat.py (+257/-0)
test/python/testtools/tests/test_content.py (+223/-0)
test/python/testtools/tests/test_content_type.py (+46/-0)
test/python/testtools/tests/test_deferredruntest.py (+738/-0)
test/python/testtools/tests/test_distutilscmd.py (+90/-0)
test/python/testtools/tests/test_fixturesupport.py (+79/-0)
test/python/testtools/tests/test_helpers.py (+106/-0)
test/python/testtools/tests/test_matchers.py (+695/-0)
test/python/testtools/tests/test_monkey.py (+167/-0)
test/python/testtools/tests/test_run.py (+77/-0)
test/python/testtools/tests/test_runtest.py (+300/-0)
test/python/testtools/tests/test_spinner.py (+332/-0)
test/python/testtools/tests/test_testresult.py (+1372/-0)
test/python/testtools/tests/test_testsuite.py (+53/-0)
test/python/testtools/tests/test_testtools.py (+1143/-0)
test/python/testtools/tests/test_with_with.py (+42/-0)
test/python/testtools/testsuite.py (+87/-0)
test/python/testtools/utils.py (+13/-0)
test/run.sh (+140/-14)
test/subunit.sh (+56/-0)
test/subunit2junitxml (+65/-0)
test/t/bug606981.sh (+8/-6)
test/t/ib_csm_csv.sh (+3/-5)
test/t/ib_incremental.sh (+8/-9)
test/t/ib_partition.sh (+0/-4)
test/t/ib_specialchar.sh (+3/-6)
test/t/xb_basic.sh (+3/-5)
test/t/xb_export.sh (+12/-8)
test/t/xb_incremental.sh (+7/-7)
test/t/xb_incremental_compressed.sh (+15/-13)
test/t/xb_parallel.sh (+3/-5)
test/t/xb_part_range.sh (+0/-3)
test/t/xb_partial.sh (+3/-4)
test/t/xb_stats.sh (+6/-8)
test/t/xb_stream.sh (+3/-5)
utils/build.sh (+3/-1)
To merge this branch: bzr merge lp:~stewart/percona-xtrabackup/1.6-testsuite-fixes
Reviewer Review Type Date Requested Status
Percona developers Pending
Review via email: mp+64140@code.launchpad.net

Description of the change

Includes Alexey's fixes, plus some more of mine.

big change is to make run.sh also output subunit, which subunit2junitxml then converts to the JUnit that Jenkins understands. (see http://jenkins.percona.com/view/Percona%20Xtrabackup/job/percona-xtrabackup-param/9/testReport/ for example of what that makes Jenkins do)

I've included needed libraries to run subunit2junitxml and updated the Jenkins percona-xtrabackup-template job to properly run it.

http://jenkins.percona.com/view/Percona%20Xtrabackup/job/percona-xtrabackup-param/10/

(also see branch that is merged with trunk)

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'test/bootstrap.sh'
2--- test/bootstrap.sh 1970-01-01 00:00:00 +0000
3+++ test/bootstrap.sh 2011-06-10 09:54:56 +0000
4@@ -0,0 +1,88 @@
5+#!/bin/bash
6+
7+set -e
8+
9+function usage()
10+{
11+ cat <<EOF
12+Usage:
13+$0 xtrabackup_target [installation_directory]
14+$0 path_to_server_tarball [installation_directory]
15+
16+Prepares a server binary directory to be used by run.sh when running XtraBackup
17+tests.
18+
19+If the argument is one of the build targets passed to build.sh
20+(i.e. innodb51_builtin innodb51 innodb55 xtradb51 xtradb55) then the
21+appropriate Linux tarball is downloaded from a pre-defined location and
22+unpacked into the specified installation directory ('./server' by default).
23+
24+Otherwise the argument is assumed to be a path to a server binary tarball.
25+EOF
26+}
27+
28+if [ -z "$1" ]
29+then
30+ usage
31+fi
32+
33+arch="`uname -m`"
34+if [ "$arch" = "i386" ]
35+then
36+ arch="i686"
37+fi
38+
39+case "$1" in
40+ innodb51_builtin | innodb51)
41+ url="http://www.percona.com/downloads/community/"
42+ tarball="mysql-5.1.49-linux-$arch-glibc23.tar.gz"
43+ ;;
44+ innodb55)
45+ url="http://www.percona.com/downloads/community/"
46+ tarball="mysql-5.5.6-rc-linux2.6-$arch.tar.gz"
47+ ;;
48+ xtradb51)
49+ url="http://www.percona.com/downloads/community/"
50+ tarball="Percona-Server-5.1.51-rel11.5-132-Linux-$arch.tar.gz"
51+ ;;
52+ xtradb55)
53+ url="http://www.percona.com/downloads/Percona-Server-5.5/Percona-Server-5.5.11-20.2/Linux/binary/"
54+ tarball="Percona-Server-5.5.11-rel20.2-116.Linux.$arch.tar.gz"
55+ ;;
56+ *)
57+ if ! test -r "$1"
58+ then
59+ echo "$1 does not exist"
60+ exit 1
61+ fi
62+ tarball="$1"
63+ ;;
64+esac
65+
66+if test -n "$2"
67+then
68+ destdir="$2"
69+else
70+ destdir="./server"
71+fi
72+
73+if test -n "$url"
74+then
75+ echo "Downloading $tarball"
76+ wget -c "$url/$tarball"
77+fi
78+
79+if test -d "$destdir"
80+then
81+ rm -rf "$destdir"
82+fi
83+mkdir "$destdir"
84+
85+echo "Unpacking $tarball into $destdir"
86+tar zxf $tarball -C $destdir
87+sourcedir="$destdir/`ls $destdir`"
88+if test -n "$sourcedir"
89+then
90+ mv $sourcedir/* $destdir
91+ rm -rf $sourcedir
92+fi
93
94=== modified file 'test/experimental/xb_race_drop.sh'
95--- test/experimental/xb_race_drop.sh 2011-03-01 16:59:27 +0000
96+++ test/experimental/xb_race_drop.sh 2011-06-10 09:54:56 +0000
97@@ -11,5 +11,3 @@
98 vlog "Starting backup"
99 xtrabackup --datadir=$mysql_datadir --backup --target-dir=$topdir/data/full
100 vlog "Full backup done"
101-stop_mysqld
102-clean
103
104=== modified file 'test/inc/common.sh'
105--- test/inc/common.sh 2011-03-14 18:46:12 +0000
106+++ test/inc/common.sh 2011-06-10 09:54:56 +0000
107@@ -1,16 +1,20 @@
108 #!/bin/env bash
109
110 set -eu
111-topdir="`pwd`/var"
112-mysql_datadir="$topdir/mysql"
113-mysql_port="3306"
114-mysql_socket=${mysql_socket:-"/tmp/xtrabackup.mysql.sock"}
115-
116-OUTFILE=results/`basename $0`_innobackupex.out
117+
118+function innobackupex()
119+{
120+ run_cmd `which innobackupex` $IB_ARGS $*
121+}
122+
123+function xtrabackup()
124+{
125+ run_cmd `which $XB_BIN` $XB_ARGS $*
126+}
127
128 function vlog
129 {
130- echo "`date +"%F %T"`: `basename "$0"`: $@"
131+ echo "`date +"%F %T"`: `basename "$0"`: $@" >> $OUTFILE
132 }
133
134 function clean_datadir()
135@@ -53,105 +57,6 @@
136 mkdir -p "$mysql_datadir"
137 }
138
139-function init_mysql()
140-{
141- url="http://www.percona.com/downloads/community/"
142- case "$MYSQL_VERSION" in
143- system)
144- echo "Using MySQL installed in the system"
145- MYSQL=mysql
146- MYSQLADMIN=mysqladmin
147- MYSQL_INSTALL_DB=mysql_install_db
148- MYSQL_ARGS="--no-defaults --socket=${mysql_socket} --user=root"
149- MYSQLD=mysqld
150- MYSQL_BASEDIR="/usr"
151- MYSQLD_ARGS="--no-defaults --basedir=${MYSQL_BASEDIR} --socket=${mysql_socket} --datadir=$mysql_datadir"
152- IB_BIN="innobackupex --defaults-file=$topdir/my.cnf"
153- XB_BIN=xtrabackup
154- ;;
155- 5.0)
156- echo "Using MySQL 5.0"
157- version="5.0.91-linux-`uname -m`-glibc23"
158- cd $topdir
159- wget "$url/mysql-$version.tar.gz" >/dev/null 2>&1
160- tar zxf mysql-$version.tar.gz
161- MYSQL=$topdir/mysql-$version/bin/mysql
162- MYSQLADMIN=$topdir/mysql-$version/bin/mysqladmin
163- MYSQL_INSTALL_DB=$topdir/mysql-$version/scripts/mysql_install_db
164- MYSQLD=$topdir/mysql-$version/bin/mysqld
165- MYSQL_BASEDIR=$topdir/mysql-$version
166- MYSQLD_ARGS="--no-defaults --basedir=${MYSQL_BASEDIR} --socket=${mysql_socket} --datadir=$mysql_datadir"
167- MYSQL_ARGS="--no-defaults --socket=${mysql_socket} --user=root"
168- IB_BIN="innobackupex --defaults-file=$topdir/my.cnf"
169- XB_BIN=xtrabackup_51
170- cd -
171- ;;
172- 5.1)
173- echo "Using MySQL 5.1"
174- version="5.1.49-linux-`uname -m`-glibc23"
175- cd $topdir
176- wget "$url/mysql-$version.tar.gz" >/dev/null 2>&1
177- tar zxf mysql-$version.tar.gz
178- MYSQL=$topdir/mysql-$version/bin/mysql
179- MYSQLADMIN=$topdir/mysql-$version/bin/mysqladmin
180- MYSQL_INSTALL_DB=$topdir/mysql-$version/scripts/mysql_install_db
181- MYSQLD=$topdir/mysql-$version/bin/mysqld
182- MYSQL_BASEDIR=$topdir/mysql-$version
183- MYSQLD_ARGS="--no-defaults --basedir=${MYSQL_BASEDIR} --socket=${mysql_socket} --datadir=$mysql_datadir"
184- MYSQL_ARGS="--no-defaults --socket=${mysql_socket} --user=root"
185- IB_BIN="innobackupex --defaults-file=$topdir/my.cnf"
186- XB_BIN=xtrabackup
187- cd -
188- ;;
189- 5.5)
190- echo "Using MySQL 5.5"
191- version="5.5.6-rc-linux2.6-`uname -m`"
192- cd $topdir
193- wget "$url/mysql-$version.tar.gz" >/dev/null 2>&1
194- tar zxf mysql-$version.tar.gz
195- MYSQL=$topdir/mysql-$version/bin/mysql
196- MYSQLADMIN=$topdir/mysql-$version/bin/mysqladmin
197- MYSQL_INSTALL_DB=$topdir/mysql-$version/scripts/mysql_install_db
198- MYSQLD=$topdir/mysql-$version/bin/mysqld
199- MYSQL_BASEDIR=$topdir/mysql-$version
200- MYSQLD_ARGS="--no-defaults --basedir=${MYSQL_BASEDIR} --socket=${mysql_socket} --datadir=$mysql_datadir"
201- MYSQL_ARGS="--no-defaults --socket=${mysql_socket} --user=root"
202- IB_BIN="innobackupex --defaults-file=$topdir/my.cnf"
203- XB_BIN=xtrabackup_55
204- cd -
205- ;;
206- percona)
207- echo "Using Percona Server"
208- version="5.1.51-rel11.5-132-Linux-`uname -m`"
209- cd $topdir
210- wget "$url/Percona-Server-$version.tar.gz" >/dev/null 2>&1
211- tar zxf Percona-Server-$version.tar.gz
212- MYSQL=$topdir/Percona-Server-$version/bin/mysql
213- MYSQLADMIN=$topdir/Percona-Server-$version/bin/mysqladmin
214- MYSQL_INSTALL_DB=$topdir/Percona-Server-$version/bin/mysql_install_db
215- MYSQLD=$topdir/Percona-Server-$version/libexec/mysqld
216- MYSQL_BASEDIR=$topdir/Percona-Server-$version
217- MYSQLD_ARGS="--no-defaults --basedir=${MYSQL_BASEDIR} --socket=${mysql_socket} --datadir=$mysql_datadir"
218- MYSQL_ARGS="--no-defaults --socket=${mysql_socket} --user=root"
219- set +u
220- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$topdir/Percona-Server-$version/lib/mysql
221- set -u
222- IB_BIN="innobackupex --defaults-file=$topdir/my.cnf"
223- XB_BIN=xtrabackup
224-
225- cd -
226- ;;
227- *)
228- usage
229- exit -1
230- ;;
231- esac
232- if [ "`whoami`" = "root" ]
233- then
234- MYSQLD_ARGS="$MYSQLD_ARGS --user=root"
235- fi
236-}
237-
238 function init_mysql_dir()
239 {
240 vlog "Creating MySQL database"
241@@ -189,13 +94,45 @@
242 result="0"
243 fi
244 echo $result
245- }
246-
247+}
248+
249+function get_xtrabackup_version()
250+{
251+ if [ "${MYSQL_VERSION:0:3}" = "5.1" ]
252+ then
253+ if [ -z "$INNODB_VERSION" ]
254+ then
255+ XB_BIN="xtrabackup_51" # InnoDB 5.1 builtin
256+ else
257+ XB_BIN="xtrabackup" # InnoDB 5.1 plugin or Percona Server 5.1
258+ fi
259+ elif [ "${MYSQL_VERSION:0:3}" = "5.5" ]
260+ then
261+ XB_BIN="xtrabackup_55"
262+ else
263+ vlog "Uknown MySQL/InnoDB version: $MYSQL_VERSION/$INNODB_VERSION"
264+ exit -1
265+ fi
266+ XTRADB_VERSION=`echo $INNODB_VERSION | sed 's/[0-9]\.[0-9]\.[0-9][0-9]*\(-[0-9][0-9]*\.[0-9][0-9]*\)*$/\1/'`
267+ vlog "Using '$XB_BIN' as xtrabackup binary"
268+ # Set the correct binary for innobackupex
269+ IB_ARGS="$IB_ARGS --ibbackup=$XB_BIN"
270+}
271+
272+function kill_leftovers()
273+{
274+ while test -f "$PWD/mysqld.pid"
275+ do
276+ vlog "Found a leftover mysqld processes with PID `cat $PWD/mysqld.pid`, stopping it"
277+ stop_mysqld 2> /dev/null
278+ done
279+}
280
281 function run_mysqld()
282 {
283 local c=0
284- ${MYSQLD} ${MYSQLD_ARGS} $* --port=$mysql_port &
285+ kill_leftovers
286+ ${MYSQLD} ${MYSQLD_ARGS} $* --pid-file="$PWD/mysqld.pid" &
287 while [ "`mysql_ping`" != "1" ]
288 do
289 if [ ${c} -eq 100 ]
290@@ -206,8 +143,13 @@
291 let c=${c}+1
292 sleep 1
293 done
294+ # Get MySQL and InnoDB versions
295+ MYSQL_VERSION=`$MYSQL ${MYSQL_ARGS} -Nsf -e "SHOW VARIABLES LIKE 'version'"`
296+ MYSQL_VERSION=${MYSQL_VERSION#"version "}
297+ INNODB_VERSION=`$MYSQL ${MYSQL_ARGS} -Nsf -e "SHOW VARIABLES LIKE 'innodb_version'"`
298+ INNODB_VERSION=${INNODB_VERSION#"innodb_version "}
299+ get_xtrabackup_version
300 vlog "MySQL started successfully"
301-
302 }
303
304 function run_cmd()
305@@ -218,8 +160,17 @@
306
307 function stop_mysqld()
308 {
309- ${MYSQLADMIN} ${MYSQL_ARGS} shutdown
310- vlog "Database server has been stopped"
311+ if [ -f "$PWD/mysqld.pid" ]
312+ then
313+ ${MYSQLADMIN} ${MYSQL_ARGS} shutdown
314+ vlog "Database server has been stopped"
315+ if [ -f "$PWD/mysqld.pid" ]
316+ then
317+ sleep 1;
318+ kill -9 `cat $PWD/mysqld.pid`
319+ rm -f $PWD/mysqld.pid
320+ fi
321+ fi
322 }
323
324 function load_sakila()
325@@ -254,9 +205,7 @@
326 function init()
327 {
328 initdir
329-init_mysql
330 init_mysql_dir
331-set_mysl_port
332 echo "
333 [mysqld]
334 datadir=$mysql_datadir" > $topdir/my.cnf
335
336=== added directory 'test/python'
337=== added file 'test/python/BytesIO.py'
338--- test/python/BytesIO.py 1970-01-01 00:00:00 +0000
339+++ test/python/BytesIO.py 2011-06-10 09:54:56 +0000
340@@ -0,0 +1,136 @@
341+
342+# http://wiki.python.org/moin/BytesIO
343+#
344+# A skeleton one used for systems that don't have BytesIO.
345+#
346+# It's enough for subunit at least....
347+
348+class BytesIO(object):
349+ """ A file-like API for reading and writing bytes objects.
350+
351+ Mostly like StringIO, but write() calls modify the underlying
352+ bytes object.
353+
354+ >>> b = bytes()
355+ >>> f = BytesIO(b, 'w')
356+ >>> f.write(bytes.fromhex('ca fe ba be'))
357+ >>> f.write(bytes.fromhex('57 41 56 45'))
358+ >>> b
359+ bytes([202, 254, 186, 190, 87, 65, 86, 69])
360+ """
361+
362+ def __init__(self, buf, mode='r'):
363+ """ Create a new BytesIO for reading or writing the given buffer.
364+
365+ buf - Back-end buffer for this BytesIO. A bytes object.
366+ Actually, anything that supports len(), slice-assignment,
367+ and += will work.
368+ mode - One of 'r', 'w', 'a'.
369+ An optional 'b' is also allowed, but it doesn't do anything.
370+ """
371+ # XXX many 'mode' possibilities aren't allowed yet: 'rw+Ut'
372+ if len(mode) == 2 and mode[-1] == 'b':
373+ mode = mode[:-1] # binary mode goes without saying
374+ if mode not in ('r', 'w', 'a'):
375+ raise ValueError("mode must be 'r', 'w', or 'a'")
376+
377+ self._buf = buf
378+ self.mode = mode
379+ self.closed = False
380+ if self.mode == 'w':
381+ del buf[:]
382+ self._point = 0
383+ elif self.mode == 'r':
384+ self._point = 0
385+ else: # 'a'
386+ self._point = len(buf)
387+
388+ def close(self):
389+ self.closed = True
390+
391+ def _check_closed(self):
392+ if self.closed:
393+ raise ValueError("file is closed")
394+
395+ def flush(self):
396+ self._check_closed()
397+
398+ def next(self):
399+ line = self.readline()
400+ if len(line) == 0:
401+ raise StopIteration
402+ return line
403+
404+ def read(self, size=None):
405+ self._check_closed()
406+ if size is None:
407+ e = len(self._buf)
408+ else:
409+ e = min(self._point + size, len(self._buf))
410+ r = self._buf[self._point:e]
411+ self._point = e
412+ return r
413+
414+ def readline(self, size=None):
415+ self._check_closed()
416+ die # XXX TODO - assume ascii and read a line
417+
418+ def readlines(self, sizehint=None):
419+ # XXX TODO handle sizehint
420+ return list(self)
421+
422+ def seek(self, offset, whence=0):
423+ self._check_closed()
424+
425+ if whence == 0:
426+ self._point = offset
427+ elif whence == 1:
428+ self._point += offset
429+ elif whence == 2:
430+ self._point = len(self._buf) + offset
431+ else:
432+ raise ValueError("whence must be 0, 1, or 2")
433+
434+ if self._point < 0:
435+ self._point = 0 # XXX is this right?
436+
437+ def tell(self):
438+ self._check_closed()
439+ return self._point
440+
441+ def truncate(self, size=None):
442+ self._check_closed()
443+ if size is None:
444+ size = self.tell()
445+ del self._buf[size:]
446+
447+ def write(self, data):
448+ self._check_closed()
449+ amt = len(data)
450+ size = len(self._buf)
451+ if self.mode == 'a':
452+ self._point = size
453+
454+ if self._point > size:
455+ if isinstance(b, bytes):
456+ blank = bytes([0])
457+ else:
458+ # Don't know what default value to insert, unfortunately
459+ raise ValueError("can't write past the end of this object")
460+ self._buf += blank * (self._point - size) + data
461+ self._point = len(self._buf)
462+ else:
463+ p = self._point
464+ self._buf[p:p + amt] = data
465+ self._point = min(p + amt, len(self._buf))
466+
467+ def writelines(self, seq):
468+ for line in seq:
469+ self.write(line)
470+
471+ def __iter__(self):
472+ return self
473+
474+ @property
475+ def name(self):
476+ return repr(self)
477
478=== added directory 'test/python/iso8601'
479=== added file 'test/python/iso8601/LICENSE'
480--- test/python/iso8601/LICENSE 1970-01-01 00:00:00 +0000
481+++ test/python/iso8601/LICENSE 2011-06-10 09:54:56 +0000
482@@ -0,0 +1,20 @@
483+Copyright (c) 2007 Michael Twomey
484+
485+Permission is hereby granted, free of charge, to any person obtaining a
486+copy of this software and associated documentation files (the
487+"Software"), to deal in the Software without restriction, including
488+without limitation the rights to use, copy, modify, merge, publish,
489+distribute, sublicense, and/or sell copies of the Software, and to
490+permit persons to whom the Software is furnished to do so, subject to
491+the following conditions:
492+
493+The above copyright notice and this permission notice shall be included
494+in all copies or substantial portions of the Software.
495+
496+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
497+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
498+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
499+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
500+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
501+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
502+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
503
504=== added file 'test/python/iso8601/README'
505--- test/python/iso8601/README 1970-01-01 00:00:00 +0000
506+++ test/python/iso8601/README 2011-06-10 09:54:56 +0000
507@@ -0,0 +1,26 @@
508+A simple package to deal with ISO 8601 date time formats.
509+
510+ISO 8601 defines a neutral, unambiguous date string format, which also
511+has the property of sorting naturally.
512+
513+e.g. YYYY-MM-DDTHH:MM:SSZ or 2007-01-25T12:00:00Z
514+
515+Currently this covers only the most common date formats encountered, not
516+all of ISO 8601 is handled.
517+
518+Currently the following formats are handled:
519+
520+* 2006-01-01T00:00:00Z
521+* 2006-01-01T00:00:00[+-]00:00
522+
523+I'll add more as I encounter them in my day to day life. Patches with
524+new formats and tests will be gratefully accepted of course :)
525+
526+References:
527+
528+* http://www.cl.cam.ac.uk/~mgk25/iso-time.html - simple overview
529+
530+* http://hydracen.com/dx/iso8601.htm - more detailed enumeration of
531+ valid formats.
532+
533+See the LICENSE file for the license this package is released under.
534
535=== added file 'test/python/iso8601/README.subunit'
536--- test/python/iso8601/README.subunit 1970-01-01 00:00:00 +0000
537+++ test/python/iso8601/README.subunit 2011-06-10 09:54:56 +0000
538@@ -0,0 +1,5 @@
539+This is a [slightly rearranged] import of http://pypi.python.org/pypi/iso8601/
540+version 0.1.4. The OS X hidden files have been stripped, and the package
541+turned into a single module, to simplify installation. The remainder of the
542+source distribution is included in the subunit source tree at python/iso8601
543+for reference.
544
545=== added file 'test/python/iso8601/setup.py'
546--- test/python/iso8601/setup.py 1970-01-01 00:00:00 +0000
547+++ test/python/iso8601/setup.py 2011-06-10 09:54:56 +0000
548@@ -0,0 +1,58 @@
549+try:
550+ from setuptools import setup
551+except ImportError:
552+ from distutils import setup
553+
554+long_description="""Simple module to parse ISO 8601 dates
555+
556+This module parses the most common forms of ISO 8601 date strings (e.g.
557+2007-01-14T20:34:22+00:00) into datetime objects.
558+
559+>>> import iso8601
560+>>> iso8601.parse_date("2007-01-25T12:00:00Z")
561+datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.iso8601.Utc ...>)
562+>>>
563+
564+Changes
565+=======
566+
567+0.1.4
568+-----
569+
570+* The default_timezone argument wasn't being passed through correctly,
571+ UTC was being used in every case. Fixes issue 10.
572+
573+0.1.3
574+-----
575+
576+* Fixed the microsecond handling, the generated microsecond values were
577+ way too small. Fixes issue 9.
578+
579+0.1.2
580+-----
581+
582+* Adding ParseError to __all__ in iso8601 module, allows people to import it.
583+ Addresses issue 7.
584+* Be a little more flexible when dealing with dates without leading zeroes.
585+ This violates the spec a little, but handles more dates as seen in the
586+ field. Addresses issue 6.
587+* Allow date/time separators other than T.
588+
589+0.1.1
590+-----
591+
592+* When parsing dates without a timezone the specified default is used. If no
593+ default is specified then UTC is used. Addresses issue 4.
594+"""
595+
596+setup(
597+ name="iso8601",
598+ version="0.1.4",
599+ description=long_description.split("\n")[0],
600+ long_description=long_description,
601+ author="Michael Twomey",
602+ author_email="micktwomey+iso8601@gmail.com",
603+ url="http://code.google.com/p/pyiso8601/",
604+ packages=["iso8601"],
605+ license="MIT",
606+)
607
608=== added file 'test/python/iso8601/test_iso8601.py'
609--- test/python/iso8601/test_iso8601.py 1970-01-01 00:00:00 +0000
610+++ test/python/iso8601/test_iso8601.py 2011-06-10 09:54:56 +0000
611@@ -0,0 +1,111 @@
612+import iso8601
613+
614+def test_iso8601_regex():
615+ assert iso8601.ISO8601_REGEX.match("2006-10-11T00:14:33Z")
616+
617+def test_timezone_regex():
618+ assert iso8601.TIMEZONE_REGEX.match("+01:00")
619+ assert iso8601.TIMEZONE_REGEX.match("+00:00")
620+ assert iso8601.TIMEZONE_REGEX.match("+01:20")
621+ assert iso8601.TIMEZONE_REGEX.match("-01:00")
622+
623+def test_parse_date():
624+ d = iso8601.parse_date("2006-10-20T15:34:56Z")
625+ assert d.year == 2006
626+ assert d.month == 10
627+ assert d.day == 20
628+ assert d.hour == 15
629+ assert d.minute == 34
630+ assert d.second == 56
631+ assert d.tzinfo == iso8601.UTC
632+
633+def test_parse_date_fraction():
634+ d = iso8601.parse_date("2006-10-20T15:34:56.123Z")
635+ assert d.year == 2006
636+ assert d.month == 10
637+ assert d.day == 20
638+ assert d.hour == 15
639+ assert d.minute == 34
640+ assert d.second == 56
641+ assert d.microsecond == 123000
642+ assert d.tzinfo == iso8601.UTC
643+
644+def test_parse_date_fraction_2():
645+ """From bug 6
646+
647+ """
648+ d = iso8601.parse_date("2007-5-7T11:43:55.328Z'")
649+ assert d.year == 2007
650+ assert d.month == 5
651+ assert d.day == 7
652+ assert d.hour == 11
653+ assert d.minute == 43
654+ assert d.second == 55
655+ assert d.microsecond == 328000
656+ assert d.tzinfo == iso8601.UTC
657+
658+def test_parse_date_tz():
659+ d = iso8601.parse_date("2006-10-20T15:34:56.123+02:30")
660+ assert d.year == 2006
661+ assert d.month == 10
662+ assert d.day == 20
663+ assert d.hour == 15
664+ assert d.minute == 34
665+ assert d.second == 56
666+ assert d.microsecond == 123000
667+ assert d.tzinfo.tzname(None) == "+02:30"
668+ offset = d.tzinfo.utcoffset(None)
669+ assert offset.days == 0
670+ assert offset.seconds == 60 * 60 * 2.5
671+
672+def test_parse_invalid_date():
673+ try:
674+ iso8601.parse_date(None)
675+ except iso8601.ParseError:
676+ pass
677+ else:
678+ assert 1 == 2
679+
680+def test_parse_invalid_date2():
681+ try:
682+ iso8601.parse_date("23")
683+ except iso8601.ParseError:
684+ pass
685+ else:
686+ assert 1 == 2
687+
688+def test_parse_no_timezone():
689+ """issue 4 - Handle datetime string without timezone
690+
691+ This tests what happens when you parse a date with no timezone. While not
692+ strictly correct this is quite common. I'll assume UTC for the time zone
693+ in this case.
694+ """
695+ d = iso8601.parse_date("2007-01-01T08:00:00")
696+ assert d.year == 2007
697+ assert d.month == 1
698+ assert d.day == 1
699+ assert d.hour == 8
700+ assert d.minute == 0
701+ assert d.second == 0
702+ assert d.microsecond == 0
703+ assert d.tzinfo == iso8601.UTC
704+
705+def test_parse_no_timezone_different_default():
706+ tz = iso8601.FixedOffset(2, 0, "test offset")
707+ d = iso8601.parse_date("2007-01-01T08:00:00", default_timezone=tz)
708+ assert d.tzinfo == tz
709+
710+def test_space_separator():
711+ """Handle a separator other than T
712+
713+ """
714+ d = iso8601.parse_date("2007-06-23 06:40:34.00Z")
715+ assert d.year == 2007
716+ assert d.month == 6
717+ assert d.day == 23
718+ assert d.hour == 6
719+ assert d.minute == 40
720+ assert d.second == 34
721+ assert d.microsecond == 0
722+ assert d.tzinfo == iso8601.UTC
723
724=== added directory 'test/python/junitxml'
725=== added file 'test/python/junitxml/__init__.py'
726--- test/python/junitxml/__init__.py 1970-01-01 00:00:00 +0000
727+++ test/python/junitxml/__init__.py 2011-06-10 09:54:56 +0000
728@@ -0,0 +1,221 @@
729+#
730+# junitxml: extensions to Python unittest to get output junitxml
731+# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
732+#
733+# Copying permitted under the LGPL-3 licence, included with this library.
734+
735+"""unittest compatible JUnit XML output."""
736+
737+
738+import datetime
739+import re
740+import time
741+import unittest
742+
743+# same format as sys.version_info: "A tuple containing the five components of
744+# the version number: major, minor, micro, releaselevel, and serial. All
745+# values except releaselevel are integers; the release level is 'alpha',
746+# 'beta', 'candidate', or 'final'. The version_info value corresponding to the
747+# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
748+# releaselevel of 'dev' for unreleased under-development code.
749+#
750+# If the releaselevel is 'alpha' then the major/minor/micro components are not
751+# established at this point, and setup.py will use a version of next-$(revno).
752+# If the releaselevel is 'final', then the tarball will be major.minor.micro.
753+# Otherwise it is major.minor.micro~$(revno).
754+__version__ = (0, 7, 0, 'alpha', 0)
755+
756+
757+def test_suite():
758+ import junitxml.tests
759+ return junitxml.tests.test_suite()
760+
761+
762+class LocalTimezone(datetime.tzinfo):
763+
764+ def __init__(self):
765+ self._offset = None
766+
767+ # It seems that the minimal possible implementation is to just return all
768+ # None for every function, but then it breaks...
769+ def utcoffset(self, dt):
770+ if self._offset is None:
771+ t = 1260423030 # arbitrary, but doesn't handle dst very well
772+ dt = datetime.datetime
773+ self._offset = (dt.fromtimestamp(t) - dt.utcfromtimestamp(t))
774+ return self._offset
775+
776+ def dst(self, dt):
777+ return datetime.timedelta(0)
778+
779+ def tzname(self, dt):
780+ return None
781+
782+
783+def _error_name(eclass):
784+ module = eclass.__module__
785+ if module not in ("__main__", "builtins", "exceptions"):
786+ return ".".join([module, eclass.__name__])
787+ return eclass.__name__
788+
789+
790+_non_cdata = "[\0-\b\x0B-\x1F\uD800-\uDFFF\uFFFE\uFFFF]+"
791+if "\\u" in _non_cdata:
792+ _non_cdata = _non_cdata.decode("unicode-escape")
793+ def _strip_invalid_chars(s, _sub=re.compile(_non_cdata, re.UNICODE).sub):
794+ if not isinstance(s, unicode):
795+ try:
796+ s = s.decode("utf-8")
797+ except UnicodeDecodeError:
798+ s = s.decode("ascii", "replace")
799+ return _sub("", s).encode("utf-8")
800+else:
801+ def _strip_invalid_chars(s, _sub=re.compile(_non_cdata, re.UNICODE).sub):
802+ return _sub("", s)
803+def _escape_content(s):
804+ return (_strip_invalid_chars(s)
805+ .replace("&", "&amp;")
806+ .replace("<", "&lt;")
807+ .replace("]]>", "]]&gt;"))
808+def _escape_attr(s):
809+ return (_strip_invalid_chars(s)
810+ .replace("&", "&amp;")
811+ .replace("<", "&lt;")
812+ .replace("]]>", "]]&gt;")
813+ .replace('"', "&quot;")
814+ .replace("\t", "&#x9;")
815+ .replace("\n", "&#xA;"))
816+
817+
818+class JUnitXmlResult(unittest.TestResult):
819+ """A TestResult which outputs JUnit compatible XML."""
820+
821+ def __init__(self, stream):
822+ """Create a JUnitXmlResult.
823+
824+ :param stream: A stream to write results to. Note that due to the
825+ nature of JUnit XML output, nnothing will be written to the stream
826+ until stopTestRun() is called.
827+ """
828+ self.__super = super(JUnitXmlResult, self)
829+ self.__super.__init__()
830+ # GZ 2010-09-03: We have a problem if passed a text stream in Python 3
831+ # as really we want to write raw UTF-8 to ensure that
832+ # the encoding is not mangled later
833+ self._stream = stream
834+ self._results = []
835+ self._set_time = None
836+ self._test_start = None
837+ self._run_start = None
838+ self._tz_info = None
839+
840+ def startTestRun(self):
841+ """Start a test run."""
842+ self._run_start = self._now()
843+
844+ def _get_tzinfo(self):
845+ if self._tz_info is None:
846+ self._tz_info = LocalTimezone()
847+ return self._tz_info
848+
849+ def _now(self):
850+ if self._set_time is not None:
851+ return self._set_time
852+ else:
853+ return datetime.datetime.now(self._get_tzinfo())
854+
855+ def time(self, a_datetime):
856+ self._set_time = a_datetime
857+ if (self._run_start is not None and
858+ self._run_start > a_datetime):
859+ self._run_start = a_datetime
860+
861+ def startTest(self, test):
862+ self.__super.startTest(test)
863+ self._test_start = self._now()
864+
865+ def _duration(self, from_datetime):
866+ try:
867+ delta = self._now() - from_datetime
868+ except TypeError:
869+ n = self._now()
870+ delta = datetime.timedelta(-1)
871+ seconds = delta.days * 3600*24 + delta.seconds
872+ return seconds + 0.000001 * delta.microseconds
873+
874+ def _test_case_string(self, test):
875+ duration = self._duration(self._test_start)
876+ test_id = test.id()
877+ # Split on the last dot not inside a parameter
878+ class_end = test_id.rfind(".", 0, test_id.find("("))
879+ if class_end == -1:
880+ classname, name = "", test_id
881+ else:
882+ classname, name = test_id[:class_end], test_id[class_end+1:]
883+ self._results.append('<testcase classname="%s" name="%s" '
884+ 'time="%0.3f"' % (_escape_attr(classname), _escape_attr(name), duration))
885+
886+ def stopTestRun(self):
887+ """Stop a test run.
888+
889+ This allows JUnitXmlResult to output the XML representation of the test
890+ run.
891+ """
892+ duration = self._duration(self._run_start)
893+ self._stream.write('<testsuite errors="%d" failures="%d" name="" '
894+ 'tests="%d" time="%0.3f">\n' % (len(self.errors),
895+ len(self.failures) + len(getattr(self, "unexpectedSuccesses", ())),
896+ self.testsRun, duration))
897+ self._stream.write(''.join(self._results))
898+ self._stream.write('</testsuite>\n')
899+
900+ def addError(self, test, error):
901+ self.__super.addError(test, error)
902+ self._test_case_string(test)
903+ self._results.append('>\n')
904+ self._results.append('<error type="%s">%s</error>\n</testcase>\n' % (
905+ _escape_attr(_error_name(error[0])),
906+ _escape_content(self._exc_info_to_string(error, test))))
907+
908+ def addFailure(self, test, error):
909+ self.__super.addFailure(test, error)
910+ self._test_case_string(test)
911+ self._results.append('>\n')
912+ self._results.append('<failure type="%s">%s</failure>\n</testcase>\n' %
913+ (_escape_attr(_error_name(error[0])),
914+ _escape_content(self._exc_info_to_string(error, test))))
915+
916+ def addSuccess(self, test):
917+ self.__super.addSuccess(test)
918+ self._test_case_string(test)
919+ self._results.append('/>\n')
920+
921+ def addSkip(self, test, reason):
922+ try:
923+ self.__super.addSkip(test, reason)
924+ except AttributeError:
925+ # Python < 2.7|3.1
926+ pass
927+ self._test_case_string(test)
928+ self._results.append('>\n')
929+ self._results.append('<skip>%s</skip>\n</testcase>\n'% _escape_attr(reason))
930+
931+ def addUnexpectedSuccess(self, test):
932+ try:
933+ self.__super.addUnexpectedSuccess(test)
934+ except AttributeError:
935+ # Python < 2.7|3.1
936+ pass
937+ self._test_case_string(test)
938+ self._results.append('>\n')
939+ self._results.append('<failure type="unittest.case._UnexpectedSuccess"/>\n</testcase>\n')
940+
941+ def addExpectedFailure(self, test, error):
942+ try:
943+ self.__super.addExpectedFailure(test, error)
944+ except AttributeError:
945+ # Python < 2.7|3.1
946+ pass
947+ self._test_case_string(test)
948+ self._results.append('/>\n')
949+
950
951=== added directory 'test/python/junitxml/tests'
952=== added file 'test/python/junitxml/tests/__init__.py'
953--- test/python/junitxml/tests/__init__.py 1970-01-01 00:00:00 +0000
954+++ test/python/junitxml/tests/__init__.py 2011-06-10 09:54:56 +0000
955@@ -0,0 +1,16 @@
956+#
957+# junitxml: extensions to Python unittest to get output junitxml
958+# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
959+#
960+# Copying permitted under the LGPL-3 licence, included with this library.
961+
962+import unittest
963+
964+from junitxml.tests import (
965+ test_junitxml,
966+ )
967+
968+def test_suite():
969+ return unittest.TestLoader().loadTestsFromNames([
970+ 'junitxml.tests.test_junitxml',
971+ ])
972
973=== added file 'test/python/junitxml/tests/test_junitxml.py'
974--- test/python/junitxml/tests/test_junitxml.py 1970-01-01 00:00:00 +0000
975+++ test/python/junitxml/tests/test_junitxml.py 2011-06-10 09:54:56 +0000
976@@ -0,0 +1,327 @@
977+#
978+# junitxml: extensions to Python unittest to get output junitxml
979+# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
980+#
981+# Copying permitted under the LGPL-3 licence, included with this library.
982+
983+
984+try:
985+ from cStringIO import StringIO
986+except ImportError:
987+ from io import StringIO
988+import datetime
989+import re
990+import sys
991+import unittest
992+import xml.dom.minidom
993+
994+import junitxml
995+
996+class TestImports(unittest.TestCase):
997+
998+ def test_result(self):
999+ from junitxml import JUnitXmlResult
1000+
1001+
1002+class TestJUnitXmlResult__init__(unittest.TestCase):
1003+
1004+ def test_with_stream(self):
1005+ result = junitxml.JUnitXmlResult(StringIO())
1006+
1007+
1008+class TestJUnitXmlResult(unittest.TestCase):
1009+
1010+ def setUp(self):
1011+ self.output = StringIO()
1012+ self.result = junitxml.JUnitXmlResult(self.output)
1013+
1014+ def get_output(self):
1015+ output = self.output.getvalue()
1016+ # Collapse detailed regions into specific strings we can match on
1017+ return re.sub(r'(?s)<failure (.*?)>.*?</failure>',
1018+ r'<failure \1>failure</failure>', re.sub(
1019+ r'(?s)<error (.*?)>.*?</error>', r'<error \1>error</error>',
1020+ re.sub(r'time="\d+\.\d+"', 'time="0.000"', output)))
1021+
1022+ def run_test_or_simulate(self, test, method_name, manual_method,
1023+ *manual_args):
1024+ if getattr(test, method_name, None):
1025+ test.run(self.result)
1026+ else:
1027+ # older python - manually execute
1028+ self.result.startTest(test)
1029+ manual_method(test, *manual_args)
1030+ self.result.stopTest(test)
1031+
1032+ def test_run_duration_handles_datestamping_in_the_past(self):
1033+ # When used via subunit2junitxml, startTestRun is called before
1034+ # any tz info in the test stream has been seen.
1035+ # So, we use the earliest reported timestamp as the start time,
1036+ # replacing _test_start if needed.
1037+ self.result.startTestRun() # the time is now.
1038+ # Lose an hour (peeks inside, a little naughty but not very).
1039+ self.result.time(self.result._run_start - datetime.timedelta(0, 3600))
1040+ self.result.stopTestRun()
1041+ self.assertEqual("""<testsuite errors="0" failures="0" name="" tests="0" time="0.000">
1042+</testsuite>
1043+""", self.get_output())
1044+
1045+ def test_startTestRun_no_output(self):
1046+ # startTestRun doesn't output anything, because JUnit wants an up-front
1047+ # summary.
1048+ self.result.startTestRun()
1049+ self.assertEqual('', self.get_output())
1050+
1051+ def test_stopTestRun_outputs(self):
1052+ # When stopTestRun is called, everything is output.
1053+ self.result.startTestRun()
1054+ self.result.stopTestRun()
1055+ self.assertEqual("""<testsuite errors="0" failures="0" name="" tests="0" time="0.000">
1056+</testsuite>
1057+""", self.get_output())
1058+
1059+ def test_test_count(self):
1060+ class Passes(unittest.TestCase):
1061+ def test_me(self):
1062+ pass
1063+ self.result.startTestRun()
1064+ Passes("test_me").run(self.result)
1065+ Passes("test_me").run(self.result)
1066+ self.result.stopTestRun()
1067+ # When tests are run, the number of tests is counted.
1068+ output = self.get_output()
1069+ self.assertTrue('tests="2"' in output)
1070+
1071+ def test_test_id_with_parameter(self):
1072+ class Passes(unittest.TestCase):
1073+ def id(self):
1074+ return unittest.TestCase.id(self) + '(version_1.6)'
1075+ def test_me(self):
1076+ pass
1077+ self.result.startTestRun()
1078+ Passes("test_me").run(self.result)
1079+ self.result.stopTestRun()
1080+ output = self.get_output()
1081+ self.assertTrue('Passes" name="test_me(version_1.6)"' in output)
1082+
1083+ def test_erroring_test(self):
1084+ class Errors(unittest.TestCase):
1085+ def test_me(self):
1086+ 1/0
1087+ self.result.startTestRun()
1088+ Errors("test_me").run(self.result)
1089+ self.result.stopTestRun()
1090+ self.assertEqual("""<testsuite errors="1" failures="0" name="" tests="1" time="0.000">
1091+<testcase classname="junitxml.tests.test_junitxml.Errors" name="test_me" time="0.000">
1092+<error type="ZeroDivisionError">error</error>
1093+</testcase>
1094+</testsuite>
1095+""", self.get_output())
1096+
1097+ def test_failing_test(self):
1098+ class Fails(unittest.TestCase):
1099+ def test_me(self):
1100+ self.fail()
1101+ self.result.startTestRun()
1102+ Fails("test_me").run(self.result)
1103+ self.result.stopTestRun()
1104+ self.assertEqual("""<testsuite errors="0" failures="1" name="" tests="1" time="0.000">
1105+<testcase classname="junitxml.tests.test_junitxml.Fails" name="test_me" time="0.000">
1106+<failure type="AssertionError">failure</failure>
1107+</testcase>
1108+</testsuite>
1109+""", self.get_output())
1110+
1111+ def test_successful_test(self):
1112+ class Passes(unittest.TestCase):
1113+ def test_me(self):
1114+ pass
1115+ self.result.startTestRun()
1116+ Passes("test_me").run(self.result)
1117+ self.result.stopTestRun()
1118+ self.assertEqual("""<testsuite errors="0" failures="0" name="" tests="1" time="0.000">
1119+<testcase classname="junitxml.tests.test_junitxml.Passes" name="test_me" time="0.000"/>
1120+</testsuite>
1121+""", self.get_output())
1122+
1123+ def test_skip_test(self):
1124+ class Skips(unittest.TestCase):
1125+ def test_me(self):
1126+ self.skipTest("yo")
1127+ self.result.startTestRun()
1128+ test = Skips("test_me")
1129+ self.run_test_or_simulate(test, 'skipTest', self.result.addSkip, 'yo')
1130+ self.result.stopTestRun()
1131+ output = self.get_output()
1132+ expected = """<testsuite errors="0" failures="0" name="" tests="1" time="0.000">
1133+<testcase classname="junitxml.tests.test_junitxml.Skips" name="test_me" time="0.000">
1134+<skip>yo</skip>
1135+</testcase>
1136+</testsuite>
1137+"""
1138+ self.assertEqual(expected, output)
1139+
1140+ def test_unexpected_success_test(self):
1141+ class Succeeds(unittest.TestCase):
1142+ def test_me(self):
1143+ pass
1144+ try:
1145+ test_me = unittest.expectedFailure(test_me)
1146+ except AttributeError:
1147+ pass # Older python - just let the test pass
1148+ self.result.startTestRun()
1149+ Succeeds("test_me").run(self.result)
1150+ self.result.stopTestRun()
1151+ output = self.get_output()
1152+ expected = """<testsuite errors="0" failures="1" name="" tests="1" time="0.000">
1153+<testcase classname="junitxml.tests.test_junitxml.Succeeds" name="test_me" time="0.000">
1154+<failure type="unittest.case._UnexpectedSuccess"/>
1155+</testcase>
1156+</testsuite>
1157+"""
1158+ expected_old = """<testsuite errors="0" failures="0" name="" tests="1" time="0.000">
1159+<testcase classname="junitxml.tests.test_junitxml.Succeeds" name="test_me" time="0.000"/>
1160+</testsuite>
1161+"""
1162+ if output != expected_old:
1163+ self.assertEqual(expected, output)
1164+
1165+ def test_expected_failure_test(self):
1166+ expected_failure_support = [True]
1167+ class ExpectedFail(unittest.TestCase):
1168+ def test_me(self):
1169+ self.fail("fail")
1170+ try:
1171+ test_me = unittest.expectedFailure(test_me)
1172+ except AttributeError:
1173+ # Older python - just let the test fail
1174+ expected_failure_support[0] = False
1175+ self.result.startTestRun()
1176+ ExpectedFail("test_me").run(self.result)
1177+ self.result.stopTestRun()
1178+ output = self.get_output()
1179+ expected = """<testsuite errors="0" failures="0" name="" tests="1" time="0.000">
1180+<testcase classname="junitxml.tests.test_junitxml.ExpectedFail" name="test_me" time="0.000"/>
1181+</testsuite>
1182+"""
1183+ expected_old = """<testsuite errors="0" failures="1" name="" tests="1" time="0.000">
1184+<testcase classname="junitxml.tests.test_junitxml.ExpectedFail" name="test_me" time="0.000">
1185+<failure type="AssertionError">failure</failure>
1186+</testcase>
1187+</testsuite>
1188+"""
1189+ if expected_failure_support[0]:
1190+ self.assertEqual(expected, output)
1191+ else:
1192+ self.assertEqual(expected_old, output)
1193+
1194+
1195+class TestWellFormedXml(unittest.TestCase):
1196+ """XML created should always be well formed even with odd test cases"""
1197+
1198+ def _run_and_parse_test(self, case):
1199+ output = StringIO()
1200+ result = junitxml.JUnitXmlResult(output)
1201+ result.startTestRun()
1202+ case.run(result)
1203+ result.stopTestRun()
1204+ return xml.dom.minidom.parseString(output.getvalue())
1205+
1206+ def test_failure_with_amp(self):
1207+ """Check the failure element content is escaped"""
1208+ class FailWithAmp(unittest.TestCase):
1209+ def runTest(self):
1210+ self.fail("& should be escaped as &amp;")
1211+ doc = self._run_and_parse_test(FailWithAmp())
1212+ self.assertTrue(
1213+ doc.getElementsByTagName("failure")[0].firstChild.nodeValue
1214+ .endswith("AssertionError: & should be escaped as &amp;\n"))
1215+
1216+ def test_quotes_in_test_case_id(self):
1217+ """Check that quotes in an attribute are escaped"""
1218+ class QuoteId(unittest.TestCase):
1219+ def id(self):
1220+ return unittest.TestCase.id(self) + '("quotes")'
1221+ def runTest(self):
1222+ pass
1223+ doc = self._run_and_parse_test(QuoteId())
1224+ self.assertEqual('runTest("quotes")',
1225+ doc.getElementsByTagName("testcase")[0].getAttribute("name"))
1226+
1227+ def test_skip_reason(self):
1228+ """Check the skip element content is escaped"""
1229+ class SkipWithLt(unittest.TestCase):
1230+ def runTest(self):
1231+ self.fail("version < 2.7")
1232+ try:
1233+ runTest = unittest.skip("2.7 <= version")(runTest)
1234+ except AttributeError:
1235+ self.has_skip = False
1236+ else:
1237+ self.has_skip = True
1238+ doc = self._run_and_parse_test(SkipWithLt())
1239+ if self.has_skip:
1240+ self.assertEqual('2.7 <= version',
1241+ doc.getElementsByTagName("skip")[0].firstChild.nodeValue)
1242+ else:
1243+ self.assertTrue(
1244+ doc.getElementsByTagName("failure")[0].firstChild.nodeValue
1245+ .endswith("AssertionError: version < 2.7\n"))
1246+
1247+ def test_error_with_control_characters(self):
1248+ """Check C0 control characters are stripped rather than output"""
1249+ class ErrorWithC0(unittest.TestCase):
1250+ def runTest(self):
1251+ raise ValueError("\x1F\x0E\x0C\x0B\x08\x01\x00lost control")
1252+ doc = self._run_and_parse_test(ErrorWithC0())
1253+ self.assertTrue(
1254+ doc.getElementsByTagName("error")[0].firstChild.nodeValue
1255+ .endswith("ValueError: lost control\n"))
1256+
1257+ def test_error_with_invalid_cdata(self):
1258+ """Check unicode outside the valid cdata range is stripped"""
1259+ if len("\uffff") == 1:
1260+ # Basic str type supports unicode
1261+ exception = ValueError("\ufffe\uffffEOF")
1262+ else:
1263+ class UTF8_Error(Exception):
1264+ def __unicode__(self):
1265+ return str(self).decode("UTF-8")
1266+ exception = UTF8_Error("\xef\xbf\xbe\xef\xbf\xbfEOF")
1267+ class ErrorWithBadUnicode(unittest.TestCase):
1268+ def runTest(self):
1269+ raise exception
1270+ doc = self._run_and_parse_test(ErrorWithBadUnicode())
1271+ self.assertTrue(
1272+ doc.getElementsByTagName("error")[0].firstChild.nodeValue
1273+ .endswith("Error: EOF\n"))
1274+
1275+ def test_error_with_surrogates(self):
1276+ """Check unicode surrogates are handled properly, paired or otherwise
1277+
1278+ This is a pain due to suboptimal unicode support in Python and the
1279+ various changes in Python 3. On UCS-2 builds there is no easy way of
1280+ getting rid of unpaired surrogates while leaving valid pairs alone, so
1281+ this test doesn't require astral characters are kept there.
1282+ """
1283+ if len("\uffff") == 1:
1284+ exception = ValueError("paired: \U000201a2"
1285+ " unpaired: "+chr(0xD800)+"-"+chr(0xDFFF))
1286+ astral_char = "\U000201a2"
1287+ else:
1288+ class UTF8_Error(Exception):
1289+ def __unicode__(self):
1290+ return str(self).decode("UTF-8")
1291+ exception = UTF8_Error("paired: \xf0\xa0\x86\xa2"
1292+ " unpaired: \xed\xa0\x80-\xed\xbf\xbf")
1293+ astral_char = "\U000201a2".decode("unicode-escape")
1294+ class ErrorWithSurrogates(unittest.TestCase):
1295+ def runTest(self):
1296+ raise exception
1297+ doc = self._run_and_parse_test(ErrorWithSurrogates())
1298+ traceback = doc.getElementsByTagName("error")[0].firstChild.nodeValue
1299+ if sys.maxunicode == 0xFFFF:
1300+ pass # would be nice to handle astral characters properly even so
1301+ else:
1302+ self.assertTrue(astral_char in traceback)
1303+ self.assertTrue(traceback.endswith(" unpaired: -\n"))
1304
1305=== added directory 'test/python/subunit'
1306=== added file 'test/python/subunit/__init__.py'
1307--- test/python/subunit/__init__.py 1970-01-01 00:00:00 +0000
1308+++ test/python/subunit/__init__.py 2011-06-10 09:54:56 +0000
1309@@ -0,0 +1,1250 @@
1310+#
1311+# subunit: extensions to Python unittest to get test results from subprocesses.
1312+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
1313+#
1314+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
1315+# license at the users choice. A copy of both licenses are available in the
1316+# project source as Apache-2.0 and BSD. You may not use this file except in
1317+# compliance with one of these two licences.
1318+#
1319+# Unless required by applicable law or agreed to in writing, software
1320+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
1321+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1322+# license you chose for the specific language governing permissions and
1323+# limitations under that license.
1324+#
1325+
1326+"""Subunit - a streaming test protocol
1327+
1328+Overview
1329+++++++++
1330+
1331+The ``subunit`` Python package provides a number of ``unittest`` extensions
1332+which can be used to cause tests to output Subunit, to parse Subunit streams
1333+into test activity, perform seamless test isolation within a regular test
1334+case and variously sort, filter and report on test runs.
1335+
1336+
1337+Key Classes
1338+-----------
1339+
1340+The ``subunit.TestProtocolClient`` class is a ``unittest.TestResult``
1341+extension which will translate a test run into a Subunit stream.
1342+
1343+The ``subunit.ProtocolTestCase`` class is an adapter between the Subunit wire
1344+protocol and the ``unittest.TestCase`` object protocol. It is used to translate
1345+a stream into a test run, which regular ``unittest.TestResult`` objects can
1346+process and report/inspect.
1347+
1348+Subunit has support for non-blocking usage too, for use with asyncore or
1349+Twisted. See the ``TestProtocolServer`` parser class for more details.
1350+
1351+Subunit includes extensions to the Python ``TestResult`` protocol. These are
1352+all done in a compatible manner: ``TestResult`` objects that do not implement
1353+the extension methods will not cause errors to be raised, instead the extension
1354+will either lose fidelity (for instance, folding expected failures to success
1355+in Python versions < 2.7 or 3.1), or discard the extended data (for extra
1356+details, tags, timestamping and progress markers).
1357+
1358+The test outcome methods ``addSuccess``, ``addError``, ``addExpectedFailure``,
1359+``addFailure``, ``addSkip`` take an optional keyword parameter ``details``
1360+which can be used instead of the usual python unittest parameter.
1361+When used the value of details should be a dict from ``string`` to
1362+``testtools.content.Content`` objects. This is a draft API being worked on with
1363+the Python Testing In Python mail list, with the goal of permitting a common
1364+way to provide additional data beyond a traceback, such as captured data from
1365+disk, logging messages etc. The reference for this API is in testtools (0.9.0
1366+and newer).
1367+
1368+The ``tags(new_tags, gone_tags)`` method is called (if present) to add or
1369+remove tags in the test run that is currently executing. If called when no
1370+test is in progress (that is, if called outside of the ``startTest``,
1371+``stopTest`` pair), the the tags apply to all sebsequent tests. If called
1372+when a test is in progress, then the tags only apply to that test.
1373+
1374+The ``time(a_datetime)`` method is called (if present) when a ``time:``
1375+directive is encountered in a Subunit stream. This is used to tell a TestResult
1376+about the time that events in the stream occured at, to allow reconstructing
1377+test timing from a stream.
1378+
1379+The ``progress(offset, whence)`` method controls progress data for a stream.
1380+The offset parameter is an int, and whence is one of subunit.PROGRESS_CUR,
1381+subunit.PROGRESS_SET, PROGRESS_PUSH, PROGRESS_POP. Push and pop operations
1382+ignore the offset parameter.
1383+
1384+
1385+Python test support
1386+-------------------
1387+
1388+``subunit.run`` is a convenience wrapper to run a Python test suite via
1389+the command line, reporting via Subunit::
1390+
1391+ $ python -m subunit.run mylib.tests.test_suite
1392+
1393+The ``IsolatedTestSuite`` class is a TestSuite that forks before running its
1394+tests, allowing isolation between the test runner and some tests.
1395+
1396+Similarly, ``IsolatedTestCase`` is a base class which can be subclassed to get
1397+tests that will fork() before that individual test is run.
1398+
1399+`ExecTestCase`` is a convenience wrapper for running an external
1400+program to get a Subunit stream and then report that back to an arbitrary
1401+result object::
1402+
1403+ class AggregateTests(subunit.ExecTestCase):
1404+
1405+ def test_script_one(self):
1406+ './bin/script_one'
1407+
1408+ def test_script_two(self):
1409+ './bin/script_two'
1410+
1411+ # Normally your normal test loading would take of this automatically,
1412+ # It is only spelt out in detail here for clarity.
1413+ suite = unittest.TestSuite([AggregateTests("test_script_one"),
1414+ AggregateTests("test_script_two")])
1415+ # Create any TestResult class you like.
1416+ result = unittest._TextTestResult(sys.stdout)
1417+ # And run your suite as normal, Subunit will exec each external script as
1418+ # needed and report to your result object.
1419+ suite.run(result)
1420+
1421+Utility modules
1422+---------------
1423+
1424+* subunit.chunked contains HTTP chunked encoding/decoding logic.
1425+* subunit.test_results contains TestResult helper classes.
1426+"""
1427+
1428+import os
1429+import re
1430+import subprocess
1431+import sys
1432+import unittest
1433+
1434+from testtools import content, content_type, ExtendedToOriginalDecorator
1435+from testtools.compat import _b, _u, BytesIO, StringIO
1436+try:
1437+ from testtools.testresult.real import _StringException
1438+ RemoteException = _StringException
1439+ # For testing: different pythons have different str() implementations.
1440+ if sys.version_info > (3, 0):
1441+ _remote_exception_str = "testtools.testresult.real._StringException"
1442+ _remote_exception_str_chunked = "34\r\n" + _remote_exception_str
1443+ else:
1444+ _remote_exception_str = "_StringException"
1445+ _remote_exception_str_chunked = "1A\r\n" + _remote_exception_str
1446+except ImportError:
1447+ raise ImportError ("testtools.testresult.real does not contain "
1448+ "_StringException, check your version.")
1449+from testtools import testresult
1450+
1451+from subunit import chunked, details, iso8601, test_results
1452+
1453+
1454+PROGRESS_SET = 0
1455+PROGRESS_CUR = 1
1456+PROGRESS_PUSH = 2
1457+PROGRESS_POP = 3
1458+
1459+
1460+def test_suite():
1461+ import subunit.tests
1462+ return subunit.tests.test_suite()
1463+
1464+
1465+def join_dir(base_path, path):
1466+ """
1467+ Returns an absolute path to C{path}, calculated relative to the parent
1468+ of C{base_path}.
1469+
1470+ @param base_path: A path to a file or directory.
1471+ @param path: An absolute path, or a path relative to the containing
1472+ directory of C{base_path}.
1473+
1474+ @return: An absolute path to C{path}.
1475+ """
1476+ return os.path.join(os.path.dirname(os.path.abspath(base_path)), path)
1477+
1478+
1479+def tags_to_new_gone(tags):
1480+ """Split a list of tags into a new_set and a gone_set."""
1481+ new_tags = set()
1482+ gone_tags = set()
1483+ for tag in tags:
1484+ if tag[0] == '-':
1485+ gone_tags.add(tag[1:])
1486+ else:
1487+ new_tags.add(tag)
1488+ return new_tags, gone_tags
1489+
1490+
1491+class DiscardStream(object):
1492+ """A filelike object which discards what is written to it."""
1493+
1494+ def write(self, bytes):
1495+ pass
1496+
1497+
1498+class _ParserState(object):
1499+ """State for the subunit parser."""
1500+
1501+ def __init__(self, parser):
1502+ self.parser = parser
1503+ self._test_sym = (_b('test'), _b('testing'))
1504+ self._colon_sym = _b(':')
1505+ self._error_sym = (_b('error'),)
1506+ self._failure_sym = (_b('failure'),)
1507+ self._progress_sym = (_b('progress'),)
1508+ self._skip_sym = _b('skip')
1509+ self._success_sym = (_b('success'), _b('successful'))
1510+ self._tags_sym = (_b('tags'),)
1511+ self._time_sym = (_b('time'),)
1512+ self._xfail_sym = (_b('xfail'),)
1513+ self._uxsuccess_sym = (_b('uxsuccess'),)
1514+ self._start_simple = _u(" [")
1515+ self._start_multipart = _u(" [ multipart")
1516+
1517+ def addError(self, offset, line):
1518+ """An 'error:' directive has been read."""
1519+ self.parser.stdOutLineReceived(line)
1520+
1521+ def addExpectedFail(self, offset, line):
1522+ """An 'xfail:' directive has been read."""
1523+ self.parser.stdOutLineReceived(line)
1524+
1525+ def addFailure(self, offset, line):
1526+ """A 'failure:' directive has been read."""
1527+ self.parser.stdOutLineReceived(line)
1528+
1529+ def addSkip(self, offset, line):
1530+ """A 'skip:' directive has been read."""
1531+ self.parser.stdOutLineReceived(line)
1532+
1533+ def addSuccess(self, offset, line):
1534+ """A 'success:' directive has been read."""
1535+ self.parser.stdOutLineReceived(line)
1536+
1537+ def lineReceived(self, line):
1538+ """a line has been received."""
1539+ parts = line.split(None, 1)
1540+ if len(parts) == 2 and line.startswith(parts[0]):
1541+ cmd, rest = parts
1542+ offset = len(cmd) + 1
1543+ cmd = cmd.rstrip(self._colon_sym)
1544+ if cmd in self._test_sym:
1545+ self.startTest(offset, line)
1546+ elif cmd in self._error_sym:
1547+ self.addError(offset, line)
1548+ elif cmd in self._failure_sym:
1549+ self.addFailure(offset, line)
1550+ elif cmd in self._progress_sym:
1551+ self.parser._handleProgress(offset, line)
1552+ elif cmd in self._skip_sym:
1553+ self.addSkip(offset, line)
1554+ elif cmd in self._success_sym:
1555+ self.addSuccess(offset, line)
1556+ elif cmd in self._tags_sym:
1557+ self.parser._handleTags(offset, line)
1558+ self.parser.subunitLineReceived(line)
1559+ elif cmd in self._time_sym:
1560+ self.parser._handleTime(offset, line)
1561+ self.parser.subunitLineReceived(line)
1562+ elif cmd in self._xfail_sym:
1563+ self.addExpectedFail(offset, line)
1564+ elif cmd in self._uxsuccess_sym:
1565+ self.addUnexpectedSuccess(offset, line)
1566+ else:
1567+ self.parser.stdOutLineReceived(line)
1568+ else:
1569+ self.parser.stdOutLineReceived(line)
1570+
1571+ def lostConnection(self):
1572+ """Connection lost."""
1573+ self.parser._lostConnectionInTest(_u('unknown state of '))
1574+
1575+ def startTest(self, offset, line):
1576+ """A test start command received."""
1577+ self.parser.stdOutLineReceived(line)
1578+
1579+
1580+class _InTest(_ParserState):
1581+ """State for the subunit parser after reading a test: directive."""
1582+
1583+ def _outcome(self, offset, line, no_details, details_state):
1584+ """An outcome directive has been read.
1585+
1586+ :param no_details: Callable to call when no details are presented.
1587+ :param details_state: The state to switch to for details
1588+ processing of this outcome.
1589+ """
1590+ test_name = line[offset:-1].decode('utf8')
1591+ if self.parser.current_test_description == test_name:
1592+ self.parser._state = self.parser._outside_test
1593+ self.parser.current_test_description = None
1594+ no_details()
1595+ self.parser.client.stopTest(self.parser._current_test)
1596+ self.parser._current_test = None
1597+ self.parser.subunitLineReceived(line)
1598+ elif self.parser.current_test_description + self._start_simple == \
1599+ test_name:
1600+ self.parser._state = details_state
1601+ details_state.set_simple()
1602+ self.parser.subunitLineReceived(line)
1603+ elif self.parser.current_test_description + self._start_multipart == \
1604+ test_name:
1605+ self.parser._state = details_state
1606+ details_state.set_multipart()
1607+ self.parser.subunitLineReceived(line)
1608+ else:
1609+ self.parser.stdOutLineReceived(line)
1610+
1611+ def _error(self):
1612+ self.parser.client.addError(self.parser._current_test,
1613+ details={})
1614+
1615+ def addError(self, offset, line):
1616+ """An 'error:' directive has been read."""
1617+ self._outcome(offset, line, self._error,
1618+ self.parser._reading_error_details)
1619+
1620+ def _xfail(self):
1621+ self.parser.client.addExpectedFailure(self.parser._current_test,
1622+ details={})
1623+
1624+ def addExpectedFail(self, offset, line):
1625+ """An 'xfail:' directive has been read."""
1626+ self._outcome(offset, line, self._xfail,
1627+ self.parser._reading_xfail_details)
1628+
1629+ def _uxsuccess(self):
1630+ self.parser.client.addUnexpectedSuccess(self.parser._current_test)
1631+
1632+ def addUnexpectedSuccess(self, offset, line):
1633+ """A 'uxsuccess:' directive has been read."""
1634+ self._outcome(offset, line, self._uxsuccess,
1635+ self.parser._reading_uxsuccess_details)
1636+
1637+ def _failure(self):
1638+ self.parser.client.addFailure(self.parser._current_test, details={})
1639+
1640+ def addFailure(self, offset, line):
1641+ """A 'failure:' directive has been read."""
1642+ self._outcome(offset, line, self._failure,
1643+ self.parser._reading_failure_details)
1644+
1645+ def _skip(self):
1646+ self.parser.client.addSkip(self.parser._current_test, details={})
1647+
1648+ def addSkip(self, offset, line):
1649+ """A 'skip:' directive has been read."""
1650+ self._outcome(offset, line, self._skip,
1651+ self.parser._reading_skip_details)
1652+
1653+ def _succeed(self):
1654+ self.parser.client.addSuccess(self.parser._current_test, details={})
1655+
1656+ def addSuccess(self, offset, line):
1657+ """A 'success:' directive has been read."""
1658+ self._outcome(offset, line, self._succeed,
1659+ self.parser._reading_success_details)
1660+
1661+ def lostConnection(self):
1662+ """Connection lost."""
1663+ self.parser._lostConnectionInTest(_u(''))
1664+
1665+
1666+class _OutSideTest(_ParserState):
1667+ """State for the subunit parser outside of a test context."""
1668+
1669+ def lostConnection(self):
1670+ """Connection lost."""
1671+
1672+ def startTest(self, offset, line):
1673+ """A test start command received."""
1674+ self.parser._state = self.parser._in_test
1675+ test_name = line[offset:-1].decode('utf8')
1676+ self.parser._current_test = RemotedTestCase(test_name)
1677+ self.parser.current_test_description = test_name
1678+ self.parser.client.startTest(self.parser._current_test)
1679+ self.parser.subunitLineReceived(line)
1680+
1681+
1682+class _ReadingDetails(_ParserState):
1683+ """Common logic for readin state details."""
1684+
1685+ def endDetails(self):
1686+ """The end of a details section has been reached."""
1687+ self.parser._state = self.parser._outside_test
1688+ self.parser.current_test_description = None
1689+ self._report_outcome()
1690+ self.parser.client.stopTest(self.parser._current_test)
1691+
1692+ def lineReceived(self, line):
1693+ """a line has been received."""
1694+ self.details_parser.lineReceived(line)
1695+ self.parser.subunitLineReceived(line)
1696+
1697+ def lostConnection(self):
1698+ """Connection lost."""
1699+ self.parser._lostConnectionInTest(_u('%s report of ') %
1700+ self._outcome_label())
1701+
1702+ def _outcome_label(self):
1703+ """The label to describe this outcome."""
1704+ raise NotImplementedError(self._outcome_label)
1705+
1706+ def set_simple(self):
1707+ """Start a simple details parser."""
1708+ self.details_parser = details.SimpleDetailsParser(self)
1709+
1710+ def set_multipart(self):
1711+ """Start a multipart details parser."""
1712+ self.details_parser = details.MultipartDetailsParser(self)
1713+
1714+
1715+class _ReadingFailureDetails(_ReadingDetails):
1716+ """State for the subunit parser when reading failure details."""
1717+
1718+ def _report_outcome(self):
1719+ self.parser.client.addFailure(self.parser._current_test,
1720+ details=self.details_parser.get_details())
1721+
1722+ def _outcome_label(self):
1723+ return "failure"
1724+
1725+
1726+class _ReadingErrorDetails(_ReadingDetails):
1727+ """State for the subunit parser when reading error details."""
1728+
1729+ def _report_outcome(self):
1730+ self.parser.client.addError(self.parser._current_test,
1731+ details=self.details_parser.get_details())
1732+
1733+ def _outcome_label(self):
1734+ return "error"
1735+
1736+
1737+class _ReadingExpectedFailureDetails(_ReadingDetails):
1738+ """State for the subunit parser when reading xfail details."""
1739+
1740+ def _report_outcome(self):
1741+ self.parser.client.addExpectedFailure(self.parser._current_test,
1742+ details=self.details_parser.get_details())
1743+
1744+ def _outcome_label(self):
1745+ return "xfail"
1746+
1747+
1748+class _ReadingUnexpectedSuccessDetails(_ReadingDetails):
1749+ """State for the subunit parser when reading uxsuccess details."""
1750+
1751+ def _report_outcome(self):
1752+ self.parser.client.addUnexpectedSuccess(self.parser._current_test,
1753+ details=self.details_parser.get_details())
1754+
1755+ def _outcome_label(self):
1756+ return "uxsuccess"
1757+
1758+
1759+class _ReadingSkipDetails(_ReadingDetails):
1760+ """State for the subunit parser when reading skip details."""
1761+
1762+ def _report_outcome(self):
1763+ self.parser.client.addSkip(self.parser._current_test,
1764+ details=self.details_parser.get_details("skip"))
1765+
1766+ def _outcome_label(self):
1767+ return "skip"
1768+
1769+
1770+class _ReadingSuccessDetails(_ReadingDetails):
1771+ """State for the subunit parser when reading success details."""
1772+
1773+ def _report_outcome(self):
1774+ self.parser.client.addSuccess(self.parser._current_test,
1775+ details=self.details_parser.get_details("success"))
1776+
1777+ def _outcome_label(self):
1778+ return "success"
1779+
1780+
1781+class TestProtocolServer(object):
1782+ """A parser for subunit.
1783+
1784+ :ivar tags: The current tags associated with the protocol stream.
1785+ """
1786+
1787+ def __init__(self, client, stream=None, forward_stream=None):
1788+ """Create a TestProtocolServer instance.
1789+
1790+ :param client: An object meeting the unittest.TestResult protocol.
1791+ :param stream: The stream that lines received which are not part of the
1792+ subunit protocol should be written to. This allows custom handling
1793+ of mixed protocols. By default, sys.stdout will be used for
1794+ convenience. It should accept bytes to its write() method.
1795+ :param forward_stream: A stream to forward subunit lines to. This
1796+ allows a filter to forward the entire stream while still parsing
1797+ and acting on it. By default forward_stream is set to
1798+ DiscardStream() and no forwarding happens.
1799+ """
1800+ self.client = ExtendedToOriginalDecorator(client)
1801+ if stream is None:
1802+ stream = sys.stdout
1803+ if sys.version_info > (3, 0):
1804+ stream = stream.buffer
1805+ self._stream = stream
1806+ self._forward_stream = forward_stream or DiscardStream()
1807+ # state objects we can switch too
1808+ self._in_test = _InTest(self)
1809+ self._outside_test = _OutSideTest(self)
1810+ self._reading_error_details = _ReadingErrorDetails(self)
1811+ self._reading_failure_details = _ReadingFailureDetails(self)
1812+ self._reading_skip_details = _ReadingSkipDetails(self)
1813+ self._reading_success_details = _ReadingSuccessDetails(self)
1814+ self._reading_xfail_details = _ReadingExpectedFailureDetails(self)
1815+ self._reading_uxsuccess_details = _ReadingUnexpectedSuccessDetails(self)
1816+ # start with outside test.
1817+ self._state = self._outside_test
1818+ # Avoid casts on every call
1819+ self._plusminus = _b('+-')
1820+ self._push_sym = _b('push')
1821+ self._pop_sym = _b('pop')
1822+
1823+ def _handleProgress(self, offset, line):
1824+ """Process a progress directive."""
1825+ line = line[offset:].strip()
1826+ if line[0] in self._plusminus:
1827+ whence = PROGRESS_CUR
1828+ delta = int(line)
1829+ elif line == self._push_sym:
1830+ whence = PROGRESS_PUSH
1831+ delta = None
1832+ elif line == self._pop_sym:
1833+ whence = PROGRESS_POP
1834+ delta = None
1835+ else:
1836+ whence = PROGRESS_SET
1837+ delta = int(line)
1838+ self.client.progress(delta, whence)
1839+
1840+ def _handleTags(self, offset, line):
1841+ """Process a tags command."""
1842+ tags = line[offset:].decode('utf8').split()
1843+ new_tags, gone_tags = tags_to_new_gone(tags)
1844+ self.client.tags(new_tags, gone_tags)
1845+
1846+ def _handleTime(self, offset, line):
1847+ # Accept it, but do not do anything with it yet.
1848+ try:
1849+ event_time = iso8601.parse_date(line[offset:-1])
1850+ except TypeError:
1851+ raise TypeError(_u("Failed to parse %r, got %r")
1852+ % (line, sys.exec_info[1]))
1853+ self.client.time(event_time)
1854+
1855+ def lineReceived(self, line):
1856+ """Call the appropriate local method for the received line."""
1857+ self._state.lineReceived(line)
1858+
1859+ def _lostConnectionInTest(self, state_string):
1860+ error_string = _u("lost connection during %stest '%s'") % (
1861+ state_string, self.current_test_description)
1862+ self.client.addError(self._current_test, RemoteError(error_string))
1863+ self.client.stopTest(self._current_test)
1864+
1865+ def lostConnection(self):
1866+ """The input connection has finished."""
1867+ self._state.lostConnection()
1868+
1869+ def readFrom(self, pipe):
1870+ """Blocking convenience API to parse an entire stream.
1871+
1872+ :param pipe: A file-like object supporting readlines().
1873+ :return: None.
1874+ """
1875+ for line in pipe.readlines():
1876+ self.lineReceived(line)
1877+ self.lostConnection()
1878+
1879+ def _startTest(self, offset, line):
1880+ """Internal call to change state machine. Override startTest()."""
1881+ self._state.startTest(offset, line)
1882+
1883+ def subunitLineReceived(self, line):
1884+ self._forward_stream.write(line)
1885+
1886+ def stdOutLineReceived(self, line):
1887+ self._stream.write(line)
1888+
1889+
1890+class TestProtocolClient(testresult.TestResult):
1891+ """A TestResult which generates a subunit stream for a test run.
1892+
1893+ # Get a TestSuite or TestCase to run
1894+ suite = make_suite()
1895+ # Create a stream (any object with a 'write' method). This should accept
1896+ # bytes not strings: subunit is a byte orientated protocol.
1897+ stream = file('tests.log', 'wb')
1898+ # Create a subunit result object which will output to the stream
1899+ result = subunit.TestProtocolClient(stream)
1900+ # Optionally, to get timing data for performance analysis, wrap the
1901+ # serialiser with a timing decorator
1902+ result = subunit.test_results.AutoTimingTestResultDecorator(result)
1903+ # Run the test suite reporting to the subunit result object
1904+ suite.run(result)
1905+ # Close the stream.
1906+ stream.close()
1907+ """
1908+
1909+ def __init__(self, stream):
1910+ testresult.TestResult.__init__(self)
1911+ self._stream = stream
1912+ _make_stream_binary(stream)
1913+ self._progress_fmt = _b("progress: ")
1914+ self._bytes_eol = _b("\n")
1915+ self._progress_plus = _b("+")
1916+ self._progress_push = _b("push")
1917+ self._progress_pop = _b("pop")
1918+ self._empty_bytes = _b("")
1919+ self._start_simple = _b(" [\n")
1920+ self._end_simple = _b("]\n")
1921+
1922+ def addError(self, test, error=None, details=None):
1923+ """Report an error in test test.
1924+
1925+ Only one of error and details should be provided: conceptually there
1926+ are two separate methods:
1927+ addError(self, test, error)
1928+ addError(self, test, details)
1929+
1930+ :param error: Standard unittest positional argument form - an
1931+ exc_info tuple.
1932+ :param details: New Testing-in-python drafted API; a dict from string
1933+ to subunit.Content objects.
1934+ """
1935+ self._addOutcome("error", test, error=error, details=details)
1936+
1937+ def addExpectedFailure(self, test, error=None, details=None):
1938+ """Report an expected failure in test test.
1939+
1940+ Only one of error and details should be provided: conceptually there
1941+ are two separate methods:
1942+ addError(self, test, error)
1943+ addError(self, test, details)
1944+
1945+ :param error: Standard unittest positional argument form - an
1946+ exc_info tuple.
1947+ :param details: New Testing-in-python drafted API; a dict from string
1948+ to subunit.Content objects.
1949+ """
1950+ self._addOutcome("xfail", test, error=error, details=details)
1951+
1952+ def addFailure(self, test, error=None, details=None):
1953+ """Report a failure in test test.
1954+
1955+ Only one of error and details should be provided: conceptually there
1956+ are two separate methods:
1957+ addFailure(self, test, error)
1958+ addFailure(self, test, details)
1959+
1960+ :param error: Standard unittest positional argument form - an
1961+ exc_info tuple.
1962+ :param details: New Testing-in-python drafted API; a dict from string
1963+ to subunit.Content objects.
1964+ """
1965+ self._addOutcome("failure", test, error=error, details=details)
1966+
1967+ def _addOutcome(self, outcome, test, error=None, details=None,
1968+ error_permitted=True):
1969+ """Report a failure in test test.
1970+
1971+ Only one of error and details should be provided: conceptually there
1972+ are two separate methods:
1973+ addOutcome(self, test, error)
1974+ addOutcome(self, test, details)
1975+
1976+ :param outcome: A string describing the outcome - used as the
1977+ event name in the subunit stream.
1978+ :param error: Standard unittest positional argument form - an
1979+ exc_info tuple.
1980+ :param details: New Testing-in-python drafted API; a dict from string
1981+ to subunit.Content objects.
1982+ :param error_permitted: If True then one and only one of error or
1983+ details must be supplied. If False then error must not be supplied
1984+ and details is still optional. """
1985+ self._stream.write(_b("%s: %s" % (outcome, test.id())))
1986+ if error_permitted:
1987+ if error is None and details is None:
1988+ raise ValueError
1989+ else:
1990+ if error is not None:
1991+ raise ValueError
1992+ if error is not None:
1993+ self._stream.write(self._start_simple)
1994+ # XXX: this needs to be made much stricter, along the lines of
1995+ # Martin[gz]'s work in testtools. Perhaps subunit can use that?
1996+ for line in self._exc_info_to_unicode(error, test).splitlines():
1997+ self._stream.write(("%s\n" % line).encode('utf8'))
1998+ elif details is not None:
1999+ self._write_details(details)
2000+ else:
2001+ self._stream.write(_b("\n"))
2002+ if details is not None or error is not None:
2003+ self._stream.write(self._end_simple)
2004+
2005+ def addSkip(self, test, reason=None, details=None):
2006+ """Report a skipped test."""
2007+ if reason is None:
2008+ self._addOutcome("skip", test, error=None, details=details)
2009+ else:
2010+ self._stream.write(_b("skip: %s [\n" % test.id()))
2011+ self._stream.write(_b("%s\n" % reason))
2012+ self._stream.write(self._end_simple)
2013+
2014+ def addSuccess(self, test, details=None):
2015+ """Report a success in a test."""
2016+ self._addOutcome("successful", test, details=details, error_permitted=False)
2017+
2018+ def addUnexpectedSuccess(self, test, details=None):
2019+ """Report an unexpected success in test test.
2020+
2021+ Details can optionally be provided: conceptually there
2022+ are two separate methods:
2023+ addError(self, test)
2024+ addError(self, test, details)
2025+
2026+ :param details: New Testing-in-python drafted API; a dict from string
2027+ to subunit.Content objects.
2028+ """
2029+ self._addOutcome("uxsuccess", test, details=details,
2030+ error_permitted=False)
2031+
2032+ def startTest(self, test):
2033+ """Mark a test as starting its test run."""
2034+ super(TestProtocolClient, self).startTest(test)
2035+ self._stream.write(_b("test: %s\n" % test.id()))
2036+ self._stream.flush()
2037+
2038+ def stopTest(self, test):
2039+ super(TestProtocolClient, self).stopTest(test)
2040+ self._stream.flush()
2041+
2042+ def progress(self, offset, whence):
2043+ """Provide indication about the progress/length of the test run.
2044+
2045+ :param offset: Information about the number of tests remaining. If
2046+ whence is PROGRESS_CUR, then offset increases/decreases the
2047+ remaining test count. If whence is PROGRESS_SET, then offset
2048+ specifies exactly the remaining test count.
2049+ :param whence: One of PROGRESS_CUR, PROGRESS_SET, PROGRESS_PUSH,
2050+ PROGRESS_POP.
2051+ """
2052+ if whence == PROGRESS_CUR and offset > -1:
2053+ prefix = self._progress_plus
2054+ offset = _b(str(offset))
2055+ elif whence == PROGRESS_PUSH:
2056+ prefix = self._empty_bytes
2057+ offset = self._progress_push
2058+ elif whence == PROGRESS_POP:
2059+ prefix = self._empty_bytes
2060+ offset = self._progress_pop
2061+ else:
2062+ prefix = self._empty_bytes
2063+ offset = _b(str(offset))
2064+ self._stream.write(self._progress_fmt + prefix + offset +
2065+ self._bytes_eol)
2066+
2067+ def time(self, a_datetime):
2068+ """Inform the client of the time.
2069+
2070+ ":param datetime: A datetime.datetime object.
2071+ """
2072+ time = a_datetime.astimezone(iso8601.Utc())
2073+ self._stream.write(_b("time: %04d-%02d-%02d %02d:%02d:%02d.%06dZ\n" % (
2074+ time.year, time.month, time.day, time.hour, time.minute,
2075+ time.second, time.microsecond)))
2076+
2077+ def _write_details(self, details):
2078+ """Output details to the stream.
2079+
2080+ :param details: An extended details dict for a test outcome.
2081+ """
2082+ self._stream.write(_b(" [ multipart\n"))
2083+ for name, content in sorted(details.items()):
2084+ self._stream.write(_b("Content-Type: %s/%s" %
2085+ (content.content_type.type, content.content_type.subtype)))
2086+ parameters = content.content_type.parameters
2087+ if parameters:
2088+ self._stream.write(_b(";"))
2089+ param_strs = []
2090+ for param, value in parameters.items():
2091+ param_strs.append("%s=%s" % (param, value))
2092+ self._stream.write(_b(",".join(param_strs)))
2093+ self._stream.write(_b("\n%s\n" % name))
2094+ encoder = chunked.Encoder(self._stream)
2095+ list(map(encoder.write, content.iter_bytes()))
2096+ encoder.close()
2097+
2098+ def done(self):
2099+ """Obey the testtools result.done() interface."""
2100+
2101+
2102+def RemoteError(description=_u("")):
2103+ return (_StringException, _StringException(description), None)
2104+
2105+
2106+class RemotedTestCase(unittest.TestCase):
2107+ """A class to represent test cases run in child processes.
2108+
2109+ Instances of this class are used to provide the Python test API a TestCase
2110+ that can be printed to the screen, introspected for metadata and so on.
2111+ However, as they are a simply a memoisation of a test that was actually
2112+ run in the past by a separate process, they cannot perform any interactive
2113+ actions.
2114+ """
2115+
2116+ def __eq__ (self, other):
2117+ try:
2118+ return self.__description == other.__description
2119+ except AttributeError:
2120+ return False
2121+
2122+ def __init__(self, description):
2123+ """Create a psuedo test case with description description."""
2124+ self.__description = description
2125+
2126+ def error(self, label):
2127+ raise NotImplementedError("%s on RemotedTestCases is not permitted." %
2128+ label)
2129+
2130+ def setUp(self):
2131+ self.error("setUp")
2132+
2133+ def tearDown(self):
2134+ self.error("tearDown")
2135+
2136+ def shortDescription(self):
2137+ return self.__description
2138+
2139+ def id(self):
2140+ return "%s" % (self.__description,)
2141+
2142+ def __str__(self):
2143+ return "%s (%s)" % (self.__description, self._strclass())
2144+
2145+ def __repr__(self):
2146+ return "<%s description='%s'>" % \
2147+ (self._strclass(), self.__description)
2148+
2149+ def run(self, result=None):
2150+ if result is None: result = self.defaultTestResult()
2151+ result.startTest(self)
2152+ result.addError(self, RemoteError(_u("Cannot run RemotedTestCases.\n")))
2153+ result.stopTest(self)
2154+
2155+ def _strclass(self):
2156+ cls = self.__class__
2157+ return "%s.%s" % (cls.__module__, cls.__name__)
2158+
2159+
2160+class ExecTestCase(unittest.TestCase):
2161+ """A test case which runs external scripts for test fixtures."""
2162+
2163+ def __init__(self, methodName='runTest'):
2164+ """Create an instance of the class that will use the named test
2165+ method when executed. Raises a ValueError if the instance does
2166+ not have a method with the specified name.
2167+ """
2168+ unittest.TestCase.__init__(self, methodName)
2169+ testMethod = getattr(self, methodName)
2170+ self.script = join_dir(sys.modules[self.__class__.__module__].__file__,
2171+ testMethod.__doc__)
2172+
2173+ def countTestCases(self):
2174+ return 1
2175+
2176+ def run(self, result=None):
2177+ if result is None: result = self.defaultTestResult()
2178+ self._run(result)
2179+
2180+ def debug(self):
2181+ """Run the test without collecting errors in a TestResult"""
2182+ self._run(testresult.TestResult())
2183+
2184+ def _run(self, result):
2185+ protocol = TestProtocolServer(result)
2186+ process = subprocess.Popen(self.script, shell=True,
2187+ stdout=subprocess.PIPE)
2188+ _make_stream_binary(process.stdout)
2189+ output = process.communicate()[0]
2190+ protocol.readFrom(BytesIO(output))
2191+
2192+
2193+class IsolatedTestCase(unittest.TestCase):
2194+ """A TestCase which executes in a forked process.
2195+
2196+ Each test gets its own process, which has a performance overhead but will
2197+ provide excellent isolation from global state (such as django configs,
2198+ zope utilities and so on).
2199+ """
2200+
2201+ def run(self, result=None):
2202+ if result is None: result = self.defaultTestResult()
2203+ run_isolated(unittest.TestCase, self, result)
2204+
2205+
2206+class IsolatedTestSuite(unittest.TestSuite):
2207+ """A TestSuite which runs its tests in a forked process.
2208+
2209+ This decorator that will fork() before running the tests and report the
2210+ results from the child process using a Subunit stream. This is useful for
2211+ handling tests that mutate global state, or are testing C extensions that
2212+ could crash the VM.
2213+ """
2214+
2215+ def run(self, result=None):
2216+ if result is None: result = testresult.TestResult()
2217+ run_isolated(unittest.TestSuite, self, result)
2218+
2219+
2220+def run_isolated(klass, self, result):
2221+ """Run a test suite or case in a subprocess, using the run method on klass.
2222+ """
2223+ c2pread, c2pwrite = os.pipe()
2224+ # fixme - error -> result
2225+ # now fork
2226+ pid = os.fork()
2227+ if pid == 0:
2228+ # Child
2229+ # Close parent's pipe ends
2230+ os.close(c2pread)
2231+ # Dup fds for child
2232+ os.dup2(c2pwrite, 1)
2233+ # Close pipe fds.
2234+ os.close(c2pwrite)
2235+
2236+ # at this point, sys.stdin is redirected, now we want
2237+ # to filter it to escape ]'s.
2238+ ### XXX: test and write that bit.
2239+ stream = os.fdopen(1, 'wb')
2240+ result = TestProtocolClient(stream)
2241+ klass.run(self, result)
2242+ stream.flush()
2243+ sys.stderr.flush()
2244+ # exit HARD, exit NOW.
2245+ os._exit(0)
2246+ else:
2247+ # Parent
2248+ # Close child pipe ends
2249+ os.close(c2pwrite)
2250+ # hookup a protocol engine
2251+ protocol = TestProtocolServer(result)
2252+ fileobj = os.fdopen(c2pread, 'rb')
2253+ protocol.readFrom(fileobj)
2254+ os.waitpid(pid, 0)
2255+ # TODO return code evaluation.
2256+ return result
2257+
2258+
2259+def TAP2SubUnit(tap, subunit):
2260+ """Filter a TAP pipe into a subunit pipe.
2261+
2262+ :param tap: A tap pipe/stream/file object.
2263+ :param subunit: A pipe/stream/file object to write subunit results to.
2264+ :return: The exit code to exit with.
2265+ """
2266+ BEFORE_PLAN = 0
2267+ AFTER_PLAN = 1
2268+ SKIP_STREAM = 2
2269+ state = BEFORE_PLAN
2270+ plan_start = 1
2271+ plan_stop = 0
2272+ def _skipped_test(subunit, plan_start):
2273+ # Some tests were skipped.
2274+ subunit.write('test test %d\n' % plan_start)
2275+ subunit.write('error test %d [\n' % plan_start)
2276+ subunit.write('test missing from TAP output\n')
2277+ subunit.write(']\n')
2278+ return plan_start + 1
2279+ # Test data for the next test to emit
2280+ test_name = None
2281+ log = []
2282+ result = None
2283+ def _emit_test():
2284+ "write out a test"
2285+ if test_name is None:
2286+ return
2287+ subunit.write("test %s\n" % test_name)
2288+ if not log:
2289+ subunit.write("%s %s\n" % (result, test_name))
2290+ else:
2291+ subunit.write("%s %s [\n" % (result, test_name))
2292+ if log:
2293+ for line in log:
2294+ subunit.write("%s\n" % line)
2295+ subunit.write("]\n")
2296+ del log[:]
2297+ for line in tap:
2298+ if state == BEFORE_PLAN:
2299+ match = re.match("(\d+)\.\.(\d+)\s*(?:\#\s+(.*))?\n", line)
2300+ if match:
2301+ state = AFTER_PLAN
2302+ _, plan_stop, comment = match.groups()
2303+ plan_stop = int(plan_stop)
2304+ if plan_start > plan_stop and plan_stop == 0:
2305+ # skipped file
2306+ state = SKIP_STREAM
2307+ subunit.write("test file skip\n")
2308+ subunit.write("skip file skip [\n")
2309+ subunit.write("%s\n" % comment)
2310+ subunit.write("]\n")
2311+ continue
2312+ # not a plan line, or have seen one before
2313+ match = re.match("(ok|not ok)(?:\s+(\d+)?)?(?:\s+([^#]*[^#\s]+)\s*)?(?:\s+#\s+(TODO|SKIP|skip|todo)(?:\s+(.*))?)?\n", line)
2314+ if match:
2315+ # new test, emit current one.
2316+ _emit_test()
2317+ status, number, description, directive, directive_comment = match.groups()
2318+ if status == 'ok':
2319+ result = 'success'
2320+ else:
2321+ result = "failure"
2322+ if description is None:
2323+ description = ''
2324+ else:
2325+ description = ' ' + description
2326+ if directive is not None:
2327+ if directive.upper() == 'TODO':
2328+ result = 'xfail'
2329+ elif directive.upper() == 'SKIP':
2330+ result = 'skip'
2331+ if directive_comment is not None:
2332+ log.append(directive_comment)
2333+ if number is not None:
2334+ number = int(number)
2335+ while plan_start < number:
2336+ plan_start = _skipped_test(subunit, plan_start)
2337+ test_name = "test %d%s" % (plan_start, description)
2338+ plan_start += 1
2339+ continue
2340+ match = re.match("Bail out\!(?:\s*(.*))?\n", line)
2341+ if match:
2342+ reason, = match.groups()
2343+ if reason is None:
2344+ extra = ''
2345+ else:
2346+ extra = ' %s' % reason
2347+ _emit_test()
2348+ test_name = "Bail out!%s" % extra
2349+ result = "error"
2350+ state = SKIP_STREAM
2351+ continue
2352+ match = re.match("\#.*\n", line)
2353+ if match:
2354+ log.append(line[:-1])
2355+ continue
2356+ subunit.write(line)
2357+ _emit_test()
2358+ while plan_start <= plan_stop:
2359+ # record missed tests
2360+ plan_start = _skipped_test(subunit, plan_start)
2361+ return 0
2362+
2363+
2364+def tag_stream(original, filtered, tags):
2365+ """Alter tags on a stream.
2366+
2367+ :param original: The input stream.
2368+ :param filtered: The output stream.
2369+ :param tags: The tags to apply. As in a normal stream - a list of 'TAG' or
2370+ '-TAG' commands.
2371+
2372+ A 'TAG' command will add the tag to the output stream,
2373+ and override any existing '-TAG' command in that stream.
2374+ Specifically:
2375+ * A global 'tags: TAG' will be added to the start of the stream.
2376+ * Any tags commands with -TAG will have the -TAG removed.
2377+
2378+ A '-TAG' command will remove the TAG command from the stream.
2379+ Specifically:
2380+ * A 'tags: -TAG' command will be added to the start of the stream.
2381+ * Any 'tags: TAG' command will have 'TAG' removed from it.
2382+ Additionally, any redundant tagging commands (adding a tag globally
2383+ present, or removing a tag globally removed) are stripped as a
2384+ by-product of the filtering.
2385+ :return: 0
2386+ """
2387+ new_tags, gone_tags = tags_to_new_gone(tags)
2388+ def write_tags(new_tags, gone_tags):
2389+ if new_tags or gone_tags:
2390+ filtered.write("tags: " + ' '.join(new_tags))
2391+ if gone_tags:
2392+ for tag in gone_tags:
2393+ filtered.write("-" + tag)
2394+ filtered.write("\n")
2395+ write_tags(new_tags, gone_tags)
2396+ # TODO: use the protocol parser and thus don't mangle test comments.
2397+ for line in original:
2398+ if line.startswith("tags:"):
2399+ line_tags = line[5:].split()
2400+ line_new, line_gone = tags_to_new_gone(line_tags)
2401+ line_new = line_new - gone_tags
2402+ line_gone = line_gone - new_tags
2403+ write_tags(line_new, line_gone)
2404+ else:
2405+ filtered.write(line)
2406+ return 0
2407+
2408+
2409+class ProtocolTestCase(object):
2410+ """Subunit wire protocol to unittest.TestCase adapter.
2411+
2412+ ProtocolTestCase honours the core of ``unittest.TestCase`` protocol -
2413+ calling a ProtocolTestCase or invoking the run() method will make a 'test
2414+ run' happen. The 'test run' will simply be a replay of the test activity
2415+ that has been encoded into the stream. The ``unittest.TestCase`` ``debug``
2416+ and ``countTestCases`` methods are not supported because there isn't a
2417+ sensible mapping for those methods.
2418+
2419+ # Get a stream (any object with a readline() method), in this case the
2420+ # stream output by the example from ``subunit.TestProtocolClient``.
2421+ stream = file('tests.log', 'rb')
2422+ # Create a parser which will read from the stream and emit
2423+ # activity to a unittest.TestResult when run() is called.
2424+ suite = subunit.ProtocolTestCase(stream)
2425+ # Create a result object to accept the contents of that stream.
2426+ result = unittest._TextTestResult(sys.stdout)
2427+ # 'run' the tests - process the stream and feed its contents to result.
2428+ suite.run(result)
2429+ stream.close()
2430+
2431+ :seealso: TestProtocolServer (the subunit wire protocol parser).
2432+ """
2433+
2434+ def __init__(self, stream, passthrough=None, forward=False):
2435+ """Create a ProtocolTestCase reading from stream.
2436+
2437+ :param stream: A filelike object which a subunit stream can be read
2438+ from.
2439+ :param passthrough: A stream pass non subunit input on to. If not
2440+ supplied, the TestProtocolServer default is used.
2441+ :param forward: A stream to pass subunit input on to. If not supplied
2442+ subunit input is not forwarded.
2443+ """
2444+ self._stream = stream
2445+ _make_stream_binary(stream)
2446+ self._passthrough = passthrough
2447+ self._forward = forward
2448+
2449+ def __call__(self, result=None):
2450+ return self.run(result)
2451+
2452+ def run(self, result=None):
2453+ if result is None:
2454+ result = self.defaultTestResult()
2455+ protocol = TestProtocolServer(result, self._passthrough, self._forward)
2456+ line = self._stream.readline()
2457+ while line:
2458+ protocol.lineReceived(line)
2459+ line = self._stream.readline()
2460+ protocol.lostConnection()
2461+
2462+
2463+class TestResultStats(testresult.TestResult):
2464+ """A pyunit TestResult interface implementation for making statistics.
2465+
2466+ :ivar total_tests: The total tests seen.
2467+ :ivar passed_tests: The tests that passed.
2468+ :ivar failed_tests: The tests that failed.
2469+ :ivar seen_tags: The tags seen across all tests.
2470+ """
2471+
2472+ def __init__(self, stream):
2473+ """Create a TestResultStats which outputs to stream."""
2474+ testresult.TestResult.__init__(self)
2475+ self._stream = stream
2476+ self.failed_tests = 0
2477+ self.skipped_tests = 0
2478+ self.seen_tags = set()
2479+
2480+ @property
2481+ def total_tests(self):
2482+ return self.testsRun
2483+
2484+ def addError(self, test, err, details=None):
2485+ self.failed_tests += 1
2486+
2487+ def addFailure(self, test, err, details=None):
2488+ self.failed_tests += 1
2489+
2490+ def addSkip(self, test, reason, details=None):
2491+ self.skipped_tests += 1
2492+
2493+ def formatStats(self):
2494+ self._stream.write("Total tests: %5d\n" % self.total_tests)
2495+ self._stream.write("Passed tests: %5d\n" % self.passed_tests)
2496+ self._stream.write("Failed tests: %5d\n" % self.failed_tests)
2497+ self._stream.write("Skipped tests: %5d\n" % self.skipped_tests)
2498+ tags = sorted(self.seen_tags)
2499+ self._stream.write("Seen tags: %s\n" % (", ".join(tags)))
2500+
2501+ @property
2502+ def passed_tests(self):
2503+ return self.total_tests - self.failed_tests - self.skipped_tests
2504+
2505+ def tags(self, new_tags, gone_tags):
2506+ """Accumulate the seen tags."""
2507+ self.seen_tags.update(new_tags)
2508+
2509+ def wasSuccessful(self):
2510+ """Tells whether or not this result was a success"""
2511+ return self.failed_tests == 0
2512+
2513+
2514+def get_default_formatter():
2515+ """Obtain the default formatter to write to.
2516+
2517+ :return: A file-like object.
2518+ """
2519+ formatter = os.getenv("SUBUNIT_FORMATTER")
2520+ if formatter:
2521+ return os.popen(formatter, "w")
2522+ else:
2523+ stream = sys.stdout
2524+ if sys.version_info > (3, 0):
2525+ stream = stream.buffer
2526+ return stream
2527+
2528+
2529+if sys.version_info > (3, 0):
2530+ from io import UnsupportedOperation as _NoFilenoError
2531+else:
2532+ _NoFilenoError = AttributeError
2533+
2534+def read_test_list(path):
2535+ """Read a list of test ids from a file on disk.
2536+
2537+ :param path: Path to the file
2538+ :return: Sequence of test ids
2539+ """
2540+ f = open(path, 'rb')
2541+ try:
2542+ return [l.rstrip("\n") for l in f.readlines()]
2543+ finally:
2544+ f.close()
2545+
2546+
2547+def _make_stream_binary(stream):
2548+ """Ensure that a stream will be binary safe. See _make_binary_on_windows."""
2549+ try:
2550+ fileno = stream.fileno()
2551+ except _NoFilenoError:
2552+ return
2553+ _make_binary_on_windows(fileno)
2554+
2555+def _make_binary_on_windows(fileno):
2556+ """Win32 mangles \r\n to \n and that breaks streams. See bug lp:505078."""
2557+ if sys.platform == "win32":
2558+ import msvcrt
2559+ msvcrt.setmode(fileno, os.O_BINARY)
2560
2561=== added file 'test/python/subunit/chunked.py'
2562--- test/python/subunit/chunked.py 1970-01-01 00:00:00 +0000
2563+++ test/python/subunit/chunked.py 2011-06-10 09:54:56 +0000
2564@@ -0,0 +1,185 @@
2565+#
2566+# subunit: extensions to python unittest to get test results from subprocesses.
2567+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
2568+# Copyright (C) 2011 Martin Pool <mbp@sourcefrog.net>
2569+#
2570+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
2571+# license at the users choice. A copy of both licenses are available in the
2572+# project source as Apache-2.0 and BSD. You may not use this file except in
2573+# compliance with one of these two licences.
2574+#
2575+# Unless required by applicable law or agreed to in writing, software
2576+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
2577+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2578+# license you chose for the specific language governing permissions and
2579+# limitations under that license.
2580+#
2581+
2582+"""Encoder/decoder for http style chunked encoding."""
2583+
2584+from testtools.compat import _b
2585+
2586+empty = _b('')
2587+
2588+class Decoder(object):
2589+ """Decode chunked content to a byte stream."""
2590+
2591+ def __init__(self, output, strict=True):
2592+ """Create a decoder decoding to output.
2593+
2594+ :param output: A file-like object. Bytes written to the Decoder are
2595+ decoded to strip off the chunking and written to the output.
2596+ Up to a full write worth of data or a single control line may be
2597+ buffered (whichever is larger). The close method should be called
2598+ when no more data is available, to detect short streams; the
2599+ write method will return none-None when the end of a stream is
2600+ detected. The output object must accept bytes objects.
2601+
2602+ :param strict: If True (the default), the decoder will not knowingly
2603+ accept input that is not conformant to the HTTP specification.
2604+ (This does not imply that it will catch every nonconformance.)
2605+ If False, it will accept incorrect input that is still
2606+ unambiguous.
2607+ """
2608+ self.output = output
2609+ self.buffered_bytes = []
2610+ self.state = self._read_length
2611+ self.body_length = 0
2612+ self.strict = strict
2613+ self._match_chars = _b("0123456789abcdefABCDEF\r\n")
2614+ self._slash_n = _b('\n')
2615+ self._slash_r = _b('\r')
2616+ self._slash_rn = _b('\r\n')
2617+ self._slash_nr = _b('\n\r')
2618+
2619+ def close(self):
2620+ """Close the decoder.
2621+
2622+ :raises ValueError: If the stream is incomplete ValueError is raised.
2623+ """
2624+ if self.state != self._finished:
2625+ raise ValueError("incomplete stream")
2626+
2627+ def _finished(self):
2628+ """Finished reading, return any remaining bytes."""
2629+ if self.buffered_bytes:
2630+ buffered_bytes = self.buffered_bytes
2631+ self.buffered_bytes = []
2632+ return empty.join(buffered_bytes)
2633+ else:
2634+ raise ValueError("stream is finished")
2635+
2636+ def _read_body(self):
2637+ """Pass body bytes to the output."""
2638+ while self.body_length and self.buffered_bytes:
2639+ if self.body_length >= len(self.buffered_bytes[0]):
2640+ self.output.write(self.buffered_bytes[0])
2641+ self.body_length -= len(self.buffered_bytes[0])
2642+ del self.buffered_bytes[0]
2643+ # No more data available.
2644+ if not self.body_length:
2645+ self.state = self._read_length
2646+ else:
2647+ self.output.write(self.buffered_bytes[0][:self.body_length])
2648+ self.buffered_bytes[0] = \
2649+ self.buffered_bytes[0][self.body_length:]
2650+ self.body_length = 0
2651+ self.state = self._read_length
2652+ return self.state()
2653+
2654+ def _read_length(self):
2655+ """Try to decode a length from the bytes."""
2656+ count_chars = []
2657+ for bytes in self.buffered_bytes:
2658+ for pos in range(len(bytes)):
2659+ byte = bytes[pos:pos+1]
2660+ if byte not in self._match_chars:
2661+ break
2662+ count_chars.append(byte)
2663+ if byte == self._slash_n:
2664+ break
2665+ if not count_chars:
2666+ return
2667+ if count_chars[-1] != self._slash_n:
2668+ return
2669+ count_str = empty.join(count_chars)
2670+ if self.strict:
2671+ if count_str[-2:] != self._slash_rn:
2672+ raise ValueError("chunk header invalid: %r" % count_str)
2673+ if self._slash_r in count_str[:-2]:
2674+ raise ValueError("too many CRs in chunk header %r" % count_str)
2675+ self.body_length = int(count_str.rstrip(self._slash_nr), 16)
2676+ excess_bytes = len(count_str)
2677+ while excess_bytes:
2678+ if excess_bytes >= len(self.buffered_bytes[0]):
2679+ excess_bytes -= len(self.buffered_bytes[0])
2680+ del self.buffered_bytes[0]
2681+ else:
2682+ self.buffered_bytes[0] = self.buffered_bytes[0][excess_bytes:]
2683+ excess_bytes = 0
2684+ if not self.body_length:
2685+ self.state = self._finished
2686+ if not self.buffered_bytes:
2687+ # May not call into self._finished with no buffered data.
2688+ return empty
2689+ else:
2690+ self.state = self._read_body
2691+ return self.state()
2692+
2693+ def write(self, bytes):
2694+ """Decode bytes to the output stream.
2695+
2696+ :raises ValueError: If the stream has already seen the end of file
2697+ marker.
2698+ :returns: None, or the excess bytes beyond the end of file marker.
2699+ """
2700+ if bytes:
2701+ self.buffered_bytes.append(bytes)
2702+ return self.state()
2703+
2704+
2705+class Encoder(object):
2706+ """Encode content to a stream using HTTP Chunked coding."""
2707+
2708+ def __init__(self, output):
2709+ """Create an encoder encoding to output.
2710+
2711+ :param output: A file-like object. Bytes written to the Encoder
2712+ will be encoded using HTTP chunking. Small writes may be buffered
2713+ and the ``close`` method must be called to finish the stream.
2714+ """
2715+ self.output = output
2716+ self.buffered_bytes = []
2717+ self.buffer_size = 0
2718+
2719+ def flush(self, extra_len=0):
2720+ """Flush the encoder to the output stream.
2721+
2722+ :param extra_len: Increase the size of the chunk by this many bytes
2723+ to allow for a subsequent write.
2724+ """
2725+ if not self.buffer_size and not extra_len:
2726+ return
2727+ buffered_bytes = self.buffered_bytes
2728+ buffer_size = self.buffer_size
2729+ self.buffered_bytes = []
2730+ self.buffer_size = 0
2731+ self.output.write(_b("%X\r\n" % (buffer_size + extra_len)))
2732+ if buffer_size:
2733+ self.output.write(empty.join(buffered_bytes))
2734+ return True
2735+
2736+ def write(self, bytes):
2737+ """Encode bytes to the output stream."""
2738+ bytes_len = len(bytes)
2739+ if self.buffer_size + bytes_len >= 65536:
2740+ self.flush(bytes_len)
2741+ self.output.write(bytes)
2742+ else:
2743+ self.buffered_bytes.append(bytes)
2744+ self.buffer_size += bytes_len
2745+
2746+ def close(self):
2747+ """Finish the stream. This does not close the output stream."""
2748+ self.flush()
2749+ self.output.write(_b("0\r\n"))
2750
2751=== added file 'test/python/subunit/details.py'
2752--- test/python/subunit/details.py 1970-01-01 00:00:00 +0000
2753+++ test/python/subunit/details.py 2011-06-10 09:54:56 +0000
2754@@ -0,0 +1,119 @@
2755+#
2756+# subunit: extensions to Python unittest to get test results from subprocesses.
2757+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
2758+#
2759+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
2760+# license at the users choice. A copy of both licenses are available in the
2761+# project source as Apache-2.0 and BSD. You may not use this file except in
2762+# compliance with one of these two licences.
2763+#
2764+# Unless required by applicable law or agreed to in writing, software
2765+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
2766+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2767+# license you chose for the specific language governing permissions and
2768+# limitations under that license.
2769+#
2770+
2771+"""Handlers for outcome details."""
2772+
2773+from testtools import content, content_type
2774+from testtools.compat import _b, BytesIO
2775+
2776+from subunit import chunked
2777+
2778+end_marker = _b("]\n")
2779+quoted_marker = _b(" ]")
2780+empty = _b('')
2781+
2782+
2783+class DetailsParser(object):
2784+ """Base class/API reference for details parsing."""
2785+
2786+
2787+class SimpleDetailsParser(DetailsParser):
2788+ """Parser for single-part [] delimited details."""
2789+
2790+ def __init__(self, state):
2791+ self._message = _b("")
2792+ self._state = state
2793+
2794+ def lineReceived(self, line):
2795+ if line == end_marker:
2796+ self._state.endDetails()
2797+ return
2798+ if line[0:2] == quoted_marker:
2799+ # quoted ] start
2800+ self._message += line[1:]
2801+ else:
2802+ self._message += line
2803+
2804+ def get_details(self, style=None):
2805+ result = {}
2806+ if not style:
2807+ # We know that subunit/testtools serialise [] formatted
2808+ # tracebacks as utf8, but perhaps we need a ReplacingContent
2809+ # or something like that.
2810+ result['traceback'] = content.Content(
2811+ content_type.ContentType("text", "x-traceback",
2812+ {"charset": "utf8"}),
2813+ lambda:[self._message])
2814+ else:
2815+ if style == 'skip':
2816+ name = 'reason'
2817+ else:
2818+ name = 'message'
2819+ result[name] = content.Content(
2820+ content_type.ContentType("text", "plain"),
2821+ lambda:[self._message])
2822+ return result
2823+
2824+ def get_message(self):
2825+ return self._message
2826+
2827+
2828+class MultipartDetailsParser(DetailsParser):
2829+ """Parser for multi-part [] surrounded MIME typed chunked details."""
2830+
2831+ def __init__(self, state):
2832+ self._state = state
2833+ self._details = {}
2834+ self._parse_state = self._look_for_content
2835+
2836+ def _look_for_content(self, line):
2837+ if line == end_marker:
2838+ self._state.endDetails()
2839+ return
2840+ # TODO error handling
2841+ field, value = line[:-1].decode('utf8').split(' ', 1)
2842+ try:
2843+ main, sub = value.split('/')
2844+ except ValueError:
2845+ raise ValueError("Invalid MIME type %r" % value)
2846+ self._content_type = content_type.ContentType(main, sub)
2847+ self._parse_state = self._get_name
2848+
2849+ def _get_name(self, line):
2850+ self._name = line[:-1].decode('utf8')
2851+ self._body = BytesIO()
2852+ self._chunk_parser = chunked.Decoder(self._body)
2853+ self._parse_state = self._feed_chunks
2854+
2855+ def _feed_chunks(self, line):
2856+ residue = self._chunk_parser.write(line)
2857+ if residue is not None:
2858+ # Line based use always ends on no residue.
2859+ assert residue == empty, 'residue: %r' % (residue,)
2860+ body = self._body
2861+ self._details[self._name] = content.Content(
2862+ self._content_type, lambda:[body.getvalue()])
2863+ self._chunk_parser.close()
2864+ self._parse_state = self._look_for_content
2865+
2866+ def get_details(self, for_skip=False):
2867+ return self._details
2868+
2869+ def get_message(self):
2870+ return None
2871+
2872+ def lineReceived(self, line):
2873+ self._parse_state(line)
2874
2875=== added file 'test/python/subunit/iso8601.py'
2876--- test/python/subunit/iso8601.py 1970-01-01 00:00:00 +0000
2877+++ test/python/subunit/iso8601.py 2011-06-10 09:54:56 +0000
2878@@ -0,0 +1,133 @@
2879+# Copyright (c) 2007 Michael Twomey
2880+#
2881+# Permission is hereby granted, free of charge, to any person obtaining a
2882+# copy of this software and associated documentation files (the
2883+# "Software"), to deal in the Software without restriction, including
2884+# without limitation the rights to use, copy, modify, merge, publish,
2885+# distribute, sublicense, and/or sell copies of the Software, and to
2886+# permit persons to whom the Software is furnished to do so, subject to
2887+# the following conditions:
2888+#
2889+# The above copyright notice and this permission notice shall be included
2890+# in all copies or substantial portions of the Software.
2891+#
2892+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
2893+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2894+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2895+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
2896+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2897+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2898+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2899+
2900+"""ISO 8601 date time string parsing
2901+
2902+Basic usage:
2903+>>> import iso8601
2904+>>> iso8601.parse_date("2007-01-25T12:00:00Z")
2905+datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.iso8601.Utc ...>)
2906+>>>
2907+
2908+"""
2909+
2910+from datetime import datetime, timedelta, tzinfo
2911+import re
2912+import sys
2913+
2914+__all__ = ["parse_date", "ParseError"]
2915+
2916+# Adapted from http://delete.me.uk/2005/03/iso8601.html
2917+ISO8601_REGEX_PATTERN = (r"(?P<year>[0-9]{4})(-(?P<month>[0-9]{1,2})(-(?P<day>[0-9]{1,2})"
2918+ r"((?P<separator>.)(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2})(:(?P<second>[0-9]{2})(\.(?P<fraction>[0-9]+))?)?"
2919+ r"(?P<timezone>Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"
2920+)
2921+TIMEZONE_REGEX_PATTERN = "(?P<prefix>[+-])(?P<hours>[0-9]{2}).(?P<minutes>[0-9]{2})"
2922+ISO8601_REGEX = re.compile(ISO8601_REGEX_PATTERN.encode('utf8'))
2923+TIMEZONE_REGEX = re.compile(TIMEZONE_REGEX_PATTERN.encode('utf8'))
2924+
2925+zulu = "Z".encode('latin-1')
2926+minus = "-".encode('latin-1')
2927+
2928+if sys.version_info < (3, 0):
2929+ bytes = str
2930+
2931+
2932+class ParseError(Exception):
2933+ """Raised when there is a problem parsing a date string"""
2934+
2935+# Yoinked from python docs
2936+ZERO = timedelta(0)
2937+class Utc(tzinfo):
2938+ """UTC
2939+
2940+ """
2941+ def utcoffset(self, dt):
2942+ return ZERO
2943+
2944+ def tzname(self, dt):
2945+ return "UTC"
2946+
2947+ def dst(self, dt):
2948+ return ZERO
2949+UTC = Utc()
2950+
2951+class FixedOffset(tzinfo):
2952+ """Fixed offset in hours and minutes from UTC
2953+
2954+ """
2955+ def __init__(self, offset_hours, offset_minutes, name):
2956+ self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes)
2957+ self.__name = name
2958+
2959+ def utcoffset(self, dt):
2960+ return self.__offset
2961+
2962+ def tzname(self, dt):
2963+ return self.__name
2964+
2965+ def dst(self, dt):
2966+ return ZERO
2967+
2968+ def __repr__(self):
2969+ return "<FixedOffset %r>" % self.__name
2970+
2971+def parse_timezone(tzstring, default_timezone=UTC):
2972+ """Parses ISO 8601 time zone specs into tzinfo offsets
2973+
2974+ """
2975+ if tzstring == zulu:
2976+ return default_timezone
2977+ # This isn't strictly correct, but it's common to encounter dates without
2978+ # timezones so I'll assume the default (which defaults to UTC).
2979+ # Addresses issue 4.
2980+ if tzstring is None:
2981+ return default_timezone
2982+ m = TIMEZONE_REGEX.match(tzstring)
2983+ prefix, hours, minutes = m.groups()
2984+ hours, minutes = int(hours), int(minutes)
2985+ if prefix == minus:
2986+ hours = -hours
2987+ minutes = -minutes
2988+ return FixedOffset(hours, minutes, tzstring)
2989+
2990+def parse_date(datestring, default_timezone=UTC):
2991+ """Parses ISO 8601 dates into datetime objects
2992+
2993+ The timezone is parsed from the date string. However it is quite common to
2994+ have dates without a timezone (not strictly correct). In this case the
2995+ default timezone specified in default_timezone is used. This is UTC by
2996+ default.
2997+ """
2998+ if not isinstance(datestring, bytes):
2999+ raise ParseError("Expecting bytes %r" % datestring)
3000+ m = ISO8601_REGEX.match(datestring)
3001+ if not m:
3002+ raise ParseError("Unable to parse date string %r" % datestring)
3003+ groups = m.groupdict()
3004+ tz = parse_timezone(groups["timezone"], default_timezone=default_timezone)
3005+ if groups["fraction"] is None:
3006+ groups["fraction"] = 0
3007+ else:
3008+ groups["fraction"] = int(float("0.%s" % groups["fraction"]) * 1e6)
3009+ return datetime(int(groups["year"]), int(groups["month"]), int(groups["day"]),
3010+ int(groups["hour"]), int(groups["minute"]), int(groups["second"]),
3011+ int(groups["fraction"]), tz)
3012
3013=== added file 'test/python/subunit/progress_model.py'
3014--- test/python/subunit/progress_model.py 1970-01-01 00:00:00 +0000
3015+++ test/python/subunit/progress_model.py 2011-06-10 09:54:56 +0000
3016@@ -0,0 +1,106 @@
3017+#
3018+# subunit: extensions to Python unittest to get test results from subprocesses.
3019+# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
3020+#
3021+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
3022+# license at the users choice. A copy of both licenses are available in the
3023+# project source as Apache-2.0 and BSD. You may not use this file except in
3024+# compliance with one of these two licences.
3025+#
3026+# Unless required by applicable law or agreed to in writing, software
3027+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
3028+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3029+# license you chose for the specific language governing permissions and
3030+# limitations under that license.
3031+#
3032+
3033+"""Support for dealing with progress state."""
3034+
3035+class ProgressModel(object):
3036+ """A model of progress indicators as subunit defines it.
3037+
3038+ Instances of this class represent a single logical operation that is
3039+ progressing. The operation may have many steps, and some of those steps may
3040+ supply their own progress information. ProgressModel uses a nested concept
3041+ where the overall state can be pushed, creating new starting state, and
3042+ later pushed to return to the prior state. Many user interfaces will want
3043+ to display an overall summary though, and accordingly the pos() and width()
3044+ methods return overall summary information rather than information on the
3045+ current subtask.
3046+
3047+ The default state is 0/0 - indicating that the overall progress is unknown.
3048+ Anytime the denominator of pos/width is 0, rendering of a ProgressModel
3049+ should should take this into consideration.
3050+
3051+ :ivar: _tasks. This private attribute stores the subtasks. Each is a tuple:
3052+ pos, width, overall_numerator, overall_denominator. The overall fields
3053+ store the calculated overall numerator and denominator for the state
3054+ that was pushed.
3055+ """
3056+
3057+ def __init__(self):
3058+ """Create a ProgressModel.
3059+
3060+ The new model has no progress data at all - it will claim a summary
3061+ width of zero and position of 0.
3062+ """
3063+ self._tasks = []
3064+ self.push()
3065+
3066+ def adjust_width(self, offset):
3067+ """Adjust the with of the current subtask."""
3068+ self._tasks[-1][1] += offset
3069+
3070+ def advance(self):
3071+ """Advance the current subtask."""
3072+ self._tasks[-1][0] += 1
3073+
3074+ def pop(self):
3075+ """Pop a subtask off the ProgressModel.
3076+
3077+ See push for a description of how push and pop work.
3078+ """
3079+ self._tasks.pop()
3080+
3081+ def pos(self):
3082+ """Return how far through the operation has progressed."""
3083+ if not self._tasks:
3084+ return 0
3085+ task = self._tasks[-1]
3086+ if len(self._tasks) > 1:
3087+ # scale up the overall pos by the current task or preserve it if
3088+ # no current width is known.
3089+ offset = task[2] * (task[1] or 1)
3090+ else:
3091+ offset = 0
3092+ return offset + task[0]
3093+
3094+ def push(self):
3095+ """Push a new subtask.
3096+
3097+ After pushing a new subtask, the overall progress hasn't changed. Calls
3098+ to adjust_width, advance, set_width will only after the progress within
3099+ the range that calling 'advance' would have before - the subtask
3100+ represents progressing one step in the earlier task.
3101+
3102+ Call pop() to restore the progress model to the state before push was
3103+ called.
3104+ """
3105+ self._tasks.append([0, 0, self.pos(), self.width()])
3106+
3107+ def set_width(self, width):
3108+ """Set the width of the current subtask."""
3109+ self._tasks[-1][1] = width
3110+
3111+ def width(self):
3112+ """Return the total width of the operation."""
3113+ if not self._tasks:
3114+ return 0
3115+ task = self._tasks[-1]
3116+ if len(self._tasks) > 1:
3117+ # scale up the overall width by the current task or preserve it if
3118+ # no current width is known.
3119+ return task[3] * (task[1] or 1)
3120+ else:
3121+ return task[1]
3122+
3123
3124=== added file 'test/python/subunit/run.py'
3125--- test/python/subunit/run.py 1970-01-01 00:00:00 +0000
3126+++ test/python/subunit/run.py 2011-06-10 09:54:56 +0000
3127@@ -0,0 +1,73 @@
3128+#!/usr/bin/python
3129+#
3130+# Simple subunit testrunner for python
3131+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
3132+#
3133+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
3134+# license at the users choice. A copy of both licenses are available in the
3135+# project source as Apache-2.0 and BSD. You may not use this file except in
3136+# compliance with one of these two licences.
3137+#
3138+# Unless required by applicable law or agreed to in writing, software
3139+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
3140+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3141+# license you chose for the specific language governing permissions and
3142+# limitations under that license.
3143+#
3144+
3145+"""Run a unittest testcase reporting results as Subunit.
3146+
3147+ $ python -m subunit.run mylib.tests.test_suite
3148+"""
3149+
3150+import sys
3151+
3152+from subunit import TestProtocolClient, get_default_formatter
3153+from testtools.run import (
3154+ BUFFEROUTPUT,
3155+ CATCHBREAK,
3156+ FAILFAST,
3157+ TestProgram,
3158+ USAGE_AS_MAIN,
3159+ )
3160+
3161+
3162+class SubunitTestRunner(object):
3163+ def __init__(self, stream=sys.stdout):
3164+ self.stream = stream
3165+
3166+ def run(self, test):
3167+ "Run the given test case or test suite."
3168+ result = TestProtocolClient(self.stream)
3169+ test(result)
3170+ return result
3171+
3172+
3173+class SubunitTestProgram(TestProgram):
3174+
3175+ USAGE = USAGE_AS_MAIN
3176+
3177+ def usageExit(self, msg=None):
3178+ if msg:
3179+ print msg
3180+ usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
3181+ 'buffer': ''}
3182+ if self.failfast != False:
3183+ usage['failfast'] = FAILFAST
3184+ if self.catchbreak != False:
3185+ usage['catchbreak'] = CATCHBREAK
3186+ if self.buffer != False:
3187+ usage['buffer'] = BUFFEROUTPUT
3188+ usage_text = self.USAGE % usage
3189+ usage_lines = usage_text.split('\n')
3190+ usage_lines.insert(2, "Run a test suite with a subunit reporter.")
3191+ usage_lines.insert(3, "")
3192+ print('\n'.join(usage_lines))
3193+ sys.exit(2)
3194+
3195+
3196+if __name__ == '__main__':
3197+ stream = get_default_formatter()
3198+ runner = SubunitTestRunner(stream)
3199+ SubunitTestProgram(module=None, argv=sys.argv, testRunner=runner,
3200+ stdout=sys.stdout)
3201
3202=== added file 'test/python/subunit/test_results.py'
3203--- test/python/subunit/test_results.py 1970-01-01 00:00:00 +0000
3204+++ test/python/subunit/test_results.py 2011-06-10 09:54:56 +0000
3205@@ -0,0 +1,492 @@
3206+#
3207+# subunit: extensions to Python unittest to get test results from subprocesses.
3208+# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
3209+#
3210+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
3211+# license at the users choice. A copy of both licenses are available in the
3212+# project source as Apache-2.0 and BSD. You may not use this file except in
3213+# compliance with one of these two licences.
3214+#
3215+# Unless required by applicable law or agreed to in writing, software
3216+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
3217+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3218+# license you chose for the specific language governing permissions and
3219+# limitations under that license.
3220+#
3221+
3222+"""TestResult helper classes used to by subunit."""
3223+
3224+import datetime
3225+
3226+import testtools
3227+
3228+from subunit import iso8601
3229+
3230+
3231+# NOT a TestResult, because we are implementing the interface, not inheriting
3232+# it.
3233+class TestResultDecorator(object):
3234+ """General pass-through decorator.
3235+
3236+ This provides a base that other TestResults can inherit from to
3237+ gain basic forwarding functionality. It also takes care of
3238+ handling the case where the target doesn't support newer methods
3239+ or features by degrading them.
3240+ """
3241+
3242+ def __init__(self, decorated):
3243+ """Create a TestResultDecorator forwarding to decorated."""
3244+ # Make every decorator degrade gracefully.
3245+ self.decorated = testtools.ExtendedToOriginalDecorator(decorated)
3246+
3247+ def startTest(self, test):
3248+ return self.decorated.startTest(test)
3249+
3250+ def startTestRun(self):
3251+ return self.decorated.startTestRun()
3252+
3253+ def stopTest(self, test):
3254+ return self.decorated.stopTest(test)
3255+
3256+ def stopTestRun(self):
3257+ return self.decorated.stopTestRun()
3258+
3259+ def addError(self, test, err=None, details=None):
3260+ return self.decorated.addError(test, err, details=details)
3261+
3262+ def addFailure(self, test, err=None, details=None):
3263+ return self.decorated.addFailure(test, err, details=details)
3264+
3265+ def addSuccess(self, test, details=None):
3266+ return self.decorated.addSuccess(test, details=details)
3267+
3268+ def addSkip(self, test, reason=None, details=None):
3269+ return self.decorated.addSkip(test, reason, details=details)
3270+
3271+ def addExpectedFailure(self, test, err=None, details=None):
3272+ return self.decorated.addExpectedFailure(test, err, details=details)
3273+
3274+ def addUnexpectedSuccess(self, test, details=None):
3275+ return self.decorated.addUnexpectedSuccess(test, details=details)
3276+
3277+ def progress(self, offset, whence):
3278+ return self.decorated.progress(offset, whence)
3279+
3280+ def wasSuccessful(self):
3281+ return self.decorated.wasSuccessful()
3282+
3283+ @property
3284+ def shouldStop(self):
3285+ return self.decorated.shouldStop
3286+
3287+ def stop(self):
3288+ return self.decorated.stop()
3289+
3290+ @property
3291+ def testsRun(self):
3292+ return self.decorated.testsRun
3293+
3294+ def tags(self, new_tags, gone_tags):
3295+ return self.decorated.tags(new_tags, gone_tags)
3296+
3297+ def time(self, a_datetime):
3298+ return self.decorated.time(a_datetime)
3299+
3300+
3301+class HookedTestResultDecorator(TestResultDecorator):
3302+ """A TestResult which calls a hook on every event."""
3303+
3304+ def __init__(self, decorated):
3305+ self.super = super(HookedTestResultDecorator, self)
3306+ self.super.__init__(decorated)
3307+
3308+ def startTest(self, test):
3309+ self._before_event()
3310+ return self.super.startTest(test)
3311+
3312+ def startTestRun(self):
3313+ self._before_event()
3314+ return self.super.startTestRun()
3315+
3316+ def stopTest(self, test):
3317+ self._before_event()
3318+ return self.super.stopTest(test)
3319+
3320+ def stopTestRun(self):
3321+ self._before_event()
3322+ return self.super.stopTestRun()
3323+
3324+ def addError(self, test, err=None, details=None):
3325+ self._before_event()
3326+ return self.super.addError(test, err, details=details)
3327+
3328+ def addFailure(self, test, err=None, details=None):
3329+ self._before_event()
3330+ return self.super.addFailure(test, err, details=details)
3331+
3332+ def addSuccess(self, test, details=None):
3333+ self._before_event()
3334+ return self.super.addSuccess(test, details=details)
3335+
3336+ def addSkip(self, test, reason=None, details=None):
3337+ self._before_event()
3338+ return self.super.addSkip(test, reason, details=details)
3339+
3340+ def addExpectedFailure(self, test, err=None, details=None):
3341+ self._before_event()
3342+ return self.super.addExpectedFailure(test, err, details=details)
3343+
3344+ def addUnexpectedSuccess(self, test, details=None):
3345+ self._before_event()
3346+ return self.super.addUnexpectedSuccess(test, details=details)
3347+
3348+ def progress(self, offset, whence):
3349+ self._before_event()
3350+ return self.super.progress(offset, whence)
3351+
3352+ def wasSuccessful(self):
3353+ self._before_event()
3354+ return self.super.wasSuccessful()
3355+
3356+ @property
3357+ def shouldStop(self):
3358+ self._before_event()
3359+ return self.super.shouldStop
3360+
3361+ def stop(self):
3362+ self._before_event()
3363+ return self.super.stop()
3364+
3365+ def time(self, a_datetime):
3366+ self._before_event()
3367+ return self.super.time(a_datetime)
3368+
3369+
3370+class AutoTimingTestResultDecorator(HookedTestResultDecorator):
3371+ """Decorate a TestResult to add time events to a test run.
3372+
3373+ By default this will cause a time event before every test event,
3374+ but if explicit time data is being provided by the test run, then
3375+ this decorator will turn itself off to prevent causing confusion.
3376+ """
3377+
3378+ def __init__(self, decorated):
3379+ self._time = None
3380+ super(AutoTimingTestResultDecorator, self).__init__(decorated)
3381+
3382+ def _before_event(self):
3383+ time = self._time
3384+ if time is not None:
3385+ return
3386+ time = datetime.datetime.utcnow().replace(tzinfo=iso8601.Utc())
3387+ self.decorated.time(time)
3388+
3389+ def progress(self, offset, whence):
3390+ return self.decorated.progress(offset, whence)
3391+
3392+ @property
3393+ def shouldStop(self):
3394+ return self.decorated.shouldStop
3395+
3396+ def time(self, a_datetime):
3397+ """Provide a timestamp for the current test activity.
3398+
3399+ :param a_datetime: If None, automatically add timestamps before every
3400+ event (this is the default behaviour if time() is not called at
3401+ all). If not None, pass the provided time onto the decorated
3402+ result object and disable automatic timestamps.
3403+ """
3404+ self._time = a_datetime
3405+ return self.decorated.time(a_datetime)
3406+
3407+
3408+class TagCollapsingDecorator(TestResultDecorator):
3409+ """Collapses many 'tags' calls into one where possible."""
3410+
3411+ def __init__(self, result):
3412+ super(TagCollapsingDecorator, self).__init__(result)
3413+ # The (new, gone) tags for the current test.
3414+ self._current_test_tags = None
3415+
3416+ def startTest(self, test):
3417+ """Start a test.
3418+
3419+ Not directly passed to the client, but used for handling of tags
3420+ correctly.
3421+ """
3422+ self.decorated.startTest(test)
3423+ self._current_test_tags = set(), set()
3424+
3425+ def stopTest(self, test):
3426+ """Stop a test.
3427+
3428+ Not directly passed to the client, but used for handling of tags
3429+ correctly.
3430+ """
3431+ # Tags to output for this test.
3432+ if self._current_test_tags[0] or self._current_test_tags[1]:
3433+ self.decorated.tags(*self._current_test_tags)
3434+ self.decorated.stopTest(test)
3435+ self._current_test_tags = None
3436+
3437+ def tags(self, new_tags, gone_tags):
3438+ """Handle tag instructions.
3439+
3440+ Adds and removes tags as appropriate. If a test is currently running,
3441+ tags are not affected for subsequent tests.
3442+
3443+ :param new_tags: Tags to add,
3444+ :param gone_tags: Tags to remove.
3445+ """
3446+ if self._current_test_tags is not None:
3447+ # gather the tags until the test stops.
3448+ self._current_test_tags[0].update(new_tags)
3449+ self._current_test_tags[0].difference_update(gone_tags)
3450+ self._current_test_tags[1].update(gone_tags)
3451+ self._current_test_tags[1].difference_update(new_tags)
3452+ else:
3453+ return self.decorated.tags(new_tags, gone_tags)
3454+
3455+
3456+class TimeCollapsingDecorator(HookedTestResultDecorator):
3457+ """Only pass on the first and last of a consecutive sequence of times."""
3458+
3459+ def __init__(self, decorated):
3460+ super(TimeCollapsingDecorator, self).__init__(decorated)
3461+ self._last_received_time = None
3462+ self._last_sent_time = None
3463+
3464+ def _before_event(self):
3465+ if self._last_received_time is None:
3466+ return
3467+ if self._last_received_time != self._last_sent_time:
3468+ self.decorated.time(self._last_received_time)
3469+ self._last_sent_time = self._last_received_time
3470+ self._last_received_time = None
3471+
3472+ def time(self, a_time):
3473+ # Don't upcall, because we don't want to call _before_event, it's only
3474+ # for non-time events.
3475+ if self._last_received_time is None:
3476+ self.decorated.time(a_time)
3477+ self._last_sent_time = a_time
3478+ self._last_received_time = a_time
3479+
3480+
3481+def all_true(bools):
3482+ """Return True if all of 'bools' are True. False otherwise."""
3483+ for b in bools:
3484+ if not b:
3485+ return False
3486+ return True
3487+
3488+
3489+class TestResultFilter(TestResultDecorator):
3490+ """A pyunit TestResult interface implementation which filters tests.
3491+
3492+ Tests that pass the filter are handed on to another TestResult instance
3493+ for further processing/reporting. To obtain the filtered results,
3494+ the other instance must be interrogated.
3495+
3496+ :ivar result: The result that tests are passed to after filtering.
3497+ :ivar filter_predicate: The callback run to decide whether to pass
3498+ a result.
3499+ """
3500+
3501+ def __init__(self, result, filter_error=False, filter_failure=False,
3502+ filter_success=True, filter_skip=False,
3503+ filter_predicate=None, fixup_expected_failures=None):
3504+ """Create a FilterResult object filtering to result.
3505+
3506+ :param filter_error: Filter out errors.
3507+ :param filter_failure: Filter out failures.
3508+ :param filter_success: Filter out successful tests.
3509+ :param filter_skip: Filter out skipped tests.
3510+ :param filter_predicate: A callable taking (test, outcome, err,
3511+ details) and returning True if the result should be passed
3512+ through. err and details may be none if no error or extra
3513+ metadata is available. outcome is the name of the outcome such
3514+ as 'success' or 'failure'.
3515+ :param fixup_expected_failures: Set of test ids to consider known
3516+ failing.
3517+ """
3518+ super(TestResultFilter, self).__init__(result)
3519+ self.decorated = TimeCollapsingDecorator(
3520+ TagCollapsingDecorator(self.decorated))
3521+ predicates = []
3522+ if filter_error:
3523+ predicates.append(lambda t, outcome, e, d: outcome != 'error')
3524+ if filter_failure:
3525+ predicates.append(lambda t, outcome, e, d: outcome != 'failure')
3526+ if filter_success:
3527+ predicates.append(lambda t, outcome, e, d: outcome != 'success')
3528+ if filter_skip:
3529+ predicates.append(lambda t, outcome, e, d: outcome != 'skip')
3530+ if filter_predicate is not None:
3531+ predicates.append(filter_predicate)
3532+ self.filter_predicate = (
3533+ lambda test, outcome, err, details:
3534+ all_true(p(test, outcome, err, details) for p in predicates))
3535+ # The current test (for filtering tags)
3536+ self._current_test = None
3537+ # Has the current test been filtered (for outputting test tags)
3538+ self._current_test_filtered = None
3539+ # Calls to this result that we don't know whether to forward on yet.
3540+ self._buffered_calls = []
3541+ if fixup_expected_failures is None:
3542+ self._fixup_expected_failures = frozenset()
3543+ else:
3544+ self._fixup_expected_failures = fixup_expected_failures
3545+
3546+ def addError(self, test, err=None, details=None):
3547+ if (self.filter_predicate(test, 'error', err, details)):
3548+ if self._failure_expected(test):
3549+ self._buffered_calls.append(
3550+ ('addExpectedFailure', [test, err], {'details': details}))
3551+ else:
3552+ self._buffered_calls.append(
3553+ ('addError', [test, err], {'details': details}))
3554+ else:
3555+ self._filtered()
3556+
3557+ def addFailure(self, test, err=None, details=None):
3558+ if (self.filter_predicate(test, 'failure', err, details)):
3559+ if self._failure_expected(test):
3560+ self._buffered_calls.append(
3561+ ('addExpectedFailure', [test, err], {'details': details}))
3562+ else:
3563+ self._buffered_calls.append(
3564+ ('addFailure', [test, err], {'details': details}))
3565+ else:
3566+ self._filtered()
3567+
3568+ def addSkip(self, test, reason=None, details=None):
3569+ if (self.filter_predicate(test, 'skip', reason, details)):
3570+ self._buffered_calls.append(
3571+ ('addSkip', [test, reason], {'details': details}))
3572+ else:
3573+ self._filtered()
3574+
3575+ def addSuccess(self, test, details=None):
3576+ if (self.filter_predicate(test, 'success', None, details)):
3577+ if self._failure_expected(test):
3578+ self._buffered_calls.append(
3579+ ('addUnexpectedSuccess', [test], {'details': details}))
3580+ else:
3581+ self._buffered_calls.append(
3582+ ('addSuccess', [test], {'details': details}))
3583+ else:
3584+ self._filtered()
3585+
3586+ def addExpectedFailure(self, test, err=None, details=None):
3587+ if self.filter_predicate(test, 'expectedfailure', err, details):
3588+ self._buffered_calls.append(
3589+ ('addExpectedFailure', [test, err], {'details': details}))
3590+ else:
3591+ self._filtered()
3592+
3593+ def addUnexpectedSuccess(self, test, details=None):
3594+ self._buffered_calls.append(
3595+ ('addUnexpectedSuccess', [test], {'details': details}))
3596+
3597+ def _filtered(self):
3598+ self._current_test_filtered = True
3599+
3600+ def _failure_expected(self, test):
3601+ return (test.id() in self._fixup_expected_failures)
3602+
3603+ def startTest(self, test):
3604+ """Start a test.
3605+
3606+ Not directly passed to the client, but used for handling of tags
3607+ correctly.
3608+ """
3609+ self._current_test = test
3610+ self._current_test_filtered = False
3611+ self._buffered_calls.append(('startTest', [test], {}))
3612+
3613+ def stopTest(self, test):
3614+ """Stop a test.
3615+
3616+ Not directly passed to the client, but used for handling of tags
3617+ correctly.
3618+ """
3619+ if not self._current_test_filtered:
3620+ # Tags to output for this test.
3621+ for method, args, kwargs in self._buffered_calls:
3622+ getattr(self.decorated, method)(*args, **kwargs)
3623+ self.decorated.stopTest(test)
3624+ self._current_test = None
3625+ self._current_test_filtered = None
3626+ self._buffered_calls = []
3627+
3628+ def time(self, a_time):
3629+ if self._current_test is not None:
3630+ self._buffered_calls.append(('time', [a_time], {}))
3631+ else:
3632+ return self.decorated.time(a_time)
3633+
3634+ def id_to_orig_id(self, id):
3635+ if id.startswith("subunit.RemotedTestCase."):
3636+ return id[len("subunit.RemotedTestCase."):]
3637+ return id
3638+
3639+
3640+class TestIdPrintingResult(testtools.TestResult):
3641+
3642+ def __init__(self, stream, show_times=False):
3643+ """Create a FilterResult object outputting to stream."""
3644+ super(TestIdPrintingResult, self).__init__()
3645+ self._stream = stream
3646+ self.failed_tests = 0
3647+ self.__time = None
3648+ self.show_times = show_times
3649+ self._test = None
3650+ self._test_duration = 0
3651+
3652+ def addError(self, test, err):
3653+ self.failed_tests += 1
3654+ self._test = test
3655+
3656+ def addFailure(self, test, err):
3657+ self.failed_tests += 1
3658+ self._test = test
3659+
3660+ def addSuccess(self, test):
3661+ self._test = test
3662+
3663+ def addSkip(self, test, reason=None, details=None):
3664+ self._test = test
3665+
3666+ def addUnexpectedSuccess(self, test, details=None):
3667+ self.failed_tests += 1
3668+ self._test = test
3669+
3670+ def addExpectedFailure(self, test, err=None, details=None):
3671+ self._test = test
3672+
3673+ def reportTest(self, test, duration):
3674+ if self.show_times:
3675+ seconds = duration.seconds
3676+ seconds += duration.days * 3600 * 24
3677+ seconds += duration.microseconds / 1000000.0
3678+ self._stream.write(test.id() + ' %0.3f\n' % seconds)
3679+ else:
3680+ self._stream.write(test.id() + '\n')
3681+
3682+ def startTest(self, test):
3683+ self._start_time = self._time()
3684+
3685+ def stopTest(self, test):
3686+ test_duration = self._time() - self._start_time
3687+ self.reportTest(self._test, test_duration)
3688+
3689+ def time(self, time):
3690+ self.__time = time
3691+
3692+ def _time(self):
3693+ return self.__time
3694+
3695+ def wasSuccessful(self):
3696+ "Tells whether or not this result was a success"
3697+ return self.failed_tests == 0
3698
3699=== added directory 'test/python/subunit/tests'
3700=== added file 'test/python/subunit/tests/TestUtil.py'
3701--- test/python/subunit/tests/TestUtil.py 1970-01-01 00:00:00 +0000
3702+++ test/python/subunit/tests/TestUtil.py 2011-06-10 09:54:56 +0000
3703@@ -0,0 +1,80 @@
3704+# Copyright (c) 2004 Canonical Limited
3705+# Author: Robert Collins <robert.collins@canonical.com>
3706+#
3707+# This program is free software; you can redistribute it and/or modify
3708+# it under the terms of the GNU General Public License as published by
3709+# the Free Software Foundation; either version 2 of the License, or
3710+# (at your option) any later version.
3711+#
3712+# This program is distributed in the hope that it will be useful,
3713+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3714+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3715+# GNU General Public License for more details.
3716+#
3717+# You should have received a copy of the GNU General Public License
3718+# along with this program; if not, write to the Free Software
3719+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3720+#
3721+
3722+import sys
3723+import logging
3724+import unittest
3725+
3726+
3727+class LogCollector(logging.Handler):
3728+ def __init__(self):
3729+ logging.Handler.__init__(self)
3730+ self.records=[]
3731+ def emit(self, record):
3732+ self.records.append(record.getMessage())
3733+
3734+
3735+def makeCollectingLogger():
3736+ """I make a logger instance that collects its logs for programmatic analysis
3737+ -> (logger, collector)"""
3738+ logger=logging.Logger("collector")
3739+ handler=LogCollector()
3740+ handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
3741+ logger.addHandler(handler)
3742+ return logger, handler
3743+
3744+
3745+def visitTests(suite, visitor):
3746+ """A foreign method for visiting the tests in a test suite."""
3747+ for test in suite._tests:
3748+ #Abusing types to avoid monkey patching unittest.TestCase.
3749+ # Maybe that would be better?
3750+ try:
3751+ test.visit(visitor)
3752+ except AttributeError:
3753+ if isinstance(test, unittest.TestCase):
3754+ visitor.visitCase(test)
3755+ elif isinstance(test, unittest.TestSuite):
3756+ visitor.visitSuite(test)
3757+ visitTests(test, visitor)
3758+ else:
3759+ print ("unvisitable non-unittest.TestCase element %r (%r)" % (test, test.__class__))
3760+
3761+
3762+class TestSuite(unittest.TestSuite):
3763+ """I am an extended TestSuite with a visitor interface.
3764+ This is primarily to allow filtering of tests - and suites or
3765+ more in the future. An iterator of just tests wouldn't scale..."""
3766+
3767+ def visit(self, visitor):
3768+ """visit the composite. Visiting is depth-first.
3769+ current callbacks are visitSuite and visitCase."""
3770+ visitor.visitSuite(self)
3771+ visitTests(self, visitor)
3772+
3773+
3774+class TestLoader(unittest.TestLoader):
3775+ """Custome TestLoader to set the right TestSuite class."""
3776+ suiteClass = TestSuite
3777+
3778+class TestVisitor(object):
3779+ """A visitor for Tests"""
3780+ def visitSuite(self, aTestSuite):
3781+ pass
3782+ def visitCase(self, aTestCase):
3783+ pass
3784
3785=== added file 'test/python/subunit/tests/__init__.py'
3786--- test/python/subunit/tests/__init__.py 1970-01-01 00:00:00 +0000
3787+++ test/python/subunit/tests/__init__.py 2011-06-10 09:54:56 +0000
3788@@ -0,0 +1,41 @@
3789+#
3790+# subunit: extensions to python unittest to get test results from subprocesses.
3791+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
3792+#
3793+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
3794+# license at the users choice. A copy of both licenses are available in the
3795+# project source as Apache-2.0 and BSD. You may not use this file except in
3796+# compliance with one of these two licences.
3797+#
3798+# Unless required by applicable law or agreed to in writing, software
3799+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
3800+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3801+# license you chose for the specific language governing permissions and
3802+# limitations under that license.
3803+#
3804+
3805+from subunit.tests import (
3806+ TestUtil,
3807+ test_chunked,
3808+ test_details,
3809+ test_progress_model,
3810+ test_subunit_filter,
3811+ test_subunit_stats,
3812+ test_subunit_tags,
3813+ test_tap2subunit,
3814+ test_test_protocol,
3815+ test_test_results,
3816+ )
3817+
3818+def test_suite():
3819+ result = TestUtil.TestSuite()
3820+ result.addTest(test_chunked.test_suite())
3821+ result.addTest(test_details.test_suite())
3822+ result.addTest(test_progress_model.test_suite())
3823+ result.addTest(test_test_results.test_suite())
3824+ result.addTest(test_test_protocol.test_suite())
3825+ result.addTest(test_tap2subunit.test_suite())
3826+ result.addTest(test_subunit_filter.test_suite())
3827+ result.addTest(test_subunit_tags.test_suite())
3828+ result.addTest(test_subunit_stats.test_suite())
3829+ return result
3830
3831=== added file 'test/python/subunit/tests/sample-script.py'
3832--- test/python/subunit/tests/sample-script.py 1970-01-01 00:00:00 +0000
3833+++ test/python/subunit/tests/sample-script.py 2011-06-10 09:54:56 +0000
3834@@ -0,0 +1,21 @@
3835+#!/usr/bin/env python
3836+import sys
3837+if sys.platform == "win32":
3838+ import msvcrt, os
3839+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3840+if len(sys.argv) == 2:
3841+ # subunit.tests.test_test_protocol.TestExecTestCase.test_sample_method_args
3842+ # uses this code path to be sure that the arguments were passed to
3843+ # sample-script.py
3844+ print "test fail"
3845+ print "error fail"
3846+ sys.exit(0)
3847+print "test old mcdonald"
3848+print "success old mcdonald"
3849+print "test bing crosby"
3850+print "failure bing crosby ["
3851+print "foo.c:53:ERROR invalid state"
3852+print "]"
3853+print "test an error"
3854+print "error an error"
3855+sys.exit(0)
3856
3857=== added file 'test/python/subunit/tests/sample-two-script.py'
3858--- test/python/subunit/tests/sample-two-script.py 1970-01-01 00:00:00 +0000
3859+++ test/python/subunit/tests/sample-two-script.py 2011-06-10 09:54:56 +0000
3860@@ -0,0 +1,7 @@
3861+#!/usr/bin/env python
3862+import sys
3863+print "test old mcdonald"
3864+print "success old mcdonald"
3865+print "test bing crosby"
3866+print "success bing crosby"
3867+sys.exit(0)
3868
3869=== added file 'test/python/subunit/tests/test_chunked.py'
3870--- test/python/subunit/tests/test_chunked.py 1970-01-01 00:00:00 +0000
3871+++ test/python/subunit/tests/test_chunked.py 2011-06-10 09:54:56 +0000
3872@@ -0,0 +1,152 @@
3873+#
3874+# subunit: extensions to python unittest to get test results from subprocesses.
3875+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
3876+# Copyright (C) 2011 Martin Pool <mbp@sourcefrog.net>
3877+#
3878+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
3879+# license at the users choice. A copy of both licenses are available in the
3880+# project source as Apache-2.0 and BSD. You may not use this file except in
3881+# compliance with one of these two licences.
3882+#
3883+# Unless required by applicable law or agreed to in writing, software
3884+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
3885+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3886+# license you chose for the specific language governing permissions and
3887+# limitations under that license.
3888+#
3889+
3890+import unittest
3891+
3892+from testtools.compat import _b, BytesIO
3893+
3894+import subunit.chunked
3895+
3896+
3897+def test_suite():
3898+ loader = subunit.tests.TestUtil.TestLoader()
3899+ result = loader.loadTestsFromName(__name__)
3900+ return result
3901+
3902+
3903+class TestDecode(unittest.TestCase):
3904+
3905+ def setUp(self):
3906+ unittest.TestCase.setUp(self)
3907+ self.output = BytesIO()
3908+ self.decoder = subunit.chunked.Decoder(self.output)
3909+
3910+ def test_close_read_length_short_errors(self):
3911+ self.assertRaises(ValueError, self.decoder.close)
3912+
3913+ def test_close_body_short_errors(self):
3914+ self.assertEqual(None, self.decoder.write(_b('2\r\na')))
3915+ self.assertRaises(ValueError, self.decoder.close)
3916+
3917+ def test_close_body_buffered_data_errors(self):
3918+ self.assertEqual(None, self.decoder.write(_b('2\r')))
3919+ self.assertRaises(ValueError, self.decoder.close)
3920+
3921+ def test_close_after_finished_stream_safe(self):
3922+ self.assertEqual(None, self.decoder.write(_b('2\r\nab')))
3923+ self.assertEqual(_b(''), self.decoder.write(_b('0\r\n')))
3924+ self.decoder.close()
3925+
3926+ def test_decode_nothing(self):
3927+ self.assertEqual(_b(''), self.decoder.write(_b('0\r\n')))
3928+ self.assertEqual(_b(''), self.output.getvalue())
3929+
3930+ def test_decode_serialised_form(self):
3931+ self.assertEqual(None, self.decoder.write(_b("F\r\n")))
3932+ self.assertEqual(None, self.decoder.write(_b("serialised\n")))
3933+ self.assertEqual(_b(''), self.decoder.write(_b("form0\r\n")))
3934+
3935+ def test_decode_short(self):
3936+ self.assertEqual(_b(''), self.decoder.write(_b('3\r\nabc0\r\n')))
3937+ self.assertEqual(_b('abc'), self.output.getvalue())
3938+
3939+ def test_decode_combines_short(self):
3940+ self.assertEqual(_b(''), self.decoder.write(_b('6\r\nabcdef0\r\n')))
3941+ self.assertEqual(_b('abcdef'), self.output.getvalue())
3942+
3943+ def test_decode_excess_bytes_from_write(self):
3944+ self.assertEqual(_b('1234'), self.decoder.write(_b('3\r\nabc0\r\n1234')))
3945+ self.assertEqual(_b('abc'), self.output.getvalue())
3946+
3947+ def test_decode_write_after_finished_errors(self):
3948+ self.assertEqual(_b('1234'), self.decoder.write(_b('3\r\nabc0\r\n1234')))
3949+ self.assertRaises(ValueError, self.decoder.write, _b(''))
3950+
3951+ def test_decode_hex(self):
3952+ self.assertEqual(_b(''), self.decoder.write(_b('A\r\n12345678900\r\n')))
3953+ self.assertEqual(_b('1234567890'), self.output.getvalue())
3954+
3955+ def test_decode_long_ranges(self):
3956+ self.assertEqual(None, self.decoder.write(_b('10000\r\n')))
3957+ self.assertEqual(None, self.decoder.write(_b('1' * 65536)))
3958+ self.assertEqual(None, self.decoder.write(_b('10000\r\n')))
3959+ self.assertEqual(None, self.decoder.write(_b('2' * 65536)))
3960+ self.assertEqual(_b(''), self.decoder.write(_b('0\r\n')))
3961+ self.assertEqual(_b('1' * 65536 + '2' * 65536), self.output.getvalue())
3962+
3963+ def test_decode_newline_nonstrict(self):
3964+ """Tolerate chunk markers with no CR character."""
3965+ # From <http://pad.lv/505078>
3966+ self.decoder = subunit.chunked.Decoder(self.output, strict=False)
3967+ self.assertEqual(None, self.decoder.write(_b('a\n')))
3968+ self.assertEqual(None, self.decoder.write(_b('abcdeabcde')))
3969+ self.assertEqual(_b(''), self.decoder.write(_b('0\n')))
3970+ self.assertEqual(_b('abcdeabcde'), self.output.getvalue())
3971+
3972+ def test_decode_strict_newline_only(self):
3973+ """Reject chunk markers with no CR character in strict mode."""
3974+ # From <http://pad.lv/505078>
3975+ self.assertRaises(ValueError,
3976+ self.decoder.write, _b('a\n'))
3977+
3978+ def test_decode_strict_multiple_crs(self):
3979+ self.assertRaises(ValueError,
3980+ self.decoder.write, _b('a\r\r\n'))
3981+
3982+ def test_decode_short_header(self):
3983+ self.assertRaises(ValueError,
3984+ self.decoder.write, _b('\n'))
3985+
3986+
3987+class TestEncode(unittest.TestCase):
3988+
3989+ def setUp(self):
3990+ unittest.TestCase.setUp(self)
3991+ self.output = BytesIO()
3992+ self.encoder = subunit.chunked.Encoder(self.output)
3993+
3994+ def test_encode_nothing(self):
3995+ self.encoder.close()
3996+ self.assertEqual(_b('0\r\n'), self.output.getvalue())
3997+
3998+ def test_encode_empty(self):
3999+ self.encoder.write(_b(''))
4000+ self.encoder.close()
4001+ self.assertEqual(_b('0\r\n'), self.output.getvalue())
4002+
4003+ def test_encode_short(self):
4004+ self.encoder.write(_b('abc'))
4005+ self.encoder.close()
4006+ self.assertEqual(_b('3\r\nabc0\r\n'), self.output.getvalue())
4007+
4008+ def test_encode_combines_short(self):
4009+ self.encoder.write(_b('abc'))
4010+ self.encoder.write(_b('def'))
4011+ self.encoder.close()
4012+ self.assertEqual(_b('6\r\nabcdef0\r\n'), self.output.getvalue())
4013+
4014+ def test_encode_over_9_is_in_hex(self):
4015+ self.encoder.write(_b('1234567890'))
4016+ self.encoder.close()
4017+ self.assertEqual(_b('A\r\n12345678900\r\n'), self.output.getvalue())
4018+
4019+ def test_encode_long_ranges_not_combined(self):
4020+ self.encoder.write(_b('1' * 65536))
4021+ self.encoder.write(_b('2' * 65536))
4022+ self.encoder.close()
4023+ self.assertEqual(_b('10000\r\n' + '1' * 65536 + '10000\r\n' +
4024+ '2' * 65536 + '0\r\n'), self.output.getvalue())
4025
4026=== added file 'test/python/subunit/tests/test_details.py'
4027--- test/python/subunit/tests/test_details.py 1970-01-01 00:00:00 +0000
4028+++ test/python/subunit/tests/test_details.py 2011-06-10 09:54:56 +0000
4029@@ -0,0 +1,112 @@
4030+#
4031+# subunit: extensions to python unittest to get test results from subprocesses.
4032+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
4033+#
4034+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
4035+# license at the users choice. A copy of both licenses are available in the
4036+# project source as Apache-2.0 and BSD. You may not use this file except in
4037+# compliance with one of these two licences.
4038+#
4039+# Unless required by applicable law or agreed to in writing, software
4040+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
4041+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4042+# license you chose for the specific language governing permissions and
4043+# limitations under that license.
4044+#
4045+
4046+import unittest
4047+
4048+from testtools.compat import _b, StringIO
4049+
4050+import subunit.tests
4051+from subunit import content, content_type, details
4052+
4053+
4054+def test_suite():
4055+ loader = subunit.tests.TestUtil.TestLoader()
4056+ result = loader.loadTestsFromName(__name__)
4057+ return result
4058+
4059+
4060+class TestSimpleDetails(unittest.TestCase):
4061+
4062+ def test_lineReceived(self):
4063+ parser = details.SimpleDetailsParser(None)
4064+ parser.lineReceived(_b("foo\n"))
4065+ parser.lineReceived(_b("bar\n"))
4066+ self.assertEqual(_b("foo\nbar\n"), parser._message)
4067+
4068+ def test_lineReceived_escaped_bracket(self):
4069+ parser = details.SimpleDetailsParser(None)
4070+ parser.lineReceived(_b("foo\n"))
4071+ parser.lineReceived(_b(" ]are\n"))
4072+ parser.lineReceived(_b("bar\n"))
4073+ self.assertEqual(_b("foo\n]are\nbar\n"), parser._message)
4074+
4075+ def test_get_message(self):
4076+ parser = details.SimpleDetailsParser(None)
4077+ self.assertEqual(_b(""), parser.get_message())
4078+
4079+ def test_get_details(self):
4080+ parser = details.SimpleDetailsParser(None)
4081+ traceback = ""
4082+ expected = {}
4083+ expected['traceback'] = content.Content(
4084+ content_type.ContentType("text", "x-traceback",
4085+ {'charset': 'utf8'}),
4086+ lambda:[_b("")])
4087+ found = parser.get_details()
4088+ self.assertEqual(expected.keys(), found.keys())
4089+ self.assertEqual(expected['traceback'].content_type,
4090+ found['traceback'].content_type)
4091+ self.assertEqual(_b('').join(expected['traceback'].iter_bytes()),
4092+ _b('').join(found['traceback'].iter_bytes()))
4093+
4094+ def test_get_details_skip(self):
4095+ parser = details.SimpleDetailsParser(None)
4096+ traceback = ""
4097+ expected = {}
4098+ expected['reason'] = content.Content(
4099+ content_type.ContentType("text", "plain"),
4100+ lambda:[_b("")])
4101+ found = parser.get_details("skip")
4102+ self.assertEqual(expected, found)
4103+
4104+ def test_get_details_success(self):
4105+ parser = details.SimpleDetailsParser(None)
4106+ traceback = ""
4107+ expected = {}
4108+ expected['message'] = content.Content(
4109+ content_type.ContentType("text", "plain"),
4110+ lambda:[_b("")])
4111+ found = parser.get_details("success")
4112+ self.assertEqual(expected, found)
4113+
4114+
4115+class TestMultipartDetails(unittest.TestCase):
4116+
4117+ def test_get_message_is_None(self):
4118+ parser = details.MultipartDetailsParser(None)
4119+ self.assertEqual(None, parser.get_message())
4120+
4121+ def test_get_details(self):
4122+ parser = details.MultipartDetailsParser(None)
4123+ self.assertEqual({}, parser.get_details())
4124+
4125+ def test_parts(self):
4126+ parser = details.MultipartDetailsParser(None)
4127+ parser.lineReceived(_b("Content-Type: text/plain\n"))
4128+ parser.lineReceived(_b("something\n"))
4129+ parser.lineReceived(_b("F\r\n"))
4130+ parser.lineReceived(_b("serialised\n"))
4131+ parser.lineReceived(_b("form0\r\n"))
4132+ expected = {}
4133+ expected['something'] = content.Content(
4134+ content_type.ContentType("text", "plain"),
4135+ lambda:[_b("serialised\nform")])
4136+ found = parser.get_details()
4137+ self.assertEqual(expected.keys(), found.keys())
4138+ self.assertEqual(expected['something'].content_type,
4139+ found['something'].content_type)
4140+ self.assertEqual(_b('').join(expected['something'].iter_bytes()),
4141+ _b('').join(found['something'].iter_bytes()))
4142
4143=== added file 'test/python/subunit/tests/test_progress_model.py'
4144--- test/python/subunit/tests/test_progress_model.py 1970-01-01 00:00:00 +0000
4145+++ test/python/subunit/tests/test_progress_model.py 2011-06-10 09:54:56 +0000
4146@@ -0,0 +1,118 @@
4147+#
4148+# subunit: extensions to Python unittest to get test results from subprocesses.
4149+# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
4150+#
4151+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
4152+# license at the users choice. A copy of both licenses are available in the
4153+# project source as Apache-2.0 and BSD. You may not use this file except in
4154+# compliance with one of these two licences.
4155+#
4156+# Unless required by applicable law or agreed to in writing, software
4157+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
4158+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4159+# license you chose for the specific language governing permissions and
4160+# limitations under that license.
4161+#
4162+
4163+import unittest
4164+
4165+import subunit
4166+from subunit.progress_model import ProgressModel
4167+
4168+
4169+class TestProgressModel(unittest.TestCase):
4170+
4171+ def assertProgressSummary(self, pos, total, progress):
4172+ """Assert that a progress model has reached a particular point."""
4173+ self.assertEqual(pos, progress.pos())
4174+ self.assertEqual(total, progress.width())
4175+
4176+ def test_new_progress_0_0(self):
4177+ progress = ProgressModel()
4178+ self.assertProgressSummary(0, 0, progress)
4179+
4180+ def test_advance_0_0(self):
4181+ progress = ProgressModel()
4182+ progress.advance()
4183+ self.assertProgressSummary(1, 0, progress)
4184+
4185+ def test_advance_1_0(self):
4186+ progress = ProgressModel()
4187+ progress.advance()
4188+ self.assertProgressSummary(1, 0, progress)
4189+
4190+ def test_set_width_absolute(self):
4191+ progress = ProgressModel()
4192+ progress.set_width(10)
4193+ self.assertProgressSummary(0, 10, progress)
4194+
4195+ def test_set_width_absolute_preserves_pos(self):
4196+ progress = ProgressModel()
4197+ progress.advance()
4198+ progress.set_width(2)
4199+ self.assertProgressSummary(1, 2, progress)
4200+
4201+ def test_adjust_width(self):
4202+ progress = ProgressModel()
4203+ progress.adjust_width(10)
4204+ self.assertProgressSummary(0, 10, progress)
4205+ progress.adjust_width(-10)
4206+ self.assertProgressSummary(0, 0, progress)
4207+
4208+ def test_adjust_width_preserves_pos(self):
4209+ progress = ProgressModel()
4210+ progress.advance()
4211+ progress.adjust_width(10)
4212+ self.assertProgressSummary(1, 10, progress)
4213+ progress.adjust_width(-10)
4214+ self.assertProgressSummary(1, 0, progress)
4215+
4216+ def test_push_preserves_progress(self):
4217+ progress = ProgressModel()
4218+ progress.adjust_width(3)
4219+ progress.advance()
4220+ progress.push()
4221+ self.assertProgressSummary(1, 3, progress)
4222+
4223+ def test_advance_advances_substack(self):
4224+ progress = ProgressModel()
4225+ progress.adjust_width(3)
4226+ progress.advance()
4227+ progress.push()
4228+ progress.adjust_width(1)
4229+ progress.advance()
4230+ self.assertProgressSummary(2, 3, progress)
4231+
4232+ def test_adjust_width_adjusts_substack(self):
4233+ progress = ProgressModel()
4234+ progress.adjust_width(3)
4235+ progress.advance()
4236+ progress.push()
4237+ progress.adjust_width(2)
4238+ progress.advance()
4239+ self.assertProgressSummary(3, 6, progress)
4240+
4241+ def test_set_width_adjusts_substack(self):
4242+ progress = ProgressModel()
4243+ progress.adjust_width(3)
4244+ progress.advance()
4245+ progress.push()
4246+ progress.set_width(2)
4247+ progress.advance()
4248+ self.assertProgressSummary(3, 6, progress)
4249+
4250+ def test_pop_restores_progress(self):
4251+ progress = ProgressModel()
4252+ progress.adjust_width(3)
4253+ progress.advance()
4254+ progress.push()
4255+ progress.adjust_width(1)
4256+ progress.advance()
4257+ progress.pop()
4258+ self.assertProgressSummary(1, 3, progress)
4259+
4260+
4261+def test_suite():
4262+ loader = subunit.tests.TestUtil.TestLoader()
4263+ result = loader.loadTestsFromName(__name__)
4264+ return result
4265
4266=== added file 'test/python/subunit/tests/test_subunit_filter.py'
4267--- test/python/subunit/tests/test_subunit_filter.py 1970-01-01 00:00:00 +0000
4268+++ test/python/subunit/tests/test_subunit_filter.py 2011-06-10 09:54:56 +0000
4269@@ -0,0 +1,208 @@
4270+#
4271+# subunit: extensions to python unittest to get test results from subprocesses.
4272+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
4273+#
4274+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
4275+# license at the users choice. A copy of both licenses are available in the
4276+# project source as Apache-2.0 and BSD. You may not use this file except in
4277+# compliance with one of these two licences.
4278+#
4279+# Unless required by applicable law or agreed to in writing, software
4280+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
4281+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4282+# license you chose for the specific language governing permissions and
4283+# limitations under that license.
4284+#
4285+
4286+"""Tests for subunit.TestResultFilter."""
4287+
4288+from datetime import datetime
4289+from subunit import iso8601
4290+import unittest
4291+
4292+from testtools import TestCase
4293+from testtools.compat import _b, BytesIO, StringIO
4294+from testtools.testresult.doubles import ExtendedTestResult
4295+
4296+import subunit
4297+from subunit.test_results import TestResultFilter
4298+
4299+
4300+class TestTestResultFilter(TestCase):
4301+ """Test for TestResultFilter, a TestResult object which filters tests."""
4302+
4303+ # While TestResultFilter works on python objects, using a subunit stream
4304+ # is an easy pithy way of getting a series of test objects to call into
4305+ # the TestResult, and as TestResultFilter is intended for use with subunit
4306+ # also has the benefit of detecting any interface skew issues.
4307+ example_subunit_stream = _b("""\
4308+tags: global
4309+test passed
4310+success passed
4311+test failed
4312+tags: local
4313+failure failed
4314+test error
4315+error error [
4316+error details
4317+]
4318+test skipped
4319+skip skipped
4320+test todo
4321+xfail todo
4322+""")
4323+
4324+ def run_tests(self, result_filter, input_stream=None):
4325+ """Run tests through the given filter.
4326+
4327+ :param result_filter: A filtering TestResult object.
4328+ :param input_stream: Bytes of subunit stream data. If not provided,
4329+ uses TestTestResultFilter.example_subunit_stream.
4330+ """
4331+ if input_stream is None:
4332+ input_stream = self.example_subunit_stream
4333+ test = subunit.ProtocolTestCase(BytesIO(input_stream))
4334+ test.run(result_filter)
4335+
4336+ def test_default(self):
4337+ """The default is to exclude success and include everything else."""
4338+ filtered_result = unittest.TestResult()
4339+ result_filter = TestResultFilter(filtered_result)
4340+ self.run_tests(result_filter)
4341+ # skips are seen as success by default python TestResult.
4342+ self.assertEqual(['error'],
4343+ [error[0].id() for error in filtered_result.errors])
4344+ self.assertEqual(['failed'],
4345+ [failure[0].id() for failure in
4346+ filtered_result.failures])
4347+ self.assertEqual(4, filtered_result.testsRun)
4348+
4349+ def test_exclude_errors(self):
4350+ filtered_result = unittest.TestResult()
4351+ result_filter = TestResultFilter(filtered_result, filter_error=True)
4352+ self.run_tests(result_filter)
4353+ # skips are seen as errors by default python TestResult.
4354+ self.assertEqual([], filtered_result.errors)
4355+ self.assertEqual(['failed'],
4356+ [failure[0].id() for failure in
4357+ filtered_result.failures])
4358+ self.assertEqual(3, filtered_result.testsRun)
4359+
4360+ def test_fixup_expected_failures(self):
4361+ filtered_result = unittest.TestResult()
4362+ result_filter = TestResultFilter(filtered_result,
4363+ fixup_expected_failures=set(["failed"]))
4364+ self.run_tests(result_filter)
4365+ self.assertEqual(['failed', 'todo'],
4366+ [failure[0].id() for failure in filtered_result.expectedFailures])
4367+ self.assertEqual([], filtered_result.failures)
4368+ self.assertEqual(4, filtered_result.testsRun)
4369+
4370+ def test_fixup_expected_errors(self):
4371+ filtered_result = unittest.TestResult()
4372+ result_filter = TestResultFilter(filtered_result,
4373+ fixup_expected_failures=set(["error"]))
4374+ self.run_tests(result_filter)
4375+ self.assertEqual(['error', 'todo'],
4376+ [failure[0].id() for failure in filtered_result.expectedFailures])
4377+ self.assertEqual([], filtered_result.errors)
4378+ self.assertEqual(4, filtered_result.testsRun)
4379+
4380+ def test_fixup_unexpected_success(self):
4381+ filtered_result = unittest.TestResult()
4382+ result_filter = TestResultFilter(filtered_result, filter_success=False,
4383+ fixup_expected_failures=set(["passed"]))
4384+ self.run_tests(result_filter)
4385+ self.assertEqual(['passed'],
4386+ [passed.id() for passed in filtered_result.unexpectedSuccesses])
4387+ self.assertEqual(5, filtered_result.testsRun)
4388+
4389+ def test_exclude_failure(self):
4390+ filtered_result = unittest.TestResult()
4391+ result_filter = TestResultFilter(filtered_result, filter_failure=True)
4392+ self.run_tests(result_filter)
4393+ self.assertEqual(['error'],
4394+ [error[0].id() for error in filtered_result.errors])
4395+ self.assertEqual([],
4396+ [failure[0].id() for failure in
4397+ filtered_result.failures])
4398+ self.assertEqual(3, filtered_result.testsRun)
4399+
4400+ def test_exclude_skips(self):
4401+ filtered_result = subunit.TestResultStats(None)
4402+ result_filter = TestResultFilter(filtered_result, filter_skip=True)
4403+ self.run_tests(result_filter)
4404+ self.assertEqual(0, filtered_result.skipped_tests)
4405+ self.assertEqual(2, filtered_result.failed_tests)
4406+ self.assertEqual(3, filtered_result.testsRun)
4407+
4408+ def test_include_success(self):
4409+ """Successes can be included if requested."""
4410+ filtered_result = unittest.TestResult()
4411+ result_filter = TestResultFilter(filtered_result,
4412+ filter_success=False)
4413+ self.run_tests(result_filter)
4414+ self.assertEqual(['error'],
4415+ [error[0].id() for error in filtered_result.errors])
4416+ self.assertEqual(['failed'],
4417+ [failure[0].id() for failure in
4418+ filtered_result.failures])
4419+ self.assertEqual(5, filtered_result.testsRun)
4420+
4421+ def test_filter_predicate(self):
4422+ """You can filter by predicate callbacks"""
4423+ filtered_result = unittest.TestResult()
4424+ def filter_cb(test, outcome, err, details):
4425+ return outcome == 'success'
4426+ result_filter = TestResultFilter(filtered_result,
4427+ filter_predicate=filter_cb,
4428+ filter_success=False)
4429+ self.run_tests(result_filter)
4430+ # Only success should pass
4431+ self.assertEqual(1, filtered_result.testsRun)
4432+
4433+ def test_time_ordering_preserved(self):
4434+ # Passing a subunit stream through TestResultFilter preserves the
4435+ # relative ordering of 'time' directives and any other subunit
4436+ # directives that are still included.
4437+ date_a = datetime(year=2000, month=1, day=1, tzinfo=iso8601.UTC)
4438+ date_b = datetime(year=2000, month=1, day=2, tzinfo=iso8601.UTC)
4439+ date_c = datetime(year=2000, month=1, day=3, tzinfo=iso8601.UTC)
4440+ subunit_stream = _b('\n'.join([
4441+ "time: %s",
4442+ "test: foo",
4443+ "time: %s",
4444+ "error: foo",
4445+ "time: %s",
4446+ ""]) % (date_a, date_b, date_c))
4447+ result = ExtendedTestResult()
4448+ result_filter = TestResultFilter(result)
4449+ self.run_tests(result_filter, subunit_stream)
4450+ foo = subunit.RemotedTestCase('foo')
4451+ self.assertEquals(
4452+ [('time', date_a),
4453+ ('startTest', foo),
4454+ ('time', date_b),
4455+ ('addError', foo, {}),
4456+ ('stopTest', foo),
4457+ ('time', date_c)], result._events)
4458+
4459+ def test_skip_preserved(self):
4460+ subunit_stream = _b('\n'.join([
4461+ "test: foo",
4462+ "skip: foo",
4463+ ""]))
4464+ result = ExtendedTestResult()
4465+ result_filter = TestResultFilter(result)
4466+ self.run_tests(result_filter, subunit_stream)
4467+ foo = subunit.RemotedTestCase('foo')
4468+ self.assertEquals(
4469+ [('startTest', foo),
4470+ ('addSkip', foo, {}),
4471+ ('stopTest', foo), ], result._events)
4472+
4473+
4474+def test_suite():
4475+ loader = subunit.tests.TestUtil.TestLoader()
4476+ result = loader.loadTestsFromName(__name__)
4477+ return result
4478
4479=== added file 'test/python/subunit/tests/test_subunit_stats.py'
4480--- test/python/subunit/tests/test_subunit_stats.py 1970-01-01 00:00:00 +0000
4481+++ test/python/subunit/tests/test_subunit_stats.py 2011-06-10 09:54:56 +0000
4482@@ -0,0 +1,84 @@
4483+#
4484+# subunit: extensions to python unittest to get test results from subprocesses.
4485+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
4486+#
4487+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
4488+# license at the users choice. A copy of both licenses are available in the
4489+# project source as Apache-2.0 and BSD. You may not use this file except in
4490+# compliance with one of these two licences.
4491+#
4492+# Unless required by applicable law or agreed to in writing, software
4493+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
4494+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4495+# license you chose for the specific language governing permissions and
4496+# limitations under that license.
4497+#
4498+
4499+"""Tests for subunit.TestResultStats."""
4500+
4501+import unittest
4502+
4503+from testtools.compat import _b, BytesIO, StringIO
4504+
4505+import subunit
4506+
4507+
4508+class TestTestResultStats(unittest.TestCase):
4509+ """Test for TestResultStats, a TestResult object that generates stats."""
4510+
4511+ def setUp(self):
4512+ self.output = StringIO()
4513+ self.result = subunit.TestResultStats(self.output)
4514+ self.input_stream = BytesIO()
4515+ self.test = subunit.ProtocolTestCase(self.input_stream)
4516+
4517+ def test_stats_empty(self):
4518+ self.test.run(self.result)
4519+ self.assertEqual(0, self.result.total_tests)
4520+ self.assertEqual(0, self.result.passed_tests)
4521+ self.assertEqual(0, self.result.failed_tests)
4522+ self.assertEqual(set(), self.result.seen_tags)
4523+
4524+ def setUpUsedStream(self):
4525+ self.input_stream.write(_b("""tags: global
4526+test passed
4527+success passed
4528+test failed
4529+tags: local
4530+failure failed
4531+test error
4532+error error
4533+test skipped
4534+skip skipped
4535+test todo
4536+xfail todo
4537+"""))
4538+ self.input_stream.seek(0)
4539+ self.test.run(self.result)
4540+
4541+ def test_stats_smoke_everything(self):
4542+ # Statistics are calculated usefully.
4543+ self.setUpUsedStream()
4544+ self.assertEqual(5, self.result.total_tests)
4545+ self.assertEqual(2, self.result.passed_tests)
4546+ self.assertEqual(2, self.result.failed_tests)
4547+ self.assertEqual(1, self.result.skipped_tests)
4548+ self.assertEqual(set(["global", "local"]), self.result.seen_tags)
4549+
4550+ def test_stat_formatting(self):
4551+ expected = ("""
4552+Total tests: 5
4553+Passed tests: 2
4554+Failed tests: 2
4555+Skipped tests: 1
4556+Seen tags: global, local
4557+""")[1:]
4558+ self.setUpUsedStream()
4559+ self.result.formatStats()
4560+ self.assertEqual(expected, self.output.getvalue())
4561+
4562+
4563+def test_suite():
4564+ loader = subunit.tests.TestUtil.TestLoader()
4565+ result = loader.loadTestsFromName(__name__)
4566+ return result
4567
4568=== added file 'test/python/subunit/tests/test_subunit_tags.py'
4569--- test/python/subunit/tests/test_subunit_tags.py 1970-01-01 00:00:00 +0000
4570+++ test/python/subunit/tests/test_subunit_tags.py 2011-06-10 09:54:56 +0000
4571@@ -0,0 +1,69 @@
4572+#
4573+# subunit: extensions to python unittest to get test results from subprocesses.
4574+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
4575+#
4576+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
4577+# license at the users choice. A copy of both licenses are available in the
4578+# project source as Apache-2.0 and BSD. You may not use this file except in
4579+# compliance with one of these two licences.
4580+#
4581+# Unless required by applicable law or agreed to in writing, software
4582+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
4583+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4584+# license you chose for the specific language governing permissions and
4585+# limitations under that license.
4586+#
4587+
4588+"""Tests for subunit.tag_stream."""
4589+
4590+import unittest
4591+
4592+from testtools.compat import StringIO
4593+
4594+import subunit
4595+import subunit.test_results
4596+
4597+
4598+class TestSubUnitTags(unittest.TestCase):
4599+
4600+ def setUp(self):
4601+ self.original = StringIO()
4602+ self.filtered = StringIO()
4603+
4604+ def test_add_tag(self):
4605+ self.original.write("tags: foo\n")
4606+ self.original.write("test: test\n")
4607+ self.original.write("tags: bar -quux\n")
4608+ self.original.write("success: test\n")
4609+ self.original.seek(0)
4610+ result = subunit.tag_stream(self.original, self.filtered, ["quux"])
4611+ self.assertEqual([
4612+ "tags: quux",
4613+ "tags: foo",
4614+ "test: test",
4615+ "tags: bar",
4616+ "success: test",
4617+ ],
4618+ self.filtered.getvalue().splitlines())
4619+
4620+ def test_remove_tag(self):
4621+ self.original.write("tags: foo\n")
4622+ self.original.write("test: test\n")
4623+ self.original.write("tags: bar -quux\n")
4624+ self.original.write("success: test\n")
4625+ self.original.seek(0)
4626+ result = subunit.tag_stream(self.original, self.filtered, ["-bar"])
4627+ self.assertEqual([
4628+ "tags: -bar",
4629+ "tags: foo",
4630+ "test: test",
4631+ "tags: -quux",
4632+ "success: test",
4633+ ],
4634+ self.filtered.getvalue().splitlines())
4635+
4636+
4637+def test_suite():
4638+ loader = subunit.tests.TestUtil.TestLoader()
4639+ result = loader.loadTestsFromName(__name__)
4640+ return result
4641
4642=== added file 'test/python/subunit/tests/test_tap2subunit.py'
4643--- test/python/subunit/tests/test_tap2subunit.py 1970-01-01 00:00:00 +0000
4644+++ test/python/subunit/tests/test_tap2subunit.py 2011-06-10 09:54:56 +0000
4645@@ -0,0 +1,445 @@
4646+#
4647+# subunit: extensions to python unittest to get test results from subprocesses.
4648+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
4649+#
4650+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
4651+# license at the users choice. A copy of both licenses are available in the
4652+# project source as Apache-2.0 and BSD. You may not use this file except in
4653+# compliance with one of these two licences.
4654+#
4655+# Unless required by applicable law or agreed to in writing, software
4656+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
4657+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4658+# license you chose for the specific language governing permissions and
4659+# limitations under that license.
4660+#
4661+
4662+"""Tests for TAP2SubUnit."""
4663+
4664+import unittest
4665+
4666+from testtools.compat import StringIO
4667+
4668+import subunit
4669+
4670+
4671+class TestTAP2SubUnit(unittest.TestCase):
4672+ """Tests for TAP2SubUnit.
4673+
4674+ These tests test TAP string data in, and subunit string data out.
4675+ This is ok because the subunit protocol is intended to be stable,
4676+ but it might be easier/pithier to write tests against TAP string in,
4677+ parsed subunit objects out (by hooking the subunit stream to a subunit
4678+ protocol server.
4679+ """
4680+
4681+ def setUp(self):
4682+ self.tap = StringIO()
4683+ self.subunit = StringIO()
4684+
4685+ def test_skip_entire_file(self):
4686+ # A file
4687+ # 1..- # Skipped: comment
4688+ # results in a single skipped test.
4689+ self.tap.write("1..0 # Skipped: entire file skipped\n")
4690+ self.tap.seek(0)
4691+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4692+ self.assertEqual(0, result)
4693+ self.assertEqual([
4694+ "test file skip",
4695+ "skip file skip [",
4696+ "Skipped: entire file skipped",
4697+ "]",
4698+ ],
4699+ self.subunit.getvalue().splitlines())
4700+
4701+ def test_ok_test_pass(self):
4702+ # A file
4703+ # ok
4704+ # results in a passed test with name 'test 1' (a synthetic name as tap
4705+ # does not require named fixtures - it is the first test in the tap
4706+ # stream).
4707+ self.tap.write("ok\n")
4708+ self.tap.seek(0)
4709+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4710+ self.assertEqual(0, result)
4711+ self.assertEqual([
4712+ "test test 1",
4713+ "success test 1",
4714+ ],
4715+ self.subunit.getvalue().splitlines())
4716+
4717+ def test_ok_test_number_pass(self):
4718+ # A file
4719+ # ok 1
4720+ # results in a passed test with name 'test 1'
4721+ self.tap.write("ok 1\n")
4722+ self.tap.seek(0)
4723+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4724+ self.assertEqual(0, result)
4725+ self.assertEqual([
4726+ "test test 1",
4727+ "success test 1",
4728+ ],
4729+ self.subunit.getvalue().splitlines())
4730+
4731+ def test_ok_test_number_description_pass(self):
4732+ # A file
4733+ # ok 1 - There is a description
4734+ # results in a passed test with name 'test 1 - There is a description'
4735+ self.tap.write("ok 1 - There is a description\n")
4736+ self.tap.seek(0)
4737+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4738+ self.assertEqual(0, result)
4739+ self.assertEqual([
4740+ "test test 1 - There is a description",
4741+ "success test 1 - There is a description",
4742+ ],
4743+ self.subunit.getvalue().splitlines())
4744+
4745+ def test_ok_test_description_pass(self):
4746+ # A file
4747+ # ok There is a description
4748+ # results in a passed test with name 'test 1 There is a description'
4749+ self.tap.write("ok There is a description\n")
4750+ self.tap.seek(0)
4751+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4752+ self.assertEqual(0, result)
4753+ self.assertEqual([
4754+ "test test 1 There is a description",
4755+ "success test 1 There is a description",
4756+ ],
4757+ self.subunit.getvalue().splitlines())
4758+
4759+ def test_ok_SKIP_skip(self):
4760+ # A file
4761+ # ok # SKIP
4762+ # results in a skkip test with name 'test 1'
4763+ self.tap.write("ok # SKIP\n")
4764+ self.tap.seek(0)
4765+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4766+ self.assertEqual(0, result)
4767+ self.assertEqual([
4768+ "test test 1",
4769+ "skip test 1",
4770+ ],
4771+ self.subunit.getvalue().splitlines())
4772+
4773+ def test_ok_skip_number_comment_lowercase(self):
4774+ self.tap.write("ok 1 # skip no samba environment available, skipping compilation\n")
4775+ self.tap.seek(0)
4776+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4777+ self.assertEqual(0, result)
4778+ self.assertEqual([
4779+ "test test 1",
4780+ "skip test 1 [",
4781+ "no samba environment available, skipping compilation",
4782+ "]"
4783+ ],
4784+ self.subunit.getvalue().splitlines())
4785+
4786+ def test_ok_number_description_SKIP_skip_comment(self):
4787+ # A file
4788+ # ok 1 foo # SKIP Not done yet
4789+ # results in a skip test with name 'test 1 foo' and a log of
4790+ # Not done yet
4791+ self.tap.write("ok 1 foo # SKIP Not done yet\n")
4792+ self.tap.seek(0)
4793+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4794+ self.assertEqual(0, result)
4795+ self.assertEqual([
4796+ "test test 1 foo",
4797+ "skip test 1 foo [",
4798+ "Not done yet",
4799+ "]",
4800+ ],
4801+ self.subunit.getvalue().splitlines())
4802+
4803+ def test_ok_SKIP_skip_comment(self):
4804+ # A file
4805+ # ok # SKIP Not done yet
4806+ # results in a skip test with name 'test 1' and a log of Not done yet
4807+ self.tap.write("ok # SKIP Not done yet\n")
4808+ self.tap.seek(0)
4809+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4810+ self.assertEqual(0, result)
4811+ self.assertEqual([
4812+ "test test 1",
4813+ "skip test 1 [",
4814+ "Not done yet",
4815+ "]",
4816+ ],
4817+ self.subunit.getvalue().splitlines())
4818+
4819+ def test_ok_TODO_xfail(self):
4820+ # A file
4821+ # ok # TODO
4822+ # results in a xfail test with name 'test 1'
4823+ self.tap.write("ok # TODO\n")
4824+ self.tap.seek(0)
4825+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4826+ self.assertEqual(0, result)
4827+ self.assertEqual([
4828+ "test test 1",
4829+ "xfail test 1",
4830+ ],
4831+ self.subunit.getvalue().splitlines())
4832+
4833+ def test_ok_TODO_xfail_comment(self):
4834+ # A file
4835+ # ok # TODO Not done yet
4836+ # results in a xfail test with name 'test 1' and a log of Not done yet
4837+ self.tap.write("ok # TODO Not done yet\n")
4838+ self.tap.seek(0)
4839+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4840+ self.assertEqual(0, result)
4841+ self.assertEqual([
4842+ "test test 1",
4843+ "xfail test 1 [",
4844+ "Not done yet",
4845+ "]",
4846+ ],
4847+ self.subunit.getvalue().splitlines())
4848+
4849+ def test_bail_out_errors(self):
4850+ # A file with line in it
4851+ # Bail out! COMMENT
4852+ # is treated as an error
4853+ self.tap.write("ok 1 foo\n")
4854+ self.tap.write("Bail out! Lifejacket engaged\n")
4855+ self.tap.seek(0)
4856+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4857+ self.assertEqual(0, result)
4858+ self.assertEqual([
4859+ "test test 1 foo",
4860+ "success test 1 foo",
4861+ "test Bail out! Lifejacket engaged",
4862+ "error Bail out! Lifejacket engaged",
4863+ ],
4864+ self.subunit.getvalue().splitlines())
4865+
4866+ def test_missing_test_at_end_with_plan_adds_error(self):
4867+ # A file
4868+ # 1..3
4869+ # ok first test
4870+ # not ok third test
4871+ # results in three tests, with the third being created
4872+ self.tap.write('1..3\n')
4873+ self.tap.write('ok first test\n')
4874+ self.tap.write('not ok second test\n')
4875+ self.tap.seek(0)
4876+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4877+ self.assertEqual(0, result)
4878+ self.assertEqual([
4879+ 'test test 1 first test',
4880+ 'success test 1 first test',
4881+ 'test test 2 second test',
4882+ 'failure test 2 second test',
4883+ 'test test 3',
4884+ 'error test 3 [',
4885+ 'test missing from TAP output',
4886+ ']',
4887+ ],
4888+ self.subunit.getvalue().splitlines())
4889+
4890+ def test_missing_test_with_plan_adds_error(self):
4891+ # A file
4892+ # 1..3
4893+ # ok first test
4894+ # not ok 3 third test
4895+ # results in three tests, with the second being created
4896+ self.tap.write('1..3\n')
4897+ self.tap.write('ok first test\n')
4898+ self.tap.write('not ok 3 third test\n')
4899+ self.tap.seek(0)
4900+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4901+ self.assertEqual(0, result)
4902+ self.assertEqual([
4903+ 'test test 1 first test',
4904+ 'success test 1 first test',
4905+ 'test test 2',
4906+ 'error test 2 [',
4907+ 'test missing from TAP output',
4908+ ']',
4909+ 'test test 3 third test',
4910+ 'failure test 3 third test',
4911+ ],
4912+ self.subunit.getvalue().splitlines())
4913+
4914+ def test_missing_test_no_plan_adds_error(self):
4915+ # A file
4916+ # ok first test
4917+ # not ok 3 third test
4918+ # results in three tests, with the second being created
4919+ self.tap.write('ok first test\n')
4920+ self.tap.write('not ok 3 third test\n')
4921+ self.tap.seek(0)
4922+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4923+ self.assertEqual(0, result)
4924+ self.assertEqual([
4925+ 'test test 1 first test',
4926+ 'success test 1 first test',
4927+ 'test test 2',
4928+ 'error test 2 [',
4929+ 'test missing from TAP output',
4930+ ']',
4931+ 'test test 3 third test',
4932+ 'failure test 3 third test',
4933+ ],
4934+ self.subunit.getvalue().splitlines())
4935+
4936+ def test_four_tests_in_a_row_trailing_plan(self):
4937+ # A file
4938+ # ok 1 - first test in a script with no plan at all
4939+ # not ok 2 - second
4940+ # ok 3 - third
4941+ # not ok 4 - fourth
4942+ # 1..4
4943+ # results in four tests numbered and named
4944+ self.tap.write('ok 1 - first test in a script with trailing plan\n')
4945+ self.tap.write('not ok 2 - second\n')
4946+ self.tap.write('ok 3 - third\n')
4947+ self.tap.write('not ok 4 - fourth\n')
4948+ self.tap.write('1..4\n')
4949+ self.tap.seek(0)
4950+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4951+ self.assertEqual(0, result)
4952+ self.assertEqual([
4953+ 'test test 1 - first test in a script with trailing plan',
4954+ 'success test 1 - first test in a script with trailing plan',
4955+ 'test test 2 - second',
4956+ 'failure test 2 - second',
4957+ 'test test 3 - third',
4958+ 'success test 3 - third',
4959+ 'test test 4 - fourth',
4960+ 'failure test 4 - fourth'
4961+ ],
4962+ self.subunit.getvalue().splitlines())
4963+
4964+ def test_four_tests_in_a_row_with_plan(self):
4965+ # A file
4966+ # 1..4
4967+ # ok 1 - first test in a script with no plan at all
4968+ # not ok 2 - second
4969+ # ok 3 - third
4970+ # not ok 4 - fourth
4971+ # results in four tests numbered and named
4972+ self.tap.write('1..4\n')
4973+ self.tap.write('ok 1 - first test in a script with a plan\n')
4974+ self.tap.write('not ok 2 - second\n')
4975+ self.tap.write('ok 3 - third\n')
4976+ self.tap.write('not ok 4 - fourth\n')
4977+ self.tap.seek(0)
4978+ result = subunit.TAP2SubUnit(self.tap, self.subunit)
4979+ self.assertEqual(0, result)
4980+ self.assertEqual([
4981+ 'test test 1 - first test in a script with a plan',
4982+ 'success test 1 - first test in a script with a plan',
4983+ 'test test 2 - second',
4984+ 'failure test 2 - second',
4985+ 'test test 3 - third',
4986+ 'success test 3 - third',
4987+ 'test test 4 - fourth',
4988+ 'failure test 4 - fourth'
4989+ ],
4990+ self.subunit.getvalue().splitlines())
4991+
4992+ def test_four_tests_in_a_row_no_plan(self):
4993+ # A file
4994+ # ok 1 - first test in a script with no plan at all
4995+ # not ok 2 - second
4996+ # ok 3 - third
4997+ # not ok 4 - fourth
4998+ # results in four tests numbered and named
4999+ self.tap.write('ok 1 - first test in a script with no plan at all\n')
5000+ self.tap.write('not ok 2 - second\n')
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches