Merge lp:~frankban/launchpad/tests-randomization into lp:launchpad

Proposed by Francesco Banconi on 2012-06-07
Status: Merged
Approved by: Graham Binns on 2012-06-07
Approved revision: no longer in the source branch.
Merged at revision: 15380
Proposed branch: lp:~frankban/launchpad/tests-randomization
Merge into: lp:launchpad
Diff against target: 119 lines (+37/-14)
3 files modified
buildout-templates/bin/test.in (+1/-1)
lib/lp/services/testing/customresult.py (+11/-1)
lib/lp/services/testing/tests/test_customresult.py (+25/-12)
To merge this branch: bzr merge lp:~frankban/launchpad/tests-randomization
Reviewer Review Type Date Requested Status
Graham Binns (community) code 2012-06-07 Approve on 2012-06-07
Review via email: mp+109103@code.launchpad.net

Commit Message

Fixed the way test cases in --load-list are randomized: now -t and --load-list produce the same random order, given the same shuffle seed.

Description of the Change

= Summary =

bin/test --shuffle --shuffle-seed [seed] can be used to reproduce the same random ordering of test cases in multiple calls. However, this ordering changes if you use --load-list rather than -t.

== Proposed fix ==

--load-list, if shuffle is requested, should generate the initial list of test cases to be shuffled ordering them by id.

== Implementation details ==

The added optional argument `reorder_tests` of lp.services.testing.customresult.filter_tests` can be used to avoid preserving the ordering of the original test cases (as they appear in the given file).
bin/test now calls `filter_tests` passing reorder_tests=True if --shuffle is used.

== Tests ==

bin/test -cvvt lp.services.testing.tests

== Demo and Q/A ==

1. Save a file mytests with this content:

lp.soyuz.tests.test_packagecopyjob.PlainPackageCopyJobTests.test_copying_closes_bugs
lp.bugs.scripts.tests.test_bugimport.ImportBugTestCase.test_import_bug
lp.scripts.tests.test_garbo.TestGarbo.test_OAuthNoncePruner
lp.bugs.scripts.tests.test_bugnotification.TestEmailNotificationsAttachments.test_added_seen

2. check --load-list still preserves test cases ordering:

$ bin/test -cvv --load-list mytests
...
  Running:
 lp.soyuz.tests.test_packagecopyjob.PlainPackageCopyJobTests.test_copying_closes_bugs
 lp.bugs.scripts.tests.test_bugimport.ImportBugTestCase.test_import_bug
 lp.scripts.tests.test_garbo.TestGarbo.test_OAuthNoncePruner
 lp.bugs.scripts.tests.test_bugnotification.TestEmailNotificationsAttachments.test_added_seen
  Ran 4 tests with 0 failures and 0 errors in 4.595 seconds.
...

3. shuffle tests, and copy the used seed:

$ bin/test -cvv --load-list mytests --shuffle
...
  Running:
 lp.scripts.tests.test_garbo.TestGarbo.test_OAuthNoncePruner
 lp.bugs.scripts.tests.test_bugnotification.TestEmailNotificationsAttachments.test_added_seen
 lp.bugs.scripts.tests.test_bugimport.ImportBugTestCase.test_import_bug
 lp.soyuz.tests.test_packagecopyjob.PlainPackageCopyJobTests.test_copying_closes_bugs
  Ran 4 tests with 0 failures and 0 errors in 4.520 seconds.
...
Tests were shuffled using seed number 342800994878.

4. Run the same tests using -t and the same shuffle seed:

$ bin/test -cvv -t lp.soyuz.tests.test_packagecopyjob.PlainPackageCopyJobTests.test_copying_closes_bugs -t lp.bugs.scripts.tests.test_bugimport.ImportBugTestCase.test_import_bug -t lp.scripts.tests.test_garbo.TestGarbo.test_OAuthNoncePruner -t lp.bugs.scripts.tests.test_bugnotification.TestEmailNotificationsAttachments.test_added_seen --shuffle --shuffle-seed 342800994878

The test ordering should be the same as the previous call.

NO QA

== lint ==

Linting changed files:
  buildout-templates/bin/test.in
  lib/lp/services/testing/customresult.py
  lib/lp/services/testing/tests/test_customresult.py

To post a comment you must log in.
Graham Binns (gmb) :
review: Approve (code)
Francesco Banconi (frankban) wrote :

Thanks Graham.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'buildout-templates/bin/test.in'
2--- buildout-templates/bin/test.in 2011-12-30 06:47:17 +0000
3+++ buildout-templates/bin/test.in 2012-06-07 10:59:20 +0000
4@@ -220,7 +220,7 @@
5 sys.exit(main(sys.argv))
6
7 def load_list(option, opt_str, list_name, parser):
8- patch_find_tests(filter_tests(list_name))
9+ patch_find_tests(filter_tests(list_name, '--shuffle' in sys.argv))
10 options.parser.add_option(
11 '--load-list', type=str, action='callback', callback=load_list)
12 options.parser.add_option(
13
14=== modified file 'lib/lp/services/testing/customresult.py'
15--- lib/lp/services/testing/customresult.py 2012-06-01 15:00:25 +0000
16+++ lib/lp/services/testing/customresult.py 2012-06-07 10:59:20 +0000
17@@ -31,11 +31,13 @@
18 find.find_tests = find_tests
19
20
21-def filter_tests(list_name):
22+def filter_tests(list_name, reorder_tests=False):
23 """Create a hook for `patch_find_tests` that filters tests based on id.
24
25 :param list_name: A filename that contains a newline-separated list of
26 test ids, as generated by `list_tests`.
27+ :param reorder_tests: if True, the tests contained in `list_name`
28+ are reordered by id. Default is False: the ordering is preserved.
29 :return: A callable that takes a result of `testrunner.find_tests` and
30 returns only those tests with ids in the file 'list_name'.
31
32@@ -46,12 +48,20 @@
33 layers are run and there is nothing to be done about that here. In
34 practice the layers are seen to be run in the same order.
35
36+ However, test cases can still be reordered if `reorder_tests` is set to
37+ True: this is useful when tests are shuffled and the test shuffler is
38+ initialized using a particoular value. This way the same seed produces
39+ the same random ordering, regardless of whether the tests are filtered
40+ using -t or --load-list.
41+
42 Should a test be listed, but not present in any of the suites, it is
43 silently ignored.
44 """
45 def do_filter(tests_by_layer_name):
46 # Read the tests, filtering out any blank lines.
47 tests = filter(None, [line.strip() for line in open(list_name, 'rb')])
48+ if reorder_tests:
49+ tests.sort()
50 test_lookup = {}
51 # Multiple unique testcases can be represented by a single id and they
52 # must be tracked separately.
53
54=== modified file 'lib/lp/services/testing/tests/test_customresult.py'
55--- lib/lp/services/testing/tests/test_customresult.py 2012-06-01 15:26:17 +0000
56+++ lib/lp/services/testing/tests/test_customresult.py 2012-06-07 10:59:20 +0000
57@@ -40,20 +40,23 @@
58 f.flush()
59
60 @staticmethod
61+ def make_suite(testnames=string.lowercase):
62+ """Make a suite containing `testnames` (default: 'a'..'z')."""
63+ suite = unittest.TestSuite()
64+ for testname in testnames:
65+ suite.addTest(FakeTestCase(testname))
66+ return suite
67+
68+ @staticmethod
69 def make_suites():
70 """Make two suites.
71
72 The first has 'a'..'m' and the second 'n'..'z'.
73 """
74- suite_am = unittest.TestSuite()
75- suite_nz = unittest.TestSuite()
76- # Create one layer with the 'a'..'m'.
77- for letter in string.lowercase[:13]:
78- suite_am.addTest(FakeTestCase(letter))
79- # And another layer with 'n'..'z'.
80- for letter in string.lowercase[13:]:
81- suite_nz.addTest(FakeTestCase(letter))
82- return suite_am, suite_nz
83+ return (
84+ TestFilterTests.make_suite(string.lowercase[:13]),
85+ TestFilterTests.make_suite(string.lowercase[13:]),
86+ )
87
88 @staticmethod
89 def make_repeated_suite(testnames):
90@@ -68,9 +71,7 @@
91 # Tests should be returned in the order seen in the testfile.
92 layername = 'layer-1'
93 testnames = ['d', 'c', 'a']
94- suite = unittest.TestSuite()
95- for letter in string.lowercase:
96- suite.addTest(FakeTestCase(letter))
97+ suite = self.make_suite()
98 with tempfile.NamedTemporaryFile() as f:
99 self.writeFile(f, testnames)
100 do_filter = filter_tests(f.name)
101@@ -80,6 +81,18 @@
102 suite = results[layername]
103 self.assertEqual(testnames, [t.id() for t in suite])
104
105+ def test_reorder_tests(self):
106+ # Tests can optionally be ordered by id.
107+ layername = 'layer-1'
108+ testnames = ['d', 'c', 'a']
109+ suite = self.make_suite()
110+ with tempfile.NamedTemporaryFile() as f:
111+ self.writeFile(f, testnames)
112+ do_filter = filter_tests(f.name, reorder_tests=True)
113+ results = do_filter({layername: suite})
114+ suite = results[layername]
115+ self.assertEqual(sorted(testnames), [t.id() for t in suite])
116+
117 def test_layer_separation(self):
118 # Tests must be kept in their layer.
119 suite1, suite2 = self.make_suites()