# 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)