v0.6.0 aba298d

containers_exec_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package executor
4
5import (
6	"strings"
7	"testing"
8)
9
10// execCalls are the `docker exec` calls recorded by the fake.
11func (f *fakeCtr) execCalls(t *testing.T) [][]string {
12	var out [][]string
13	for _, c := range f.calls(t) {
14		if c[0] == "exec" {
15			out = append(out, c)
16		}
17	}
18	return out
19}
20
21func TestRunStepThroughDockerExec(t *testing.T) {
22	f := newFakeCtr(t)
23	f.set(t, "exec_out", "hello from the container\n")
24	var work string
25	rec, res := ctrRun(t, f, "    container:\n      image: ubuntu:24.04\n      env:\n        CVAR: c\n",
26		"      - name: s\n        working-directory: sub\n        env:\n          FOO: bar\n        run: echo hi\n", func(c *Config) { work = c.Layout.WorkRoot })
27	if res.Conclusion != ConclusionSuccess {
28		t.Fatalf("%+v\n%s", res, logOf(rec.step(t, "Initialize containers")))
29	}
30	if got := strings.Join(rec.names(), "|"); got != "Set up job|Initialize containers|s|Stop containers|Complete job" {
31		t.Fatalf("steps %s", got)
32	}
33	ex := f.execCalls(t)
34	if len(ex) != 1 {
35		t.Fatalf("exec calls %v", ex)
36	}
37	c := strings.Join(ex[0], "\n")
38	id := strings.Repeat("0", 63) + "1"
39	for _, w := range []string{
40		"exec\n-i\n--workdir\n/__w/hello/hello/sub\n-e\nFOO\n", "\n-e\nGITHUB_WORKSPACE\n", "\n-e\nGITHUB_OUTPUT\n",
41		"\n" + id + "\nsh\n-e\n/__w/_temp/",
42	} {
43		if !strings.Contains(c, w) {
44			t.Errorf("exec lacks %q:\n%s", w, c)
45		}
46	}
47	if strings.Contains(c, "bar") || strings.Contains(c, work) {
48		t.Errorf("values and host paths stay off the command line:\n%s", c)
49	}
50	log := logOf(rec.step(t, "s"))
51	if !strings.Contains(log, "shell: sh -e {0}\n") || !strings.Contains(log, "hello from the container") {
52		t.Errorf("log:\n%s", log)
53	}
54	// The create command of the job container.
55	create := logOf(rec.step(t, "Initialize containers"))
56	for _, w := range []string{
57		"--workdir /__w/hello/hello --network github_network_", "  -e \"CVAR=c\" -e \"HOME=/github/home\" -e GITHUB_ACTIONS=true -e CI=true -v \"/var/run/docker.sock\":\"/var/run/docker.sock\" -v \"" + work + "\":\"/__w\"",
58		"\":\"/__e\":ro -v \"" + work + "/_temp\":\"/__w/_temp\"", "\":\"/__t\" -v \"" + work + "/_temp/_github_home\":\"/github/home\" -v \"" + work + "/_temp/_github_workflow\":\"/github/workflow\" --entrypoint \"tail\" ubuntu:24.04 \"-f\" \"/dev/null\"",
59	} {
60		if !strings.Contains(create, w) {
61			t.Errorf("create lacks %q:\n%s", w, create)
62		}
63	}
64}
65
66func TestRunEnvIsTranslatedForTheContainer(t *testing.T) {
67	f := newFakeCtr(t)
68	f.set(t, "exec_env", "1")
69	rec, res := ctrRun(t, f, "    container: ubuntu:24.04\n",
70		"      - name: s\n        run: echo hi\n", nil)
71	if res.Conclusion != ConclusionSuccess {
72		t.Fatalf("%+v %v", res, rec.names())
73	}
74	// HOME is the container's own (/github/home): it is not passed with -e.
75	if c := strings.Join(f.execCalls(t)[0], "\n"); strings.Contains(c, "\nHOME\n") {
76		t.Errorf("HOME must not be passed:\n%s", c)
77	}
78	log := logOf(rec.step(t, "s"))
79	for _, w := range []string{
80		"ENV GITHUB_WORKSPACE=/__w/hello/hello", "RUNNER_TEMP=/__w/_temp",
81		"GITHUB_OUTPUT=/__w/_temp/_runner_file_commands/set_output_", "GITHUB_EVENT_PATH=/github/workflow/event.json",
82	} {
83		if !strings.Contains(log, w) {
84			t.Errorf("log lacks %q:\n%s", w, log)
85		}
86	}
87}
88
89func TestNodeActionInContainerUsesExternalsAndAlpine(t *testing.T) {
90	for _, tc := range []struct{ release, node string }{{"ID=ubuntu\nVERSION_ID=\"24.04\"\n", "node24"}, {"ID=alpine\nVERSION_ID=3.23.4\n", "node24_alpine"}} {
91		f := newFakeCtr(t)
92		f.set(t, "exec_out", tc.release)
93		tree := files{
94			".github/actions/x/action.yml": "name: x\nruns:\n  using: node24\n  main: index.js\n",
95			".github/actions/x/index.js":   "x\n",
96		}
97		rec, res := ctrRun(t, f, "    container: ubuntu:24.04\n", "      - name: a\n        uses: ./.github/actions/x\n", func(c *Config) {
98			writeFiles(t, c.Layout.Workspace, tree)
99		})
100		if res.Conclusion != ConclusionSuccess {
101			t.Fatalf("%s: %+v\n%s", tc.node, res, logOf(rec.step(t, "a")))
102		}
103		ex := f.execCalls(t)
104		if len(ex) != 2 {
105			t.Fatalf("%s: exec calls %v", tc.node, ex)
106		}
107		if got := strings.Join(ex[0][len(ex[0])-4:len(ex[0])-1], " "); got != "sh -c "+alpineCheck {
108			t.Errorf("probe: %v", ex[0])
109		}
110		run := strings.Join(ex[1], "\n")
111		if !strings.Contains(run, "\n/__e/"+tc.node+"/bin/node\n") || !strings.Contains(run, "\n/__w/hello/hello/./.github/actions/x/index.js") {
112			t.Errorf("%s: node call:\n%s", tc.node, run)
113		}
114		if got := logOf(rec.step(t, "a")); !strings.Contains(got, "exec  "+strings.Repeat("0", 63)+"1 sh -c \"cat /etc/*release | grep ^ID\"") {
115			t.Errorf("probe line missing:\n%s", got)
116		}
117	}
118}
119
120func TestCommandPathsFromTheContainerAreTranslated(t *testing.T) {
121	f := newFakeCtr(t)
122	// The step prints an annotation for a file at its container path.
123	f.set(t, "exec_out", "::error file=/__w/hello/hello/src/a.go,line=3::boom\n")
124	rec, res := ctrRun(t, f, ctrJob, "      - name: s\n        run: echo\n", nil)
125	if res.Conclusion != ConclusionSuccess {
126		t.Fatalf("%+v", res)
127	}
128	log := logOf(rec.step(t, "s"))
129	if !strings.Contains(log, "##[error]boom") {
130		t.Errorf("log:\n%s", log)
131	}
132}
133
134func TestDockerActionJoinsTheJobNetworkAndContextHasContainerKey(t *testing.T) {
135	f := newFakeCtr(t)
136	rec, res := ctrRun(t, f, "    services:\n      redis:\n        image: redis:7\n",
137		"      - uses: docker://alpine:3.20\n      - name: ctx\n        run: echo \"${{ toJSON(job.container) }}\"\n", nil)
138	if res.Conclusion != ConclusionSuccess {
139		t.Fatalf("%+v %v", res, rec.names())
140	}
141	var run []string
142	for _, c := range f.calls(t) {
143		if c[0] == "run" {
144			run = c
145		}
146	}
147	if run == nil || !strings.Contains(strings.Join(run, "\n"), "\n--network\ngithub_network_") {
148		t.Errorf("docker run without the job network: %v", run)
149	}
150	if got := logOf(rec.step(t, "ctx")); !strings.Contains(got, "\"network\": \"github_network_") {
151		t.Errorf("ctx:\n%s", got)
152	}
153}

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