From 572032fc0b2c35f12787a170a891da6c59b4bc90 Mon Sep 17 00:00:00 2001 From: tuttieee Date: Wed, 6 Feb 2019 20:39:19 +0900 Subject: [PATCH] Fix Project#build_container_operation_with_timeout_func not to mutate a 'option' dict over multiple containers Signed-off-by: Yuichiro Tsuchiya --- compose/project.py | 7 ++++--- tests/unit/project_test.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/compose/project.py b/compose/project.py index a2307fa05..bdeff19e6 100644 --- a/compose/project.py +++ b/compose/project.py @@ -725,10 +725,11 @@ class Project(object): def build_container_operation_with_timeout_func(self, operation, options): def container_operation_with_timeout(container): - if options.get('timeout') is None: + _options = options.copy() + if _options.get('timeout') is None: service = self.get_service(container.service) - options['timeout'] = service.stop_timeout(None) - return getattr(container, operation)(**options) + _options['timeout'] = service.stop_timeout(None) + return getattr(container, operation)(**_options) return container_operation_with_timeout diff --git a/tests/unit/project_test.py b/tests/unit/project_test.py index 4aea91a0d..89b080d20 100644 --- a/tests/unit/project_test.py +++ b/tests/unit/project_test.py @@ -15,6 +15,8 @@ from compose.config.types import VolumeFromSpec from compose.const import COMPOSEFILE_V1 as V1 from compose.const import COMPOSEFILE_V2_0 as V2_0 from compose.const import COMPOSEFILE_V2_4 as V2_4 +from compose.const import COMPOSEFILE_V3_7 as V3_7 +from compose.const import DEFAULT_TIMEOUT from compose.const import LABEL_SERVICE from compose.container import Container from compose.errors import OperationFailedError @@ -765,6 +767,34 @@ class ProjectTest(unittest.TestCase): ) assert project.get_service('web').platform == 'linux/s390x' + def test_build_container_operation_with_timeout_func_does_not_mutate_options_with_timeout(self): + config_data = Config( + version=V3_7, + services=[ + {'name': 'web', 'image': 'busybox:latest'}, + {'name': 'db', 'image': 'busybox:latest', 'stop_grace_period': '1s'}, + ], + networks={}, volumes={}, secrets=None, configs=None, + ) + + project = Project.from_config(name='test', client=self.mock_client, config_data=config_data) + + stop_op = project.build_container_operation_with_timeout_func('stop', options={}) + + web_container = mock.create_autospec(Container, service='web') + db_container = mock.create_autospec(Container, service='db') + + # `stop_grace_period` is not set to 'web' service, + # then it is stopped with the default timeout. + stop_op(web_container) + web_container.stop.assert_called_once_with(timeout=DEFAULT_TIMEOUT) + + # `stop_grace_period` is set to 'db' service, + # then it is stopped with the specified timeout and + # the value is not overridden by the previous function call. + stop_op(db_container) + db_container.stop.assert_called_once_with(timeout=1) + @mock.patch('compose.parallel.ParallelStreamWriter._write_noansi') def test_error_parallel_pull(self, mock_write): project = Project.from_config(