Merge pull request #440 from dnephin/python3

Support python 3
This commit is contained in:
Aanand Prasad 2015-08-25 18:22:40 +01:00
commit fc63454c99
27 changed files with 197 additions and 143 deletions

View File

@ -3,6 +3,7 @@ FROM debian:wheezy
RUN set -ex; \ RUN set -ex; \
apt-get update -qq; \ apt-get update -qq; \
apt-get install -y \ apt-get install -y \
locales \
gcc \ gcc \
make \ make \
zlib1g \ zlib1g \
@ -30,6 +31,18 @@ RUN set -ex; \
rm -rf /Python-2.7.9; \ rm -rf /Python-2.7.9; \
rm Python-2.7.9.tgz rm Python-2.7.9.tgz
# Build python 3.4 from source
RUN set -ex; \
curl -LO https://www.python.org/ftp/python/3.4.3/Python-3.4.3.tgz; \
tar -xzf Python-3.4.3.tgz; \
cd Python-3.4.3; \
./configure --enable-shared; \
make; \
make install; \
cd ..; \
rm -rf /Python-3.4.3; \
rm Python-3.4.3.tgz
# Make libpython findable # Make libpython findable
ENV LD_LIBRARY_PATH /usr/local/lib ENV LD_LIBRARY_PATH /usr/local/lib
@ -49,6 +62,10 @@ RUN set -ex; \
rm -rf pip-7.0.1; \ rm -rf pip-7.0.1; \
rm pip-7.0.1.tar.gz rm pip-7.0.1.tar.gz
# Python3 requires a valid locale
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
ENV LANG en_US.UTF-8
ENV ALL_DOCKER_VERSIONS 1.7.1 1.8.1 ENV ALL_DOCKER_VERSIONS 1.7.1 1.8.1
RUN set -ex; \ RUN set -ex; \
@ -63,6 +80,8 @@ RUN ln -s /usr/local/bin/docker-1.7.1 /usr/local/bin/docker
RUN useradd -d /home/user -m -s /bin/bash user RUN useradd -d /home/user -m -s /bin/bash user
WORKDIR /code/ WORKDIR /code/
RUN pip install tox
ADD requirements.txt /code/ ADD requirements.txt /code/
RUN pip install -r requirements.txt RUN pip install -r requirements.txt

View File

@ -4,9 +4,12 @@ from __future__ import unicode_literals
import sys import sys
from itertools import cycle from itertools import cycle
from six import next
from . import colors from . import colors
from .multiplexer import Multiplexer from .multiplexer import Multiplexer
from .utils import split_buffer from .utils import split_buffer
from compose import utils
class LogPrinter(object): class LogPrinter(object):
@ -15,7 +18,7 @@ class LogPrinter(object):
self.attach_params = attach_params or {} self.attach_params = attach_params or {}
self.prefix_width = self._calculate_prefix_width(containers) self.prefix_width = self._calculate_prefix_width(containers)
self.generators = self._make_log_generators(monochrome) self.generators = self._make_log_generators(monochrome)
self.output = output self.output = utils.get_output_stream(output)
def run(self): def run(self):
mux = Multiplexer(self.generators) mux = Multiplexer(self.generators)
@ -52,7 +55,7 @@ class LogPrinter(object):
return generators return generators
def _make_log_generator(self, container, color_fn): def _make_log_generator(self, container, color_fn):
prefix = color_fn(self._generate_prefix(container)).encode('utf-8') prefix = color_fn(self._generate_prefix(container))
# Attach to container before log printer starts running # Attach to container before log printer starts running
line_generator = split_buffer(self._attach(container), '\n') line_generator = split_buffer(self._attach(container), '\n')

View File

@ -9,6 +9,7 @@ import ssl
import subprocess import subprocess
from docker import version as docker_py_version from docker import version as docker_py_version
from six.moves import input
from .. import __version__ from .. import __version__
@ -23,7 +24,7 @@ def yesno(prompt, default=None):
Unrecognised input (anything other than "y", "n", "yes", Unrecognised input (anything other than "y", "n", "yes",
"no" or "") will return None. "no" or "") will return None.
""" """
answer = raw_input(prompt).strip().lower() answer = input(prompt).strip().lower()
if answer == "y" or answer == "yes": if answer == "y" or answer == "yes":
return True return True

View File

@ -150,7 +150,7 @@ def process_errors(errors):
config_key = error.path[0] config_key = error.path[0]
required.append("Service '{}' option '{}' is invalid, {}".format(service_name, config_key, _clean_error_message(error.message))) required.append("Service '{}' option '{}' is invalid, {}".format(service_name, config_key, _clean_error_message(error.message)))
elif error.validator == 'dependencies': elif error.validator == 'dependencies':
dependency_key = error.validator_value.keys()[0] dependency_key = list(error.validator_value.keys())[0]
required_keys = ",".join(error.validator_value[dependency_key]) required_keys = ",".join(error.validator_value[dependency_key])
required.append("Invalid '{}' configuration for '{}' service: when defining '{}' you must set '{}' as well".format( required.append("Invalid '{}' configuration for '{}' service: when defining '{}' you must set '{}' as well".format(
dependency_key, service_name, dependency_key, required_keys)) dependency_key, service_name, dependency_key, required_keys))

View File

@ -1,6 +1,8 @@
import codecs
import json import json
import os
import six
from compose import utils
class StreamOutputError(Exception): class StreamOutputError(Exception):
@ -8,13 +10,15 @@ class StreamOutputError(Exception):
def stream_output(output, stream): def stream_output(output, stream):
is_terminal = hasattr(stream, 'fileno') and os.isatty(stream.fileno()) is_terminal = hasattr(stream, 'isatty') and stream.isatty()
stream = codecs.getwriter('utf-8')(stream) stream = utils.get_output_stream(stream)
all_events = [] all_events = []
lines = {} lines = {}
diff = 0 diff = 0
for chunk in output: for chunk in output:
if six.PY3:
chunk = chunk.decode('utf-8')
event = json.loads(chunk) event = json.loads(chunk)
all_events.append(event) all_events.append(event)
@ -55,7 +59,6 @@ def print_output_event(event, stream, is_terminal):
# erase current line # erase current line
stream.write("%c[2K\r" % 27) stream.write("%c[2K\r" % 27)
terminator = "\r" terminator = "\r"
pass
elif 'progressDetail' in event: elif 'progressDetail' in event:
return return

View File

@ -17,6 +17,7 @@ from .legacy import check_for_legacy_containers
from .service import Service from .service import Service
from .utils import parallel_execute from .utils import parallel_execute
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -323,11 +324,11 @@ class Project(object):
else: else:
service_names = self.service_names service_names = self.service_names
containers = filter(None, [ containers = list(filter(None, [
Container.from_ps(self.client, container) Container.from_ps(self.client, container)
for container in self.client.containers( for container in self.client.containers(
all=stopped, all=stopped,
filters={'label': self.labels(one_off=one_off)})]) filters={'label': self.labels(one_off=one_off)})]))
def matches_service_names(container): def matches_service_names(container):
return container.labels.get(LABEL_SERVICE) in service_names return container.labels.get(LABEL_SERVICE) in service_names
@ -339,7 +340,7 @@ class Project(object):
self.service_names, self.service_names,
) )
return filter(matches_service_names, containers) return [c for c in containers if matches_service_names(c)]
def _inject_deps(self, acc, service): def _inject_deps(self, acc, service):
dep_names = service.get_dependency_names() dep_names = service.get_dependency_names()

View File

@ -103,11 +103,11 @@ class Service(object):
def containers(self, stopped=False, one_off=False, filters={}): def containers(self, stopped=False, one_off=False, filters={}):
filters.update({'label': self.labels(one_off=one_off)}) filters.update({'label': self.labels(one_off=one_off)})
containers = filter(None, [ containers = list(filter(None, [
Container.from_ps(self.client, container) Container.from_ps(self.client, container)
for container in self.client.containers( for container in self.client.containers(
all=stopped, all=stopped,
filters=filters)]) filters=filters)]))
if not containers: if not containers:
check_for_legacy_containers( check_for_legacy_containers(
@ -709,7 +709,11 @@ class Service(object):
def build(self, no_cache=False): def build(self, no_cache=False):
log.info('Building %s...' % self.name) log.info('Building %s...' % self.name)
path = six.binary_type(self.options['build']) path = self.options['build']
# python2 os.path() doesn't support unicode, so we need to encode it to
# a byte string
if not six.PY3:
path = path.encode('utf8')
build_output = self.client.build( build_output = self.client.build(
path=path, path=path,
@ -724,7 +728,7 @@ class Service(object):
try: try:
all_events = stream_output(build_output, sys.stdout) all_events = stream_output(build_output, sys.stdout)
except StreamOutputError as e: except StreamOutputError as e:
raise BuildError(self, unicode(e)) raise BuildError(self, six.text_type(e))
# Ensure the HTTP connection is not reused for another # Ensure the HTTP connection is not reused for another
# streaming command, as the Docker daemon can sometimes # streaming command, as the Docker daemon can sometimes
@ -840,7 +844,7 @@ def merge_volume_bindings(volumes_option, previous_container):
volume_bindings.update( volume_bindings.update(
get_container_data_volumes(previous_container, volumes_option)) get_container_data_volumes(previous_container, volumes_option))
return volume_bindings.values() return list(volume_bindings.values())
def get_container_data_volumes(container, volumes_option): def get_container_data_volumes(container, volumes_option):
@ -853,7 +857,7 @@ def get_container_data_volumes(container, volumes_option):
container_volumes = container.get('Volumes') or {} container_volumes = container.get('Volumes') or {}
image_volumes = container.image_config['ContainerConfig'].get('Volumes') or {} image_volumes = container.image_config['ContainerConfig'].get('Volumes') or {}
for volume in set(volumes_option + image_volumes.keys()): for volume in set(volumes_option + list(image_volumes)):
volume = parse_volume_spec(volume) volume = parse_volume_spec(volume)
# No need to preserve host volumes # No need to preserve host volumes
if volume.external: if volume.external:

View File

@ -3,11 +3,12 @@ import hashlib
import json import json
import logging import logging
import sys import sys
from Queue import Empty
from Queue import Queue
from threading import Thread from threading import Thread
import six
from docker.errors import APIError from docker.errors import APIError
from six.moves.queue import Empty
from six.moves.queue import Queue
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -18,7 +19,7 @@ def parallel_execute(objects, obj_callable, msg_index, msg):
For a given list of objects, call the callable passing in the first For a given list of objects, call the callable passing in the first
object we give it. object we give it.
""" """
stream = codecs.getwriter('utf-8')(sys.stdout) stream = get_output_stream(sys.stdout)
lines = [] lines = []
errors = {} errors = {}
@ -70,6 +71,12 @@ def parallel_execute(objects, obj_callable, msg_index, msg):
stream.write("ERROR: for {} {} \n".format(error, errors[error])) stream.write("ERROR: for {} {} \n".format(error, errors[error]))
def get_output_stream(stream):
if six.PY3:
return stream
return codecs.getwriter('utf-8')(stream)
def write_out_msg(stream, lines, msg_index, msg, status="done"): def write_out_msg(stream, lines, msg_index, msg, status="done"):
""" """
Using special ANSI code characters we can write out the msg over the top of Using special ANSI code characters we can write out the msg over the top of
@ -97,5 +104,5 @@ def write_out_msg(stream, lines, msg_index, msg, status="done"):
def json_hash(obj): def json_hash(obj):
dump = json.dumps(obj, sort_keys=True, separators=(',', ':')) dump = json.dumps(obj, sort_keys=True, separators=(',', ':'))
h = hashlib.sha256() h = hashlib.sha256()
h.update(dump) h.update(dump.encode('utf8'))
return h.hexdigest() return h.hexdigest()

View File

@ -4,4 +4,3 @@ git+https://github.com/pyinstaller/pyinstaller.git@12e40471c77f588ea5be352f7219c
mock >= 1.0.1 mock >= 1.0.1
nose==1.3.4 nose==1.3.4
pep8==1.6.1 pep8==1.6.1
unittest2==0.8.0

View File

@ -24,5 +24,5 @@ for version in $DOCKER_VERSIONS; do
-e "DOCKER_DAEMON_ARGS" \ -e "DOCKER_DAEMON_ARGS" \
--entrypoint="script/dind" \ --entrypoint="script/dind" \
"$TAG" \ "$TAG" \
script/wrapdocker nosetests --with-coverage --cover-branches --cover-package=compose --cover-erase --cover-html-dir=coverage-html --cover-html "$@" script/wrapdocker tox -e py27,py34 -- "$@"
done done

View File

@ -41,15 +41,15 @@ install_requires = [
tests_require = [ tests_require = [
'mock >= 1.0.1',
'nose', 'nose',
'pyinstaller',
'flake8', 'flake8',
] ]
if sys.version_info < (2, 7): if sys.version_info < (2, 7):
tests_require.append('unittest2') tests_require.append('unittest2')
if sys.version_info[:1] < (3,):
tests_require.append('mock >= 1.0.1')
setup( setup(

View File

@ -4,3 +4,8 @@ if sys.version_info >= (2, 7):
import unittest # NOQA import unittest # NOQA
else: else:
import unittest2 as unittest # NOQA import unittest2 as unittest # NOQA
try:
from unittest import mock
except ImportError:
import mock # NOQA

View File

@ -5,9 +5,9 @@ import shlex
import sys import sys
from operator import attrgetter from operator import attrgetter
from mock import patch
from six import StringIO from six import StringIO
from .. import mock
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
from compose.cli.errors import UserError from compose.cli.errors import UserError
from compose.cli.main import TopLevelCommand from compose.cli.main import TopLevelCommand
@ -51,13 +51,13 @@ class CLITestCase(DockerClientTestCase):
self.command.base_dir = old_base_dir self.command.base_dir = old_base_dir
# TODO: address the "Inappropriate ioctl for device" warnings in test output # TODO: address the "Inappropriate ioctl for device" warnings in test output
@patch('sys.stdout', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO)
def test_ps(self, mock_stdout): def test_ps(self, mock_stdout):
self.project.get_service('simple').create_container() self.project.get_service('simple').create_container()
self.command.dispatch(['ps'], None) self.command.dispatch(['ps'], None)
self.assertIn('simplecomposefile_simple_1', mock_stdout.getvalue()) self.assertIn('simplecomposefile_simple_1', mock_stdout.getvalue())
@patch('sys.stdout', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO)
def test_ps_default_composefile(self, mock_stdout): def test_ps_default_composefile(self, mock_stdout):
self.command.base_dir = 'tests/fixtures/multiple-composefiles' self.command.base_dir = 'tests/fixtures/multiple-composefiles'
self.command.dispatch(['up', '-d'], None) self.command.dispatch(['up', '-d'], None)
@ -68,7 +68,7 @@ class CLITestCase(DockerClientTestCase):
self.assertIn('multiplecomposefiles_another_1', output) self.assertIn('multiplecomposefiles_another_1', output)
self.assertNotIn('multiplecomposefiles_yetanother_1', output) self.assertNotIn('multiplecomposefiles_yetanother_1', output)
@patch('sys.stdout', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO)
def test_ps_alternate_composefile(self, mock_stdout): def test_ps_alternate_composefile(self, mock_stdout):
config_path = os.path.abspath( config_path = os.path.abspath(
'tests/fixtures/multiple-composefiles/compose2.yml') 'tests/fixtures/multiple-composefiles/compose2.yml')
@ -83,19 +83,19 @@ class CLITestCase(DockerClientTestCase):
self.assertNotIn('multiplecomposefiles_another_1', output) self.assertNotIn('multiplecomposefiles_another_1', output)
self.assertIn('multiplecomposefiles_yetanother_1', output) self.assertIn('multiplecomposefiles_yetanother_1', output)
@patch('compose.service.log') @mock.patch('compose.service.log')
def test_pull(self, mock_logging): def test_pull(self, mock_logging):
self.command.dispatch(['pull'], None) self.command.dispatch(['pull'], None)
mock_logging.info.assert_any_call('Pulling simple (busybox:latest)...') mock_logging.info.assert_any_call('Pulling simple (busybox:latest)...')
mock_logging.info.assert_any_call('Pulling another (busybox:latest)...') mock_logging.info.assert_any_call('Pulling another (busybox:latest)...')
@patch('compose.service.log') @mock.patch('compose.service.log')
def test_pull_with_digest(self, mock_logging): def test_pull_with_digest(self, mock_logging):
self.command.dispatch(['-f', 'digest.yml', 'pull'], None) self.command.dispatch(['-f', 'digest.yml', 'pull'], None)
mock_logging.info.assert_any_call('Pulling simple (busybox:latest)...') mock_logging.info.assert_any_call('Pulling simple (busybox:latest)...')
mock_logging.info.assert_any_call('Pulling digest (busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d)...') mock_logging.info.assert_any_call('Pulling digest (busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d)...')
@patch('sys.stdout', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO)
def test_build_no_cache(self, mock_stdout): def test_build_no_cache(self, mock_stdout):
self.command.base_dir = 'tests/fixtures/simple-dockerfile' self.command.base_dir = 'tests/fixtures/simple-dockerfile'
self.command.dispatch(['build', 'simple'], None) self.command.dispatch(['build', 'simple'], None)
@ -189,7 +189,7 @@ class CLITestCase(DockerClientTestCase):
self.assertFalse(config['AttachStdout']) self.assertFalse(config['AttachStdout'])
self.assertFalse(config['AttachStdin']) self.assertFalse(config['AttachStdin'])
@patch('dockerpty.start') @mock.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'
self.command.dispatch(['run', 'console', '/bin/true'], None) self.command.dispatch(['run', 'console', '/bin/true'], None)
@ -202,7 +202,7 @@ class CLITestCase(DockerClientTestCase):
self.assertTrue(config['AttachStdout']) self.assertTrue(config['AttachStdout'])
self.assertTrue(config['AttachStdin']) self.assertTrue(config['AttachStdin'])
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_service_with_links(self, __): def test_run_service_with_links(self, __):
self.command.base_dir = 'tests/fixtures/links-composefile' self.command.base_dir = 'tests/fixtures/links-composefile'
self.command.dispatch(['run', 'web', '/bin/true'], None) self.command.dispatch(['run', 'web', '/bin/true'], None)
@ -211,14 +211,14 @@ class CLITestCase(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)
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_with_no_deps(self, __): def test_run_with_no_deps(self, __):
self.command.base_dir = 'tests/fixtures/links-composefile' self.command.base_dir = 'tests/fixtures/links-composefile'
self.command.dispatch(['run', '--no-deps', 'web', '/bin/true'], None) self.command.dispatch(['run', '--no-deps', 'web', '/bin/true'], None)
db = self.project.get_service('db') db = self.project.get_service('db')
self.assertEqual(len(db.containers()), 0) self.assertEqual(len(db.containers()), 0)
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_does_not_recreate_linked_containers(self, __): def test_run_does_not_recreate_linked_containers(self, __):
self.command.base_dir = 'tests/fixtures/links-composefile' self.command.base_dir = 'tests/fixtures/links-composefile'
self.command.dispatch(['up', '-d', 'db'], None) self.command.dispatch(['up', '-d', 'db'], None)
@ -234,7 +234,7 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(old_ids, new_ids) self.assertEqual(old_ids, new_ids)
@patch('dockerpty.start') @mock.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')
@ -255,7 +255,7 @@ class CLITestCase(DockerClientTestCase):
[u'/bin/true'], [u'/bin/true'],
) )
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_service_with_entrypoint_overridden(self, _): def test_run_service_with_entrypoint_overridden(self, _):
self.command.base_dir = 'tests/fixtures/dockerfile_with_entrypoint' self.command.base_dir = 'tests/fixtures/dockerfile_with_entrypoint'
name = 'service' name = 'service'
@ -270,18 +270,18 @@ class CLITestCase(DockerClientTestCase):
[u'/bin/echo', u'helloworld'], [u'/bin/echo', u'helloworld'],
) )
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_service_with_user_overridden(self, _): def test_run_service_with_user_overridden(self, _):
self.command.base_dir = 'tests/fixtures/user-composefile' self.command.base_dir = 'tests/fixtures/user-composefile'
name = 'service' name = 'service'
user = 'sshd' user = 'sshd'
args = ['run', '--user={}'.format(user), name] args = ['run', '--user={user}'.format(user=user), name]
self.command.dispatch(args, None) self.command.dispatch(args, None)
service = self.project.get_service(name) service = self.project.get_service(name)
container = service.containers(stopped=True, one_off=True)[0] container = service.containers(stopped=True, one_off=True)[0]
self.assertEqual(user, container.get('Config.User')) self.assertEqual(user, container.get('Config.User'))
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_service_with_user_overridden_short_form(self, _): def test_run_service_with_user_overridden_short_form(self, _):
self.command.base_dir = 'tests/fixtures/user-composefile' self.command.base_dir = 'tests/fixtures/user-composefile'
name = 'service' name = 'service'
@ -292,7 +292,7 @@ class CLITestCase(DockerClientTestCase):
container = service.containers(stopped=True, one_off=True)[0] container = service.containers(stopped=True, one_off=True)[0]
self.assertEqual(user, container.get('Config.User')) self.assertEqual(user, container.get('Config.User'))
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_service_with_environement_overridden(self, _): def test_run_service_with_environement_overridden(self, _):
name = 'service' name = 'service'
self.command.base_dir = 'tests/fixtures/environment-composefile' self.command.base_dir = 'tests/fixtures/environment-composefile'
@ -312,7 +312,7 @@ class CLITestCase(DockerClientTestCase):
# make sure a value with a = don't crash out # make sure a value with a = don't crash out
self.assertEqual('moto=bobo', container.environment['allo']) self.assertEqual('moto=bobo', container.environment['allo'])
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_service_without_map_ports(self, __): def test_run_service_without_map_ports(self, __):
# create one off container # create one off container
self.command.base_dir = 'tests/fixtures/ports-composefile' self.command.base_dir = 'tests/fixtures/ports-composefile'
@ -330,7 +330,7 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(port_random, None) self.assertEqual(port_random, None)
self.assertEqual(port_assigned, None) self.assertEqual(port_assigned, None)
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_service_with_map_ports(self, __): def test_run_service_with_map_ports(self, __):
# create one off container # create one off container
@ -353,7 +353,7 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(port_range[0], "0.0.0.0:49153") self.assertEqual(port_range[0], "0.0.0.0:49153")
self.assertEqual(port_range[1], "0.0.0.0:49154") self.assertEqual(port_range[1], "0.0.0.0:49154")
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_service_with_explicitly_maped_ports(self, __): def test_run_service_with_explicitly_maped_ports(self, __):
# create one off container # create one off container
@ -372,7 +372,7 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(port_short, "0.0.0.0:30000") self.assertEqual(port_short, "0.0.0.0:30000")
self.assertEqual(port_full, "0.0.0.0:30001") self.assertEqual(port_full, "0.0.0.0:30001")
@patch('dockerpty.start') @mock.patch('dockerpty.start')
def test_run_service_with_explicitly_maped_ip_ports(self, __): def test_run_service_with_explicitly_maped_ip_ports(self, __):
# create one off container # create one off container
@ -508,7 +508,7 @@ class CLITestCase(DockerClientTestCase):
self.command.dispatch(['up', '-d'], None) self.command.dispatch(['up', '-d'], None)
container = self.project.get_service('simple').get_container() container = self.project.get_service('simple').get_container()
@patch('sys.stdout', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO)
def get_port(number, mock_stdout): def get_port(number, mock_stdout):
self.command.dispatch(['port', 'simple', str(number)], None) self.command.dispatch(['port', 'simple', str(number)], None)
return mock_stdout.getvalue().rstrip() return mock_stdout.getvalue().rstrip()
@ -525,7 +525,7 @@ class CLITestCase(DockerClientTestCase):
self.project.containers(service_names=['simple']), self.project.containers(service_names=['simple']),
key=attrgetter('name')) key=attrgetter('name'))
@patch('sys.stdout', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO)
def get_port(number, mock_stdout, index=None): def get_port(number, mock_stdout, index=None):
if index is None: if index is None:
self.command.dispatch(['port', 'simple', str(number)], None) self.command.dispatch(['port', 'simple', str(number)], None)
@ -547,7 +547,7 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(containers), 1) self.assertEqual(len(containers), 1)
self.assertIn("FOO=1", containers[0].get('Config.Env')) self.assertIn("FOO=1", containers[0].get('Config.Env'))
@patch.dict(os.environ) @mock.patch.dict(os.environ)
def test_home_and_env_var_in_volume_path(self): def test_home_and_env_var_in_volume_path(self):
os.environ['VOLUME_NAME'] = 'my-volume' os.environ['VOLUME_NAME'] = 'my-volume'
os.environ['HOME'] = '/tmp/home-dir' os.environ['HOME'] = '/tmp/home-dir'

View File

@ -1,8 +1,8 @@
import unittest import unittest
from docker.errors import APIError from docker.errors import APIError
from mock import Mock
from .. import mock
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
from compose import legacy from compose import legacy
from compose.project import Project from compose.project import Project
@ -66,7 +66,7 @@ class UtilitiesTestCase(unittest.TestCase):
) )
def test_get_legacy_containers(self): def test_get_legacy_containers(self):
client = Mock() client = mock.Mock()
client.containers.return_value = [ client.containers.return_value = [
{ {
"Id": "abc123", "Id": "abc123",

View File

@ -1,8 +1,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import mock from .. import mock
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
from compose.project import Project from compose.project import Project

View File

@ -7,10 +7,10 @@ import tempfile
from os import path from os import path
from docker.errors import APIError from docker.errors import APIError
from mock import patch
from six import StringIO from six import StringIO
from six import text_type from six import text_type
from .. import mock
from .testcases import DockerClientTestCase from .testcases import DockerClientTestCase
from compose import __version__ from compose import __version__
from compose.const import LABEL_CONTAINER_NUMBER from compose.const import LABEL_CONTAINER_NUMBER
@ -358,12 +358,12 @@ class ServiceTest(DockerClientTestCase):
) )
old_container = create_and_start_container(service) old_container = create_and_start_container(service)
self.assertEqual(old_container.get('Volumes').keys(), ['/data']) self.assertEqual(list(old_container.get('Volumes').keys()), ['/data'])
volume_path = old_container.get('Volumes')['/data'] volume_path = old_container.get('Volumes')['/data']
new_container, = service.execute_convergence_plan( new_container, = service.execute_convergence_plan(
ConvergencePlan('recreate', [old_container])) ConvergencePlan('recreate', [old_container]))
self.assertEqual(new_container.get('Volumes').keys(), ['/data']) self.assertEqual(list(new_container.get('Volumes')), ['/data'])
self.assertEqual(new_container.get('Volumes')['/data'], volume_path) self.assertEqual(new_container.get('Volumes')['/data'], volume_path)
def test_start_container_passes_through_options(self): def test_start_container_passes_through_options(self):
@ -460,7 +460,7 @@ class ServiceTest(DockerClientTestCase):
) )
container = create_and_start_container(service) container = create_and_start_container(service)
container.wait() container.wait()
self.assertIn('success', container.logs()) self.assertIn(b'success', container.logs())
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):
@ -473,7 +473,7 @@ class ServiceTest(DockerClientTestCase):
) )
container = create_and_start_container(service) container = create_and_start_container(service)
container.wait() container.wait()
self.assertIn('success', container.logs()) self.assertIn(b'success', container.logs())
def test_start_container_creates_ports(self): def test_start_container_creates_ports(self):
service = self.create_service('web', ports=[8000]) service = self.create_service('web', ports=[8000])
@ -498,7 +498,7 @@ class ServiceTest(DockerClientTestCase):
with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f: with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
f.write("FROM busybox\n") f.write("FROM busybox\n")
with open(os.path.join(base_dir, b'foo\xE2bar'), 'w') as f: with open(os.path.join(base_dir.encode('utf8'), b'foo\xE2bar'), 'w') as f:
f.write("hello world\n") f.write("hello world\n")
self.create_service('web', build=text_type(base_dir)).build() self.create_service('web', build=text_type(base_dir)).build()
@ -581,8 +581,7 @@ class ServiceTest(DockerClientTestCase):
service.scale(0) service.scale(0)
self.assertEqual(len(service.containers()), 0) self.assertEqual(len(service.containers()), 0)
@patch('sys.stdout', new_callable=StringIO) def test_scale_with_stopped_containers(self):
def test_scale_with_stopped_containers(self, mock_stdout):
""" """
Given there are some stopped containers and scale is called with a Given there are some stopped containers and scale is called with a
desired number that is the same as the number of stopped containers, desired number that is the same as the number of stopped containers,
@ -591,15 +590,11 @@ class ServiceTest(DockerClientTestCase):
service = self.create_service('web') service = self.create_service('web')
next_number = service._next_container_number() next_number = service._next_container_number()
valid_numbers = [next_number, next_number + 1] valid_numbers = [next_number, next_number + 1]
service.create_container(number=next_number, quiet=True) service.create_container(number=next_number)
service.create_container(number=next_number + 1, quiet=True) service.create_container(number=next_number + 1)
for container in service.containers(): with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout:
self.assertFalse(container.is_running) service.scale(2)
service.scale(2)
self.assertEqual(len(service.containers()), 2)
for container in service.containers(): for container in service.containers():
self.assertTrue(container.is_running) self.assertTrue(container.is_running)
self.assertTrue(container.number in valid_numbers) self.assertTrue(container.number in valid_numbers)
@ -608,7 +603,7 @@ class ServiceTest(DockerClientTestCase):
self.assertNotIn('Creating', captured_output) self.assertNotIn('Creating', captured_output)
self.assertIn('Starting', captured_output) self.assertIn('Starting', captured_output)
@patch('sys.stdout', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO)
def test_scale_with_stopped_containers_and_needing_creation(self, mock_stdout): def test_scale_with_stopped_containers_and_needing_creation(self, mock_stdout):
""" """
Given there are some stopped containers and scale is called with a Given there are some stopped containers and scale is called with a
@ -632,7 +627,7 @@ class ServiceTest(DockerClientTestCase):
self.assertIn('Creating', captured_output) self.assertIn('Creating', captured_output)
self.assertIn('Starting', captured_output) self.assertIn('Starting', captured_output)
@patch('sys.stdout', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO)
def test_scale_with_api_returns_errors(self, mock_stdout): def test_scale_with_api_returns_errors(self, mock_stdout):
""" """
Test that when scaling if the API returns an error, that error is handled Test that when scaling if the API returns an error, that error is handled
@ -642,7 +637,7 @@ class ServiceTest(DockerClientTestCase):
next_number = service._next_container_number() next_number = service._next_container_number()
service.create_container(number=next_number, quiet=True) service.create_container(number=next_number, quiet=True)
with patch( with mock.patch(
'compose.container.Container.create', 'compose.container.Container.create',
side_effect=APIError(message="testing", response={}, explanation="Boom")): side_effect=APIError(message="testing", response={}, explanation="Boom")):
@ -652,7 +647,7 @@ class ServiceTest(DockerClientTestCase):
self.assertTrue(service.containers()[0].is_running) self.assertTrue(service.containers()[0].is_running)
self.assertIn("ERROR: for 2 Boom", mock_stdout.getvalue()) self.assertIn("ERROR: for 2 Boom", mock_stdout.getvalue())
@patch('sys.stdout', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO)
def test_scale_with_api_returns_unexpected_exception(self, mock_stdout): def test_scale_with_api_returns_unexpected_exception(self, mock_stdout):
""" """
Test that when scaling if the API returns an error, that is not of type Test that when scaling if the API returns an error, that is not of type
@ -662,7 +657,7 @@ class ServiceTest(DockerClientTestCase):
next_number = service._next_container_number() next_number = service._next_container_number()
service.create_container(number=next_number, quiet=True) service.create_container(number=next_number, quiet=True)
with patch( with mock.patch(
'compose.container.Container.create', 'compose.container.Container.create',
side_effect=ValueError("BOOM")): side_effect=ValueError("BOOM")):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -671,7 +666,7 @@ class ServiceTest(DockerClientTestCase):
self.assertEqual(len(service.containers()), 1) self.assertEqual(len(service.containers()), 1)
self.assertTrue(service.containers()[0].is_running) self.assertTrue(service.containers()[0].is_running)
@patch('compose.service.log') @mock.patch('compose.service.log')
def test_scale_with_desired_number_already_achieved(self, mock_log): def test_scale_with_desired_number_already_achieved(self, mock_log):
""" """
Test that calling scale with a desired number that is equal to the Test that calling scale with a desired number that is equal to the
@ -694,14 +689,13 @@ class ServiceTest(DockerClientTestCase):
captured_output = mock_log.info.call_args[0] captured_output = mock_log.info.call_args[0]
self.assertIn('Desired container number already achieved', captured_output) self.assertIn('Desired container number already achieved', captured_output)
@patch('compose.service.log') @mock.patch('compose.service.log')
def test_scale_with_custom_container_name_outputs_warning(self, mock_log): def test_scale_with_custom_container_name_outputs_warning(self, mock_log):
""" """
Test that calling scale on a service that has a custom container name Test that calling scale on a service that has a custom container name
results in warning output. results in warning output.
""" """
service = self.create_service('web', container_name='custom-container') service = self.create_service('web', container_name='custom-container')
self.assertEqual(service.custom_container_name(), 'custom-container') self.assertEqual(service.custom_container_name(), 'custom-container')
service.scale(3) service.scale(3)
@ -815,7 +809,7 @@ class ServiceTest(DockerClientTestCase):
for k, v in {'ONE': '1', 'TWO': '2', 'THREE': '3', 'FOO': 'baz', 'DOO': 'dah'}.items(): for k, v in {'ONE': '1', 'TWO': '2', 'THREE': '3', 'FOO': 'baz', 'DOO': 'dah'}.items():
self.assertEqual(env[k], v) self.assertEqual(env[k], v)
@patch.dict(os.environ) @mock.patch.dict(os.environ)
def test_resolve_env(self): def test_resolve_env(self):
os.environ['FILE_DEF'] = 'E1' os.environ['FILE_DEF'] = 'E1'
os.environ['FILE_DEF_EMPTY'] = 'E2' os.environ['FILE_DEF_EMPTY'] = 'E2'

View File

@ -3,9 +3,8 @@ from __future__ import unicode_literals
import os import os
import mock
from compose.cli import docker_client from compose.cli import docker_client
from tests import mock
from tests import unittest from tests import unittest

View File

@ -1,6 +1,8 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import six
from compose.cli import verbose_proxy from compose.cli import verbose_proxy
from tests import unittest from tests import unittest
@ -8,7 +10,8 @@ from tests import unittest
class VerboseProxyTestCase(unittest.TestCase): class VerboseProxyTestCase(unittest.TestCase):
def test_format_call(self): def test_format_call(self):
expected = "(u'arg1', True, key=u'value')" prefix = '' if six.PY3 else 'u'
expected = "(%(p)s'arg1', True, key=%(p)s'value')" % dict(p=prefix)
actual = verbose_proxy.format_call( actual = verbose_proxy.format_call(
("arg1", True), ("arg1", True),
{'key': 'value'}) {'key': 'value'})
@ -21,7 +24,7 @@ class VerboseProxyTestCase(unittest.TestCase):
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
def test_format_return(self): def test_format_return(self):
expected = "{u'Id': u'ok'}" expected = repr({'Id': 'ok'})
actual = verbose_proxy.format_return({'Id': 'ok'}, 2) actual = verbose_proxy.format_return({'Id': 'ok'}, 2)
self.assertEqual(expected, actual) self.assertEqual(expected, actual)

View File

@ -4,8 +4,8 @@ from __future__ import unicode_literals
import os import os
import docker import docker
import mock
from .. import mock
from .. import unittest from .. import unittest
from compose.cli.docopt_command import NoSuchCommand from compose.cli.docopt_command import NoSuchCommand
from compose.cli.errors import UserError from compose.cli.errors import UserError

View File

@ -1,9 +1,11 @@
from __future__ import print_function
import os import os
import shutil import shutil
import tempfile import tempfile
from operator import itemgetter
import mock from .. import mock
from .. import unittest from .. import unittest
from compose.config import config from compose.config import config
from compose.config.errors import ConfigurationError from compose.config.errors import ConfigurationError
@ -16,6 +18,10 @@ def make_service_dict(name, service_dict, working_dir):
return config.ServiceLoader(working_dir=working_dir).make_service_dict(name, service_dict) return config.ServiceLoader(working_dir=working_dir).make_service_dict(name, service_dict)
def service_sort(services):
return sorted(services, key=itemgetter('name'))
class ConfigTest(unittest.TestCase): class ConfigTest(unittest.TestCase):
def test_load(self): def test_load(self):
service_dicts = config.load( service_dicts = config.load(
@ -30,8 +36,8 @@ class ConfigTest(unittest.TestCase):
) )
self.assertEqual( self.assertEqual(
sorted(service_dicts, key=lambda d: d['name']), service_sort(service_dicts),
sorted([ service_sort([
{ {
'name': 'bar', 'name': 'bar',
'image': 'busybox', 'image': 'busybox',
@ -41,7 +47,7 @@ class ConfigTest(unittest.TestCase):
'name': 'foo', 'name': 'foo',
'image': 'busybox', 'image': 'busybox',
} }
], key=lambda d: d['name']) ])
) )
def test_load_throws_error_when_not_dict(self): def test_load_throws_error_when_not_dict(self):
@ -682,12 +688,7 @@ class ExtendsTest(unittest.TestCase):
def test_extends(self): def test_extends(self):
service_dicts = load_from_filename('tests/fixtures/extends/docker-compose.yml') service_dicts = load_from_filename('tests/fixtures/extends/docker-compose.yml')
service_dicts = sorted( self.assertEqual(service_sort(service_dicts), service_sort([
service_dicts,
key=lambda sd: sd['name'],
)
self.assertEqual(service_dicts, [
{ {
'name': 'mydb', 'name': 'mydb',
'image': 'busybox', 'image': 'busybox',
@ -704,7 +705,7 @@ class ExtendsTest(unittest.TestCase):
"BAZ": "2", "BAZ": "2",
}, },
} }
]) ]))
def test_nested(self): def test_nested(self):
service_dicts = load_from_filename('tests/fixtures/extends/nested.yml') service_dicts = load_from_filename('tests/fixtures/extends/nested.yml')
@ -726,7 +727,7 @@ class ExtendsTest(unittest.TestCase):
We specify a 'file' key that is the filename we're already in. We specify a 'file' key that is the filename we're already in.
""" """
service_dicts = load_from_filename('tests/fixtures/extends/specify-file-as-self.yml') service_dicts = load_from_filename('tests/fixtures/extends/specify-file-as-self.yml')
self.assertEqual(service_dicts, [ self.assertEqual(service_sort(service_dicts), service_sort([
{ {
'environment': 'environment':
{ {
@ -747,7 +748,7 @@ class ExtendsTest(unittest.TestCase):
'image': 'busybox', 'image': 'busybox',
'name': 'web' 'name': 'web'
} }
]) ]))
def test_circular(self): def test_circular(self):
try: try:
@ -854,7 +855,7 @@ class ExtendsTest(unittest.TestCase):
config is valid and correctly extends from itself. config is valid and correctly extends from itself.
""" """
service_dicts = load_from_filename('tests/fixtures/extends/no-file-specified.yml') service_dicts = load_from_filename('tests/fixtures/extends/no-file-specified.yml')
self.assertEqual(service_dicts, [ self.assertEqual(service_sort(service_dicts), service_sort([
{ {
'name': 'myweb', 'name': 'myweb',
'image': 'busybox', 'image': 'busybox',
@ -870,7 +871,7 @@ class ExtendsTest(unittest.TestCase):
"BAZ": "3", "BAZ": "3",
} }
} }
]) ]))
def test_blacklisted_options(self): def test_blacklisted_options(self):
def load_config(): def load_config():
@ -885,24 +886,24 @@ class ExtendsTest(unittest.TestCase):
other_config = {'web': {'links': ['db']}} other_config = {'web': {'links': ['db']}}
with mock.patch.object(config, 'load_yaml', return_value=other_config): with mock.patch.object(config, 'load_yaml', return_value=other_config):
print load_config() print(load_config())
with self.assertRaisesRegexp(ConfigurationError, 'volumes_from'): with self.assertRaisesRegexp(ConfigurationError, 'volumes_from'):
other_config = {'web': {'volumes_from': ['db']}} other_config = {'web': {'volumes_from': ['db']}}
with mock.patch.object(config, 'load_yaml', return_value=other_config): with mock.patch.object(config, 'load_yaml', return_value=other_config):
print load_config() print(load_config())
with self.assertRaisesRegexp(ConfigurationError, 'net'): with self.assertRaisesRegexp(ConfigurationError, 'net'):
other_config = {'web': {'net': 'container:db'}} other_config = {'web': {'net': 'container:db'}}
with mock.patch.object(config, 'load_yaml', return_value=other_config): with mock.patch.object(config, 'load_yaml', return_value=other_config):
print load_config() print(load_config())
other_config = {'web': {'net': 'host'}} other_config = {'web': {'net': 'host'}}
with mock.patch.object(config, 'load_yaml', return_value=other_config): with mock.patch.object(config, 'load_yaml', return_value=other_config):
print load_config() print(load_config())
def test_volume_path(self): def test_volume_path(self):
dicts = load_from_filename('tests/fixtures/volume-path/docker-compose.yml') dicts = load_from_filename('tests/fixtures/volume-path/docker-compose.yml')

View File

@ -1,8 +1,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import docker import docker
import mock
from .. import mock
from .. import unittest from .. import unittest
from compose.container import Container from compose.container import Container
from compose.container import get_container_name from compose.container import get_container_name

View File

@ -3,6 +3,8 @@ from __future__ import unicode_literals
import os import os
import six
from .. import unittest from .. import unittest
from compose.cli.log_printer import LogPrinter from compose.cli.log_printer import LogPrinter
@ -31,14 +33,17 @@ class LogPrinterTest(unittest.TestCase):
self.assertIn('\033[', output) self.assertIn('\033[', output)
def test_unicode(self): def test_unicode(self):
glyph = u'\u2022'.encode('utf-8') glyph = u'\u2022'
def reader(*args, **kwargs): def reader(*args, **kwargs):
yield glyph + b'\n' yield glyph + '\n'
container = MockContainer(reader) container = MockContainer(reader)
output = run_log_printer([container]) output = run_log_printer([container])
if six.PY2:
output = output.decode('utf-8')
self.assertIn(glyph, output) self.assertIn(glyph, output)

View File

@ -8,30 +8,29 @@ from tests import unittest
class ProgressStreamTestCase(unittest.TestCase): class ProgressStreamTestCase(unittest.TestCase):
def test_stream_output(self): def test_stream_output(self):
output = [ output = [
'{"status": "Downloading", "progressDetail": {"current": ' b'{"status": "Downloading", "progressDetail": {"current": '
'31019763, "start": 1413653874, "total": 62763875}, ' b'31019763, "start": 1413653874, "total": 62763875}, '
'"progress": "..."}', b'"progress": "..."}',
] ]
events = progress_stream.stream_output(output, StringIO()) events = progress_stream.stream_output(output, StringIO())
self.assertEqual(len(events), 1) self.assertEqual(len(events), 1)
def test_stream_output_div_zero(self): def test_stream_output_div_zero(self):
output = [ output = [
'{"status": "Downloading", "progressDetail": {"current": ' b'{"status": "Downloading", "progressDetail": {"current": '
'0, "start": 1413653874, "total": 0}, ' b'0, "start": 1413653874, "total": 0}, '
'"progress": "..."}', b'"progress": "..."}',
] ]
events = progress_stream.stream_output(output, StringIO()) events = progress_stream.stream_output(output, StringIO())
self.assertEqual(len(events), 1) self.assertEqual(len(events), 1)
def test_stream_output_null_total(self): def test_stream_output_null_total(self):
output = [ output = [
'{"status": "Downloading", "progressDetail": {"current": ' b'{"status": "Downloading", "progressDetail": {"current": '
'0, "start": 1413653874, "total": null}, ' b'0, "start": 1413653874, "total": null}, '
'"progress": "..."}', b'"progress": "..."}',
] ]
events = progress_stream.stream_output(output, StringIO()) events = progress_stream.stream_output(output, StringIO())
self.assertEqual(len(events), 1) self.assertEqual(len(events), 1)

View File

@ -1,8 +1,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import docker import docker
import mock
from .. import mock
from .. import unittest from .. import unittest
from compose.const import LABEL_SERVICE from compose.const import LABEL_SERVICE
from compose.container import Container from compose.container import Container

View File

@ -2,9 +2,9 @@ from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import docker import docker
import mock
from docker.utils import LogConfig from docker.utils import LogConfig
from .. import mock
from .. import unittest from .. import unittest
from compose.const import LABEL_ONE_OFF from compose.const import LABEL_ONE_OFF
from compose.const import LABEL_PROJECT from compose.const import LABEL_PROJECT
@ -34,14 +34,14 @@ class ServiceTest(unittest.TestCase):
def test_containers(self): def test_containers(self):
service = Service('db', self.mock_client, 'myproject', image='foo') service = Service('db', self.mock_client, 'myproject', image='foo')
self.mock_client.containers.return_value = [] self.mock_client.containers.return_value = []
self.assertEqual(service.containers(), []) self.assertEqual(list(service.containers()), [])
def test_containers_with_containers(self): def test_containers_with_containers(self):
self.mock_client.containers.return_value = [ self.mock_client.containers.return_value = [
dict(Name=str(i), Image='foo', Id=i) for i in range(3) dict(Name=str(i), Image='foo', Id=i) for i in range(3)
] ]
service = Service('db', self.mock_client, 'myproject', image='foo') service = Service('db', self.mock_client, 'myproject', image='foo')
self.assertEqual([c.id for c in service.containers()], range(3)) self.assertEqual([c.id for c in service.containers()], list(range(3)))
expected_labels = [ expected_labels = [
'{0}=myproject'.format(LABEL_PROJECT), '{0}=myproject'.format(LABEL_PROJECT),
@ -280,7 +280,7 @@ class ServiceTest(unittest.TestCase):
def test_build_does_not_pull(self): def test_build_does_not_pull(self):
self.mock_client.build.return_value = [ self.mock_client.build.return_value = [
'{"stream": "Successfully built 12345"}', b'{"stream": "Successfully built 12345"}',
] ]
service = Service('foo', client=self.mock_client, build='.') service = Service('foo', client=self.mock_client, build='.')

View File

@ -8,38 +8,38 @@ from compose.cli.utils import split_buffer
class SplitBufferTest(unittest.TestCase): class SplitBufferTest(unittest.TestCase):
def test_single_line_chunks(self): def test_single_line_chunks(self):
def reader(): def reader():
yield b'abc\n' yield 'abc\n'
yield b'def\n' yield 'def\n'
yield b'ghi\n' yield 'ghi\n'
self.assert_produces(reader, [b'abc\n', b'def\n', b'ghi\n']) self.assert_produces(reader, ['abc\n', 'def\n', 'ghi\n'])
def test_no_end_separator(self): def test_no_end_separator(self):
def reader(): def reader():
yield b'abc\n' yield 'abc\n'
yield b'def\n' yield 'def\n'
yield b'ghi' yield 'ghi'
self.assert_produces(reader, [b'abc\n', b'def\n', b'ghi']) self.assert_produces(reader, ['abc\n', 'def\n', 'ghi'])
def test_multiple_line_chunk(self): def test_multiple_line_chunk(self):
def reader(): def reader():
yield b'abc\ndef\nghi' yield 'abc\ndef\nghi'
self.assert_produces(reader, [b'abc\n', b'def\n', b'ghi']) self.assert_produces(reader, ['abc\n', 'def\n', 'ghi'])
def test_chunked_line(self): def test_chunked_line(self):
def reader(): def reader():
yield b'a' yield 'a'
yield b'b' yield 'b'
yield b'c' yield 'c'
yield b'\n' yield '\n'
yield b'd' yield 'd'
self.assert_produces(reader, [b'abc\n', b'd']) self.assert_produces(reader, ['abc\n', 'd'])
def test_preserves_unicode_sequences_within_lines(self): def test_preserves_unicode_sequences_within_lines(self):
string = u"a\u2022c\n".encode('utf-8') string = u"a\u2022c\n"
def reader(): def reader():
yield string yield string
@ -47,7 +47,7 @@ class SplitBufferTest(unittest.TestCase):
self.assert_produces(reader, [string]) self.assert_produces(reader, [string])
def assert_produces(self, reader, expectations): def assert_produces(self, reader, expectations):
split = split_buffer(reader(), b'\n') split = split_buffer(reader(), '\n')
for (actual, expected) in zip(split, expectations): for (actual, expected) in zip(split, expectations):
self.assertEqual(type(actual), type(expected)) self.assertEqual(type(actual), type(expected))

18
tox.ini
View File

@ -1,15 +1,16 @@
[tox] [tox]
envlist = py27,pre-commit envlist = py27,py34,pre-commit
[testenv] [testenv]
usedevelop=True usedevelop=True
passenv = passenv =
LD_LIBRARY_PATH LD_LIBRARY_PATH
setenv =
HOME=/tmp
deps = deps =
-rrequirements.txt -rrequirements.txt
-rrequirements-dev.txt
commands = commands =
nosetests -v {posargs} nosetests -v --with-coverage --cover-branches --cover-package=compose --cover-erase --cover-html-dir=coverage-html --cover-html {posargs}
flake8 compose tests setup.py flake8 compose tests setup.py
[testenv:pre-commit] [testenv:pre-commit]
@ -20,6 +21,17 @@ commands =
pre-commit install pre-commit install
pre-commit run --all-files pre-commit run --all-files
[testenv:py27]
deps =
{[testenv]deps}
-rrequirements-dev.txt
[testenv:py34]
deps =
{[testenv]deps}
flake8
nose
[flake8] [flake8]
# ignore line-length for now # ignore line-length for now
ignore = E501,E203 ignore = E501,E203