3737 required : false
3838 default : ' '
3939 worker_runtime :
40- description : ' Worker runtime to use'
40+ description : ' Worker runtime to use (both = run openclaw and copaw in parallel) '
4141 required : false
4242 type : choice
4343 options :
44+ - both
4445 - openclaw
4546 - copaw
46- default : ' openclaw '
47+ default : ' both '
4748 model :
4849 description : ' LLM model to use'
4950 required : false
5051 default : ' qwen3.5-plus'
5152
5253env :
53- # Tests that do not require a GitHub token
54- NON_GITHUB_TESTS : " 01 02 03 04 05 06 14 15 17 18 19 20 100"
54+ # Tests that do not require a GitHub token, split into four shards for parallel execution
55+ # Shard A: LLM interaction tests (sequential dependency: 02 creates alice → 03-06 use alice)
56+ # Shard B: LLM interaction tests 2 (task assignment with LLM)
57+ # Shard C: Controller/CR tests (independent, no cross-test dependencies)
58+ # Shard D: Controller/CR tests 2 (team project DAG)
59+ SHARD_A_TESTS : " 01 02 03 04 05 06"
60+ SHARD_B_TESTS : " 14"
61+ SHARD_C_TESTS : " 15 17 18 19 20 100"
62+ SHARD_D_TESTS : " 21"
63+ NON_GITHUB_TESTS : " 01 02 03 04 05 06 14 15 17 18 19 20 21 100"
5564
5665jobs :
66+ # Step 1: Build the shared base image (hiclaw-controller)
67+ build-base :
68+ runs-on : ubuntu-latest
69+ timeout-minutes : 15
70+ steps :
71+ - name : Free Up Disk Space
72+ uses : jlumbroso/free-disk-space@main
73+ with :
74+ tool-cache : false
75+ android : true
76+ dotnet : true
77+ haskell : true
78+ large-packages : true
79+ swap-storage : true
80+
81+ - name : Checkout code
82+ uses : actions/checkout@v4
83+ with :
84+ ref : ${{ github.event.pull_request.head.sha || github.sha }}
85+ repository : ${{ github.event.pull_request.head.repo.full_name || github.repository }}
86+
87+ - name : Set up Docker Buildx
88+ uses : docker/setup-buildx-action@v3
89+
90+ - name : Build hiclaw-controller
91+ run : make build-hiclaw-controller DOCKER_BUILD_ARGS="--build-arg APT_MIRROR="
92+
93+ - name : Save image
94+ run : |
95+ docker save hiclaw/hiclaw-controller:latest | gzip > /tmp/hiclaw-controller.tar.gz
96+
97+ - name : Upload image
98+ uses : actions/upload-artifact@v4
99+ with :
100+ name : image-controller
101+ path : /tmp/hiclaw-controller.tar.gz
102+ retention-days : 1
103+
104+ # Step 2: Build downstream images in parallel (all depend on hiclaw-controller)
105+ build-images :
106+ needs : build-base
107+ runs-on : ubuntu-latest
108+ timeout-minutes : 20
109+ strategy :
110+ fail-fast : false
111+ matrix :
112+ target : [embedded, manager, manager-copaw, worker, copaw-worker]
113+ steps :
114+ - name : Free Up Disk Space
115+ uses : jlumbroso/free-disk-space@main
116+ with :
117+ tool-cache : false
118+ android : true
119+ dotnet : true
120+ haskell : true
121+ large-packages : true
122+ swap-storage : true
123+
124+ - name : Checkout code
125+ uses : actions/checkout@v4
126+ with :
127+ ref : ${{ github.event.pull_request.head.sha || github.sha }}
128+ repository : ${{ github.event.pull_request.head.repo.full_name || github.repository }}
129+
130+ - name : Set up Docker Buildx
131+ uses : docker/setup-buildx-action@v3
132+
133+ - name : Download controller image
134+ uses : actions/download-artifact@v4
135+ with :
136+ name : image-controller
137+ path : /tmp
138+
139+ - name : Load controller image
140+ run : gunzip -c /tmp/hiclaw-controller.tar.gz | docker load
141+
142+ - name : Build image
143+ run : make build-${{ matrix.target }} DOCKER_BUILD_ARGS="--build-arg APT_MIRROR="
144+
145+ - name : Save image
146+ run : |
147+ docker save \
148+ $(docker images --format '{{.Repository}}:{{.Tag}}' | grep -E '^hiclaw/' | grep -v '<none>' | grep -v 'hiclaw-controller') \
149+ | gzip > /tmp/hiclaw-${{ matrix.target }}.tar.gz
150+
151+ - name : Upload image
152+ uses : actions/upload-artifact@v4
153+ with :
154+ name : image-${{ matrix.target }}
155+ path : /tmp/hiclaw-${{ matrix.target }}.tar.gz
156+ retention-days : 1
157+
158+ # Step 3: Run test shards in parallel, each on its own runner with isolated cluster
57159 integration-tests :
160+ needs : build-images
161+ if : >-
162+ inputs.worker_runtime == '' || inputs.worker_runtime == 'both'
163+ || inputs.worker_runtime == matrix.runtime
58164 runs-on : ubuntu-latest
59- timeout-minutes : 120
165+ timeout-minutes : 90
60166 permissions :
61167 contents : write
62168 pull-requests : write
63169 actions : read
170+ strategy :
171+ fail-fast : false
172+ matrix :
173+ include :
174+ - shard : llm-interaction
175+ filter_env : SHARD_A_TESTS
176+ runtime : openclaw
177+ - shard : llm-interaction-2
178+ filter_env : SHARD_B_TESTS
179+ runtime : openclaw
180+ - shard : controller-cr
181+ filter_env : SHARD_C_TESTS
182+ runtime : openclaw
183+ - shard : controller-cr-2
184+ filter_env : SHARD_D_TESTS
185+ runtime : openclaw
186+ - shard : llm-interaction
187+ filter_env : SHARD_A_TESTS
188+ runtime : copaw
189+ - shard : llm-interaction-2
190+ filter_env : SHARD_B_TESTS
191+ runtime : copaw
192+ - shard : controller-cr
193+ filter_env : SHARD_C_TESTS
194+ runtime : copaw
195+ - shard : controller-cr-2
196+ filter_env : SHARD_D_TESTS
197+ runtime : copaw
64198
65199 steps :
66200 - name : Free Up Disk Space
@@ -79,8 +213,17 @@ jobs:
79213 ref : ${{ github.event.pull_request.head.sha || github.sha }}
80214 repository : ${{ github.event.pull_request.head.repo.full_name || github.repository }}
81215
82- - name : Set up Docker Buildx
83- uses : docker/setup-buildx-action@v3
216+ - name : Download all images
217+ uses : actions/download-artifact@v4
218+ with :
219+ pattern : image-*
220+ path : /tmp/images
221+
222+ - name : Load images
223+ run : |
224+ for f in /tmp/images/image-*/*.tar.gz; do
225+ gunzip -c "$f" | docker load
226+ done
84227
85228 - name : Install dependencies
86229 run : sudo apt-get update && sudo apt-get install -y jq curl unzip
@@ -91,20 +234,21 @@ jobs:
91234 HICLAW_LLM_API_KEY : ${{ secrets.HICLAW_LLM_API_KEY }}
92235 HICLAW_LLM_PROVIDER : qwen
93236 HICLAW_DEFAULT_MODEL : ${{ inputs.model || 'qwen3.5-plus' }}
94- HICLAW_MANAGER_RUNTIME : ${{ inputs.worker_runtime || 'openclaw' }}
237+ HICLAW_MANAGER_RUNTIME : ${{ matrix.runtime }}
95238 run : |
96239 FILTER="${{ github.event.inputs.test_filter }}"
97- [ -z "$FILTER" ] && FILTER="${NON_GITHUB_TESTS}"
98- make test-embedded \
99- DOCKER_BUILD_ARGS="--build-arg APT_MIRROR=" \
240+ if [ -z "$FILTER" ]; then
241+ FILTER="${{ env[matrix.filter_env] }}"
242+ fi
243+ SKIP_BUILD=1 make test-embedded \
100244 TEST_FILTER="$FILTER"
101245
102246 # ============================================================
103247 # Metrics: download latest release baseline for comparison
104248 # ============================================================
105249
106250 - name : Download latest release baseline
107- if : github.event_name == 'pull_request_target'
251+ if : github.event_name == 'pull_request_target' && matrix.shard == 'llm-interaction' && matrix.runtime == 'openclaw'
108252 continue-on-error : true
109253 run : |
110254 mkdir -p baseline-metrics
@@ -124,15 +268,17 @@ jobs:
124268 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
125269
126270 - name : Generate metrics comparison and post PR comment
127- if : github.event_name == 'pull_request_target'
271+ if : github.event_name == 'pull_request_target' && matrix.shard == 'llm-interaction' && matrix.runtime == 'openclaw'
128272 env :
129273 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
130274 run : |
131275 source tests/lib/agent-metrics.sh
132276
133277 # Collect current summary across all tests that ran
134278 FILTER="${{ github.event.inputs.test_filter }}"
135- [ -z "$FILTER" ] && FILTER="${NON_GITHUB_TESTS}"
279+ if [ -z "$FILTER" ]; then
280+ FILTER="${{ env[matrix.filter_env] }}"
281+ fi
136282 TEST_NAMES=$(echo "$FILTER" | tr ' ' '\n' | while read n; do
137283 f=$(ls tests/output/metrics-*${n}*.json 2>/dev/null | head -1)
138284 [ -n "$f" ] && basename "$f" .json | sed 's/^metrics-//'
@@ -209,7 +355,7 @@ jobs:
209355 DEBUG_TAIL=$(find "$DEBUG_DIR" -name "*.log" -exec tail -20 {} + 2>/dev/null | tail -80)
210356 fi
211357
212- BODY="## ❌ Integration Tests Failed
358+ BODY="## ❌ Integration Tests Failed (${{ matrix.shard }} / ${{ matrix.runtime }})
213359
214360 **Commit:** ${{ github.event.pull_request.head.sha }}
215361 **Workflow run:** [#${{ github.run_number }}](${ARTIFACT_URL})
@@ -237,7 +383,7 @@ jobs:
237383
238384 # Update or create comment
239385 EXISTING=$(gh api "repos/$REPO/issues/$PR_NUM/comments" \
240- --jq '.[] | select(.body | startswith("## ❌ Integration Tests Failed")) | .id' | head -1)
386+ --jq '.[] | select(.body | startswith("## ❌ Integration Tests Failed (${{ matrix.shard }} / ${{ matrix.runtime }}) ")) | .id' | head -1)
241387 if [ -n "$EXISTING" ]; then
242388 gh api --method PATCH "repos/$REPO/issues/comments/$EXISTING" -f body="$BODY"
243389 else
@@ -255,7 +401,7 @@ jobs:
255401 if : always()
256402 uses : actions/upload-artifact@v4
257403 with :
258- name : test-artifacts-${{ github.sha }}
404+ name : test-artifacts-${{ matrix.shard }}-${{ matrix.runtime }}-${{ github.sha }}
259405 path : test-artifacts/
260406 retention-days : 7
261407
@@ -264,7 +410,7 @@ jobs:
264410 # ============================================================
265411
266412 - name : Generate release baseline
267- if : startsWith(github.ref, 'refs/tags/v')
413+ if : startsWith(github.ref, 'refs/tags/v') && matrix.shard == 'llm-interaction' && matrix.runtime == 'openclaw'
268414 run : |
269415 source tests/lib/agent-metrics.sh
270416 TEST_NAMES=$(echo "$NON_GITHUB_TESTS" | tr ' ' '\n' | while read n; do
@@ -276,7 +422,7 @@ jobs:
276422 cat metrics-baseline.json | jq '{totals: .totals, by_role: .by_role}'
277423
278424 - name : Upload baseline to release
279- if : startsWith(github.ref, 'refs/tags/v')
425+ if : startsWith(github.ref, 'refs/tags/v') && matrix.shard == 'llm-interaction' && matrix.runtime == 'openclaw'
280426 env :
281427 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
282428 run : |
@@ -285,7 +431,7 @@ jobs:
285431 echo "✅ Baseline uploaded to release ${GITHUB_REF_NAME}"
286432
287433 - name : Upload debug log to release
288- if : startsWith(github.ref, 'refs/tags/v') && always()
434+ if : startsWith(github.ref, 'refs/tags/v') && matrix.shard == 'llm-interaction' && matrix.runtime == 'openclaw' && always()
289435 continue-on-error : true
290436 env :
291437 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
@@ -305,8 +451,9 @@ jobs:
305451 if : always()
306452 run : |
307453 echo "### Integration Test Summary" >> $GITHUB_STEP_SUMMARY
308- echo "- Tests: \`$NON_GITHUB_TESTS\`" >> $GITHUB_STEP_SUMMARY
309- echo "- Worker Runtime: \`${{ inputs.worker_runtime || 'openclaw' }}\`" >> $GITHUB_STEP_SUMMARY
454+ echo "- Shard: \`${{ matrix.shard }}\`" >> $GITHUB_STEP_SUMMARY
455+ echo "- Tests: \`${{ env[matrix.filter_env] }}\`" >> $GITHUB_STEP_SUMMARY
456+ echo "- Worker Runtime: \`${{ matrix.runtime }}\`" >> $GITHUB_STEP_SUMMARY
310457 echo "- Model: \`${{ inputs.model || 'qwen3.5-plus' }}\`" >> $GITHUB_STEP_SUMMARY
311458 echo "- Install Mode: embedded (make test-embedded)" >> $GITHUB_STEP_SUMMARY
312459
0 commit comments