Merge lp:~bac/launchpad/ordered-load-list into lp:launchpad

Proposed by Brad Crittenden on 2012-05-01
Status: Merged
Approved by: Gary Poster on 2012-05-01
Approved revision: no longer in the source branch.
Merged at revision: 15186
Proposed branch: lp:~bac/launchpad/ordered-load-list
Merge into: lp:launchpad
Diff against target: 61 lines (+24/-9)
1 file modified
lib/lp/services/testing/customresult.py (+24/-9)
To merge this branch: bzr merge lp:~bac/launchpad/ordered-load-list
Reviewer Review Type Date Requested Status
Gary Poster (community) 2012-05-01 Approve on 2012-05-01
Review via email: mp+104287@code.launchpad.net

Commit Message

Change bin/test --load-list to preserve the order presented in the original test list, at least within layers.

Description of the Change

= Summary =

"bin/test --load-list <fn>" loads the tests but then does not respect
their ordering due to the use of set() and the way the tests are
assigned to layers.

The approach presently taken makes sense from an efficiency standpoint
but at the cost of ordering. One of the great uses of --load-list is
to repeat a test run to find test isolation problems and that
utility is defeated if the ordering is not maintained.

== Proposed fix ==

Change 'filter_tests' to maintain order within a layer.

The order in which the layers are run may change, though that has not
been seen in practice. Should the layer ordering change, the tests
are still run in order per layer, which is probably sufficient for
isolating test failures.

== Pre-implementation notes ==

Chat with Gary.

== Implementation details ==

As above.

== Tests ==

bin/test --load-list in.txt --list-tests > out.txt

Then verify in.txt and out.txt are the same modulo the "Listing layer"
messages and any layer rearrangement issues as described above.

== Demo and Q/A ==

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/services/testing/customresult.py

To post a comment you must log in.
Gary Poster (gary) wrote :

Cool, thank you. This will be a big help.

A brief summary of our IRC conversation follows. All of these are negotiable.

 - I don't think we should raise an exception if LP no longer has a test in the test list. The previous code did not have that behavior.

- ordered_layers isn't doing anything, and we should delete it.

- The suites_by_layer data structure is unnecessarily inefficient for the question you want to answer. If instead you build a map of test name to (layer, test), you can replace find_layers with a single hash lookup.

- We should include a comment as to why the code is maintaining order, so that future code navigators will have a clue as to intent.

Brad Crittenden (bac) wrote :

Thanks for the review Gary. I incorporated all of your suggestions.

Gary Poster (gary) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/services/testing/customresult.py'
2--- lib/lp/services/testing/customresult.py 2012-01-01 02:58:52 +0000
3+++ lib/lp/services/testing/customresult.py 2012-05-01 20:53:23 +0000
4@@ -11,7 +11,6 @@
5
6 from unittest import TestSuite
7
8-from testtools import iterate_tests
9 from zope.testing.testrunner import find
10
11
12@@ -25,8 +24,10 @@
13 structure.
14 """
15 real_find_tests = find.find_tests
16+
17 def find_tests(*args):
18 return hook(real_find_tests(*args))
19+
20 find.find_tests = find_tests
21
22
23@@ -37,16 +38,30 @@
24 test ids, as generated by `list_tests`.
25 :return: A callable that takes a result of `testrunner.find_tests` and
26 returns only those tests with ids in the file 'list_name'.
27+
28+ Note great care is taken to preserve the ordering of the original test
29+ cases, which is quite important if trying to figure out test isolation
30+ bugs. The original ordering is maintained within layers but since the
31+ results are returned as a dictionary the caller may shuffle the way the
32+ layers are run and there is nothing to be done about that here. In
33+ practice the layers are seen to be run in the same order.
34+
35+ Should a test be listed, but not present in any of the suites, it is
36+ silently ignored.
37 """
38 def do_filter(tests_by_layer_name):
39- tests = sorted(set(line.strip() for line in open(list_name, 'rb')))
40+ # Read the tests, filtering out any blank lines.
41+ tests = filter(None, [line.strip() for line in open(list_name, 'rb')])
42+ test_lookup = {}
43+ for layer_name, suite in tests_by_layer_name.iteritems():
44+ for testcase in suite:
45+ test_lookup[testcase.id()] = (testcase, layer_name)
46+
47 result = {}
48- for layer_name, suite in tests_by_layer_name.iteritems():
49- new_suite = TestSuite()
50- for test in iterate_tests(suite):
51- if test.id() in tests:
52- new_suite.addTest(test)
53- if new_suite.countTestCases():
54- result[layer_name] = new_suite
55+ for testname in tests:
56+ testcase, layer = test_lookup.get(testname, (None, None))
57+ if layer is not None:
58+ suite = result.setdefault(layer, TestSuite())
59+ suite.addTest(testcase)
60 return result
61 return do_filter