From cc52cd346dbd8945497bc32fea09c0f66f13c7c9 Mon Sep 17 00:00:00 2001 From: Daniel McCoy Stephenson Date: Tue, 22 Sep 2026 03:25:12 -0600 Subject: [PATCH] Pin the voyage event roll on the module docks actually calls #201 patched rollEvent on src.business.adventures, but docks imports adventures as business.adventures - pytest.ini puts both . and src on the path, so those are two module objects and the patch never reached the call site. The flake it meant to fix survived it: main went red again on the very next run (35556530705). The roll is now pinned with patch.object on docks.adventures, and the day assertion is exact (she founders on leg one), so a roll that isn't pinned fails five runs in seven instead of one in forty. Forcing the real roll to driftwood every leg fails on main's version of the test and passes here; 200 consecutive plain runs, 0 failures. Co-Authored-By: Claude Opus 5 (1M context) --- tests/location/test_docks.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/location/test_docks.py b/tests/location/test_docks.py index 903f8a0..f85a194 100644 --- a/tests/location/test_docks.py +++ b/tests/location/test_docks.py @@ -1495,7 +1495,6 @@ def test_a_voyage_that_founders_ends_early(): boat["damage"] = boats.MAX_DAMAGE - 5 # damaged but still just seaworthy boats.repairBoat(docksInstance.player, boat["id"]) boats.damageBoat(boat, boats.UNSEAWORTHY_DAMAGE - 1) - plan = adventures.VOYAGE_PLANS[2] docksInstance.userInterface.showOptions = MagicMock( side_effect=voyageChooser( "Marauder", "The far water", "Full stores", then="first" @@ -1506,18 +1505,28 @@ def test_a_voyage_that_founders_ends_early(): # call - every leg presents the leak, whose first choice always damages, # and every damage roll is at its worst. rollEvent draws from the role's - # pool with random.choice, which the two patches below never covered: - # a run that drew driftwood, calm water or good grounds every leg took - # no damage and never foundered, and the test failed about one run in - # forty (#200). - leak = next(event for event in adventures.EVENTS if event["id"] == "leak") - with patch("src.business.adventures.rollEvent", return_value=leak): + # pool with random.choice, which the two random patches below never + # covered: a run that drew driftwood, calm water or good grounds every + # leg took no damage and never foundered, and the test failed about one + # run in forty (#200). + # + # The roll is pinned on the module docks actually calls into. pytest.ini + # puts both `.` and `src` on the path, so `from business import + # adventures` (docks) and `from src.business import adventures` (this + # file) load two separate module objects - a patch on the `src.`-prefixed + # one never reaches docks, and the flake survived #201 that way. The + # random patches are unaffected because `random` is one shared module + # whichever way adventures was imported. + sailedAdventures = docks.adventures + leak = next(event for event in sailedAdventures.EVENTS if event["id"] == "leak") + with patch.object(sailedAdventures, "rollEvent", return_value=leak): with patch("src.business.adventures.random.randint", return_value=99): with patch("src.business.adventures.random.random", return_value=0.0): docksInstance.takeTheHelm() - # check - she came home early with nothing, but she came home - assert docksInstance.timeService.day < startingDay + plan["legs"] + # check - she came home early with nothing, but she came home. With 51% + # of hull and a 99-point leak she goes down on the first leg, exactly. + assert docksInstance.timeService.day == startingDay + 1 assert docksInstance.stats.totalVoyagesFoundered == 1 assert boat in docksInstance.player.boats assert boats.isAtSea(boat) is False