Skip to content

Commit 2685685

Browse files
committed
Merge branch 'master' of https://github.com/testcontainers/testcontainers-python into npipe-hostname-fix
2 parents af519c5 + 71f2a87 commit 2685685

12 files changed

+106
-32
lines changed

requirements.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-e file:.[docker-compose,mysql,oracle,postgresql,selenium,google-cloud-pubsub,mongo,redis,mssqlserver]
2-
codecov
2+
codecov>=2.1.0
33
flake8
44
pytest
55
pytest-cov

requirements/3.5.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ cachetools==4.0.0 # via google-auth
1515
certifi==2019.11.28 # via requests
1616
cffi==1.14.0 # via bcrypt, cryptography, pynacl
1717
chardet==3.0.4 # via requests
18-
codecov==2.0.15 # via -r requirements.in
18+
codecov==2.1.7 # via -r requirements.in
1919
colorama==0.4.3 # via crayons
2020
coverage==5.0.3 # via codecov, pytest-cov
2121
crayons==0.3.0 # via testcontainers

requirements/3.6.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ cachetools==4.0.0 # via google-auth
1515
certifi==2019.11.28 # via requests
1616
cffi==1.14.0 # via bcrypt, cryptography, pynacl
1717
chardet==3.0.4 # via requests
18-
codecov==2.0.15 # via -r requirements.in
18+
codecov==2.1.7 # via -r requirements.in
1919
colorama==0.4.3 # via crayons
2020
coverage==5.0.3 # via codecov, pytest-cov
2121
crayons==0.3.0 # via testcontainers

requirements/3.7.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ cachetools==4.0.0 # via google-auth
1515
certifi==2019.11.28 # via requests
1616
cffi==1.14.0 # via bcrypt, cryptography, pynacl
1717
chardet==3.0.4 # via requests
18-
codecov==2.0.15 # via -r requirements.in
18+
codecov==2.1.7 # via -r requirements.in
1919
colorama==0.4.3 # via crayons
2020
coverage==5.0.3 # via codecov, pytest-cov
2121
crayons==0.3.0 # via testcontainers

requirements/3.8.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ cachetools==4.0.0 # via google-auth
1515
certifi==2019.11.28 # via requests
1616
cffi==1.14.0 # via bcrypt, cryptography, pynacl
1717
chardet==3.0.4 # via requests
18-
codecov==2.0.15 # via -r requirements.in
18+
codecov==2.1.7 # via -r requirements.in
1919
colorama==0.4.3 # via crayons
2020
coverage==5.0.3 # via codecov, pytest-cov
2121
crayons==0.3.0 # via testcontainers

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
setuptools.setup(
2020
name='testcontainers',
2121
packages=setuptools.find_packages(exclude=['tests']),
22-
version='3.0.2',
22+
version='3.0.3',
2323
description='Library provides lightweight, throwaway instances of common databases, Selenium '
2424
'web browsers, or anything else that can run in a Docker container',
2525
author='Sergey Pirogov',

testcontainers/compose.py

+34-11
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,19 @@ class DockerCompose(object):
2222
-------
2323
::
2424
25-
with DockerCompose("/home/project", pull=True) as compose:
25+
with DockerCompose("/home/project",
26+
compose_file_name=["docker-compose-1.yml", "docker-compose-2.yml"],
27+
pull=True) as compose:
2628
host = compose.get_service_host("hub", 4444)
2729
port = compose.get_service_port("hub", 4444)
2830
driver = webdriver.Remote(
2931
command_executor=("http://{}:{}/wd/hub".format(host,port)),
3032
desired_capabilities=CHROME,
3133
)
3234
driver.get("http://automation-remarks.com")
35+
stdout, stderr = compose.get_logs()
36+
if stderr:
37+
print("Errors\\n:{}".format(stderr))
3338
3439
3540
.. code-block:: yaml
@@ -57,7 +62,9 @@ def __init__(
5762
compose_file_name="docker-compose.yml",
5863
pull=False):
5964
self.filepath = filepath
60-
self.compose_file_name = compose_file_name
65+
self.compose_file_names = compose_file_name if isinstance(
66+
compose_file_name, (list, tuple)
67+
) else [compose_file_name]
6168
self.pull = pull
6269

6370
def __enter__(self):
@@ -67,18 +74,35 @@ def __enter__(self):
6774
def __exit__(self, exc_type, exc_val, exc_tb):
6875
self.stop()
6976

77+
def docker_compose_command(self):
78+
docker_compose_cmd = ['docker-compose']
79+
for file in self.compose_file_names:
80+
docker_compose_cmd += ['-f', file]
81+
return docker_compose_cmd
82+
7083
def start(self):
7184
with blindspin.spinner():
7285
if self.pull:
73-
subprocess.call(["docker-compose", "-f", self.compose_file_name, "pull"],
74-
cwd=self.filepath)
75-
subprocess.call(["docker-compose", "-f", self.compose_file_name, "up", "-d"],
76-
cwd=self.filepath)
86+
pull_cmd = self.docker_compose_command() + ['pull']
87+
subprocess.call(pull_cmd, cwd=self.filepath)
88+
up_cmd = self.docker_compose_command() + ['up', '-d']
89+
subprocess.call(up_cmd, cwd=self.filepath)
7790

7891
def stop(self):
7992
with blindspin.spinner():
80-
subprocess.call(["docker-compose", "-f", self.compose_file_name, "down", "-v"],
81-
cwd=self.filepath)
93+
down_cmd = self.docker_compose_command() + ['down', '-v']
94+
subprocess.call(down_cmd, cwd=self.filepath)
95+
96+
def get_logs(self):
97+
logs_cmd = self.docker_compose_command() + ["logs"]
98+
with blindspin.spinner():
99+
result = subprocess.run(
100+
logs_cmd,
101+
cwd=self.filepath,
102+
stdout=subprocess.PIPE,
103+
stderr=subprocess.PIPE,
104+
)
105+
return result.stdout, result.stderr
82106

83107
def get_service_port(self, service_name, port):
84108
return self._get_service_info(service_name, port)[1]
@@ -87,9 +111,8 @@ def get_service_host(self, service_name, port):
87111
return self._get_service_info(service_name, port)[0]
88112

89113
def _get_service_info(self, service, port):
90-
cmd_as_list = ["docker-compose", "-f", self.compose_file_name, "port", service, str(port)]
91-
output = subprocess.check_output(cmd_as_list,
92-
cwd=self.filepath).decode("utf-8")
114+
port_cmd = self.docker_compose_command() + ["port", service, str(port)]
115+
output = subprocess.check_output(port_cmd, cwd=self.filepath).decode("utf-8")
93116
result = str(output).rstrip().split(":")
94117
if len(result) == 1:
95118
raise NoSuchPortExposed("Port {} was not exposed for service {}"

testcontainers/core/generic.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818

1919
class DbContainer(DockerContainer):
20-
def __init__(self, image):
21-
super(DbContainer, self).__init__(image)
20+
def __init__(self, image, **kwargs):
21+
super(DbContainer, self).__init__(image, **kwargs)
2222

2323
@wait_container_is_ready()
2424
def _connect(self):
@@ -29,12 +29,17 @@ def _connect(self):
2929
def get_connection_url(self):
3030
raise NotImplementedError
3131

32-
def _create_connection_url(self, dialect, username, password, port, db_name):
32+
def _create_connection_url(self, dialect, username, password, port, db_name=None):
33+
if self._container is None:
34+
raise RuntimeError("container has not been started")
3335
host = self.get_container_host_ip()
3436
port = self.get_exposed_port(port)
35-
return "{dialect}://{username}:{password}@{host}:{port}/{db}".format(
36-
dialect=dialect, username=username, password=password, host=host, port=port, db=db_name
37+
url = "{dialect}://{username}:{password}@{host}:{port}".format(
38+
dialect=dialect, username=username, password=password, host=host, port=port
3739
)
40+
if db_name:
41+
url += '/' + db_name
42+
return url
3843

3944
def start(self):
4045
self._configure()

testcontainers/mongodb.py

+16-9
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
# under the License.
1313
import os
1414

15-
from testcontainers.core.generic import DockerContainer
15+
from testcontainers.core.generic import DbContainer
16+
from testcontainers.core.waiting_utils import wait_container_is_ready
1617

1718

18-
class MongoDbContainer(DockerContainer):
19+
class MongoDbContainer(DbContainer):
1920
"""
2021
Mongo document-based database container.
2122
@@ -49,23 +50,29 @@ class MongoDbContainer(DockerContainer):
4950
MONGO_INITDB_ROOT_PASSWORD = os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test")
5051
MONGO_DB = os.environ.get("MONGO_DB", "test")
5152

52-
def __init__(self, image="mongodb:latest"):
53+
def __init__(self, image="mongo:latest"):
5354
super(MongoDbContainer, self).__init__(image=image)
5455
self.command = "mongo"
5556
self.port_to_expose = 27017
5657
self.with_exposed_ports(self.port_to_expose)
5758

5859
def _configure(self):
59-
6060
self.with_env("MONGO_INITDB_ROOT_USERNAME", self.MONGO_INITDB_ROOT_USERNAME)
6161
self.with_env("MONGO_INITDB_ROOT_PASSWORD", self.MONGO_INITDB_ROOT_PASSWORD)
6262
self.with_env("MONGO_DB", self.MONGO_DB)
6363

6464
def get_connection_url(self):
65-
port = self.get_exposed_port(self.port_to_expose)
66-
return "mongodb://{}:{}".format(self.get_container_host_ip(), port)
65+
return self._create_connection_url(
66+
dialect='mongodb',
67+
username=self.MONGO_INITDB_ROOT_USERNAME,
68+
password=self.MONGO_INITDB_ROOT_PASSWORD,
69+
port=self.port_to_expose,
70+
)
6771

68-
def get_connection_client(self):
72+
@wait_container_is_ready()
73+
def _connect(self):
6974
from pymongo import MongoClient
70-
return MongoClient("mongodb://{}:{}".format(self.get_container_host_ip(),
71-
self.get_exposed_port(self.port_to_expose)))
75+
return MongoClient(self.get_connection_url())
76+
77+
def get_connection_client(self):
78+
return self._connect()

tests/docker-compose-2.yml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
mysql:
2+
image: mysql
3+
ports:
4+
- "3306:3306"
5+
environment:
6+
MYSQL_ALLOW_EMPTY_PASSWORD: "true"

tests/test_db_containers.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import pytest
22
import sqlalchemy
33
from pymongo import MongoClient
4+
from pymongo.errors import OperationFailure
45

56
from testcontainers.core.generic import GenericContainer
67
from testcontainers.core.waiting_utils import wait_for
8+
from testcontainers.mongodb import MongoDbContainer
79
from testcontainers.mssql import SqlServerContainer
810
from testcontainers.mysql import MySqlContainer, MariaDbContainer
911
from testcontainers.oracle import OracleDbContainer
1012
from testcontainers.postgres import PostgresContainer
11-
from testcontainers.mongodb import MongoDbContainer
1213

1314

1415
def test_docker_run_mysql():
@@ -73,6 +74,16 @@ def test_docker_run_mongodb():
7374
assert cursor.next()['restaurant_id'] == doc['restaurant_id']
7475

7576

77+
def test_docker_run_mongodb_connect_without_credentials():
78+
mongo_container = MongoDbContainer()
79+
with mongo_container as mongo:
80+
connection_url = "mongodb://{}:{}".format(mongo.get_container_host_ip(),
81+
mongo.get_exposed_port(mongo.port_to_expose))
82+
db = MongoClient(connection_url).test
83+
with pytest.raises(OperationFailure):
84+
db.restaurants.insert_one({})
85+
86+
7687
def test_docker_generic_db():
7788
mongo_container = GenericContainer("mongo:latest")
7889
mongo_container.with_bind_ports(27017, 27017)

tests/test_docker_compose.py

+22
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,25 @@ def test_compose_wait_for_container_ready():
3131
with DockerCompose("tests") as compose:
3232
docker = DockerClient()
3333
compose.wait_for("http://%s:4444/wd/hub" % docker.host())
34+
35+
36+
def test_can_parse_multiple_compose_files():
37+
with DockerCompose(filepath="tests",
38+
compose_file_name=["docker-compose.yml", "docker-compose-2.yml"]) as compose:
39+
host = compose.get_service_host("mysql", 3306)
40+
port = compose.get_service_port("mysql", 3306)
41+
assert host == "0.0.0.0"
42+
assert port == "3306"
43+
44+
host = compose.get_service_host("hub", 4444)
45+
port = compose.get_service_port("hub", 4444)
46+
assert host == "0.0.0.0"
47+
assert port == "4444"
48+
49+
50+
def test_can_get_logs():
51+
with DockerCompose("tests") as compose:
52+
docker = DockerClient()
53+
compose.wait_for("http://%s:4444/wd/hub" % docker.host())
54+
stdout, stderr = compose.get_logs()
55+
assert stdout, 'There should be something on stdout'

0 commit comments

Comments
 (0)