|  | 
| 1 |  | -from contextlib import contextmanager | 
| 2 |  | - | 
|  | 1 | +from time import sleep | 
| 3 | 2 | import pytest | 
|  | 3 | +from pytest import MonkeyPatch | 
|  | 4 | + | 
|  | 5 | +from docker import DockerClient | 
|  | 6 | +from docker.errors import NotFound | 
| 4 | 7 | 
 | 
| 5 |  | -from testcontainers.core import container | 
|  | 8 | +from testcontainers.core import container as container_module | 
| 6 | 9 | from testcontainers.core.container import Reaper | 
| 7 | 10 | from testcontainers.core.container import DockerContainer | 
| 8 | 11 | from testcontainers.core.waiting_utils import wait_for_logs | 
| 9 | 12 | 
 | 
| 10 | 13 | 
 | 
| 11 |  | -@pytest.mark.skip("invalid test - ryuk logs 'Removed' right before exiting") | 
| 12 |  | -def test_wait_for_reaper(): | 
|  | 14 | +def test_wait_for_reaper(monkeypatch: MonkeyPatch): | 
|  | 15 | +    Reaper.delete_instance() | 
|  | 16 | +    monkeypatch.setattr(container_module, "RYUK_RECONNECTION_TIMEOUT", "0.1s") | 
|  | 17 | +    docker_client = DockerClient() | 
| 13 | 18 |     container = DockerContainer("hello-world").start() | 
|  | 19 | + | 
|  | 20 | +    container_id = container.get_wrapped_container().short_id | 
|  | 21 | +    reaper_id = Reaper._container.get_wrapped_container().short_id | 
|  | 22 | + | 
|  | 23 | +    assert docker_client.containers.get(container_id) is not None | 
|  | 24 | +    assert docker_client.containers.get(reaper_id) is not None | 
|  | 25 | + | 
| 14 | 26 |     wait_for_logs(container, "Hello from Docker!") | 
| 15 | 27 | 
 | 
| 16 |  | -    assert Reaper._socket is not None | 
| 17 | 28 |     Reaper._socket.close() | 
| 18 | 29 | 
 | 
| 19 |  | -    assert Reaper._container is not None | 
| 20 |  | -    wait_for_logs(Reaper._container, r".* Removed \d .*", timeout=30) | 
|  | 30 | +    sleep(0.6)  # Sleep until Ryuk reaps all dangling containers. 0.5 extra seconds for good measure. | 
| 21 | 31 | 
 | 
|  | 32 | +    with pytest.raises(NotFound): | 
|  | 33 | +        docker_client.containers.get(container_id) | 
|  | 34 | +    with pytest.raises(NotFound): | 
|  | 35 | +        docker_client.containers.get(reaper_id) | 
|  | 36 | + | 
|  | 37 | +    # Cleanup Ryuk class fields after manual Ryuk shutdown | 
|  | 38 | +    Reaper.delete_instance() | 
|  | 39 | + | 
|  | 40 | + | 
|  | 41 | +def test_container_without_ryuk(monkeypatch: MonkeyPatch): | 
| 22 | 42 |     Reaper.delete_instance() | 
|  | 43 | +    monkeypatch.setattr(container_module, "RYUK_DISABLED", True) | 
|  | 44 | +    with DockerContainer("hello-world") as container: | 
|  | 45 | +        wait_for_logs(container, "Hello from Docker!") | 
|  | 46 | +        assert Reaper._instance is None | 
| 23 | 47 | 
 | 
| 24 | 48 | 
 | 
| 25 |  | -@contextmanager | 
| 26 |  | -def reset_reaper_instance(): | 
| 27 |  | -    old_value = Reaper._instance | 
| 28 |  | -    Reaper._instance = None | 
| 29 |  | -    yield | 
| 30 |  | -    Reaper._instance = old_value | 
|  | 49 | +def test_ryuk_is_reused_in_same_process(): | 
|  | 50 | +    with DockerContainer("hello-world") as container: | 
|  | 51 | +        wait_for_logs(container, "Hello from Docker!") | 
|  | 52 | +        reaper_instance = Reaper._instance | 
| 31 | 53 | 
 | 
|  | 54 | +    assert reaper_instance is not None | 
| 32 | 55 | 
 | 
| 33 |  | -def test_container_without_ryuk(monkeypatch): | 
| 34 |  | -    monkeypatch.setattr(container, "RYUK_DISABLED", True) | 
| 35 |  | -    with reset_reaper_instance(), DockerContainer("hello-world") as cont: | 
| 36 |  | -        wait_for_logs(cont, "Hello from Docker!") | 
| 37 |  | -        assert Reaper._instance is None | 
|  | 56 | +    with DockerContainer("hello-world") as container: | 
|  | 57 | +        wait_for_logs(container, "Hello from Docker!") | 
|  | 58 | +        assert reaper_instance is Reaper._instance | 
0 commit comments