mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-04 06:25:32 +00:00
1. Restructures the README into installation, quickstart, deployment, configuration, jobs and operation, and shortens it to user-facing prose. Option details are left to `config.example.yaml` and `docs/`, existing heading anchors are kept where possible. 2. FreeBSD builds used the Docker stub, so `docker://` labels, services and Docker actions failed there. The Docker code works on FreeBSD against a remote daemon, so it is enabled, and host jobs report `runner.os` as `FreeBSD`. 3. `make build` on Windows now writes `gitea-runner.exe`, taking the suffix from `go env GOEXE`. Reviewed-on: https://gitea.com/gitea/runner/pulls/1367 Reviewed-by: Lunny Xiao <[email protected]> Co-authored-by: silverwind <[email protected]>
241 lines
7.4 KiB
Go
241 lines
7.4 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// Copyright 2022 The nektos/act Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package container
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"testing/synctest"
|
|
"time"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
"gitea.com/gitea/runner/act/common/logging"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var _ ExecutionsEnvironment = &HostEnvironment{}
|
|
|
|
func TestActionPlatformNames(t *testing.T) {
|
|
assert.Equal(t, []string{"Linux", "macOS", "Windows", "FreeBSD"}, []string{goOsToActionOs("linux"), goOsToActionOs("darwin"), goOsToActionOs("windows"), goOsToActionOs("freebsd")})
|
|
assert.Equal(t, []string{"X86", "X64", "ARM", "ARM64"}, []string{goArchToActionArch("386"), goArchToActionArch("amd64"), goArchToActionArch("arm"), goArchToActionArch("arm64")})
|
|
}
|
|
|
|
func TestCopyDir(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
useGitIgnore, skipGitDir bool
|
|
}{
|
|
{name: "git dir kept for a workspace copy"},
|
|
{name: "git dir skipped for an action copy", skipGitDir: true},
|
|
{name: "unfiltered outside a git repository", useGitIgnore: true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
src := filepath.Join(t.TempDir(), "action")
|
|
require.NoError(t, os.MkdirAll(filepath.Join(src, ".git", "objects"), 0o700))
|
|
require.NoError(t, os.WriteFile(filepath.Join(src, ".git", "objects", "pack"), []byte("x"), 0o600))
|
|
require.NoError(t, os.WriteFile(filepath.Join(src, "action.yml"), []byte("name: x\n"), 0o600))
|
|
|
|
dest := filepath.Join(t.TempDir(), "dest")
|
|
require.NoError(t, (&HostEnvironment{}).CopyDir(dest, src+string(filepath.Separator), test.useGitIgnore, test.skipGitDir)(t.Context()))
|
|
|
|
assert.FileExists(t, filepath.Join(dest, "action.yml"))
|
|
if test.skipGitDir {
|
|
assert.NoDirExists(t, filepath.Join(dest, ".git"))
|
|
} else {
|
|
assert.FileExists(t, filepath.Join(dest, ".git", "objects", "pack"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetContainerArchive(t *testing.T) {
|
|
ctx := t.Context()
|
|
e := &HostEnvironment{Path: t.TempDir()}
|
|
expectedContent := []byte("sdde/7sh")
|
|
err := os.WriteFile(filepath.Join(e.Path, "action.yml"), expectedContent, 0o600)
|
|
require.NoError(t, err)
|
|
archive, err := e.GetContainerArchive(ctx, e.Path)
|
|
require.NoError(t, err)
|
|
defer archive.Close()
|
|
reader := tar.NewReader(archive)
|
|
h, err := reader.Next()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "action.yml", h.Name)
|
|
content, err := io.ReadAll(reader)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expectedContent, content)
|
|
_, err = reader.Next()
|
|
assert.ErrorIs(t, err, io.EOF)
|
|
}
|
|
|
|
func TestHostEnvironmentExecExitCode(t *testing.T) {
|
|
t.Parallel()
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("uses POSIX shell")
|
|
}
|
|
for name, allocatePTY := range map[string]bool{"without PTY": false, "with PTY": true} {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
synctest.Test(t, func(t *testing.T) {
|
|
reader, writer := io.Pipe()
|
|
defer reader.Close()
|
|
e := &HostEnvironment{Path: t.TempDir(), StdOut: writer, AllocatePTY: allocatePTY}
|
|
t.Cleanup(func() { assert.NoError(t, e.Remove()(t.Context())) })
|
|
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
defer writer.Close()
|
|
result <- e.Exec([]string{"sh", "-c", "printf 'failed\\n'; exit 3"}, map[string]string{"PATH": os.Getenv("PATH")}, "", "")(t.Context())
|
|
}()
|
|
first := make([]byte, 1)
|
|
_, err := io.ReadFull(reader, first)
|
|
require.NoError(t, err)
|
|
synctest.Wait()
|
|
select {
|
|
case err := <-result:
|
|
t.Fatalf("Exec returned before its output was consumed: %v", err)
|
|
default:
|
|
}
|
|
remaining, err := io.ReadAll(reader)
|
|
require.NoError(t, err)
|
|
err = <-result
|
|
var exitErr ExitCodeError
|
|
require.ErrorAs(t, err, &exitErr)
|
|
assert.Equal(t, ExitCodeError(3), exitErr)
|
|
assert.Equal(t, "Process completed with exit code 3.", err.Error())
|
|
assert.Equal(t, "failed\n", string(first)+string(remaining))
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHostEnvironmentAllocatePTY(t *testing.T) {
|
|
t.Parallel()
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("uses POSIX shell")
|
|
}
|
|
for _, tc := range []struct {
|
|
name string
|
|
allocPTY bool
|
|
expect string
|
|
}{
|
|
{name: "off", allocPTY: false, expect: "NOTTY"},
|
|
{name: "on", allocPTY: true, expect: "TTY"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
buf := &bytes.Buffer{}
|
|
e := &HostEnvironment{
|
|
Path: t.TempDir(),
|
|
StdOut: buf,
|
|
AllocatePTY: tc.allocPTY,
|
|
}
|
|
|
|
err := e.Exec(
|
|
[]string{"sh", "-c", "[ -t 1 ] && printf TTY || printf NOTTY"},
|
|
map[string]string{"PATH": os.Getenv("PATH")}, "", "",
|
|
)(t.Context())
|
|
require.NoError(t, err)
|
|
got := strings.TrimSpace(strings.ReplaceAll(buf.String(), "\r", ""))
|
|
assert.Equal(t, tc.expect, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHostEnvironmentRemoveWorkdir(t *testing.T) {
|
|
for name, owned := range map[string]bool{"preserves caller workdir": false, "cleans owned workdir": true} {
|
|
t.Run(name, func(t *testing.T) {
|
|
miscRoot, workdir := t.TempDir(), t.TempDir()
|
|
e := &HostEnvironment{
|
|
Path: filepath.Join(miscRoot, "hostexecutor"),
|
|
Workdir: workdir,
|
|
CleanWorkdir: owned,
|
|
Root: miscRoot,
|
|
}
|
|
require.NoError(t, os.MkdirAll(e.Path, 0o700))
|
|
require.NoError(t, os.MkdirAll(filepath.Join(miscRoot, "tmp"), 0o700))
|
|
require.NoError(t, e.Remove()(t.Context()))
|
|
assert.NoDirExists(t, miscRoot)
|
|
_, err := os.Stat(workdir)
|
|
if owned {
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemoveAllWithContextDoesNotHangOnStuckDelete(t *testing.T) {
|
|
synctest.Test(t, func(t *testing.T) {
|
|
release := make(chan struct{})
|
|
stubDone := make(chan struct{})
|
|
|
|
orig := removeAll
|
|
removeAll = func(string) error {
|
|
defer close(stubDone)
|
|
<-release
|
|
return nil
|
|
}
|
|
t.Cleanup(func() {
|
|
close(release)
|
|
<-stubDone
|
|
removeAll = orig
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(t.Context(), 100*time.Millisecond)
|
|
defer cancel()
|
|
|
|
err := removeAllWithContext(ctx, t.TempDir())
|
|
require.ErrorIs(t, err, context.DeadlineExceeded)
|
|
})
|
|
}
|
|
|
|
func TestHostEnvironmentRemoveDoesNotHang(t *testing.T) {
|
|
synctest.Test(t, func(t *testing.T) {
|
|
origTimeout, origRemoveAll := hostCleanupTimeout, removeAll
|
|
hostCleanupTimeout = 100 * time.Millisecond
|
|
release, stubDone := make(chan struct{}), make(chan struct{})
|
|
removeAll = func(string) error {
|
|
defer close(stubDone)
|
|
<-release
|
|
return nil
|
|
}
|
|
t.Cleanup(func() {
|
|
close(release)
|
|
<-stubDone
|
|
hostCleanupTimeout, removeAll = origTimeout, origRemoveAll
|
|
})
|
|
e := &HostEnvironment{Path: filepath.Join(t.TempDir(), "misc", "hostexecutor"), StdOut: os.Stdout}
|
|
require.NoError(t, os.MkdirAll(e.Path, 0o700))
|
|
done := make(chan error, 1)
|
|
go func() { done <- e.Remove()(common.WithLogger(t.Context(), logging.Default())) }()
|
|
select {
|
|
case err := <-done:
|
|
require.NoError(t, err)
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("Remove() hung on a stuck path removal")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestJoinPath(t *testing.T) {
|
|
assert.Equal(t, "//server/share/act/workflow/envs.txt", JoinPath("//server/share/act", "workflow/envs.txt"))
|
|
assert.Equal(t, "/var/run/act/workflow/envs.txt", JoinPath("/var/run/act/", "workflow", "envs.txt"))
|
|
for _, root := range []string{"//server/share/act", "/var/run/act", "C:/act"} {
|
|
assert.Equal(t, root+"/actions/key/main.go.out", JoinPath(root+"/actions/key", "main.go.out"))
|
|
assert.Equal(t, root+"/workflow", JoinPath(root+"/workflow/outputcmd.txt", ".."))
|
|
}
|
|
}
|