diff --git a/tests/integration/actions_job_test.go b/tests/integration/actions_job_test.go index c5407e6161964..e13277678d623 100644 --- a/tests/integration/actions_job_test.go +++ b/tests/integration/actions_job_test.go @@ -25,7 +25,7 @@ func TestJobWithNeeds(t *testing.T) { testCases := []struct { treePath string fileContent string - execPolicies map[string]*taskExecPolicy + outcomes map[string]*mockTaskOutcome expectedStatuses map[string]string }{ { @@ -46,7 +46,7 @@ jobs: steps: - run: echo job2 `, - execPolicies: map[string]*taskExecPolicy{ + outcomes: map[string]*mockTaskOutcome{ "job1": { result: runnerv1.Result_RESULT_SUCCESS, }, @@ -77,7 +77,7 @@ jobs: steps: - run: echo job2 `, - execPolicies: map[string]*taskExecPolicy{ + outcomes: map[string]*mockTaskOutcome{ "job1": { result: runnerv1.Result_RESULT_FAILURE, }, @@ -106,7 +106,7 @@ jobs: steps: - run: echo job2 `, - execPolicies: map[string]*taskExecPolicy{ + outcomes: map[string]*mockTaskOutcome{ "job1": { result: runnerv1.Result_RESULT_FAILURE, }, @@ -136,12 +136,12 @@ jobs: fileResp := createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, opts) // fetch and execute task - for i := 0; i < len(tc.execPolicies); i++ { + for i := 0; i < len(tc.outcomes); i++ { task := runner.fetchTask(t) jobName := getTaskJobNameByTaskID(t, token, user2.Name, apiRepo.Name, task.Id) - policy := tc.execPolicies[jobName] - assert.NotNil(t, policy) - runner.execTask(t, task, policy) + outcome := tc.outcomes[jobName] + assert.NotNil(t, outcome) + runner.execTask(t, task, outcome) } // check result @@ -169,7 +169,7 @@ func TestJobNeedsMatrix(t *testing.T) { testCases := []struct { treePath string fileContent string - execPolicies map[string]*taskExecPolicy + outcomes map[string]*mockTaskOutcome expectedTaskNeeds map[string]*runnerv1.TaskNeed // jobID => TaskNeed }{ { @@ -201,7 +201,7 @@ jobs: steps: - run: echo '${{ toJSON(needs.job1.outputs) }}' `, - execPolicies: map[string]*taskExecPolicy{ + outcomes: map[string]*mockTaskOutcome{ "job1 (1)": { result: runnerv1.Result_RESULT_SUCCESS, outputs: map[string]string{ @@ -268,7 +268,7 @@ jobs: steps: - run: echo '${{ toJSON(needs.job1.outputs) }}' `, - execPolicies: map[string]*taskExecPolicy{ + outcomes: map[string]*mockTaskOutcome{ "job1 (1)": { result: runnerv1.Result_RESULT_SUCCESS, outputs: map[string]string{ @@ -320,12 +320,12 @@ jobs: opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, fmt.Sprintf("create %s", tc.treePath), tc.fileContent) createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, opts) - for i := 0; i < len(tc.execPolicies); i++ { + for i := 0; i < len(tc.outcomes); i++ { task := runner.fetchTask(t) jobName := getTaskJobNameByTaskID(t, token, user2.Name, apiRepo.Name, task.Id) - policy := tc.execPolicies[jobName] - assert.NotNil(t, policy) - runner.execTask(t, task, policy) + outcome := tc.outcomes[jobName] + assert.NotNil(t, outcome) + runner.execTask(t, task, outcome) } task := runner.fetchTask(t) diff --git a/tests/integration/actions_log_test.go b/tests/integration/actions_log_test.go new file mode 100644 index 0000000000000..91e0b1835869b --- /dev/null +++ b/tests/integration/actions_log_test.go @@ -0,0 +1,100 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "strings" + "testing" + "time" + + runnerv1 "code.gitea.io/actions-proto-go/runner/v1" + 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" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/storage" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/stretchr/testify/assert" +) + +func TestDownloadTaskLogs(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-download-task-logs", false) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) + runner := newMockRunner() + runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}) + + treePath := ".gitea/workflows/download-task-logs.yml" + fileContent := `name: download-task-logs +on: push +jobs: + job1: + runs-on: ubuntu-latest + steps: + - run: echo job1 +` + + // create the workflow file + opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, fmt.Sprintf("create %s", treePath), fileContent) + createWorkflowFile(t, token, user2.Name, repo.Name, treePath, opts) + + now := time.Now() + outcome := &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(5 * time.Second)), + Content: "job1", + }, + { + Time: timestamppb.New(now.Add(8 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, + }, + } + + // fetch and execute task + task := runner.fetchTask(t) + runner.execTask(t, task, outcome) + + // check whether the log file exists + logFileName := fmt.Sprintf("%s/%02x/%d.log", repo.FullName(), task.Id%256, task.Id) + if setting.Actions.LogCompression.IsZstd() { + logFileName += ".zst" + } + _, err := storage.Actions.Stat(logFileName) + assert.NoError(t, err) + + // download task logs and check content + runIndex := task.Context.GetFields()["run_number"].String() + req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/0/logs", user2.Name, repo.Name, runIndex)). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n") + assert.Len(t, logTextLines, len(outcome.logRows)) + for idx, lr := range outcome.logRows { + assert.Equal( + t, + fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), + logTextLines[idx], + ) + } + + httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) + doAPIDeleteRepository(httpContext)(t) + }) +} diff --git a/tests/integration/actions_runner_test.go b/tests/integration/actions_runner_test.go index b5a62b8821276..355ea1705e23c 100644 --- a/tests/integration/actions_runner_test.go +++ b/tests/integration/actions_runner_test.go @@ -24,9 +24,6 @@ import ( type mockRunner struct { client *mockRunnerClient - - id int64 - name string } type mockRunnerClient struct { @@ -79,8 +76,6 @@ func (r *mockRunner) doRegister(t *testing.T, name, token string, labels []strin Labels: labels, })) assert.NoError(t, err) - r.id = resp.Msg.Runner.Id - r.name = resp.Msg.Runner.Name r.client = newMockRunnerClient(resp.Msg.Runner.Uuid, resp.Msg.Runner.Token) } @@ -118,26 +113,26 @@ func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1 return task } -type taskExecPolicy struct { +type mockTaskOutcome struct { result runnerv1.Result outputs map[string]string logRows []*runnerv1.LogRow execTime time.Duration } -func (r *mockRunner) execTask(t *testing.T, task *runnerv1.Task, policy *taskExecPolicy) { - for idx, lr := range policy.logRows { +func (r *mockRunner) execTask(t *testing.T, task *runnerv1.Task, outcome *mockTaskOutcome) { + for idx, lr := range outcome.logRows { resp, err := r.client.runnerServiceClient.UpdateLog(context.Background(), connect.NewRequest(&runnerv1.UpdateLogRequest{ TaskId: task.Id, Index: int64(idx), Rows: []*runnerv1.LogRow{lr}, - NoMore: idx == len(policy.logRows)-1, + NoMore: idx == len(outcome.logRows)-1, })) assert.NoError(t, err) assert.EqualValues(t, idx+1, resp.Msg.AckIndex) } - sentOutputKeys := make([]string, 0, len(policy.outputs)) - for outputKey, outputValue := range policy.outputs { + sentOutputKeys := make([]string, 0, len(outcome.outputs)) + for outputKey, outputValue := range outcome.outputs { resp, err := r.client.runnerServiceClient.UpdateTask(context.Background(), connect.NewRequest(&runnerv1.UpdateTaskRequest{ State: &runnerv1.TaskState{ Id: task.Id, @@ -149,14 +144,14 @@ func (r *mockRunner) execTask(t *testing.T, task *runnerv1.Task, policy *taskExe sentOutputKeys = append(sentOutputKeys, outputKey) assert.ElementsMatch(t, sentOutputKeys, resp.Msg.SentOutputs) } - time.Sleep(policy.execTime) + time.Sleep(outcome.execTime) resp, err := r.client.runnerServiceClient.UpdateTask(context.Background(), connect.NewRequest(&runnerv1.UpdateTaskRequest{ State: &runnerv1.TaskState{ Id: task.Id, - Result: policy.result, + Result: outcome.result, StoppedAt: timestamppb.Now(), }, })) assert.NoError(t, err) - assert.Equal(t, policy.result, resp.Msg.State.Result) + assert.Equal(t, outcome.result, resp.Msg.State.Result) }