Skip to content

Commit 728456f

Browse files
committed
Adding integ tests to verify docker compose version
1 parent 464aa5c commit 728456f

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
"""Integration tests for Docker Compose version detection fix (issue #5739).
14+
15+
These tests verify that _get_compose_cmd_prefix correctly accepts Docker Compose
16+
versions >= 2 (including v3, v4, v5, etc.) rather than only accepting v2.
17+
18+
The tests run against the real Docker Compose installation on the machine — no mocking.
19+
Requires: Docker with Compose plugin installed (any version >= 2).
20+
"""
21+
from __future__ import absolute_import
22+
23+
import re
24+
import subprocess
25+
26+
import pytest
27+
28+
from sagemaker.core.local.image import _SageMakerContainer
29+
from sagemaker.core.modules.local_core.local_container import (
30+
_LocalContainer as CoreModulesLocalContainer,
31+
)
32+
from sagemaker.core.shapes import Channel, DataSource, S3DataSource
33+
from sagemaker.train.local.local_container import (
34+
_LocalContainer as TrainLocalContainer,
35+
)
36+
37+
38+
def _get_installed_compose_major_version():
39+
"""Return the major version int of the installed Docker Compose, or None."""
40+
try:
41+
output = subprocess.check_output(
42+
["docker", "compose", "version"],
43+
stderr=subprocess.DEVNULL,
44+
encoding="UTF-8",
45+
)
46+
match = re.search(r"v(\d+)", output.strip())
47+
if match:
48+
return int(match.group(1))
49+
except (subprocess.CalledProcessError, FileNotFoundError):
50+
pass
51+
return None
52+
53+
54+
# Skip the entire module if Docker Compose >= 2 is not available
55+
_compose_major = _get_installed_compose_major_version()
56+
pytestmark = pytest.mark.skipif(
57+
_compose_major is None or _compose_major < 2,
58+
reason=f"Docker Compose >= 2 required (found: v{_compose_major})",
59+
)
60+
61+
62+
def _make_basic_channel():
63+
"""Create a minimal Channel for constructing _LocalContainer instances."""
64+
data_source = DataSource(
65+
s3_data_source=S3DataSource(
66+
s3_uri="s3://bucket/data",
67+
s3_data_type="S3Prefix",
68+
s3_data_distribution_type="FullyReplicated",
69+
)
70+
)
71+
return Channel(channel_name="training", data_source=data_source)
72+
73+
74+
def _make_local_container(container_cls):
75+
"""Construct a _LocalContainer with minimal valid args.
76+
77+
sagemaker_session is None since _get_compose_cmd_prefix doesn't use it,
78+
and the Pydantic model rejects Mock objects.
79+
"""
80+
return container_cls(
81+
training_job_name="integ-test-compose-detection",
82+
instance_type="local",
83+
instance_count=1,
84+
image="test-image:latest",
85+
container_root="/tmp/test",
86+
input_data_config=[_make_basic_channel()],
87+
environment={},
88+
hyper_parameters={},
89+
container_entrypoint=[],
90+
container_arguments=[],
91+
sagemaker_session=None,
92+
)
93+
94+
95+
@pytest.fixture
96+
def _core_modules_container():
97+
return _make_local_container(CoreModulesLocalContainer)
98+
99+
100+
@pytest.fixture
101+
def _train_container():
102+
return _make_local_container(TrainLocalContainer)
103+
104+
105+
class TestDockerComposeVersionDetection:
106+
"""Integration tests for _get_compose_cmd_prefix across all three code locations.
107+
108+
Validates the fix for https://github.com/aws/sagemaker-python-sdk/issues/5739
109+
where Docker Compose v3+ was incorrectly rejected.
110+
"""
111+
112+
def test_sagemaker_core_image_accepts_installed_compose(self):
113+
"""sagemaker-core local/image.py _SageMakerContainer._get_compose_cmd_prefix
114+
should accept the installed Docker Compose version."""
115+
result = _SageMakerContainer._get_compose_cmd_prefix()
116+
117+
assert result == ["docker", "compose"], (
118+
f"Expected ['docker', 'compose'] but got {result}. "
119+
f"Installed Docker Compose is v{_compose_major}."
120+
)
121+
122+
def test_sagemaker_core_modules_local_container_accepts_installed_compose(
123+
self, _core_modules_container
124+
):
125+
"""sagemaker-core modules/local_core/local_container.py
126+
_LocalContainer._get_compose_cmd_prefix should accept the installed version."""
127+
result = _core_modules_container._get_compose_cmd_prefix()
128+
129+
assert result == ["docker", "compose"], (
130+
f"Expected ['docker', 'compose'] but got {result}. "
131+
f"Installed Docker Compose is v{_compose_major}."
132+
)
133+
134+
def test_sagemaker_train_local_container_accepts_installed_compose(
135+
self, _train_container
136+
):
137+
"""sagemaker-train local/local_container.py
138+
_LocalContainer._get_compose_cmd_prefix should accept the installed version."""
139+
result = _train_container._get_compose_cmd_prefix()
140+
141+
assert result == ["docker", "compose"], (
142+
f"Expected ['docker', 'compose'] but got {result}. "
143+
f"Installed Docker Compose is v{_compose_major}."
144+
)
145+
146+
def test_returned_command_is_functional(self):
147+
"""The command returned by _get_compose_cmd_prefix should actually work."""
148+
cmd = _SageMakerContainer._get_compose_cmd_prefix()
149+
150+
# Run the returned command with "version" to prove it's functional
151+
result = subprocess.run(
152+
cmd + ["version"],
153+
capture_output=True,
154+
text=True,
155+
timeout=10,
156+
)
157+
assert result.returncode == 0, (
158+
f"Command {cmd + ['version']} failed: {result.stderr}"
159+
)
160+
assert "version" in result.stdout.lower(), (
161+
f"Unexpected output from {cmd + ['version']}: {result.stdout}"
162+
)
163+
164+
@pytest.mark.skipif(
165+
_compose_major is not None and _compose_major < 3,
166+
reason="This test specifically validates v3+ acceptance (installed is v2)",
167+
)
168+
def test_v3_plus_specifically_accepted(self):
169+
"""When Docker Compose v3+ is installed, it must be accepted — not rejected.
170+
171+
This is the core regression test for issue #5739.
172+
"""
173+
result = _SageMakerContainer._get_compose_cmd_prefix()
174+
assert result == ["docker", "compose"], (
175+
f"Docker Compose v{_compose_major} was rejected. "
176+
"This is the exact bug described in issue #5739."
177+
)

0 commit comments

Comments
 (0)