Files
Runner/e2e/main_test.go
T
bircni 8260e2def2 test: add end-to-end Gitea compatibility suite (#1180)
Adds a real-Gitea compatibility suite against stable and nightly to catch runner and API drift before release.

The suite shares one Gitea and regular runner, using repository runners only for cache v1/v2 and ephemeral behavior. It covers registration, payload and log encoding, secrets, variables, services, artifacts, outputs, matrices, cache, dispatch, live logs, cancellation, and ephemeral teardown.

CI runs both images in parallel. Warm local timings:

| Image | Before | After |
| --- | ---: | ---: |
| `gitea/gitea:latest` | ~70s | 26.54s |
| `gitea/gitea:main-nightly` | ~54s | 22.53s |

The former serial matrix took about 2 minutes. Parallel suite execution is now bounded by the slower ~26.5-second variant.

Shared Renovate matcher: https://gitea.com/gitea/renovate-config/pulls/552

---------

Co-authored-by: silverwind <me@silverwind.io>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1180
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Co-authored-by: bircni <bircni@icloud.com>
2026-08-23 12:32:22 +00:00

163 lines
3.8 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build e2e
package e2e
import (
"context"
"fmt"
"os"
"regexp"
"strings"
"testing"
"time"
"gitea.com/gitea/runner/internal/app/poll"
)
const runTimeout = 3 * time.Minute
var nonRepoChars = regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
func repoName(t *testing.T) string {
return strings.ToLower(nonRepoChars.ReplaceAllString(t.Name(), "-"))
}
var fixture *GiteaFixture
var skipReason string
func TestMain(m *testing.M) {
os.Exit(runSuite(m))
}
func runSuite(m *testing.M) int {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
cli, err := dockerClient(ctx)
if err != nil {
skipReason = fmt.Sprintf("docker unavailable: %v", err)
return m.Run()
}
f, err := StartGitea(ctx, cli)
if err != nil {
fmt.Fprintf(os.Stderr, "start gitea fixture: %v\n", err)
return 1
}
fixture = f
defer func() { _ = fixture.Close(context.Background()) }()
fmt.Fprintf(os.Stderr, "gitea fixture: image=%s version=%s\n", fixture.image, fixture.version)
return m.Run()
}
func newScenario(t *testing.T) (*GiteaAPI, string) {
t.Helper()
repo := repoName(t)
api := &GiteaAPI{baseURL: fixture.baseURL, token: fixture.adminToken}
if err := api.CreateRepo(t.Context(), repo); err != nil {
t.Fatalf("create repo: %v", err)
}
return api, repo
}
func startIsolatedScenario(t *testing.T, workflow, label string, options runnerOptions) (*GiteaAPI, string, *poll.Poller) {
t.Helper()
api, repo := newScenario(t)
poller := startRunner(t, repo, label, options)
pushWorkflow(t, api, repo, workflow)
return api, repo, poller
}
func pushWorkflow(t *testing.T, api *GiteaAPI, repo, workflow string) {
t.Helper()
content, err := os.ReadFile("testdata/workflows/" + workflow)
if err != nil {
t.Fatalf("read workflow fixture %s: %v", workflow, err)
}
if err := api.CreateFile(t.Context(), repo, ".gitea/workflows/"+workflow, string(content), "add "+workflow); err != nil {
t.Fatalf("push workflow %s: %v", workflow, err)
}
}
func requireSuccess(t *testing.T, api *GiteaAPI, repo string, runID int64) {
t.Helper()
completed, err := api.WaitForRunConclusion(t.Context(), repo, runID, runTimeout)
if err != nil {
dumpRunLogs(t, api, repo, runID)
t.Fatalf("wait for run: %v", err)
}
if completed.Conclusion != "success" {
dumpRunLogs(t, api, repo, runID)
t.Fatalf("run concluded %q, want success", completed.Conclusion)
}
}
func runLogs(t *testing.T, api *GiteaAPI, repo string, runID int64) string {
t.Helper()
ctx := t.Context()
jobs, err := api.Jobs(ctx, repo, runID)
if err != nil {
t.Fatalf("list jobs: %v", err)
}
var all strings.Builder
for _, job := range jobs {
logs, err := api.JobLogs(ctx, repo, job.ID)
if err != nil {
t.Fatalf("job logs for %s: %v", job.Name, err)
}
all.WriteString(logs)
}
return all.String()
}
func dumpRunLogs(t *testing.T, api *GiteaAPI, repo string, runID int64) {
t.Helper()
ctx := t.Context()
jobs, err := api.Jobs(ctx, repo, runID)
if err != nil {
t.Logf("dump run %d: list jobs: %v", runID, err)
return
}
for _, job := range jobs {
logs, err := api.JobLogs(ctx, repo, job.ID)
if err != nil {
t.Logf("job %q (%s): logs unavailable: %v", job.Name, job.Conclusion, err)
continue
}
t.Logf("job %q concluded %q:\n%s", job.Name, job.Conclusion, logs)
}
}
func waitForRun(t *testing.T, api *GiteaAPI, repo string) *ActionRun {
t.Helper()
ctx, cancel := context.WithTimeout(t.Context(), time.Minute)
defer cancel()
for {
runs, err := api.Runs(ctx, repo)
if err != nil {
t.Fatalf("get latest run: %v", err)
}
if len(runs) > 0 {
return &runs[0]
}
select {
case <-ctx.Done():
t.Fatalf("no run appeared for %s within timeout", repo)
case <-time.After(pollInterval):
}
}
}