mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-03 00:02:43 +02:00
support concurrency
This commit is contained in:
1 parent
73f640fc15
commit
d6a95cebf2
18 files changed
+1666
-194
No files matched your search
@@ -0,0 +1,854 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
|
||||
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWorkflowConcurrency(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
|
||||
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(httpContext)(t)
|
||||
|
||||
runner := newMockRunner()
|
||||
runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
// add a variable for test
|
||||
req := NewRequestWithJSON(t, "POST",
|
||||
fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/myvar", user2.Name, repo.Name), &api.CreateVariableOption{
|
||||
Value: "abc123",
|
||||
}).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml"
|
||||
wf1FileContent := `name: concurrent-workflow-1
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-1.yml'
|
||||
concurrency:
|
||||
group: workflow-main-abc123-user2
|
||||
jobs:
|
||||
wf1-job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'job from workflow1'
|
||||
`
|
||||
wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml"
|
||||
wf2FileContent := `name: concurrent-workflow-2
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-2.yml'
|
||||
concurrency:
|
||||
group: workflow-${{ gitea.ref_name }}-${{ vars.myvar }}-${{ gitea.event.pusher.username }}
|
||||
jobs:
|
||||
wf2-job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'job from workflow2'
|
||||
`
|
||||
wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml"
|
||||
wf3FileContent := `name: concurrent-workflow-3
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-3.yml'
|
||||
concurrency:
|
||||
group: workflow-main-abc${{ 123 }}-${{ gitea.event.pusher.username }}
|
||||
jobs:
|
||||
wf3-job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'job from workflow3'
|
||||
`
|
||||
opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf1TreePath, wf1FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1)
|
||||
opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf2TreePath, wf2FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2)
|
||||
opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf3TreePath, wf3FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3)
|
||||
|
||||
// fetch and exec workflow1, workflow2 and workflow3 are blocked
|
||||
task := runner.fetchTask(t)
|
||||
_, _, run := getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID)
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
|
||||
// fetch workflow2 or workflow3
|
||||
workflowNames := []string{"concurrent-workflow-2.yml", "concurrent-workflow-3.yml"}
|
||||
task = runner.fetchTask(t)
|
||||
_, _, run = getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Contains(t, workflowNames, run.WorkflowID)
|
||||
workflowNames = slices.DeleteFunc(workflowNames, func(wfn string) bool { return wfn == run.WorkflowID })
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
|
||||
// fetch the last workflow (workflow2 or workflow3)
|
||||
task = runner.fetchTask(t)
|
||||
_, _, run = getTaskAndJobAndRunByTaskID(t, task.Id)
|
||||
assert.Equal(t, "workflow-main-abc123-user2", run.ConcurrencyGroup)
|
||||
assert.Equal(t, workflowNames[0], run.WorkflowID)
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPullRequestWorkflowConcurrency(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
// user2 is the owner of the base repo
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user2Session := loginUser(t, user2.Name)
|
||||
user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
// user4 is the owner of the forked repo
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
user4Token := getTokenForLoggedInUser(t, loginUser(t, user4.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-concurrency", false)
|
||||
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID})
|
||||
user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(user2APICtx)(t)
|
||||
|
||||
runner := newMockRunner()
|
||||
runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
// init the workflow
|
||||
wfTreePath := ".gitea/workflows/pull.yml"
|
||||
wfFileContent := `name: Pull Request
|
||||
on: pull_request
|
||||
concurrency:
|
||||
group: pull-request-test
|
||||
cancel-in-progress: ${{ !startsWith(gitea.head_ref, 'do-not-cancel/') }}
|
||||
jobs:
|
||||
wf1-job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'test the pull'
|
||||
`
|
||||
opts1 := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create %s"+wfTreePath, wfFileContent)
|
||||
createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts1)
|
||||
// user2 creates a pull request
|
||||
doAPICreateFile(user2APICtx, "user2-fix.txt", &api.CreateFileOptions{
|
||||
FileOptions: api.FileOptions{
|
||||
NewBranchName: "bugfix/aaa",
|
||||
Message: "create user2-fix.txt",
|
||||
Author: api.Identity{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
},
|
||||
Dates: api.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
Committer: time.Now(),
|
||||
},
|
||||
},
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte("user2-fix")),
|
||||
})(t)
|
||||
doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "bugfix/aaa")(t)
|
||||
pr1Task1 := runner.fetchTask(t)
|
||||
_, _, pr1Run1 := getTaskAndJobAndRunByTaskID(t, pr1Task1.Id)
|
||||
assert.Equal(t, "pull-request-test", pr1Run1.ConcurrencyGroup)
|
||||
assert.True(t, pr1Run1.ConcurrencyCancel)
|
||||
assert.Equal(t, actions_model.StatusRunning, pr1Run1.Status)
|
||||
|
||||
// user4 forks the repo
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseRepo.OwnerName, baseRepo.Name),
|
||||
&api.CreateForkOption{
|
||||
Name: util.ToPointer("actions-concurrency-fork"),
|
||||
}).AddTokenAuth(user4Token)
|
||||
resp := MakeRequest(t, req, http.StatusAccepted)
|
||||
var apiForkRepo api.Repository
|
||||
DecodeJSON(t, resp, &apiForkRepo)
|
||||
forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiForkRepo.ID})
|
||||
user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(user4APICtx)(t)
|
||||
|
||||
// user4 creates a pull request from branch "bugfix/bbb"
|
||||
doAPICreateFile(user4APICtx, "user4-fix.txt", &api.CreateFileOptions{
|
||||
FileOptions: api.FileOptions{
|
||||
NewBranchName: "bugfix/bbb",
|
||||
Message: "create user4-fix.txt",
|
||||
Author: api.Identity{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
},
|
||||
Dates: api.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
Committer: time.Now(),
|
||||
},
|
||||
},
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte("user4-fix")),
|
||||
})(t)
|
||||
doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":bugfix/bbb")(t)
|
||||
// cannot fetch the task because an approval is required
|
||||
runner.fetchNoTask(t)
|
||||
// user2 approves the run
|
||||
pr2Run1 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID})
|
||||
req = NewRequestWithValues(t, "POST",
|
||||
fmt.Sprintf("/%s/%s/actions/runs/%d/approve", baseRepo.OwnerName, baseRepo.Name, pr2Run1.Index),
|
||||
map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, user2Session),
|
||||
})
|
||||
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||
// fetch the task and the previous task has been cancelled
|
||||
pr2Task1 := runner.fetchTask(t)
|
||||
_, _, pr2Run1 = getTaskAndJobAndRunByTaskID(t, pr2Task1.Id)
|
||||
assert.Equal(t, "pull-request-test", pr2Run1.ConcurrencyGroup)
|
||||
assert.True(t, pr2Run1.ConcurrencyCancel)
|
||||
assert.Equal(t, actions_model.StatusRunning, pr2Run1.Status)
|
||||
pr1Run1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: pr1Run1.ID})
|
||||
assert.Equal(t, actions_model.StatusCancelled, pr1Run1.Status)
|
||||
|
||||
// user4 creates another pull request from branch "do-not-cancel/ccc"
|
||||
doAPICreateFile(user4APICtx, "user4-fix2.txt", &api.CreateFileOptions{
|
||||
FileOptions: api.FileOptions{
|
||||
NewBranchName: "do-not-cancel/ccc",
|
||||
Message: "create user4-fix2.txt",
|
||||
Author: api.Identity{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
},
|
||||
Dates: api.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
Committer: time.Now(),
|
||||
},
|
||||
},
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte("user4-fix2")),
|
||||
})(t)
|
||||
doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":do-not-cancel/ccc")(t)
|
||||
// cannot fetch the task because cancel-in-progress is false
|
||||
runner.fetchNoTask(t)
|
||||
runner.execTask(t, pr2Task1, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
pr2Run1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: pr2Run1.ID})
|
||||
assert.Equal(t, actions_model.StatusSuccess, pr2Run1.Status)
|
||||
// fetch the task
|
||||
pr3Task1 := runner.fetchTask(t)
|
||||
_, _, pr3Run1 := getTaskAndJobAndRunByTaskID(t, pr3Task1.Id)
|
||||
assert.Equal(t, "pull-request-test", pr3Run1.ConcurrencyGroup)
|
||||
assert.False(t, pr3Run1.ConcurrencyCancel)
|
||||
assert.Equal(t, actions_model.StatusRunning, pr3Run1.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestJobConcurrency(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
|
||||
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(httpContext)(t)
|
||||
|
||||
runner1 := newMockRunner()
|
||||
runner1.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner-1", []string{"runner1"}, false)
|
||||
runner2 := newMockRunner()
|
||||
runner2.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner-2", []string{"runner2"}, false)
|
||||
|
||||
// add a variable for test
|
||||
req := NewRequestWithJSON(t, "POST",
|
||||
fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/version_var", user2.Name, repo.Name), &api.CreateVariableOption{
|
||||
Value: "v1.23.0",
|
||||
}).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml"
|
||||
wf1FileContent := `name: concurrent-workflow-1
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-1.yml'
|
||||
jobs:
|
||||
wf1-job1:
|
||||
runs-on: runner1
|
||||
concurrency:
|
||||
group: job-main-${{ vars.version_var }}
|
||||
steps:
|
||||
- run: echo 'wf1-job1'
|
||||
`
|
||||
wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml"
|
||||
wf2FileContent := `name: concurrent-workflow-2
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-2.yml'
|
||||
jobs:
|
||||
wf2-job1:
|
||||
runs-on: runner2
|
||||
outputs:
|
||||
version: ${{ steps.version_step.outputs.app_version }}
|
||||
steps:
|
||||
- id: version_step
|
||||
run: echo "app_version=v1.23.0" >> "$GITHUB_OUTPUT"
|
||||
wf2-job2:
|
||||
runs-on: runner1
|
||||
needs: [wf2-job1]
|
||||
concurrency:
|
||||
group: job-main-${{ needs.wf2-job1.outputs.version }}
|
||||
steps:
|
||||
- run: echo 'wf2-job2'
|
||||
`
|
||||
wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml"
|
||||
wf3FileContent := `name: concurrent-workflow-3
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-3.yml'
|
||||
jobs:
|
||||
wf3-job1:
|
||||
runs-on: runner1
|
||||
concurrency:
|
||||
group: job-main-${{ vars.version_var }}
|
||||
cancel-in-progress: ${{ vars.version_var == 'v1.23.0' }}
|
||||
steps:
|
||||
- run: echo 'wf3-job1'
|
||||
`
|
||||
|
||||
opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1)
|
||||
opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf2TreePath, wf2FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2)
|
||||
|
||||
// fetch wf1-job1
|
||||
wf1Job1Task := runner1.fetchTask(t)
|
||||
_, wf1Job1ActionJob, _ := getTaskAndJobAndRunByTaskID(t, wf1Job1Task.Id)
|
||||
assert.Equal(t, "job-main-v1.23.0", wf1Job1ActionJob.ConcurrencyGroup)
|
||||
assert.Equal(t, actions_model.StatusRunning, wf1Job1ActionJob.Status)
|
||||
// fetch and exec wf2-job1
|
||||
wf2Job1Task := runner2.fetchTask(t)
|
||||
runner2.execTask(t, wf2Job1Task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
outputs: map[string]string{
|
||||
"version": "v1.23.0",
|
||||
},
|
||||
})
|
||||
// cannot fetch wf2-job2 because wf1-job1 is running
|
||||
runner1.fetchNoTask(t)
|
||||
// exec wf1-job1
|
||||
runner1.execTask(t, wf1Job1Task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
// fetch wf2-job2
|
||||
wf2Job2Task := runner1.fetchTask(t)
|
||||
_, wf2Job2ActionJob, _ := getTaskAndJobAndRunByTaskID(t, wf2Job2Task.Id)
|
||||
assert.Equal(t, "job-main-v1.23.0", wf2Job2ActionJob.ConcurrencyGroup)
|
||||
assert.Equal(t, actions_model.StatusRunning, wf2Job2ActionJob.Status)
|
||||
// push workflow3 to trigger wf3-job1
|
||||
opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf3TreePath, wf3FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3)
|
||||
// fetch wf3-job1
|
||||
wf3Job1Task := runner1.fetchTask(t)
|
||||
_, wf3Job1ActionJob, _ := getTaskAndJobAndRunByTaskID(t, wf3Job1Task.Id)
|
||||
assert.Equal(t, "job-main-v1.23.0", wf3Job1ActionJob.ConcurrencyGroup)
|
||||
assert.Equal(t, actions_model.StatusRunning, wf3Job1ActionJob.Status)
|
||||
// wf2-job2 has been cancelled
|
||||
_, wf2Job2ActionJob, _ = getTaskAndJobAndRunByTaskID(t, wf2Job2Task.Id)
|
||||
assert.Equal(t, actions_model.StatusCancelled, wf2Job2ActionJob.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMatrixConcurrency(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
|
||||
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(httpContext)(t)
|
||||
|
||||
linuxRunner := newMockRunner()
|
||||
linuxRunner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-linux-runner", []string{"linux-runner"}, false)
|
||||
windowsRunner := newMockRunner()
|
||||
windowsRunner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-windows-runner", []string{"windows-runner"}, false)
|
||||
darwinRunner := newMockRunner()
|
||||
darwinRunner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-darwin-runner", []string{"darwin-runner"}, false)
|
||||
|
||||
wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml"
|
||||
wf1FileContent := `name: concurrent-workflow-1
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-1.yml'
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: ${{ matrix.os }}-runner
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows, linux]
|
||||
concurrency:
|
||||
group: job-os-${{ matrix.os }}
|
||||
steps:
|
||||
- run: echo 'job1'
|
||||
job2:
|
||||
runs-on: ${{ matrix.os }}-runner
|
||||
strategy:
|
||||
matrix:
|
||||
os: [darwin, windows, linux]
|
||||
concurrency:
|
||||
group: job-os-${{ matrix.os }}
|
||||
steps:
|
||||
- run: echo 'job2'
|
||||
`
|
||||
|
||||
opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1)
|
||||
|
||||
job1WinTask := windowsRunner.fetchTask(t)
|
||||
job1LinuxTask := linuxRunner.fetchTask(t)
|
||||
windowsRunner.fetchNoTask(t)
|
||||
linuxRunner.fetchNoTask(t)
|
||||
job2DarwinTask := darwinRunner.fetchTask(t)
|
||||
_, job1WinJob, _ := getTaskAndJobAndRunByTaskID(t, job1WinTask.Id)
|
||||
assert.Equal(t, "job1 (windows)", job1WinJob.Name)
|
||||
assert.Equal(t, "job-os-windows", job1WinJob.ConcurrencyGroup)
|
||||
_, job1LinuxJob, _ := getTaskAndJobAndRunByTaskID(t, job1LinuxTask.Id)
|
||||
assert.Equal(t, "job1 (linux)", job1LinuxJob.Name)
|
||||
assert.Equal(t, "job-os-linux", job1LinuxJob.ConcurrencyGroup)
|
||||
_, job2DarwinJob, _ := getTaskAndJobAndRunByTaskID(t, job2DarwinTask.Id)
|
||||
assert.Equal(t, "job2 (darwin)", job2DarwinJob.Name)
|
||||
assert.Equal(t, "job-os-darwin", job2DarwinJob.ConcurrencyGroup)
|
||||
windowsRunner.execTask(t, job1WinTask, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
linuxRunner.execTask(t, job1LinuxTask, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
|
||||
job2WinTask := windowsRunner.fetchTask(t)
|
||||
job2LinuxTask := linuxRunner.fetchTask(t)
|
||||
_, job2WinJob, _ := getTaskAndJobAndRunByTaskID(t, job2WinTask.Id)
|
||||
assert.Equal(t, "job2 (windows)", job2WinJob.Name)
|
||||
assert.Equal(t, "job-os-windows", job2WinJob.ConcurrencyGroup)
|
||||
_, job2LinuxJob, _ := getTaskAndJobAndRunByTaskID(t, job2LinuxTask.Id)
|
||||
assert.Equal(t, "job2 (linux)", job2LinuxJob.Name)
|
||||
assert.Equal(t, "job-os-linux", job2LinuxJob.ConcurrencyGroup)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkflowDispatchConcurrency(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
|
||||
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(httpContext)(t)
|
||||
|
||||
runner := newMockRunner()
|
||||
runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
wf1TreePath := ".gitea/workflows/workflow-dispatch-concurrency.yml"
|
||||
wf1FileContent := `name: workflow-dispatch-concurrency
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
appVersion:
|
||||
description: 'APP version'
|
||||
required: true
|
||||
default: 'v1.23'
|
||||
type: choice
|
||||
options:
|
||||
- v1.21
|
||||
- v1.22
|
||||
- v1.23
|
||||
cancel:
|
||||
description: 'Cancel running workflows'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
concurrency:
|
||||
group: workflow-dispatch-${{ inputs.appVersion }}
|
||||
cancel-in-progress: ${{ inputs.cancel }}
|
||||
jobs:
|
||||
job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'workflow dispatch job'
|
||||
`
|
||||
|
||||
opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1)
|
||||
|
||||
// run the workflow with appVersion=v1.21 and cancel=false
|
||||
urlStr := fmt.Sprintf("/%s/%s/actions/run?workflow=%s", user2.Name, repo.Name, "workflow-dispatch-concurrency.yml")
|
||||
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"ref": "refs/heads/main",
|
||||
"appVersion": "v1.21",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task1 := runner.fetchTask(t)
|
||||
_, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.21", run1.ConcurrencyGroup)
|
||||
|
||||
// run the workflow with appVersion=v1.22 and cancel=false
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"ref": "refs/heads/main",
|
||||
"appVersion": "v1.22",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task2 := runner.fetchTask(t)
|
||||
_, _, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run2.ConcurrencyGroup)
|
||||
|
||||
// run the workflow with appVersion=v1.22 and cancel=false again
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"ref": "refs/heads/main",
|
||||
"appVersion": "v1.22",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
runner.fetchNoTask(t) // cannot fetch task because task2 is not completed
|
||||
|
||||
// run the workflow with appVersion=v1.22 and cancel=true
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"ref": "refs/heads/main",
|
||||
"appVersion": "v1.22",
|
||||
"cancel": "on",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
task4 := runner.fetchTask(t)
|
||||
_, _, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id)
|
||||
assert.Equal(t, "workflow-dispatch-v1.22", run4.ConcurrencyGroup)
|
||||
_, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, actions_model.StatusCancelled, run2.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScheduleConcurrency(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
|
||||
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(httpContext)(t)
|
||||
|
||||
runner := newMockRunner()
|
||||
runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
||||
|
||||
wf1TreePath := ".gitea/workflows/schedule-concurrency.yml"
|
||||
wf1FileContent := `name: schedule-concurrency
|
||||
on:
|
||||
push:
|
||||
schedule:
|
||||
- cron: '@every 1m'
|
||||
concurrency:
|
||||
group: schedule-concurrency
|
||||
cancel-in-progress: ${{ gitea.event_name == 'push' }}
|
||||
jobs:
|
||||
job:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo 'schedule workflow'
|
||||
`
|
||||
|
||||
opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1)
|
||||
|
||||
// fetch the task triggered by push
|
||||
task1 := runner.fetchTask(t)
|
||||
_, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id)
|
||||
assert.Equal(t, "schedule-concurrency", run1.ConcurrencyGroup)
|
||||
assert.True(t, run1.ConcurrencyCancel)
|
||||
assert.Equal(t, string(webhook_module.HookEventPush), run1.TriggerEvent)
|
||||
assert.Equal(t, actions_model.StatusRunning, run1.Status)
|
||||
|
||||
// trigger the task by schedule
|
||||
spec := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: repo.ID})
|
||||
spec.Next = timeutil.TimeStampNow() // manually update "Next"
|
||||
assert.NoError(t, actions_model.UpdateScheduleSpec(t.Context(), spec, "next"))
|
||||
assert.NoError(t, actions_service.StartScheduleTasks(t.Context()))
|
||||
runner.fetchNoTask(t) // cannot fetch because task1 is not completed
|
||||
runner.execTask(t, task1, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
_, _, run1 = getTaskAndJobAndRunByTaskID(t, task1.Id)
|
||||
assert.Equal(t, actions_model.StatusSuccess, run1.Status)
|
||||
task2 := runner.fetchTask(t)
|
||||
_, _, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id)
|
||||
assert.Equal(t, "schedule-concurrency", run2.ConcurrencyGroup)
|
||||
assert.False(t, run2.ConcurrencyCancel)
|
||||
assert.Equal(t, string(webhook_module.HookEventSchedule), run2.TriggerEvent)
|
||||
assert.Equal(t, actions_model.StatusRunning, run2.Status)
|
||||
|
||||
// trigger the task by schedule again
|
||||
spec = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionScheduleSpec{RepoID: repo.ID})
|
||||
spec.Next = timeutil.TimeStampNow() // manually update "Next"
|
||||
assert.NoError(t, actions_model.UpdateScheduleSpec(t.Context(), spec, "next"))
|
||||
assert.NoError(t, actions_service.StartScheduleTasks(t.Context()))
|
||||
runner.fetchNoTask(t) // cannot fetch because task2 is not completed
|
||||
run3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID, Status: actions_model.StatusBlocked})
|
||||
assert.Equal(t, "schedule-concurrency", run3.ConcurrencyGroup)
|
||||
assert.False(t, run3.ConcurrencyCancel)
|
||||
assert.Equal(t, string(webhook_module.HookEventSchedule), run3.TriggerEvent)
|
||||
|
||||
// trigger the task by push
|
||||
doAPICreateFile(httpContext, "doc.txt", &api.CreateFileOptions{
|
||||
FileOptions: api.FileOptions{
|
||||
NewBranchName: "main",
|
||||
Message: "create doc.txt",
|
||||
Author: api.Identity{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
},
|
||||
Dates: api.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
Committer: time.Now(),
|
||||
},
|
||||
},
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte("doc")),
|
||||
})(t)
|
||||
|
||||
task4 := runner.fetchTask(t)
|
||||
_, _, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id)
|
||||
assert.Equal(t, "schedule-concurrency", run4.ConcurrencyGroup)
|
||||
assert.True(t, run4.ConcurrencyCancel)
|
||||
assert.Equal(t, string(webhook_module.HookEventPush), run4.TriggerEvent)
|
||||
run3 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run3.ID})
|
||||
assert.Equal(t, actions_model.StatusCancelled, run3.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkflowAndJobConcurrency(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user2.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
|
||||
|
||||
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
|
||||
defer doAPIDeleteRepository(httpContext)(t)
|
||||
|
||||
runner1 := newMockRunner()
|
||||
runner1.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner-1", []string{"runner1"}, false)
|
||||
runner2 := newMockRunner()
|
||||
runner2.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner-2", []string{"runner2"}, false)
|
||||
|
||||
wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml"
|
||||
wf1FileContent := `name: concurrent-workflow-1
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-1.yml'
|
||||
concurrency:
|
||||
group: workflow-group-1
|
||||
jobs:
|
||||
wf1-job1:
|
||||
runs-on: runner1
|
||||
concurrency:
|
||||
group: job-group-1
|
||||
steps:
|
||||
- run: echo 'wf1-job1'
|
||||
wf1-job2:
|
||||
runs-on: runner2
|
||||
concurrency:
|
||||
group: job-group-2
|
||||
steps:
|
||||
- run: echo 'wf1-job2'
|
||||
`
|
||||
wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml"
|
||||
wf2FileContent := `name: concurrent-workflow-2
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-2.yml'
|
||||
concurrency:
|
||||
group: workflow-group-1
|
||||
jobs:
|
||||
wf2-job1:
|
||||
runs-on: runner1
|
||||
concurrency:
|
||||
group: job-group-1
|
||||
steps:
|
||||
- run: echo 'wf2-job2'
|
||||
wf2-job2:
|
||||
runs-on: runner2
|
||||
concurrency:
|
||||
group: job-group-2
|
||||
steps:
|
||||
- run: echo 'wf2-job2'
|
||||
`
|
||||
wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml"
|
||||
wf3FileContent := `name: concurrent-workflow-3
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-3.yml'
|
||||
concurrency:
|
||||
group: workflow-group-2
|
||||
jobs:
|
||||
wf3-job1:
|
||||
runs-on: runner1
|
||||
concurrency:
|
||||
group: job-group-1
|
||||
steps:
|
||||
- run: echo 'wf3-job1'
|
||||
`
|
||||
|
||||
wf4TreePath := ".gitea/workflows/concurrent-workflow-4.yml"
|
||||
wf4FileContent := `name: concurrent-workflow-4
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.gitea/workflows/concurrent-workflow-4.yml'
|
||||
concurrency:
|
||||
group: workflow-group-2
|
||||
jobs:
|
||||
wf4-job1:
|
||||
runs-on: runner2
|
||||
concurrency:
|
||||
group: job-group-2
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- run: echo 'wf4-job1'
|
||||
`
|
||||
|
||||
// push workflow 1, 2 and 3
|
||||
opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1)
|
||||
opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf2TreePath, wf2FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2)
|
||||
opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf3TreePath, wf3FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3)
|
||||
// fetch wf1-job1 and wf1-job2
|
||||
w1j1Task := runner1.fetchTask(t)
|
||||
w1j2Task := runner2.fetchTask(t)
|
||||
// cannot fetch wf2-job1 and wf2-job2 because workflow-2 is blocked by workflow-1's concurrency group "workflow-group-1"
|
||||
// cannot fetch wf3-job1 because it is blocked by wf1-job1's concurrency group "job-group-1"
|
||||
runner1.fetchNoTask(t)
|
||||
runner2.fetchNoTask(t)
|
||||
_, w1j1Job, w1Run := getTaskAndJobAndRunByTaskID(t, w1j1Task.Id)
|
||||
assert.Equal(t, "job-group-1", w1j1Job.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-1", w1Run.ConcurrencyGroup)
|
||||
assert.Equal(t, "concurrent-workflow-1.yml", w1Run.WorkflowID)
|
||||
_, w1j2Job, _ := getTaskAndJobAndRunByTaskID(t, w1j2Task.Id)
|
||||
assert.Equal(t, "job-group-2", w1j2Job.ConcurrencyGroup)
|
||||
// exec wf1-job1
|
||||
runner1.execTask(t, w1j1Task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
// fetch wf3-job1
|
||||
w3j1Task := runner1.fetchTask(t)
|
||||
// cannot fetch wf2-job1 and wf2-job2 because workflow-2 is blocked by workflow-1's concurrency group "workflow-group-1"
|
||||
runner1.fetchNoTask(t)
|
||||
runner2.fetchNoTask(t)
|
||||
_, w3j1Job, w3Run := getTaskAndJobAndRunByTaskID(t, w3j1Task.Id)
|
||||
assert.Equal(t, "job-group-1", w3j1Job.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-2", w3Run.ConcurrencyGroup)
|
||||
assert.Equal(t, "concurrent-workflow-3.yml", w3Run.WorkflowID)
|
||||
// push workflow-4
|
||||
opts4 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf4TreePath, wf4FileContent)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, wf4TreePath, opts4)
|
||||
// exec wf1-job2
|
||||
runner2.execTask(t, w1j2Task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
// wf2-job2
|
||||
w2j2Task := runner2.fetchTask(t)
|
||||
// cannot fetch wf2-job1 because it is blocked by wf3-job1's concurrency group "job-group-1"
|
||||
// cannot fetch wf4-job1 because it is blocked by workflow-3's concurrency group "workflow-group-2"
|
||||
runner1.fetchNoTask(t)
|
||||
runner2.fetchNoTask(t)
|
||||
_, w2j2Job, w2Run := getTaskAndJobAndRunByTaskID(t, w2j2Task.Id)
|
||||
assert.Equal(t, "job-group-2", w2j2Job.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-1", w2Run.ConcurrencyGroup)
|
||||
assert.Equal(t, "concurrent-workflow-2.yml", w2Run.WorkflowID)
|
||||
// exec wf3-job1
|
||||
runner1.execTask(t, w3j1Task, &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
})
|
||||
// fetch wf2-job1
|
||||
w2j1Task := runner1.fetchTask(t)
|
||||
// fetch wf4-job1
|
||||
w4j1Task := runner2.fetchTask(t)
|
||||
// all tasks have been fetched
|
||||
runner1.fetchNoTask(t)
|
||||
runner2.fetchNoTask(t)
|
||||
_, w2j1Job, _ := getTaskAndJobAndRunByTaskID(t, w2j1Task.Id)
|
||||
assert.Equal(t, "job-group-1", w2j1Job.ConcurrencyGroup)
|
||||
assert.Equal(t, actions_model.StatusRunning, w2j2Job.Status)
|
||||
_, w2j2Job, w2Run = getTaskAndJobAndRunByTaskID(t, w2j2Task.Id)
|
||||
// wf2-job2 is cancelled because wf4-job1's cancel-in-progress is true
|
||||
assert.Equal(t, actions_model.StatusCancelled, w2j2Job.Status)
|
||||
assert.Equal(t, actions_model.StatusCancelled, w2Run.Status)
|
||||
_, w4j1Job, w4Run := getTaskAndJobAndRunByTaskID(t, w4j1Task.Id)
|
||||
assert.Equal(t, "job-group-2", w4j1Job.ConcurrencyGroup)
|
||||
assert.Equal(t, "workflow-group-2", w4Run.ConcurrencyGroup)
|
||||
assert.Equal(t, "concurrent-workflow-4.yml", w4Run.WorkflowID)
|
||||
})
|
||||
}
|
||||
|
||||
func getTaskAndJobAndRunByTaskID(t *testing.T, taskID int64) (*actions_model.ActionTask, *actions_model.ActionRunJob, *actions_model.ActionRun) {
|
||||
actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: taskID})
|
||||
actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID})
|
||||
actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID})
|
||||
return actionTask, actionRunJob, actionRun
|
||||
}
|
||||
@@ -93,7 +93,20 @@ func (r *mockRunner) registerAsRepoRunner(t *testing.T, ownerName, repoName, run
|
||||
}
|
||||
|
||||
func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1.Task {
|
||||
fetchTimeout := 10 * time.Second
|
||||
task := r.tryFetchTask(t, timeout...)
|
||||
assert.NotNil(t, task, "failed to fetch a task")
|
||||
return task
|
||||
}
|
||||
|
||||
func (r *mockRunner) fetchNoTask(t *testing.T, timeout ...time.Duration) {
|
||||
task := r.tryFetchTask(t, timeout...)
|
||||
assert.Nil(t, task, "a task is fetched")
|
||||
}
|
||||
|
||||
const defaultFetchTaskTimeout = 1 * time.Second
|
||||
|
||||
func (r *mockRunner) tryFetchTask(t *testing.T, timeout ...time.Duration) *runnerv1.Task {
|
||||
fetchTimeout := defaultFetchTaskTimeout
|
||||
if len(timeout) > 0 {
|
||||
fetchTimeout = timeout[0]
|
||||
}
|
||||
@@ -108,9 +121,9 @@ func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1
|
||||
task = resp.Msg.Task
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
assert.NotNil(t, task, "failed to fetch a task")
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user