| 8 | |
| 9 | |
| 10 | class ParallelTestCase(TestCase): |
| 11 | def __init__(self, test_case: TestCase, num_threads: int): |
| 12 | self.test_case = test_case |
| 13 | self.num_threads = num_threads |
| 14 | self._testMethodName = test_case._testMethodName |
| 15 | self._testMethodDoc = test_case._testMethodDoc |
| 16 | |
| 17 | def __str__(self): |
| 18 | return f"{str(self.test_case)} [threads={self.num_threads}]" |
| 19 | |
| 20 | def run_worker(self, test_case: TestCase, result: unittest.TestResult, |
| 21 | barrier: threading.Barrier): |
| 22 | barrier.wait() |
| 23 | test_case.run(result) |
| 24 | |
| 25 | def run(self, result=None): |
| 26 | if result is None: |
| 27 | result = test_case.defaultTestResult() |
| 28 | startTestRun = getattr(result, 'startTestRun', None) |
| 29 | stopTestRun = getattr(result, 'stopTestRun', None) |
| 30 | if startTestRun is not None: |
| 31 | startTestRun() |
| 32 | else: |
| 33 | stopTestRun = None |
| 34 | |
| 35 | # Called at the beginning of each test. See TestCase.run. |
| 36 | result.startTest(self) |
| 37 | |
| 38 | cases = [copy.copy(self.test_case) for _ in range(self.num_threads)] |
| 39 | results = [unittest.TestResult() for _ in range(self.num_threads)] |
| 40 | |
| 41 | barrier = threading.Barrier(self.num_threads) |
| 42 | threads = [] |
| 43 | for i, (case, r) in enumerate(zip(cases, results)): |
| 44 | thread = threading.Thread(target=self.run_worker, |
| 45 | args=(case, r, barrier), |
| 46 | name=f"{str(self.test_case)}-{i}", |
| 47 | daemon=True) |
| 48 | threads.append(thread) |
| 49 | |
| 50 | for thread in threads: |
| 51 | thread.start() |
| 52 | |
| 53 | for threads in threads: |
| 54 | threads.join() |
| 55 | |
| 56 | # Aggregate test results |
| 57 | if all(r.wasSuccessful() for r in results): |
| 58 | result.addSuccess(self) |
| 59 | |
| 60 | # Note: We can't call result.addError, result.addFailure, etc. because |
| 61 | # we no longer have the original exception, just the string format. |
| 62 | for r in results: |
| 63 | if len(r.errors) > 0 or len(r.failures) > 0: |
| 64 | result._mirrorOutput = True |
| 65 | result.errors.extend(r.errors) |
| 66 | result.failures.extend(r.failures) |
| 67 | result.skipped.extend(r.skipped) |
no outgoing calls
no test coverage detected
searching dependent graphs…