Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(343)

Side by Side Diff: scripts/slave/recipe_modules/test_utils/api.py

Issue 313693003: Swarming: conditionally run tests on swarming in chromium_trybot recipe. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 # Copyright 2013 The Chromium Authors. All rights reserved. 1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 from slave import recipe_api 5 from slave import recipe_api
6 6
7 class TestUtilsApi(recipe_api.RecipeApi): 7 class TestUtilsApi(recipe_api.RecipeApi):
8 @staticmethod 8 @staticmethod
9 def format_step_text(data): 9 def format_step_text(data):
10 """ 10 """
(...skipping 22 matching lines...) Expand all
33 else: # pragma: no cover 33 else: # pragma: no cover
34 raise ValueError( 34 raise ValueError(
35 'Expected a one or two-element list, got %r instead.' % section) 35 'Expected a one or two-element list, got %r instead.' % section)
36 return ''.join(step_text) 36 return ''.join(step_text)
37 37
38 class Test(object): 38 class Test(object):
39 """ 39 """
40 Base class for tests that can be retried after deapplying a previously 40 Base class for tests that can be retried after deapplying a previously
41 applied patch. 41 applied patch.
42 """ 42 """
43 # If True, the test supports asynchronous execution. In that case 'trigger'
44 # and 'collect' will be used instead of 'run'. 'trigger' produces a step
45 # that asynchronously launches the test (just starts the test and
46 # immediately returns control back to the recipe), and 'collect' produces
47 # a step that blocks until the test is finished. The recipe can execute any
48 # other steps in between. This mode is used by tests running on Swarming.
49 async = False
43 50
44 @property 51 @property
45 def name(self): # pragma: no cover 52 def name(self): # pragma: no cover
46 """Name of the test.""" 53 """Name of the test."""
47 raise NotImplementedError() 54 raise NotImplementedError()
48 55
49 def run(self, suffix): # pragma: no cover 56 def run(self, suffix): # pragma: no cover
50 """Run the test. suffix is 'with patch' or 'without patch'.""" 57 """Run the test. suffix is 'with patch' or 'without patch'."""
51 raise NotImplementedError() 58 raise NotImplementedError()
52 59
60 def trigger(self, suffix): # pragma: no cover
61 """Launch the test asynchronously, used if self.async == True."""
62 raise NotImplementedError()
63
64 def collect(self, suffix): # pragma: no cover
65 """Wait for triggered test to finish, used if self.async == True."""
66 raise NotImplementedError()
67
53 def has_valid_results(self, suffix): # pragma: no cover 68 def has_valid_results(self, suffix): # pragma: no cover
54 """ 69 """
55 Returns True if results (failures) are valid. 70 Returns True if results (failures) are valid.
56 71
57 This makes it possible to distinguish between the case of no failures 72 This makes it possible to distinguish between the case of no failures
58 and the test failing to even report its results in machine-readable 73 and the test failing to even report its results in machine-readable
59 format. 74 format.
60 """ 75 """
61 raise NotImplementedError() 76 raise NotImplementedError()
62 77
63 def failures(self, suffix): # pragma: no cover 78 def failures(self, suffix): # pragma: no cover
64 """Return list of failures (list of strings).""" 79 """Return list of failures (list of strings)."""
65 raise NotImplementedError() 80 raise NotImplementedError()
66 81
67 def _step_name(self, suffix): 82 def _step_name(self, suffix):
68 """Helper to uniformly combine tests's name with a suffix.""" 83 """Helper to uniformly combine tests's name with a suffix."""
69 return '%s (%s)' % (self.name, suffix) 84 return '%s (%s)' % (self.name, suffix)
70 85
71 def determine_new_failures(self, tests, deapply_patch_fn): 86 def determine_new_failures(self, tests, deapply_patch_fn):
72 """ 87 """
73 Utility function for running steps with a patch applied, and retrying 88 Utility function for running steps with a patch applied, and retrying
74 failing steps without the patch. Failures from the run without the patch are 89 failing steps without the patch. Failures from the run without the patch are
75 ignored. 90 ignored.
76 91
77 Args: 92 Args:
78 tests - iterable of objects implementing the Test interface above 93 tests - iterable of objects implementing the Test interface above
79 deapply_patch_fn - function that takes a list of failing tests 94 deapply_patch_fn - function that takes a list of failing tests
80 and undoes any effect of the previously applied patch 95 and undoes any effect of the previously applied patch
81 """ 96 """
97 # Convert iterable to list, since it is enumerated multiple times.
98 tests = list(tests)
99
82 if self.m.step_history.failed: 100 if self.m.step_history.failed:
83 yield self.m.python.inline( 101 yield self.m.python.inline(
84 'Aborting due to failed build state.', 102 'Aborting due to failed build state.',
85 "import sys; sys.exit(1)", 103 "import sys; sys.exit(1)",
86 always_run=True, abort_on_failure=True) 104 always_run=True, abort_on_failure=True)
87 return # won't actually hit this, but be explicit 105 return # won't actually hit this, but be explicit
88 106
89 yield (t.run('with patch') for t in tests) 107 def run(prefix, tests):
108 """Runs synchronous and asynchronous tests (at the same time).
109
110 Asynchronous tests are launched first (just launched, not being blocked
111 on). While they are running, the recipe blocks on synchronous tests
112 (sequentially, one by one). And then finally waits for all asynchronous
113 tests to finish. Effectively asynchronous tests are running in parallel
114 with each other and with synchronous tests.
115 """
116 # Trigger all async tests first, so that they are running in parallel
Paweł Hajdan Jr. 2014/06/10 08:36:56 Could you make one design change here? Instead of
Vadim Sh. 2014/06/12 01:00:08 Can you provide any valid use case for pre_run or
Paweł Hajdan Jr. 2014/06/12 11:16:10 Given the three-phase run it seems it would always
Vadim Sh. 2014/06/12 18:34:10 A test can't be synchronous and asynchronous at th
117 # with synchronous tests.
118 yield (t.trigger(prefix) for t in tests if t.async)
119 # Now block on all synchronous tests.
120 yield (t.run(prefix) for t in tests if not t.async)
121 # And finally wait for all pending asynchronous tests to complete.
122 yield (t.collect(prefix) for t in tests if t.async)
123
124 yield run('with patch', tests)
90 125
91 failing_tests = [] 126 failing_tests = []
92 for t in tests: 127 for t in tests:
93 if not t.has_valid_results('with patch'): 128 if not t.has_valid_results('with patch'):
94 yield self.m.python.inline( 129 yield self.m.python.inline(
95 t.name, 130 t.name,
96 r""" 131 r"""
97 import sys 132 import sys
98 print 'TEST RESULTS WERE INVALID' 133 print 'TEST RESULTS WERE INVALID'
99 sys.exit(1) 134 sys.exit(1)
100 """, 135 """,
101 always_run=True) 136 always_run=True)
102 elif t.failures('with patch'): 137 elif t.failures('with patch'):
103 failing_tests.append(t) 138 failing_tests.append(t)
104 if not failing_tests: 139 if not failing_tests:
105 return 140 return
106 141
107 yield deapply_patch_fn(failing_tests) 142 yield deapply_patch_fn(failing_tests)
108 143
109 yield (t.run('without patch') for t in failing_tests) 144 yield run('without patch', failing_tests)
110 yield (self._summarize_retried_test(t) for t in failing_tests) 145 yield (self._summarize_retried_test(t) for t in failing_tests)
111 146
112 def _summarize_retried_test(self, test): 147 def _summarize_retried_test(self, test):
113 if not test.has_valid_results('without patch'): 148 if not test.has_valid_results('without patch'):
114 return self.m.python.inline( 149 return self.m.python.inline(
115 test.name, 150 test.name,
116 r""" 151 r"""
117 import sys 152 import sys
118 print 'TEST RESULTS WERE INVALID' 153 print 'TEST RESULTS WERE INVALID'
119 sys.exit(1) 154 sys.exit(1)
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
159 """, 194 """,
160 args=[ 195 args=[
161 self.m.json.input({ 196 self.m.json.input({
162 'new': list(new_failures), 197 'new': list(new_failures),
163 'ignored': list(ignored_failures), 198 'ignored': list(ignored_failures),
164 }) 199 })
165 ], 200 ],
166 followup_fn=followup_fn, 201 followup_fn=followup_fn,
167 always_run=True, 202 always_run=True,
168 ) 203 )
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698