瀏覽代碼

Fix Project#build_container_operation_with_timeout_func not to mutate a 'option' dict over multiple containers

Signed-off-by: Yuichiro Tsuchiya <[email protected]>
tuttieee 6 年之前
父節點
當前提交
572032fc0b
共有 2 個文件被更改,包括 34 次插入3 次删除
  1. 4 3
      compose/project.py
  2. 30 0
      tests/unit/project_test.py

+ 4 - 3
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
 
 

+ 30 - 0
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(