Skip to content

Commit b1453e8

Browse files
feat(core): add ability to do OR & AND for waitforlogs (#661)
1 parent e02c1b3 commit b1453e8

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

core/testcontainers/core/waiting_utils.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,12 @@ def wait_for(condition: Callable[..., bool]) -> bool:
7878

7979

8080
def wait_for_logs(
81-
container: "DockerContainer", predicate: Union[Callable, str], timeout: float = config.timeout, interval: float = 1
81+
container: "DockerContainer",
82+
predicate: Union[Callable, str],
83+
timeout: float = config.timeout,
84+
interval: float = 1,
85+
predicate_streams_and: bool = False,
86+
#
8287
) -> float:
8388
"""
8489
Wait for the container to emit logs satisfying the predicate.
@@ -90,6 +95,7 @@ def wait_for_logs(
9095
timeout: Number of seconds to wait for the predicate to be satisfied. Defaults to wait
9196
indefinitely.
9297
interval: Interval at which to poll the logs.
98+
predicate_streams_and: should the predicate be applied to both
9399
94100
Returns:
95101
duration: Number of seconds until the predicate was satisfied.
@@ -101,7 +107,13 @@ def wait_for_logs(
101107
duration = time.time() - start
102108
stdout = container.get_logs()[0].decode()
103109
stderr = container.get_logs()[1].decode()
104-
if predicate(stdout) or predicate(stderr):
110+
predicate_result = (
111+
predicate(stdout) or predicate(stderr)
112+
if predicate_streams_and is False
113+
else predicate(stdout) and predicate(stderr)
114+
#
115+
)
116+
if predicate_result:
105117
return duration
106118
if duration > timeout:
107119
raise TimeoutError(f"Container did not emit logs satisfying predicate in {timeout:.3f} " "seconds")

modules/postgres/testcontainers/postgres/__init__.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,37 @@ def get_connection_url(self, host: Optional[str] = None, driver: Optional[str] =
9191

9292
@wait_container_is_ready()
9393
def _connect(self) -> None:
94-
wait_for_logs(self, ".*database system is ready to accept connections.*", c.max_tries, c.sleep_time)
94+
# postgres itself logs these messages to the standard error stream:
95+
#
96+
# $ /opt/homebrew/opt/postgresql@14/bin/postgres -D /opt/homebrew/var/postgresql@14 \
97+
# > | grep -o -a -m 1 -h 'database system is ready to accept connections'
98+
# 2024-08-03 00:13:02.799 EDT [70226] LOG: starting PostgreSQL 14.11 (Homebrew) ....
99+
# 2024-08-03 00:13:02.804 EDT [70226] LOG: listening on IPv4 address "127.0.0.1", port 5432
100+
# ...
101+
# ^C2024-08-03 00:13:04.226 EDT [70226] LOG: received fast shutdown request
102+
# ...
103+
#
104+
# $ /opt/homebrew/opt/postgresql@14/bin/postgres -D /opt/homebrew/var/postgresql@14 2>&1 \
105+
# > | grep -o -a -m 1 -h 'database system is ready to accept connections'
106+
# database system is ready to accept connections
107+
#
108+
# and the setup script inside docker library postgres
109+
# uses pg_ctl:
110+
# https://github.com/docker-library/postgres/blob/66da3846b40396249936938ee17e9684e6968a57/16/alpine3.20/docker-entrypoint.sh#L261-L282
111+
# which prints logs to stdout:
112+
# https://www.postgresql.org/docs/current/app-pg-ctl.html#:~:text=the%20server%27s%20standard%20output%20and%20standard%20error%20are%20sent%20to%20pg_ctl%27s%20standard%20output
113+
#
114+
# so we must wait for both the setup and real startup:
115+
predicate_streams_and = True
116+
117+
wait_for_logs(
118+
self,
119+
".*database system is ready to accept connections.*",
120+
c.max_tries,
121+
c.sleep_time,
122+
predicate_streams_and=predicate_streams_and,
123+
#
124+
)
95125

96126
count = 0
97127
while count < c.max_tries:

0 commit comments

Comments
 (0)