mirror of
https://code.forgejo.org/forgejo/runner.git
synced 2025-09-05 18:40:59 +00:00
Performance has been in the range of 2.5 - 3 minutes for the "runner integration tests" step, causing this arbitrary timeout to be hit. The timeout was useful for early test development when nothing was running successfully, but meaningful now. Fixes #905. <!--start release-notes-assistant--> <!--URL:https://code.forgejo.org/forgejo/runner--> - other - [PR](https://code.forgejo.org/forgejo/runner/pulls/906): <!--number 906 --><!--line 0 --><!--description dGVzdDogcmVtb3ZlIGludGVybmFsIHRpbWVvdXQgaW4gVGVzdFJ1bm5lckNhY2hlQ29uZmlndXJhdGlvbg==-->test: remove internal timeout in TestRunnerCacheConfiguration<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://code.forgejo.org/forgejo/runner/pulls/906 Reviewed-by: earl-warren <earl-warren@noreply.code.forgejo.org> Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net> Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
236 lines
7.7 KiB
Go
236 lines
7.7 KiB
Go
package run
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
pingv1 "code.forgejo.org/forgejo/actions-proto/ping/v1"
|
|
runnerv1 "code.forgejo.org/forgejo/actions-proto/runner/v1"
|
|
"code.forgejo.org/forgejo/runner/v9/internal/pkg/config"
|
|
"code.forgejo.org/forgejo/runner/v9/internal/pkg/labels"
|
|
"code.forgejo.org/forgejo/runner/v9/internal/pkg/report"
|
|
"connectrpc.com/connect"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestExplainFailedGenerateWorkflow(t *testing.T) {
|
|
logged := ""
|
|
log := func(message string, args ...any) {
|
|
logged += fmt.Sprintf(message, args...) + "\n"
|
|
}
|
|
task := &runnerv1.Task{
|
|
WorkflowPayload: []byte("on: [push]\njobs:\n"),
|
|
}
|
|
generateWorkflowError := errors.New("message 1\nmessage 2")
|
|
err := explainFailedGenerateWorkflow(task, log, generateWorkflowError)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, " 1: on: [push]\n 2: jobs:\n 3: \nErrors were found and although they tend to be cryptic the line number they refer to gives a hint as to where the problem might be.\nmessage 1\nmessage 2\n", logged)
|
|
}
|
|
|
|
func TestLabelUpdate(t *testing.T) {
|
|
ctx := context.Background()
|
|
ls := labels.Labels{}
|
|
|
|
initialLabel, err := labels.Parse("testlabel:docker://alpine")
|
|
assert.NoError(t, err)
|
|
ls = append(ls, initialLabel)
|
|
|
|
newLs := labels.Labels{}
|
|
|
|
newLabel, err := labels.Parse("next label:host")
|
|
assert.NoError(t, err)
|
|
newLs = append(newLs, initialLabel)
|
|
newLs = append(newLs, newLabel)
|
|
|
|
runner := Runner{
|
|
labels: ls,
|
|
}
|
|
|
|
assert.Contains(t, runner.labels, initialLabel)
|
|
assert.NotContains(t, runner.labels, newLabel)
|
|
|
|
runner.Update(ctx, newLs)
|
|
|
|
assert.Contains(t, runner.labels, initialLabel)
|
|
assert.Contains(t, runner.labels, newLabel)
|
|
}
|
|
|
|
type forgejoClientMock struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *forgejoClientMock) Address() string {
|
|
args := m.Called()
|
|
return args.String(0)
|
|
}
|
|
|
|
func (m *forgejoClientMock) Insecure() bool {
|
|
args := m.Called()
|
|
return args.Bool(0)
|
|
}
|
|
|
|
func (m *forgejoClientMock) Ping(ctx context.Context, request *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error) {
|
|
args := m.Called(ctx, request)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*connect.Response[pingv1.PingResponse]), args.Error(1)
|
|
}
|
|
|
|
func (m *forgejoClientMock) Register(ctx context.Context, request *connect.Request[runnerv1.RegisterRequest]) (*connect.Response[runnerv1.RegisterResponse], error) {
|
|
args := m.Called(ctx, request)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*connect.Response[runnerv1.RegisterResponse]), args.Error(1)
|
|
}
|
|
|
|
func (m *forgejoClientMock) Declare(ctx context.Context, request *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error) {
|
|
args := m.Called(ctx, request)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*connect.Response[runnerv1.DeclareResponse]), args.Error(1)
|
|
}
|
|
|
|
func (m *forgejoClientMock) FetchTask(ctx context.Context, request *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error) {
|
|
args := m.Called(ctx, request)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*connect.Response[runnerv1.FetchTaskResponse]), args.Error(1)
|
|
}
|
|
|
|
func (m *forgejoClientMock) UpdateTask(ctx context.Context, request *connect.Request[runnerv1.UpdateTaskRequest]) (*connect.Response[runnerv1.UpdateTaskResponse], error) {
|
|
args := m.Called(ctx, request)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*connect.Response[runnerv1.UpdateTaskResponse]), args.Error(1)
|
|
}
|
|
|
|
func (m *forgejoClientMock) UpdateLog(ctx context.Context, request *connect.Request[runnerv1.UpdateLogRequest]) (*connect.Response[runnerv1.UpdateLogResponse], error) {
|
|
// Enable for log output from runs if needed.
|
|
// for _, row := range request.Msg.Rows {
|
|
// println(fmt.Sprintf("UpdateLog: %q", row.Content))
|
|
// }
|
|
args := m.Called(ctx, request)
|
|
mockRetval := args.Get(0)
|
|
mockError := args.Error(1)
|
|
if mockRetval != nil {
|
|
return mockRetval.(*connect.Response[runnerv1.UpdateLogResponse]), mockError
|
|
} else if mockError != nil {
|
|
return nil, mockError
|
|
}
|
|
// Unless overridden by mock, default to returning indication that logs were received successfully
|
|
return connect.NewResponse(&runnerv1.UpdateLogResponse{
|
|
AckIndex: request.Msg.Index + int64(len(request.Msg.Rows)),
|
|
}), nil
|
|
}
|
|
|
|
func TestRunnerCacheConfiguration(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
forgejoClient := &forgejoClientMock{}
|
|
|
|
forgejoClient.On("Address").Return("https://127.0.0.1:8080") // not expected to be used in this test
|
|
forgejoClient.On("UpdateLog", mock.Anything, mock.Anything).Return(nil, nil)
|
|
forgejoClient.On("UpdateTask", mock.Anything, mock.Anything).
|
|
Return(connect.NewResponse(&runnerv1.UpdateTaskResponse{}), nil)
|
|
|
|
runner := NewRunner(
|
|
&config.Config{
|
|
Cache: config.Cache{
|
|
// Note that this test requires that containers on the local dev environment can access localhost to
|
|
// reach the cache proxy, and that the cache proxy can access localhost to reach the cache, both of
|
|
// which are embedded servers that the Runner will start. If any specific firewall config is needed
|
|
// it's easier to do that with statically configured ports, so...
|
|
Port: 40713,
|
|
ProxyPort: 40714,
|
|
Dir: t.TempDir(),
|
|
},
|
|
Host: config.Host{
|
|
WorkdirParent: t.TempDir(),
|
|
},
|
|
},
|
|
&config.Registration{
|
|
Labels: []string{"ubuntu-latest:docker://code.forgejo.org/oci/node:20-bookworm"},
|
|
},
|
|
forgejoClient)
|
|
require.NotNil(t, runner)
|
|
|
|
// Must set up cache for our test
|
|
require.NotNil(t, runner.cacheProxy)
|
|
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
defer cancel()
|
|
|
|
// Run a given workflow w/ event...
|
|
runWorkflow := func(yamlContent, eventName, ref, description string) {
|
|
task := &runnerv1.Task{
|
|
WorkflowPayload: []byte(yamlContent),
|
|
Context: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"token": structpb.NewStringValue("some token here"),
|
|
"forgejo_default_actions_url": structpb.NewStringValue("https://data.forgejo.org"),
|
|
"repository": structpb.NewStringValue("runner"),
|
|
"event_name": structpb.NewStringValue(eventName),
|
|
"ref": structpb.NewStringValue(ref),
|
|
},
|
|
},
|
|
}
|
|
|
|
reporter := report.NewReporter(ctx, cancel, forgejoClient, task, time.Second)
|
|
err := runner.run(ctx, task, reporter)
|
|
reporter.Close(nil)
|
|
require.NoError(t, err, description)
|
|
}
|
|
|
|
// Step 1: Populate shared cache with push workflow
|
|
populateYaml := `
|
|
name: Cache Testing Action
|
|
on:
|
|
push:
|
|
jobs:
|
|
job-cache-check-1:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: cache_path_1
|
|
key: cache-key-1
|
|
- run: |
|
|
mkdir -p cache_path_1
|
|
echo "Hello from push workflow!" > cache_path_1/cache_content_1
|
|
`
|
|
runWorkflow(populateYaml, "push", "refs/heads/main", "step 1: push cache populate expected to succeed")
|
|
|
|
// Step 2: Validate that cache is accessible; mostly a sanity check that the test environment and mock context
|
|
// provides everything needed for the cache setup.
|
|
checkYaml := `
|
|
name: Cache Testing Action
|
|
on:
|
|
push:
|
|
jobs:
|
|
job-cache-check-2:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: cache_path_1
|
|
key: cache-key-1
|
|
- run: |
|
|
[[ -f cache_path_1/cache_content_1 ]] && echo "Step 2: cache file found." || echo "Step 2: cache file missing!"
|
|
[[ -f cache_path_1/cache_content_1 ]] || exit 1
|
|
`
|
|
runWorkflow(checkYaml, "push", "refs/heads/main", "step 2: push cache check expected to succeed")
|
|
}
|