1
1
package integration
2
2
3
3
import (
4
+ "encoding/base64"
4
5
"fmt"
5
6
"net/http"
6
7
"net/url"
7
8
"slices"
8
9
"testing"
10
+ "time"
9
11
10
12
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
11
13
actions_model "code.gitea.io/gitea/models/actions"
@@ -14,6 +16,7 @@ import (
14
16
"code.gitea.io/gitea/models/unittest"
15
17
user_model "code.gitea.io/gitea/models/user"
16
18
api "code.gitea.io/gitea/modules/structs"
19
+ "code.gitea.io/gitea/modules/util"
17
20
18
21
"github.com/stretchr/testify/assert"
19
22
)
@@ -29,6 +32,7 @@ func TestWorkflowConcurrency_NoCancellation(t *testing.T) {
29
32
runner := newMockRunner ()
30
33
runner .registerAsRepoRunner (t , user2 .Name , repo .Name , "mock-runner" , []string {"ubuntu-latest" })
31
34
35
+ // add a variable for test
32
36
req := NewRequestWithJSON (t , "POST" ,
33
37
fmt .Sprintf ("/api/v1/repos/%s/%s/actions/variables/qwe" , user2 .Name , repo .Name ), & api.CreateVariableOption {
34
38
Value : "abc123" ,
87
91
88
92
// fetch and exec workflow1, workflow2 and workflow3 are blocked
89
93
task := runner .fetchTask (t )
90
- actionTask := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionTask {ID : task .Id })
91
- actionRunJob := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRunJob {ID : actionTask .JobID })
92
- actionRun := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {ID : actionRunJob .RunID })
93
- assert .Equal (t , "workflow-main-abc123" , actionRun .ConcurrencyGroup )
94
- assert .Equal (t , "concurrent-workflow-1.yml" , actionRun .WorkflowID )
94
+ _ , _ , run := getTaskAndJobAndRunByTaskID (t , task .Id )
95
+ assert .Equal (t , "workflow-main-abc123" , run .ConcurrencyGroup )
96
+ assert .Equal (t , "concurrent-workflow-1.yml" , run .WorkflowID )
95
97
runner .fetchNoTask (t )
96
98
runner .execTask (t , task , & mockTaskOutcome {
97
99
result : runnerv1 .Result_RESULT_SUCCESS ,
@@ -100,24 +102,20 @@ jobs:
100
102
// fetch workflow2 or workflow3
101
103
workflowNames := []string {"concurrent-workflow-2.yml" , "concurrent-workflow-3.yml" }
102
104
task = runner .fetchTask (t )
103
- actionTask = unittest .AssertExistsAndLoadBean (t , & actions_model.ActionTask {ID : task .Id })
104
- actionRunJob = unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRunJob {ID : actionTask .JobID })
105
- actionRun = unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {ID : actionRunJob .RunID })
106
- assert .Contains (t , workflowNames , actionRun .WorkflowID )
107
- workflowNames = slices .DeleteFunc (workflowNames , func (wfn string ) bool { return wfn == actionRun .WorkflowID })
108
- assert .Equal (t , "workflow-main-abc123" , actionRun .ConcurrencyGroup )
105
+ _ , _ , run = getTaskAndJobAndRunByTaskID (t , task .Id )
106
+ assert .Contains (t , workflowNames , run .WorkflowID )
107
+ workflowNames = slices .DeleteFunc (workflowNames , func (wfn string ) bool { return wfn == run .WorkflowID })
108
+ assert .Equal (t , "workflow-main-abc123" , run .ConcurrencyGroup )
109
109
runner .fetchNoTask (t )
110
110
runner .execTask (t , task , & mockTaskOutcome {
111
111
result : runnerv1 .Result_RESULT_SUCCESS ,
112
112
})
113
113
114
114
// fetch the last workflow (workflow2 or workflow3)
115
115
task = runner .fetchTask (t )
116
- actionTask = unittest .AssertExistsAndLoadBean (t , & actions_model.ActionTask {ID : task .Id })
117
- actionRunJob = unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRunJob {ID : actionTask .JobID })
118
- actionRun = unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {ID : actionRunJob .RunID })
119
- assert .Equal (t , "workflow-main-abc123" , actionRun .ConcurrencyGroup )
120
- assert .Equal (t , workflowNames [0 ], actionRun .WorkflowID )
116
+ _ , _ , run = getTaskAndJobAndRunByTaskID (t , task .Id )
117
+ assert .Equal (t , "workflow-main-abc123" , run .ConcurrencyGroup )
118
+ assert .Equal (t , workflowNames [0 ], run .WorkflowID )
121
119
runner .fetchNoTask (t )
122
120
runner .execTask (t , task , & mockTaskOutcome {
123
121
result : runnerv1 .Result_RESULT_SUCCESS ,
@@ -130,84 +128,121 @@ jobs:
130
128
131
129
func TestWorkflowConcurrency_WithCancellation (t * testing.T ) {
132
130
onGiteaRun (t , func (t * testing.T , u * url.URL ) {
131
+ // user2 is the owner of the base repo
133
132
user2 := unittest .AssertExistsAndLoadBean (t , & user_model.User {ID : 2 })
134
- session := loginUser (t , user2 .Name )
135
- token := getTokenForLoggedInUser (t , session , auth_model .AccessTokenScopeWriteRepository , auth_model .AccessTokenScopeWriteUser )
133
+ user2Session := loginUser (t , user2 .Name )
134
+ user2Token := getTokenForLoggedInUser (t , user2Session , auth_model .AccessTokenScopeWriteRepository , auth_model .AccessTokenScopeWriteUser )
135
+ // user4 is the owner of the forked repo
136
+ user4 := unittest .AssertExistsAndLoadBean (t , & user_model.User {ID : 4 })
137
+ user4Token := getTokenForLoggedInUser (t , loginUser (t , user4 .Name ), auth_model .AccessTokenScopeWriteRepository , auth_model .AccessTokenScopeWriteUser )
136
138
137
- apiRepo := createActionsTestRepo (t , token , "actions-concurrency" , false )
138
- repo := unittest .AssertExistsAndLoadBean (t , & repo_model.Repository {ID : apiRepo .ID })
139
- runner := newMockRunner ()
140
- runner .registerAsRepoRunner (t , user2 .Name , repo .Name , "mock-runner" , []string {"ubuntu-latest" })
139
+ apiBaseRepo := createActionsTestRepo (t , user2Token , "actions-concurrency" , false )
140
+ baseRepo := unittest .AssertExistsAndLoadBean (t , & repo_model.Repository {ID : apiBaseRepo .ID })
141
+ user2APICtx := NewAPITestContext (t , baseRepo .OwnerName , baseRepo .Name , auth_model .AccessTokenScopeWriteRepository )
141
142
142
- req := NewRequestWithJSON (t , "POST" ,
143
- fmt .Sprintf ("/api/v1/repos/%s/%s/actions/variables/qwe" , user2 .Name , repo .Name ), & api.CreateVariableOption {
144
- Value : "abc123" ,
145
- }).
146
- AddTokenAuth (token )
147
- MakeRequest (t , req , http .StatusNoContent )
143
+ runner := newMockRunner ()
144
+ runner .registerAsRepoRunner (t , baseRepo .OwnerName , baseRepo .Name , "mock-runner" , []string {"ubuntu-latest" })
148
145
149
- wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml"
150
- wf1FileContent := `name: concurrent-workflow-1
151
- on:
152
- push:
153
- paths:
154
- - '.gitea/workflows/concurrent-workflow-1.yml'
146
+ // init the workflow
147
+ wfTreePath := ".gitea/workflows/pull.yml"
148
+ wfFileContent := `name: Pull Request
149
+ on: pull_request
155
150
concurrency:
156
- group: workflow-main-abc123
151
+ group: pull-request-test
152
+ cancel-in-progress: ${{ !startsWith(github.ref_name, 'dev-pub/') }}
157
153
jobs:
158
154
wf1-job:
159
155
runs-on: ubuntu-latest
160
156
steps:
161
- - run: echo 'job from workflow1 '
157
+ - run: echo 'test the pull '
162
158
`
163
- wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml"
164
- wf2FileContent := `name: concurrent-workflow-2
165
- on:
166
- push:
167
- paths:
168
- - '.gitea/workflows/concurrent-workflow-2.yml'
169
- concurrency:
170
- group: workflow-${{ github.ref_name }}-${{ vars.qwe }}
171
- cancel-in-progress: ${{ github.ref_name == 'main' }}
172
- jobs:
173
- wf2-job:
174
- runs-on: ubuntu-latest
175
- steps:
176
- - run: echo 'job from workflow2'
177
- `
178
-
179
- // push workflow1
180
- opts1 := getWorkflowCreateFileOptions (user2 , repo .DefaultBranch , fmt .Sprintf ("create %s" , wf1TreePath ), wf1FileContent )
181
- createWorkflowFile (t , token , user2 .Name , repo .Name , wf1TreePath , opts1 )
182
- // fetch the task of workflow1
183
- task1 := runner .fetchTask (t )
184
- actionTask1 := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionTask {ID : task1 .Id })
185
- actionRunJob1 := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRunJob {ID : actionTask1 .JobID })
186
- actionRun1 := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {ID : actionRunJob1 .RunID })
187
- assert .Equal (t , "workflow-main-abc123" , actionRun1 .ConcurrencyGroup )
188
- assert .Equal (t , "concurrent-workflow-1.yml" , actionRun1 .WorkflowID )
189
- assert .False (t , actionRun1 .ConcurrencyCancel )
190
- assert .True (t , actionRun1 .Status .IsRunning ())
191
-
192
- // push workflow2
193
- opts2 := getWorkflowCreateFileOptions (user2 , repo .DefaultBranch , fmt .Sprintf ("create %s" , wf2TreePath ), wf2FileContent )
194
- createWorkflowFile (t , token , user2 .Name , repo .Name , wf2TreePath , opts2 )
195
- // fetch the task of workflow2
196
- task2 := runner .fetchTask (t )
197
- actionTask2 := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionTask {ID : task2 .Id })
198
- actionRunJob2 := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRunJob {ID : actionTask2 .JobID })
199
- actionRun2 := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {ID : actionRunJob2 .RunID })
200
- assert .Equal (t , "workflow-main-abc123" , actionRun2 .ConcurrencyGroup )
201
- assert .Equal (t , "concurrent-workflow-2.yml" , actionRun2 .WorkflowID )
202
- assert .True (t , actionRun2 .ConcurrencyCancel )
203
- assert .True (t , actionRun2 .Status .IsRunning ())
204
-
205
- // after pushing workflow2, the status of the run of workflow1 should be "cancelled"
206
- // fetch the last workflow (workflow2 or workflow3)
207
- actionRun1 = unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {ID : actionRunJob1 .RunID })
208
- assert .True (t , actionRun1 .Status .IsCancelled ())
209
-
210
- httpContext := NewAPITestContext (t , user2 .Name , repo .Name , auth_model .AccessTokenScopeWriteRepository )
211
- doAPIDeleteRepository (httpContext )(t )
159
+ opts1 := getWorkflowCreateFileOptions (user2 , baseRepo .DefaultBranch , fmt .Sprintf ("create %s" , wfTreePath ), wfFileContent )
160
+ createWorkflowFile (t , user2Token , baseRepo .OwnerName , baseRepo .Name , wfTreePath , opts1 )
161
+ // user2 creates a pull request
162
+ doAPICreateFile (user2APICtx , "user2-fix.txt" , & api.CreateFileOptions {
163
+ FileOptions : api.FileOptions {
164
+ NewBranchName : "bugfix/aaa" ,
165
+ Message : "create user2-fix.txt" ,
166
+ Author : api.Identity {
167
+ Name : user4 .Name ,
168
+ Email : user4 .Email ,
169
+ },
170
+ Committer : api.Identity {
171
+ Name : user4 .Name ,
172
+ Email : user4 .Email ,
173
+ },
174
+ Dates : api.CommitDateOptions {
175
+ Author : time .Now (),
176
+ Committer : time .Now (),
177
+ },
178
+ },
179
+ ContentBase64 : base64 .StdEncoding .EncodeToString ([]byte ("user2-fix" )),
180
+ })(t )
181
+ doAPICreatePullRequest (user2APICtx , baseRepo .OwnerName , baseRepo .Name , baseRepo .DefaultBranch , "bugfix/aaa" )(t )
182
+ pr1Task1 := runner .fetchTask (t )
183
+ _ , _ , pr1Run1 := getTaskAndJobAndRunByTaskID (t , pr1Task1 .Id )
184
+ assert .Equal (t , "pull-request-test" , pr1Run1 .ConcurrencyGroup )
185
+ assert .True (t , pr1Run1 .ConcurrencyCancel )
186
+ assert .True (t , pr1Run1 .Status .IsRunning ())
187
+
188
+ // user4 forks the repo
189
+ req := NewRequestWithJSON (t , "POST" , fmt .Sprintf ("/api/v1/repos/%s/%s/forks" , baseRepo .OwnerName , baseRepo .Name ),
190
+ & api.CreateForkOption {
191
+ Name : util .ToPointer ("actions-concurrency-fork" ),
192
+ }).AddTokenAuth (user4Token )
193
+ resp := MakeRequest (t , req , http .StatusAccepted )
194
+ var apiForkRepo api.Repository
195
+ DecodeJSON (t , resp , & apiForkRepo )
196
+ forkRepo := unittest .AssertExistsAndLoadBean (t , & repo_model.Repository {ID : apiForkRepo .ID })
197
+ user4APICtx := NewAPITestContext (t , user4 .Name , forkRepo .Name , auth_model .AccessTokenScopeWriteRepository )
198
+ // user4 creates a pull request
199
+ doAPICreateFile (user4APICtx , "user4-fix.txt" , & api.CreateFileOptions {
200
+ FileOptions : api.FileOptions {
201
+ NewBranchName : "bugfix/bbb" ,
202
+ Message : "create user4-fix.txt" ,
203
+ Author : api.Identity {
204
+ Name : user4 .Name ,
205
+ Email : user4 .Email ,
206
+ },
207
+ Committer : api.Identity {
208
+ Name : user4 .Name ,
209
+ Email : user4 .Email ,
210
+ },
211
+ Dates : api.CommitDateOptions {
212
+ Author : time .Now (),
213
+ Committer : time .Now (),
214
+ },
215
+ },
216
+ ContentBase64 : base64 .StdEncoding .EncodeToString ([]byte ("user4-fix" )),
217
+ })(t )
218
+ doAPICreatePullRequest (user4APICtx , baseRepo .OwnerName , baseRepo .Name , baseRepo .DefaultBranch , fmt .Sprintf ("%s:bugfix/bbb" , user4 .Name ))(t )
219
+ // cannot fetch the task because an approval is required
220
+ runner .fetchNoTask (t )
221
+ // user2 approves the run
222
+ pr2Run1 := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {RepoID : baseRepo .ID , TriggerUserID : user4 .ID })
223
+ req = NewRequestWithValues (t , "POST" ,
224
+ fmt .Sprintf ("/%s/%s/actions/runs/%d/approve" , baseRepo .OwnerName , baseRepo .Name , pr2Run1 .Index ),
225
+ map [string ]string {
226
+ "_csrf" : GetUserCSRFToken (t , user2Session ),
227
+ })
228
+ user2Session .MakeRequest (t , req , http .StatusOK )
229
+ // fetch the task and the previous task has been cancelled
230
+ runner .fetchTask (t )
231
+ pr2Run1 = unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {ID : pr2Run1 .ID })
232
+ assert .Equal (t , "pull-request-test" , pr2Run1 .ConcurrencyGroup )
233
+ assert .True (t , pr2Run1 .ConcurrencyCancel )
234
+ assert .True (t , pr2Run1 .Status .IsRunning ())
235
+ pr1Run1 = unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {ID : pr1Run1 .ID })
236
+ assert .True (t , pr1Run1 .Status .IsCancelled ())
237
+
238
+ doAPIDeleteRepository (user4APICtx )(t )
239
+ doAPIDeleteRepository (user2APICtx )(t )
212
240
})
213
241
}
242
+
243
+ func getTaskAndJobAndRunByTaskID (t * testing.T , taskID int64 ) (* actions_model.ActionTask , * actions_model.ActionRunJob , * actions_model.ActionRun ) {
244
+ actionTask := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionTask {ID : taskID })
245
+ actionRunJob := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRunJob {ID : actionTask .JobID })
246
+ actionRun := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {ID : actionRunJob .RunID })
247
+ return actionTask , actionRunJob , actionRun
248
+ }
0 commit comments