mirror of
https://code.forgejo.org/forgejo/runner.git
synced 2025-09-05 18:40:59 +00:00
test: add an integration test for embedded runner cache (#889)
Adds a limited integration test in the runner which verifies that the embedded cache server starts up, can be written to by an action, and can be read by a subsequent action. This is a solid base foundation for future nearly-end-to-end tests. <!--start release-notes-assistant--> <!--URL:https://code.forgejo.org/forgejo/runner--> - other - [PR](https://code.forgejo.org/forgejo/runner/pulls/889): <!--number 889 --><!--line 0 --><!--description dGVzdDogYWRkIGFuIGludGVncmF0aW9uIHRlc3QgZm9yIGVtYmVkZGVkIHJ1bm5lciBjYWNoZQ==-->test: add an integration test for embedded runner cache<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://code.forgejo.org/forgejo/runner/pulls/889 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>
This commit is contained in:
parent
22762480d8
commit
91e7940947
3 changed files with 223 additions and 1 deletions
|
@ -149,6 +149,44 @@ jobs:
|
|||
go test ./act/container
|
||||
go test -timeout 30m ./act/runner/...
|
||||
|
||||
runner-integration-tests:
|
||||
name: runner integration tests
|
||||
if: vars.ROLE == 'forgejo-coding'
|
||||
runs-on: lxc-bookworm
|
||||
needs: [build-and-tests]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: install docker
|
||||
run: |
|
||||
mkdir /etc/docker
|
||||
cat > /etc/docker/daemon.json <<EOF
|
||||
{
|
||||
"ipv6": true,
|
||||
"experimental": true,
|
||||
"ip6tables": true,
|
||||
"fixed-cidr-v6": "fd05:d0ca:1::/64",
|
||||
"default-address-pools": [
|
||||
{
|
||||
"base": "172.19.0.0/16",
|
||||
"size": 24
|
||||
},
|
||||
{
|
||||
"base": "fd05:d0ca:2::/104",
|
||||
"size": 112
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
apt --quiet install --yes -qq docker.io make
|
||||
|
||||
- run: make integration-test
|
||||
|
||||
validate-mocks:
|
||||
name: validate mocks
|
||||
if: vars.ROLE == 'forgejo-coding'
|
||||
|
|
5
Makefile
5
Makefile
|
@ -106,10 +106,13 @@ fmt-check:
|
|||
fi;
|
||||
|
||||
test: lint-check fmt-check
|
||||
$(GO) test -v -cover -coverprofile coverage.txt ./internal/...
|
||||
$(GO) test -v -short -cover -coverprofile coverage.txt ./internal/...
|
||||
$(GO) test -short ./act/container
|
||||
$(GO) test ./act/artifactcache/... ./act/workflowpattern/... ./act/filecollector/... ./act/common/... ./act/jobparser ./act/model ./act/exprparser ./act/schema
|
||||
|
||||
integration-test:
|
||||
@$(GO) test -v ./internal/app/run/...
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
@echo "Running go vet..."
|
||||
|
|
|
@ -5,11 +5,19 @@ import (
|
|||
"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) {
|
||||
|
@ -53,3 +61,176 @@ func TestLabelUpdate(t *testing.T) {
|
|||
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.WithTimeout(context.Background(), 3*time.Minute)
|
||||
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")
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue