Skip to content

Commit 0aa1e54

Browse files
committed
test: pull in conftest isolation improvements from Henrik/test-pr-297
- restore_flash_sys_modules autouse fixture: snapshots and restores runpod_flash.* sys.modules entries after each test, preventing class identity splits under -n auto - stale state file cleanup in reset_singletons: deletes corrupt/partial .flash/resources.pkl before each test so it cannot poison the next worker - fix test_serialization_error_handling: update docstring to reflect AE-2370 behaviour (caching non-fatal, method call still raises SerializationError)
1 parent ba2d60a commit 0aa1e54

2 files changed

Lines changed: 44 additions & 8 deletions

File tree

tests/conftest.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,32 @@ def worker_flash_dir(worker_temp_dir: Path) -> Path:
267267
return flash_dir
268268

269269

270+
@pytest.fixture(autouse=True)
271+
def restore_flash_sys_modules():
272+
"""Restore sys.modules entries for runpod_flash after each test.
273+
274+
Tests that call importlib.reload() or delete runpod_flash entries from
275+
sys.modules leave stale or reloaded class objects in the interpreter.
276+
Subsequent tests on the same worker then see different class objects for
277+
the same class (e.g. LiveServerlessMixin, _ClientCoroutine), causing
278+
isinstance() to return False even when the type looks correct.
279+
280+
This fixture snapshots all runpod_flash.* entries before the test and
281+
restores them after, keeping the module registry clean regardless of what
282+
the test does to sys.modules.
283+
"""
284+
import sys
285+
286+
saved = {k: v for k, v in sys.modules.items() if k.startswith("runpod_flash")}
287+
yield
288+
# Remove any entries added or replaced during the test
289+
for k in list(sys.modules.keys()):
290+
if k.startswith("runpod_flash"):
291+
del sys.modules[k]
292+
# Restore the snapshot
293+
sys.modules.update(saved)
294+
295+
270296
@pytest.fixture(autouse=True)
271297
def isolate_resource_state_file(
272298
monkeypatch: pytest.MonkeyPatch, worker_flash_dir: Path
@@ -315,6 +341,15 @@ def reset_singletons():
315341
This fixture runs automatically for all tests to ensure
316342
clean state between test executions.
317343
"""
344+
# Delete stale state file before each test so that a corrupt or partially-
345+
# written pickle from a previous test cannot poison the ResourceManager of
346+
# the next test in the same worker.
347+
from runpod_flash.core.resources import resource_manager as _rm_module
348+
349+
_state_file = _rm_module.RESOURCE_STATE_FILE
350+
if _state_file.exists():
351+
_state_file.unlink(missing_ok=True)
352+
318353
# Patch cloudpickle to handle threading.Lock objects that may be left over
319354
# from previous tests. This prevents "cannot pickle '_thread.lock'" errors
320355
# when test pollution causes old lock instances to be in the object graph.

tests/integration/test_class_execution_integration.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from runpod_flash.client import remote
1919
from runpod_flash.core.resources import ServerlessResource
2020
from runpod_flash.execute_class import create_remote_class
21+
from runpod_flash.runtime.exceptions import SerializationError
2122

2223

2324
@patch.dict(os.environ, {"RUNPOD_ENDPOINT_ID": "", "RUNPOD_POD_ID": ""})
@@ -606,7 +607,13 @@ async def mock_failing_ensure_initialized():
606607

607608
@pytest.mark.asyncio
608609
async def test_serialization_error_handling(self):
609-
"""Test error handling for serialization issues."""
610+
"""Test error handling for serialization issues.
611+
612+
AE-2370 (v1.11.3): constructor arg *caching* is non-fatal — a warning is
613+
logged and the cache entry is skipped. However, the method call itself still
614+
raises SerializationError when the args cannot be serialized at call time,
615+
because the fallback path (execute_class.py:272) also calls serialize_args.
616+
"""
610617

611618
class UnserializableClass:
612619
def __init__(self, file_handle=None):
@@ -615,18 +622,15 @@ def __init__(self, file_handle=None):
615622
def process_file(self):
616623
return "Processing file"
617624

618-
# Create instance with unserializable object
619625
import tempfile
620626

621627
with tempfile.NamedTemporaryFile() as temp_file:
622628
RemoteUnserializableClass = create_remote_class(
623629
UnserializableClass, self.mock_resource_config, [], [], True
624630
)
625631

626-
# This should not fail during initialization (lazy serialization)
627632
instance = RemoteUnserializableClass(temp_file)
628633

629-
# Mock ensure_initialized to avoid actual resource calls
630634
mock_stub = AsyncMock()
631635

632636
async def mock_ensure_initialized():
@@ -638,14 +642,11 @@ async def mock_ensure_initialized():
638642
with patch.object(
639643
instance, "_ensure_initialized", side_effect=mock_ensure_initialized
640644
):
641-
# The error should occur during method call when trying to serialize
642-
# Mock cloudpickle.dumps to raise an error
643-
from runpod_flash.runtime.exceptions import SerializationError
644-
645645
with patch(
646646
"runpod_flash.runtime.serialization.cloudpickle.dumps",
647647
side_effect=TypeError("Can't pickle file objects"),
648648
):
649+
# Caching is skipped with warnings (AE-2370); method call still raises
649650
with pytest.raises(
650651
SerializationError, match="Can't pickle file objects"
651652
):

0 commit comments

Comments
 (0)