Merge lp:~bac/launchpad/retest into lp:launchpad

Proposed by Brad Crittenden
Status: Merged
Approved by: Barry Warsaw
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~bac/launchpad/retest
Merge into: lp:launchpad
Diff against target: 83 lines
1 file modified
utilities/retest.py (+79/-0)
To merge this branch: bzr merge lp:~bac/launchpad/retest
Reviewer Review Type Date Requested Status
Barry Warsaw (community) Approve
Review via email: mp+12421@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

= Summary =

Adding a new utility to make re-running a failed test set easy.

== Proposed fix ==

utilities/retest.py will take the output of a previous run with test failures and
re-run them all. It looks for numbered tests 'stories/foo/01-dosomething.txt' and
will run the entire story directory.

== Pre-implementation notes ==

Talked briefly to Gary who thought the script belongs in utilities.

== Implementation details ==

None.

== Tests ==

Take a previous run with failures and feed it to the script. You can use the email
sent from ec2 as input, e.g.

% utilities/retest.py test.out

== Demo and Q/A ==

As above.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  utilities/retest.py

Revision history for this message
Barry Warsaw (barry) wrote :

On Sep 25, 2009, at 02:40 PM, Brad Crittenden wrote:

>Adding a new utility to make re-running a failed test set easy.

Okay, you are my new best friend! Thanks for writing this awesome utility.
I have some minor comments about style, but merge-conditional r=me with their
consideration.

 review approve
 status approve

=== added file 'utilities/retest.py'
--- utilities/retest.py 1970-01-01 00:00:00 +0000
+++ utilities/retest.py 2009-09-25 14:40:29 +0000
> @@ -0,0 +1,80 @@
> +#!/usr/bin/env python
> +#
> +# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +"""
> +Given an error report, run all of the failed tests again.
> +
> +For instance, it can be used in the following scenario:
> +% bin/test -vvm lp.registry | tee test.out
> +% # Oh nos! Failures!
> +% # Fix tests.
> +% utilities/retest.py test.out
> +"""
> +
> +import subprocess
> +import sys
> +import re
> +from pprint import pprint
> +
> +# Regular expression to match numbered stories.
> +STORY_RE = re.compile("(.*)/\d{2}-.*")
> +
> +
> +def getTestName(test):
> + """Get the test name of a failed test.
> +
> + If the test is part of a numbered story,
> + e.g. 'stories/gpg-coc/01-claimgpgp.txt', then return the directory name
> + since all of the stories must be run together.
> + """
> + match = STORY_RE.match(test)
> + if match:
> + return match.group(1)
> + return test
> +
> +
> +def extractTests(fd):
> + """Get the set of tests to be run.
> +
> + Given an open file descriptor pointing to a test summary report, find all
> + of the tests to be run.
> + """

Isn't fd really a file object, not a file descriptor? Also, to avoid
abbreviations maybe 'input_file' is a better name?

> + failed_tests = set()
> + reading_tests = False
> + line = True
> + while (line):
> + line = fd.readline()

You can do this more efficiently like so

    for line in input_file:

> + if line.startswith('Tests with failures:'):
> + reading_tests = True
> + continue
> + if reading_tests:
> + if line.startswith('Total:'):
> + break
> + test = getTestName(line.strip())
> + failed_tests.add(test)
> + return failed_tests
> +
> +
> +def run_tests(tests):
> + """Given a set of tests, run them as one group."""
> + cmd = ['bin/test', '-vv']
> + print "Running tests:"
> + pprint(sorted(list(tests)))
> + for test in tests:
> + cmd.append('-t')
> + cmd.append(test)
> + p = subprocess.Popen(cmd)
> + p.wait()
> +
> +
> +if __name__ == '__main__':
> + try:
> + log_file = sys.argv[1]
> + except IndexError:
> + print "Usage: %s test_output_file" % (sys.argv[0])
> + sys.exit(-1)
> + fd = open(log_file, 'r')
> + failed_tests = extractTests(fd)
> + run_tests(failed_tests)

review: Approve
Revision history for this message
Brad Crittenden (bac) wrote :

On Sep 25, 2009, at 11:39 , Barry Warsaw wrote:

> Review: Approve
> On Sep 25, 2009, at 02:40 PM, Brad Crittenden wrote:
>
>> Adding a new utility to make re-running a failed test set easy.
>
> Okay, you are my new best friend! Thanks for writing this awesome
> utility.
> I have some minor comments about style, but merge-conditional r=me
> with their
> consideration.
>
> review approve
> status approve

Thanks!

>
> === added file 'utilities/retest.py'
> --- utilities/retest.py 1970-01-01 00:00:00 +0000
> +++ utilities/retest.py 2009-09-25 14:40:29 +0000
>> +def extractTests(fd):
>> + """Get the set of tests to be run.
>> +
>> + Given an open file descriptor pointing to a test summary
>> report, find all
>> + of the tests to be run.
>> + """
>
> Isn't fd really a file object, not a file descriptor? Also, to avoid
> abbreviations maybe 'input_file' is a better name?

Right. Changed.

>
>> + failed_tests = set()
>> + reading_tests = False
>> + line = True
>> + while (line):
>> + line = fd.readline()
>
> You can do this more efficiently like so
>
> for line in input_file:

Fixed.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'utilities/retest.py'
2--- utilities/retest.py 1970-01-01 00:00:00 +0000
3+++ utilities/retest.py 2009-09-26 01:04:14 +0000
4@@ -0,0 +1,79 @@
5+#!/usr/bin/env python
6+#
7+# Copyright 2009 Canonical Ltd. This software is licensed under the
8+# GNU Affero General Public License version 3 (see the file LICENSE).
9+
10+"""
11+Given an error report, run all of the failed tests again.
12+
13+For instance, it can be used in the following scenario:
14+% bin/test -vvm lp.registry | tee test.out
15+% # Oh nos! Failures!
16+% # Fix tests.
17+% utilities/retest.py test.out
18+"""
19+
20+import subprocess
21+import sys
22+import re
23+from pprint import pprint
24+
25+
26+# Regular expression to match numbered stories.
27+STORY_RE = re.compile("(.*)/\d{2}-.*")
28+
29+
30+def getTestName(test):
31+ """Get the test name of a failed test.
32+
33+ If the test is part of a numbered story,
34+ e.g. 'stories/gpg-coc/01-claimgpgp.txt', then return the directory name
35+ since all of the stories must be run together.
36+ """
37+ match = STORY_RE.match(test)
38+ if match:
39+ return match.group(1)
40+ return test
41+
42+
43+def extractTests(input_file):
44+ """Get the set of tests to be run.
45+
46+ Given a file object for a test summary report, find all of the tests to be
47+ run.
48+ """
49+ failed_tests = set()
50+ reading_tests = False
51+ for line in input_file:
52+ if line.startswith('Tests with failures:'):
53+ reading_tests = True
54+ continue
55+ if reading_tests:
56+ if line.startswith('Total:'):
57+ break
58+ test = getTestName(line.strip())
59+ failed_tests.add(test)
60+ return failed_tests
61+
62+
63+def run_tests(tests):
64+ """Given a set of tests, run them as one group."""
65+ cmd = ['bin/test', '-vv']
66+ print "Running tests:"
67+ pprint(sorted(list(tests)))
68+ for test in tests:
69+ cmd.append('-t')
70+ cmd.append(test)
71+ p = subprocess.Popen(cmd)
72+ p.wait()
73+
74+
75+if __name__ == '__main__':
76+ try:
77+ log_file = sys.argv[1]
78+ except IndexError:
79+ print "Usage: %s test_output_file" % (sys.argv[0])
80+ sys.exit(-1)
81+ fd = open(log_file, 'r')
82+ failed_tests = extractTests(fd)
83+ run_tests(failed_tests)