11package integration
22
33import (
4+ "encoding/base64"
45 "fmt"
56 "net/http"
67 "net/url"
78 "slices"
89 "testing"
10+ "time"
911
1012 runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
1113 actions_model "code.gitea.io/gitea/models/actions"
@@ -14,6 +16,7 @@ import (
1416 "code.gitea.io/gitea/models/unittest"
1517 user_model "code.gitea.io/gitea/models/user"
1618 api "code.gitea.io/gitea/modules/structs"
19+ "code.gitea.io/gitea/modules/util"
1720
1821 "github.com/stretchr/testify/assert"
1922)
@@ -29,6 +32,7 @@ func TestWorkflowConcurrency_NoCancellation(t *testing.T) {
2932 runner := newMockRunner ()
3033 runner .registerAsRepoRunner (t , user2 .Name , repo .Name , "mock-runner" , []string {"ubuntu-latest" })
3134
35+ // add a variable for test
3236 req := NewRequestWithJSON (t , "POST" ,
3337 fmt .Sprintf ("/api/v1/repos/%s/%s/actions/variables/qwe" , user2 .Name , repo .Name ), & api.CreateVariableOption {
3438 Value : "abc123" ,
8791
8892 // fetch and exec workflow1, workflow2 and workflow3 are blocked
8993 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 )
9597 runner .fetchNoTask (t )
9698 runner .execTask (t , task , & mockTaskOutcome {
9799 result : runnerv1 .Result_RESULT_SUCCESS ,
@@ -100,24 +102,20 @@ jobs:
100102 // fetch workflow2 or workflow3
101103 workflowNames := []string {"concurrent-workflow-2.yml" , "concurrent-workflow-3.yml" }
102104 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 )
109109 runner .fetchNoTask (t )
110110 runner .execTask (t , task , & mockTaskOutcome {
111111 result : runnerv1 .Result_RESULT_SUCCESS ,
112112 })
113113
114114 // fetch the last workflow (workflow2 or workflow3)
115115 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 )
121119 runner .fetchNoTask (t )
122120 runner .execTask (t , task , & mockTaskOutcome {
123121 result : runnerv1 .Result_RESULT_SUCCESS ,
@@ -130,84 +128,121 @@ jobs:
130128
131129func TestWorkflowConcurrency_WithCancellation (t * testing.T ) {
132130 onGiteaRun (t , func (t * testing.T , u * url.URL ) {
131+ // user2 is the owner of the base repo
133132 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 )
136138
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 )
141142
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" })
148145
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
155150concurrency:
156- group: workflow-main-abc123
151+ group: pull-request-test
152+ cancel-in-progress: ${{ !startsWith(github.ref_name, 'dev-pub/') }}
157153jobs:
158154 wf1-job:
159155 runs-on: ubuntu-latest
160156 steps:
161- - run: echo 'job from workflow1 '
157+ - run: echo 'test the pull '
162158`
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 )
212240 })
213241}
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