Skip to content

Commit 1f0532d

Browse files
committed
fix(ofrep): stop leaking the testbed when it never becomes ready
Two leaks on the same path, both reported by coderabbit on #414. running_testbed called start() outside the try, and start() brings the compose stack up before waiting for readiness. So a readiness timeout left containers running that nothing would stop -- and a suite that cannot reach its backend is exactly when someone runs it again, against the stack the last run abandoned. stop() also never removed the temporary directory that __init__ creates for the bind mount, so every constructed testbed left one behind whether or not anything failed. It now comes off in a finally, because a compose failure is precisely when it would otherwise be missed. Verified on the failing path rather than by inspection: with readiness forced to raise, the temporary directory is removed and no containers remain. The suite is unchanged at 23 passed, 5 skipped, 1 xfailed. Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
1 parent d6b884c commit 1f0532d

1 file changed

Lines changed: 19 additions & 3 deletions

File tree

  • providers/openfeature-provider-ofrep/tests/tck

‎providers/openfeature-provider-ofrep/tests/tck/testbed.py‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from __future__ import annotations
2828

2929
import os
30+
import shutil
3031
import tempfile
3132
import time
3233
import typing
@@ -121,7 +122,16 @@ def start(self) -> FlagdTestbed:
121122
return self
122123

123124
def stop(self) -> None:
124-
self._compose.stop()
125+
"""Stop the stack and remove the temporary flag directory.
126+
127+
The directory comes off in a ``finally`` because a compose failure is
128+
exactly when it would otherwise be left behind, and it is created in
129+
``__init__`` -- so every constructed testbed leaks one until this runs.
130+
"""
131+
try:
132+
self._compose.stop()
133+
finally:
134+
shutil.rmtree(self._flags_dir, ignore_errors=True)
125135

126136
def get_ofrep_url(self) -> str:
127137
"""Return the base URL to hand to ``OFREPProvider``.
@@ -163,10 +173,16 @@ def _await_ready(self) -> None:
163173

164174

165175
def running_testbed() -> typing.Iterator[FlagdTestbed]:
166-
"""Yield a started testbed and stop it afterwards. Used by the session fixture."""
176+
"""Yield a started testbed and stop it afterwards. Used by the session fixture.
177+
178+
``start`` is inside the ``try`` on purpose. It brings the stack up and then
179+
waits for readiness, so a readiness timeout leaves containers running that
180+
nothing would otherwise stop -- and a suite that cannot reach its backend is
181+
precisely when a developer is going to run it again.
182+
"""
167183
testbed = FlagdTestbed()
168-
testbed.start()
169184
try:
185+
testbed.start()
170186
yield testbed
171187
finally:
172188
testbed.stop()

0 commit comments

Comments
 (0)