Files
runner/internal/app/run/post_task_script_test.go
T
silverwind d6f5b68a0d enhance: test on Windows and fix what it found (#1348)
Adds a weekly and on-demand workflow that runs the cross-compiled tests on Windows Server 2025 Core in a [dockur/windows](https://github.com/dockur/windows) QEMU VM, and fixes the Windows bugs it found in host and UNC paths, env handling, shell quoting, post-task scripts, health checks, checkout, action symlinks, step summaries, Git client certificates and docker mode from a Windows host.

Host-mode steps now emit UTF-8, `bash` falls back to Git's bash instead of the WSL launcher, the runner can run as a native Windows service, steps can keep background helpers with `RUNNER_TRACKING_ID`, and Windows arm64 binaries are released.

Fixes: https://gitea.com/gitea/runner/issues/1339
Fixes: https://gitea.com/gitea/runner/issues/637
Fixes: https://gitea.com/gitea/runner/issues/1172
Fixes: https://gitea.com/gitea/runner/issues/347
Reviewed-on: https://gitea.com/gitea/runner/pulls/1348
Reviewed-by: bircni <bircni@icloud.com>
2026-11-03 01:26:10 +00:00

173 lines
5.4 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package run
import (
"context"
"os"
"path/filepath"
"runtime"
"testing"
"time"
"gitea.com/gitea/runner/act/common/logging"
"gitea.com/gitea/runner/act/common/logging/logtest"
"gitea.com/gitea/runner/internal/pkg/config"
"gitea.com/gitea/runner/internal/pkg/metrics"
"gitea.com/gitea/runner/internal/pkg/report"
runnerv1 "gitea.dev/actionslib/runner/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
)
func TestRunPostTaskScriptSkippedWhenEmpty(t *testing.T) {
r := &Runner{
cfg: &config.Config{},
}
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
taskCtx, err := structpb.NewStruct(map[string]any{})
require.NoError(t, err)
task := &runnerv1.Task{Id: 1, Context: taskCtx}
reporter := report.NewReporter(ctx, cancel, nil, task, r.cfg)
require.NotPanics(t, func() {
r.runPostTaskScript(ctx, reporter, task, "/workspace/owner/repo")
})
}
func TestRunPostTaskScriptNonZeroExitDoesNotPanic(t *testing.T) {
hook := logtest.SetDefault(t)
dir := t.TempDir()
scriptPath := filepath.Join(dir, "fail.sh")
script := "#!/bin/sh\nexit 2\n"
if runtime.GOOS == "windows" {
scriptPath = filepath.Join(dir, "fail.cmd")
script = "@exit /b 2\r\n"
}
require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o700))
cfg, err := config.LoadDefault("")
require.NoError(t, err)
cfg.Runner.PostTaskScript = scriptPath
r := &Runner{cfg: cfg}
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
taskCtx, err := structpb.NewStruct(map[string]any{})
require.NoError(t, err)
task := &runnerv1.Task{Id: 1, Context: taskCtx}
reporter := report.NewReporter(ctx, cancel, nil, task, cfg)
require.NotPanics(t, func() {
r.runPostTaskScript(ctx, reporter, task, "/workspace/owner/repo")
})
require.NotNil(t, hook.LastRecord())
assert.Contains(t, hook.LastRecord().Message, "exited with code 2")
}
func TestPostTaskScriptContextUsesFullTimeout(t *testing.T) {
const timeout = 5 * time.Minute
// A task context that finished close to its own deadline must not truncate the
// script's budget: the script should still get its full configured timeout.
near, cancelNear := context.WithTimeout(t.Context(), time.Second)
defer cancelNear()
scriptCtx, cancel := postTaskScriptContext(near, timeout)
defer cancel()
deadline, ok := scriptCtx.Deadline()
require.True(t, ok)
assert.Greater(t, time.Until(deadline), time.Minute, "script timeout truncated to task deadline")
// An already-cancelled task context must not cancel the script either.
cancelledCtx, cancelIt := context.WithCancel(t.Context())
cancelIt()
scriptCtx2, cancel2 := postTaskScriptContext(cancelledCtx, timeout)
defer cancel2()
assert.NoError(t, scriptCtx2.Err(), "script context inherited the cancelled task context")
}
func TestPostTaskScriptEnv(t *testing.T) {
cfg, err := config.LoadDefault("")
require.NoError(t, err)
r := &Runner{
cfg: cfg,
envs: map[string]string{"BASE": "1"},
}
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
taskCtx, err := structpb.NewStruct(map[string]any{
"run_id": "99",
"repository": "acme/widget",
})
require.NoError(t, err)
task := &runnerv1.Task{Id: 3, Context: taskCtx}
reporter := report.NewReporter(ctx, cancel, nil, task, cfg)
setReporterJobResult(t, reporter, runnerv1.Result_RESULT_FAILURE)
env := r.postTaskScriptEnv(reporter, task, "/tmp/workspace")
assert.Equal(t, "1", env["BASE"])
assert.Equal(t, "3", env["GITEA_TASK_ID"])
assert.Equal(t, "99", env["GITEA_RUN_ID"])
assert.Equal(t, "acme/widget", env["GITEA_REPOSITORY"])
assert.Equal(t, "/tmp/workspace", env["GITEA_WORKSPACE"])
assert.Equal(t, "failure", env["GITEA_JOB_RESULT"])
}
func TestRunPostTaskScriptIntegration(t *testing.T) {
dir := filepath.Join(t.TempDir(), "script directory")
require.NoError(t, os.Mkdir(dir, 0o700))
outFile := filepath.Join(dir, "out.txt")
scriptPath := filepath.Join(dir, "post-task.sh")
script := "#!/bin/sh\nprintf '%s %s %s' \"$GITEA_TASK_ID\" \"$GITEA_JOB_RESULT\" \"$CUSTOM\" > \"" + outFile + "\"\n"
if runtime.GOOS == "windows" {
scriptPath = filepath.Join(dir, "post-task.cmd")
script = "@echo off\r\n<nul set /p \"=%GITEA_TASK_ID% %GITEA_JOB_RESULT% %CUSTOM%\" > \"" + outFile + "\"\r\nexit /b 0\r\n"
}
require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o700))
cfg, err := config.LoadDefault("")
require.NoError(t, err)
cfg.Runner.PostTaskScript = scriptPath
r := &Runner{
cfg: cfg,
envs: map[string]string{"CUSTOM": "runner-env"},
}
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
taskCtx, err := structpb.NewStruct(map[string]any{})
require.NoError(t, err)
task := &runnerv1.Task{Id: 11, Context: taskCtx}
reporter := report.NewReporter(ctx, cancel, nil, task, cfg)
setReporterJobResult(t, reporter, runnerv1.Result_RESULT_SUCCESS)
r.runPostTaskScript(ctx, reporter, task, "/workspace/acme/repo")
content, err := os.ReadFile(outFile)
require.NoError(t, err)
assert.Equal(t, "11 success runner-env", string(content))
}
func setReporterJobResult(t *testing.T, reporter *report.Reporter, result runnerv1.Result) {
t.Helper()
require.NoError(t, reporter.Handle(t.Context(), logging.Record{
Time: time.Now(),
Message: "job finished",
Attrs: []logging.Attr{
{Key: "stage", Value: "Post"},
{Key: "jobResult", Value: metrics.ResultToStatusLabel(result)},
},
}))
}