Skip to content

Commit 546d303

Browse files
wip
1 parent ae8af9d commit 546d303

File tree

4 files changed

+74
-53
lines changed

4 files changed

+74
-53
lines changed

core/testcontainers/core/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ def tc_properties_tc_host(self) -> Union[str, None]:
8787
return self.tc_properties.get("tc.host")
8888

8989
@property
90-
def tc_properties_testcontainers_reuse_enable(self) -> Union[bool, None]:
91-
enabled = self.tc_properties.get("testcontainers.reuse.enable")
92-
return enabled == "true" if enabled else None
90+
def tc_properties_testcontainers_reuse_enabled(self) -> bool:
91+
enabled = self.tc_properties.get("testcontainers.reuse.enabled")
92+
return enabled == "true"
9393

9494
@property
9595
def timeout(self):

core/testcontainers/core/container.py

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -88,62 +88,59 @@ def maybe_emulate_amd64(self) -> Self:
8888
return self
8989

9090
def start(self) -> Self:
91-
breakpoint()
92-
def _start():
93-
docker_client = self.get_docker_client()
94-
self._container = docker_client.run(
95-
self.image,
96-
command=self._command,
97-
detach=True,
98-
environment=self.env,
99-
ports=self.ports,
100-
name=self._name,
101-
volumes=self.volumes,
102-
labels={"hash": hash_},
103-
**self._kwargs,
104-
)
105-
logger.info("Container started: %s", self._container.short_id)
106-
10791
if not c.ryuk_disabled and self.image != c.ryuk_image:
10892
logger.debug("Creating Ryuk container")
10993
Reaper.get_instance()
11094
logger.info("Pulling image %s", self.image)
11195
self._configure()
11296

113-
hash_ = hashlib.sha256(
114-
bytes(
115-
str(
116-
(
117-
self.image,
118-
self._command,
119-
self.env,
120-
self.ports,
121-
self._name,
122-
self.volumes,
123-
self._kwargs,
124-
)
125-
),
126-
encoding="utf-8",
127-
)
128-
).hexdigest()
129-
# TODO: warn user if with_reuse in use but REUSE_ENABLED false
130-
if self._reuse and c.reuse_enabled:
97+
# container hash consisting of run arguments
98+
args = (
99+
self.image,
100+
self._command,
101+
self.env,
102+
self.ports,
103+
self._name,
104+
self.volumes,
105+
str(tuple(sorted(self._kwargs.items()))),
106+
)
107+
hash_ = hashlib.sha256(bytes(str(args), encoding="utf-8")).hexdigest()
108+
109+
# TODO: warn user if self._reuse=True but reuse_enabled=False
110+
reuse_enabled = c.reuse_enabled or c.tc_properties_testcontainers_reuse_enabled
111+
if self._reuse and reuse_enabled:
131112
docker_client = self.get_docker_client()
132113
container = docker_client.find_container_by_hash(hash_)
133114
if container:
134115
if container.status != "running":
135116
container.start()
117+
logger.info("Existing container started: %s", container.id)
118+
logger.info("Container is already running: %s", container.id)
136119
self._container = container
137-
logger.info("Existing container started: %s", container.id)
138120
else:
139-
_start()
121+
self._start(hash_)
140122
else:
141-
_start()
123+
self._start(hash_)
142124

143125
if self._network:
144126
self._network.connect(self._container.id, self._network_aliases)
145127
return self
146128

129+
def _start(self, hash_):
130+
docker_client = self.get_docker_client()
131+
self._container = docker_client.run(
132+
self.image,
133+
command=self._command,
134+
detach=True,
135+
environment=self.env,
136+
ports=self.ports,
137+
name=self._name,
138+
volumes=self.volumes,
139+
labels={"hash": hash_},
140+
**self._kwargs,
141+
)
142+
logger.info("Container started: %s", self._container.short_id)
143+
147144
def stop(self, force=True, delete_volume=True) -> None:
148145
if self._container:
149146
if self._reuse and c.reuse_enabled:

core/testcontainers/core/docker_client.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,8 @@ def client_networks_create(self, name: str, param: dict):
217217

218218
def find_container_by_hash(self, hash_: str) -> Container | None:
219219
for container in self.client.containers.list(all=True):
220-
if "hash" in container.labels.keys():
221-
if container.labels["hash"] == hash_:
222-
return container
220+
if container.labels.get("hash", None) == hash_:
221+
return container
223222
return None
224223

225224

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1+
from time import sleep
2+
13
from docker.models.containers import Container
4+
from docker import DockerClient as DockerDockerClient
5+
6+
from testcontainers.core.config import testcontainers_config
27
from testcontainers.core.container import DockerContainer
38
from testcontainers.core.docker_client import DockerClient
49
from testcontainers.core.waiting_utils import wait_for_logs
10+
from testcontainers.core.container import Reaper
511

612

713
def test_docker_container_reuse_default():
814
with DockerContainer("hello-world") as container:
915
assert container._reuse == False
1016
id = container._container.id
17+
wait_for_logs(container, "Hello from Docker!")
1118
containers = DockerClient().client.containers.list(all=True)
1219
assert id not in [container.id for container in containers]
1320

@@ -16,34 +23,51 @@ def test_docker_container_with_reuse_reuse_disabled():
1623
with DockerContainer("hello-world").with_reuse() as container:
1724
assert container._reuse == True
1825
id = container._container.id
26+
wait_for_logs(container, "Hello from Docker!")
1927
containers = DockerClient().client.containers.list(all=True)
2028
assert id not in [container.id for container in containers]
2129

2230

2331
def test_docker_container_with_reuse_reuse_enabled_ryuk_enabled(monkeypatch):
24-
monkeypatch.setattr("testcontainers.core.config.testcontainers_config.reuse_enabled", True)
32+
# Make sure Ryuk cleanup is not active from previous test runs
33+
Reaper.delete_instance()
34+
monkeypatch.setattr(testcontainers_config, "reuse_enabled", True)
35+
monkeypatch.setattr(testcontainers_config, "ryuk_reconnection_timeout", "0.1s")
36+
2537
with DockerContainer("hello-world").with_reuse() as container:
26-
assert container._reuse == True
2738
id = container._container.id
39+
wait_for_logs(container, "Hello from Docker!")
40+
41+
Reaper._socket.close()
42+
# Sleep until Ryuk reaps all dangling containers
43+
sleep(0.6)
44+
2845
containers = DockerClient().client.containers.list(all=True)
29-
assert id in [container.id for container in containers]
46+
assert id not in [container.id for container in containers]
47+
48+
# Cleanup Ryuk class fields after manual Ryuk shutdown
49+
Reaper.delete_instance()
3050

3151

32-
# TODO: do not run test while ryuk is running
33-
# TODO: clean up container after test run
34-
def test_docker_container_with_reuse_reuse_enabled_ryuk_diabled(monkeypatch):
35-
monkeypatch.setattr("testcontainers.core.config.testcontainers_config.reuse_enabled", True)
36-
monkeypatch.setattr("testcontainers.core.config.testcontainers_config.ryuk_disabled", True)
52+
def test_docker_container_with_reuse_reuse_enabled_ryuk_disabled(monkeypatch):
53+
# Make sure Ryuk cleanup is not active from previous test runs
54+
Reaper.delete_instance()
55+
monkeypatch.setattr(testcontainers_config, "reuse_enabled", True)
56+
monkeypatch.setattr(testcontainers_config, "ryuk_disabled", True)
3757
with DockerContainer("hello-world").with_reuse() as container:
3858
assert container._reuse == True
3959
id = container._container.id
60+
wait_for_logs(container, "Hello from Docker!")
4061
containers = DockerClient().client.containers.list(all=True)
4162
assert id in [container.id for container in containers]
63+
# Cleanup after keeping container alive (with_reuse)
64+
container._container.remove(force=True)
4265

4366

4467
def test_docker_container_labels_hash():
68+
expected_hash = "91fde3c09244e1d3ec6f18a225b9261396b9a1cb0f6365b39b9795782817c128"
4569
with DockerContainer("hello-world").with_reuse() as container:
46-
assert container._container.labels["hash"] == "505d1d913abe7f54b5a66202e8559a4f798038a204d39fe8b1577735ed632e32"
70+
assert container._container.labels["hash"] == expected_hash
4771

4872

4973
def test_docker_client_find_container_by_hash_not_existing():
@@ -53,5 +77,6 @@ def test_docker_client_find_container_by_hash_not_existing():
5377

5478
def test_docker_client_find_container_by_hash_existing():
5579
with DockerContainer("hello-world").with_reuse() as container:
56-
found_container = DockerClient().find_container_by_hash(container._container.labels["hash"])
80+
hash_ = container._container.labels["hash"]
81+
found_container = DockerClient().find_container_by_hash(hash_)
5782
assert isinstance(found_container, Container)

0 commit comments

Comments
 (0)