Skip to content

Commit 52c28fb

Browse files
cursoragentdobrac
andcommitted
Refactor sandbox fixtures for better async handling
Co-authored-by: jakub.dobry <jakub.dobry@e2b.dev>
1 parent 5f7df48 commit 52c28fb

File tree

1 file changed

+40
-35
lines changed

1 file changed

+40
-35
lines changed

python/tests/conftest.py

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,17 @@
1111
timeout = 60
1212

1313

14-
@pytest_asyncio.fixture(scope="function")
14+
# Override the event loop so it never closes during test execution
15+
# This helps with pytest-xdist and prevents "Event loop is closed" errors
16+
@pytest.fixture(scope="session")
1517
def event_loop():
16-
"""Create an instance of the default event loop for each test case."""
17-
policy = asyncio.get_event_loop_policy()
18-
loop = policy.new_event_loop()
19-
yield loop
20-
# Clean up any remaining tasks
21-
try:
22-
pending = asyncio.all_tasks(loop)
23-
for task in pending:
24-
task.cancel()
25-
if pending:
26-
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
27-
except Exception:
28-
pass
29-
# Close the loop gracefully
18+
"""Create a session-scoped event loop for all async tests."""
3019
try:
31-
loop.close()
32-
except Exception:
33-
pass
20+
loop = asyncio.get_running_loop()
21+
except RuntimeError:
22+
loop = asyncio.new_event_loop()
23+
yield loop
24+
loop.close()
3425

3526

3627
@pytest.fixture()
@@ -54,24 +45,38 @@ def sandbox(template, debug):
5445
)
5546

5647

57-
@pytest_asyncio.fixture(loop_scope="function")
58-
async def async_sandbox(template, debug):
59-
async_sandbox = await AsyncSandbox.create(template, timeout=timeout, debug=debug)
48+
@pytest.fixture
49+
def async_sandbox_factory(request, template, debug, event_loop):
50+
"""Factory for creating async sandboxes with proper cleanup."""
51+
async def factory(template_override=None, **kwargs):
52+
template_name = template_override or template
53+
kwargs.setdefault("timeout", timeout)
54+
kwargs.setdefault("debug", debug)
55+
56+
sandbox = await AsyncSandbox.create(template_name, **kwargs)
6057

61-
try:
62-
yield async_sandbox
63-
finally:
64-
try:
65-
await async_sandbox.kill()
66-
except RuntimeError as e:
67-
# Ignore "Event loop is closed" errors during cleanup in pytest-xdist
68-
if "Event loop is closed" not in str(e):
69-
raise
70-
except: # noqa: E722
71-
if not debug:
72-
warning(
73-
"Failed to kill sandbox — this is expected if the test runs with local envd."
74-
)
58+
def kill():
59+
async def _kill():
60+
try:
61+
await sandbox.kill()
62+
except: # noqa: E722
63+
if not debug:
64+
warning(
65+
"Failed to kill sandbox — this is expected if the test runs with local envd."
66+
)
67+
68+
event_loop.run_until_complete(_kill())
69+
70+
request.addfinalizer(kill)
71+
return sandbox
72+
73+
return factory
74+
75+
76+
@pytest.fixture
77+
async def async_sandbox(async_sandbox_factory):
78+
"""Default async sandbox fixture."""
79+
return await async_sandbox_factory()
7580

7681

7782
@pytest.fixture

0 commit comments

Comments
 (0)