This commit is contained in:
@@ -11,19 +11,22 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
|
||||
actions_model "gitea.dev/models/actions"
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/timeutil"
|
||||
webhook_module "gitea.dev/modules/webhook"
|
||||
actions_web "gitea.dev/routers/web/repo/actions"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWorkflowConcurrency(t *testing.T) {
|
||||
@@ -96,7 +99,7 @@ jobs:
|
||||
// fetch and exec workflow1
|
||||
task := runner.fetchTask(t)
|
||||
_, _, run := getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run))
|
||||
assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID)
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{
|
||||
@@ -109,7 +112,7 @@ jobs:
|
||||
// fetch workflow2
|
||||
task = runner.fetchTask(t)
|
||||
_, _, run = getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run))
|
||||
assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID)
|
||||
|
||||
// push workflow3
|
||||
@@ -125,7 +128,7 @@ jobs:
|
||||
// fetch and exec workflow3
|
||||
task = runner.fetchTask(t)
|
||||
_, _, run = getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run))
|
||||
assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID)
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{
|
||||
@@ -201,7 +204,7 @@ jobs:
|
||||
// fetch and exec workflow1
|
||||
task := runner.fetchTask(t)
|
||||
_, _, run := getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run))
|
||||
assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID)
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{
|
||||
@@ -214,7 +217,7 @@ jobs:
|
||||
// fetch workflow2
|
||||
task = runner.fetchTask(t)
|
||||
_, _, run = getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run))
|
||||
assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID)
|
||||
|
||||
// push workflow3
|
||||
@@ -230,7 +233,7 @@ jobs:
|
||||
// fetch and exec workflow3
|
||||
task = runner.fetchTask(t)
|
||||
_, _, run = getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run))
|
||||
assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID)
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{
|
||||
@@ -318,7 +321,7 @@ jobs:
|
||||
// fetch and exec workflow1
|
||||
task := runner.fetchTask(t)
|
||||
_, _, run := getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run))
|
||||
assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID)
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{
|
||||
@@ -331,7 +334,7 @@ jobs:
|
||||
// fetch workflow2
|
||||
task = runner.fetchTask(t)
|
||||
_, _, run = getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run))
|
||||
assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID)
|
||||
|
||||
// push workflow3
|
||||
@@ -347,7 +350,7 @@ jobs:
|
||||
// fetch and exec workflow3
|
||||
task = runner.fetchTask(t)
|
||||
_, _, run = getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run))
|
||||
assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID)
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{
|
||||
@@ -412,8 +415,8 @@ jobs:
|
||||
doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "bugfix/aaa")(t)
|
||||
pr1Task1 := runner.fetchTask(t)
|
||||
_, _, pr1Run1 := getTaskAndJobAndRunByTaskID(t, pr1Task1.Id)
|
||||
assert.Equal(t, "pull-request-test", pr1Run1.ConcurrencyGroup)
|
||||
assert.True(t, pr1Run1.ConcurrencyCancel)
|
||||
assert.Equal(t, "pull-request-test", getRunConcurrencyGroup(t, pr1Run1))
|
||||
assert.True(t, getRunConcurrencyCancel(t, pr1Run1))
|
||||
assert.Equal(t, actions_model.StatusRunning, pr1Run1.Status)
|
||||
|
||||
// user4 forks the repo
|
||||
@@ -422,8 +425,7 @@ jobs:
|
||||
Name: new("actions-concurrency-fork"),
|
||||
}).AddTokenAuth(user4Token)
|
||||
resp := MakeRequest(t, req, http.StatusAccepted)
|
||||
var apiForkRepo api.Repository
|
||||
DecodeJSON(t, resp, &apiForkRepo)
|
||||
apiForkRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiForkRepo.ID})
|
||||
user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(user4APICtx)(t)
|
||||
@@ -458,8 +460,8 @@ jobs:
|
||||
// fetch the task and the previous task has been cancelled
|
||||
pr2Task1 := runner.fetchTask(t)
|
||||
_, _, pr2Run1 = getTaskAndJobAndRunByTaskID(t, pr2Task1.Id)
|
||||
assert.Equal(t, "pull-request-test", pr2Run1.ConcurrencyGroup)
|
||||
assert.True(t, pr2Run1.ConcurrencyCancel)
|
||||
assert.Equal(t, "pull-request-test", getRunConcurrencyGroup(t, pr2Run1))
|
||||
assert.True(t, getRunConcurrencyCancel(t, pr2Run1))
|
||||
assert.Equal(t, actions_model.StatusRunning, pr2Run1.Status)
|
||||
pr1Run1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: pr1Run1.ID})
|
||||
assert.Equal(t, actions_model.StatusCancelled, pr1Run1.Status)
|
||||
@@ -484,19 +486,23 @@ jobs:
|
||||
},
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte("user4-fix2")),
|
||||
})(t)
|
||||
doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":do-not-cancel/ccc")(t)
|
||||
// cannot fetch the task because cancel-in-progress is false
|
||||
pr3, _ := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":do-not-cancel/ccc")(t)
|
||||
// cannot fetch the task: approval still required (user4 has no merged PR) and cancel-in-progress is false
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, pr2Task1, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
pr2Run1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: pr2Run1.ID})
|
||||
assert.Equal(t, actions_model.StatusSuccess, pr2Run1.Status)
|
||||
// user2 approves the third PR's run (user4 still has no merged PR, approval still required)
|
||||
pr3Run1Pending := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID, Ref: fmt.Sprintf("refs/pull/%d/head", pr3.Index)})
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/approve", baseRepo.OwnerName, baseRepo.Name, pr3Run1Pending.ID))
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
// fetch the task
|
||||
pr3Task1 := runner.fetchTask(t)
|
||||
_, _, pr3Run1 := getTaskAndJobAndRunByTaskID(t, pr3Task1.Id)
|
||||
assert.Equal(t, "pull-request-test", pr3Run1.ConcurrencyGroup)
|
||||
assert.False(t, pr3Run1.ConcurrencyCancel)
|
||||
assert.Equal(t, "pull-request-test", getRunConcurrencyGroup(t, pr3Run1))
|
||||
assert.False(t, getRunConcurrencyCancel(t, pr3Run1))
|
||||
assert.Equal(t, actions_model.StatusRunning, pr3Run1.Status)
|
||||
})
|
||||
}
|
||||
@@ -643,6 +649,7 @@ jobs:
|
||||
assert.Equal(t, "job-main-v1.24.0", wf2Job2Rerun1Job.ConcurrencyGroup)
|
||||
|
||||
// rerun wf2-job2
|
||||
wf2Job2ActionJob = getLatestAttemptJobByTemplateJobID(t, wf2Run.ID, wf2Job2ActionJob.ID)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, wf2Run.ID, wf2Job2ActionJob.ID))
|
||||
_ = session.MakeRequest(t, req, http.StatusOK)
|
||||
// (rerun2) fetch and exec wf2-job2
|
||||
@@ -803,7 +810,7 @@ jobs:
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task1 := runner.fetchTask(t)
|
||||
_, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.21", run1.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.21", getRunConcurrencyGroup(t, run1))
|
||||
|
||||
// run the workflow with appVersion=v1.22 and cancel=false
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
@@ -813,7 +820,7 @@ jobs:
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task2 := runner.fetchTask(t)
|
||||
_, _, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run2.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run2))
|
||||
|
||||
// run the workflow with appVersion=v1.22 and cancel=false again
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
@@ -832,7 +839,7 @@ jobs:
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task4 := runner.fetchTask(t)
|
||||
_, _, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run4.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4))
|
||||
_, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, actions_model.StatusCancelled, run2.Status)
|
||||
})
|
||||
@@ -893,7 +900,7 @@ jobs:
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task1 := runner.fetchTask(t)
|
||||
_, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.21", run1.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.21", getRunConcurrencyGroup(t, run1))
|
||||
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"ref": "refs/heads/main",
|
||||
@@ -902,7 +909,7 @@ jobs:
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task2 := runner.fetchTask(t)
|
||||
_, _, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run2.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run2))
|
||||
|
||||
// run the workflow with appVersion=v1.22 and cancel=false again
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
@@ -927,7 +934,7 @@ jobs:
|
||||
task4 := runner.fetchTask(t)
|
||||
_, _, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id)
|
||||
assert.Equal(t, actions_model.StatusRunning, run4.Status)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run4.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4))
|
||||
_, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, actions_model.StatusCancelled, run2.Status)
|
||||
|
||||
@@ -945,7 +952,7 @@ jobs:
|
||||
|
||||
task5 := runner.fetchTask(t)
|
||||
_, _, run4_1 := getTaskAndJobAndRunByTaskID(t, task5.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run4_1.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4_1))
|
||||
assert.Equal(t, run4.ID, run4_1.ID)
|
||||
_, _, run2_1 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, actions_model.StatusCancelled, run2_1.Status)
|
||||
@@ -969,7 +976,7 @@ jobs:
|
||||
_, _, run3_2 := getTaskAndJobAndRunByTaskID(t, task6.Id)
|
||||
assert.Equal(t, run3.ID, run3_2.ID)
|
||||
assert.Equal(t, actions_model.StatusRunning, run3_2.Status)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run3.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run3))
|
||||
|
||||
run2_2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2_2.ID})
|
||||
assert.Equal(t, actions_model.StatusCancelled, run2_2.Status) // cancelled by run3
|
||||
@@ -1031,7 +1038,7 @@ jobs:
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task1 := runner.fetchTask(t)
|
||||
_, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.21", run1.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.21", getRunConcurrencyGroup(t, run1))
|
||||
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"ref": "refs/heads/main",
|
||||
@@ -1040,7 +1047,7 @@ jobs:
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task2 := runner.fetchTask(t)
|
||||
_, job2, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run2.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run2))
|
||||
|
||||
// run the workflow with appVersion=v1.22 and cancel=false again
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
@@ -1065,7 +1072,7 @@ jobs:
|
||||
task4 := runner.fetchTask(t)
|
||||
_, job4, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id)
|
||||
assert.Equal(t, actions_model.StatusRunning, run4.Status)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run4.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4))
|
||||
_, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, actions_model.StatusCancelled, run2.Status)
|
||||
|
||||
@@ -1074,15 +1081,17 @@ jobs:
|
||||
})
|
||||
|
||||
// rerun cancel true scenario
|
||||
job2 = getLatestAttemptJobByTemplateJobID(t, run2.ID, job2.ID)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run2.ID, job2.ID))
|
||||
_ = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
job4 = getLatestAttemptJobByTemplateJobID(t, run4.ID, job4.ID)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run4.ID, job4.ID))
|
||||
_ = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
task5 := runner.fetchTask(t)
|
||||
_, _, run4_1 := getTaskAndJobAndRunByTaskID(t, task5.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run4_1.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4_1))
|
||||
assert.Equal(t, run4.ID, run4_1.ID)
|
||||
_, _, run2_1 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, actions_model.StatusCancelled, run2_1.Status)
|
||||
@@ -1093,18 +1102,20 @@ jobs:
|
||||
|
||||
// rerun cancel false scenario
|
||||
|
||||
job2 = getLatestAttemptJobByTemplateJobID(t, run2.ID, job2.ID)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run2.ID, job2.ID))
|
||||
_ = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
run2_2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2.ID})
|
||||
assert.Equal(t, actions_model.StatusWaiting, run2_2.Status)
|
||||
|
||||
job3 = getLatestAttemptJobByTemplateJobID(t, run3.ID, job3.ID)
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run3.ID, job3.ID))
|
||||
_ = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
task6 := runner.fetchTask(t)
|
||||
_, _, run3 = getTaskAndJobAndRunByTaskID(t, task6.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run3.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run3))
|
||||
|
||||
run2_2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2_2.ID})
|
||||
assert.Equal(t, actions_model.StatusCancelled, run2_2.Status) // cancelled by run3
|
||||
@@ -1147,8 +1158,8 @@ jobs:
|
||||
// fetch the task triggered by push
|
||||
task1 := runner.fetchTask(t)
|
||||
_, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id)
|
||||
assert.Equal(t, "schedule-concurrency", run1.ConcurrencyGroup)
|
||||
assert.True(t, run1.ConcurrencyCancel)
|
||||
assert.Equal(t, "schedule-concurrency", getRunConcurrencyGroup(t, run1))
|
||||
assert.True(t, getRunConcurrencyCancel(t, run1))
|
||||
assert.Equal(t, string(webhook_module.HookEventPush), run1.TriggerEvent)
|
||||
assert.Equal(t, actions_model.StatusRunning, run1.Status)
|
||||
|
||||
@@ -1165,8 +1176,8 @@ jobs:
|
||||
assert.Equal(t, actions_model.StatusSuccess, run1.Status)
|
||||
task2 := runner.fetchTask(t)
|
||||
_, _, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, "schedule-concurrency", run2.ConcurrencyGroup)
|
||||
assert.False(t, run2.ConcurrencyCancel)
|
||||
assert.Equal(t, "schedule-concurrency", getRunConcurrencyGroup(t, run2))
|
||||
assert.False(t, getRunConcurrencyCancel(t, run2))
|
||||
assert.Equal(t, string(webhook_module.HookEventSchedule), run2.TriggerEvent)
|
||||
assert.Equal(t, actions_model.StatusRunning, run2.Status)
|
||||
|
||||
@@ -1177,8 +1188,8 @@ jobs:
|
||||
assert.NoError(t, actions_service.StartScheduleTasks(t.Context()))
|
||||
runner.fetchNoTask(t) // cannot fetch because task2 is not completed
|
||||
run3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID, Status: actions_model.StatusBlocked})
|
||||
assert.Equal(t, "schedule-concurrency", run3.ConcurrencyGroup)
|
||||
assert.False(t, run3.ConcurrencyCancel)
|
||||
assert.Equal(t, "schedule-concurrency", getRunConcurrencyGroup(t, run3))
|
||||
assert.False(t, getRunConcurrencyCancel(t, run3))
|
||||
assert.Equal(t, string(webhook_module.HookEventSchedule), run3.TriggerEvent)
|
||||
|
||||
// trigger the task by push
|
||||
@@ -1204,8 +1215,8 @@ jobs:
|
||||
|
||||
task4 := runner.fetchTask(t)
|
||||
_, _, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id)
|
||||
assert.Equal(t, "schedule-concurrency", run4.ConcurrencyGroup)
|
||||
assert.True(t, run4.ConcurrencyCancel)
|
||||
assert.Equal(t, "schedule-concurrency", getRunConcurrencyGroup(t, run4))
|
||||
assert.True(t, getRunConcurrencyCancel(t, run4))
|
||||
assert.Equal(t, string(webhook_module.HookEventPush), run4.TriggerEvent)
|
||||
run3 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run3.ID})
|
||||
assert.Equal(t, actions_model.StatusCancelled, run3.Status)
|
||||
@@ -1317,7 +1328,7 @@ jobs:
|
||||
w1j2Task := runner2.fetchTask(t)
|
||||
_, w1j1Job, w1Run := getTaskAndJobAndRunByTaskID(t, w1j1Task.Id)
|
||||
assert.Equal(t, "job-group-1", w1j1Job.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-1", w1Run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-1", getRunConcurrencyGroup(t, w1Run))
|
||||
assert.Equal(t, "concurrent-workflow-1.yml", w1Run.WorkflowID)
|
||||
assert.Equal(t, actions_model.StatusRunning, w1j1Job.Status)
|
||||
_, w1j2Job, _ := getTaskAndJobAndRunByTaskID(t, w1j2Task.Id)
|
||||
@@ -1358,7 +1369,7 @@ jobs:
|
||||
w3j1Task := runner1.fetchTask(t)
|
||||
_, w3j1Job, w3Run = getTaskAndJobAndRunByTaskID(t, w3j1Task.Id)
|
||||
assert.Equal(t, "job-group-1", w3j1Job.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-2", w3Run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-2", getRunConcurrencyGroup(t, w3Run))
|
||||
assert.Equal(t, "concurrent-workflow-3.yml", w3Run.WorkflowID)
|
||||
|
||||
// exec wf1-job2
|
||||
@@ -1370,7 +1381,7 @@ jobs:
|
||||
w2j2Task := runner2.fetchTask(t)
|
||||
_, w2j2Job, w2Run := getTaskAndJobAndRunByTaskID(t, w2j2Task.Id)
|
||||
assert.Equal(t, "job-group-2", w2j2Job.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-1", w2Run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-1", getRunConcurrencyGroup(t, w2Run))
|
||||
assert.Equal(t, "concurrent-workflow-2.yml", w2Run.WorkflowID)
|
||||
assert.Equal(t, actions_model.StatusRunning, w2j2Job.Status)
|
||||
|
||||
@@ -1397,7 +1408,7 @@ jobs:
|
||||
assert.Equal(t, actions_model.StatusCancelled, w2Run.Status)
|
||||
_, w4j1Job, w4Run := getTaskAndJobAndRunByTaskID(t, w4j1Task.Id)
|
||||
assert.Equal(t, "job-group-2", w4j1Job.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-2", w4Run.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-2", getRunConcurrencyGroup(t, w4Run))
|
||||
assert.Equal(t, "concurrent-workflow-4.yml", w4Run.WorkflowID)
|
||||
})
|
||||
}
|
||||
@@ -1435,8 +1446,8 @@ jobs:
|
||||
// fetch and check the first task
|
||||
task1 := runner.fetchTask(t)
|
||||
_, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id)
|
||||
assert.Equal(t, "cancel-run-group", run1.ConcurrencyGroup)
|
||||
assert.False(t, run1.ConcurrencyCancel)
|
||||
assert.Equal(t, "cancel-run-group", getRunConcurrencyGroup(t, run1))
|
||||
assert.False(t, getRunConcurrencyCancel(t, run1))
|
||||
assert.Equal(t, actions_model.StatusRunning, run1.Status)
|
||||
|
||||
// push another file to trigger the workflow again
|
||||
@@ -1473,8 +1484,8 @@ jobs:
|
||||
// fetch and check the second task
|
||||
task2 := runner.fetchTask(t)
|
||||
_, _, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, "cancel-run-group", run2.ConcurrencyGroup)
|
||||
assert.False(t, run2.ConcurrencyCancel)
|
||||
assert.Equal(t, "cancel-run-group", getRunConcurrencyGroup(t, run2))
|
||||
assert.False(t, getRunConcurrencyCancel(t, run2))
|
||||
assert.Equal(t, actions_model.StatusRunning, run2.Status)
|
||||
})
|
||||
}
|
||||
@@ -1533,7 +1544,7 @@ jobs:
|
||||
// fetch wf1-job1
|
||||
w1j1Task := runner.fetchTask(t)
|
||||
_, _, run1 := getTaskAndJobAndRunByTaskID(t, w1j1Task.Id)
|
||||
assert.Equal(t, "test-group", run1.ConcurrencyGroup)
|
||||
assert.Equal(t, "test-group", getRunConcurrencyGroup(t, run1))
|
||||
assert.Equal(t, actions_model.StatusRunning, run1.Status)
|
||||
// query wf1-job2 from db and check its status
|
||||
w1j2Job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{RunID: run1.ID, JobID: "wf1-job2"})
|
||||
@@ -1553,6 +1564,9 @@ jobs:
|
||||
// run2 is blocked because it is blocked by workflow1's concurrency group "test-group"
|
||||
assert.Equal(t, actions_model.StatusBlocked, run2.Status)
|
||||
|
||||
// complete wf1-job1
|
||||
runner.execTask(t, w1j1Task, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||
|
||||
// mock time
|
||||
fakeNow := now.Add(setting.Actions.AbandonedJobTimeout)
|
||||
timeutil.MockSet(fakeNow)
|
||||
@@ -1571,7 +1585,7 @@ jobs:
|
||||
// fetch wf2-job1 and check
|
||||
w2j1Task := runner.fetchTask(t)
|
||||
_, w2j1Job, run2 := getTaskAndJobAndRunByTaskID(t, w2j1Task.Id)
|
||||
assert.Equal(t, "test-group", run2.ConcurrencyGroup)
|
||||
assert.Equal(t, "test-group", getRunConcurrencyGroup(t, run2))
|
||||
assert.Equal(t, "wf2-job1", w2j1Job.JobID)
|
||||
assert.Equal(t, actions_model.StatusRunning, run2.Status)
|
||||
assert.Equal(t, actions_model.StatusRunning, w2j1Job.Status)
|
||||
@@ -1650,7 +1664,7 @@ jobs:
|
||||
// cannot fetch run2 because run1 is still running
|
||||
runner.fetchNoTask(t)
|
||||
run2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID, WorkflowID: "concurrent-workflow-2.yml"})
|
||||
assert.Equal(t, "test-group", run2.ConcurrencyGroup)
|
||||
assert.Equal(t, "test-group", getRunConcurrencyGroup(t, run2))
|
||||
assert.Equal(t, actions_model.StatusBlocked, run2.Status)
|
||||
|
||||
// exec run1
|
||||
@@ -1677,3 +1691,164 @@ jobs:
|
||||
assert.Equal(t, actions_model.StatusCancelled, run2.Status)
|
||||
})
|
||||
}
|
||||
|
||||
// TestCancelLegacyRunBlockedByConcurrency simulates a workflow run created before migration v331:
|
||||
// it has no ActionRunAttempt record (LatestAttemptID == 0) and was blocked by workflow-level concurrency.
|
||||
// Migration v331 drops action_run.concurrency_group / concurrency_cancel, so the run ends up "stuck" with no way for the job emitter to naturally unblock it.
|
||||
// The test verifies the user can still:
|
||||
// 1. view the stuck legacy run correctly (web view renders)
|
||||
// 2. cancel it from the UI, which transitions the run and all its jobs to Cancelled
|
||||
// 3. rerun the (now cancelled) legacy run successfully
|
||||
func TestCancelLegacyRunBlockedByConcurrency(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiRepo := createActionsTestRepo(t, token, "actions-legacy-concurrency", false)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
|
||||
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(httpContext)(t)
|
||||
|
||||
runner := newMockRunner()
|
||||
runner.registerAsRepoRunner(t, repo.OwnerName, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
// Manually insert a "legacy" run blocked by workflow-level concurrency: no ActionRunAttempt, LatestAttemptID=0.
|
||||
// Its workflow-level concurrency info would have been stored on action_run.concurrency_group pre-v331;
|
||||
// after the migration that column is gone, so we simply mark the run (and its jobs) as Blocked.
|
||||
legacyWfContent := `name: legacy-blocked
|
||||
on:
|
||||
workflow_dispatch:
|
||||
concurrency:
|
||||
group: test-group
|
||||
jobs:
|
||||
legacy-job1:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'legacy-job1'
|
||||
legacy-job2:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'legacy-job2'
|
||||
`
|
||||
payloads := mustParseSingleWorkflowPayloads(t, legacyWfContent)
|
||||
now := timeutil.TimeStamp(time.Now().Unix())
|
||||
legacyRun := &actions_model.ActionRun{
|
||||
Title: "legacy blocked run",
|
||||
RepoID: repo.ID,
|
||||
OwnerID: repo.OwnerID,
|
||||
WorkflowID: "legacy-blocked.yml",
|
||||
Index: 1,
|
||||
TriggerUserID: user2.ID,
|
||||
Ref: "refs/heads/" + repo.DefaultBranch,
|
||||
CommitSHA: "0000000000000000000000000000000000000000",
|
||||
Event: "workflow_dispatch",
|
||||
TriggerEvent: "workflow_dispatch",
|
||||
EventPayload: "{}",
|
||||
Status: actions_model.StatusBlocked,
|
||||
Created: now - 1,
|
||||
Updated: now - 1,
|
||||
}
|
||||
require.NoError(t, db.Insert(t.Context(), legacyRun))
|
||||
|
||||
legacyJob1 := &actions_model.ActionRunJob{
|
||||
RunID: legacyRun.ID,
|
||||
RepoID: repo.ID,
|
||||
OwnerID: repo.OwnerID,
|
||||
CommitSHA: legacyRun.CommitSHA,
|
||||
Name: payloads["legacy-job1"].name,
|
||||
Attempt: 1,
|
||||
WorkflowPayload: payloads["legacy-job1"].payload,
|
||||
JobID: "legacy-job1",
|
||||
Needs: payloads["legacy-job1"].needs,
|
||||
RunsOn: payloads["legacy-job1"].runsOn,
|
||||
Status: actions_model.StatusBlocked,
|
||||
RunAttemptID: 0,
|
||||
AttemptJobID: 0,
|
||||
}
|
||||
legacyJob2 := &actions_model.ActionRunJob{
|
||||
RunID: legacyRun.ID,
|
||||
RepoID: repo.ID,
|
||||
OwnerID: repo.OwnerID,
|
||||
CommitSHA: legacyRun.CommitSHA,
|
||||
Name: payloads["legacy-job2"].name,
|
||||
Attempt: 1,
|
||||
WorkflowPayload: payloads["legacy-job2"].payload,
|
||||
JobID: "legacy-job2",
|
||||
Needs: payloads["legacy-job2"].needs,
|
||||
RunsOn: payloads["legacy-job2"].runsOn,
|
||||
Status: actions_model.StatusBlocked,
|
||||
RunAttemptID: 0,
|
||||
AttemptJobID: 0,
|
||||
}
|
||||
require.NoError(t, db.Insert(t.Context(), legacyJob1, legacyJob2))
|
||||
|
||||
// 1) User visits the legacy run's web view - it renders without error.
|
||||
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, legacyRun.ID))
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
viewResp := DecodeJSON(t, resp, &actions_web.ViewResponse{})
|
||||
// Legacy run has no attempt record, so RunAttempt is 0 and Attempts is empty.
|
||||
assert.EqualValues(t, 0, viewResp.State.Run.RunAttempt)
|
||||
assert.Empty(t, viewResp.State.Run.Attempts)
|
||||
assert.Equal(t, actions_model.StatusBlocked.String(), viewResp.State.Run.Status)
|
||||
assert.False(t, viewResp.State.Run.Done)
|
||||
// Legacy workflow-level concurrency info is gone (columns dropped by v331), so GetEffectiveConcurrency returns "": the run cannot self-unblock via job_emitter.
|
||||
afterLoadRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: legacyRun.ID})
|
||||
assert.Empty(t, getRunConcurrencyGroup(t, afterLoadRun))
|
||||
// Still Blocked, not Done, but user should be able to cancel.
|
||||
assert.True(t, viewResp.State.Run.CanCancel)
|
||||
assert.False(t, viewResp.State.Run.CanRerun)
|
||||
if assert.Len(t, viewResp.State.Run.Jobs, 2) {
|
||||
assert.Equal(t, actions_model.StatusBlocked.String(), viewResp.State.Run.Jobs[0].Status)
|
||||
assert.Equal(t, actions_model.StatusBlocked.String(), viewResp.State.Run.Jobs[1].Status)
|
||||
}
|
||||
|
||||
// 2) User cancels the legacy run to clean it up.
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/cancel", user2.Name, repo.Name, legacyRun.ID))
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
// Run and all its jobs transition to Cancelled.
|
||||
cancelledRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: legacyRun.ID})
|
||||
assert.Equal(t, actions_model.StatusCancelled, cancelledRun.Status)
|
||||
cancelledJob1 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: legacyJob1.ID})
|
||||
assert.Equal(t, actions_model.StatusCancelled, cancelledJob1.Status)
|
||||
cancelledJob2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: legacyJob2.ID})
|
||||
assert.Equal(t, actions_model.StatusCancelled, cancelledJob2.Status)
|
||||
|
||||
// 3) User reruns the now-cancelled legacy run.
|
||||
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, legacyRun.ID))
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
rerunRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: legacyRun.ID})
|
||||
assert.Positive(t, rerunRun.LatestAttemptID)
|
||||
assert.EqualValues(t, 2, getRunLatestAttemptNum(t, legacyRun.ID))
|
||||
// Both jobs run successfully on the registered runner.
|
||||
for range 2 {
|
||||
task := runner.fetchTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
||||
}
|
||||
finalRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: legacyRun.ID})
|
||||
assert.Equal(t, actions_model.StatusSuccess, finalRun.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func getRunConcurrencyGroup(t *testing.T, run *actions_model.ActionRun) string {
|
||||
cg, _, err := run.GetEffectiveConcurrency(t.Context())
|
||||
assert.NoError(t, err)
|
||||
return cg
|
||||
}
|
||||
|
||||
func getRunConcurrencyCancel(t *testing.T, run *actions_model.ActionRun) bool {
|
||||
_, cc, err := run.GetEffectiveConcurrency(t.Context())
|
||||
assert.NoError(t, err)
|
||||
return cc
|
||||
}
|
||||
|
||||
func getLatestAttemptJobByTemplateJobID(t *testing.T, runID, templateJobID int64) *actions_model.ActionRunJob {
|
||||
t.Helper()
|
||||
|
||||
templateJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: templateJobID, RunID: runID})
|
||||
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: runID})
|
||||
job, err := actions_model.GetRunJobByAttemptJobID(t.Context(), run.ID, run.LatestAttemptID, templateJob.AttemptJobID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return job
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user