gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "context"
  6. "fmt"
  7. "net/http"
  8. "testing"
  9. "time"
  10. auth_model "code.gitea.io/gitea/models/auth"
  11. "code.gitea.io/gitea/modules/setting"
  12. pingv1 "code.gitea.io/actions-proto-go/ping/v1"
  13. "code.gitea.io/actions-proto-go/ping/v1/pingv1connect"
  14. runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
  15. "code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
  16. "connectrpc.com/connect"
  17. "github.com/stretchr/testify/assert"
  18. "google.golang.org/protobuf/types/known/timestamppb"
  19. )
  20. type mockRunner struct {
  21. client *mockRunnerClient
  22. }
  23. type mockRunnerClient struct {
  24. pingServiceClient pingv1connect.PingServiceClient
  25. runnerServiceClient runnerv1connect.RunnerServiceClient
  26. }
  27. func newMockRunner() *mockRunner {
  28. client := newMockRunnerClient("", "")
  29. return &mockRunner{client: client}
  30. }
  31. func newMockRunnerClient(uuid, token string) *mockRunnerClient {
  32. baseURL := setting.AppURL + "api/actions"
  33. opt := connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc {
  34. return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
  35. if uuid != "" {
  36. req.Header().Set("x-runner-uuid", uuid)
  37. }
  38. if token != "" {
  39. req.Header().Set("x-runner-token", token)
  40. }
  41. return next(ctx, req)
  42. }
  43. }))
  44. client := &mockRunnerClient{
  45. pingServiceClient: pingv1connect.NewPingServiceClient(http.DefaultClient, baseURL, opt),
  46. runnerServiceClient: runnerv1connect.NewRunnerServiceClient(http.DefaultClient, baseURL, opt),
  47. }
  48. return client
  49. }
  50. func (r *mockRunner) doPing(t *testing.T) {
  51. resp, err := r.client.pingServiceClient.Ping(t.Context(), connect.NewRequest(&pingv1.PingRequest{
  52. Data: "mock-runner",
  53. }))
  54. assert.NoError(t, err)
  55. assert.Equal(t, "Hello, mock-runner!", resp.Msg.Data)
  56. }
  57. func (r *mockRunner) doRegister(t *testing.T, name, token string, labels []string, ephemeral bool) {
  58. r.doPing(t)
  59. resp, err := r.client.runnerServiceClient.Register(t.Context(), connect.NewRequest(&runnerv1.RegisterRequest{
  60. Name: name,
  61. Token: token,
  62. Version: "mock-runner-version",
  63. Labels: labels,
  64. Ephemeral: ephemeral,
  65. }))
  66. assert.NoError(t, err)
  67. r.client = newMockRunnerClient(resp.Msg.Runner.Uuid, resp.Msg.Runner.Token)
  68. }
  69. func (r *mockRunner) registerAsRepoRunner(t *testing.T, ownerName, repoName, runnerName string, labels []string, ephemeral bool) {
  70. session := loginUser(t, ownerName)
  71. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
  72. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/runners/registration-token", ownerName, repoName)).AddTokenAuth(token)
  73. resp := MakeRequest(t, req, http.StatusOK)
  74. var registrationToken struct {
  75. Token string `json:"token"`
  76. }
  77. DecodeJSON(t, resp, &registrationToken)
  78. r.doRegister(t, runnerName, registrationToken.Token, labels, ephemeral)
  79. }
  80. func (r *mockRunner) fetchTask(t *testing.T, timeout ...time.Duration) *runnerv1.Task {
  81. fetchTimeout := 10 * time.Second
  82. if len(timeout) > 0 {
  83. fetchTimeout = timeout[0]
  84. }
  85. ddl := time.Now().Add(fetchTimeout)
  86. var task *runnerv1.Task
  87. for time.Now().Before(ddl) {
  88. resp, err := r.client.runnerServiceClient.FetchTask(t.Context(), connect.NewRequest(&runnerv1.FetchTaskRequest{
  89. TasksVersion: 0,
  90. }))
  91. assert.NoError(t, err)
  92. if resp.Msg.Task != nil {
  93. task = resp.Msg.Task
  94. break
  95. }
  96. time.Sleep(time.Second)
  97. }
  98. assert.NotNil(t, task, "failed to fetch a task")
  99. return task
  100. }
  101. type mockTaskOutcome struct {
  102. result runnerv1.Result
  103. outputs map[string]string
  104. logRows []*runnerv1.LogRow
  105. }
  106. func (r *mockRunner) execTask(t *testing.T, task *runnerv1.Task, outcome *mockTaskOutcome) {
  107. for idx, lr := range outcome.logRows {
  108. resp, err := r.client.runnerServiceClient.UpdateLog(t.Context(), connect.NewRequest(&runnerv1.UpdateLogRequest{
  109. TaskId: task.Id,
  110. Index: int64(idx),
  111. Rows: []*runnerv1.LogRow{lr},
  112. NoMore: idx == len(outcome.logRows)-1,
  113. }))
  114. assert.NoError(t, err)
  115. assert.EqualValues(t, idx+1, resp.Msg.AckIndex)
  116. }
  117. sentOutputKeys := make([]string, 0, len(outcome.outputs))
  118. for outputKey, outputValue := range outcome.outputs {
  119. resp, err := r.client.runnerServiceClient.UpdateTask(t.Context(), connect.NewRequest(&runnerv1.UpdateTaskRequest{
  120. State: &runnerv1.TaskState{
  121. Id: task.Id,
  122. Result: runnerv1.Result_RESULT_UNSPECIFIED,
  123. },
  124. Outputs: map[string]string{outputKey: outputValue},
  125. }))
  126. assert.NoError(t, err)
  127. sentOutputKeys = append(sentOutputKeys, outputKey)
  128. assert.ElementsMatch(t, sentOutputKeys, resp.Msg.SentOutputs)
  129. }
  130. resp, err := r.client.runnerServiceClient.UpdateTask(t.Context(), connect.NewRequest(&runnerv1.UpdateTaskRequest{
  131. State: &runnerv1.TaskState{
  132. Id: task.Id,
  133. Result: outcome.result,
  134. StoppedAt: timestamppb.Now(),
  135. },
  136. }))
  137. assert.NoError(t, err)
  138. assert.Equal(t, outcome.result, resp.Msg.State.Result)
  139. }