| # Copyright 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import threading |
| |
| class TestCollection(object): |
| """A threadsafe collection of tests. |
| |
| Args: |
| tests: List of tests to put in the collection. |
| """ |
| |
| def __init__(self, tests=None): |
| if not tests: |
| tests = [] |
| self._lock = threading.Lock() |
| self._tests = [] |
| self._tests_in_progress = 0 |
| # Used to signal that an item is available or all items have been handled. |
| self._item_available_or_all_done = threading.Event() |
| for t in tests: |
| self.add(t) |
| |
| def _pop(self): |
| """Pop a test from the collection. |
| |
| Waits until a test is available or all tests have been handled. |
| |
| Returns: |
| A test or None if all tests have been handled. |
| """ |
| while True: |
| # Wait for a test to be available or all tests to have been handled. |
| self._item_available_or_all_done.wait() |
| with self._lock: |
| # Check which of the two conditions triggered the signal. |
| if self._tests_in_progress == 0: |
| return None |
| try: |
| return self._tests.pop(0) |
| except IndexError: |
| # Another thread beat us to the available test, wait again. |
| self._item_available_or_all_done.clear() |
| |
| def add(self, test): |
| """Add a test to the collection. |
| |
| Args: |
| test: A test to add. |
| """ |
| with self._lock: |
| self._tests.append(test) |
| self._item_available_or_all_done.set() |
| self._tests_in_progress += 1 |
| |
| def test_completed(self): |
| """Indicate that a test has been fully handled.""" |
| with self._lock: |
| self._tests_in_progress -= 1 |
| if self._tests_in_progress == 0: |
| # All tests have been handled, signal all waiting threads. |
| self._item_available_or_all_done.set() |
| |
| def __iter__(self): |
| """Iterate through tests in the collection until all have been handled.""" |
| while True: |
| r = self._pop() |
| if r is None: |
| break |
| yield r |
| |
| def __len__(self): |
| """Return the number of tests currently in the collection.""" |
| return len(self._tests) |
| |
| def test_names(self): |
| """Return a list of the names of the tests currently in the collection.""" |
| with self._lock: |
| return list(t.test for t in self._tests) |