|
10 | 10 | import shutil |
11 | 11 | import sys |
12 | 12 | import tempfile |
| 13 | +import subprocess |
13 | 14 | import unittest |
14 | 15 | from collections.abc import Iterable |
15 | 16 | from typing import TYPE_CHECKING, Callable, ClassVar |
@@ -354,6 +355,44 @@ def test_user_agent(self) -> None: |
354 | 355 | self.assertEqual(ua[:23], "MyApp/1.2.3 python-tuf/") |
355 | 356 |
|
356 | 357 |
|
| 358 | +class TestParallelUpdater(TestUpdater): |
| 359 | + def test_parallel_updaters(self) -> None: |
| 360 | + # Refresh two updaters in parallel many times, using the same local metadata cache. |
| 361 | + # This should reveal race conditions. |
| 362 | + |
| 363 | + iterations = 100 |
| 364 | + |
| 365 | + # The project root is the parent of the tests directory |
| 366 | + project_root = os.path.dirname(utils.TESTS_DIR) |
| 367 | + |
| 368 | + command = [ |
| 369 | + sys.executable, |
| 370 | + "-m", |
| 371 | + "tests.refresh_script", |
| 372 | + str(iterations), |
| 373 | + self.client_directory, |
| 374 | + self.metadata_url, |
| 375 | + ] |
| 376 | + |
| 377 | + p1 = subprocess.Popen( |
| 378 | + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=project_root |
| 379 | + ) |
| 380 | + p2 = subprocess.Popen( |
| 381 | + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=project_root |
| 382 | + ) |
| 383 | + |
| 384 | + stdout1, stderr1 = p1.communicate() |
| 385 | + stdout2, stderr2 = p2.communicate() |
| 386 | + |
| 387 | + if p1.returncode != 0 or p2.returncode != 0: |
| 388 | + self.fail( |
| 389 | + "Parallel refresh failed" |
| 390 | + f"\nprocess 1 stdout: \n{stdout1.decode()}" |
| 391 | + f"\nprocess 1 stderr: \n{stderr1.decode()}" |
| 392 | + f"\nprocess 2 stdout: \n{stdout2.decode()}" |
| 393 | + f"\nprocess 2 stderr: \n{stderr2.decode()}" |
| 394 | + ) |
| 395 | + |
357 | 396 | if __name__ == "__main__": |
358 | 397 | utils.configure_test_logging(sys.argv) |
359 | 398 | unittest.main() |
0 commit comments