mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-05 06:45:44 +00:00
Compare commits
1
Commits
v5.4.0
...
lean-tests
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e6bf67d7d |
No files matched your search
@@ -851,6 +851,9 @@ func TestHandler_touchCacheSkipsFreshCompletedEntries(t *testing.T) {
|
||||
func newTestHandler(t *testing.T, policy Policy, entries ...*Cache) *Handler {
|
||||
t.Helper()
|
||||
handler := startTestHandler(t, Options{Policy: policy})
|
||||
if len(entries) == 0 {
|
||||
return handler
|
||||
}
|
||||
|
||||
db, err := handler.openDB()
|
||||
require.NoError(t, err)
|
||||
@@ -940,6 +943,10 @@ func TestHandler_RejectsBearer(t *testing.T) {
|
||||
|
||||
func TestHandler_UnregisterRevokes(t *testing.T) {
|
||||
t.Parallel()
|
||||
handler, err := StartHandler(Options{Dir: filepath.Join(t.TempDir(), "artifactcache")})
|
||||
require.NoError(t, err)
|
||||
defer handler.Close()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
token string
|
||||
@@ -950,9 +957,6 @@ func TestHandler_UnregisterRevokes(t *testing.T) {
|
||||
{"reference counted", "shared", "v", 2},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
handler, err := StartHandler(Options{Dir: filepath.Join(t.TempDir(), "artifactcache")})
|
||||
require.NoError(t, err)
|
||||
defer handler.Close()
|
||||
revokers := make([]func(), tc.registrations)
|
||||
for i := range revokers {
|
||||
revokers[i] = handler.RegisterJob(tc.token, JobCredential{Repo: testRepo})
|
||||
@@ -1049,9 +1053,8 @@ func TestHandler_ArtifactSignature(t *testing.T) {
|
||||
|
||||
base := handler.ExternalURL() + apiPath
|
||||
good := signArtifactURL(handler, 1)
|
||||
other, err := StartHandler(Options{Dir: filepath.Join(t.TempDir(), "artifactcache2")})
|
||||
require.NoError(t, err)
|
||||
defer other.Close()
|
||||
secret := bytes.Clone(handler.secret)
|
||||
secret[0] ^= 1
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
@@ -1060,7 +1063,7 @@ func TestHandler_ArtifactSignature(t *testing.T) {
|
||||
{"missing signature", fmt.Sprintf("%s/artifacts/%d", base, 1)},
|
||||
{"tampered signature", good[:len(good)-4] + "dead"},
|
||||
{"expired signature", handler.signedArtifactURL(JobCredential{}, 1, time.Now().Add(-time.Second))},
|
||||
{"signature from a different server", base + strings.SplitN(signArtifactURL(other, 1), apiPath, 2)[1]},
|
||||
{"signature from a different server", base + strings.SplitN(signArtifactURL(&Handler{secret: secret}, 1), apiPath, 2)[1]},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
resp, err := testClient.Get(tc.url)
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestFetchAction(t *testing.T) {
|
||||
|
||||
root := t.TempDir()
|
||||
remote := filepath.Join(root, "org", "action")
|
||||
require.NoError(t, gitCmd("init", "--initial-branch=main", remote))
|
||||
require.NoError(t, os.CopyFS(remote, os.DirFS(filepath.Join(gitFixtureDir, "work"))))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(remote, "action.yml"), []byte("name: action\n"), 0o644))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(remote, "run.sh"), []byte("#!/bin/sh\n"), 0o755))
|
||||
require.NoError(t, os.Symlink("action.yml", filepath.Join(remote, "link")))
|
||||
@@ -53,6 +53,7 @@ func TestFetchAction(t *testing.T) {
|
||||
var archiveRequests []http.Header
|
||||
var mirror *httptest.Server
|
||||
archiveCache := map[string][]byte{}
|
||||
revisions := map[string]string{}
|
||||
requests, archives, failures := 0, "", 0
|
||||
handler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
mu.Lock()
|
||||
@@ -79,7 +80,14 @@ func TestFetchAction(t *testing.T) {
|
||||
http.Redirect(writer, request, mirror.URL+request.URL.Path, http.StatusFound)
|
||||
return
|
||||
}
|
||||
sha, _ := gitOutput(request.Context(), remote, "rev-parse", strings.TrimSuffix(ref, ".tar.gz"))
|
||||
sha, ok := revisions[ref]
|
||||
if !ok {
|
||||
var err error
|
||||
sha, err = gitOutput(request.Context(), remote, "rev-parse", strings.TrimSuffix(ref, ".tar.gz"))
|
||||
if err == nil {
|
||||
revisions[ref] = sha
|
||||
}
|
||||
}
|
||||
etag := fmt.Sprintf(`W/"%s-tar.gz-%t"`, sha, archives != "unprefixed")
|
||||
if request.Header.Get("If-None-Match") == etag {
|
||||
writer.WriteHeader(http.StatusNotModified)
|
||||
@@ -208,6 +216,9 @@ func TestFetchAction(t *testing.T) {
|
||||
require.NoError(t, gitCmd("-C", remote, "commit", "--allow-empty", "-m", "moved"))
|
||||
require.NoError(t, gitCmd("-C", remote, "tag", "--force", "v1"))
|
||||
expectedSha = gitRevParse(t, remote, "v1")
|
||||
mu.Lock()
|
||||
clear(revisions)
|
||||
mu.Unlock()
|
||||
moved, movedSha, err := FetchAction(t.Context(), input)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSha, movedSha)
|
||||
@@ -253,7 +264,7 @@ func TestFetchAction(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("downloads again over a tree whose files changed size or that has no record, not under a job copying it", func(t *testing.T) {
|
||||
sha := gitRevParse(t, remote, "main")
|
||||
sha := expectedSha
|
||||
for _, mode := range []string{"prefixed", "none"} {
|
||||
serve(mode, 0)
|
||||
input := newInput(sha)
|
||||
|
||||
+25
-31
@@ -112,8 +112,7 @@ func TestFindGitMetadataOfSHA256RepositoryUsesOrigin(t *testing.T) {
|
||||
t.Parallel()
|
||||
basedir := t.TempDir()
|
||||
const remoteURL = "https://github.com/owner/repo.git"
|
||||
require.NoError(t, gitCmd("init", "--object-format=sha256", "--initial-branch=main", basedir))
|
||||
require.NoError(t, os.RemoveAll(filepath.Join(basedir, ".git", "hooks")))
|
||||
require.NoError(t, gitCmd("init", "--template=", "--object-format=sha256", "--initial-branch=main", basedir))
|
||||
require.NoError(t, gitCmd("-C", basedir, "commit", "--allow-empty", "-m", "init"))
|
||||
require.NoError(t, gitCmd("-C", basedir, "remote", "add", "origin", remoteURL))
|
||||
require.NoError(t, gitCmd("-C", basedir, "config", "--add", "remote.origin.url", "https://github.com/other/repo.git"))
|
||||
@@ -205,16 +204,13 @@ func TestGitCloneExecutor(t *testing.T) {
|
||||
require.NoError(t, gitCmd("-C", workDir, "add", "action.sh", ".gitattributes"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "commit", "-m", "initial"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "tag", "v2"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "origin", "v2"))
|
||||
|
||||
require.NoError(t, gitCmd("-C", workDir, "checkout", "-b", "act-fails"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "commit", "--allow-empty", "-m", "branch-commit"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "origin", "act-fails"))
|
||||
|
||||
fullSha := gitRevParse(t, workDir, "main")
|
||||
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "origin", "main:refs/heads/"+fullSha[:4]))
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main", "v2", "act-fails", "main:refs/heads/"+fullSha[:4]))
|
||||
|
||||
hostConfig := filepath.Join(t.TempDir(), "gitconfig")
|
||||
require.NoError(t, os.WriteFile(hostConfig, []byte("[core]\n\tautocrlf = true\n\teol = crlf\n[url "+strconv.Quote(remoteDir)+"]\n\tinsteadOf = https://example.invalid/action\n"), 0o644))
|
||||
@@ -282,10 +278,10 @@ func TestGitCloneExecutor(t *testing.T) {
|
||||
func TestGitCloneExecutorReclonesWhenOriginURLChanges(t *testing.T) {
|
||||
t.Parallel()
|
||||
createRemote := func(message string) string {
|
||||
remoteDir, workDir := newGitRemote(t, message)
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main"))
|
||||
|
||||
return remoteDir
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, os.CopyFS(dir, os.DirFS(filepath.Join(gitFixtureDir, "work"))))
|
||||
require.NoError(t, gitCmd("-C", dir, "commit", "--allow-empty", "-m", message))
|
||||
return dir
|
||||
}
|
||||
|
||||
oldRemoteDir := createRemote("old-action")
|
||||
@@ -405,31 +401,30 @@ func TestGitCloneExecutorShallow(t *testing.T) {
|
||||
remoteDir, workDir := newGitRemote(t, "c1", "c2", "c3")
|
||||
require.NoError(t, gitCmd("-C", workDir, "tag", "v1"))
|
||||
unadvertisedSHA := gitRevParse(t, workDir, "HEAD~1")
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "origin", "v1"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main", "v1"))
|
||||
|
||||
shallowMarker := func(dir string) string { return filepath.Join(dir, ".git", "shallow") }
|
||||
|
||||
for name, ref := range map[string]string{"branch is cloned shallowly": "main", "tag is cloned shallowly": "v1"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, NewGitCloneExecutor(NewGitCloneExecutorInput{
|
||||
URL: remoteDir, Ref: ref, Dir: dir, Depth: 1,
|
||||
})(t.Context()))
|
||||
assert.FileExists(t, shallowMarker(dir), "clone should be shallow")
|
||||
assert.Equal(t, 1, gitRevCount(t, dir), "only the tip commit should be present")
|
||||
assert.Equal(t, "c3", gitHeadSubject(t, dir))
|
||||
assert.Equal(t, []string{"c3"}, gitSubjects(t, dir), "only the tip commit should be present")
|
||||
})
|
||||
}
|
||||
|
||||
unadvertisedRemote := func(t *testing.T, allowUnadvertised bool) string {
|
||||
remote := filepath.Join(t.TempDir(), "remote.git")
|
||||
require.NoError(t, gitCmd("clone", "--bare", remoteDir, remote))
|
||||
require.NoError(t, gitCmd("-C", remote, "config", "uploadpack.allowAnySHA1InWant", strconv.FormatBool(allowUnadvertised)))
|
||||
require.NoError(t, gitCmd("clone", "--bare", "--template=", "--config", "uploadpack.allowAnySHA1InWant="+strconv.FormatBool(allowUnadvertised), remoteDir, remote))
|
||||
return remote
|
||||
}
|
||||
|
||||
t.Run("commit hash resolves when the remote accepts a direct shallow fetch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, NewGitCloneExecutor(NewGitCloneExecutorInput{
|
||||
URL: unadvertisedRemote(t, false), Ref: unadvertisedSHA, Dir: dir, Depth: 1,
|
||||
@@ -438,6 +433,7 @@ func TestGitCloneExecutorShallow(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("commit hash is fetched shallowly, moved to another hash, then reused without reaching the remote", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
remote := unadvertisedRemote(t, true)
|
||||
dir := t.TempDir()
|
||||
cloneAt := func(ref string) error {
|
||||
@@ -448,7 +444,7 @@ func TestGitCloneExecutorShallow(t *testing.T) {
|
||||
|
||||
require.NoError(t, cloneAt(unadvertisedSHA))
|
||||
assert.FileExists(t, shallowMarker(dir))
|
||||
assert.Equal(t, 1, gitRevCount(t, dir))
|
||||
assert.Len(t, gitSubjects(t, dir), 1)
|
||||
assert.Equal(t, unadvertisedSHA, gitRevParse(t, dir, "HEAD"))
|
||||
|
||||
olderSHA := gitRevParse(t, workDir, "HEAD~2")
|
||||
@@ -461,6 +457,8 @@ func TestGitCloneExecutorShallow(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("moving hexadecimal-named branch updates while staying shallow", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
remoteDir, workDir := newGitRemote(t, "c2", "c3")
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "origin", "main:deadbeef"))
|
||||
dir := t.TempDir()
|
||||
clone := NewGitCloneExecutor(NewGitCloneExecutorInput{
|
||||
@@ -473,19 +471,17 @@ func TestGitCloneExecutorShallow(t *testing.T) {
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "origin", "main:deadbeef"))
|
||||
|
||||
require.NoError(t, clone(t.Context()))
|
||||
assert.Equal(t, "c4", gitHeadSubject(t, dir), "reused shallow clone should update to the new tip")
|
||||
assert.Equal(t, []string{"c4"}, gitSubjects(t, dir), "reused shallow clone should update to the new tip")
|
||||
assert.FileExists(t, shallowMarker(dir), "repo should remain shallow after update")
|
||||
assert.Equal(t, 1, gitRevCount(t, dir))
|
||||
})
|
||||
|
||||
t.Run("sha256 full hash is cloned shallowly, reused without the remote, and its 40-character prefix is short", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
remote := filepath.Join(t.TempDir(), "remote.git")
|
||||
require.NoError(t, gitCmd("init", "--bare", "--object-format=sha256", "--initial-branch=main", remote))
|
||||
work := t.TempDir()
|
||||
require.NoError(t, gitCmd("init", "--object-format=sha256", "--initial-branch=main", work))
|
||||
require.NoError(t, gitCmd("-C", work, "remote", "add", "origin", remote))
|
||||
require.NoError(t, gitCmd("init", "--template=", "--object-format=sha256", "--initial-branch=main", work))
|
||||
require.NoError(t, gitCmd("-C", work, "commit", "--allow-empty", "-m", "pinned"))
|
||||
require.NoError(t, gitCmd("-C", work, "push", "origin", "main"))
|
||||
require.NoError(t, gitCmd("clone", "--bare", "--template=", work, remote))
|
||||
sha := gitRevParse(t, work, "HEAD")
|
||||
dir := t.TempDir()
|
||||
clone := NewGitCloneExecutor(NewGitCloneExecutorInput{URL: remote, Ref: sha, Dir: dir, Depth: 1})
|
||||
@@ -504,11 +500,11 @@ func TestGitCloneExecutorPinnedHashIgnoresShadowingRef(t *testing.T) {
|
||||
pinned := gitRevParse(t, workDir, "HEAD")
|
||||
require.NoError(t, gitCmd("-C", workDir, "commit", "--allow-empty", "-m", "decoy"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "tag", pinned))
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main"))
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "origin", pinned))
|
||||
require.NoError(t, gitCmd("-C", workDir, "push", "-u", "origin", "main", pinned))
|
||||
|
||||
for name, depth := range map[string]int{"shallow": 1, "full clone": 0} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, NewGitCloneExecutor(NewGitCloneExecutorInput{
|
||||
URL: remoteDir, Ref: pinned, Dir: dir, Depth: depth,
|
||||
@@ -540,13 +536,11 @@ func gitRevParse(t *testing.T, dir, rev string) string {
|
||||
return strings.TrimSpace(string(out))
|
||||
}
|
||||
|
||||
func gitRevCount(t *testing.T, dir string) int {
|
||||
func gitSubjects(t *testing.T, dir string) []string {
|
||||
t.Helper()
|
||||
out, err := exec.Command("git", "-C", dir, "rev-list", "--count", "HEAD").Output()
|
||||
out, err := exec.Command("git", "-C", dir, "log", "--format=%s").Output()
|
||||
require.NoError(t, err)
|
||||
n, err := strconv.Atoi(strings.TrimSpace(string(out)))
|
||||
require.NoError(t, err)
|
||||
return n
|
||||
return strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||
}
|
||||
|
||||
func gitHeadSubject(t *testing.T, dir string) string {
|
||||
@@ -680,7 +674,7 @@ func TestNewGitCloneExecutorFetchSendsTokenAndHonoursContext(t *testing.T) {
|
||||
})
|
||||
|
||||
dir := filepath.Join(t.TempDir(), "cached-action")
|
||||
require.NoError(t, gitCmd("init", dir))
|
||||
require.NoError(t, os.CopyFS(dir, os.DirFS(filepath.Join(gitFixtureDir, "work"))))
|
||||
require.NoError(t, gitCmd("-C", dir, "remote", "add", "origin", server.URL))
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
|
||||
@@ -42,11 +42,11 @@ func TestDockerBuildLabelsImageWithItsTagAndReportsDaemonError(t *testing.T) {
|
||||
|
||||
func TestCreateBuildContextFollowsDockerignoreAndSymlinkedRootButKeepsDockerfile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, os.Mkdir(filepath.Join(dir, "build"), 0o755))
|
||||
for name, content := range map[string]string{
|
||||
".dockerignore": "*.log\nbuild\n!build/keep.txt\nDockerfile", "Dockerfile": "FROM scratch", "a.txt": "a",
|
||||
"a.log": "log", "build/drop.txt": "drop", "build/keep.txt": "keep",
|
||||
} {
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(dir, filepath.Dir(name)), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644))
|
||||
}
|
||||
require.NoError(t, os.Mkdir(filepath.Join(dir, "empty"), 0o755))
|
||||
|
||||
@@ -540,6 +540,9 @@ func TestRejectsMissingContainer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPublicCopyPipelineHandlesStaleID(t *testing.T) {
|
||||
source := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(source, "x"), nil, 0o644))
|
||||
|
||||
for _, testcase := range []struct {
|
||||
name, destination, extractionRoot, entry string
|
||||
directory bool
|
||||
@@ -567,8 +570,6 @@ func TestPublicCopyPipelineHandlesStaleID(t *testing.T) {
|
||||
|
||||
cr := &containerReference{id: "stale", cli: client, input: &NewContainerInput{Name: "job-1"}}
|
||||
if testcase.directory {
|
||||
source := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(source, "x"), nil, 0o644))
|
||||
require.NoError(t, cr.CopyDir(testcase.destination, source+string(filepath.Separator), false, true)(ctx))
|
||||
} else {
|
||||
require.NoError(t, cr.Copy(testcase.destination, &FileEntry{Name: "x", Mode: 0o644})(ctx))
|
||||
@@ -1108,6 +1109,10 @@ func TestConnectTakesDaemonOSFromPing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestContainerCopyModes(t *testing.T) {
|
||||
source := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(source, "run.sh"), []byte("#!/bin/sh\n"), 0o666))
|
||||
require.NoError(t, os.Chmod(filepath.Join(source, "run.sh"), 0o666))
|
||||
|
||||
for _, testcase := range []struct {
|
||||
hostOS, serverOS string
|
||||
expected int64
|
||||
@@ -1117,9 +1122,6 @@ func TestContainerCopyModes(t *testing.T) {
|
||||
{"linux", "linux", 0o666},
|
||||
} {
|
||||
t.Run(testcase.hostOS+"-"+testcase.serverOS, func(t *testing.T) {
|
||||
source := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(source, "run.sh"), []byte("#!/bin/sh\n"), 0o666))
|
||||
require.NoError(t, os.Chmod(filepath.Join(source, "run.sh"), 0o666))
|
||||
file := &FileEntry{Name: "run.sh", Mode: 0o600, Body: "#!/bin/sh\n"}
|
||||
original := hostOS
|
||||
hostOS = testcase.hostOS
|
||||
|
||||
@@ -32,6 +32,11 @@ func TestActionPlatformNames(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCopyDir(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))
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
useGitIgnore, skipGitDir bool
|
||||
@@ -41,11 +46,6 @@ func TestCopyDir(t *testing.T) {
|
||||
{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()))
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -15,14 +16,9 @@ import (
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
)
|
||||
|
||||
func newTestHostEnv(t *testing.T) (*HostEnvironment, string) {
|
||||
t.Helper()
|
||||
e := &HostEnvironment{Path: t.TempDir()}
|
||||
return e, filepath.Join(e.Path, "envfile")
|
||||
}
|
||||
|
||||
func TestParseEnvFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
hostEnv := &HostEnvironment{Path: t.TempDir()}
|
||||
largeValue := strings.Repeat("x", 2*1024*1024)
|
||||
encodeUTF16 := func(endianness unicode.Endianness) string {
|
||||
content, err := unicode.UTF16(endianness, unicode.UseBOM).NewEncoder().Bytes([]byte("FOO=bar\r\nMULTI<<EOF\r\nline1\r\nEOF\r\n"))
|
||||
@@ -51,15 +47,15 @@ func TestParseEnvFile(t *testing.T) {
|
||||
{"UTF16 big endian", encodeUTF16(unicode.BigEndian), map[string]string{"FOO": "bar\r", "MULTI": "line1\r"}},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
e, envPath := newTestHostEnv(t)
|
||||
envPath := filepath.Join(hostEnv.Path, test.name)
|
||||
require.NoError(t, os.WriteFile(envPath, []byte(test.content), 0o600))
|
||||
env := map[string]string{}
|
||||
require.NoError(t, parseEnvFile(e, envPath, &env, false)(t.Context()))
|
||||
require.NoError(t, parseEnvFile(hostEnv, envPath, &env, false)(t.Context()))
|
||||
assert.Equal(t, test.want, env)
|
||||
})
|
||||
}
|
||||
t.Run("ordered entries preserve repeated names", func(t *testing.T) {
|
||||
hostEnv, envPath := newTestHostEnv(t)
|
||||
envPath := filepath.Join(hostEnv.Path, "ordered")
|
||||
require.NoError(t, os.WriteFile(envPath, []byte("FOO=first\r\nfoo<<EOF\r\nmiddle\r\nEOF\r\nFOO=last\r\n"), 0o600))
|
||||
var entries [][2]string
|
||||
require.NoError(t, ParseEnvFile(hostEnv, envPath, func(name, value string) {
|
||||
@@ -70,7 +66,7 @@ func TestParseEnvFile(t *testing.T) {
|
||||
}, entries)
|
||||
})
|
||||
t.Run("Windows line endings", func(t *testing.T) {
|
||||
hostEnv, envPath := newTestHostEnv(t)
|
||||
envPath := filepath.Join(hostEnv.Path, "windows")
|
||||
require.NoError(t, os.WriteFile(envPath, []byte("FOO=bar\r\nMULTI<<EOF\r\nline1\r\nline2\nEOF\r\n"), 0o600))
|
||||
env := map[string]string{}
|
||||
require.NoError(t, parseEnvFile(hostEnv, envPath, &env, true)(t.Context()))
|
||||
@@ -80,16 +76,22 @@ func TestParseEnvFile(t *testing.T) {
|
||||
|
||||
func TestParseEnvFileLineExceedsBufferReportsScannerError(t *testing.T) {
|
||||
t.Parallel()
|
||||
e, envPath := newTestHostEnv(t)
|
||||
require.NoError(t, os.WriteFile(envPath, []byte("FOO<<EOF\n"+strings.Repeat("x", 17*1024*1024)+"\nEOF\n"), 0o600))
|
||||
hostEnv := &HostEnvironment{Path: t.TempDir()}
|
||||
envPath := filepath.Join(hostEnv.Path, "envfile")
|
||||
const header, footer = "FOO<<EOF\n", "\nEOF\n"
|
||||
content := bytes.Repeat([]byte("x"), len(header)+17*1024*1024+len(footer))
|
||||
copy(content, header)
|
||||
copy(content[len(content)-len(footer):], footer)
|
||||
require.NoError(t, os.WriteFile(envPath, content, 0o600))
|
||||
|
||||
env := map[string]string{}
|
||||
err := parseEnvFile(e, envPath, &env, false)(t.Context())
|
||||
err := parseEnvFile(hostEnv, envPath, &env, false)(t.Context())
|
||||
require.ErrorIs(t, err, bufio.ErrTooLong)
|
||||
assert.Contains(t, err.Error(), "reading env file")
|
||||
}
|
||||
|
||||
func TestParseEnvFileInvalidHeredoc(t *testing.T) {
|
||||
hostEnv := &HostEnvironment{Path: t.TempDir()}
|
||||
for name, content := range map[string]string{
|
||||
"missing delimiter": "FOO<<EOF\nline1\nline2\n",
|
||||
"empty name": "<<EOF\nline1\nEOF\n",
|
||||
@@ -97,11 +99,11 @@ func TestParseEnvFileInvalidHeredoc(t *testing.T) {
|
||||
"delimiter with carriage return": "FOO<<EOF\nline1\nEOF\r\n",
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
e, envPath := newTestHostEnv(t)
|
||||
envPath := filepath.Join(hostEnv.Path, name)
|
||||
require.NoError(t, os.WriteFile(envPath, []byte("APPLIED=before-error\n"+content), 0o600))
|
||||
|
||||
env := map[string]string{}
|
||||
err := parseEnvFile(e, envPath, &env, false)(t.Context())
|
||||
err := parseEnvFile(hostEnv, envPath, &env, false)(t.Context())
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "delimiter")
|
||||
assert.Equal(t, map[string]string{"APPLIED": "before-error"}, env)
|
||||
|
||||
@@ -28,10 +28,12 @@ func gitCmd(t *testing.T, args ...string) {
|
||||
}
|
||||
|
||||
func TestCollectFilesKeepsWhatGitTracksOrDoesNotIgnore(t *testing.T) {
|
||||
repoDir := filepath.Join(t.TempDir(), "mygitrepo")
|
||||
for _, dir := range []string{repoDir, filepath.Join(repoDir, "sub"), filepath.Join(repoDir, "nested")} {
|
||||
gitCmd(t, "init", "-q", dir)
|
||||
repoDir := t.TempDir()
|
||||
gitCmd(t, "init", "-q", "--template=", repoDir)
|
||||
for _, dir := range []string{"sub", "nested"} {
|
||||
require.NoError(t, os.CopyFS(filepath.Join(repoDir, dir, ".git"), os.DirFS(filepath.Join(repoDir, ".git"))))
|
||||
}
|
||||
require.NoError(t, os.Mkdir(filepath.Join(repoDir, "ign"), 0o755))
|
||||
for name, body := range map[string]string{
|
||||
".gitignore": ".*\nign/\n",
|
||||
".env": "",
|
||||
@@ -43,15 +45,15 @@ func TestCollectFilesKeepsWhatGitTracksOrDoesNotIgnore(t *testing.T) {
|
||||
"sub/kept": "",
|
||||
"nested/kept": "",
|
||||
} {
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(repoDir, name)), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(repoDir, name), []byte(body), 0o644))
|
||||
}
|
||||
require.NoError(t, os.Mkdir(filepath.Join(repoDir, "uninitialized"), 0o755))
|
||||
gitCmd(t, "-C", repoDir, "add", "-f", ".gitignore", "Renamed", "ign/tracked")
|
||||
require.NoError(t, os.Rename(filepath.Join(repoDir, "Renamed"), filepath.Join(repoDir, "renamed")))
|
||||
for _, gitlink := range []string{"sub", "uninitialized"} {
|
||||
gitCmd(t, "-C", repoDir, "update-index", "--add", "--cacheinfo", "160000,4b825dc642cb6eb9a060e54bf8d69288fbee4904,"+gitlink)
|
||||
}
|
||||
gitCmd(t, "-C", repoDir, "update-index", "--add",
|
||||
"--cacheinfo", "160000,4b825dc642cb6eb9a060e54bf8d69288fbee4904,sub",
|
||||
"--cacheinfo", "160000,4b825dc642cb6eb9a060e54bf8d69288fbee4904,uninitialized",
|
||||
)
|
||||
expected := map[string]string{".gitignore": "", "ign/tracked": "", "nested/kept": "", "renamed": "", "sub/.gitignore": "", "sub/kept": ""}
|
||||
if runtime.GOOS != "windows" {
|
||||
require.NoError(t, os.Chmod(filepath.Join(repoDir, "renamed"), 0o777|os.ModeSticky))
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestCompositeActionParity(t *testing.T) {
|
||||
return "", "", assert.AnError
|
||||
})
|
||||
rc := &RunContext{
|
||||
Config: &Config{GitHubInstance: "github.com", ActionCacheDir: t.TempDir(), PresetGitHubContext: &model.GithubContext{}},
|
||||
Config: &Config{GitHubInstance: "github.com", PresetGitHubContext: &model.GithubContext{}},
|
||||
Run: &model.Run{JobID: "job", Workflow: &model.Workflow{Jobs: map[string]*model.Job{"job": {}}}},
|
||||
JobContainer: &jobContainerMock{},
|
||||
}
|
||||
|
||||
+17
-13
@@ -249,6 +249,7 @@ func TestActionRunner(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGoActionUsesLinuxExecutablePaths(t *testing.T) {
|
||||
actionRoot := t.TempDir()
|
||||
for _, stage := range []string{"main", "pre", "post"} {
|
||||
t.Run(stage, func(t *testing.T) {
|
||||
cm := &containerMock{}
|
||||
@@ -264,7 +265,7 @@ func TestGoActionUsesLinuxExecutablePaths(t *testing.T) {
|
||||
JobContainer: cm,
|
||||
},
|
||||
action: &model.Action{Runs: model.ActionRuns{Using: model.ActionRunsUsingGo, Main: "main.go", Pre: "pre.go", Post: "post.go"}},
|
||||
actionDir: t.TempDir(),
|
||||
actionDir: filepath.Join(actionRoot, stage),
|
||||
env: map[string]string{},
|
||||
remoteAction: mustNewRemoteAction(t, "org/repo@ref"),
|
||||
}
|
||||
@@ -504,11 +505,21 @@ func TestExecAsDockerHoldsCloneLockForRemoteUncached(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExecAsDockerImageChecks(t *testing.T) {
|
||||
actionRoot := t.TempDir()
|
||||
originalExists, originalBuild, originalContainer := ContainerImageExistsLocally, ContainerNewDockerBuildExecutor, ContainerNewContainer
|
||||
t.Cleanup(func() {
|
||||
ContainerImageExistsLocally, ContainerNewDockerBuildExecutor, ContainerNewContainer = originalExists, originalBuild, originalContainer
|
||||
})
|
||||
ContainerNewContainer = func(*container.NewContainerInput) container.ExecutionsEnvironment { return fakeContainer{} }
|
||||
var removed bool
|
||||
fakeDockerDaemon(t, func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodDelete {
|
||||
removed = true
|
||||
_, _ = io.WriteString(writer, `[]`)
|
||||
} else {
|
||||
_, _ = io.WriteString(writer, `{"Architecture":"aarch64"}`)
|
||||
}
|
||||
})
|
||||
|
||||
for _, testcase := range []struct {
|
||||
name, platform string
|
||||
@@ -529,18 +540,11 @@ func TestExecAsDockerImageChecks(t *testing.T) {
|
||||
checks = append(checks, platform)
|
||||
return testcase.exists && (platform == "any" || testcase.matches), nil
|
||||
}
|
||||
var built, removed bool
|
||||
var built bool
|
||||
removed = false
|
||||
ContainerNewDockerBuildExecutor = func(container.NewDockerBuildExecutorInput) common.Executor {
|
||||
return func(context.Context) error { built = true; return nil }
|
||||
}
|
||||
fakeDockerDaemon(t, func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodDelete {
|
||||
removed = true
|
||||
_, _ = io.WriteString(writer, `[]`)
|
||||
} else {
|
||||
_, _ = io.WriteString(writer, `{"Architecture":"aarch64"}`)
|
||||
}
|
||||
})
|
||||
step := &stepActionRemote{
|
||||
Step: &model.Step{ID: "action"},
|
||||
RunContext: &RunContext{
|
||||
@@ -555,7 +559,7 @@ func TestExecAsDockerImageChecks(t *testing.T) {
|
||||
action: &model.Action{Runs: model.ActionRuns{Using: "docker", Image: "Dockerfile"}},
|
||||
env: map[string]string{},
|
||||
}
|
||||
dir := t.TempDir()
|
||||
dir := filepath.Join(actionRoot, testcase.name)
|
||||
require.NoError(t, execAsDocker(t.Context(), step, "action", dir, dir, false, stepStageMain))
|
||||
assert.Equal(t, testcase.wantChecks, checks)
|
||||
assert.Equal(t, testcase.wantBuild, built)
|
||||
@@ -727,9 +731,9 @@ func TestExecAsDockerStageEntrypoint(t *testing.T) {
|
||||
cm.On("Close").Return(func(context.Context) error { return nil })
|
||||
|
||||
if tc.stage == stepStageMain {
|
||||
require.NoError(t, runActionImpl(step, t.TempDir(), step.remoteAction)(ctx))
|
||||
require.NoError(t, runActionImpl(step, "", step.remoteAction)(ctx))
|
||||
} else {
|
||||
require.NoError(t, execAsDocker(ctx, step, "action", t.TempDir(), t.TempDir(), false, tc.stage))
|
||||
require.NoError(t, execAsDocker(ctx, step, "action", "", "", false, tc.stage))
|
||||
}
|
||||
if tc.warning == "" {
|
||||
assert.Empty(t, hook.AllRecords())
|
||||
|
||||
@@ -412,11 +412,10 @@ func TestGetEvaluatorInputsBoolean(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, eventName := range []string{"workflow_call", "workflow_dispatch"} {
|
||||
wf, err := model.ReadWorkflow(strings.NewReader(workflow(eventName)))
|
||||
require.NoError(t, err)
|
||||
for _, table := range tables {
|
||||
t.Run(eventName+"/"+table.name, func(t *testing.T) {
|
||||
wf, err := model.ReadWorkflow(strings.NewReader(workflow(eventName)))
|
||||
require.NoError(t, err)
|
||||
|
||||
rc := &RunContext{
|
||||
Config: &Config{Workdir: "."},
|
||||
Run: &model.Run{JobID: "job1", Workflow: wf},
|
||||
|
||||
@@ -993,6 +993,7 @@ func tarArchive(t *testing.T, entries ...tarEntry) []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, entry := range entries {
|
||||
buf.Grow(len(entry.body) + 3*512)
|
||||
typeflag := entry.typeflag
|
||||
if typeflag == 0 {
|
||||
typeflag = tar.TypeReg
|
||||
@@ -1009,7 +1010,7 @@ func tarArchive(t *testing.T, entries ...tarEntry) []byte {
|
||||
}
|
||||
require.NoError(t, tw.WriteHeader(header))
|
||||
if typeflag == tar.TypeReg {
|
||||
_, err := tw.Write([]byte(entry.body))
|
||||
_, err := io.CopyN(tw, strings.NewReader(entry.body), int64(len(entry.body)))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
@@ -1149,12 +1150,12 @@ func TestTryUploadJobSummaryMasksSecrets(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
content, err := testcase.encoding.NewEncoder().Bytes([]byte(testcase.content))
|
||||
content, err := testcase.encoding.NewEncoder().String(testcase.content)
|
||||
require.NoError(t, err)
|
||||
cm := &containerMock{}
|
||||
cm.On("GetContainerArchive", mock.Anything, "/var/run/act/workflow/step-summary-0.md").Return(
|
||||
io.NopCloser(bytes.NewReader(tarArchive(t, tarEntry{
|
||||
name: "step-summary-0.md", body: string(content),
|
||||
name: "step-summary-0.md", body: content,
|
||||
}))),
|
||||
nil,
|
||||
).Once()
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
@@ -209,14 +210,14 @@ func TestPatchedBundleAcrossActions(t *testing.T) {
|
||||
assert.Equal(t, data, out, "an untouched bundle must come back byte for byte")
|
||||
return
|
||||
}
|
||||
assert.NotContains(t, string(out), ".LOCALHOST", "a copy of the gate was missed")
|
||||
assert.False(t, bytes.Contains(out, []byte(".LOCALHOST")), "a copy of the gate was missed")
|
||||
|
||||
if !strings.Contains(string(data), CacheServiceV2Env) {
|
||||
if !bytes.Contains(data, []byte(CacheServiceV2Env)) {
|
||||
return
|
||||
}
|
||||
assert.NotZero(t, strings.Count(string(out), "(process.env."+cacheURLEnv+"||process.env"),
|
||||
assert.NotZero(t, bytes.Count(out, []byte("(process.env."+cacheURLEnv+"||process.env")),
|
||||
"the cache service URL was not redirected")
|
||||
assert.Equal(t, strings.Count(string(data), resultsURLEnv), strings.Count(string(out), resultsURLEnv),
|
||||
assert.Equal(t, bytes.Count(data, []byte(resultsURLEnv)), bytes.Count(out, []byte(resultsURLEnv)),
|
||||
"a read of the results URL was lost, it must stay as the fallback")
|
||||
})
|
||||
}
|
||||
@@ -311,18 +312,18 @@ func TestCodeSplitActionFindsTheCacheService(t *testing.T) {
|
||||
requireHostTools(t, "node", "rustc")
|
||||
|
||||
dir := tempDirPath(t)
|
||||
require.NoError(t, os.Mkdir(filepath.Join(dir, "dist"), 0o755))
|
||||
for _, name := range []string{
|
||||
"action.yml", "dist/restore.js", "dist/save.js", "dist/cleanup-BWEbZ6YT.js",
|
||||
"dist/cache-CTfQoCTL.js", "dist/cache-DC26rUaF.js", "dist/state-cjs-CkvFUaNy.js",
|
||||
} {
|
||||
body, err := os.ReadFile(bundleFromGitHub(t, "Swatinem/rust-cache", "v2.9.2", name))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, name), body, 0o600))
|
||||
}
|
||||
body, err := os.ReadFile(filepath.Join(dir, "action.yml"))
|
||||
require.NoError(t, err)
|
||||
action, err := model.ReadAction(strings.NewReader(string(body)))
|
||||
action, err := model.ReadAction(bytes.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
inputs := map[string]string{"lookup-only": "true", "add-rust-environment-hash-key": "false", "cache-bin": "false"}
|
||||
for name, input := range action.Inputs {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -182,9 +183,9 @@ func TestPatchedBundleSurvivesInsideAStringLiteral(t *testing.T) {
|
||||
out, patched := patchedBundle([]byte(embedded))
|
||||
require.True(t, patched)
|
||||
|
||||
file := filepath.Join(t.TempDir(), "bundle.js")
|
||||
require.NoError(t, os.WriteFile(file, out, 0o600))
|
||||
checked, err := exec.CommandContext(t.Context(), "node", "--check", file).CombinedOutput()
|
||||
cmd := exec.CommandContext(t.Context(), "node", "--check", "-")
|
||||
cmd.Stdin = bytes.NewReader(out)
|
||||
checked, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, "%s", checked)
|
||||
}
|
||||
|
||||
@@ -256,6 +257,14 @@ func TestActionScriptPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPatchActionsAtTheContainerCopy(t *testing.T) {
|
||||
actionDir := t.TempDir()
|
||||
script := filepath.Join(actionDir, "dist", "cache.js")
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(script), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(script), "index.js"), []byte("require('./cache.js')"), 0o600))
|
||||
require.NoError(t, os.WriteFile(script, []byte(gateTSC), 0o600))
|
||||
info, err := os.Stat(script)
|
||||
require.NoError(t, err)
|
||||
|
||||
copiedBundles := func(t *testing.T, noPatch bool) ([]string, []*container.FileEntry, int64) {
|
||||
t.Helper()
|
||||
|
||||
@@ -264,19 +273,12 @@ func TestPatchActionsAtTheContainerCopy(t *testing.T) {
|
||||
Step: &model.Step{Uses: "owner/repo/sub@v1"},
|
||||
remoteAction: &remoteAction{Org: "owner", Repo: "repo", Path: "sub", Ref: "v1"},
|
||||
action: &model.Action{Runs: model.ActionRuns{Using: "node20", Main: "../dist/index.js"}},
|
||||
actionDir: t.TempDir(),
|
||||
actionDir: actionDir,
|
||||
RunContext: &RunContext{
|
||||
Config: &Config{ActionCacheDir: t.TempDir(), NoActionPatch: noPatch},
|
||||
Config: &Config{NoActionPatch: noPatch},
|
||||
JobContainer: cm,
|
||||
},
|
||||
}
|
||||
script := filepath.Join(sar.actionDir, "dist", "cache.js")
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(script), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(script), "index.js"), []byte("require('./cache.js')"), 0o600))
|
||||
require.NoError(t, os.WriteFile(script, []byte(gateTSC), 0o600))
|
||||
info, err := os.Stat(script)
|
||||
require.NoError(t, err)
|
||||
|
||||
var calls []string
|
||||
var copied []*container.FileEntry
|
||||
cm.On("CopyDir", "/var/run/act/actions/repo/", sar.actionDir+"/", false, true).Return(func(context.Context) error {
|
||||
|
||||
@@ -33,24 +33,17 @@ func TestReusableWorkflowCachedBranchRefRefreshes(t *testing.T) {
|
||||
}
|
||||
|
||||
remoteDir := t.TempDir()
|
||||
gitMust(t, "", "init", "--bare", "--initial-branch=master", remoteDir)
|
||||
|
||||
workDir := t.TempDir()
|
||||
gitMust(t, "", "clone", remoteDir, workDir)
|
||||
gitMust(t, workDir, "config", "user.email", "test@test")
|
||||
gitMust(t, workDir, "config", "user.name", "test")
|
||||
gitMust(t, workDir, "checkout", "-b", "master")
|
||||
gitMust(t, "", "init", "--template=", "--initial-branch=master", remoteDir)
|
||||
|
||||
const workflowPath = ".gitea/workflows/reusable.yml"
|
||||
tmpl := func(tag string) string {
|
||||
return "name: reusable\non:\n workflow_call:\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo " + tag + "\n"
|
||||
}
|
||||
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(workDir, ".gitea/workflows"), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(workDir, workflowPath), []byte(tmpl("v1")), 0o644))
|
||||
gitMust(t, workDir, "add", workflowPath)
|
||||
gitMust(t, workDir, "commit", "-m", "v1")
|
||||
gitMust(t, workDir, "push", "-u", "origin", "master")
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(remoteDir, ".gitea/workflows"), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(remoteDir, workflowPath), []byte(tmpl("v1")), 0o644))
|
||||
gitMust(t, remoteDir, "add", workflowPath)
|
||||
gitMust(t, remoteDir, "commit", "-m", "v1")
|
||||
|
||||
rc := &RunContext{
|
||||
Config: &Config{Env: testCheckoutEnv()},
|
||||
@@ -70,9 +63,8 @@ func TestReusableWorkflowCachedBranchRefRefreshes(t *testing.T) {
|
||||
require.Equal(t, tmpl("v1"), string(got))
|
||||
|
||||
// Branch tip moves; cache key (cacheDir) does not.
|
||||
require.NoError(t, os.WriteFile(filepath.Join(workDir, workflowPath), []byte(tmpl("v2")), 0o644))
|
||||
gitMust(t, workDir, "commit", "-am", "v2")
|
||||
gitMust(t, workDir, "push", "origin", "master")
|
||||
require.NoError(t, os.WriteFile(filepath.Join(remoteDir, workflowPath), []byte(tmpl("v2")), 0o644))
|
||||
gitMust(t, remoteDir, "commit", "-am", "v2")
|
||||
|
||||
require.NoError(t, cloneRemoteReusableWorkflow(rc, remoteDir, "master", cacheDir)(t.Context()))
|
||||
got, err = os.ReadFile(filepath.Join(cacheDir, workflowPath))
|
||||
@@ -214,7 +206,7 @@ func TestInstanceAuthAndTransportOnlyApplyToTheInstance(t *testing.T) {
|
||||
|
||||
func gitMust(t *testing.T, dir string, args ...string) {
|
||||
t.Helper()
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd := exec.Command("git", append([]string{"-c", "user.name=test", "-c", "user.email=test@test"}, args...)...)
|
||||
if dir != "" {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
|
||||
@@ -1666,21 +1666,21 @@ func TestRunContextUpdateExtraPath(t *testing.T) {
|
||||
longPath := strings.Repeat("x", 64*1024+1)
|
||||
for _, testcase := range []struct {
|
||||
name string
|
||||
content []byte
|
||||
content string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "UTF-8 BOM", content: []byte("\xef\xbb\xbf/utf8/tools\n"), want: "/utf8/tools"},
|
||||
{name: "UTF-16 BOM", content: []byte{0xff, 0xfe, 'C', 0, ':', 0, '\\', 0, 't', 0, 'o', 0, 'o', 0, 'l', 0, 's', 0, '\n', 0}, want: `C:\tools`},
|
||||
{name: "over 64 KiB", content: []byte(longPath + "\n"), want: longPath},
|
||||
{name: "over 16 MiB", content: []byte(strings.Repeat("x", 16*1024*1024+1)), wantErr: true},
|
||||
{name: "UTF-8 BOM", content: "\xef\xbb\xbf/utf8/tools\n", want: "/utf8/tools"},
|
||||
{name: "UTF-16 BOM", content: "\xff\xfeC\x00:\x00\\\x00t\x00o\x00o\x00l\x00s\x00\n\x00", want: `C:\tools`},
|
||||
{name: "over 64 KiB", content: longPath + "\n", want: longPath},
|
||||
{name: "over 16 MiB", content: strings.Repeat("x", 16*1024*1024+1), wantErr: true},
|
||||
} {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
logger := logging.New(logging.DiscardHandler)
|
||||
ctx := common.WithLogger(t.Context(), logger)
|
||||
jobContainer := &containerMock{}
|
||||
jobContainer.On("GetContainerArchive", mock.Anything, "/github/path").
|
||||
Return(io.NopCloser(bytes.NewReader(tarArchive(t, tarEntry{name: "path", body: string(testcase.content)}))), nil).Once()
|
||||
Return(io.NopCloser(bytes.NewReader(tarArchive(t, tarEntry{name: "path", body: testcase.content}))), nil).Once()
|
||||
defer jobContainer.AssertExpectations(t)
|
||||
rc := &RunContext{JobContainer: jobContainer}
|
||||
|
||||
|
||||
@@ -35,14 +35,8 @@ func setFetch(t *testing.T, fetch func(context.Context, git.NewGitCloneExecutorI
|
||||
func TestShortSHAActionRejected(t *testing.T) {
|
||||
actionRoot := t.TempDir()
|
||||
repo := filepath.Join(actionRoot, "actions", "hello-world-docker-action")
|
||||
require.NoError(t, os.MkdirAll(repo, 0o755))
|
||||
gitMust(t, "", "init", "--initial-branch=main", repo)
|
||||
gitMust(t, repo, "config", "user.email", "test@test")
|
||||
gitMust(t, repo, "config", "user.name", "test")
|
||||
require.NoError(t, os.WriteFile(filepath.Join(repo, "action.yml"),
|
||||
[]byte("name: hello\nruns:\n using: node24\n main: index.js\n"), 0o644))
|
||||
gitMust(t, repo, "add", ".")
|
||||
gitMust(t, repo, "commit", "-m", "initial")
|
||||
gitMust(t, "", "init", "--template=", "--initial-branch=main", repo)
|
||||
gitMust(t, repo, "commit", "--allow-empty", "-m", "initial")
|
||||
output, err := exec.Command("git", "-C", repo, "rev-parse", "--short=7", "HEAD").Output()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -393,7 +387,7 @@ func TestStepActionRemotePreResolvesDownloadedCommit(t *testing.T) {
|
||||
[]byte("name: setup-go\nruns:\n using: node20\n main: index.js\n"), 0o600))
|
||||
|
||||
for _, args := range [][]string{
|
||||
{"init", "--initial-branch=main", actionDir},
|
||||
{"init", "--template=", "--initial-branch=main", actionDir},
|
||||
{"-C", actionDir, "add", "action.yml"},
|
||||
{"-C", actionDir, "-c", "user.name=runner", "-c", "[email protected]", "-c", "commit.gpgsign=false", "commit", "-m", "action"},
|
||||
} {
|
||||
|
||||
@@ -128,6 +128,11 @@ func (*daemonTestService) UpdateLog(_ context.Context, req *runnerv1.UpdateLogRe
|
||||
func TestDaemonRetriesDeclareOutagesAndRemovesOnlyConsumedEphemeralRegistration(t *testing.T) {
|
||||
defer func(delay time.Duration) { declareRetryDelay = delay }(declareRetryDelay)
|
||||
declareRetryDelay = 0
|
||||
dir := t.TempDir()
|
||||
regFile := filepath.Join(dir, "registration.json")
|
||||
configFile := filepath.Join(dir, "config.yaml")
|
||||
require.NoError(t, os.WriteFile(configFile, []byte("runner:\n file: "+regFile+"\n idle_cleanup_interval: 0s\ncache:\n enabled: false\n"), 0o600))
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
ephemeral, beforeTask bool
|
||||
@@ -154,10 +159,6 @@ func TestDaemonRetriesDeclareOutagesAndRemovesOnlyConsumedEphemeralRegistration(
|
||||
connecthttp.Mount(mux, rpcServer, connecthttp.WithReadMaxBytes(0))
|
||||
server := httptest.NewServer(http.StripPrefix("/api/actions", mux))
|
||||
defer server.Close()
|
||||
dir := t.TempDir()
|
||||
regFile := filepath.Join(dir, "registration.json")
|
||||
configFile := filepath.Join(dir, "config.yaml")
|
||||
require.NoError(t, os.WriteFile(configFile, []byte("runner:\n file: "+regFile+"\n idle_cleanup_interval: 0s\ncache:\n enabled: false\n"), 0o600))
|
||||
require.NoError(t, config.SaveRegistration(regFile, &config.Registration{Address: server.URL, UUID: "test", Name: "test", Token: "test", Labels: []string{"host:host"}, Ephemeral: tc.ephemeral}))
|
||||
err := runDaemon(ctx, &daemonArgs{Once: true}, &configFile)(nil, nil)
|
||||
if tc.declareErr == nil && tc.fetchErr == nil || tc.beforeTask {
|
||||
|
||||
@@ -75,40 +75,35 @@ func TestExecuteArgsLoadInputs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExecuteArgsEventJSON(t *testing.T) {
|
||||
payload := func(content string) string {
|
||||
path := filepath.Join(t.TempDir(), "event.json")
|
||||
require.NoError(t, os.WriteFile(path, []byte(content), 0o600))
|
||||
return path
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
tests := []struct {
|
||||
name string
|
||||
eventpath string
|
||||
inputs []string
|
||||
expected string
|
||||
name string
|
||||
payload string
|
||||
inputs []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "keeps a payload without inputs to merge",
|
||||
eventpath: payload(`{"inputs": {"kept": "from_event"}}`),
|
||||
expected: `{"inputs": {"kept": "from_event"}}`,
|
||||
name: "keeps a payload without inputs to merge",
|
||||
payload: `{"inputs": {"kept": "from_event"}}`,
|
||||
expected: `{"inputs": {"kept": "from_event"}}`,
|
||||
},
|
||||
{
|
||||
name: "overrides the event inputs it names, keeping the rest",
|
||||
eventpath: payload(`{"inputs": {"kept": "from_event", "named": "from_event"}}`),
|
||||
inputs: []string{"named=from_cli"},
|
||||
expected: `{"inputs": {"kept": "from_event", "named": "from_cli"}}`,
|
||||
name: "overrides the event inputs it names, keeping the rest",
|
||||
payload: `{"inputs": {"kept": "from_event", "named": "from_event"}}`,
|
||||
inputs: []string{"named=from_cli"},
|
||||
expected: `{"inputs": {"kept": "from_event", "named": "from_cli"}}`,
|
||||
},
|
||||
{
|
||||
name: "adds inputs to a payload carrying none",
|
||||
eventpath: payload(`{"repository": {"name": "test-repo"}}`),
|
||||
inputs: []string{"flag=val"},
|
||||
expected: `{"repository": {"name": "test-repo"}, "inputs": {"flag": "val"}}`,
|
||||
name: "adds inputs to a payload carrying none",
|
||||
payload: `{"repository": {"name": "test-repo"}}`,
|
||||
inputs: []string{"flag=val"},
|
||||
expected: `{"repository": {"name": "test-repo"}, "inputs": {"flag": "val"}}`,
|
||||
},
|
||||
{
|
||||
name: "handles a null payload",
|
||||
eventpath: payload("null"),
|
||||
inputs: []string{"flag=val"},
|
||||
expected: `{"inputs": {"flag": "val"}}`,
|
||||
name: "handles a null payload",
|
||||
payload: "null",
|
||||
inputs: []string{"flag=val"},
|
||||
expected: `{"inputs": {"flag": "val"}}`,
|
||||
},
|
||||
{
|
||||
name: "builds a payload without an event file",
|
||||
@@ -119,7 +114,12 @@ func TestExecuteArgsEventJSON(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
eventJSON, err := (&executeArgs{eventpath: tt.eventpath, inputs: tt.inputs}).eventJSON()
|
||||
var eventpath string
|
||||
if tt.payload != "" {
|
||||
eventpath = filepath.Join(dir, tt.name+".json")
|
||||
require.NoError(t, os.WriteFile(eventpath, []byte(tt.payload), 0o600))
|
||||
}
|
||||
eventJSON, err := (&executeArgs{eventpath: eventpath, inputs: tt.inputs}).eventJSON()
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, tt.expected, eventJSON)
|
||||
})
|
||||
@@ -275,21 +275,13 @@ func workflowForList(name, file, event, jobID, jobName string) *model.Workflow {
|
||||
return &model.Workflow{
|
||||
Name: name,
|
||||
File: file,
|
||||
RawOn: rawOnNode(event),
|
||||
RawOn: yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: event},
|
||||
Jobs: map[string]*model.Job{
|
||||
jobID: {Name: jobName},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func rawOnNode(event string) yaml.Node {
|
||||
var node yaml.Node
|
||||
if err := yaml.Unmarshal([]byte(event), &node); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return *node.Content[0]
|
||||
}
|
||||
|
||||
func TestExecuteArgsLoadEnvsResultsOrigin(t *testing.T) {
|
||||
t.Setenv("ACTIONS_RESULTS_URL", "") // the default is only supplied when nothing else does
|
||||
handler, err := artifactcache.StartHandler(artifactcache.Options{Dir: t.TempDir(), OutboundIP: "127.0.0.1"})
|
||||
|
||||
@@ -27,16 +27,9 @@ 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")
|
||||
r.runPostTaskScript(t.Context(), nil, nil, "/workspace/owner/repo")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,9 +52,7 @@ func TestRunPostTaskScriptNonZeroExitDoesNotPanic(t *testing.T) {
|
||||
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}
|
||||
task := &runnerv1.Task{Id: 1, Context: &structpb.Struct{}}
|
||||
reporter := report.NewReporter(ctx, cancel, nil, task, cfg)
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
@@ -146,9 +137,7 @@ func TestRunPostTaskScriptIntegration(t *testing.T) {
|
||||
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}
|
||||
task := &runnerv1.Task{Id: 11, Context: &structpb.Struct{}}
|
||||
reporter := report.NewReporter(ctx, cancel, nil, task, cfg)
|
||||
setReporterJobResult(t, reporter, runnerv1.Result_RESULT_SUCCESS)
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@ import (
|
||||
|
||||
func TestRunnerCleanupStaleTaskDirs(t *testing.T) {
|
||||
now := time.Date(2026, time.April, 29, 20, 0, 0, 0, time.UTC)
|
||||
workdirRoot := filepath.Join(t.TempDir(), "workspace")
|
||||
require.NoError(t, os.MkdirAll(workdirRoot, 0o700))
|
||||
workdirRoot := t.TempDir()
|
||||
|
||||
oldTask := filepath.Join(workdirRoot, "1001")
|
||||
freshTask := filepath.Join(workdirRoot, "1002")
|
||||
@@ -59,8 +58,7 @@ func TestRunnerCleanupStaleTaskDirs(t *testing.T) {
|
||||
// be reclaimed, while the shared tool_cache and operator data are preserved.
|
||||
func TestRunnerOnIdleCleansStaleHostScratchDirs(t *testing.T) {
|
||||
now := time.Date(2026, time.April, 29, 20, 0, 0, 0, time.UTC)
|
||||
hostRoot := filepath.Join(t.TempDir(), "act")
|
||||
require.NoError(t, os.MkdirAll(hostRoot, 0o700))
|
||||
hostRoot := t.TempDir()
|
||||
|
||||
staleScratch := filepath.Join(hostRoot, "0123456789abcdef") // 16 hex
|
||||
freshScratch := filepath.Join(hostRoot, "fedcba9876543210")
|
||||
@@ -118,8 +116,7 @@ func TestRunnerCleanupStaleTaskDirsMissingRoot(t *testing.T) {
|
||||
|
||||
func TestRunnerCleanupStaleTaskDirsHonorsContext(t *testing.T) {
|
||||
now := time.Date(2026, time.April, 29, 20, 0, 0, 0, time.UTC)
|
||||
workdirRoot := filepath.Join(t.TempDir(), "workspace")
|
||||
require.NoError(t, os.MkdirAll(workdirRoot, 0o700))
|
||||
workdirRoot := t.TempDir()
|
||||
|
||||
for i := 1001; i <= 1003; i++ {
|
||||
dir := filepath.Join(workdirRoot, strconv.Itoa(i))
|
||||
|
||||
@@ -96,6 +96,7 @@ func TestSetupLinesInputs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPrettyOSName(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
osRelease string
|
||||
@@ -109,7 +110,7 @@ func TestPrettyOSName(t *testing.T) {
|
||||
{"missing file", "", "", true},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "os-release")
|
||||
path := filepath.Join(dir, tt.name)
|
||||
if !tt.missing {
|
||||
require.NoError(t, os.WriteFile(path, []byte(tt.osRelease), 0o600))
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestCheckout(t *testing.T) {
|
||||
t.Run("history", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
work := t.TempDir()
|
||||
runGit(t, "init", "--initial-branch=main", work)
|
||||
runGit(t, "init", "--template=", "--initial-branch=main", work)
|
||||
runGit(t, "-C", work, "commit", "--allow-empty", "-m", "c1")
|
||||
sha = runGit(t, "-C", work, "rev-parse", "HEAD")
|
||||
runGit(t, "-C", work, "tag", "v1")
|
||||
@@ -77,7 +77,7 @@ func TestCheckout(t *testing.T) {
|
||||
t.Run("sha256", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
sha256Repo := filepath.Join(server, "org", "sha256")
|
||||
runGit(t, "init", "--object-format=sha256", "--initial-branch=main", sha256Repo)
|
||||
runGit(t, "init", "--template=", "--object-format=sha256", "--initial-branch=main", sha256Repo)
|
||||
runGit(t, "-C", sha256Repo, "commit", "--allow-empty", "-m", "s1")
|
||||
runGit(t, "-C", sha256Repo, "checkout", "--detach")
|
||||
})
|
||||
@@ -267,21 +267,41 @@ exec "$LFS" "$@"
|
||||
assert.Contains(t, string(outputs), "ref="+cmp.Or(tc.eventRef, "refs/heads/main")+"\n")
|
||||
return
|
||||
}
|
||||
if tc.head == "" {
|
||||
assert.Empty(t, gitOutput(dst, "symbolic-ref", "-q", "HEAD"))
|
||||
} else {
|
||||
assert.Equal(t, "refs/heads/"+tc.head, gitOutput(dst, "symbolic-ref", "-q", "HEAD"))
|
||||
assert.Equal(t, "origin/"+tc.head, gitOutput(dst, "rev-parse", "--abbrev-ref", "@{u}"))
|
||||
headArgs := []string{"-C", dst, "rev-parse", "HEAD", "--symbolic-full-name", "HEAD"}
|
||||
if tc.head != "" {
|
||||
headArgs = append(headArgs, "--abbrev-ref", "@{u}")
|
||||
}
|
||||
assert.Contains(t, string(outputs), "commit="+gitOutput(dst, "rev-parse", "HEAD")+"\n")
|
||||
head := strings.Fields(runGit(t, headArgs...))
|
||||
if tc.head == "" {
|
||||
require.Len(t, head, 2)
|
||||
assert.Equal(t, "HEAD", head[1])
|
||||
} else {
|
||||
require.Len(t, head, 3)
|
||||
assert.Equal(t, "refs/heads/"+tc.head, head[1])
|
||||
assert.Equal(t, "origin/"+tc.head, head[2])
|
||||
}
|
||||
assert.Contains(t, string(outputs), "commit="+head[0]+"\n")
|
||||
if inputRef := tc.inputs["ref"]; inputRef != "" && inputRef != sha {
|
||||
assert.Contains(t, string(outputs), "ref="+inputRef+"\n")
|
||||
}
|
||||
assert.Equal(t, tc.inputs["sparse-checkout"] != "", gitOutput(dst, "config", "--local", "extensions.worktreeConfig") == "true")
|
||||
persisted := tc.inputs["persist-credentials"] != "false"
|
||||
for _, repo := range repos {
|
||||
var credentialIncludes []string
|
||||
var worktreeConfig string
|
||||
for entry := range strings.SplitSeq(runGit(t, "-C", repo, "config", "--local", "--no-includes", "--null", "--list"), "\x00") {
|
||||
key, value, _ := strings.Cut(entry, "\n")
|
||||
switch key {
|
||||
case "extensions.worktreeconfig":
|
||||
worktreeConfig = value
|
||||
case strings.ToLower(credentialsInclude):
|
||||
credentialIncludes = append(credentialIncludes, value)
|
||||
}
|
||||
}
|
||||
if repo == dst {
|
||||
assert.Equal(t, tc.inputs["sparse-checkout"] != "", strings.TrimSpace(worktreeConfig) == "true")
|
||||
}
|
||||
assert.Equal(t, tc.inputs["clean"] == "false", fileExists(filepath.Join(repo, "untracked")), repo)
|
||||
assert.Len(t, strings.Fields(gitOutput(repo, "config", "--local", "--get-all", credentialsInclude)), 1, repo)
|
||||
assert.Len(t, strings.Fields(strings.Join(credentialIncludes, "\n")), 1, repo)
|
||||
headers, _ := exec.Command("git", "-C", repo, "config", "--get-all", "http."+serverURL+"/.extraheader").Output()
|
||||
assert.Equal(t, persisted, string(headers) == "\nAuthorization: Basic dG9rZW46dG9r\n", repo)
|
||||
assert.Equal(t, persisted, gitOutput(repo, "config", "http."+serverURL+"/.sslcainfo") != "", repo)
|
||||
@@ -345,15 +365,16 @@ func TestCheckoutTrustsTheRunnerCAAndOffersClientCertOnlyDuringTheStep(t *testin
|
||||
|
||||
func publish(t *testing.T, server, name string, files, submodules map[string]string) {
|
||||
work := t.TempDir()
|
||||
runGit(t, "init", "--initial-branch=main", work)
|
||||
runGit(t, "init", "--template=", "--initial-branch=main", work)
|
||||
for file, content := range files {
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(work, file)), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(work, file), []byte(content), 0o644))
|
||||
}
|
||||
runGit(t, "-C", work, "add", ".")
|
||||
var moduleConfig []byte
|
||||
for path, url := range submodules {
|
||||
runGit(t, "-C", work, "config", "-f", ".gitmodules", "submodule."+path+".path", path)
|
||||
runGit(t, "-C", work, "config", "-f", ".gitmodules", "submodule."+path+".url", url)
|
||||
moduleConfig = fmt.Appendf(moduleConfig, "[submodule %q]\n\tpath = %q\n\turl = %q\n", path, path, url)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(work, ".gitmodules"), moduleConfig, 0o644))
|
||||
commit := runGit(t, "--git-dir", filepath.Join(server, name, url), "rev-parse", "main")
|
||||
runGit(t, "-C", work, "update-index", "--add", "--cacheinfo", "160000,"+commit+","+path)
|
||||
runGit(t, "-C", work, "add", ".gitmodules")
|
||||
|
||||
@@ -6,7 +6,6 @@ package process
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -23,16 +22,13 @@ func TestGroupClosePropagatesToOrphan(t *testing.T) {
|
||||
childPID := ready()
|
||||
|
||||
require.NoError(t, cmd.Wait())
|
||||
require.Eventually(t, func() bool {
|
||||
return !processAlive(cmd.Process.Pid)
|
||||
}, 20*time.Second, 200*time.Millisecond, "the step process should have exited on its own")
|
||||
require.True(t, cmd.ProcessState.Exited(), "the step process should have exited on its own")
|
||||
|
||||
require.True(t, processAlive(childPID), "orphan should outlive its parent")
|
||||
|
||||
waitForExit := newProcessExitWaiter(t, childPID)
|
||||
require.NoError(t, group.Close())
|
||||
require.Eventually(t, func() bool {
|
||||
return !processAlive(childPID)
|
||||
}, 20*time.Second, 200*time.Millisecond, "closing the job must terminate the orphan")
|
||||
waitForExit()
|
||||
}
|
||||
|
||||
// TestGroupNestsWithKiller: assigned to the group first and the step's Killer
|
||||
@@ -53,10 +49,9 @@ func TestGroupNestsWithKiller(t *testing.T) {
|
||||
require.NoError(t, err, "the step's job object must nest inside the group's")
|
||||
defer killer.Close()
|
||||
|
||||
waitForExit := newProcessExitWaiter(t, cmd.Process.Pid)
|
||||
require.NoError(t, killer.Kill())
|
||||
require.Eventually(t, func() bool {
|
||||
return !processAlive(cmd.Process.Pid)
|
||||
}, 20*time.Second, 200*time.Millisecond, "cancelling the step should still kill its tree")
|
||||
waitForExit()
|
||||
}
|
||||
|
||||
// TestGroupNilSafe covers the fallback path: when the job object cannot be
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -46,23 +44,8 @@ func processAlive(pid int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// TestKillerKillsTree verifies that a process group captured by the killer is
|
||||
// terminated together with a child the process spawns afterwards. This mirrors
|
||||
// a step or post-task script that launches a child which spawns further
|
||||
// processes, where cancelling must take down the whole tree, not just the
|
||||
// direct child.
|
||||
func TestKillerKillsTree(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
pidFile := filepath.Join(dir, "child.pid")
|
||||
|
||||
// Parent shell backgrounds a long-lived child (writing its PID to a file)
|
||||
// and then sleeps. With job control off (non-interactive sh) the backgrounded
|
||||
// child stays in the parent's process group, so the group kill must reach it.
|
||||
script := fmt.Sprintf(`sleep 600 & echo $! > %q; sleep 600`, pidFile)
|
||||
cmd := exec.Command("/bin/sh", "-c", script)
|
||||
// Launch as its own process-group leader, exactly like a real process does
|
||||
// (see SysProcAttr), so the killer's PGID == the process PID.
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
cmd, ready := newTestProcess(t, "parent", false)
|
||||
require.NoError(t, cmd.Start())
|
||||
t.Cleanup(func() {
|
||||
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||
@@ -73,25 +56,10 @@ func TestKillerKillsTree(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer killer.Close()
|
||||
|
||||
// Wait for the backgrounded child PID to be reported.
|
||||
var childPID int
|
||||
require.Eventually(t, func() bool {
|
||||
b, e := os.ReadFile(pidFile)
|
||||
if e != nil {
|
||||
return false
|
||||
}
|
||||
s := strings.TrimSpace(string(b))
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
childPID, _ = strconv.Atoi(s)
|
||||
return childPID > 0 && processAlive(childPID)
|
||||
}, 20*time.Second, 100*time.Millisecond, "child process should start")
|
||||
childPID := ready()
|
||||
require.True(t, processAlive(childPID), "child process should start")
|
||||
|
||||
// Killing the group must terminate both the parent and the backgrounded child.
|
||||
require.NoError(t, killer.Kill())
|
||||
// Reap the parent so it does not linger as a zombie (which would still report
|
||||
// as alive); SIGKILL makes Wait return promptly.
|
||||
_ = cmd.Wait()
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
|
||||
@@ -5,7 +5,6 @@ package process
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -25,6 +24,23 @@ func processAlive(pid int) bool {
|
||||
return code == stillActive
|
||||
}
|
||||
|
||||
func newProcessExitWaiter(t *testing.T, pids ...int) func() {
|
||||
t.Helper()
|
||||
handles := make([]windows.Handle, 0, len(pids))
|
||||
for _, pid := range pids {
|
||||
handle, err := windows.OpenProcess(windows.SYNCHRONIZE, false, uint32(pid))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, windows.CloseHandle(handle)) })
|
||||
handles = append(handles, handle)
|
||||
}
|
||||
return func() {
|
||||
t.Helper()
|
||||
result, err := windows.WaitForMultipleObjects(handles, true, 20000)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, windows.WAIT_OBJECT_0, result, "processes should exit: %v", pids)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKillerKillsTree(t *testing.T) {
|
||||
cmd, ready := newTestProcess(t, "parent", false)
|
||||
require.NoError(t, cmd.Start())
|
||||
@@ -39,9 +55,7 @@ func TestKillerKillsTree(t *testing.T) {
|
||||
childPID := ready()
|
||||
require.True(t, processAlive(childPID), "child process should start")
|
||||
|
||||
waitForExit := newProcessExitWaiter(t, cmd.Process.Pid, childPID)
|
||||
require.NoError(t, killer.Kill())
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return !processAlive(cmd.Process.Pid) && !processAlive(childPID)
|
||||
}, 20*time.Second, 200*time.Millisecond, "parent and child should both be terminated")
|
||||
waitForExit()
|
||||
}
|
||||
@@ -173,11 +173,9 @@ func TestReporter_RunDaemonClose_Race(t *testing.T) {
|
||||
)
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
taskCtx, err := structpb.NewStruct(map[string]any{})
|
||||
require.NoError(t, err)
|
||||
cfg, _ := config.LoadDefault("")
|
||||
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{
|
||||
Context: taskCtx,
|
||||
Context: &structpb.Struct{},
|
||||
}, cfg)
|
||||
reporter.ResetSteps(newTestSteps(1))
|
||||
|
||||
@@ -474,10 +472,8 @@ func TestReporter_ServerCancelStillFlushesFinal(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
taskCtx, err := structpb.NewStruct(map[string]any{})
|
||||
require.NoError(t, err)
|
||||
cfg, _ := config.LoadDefault("")
|
||||
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{Context: taskCtx}, cfg)
|
||||
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{Context: &structpb.Struct{}}, cfg)
|
||||
reporter.ResetSteps(newTestSteps(1))
|
||||
|
||||
reporter.stateMu.Lock()
|
||||
@@ -527,10 +523,8 @@ func TestReporter_CloseReportsCancelledOnCanceledCtx(t *testing.T) {
|
||||
)
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
taskCtx, err := structpb.NewStruct(map[string]any{})
|
||||
require.NoError(t, err)
|
||||
cfg, _ := config.LoadDefault("")
|
||||
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{Context: taskCtx}, cfg)
|
||||
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{Context: &structpb.Struct{}}, cfg)
|
||||
reporter.ResetSteps(newTestSteps(1))
|
||||
|
||||
cancel()
|
||||
@@ -671,19 +665,15 @@ func TestReporter_SetOutputs(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "caf\xff", got.value)
|
||||
|
||||
maxValue := strings.Repeat("v", maxOutputValueLen)
|
||||
r.SetOutputs(map[string]string{"atlimit": maxValue})
|
||||
r.SetOutputs(map[string]string{"atlimit": longValue[:maxOutputValueLen]})
|
||||
got, ok = r.outputs["atlimit"]
|
||||
require.True(t, ok)
|
||||
assert.Len(t, got.value, maxOutputValueLen)
|
||||
}
|
||||
|
||||
func TestReporter_DebugSettingsAreNotMasked(t *testing.T) {
|
||||
taskCtx, err := structpb.NewStruct(map[string]any{})
|
||||
require.NoError(t, err)
|
||||
|
||||
reporter := NewReporter(t.Context(), nil, nil, &runnerv1.Task{
|
||||
Context: taskCtx,
|
||||
Context: &structpb.Struct{},
|
||||
Secrets: map[string]string{"ACTIONS_STEP_DEBUG": "true", "ACTIONS_RUNNER_DEBUG": "true", "TOKEN": "s3cr3t"},
|
||||
}, &config.Config{})
|
||||
defer deregisterGlobalMasks(reporter)
|
||||
@@ -861,10 +851,8 @@ func TestReporter_ServerFailureResultIsReportedAsFailure(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
taskCtx, err := structpb.NewStruct(map[string]any{})
|
||||
require.NoError(t, err)
|
||||
cfg, _ := config.LoadDefault("")
|
||||
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{Context: taskCtx}, cfg)
|
||||
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{Context: &structpb.Struct{}}, cfg)
|
||||
close(reporter.daemon)
|
||||
|
||||
require.NoError(t, reporter.ReportState(false))
|
||||
@@ -1031,9 +1019,7 @@ func newTestReporter(t *testing.T, client *mocks.Client, cfg *config.Config) *Re
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
t.Cleanup(cancel)
|
||||
taskCtx, err := structpb.NewStruct(map[string]any{})
|
||||
require.NoError(t, err)
|
||||
return NewReporter(ctx, cancel, client, &runnerv1.Task{Context: taskCtx}, cfg)
|
||||
return NewReporter(ctx, cancel, client, &runnerv1.Task{Context: &structpb.Struct{}}, cfg)
|
||||
}
|
||||
|
||||
func newTestSteps(n int) []*model.Step {
|
||||
@@ -1051,11 +1037,9 @@ func TestReporter_ReportedSteps(t *testing.T) {
|
||||
return &runnerv1.UpdateLogResponse{AckIndex: req.Index + int64(len(req.Rows))}, nil
|
||||
})
|
||||
client.On("UpdateTask", mock.Anything, mock.Anything).Maybe().Return(&runnerv1.UpdateTaskResponse{}, nil)
|
||||
taskCtx, err := structpb.NewStruct(map[string]any{})
|
||||
require.NoError(t, err)
|
||||
cfg, _ := config.LoadDefault("")
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{Context: taskCtx, Secrets: map[string]string{"TOKEN": "s3cr3t"}}, cfg)
|
||||
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{Context: &structpb.Struct{}, Secrets: map[string]string{"TOKEN": "s3cr3t"}}, cfg)
|
||||
reporter.RunDaemon()
|
||||
reporter.Logf("runner version")
|
||||
reporter.ResetSteps(steps)
|
||||
|
||||
@@ -117,7 +117,8 @@ func TestExportRetriesAndMatchesOTLP(t *testing.T) {
|
||||
certificate := server.TLS.Certificates[0]
|
||||
key, err := x509.MarshalPKCS8PrivateKey(certificate.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
certificatePath, keyPath := filepath.Join(t.TempDir(), "cert.pem"), filepath.Join(t.TempDir(), "key.pem")
|
||||
dir := t.TempDir()
|
||||
certificatePath, keyPath := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem")
|
||||
require.NoError(t, os.WriteFile(certificatePath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Certificate[0]}), 0o600))
|
||||
require.NoError(t, os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key}), 0o600))
|
||||
setEnv(t, map[string]string{
|
||||
|
||||
Reference in new issue
Block a user