From 2d456463517bb090224a5d0b8e5767110426d807 Mon Sep 17 00:00:00 2001 From: Kirill Smelkov Date: Tue, 18 Apr 2023 11:51:49 +0300 Subject: [PATCH] racetest: Review 2 - Rename `TestGroup` -> `TestWorkGroup`. I originally named it as just WorkGroup similarly to sync.WorkGroup in pygolang, but later added "Test" prefix to highlight the difference that this class manages working group of threads that take part in tests, instead of group of arbitrary threads. However in that process I dropped the "Work" part which turned the name to be somewhat ambiguous: it is not clear whether "TestGroup" is a group of threads serving one test, or a group of separate tests without any relation to threading. To remove the ambiguity let's restore the "Work" in the name so that both "Test" and "WorkGroup" are there. - Review test_racetest.py a bit: * make it a bit more robust by increasing "ok" timeout from 0.1s to 10s. in my experience 0.1s is too little and will regularly hit "timeout" when CI machines are overloaded. I'm still somewhat uncomfortable with 0.1s timeout we left in tests that exercise timeout handling, but let's leave it as is for now and see how it goes. * add test for cases when only one thread fails out of many, and when several threads fail too. * minor cosmetics. - spellcheck. --- src/ZODB/tests/racetest.py | 24 +++++++++---------- src/ZODB/tests/test_racetest.py | 42 ++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/ZODB/tests/racetest.py b/src/ZODB/tests/racetest.py index e515446b7..efd7504d6 100644 --- a/src/ZODB/tests/racetest.py +++ b/src/ZODB/tests/racetest.py @@ -81,7 +81,7 @@ class T2ObjectsInc: """T2ObjectsInc is specification with behaviour where two objects obj1 and obj2 are incremented synchronously. - It is used in tests where bugs can be immedeately observed after the race. + It is used in tests where bugs can be immediately observed after the race. invariant: obj1 == obj2 """ @@ -207,7 +207,7 @@ def xrun(tg, tx, f, N): init() N = 500 - tg = TestGroup(self) + tg = TestWorkGroup(self) tg.go(xrun, verify, N, name='Tverify') tg.go(xrun, modify, N, name='Tmodify') tg.wait(60) @@ -317,7 +317,7 @@ def t_(): init() N = 100 - tg = TestGroup(self) + tg = TestWorkGroup(self) for _ in range(nwork): tg.go(T, N) tg.wait(60) @@ -325,9 +325,9 @@ def t_(): # verify storage for race in between client disconnect and external # invalidations. https://github.com/zopefoundation/ZEO/issues/209 # - # This test is simlar to check_race_load_vs_external_invalidate, but + # This test is similar to check_race_load_vs_external_invalidate, but # increases the number of workers and also makes every worker to repeatedly - # reconnect to the storage, so that the probability of disconection is + # reconnect to the storage, so that the probability of disconnection is # high. It also uses T2ObjectsInc2Phase instead of T2ObjectsInc because if # an invalidation is skipped due to the disconnect/invalidation race, # T2ObjectsInc won't catch the bug as both objects will be either in old @@ -408,7 +408,7 @@ def work1(db): init() N = 100 // (2*4) # N reduced to save time - tg = TestGroup(self) + tg = TestWorkGroup(self) for _ in range(nwork): tg.go(T, N) tg.wait(60) @@ -425,7 +425,7 @@ def _state_init(db, spec): zconn.close() -# `_state_invalidate_half1` invalidatates first 50% of database objects, so +# `_state_invalidate_half1` invalidates first 50% of database objects, so # that the next time they are accessed, they are reloaded from the storage. def _state_invalidate_half1(root): keys = list(sorted(root.keys())) @@ -483,9 +483,9 @@ def load(key): return txt -class TestGroup(object): - """TestGroup represents group of threads that run together to verify - somthing. +class TestWorkGroup(object): + """TestWorkGroup represents group of threads that run together to verify + something. - .go() adds test thread to the group. - .wait() waits for all spawned threads to finish and reports all @@ -498,7 +498,7 @@ def __init__(self, testcase): self.testcase = testcase self.failed_event = threading.Event() self.fail_mu = threading.Lock() - self.failv = [] # failures registerd by .fail + self.failv = [] # failures registered by .fail self.threadv = [] # spawned threads self.waitg = WaitGroup() # to wait for spawned threads @@ -509,7 +509,7 @@ def fail(self, msg): self.failed_event.set() def failed(self): - """did the thest already fail.""" + """did the test already fail.""" return self.failed_event.is_set() def go(self, f, *argv, **kw): diff --git a/src/ZODB/tests/test_racetest.py b/src/ZODB/tests/test_racetest.py index 4c7cce531..e2729635f 100644 --- a/src/ZODB/tests/test_racetest.py +++ b/src/ZODB/tests/test_racetest.py @@ -14,14 +14,14 @@ from time import sleep from unittest import TestCase -from .racetest import TestGroup +from .racetest import TestWorkGroup -class TestGroupTests(TestCase): +class TestWorkGroupTests(TestCase): def setUp(self): self._failed = failed = [] case_mockup = SimpleNamespace(fail=failed.append) - self.tg = TestGroup(case_mockup) + self.tg = TestWorkGroup(case_mockup) @property def failed(self): @@ -30,19 +30,42 @@ def failed(self): def test_success(self): tg = self.tg tg.go(tg_test_function) - tg.wait(0.1) + tg.wait(10) self.assertEqual(self.failed, "") - def test_failure(self): + def test_failure1(self): tg = self.tg tg.go(tg_test_function, T_FAIL) - tg.wait(0.1) - self.assertEqual(self.failed, "0 failed") + tg.wait(10) + self.assertEqual(self.failed, "T0 failed") + + def test_failure1_okmany(self): + tg = self.tg + tg.go(tg_test_function, T_SUCCESS) + tg.go(tg_test_function, T_SUCCESS) + tg.go(tg_test_function, T_SUCCESS) + tg.go(tg_test_function, T_FAIL) + tg.wait(10) + self.assertEqual(self.failed, "T3 failed") + + def test_failure_many(self): + tg = self.tg + tg.go(tg_test_function, T_FAIL) + tg.go(tg_test_function, T_SUCCESS) + tg.go(tg_test_function, T_FAIL) + tg.go(tg_test_function, T_SUCCESS) + tg.go(tg_test_function, T_FAIL) + tg.wait(10) + self.assertIn("T0 failed", self.failed) + self.assertIn("T2 failed", self.failed) + self.assertIn("T4 failed", self.failed) + self.assertNotIn("T1 failed", self.failed) + self.assertNotIn("T3 failed", self.failed) def test_exception(self): tg = self.tg tg.go(tg_test_function, T_EXC) - tg.wait(0.1) + tg.wait(10) self.assertIn("Unhandled exception", self.failed) self.assertIn("in thread T0", self.failed) @@ -74,10 +97,11 @@ def tg_test_function(tg, tx, mode=T_SUCCESS, waits=1, wait_time=0.2): if mode == T_SUCCESS: return if mode == T_FAIL: - tg.fail("%d failed" % tx) + tg.fail("T%d failed" % tx) return if mode == T_EXC: raise ValueError(str(tx)) + assert mode == T_SLOW while waits: waits -= 1 if tg.failed():