Skip to content

Commit 9cfe64d

Browse files
Hat Daoclaude
andcommitted
Fix GitHub Actions and add comprehensive tests
- Fixed GitHub Actions error with boolean flag handling - Changed from --push=False to --no-push pattern (more idiomatic) - Added comprehensive test suite for tasks.py - Updated documentation with correct usage examples - Added run_tests.sh script for easy test execution The tests verify: - Multi-architecture build functionality - Platform parameter handling - Version filtering and selection - Docker buildx integration - Pool size configuration 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
1 parent b8cd0e4 commit 9cfe64d

File tree

6 files changed

+241
-7
lines changed

6 files changed

+241
-7
lines changed

.github/workflows/dockerimage.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ jobs:
2525
- name: Build multi-architecture images
2626
run: |
2727
# Build for both amd64 and arm64
28-
invoke buildx latest --push=False
28+
# Note: --no-push flag is used instead of --push=False
29+
invoke buildx latest --no-push
2930
3031
- name: Test the built image
3132
run: |

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ jobs:
5050
5151
# Determine if we should push
5252
if [ "${{ github.event.inputs.push }}" == "true" ] || [ "${{ github.event_name }}" == "release" ]; then
53-
PUSH_FLAG="--push=True"
53+
PUSH_FLAG="" # Default behavior is to push
5454
else
55-
PUSH_FLAG="--push=False"
55+
PUSH_FLAG="--no-push"
5656
fi
5757
5858
echo "Building version: $VERSION with push flag: $PUSH_FLAG"

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,17 +239,20 @@ To build multi-architecture images locally, you need Docker Buildx (included in
239239

240240
#### Using invoke tasks (recommended)
241241

242-
# Build for both AMD64 and ARM64 (default)
242+
# Build and push to Docker Hub (default behavior)
243243
invoke buildx 7.2.5
244244

245-
# Build and push to Docker Hub
246-
invoke buildx 7.2.5 --push=True
245+
# Build without pushing (for local testing)
246+
invoke buildx 7.2.5 --no-push
247247

248248
# Build for specific platforms only
249249
invoke buildx 7.2.5 --platforms="linux/amd64"
250250

251251
# Build all versions for multi-arch
252252
invoke buildx all
253+
254+
# Build without pushing for a specific platform
255+
invoke buildx 7.2.5 --no-push --platforms="linux/arm64"
253256

254257
#### Using docker buildx directly
255258

run_tests.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
echo "Running tests for tasks.py..."
4+
5+
# Install test dependencies if needed
6+
pip install -q invoke requests
7+
8+
# Run the tests
9+
python -m unittest test_tasks -v
10+
11+
# Check exit code
12+
if [ $? -eq 0 ]; then
13+
echo -e "\n✅ All tests passed!"
14+
else
15+
echo -e "\n❌ Some tests failed!"
16+
exit 1
17+
fi

tasks.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,20 @@ def push(c, version, cpu=None, platforms="linux/amd64,linux/arm64"):
135135

136136

137137
@task
138-
def buildx(c, version, cpu=None, platforms="linux/amd64,linux/arm64", push=True):
138+
def buildx(c, version, cpu=None, platforms="linux/amd64,linux/arm64", push=True, no_push=False):
139139
"""
140140
Build multi-architecture images using docker buildx.
141141
This is more efficient than separate build and push commands for multi-arch.
142+
143+
Usage:
144+
invoke buildx 7.2.5 # Build and push
145+
invoke buildx 7.2.5 --no-push # Build without pushing
146+
invoke buildx 7.2.5 --push # Build and push (explicit)
142147
"""
148+
# Handle the no_push flag
149+
if no_push:
150+
push = False
151+
143152
print(f" -- Docker buildx for version : {version} on platforms : {platforms} (push={push})")
144153

145154
# Create buildx builder if it doesn't exist

test_tasks.py

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import unittest
2+
from unittest.mock import Mock, patch, call
3+
import tasks
4+
from invoke import Context
5+
6+
7+
class TestTasks(unittest.TestCase):
8+
9+
def setUp(self):
10+
self.mock_context = Mock(spec=Context)
11+
12+
def test_version_name_to_version_all(self):
13+
"""Test version_name_to_version returns all versions when 'all' is passed"""
14+
result = tasks.version_name_to_version("all")
15+
self.assertEqual(result, tasks.version_config_mapping)
16+
17+
def test_version_name_to_version_latest(self):
18+
"""Test version_name_to_version returns latest version when 'latest' is passed"""
19+
result = tasks.version_name_to_version("latest")
20+
self.assertEqual(result, [tasks.latest_version_string])
21+
22+
def test_version_name_to_version_specific(self):
23+
"""Test version_name_to_version filters versions correctly"""
24+
result = tasks.version_name_to_version("7.2")
25+
expected = [v for v in tasks.version_config_mapping if v.startswith("7.2")]
26+
self.assertEqual(result, expected)
27+
28+
def test_filter_versions(self):
29+
"""Test filter_versions returns correct subset"""
30+
result = tasks.filter_versions("7.0")
31+
expected = [v for v in tasks.version_config_mapping if v.startswith("7.0")]
32+
self.assertEqual(result, expected)
33+
self.assertTrue(len(result) > 0)
34+
35+
@patch('multiprocessing.cpu_count')
36+
def test_get_pool_size_default(self, mock_cpu_count):
37+
"""Test get_pool_size uses CPU count minus 1 by default"""
38+
mock_cpu_count.return_value = 8
39+
result = tasks.get_pool_size(None)
40+
self.assertEqual(result, 7)
41+
42+
def test_get_pool_size_custom(self):
43+
"""Test get_pool_size uses provided value"""
44+
result = tasks.get_pool_size("4")
45+
self.assertEqual(result, 4)
46+
47+
def test_docker_build_with_platforms(self):
48+
"""Test _docker_build executes correct buildx commands"""
49+
config = [self.mock_context, "7.2.5", "linux/amd64,linux/arm64"]
50+
51+
tasks._docker_build(config)
52+
53+
# Verify buildx setup
54+
self.mock_context.run.assert_any_call(
55+
"docker buildx create --use --name redis-cluster-builder || docker buildx use redis-cluster-builder",
56+
warn=True
57+
)
58+
59+
# Verify build command
60+
expected_build_cmd = "docker buildx build --platform=linux/amd64,linux/arm64 --build-arg redis_version=7.2.5 -t grokzen/redis-cluster:7.2.5 --load ."
61+
self.mock_context.run.assert_any_call(expected_build_cmd)
62+
63+
def test_docker_push_with_platforms(self):
64+
"""Test _docker_push executes correct buildx push commands"""
65+
config = [self.mock_context, "7.2.5", "linux/amd64,linux/arm64"]
66+
67+
tasks._docker_push(config)
68+
69+
# Verify buildx setup
70+
self.mock_context.run.assert_any_call(
71+
"docker buildx create --use --name redis-cluster-builder || docker buildx use redis-cluster-builder",
72+
warn=True
73+
)
74+
75+
# Verify push command
76+
expected_push_cmd = "docker buildx build --platform=linux/amd64,linux/arm64 --build-arg redis_version=7.2.5 -t grokzen/redis-cluster:7.2.5 --push ."
77+
self.mock_context.run.assert_any_call(expected_push_cmd)
78+
79+
def test_docker_buildx_push_true(self):
80+
"""Test _docker_buildx with push=True"""
81+
config = [self.mock_context, "7.2.5", "linux/amd64,linux/arm64", True]
82+
83+
tasks._docker_buildx(config)
84+
85+
expected_cmd = "docker buildx build --platform=linux/amd64,linux/arm64 --build-arg redis_version=7.2.5 -t grokzen/redis-cluster:7.2.5 --push ."
86+
self.mock_context.run.assert_called_with(expected_cmd)
87+
88+
def test_docker_buildx_push_false(self):
89+
"""Test _docker_buildx with push=False"""
90+
config = [self.mock_context, "7.2.5", "linux/amd64,linux/arm64", False]
91+
92+
tasks._docker_buildx(config)
93+
94+
expected_cmd = "docker buildx build --platform=linux/amd64,linux/arm64 --build-arg redis_version=7.2.5 -t grokzen/redis-cluster:7.2.5 --load ."
95+
self.mock_context.run.assert_called_with(expected_cmd)
96+
97+
@patch('tasks.Pool')
98+
def test_build_task_with_default_platforms(self, mock_pool_class):
99+
"""Test build task with default platforms"""
100+
mock_pool = Mock()
101+
mock_pool_class.return_value = mock_pool
102+
103+
tasks.build(self.mock_context, "7.2.5")
104+
105+
# Verify pool.map was called with correct arguments
106+
mock_pool.map.assert_called_once()
107+
call_args = mock_pool.map.call_args[0]
108+
self.assertEqual(call_args[0], tasks._docker_build)
109+
110+
# Check that platform parameter is included
111+
configs = call_args[1]
112+
self.assertEqual(len(configs), 1)
113+
self.assertEqual(configs[0][2], "linux/amd64,linux/arm64")
114+
115+
@patch('tasks.Pool')
116+
def test_push_task_with_custom_platforms(self, mock_pool_class):
117+
"""Test push task with custom platforms"""
118+
mock_pool = Mock()
119+
mock_pool_class.return_value = mock_pool
120+
121+
tasks.push(self.mock_context, "7.2.5", platforms="linux/arm64")
122+
123+
# Verify pool.map was called with correct arguments
124+
mock_pool.map.assert_called_once()
125+
call_args = mock_pool.map.call_args[0]
126+
configs = call_args[1]
127+
self.assertEqual(configs[0][2], "linux/arm64")
128+
129+
@patch('tasks.Pool')
130+
def test_buildx_task(self, mock_pool_class):
131+
"""Test buildx task functionality"""
132+
mock_pool = Mock()
133+
mock_pool_class.return_value = mock_pool
134+
135+
# Test with push=True
136+
tasks.buildx(self.mock_context, "latest", push=True)
137+
138+
# Verify buildx setup
139+
self.mock_context.run.assert_called_with(
140+
"docker buildx create --use --name redis-cluster-builder || docker buildx use redis-cluster-builder",
141+
warn=True
142+
)
143+
144+
# Verify pool.map was called
145+
mock_pool.map.assert_called_once()
146+
call_args = mock_pool.map.call_args[0]
147+
self.assertEqual(call_args[0], tasks._docker_buildx)
148+
149+
configs = call_args[1]
150+
self.assertEqual(len(configs), 1)
151+
self.assertEqual(configs[0][1], tasks.latest_version_string)
152+
self.assertEqual(configs[0][2], "linux/amd64,linux/arm64")
153+
self.assertEqual(configs[0][3], True)
154+
155+
@patch('tasks.Pool')
156+
def test_buildx_task_no_push(self, mock_pool_class):
157+
"""Test buildx task with --no-push flag"""
158+
mock_pool = Mock()
159+
mock_pool_class.return_value = mock_pool
160+
161+
# Test with no_push=True
162+
tasks.buildx(self.mock_context, "latest", no_push=True)
163+
164+
# Verify pool.map was called with push=False
165+
mock_pool.map.assert_called_once()
166+
call_args = mock_pool.map.call_args[0]
167+
configs = call_args[1]
168+
self.assertEqual(configs[0][3], False) # push should be False
169+
170+
171+
class TestIntegration(unittest.TestCase):
172+
"""Integration tests that verify the full workflow"""
173+
174+
@patch('tasks.Pool')
175+
@patch('multiprocessing.cpu_count')
176+
def test_multi_version_build(self, mock_cpu_count, mock_pool_class):
177+
"""Test building multiple versions in parallel"""
178+
mock_cpu_count.return_value = 4
179+
mock_pool = Mock()
180+
mock_pool_class.return_value = mock_pool
181+
mock_context = Mock(spec=Context)
182+
183+
# Build all 7.2.x versions
184+
tasks.build(mock_context, "7.2")
185+
186+
# Verify pool was created with correct size
187+
mock_pool_class.assert_called_with(3) # cpu_count - 1
188+
189+
# Verify map was called
190+
mock_pool.map.assert_called_once()
191+
configs = mock_pool.map.call_args[0][1]
192+
193+
# Should build all 7.2.x versions
194+
versions = [cfg[1] for cfg in configs]
195+
expected_versions = [v for v in tasks.version_config_mapping if v.startswith("7.2")]
196+
self.assertEqual(versions, expected_versions)
197+
198+
# All should have multi-arch platforms
199+
platforms = [cfg[2] for cfg in configs]
200+
self.assertTrue(all(p == "linux/amd64,linux/arm64" for p in platforms))
201+
202+
203+
if __name__ == '__main__':
204+
unittest.main()

0 commit comments

Comments
 (0)