v0.4.4 edcdd4c

toolkit_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package cachesvc
4
5import (
6	"bytes"
7	"context"
8	"os"
9	"os/exec"
10	"path/filepath"
11	"strings"
12	"testing"
13	"time"
14)
15
16// envSetupGoDist is the dist directory of a checkout of actions/setup-go
17// (dist/setup/index.js and dist/cache-save/index.js bundle the real
18// @actions/cache, 6.3.0 at the time of writing). TestSetupGoCacheRoundTrip runs
19// them against the service: the protocol as the toolkit speaks it, not as the
20// other tests of this package spell it. It needs node, tar, zstd and the
21// checkout, and no network; without them the test is skipped.
22const envSetupGoDist = "ERMITE_TEST_SETUP_GO_DIST"
23
24// TestSetupGoCacheRoundTrip is AC7's "second run of a setup-go cache: true
25// workflow logs a cache hit and restores", with the real action code: run 1
26// misses, the post step saves, run 2 hits and restores the files; jobs of
27// another repository or of another ref miss.
28func TestSetupGoCacheRoundTrip(t *testing.T) {
29	dist := os.Getenv(envSetupGoDist)
30	if dist == "" {
31		t.Skip(envSetupGoDist + " is not set (a built checkout of actions/setup-go)")
32	}
33	for _, bin := range []string{"node", "tar", "zstd", "go", "sed"} {
34		if _, err := exec.LookPath(bin); err != nil {
35			t.Skip(bin + " not found")
36		}
37	}
38	r := newRig(t, 1<<30)
39	root := t.TempDir()
40	work := filepath.Join(root, "ws")
41	modcache, gocache := filepath.Join(root, "gomod"), filepath.Join(root, "gocache")
42	for _, d := range []string{work, filepath.Join(modcache, "pkg"), gocache, filepath.Join(root, "tmp"), filepath.Join(root, "tc"), filepath.Join(root, "bin")} {
43		if err := os.MkdirAll(d, 0o755); err != nil {
44			t.Fatal(err)
45		}
46	}
47	cached := map[string]string{
48		filepath.Join(modcache, "pkg", "a.txt"): "module cache file\n",
49		filepath.Join(gocache, "blob"):          strings.Repeat("0123456789abcdef", 1<<16),
50	}
51	write := func() {
52		t.Helper()
53		for p, c := range cached {
54			if err := os.WriteFile(p, []byte(c), 0o644); err != nil {
55				t.Fatal(err)
56			}
57		}
58	}
59	remove := func() {
60		for p := range cached {
61			_ = os.Remove(p)
62		}
63	}
64	if err := os.WriteFile(filepath.Join(work, "go.sum"), []byte("sum\n"), 0o644); err != nil {
65		t.Fatal(err)
66	}
67	write()
68
69	// A "go" without the "-X:…" suffix that some distributions add to the
70	// version: the setup-go key then has no ':' (a quirk of the local toolchain,
71	// unrelated to the service).
72	realGo, _ := exec.LookPath("go")
73	shim := "#!/bin/sh\n" + realGo + " \"$@\" | sed 's/-X:[a-z0-9]*//g'\n"
74	if err := os.WriteFile(filepath.Join(root, "bin", "go"), []byte(shim), 0o755); err != nil {
75		t.Fatal(err)
76	}
77
78	// step runs one entry point of the action as the job would run it.
79	step := func(j *job, script string, extra ...string) (out, state, output string) {
80		t.Helper()
81		for _, f := range []string{"state", "output", "env", "path"} {
82			if err := os.WriteFile(filepath.Join(root, f+".file"), nil, 0o644); err != nil {
83				t.Fatal(err)
84			}
85		}
86		ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
87		defer cancel()
88		cmd := exec.CommandContext(ctx, "node", filepath.Join(dist, script, "index.js"))
89		cmd.Dir = work
90		cmd.Env = append([]string{
91			"PATH=" + filepath.Join(root, "bin") + ":" + os.Getenv("PATH"), "HOME=" + root,
92			"GITHUB_SERVER_URL=http://forge.test", "GITHUB_ACTIONS=true", "GITHUB_WORKSPACE=" + work,
93			"RUNNER_OS=Linux", "ImageOS=ubuntu24", "RUNNER_TEMP=" + filepath.Join(root, "tmp"), "RUNNER_TOOL_CACHE=" + filepath.Join(root, "tc"),
94			// What the executor sets for node actions (executor.addRuntimeEnv).
95			"ACTIONS_CACHE_URL=" + j.r.srv.URL + "/", "ACTIONS_RUNTIME_URL=" + j.r.srv.URL + "/", "ACTIONS_RUNTIME_TOKEN=" + j.token,
96			"GITHUB_STATE=" + filepath.Join(root, "state.file"), "GITHUB_OUTPUT=" + filepath.Join(root, "output.file"),
97			"GITHUB_ENV=" + filepath.Join(root, "env.file"), "GITHUB_PATH=" + filepath.Join(root, "path.file"),
98			"GOMODCACHE=" + modcache, "GOCACHE=" + gocache, "GOPATH=" + filepath.Join(root, "gopath"),
99			"INPUT_CACHE=true", "INPUT_CHECK-LATEST=false", "INPUT_GO-VERSION=", "INPUT_CACHE-DEPENDENCY-PATH=" + filepath.Join(work, "go.sum"),
100		}, extra...)
101		var buf bytes.Buffer
102		cmd.Stdout, cmd.Stderr = &buf, &buf
103		cmd.WaitDelay = 5 * time.Second
104		// Run is synchronous; the output is read after it returns.
105		if err := cmd.Run(); err != nil {
106			t.Fatalf("%s: %v\n%s", script, err, buf.String())
107		}
108		st, _ := os.ReadFile(filepath.Join(root, "state.file"))
109		op, _ := os.ReadFile(filepath.Join(root, "output.file"))
110		return buf.String(), string(st), string(op)
111	}
112	// value reads "name<<delimiter\nvalue\ndelimiter" of a file command file.
113	value := func(file, name string) string {
114		lines := strings.Split(file, "\n")
115		for i, l := range lines {
116			if strings.HasPrefix(l, name+"<<") && i+1 < len(lines) {
117				return lines[i+1]
118			}
119		}
120		return ""
121	}
122
123	a := r.job("run1", "bnema/app", main_, main_)
124	out, state, output := step(a, "setup")
125	if !strings.Contains(out, "Cache is not found") || value(output, "cache-hit") != "false" {
126		t.Fatalf("run 1 must miss:\n%s", out)
127	}
128	key := value(state, "CACHE_KEY")
129	if !strings.HasPrefix(key, "setup-go-Linux-") {
130		t.Fatalf("state: %q", state)
131	}
132	out, _, _ = step(a, "cache-save", "STATE_CACHE_KEY="+key)
133	if !strings.Contains(out, "Cache saved successfully") {
134		t.Fatalf("save:\n%s", out)
135	}
136	if n, _, _ := r.svc.store.stats(); n != 1 {
137		t.Fatalf("entries after save: %d", n)
138	}
139
140	// Run 2: a new job of the same repository and ref, with the caches gone.
141	remove()
142	out, _, output = step(r.job("run2", "bnema/app", main_, main_), "setup")
143	if !strings.Contains(out, "Cache restored from key: "+key) || value(output, "cache-hit") != "true" {
144		t.Fatalf("run 2 must hit:\n%s", out)
145	}
146	for p, want := range cached {
147		if got, err := os.ReadFile(p); err != nil || string(got) != want {
148			t.Errorf("%s not restored (%v)", p, err)
149		}
150	}
151
152	// Another repository, and another ref that does not read main, restore nothing.
153	for name, j := range map[string]*job{
154		"another repository": r.job("run3", "bnema/other", main_, main_),
155		"another ref":        r.job("run4", "bnema/app", feat, feat),
156	} {
157		remove()
158		out, _, output = step(j, "setup")
159		if !strings.Contains(out, "Cache is not found") || value(output, "cache-hit") != "false" {
160			t.Fatalf("%s must miss:\n%s", name, out)
161		}
162		if _, err := os.Stat(filepath.Join(gocache, "blob")); err == nil {
163			t.Fatalf("%s: files were restored", name)
164		}
165	}
166}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close