1// SPDX-License-Identifier: MIT
2
3package executor
4
5import (
6 "os"
7 "os/exec"
8 "strings"
9 "testing"
10 "time"
11)
12
13// Real docker: set ERMITE_RUNNER_IT_DOCKER=1 on a host with a Docker daemon
14// and the images alpine:3.20 and busybox:latest (no pulls are needed; the
15// tests do pull, so the network must work otherwise).
16func realDocker(t *testing.T) string {
17 t.Helper()
18 if os.Getenv("ERMITE_RUNNER_IT_DOCKER") != "1" {
19 t.Skip("set ERMITE_RUNNER_IT_DOCKER=1 (needs a Docker daemon)")
20 }
21 p, err := exec.LookPath("docker")
22 if err != nil {
23 t.Skip("no docker")
24 }
25 return p
26}
27
28func TestRealDockerJobContainerAndService(t *testing.T) {
29 docker := realDocker(t)
30 needTools(t, "bash")
31 yaml := ` container: alpine:3.20
32 services:
33 web:
34 image: busybox:latest
35 options: >-
36 --health-cmd "true" --health-interval 1s --health-retries 3
37 --entrypoint sh
38 command: -c "sleep 300"
39 steps:
40 - name: where
41 run: |
42 echo "pwd=$PWD"
43 echo "id=$(id -u)"
44 echo "home=$HOME"
45 echo "temp=$RUNNER_TEMP"
46 ls -d /__w /__t /__e /github/home /github/workflow
47 echo "hi from alpine" >> "$GITHUB_STEP_SUMMARY"
48 echo "k=v" >> "$GITHUB_OUTPUT"
49 - name: svc
50 run: ping -c1 web >/dev/null && echo "web resolves"
51`
52 rec, res := runLocal(t, wfHead+yaml, "j", func(c *Config) {
53 c.Actions.DockerPath = docker
54 c.BaseEnv = append(c.BaseEnv, "ACTIONS_SERVICE_CONTAINER_COMMAND=true", "DOCKER_HOST="+os.Getenv("DOCKER_HOST"), "XDG_RUNTIME_DIR="+os.Getenv("XDG_RUNTIME_DIR"))
55 c.Actions.ExternalsDir = fakeNode(t)
56 })
57 log := logOf(rec.step(t, "where")) + logOf(rec.step(t, "svc"))
58 if res.Conclusion != ConclusionSuccess {
59 t.Fatalf("%+v\n%s\n%s", res, logOf(rec.step(t, "Initialize containers")), log)
60 }
61 for _, w := range []string{"pwd=/__w/hello/hello", "home=/github/home", "temp=/__w/_temp", "/github/workflow", "web resolves"} {
62 if !strings.Contains(log, w) {
63 t.Errorf("lacks %q:\n%s", w, log)
64 }
65 }
66 if out, _ := exec.Command(docker, "ps", "-a", "--format", "{{.Names}}", "--filter", "name=busybox").Output(); strings.TrimSpace(string(out)) != "" {
67 t.Errorf("containers left: %s", out)
68 }
69 if out, _ := exec.Command(docker, "network", "ls", "--format", "{{.Name}}", "--filter", "name=github_network_").Output(); strings.TrimSpace(string(out)) != "" {
70 t.Errorf("networks left: %s", out)
71 }
72}
73
74func TestRealDockerCancelRemovesEverything(t *testing.T) {
75 docker := realDocker(t)
76 needTools(t, "bash")
77 cancel := NewCanceller()
78 go func() { time.Sleep(6 * time.Second); cancel.Cancel(CancelUser) }()
79 rec, res := runLocalC(t, cancel, wfHead+" container: alpine:3.20\n steps:\n - name: long\n run: sleep 120\n", "j", func(c *Config) {
80 c.Actions.DockerPath = docker
81 c.BaseEnv = append(c.BaseEnv, "XDG_RUNTIME_DIR="+os.Getenv("XDG_RUNTIME_DIR"))
82 })
83 if res.Conclusion != ConclusionCancelled {
84 t.Fatalf("%+v %v", res, rec.names())
85 }
86 if out, _ := exec.Command(docker, "network", "ls", "--format", "{{.Name}}", "--filter", "name=github_network_").Output(); strings.TrimSpace(string(out)) != "" {
87 t.Errorf("networks left: %s", out)
88 }
89}