mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-06-10 11:34:31 +02:00
fix: matrix-job data races + outputs, leaner offline test suite (#994)
Running the full suite under `-race` (dropping `-short`) exposed pre-existing data races in parallel matrix-job execution, fixed by not sharing mutable state across combinations: - `containerDaemonSocket()`/`validVolumes()` derive per-job values instead of mutating shared `Config` - `getWorkflowSecrets` builds a fresh map, `rc.steps()` clones each step, and go-git workdir access is serialized - every write to a shared `Job`'s result/outputs runs under a per-`Job` lock, each combo interpolating outputs from a pristine snapshot (last wins, as on GitHub) ### Test suite - capability gates (docker / network / host-tools / Linux) replace the `-short` skips, and the suite runs offline via local fixtures (the artifact flow uses an in-process loopback server, only the docker-action force-pull needs the network) - drops redundant tests, adds a regression test for https://gitea.com/gitea/runner/issues/981 and a docker-in-docker harness (`make test-dind`) --- This PR was written with the help of Claude Opus 4.7 Reviewed-on: https://gitea.com/gitea/runner/pulls/994 Reviewed-by: Nicolas <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io> Co-committed-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -281,6 +281,44 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunContextValidVolumes(t *testing.T) {
|
||||
rc := &RunContext{
|
||||
Name: "job",
|
||||
Run: &model.Run{Workflow: &model.Workflow{Name: "wf"}},
|
||||
Config: &Config{ValidVolumes: []string{"my-vol", "/host/path"}},
|
||||
}
|
||||
name := rc.jobContainerName()
|
||||
|
||||
got := rc.validVolumes()
|
||||
|
||||
// the configured volumes plus the four the runner mounts automatically
|
||||
assert.Subset(t, got, []string{"my-vol", "/host/path", "act-toolcache", name, name + "-env", "/var/run/docker.sock"})
|
||||
|
||||
// deriving the list must never mutate or grow the shared Config slice: parallel matrix
|
||||
// combinations share one *Config, and the previous in-place append was a data race.
|
||||
assert.Equal(t, []string{"my-vol", "/host/path"}, rc.Config.ValidVolumes)
|
||||
assert.Len(t, rc.validVolumes(), len(got), "repeated calls must be stable, not accumulate")
|
||||
}
|
||||
|
||||
// TestInterpolateOutputsIsPerMatrixCombo guards the matrix-output fix: combinations share one
|
||||
// *model.Job, so each must interpolate from its own pristine snapshot. Otherwise the first
|
||||
// combo's resolved value freezes the shared template and later combos can't resolve their own.
|
||||
func TestInterpolateOutputsIsPerMatrixCombo(t *testing.T) {
|
||||
job := &model.Job{Outputs: map[string]string{"o": "${{ matrix.v }}"}}
|
||||
run := &model.Run{JobID: "j", Workflow: &model.Workflow{Name: "w", Jobs: map[string]*model.Job{"j": job}}}
|
||||
r := &runnerImpl{config: &Config{}}
|
||||
ctx := context.Background()
|
||||
|
||||
rcA := r.newRunContext(ctx, run, map[string]any{"v": "a"})
|
||||
rcB := r.newRunContext(ctx, run, map[string]any{"v": "b"})
|
||||
|
||||
require.NoError(t, rcA.interpolateOutputs()(ctx))
|
||||
require.NoError(t, rcB.interpolateOutputs()(ctx))
|
||||
|
||||
// Last combo wins (matching GitHub) instead of being frozen to combo A's "a".
|
||||
require.Equal(t, "b", job.Outputs["o"])
|
||||
}
|
||||
|
||||
func TestGetGitHubContext(t *testing.T) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user