summaryrefslogtreecommitdiff
path: root/tests/acceptance/cli_test.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/acceptance/cli_test.py')
-rw-r--r--tests/acceptance/cli_test.py865
1 files changed, 783 insertions, 82 deletions
diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py
index ffba3002..bba2238e 100644
--- a/tests/acceptance/cli_test.py
+++ b/tests/acceptance/cli_test.py
@@ -1,9 +1,12 @@
+# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
import datetime
import json
import os
+import os.path
+import re
import signal
import subprocess
import time
@@ -11,19 +14,27 @@ from collections import Counter
from collections import namedtuple
from operator import attrgetter
-import py
+import pytest
+import six
import yaml
from docker import errors
from .. import mock
+from ..helpers import create_host_file
from compose.cli.command import get_project
+from compose.config.errors import DuplicateOverrideFileFound
from compose.container import Container
from compose.project import OneOffFilter
+from compose.utils import nanoseconds_from_time_seconds
from tests.integration.testcases import DockerClientTestCase
from tests.integration.testcases import get_links
+from tests.integration.testcases import is_cluster
+from tests.integration.testcases import no_cluster
from tests.integration.testcases import pull_busybox
+from tests.integration.testcases import SWARM_SKIP_RM_VOLUMES
+from tests.integration.testcases import v2_1_only
from tests.integration.testcases import v2_only
-
+from tests.integration.testcases import v3_only
ProcessResult = namedtuple('ProcessResult', 'stdout stderr')
@@ -61,7 +72,8 @@ def wait_on_condition(condition, delay=0.1, timeout=40):
def kill_service(service):
for container in service.containers():
- container.kill()
+ if container.is_running:
+ container.kill()
class ContainerCountCondition(object):
@@ -71,7 +83,7 @@ class ContainerCountCondition(object):
self.expected = expected
def __call__(self):
- return len(self.project.containers()) == self.expected
+ return len([c for c in self.project.containers() if c.is_running]) == self.expected
def __str__(self):
return "waiting for counter count == %s" % self.expected
@@ -100,19 +112,25 @@ class CLITestCase(DockerClientTestCase):
def setUp(self):
super(CLITestCase, self).setUp()
self.base_dir = 'tests/fixtures/simple-composefile'
+ self.override_dir = None
def tearDown(self):
if self.base_dir:
self.project.kill()
- self.project.remove_stopped()
+ self.project.down(None, True)
for container in self.project.containers(stopped=True, one_off=OneOffFilter.only):
container.remove(force=True)
-
networks = self.client.networks()
for n in networks:
- if n['Name'].startswith('{}_'.format(self.project.name)):
+ if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name)):
self.client.remove_network(n['Name'])
+ volumes = self.client.volumes().get('Volumes') or []
+ for v in volumes:
+ if v['Name'].split('/')[-1].startswith('{}_'.format(self.project.name)):
+ self.client.remove_volume(v['Name'])
+ if hasattr(self, '_project'):
+ del self._project
super(CLITestCase, self).tearDown()
@@ -120,7 +138,7 @@ class CLITestCase(DockerClientTestCase):
def project(self):
# Hack: allow project to be overridden
if not hasattr(self, '_project'):
- self._project = get_project(self.base_dir)
+ self._project = get_project(self.base_dir, override_dir=self.override_dir)
return self._project
def dispatch(self, options, project_options=None, returncode=0):
@@ -141,10 +159,16 @@ class CLITestCase(DockerClientTestCase):
def test_help(self):
self.base_dir = 'tests/fixtures/no-composefile'
result = self.dispatch(['help', 'up'], returncode=0)
- assert 'Usage: up [options] [SERVICE...]' in result.stdout
+ assert 'Usage: up [options] [--scale SERVICE=NUM...] [SERVICE...]' in result.stdout
# Prevent tearDown from trying to create a project
self.base_dir = None
+ def test_help_nonexistent(self):
+ self.base_dir = 'tests/fixtures/no-composefile'
+ result = self.dispatch(['help', 'foobar'], returncode=1)
+ assert 'No such command' in result.stderr
+ self.base_dir = None
+
def test_shorthand_host_opt(self):
self.dispatch(
['-H={0}'.format(os.environ.get('DOCKER_HOST', 'unix://')),
@@ -152,11 +176,32 @@ class CLITestCase(DockerClientTestCase):
returncode=0
)
+ def test_host_not_reachable(self):
+ result = self.dispatch(['-H=tcp://doesnotexist:8000', 'ps'], returncode=1)
+ assert "Couldn't connect to Docker daemon" in result.stderr
+
+ def test_host_not_reachable_volumes_from_container(self):
+ self.base_dir = 'tests/fixtures/volumes-from-container'
+
+ container = self.client.create_container(
+ 'busybox', 'true', name='composetest_data_container',
+ host_config={}
+ )
+ self.addCleanup(self.client.remove_container, container)
+
+ result = self.dispatch(['-H=tcp://doesnotexist:8000', 'ps'], returncode=1)
+ assert "Couldn't connect to Docker daemon" in result.stderr
+
def test_config_list_services(self):
self.base_dir = 'tests/fixtures/v2-full'
result = self.dispatch(['config', '--services'])
assert set(result.stdout.rstrip().split('\n')) == {'web', 'other'}
+ def test_config_list_volumes(self):
+ self.base_dir = 'tests/fixtures/v2-full'
+ result = self.dispatch(['config', '--volumes'])
+ assert set(result.stdout.rstrip().split('\n')) == {'data'}
+
def test_config_quiet_with_error(self):
self.base_dir = None
result = self.dispatch([
@@ -191,7 +236,7 @@ class CLITestCase(DockerClientTestCase):
'other': {
'image': 'busybox:latest',
'command': 'top',
- 'volumes': ['/data:rw'],
+ 'volumes': ['/data'],
},
},
}
@@ -219,9 +264,11 @@ class CLITestCase(DockerClientTestCase):
'image': 'busybox',
'restart': 'on-failure:5',
},
+ 'restart-null': {
+ 'image': 'busybox',
+ 'restart': ''
+ },
},
- 'networks': {},
- 'volumes': {},
}
def test_config_external_network(self):
@@ -238,11 +285,75 @@ class CLITestCase(DockerClientTestCase):
}
}
+ def test_config_external_volume_v2(self):
+ self.base_dir = 'tests/fixtures/volumes'
+ result = self.dispatch(['-f', 'external-volumes-v2.yml', 'config'])
+ json_result = yaml.load(result.stdout)
+ assert 'volumes' in json_result
+ assert json_result['volumes'] == {
+ 'foo': {
+ 'external': True,
+ },
+ 'bar': {
+ 'external': {
+ 'name': 'some_bar',
+ },
+ }
+ }
+
+ def test_config_external_volume_v2_x(self):
+ self.base_dir = 'tests/fixtures/volumes'
+ result = self.dispatch(['-f', 'external-volumes-v2-x.yml', 'config'])
+ json_result = yaml.load(result.stdout)
+ assert 'volumes' in json_result
+ assert json_result['volumes'] == {
+ 'foo': {
+ 'external': True,
+ 'name': 'some_foo',
+ },
+ 'bar': {
+ 'external': True,
+ 'name': 'some_bar',
+ }
+ }
+
+ def test_config_external_volume_v3_x(self):
+ self.base_dir = 'tests/fixtures/volumes'
+ result = self.dispatch(['-f', 'external-volumes-v3-x.yml', 'config'])
+ json_result = yaml.load(result.stdout)
+ assert 'volumes' in json_result
+ assert json_result['volumes'] == {
+ 'foo': {
+ 'external': True,
+ },
+ 'bar': {
+ 'external': {
+ 'name': 'some_bar',
+ },
+ }
+ }
+
+ def test_config_external_volume_v3_4(self):
+ self.base_dir = 'tests/fixtures/volumes'
+ result = self.dispatch(['-f', 'external-volumes-v3-4.yml', 'config'])
+ json_result = yaml.load(result.stdout)
+ assert 'volumes' in json_result
+ assert json_result['volumes'] == {
+ 'foo': {
+ 'external': True,
+ 'name': 'some_foo',
+ },
+ 'bar': {
+ 'external': True,
+ 'name': 'some_bar',
+ }
+ }
+
def test_config_v1(self):
self.base_dir = 'tests/fixtures/v1-config'
result = self.dispatch(['config'])
assert yaml.load(result.stdout) == {
- 'version': '2.0',
+ 'version': '2.1',
'services': {
'net': {
'image': 'busybox',
@@ -250,7 +361,7 @@ class CLITestCase(DockerClientTestCase):
},
'volume': {
'image': 'busybox',
- 'volumes': ['/data:rw'],
+ 'volumes': ['/data'],
'network_mode': 'bridge',
},
'app': {
@@ -259,8 +370,73 @@ class CLITestCase(DockerClientTestCase):
'network_mode': 'service:net',
},
},
- 'networks': {},
- 'volumes': {},
+ }
+
+ @v3_only()
+ def test_config_v3(self):
+ self.base_dir = 'tests/fixtures/v3-full'
+ result = self.dispatch(['config'])
+
+ assert yaml.load(result.stdout) == {
+ 'version': '3.2',
+ 'volumes': {
+ 'foobar': {
+ 'labels': {
+ 'com.docker.compose.test': 'true',
+ },
+ },
+ },
+ 'services': {
+ 'web': {
+ 'image': 'busybox',
+ 'deploy': {
+ 'mode': 'replicated',
+ 'replicas': 6,
+ 'labels': ['FOO=BAR'],
+ 'update_config': {
+ 'parallelism': 3,
+ 'delay': '10s',
+ 'failure_action': 'continue',
+ 'monitor': '60s',
+ 'max_failure_ratio': 0.3,
+ },
+ 'resources': {
+ 'limits': {
+ 'cpus': '0.001',
+ 'memory': '50M',
+ },
+ 'reservations': {
+ 'cpus': '0.0001',
+ 'memory': '20M',
+ },
+ },
+ 'restart_policy': {
+ 'condition': 'on_failure',
+ 'delay': '5s',
+ 'max_attempts': 3,
+ 'window': '120s',
+ },
+ 'placement': {
+ 'constraints': ['node=foo'],
+ },
+ },
+
+ 'healthcheck': {
+ 'test': 'cat /etc/passwd',
+ 'interval': '10s',
+ 'timeout': '1s',
+ 'retries': 5,
+ },
+ 'volumes': [
+ '/host/path:/container/path:ro',
+ 'foobar:/container/volumepath:rw',
+ '/anonymous',
+ 'foobar:/container/volumepath2:nocopy'
+ ],
+
+ 'stop_grace_period': '20s',
+ },
+ },
}
def test_ps(self):
@@ -308,19 +484,38 @@ class CLITestCase(DockerClientTestCase):
def test_pull_with_ignore_pull_failures(self):
result = self.dispatch([
'-f', 'ignore-pull-failures.yml',
- 'pull', '--ignore-pull-failures'])
+ 'pull', '--ignore-pull-failures']
+ )
assert 'Pulling simple (busybox:latest)...' in result.stderr
assert 'Pulling another (nonexisting-image:latest)...' in result.stderr
- assert 'Error: image library/nonexisting-image' in result.stderr
- assert 'not found' in result.stderr
+ assert ('repository nonexisting-image not found' in result.stderr or
+ 'image library/nonexisting-image:latest not found' in result.stderr or
+ 'pull access denied for nonexisting-image' in result.stderr)
+
+ def test_pull_with_parallel_failure(self):
+ result = self.dispatch([
+ '-f', 'ignore-pull-failures.yml', 'pull', '--parallel'],
+ returncode=1
+ )
+
+ self.assertRegexpMatches(result.stderr, re.compile('^Pulling simple', re.MULTILINE))
+ self.assertRegexpMatches(result.stderr, re.compile('^Pulling another', re.MULTILINE))
+ self.assertRegexpMatches(result.stderr,
+ re.compile('^ERROR: for another .*does not exist.*', re.MULTILINE))
+ self.assertRegexpMatches(result.stderr,
+ re.compile('''^(ERROR: )?(b')?.* nonexisting-image''',
+ re.MULTILINE))
+
+ def test_pull_with_quiet(self):
+ assert self.dispatch(['pull', '--quiet']).stderr == ''
+ assert self.dispatch(['pull', '--quiet']).stdout == ''
def test_build_plain(self):
self.base_dir = 'tests/fixtures/simple-dockerfile'
self.dispatch(['build', 'simple'])
result = self.dispatch(['build', 'simple'])
- assert BUILD_CACHE_TEXT in result.stdout
assert BUILD_PULL_TEXT not in result.stdout
def test_build_no_cache(self):
@@ -338,7 +533,9 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['build', 'simple'], None)
result = self.dispatch(['build', '--pull', 'simple'])
- assert BUILD_CACHE_TEXT in result.stdout
+ if not is_cluster(self.client):
+ # If previous build happened on another node, cache won't be available
+ assert BUILD_CACHE_TEXT in result.stdout
assert BUILD_PULL_TEXT in result.stdout
def test_build_no_cache_pull(self):
@@ -351,6 +548,7 @@ class CLITestCase(DockerClientTestCase):
assert BUILD_CACHE_TEXT not in result.stdout
assert BUILD_PULL_TEXT in result.stdout
+ @pytest.mark.xfail(reason='17.10.0 RC bug remove after GA https://github.com/moby/moby/issues/35116')
def test_build_failed(self):
self.base_dir = 'tests/fixtures/simple-failing-dockerfile'
self.dispatch(['build', 'simple'], returncode=1)
@@ -364,6 +562,7 @@ class CLITestCase(DockerClientTestCase):
]
assert len(containers) == 1
+ @pytest.mark.xfail(reason='17.10.0 RC bug remove after GA https://github.com/moby/moby/issues/35116')
def test_build_failed_forcerm(self):
self.base_dir = 'tests/fixtures/simple-failing-dockerfile'
self.dispatch(['build', '--force-rm', 'simple'], returncode=1)
@@ -378,9 +577,15 @@ class CLITestCase(DockerClientTestCase):
]
assert not containers
+ def test_build_shm_size_build_option(self):
+ pull_busybox(self.client)
+ self.base_dir = 'tests/fixtures/build-shm-size'
+ result = self.dispatch(['build', '--no-cache'], None)
+ assert 'shm_size: 96' in result.stdout
+
def test_bundle_with_digests(self):
self.base_dir = 'tests/fixtures/bundle-with-digests/'
- tmpdir = py.test.ensuretemp('cli_test_bundle')
+ tmpdir = pytest.ensuretemp('cli_test_bundle')
self.addCleanup(tmpdir.remove)
filename = str(tmpdir.join('example.dab'))
@@ -404,46 +609,136 @@ class CLITestCase(DockerClientTestCase):
},
}
+ def test_build_override_dir(self):
+ self.base_dir = 'tests/fixtures/build-path-override-dir'
+ self.override_dir = os.path.abspath('tests/fixtures')
+ result = self.dispatch([
+ '--project-directory', self.override_dir,
+ 'build'])
+
+ assert 'Successfully built' in result.stdout
+
+ def test_build_override_dir_invalid_path(self):
+ config_path = os.path.abspath('tests/fixtures/build-path-override-dir/docker-compose.yml')
+ result = self.dispatch([
+ '-f', config_path,
+ 'build'], returncode=1)
+
+ assert 'does not exist, is not accessible, or is not a valid URL' in result.stderr
+
def test_create(self):
self.dispatch(['create'])
service = self.project.get_service('simple')
another = self.project.get_service('another')
- self.assertEqual(len(service.containers()), 0)
- self.assertEqual(len(another.containers()), 0)
- self.assertEqual(len(service.containers(stopped=True)), 1)
- self.assertEqual(len(another.containers(stopped=True)), 1)
+ service_containers = service.containers(stopped=True)
+ another_containers = another.containers(stopped=True)
+ assert len(service_containers) == 1
+ assert len(another_containers) == 1
+ assert not service_containers[0].is_running
+ assert not another_containers[0].is_running
def test_create_with_force_recreate(self):
self.dispatch(['create'], None)
service = self.project.get_service('simple')
- self.assertEqual(len(service.containers()), 0)
- self.assertEqual(len(service.containers(stopped=True)), 1)
+ service_containers = service.containers(stopped=True)
+ assert len(service_containers) == 1
+ assert not service_containers[0].is_running
old_ids = [c.id for c in service.containers(stopped=True)]
self.dispatch(['create', '--force-recreate'], None)
- self.assertEqual(len(service.containers()), 0)
- self.assertEqual(len(service.containers(stopped=True)), 1)
+ service_containers = service.containers(stopped=True)
+ assert len(service_containers) == 1
+ assert not service_containers[0].is_running
- new_ids = [c.id for c in service.containers(stopped=True)]
+ new_ids = [c.id for c in service_containers]
- self.assertNotEqual(old_ids, new_ids)
+ assert old_ids != new_ids
def test_create_with_no_recreate(self):
self.dispatch(['create'], None)
service = self.project.get_service('simple')
- self.assertEqual(len(service.containers()), 0)
- self.assertEqual(len(service.containers(stopped=True)), 1)
+ service_containers = service.containers(stopped=True)
+ assert len(service_containers) == 1
+ assert not service_containers[0].is_running
old_ids = [c.id for c in service.containers(stopped=True)]
self.dispatch(['create', '--no-recreate'], None)
- self.assertEqual(len(service.containers()), 0)
- self.assertEqual(len(service.containers(stopped=True)), 1)
+ service_containers = service.containers(stopped=True)
+ assert len(service_containers) == 1
+ assert not service_containers[0].is_running
- new_ids = [c.id for c in service.containers(stopped=True)]
+ new_ids = [c.id for c in service_containers]
- self.assertEqual(old_ids, new_ids)
+ assert old_ids == new_ids
+
+ def test_run_one_off_with_volume(self):
+ self.base_dir = 'tests/fixtures/simple-composefile-volume-ready'
+ volume_path = os.path.abspath(os.path.join(os.getcwd(), self.base_dir, 'files'))
+ node = create_host_file(self.client, os.path.join(volume_path, 'example.txt'))
+
+ self.dispatch([
+ 'run',
+ '-v', '{}:/data'.format(volume_path),
+ '-e', 'constraint:node=={}'.format(node if node is not None else '*'),
+ 'simple',
+ 'test', '-f', '/data/example.txt'
+ ], returncode=0)
+
+ service = self.project.get_service('simple')
+ container_data = service.containers(one_off=OneOffFilter.only, stopped=True)[0]
+ mount = container_data.get('Mounts')[0]
+ assert mount['Source'] == volume_path
+ assert mount['Destination'] == '/data'
+ assert mount['Type'] == 'bind'
+
+ def test_run_one_off_with_multiple_volumes(self):
+ self.base_dir = 'tests/fixtures/simple-composefile-volume-ready'
+ volume_path = os.path.abspath(os.path.join(os.getcwd(), self.base_dir, 'files'))
+ node = create_host_file(self.client, os.path.join(volume_path, 'example.txt'))
+
+ self.dispatch([
+ 'run',
+ '-v', '{}:/data'.format(volume_path),
+ '-v', '{}:/data1'.format(volume_path),
+ '-e', 'constraint:node=={}'.format(node if node is not None else '*'),
+ 'simple',
+ 'test', '-f', '/data/example.txt'
+ ], returncode=0)
+
+ self.dispatch([
+ 'run',
+ '-v', '{}:/data'.format(volume_path),
+ '-v', '{}:/data1'.format(volume_path),
+ '-e', 'constraint:node=={}'.format(node if node is not None else '*'),
+ 'simple',
+ 'test', '-f' '/data1/example.txt'
+ ], returncode=0)
+
+ def test_run_one_off_with_volume_merge(self):
+ self.base_dir = 'tests/fixtures/simple-composefile-volume-ready'
+ volume_path = os.path.abspath(os.path.join(os.getcwd(), self.base_dir, 'files'))
+ create_host_file(self.client, os.path.join(volume_path, 'example.txt'))
+
+ self.dispatch([
+ '-f', 'docker-compose.merge.yml',
+ 'run',
+ '-v', '{}:/data'.format(volume_path),
+ 'simple',
+ 'test', '-f', '/data/example.txt'
+ ], returncode=0)
+
+ service = self.project.get_service('simple')
+ container_data = service.containers(one_off=OneOffFilter.only, stopped=True)[0]
+ mounts = container_data.get('Mounts')
+ assert len(mounts) == 2
+ config_mount = [m for m in mounts if m['Destination'] == '/data1'][0]
+ override_mount = [m for m in mounts if m['Destination'] == '/data'][0]
+
+ assert config_mount['Type'] == 'volume'
+ assert override_mount['Source'] == volume_path
+ assert override_mount['Type'] == 'bind'
def test_create_with_force_recreate_and_no_recreate(self):
self.dispatch(
@@ -511,7 +806,7 @@ class CLITestCase(DockerClientTestCase):
network_name = self.project.networks.networks['default'].full_name
networks = self.client.networks(names=[network_name])
self.assertEqual(len(networks), 1)
- self.assertEqual(networks[0]['Driver'], 'bridge')
+ assert networks[0]['Driver'] == 'bridge' if not is_cluster(self.client) else 'overlay'
assert 'com.docker.network.bridge.enable_icc' not in networks[0]['Options']
network = self.client.inspect_network(networks[0]['Id'])
@@ -534,6 +829,45 @@ class CLITestCase(DockerClientTestCase):
assert self.lookup(container, service.name)
@v2_only()
+ def test_up_no_start(self):
+ self.base_dir = 'tests/fixtures/v2-full'
+ self.dispatch(['up', '--no-start'], None)
+
+ services = self.project.get_services()
+
+ default_network = self.project.networks.networks['default'].full_name
+ front_network = self.project.networks.networks['front'].full_name
+ networks = self.client.networks(names=[default_network, front_network])
+ assert len(networks) == 2
+
+ for service in services:
+ containers = service.containers(stopped=True)
+ assert len(containers) == 1
+
+ container = containers[0]
+ assert not container.is_running
+ assert container.get('State.Status') == 'created'
+
+ volumes = self.project.volumes.volumes
+ assert 'data' in volumes
+ volume = volumes['data']
+
+ # The code below is a Swarm-compatible equivalent to volume.exists()
+ remote_volumes = [
+ v for v in self.client.volumes().get('Volumes', [])
+ if v['Name'].split('/')[-1] == volume.full_name
+ ]
+ assert len(remote_volumes) > 0
+
+ @v2_only()
+ def test_up_no_ansi(self):
+ self.base_dir = 'tests/fixtures/v2-simple'
+ result = self.dispatch(['--no-ansi', 'up', '-d'], None)
+ assert "%c[2K\r" % 27 not in result.stderr
+ assert "%c[1A" % 27 not in result.stderr
+ assert "%c[1B" % 27 not in result.stderr
+
+ @v2_only()
def test_up_with_default_network_config(self):
filename = 'default-network-config.yml'
@@ -557,11 +891,11 @@ class CLITestCase(DockerClientTestCase):
networks = [
n for n in self.client.networks()
- if n['Name'].startswith('{}_'.format(self.project.name))
+ if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
]
# Two networks were created: back and front
- assert sorted(n['Name'] for n in networks) == [back_name, front_name]
+ assert sorted(n['Name'].split('/')[-1] for n in networks) == [back_name, front_name]
web_container = self.project.get_service('web').containers()[0]
back_aliases = web_container.get(
@@ -576,6 +910,24 @@ class CLITestCase(DockerClientTestCase):
assert 'ahead' in front_aliases
@v2_only()
+ def test_up_with_network_internal(self):
+ self.require_api_version('1.23')
+ filename = 'network-internal.yml'
+ self.base_dir = 'tests/fixtures/networks'
+ self.dispatch(['-f', filename, 'up', '-d'], None)
+ internal_net = '{}_internal'.format(self.project.name)
+
+ networks = [
+ n for n in self.client.networks()
+ if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
+ ]
+
+ # One network was created: internal
+ assert sorted(n['Name'].split('/')[-1] for n in networks) == [internal_net]
+
+ assert networks[0]['Internal'] is True
+
+ @v2_only()
def test_up_with_network_static_addresses(self):
filename = 'network-static-addresses.yml'
ipv4_address = '172.16.100.100'
@@ -586,11 +938,11 @@ class CLITestCase(DockerClientTestCase):
networks = [
n for n in self.client.networks()
- if n['Name'].startswith('{}_'.format(self.project.name))
+ if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
]
# One networks was created: front
- assert sorted(n['Name'] for n in networks) == [static_net]
+ assert sorted(n['Name'].split('/')[-1] for n in networks) == [static_net]
web_container = self.project.get_service('web').containers()[0]
ipam_config = web_container.get(
@@ -609,14 +961,19 @@ class CLITestCase(DockerClientTestCase):
networks = [
n for n in self.client.networks()
- if n['Name'].startswith('{}_'.format(self.project.name))
+ if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
]
# Two networks were created: back and front
- assert sorted(n['Name'] for n in networks) == [back_name, front_name]
+ assert sorted(n['Name'].split('/')[-1] for n in networks) == [back_name, front_name]
- back_network = [n for n in networks if n['Name'] == back_name][0]
- front_network = [n for n in networks if n['Name'] == front_name][0]
+ # lookup by ID instead of name in case of duplicates
+ back_network = self.client.inspect_network(
+ [n for n in networks if n['Name'] == back_name][0]['Id']
+ )
+ front_network = self.client.inspect_network(
+ [n for n in networks if n['Name'] == front_name][0]['Id']
+ )
web_container = self.project.get_service('web').containers()[0]
app_container = self.project.get_service('app').containers()[0]
@@ -653,8 +1010,12 @@ class CLITestCase(DockerClientTestCase):
assert 'Service "web" uses an undefined network "foo"' in result.stderr
@v2_only()
+ @no_cluster('container networks not supported in Swarm')
def test_up_with_network_mode(self):
- c = self.client.create_container('busybox', 'top', name='composetest_network_mode_container')
+ c = self.client.create_container(
+ 'busybox', 'top', name='composetest_network_mode_container',
+ host_config={}
+ )
self.addCleanup(self.client.remove_container, c, force=True)
self.client.start(c)
container_mode_source = 'container:{}'.format(c['Id'])
@@ -668,7 +1029,7 @@ class CLITestCase(DockerClientTestCase):
networks = [
n for n in self.client.networks()
- if n['Name'].startswith('{}_'.format(self.project.name))
+ if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
]
assert not networks
@@ -705,7 +1066,7 @@ class CLITestCase(DockerClientTestCase):
network_names = ['{}_{}'.format(self.project.name, n) for n in ['foo', 'bar']]
for name in network_names:
- self.client.create_network(name)
+ self.client.create_network(name, attachable=True)
self.dispatch(['-f', filename, 'up', '-d'])
container = self.project.containers()[0]
@@ -723,17 +1084,57 @@ class CLITestCase(DockerClientTestCase):
networks = [
n['Name'] for n in self.client.networks()
- if n['Name'].startswith('{}_'.format(self.project.name))
+ if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
]
assert not networks
network_name = 'composetest_external_network'
- self.client.create_network(network_name)
+ self.client.create_network(network_name, attachable=True)
self.dispatch(['-f', filename, 'up', '-d'])
container = self.project.containers()[0]
assert list(container.get('NetworkSettings.Networks')) == [network_name]
+ @v2_1_only()
+ def test_up_with_network_labels(self):
+ filename = 'network-label.yml'
+
+ self.base_dir = 'tests/fixtures/networks'
+ self._project = get_project(self.base_dir, [filename])
+
+ self.dispatch(['-f', filename, 'up', '-d'], returncode=0)
+
+ network_with_label = '{}_network_with_label'.format(self.project.name)
+
+ networks = [
+ n for n in self.client.networks()
+ if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
+ ]
+
+ assert [n['Name'].split('/')[-1] for n in networks] == [network_with_label]
+ assert 'label_key' in networks[0]['Labels']
+ assert networks[0]['Labels']['label_key'] == 'label_val'
+
+ @v2_1_only()
+ def test_up_with_volume_labels(self):
+ filename = 'volume-label.yml'
+
+ self.base_dir = 'tests/fixtures/volumes'
+ self._project = get_project(self.base_dir, [filename])
+
+ self.dispatch(['-f', filename, 'up', '-d'], returncode=0)
+
+ volume_with_label = '{}_volume_with_label'.format(self.project.name)
+
+ volumes = [
+ v for v in self.client.volumes().get('Volumes', [])
+ if v['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
+ ]
+
+ assert set([v['Name'].split('/')[-1] for v in volumes]) == set([volume_with_label])
+ assert 'label_key' in volumes[0]['Labels']
+ assert volumes[0]['Labels']['label_key'] == 'label_val'
+
@v2_only()
def test_up_no_services(self):
self.base_dir = 'tests/fixtures/no-services'
@@ -741,7 +1142,7 @@ class CLITestCase(DockerClientTestCase):
network_names = [
n['Name'] for n in self.client.networks()
- if n['Name'].startswith('{}_'.format(self.project.name))
+ if n['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
]
assert network_names == []
@@ -776,6 +1177,7 @@ class CLITestCase(DockerClientTestCase):
assert "Unsupported config option for services.bar: 'net'" in result.stderr
+ @no_cluster("Legacy networking not supported on Swarm")
def test_up_with_net_v1(self):
self.base_dir = 'tests/fixtures/net-container'
self.dispatch(['up', '-d'], None)
@@ -789,6 +1191,50 @@ class CLITestCase(DockerClientTestCase):
assert foo_container.get('HostConfig.NetworkMode') == \
'container:{}'.format(bar_container.id)
+ @v3_only()
+ def test_up_with_healthcheck(self):
+ def wait_on_health_status(container, status):
+ def condition():
+ container.inspect()
+ return container.get('State.Health.Status') == status
+
+ return wait_on_condition(condition, delay=0.5)
+
+ self.base_dir = 'tests/fixtures/healthcheck'
+ self.dispatch(['up', '-d'], None)
+
+ passes = self.project.get_service('passes')
+ passes_container = passes.containers()[0]
+
+ assert passes_container.get('Config.Healthcheck') == {
+ "Test": ["CMD-SHELL", "/bin/true"],
+ "Interval": nanoseconds_from_time_seconds(1),
+ "Timeout": nanoseconds_from_time_seconds(30 * 60),
+ "Retries": 1,
+ }
+
+ wait_on_health_status(passes_container, 'healthy')
+
+ fails = self.project.get_service('fails')
+ fails_container = fails.containers()[0]
+
+ assert fails_container.get('Config.Healthcheck') == {
+ "Test": ["CMD", "/bin/false"],
+ "Interval": nanoseconds_from_time_seconds(2.5),
+ "Retries": 2,
+ }
+
+ wait_on_health_status(fails_container, 'unhealthy')
+
+ disabled = self.project.get_service('disabled')
+ disabled_container = disabled.containers()[0]
+
+ assert disabled_container.get('Config.Healthcheck') == {
+ "Test": ["NONE"],
+ }
+
+ assert 'Health' not in disabled_container.get('State')
+
def test_up_with_no_deps(self):
self.base_dir = 'tests/fixtures/links-composefile'
self.dispatch(['up', '-d', '--no-deps', 'web'], None)
@@ -871,10 +1317,44 @@ class CLITestCase(DockerClientTestCase):
wait_on_condition(ContainerCountCondition(self.project, 0))
def test_up_handles_abort_on_container_exit(self):
- start_process(self.base_dir, ['up', '--abort-on-container-exit'])
- wait_on_condition(ContainerCountCondition(self.project, 2))
- self.project.stop(['simple'])
+ self.base_dir = 'tests/fixtures/abort-on-container-exit-0'
+ proc = start_process(self.base_dir, ['up', '--abort-on-container-exit'])
wait_on_condition(ContainerCountCondition(self.project, 0))
+ proc.wait()
+ self.assertEqual(proc.returncode, 0)
+
+ def test_up_handles_abort_on_container_exit_code(self):
+ self.base_dir = 'tests/fixtures/abort-on-container-exit-1'
+ proc = start_process(self.base_dir, ['up', '--abort-on-container-exit'])
+ wait_on_condition(ContainerCountCondition(self.project, 0))
+ proc.wait()
+ self.assertEqual(proc.returncode, 1)
+
+ @v2_only()
+ @no_cluster('Container PID mode does not work across clusters')
+ def test_up_with_pid_mode(self):
+ c = self.client.create_container(
+ 'busybox', 'top', name='composetest_pid_mode_container',
+ host_config={}
+ )
+ self.addCleanup(self.client.remove_container, c, force=True)
+ self.client.start(c)
+ container_mode_source = 'container:{}'.format(c['Id'])
+
+ self.base_dir = 'tests/fixtures/pid-mode'
+
+ self.dispatch(['up', '-d'], None)
+
+ service_mode_source = 'container:{}'.format(
+ self.project.get_service('container').containers()[0].id)
+ service_mode_container = self.project.get_service('service').containers()[0]
+ assert service_mode_container.get('HostConfig.PidMode') == service_mode_source
+
+ container_mode_container = self.project.get_service('container').containers()[0]
+ assert container_mode_container.get('HostConfig.PidMode') == container_mode_source
+
+ host_mode_container = self.project.get_service('host').containers()[0]
+ assert host_mode_container.get('HostConfig.PidMode') == 'host'
def test_exec_without_tty(self):
self.base_dir = 'tests/fixtures/links-composefile'
@@ -882,8 +1362,8 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(self.project.containers()), 1)
stdout, stderr = self.dispatch(['exec', '-T', 'console', 'ls', '-1d', '/'])
- self.assertEquals(stdout, "/\n")
- self.assertEquals(stderr, "")
+ self.assertEqual(stderr, "")
+ self.assertEqual(stdout, "/\n")
def test_exec_custom_user(self):
self.base_dir = 'tests/fixtures/links-composefile'
@@ -891,8 +1371,8 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(self.project.containers()), 1)
stdout, stderr = self.dispatch(['exec', '-T', '--user=operator', 'console', 'whoami'])
- self.assertEquals(stdout, "operator\n")
- self.assertEquals(stderr, "")
+ self.assertEqual(stdout, "operator\n")
+ self.assertEqual(stderr, "")
def test_run_service_without_links(self):
self.base_dir = 'tests/fixtures/links-composefile'
@@ -923,6 +1403,17 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(db.containers()), 1)
self.assertEqual(len(console.containers()), 0)
+ def test_run_service_with_scaled_dependencies(self):
+ self.base_dir = 'tests/fixtures/v2-dependencies'
+ self.dispatch(['up', '-d', '--scale', 'db=2', '--scale', 'console=0'])
+ db = self.project.get_service('db')
+ console = self.project.get_service('console')
+ assert len(db.containers()) == 2
+ assert len(console.containers()) == 0
+ self.dispatch(['run', 'web', '/bin/true'], None)
+ assert len(db.containers()) == 2
+ assert len(console.containers()) == 0
+
def test_run_with_no_deps(self):
self.base_dir = 'tests/fixtures/links-composefile'
self.dispatch(['run', '--no-deps', 'web', '/bin/true'])
@@ -964,6 +1455,37 @@ class CLITestCase(DockerClientTestCase):
[u'/bin/true'],
)
+ @pytest.mark.skipif(SWARM_SKIP_RM_VOLUMES, reason='Swarm DELETE /containers/<id> bug')
+ def test_run_rm(self):
+ self.base_dir = 'tests/fixtures/volume'
+ proc = start_process(self.base_dir, ['run', '--rm', 'test'])
+ wait_on_condition(ContainerStateCondition(
+ self.project.client,
+ 'volume_test_run_1',
+ 'running'))
+ service = self.project.get_service('test')
+ containers = service.containers(one_off=OneOffFilter.only)
+ self.assertEqual(len(containers), 1)
+ mounts = containers[0].get('Mounts')
+ for mount in mounts:
+ if mount['Destination'] == '/container-path':
+ anonymous_name = mount['Name']
+ break
+ os.kill(proc.pid, signal.SIGINT)
+ wait_on_process(proc, 1)
+
+ self.assertEqual(len(service.containers(stopped=True, one_off=OneOffFilter.only)), 0)
+
+ volumes = self.client.volumes()['Volumes']
+ assert volumes is not None
+ for volume in service.options.get('volumes'):
+ if volume.internal == '/container-named-path':
+ name = volume.external
+ break
+ volume_names = [v['Name'].split('/')[-1] for v in volumes]
+ assert name in volume_names
+ assert anonymous_name not in volume_names
+
def test_run_service_with_dockerfile_entrypoint(self):
self.base_dir = 'tests/fixtures/entrypoint-dockerfile'
self.dispatch(['run', 'test'])
@@ -1031,7 +1553,7 @@ class CLITestCase(DockerClientTestCase):
container = service.containers(stopped=True, one_off=OneOffFilter.only)[0]
self.assertEqual(user, container.get('Config.User'))
- def test_run_service_with_environement_overridden(self):
+ def test_run_service_with_environment_overridden(self):
name = 'service'
self.base_dir = 'tests/fixtures/environment-composefile'
self.dispatch([
@@ -1043,9 +1565,9 @@ class CLITestCase(DockerClientTestCase):
])
service = self.project.get_service(name)
container = service.containers(stopped=True, one_off=OneOffFilter.only)[0]
- # env overriden
+ # env overridden
self.assertEqual('notbar', container.environment['foo'])
- # keep environement from yaml
+ # keep environment from yaml
self.assertEqual('world', container.environment['hello'])
# added option from command line
self.assertEqual('beta', container.environment['alpha'])
@@ -1084,13 +1606,12 @@ class CLITestCase(DockerClientTestCase):
container.stop()
# check the ports
- self.assertNotEqual(port_random, None)
- self.assertIn("0.0.0.0", port_random)
- self.assertEqual(port_assigned, "0.0.0.0:49152")
- self.assertEqual(port_range[0], "0.0.0.0:49153")
- self.assertEqual(port_range[1], "0.0.0.0:49154")
+ assert port_random is not None
+ assert port_assigned.endswith(':49152')
+ assert port_range[0].endswith(':49153')
+ assert port_range[1].endswith(':49154')
- def test_run_service_with_explicitly_maped_ports(self):
+ def test_run_service_with_explicitly_mapped_ports(self):
# create one off container
self.base_dir = 'tests/fixtures/ports-composefile'
self.dispatch(['run', '-d', '-p', '30000:3000', '--publish', '30001:3001', 'simple'])
@@ -1104,10 +1625,10 @@ class CLITestCase(DockerClientTestCase):
container.stop()
# check the ports
- self.assertEqual(port_short, "0.0.0.0:30000")
- self.assertEqual(port_full, "0.0.0.0:30001")
+ assert port_short.endswith(':30000')
+ assert port_full.endswith(':30001')
- def test_run_service_with_explicitly_maped_ip_ports(self):
+ def test_run_service_with_explicitly_mapped_ip_ports(self):
# create one off container
self.base_dir = 'tests/fixtures/ports-composefile'
self.dispatch([
@@ -1254,6 +1775,23 @@ class CLITestCase(DockerClientTestCase):
'exited'))
@mock.patch.dict(os.environ)
+ def test_run_unicode_env_values_from_system(self):
+ value = 'ą, ć, ę, ł, ń, ó, ś, ź, ż'
+ if six.PY2: # os.environ doesn't support unicode values in Py2
+ os.environ['BAR'] = value.encode('utf-8')
+ else: # ... and doesn't support byte values in Py3
+ os.environ['BAR'] = value
+ self.base_dir = 'tests/fixtures/unicode-environment'
+ result = self.dispatch(['run', 'simple'])
+
+ if six.PY2: # Can't retrieve output on Py3. See issue #3670
+ assert value == result.stdout.strip()
+
+ container = self.project.containers(one_off=OneOffFilter.only, stopped=True)[0]
+ environment = container.get('Config.Env')
+ assert 'FOO={}'.format(value) in environment
+
+ @mock.patch.dict(os.environ)
def test_run_env_values_from_system(self):
os.environ['FOO'] = 'bar'
os.environ['BAR'] = 'baz'
@@ -1278,6 +1816,27 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(service.containers(stopped=True)), 1)
self.dispatch(['rm', '-f'], None)
self.assertEqual(len(service.containers(stopped=True)), 0)
+ service = self.project.get_service('simple')
+ service.create_container()
+ self.dispatch(['rm', '-fs'], None)
+ self.assertEqual(len(service.containers(stopped=True)), 0)
+
+ def test_rm_stop(self):
+ self.dispatch(['up', '-d'], None)
+ simple = self.project.get_service('simple')
+ another = self.project.get_service('another')
+ assert len(simple.containers()) == 1
+ assert len(another.containers()) == 1
+ self.dispatch(['rm', '-fs'], None)
+ assert len(simple.containers(stopped=True)) == 0
+ assert len(another.containers(stopped=True)) == 0
+
+ self.dispatch(['up', '-d'], None)
+ assert len(simple.containers()) == 1
+ assert len(another.containers()) == 1
+ self.dispatch(['rm', '-fs', 'another'], None)
+ assert len(simple.containers()) == 1
+ assert len(another.containers(stopped=True)) == 0
def test_rm_all(self):
service = self.project.get_service('simple')
@@ -1383,7 +1942,13 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['logs', '-f'])
- assert result.stdout.count('\n') == 5
+ if not is_cluster(self.client):
+ assert result.stdout.count('\n') == 5
+ else:
+ # Sometimes logs are picked up from old containers that haven't yet
+ # been removed (removal in Swarm is async)
+ assert result.stdout.count('\n') >= 5
+
assert 'simple' in result.stdout
assert 'another' in result.stdout
assert 'exited with code 0' in result.stdout
@@ -1439,7 +2004,10 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['up'])
result = self.dispatch(['logs', '--tail', '2'])
- assert result.stdout.count('\n') == 3
+ assert 'c\n' in result.stdout
+ assert 'd\n' in result.stdout
+ assert 'a\n' not in result.stdout
+ assert 'b\n' not in result.stdout
def test_kill(self):
self.dispatch(['up', '-d'], None)
@@ -1526,6 +2094,59 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(project.get_service('simple').containers()), 0)
self.assertEqual(len(project.get_service('another').containers()), 0)
+ def test_scale_v2_2(self):
+ self.base_dir = 'tests/fixtures/scale'
+ result = self.dispatch(['scale', 'web=1'], returncode=1)
+ assert 'incompatible with the v2.2 format' in result.stderr
+
+ def test_up_scale_scale_up(self):
+ self.base_dir = 'tests/fixtures/scale'
+ project = self.project
+
+ self.dispatch(['up', '-d'])
+ assert len(project.get_service('web').containers()) == 2
+ assert len(project.get_service('db').containers()) == 1
+
+ self.dispatch(['up', '-d', '--scale', 'web=3'])
+ assert len(project.get_service('web').containers()) == 3
+ assert len(project.get_service('db').containers()) == 1
+
+ def test_up_scale_scale_down(self):
+ self.base_dir = 'tests/fixtures/scale'
+ project = self.project
+
+ self.dispatch(['up', '-d'])
+ assert len(project.get_service('web').containers()) == 2
+ assert len(project.get_service('db').containers()) == 1
+
+ self.dispatch(['up', '-d', '--scale', 'web=1'])
+ assert len(project.get_service('web').containers()) == 1
+ assert len(project.get_service('db').containers()) == 1
+
+ def test_up_scale_reset(self):
+ self.base_dir = 'tests/fixtures/scale'
+ project = self.project
+
+ self.dispatch(['up', '-d', '--scale', 'web=3', '--scale', 'db=3'])
+ assert len(project.get_service('web').containers()) == 3
+ assert len(project.get_service('db').containers()) == 3
+
+ self.dispatch(['up', '-d'])
+ assert len(project.get_service('web').containers()) == 2
+ assert len(project.get_service('db').containers()) == 1
+
+ def test_up_scale_to_zero(self):
+ self.base_dir = 'tests/fixtures/scale'
+ project = self.project
+
+ self.dispatch(['up', '-d'])
+ assert len(project.get_service('web').containers()) == 2
+ assert len(project.get_service('db').containers()) == 1
+
+ self.dispatch(['up', '-d', '--scale', 'web=0', '--scale', 'db=0'])
+ assert len(project.get_service('web').containers()) == 0
+ assert len(project.get_service('db').containers()) == 0
+
def test_port(self):
self.base_dir = 'tests/fixtures/ports-composefile'
self.dispatch(['up', '-d'], None)
@@ -1535,9 +2156,22 @@ class CLITestCase(DockerClientTestCase):
result = self.dispatch(['port', 'simple', str(number)])
return result.stdout.rstrip()
- self.assertEqual(get_port(3000), container.get_local_port(3000))
- self.assertEqual(get_port(3001), "0.0.0.0:49152")
- self.assertEqual(get_port(3002), "0.0.0.0:49153")
+ assert get_port(3000) == container.get_local_port(3000)
+ assert ':49152' in get_port(3001)
+ assert ':49153' in get_port(3002)
+
+ def test_expanded_port(self):
+ self.base_dir = 'tests/fixtures/ports-composefile'
+ self.dispatch(['-f', 'expanded-notation.yml', 'up', '-d'])
+ container = self.project.get_service('simple').get_container()
+
+ def get_port(number):
+ result = self.dispatch(['port', 'simple', str(number)])
+ return result.stdout.rstrip()
+
+ assert get_port(3000) == container.get_local_port(3000)
+ assert ':53222' in get_port(3001)
+ assert ':53223' in get_port(3002)
def test_port_with_scale(self):
self.base_dir = 'tests/fixtures/ports-composefile-scale'
@@ -1590,12 +2224,14 @@ class CLITestCase(DockerClientTestCase):
assert len(lines) == 2
container, = self.project.containers()
- expected_template = (
- ' container {} {} (image=busybox:latest, '
- 'name=simplecomposefile_simple_1)')
+ expected_template = ' container {} {}'
+ expected_meta_info = ['image=busybox:latest', 'name=simplecomposefile_simple_1']
assert expected_template.format('create', container.id) in lines[0]
assert expected_template.format('start', container.id) in lines[1]
+ for line in lines:
+ for info in expected_meta_info:
+ assert info in line
assert has_timestamp(lines[0])
@@ -1638,7 +2274,6 @@ class CLITestCase(DockerClientTestCase):
'docker-compose.yml',
'docker-compose.override.yml',
'extra.yml',
-
]
self._project = get_project(self.base_dir, config_paths)
self.dispatch(
@@ -1655,7 +2290,6 @@ class CLITestCase(DockerClientTestCase):
web, other, db = containers
self.assertEqual(web.human_readable_command, 'top')
- self.assertTrue({'db', 'other'} <= set(get_links(web)))
self.assertEqual(db.human_readable_command, 'top')
self.assertEqual(other.human_readable_command, 'top')
@@ -1687,3 +2321,70 @@ class CLITestCase(DockerClientTestCase):
"BAZ=2",
])
self.assertTrue(expected_env <= set(web.get('Config.Env')))
+
+ def test_top_services_not_running(self):
+ self.base_dir = 'tests/fixtures/top'
+ result = self.dispatch(['top'])
+ assert len(result.stdout) == 0
+
+ def test_top_services_running(self):
+ self.base_dir = 'tests/fixtures/top'
+ self.dispatch(['up', '-d'])
+ result = self.dispatch(['top'])
+
+ self.assertIn('top_service_a', result.stdout)
+ self.assertIn('top_service_b', result.stdout)
+ self.assertNotIn('top_not_a_service', result.stdout)
+
+ def test_top_processes_running(self):
+ self.base_dir = 'tests/fixtures/top'
+ self.dispatch(['up', '-d'])
+ result = self.dispatch(['top'])
+ assert result.stdout.count("top") == 4
+
+ def test_forward_exitval(self):
+ self.base_dir = 'tests/fixtures/exit-code-from'
+ proc = start_process(
+ self.base_dir,
+ ['up', '--abort-on-container-exit', '--exit-code-from', 'another'])
+
+ result = wait_on_process(proc, returncode=1)
+
+ assert 'exitcodefrom_another_1 exited with code 1' in result.stdout
+
+ def test_images(self):
+ self.project.get_service('simple').create_container()
+ result = self.dispatch(['images'])
+ assert 'busybox' in result.stdout
+ assert 'simplecomposefile_simple_1' in result.stdout
+
+ def test_images_default_composefile(self):
+ self.base_dir = 'tests/fixtures/multiple-composefiles'
+ self.dispatch(['up', '-d'])
+ result = self.dispatch(['images'])
+
+ assert 'busybox' in result.stdout
+ assert 'multiplecomposefiles_another_1' in result.stdout
+ assert 'multiplecomposefiles_simple_1' in result.stdout
+
+ def test_up_with_override_yaml(self):
+ self.base_dir = 'tests/fixtures/override-yaml-files'
+ self._project = get_project(self.base_dir, [])
+ self.dispatch(
+ [
+ 'up', '-d',
+ ],
+ None)
+
+ containers = self.project.containers()
+ self.assertEqual(len(containers), 2)
+
+ web, db = containers
+ self.assertEqual(web.human_readable_command, 'sleep 100')
+ self.assertEqual(db.human_readable_command, 'top')
+
+ def test_up_with_duplicate_override_yaml_files(self):
+ self.base_dir = 'tests/fixtures/duplicate-override-yaml-files'
+ with self.assertRaises(DuplicateOverrideFileFound):
+ get_project(self.base_dir, [])
+ self.base_dir = None