/media/webrtc/trunk/build/android/pylib/python_test_sharder.py

https://github.com/rillian/firefox
Python | 203 lines | 83 code | 26 blank | 94 comment | 8 complexity | 98838bb314601042ec3810b1c7193d74 MD5 | raw file
  1. # Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Takes care of sharding the python-drive tests in multiple devices."""
  5. import copy
  6. import logging
  7. import multiprocessing
  8. from python_test_caller import CallPythonTest
  9. from run_java_tests import FatalTestException
  10. import sharded_tests_queue
  11. from test_result import TestResults
  12. def SetTestsContainer(tests_container):
  13. """Sets PythonTestSharder as a top-level field.
  14. PythonTestSharder uses multiprocessing.Pool, which creates a pool of
  15. processes. This is used to initialize each worker in the pool, ensuring that
  16. each worker has access to this shared pool of tests.
  17. The multiprocessing module requires that this be a top-level method.
  18. Args:
  19. tests_container: the container for all the tests.
  20. """
  21. PythonTestSharder.tests_container = tests_container
  22. def _DefaultRunnable(test_runner):
  23. """A default runnable for a PythonTestRunner.
  24. Args:
  25. test_runner: A PythonTestRunner which will run tests.
  26. Returns:
  27. The test results.
  28. """
  29. return test_runner.RunTests()
  30. class PythonTestRunner(object):
  31. """Thin wrapper around a list of PythonTestBase instances.
  32. This is meant to be a long-lived object which can run multiple Python tests
  33. within its lifetime. Tests will receive the device_id and shard_index.
  34. The shard index affords the ability to create unique port numbers (e.g.
  35. DEFAULT_PORT + shard_index) if the test so wishes.
  36. """
  37. def __init__(self, options):
  38. """Constructor.
  39. Args:
  40. options: Options to use for setting up tests.
  41. """
  42. self.options = options
  43. def RunTests(self):
  44. """Runs tests from the shared pool of tests, aggregating results.
  45. Returns:
  46. A list of test results for all of the tests which this runner executed.
  47. """
  48. tests = PythonTestSharder.tests_container
  49. results = []
  50. for t in tests:
  51. res = CallPythonTest(t, self.options)
  52. results.append(res)
  53. return TestResults.FromTestResults(results)
  54. class PythonTestSharder(object):
  55. """Runs Python tests in parallel on multiple devices.
  56. This is lifted more or less wholesale from BaseTestRunner.
  57. Under the covers, it creates a pool of long-lived PythonTestRunners, which
  58. execute tests from the pool of tests.
  59. Args:
  60. attached_devices: a list of device IDs attached to the host.
  61. available_tests: a list of tests to run which subclass PythonTestBase.
  62. options: Options to use for setting up tests.
  63. Returns:
  64. An aggregated list of test results.
  65. """
  66. tests_container = None
  67. def __init__(self, attached_devices, available_tests, options):
  68. self.options = options
  69. self.attached_devices = attached_devices
  70. self.retries = options.shard_retries
  71. self.tests = available_tests
  72. def _SetupSharding(self, tests):
  73. """Creates the shared pool of tests and makes it available to test runners.
  74. Args:
  75. tests: the list of tests which will be consumed by workers.
  76. """
  77. SetTestsContainer(sharded_tests_queue.ShardedTestsQueue(
  78. len(self.attached_devices), tests))
  79. def RunShardedTests(self):
  80. """Runs tests in parallel using a pool of workers.
  81. Returns:
  82. A list of test results aggregated from all test runs.
  83. """
  84. logging.warning('*' * 80)
  85. logging.warning('Sharding in ' + str(len(self.attached_devices)) +
  86. ' devices.')
  87. logging.warning('Note that the output is not synchronized.')
  88. logging.warning('Look for the "Final result" banner in the end.')
  89. logging.warning('*' * 80)
  90. all_passed = []
  91. test_results = TestResults()
  92. tests_to_run = self.tests
  93. for retry in xrange(self.retries):
  94. logging.warning('Try %d of %d', retry + 1, self.retries)
  95. self._SetupSharding(self.tests)
  96. test_runners = self._MakeTestRunners(self.attached_devices)
  97. logging.warning('Starting...')
  98. pool = multiprocessing.Pool(len(self.attached_devices),
  99. SetTestsContainer,
  100. [PythonTestSharder.tests_container])
  101. # List of TestResults objects from each test execution.
  102. try:
  103. results_lists = pool.map(_DefaultRunnable, test_runners)
  104. except Exception:
  105. logging.exception('Unable to run tests. Something with the '
  106. 'PythonTestRunners has gone wrong.')
  107. raise FatalTestException('PythonTestRunners were unable to run tests.')
  108. test_results = TestResults.FromTestResults(results_lists)
  109. # Accumulate passing results.
  110. all_passed += test_results.ok
  111. # If we have failed tests, map them to tests to retry.
  112. failed_tests = test_results.GetAllBroken()
  113. tests_to_run = self._GetTestsToRetry(self.tests,
  114. failed_tests)
  115. # Bail out early if we have no more tests. This can happen if all tests
  116. # pass before we're out of retries, for example.
  117. if not tests_to_run:
  118. break
  119. final_results = TestResults()
  120. # all_passed has accumulated all passing test results.
  121. # test_results will have the results from the most recent run, which could
  122. # include a variety of failure modes (unknown, crashed, failed, etc).
  123. final_results = test_results
  124. final_results.ok = all_passed
  125. return final_results
  126. def _MakeTestRunners(self, attached_devices):
  127. """Initialize and return a list of PythonTestRunners.
  128. Args:
  129. attached_devices: list of device IDs attached to host.
  130. Returns:
  131. A list of PythonTestRunners, one for each device.
  132. """
  133. test_runners = []
  134. for index, device in enumerate(attached_devices):
  135. logging.warning('*' * 80)
  136. logging.warning('Creating shard %d for %s', index, device)
  137. logging.warning('*' * 80)
  138. # Bind the PythonTestRunner to a device & shard index. Give it the
  139. # runnable which it will use to actually execute the tests.
  140. test_options = copy.deepcopy(self.options)
  141. test_options.ensure_value('device_id', device)
  142. test_options.ensure_value('shard_index', index)
  143. test_runner = PythonTestRunner(test_options)
  144. test_runners.append(test_runner)
  145. return test_runners
  146. def _GetTestsToRetry(self, available_tests, failed_tests):
  147. """Infers a list of tests to retry from failed tests and available tests.
  148. Args:
  149. available_tests: a list of tests which subclass PythonTestBase.
  150. failed_tests: a list of SingleTestResults representing failed tests.
  151. Returns:
  152. A list of test objects which correspond to test names found in
  153. failed_tests, or an empty list if there is no correspondence.
  154. """
  155. failed_test_names = map(lambda t: t.test_name, failed_tests)
  156. tests_to_retry = [t for t in available_tests
  157. if t.qualified_name in failed_test_names]
  158. return tests_to_retry