Skip to content

Commit 6679f53

Browse files
committed
add TestWorkflowConcurrency_NoCancellation
1 parent 401f8b1 commit 6679f53

File tree

2 files changed

+143
-2
lines changed

2 files changed

+143
-2
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package integration
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/url"
7+
"slices"
8+
"testing"
9+
10+
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
11+
actions_model "code.gitea.io/gitea/models/actions"
12+
auth_model "code.gitea.io/gitea/models/auth"
13+
repo_model "code.gitea.io/gitea/models/repo"
14+
"code.gitea.io/gitea/models/unittest"
15+
user_model "code.gitea.io/gitea/models/user"
16+
api "code.gitea.io/gitea/modules/structs"
17+
"github.com/stretchr/testify/assert"
18+
)
19+
20+
func TestWorkflowConcurrency_NoCancellation(t *testing.T) {
21+
onGiteaRun(t, func(t *testing.T, u *url.URL) {
22+
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
23+
session := loginUser(t, user2.Name)
24+
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
25+
26+
apiRepo := createActionsTestRepo(t, token, "actions-download-task-logs", false)
27+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
28+
runner := newMockRunner()
29+
runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"})
30+
31+
req := NewRequestWithJSON(t, "POST",
32+
fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/qwe", user2.Name, repo.Name), &api.CreateVariableOption{
33+
Value: "abc123",
34+
}).
35+
AddTokenAuth(token)
36+
MakeRequest(t, req, http.StatusNoContent)
37+
38+
wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml"
39+
wf1FileContent := `name: concurrent-workflow-1
40+
on:
41+
push:
42+
paths:
43+
- '.gitea/workflows/concurrent-workflow-1.yml'
44+
concurrency:
45+
group: workflow-main-abc123
46+
jobs:
47+
wf1-job:
48+
runs-on: ubuntu-latest
49+
steps:
50+
- run: echo 'job from workflow1'
51+
`
52+
wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml"
53+
wf2FileContent := `name: concurrent-workflow-2
54+
on:
55+
push:
56+
paths:
57+
- '.gitea/workflows/concurrent-workflow-2.yml'
58+
concurrency:
59+
group: workflow-${{ github.ref_name }}-${{ vars.qwe }}
60+
jobs:
61+
wf2-job:
62+
runs-on: ubuntu-latest
63+
steps:
64+
- run: echo 'job from workflow2'
65+
`
66+
wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml"
67+
wf3FileContent := `name: concurrent-workflow-3
68+
on:
69+
push:
70+
paths:
71+
- '.gitea/workflows/concurrent-workflow-3.yml'
72+
concurrency:
73+
group: workflow-main-abc${{ 123 }}
74+
jobs:
75+
wf3-job:
76+
runs-on: ubuntu-latest
77+
steps:
78+
- run: echo 'job from workflow3'
79+
`
80+
opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, fmt.Sprintf("create %s", wf1TreePath), wf1FileContent)
81+
createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1)
82+
opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, fmt.Sprintf("create %s", wf2TreePath), wf2FileContent)
83+
createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2)
84+
opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, fmt.Sprintf("create %s", wf3TreePath), wf3FileContent)
85+
createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3)
86+
87+
// fetch and exec workflow1, workflow2 and workflow3 are blocked
88+
task := runner.fetchTask(t)
89+
actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id})
90+
actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID})
91+
actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID})
92+
assert.Equal(t, "workflow-main-abc123", actionRun.ConcurrencyGroup)
93+
assert.Equal(t, "concurrent-workflow-1.yml", actionRun.WorkflowID)
94+
runner.fetchNoTask(t)
95+
runner.execTask(t, task, &mockTaskOutcome{
96+
result: runnerv1.Result_RESULT_SUCCESS,
97+
})
98+
99+
// fetch workflow2 or workflow3
100+
workflowNames := []string{"concurrent-workflow-2.yml", "concurrent-workflow-3.yml"}
101+
task = runner.fetchTask(t)
102+
actionTask = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id})
103+
actionRunJob = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID})
104+
actionRun = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID})
105+
assert.Contains(t, workflowNames, actionRun.WorkflowID)
106+
workflowNames = slices.DeleteFunc(workflowNames, func(wfn string) bool { return wfn == actionRun.WorkflowID })
107+
assert.Equal(t, "workflow-main-abc123", actionRun.ConcurrencyGroup)
108+
runner.fetchNoTask(t)
109+
runner.execTask(t, task, &mockTaskOutcome{
110+
result: runnerv1.Result_RESULT_SUCCESS,
111+
})
112+
113+
// fetch the last workflow (workflow2 or workflow3)
114+
task = runner.fetchTask(t)
115+
actionTask = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id})
116+
actionRunJob = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID})
117+
actionRun = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID})
118+
assert.Equal(t, "workflow-main-abc123", actionRun.ConcurrencyGroup)
119+
assert.Equal(t, workflowNames[0], actionRun.WorkflowID)
120+
runner.fetchNoTask(t)
121+
runner.execTask(t, task, &mockTaskOutcome{
122+
result: runnerv1.Result_RESULT_SUCCESS,
123+
})
124+
125+
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
126+
doAPIDeleteRepository(httpContext)(t)
127+
})
128+
}

tests/integration/actions_runner_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,20 @@ func (r *mockRunner) registerAsRepoRunner(t *testing.T, ownerName, repoName, run
9292
}
9393

9494
func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1.Task {
95-
fetchTimeout := 10 * time.Second
95+
task := r.tryFetchTask(t, timeout...)
96+
assert.NotNil(t, task, "failed to fetch a task")
97+
return task
98+
}
99+
100+
func (r *mockRunner) fetchNoTask(t *testing.T, timeout ...time.Duration) {
101+
task := r.tryFetchTask(t, timeout...)
102+
assert.Nil(t, task, "a task is fetched")
103+
}
104+
105+
const defaultFetchTaskTimeout = 5 * time.Second
106+
107+
func (r *mockRunner) tryFetchTask(t *testing.T, timeout ...time.Duration) *runnerv1.Task {
108+
fetchTimeout := defaultFetchTaskTimeout
96109
if len(timeout) > 0 {
97110
fetchTimeout = timeout[0]
98111
}
@@ -109,7 +122,7 @@ func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1
109122
}
110123
time.Sleep(time.Second)
111124
}
112-
assert.NotNil(t, task, "failed to fetch a task")
125+
113126
return task
114127
}
115128

0 commit comments

Comments
 (0)