mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-16 04:18:35 +00:00
fix: use isolated module loading in Windows compatibility tests
The previous approach using importlib.reload() on the canonical module caused test interference - the reloaded classes were different objects from the ones imported by other tests, breaking event bus registration. This fix uses importlib.util to load an isolated copy of the module under a different name, avoiding pollution of the canonical module. Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
@@ -210,6 +210,42 @@ class TestSignalEventSerialization:
|
|||||||
assert restored.type == original.type
|
assert restored.type == original.type
|
||||||
|
|
||||||
|
|
||||||
|
def _load_isolated_system_events_module(monkeypatch: pytest.MonkeyPatch):
|
||||||
|
"""Load an isolated copy of system_events module with missing signals.
|
||||||
|
|
||||||
|
This avoids reloading the canonical module which would break other tests
|
||||||
|
that depend on the original class references.
|
||||||
|
"""
|
||||||
|
import importlib.util
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import crewai.events.types.system_events as orig_module
|
||||||
|
|
||||||
|
# Simulate Windows by removing optional signals
|
||||||
|
monkeypatch.delattr(signal, "SIGHUP", raising=False)
|
||||||
|
monkeypatch.delattr(signal, "SIGTSTP", raising=False)
|
||||||
|
monkeypatch.delattr(signal, "SIGCONT", raising=False)
|
||||||
|
|
||||||
|
# Load an isolated copy of the module under a different name
|
||||||
|
path = pathlib.Path(orig_module.__file__)
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"crewai.events.types.system_events_isolated", path
|
||||||
|
)
|
||||||
|
assert spec is not None
|
||||||
|
assert spec.loader is not None
|
||||||
|
|
||||||
|
isolated_module = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules[spec.name] = isolated_module
|
||||||
|
try:
|
||||||
|
spec.loader.exec_module(isolated_module)
|
||||||
|
finally:
|
||||||
|
# Clean up to avoid polluting sys.modules
|
||||||
|
del sys.modules[spec.name]
|
||||||
|
|
||||||
|
return isolated_module
|
||||||
|
|
||||||
|
|
||||||
class TestWindowsCompatibility:
|
class TestWindowsCompatibility:
|
||||||
"""Tests for Windows compatibility (signals not available on Windows)."""
|
"""Tests for Windows compatibility (signals not available on Windows)."""
|
||||||
|
|
||||||
@@ -220,32 +256,22 @@ class TestWindowsCompatibility:
|
|||||||
|
|
||||||
This is a regression test for GitHub issue #4062.
|
This is a regression test for GitHub issue #4062.
|
||||||
"""
|
"""
|
||||||
import importlib
|
isolated = _load_isolated_system_events_module(monkeypatch)
|
||||||
|
|
||||||
import crewai.events.types.system_events as system_events_module
|
|
||||||
|
|
||||||
# Simulate a Windows-like signal module by removing optional signals
|
|
||||||
monkeypatch.delattr(signal, "SIGHUP", raising=False)
|
|
||||||
monkeypatch.delattr(signal, "SIGTSTP", raising=False)
|
|
||||||
monkeypatch.delattr(signal, "SIGCONT", raising=False)
|
|
||||||
|
|
||||||
# Reload after patching so class definitions see the modified signal module
|
|
||||||
reloaded = importlib.reload(system_events_module)
|
|
||||||
|
|
||||||
# Import should succeed and enum members should exist
|
# Import should succeed and enum members should exist
|
||||||
assert hasattr(reloaded.SignalType, "SIGHUP")
|
assert hasattr(isolated.SignalType, "SIGHUP")
|
||||||
assert hasattr(reloaded.SignalType, "SIGTSTP")
|
assert hasattr(isolated.SignalType, "SIGTSTP")
|
||||||
assert hasattr(reloaded.SignalType, "SIGCONT")
|
assert hasattr(isolated.SignalType, "SIGCONT")
|
||||||
|
|
||||||
# Event classes should still be importable
|
# Event classes should still be importable
|
||||||
assert hasattr(reloaded, "SigHupEvent")
|
assert hasattr(isolated, "SigHupEvent")
|
||||||
assert hasattr(reloaded, "SigTStpEvent")
|
assert hasattr(isolated, "SigTStpEvent")
|
||||||
assert hasattr(reloaded, "SigContEvent")
|
assert hasattr(isolated, "SigContEvent")
|
||||||
|
|
||||||
# Fallback values should be negative (to avoid conflicts with real signals)
|
# Fallback values should be negative (to avoid conflicts with real signals)
|
||||||
assert reloaded.SignalType.SIGHUP < 0
|
assert isolated.SignalType.SIGHUP < 0
|
||||||
assert reloaded.SignalType.SIGTSTP < 0
|
assert isolated.SignalType.SIGTSTP < 0
|
||||||
assert reloaded.SignalType.SIGCONT < 0
|
assert isolated.SignalType.SIGCONT < 0
|
||||||
|
|
||||||
def test_signal_events_can_be_created_when_signals_missing(
|
def test_signal_events_can_be_created_when_signals_missing(
|
||||||
self, monkeypatch: pytest.MonkeyPatch
|
self, monkeypatch: pytest.MonkeyPatch
|
||||||
@@ -254,24 +280,14 @@ class TestWindowsCompatibility:
|
|||||||
|
|
||||||
This is a regression test for GitHub issue #4062.
|
This is a regression test for GitHub issue #4062.
|
||||||
"""
|
"""
|
||||||
import importlib
|
isolated = _load_isolated_system_events_module(monkeypatch)
|
||||||
|
|
||||||
import crewai.events.types.system_events as system_events_module
|
|
||||||
|
|
||||||
# Simulate a Windows-like signal module
|
|
||||||
monkeypatch.delattr(signal, "SIGHUP", raising=False)
|
|
||||||
monkeypatch.delattr(signal, "SIGTSTP", raising=False)
|
|
||||||
monkeypatch.delattr(signal, "SIGCONT", raising=False)
|
|
||||||
|
|
||||||
# Reload after patching
|
|
||||||
reloaded = importlib.reload(system_events_module)
|
|
||||||
|
|
||||||
# Events should be creatable
|
# Events should be creatable
|
||||||
hup_event = reloaded.SigHupEvent()
|
hup_event = isolated.SigHupEvent()
|
||||||
assert hup_event.type == "SIGHUP"
|
assert hup_event.type == "SIGHUP"
|
||||||
|
|
||||||
tstp_event = reloaded.SigTStpEvent()
|
tstp_event = isolated.SigTStpEvent()
|
||||||
assert tstp_event.type == "SIGTSTP"
|
assert tstp_event.type == "SIGTSTP"
|
||||||
|
|
||||||
cont_event = reloaded.SigContEvent()
|
cont_event = isolated.SigContEvent()
|
||||||
assert cont_event.type == "SIGCONT"
|
assert cont_event.type == "SIGCONT"
|
||||||
|
|||||||
@@ -130,10 +130,15 @@ def test_telemetry_register_shutdown_handlers_with_missing_optional_signals(
|
|||||||
"""Telemetry shouldn't fail when optional signals are missing (Windows-like).
|
"""Telemetry shouldn't fail when optional signals are missing (Windows-like).
|
||||||
|
|
||||||
This is a regression test for GitHub issue #4062.
|
This is a regression test for GitHub issue #4062.
|
||||||
"""
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
from crewai.telemetry import telemetry as telemetry_module
|
Note: This test uses an isolated module loading approach to avoid
|
||||||
|
polluting the canonical telemetry module which other tests depend on.
|
||||||
|
"""
|
||||||
|
import importlib.util
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from crewai.telemetry import telemetry as orig_telemetry_module
|
||||||
|
|
||||||
# Disable telemetry to avoid real OTLP setup
|
# Disable telemetry to avoid real OTLP setup
|
||||||
monkeypatch.setenv("CREWAI_DISABLE_TELEMETRY", "true")
|
monkeypatch.setenv("CREWAI_DISABLE_TELEMETRY", "true")
|
||||||
@@ -143,15 +148,28 @@ def test_telemetry_register_shutdown_handlers_with_missing_optional_signals(
|
|||||||
monkeypatch.delattr(signal, "SIGTSTP", raising=False)
|
monkeypatch.delattr(signal, "SIGTSTP", raising=False)
|
||||||
monkeypatch.delattr(signal, "SIGCONT", raising=False)
|
monkeypatch.delattr(signal, "SIGCONT", raising=False)
|
||||||
|
|
||||||
# Reload after patching so the module sees the modified signal module
|
# Load an isolated copy of the telemetry module under a different name
|
||||||
reloaded = importlib.reload(telemetry_module)
|
path = pathlib.Path(orig_telemetry_module.__file__)
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"crewai.telemetry.telemetry_isolated", path
|
||||||
|
)
|
||||||
|
assert spec is not None
|
||||||
|
assert spec.loader is not None
|
||||||
|
|
||||||
# Reset the singleton to allow a new instance
|
isolated_module = importlib.util.module_from_spec(spec)
|
||||||
reloaded.Telemetry._instance = None
|
sys.modules[spec.name] = isolated_module
|
||||||
reloaded.Telemetry._lock = threading.Lock()
|
try:
|
||||||
|
spec.loader.exec_module(isolated_module)
|
||||||
|
|
||||||
# This should not raise an error even with missing signals
|
# Reset the singleton to allow a new instance
|
||||||
telemetry = reloaded.Telemetry()
|
isolated_module.Telemetry._instance = None
|
||||||
|
isolated_module.Telemetry._lock = threading.Lock()
|
||||||
|
|
||||||
# Telemetry should be disabled (due to env var), but import should succeed
|
# This should not raise an error even with missing signals
|
||||||
assert telemetry.ready is False
|
telemetry = isolated_module.Telemetry()
|
||||||
|
|
||||||
|
# Telemetry should be disabled (due to env var), but import should succeed
|
||||||
|
assert telemetry.ready is False
|
||||||
|
finally:
|
||||||
|
# Clean up to avoid polluting sys.modules
|
||||||
|
del sys.modules[spec.name]
|
||||||
|
|||||||
Reference in New Issue
Block a user