mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 19:45:25 +01:00 
			
		
		
		
	The job should always run when if is always() (#29464)
				
					
				
			Fix #27906 According to GitHub's [documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds), a job should always run when its `if` is `always()` > If you would like a job to run even if a job it is dependent on did not succeed, use the `always()` conditional expression in `jobs.<job_id>.if`. --------- Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
		
							parent
							
								
									d557fbc5a7
								
							
						
					
					
						commit
						d0fe6ea4e1
					
				| @ -7,12 +7,14 @@ import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	actions_model "code.gitea.io/gitea/models/actions" | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/modules/graceful" | ||||
| 	"code.gitea.io/gitea/modules/queue" | ||||
| 
 | ||||
| 	"github.com/nektos/act/pkg/jobparser" | ||||
| 	"xorm.io/builder" | ||||
| ) | ||||
| 
 | ||||
| @ -76,12 +78,15 @@ func checkJobsOfRun(ctx context.Context, runID int64) error { | ||||
| type jobStatusResolver struct { | ||||
| 	statuses map[int64]actions_model.Status | ||||
| 	needs    map[int64][]int64 | ||||
| 	jobMap   map[int64]*actions_model.ActionRunJob | ||||
| } | ||||
| 
 | ||||
| func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver { | ||||
| 	idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs)) | ||||
| 	jobMap := make(map[int64]*actions_model.ActionRunJob) | ||||
| 	for _, job := range jobs { | ||||
| 		idToJobs[job.JobID] = append(idToJobs[job.JobID], job) | ||||
| 		jobMap[job.ID] = job | ||||
| 	} | ||||
| 
 | ||||
| 	statuses := make(map[int64]actions_model.Status, len(jobs)) | ||||
| @ -97,6 +102,7 @@ func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver { | ||||
| 	return &jobStatusResolver{ | ||||
| 		statuses: statuses, | ||||
| 		needs:    needs, | ||||
| 		jobMap:   jobMap, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -135,7 +141,20 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status { | ||||
| 			if allSucceed { | ||||
| 				ret[id] = actions_model.StatusWaiting | ||||
| 			} else { | ||||
| 				ret[id] = actions_model.StatusSkipped | ||||
| 				// If a job's "if" condition is "always()", the job should always run even if some of its dependencies did not succeed. | ||||
| 				// See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds | ||||
| 				always := false | ||||
| 				if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 { | ||||
| 					_, wfJob := wfJobs[0].Job() | ||||
| 					expr := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(wfJob.If.Value, "${{"), "}}")) | ||||
| 					always = expr == "always()" | ||||
| 				} | ||||
| 
 | ||||
| 				if always { | ||||
| 					ret[id] = actions_model.StatusWaiting | ||||
| 				} else { | ||||
| 					ret[id] = actions_model.StatusSkipped | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -70,6 +70,62 @@ func Test_jobStatusResolver_Resolve(t *testing.T) { | ||||
| 			}, | ||||
| 			want: map[int64]actions_model.Status{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "with ${{ always() }} condition", | ||||
| 			jobs: actions_model.ActionJobList{ | ||||
| 				{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}}, | ||||
| 				{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte( | ||||
| 					` | ||||
| name: test | ||||
| on: push | ||||
| jobs: | ||||
|   job2: | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: job1 | ||||
|     if: ${{ always() }} | ||||
|     steps: | ||||
|       - run: echo "always run" | ||||
| `)}, | ||||
| 			}, | ||||
| 			want: map[int64]actions_model.Status{2: actions_model.StatusWaiting}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "with always() condition", | ||||
| 			jobs: actions_model.ActionJobList{ | ||||
| 				{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}}, | ||||
| 				{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte( | ||||
| 					` | ||||
| name: test | ||||
| on: push | ||||
| jobs: | ||||
|   job2: | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: job1 | ||||
|     if: always() | ||||
|     steps: | ||||
|       - run: echo "always run" | ||||
| `)}, | ||||
| 			}, | ||||
| 			want: map[int64]actions_model.Status{2: actions_model.StatusWaiting}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "without always() condition", | ||||
| 			jobs: actions_model.ActionJobList{ | ||||
| 				{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}}, | ||||
| 				{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte( | ||||
| 					` | ||||
| name: test | ||||
| on: push | ||||
| jobs: | ||||
|   job2: | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: job1 | ||||
|     steps: | ||||
|       - run: echo "not always run" | ||||
| `)}, | ||||
| 			}, | ||||
| 			want: map[int64]actions_model.Status{2: actions_model.StatusSkipped}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user