mirror of
https://gitea.com/gitea/act_runner
synced 2026-11-03 10:57:22 +01:00
Without a docker daemon six `act/runner` tests failed instead of passing. A dry run still created and removed job networks, the job teardown dropped the dry-run flag along with every other context value, and the container mocks asked the daemon for the runner arch. This also speeds up the tests and CI without dropping assertions. Most of the time went to git and docker round trips, which are now pinned, batched or coalesced, and the dind and e2e steps cache or overlap more of their setup. On CI, `act/runner` went from 77s to 55s and `test-dind` from 108s to 55s. Locally, `act/runner` without docker went from 53s to 7s. Split out of https://gitea.com/gitea/runner/pulls/1348, which adds Windows CI on top. The go cache changes moved to https://gitea.com/gitea/runner/pulls/1350. Written by Claude Code. --------- Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1349 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
155 lines
6.0 KiB
Go
155 lines
6.0 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package runner
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
"gitea.com/gitea/runner/act/common/git"
|
|
"gitea.com/gitea/runner/act/common/logging/logtest"
|
|
|
|
"gitea.dev/actionslib/pkg/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
func TestCompositeActionParity(t *testing.T) {
|
|
t.Run("inherits contexts without leaking inputs", func(t *testing.T) {
|
|
logger, hook := logtest.NewLogger()
|
|
ctx := common.WithLogger(t.Context(), logger)
|
|
strategy := &model.Strategy{MaxParallelString: "3", RawMatrix: yaml.Node{Kind: yaml.MappingNode}}
|
|
parent := &RunContext{
|
|
Config: &Config{PresetGitHubContext: &model.GithubContext{}},
|
|
Matrix: map[string]any{"os": "linux"},
|
|
Run: &model.Run{JobID: "job", Workflow: &model.Workflow{Name: "workflow", Jobs: map[string]*model.Job{"job": {Strategy: strategy}}}},
|
|
JobContainer: &jobContainerMock{},
|
|
platformImage: "-self-hosted",
|
|
jobIndex: 1,
|
|
jobTotal: 4,
|
|
}
|
|
step := &stepActionLocal{
|
|
Step: &model.Step{Uses: "./action", With: map[string]string{"SHARED": "outer", "extra": "literal${{ github.job }}"}},
|
|
RunContext: parent,
|
|
action: &model.Action{Inputs: map[string]model.Input{"shared": {Default: "outer-default"}}, Runs: model.ActionRuns{Using: "composite"}},
|
|
env: map[string]string{"INPUT_SHARED": "outer"},
|
|
}
|
|
require.NoError(t, runActionImpl(step, "/action", nil)(ctx))
|
|
require.Len(t, hook.AllRecords(), 1)
|
|
assert.Equal(t, "##[warning]Unexpected input(s) 'extra', valid inputs are ['shared']", hook.LastRecord().Message)
|
|
composite, err := step.getCompositeRunContext(ctx)
|
|
require.NoError(t, err)
|
|
|
|
assert.Same(t, strategy, composite.Run.Job().Strategy)
|
|
assert.True(t, composite.IsHostEnv())
|
|
interpolated, err := composite.NewExpressionEvaluator(ctx).Interpolate(ctx,
|
|
"${{ matrix.os }}|${{ strategy.max-parallel }}|${{ strategy.job-index }}|${{ inputs.shared }}|${{ inputs.extra }}")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "linux|3|1|outer|literal${{ github.job }}", interpolated)
|
|
assert.NotContains(t, composite.Env, "INPUT_SHARED")
|
|
assert.NotContains(t, composite.Env, "INPUT_EXTRA")
|
|
|
|
nestedEnv := composite.GetEnv()
|
|
require.NoError(t, populateEnvsFromInput(ctx, &nestedEnv, &model.Action{Inputs: map[string]model.Input{"shared": {Default: "inner-default"}}}, composite))
|
|
assert.Equal(t, "inner-default", nestedEnv["INPUT_SHARED"])
|
|
})
|
|
|
|
t.Run("propagates pre failures", func(t *testing.T) {
|
|
setFetch(t, func(_ctx context.Context, _input git.NewGitCloneExecutorInput) (string, string, error) {
|
|
return "", "", assert.AnError
|
|
})
|
|
rc := &RunContext{
|
|
Config: &Config{GitHubInstance: "github.com", ActionCacheDir: t.TempDir(), PresetGitHubContext: &model.GithubContext{}},
|
|
Run: &model.Run{JobID: "job", Workflow: &model.Workflow{Jobs: map[string]*model.Job{"job": {}}}},
|
|
JobContainer: &jobContainerMock{},
|
|
}
|
|
|
|
require.ErrorIs(t, rc.compositeExecutor(&model.Action{Runs: model.ActionRuns{Using: "composite", Steps: []model.Step{{ID: "nested", Uses: "org/action@v1"}}}}).pre(t.Context()), assert.AnError)
|
|
})
|
|
}
|
|
|
|
func TestAppendUniqueMasks(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
dst []string
|
|
src []string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "appends each new mask once",
|
|
dst: []string{"a"},
|
|
src: []string{"b", "c", "b", "a"},
|
|
want: []string{"a", "b", "c"},
|
|
},
|
|
{
|
|
name: "skips masks already present",
|
|
dst: []string{"a", "b"},
|
|
src: []string{"a", "b"},
|
|
want: []string{"a", "b"},
|
|
},
|
|
{
|
|
name: "empty src leaves dst unchanged",
|
|
dst: []string{"a"},
|
|
src: nil,
|
|
want: []string{"a"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.want, appendUniqueMasks(tt.dst, tt.src))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompositeStepIfReadsJobStatusExceptInItsOwnMainSteps(t *testing.T) {
|
|
failStep := func(rc *RunContext) {
|
|
rc.StepResults["failed"] = &model.StepResult{Conclusion: model.StepStatusFailure}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
setup func(job, composite *RunContext)
|
|
nested bool
|
|
stage stepStage
|
|
want map[string]bool
|
|
}{
|
|
{"post-if reads the failed job", func(job, _ *RunContext) { failStep(job) }, false, stepStagePost, map[string]bool{"success()": false, "failure()": true, "job.status == 'failure'": true}},
|
|
{"main if ignores the failed job", func(job, _ *RunContext) { failStep(job) }, false, stepStageMain, map[string]bool{"success()": true, "failure()": false}},
|
|
{"main if reads the composite's own failure", func(_, composite *RunContext) { failStep(composite) }, false, stepStageMain, map[string]bool{"success()": false, "failure()": true}},
|
|
{"nested main if ignores the outer composite's failure", func(_, composite *RunContext) { failStep(composite) }, true, stepStageMain, map[string]bool{"success()": true}},
|
|
{"main if sees the cancelled job", func(job, _ *RunContext) { job.markCancelled() }, false, stepStageMain, map[string]bool{"cancelled()": true, "success()": false}},
|
|
{"post-if sees the cancelled job", func(job, _ *RunContext) { job.markCancelled() }, false, stepStagePost, map[string]bool{"cancelled()": true, "success()": false}},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
job := makeTestRC(t, "runs-on: ubuntu-latest")
|
|
composite := newTestCompositeRunContext(t, job)
|
|
rc := composite
|
|
if tt.nested {
|
|
rc = newTestCompositeRunContext(t, composite)
|
|
}
|
|
tt.setup(job, composite)
|
|
step := &stepRun{RunContext: rc}
|
|
for expr, want := range tt.want {
|
|
enabled, err := isStepEnabled(t.Context(), expr, step, tt.stage)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, want, enabled, expr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func newTestCompositeRunContext(t *testing.T, parent *RunContext) *RunContext {
|
|
composite, err := newCompositeRunContext(t.Context(), parent, &stepActionRemote{
|
|
Step: &model.Step{},
|
|
RunContext: parent,
|
|
action: &model.Action{},
|
|
env: map[string]string{},
|
|
}, "/action")
|
|
require.NoError(t, err)
|
|
return composite
|
|
}
|