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

Side by Side Diff: Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner.py

Issue 546133003: Reformat webkitpy.layout_tests w/ format-webkitpy. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 6 years, 3 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 (C) 2011 Google Inc. All rights reserved. 1 # Copyright (C) 2011 Google Inc. All rights reserved.
2 # 2 #
3 # Redistribution and use in source and binary forms, with or without 3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are 4 # modification, are permitted provided that the following conditions are
5 # met: 5 # met:
6 # 6 #
7 # * Redistributions of source code must retain the above copyright 7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer. 8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above 9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer 10 # copyright notice, this list of conditions and the following disclaimer
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
43 _log = logging.getLogger(__name__) 43 _log = logging.getLogger(__name__)
44 44
45 45
46 TestExpectations = test_expectations.TestExpectations 46 TestExpectations = test_expectations.TestExpectations
47 47
48 # Export this so callers don't need to know about message pools. 48 # Export this so callers don't need to know about message pools.
49 WorkerException = message_pool.WorkerException 49 WorkerException = message_pool.WorkerException
50 50
51 51
52 class TestRunInterruptedException(Exception): 52 class TestRunInterruptedException(Exception):
53
53 """Raised when a test run should be stopped immediately.""" 54 """Raised when a test run should be stopped immediately."""
55
54 def __init__(self, reason): 56 def __init__(self, reason):
55 Exception.__init__(self) 57 Exception.__init__(self)
56 self.reason = reason 58 self.reason = reason
57 self.msg = reason 59 self.msg = reason
58 60
59 def __reduce__(self): 61 def __reduce__(self):
60 return self.__class__, (self.reason,) 62 return self.__class__, (self.reason,)
61 63
62 64
63 class LayoutTestRunner(object): 65 class LayoutTestRunner(object):
66
64 def __init__(self, options, port, printer, results_directory, test_is_slow_f n): 67 def __init__(self, options, port, printer, results_directory, test_is_slow_f n):
65 self._options = options 68 self._options = options
66 self._port = port 69 self._port = port
67 self._printer = printer 70 self._printer = printer
68 self._results_directory = results_directory 71 self._results_directory = results_directory
69 self._test_is_slow = test_is_slow_fn 72 self._test_is_slow = test_is_slow_fn
70 self._sharder = Sharder(self._port.split_test, self._options.max_locked_ shards) 73 self._sharder = Sharder(self._port.split_test, self._options.max_locked_ shards)
71 self._filesystem = self._port.host.filesystem 74 self._filesystem = self._port.host.filesystem
72 75
73 self._expectations = None 76 self._expectations = None
(...skipping 17 matching lines...) Expand all
91 if not retrying: 94 if not retrying:
92 self._printer.print_expected(run_results, self._expectations.get_tes ts_with_result_type) 95 self._printer.print_expected(run_results, self._expectations.get_tes ts_with_result_type)
93 96
94 for test_name in set(tests_to_skip): 97 for test_name in set(tests_to_skip):
95 result = test_results.TestResult(test_name) 98 result = test_results.TestResult(test_name)
96 result.type = test_expectations.SKIP 99 result.type = test_expectations.SKIP
97 run_results.add(result, expected=True, test_is_slow=self._test_is_sl ow(test_name)) 100 run_results.add(result, expected=True, test_is_slow=self._test_is_sl ow(test_name))
98 101
99 self._printer.write_update('Sharding tests ...') 102 self._printer.write_update('Sharding tests ...')
100 locked_shards, unlocked_shards = self._sharder.shard_tests(test_inputs, 103 locked_shards, unlocked_shards = self._sharder.shard_tests(test_inputs,
101 int(self._options.child_processes), self._options.fully_parallel, 104 int(self._opt ions.child_processes), self._options.fully_parallel,
102 self._options.run_singly or (self._options.batch_size == 1)) 105 self._options .run_singly or (self._options.batch_size == 1))
103 106
104 # We don't have a good way to coordinate the workers so that they don't 107 # We don't have a good way to coordinate the workers so that they don't
105 # try to run the shards that need a lock. The easiest solution is to 108 # try to run the shards that need a lock. The easiest solution is to
106 # run all of the locked shards first. 109 # run all of the locked shards first.
107 all_shards = locked_shards + unlocked_shards 110 all_shards = locked_shards + unlocked_shards
108 num_workers = min(num_workers, len(all_shards)) 111 num_workers = min(num_workers, len(all_shards))
109 self._printer.print_workers_and_shards(num_workers, len(all_shards), len (locked_shards)) 112 self._printer.print_workers_and_shards(num_workers, len(all_shards), len (locked_shards))
110 113
111 if self._options.dry_run: 114 if self._options.dry_run:
112 return run_results 115 return run_results
113 116
114 self._printer.write_update('Starting %s ...' % grammar.pluralize('worker ', num_workers)) 117 self._printer.write_update('Starting %s ...' % grammar.pluralize('worker ', num_workers))
115 118
116 start_time = time.time() 119 start_time = time.time()
117 try: 120 try:
118 with message_pool.get(self, self._worker_factory, num_workers, self. _port.host) as pool: 121 with message_pool.get(self, self._worker_factory, num_workers, self. _port.host) as pool:
119 pool.run(('test_list', shard.name, shard.test_inputs) for shard in all_shards) 122 pool.run(('test_list', shard.name, shard.test_inputs) for shard in all_shards)
120 123
121 if self._shards_to_redo: 124 if self._shards_to_redo:
122 num_workers -= len(self._shards_to_redo) 125 num_workers -= len(self._shards_to_redo)
123 if num_workers > 0: 126 if num_workers > 0:
124 with message_pool.get(self, self._worker_factory, num_worker s, self._port.host) as pool: 127 with message_pool.get(self, self._worker_factory, num_worker s, self._port.host) as pool:
125 pool.run(('test_list', shard.name, shard.test_inputs) fo r shard in self._shards_to_redo) 128 pool.run(('test_list', shard.name, shard.test_inputs) fo r shard in self._shards_to_redo)
126 except TestRunInterruptedException, e: 129 except TestRunInterruptedException as e:
127 _log.warning(e.reason) 130 _log.warning(e.reason)
128 run_results.interrupted = True 131 run_results.interrupted = True
129 except KeyboardInterrupt: 132 except KeyboardInterrupt:
130 self._printer.flush() 133 self._printer.flush()
131 self._printer.writeln('Interrupted, exiting ...') 134 self._printer.writeln('Interrupted, exiting ...')
132 run_results.keyboard_interrupted = True 135 run_results.keyboard_interrupted = True
133 except Exception, e: 136 except Exception as e:
134 _log.debug('%s("%s") raised, exiting' % (e.__class__.__name__, str(e ))) 137 _log.debug('%s("%s") raised, exiting' % (e.__class__.__name__, str(e )))
135 raise 138 raise
136 finally: 139 finally:
137 run_results.run_time = time.time() - start_time 140 run_results.run_time = time.time() - start_time
138 141
139 return run_results 142 return run_results
140 143
141 def _worker_factory(self, worker_connection): 144 def _worker_factory(self, worker_connection):
142 results_directory = self._results_directory 145 results_directory = self._results_directory
143 if self._retrying: 146 if self._retrying:
144 self._filesystem.maybe_make_directory(self._filesystem.join(self._re sults_directory, 'retries')) 147 self._filesystem.maybe_make_directory(self._filesystem.join(self._re sults_directory, 'retries'))
145 results_directory = self._filesystem.join(self._results_directory, ' retries') 148 results_directory = self._filesystem.join(self._results_directory, ' retries')
146 return Worker(worker_connection, results_directory, self._options) 149 return Worker(worker_connection, results_directory, self._options)
147 150
148 def _mark_interrupted_tests_as_skipped(self, run_results): 151 def _mark_interrupted_tests_as_skipped(self, run_results):
149 for test_input in self._test_inputs: 152 for test_input in self._test_inputs:
150 if test_input.test_name not in run_results.results_by_name: 153 if test_input.test_name not in run_results.results_by_name:
151 result = test_results.TestResult(test_input.test_name, [test_fai lures.FailureEarlyExit()]) 154 result = test_results.TestResult(test_input.test_name, [test_fai lures.FailureEarlyExit()])
152 # FIXME: We probably need to loop here if there are multiple ite rations. 155 # FIXME: We probably need to loop here if there are multiple ite rations.
153 # FIXME: Also, these results are really neither expected nor une xpected. We probably 156 # FIXME: Also, these results are really neither expected nor une xpected. We probably
154 # need a third type of result. 157 # need a third type of result.
155 run_results.add(result, expected=False, test_is_slow=self._test_ is_slow(test_input.test_name)) 158 run_results.add(result, expected=False, test_is_slow=self._test_ is_slow(test_input.test_name))
156 159
157 def _interrupt_if_at_failure_limits(self, run_results): 160 def _interrupt_if_at_failure_limits(self, run_results):
158 # Note: The messages in this method are constructed to match old-run-web kit-tests 161 # Note: The messages in this method are constructed to match old-run-web kit-tests
159 # so that existing buildbot grep rules work. 162 # so that existing buildbot grep rules work.
160 def interrupt_if_at_failure_limit(limit, failure_count, run_results, mes sage): 163 def interrupt_if_at_failure_limit(limit, failure_count, run_results, mes sage):
161 if limit and failure_count >= limit: 164 if limit and failure_count >= limit:
162 message += " %d tests run." % (run_results.expected + run_result s.unexpected) 165 message += ' %d tests run.' % (run_results.expected + run_result s.unexpected)
163 self._mark_interrupted_tests_as_skipped(run_results) 166 self._mark_interrupted_tests_as_skipped(run_results)
164 raise TestRunInterruptedException(message) 167 raise TestRunInterruptedException(message)
165 168
166 interrupt_if_at_failure_limit( 169 interrupt_if_at_failure_limit(
167 self._options.exit_after_n_failures, 170 self._options.exit_after_n_failures,
168 run_results.unexpected_failures, 171 run_results.unexpected_failures,
169 run_results, 172 run_results,
170 "Exiting early after %d failures." % run_results.unexpected_failures ) 173 'Exiting early after %d failures.' % run_results.unexpected_failures )
171 interrupt_if_at_failure_limit( 174 interrupt_if_at_failure_limit(
172 self._options.exit_after_n_crashes_or_timeouts, 175 self._options.exit_after_n_crashes_or_timeouts,
173 run_results.unexpected_crashes + run_results.unexpected_timeouts, 176 run_results.unexpected_crashes + run_results.unexpected_timeouts,
174 run_results, 177 run_results,
175 # This differs from ORWT because it does not include WebProcess cras hes. 178 # This differs from ORWT because it does not include WebProcess cras hes.
176 "Exiting early after %d crashes and %d timeouts." % (run_results.une xpected_crashes, run_results.unexpected_timeouts)) 179 'Exiting early after %d crashes and %d timeouts.' % (run_results.une xpected_crashes, run_results.unexpected_timeouts))
177 180
178 def _update_summary_with_result(self, run_results, result): 181 def _update_summary_with_result(self, run_results, result):
179 expected = self._expectations.matches_an_expected_result(result.test_nam e, result.type, self._options.pixel_tests or result.reftest_type, self._options. enable_sanitizer) 182 expected = self._expectations.matches_an_expected_result(
183 result.test_name,
184 result.type,
185 self._options.pixel_tests or result.reftest_type,
186 self._options.enable_sanitizer)
180 exp_str = self._expectations.get_expectations_string(result.test_name) 187 exp_str = self._expectations.get_expectations_string(result.test_name)
181 got_str = self._expectations.expectation_to_string(result.type) 188 got_str = self._expectations.expectation_to_string(result.type)
182 189
183 if result.device_failed: 190 if result.device_failed:
184 self._printer.print_finished_test(result, False, exp_str, "Aborted") 191 self._printer.print_finished_test(result, False, exp_str, 'Aborted')
185 return 192 return
186 193
187 run_results.add(result, expected, self._test_is_slow(result.test_name)) 194 run_results.add(result, expected, self._test_is_slow(result.test_name))
188 self._printer.print_finished_test(result, expected, exp_str, got_str) 195 self._printer.print_finished_test(result, expected, exp_str, got_str)
189 self._interrupt_if_at_failure_limits(run_results) 196 self._interrupt_if_at_failure_limits(run_results)
190 197
191 def handle(self, name, source, *args): 198 def handle(self, name, source, *args):
192 method = getattr(self, '_handle_' + name) 199 method = getattr(self, '_handle_' + name)
193 if method: 200 if method:
194 return method(source, *args) 201 return method(source, *args)
195 raise AssertionError('unknown message %s received from %s, args=%s' % (n ame, source, repr(args))) 202 raise AssertionError('unknown message %s received from %s, args=%s' % (n ame, source, repr(args)))
196 203
197 def _handle_started_test(self, worker_name, test_input, test_timeout_sec): 204 def _handle_started_test(self, worker_name, test_input, test_timeout_sec):
198 self._printer.print_started_test(test_input.test_name) 205 self._printer.print_started_test(test_input.test_name)
199 206
200 def _handle_finished_test_list(self, worker_name, list_name): 207 def _handle_finished_test_list(self, worker_name, list_name):
201 pass 208 pass
202 209
203 def _handle_finished_test(self, worker_name, result, log_messages=[]): 210 def _handle_finished_test(self, worker_name, result, log_messages=[]):
204 self._update_summary_with_result(self._current_run_results, result) 211 self._update_summary_with_result(self._current_run_results, result)
205 212
206 def _handle_device_failed(self, worker_name, list_name, remaining_tests): 213 def _handle_device_failed(self, worker_name, list_name, remaining_tests):
207 _log.warning("%s has failed" % worker_name) 214 _log.warning('%s has failed' % worker_name)
208 if remaining_tests: 215 if remaining_tests:
209 self._shards_to_redo.append(TestShard(list_name, remaining_tests)) 216 self._shards_to_redo.append(TestShard(list_name, remaining_tests))
210 217
218
211 class Worker(object): 219 class Worker(object):
220
212 def __init__(self, caller, results_directory, options): 221 def __init__(self, caller, results_directory, options):
213 self._caller = caller 222 self._caller = caller
214 self._worker_number = caller.worker_number 223 self._worker_number = caller.worker_number
215 self._name = caller.name 224 self._name = caller.name
216 self._results_directory = results_directory 225 self._results_directory = results_directory
217 self._options = options 226 self._options = options
218 227
219 # The remaining fields are initialized in start() 228 # The remaining fields are initialized in start()
220 self._host = None 229 self._host = None
221 self._port = None 230 self._port = None
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
278 self._driver = self._port.create_driver(self._worker_number) 287 self._driver = self._port.create_driver(self._worker_number)
279 288
280 if not self._driver: 289 if not self._driver:
281 # FIXME: Is this the best way to handle a device crashing in the mid dle of the test, or should we create 290 # FIXME: Is this the best way to handle a device crashing in the mid dle of the test, or should we create
282 # a new failure type? 291 # a new failure type?
283 device_failed = True 292 device_failed = True
284 return device_failed 293 return device_failed
285 294
286 self._caller.post('started_test', test_input, test_timeout_sec) 295 self._caller.post('started_test', test_input, test_timeout_sec)
287 result = single_test_runner.run_single_test(self._port, self._options, s elf._results_directory, 296 result = single_test_runner.run_single_test(self._port, self._options, s elf._results_directory,
288 self._name, self._driver, test_input, stop_when_done) 297 self._name, self._driver, te st_input, stop_when_done)
289 298
290 result.shard_name = shard_name 299 result.shard_name = shard_name
291 result.worker_name = self._name 300 result.worker_name = self._name
292 result.total_run_time = time.time() - start 301 result.total_run_time = time.time() - start
293 result.test_number = self._num_tests 302 result.test_number = self._num_tests
294 self._num_tests += 1 303 self._num_tests += 1
295 self._caller.post('finished_test', result) 304 self._caller.post('finished_test', result)
296 self._clean_up_after_test(test_input, result) 305 self._clean_up_after_test(test_input, result)
297 return result.device_failed 306 return result.device_failed
298 307
299 def stop(self): 308 def stop(self):
300 _log.debug("%s cleaning up" % self._name) 309 _log.debug('%s cleaning up' % self._name)
301 self._kill_driver() 310 self._kill_driver()
302 311
303 def _timeout(self, test_input): 312 def _timeout(self, test_input):
304 """Compute the appropriate timeout value for a test.""" 313 """Compute the appropriate timeout value for a test."""
305 # The driver watchdog uses 2.5x the timeout; we want to be 314 # The driver watchdog uses 2.5x the timeout; we want to be
306 # larger than that. We also add a little more padding if we're 315 # larger than that. We also add a little more padding if we're
307 # running tests in a separate thread. 316 # running tests in a separate thread.
308 # 317 #
309 # Note that we need to convert the test timeout from a 318 # Note that we need to convert the test timeout from a
310 # string value in milliseconds to a float for Python. 319 # string value in milliseconds to a float for Python.
311 320
312 # FIXME: Can we just return the test_input.timeout now? 321 # FIXME: Can we just return the test_input.timeout now?
313 driver_timeout_sec = 3.0 * float(test_input.timeout) / 1000.0 322 driver_timeout_sec = 3.0 * float(test_input.timeout) / 1000.0
314 323
315 def _kill_driver(self): 324 def _kill_driver(self):
316 # Be careful about how and when we kill the driver; if driver.stop() 325 # Be careful about how and when we kill the driver; if driver.stop()
317 # raises an exception, this routine may get re-entered via __del__. 326 # raises an exception, this routine may get re-entered via __del__.
318 driver = self._driver 327 driver = self._driver
319 self._driver = None 328 self._driver = None
320 if driver: 329 if driver:
321 _log.debug("%s killing driver" % self._name) 330 _log.debug('%s killing driver' % self._name)
322 driver.stop() 331 driver.stop()
323 332
324
325 def _clean_up_after_test(self, test_input, result): 333 def _clean_up_after_test(self, test_input, result):
326 test_name = test_input.test_name 334 test_name = test_input.test_name
327 335
328 if result.failures: 336 if result.failures:
329 # Check and kill the driver if we need to. 337 # Check and kill the driver if we need to.
330 if any([f.driver_needs_restart() for f in result.failures]): 338 if any([f.driver_needs_restart() for f in result.failures]):
331 self._kill_driver() 339 self._kill_driver()
332 # Reset the batch count since the shell just bounced. 340 # Reset the batch count since the shell just bounced.
333 self._batch_count = 0 341 self._batch_count = 0
334 342
335 # Print the error message(s). 343 # Print the error message(s).
336 _log.debug("%s %s failed:" % (self._name, test_name)) 344 _log.debug('%s %s failed:' % (self._name, test_name))
337 for f in result.failures: 345 for f in result.failures:
338 _log.debug("%s %s" % (self._name, f.message())) 346 _log.debug('%s %s' % (self._name, f.message()))
339 elif result.type == test_expectations.SKIP: 347 elif result.type == test_expectations.SKIP:
340 _log.debug("%s %s skipped" % (self._name, test_name)) 348 _log.debug('%s %s skipped' % (self._name, test_name))
341 else: 349 else:
342 _log.debug("%s %s passed" % (self._name, test_name)) 350 _log.debug('%s %s passed' % (self._name, test_name))
343 351
344 352
345 class TestShard(object): 353 class TestShard(object):
354
346 """A test shard is a named list of TestInputs.""" 355 """A test shard is a named list of TestInputs."""
347 356
348 def __init__(self, name, test_inputs): 357 def __init__(self, name, test_inputs):
349 self.name = name 358 self.name = name
350 self.test_inputs = test_inputs 359 self.test_inputs = test_inputs
351 self.requires_lock = test_inputs[0].requires_lock 360 self.requires_lock = test_inputs[0].requires_lock
352 361
353 def __repr__(self): 362 def __repr__(self):
354 return "TestShard(name='%s', test_inputs=%s, requires_lock=%s'" % (self. name, self.test_inputs, self.requires_lock) 363 return "TestShard(name='%s', test_inputs=%s, requires_lock=%s'" % (self. name, self.test_inputs, self.requires_lock)
355 364
356 def __eq__(self, other): 365 def __eq__(self, other):
357 return self.name == other.name and self.test_inputs == other.test_inputs 366 return self.name == other.name and self.test_inputs == other.test_inputs
358 367
359 368
360 class Sharder(object): 369 class Sharder(object):
370
361 def __init__(self, test_split_fn, max_locked_shards): 371 def __init__(self, test_split_fn, max_locked_shards):
362 self._split = test_split_fn 372 self._split = test_split_fn
363 self._max_locked_shards = max_locked_shards 373 self._max_locked_shards = max_locked_shards
364 374
365 def shard_tests(self, test_inputs, num_workers, fully_parallel, run_singly): 375 def shard_tests(self, test_inputs, num_workers, fully_parallel, run_singly):
366 """Groups tests into batches. 376 """Groups tests into batches.
367 This helps ensure that tests that depend on each other (aka bad tests!) 377 This helps ensure that tests that depend on each other (aka bad tests!)
368 continue to run together as most cross-tests dependencies tend to 378 continue to run together as most cross-tests dependencies tend to
369 occur within the same directory. 379 occur within the same directory.
370 Return: 380 Return:
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
422 # of content_shell, it's too slow to shard them fully. 432 # of content_shell, it's too slow to shard them fully.
423 virtual_inputs.append(test_input) 433 virtual_inputs.append(test_input)
424 else: 434 else:
425 unlocked_shards.append(TestShard('.', [test_input])) 435 unlocked_shards.append(TestShard('.', [test_input]))
426 436
427 locked_virtual_shards, unlocked_virtual_shards = self._shard_by_director y(virtual_inputs) 437 locked_virtual_shards, unlocked_virtual_shards = self._shard_by_director y(virtual_inputs)
428 438
429 # The locked shards still need to be limited to self._max_locked_shards in order to not 439 # The locked shards still need to be limited to self._max_locked_shards in order to not
430 # overload the http server for the http tests. 440 # overload the http server for the http tests.
431 return (self._resize_shards(locked_virtual_shards + locked_shards, self. _max_locked_shards, 'locked_shard'), 441 return (self._resize_shards(locked_virtual_shards + locked_shards, self. _max_locked_shards, 'locked_shard'),
432 unlocked_virtual_shards + unlocked_shards) 442 unlocked_virtual_shards + unlocked_shards)
433 443
434 def _shard_by_directory(self, test_inputs): 444 def _shard_by_directory(self, test_inputs):
435 """Returns two lists of shards, each shard containing all the files in a directory. 445 """Returns two lists of shards, each shard containing all the files in a directory.
436 446
437 This is the default mode, and gets as much parallelism as we can while 447 This is the default mode, and gets as much parallelism as we can while
438 minimizing flakiness caused by inter-test dependencies.""" 448 minimizing flakiness caused by inter-test dependencies."""
439 locked_shards = [] 449 locked_shards = []
440 unlocked_shards = [] 450 unlocked_shards = []
441 unlocked_slow_shards = [] 451 unlocked_slow_shards = []
442 tests_by_dir = {} 452 tests_by_dir = {}
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
497 def split_at(seq, index): 507 def split_at(seq, index):
498 return (seq[:index], seq[index:]) 508 return (seq[:index], seq[index:])
499 509
500 num_old_per_new = divide_and_round_up(len(old_shards), max_new_shards) 510 num_old_per_new = divide_and_round_up(len(old_shards), max_new_shards)
501 new_shards = [] 511 new_shards = []
502 remaining_shards = old_shards 512 remaining_shards = old_shards
503 while remaining_shards: 513 while remaining_shards:
504 some_shards, remaining_shards = split_at(remaining_shards, num_old_p er_new) 514 some_shards, remaining_shards = split_at(remaining_shards, num_old_p er_new)
505 new_shards.append(TestShard('%s_%d' % (shard_name_prefix, len(new_sh ards) + 1), extract_and_flatten(some_shards))) 515 new_shards.append(TestShard('%s_%d' % (shard_name_prefix, len(new_sh ards) + 1), extract_and_flatten(some_shards)))
506 return new_shards 516 return new_shards
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698