mirror of
https://code.forgejo.org/forgejo/runner.git
synced 2024-12-18 11:52:16 +03:00
93bced9c7b
bufbuild/connect-go was archived with maintenance transferred to the ConnectRPC organization. Gitea's protobuf library for actions now uses the ConnectRPC dependency as of v0.4.0, removing the need to continue using the dead package.
263 lines
5.1 KiB
Go
263 lines
5.1 KiB
Go
// Copyright The Forgejo Authors.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package poll
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"connectrpc.com/connect"
|
|
|
|
"code.gitea.io/actions-proto-go/ping/v1/pingv1connect"
|
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
|
"code.gitea.io/actions-proto-go/runner/v1/runnerv1connect"
|
|
"gitea.com/gitea/act_runner/internal/pkg/config"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
type mockPoller struct {
|
|
poller
|
|
}
|
|
|
|
func (o *mockPoller) Poll() {
|
|
o.poller.Poll()
|
|
}
|
|
|
|
type mockClient struct {
|
|
pingv1connect.PingServiceClient
|
|
runnerv1connect.RunnerServiceClient
|
|
|
|
sleep time.Duration
|
|
cancel bool
|
|
err error
|
|
noTask bool
|
|
}
|
|
|
|
func (o mockClient) Address() string {
|
|
return ""
|
|
}
|
|
|
|
func (o mockClient) Insecure() bool {
|
|
return true
|
|
}
|
|
|
|
func (o *mockClient) FetchTask(ctx context.Context, req *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error) {
|
|
if o.sleep > 0 {
|
|
select {
|
|
case <-ctx.Done():
|
|
log.Trace("fetch task done")
|
|
return nil, context.DeadlineExceeded
|
|
case <-time.After(o.sleep):
|
|
log.Trace("slept")
|
|
return nil, fmt.Errorf("unexpected")
|
|
}
|
|
}
|
|
if o.cancel {
|
|
return nil, context.Canceled
|
|
}
|
|
if o.err != nil {
|
|
return nil, o.err
|
|
}
|
|
task := &runnerv1.Task{}
|
|
if o.noTask {
|
|
task = nil
|
|
o.noTask = false
|
|
}
|
|
|
|
return connect.NewResponse(&runnerv1.FetchTaskResponse{
|
|
Task: task,
|
|
TasksVersion: int64(1),
|
|
}), nil
|
|
}
|
|
|
|
type mockRunner struct {
|
|
cfg *config.Runner
|
|
log chan string
|
|
panics bool
|
|
err error
|
|
}
|
|
|
|
func (o *mockRunner) Run(ctx context.Context, task *runnerv1.Task) error {
|
|
o.log <- "runner starts"
|
|
if o.panics {
|
|
log.Trace("panics")
|
|
o.log <- "runner panics"
|
|
o.panics = false
|
|
panic("whatever")
|
|
}
|
|
if o.err != nil {
|
|
log.Trace("error")
|
|
o.log <- "runner error"
|
|
err := o.err
|
|
o.err = nil
|
|
return err
|
|
}
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
log.Trace("shutdown")
|
|
o.log <- "runner shutdown"
|
|
return nil
|
|
case <-time.After(o.cfg.Timeout):
|
|
log.Trace("after")
|
|
o.log <- "runner timeout"
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func setTrace(t *testing.T) {
|
|
t.Helper()
|
|
log.SetReportCaller(true)
|
|
log.SetLevel(log.TraceLevel)
|
|
}
|
|
|
|
func TestPoller_New(t *testing.T) {
|
|
p := New(&config.Config{}, &mockClient{}, &mockRunner{})
|
|
assert.NotNil(t, p)
|
|
}
|
|
|
|
func TestPoller_Runner(t *testing.T) {
|
|
setTrace(t)
|
|
for _, testCase := range []struct {
|
|
name string
|
|
timeout time.Duration
|
|
noTask bool
|
|
panics bool
|
|
err error
|
|
expected string
|
|
contextTimeout time.Duration
|
|
}{
|
|
{
|
|
name: "Simple",
|
|
timeout: 10 * time.Second,
|
|
expected: "runner shutdown",
|
|
},
|
|
{
|
|
name: "Panics",
|
|
timeout: 10 * time.Second,
|
|
panics: true,
|
|
expected: "runner panics",
|
|
},
|
|
{
|
|
name: "Error",
|
|
timeout: 10 * time.Second,
|
|
err: fmt.Errorf("ERROR"),
|
|
expected: "runner error",
|
|
},
|
|
{
|
|
name: "PollTaskError",
|
|
timeout: 10 * time.Second,
|
|
noTask: true,
|
|
expected: "runner shutdown",
|
|
},
|
|
{
|
|
name: "ShutdownTimeout",
|
|
timeout: 1 * time.Second,
|
|
contextTimeout: 1 * time.Minute,
|
|
expected: "runner timeout",
|
|
},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
runnerLog := make(chan string, 3)
|
|
configRunner := config.Runner{
|
|
FetchInterval: 1,
|
|
Capacity: 1,
|
|
Timeout: testCase.timeout,
|
|
}
|
|
p := &mockPoller{}
|
|
p.init(
|
|
&config.Config{
|
|
Runner: configRunner,
|
|
},
|
|
&mockClient{
|
|
noTask: testCase.noTask,
|
|
},
|
|
&mockRunner{
|
|
cfg: &configRunner,
|
|
log: runnerLog,
|
|
panics: testCase.panics,
|
|
err: testCase.err,
|
|
})
|
|
go p.Poll()
|
|
assert.Equal(t, "runner starts", <-runnerLog)
|
|
var ctx context.Context
|
|
var cancel context.CancelFunc
|
|
if testCase.contextTimeout > 0 {
|
|
ctx, cancel = context.WithTimeout(context.Background(), testCase.contextTimeout)
|
|
defer cancel()
|
|
} else {
|
|
ctx, cancel = context.WithCancel(context.Background())
|
|
cancel()
|
|
}
|
|
p.Shutdown(ctx)
|
|
<-p.done
|
|
assert.Equal(t, testCase.expected, <-runnerLog)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPoller_Fetch(t *testing.T) {
|
|
setTrace(t)
|
|
for _, testCase := range []struct {
|
|
name string
|
|
noTask bool
|
|
sleep time.Duration
|
|
err error
|
|
cancel bool
|
|
success bool
|
|
}{
|
|
{
|
|
name: "Success",
|
|
success: true,
|
|
},
|
|
{
|
|
name: "Timeout",
|
|
sleep: 100 * time.Millisecond,
|
|
},
|
|
{
|
|
name: "Canceled",
|
|
cancel: true,
|
|
},
|
|
{
|
|
name: "NoTask",
|
|
noTask: true,
|
|
},
|
|
{
|
|
name: "Error",
|
|
err: fmt.Errorf("random error"),
|
|
},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
configRunner := config.Runner{
|
|
FetchTimeout: 1 * time.Millisecond,
|
|
}
|
|
p := &mockPoller{}
|
|
p.init(
|
|
&config.Config{
|
|
Runner: configRunner,
|
|
},
|
|
&mockClient{
|
|
sleep: testCase.sleep,
|
|
cancel: testCase.cancel,
|
|
noTask: testCase.noTask,
|
|
err: testCase.err,
|
|
},
|
|
&mockRunner{},
|
|
)
|
|
task, ok := p.fetchTask(context.Background())
|
|
if testCase.success {
|
|
assert.True(t, ok)
|
|
assert.NotNil(t, task)
|
|
} else {
|
|
assert.False(t, ok)
|
|
assert.Nil(t, task)
|
|
}
|
|
})
|
|
}
|
|
}
|