Merge pull request #1489 from dnephin/faster_integration_tests

Faster integration tests
This commit is contained in:
Aanand Prasad 2015-06-15 10:04:45 -07:00
commit 5231288b4e
15 changed files with 153 additions and 112 deletions

View File

@ -10,7 +10,9 @@ import sys
from docker.errors import APIError from docker.errors import APIError
import dockerpty import dockerpty
from .. import __version__, legacy from .. import __version__
from .. import legacy
from ..const import DEFAULT_TIMEOUT
from ..project import NoSuchService, ConfigurationError from ..project import NoSuchService, ConfigurationError
from ..service import BuildError, NeedsBuildError from ..service import BuildError, NeedsBuildError
from ..config import parse_environment from ..config import parse_environment
@ -394,9 +396,8 @@ class TopLevelCommand(Command):
-t, --timeout TIMEOUT Specify a shutdown timeout in seconds. -t, --timeout TIMEOUT Specify a shutdown timeout in seconds.
(default: 10) (default: 10)
""" """
timeout = options.get('--timeout') timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT)
params = {} if timeout is None else {'timeout': int(timeout)} project.stop(service_names=options['SERVICE'], timeout=timeout)
project.stop(service_names=options['SERVICE'], **params)
def restart(self, project, options): def restart(self, project, options):
""" """
@ -408,9 +409,8 @@ class TopLevelCommand(Command):
-t, --timeout TIMEOUT Specify a shutdown timeout in seconds. -t, --timeout TIMEOUT Specify a shutdown timeout in seconds.
(default: 10) (default: 10)
""" """
timeout = options.get('--timeout') timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT)
params = {} if timeout is None else {'timeout': int(timeout)} project.restart(service_names=options['SERVICE'], timeout=timeout)
project.restart(service_names=options['SERVICE'], **params)
def up(self, project, options): def up(self, project, options):
""" """
@ -439,9 +439,9 @@ class TopLevelCommand(Command):
image needs to be updated. (EXPERIMENTAL) image needs to be updated. (EXPERIMENTAL)
--no-recreate If containers already exist, don't recreate them. --no-recreate If containers already exist, don't recreate them.
--no-build Don't build an image, even if it's missing --no-build Don't build an image, even if it's missing
-t, --timeout TIMEOUT When attached, use this timeout in seconds -t, --timeout TIMEOUT Use this timeout in seconds for container shutdown
for the shutdown. (default: 10) when attached or when containers are already
running. (default: 10)
""" """
insecure_registry = options['--allow-insecure-ssl'] insecure_registry = options['--allow-insecure-ssl']
detached = options['-d'] detached = options['-d']
@ -452,6 +452,7 @@ class TopLevelCommand(Command):
allow_recreate = not options['--no-recreate'] allow_recreate = not options['--no-recreate']
smart_recreate = options['--x-smart-recreate'] smart_recreate = options['--x-smart-recreate']
service_names = options['SERVICE'] service_names = options['SERVICE']
timeout = float(options.get('--timeout') or DEFAULT_TIMEOUT)
project.up( project.up(
service_names=service_names, service_names=service_names,
@ -460,6 +461,7 @@ class TopLevelCommand(Command):
smart_recreate=smart_recreate, smart_recreate=smart_recreate,
insecure_registry=insecure_registry, insecure_registry=insecure_registry,
do_build=not options['--no-build'], do_build=not options['--no-build'],
timeout=timeout
) )
to_attach = [c for s in project.get_services(service_names) for c in s.containers()] to_attach = [c for s in project.get_services(service_names) for c in s.containers()]
@ -477,9 +479,7 @@ class TopLevelCommand(Command):
signal.signal(signal.SIGINT, handler) signal.signal(signal.SIGINT, handler)
print("Gracefully stopping... (press Ctrl+C again to force)") print("Gracefully stopping... (press Ctrl+C again to force)")
timeout = options.get('--timeout') project.stop(service_names=service_names, timeout=timeout)
params = {} if timeout is None else {'timeout': int(timeout)}
project.stop(service_names=service_names, **params)
def migrate_to_labels(self, project, _options): def migrate_to_labels(self, project, _options):
""" """

View File

@ -1,4 +1,5 @@
DEFAULT_TIMEOUT = 10
LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number' LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number'
LABEL_ONE_OFF = 'com.docker.compose.oneoff' LABEL_ONE_OFF = 'com.docker.compose.oneoff'
LABEL_PROJECT = 'com.docker.compose.project' LABEL_PROJECT = 'com.docker.compose.project'

View File

@ -6,7 +6,7 @@ from functools import reduce
from docker.errors import APIError from docker.errors import APIError
from .config import get_service_name_from_net, ConfigurationError from .config import get_service_name_from_net, ConfigurationError
from .const import LABEL_PROJECT, LABEL_SERVICE, LABEL_ONE_OFF from .const import LABEL_PROJECT, LABEL_SERVICE, LABEL_ONE_OFF, DEFAULT_TIMEOUT
from .service import Service from .service import Service
from .container import Container from .container import Container
from .legacy import check_for_legacy_containers from .legacy import check_for_legacy_containers
@ -211,7 +211,8 @@ class Project(object):
allow_recreate=True, allow_recreate=True,
smart_recreate=False, smart_recreate=False,
insecure_registry=False, insecure_registry=False,
do_build=True): do_build=True,
timeout=DEFAULT_TIMEOUT):
services = self.get_services(service_names, include_deps=start_deps) services = self.get_services(service_names, include_deps=start_deps)
@ -228,6 +229,7 @@ class Project(object):
plans[service.name], plans[service.name],
insecure_registry=insecure_registry, insecure_registry=insecure_registry,
do_build=do_build, do_build=do_build,
timeout=timeout
) )
] ]

View File

@ -13,6 +13,7 @@ from docker.utils import create_host_config, LogConfig
from . import __version__ from . import __version__
from .config import DOCKER_CONFIG_KEYS, merge_environment from .config import DOCKER_CONFIG_KEYS, merge_environment
from .const import ( from .const import (
DEFAULT_TIMEOUT,
LABEL_CONTAINER_NUMBER, LABEL_CONTAINER_NUMBER,
LABEL_ONE_OFF, LABEL_ONE_OFF,
LABEL_PROJECT, LABEL_PROJECT,
@ -251,26 +252,6 @@ class Service(object):
else: else:
return self.options['image'] return self.options['image']
def converge(self,
allow_recreate=True,
smart_recreate=False,
insecure_registry=False,
do_build=True):
"""
If a container for this service doesn't exist, create and start one. If there are
any, stop them, create+start new ones, and remove the old containers.
"""
plan = self.convergence_plan(
allow_recreate=allow_recreate,
smart_recreate=smart_recreate,
)
return self.execute_convergence_plan(
plan,
insecure_registry=insecure_registry,
do_build=do_build,
)
def convergence_plan(self, def convergence_plan(self,
allow_recreate=True, allow_recreate=True,
smart_recreate=False): smart_recreate=False):
@ -311,7 +292,8 @@ class Service(object):
def execute_convergence_plan(self, def execute_convergence_plan(self,
plan, plan,
insecure_registry=False, insecure_registry=False,
do_build=True): do_build=True,
timeout=DEFAULT_TIMEOUT):
(action, containers) = plan (action, containers) = plan
if action == 'create': if action == 'create':
@ -328,6 +310,7 @@ class Service(object):
self.recreate_container( self.recreate_container(
c, c,
insecure_registry=insecure_registry, insecure_registry=insecure_registry,
timeout=timeout
) )
for c in containers for c in containers
] ]
@ -349,7 +332,8 @@ class Service(object):
def recreate_container(self, def recreate_container(self,
container, container,
insecure_registry=False): insecure_registry=False,
timeout=DEFAULT_TIMEOUT):
"""Recreate a container. """Recreate a container.
The original container is renamed to a temporary name so that data The original container is renamed to a temporary name so that data
@ -358,7 +342,7 @@ class Service(object):
""" """
log.info("Recreating %s..." % container.name) log.info("Recreating %s..." % container.name)
try: try:
container.stop() container.stop(timeout=timeout)
except APIError as e: except APIError as e:
if (e.response.status_code == 500 if (e.response.status_code == 500
and e.explanation and e.explanation

View File

@ -1,2 +1,3 @@
FROM busybox:latest FROM busybox:latest
LABEL com.docker.compose.test_image=true
CMD echo "success" CMD echo "success"

View File

@ -1,3 +1,4 @@
FROM busybox FROM busybox:latest
LABEL com.docker.compose.test_image=true
VOLUME /data VOLUME /data
CMD top CMD top

View File

@ -1,2 +1,3 @@
FROM busybox:latest FROM busybox:latest
LABEL com.docker.compose.test_image=true
ENTRYPOINT echo "From prebuilt entrypoint" ENTRYPOINT echo "From prebuilt entrypoint"

View File

@ -1,2 +1,3 @@
FROM busybox:latest FROM busybox:latest
LABEL com.docker.compose.test_image=true
CMD echo "success" CMD echo "success"

View File

@ -24,6 +24,7 @@ class CLITestCase(DockerClientTestCase):
self.project.remove_stopped() self.project.remove_stopped()
for container in self.project.containers(stopped=True, one_off=True): for container in self.project.containers(stopped=True, one_off=True):
container.remove(force=True) container.remove(force=True)
super(CLITestCase, self).tearDown()
@property @property
def project(self): def project(self):
@ -161,6 +162,19 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(old_ids, new_ids) self.assertEqual(old_ids, new_ids)
def test_up_with_timeout(self):
self.command.dispatch(['up', '-d', '-t', '1'], None)
service = self.project.get_service('simple')
another = self.project.get_service('another')
self.assertEqual(len(service.containers()), 1)
self.assertEqual(len(another.containers()), 1)
# Ensure containers don't have stdin and stdout connected in -d mode
config = service.containers()[0].inspect()['Config']
self.assertFalse(config['AttachStderr'])
self.assertFalse(config['AttachStdout'])
self.assertFalse(config['AttachStdin'])
@patch('dockerpty.start') @patch('dockerpty.start')
def test_run_service_without_links(self, mock_stdout): def test_run_service_without_links(self, mock_stdout):
self.command.base_dir = 'tests/fixtures/links-composefile' self.command.base_dir = 'tests/fixtures/links-composefile'
@ -207,13 +221,10 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(old_ids, new_ids) self.assertEqual(old_ids, new_ids)
@patch('dockerpty.start') @patch('dockerpty.start')
def test_run_without_command(self, __): def test_run_without_command(self, _):
self.command.base_dir = 'tests/fixtures/commands-composefile' self.command.base_dir = 'tests/fixtures/commands-composefile'
self.check_build('tests/fixtures/simple-dockerfile', tag='composetest_test') self.check_build('tests/fixtures/simple-dockerfile', tag='composetest_test')
for c in self.project.containers(stopped=True, one_off=True):
c.remove()
self.command.dispatch(['run', 'implicit'], None) self.command.dispatch(['run', 'implicit'], None)
service = self.project.get_service('implicit') service = self.project.get_service('implicit')
containers = service.containers(stopped=True, one_off=True) containers = service.containers(stopped=True, one_off=True)

View File

@ -1,12 +1,15 @@
from docker.errors import APIError
from compose import legacy from compose import legacy
from compose.project import Project from compose.project import Project
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
class ProjectTest(DockerClientTestCase): class LegacyTestCase(DockerClientTestCase):
def setUp(self): def setUp(self):
super(ProjectTest, self).setUp() super(LegacyTestCase, self).setUp()
self.containers = []
db = self.create_service('db') db = self.create_service('db')
web = self.create_service('web', links=[(db, 'db')]) web = self.create_service('web', links=[(db, 'db')])
@ -23,12 +26,25 @@ class ProjectTest(DockerClientTestCase):
**service.options **service.options
) )
self.client.start(container) self.client.start(container)
self.containers.append(container)
# Create a single one-off legacy container # Create a single one-off legacy container
self.client.create_container( self.containers.append(self.client.create_container(
name='{}_{}_run_1'.format(self.project.name, self.services[0].name), name='{}_{}_run_1'.format(self.project.name, self.services[0].name),
**self.services[0].options **self.services[0].options
) ))
def tearDown(self):
super(LegacyTestCase, self).tearDown()
for container in self.containers:
try:
self.client.kill(container)
except APIError:
pass
try:
self.client.remove_container(container)
except APIError:
pass
def get_legacy_containers(self, **kwargs): def get_legacy_containers(self, **kwargs):
return list(legacy.get_legacy_containers( return list(legacy.get_legacy_containers(

View File

@ -1,5 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from compose import config from compose import config
from compose.const import LABEL_PROJECT
from compose.project import Project from compose.project import Project
from compose.container import Container from compose.container import Container
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
@ -55,6 +57,7 @@ class ProjectTest(DockerClientTestCase):
image='busybox:latest', image='busybox:latest',
volumes=['/var/data'], volumes=['/var/data'],
name='composetest_data_container', name='composetest_data_container',
labels={LABEL_PROJECT: 'composetest'},
) )
project = Project.from_dicts( project = Project.from_dicts(
name='composetest', name='composetest',
@ -69,9 +72,6 @@ class ProjectTest(DockerClientTestCase):
db = project.get_service('db') db = project.get_service('db')
self.assertEqual(db.volumes_from, [data_container]) self.assertEqual(db.volumes_from, [data_container])
project.kill()
project.remove_stopped()
def test_net_from_service(self): def test_net_from_service(self):
project = Project.from_dicts( project = Project.from_dicts(
name='composetest', name='composetest',
@ -95,15 +95,13 @@ class ProjectTest(DockerClientTestCase):
net = project.get_service('net') net = project.get_service('net')
self.assertEqual(web._get_net(), 'container:' + net.containers()[0].id) self.assertEqual(web._get_net(), 'container:' + net.containers()[0].id)
project.kill()
project.remove_stopped()
def test_net_from_container(self): def test_net_from_container(self):
net_container = Container.create( net_container = Container.create(
self.client, self.client,
image='busybox:latest', image='busybox:latest',
name='composetest_net_container', name='composetest_net_container',
command='top' command='top',
labels={LABEL_PROJECT: 'composetest'},
) )
net_container.start() net_container.start()
@ -123,9 +121,6 @@ class ProjectTest(DockerClientTestCase):
web = project.get_service('web') web = project.get_service('web')
self.assertEqual(web._get_net(), 'container:' + net_container.id) self.assertEqual(web._get_net(), 'container:' + net_container.id)
project.kill()
project.remove_stopped()
def test_start_stop_kill_remove(self): def test_start_stop_kill_remove(self):
web = self.create_service('web') web = self.create_service('web')
db = self.create_service('db') db = self.create_service('db')
@ -171,9 +166,6 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(db.containers()), 1) self.assertEqual(len(db.containers()), 1)
self.assertEqual(len(web.containers()), 0) self.assertEqual(len(web.containers()), 0)
project.kill()
project.remove_stopped()
def test_project_up_starts_uncreated_services(self): def test_project_up_starts_uncreated_services(self):
db = self.create_service('db') db = self.create_service('db')
web = self.create_service('web', links=[(db, 'db')]) web = self.create_service('web', links=[(db, 'db')])
@ -205,9 +197,6 @@ class ProjectTest(DockerClientTestCase):
self.assertNotEqual(db_container.id, old_db_id) self.assertNotEqual(db_container.id, old_db_id)
self.assertEqual(db_container.get('Volumes./etc'), db_volume_path) self.assertEqual(db_container.get('Volumes./etc'), db_volume_path)
project.kill()
project.remove_stopped()
def test_project_up_with_no_recreate_running(self): def test_project_up_with_no_recreate_running(self):
web = self.create_service('web') web = self.create_service('web')
db = self.create_service('db', volumes=['/var/db']) db = self.create_service('db', volumes=['/var/db'])
@ -228,9 +217,6 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(db_container.inspect()['Volumes']['/var/db'], self.assertEqual(db_container.inspect()['Volumes']['/var/db'],
db_volume_path) db_volume_path)
project.kill()
project.remove_stopped()
def test_project_up_with_no_recreate_stopped(self): def test_project_up_with_no_recreate_stopped(self):
web = self.create_service('web') web = self.create_service('web')
db = self.create_service('db', volumes=['/var/db']) db = self.create_service('db', volumes=['/var/db'])
@ -258,9 +244,6 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(db_container.inspect()['Volumes']['/var/db'], self.assertEqual(db_container.inspect()['Volumes']['/var/db'],
db_volume_path) db_volume_path)
project.kill()
project.remove_stopped()
def test_project_up_without_all_services(self): def test_project_up_without_all_services(self):
console = self.create_service('console') console = self.create_service('console')
db = self.create_service('db') db = self.create_service('db')
@ -273,9 +256,6 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(db.containers()), 1) self.assertEqual(len(db.containers()), 1)
self.assertEqual(len(console.containers()), 1) self.assertEqual(len(console.containers()), 1)
project.kill()
project.remove_stopped()
def test_project_up_starts_links(self): def test_project_up_starts_links(self):
console = self.create_service('console') console = self.create_service('console')
db = self.create_service('db', volumes=['/var/db']) db = self.create_service('db', volumes=['/var/db'])
@ -291,9 +271,6 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(db.containers()), 1) self.assertEqual(len(db.containers()), 1)
self.assertEqual(len(console.containers()), 0) self.assertEqual(len(console.containers()), 0)
project.kill()
project.remove_stopped()
def test_project_up_starts_depends(self): def test_project_up_starts_depends(self):
project = Project.from_dicts( project = Project.from_dicts(
name='composetest', name='composetest',
@ -329,9 +306,6 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(project.get_service('data').containers()), 1) self.assertEqual(len(project.get_service('data').containers()), 1)
self.assertEqual(len(project.get_service('console').containers()), 0) self.assertEqual(len(project.get_service('console').containers()), 0)
project.kill()
project.remove_stopped()
def test_project_up_with_no_deps(self): def test_project_up_with_no_deps(self):
project = Project.from_dicts( project = Project.from_dicts(
name='composetest', name='composetest',
@ -368,9 +342,6 @@ class ProjectTest(DockerClientTestCase):
self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1) self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1)
self.assertEqual(len(project.get_service('console').containers()), 0) self.assertEqual(len(project.get_service('console').containers()), 0)
project.kill()
project.remove_stopped()
def test_unscale_after_restart(self): def test_unscale_after_restart(self):
web = self.create_service('web') web = self.create_service('web')
project = Project('composetest', [web], self.client) project = Project('composetest', [web], self.client)
@ -395,5 +366,3 @@ class ProjectTest(DockerClientTestCase):
project.up() project.up()
service = project.get_service('web') service = project.get_service('web')
self.assertEqual(len(service.containers()), 1) self.assertEqual(len(service.containers()), 1)
project.kill()
project.remove_stopped()

View File

@ -2,8 +2,9 @@ from __future__ import unicode_literals
from __future__ import absolute_import from __future__ import absolute_import
import os import os
from os import path from os import path
import mock
from docker.errors import APIError
import mock
import tempfile import tempfile
import shutil import shutil
import six import six
@ -18,11 +19,11 @@ from compose.const import (
) )
from compose.service import ( from compose.service import (
ConfigError, ConfigError,
ConvergencePlan,
Service, Service,
build_extra_hosts, build_extra_hosts,
) )
from compose.container import Container from compose.container import Container
from docker.errors import APIError
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
@ -235,7 +236,12 @@ class ServiceTest(DockerClientTestCase):
def test_create_container_with_volumes_from(self): def test_create_container_with_volumes_from(self):
volume_service = self.create_service('data') volume_service = self.create_service('data')
volume_container_1 = volume_service.create_container() volume_container_1 = volume_service.create_container()
volume_container_2 = Container.create(self.client, image='busybox:latest', command=["top"]) volume_container_2 = Container.create(
self.client,
image='busybox:latest',
command=["top"],
labels={LABEL_PROJECT: 'composetest'},
)
host_service = self.create_service('host', volumes_from=[volume_service, volume_container_2]) host_service = self.create_service('host', volumes_from=[volume_service, volume_container_2])
host_container = host_service.create_container() host_container = host_service.create_container()
host_service.start_container(host_container) host_service.start_container(host_container)
@ -244,7 +250,7 @@ class ServiceTest(DockerClientTestCase):
self.assertIn(volume_container_2.id, self.assertIn(volume_container_2.id,
host_container.get('HostConfig.VolumesFrom')) host_container.get('HostConfig.VolumesFrom'))
def test_converge(self): def test_execute_convergence_plan_recreate(self):
service = self.create_service( service = self.create_service(
'db', 'db',
environment={'FOO': '1'}, environment={'FOO': '1'},
@ -264,7 +270,8 @@ class ServiceTest(DockerClientTestCase):
num_containers_before = len(self.client.containers(all=True)) num_containers_before = len(self.client.containers(all=True))
service.options['environment']['FOO'] = '2' service.options['environment']['FOO'] = '2'
new_container = service.converge()[0] new_container, = service.execute_convergence_plan(
ConvergencePlan('recreate', [old_container]))
self.assertEqual(new_container.get('Config.Entrypoint'), ['top']) self.assertEqual(new_container.get('Config.Entrypoint'), ['top'])
self.assertEqual(new_container.get('Config.Cmd'), ['-d', '1']) self.assertEqual(new_container.get('Config.Cmd'), ['-d', '1'])
@ -281,7 +288,7 @@ class ServiceTest(DockerClientTestCase):
self.client.inspect_container, self.client.inspect_container,
old_container.id) old_container.id)
def test_converge_when_containers_are_stopped(self): def test_execute_convergence_plan_when_containers_are_stopped(self):
service = self.create_service( service = self.create_service(
'db', 'db',
environment={'FOO': '1'}, environment={'FOO': '1'},
@ -290,11 +297,21 @@ class ServiceTest(DockerClientTestCase):
command=['-d', '1'] command=['-d', '1']
) )
service.create_container() service.create_container()
self.assertEqual(len(service.containers(stopped=True)), 1)
service.converge()
self.assertEqual(len(service.containers(stopped=True)), 1)
def test_converge_with_image_declared_volume(self): containers = service.containers(stopped=True)
self.assertEqual(len(containers), 1)
container, = containers
self.assertFalse(container.is_running)
service.execute_convergence_plan(ConvergencePlan('start', [container]))
containers = service.containers()
self.assertEqual(len(containers), 1)
container.inspect()
self.assertEqual(container, containers[0])
self.assertTrue(container.is_running)
def test_execute_convergence_plan_with_image_declared_volume(self):
service = Service( service = Service(
project='composetest', project='composetest',
name='db', name='db',
@ -306,7 +323,8 @@ class ServiceTest(DockerClientTestCase):
self.assertEqual(old_container.get('Volumes').keys(), ['/data']) self.assertEqual(old_container.get('Volumes').keys(), ['/data'])
volume_path = old_container.get('Volumes')['/data'] volume_path = old_container.get('Volumes')['/data']
new_container = service.converge()[0] new_container, = service.execute_convergence_plan(
ConvergencePlan('recreate', [old_container]))
self.assertEqual(new_container.get('Volumes').keys(), ['/data']) self.assertEqual(new_container.get('Volumes').keys(), ['/data'])
self.assertEqual(new_container.get('Volumes')['/data'], volume_path) self.assertEqual(new_container.get('Volumes')['/data'], volume_path)
@ -408,7 +426,7 @@ class ServiceTest(DockerClientTestCase):
self.assertEqual(len(self.client.images(name='composetest_test')), 1) self.assertEqual(len(self.client.images(name='composetest_test')), 1)
def test_start_container_uses_tagged_image_if_it_exists(self): def test_start_container_uses_tagged_image_if_it_exists(self):
self.client.build('tests/fixtures/simple-dockerfile', tag='composetest_test') self.check_build('tests/fixtures/simple-dockerfile', tag='composetest_test')
service = Service( service = Service(
name='test', name='test',
client=self.client, client=self.client,

View File

@ -12,8 +12,8 @@ from .testcases import DockerClientTestCase
class ProjectTestCase(DockerClientTestCase): class ProjectTestCase(DockerClientTestCase):
def run_up(self, cfg, **kwargs): def run_up(self, cfg, **kwargs):
if 'smart_recreate' not in kwargs: kwargs.setdefault('smart_recreate', True)
kwargs['smart_recreate'] = True kwargs.setdefault('timeout', 0.1)
project = self.make_project(cfg) project = self.make_project(cfg)
project.up(**kwargs) project.up(**kwargs)
@ -153,7 +153,31 @@ class ProjectWithDependenciesTest(ProjectTestCase):
self.assertEqual(new_containers - old_containers, set()) self.assertEqual(new_containers - old_containers, set())
def converge(service,
allow_recreate=True,
smart_recreate=False,
insecure_registry=False,
do_build=True):
"""
If a container for this service doesn't exist, create and start one. If there are
any, stop them, create+start new ones, and remove the old containers.
"""
plan = service.convergence_plan(
allow_recreate=allow_recreate,
smart_recreate=smart_recreate,
)
return service.execute_convergence_plan(
plan,
insecure_registry=insecure_registry,
do_build=do_build,
timeout=0.1,
)
class ServiceStateTest(DockerClientTestCase): class ServiceStateTest(DockerClientTestCase):
"""Test cases for Service.convergence_plan."""
def test_trigger_create(self): def test_trigger_create(self):
web = self.create_service('web') web = self.create_service('web')
self.assertEqual(('create', []), web.convergence_plan(smart_recreate=True)) self.assertEqual(('create', []), web.convergence_plan(smart_recreate=True))
@ -216,18 +240,19 @@ class ServiceStateTest(DockerClientTestCase):
def test_trigger_recreate_with_build(self): def test_trigger_recreate_with_build(self):
context = tempfile.mkdtemp() context = tempfile.mkdtemp()
base_image = "FROM busybox\nLABEL com.docker.compose.test_image=true\n"
try: try:
dockerfile = os.path.join(context, 'Dockerfile') dockerfile = os.path.join(context, 'Dockerfile')
with open(dockerfile, 'w') as f: with open(dockerfile, 'w') as f:
f.write('FROM busybox\n') f.write(base_image)
web = self.create_service('web', build=context) web = self.create_service('web', build=context)
container = web.create_container() container = web.create_container()
with open(dockerfile, 'w') as f: with open(dockerfile, 'w') as f:
f.write('FROM busybox\nCMD echo hello world\n') f.write(base_image + 'CMD echo hello world\n')
web.build() web.build()
web = self.create_service('web', build=context) web = self.create_service('web', build=context)
@ -249,15 +274,15 @@ class ConfigHashTest(DockerClientTestCase):
def test_config_hash_with_custom_labels(self): def test_config_hash_with_custom_labels(self):
web = self.create_service('web', labels={'foo': '1'}) web = self.create_service('web', labels={'foo': '1'})
container = web.converge()[0] container = converge(web)[0]
self.assertIn(LABEL_CONFIG_HASH, container.labels) self.assertIn(LABEL_CONFIG_HASH, container.labels)
self.assertIn('foo', container.labels) self.assertIn('foo', container.labels)
def test_config_hash_sticks_around(self): def test_config_hash_sticks_around(self):
web = self.create_service('web', command=["top"]) web = self.create_service('web', command=["top"])
container = web.converge()[0] container = converge(web)[0]
self.assertIn(LABEL_CONFIG_HASH, container.labels) self.assertIn(LABEL_CONFIG_HASH, container.labels)
web = self.create_service('web', command=["top", "-d", "1"]) web = self.create_service('web', command=["top", "-d", "1"])
container = web.converge()[0] container = converge(web)[0]
self.assertIn(LABEL_CONFIG_HASH, container.labels) self.assertIn(LABEL_CONFIG_HASH, container.labels)

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
from __future__ import absolute_import from __future__ import absolute_import
from compose.service import Service from compose.service import Service
from compose.config import make_service_dict from compose.config import make_service_dict
from compose.const import LABEL_PROJECT
from compose.cli.docker_client import docker_client from compose.cli.docker_client import docker_client
from compose.progress_stream import stream_output from compose.progress_stream import stream_output
from .. import unittest from .. import unittest
@ -12,15 +13,15 @@ class DockerClientTestCase(unittest.TestCase):
def setUpClass(cls): def setUpClass(cls):
cls.client = docker_client() cls.client = docker_client()
# TODO: update to use labels in #652 def tearDown(self):
def setUp(self): for c in self.client.containers(
for c in self.client.containers(all=True): all=True,
if c['Names'] and 'composetest' in c['Names'][0]: filters={'label': '%s=composetest' % LABEL_PROJECT}):
self.client.kill(c['Id']) self.client.kill(c['Id'])
self.client.remove_container(c['Id']) self.client.remove_container(c['Id'])
for i in self.client.images(): for i in self.client.images(
if isinstance(i.get('Tag'), basestring) and 'composetest' in i['Tag']: filters={'label': 'com.docker.compose.test_image'}):
self.client.remove_image(i) self.client.remove_image(i)
def create_service(self, name, **kwargs): def create_service(self, name, **kwargs):
if 'image' not in kwargs and 'build' not in kwargs: if 'image' not in kwargs and 'build' not in kwargs:
@ -36,5 +37,6 @@ class DockerClientTestCase(unittest.TestCase):
) )
def check_build(self, *args, **kwargs): def check_build(self, *args, **kwargs):
kwargs.setdefault('rm', True)
build_output = self.client.build(*args, **kwargs) build_output = self.client.build(*args, **kwargs)
stream_output(build_output, open('/dev/null', 'w')) stream_output(build_output, open('/dev/null', 'w'))

View File

@ -246,7 +246,7 @@ class ServiceTest(unittest.TestCase):
service.image = lambda: {'Id': 'abc123'} service.image = lambda: {'Id': 'abc123'}
new_container = service.recreate_container(mock_container) new_container = service.recreate_container(mock_container)
mock_container.stop.assert_called_once_with() mock_container.stop.assert_called_once_with(timeout=10)
self.mock_client.rename.assert_called_once_with( self.mock_client.rename.assert_called_once_with(
mock_container.id, mock_container.id,
'%s_%s' % (mock_container.short_id, mock_container.name)) '%s_%s' % (mock_container.short_id, mock_container.name))
@ -254,6 +254,15 @@ class ServiceTest(unittest.TestCase):
new_container.start.assert_called_once_with() new_container.start.assert_called_once_with()
mock_container.remove.assert_called_once_with() mock_container.remove.assert_called_once_with()
@mock.patch('compose.service.Container', autospec=True)
def test_recreate_container_with_timeout(self, _):
mock_container = mock.create_autospec(Container)
self.mock_client.inspect_image.return_value = {'Id': 'abc123'}
service = Service('foo', client=self.mock_client, image='someimage')
service.recreate_container(mock_container, timeout=1)
mock_container.stop.assert_called_once_with(timeout=1)
def test_parse_repository_tag(self): def test_parse_repository_tag(self):
self.assertEqual(parse_repository_tag("root"), ("root", "")) self.assertEqual(parse_repository_tag("root"), ("root", ""))
self.assertEqual(parse_repository_tag("root:tag"), ("root", "tag")) self.assertEqual(parse_repository_tag("root:tag"), ("root", "tag"))