mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-27 15:25:25 +00:00
536 lines
23 KiB
Go
536 lines
23 KiB
Go
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
actions_model "gitea.dev/models/actions"
|
|
auth_model "gitea.dev/models/auth"
|
|
"gitea.dev/models/db"
|
|
repo_model "gitea.dev/models/repo"
|
|
"gitea.dev/models/unittest"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/actions/jobparser"
|
|
"gitea.dev/modules/setting"
|
|
api "gitea.dev/modules/structs"
|
|
"gitea.dev/modules/test"
|
|
"gitea.dev/modules/timeutil"
|
|
actions_web "gitea.dev/routers/web/repo/actions"
|
|
|
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestActionsRerun(t *testing.T) {
|
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
|
userAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
|
sessionAdmin := loginUser(t, userAdmin.Name)
|
|
|
|
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-rerun", 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, repo.OwnerName, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
|
|
|
wfTreePath := ".gitea/workflows/actions-rerun-workflow-1.yml"
|
|
wfFileContent := `name: actions-rerun-workflow-1
|
|
on:
|
|
push:
|
|
paths:
|
|
- '.gitea/workflows/actions-rerun-workflow-1.yml'
|
|
jobs:
|
|
job1:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- run: echo 'job1'
|
|
job2:
|
|
runs-on: ubuntu-latest
|
|
needs: [job1]
|
|
steps:
|
|
- run: echo 'job2'
|
|
`
|
|
|
|
opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create"+wfTreePath, wfFileContent)
|
|
createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, opts)
|
|
|
|
// fetch and exec job1
|
|
job1Task := runner.fetchTask(t)
|
|
assert.Equal(t, "1", job1Task.Context.GetFields()["run_attempt"].GetStringValue())
|
|
_, job1, run := getTaskAndJobAndRunByTaskID(t, job1Task.Id)
|
|
runner.execTask(t, job1Task, &mockTaskOutcome{
|
|
result: runnerv1.Result_RESULT_SUCCESS,
|
|
})
|
|
// RERUN-FAILURE: the run is not done
|
|
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.ID))
|
|
session.MakeRequest(t, req, http.StatusBadRequest)
|
|
// fetch and exec job2
|
|
job2Task := runner.fetchTask(t)
|
|
_, job2, _ := getTaskAndJobAndRunByTaskID(t, job2Task.Id)
|
|
runner.execTask(t, job2Task, &mockTaskOutcome{
|
|
result: runnerv1.Result_RESULT_SUCCESS,
|
|
})
|
|
assert.EqualValues(t, 1, getRunLatestAttemptNum(t, run.ID))
|
|
|
|
// RERUN-1: rerun the run
|
|
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.ID))
|
|
sessionAdmin.MakeRequest(t, req, http.StatusOK) // triggered by admin user
|
|
// fetch and exec job1
|
|
job1TaskR1 := runner.fetchTask(t)
|
|
assert.Equal(t, "2", job1TaskR1.Context.GetFields()["run_attempt"].GetStringValue())
|
|
_, job1R1, _ := getTaskAndJobAndRunByTaskID(t, job1TaskR1.Id)
|
|
assert.Equal(t, job1.AttemptJobID, job1R1.AttemptJobID)
|
|
runner.execTask(t, job1TaskR1, &mockTaskOutcome{
|
|
result: runnerv1.Result_RESULT_SUCCESS,
|
|
})
|
|
// fetch and exec job2
|
|
job2TaskR1 := runner.fetchTask(t)
|
|
assert.Equal(t, "2", job2TaskR1.Context.GetFields()["run_attempt"].GetStringValue())
|
|
_, job2R1, _ := getTaskAndJobAndRunByTaskID(t, job2TaskR1.Id)
|
|
assert.Equal(t, job2.AttemptJobID, job2R1.AttemptJobID)
|
|
runner.execTask(t, job2TaskR1, &mockTaskOutcome{
|
|
result: runnerv1.Result_RESULT_SUCCESS,
|
|
})
|
|
assert.EqualValues(t, 2, getRunLatestAttemptNum(t, run.ID))
|
|
|
|
// RERUN-2: rerun job1
|
|
job1 = getLatestAttemptJobByTemplateJobID(t, run.ID, job1.ID)
|
|
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.ID, job1.ID))
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
// job2 needs job1, so rerunning job1 will also rerun job2
|
|
// fetch and exec job1
|
|
job1TaskR2 := runner.fetchTask(t)
|
|
assert.Equal(t, "3", job1TaskR2.Context.GetFields()["run_attempt"].GetStringValue())
|
|
runner.execTask(t, job1TaskR2, &mockTaskOutcome{
|
|
result: runnerv1.Result_RESULT_SUCCESS,
|
|
})
|
|
// fetch and exec job2
|
|
job2TaskR2 := runner.fetchTask(t)
|
|
assert.Equal(t, "3", job2TaskR2.Context.GetFields()["run_attempt"].GetStringValue())
|
|
runner.execTask(t, job2TaskR2, &mockTaskOutcome{
|
|
result: runnerv1.Result_RESULT_SUCCESS,
|
|
})
|
|
assert.EqualValues(t, 3, getRunLatestAttemptNum(t, run.ID))
|
|
|
|
// RERUN-3: rerun job2
|
|
job2 = getLatestAttemptJobByTemplateJobID(t, run.ID, job2.ID)
|
|
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.ID, job2.ID))
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
// only job2 will rerun
|
|
// fetch and exec job2
|
|
job2TaskR3 := runner.fetchTask(t)
|
|
assert.Equal(t, "4", job2TaskR3.Context.GetFields()["run_attempt"].GetStringValue())
|
|
runner.execTask(t, job2TaskR3, &mockTaskOutcome{
|
|
result: runnerv1.Result_RESULT_SUCCESS,
|
|
})
|
|
runner.fetchNoTask(t)
|
|
assert.EqualValues(t, 4, getRunLatestAttemptNum(t, run.ID))
|
|
|
|
runLatestAttempt := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run.ID})
|
|
job2LatestAttempt := getLatestAttemptJobByTemplateJobID(t, run.ID, job2.ID)
|
|
assert.Equal(t, runLatestAttempt.LatestAttemptID, job2LatestAttempt.RunAttemptID)
|
|
|
|
t.Run("AttemptAPI", func(t *testing.T) {
|
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d/attempts/2", user2.Name, repo.Name, run.ID)).
|
|
AddTokenAuth(token)
|
|
attemptResp := MakeRequest(t, req, http.StatusOK)
|
|
apiAttempt := DecodeJSON(t, attemptResp, &api.ActionWorkflowRun{})
|
|
assert.Equal(t, run.ID, apiAttempt.ID)
|
|
assert.EqualValues(t, 2, apiAttempt.RunAttempt)
|
|
assert.Equal(t, "completed", apiAttempt.Status)
|
|
assert.Equal(t, "success", apiAttempt.Conclusion)
|
|
assert.NotNil(t, apiAttempt.PreviousAttemptURL)
|
|
assert.True(t, strings.HasSuffix(*apiAttempt.PreviousAttemptURL, fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d/attempts/1", user2.Name, repo.Name, run.ID)))
|
|
assert.Equal(t, user2.Name, apiAttempt.Actor.UserName)
|
|
assert.Equal(t, userAdmin.Name, apiAttempt.TriggerActor.UserName)
|
|
|
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d/attempts/2/jobs", user2.Name, repo.Name, run.ID)).
|
|
AddTokenAuth(token)
|
|
attemptJobsResp := MakeRequest(t, req, http.StatusOK)
|
|
apiAttemptJobs := DecodeJSON(t, attemptJobsResp, &api.ActionWorkflowJobsResponse{})
|
|
assert.Len(t, apiAttemptJobs.Entries, 2)
|
|
assert.ElementsMatch(t, []int64{job1R1.ID, job2R1.ID}, []int64{apiAttemptJobs.Entries[0].ID, apiAttemptJobs.Entries[1].ID})
|
|
})
|
|
|
|
t.Run("MaxRerunAttempts", func(t *testing.T) {
|
|
// The run has 4 attempts after the previous reruns. Lower the cap to 4 to hit the limit.
|
|
defer test.MockVariableValue(&setting.Actions.MaxRerunAttempts, int64(4))()
|
|
|
|
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.ID))
|
|
resp := session.MakeRequest(t, req, http.StatusBadRequest)
|
|
assert.Contains(t, resp.Body.String(), "workflow run has reached the maximum")
|
|
assert.EqualValues(t, 4, getRunLatestAttemptNum(t, run.ID))
|
|
|
|
// Raising the cap lets rerun proceed again.
|
|
defer test.MockVariableValue(&setting.Actions.MaxRerunAttempts, int64(5))()
|
|
|
|
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.ID))
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
// fetch and exec job1
|
|
job1TaskR4 := runner.fetchTask(t)
|
|
assert.Equal(t, "5", job1TaskR4.Context.GetFields()["run_attempt"].GetStringValue())
|
|
runner.execTask(t, job1TaskR4, &mockTaskOutcome{
|
|
result: runnerv1.Result_RESULT_SUCCESS,
|
|
})
|
|
job2TaskR4 := runner.fetchTask(t)
|
|
assert.Equal(t, "5", job2TaskR4.Context.GetFields()["run_attempt"].GetStringValue())
|
|
runner.execTask(t, job2TaskR4, &mockTaskOutcome{
|
|
result: runnerv1.Result_RESULT_SUCCESS,
|
|
})
|
|
assert.EqualValues(t, 5, getRunLatestAttemptNum(t, run.ID))
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestActionsRerunLegacyNoAttemptRun(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-rerun-legacy", 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, repo.OwnerName, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
|
|
|
|
wfTreePath := ".gitea/workflows/actions-rerun-legacy.yml"
|
|
wfFileContent := `name: actions-rerun-legacy
|
|
on:
|
|
workflow_dispatch:
|
|
jobs:
|
|
job1:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- run: echo 'job1'
|
|
job2:
|
|
runs-on: ubuntu-latest
|
|
needs: [job1]
|
|
steps:
|
|
- run: echo 'job2'
|
|
`
|
|
|
|
opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wfTreePath, wfFileContent)
|
|
fileResp := createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, opts)
|
|
require.NotNil(t, fileResp)
|
|
|
|
// Start preparing legacy data
|
|
|
|
payloads := mustParseSingleWorkflowPayloads(t, wfFileContent)
|
|
now := timeutil.TimeStamp(time.Now().Unix())
|
|
started := now - 20
|
|
stopped := now - 10
|
|
|
|
legacyRun := &actions_model.ActionRun{
|
|
Title: "legacy rerun test",
|
|
RepoID: repo.ID,
|
|
OwnerID: repo.OwnerID,
|
|
WorkflowID: "actions-rerun-legacy.yml",
|
|
Index: 1,
|
|
TriggerUserID: user2.ID,
|
|
Ref: "refs/heads/" + repo.DefaultBranch,
|
|
CommitSHA: fileResp.Commit.SHA,
|
|
Event: "workflow_dispatch",
|
|
TriggerEvent: "workflow_dispatch",
|
|
EventPayload: "{}",
|
|
Status: actions_model.StatusSuccess,
|
|
Started: started,
|
|
Stopped: stopped,
|
|
Created: started - 5,
|
|
Updated: stopped,
|
|
}
|
|
require.NoError(t, db.Insert(t.Context(), legacyRun))
|
|
// xorm does not update "created"-tagged fields via ORM methods; use raw SQL to backfill historical timestamps.
|
|
_, err := db.GetEngine(t.Context()).Exec("UPDATE action_run SET created=?, updated=? WHERE id=?", int64(started-5), int64(stopped), legacyRun.ID)
|
|
require.NoError(t, err)
|
|
legacyRun.Created = started - 5
|
|
legacyRun.Updated = stopped
|
|
|
|
legacyJob1 := &actions_model.ActionRunJob{
|
|
RunID: legacyRun.ID,
|
|
RepoID: repo.ID,
|
|
OwnerID: repo.OwnerID,
|
|
CommitSHA: legacyRun.CommitSHA,
|
|
Name: payloads["job1"].name,
|
|
Attempt: 1,
|
|
WorkflowPayload: payloads["job1"].payload,
|
|
JobID: "job1",
|
|
Needs: payloads["job1"].needs,
|
|
RunsOn: payloads["job1"].runsOn,
|
|
Status: actions_model.StatusSuccess,
|
|
RunAttemptID: 0,
|
|
AttemptJobID: 0,
|
|
Started: started,
|
|
Stopped: stopped,
|
|
IsForkPullRequest: false,
|
|
}
|
|
legacyJob2 := &actions_model.ActionRunJob{
|
|
RunID: legacyRun.ID,
|
|
RepoID: repo.ID,
|
|
OwnerID: repo.OwnerID,
|
|
CommitSHA: legacyRun.CommitSHA,
|
|
Name: payloads["job2"].name,
|
|
Attempt: 1,
|
|
WorkflowPayload: payloads["job2"].payload,
|
|
JobID: "job2",
|
|
Needs: payloads["job2"].needs,
|
|
RunsOn: payloads["job2"].runsOn,
|
|
Status: actions_model.StatusSuccess,
|
|
RunAttemptID: 0,
|
|
AttemptJobID: 0,
|
|
Started: started,
|
|
Stopped: stopped,
|
|
IsForkPullRequest: false,
|
|
}
|
|
require.NoError(t, db.Insert(t.Context(), legacyJob1, legacyJob2))
|
|
|
|
legacyTask1 := &actions_model.ActionTask{
|
|
JobID: legacyJob1.ID,
|
|
Attempt: 1,
|
|
Status: actions_model.StatusSuccess,
|
|
Started: started,
|
|
Stopped: stopped,
|
|
RepoID: repo.ID,
|
|
OwnerID: repo.OwnerID,
|
|
CommitSHA: legacyRun.CommitSHA,
|
|
IsForkPullRequest: false,
|
|
}
|
|
legacyTask1.GenerateAndFillToken()
|
|
legacyTask2 := &actions_model.ActionTask{
|
|
JobID: legacyJob2.ID,
|
|
Attempt: 1,
|
|
Status: actions_model.StatusSuccess,
|
|
Started: started,
|
|
Stopped: stopped,
|
|
RepoID: repo.ID,
|
|
OwnerID: repo.OwnerID,
|
|
CommitSHA: legacyRun.CommitSHA,
|
|
IsForkPullRequest: false,
|
|
}
|
|
legacyTask2.GenerateAndFillToken()
|
|
require.NoError(t, db.Insert(t.Context(), legacyTask1, legacyTask2))
|
|
|
|
legacyJob1.TaskID = legacyTask1.ID
|
|
legacyJob2.TaskID = legacyTask2.ID
|
|
_, err = db.GetEngine(t.Context()).ID(legacyJob1.ID).Cols("task_id").Update(legacyJob1)
|
|
require.NoError(t, err)
|
|
_, err = db.GetEngine(t.Context()).ID(legacyJob2.ID).Cols("task_id").Update(legacyJob2)
|
|
require.NoError(t, err)
|
|
|
|
legacyArtifact := &actions_model.ActionArtifact{
|
|
RunID: legacyRun.ID,
|
|
RunAttemptID: 0,
|
|
RepoID: repo.ID,
|
|
OwnerID: repo.OwnerID,
|
|
CommitSHA: legacyRun.CommitSHA,
|
|
StoragePath: "artifacts/legacy-artifact.zip",
|
|
FileSize: 123,
|
|
FileCompressedSize: 123,
|
|
ContentEncodingOrType: actions_model.ContentTypeZip,
|
|
ArtifactPath: "legacy-artifact.zip",
|
|
ArtifactName: "legacy-artifact",
|
|
Status: actions_model.ArtifactStatusUploadConfirmed,
|
|
ExpiredUnix: now + timeutil.Day,
|
|
}
|
|
require.NoError(t, db.Insert(t.Context(), legacyArtifact))
|
|
|
|
// Done preparing legacy data
|
|
|
|
// assert the web view for the legacy run before rerun
|
|
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, legacyRun.ID))
|
|
legacyResp := session.MakeRequest(t, req, http.StatusOK)
|
|
legacyView := DecodeJSON(t, legacyResp, &actions_web.ViewResponse{})
|
|
// legacy run has no attempt records, so RunAttempt is 0 and Attempts list is empty
|
|
assert.EqualValues(t, 0, legacyView.State.Run.RunAttempt)
|
|
assert.Empty(t, legacyView.State.Run.Attempts)
|
|
assert.Equal(t, "success", legacyView.State.Run.Status)
|
|
assert.True(t, legacyView.State.Run.Done)
|
|
// isLatestAttempt=true, done=true: can rerun but not cancel
|
|
assert.False(t, legacyView.State.Run.CanCancel)
|
|
assert.False(t, legacyView.State.Run.CanApprove)
|
|
assert.True(t, legacyView.State.Run.CanRerun)
|
|
assert.False(t, legacyView.State.Run.CanRerunFailed) // all jobs succeeded
|
|
assert.True(t, legacyView.State.Run.CanDeleteArtifact)
|
|
if assert.Len(t, legacyView.State.Run.Jobs, 2) {
|
|
assert.Equal(t, legacyJob1.ID, legacyView.State.Run.Jobs[0].ID)
|
|
assert.Equal(t, legacyJob2.ID, legacyView.State.Run.Jobs[1].ID)
|
|
}
|
|
if assert.Len(t, legacyView.Artifacts, 1) {
|
|
assert.Equal(t, legacyArtifact.ArtifactName, legacyView.Artifacts[0].Name)
|
|
assert.Equal(t, "completed", legacyView.Artifacts[0].Status)
|
|
}
|
|
|
|
// rerun the legacy run
|
|
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, legacyRun.ID))
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
runAfterRerun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: legacyRun.ID})
|
|
assert.EqualValues(t, 2, getRunLatestAttemptNum(t, legacyRun.ID))
|
|
jobsAfterRerun, err := actions_model.GetRunJobsByRunAndAttemptID(t.Context(), legacyRun.ID, runAfterRerun.LatestAttemptID)
|
|
require.NoError(t, err)
|
|
require.Len(t, jobsAfterRerun, 2)
|
|
rerunJobsByJobID := map[string]*actions_model.ActionRunJob{}
|
|
for _, job := range jobsAfterRerun {
|
|
rerunJobsByJobID[job.JobID] = job
|
|
}
|
|
require.Contains(t, rerunJobsByJobID, "job1")
|
|
require.Contains(t, rerunJobsByJobID, "job2")
|
|
assert.Equal(t, actions_model.StatusWaiting, rerunJobsByJobID["job1"].Status)
|
|
assert.Equal(t, actions_model.StatusBlocked, rerunJobsByJobID["job2"].Status)
|
|
|
|
// fetch job1 rerun task
|
|
job1TaskR1 := runner.fetchTask(t)
|
|
assert.Equal(t, "2", job1TaskR1.Context.GetFields()["run_attempt"].GetStringValue())
|
|
rerunJob1Task, rerunJob1, rerunRun := getTaskAndJobAndRunByTaskID(t, job1TaskR1.Id)
|
|
assert.Equal(t, legacyRun.ID, rerunRun.ID)
|
|
assert.Equal(t, rerunJob1.RunAttemptID, rerunRun.LatestAttemptID)
|
|
runner.execTask(t, job1TaskR1, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
|
|
|
// fetch job2 rerun task
|
|
job2TaskR1 := runner.fetchTask(t)
|
|
assert.Equal(t, "2", job2TaskR1.Context.GetFields()["run_attempt"].GetStringValue())
|
|
rerunJob2Task, rerunJob2, _ := getTaskAndJobAndRunByTaskID(t, job2TaskR1.Id)
|
|
runner.execTask(t, job2TaskR1, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
|
|
runner.fetchNoTask(t)
|
|
|
|
// query the 2 attempts
|
|
runAfterRerun = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: legacyRun.ID})
|
|
attempt1, err := actions_model.GetRunAttemptByRunIDAndAttemptNum(t.Context(), legacyRun.ID, 1)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, legacyRun.Created, attempt1.Created)
|
|
assert.Equal(t, legacyRun.Started, attempt1.Started)
|
|
assert.Equal(t, legacyRun.Stopped, attempt1.Stopped)
|
|
attempt2, err := actions_model.GetRunAttemptByRunIDAndAttemptNum(t.Context(), legacyRun.ID, 2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, attempt2.ID, runAfterRerun.LatestAttemptID)
|
|
assert.Equal(t, runAfterRerun.Created, attempt1.Created)
|
|
assert.Equal(t, runAfterRerun.Started, attempt2.Started)
|
|
assert.Equal(t, runAfterRerun.Stopped, attempt2.Stopped)
|
|
|
|
// assert legacy jobs
|
|
legacyJob1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: legacyJob1.ID})
|
|
legacyJob2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: legacyJob2.ID})
|
|
assert.Equal(t, attempt1.ID, legacyJob1.RunAttemptID)
|
|
assert.Equal(t, attempt1.ID, legacyJob2.RunAttemptID)
|
|
assert.EqualValues(t, 1, legacyJob1.Attempt)
|
|
assert.EqualValues(t, 1, legacyJob2.Attempt)
|
|
assert.EqualValues(t, 1, legacyJob1.AttemptJobID)
|
|
assert.EqualValues(t, 2, legacyJob2.AttemptJobID)
|
|
legacyTask1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: legacyTask1.ID})
|
|
legacyTask2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: legacyTask2.ID})
|
|
assert.EqualValues(t, 1, legacyTask1.Attempt)
|
|
assert.EqualValues(t, 1, legacyTask2.Attempt)
|
|
|
|
// assert legacy artifacts
|
|
legacyArtifact = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionArtifact{ID: legacyArtifact.ID})
|
|
assert.Equal(t, attempt1.ID, legacyArtifact.RunAttemptID)
|
|
|
|
// assert jobs of the latest rerun
|
|
rerunJob1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: rerunJob1.ID})
|
|
rerunJob2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: rerunJob2.ID})
|
|
assert.Equal(t, attempt2.ID, rerunJob1.RunAttemptID)
|
|
assert.Equal(t, attempt2.ID, rerunJob2.RunAttemptID)
|
|
assert.Equal(t, legacyJob1.AttemptJobID, rerunJob1.AttemptJobID)
|
|
assert.Equal(t, legacyJob2.AttemptJobID, rerunJob2.AttemptJobID)
|
|
assert.EqualValues(t, 2, rerunJob1Task.Attempt)
|
|
assert.EqualValues(t, 2, rerunJob2Task.Attempt)
|
|
|
|
// assert the web view for the original attempt
|
|
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/attempts/1", user2.Name, repo.Name, legacyRun.ID))
|
|
attempt1Resp := session.MakeRequest(t, req, http.StatusOK)
|
|
attempt1View := DecodeJSON(t, attempt1Resp, &actions_web.ViewResponse{})
|
|
assert.EqualValues(t, 1, attempt1View.State.Run.RunAttempt)
|
|
if assert.Len(t, attempt1View.State.Run.Attempts, 2) {
|
|
// attempts ordered by attempt DESC: index 0 = attempt #2 (latest), index 1 = attempt #1 (current)
|
|
assert.False(t, attempt1View.State.Run.Attempts[0].Current)
|
|
assert.True(t, attempt1View.State.Run.Attempts[0].Latest)
|
|
assert.True(t, attempt1View.State.Run.Attempts[1].Current)
|
|
assert.False(t, attempt1View.State.Run.Attempts[1].Latest)
|
|
}
|
|
// isLatestAttempt=false: all write operations disabled
|
|
assert.False(t, attempt1View.State.Run.CanCancel)
|
|
assert.False(t, attempt1View.State.Run.CanApprove)
|
|
assert.False(t, attempt1View.State.Run.CanRerun)
|
|
assert.False(t, attempt1View.State.Run.CanRerunFailed)
|
|
assert.True(t, attempt1View.State.Run.CanDeleteArtifact)
|
|
assert.Equal(t, legacyJob1.ID, attempt1View.State.Run.Jobs[0].ID)
|
|
assert.Equal(t, legacyJob2.ID, attempt1View.State.Run.Jobs[1].ID)
|
|
if assert.Len(t, attempt1View.Artifacts, 1) {
|
|
assert.Equal(t, attempt1View.Artifacts[0].Name, legacyArtifact.ArtifactName)
|
|
}
|
|
|
|
// assert the web view for the latest attempt
|
|
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d", user2.Name, repo.Name, legacyRun.ID))
|
|
attempt2Resp := session.MakeRequest(t, req, http.StatusOK)
|
|
attempt2View := DecodeJSON(t, attempt2Resp, &actions_web.ViewResponse{})
|
|
assert.EqualValues(t, 2, attempt2View.State.Run.RunAttempt)
|
|
if assert.Len(t, attempt2View.State.Run.Attempts, 2) {
|
|
// attempts ordered by attempt DESC: index 0 = attempt #2 (latest, current), index 1 = attempt #1
|
|
assert.True(t, attempt2View.State.Run.Attempts[0].Current)
|
|
assert.True(t, attempt2View.State.Run.Attempts[0].Latest)
|
|
assert.False(t, attempt2View.State.Run.Attempts[1].Current)
|
|
assert.False(t, attempt2View.State.Run.Attempts[1].Latest)
|
|
}
|
|
// isLatestAttempt=true, done=true: can rerun but not cancel
|
|
assert.False(t, attempt2View.State.Run.CanCancel)
|
|
assert.False(t, attempt2View.State.Run.CanApprove)
|
|
assert.True(t, attempt2View.State.Run.CanRerun)
|
|
assert.False(t, attempt2View.State.Run.CanRerunFailed) // all jobs succeeded
|
|
assert.True(t, attempt2View.State.Run.CanDeleteArtifact)
|
|
assert.Equal(t, rerunJob1.ID, attempt2View.State.Run.Jobs[0].ID)
|
|
assert.Equal(t, rerunJob2.ID, attempt2View.State.Run.Jobs[1].ID)
|
|
assert.Empty(t, attempt2View.Artifacts)
|
|
})
|
|
}
|
|
|
|
type workflowJobPayload struct {
|
|
name string
|
|
payload []byte
|
|
needs []string
|
|
runsOn []string
|
|
}
|
|
|
|
func mustParseSingleWorkflowPayloads(t *testing.T, workflowContent string) map[string]workflowJobPayload {
|
|
t.Helper()
|
|
|
|
workflows, err := jobparser.Parse([]byte(workflowContent))
|
|
require.NoError(t, err)
|
|
|
|
payloads := make(map[string]workflowJobPayload, len(workflows))
|
|
for _, workflow := range workflows {
|
|
id, job := workflow.Job()
|
|
needs := job.Needs()
|
|
require.NoError(t, workflow.SetJob(id, job.EraseNeeds()))
|
|
payload, err := workflow.Marshal()
|
|
require.NoError(t, err)
|
|
payloads[id] = workflowJobPayload{
|
|
name: job.Name,
|
|
payload: payload,
|
|
needs: needs,
|
|
runsOn: job.RunsOn(),
|
|
}
|
|
}
|
|
return payloads
|
|
}
|
|
|
|
func getRunLatestAttemptNum(t *testing.T, runID int64) int64 {
|
|
t.Helper()
|
|
|
|
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: runID})
|
|
attempt := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunAttempt{ID: run.LatestAttemptID})
|
|
return attempt.Attempt
|
|
}
|