test: Enhance Coverage + CI (#1055)

Reviewed-on: https://gitea.com/gitea/runner/pulls/1055
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni
2026-07-01 03:26:42 +00:00
committed by Lunny Xiao
parent 745b0ab6e4
commit 3396021e0f
27 changed files with 1980 additions and 1 deletions

View File

@@ -5,8 +5,12 @@ package client
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"connectrpc.com/connect"
pingv1 "gitea.dev/actions-proto-go/ping/v1"
"github.com/stretchr/testify/require"
)
@@ -25,3 +29,67 @@ func TestGetHTTPClientUsesProxyFromEnvironment(t *testing.T) {
require.NotNil(t, proxyURL)
require.Equal(t, "http://proxy.example.com:8080", proxyURL.String())
}
func TestGetHTTPClientInsecureTLS(t *testing.T) {
// insecure only takes effect for https endpoints
httpsInsecure := getHTTPClient("https://gitea.example.com", true)
transport, ok := httpsInsecure.Transport.(*http.Transport)
require.True(t, ok)
require.NotNil(t, transport.TLSClientConfig)
require.True(t, transport.TLSClientConfig.InsecureSkipVerify)
for _, tc := range []struct {
name string
endpoint string
insecure bool
}{
{"https secure", "https://gitea.example.com", false},
{"http insecure ignored", "http://gitea.example.com", true},
} {
t.Run(tc.name, func(t *testing.T) {
c := getHTTPClient(tc.endpoint, tc.insecure)
tr, ok := c.Transport.(*http.Transport)
require.True(t, ok)
require.Nil(t, tr.TLSClientConfig)
})
}
}
func TestNewSetsBaseURLAndHeaders(t *testing.T) {
var gotPath string
gotHeaders := make(http.Header)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path
gotHeaders = r.Header.Clone()
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
// trailing slash must be trimmed before "/api/actions" is appended
c := New(server.URL+"/", false, "the-uuid", "the-token")
// Address returns the endpoint as supplied (untrimmed)
require.Equal(t, server.URL+"/", c.Address())
require.False(t, c.Insecure())
// the call is expected to fail (server returns 500), we only assert what was sent
_, _ = c.Ping(t.Context(), connect.NewRequest(&pingv1.PingRequest{Data: "hi"}))
require.True(t, strings.HasPrefix(gotPath, "/api/actions/"), "unexpected path %q", gotPath)
require.Equal(t, "the-uuid", gotHeaders.Get(UUIDHeader))
require.Equal(t, "the-token", gotHeaders.Get(TokenHeader))
}
func TestNewOmitsEmptyHeaders(t *testing.T) {
gotHeaders := make(http.Header)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotHeaders = r.Header.Clone()
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
c := New(server.URL, false, "", "")
_, _ = c.Ping(t.Context(), connect.NewRequest(&pingv1.PingRequest{Data: "hi"}))
require.Empty(t, gotHeaders.Get(UUIDHeader))
require.Empty(t, gotHeaders.Get(TokenHeader))
}

View File

@@ -0,0 +1,51 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package config
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestSaveAndLoadRegistration(t *testing.T) {
file := filepath.Join(t.TempDir(), ".runner")
reg := &Registration{
ID: 42,
UUID: "the-uuid",
Name: "runner",
Token: "the-token",
Address: "http://localhost:3000",
Labels: []string{"ubuntu:host", "ubuntu:docker://node:18"},
Ephemeral: true,
}
require.NoError(t, SaveRegistration(file, reg))
// SaveRegistration stamps the warning onto the in-memory struct
require.Equal(t, registrationWarning, reg.Warning)
loaded, err := LoadRegistration(file)
require.NoError(t, err)
// the warning is intentionally cleared on load
require.Empty(t, loaded.Warning)
loaded.Warning = reg.Warning
require.Equal(t, reg, loaded)
}
func TestLoadRegistrationMissingFile(t *testing.T) {
_, err := LoadRegistration(filepath.Join(t.TempDir(), "does-not-exist"))
require.Error(t, err)
}
func TestLoadRegistrationInvalidJSON(t *testing.T) {
file := filepath.Join(t.TempDir(), ".runner")
require.NoError(t, os.WriteFile(file, []byte("not json"), 0o600))
_, err := LoadRegistration(file)
require.Error(t, err)
}

View File

@@ -0,0 +1,20 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package envcheck
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestCheckIfDockerRunningReturnsPingError(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := CheckIfDockerRunning(ctx, "unix:///definitely/missing/docker.sock")
require.Error(t, err)
require.Contains(t, err.Error(), "cannot ping the docker daemon")
}

View File

@@ -61,3 +61,75 @@ func TestParse(t *testing.T) {
})
}
}
// mustParse parses the given label strings, failing the test on any error.
func mustParse(t *testing.T, strs ...string) Labels {
t.Helper()
ls := make(Labels, 0, len(strs))
for _, s := range strs {
l, err := Parse(s)
require.NoError(t, err)
ls = append(ls, l)
}
return ls
}
func TestRequireDocker(t *testing.T) {
tests := []struct {
name string
strs []string
want bool
}{
{"empty", nil, false},
{"only host", []string{"ubuntu:host", "self-hosted"}, false},
{"has docker", []string{"ubuntu:host", "ubuntu:docker://node:18"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, mustParse(t, tt.strs...).RequireDocker())
})
}
}
func TestPickPlatform(t *testing.T) {
ls := mustParse(t,
"ubuntu:docker://node:18",
"self-hosted:host",
)
tests := []struct {
name string
runsOn []string
want string
}{
{"docker strips leading slashes", []string{"ubuntu"}, "node:18"},
{"host maps to self-hosted marker", []string{"self-hosted"}, "-self-hosted"},
{"first match wins", []string{"self-hosted", "ubuntu"}, "-self-hosted"},
{"unknown falls back to default", []string{"windows"}, "docker.gitea.com/runner-images:ubuntu-latest"},
{"no runsOn falls back to default", nil, "docker.gitea.com/runner-images:ubuntu-latest"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, ls.PickPlatform(tt.runsOn))
})
}
}
func TestNames(t *testing.T) {
ls := mustParse(t, "ubuntu:docker://node:18", "self-hosted:host")
require.Equal(t, []string{"ubuntu", "self-hosted"}, ls.Names())
require.Empty(t, Labels{}.Names())
}
func TestToStrings(t *testing.T) {
ls := mustParse(t,
"ubuntu:docker://node:18",
"self-hosted:host",
"bare",
)
require.Equal(t, []string{
"ubuntu:docker://node:18",
"self-hosted:host",
"bare:host",
}, ls.ToStrings())
}

View File

@@ -0,0 +1,95 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package metrics
import (
"context"
"strings"
"sync"
"testing"
"time"
runnerv1 "gitea.dev/actions-proto-go/runner/v1"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/require"
)
func TestResultToStatusLabel(t *testing.T) {
tests := []struct {
name string
result runnerv1.Result
want string
}{
{"success", runnerv1.Result_RESULT_SUCCESS, LabelStatusSuccess},
{"failure", runnerv1.Result_RESULT_FAILURE, LabelStatusFailure},
{"cancelled", runnerv1.Result_RESULT_CANCELLED, LabelStatusCancelled},
{"skipped", runnerv1.Result_RESULT_SKIPPED, LabelStatusSkipped},
{"unspecified", runnerv1.Result_RESULT_UNSPECIFIED, LabelStatusUnknown},
{"out of range", runnerv1.Result(999), LabelStatusUnknown},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, ResultToStatusLabel(tt.result))
})
}
}
func TestInitAndDynamicMetricRegistration(t *testing.T) {
oldRegistry := Registry
t.Cleanup(func() {
Registry = oldRegistry
})
Registry = prometheus.NewRegistry()
initOnce = sync.Once{}
Init()
Init()
RunnerInfo.WithLabelValues("test", "runner").Set(1)
RegisterUptimeFunc(time.Now().Add(-time.Second))
RegisterRunningJobsFunc(func() int64 { return 2 }, 4)
metrics, err := Registry.Gather()
require.NoError(t, err)
require.True(t, hasMetric(metrics, "gitea_runner_info"))
require.True(t, hasMetric(metrics, "gitea_runner_uptime_seconds"))
require.True(t, hasMetric(metrics, "gitea_runner_job_running"))
require.True(t, hasMetric(metrics, "gitea_runner_job_capacity_utilization_ratio"))
}
func TestRegisterRunningJobsFuncZeroCapacity(t *testing.T) {
oldRegistry := Registry
t.Cleanup(func() { Registry = oldRegistry })
Registry = prometheus.NewRegistry()
RegisterRunningJobsFunc(func() int64 { return 3 }, 0)
metrics, err := Registry.Gather()
require.NoError(t, err)
for _, mf := range metrics {
if mf.GetName() == "gitea_runner_job_capacity_utilization_ratio" {
require.Len(t, mf.GetMetric(), 1)
require.InDelta(t, 0, mf.GetMetric()[0].GetGauge().GetValue(), 0)
return
}
}
t.Fatal("capacity utilization metric not gathered")
}
func TestStartServerCanBeCancelled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
StartServer(ctx, "127.0.0.1:0")
cancel()
}
func hasMetric(metrics []*dto.MetricFamily, name string) bool {
for _, mf := range metrics {
if strings.EqualFold(mf.GetName(), name) {
return true
}
}
return false
}

View File

@@ -0,0 +1,23 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !windows && !plan9
package process
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSysProcAttrUnixModes(t *testing.T) {
plain := SysProcAttr("", false)
require.True(t, plain.Setpgid)
require.False(t, plain.Setsid)
tty := SysProcAttr("", true)
require.True(t, tty.Setsid)
require.True(t, tty.Setctty)
require.False(t, tty.Setpgid)
}

View File

@@ -0,0 +1,36 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package process
import (
"context"
"os/exec"
"testing"
"github.com/stretchr/testify/require"
)
func TestNewTreeKillConfiguresCommand(t *testing.T) {
cmd := exec.CommandContext(context.Background(), "sleep", "1")
tk := NewTreeKill(cmd)
require.NotNil(t, tk)
require.NotNil(t, cmd.Cancel)
require.Equal(t, treeKillWaitDelay, cmd.WaitDelay)
require.NoError(t, cmd.Cancel())
}
func TestTreeKillCaptureStoresKiller(t *testing.T) {
cmd := exec.CommandContext(context.Background(), "sleep", "10")
cmd.SysProcAttr = SysProcAttr("", false)
tk := NewTreeKill(cmd)
require.NoError(t, cmd.Start())
defer func() { _ = cmd.Wait() }()
killer, err := tk.Capture(cmd.Process)
require.NoError(t, err)
require.NotNil(t, killer)
require.NoError(t, cmd.Cancel())
require.NoError(t, killer.Close())
}

View File

@@ -983,3 +983,75 @@ func TestReporter_StopHeartbeats(t *testing.T) {
assert.Greater(t, updateTaskCalls.Load(), beforeStop,
"Close() must still send a final UpdateTask after StopHeartbeats")
}
func TestAppendIfNotNil(t *testing.T) {
var s []*int
s = appendIfNotNil(s, nil)
assert.Empty(t, s)
v := 7
s = appendIfNotNil(s, &v)
require.Len(t, s, 1)
assert.Equal(t, &v, s[0])
s = appendIfNotNil(s, nil)
require.Len(t, s, 1)
}
func TestReporter_Levels(t *testing.T) {
assert.Equal(t, log.AllLevels, (&Reporter{}).Levels())
}
func TestReporter_Result(t *testing.T) {
r := &Reporter{state: &runnerv1.TaskState{Result: runnerv1.Result_RESULT_SUCCESS}}
assert.Equal(t, runnerv1.Result_RESULT_SUCCESS, r.Result())
}
func TestReporter_SetOutputs(t *testing.T) {
r := &Reporter{state: &runnerv1.TaskState{}}
r.SetOutputs(map[string]string{"foo": "bar"})
got, ok := r.outputs.Load("foo")
require.True(t, ok)
assert.Equal(t, "bar", got)
// first value wins: a later write to the same key is ignored
r.SetOutputs(map[string]string{"foo": "baz"})
got, _ = r.outputs.Load("foo")
assert.Equal(t, "bar", got)
// keys longer than 255 chars are dropped
longKey := strings.Repeat("k", 256)
r.SetOutputs(map[string]string{longKey: "v"})
_, ok = r.outputs.Load(longKey)
assert.False(t, ok)
}
func TestReporter_EffectiveCloseTimeout(t *testing.T) {
assert.Equal(t, 10*time.Second, (&Reporter{}).effectiveCloseTimeout())
assert.Equal(t, 5*time.Second, (&Reporter{closeTimeout: 5 * time.Second}).effectiveCloseTimeout())
}
func TestReporter_ParseResult(t *testing.T) {
r := &Reporter{}
tests := []struct {
name string
input any
want runnerv1.Result
wantOk bool
}{
{"job result string", "success", runnerv1.Result_RESULT_SUCCESS, true},
{"failure string", "failure", runnerv1.Result_RESULT_FAILURE, true},
{"step result stringer", runnerv1.Result_RESULT_SKIPPED, runnerv1.Result_RESULT_UNSPECIFIED, false},
{"unknown string", "bogus", runnerv1.Result_RESULT_UNSPECIFIED, false},
{"unsupported type", 123, runnerv1.Result_RESULT_UNSPECIFIED, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := r.parseResult(tt.input)
assert.Equal(t, tt.wantOk, ok)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,13 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package ver
import "testing"
func TestVersion(t *testing.T) {
// version defaults to "dev" and is overridden at build time via -ldflags
if got := Version(); got != version {
t.Errorf("Version() = %q, want %q", got, version)
}
}