mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-01 01:48:24 +00:00
Backport https://github.com/go-gitea/gitea/pull/40106 Repository-level and instance-level runners still picked up waiting jobs of repositories with Actions disabled. https://github.com/go-gitea/gitea/pull/30331 only covered owner-level runners, by filtering them out in SQL. Pickup now checks the Actions unit of each job it is about to claim, for every runner scope, and cancels the job instead when Actions is disabled, also honoring `DISABLED_REPO_UNITS`. Disabling Actions therefore cancels its queued jobs once a matching runner polls, instead of leaving them waiting until `ABANDONED_JOB_TIMEOUT`. Co-authored-by: silverwind <me@silverwind.io>
90 lines
3.4 KiB
Go
90 lines
3.4 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"testing"
|
|
|
|
actions_model "gitea.dev/models/actions"
|
|
"gitea.dev/models/db"
|
|
"gitea.dev/models/unittest"
|
|
"gitea.dev/modules/actions/jobparser"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestTryPickTaskThrottled(t *testing.T) {
|
|
sem := taskPickLimiter()
|
|
|
|
// Saturate every assignment slot so the next attempt must be throttled.
|
|
for range cap(sem) {
|
|
sem <- struct{}{}
|
|
}
|
|
defer func() {
|
|
for range cap(sem) {
|
|
<-sem
|
|
}
|
|
}()
|
|
|
|
// No DB access happens on the throttled path, so this is safe without fixtures.
|
|
task, ok, throttled, err := TryPickTask(t.Context(), &actions_model.ActionRunner{})
|
|
require.NoError(t, err)
|
|
assert.Nil(t, task)
|
|
assert.False(t, ok)
|
|
assert.True(t, throttled)
|
|
}
|
|
|
|
// TestReleaseTaskForRunnerCleanup verifies the cleanup used by PickTask releases a claimed task through a
|
|
// fresh context. PickTask reaches this path when the request context is already canceled, and on
|
|
// PostgreSQL/MySQL a DB transaction on a canceled context fails immediately; reusing it would strand the
|
|
// claimed job in running state, so the cleanup must not use the request context.
|
|
func TestReleaseTaskForRunnerCleanup(t *testing.T) {
|
|
require.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
run := &actions_model.ActionRun{
|
|
Title: "cleanup-run", RepoID: 1, OwnerID: 2, WorkflowID: "test.yaml",
|
|
TriggerUserID: 2, Ref: "refs/heads/main",
|
|
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", Event: "push", TriggerEvent: "push",
|
|
Status: actions_model.StatusWaiting,
|
|
}
|
|
require.NoError(t, db.Insert(t.Context(), run))
|
|
job := &actions_model.ActionRunJob{
|
|
RunID: run.ID, RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA,
|
|
Name: "cleanup-job", Attempt: 1, JobID: "cleanup-job", Status: actions_model.StatusWaiting,
|
|
RunsOn: []string{"ubuntu-latest"},
|
|
WorkflowPayload: []byte("on: push\njobs:\n cleanup-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"),
|
|
}
|
|
require.NoError(t, db.Insert(t.Context(), job))
|
|
runner := &actions_model.ActionRunner{Name: "cleanup-runner", AgentLabels: []string{"ubuntu-latest"}}
|
|
runner.GenerateAndFillToken()
|
|
require.NoError(t, db.Insert(t.Context(), runner))
|
|
|
|
task, _, err := actions_model.CreateTaskForRunner(t.Context(), runner)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, task)
|
|
require.Equal(t, actions_model.StatusRunning, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: job.ID}).Status)
|
|
|
|
// the cleanup helper uses its own context, so the claimed job is returned to the waiting queue
|
|
releaseTaskForRunnerCleanup(task)
|
|
released := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: job.ID})
|
|
assert.Equal(t, actions_model.StatusWaiting, released.Status)
|
|
assert.Zero(t, released.TaskID)
|
|
unittest.AssertNotExistsBean(t, &actions_model.ActionTask{ID: task.ID})
|
|
}
|
|
|
|
func TestRunnerWorkflowPayload(t *testing.T) {
|
|
payload, err := runnerWorkflowPayload(&actions_model.ActionRunJob{
|
|
JobID: "build",
|
|
WorkflowPayload: []byte("name: ci\njobs:\n build:\n if: false\n steps:\n - run: echo\n"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
swf, parsedJob, err := jobparser.ParseRawSingleWorkflow(payload)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "ci", swf.Name)
|
|
assert.Equal(t, "always()", parsedJob.If.Value)
|
|
assert.Len(t, parsedJob.Steps, 1)
|
|
}
|