mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-31 21:48:19 +00:00
11d0ed699b
Follow-up to https://github.com/go-gitea/gitea/pull/36564 (dynamic matrix) and https://github.com/go-gitea/gitea/pull/36357 (max-parallel), fixing issues found reviewing the two features together. - **A placeholder could stall its run forever.** Its payload keeps the raw matrix but loses its `needs`, so `ParseJob` re-expanded it instead of reading it back — fatal for `include: ${{ fromJson(needs.*.outputs.*) }}`. - **An `if:` reading `matrix.*` skipped the whole job**, with or without the `${{ }}`. It now reduces to the needs gate, except under `always()`/`failure()`/`cancelled()`, and each combination is decided on its own values once the matrix expands. - **Dependents could be skipped before the combinations ran**, since inserted siblings are absent from the resolver's job set. The pass now stops after an insert and defers to the re-emit it schedules. - **Expansion failures stranded the placeholder.** A retryable one is returned so the queue retries it; a malformed payload fails the job instead of requeueing forever. - **Rerun could rewind a pass-through row** into a raw placeholder keeping its old terminal status, which nothing expands. Now gated on the anchor itself. Plus: `max-parallel` distinguishes an unevaluated `${{ }}` (debug) from a non-numeric literal (warn — it silently drops the cap). Co-authored-by: Zettat123 <zettat123@gmail.com> Co-authored-by: silverwind <me@silverwind.io>
61 lines
2.0 KiB
Go
61 lines
2.0 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
actions_model "gitea.dev/models/actions"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestEvaluateJobIfDefersMatrixExpression(t *testing.T) {
|
|
// A placeholder's `if:` reading `matrix.*` can only be decided per combination once the matrix is expanded.
|
|
|
|
emptyRun := &actions_model.ActionRun{} // if the gate is removed, the emptyRun will cause an error
|
|
deferredJob := func(ifExpr string) *actions_model.ActionRunJob {
|
|
return &actions_model.ActionRunJob{
|
|
ID: 1, JobID: "build", Needs: []string{"setup"}, IsMatrixDeferred: true,
|
|
WorkflowPayload: fmt.Appendf(nil, `name: test
|
|
on: push
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
if: %s
|
|
strategy:
|
|
matrix:
|
|
value: ${{ fromJson(needs.setup.outputs.m) }}
|
|
steps:
|
|
- run: echo
|
|
`, ifExpr),
|
|
}
|
|
}
|
|
|
|
for _, tt := range []struct {
|
|
ifExpr string
|
|
// wantNeedsFailed is what the `if:` decides when the needs did not all succeed.
|
|
wantNeedsFailed bool
|
|
}{
|
|
// These hold only for a real combination, so none can be decided before the matrix expands,
|
|
// and the fallback is the needs gate: a job whose needs did not succeed is still skipped.
|
|
{ifExpr: "${{ matrix.value == 1 }}"},
|
|
{ifExpr: "matrix.value == 1"}, // an `if:` may omit the `${{ }}`
|
|
// always() asks to run whatever the needs did, so it must not fall back to their gate.
|
|
{ifExpr: "${{ always() && matrix.value == 1 }}", wantNeedsFailed: true},
|
|
} {
|
|
t.Run(tt.ifExpr, func(t *testing.T) {
|
|
got, err := evaluateJobIf(t.Context(), emptyRun, nil, deferredJob(tt.ifExpr), nil, true)
|
|
require.NoError(t, err)
|
|
assert.True(t, got, "must reach the expansion that gates each combination on its own values")
|
|
|
|
got, err = evaluateJobIf(t.Context(), emptyRun, nil, deferredJob(tt.ifExpr), nil, false)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.wantNeedsFailed, got)
|
|
})
|
|
}
|
|
}
|