Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 10 additions & 44 deletions modules/postgres/testcontainers/postgres/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
from time import sleep
from typing import Optional

from testcontainers.core.config import testcontainers_config as c
from testcontainers.core.generic import DbContainer
from testcontainers.core.utils import raise_for_deprecated_parameter
from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs
from testcontainers.core.waiting_utils import wait_container_is_ready

_UNSET = object()

Expand Down Expand Up @@ -91,45 +89,13 @@ def get_connection_url(self, host: Optional[str] = None, driver: Optional[str] =

@wait_container_is_ready()
def _connect(self) -> None:
# postgres itself logs these messages to the standard error stream:
#
# $ /opt/homebrew/opt/postgresql@14/bin/postgres -D /opt/homebrew/var/postgresql@14 \
# > | grep -o -a -m 1 -h 'database system is ready to accept connections'
# 2024-08-03 00:13:02.799 EDT [70226] LOG: starting PostgreSQL 14.11 (Homebrew) ....
# 2024-08-03 00:13:02.804 EDT [70226] LOG: listening on IPv4 address "127.0.0.1", port 5432
# ...
# ^C2024-08-03 00:13:04.226 EDT [70226] LOG: received fast shutdown request
# ...
#
# $ /opt/homebrew/opt/postgresql@14/bin/postgres -D /opt/homebrew/var/postgresql@14 2>&1 \
# > | grep -o -a -m 1 -h 'database system is ready to accept connections'
# database system is ready to accept connections
#
# and the setup script inside docker library postgres
# uses pg_ctl:
# https://github.com/docker-library/postgres/blob/66da3846b40396249936938ee17e9684e6968a57/16/alpine3.20/docker-entrypoint.sh#L261-L282
# which prints logs to stdout:
# 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
#
# so we must wait for both the setup and real startup:
predicate_streams_and = True

wait_for_logs(
self,
".*database system is ready to accept connections.*",
c.max_tries,
c.sleep_time,
predicate_streams_and=predicate_streams_and,
#
escaped_single_password = self.password.replace("'", "'\"'\"'")
result = self.exec(
[
"sh",
"-c",
f"PGPASSWORD='{escaped_single_password}' psql --username {self.username} --dbname {self.dbname} --host 127.0.0.1 -c 'select version();'",
]
)

count = 0
while count < c.max_tries:
status, _ = self.exec(f"pg_isready -hlocalhost -p{self.port} -U{self.username}")
if status == 0:
return

sleep(c.sleep_time)
count += 1

raise RuntimeError("Postgres could not get into a ready state")
if result.exit_code:
raise ConnectionError("pg_isready is not ready yet")
12 changes: 11 additions & 1 deletion modules/postgres/tests/test_postgres.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from pathlib import Path

import pytest
import sqlalchemy

from testcontainers.postgres import PostgresContainer
import sqlalchemy


# https://www.postgresql.org/support/versioning/
Expand Down Expand Up @@ -46,6 +46,16 @@ def test_docker_run_postgres_with_driver_pg8000():
connection.execute(sqlalchemy.text("select 1=1"))


def test_password_with_quotes():
postgres_container = PostgresContainer("postgres:9.5", password="'''''")
with postgres_container as postgres:
engine = sqlalchemy.create_engine(postgres.get_connection_url())
with engine.begin() as connection:
result = connection.execute(sqlalchemy.text("select version()"))
for row in result:
assert row[0].lower().startswith("postgresql 9.5")


# This is a feature in the generic DbContainer class
# but it can't be tested on its own
# so is tested in various database modules:
Expand Down