@@ -593,6 +593,178 @@ jobs:
593593 })
594594}
595595
596+ func TestWorkflowAndJobConcurrency (t * testing.T ) {
597+ onGiteaRun (t , func (t * testing.T , u * url.URL ) {
598+ user2 := unittest .AssertExistsAndLoadBean (t , & user_model.User {ID : 2 })
599+ session := loginUser (t , user2 .Name )
600+ token := getTokenForLoggedInUser (t , session , auth_model .AccessTokenScopeWriteRepository , auth_model .AccessTokenScopeWriteUser )
601+
602+ apiRepo := createActionsTestRepo (t , token , "actions-concurrency" , false )
603+ repo := unittest .AssertExistsAndLoadBean (t , & repo_model.Repository {ID : apiRepo .ID })
604+ runner1 := newMockRunner ()
605+ runner1 .registerAsRepoRunner (t , user2 .Name , repo .Name , "mock-runner-1" , []string {"runner1" })
606+ runner2 := newMockRunner ()
607+ runner2 .registerAsRepoRunner (t , user2 .Name , repo .Name , "mock-runner-2" , []string {"runner2" })
608+ runner3 := newMockRunner ()
609+ runner3 .registerAsRepoRunner (t , user2 .Name , repo .Name , "mock-runner-3" , []string {"runner3" })
610+
611+ wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml"
612+ wf1FileContent := `name: concurrent-workflow-1
613+ on:
614+ push:
615+ paths:
616+ - '.gitea/workflows/concurrent-workflow-1.yml'
617+ concurrency:
618+ group: workflow-group-1
619+ jobs:
620+ wf1-job1:
621+ runs-on: runner1
622+ concurrency:
623+ group: job-group-1
624+ steps:
625+ - run: echo 'wf1-job1'
626+ wf1-job2:
627+ runs-on: runner2
628+ concurrency:
629+ group: job-group-2
630+ steps:
631+ - run: echo 'wf1-job2'
632+ `
633+ wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml"
634+ wf2FileContent := `name: concurrent-workflow-2
635+ on:
636+ push:
637+ paths:
638+ - '.gitea/workflows/concurrent-workflow-2.yml'
639+ concurrency:
640+ group: workflow-group-1
641+ jobs:
642+ wf2-job1:
643+ runs-on: runner1
644+ concurrency:
645+ group: job-group-1
646+ steps:
647+ - run: echo 'wf2-job2'
648+ wf2-job2:
649+ runs-on: runner2
650+ concurrency:
651+ group: job-group-2
652+ steps:
653+ - run: echo 'wf2-job2'
654+ `
655+ wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml"
656+ wf3FileContent := `name: concurrent-workflow-3
657+ on:
658+ push:
659+ paths:
660+ - '.gitea/workflows/concurrent-workflow-3.yml'
661+ concurrency:
662+ group: workflow-group-2
663+ jobs:
664+ wf3-job1:
665+ runs-on: runner1
666+ concurrency:
667+ group: job-group-1
668+ steps:
669+ - run: echo 'wf3-job1'
670+ `
671+
672+ wf4TreePath := ".gitea/workflows/concurrent-workflow-4.yml"
673+ wf4FileContent := `name: concurrent-workflow-4
674+ on:
675+ push:
676+ paths:
677+ - '.gitea/workflows/concurrent-workflow-4.yml'
678+ concurrency:
679+ group: workflow-group-2
680+ jobs:
681+ wf4-job1:
682+ runs-on: runner2
683+ concurrency:
684+ group: job-group-2
685+ cancel-in-progress: true
686+ steps:
687+ - run: echo 'wf4-job1'
688+ `
689+
690+ // push workflow 1, 2 and 3
691+ opts1 := getWorkflowCreateFileOptions (user2 , repo .DefaultBranch , fmt .Sprintf ("create %s" , wf1TreePath ), wf1FileContent )
692+ createWorkflowFile (t , token , user2 .Name , repo .Name , wf1TreePath , opts1 )
693+ opts2 := getWorkflowCreateFileOptions (user2 , repo .DefaultBranch , fmt .Sprintf ("create %s" , wf2TreePath ), wf2FileContent )
694+ createWorkflowFile (t , token , user2 .Name , repo .Name , wf2TreePath , opts2 )
695+ opts3 := getWorkflowCreateFileOptions (user2 , repo .DefaultBranch , fmt .Sprintf ("create %s" , wf3TreePath ), wf3FileContent )
696+ createWorkflowFile (t , token , user2 .Name , repo .Name , wf3TreePath , opts3 )
697+ // fetch wf1-job1 and wf1-job2
698+ w1j1Task := runner1 .fetchTask (t )
699+ w1j2Task := runner2 .fetchTask (t )
700+ // cannot fetch wf2-job1 and wf2-job2 because workflow-2 is blocked by workflow-1's concurrency group "workflow-group-1"
701+ // cannot fetch wf3-job1 because it is blocked by wf1-job1's concurrency group "job-group-1"
702+ runner1 .fetchNoTask (t )
703+ runner2 .fetchNoTask (t )
704+ _ , w1j1Job , w1Run := getTaskAndJobAndRunByTaskID (t , w1j1Task .Id )
705+ assert .Equal (t , "job-group-1" , w1j1Job .ConcurrencyGroup )
706+ assert .Equal (t , "workflow-group-1" , w1Run .ConcurrencyGroup )
707+ assert .Equal (t , "concurrent-workflow-1.yml" , w1Run .WorkflowID )
708+ _ , w1j2Job , _ := getTaskAndJobAndRunByTaskID (t , w1j2Task .Id )
709+ assert .Equal (t , "job-group-2" , w1j2Job .ConcurrencyGroup )
710+ // exec wf1-job1
711+ runner1 .execTask (t , w1j1Task , & mockTaskOutcome {
712+ result : runnerv1 .Result_RESULT_SUCCESS ,
713+ })
714+ // fetch wf3-job1
715+ w3j1Task := runner1 .fetchTask (t )
716+ // cannot fetch wf2-job1 and wf2-job2 because workflow-2 is blocked by workflow-1's concurrency group "workflow-group-1"
717+ runner1 .fetchNoTask (t )
718+ runner2 .fetchNoTask (t )
719+ _ , w3j1Job , w3Run := getTaskAndJobAndRunByTaskID (t , w3j1Task .Id )
720+ assert .Equal (t , "job-group-1" , w3j1Job .ConcurrencyGroup )
721+ assert .Equal (t , "workflow-group-2" , w3Run .ConcurrencyGroup )
722+ assert .Equal (t , "concurrent-workflow-3.yml" , w3Run .WorkflowID )
723+ // push workflow-4
724+ opts4 := getWorkflowCreateFileOptions (user2 , repo .DefaultBranch , fmt .Sprintf ("create %s" , wf4TreePath ), wf4FileContent )
725+ createWorkflowFile (t , token , user2 .Name , repo .Name , wf4TreePath , opts4 )
726+ // exec wf1-job2
727+ runner2 .execTask (t , w1j2Task , & mockTaskOutcome {
728+ result : runnerv1 .Result_RESULT_SUCCESS ,
729+ })
730+ // wf2-job2
731+ w2j2Task := runner2 .fetchTask (t )
732+ // cannot fetch wf2-job1 because it is blocked by wf3-job1's concurrency group "job-group-1"
733+ // cannot fetch wf4-job1 because it is blocked by workflow-3's concurrency group "workflow-group-2"
734+ runner1 .fetchNoTask (t )
735+ runner2 .fetchNoTask (t )
736+ _ , w2j2Job , w2Run := getTaskAndJobAndRunByTaskID (t , w2j2Task .Id )
737+ assert .Equal (t , "job-group-2" , w2j2Job .ConcurrencyGroup )
738+ assert .Equal (t , "workflow-group-1" , w2Run .ConcurrencyGroup )
739+ assert .Equal (t , "concurrent-workflow-2.yml" , w2Run .WorkflowID )
740+ // exec wf3-job1
741+ runner1 .execTask (t , w3j1Task , & mockTaskOutcome {
742+ result : runnerv1 .Result_RESULT_SUCCESS ,
743+ })
744+ // fetch wf2-job1
745+ w2j1Task := runner1 .fetchTask (t )
746+ // fetch wf4-job1
747+ w4j1Task := runner2 .fetchTask (t )
748+ // all tasks have been fetched
749+ runner1 .fetchNoTask (t )
750+ runner2 .fetchNoTask (t )
751+ _ , w2j1Job , _ := getTaskAndJobAndRunByTaskID (t , w2j1Task .Id )
752+ assert .Equal (t , "job-group-1" , w2j1Job .ConcurrencyGroup )
753+ assert .Equal (t , actions_model .StatusRunning , w2j2Job .Status )
754+ _ , w2j2Job , w2Run = getTaskAndJobAndRunByTaskID (t , w2j2Task .Id )
755+ // wf2-job2 is cancelled because wf4-job1's cancel-in-progress is true
756+ assert .Equal (t , actions_model .StatusCancelled , w2j2Job .Status )
757+ assert .Equal (t , actions_model .StatusCancelled , w2Run .Status )
758+ _ , w4j1Job , w4Run := getTaskAndJobAndRunByTaskID (t , w4j1Task .Id )
759+ assert .Equal (t , "job-group-2" , w4j1Job .ConcurrencyGroup )
760+ assert .Equal (t , "workflow-group-2" , w4Run .ConcurrencyGroup )
761+ assert .Equal (t , "concurrent-workflow-4.yml" , w4Run .WorkflowID )
762+
763+ httpContext := NewAPITestContext (t , user2 .Name , repo .Name , auth_model .AccessTokenScopeWriteRepository )
764+ doAPIDeleteRepository (httpContext )(t )
765+ })
766+ }
767+
596768func getTaskAndJobAndRunByTaskID (t * testing.T , taskID int64 ) (* actions_model.ActionTask , * actions_model.ActionRunJob , * actions_model.ActionRun ) {
597769 actionTask := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionTask {ID : taskID })
598770 actionRunJob := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRunJob {ID : actionTask .JobID })
0 commit comments