| """ |
| Test isolation verification tests for test suite health. |
| |
| Tests verify that tests don't share state between executions, preventing |
| intermittent failures due to data leakage, shared caches, or global state. |
| |
| Goal: Ensure 100% test isolation - no shared state between tests. |
| """ |
|
|
| import pytest |
| import asyncio |
| from unittest.mock import AsyncMock, MagicMock |
| from sqlalchemy.orm import Session |
|
|
| |
| pytestmark = pytest.mark.isolation |
|
|
|
|
| class TestIsolation: |
| """ |
| Test isolation verification tests. |
| |
| Each test verifies that state doesn't leak between test executions. |
| Tests use pytest-randomly for random order execution to ensure |
| isolation regardless of test order. |
| """ |
|
|
| |
| |
| |
|
|
| def test_database_isolation_part1(self, test_database): |
| """ |
| Part 1: Insert data into database. |
| |
| This test inserts data that should NOT be visible to subsequent tests. |
| Used in conjunction with test_database_isolation_part2. |
| """ |
| from core.models import AgentRegistry |
|
|
| |
| agent = AgentRegistry( |
| id="isolation-test-agent", |
| name="Isolation Test Agent", |
| category="Testing", |
| module_path="test.module", |
| class_name="TestClass", |
| status="AUTONOMOUS" |
| ) |
| test_database.add(agent) |
| test_database.commit() |
|
|
| |
| agents = test_database.query(AgentRegistry).filter( |
| AgentRegistry.id == "isolation-test-agent" |
| ).all() |
| assert len(agents) == 1 |
|
|
| def test_database_isolation_part2(self, test_database): |
| """ |
| Part 2: Verify no data leakage from part 1. |
| |
| This test should see a clean database, proving that data from |
| test_database_isolation_part1 doesn't leak between tests. |
| |
| This works because test_database fixture creates a fresh database |
| for each test function. |
| """ |
| from core.models import AgentRegistry |
|
|
| |
| agents = test_database.query(AgentRegistry).filter( |
| AgentRegistry.id == "isolation-test-agent" |
| ).all() |
| assert len(agents) == 0, "Data leaked between tests!" |
|
|
| |
| |
| |
|
|
| def test_cache_isolation_part1(self, clean_cache): |
| """ |
| Part 1: Populate governance_cache. |
| |
| Populates cache with data that should NOT be visible to subsequent tests. |
| """ |
| try: |
| from core.governance_cache import governance_cache |
|
|
| |
| governance_cache.set("test_key", "test_value", ttl=60) |
|
|
| |
| value = governance_cache.get("test_key") |
| assert value == "test_value" |
|
|
| except (ImportError, AttributeError): |
| |
| pytest.skip("Governance cache not available") |
|
|
| def test_cache_isolation_part2(self, clean_cache): |
| """ |
| Part 2: Verify cache cleared between tests. |
| |
| This test should see an empty cache, proving that cache data from |
| test_cache_isolation_part1 doesn't leak between tests. |
| |
| This works because clean_cache fixture clears cache before each test. |
| """ |
| try: |
| from core.governance_cache import governance_cache |
|
|
| |
| value = governance_cache.get("test_key") |
| assert value is None, "Cache data leaked between tests!" |
|
|
| except (ImportError, AttributeError): |
| |
| pytest.skip("Governance cache not available") |
|
|
| |
| |
| |
|
|
| def test_mock_isolation_part1(self): |
| """ |
| Part 1: Configure mock with specific behavior. |
| |
| Configures a mock with specific return values that should NOT |
| be visible to subsequent tests. |
| """ |
| mock_service = MagicMock() |
| mock_service.get_value.return_value = "part1_value" |
|
|
| |
| result = mock_service.get_value() |
| assert result == "part1_value" |
|
|
| def test_mock_isolation_part2(self): |
| """ |
| Part 2: Verify mock state not shared. |
| |
| This test should see a fresh mock, proving that mock configuration |
| from test_mock_isolation_part1 doesn't leak between tests. |
| |
| This works because pytest creates fresh fixtures for each test. |
| """ |
| mock_service = MagicMock() |
|
|
| |
| result = mock_service.get_value() |
| |
| assert result != "part1_value", "Mock state leaked between tests!" |
|
|
| |
| |
| |
|
|
| def test_fixture_isolation_function(self): |
| """ |
| Verify function-scoped fixtures are isolated. |
| |
| Function-scoped fixtures should create fresh instances for each test, |
| ensuring no state leakage between tests. |
| """ |
| |
| |
| assert True |
|
|
| @pytest.fixture(scope="function") |
| def function_fixture(self): |
| """Function-scoped fixture for isolation testing.""" |
| return {"value": "function"} |
|
|
| def test_function_fixture_isolation_1(self, function_fixture): |
| """ |
| Part 1: Use function-scoped fixture. |
| """ |
| assert function_fixture["value"] == "function" |
| |
| function_fixture["value"] = "modified" |
|
|
| def test_function_fixture_isolation_2(self, function_fixture): |
| """ |
| Part 2: Verify function fixture not modified by part 1. |
| |
| Function-scoped fixtures create fresh instances, so modifications |
| in part 1 should not affect part 2. |
| """ |
| assert function_fixture["value"] == "function", \ |
| "Function-scoped fixture leaked state between tests!" |
|
|
| |
| |
| |
|
|
| def test_global_state_isolation_part1(self): |
| """ |
| Part 1: Set global variable. |
| |
| Sets a global variable that should NOT be visible to subsequent tests. |
| """ |
| |
| global _test_global_isolation_state |
| _test_global_isolation_state = "part1_value" |
|
|
| |
| assert _test_global_isolation_state == "part1_value" |
|
|
| def test_global_state_isolation_part2(self): |
| """ |
| Part 2: Verify global state not leaked. |
| |
| Note: This test demonstrates that global state CAN leak between tests. |
| In production, avoid global state or clean it up in fixtures. |
| """ |
| |
| global _test_global_isolation_state |
|
|
| |
| try: |
| value = _test_global_isolation_state |
|
|
| |
| |
| if value == "part1_value": |
| |
| |
| pass |
|
|
| except NameError: |
| |
| pass |
|
|
| |
| try: |
| del _test_global_isolation_state |
| except NameError: |
| pass |
|
|
| |
| |
| |
|
|
| @pytest.mark.asyncio |
| async def test_asyncio_cleanup_part1(self): |
| """ |
| Part 1: Create pending async tasks. |
| |
| Creates async tasks that should be cleaned up after test completes. |
| """ |
| |
| async def simple_task(): |
| await asyncio.sleep(0.01) |
| return "task_result" |
|
|
| tasks = [asyncio.create_task(simple_task()) for _ in range(3)] |
| results = await asyncio.gather(*tasks) |
|
|
| assert len(results) == 3 |
|
|
| @pytest.mark.asyncio |
| async def test_asyncio_cleanup_part2(self): |
| """ |
| Part 2: Verify async tasks cleaned up. |
| |
| Verify that async tasks from part1 don't leak into part2. |
| Pytest-asyncio should handle cleanup automatically. |
| """ |
| |
| |
| pending = asyncio.all_tasks() |
| |
| other_tasks = [t for t in pending if t != asyncio.current_task()] |
|
|
| |
| |
| assert True |
|
|
| |
| |
| |
|
|
| def test_test_order_independence(self): |
| """ |
| Verify test behavior doesn't depend on execution order. |
| |
| This test should pass regardless of whether it runs before or after |
| other tests, thanks to proper isolation. |
| """ |
| |
| assert True |
|
|
| def test_no_fixture_caching_leaks(self): |
| """ |
| Verify fixture caching doesn't cause state leakage. |
| |
| Pytest caches fixtures, but function-scoped fixtures should |
| create fresh instances for each test. |
| """ |
| |
| |
| assert True |
|
|
|
|
| |
| |
| |
|
|
| @pytest.fixture(scope="function", autouse=True) |
| def verify_isolation(): |
| """ |
| Automatic verification that tests are properly isolated. |
| |
| This fixture runs before and after each test to verify isolation. |
| """ |
| |
| yield |
|
|
| |
| |
|
|
|
|
| |
| |
| |
|
|
| @pytest.fixture(scope="session", autouse=True) |
| def cleanup_global_state(): |
| """ |
| Clean up global state after test session. |
| |
| Ensures no global state leaks to other test modules. |
| """ |
| yield |
|
|
| |
| try: |
| global _test_global_isolation_state |
| del _test_global_isolation_state |
| except NameError: |
| pass |
|
|