package engine import ( "errors" "os" "path/filepath" "testing" ) func TestCheckCommandAnswersLikeExecvp(t *testing.T) { rootfs := t.TempDir() for dir, mode := range map[string]os.FileMode{"usr/bin": 0o755, "usr/local/bin": 0o755, "opt/bin": 0o755} { if err := os.MkdirAll(filepath.Join(rootfs, dir), mode); err != nil { t.Fatal(err) } } for file, mode := range map[string]os.FileMode{ "usr/bin/busybox": 0o755, "usr/bin/dash": 0o755, "usr/bin/notes": 0o644, "usr/local/bin/tool": 0o644, // shadowed by an executable later in PATH "usr/bin/tool": 0o755, "usr/local/bin/broken": 0o644, } { if err := os.WriteFile(filepath.Join(rootfs, file), []byte("#!/bin/sh\n"), mode); err != nil { t.Fatal(err) } } // merged /usr with a relative link, and a busybox applet with an absolute one for link, target := range map[string]string{"bin": "usr/bin", "usr/bin/sh": "dash", "usr/bin/ash": "/bin/busybox"} { if err := os.Symlink(target, filepath.Join(rootfs, link)); err != nil { t.Fatal(err) } } work := []Mount{{Mode: MountBind, Source: t.TempDir(), Dest: "/work"}} tests := []struct { name string command string workdir string env []EnvVar mounts []Mount wantCode int // 0: runnable or not known before the start }{ {"a name found in PATH", "sh", "", nil, nil, 0}, {"an absolute path through the merged-usr link", "/bin/sh", "", nil, nil, 0}, {"an absolute symlink resolved in the rootfs, not on the host", "ash", "", nil, nil, 0}, {"a name nowhere in PATH", "missing", "", nil, nil, ExitNotFound}, {"a path that does not exist", "/bin/missing", "", nil, nil, ExitNotFound}, {"a file without an execute bit", "notes", "", nil, nil, ExitNotExecutable}, {"a directory", "/usr/bin", "", nil, nil, ExitNotExecutable}, {"an executable later in PATH wins over a non-executable earlier", "tool", "", nil, nil, 0}, {"a non-executable and nothing after it", "broken", "", nil, nil, ExitNotExecutable}, {"a path under a mount is not checked", "/work/build.sh", "", nil, work, 0}, {"a relative path from a mounted workdir is not checked", "./build.sh", "/work", nil, work, 0}, {"a path in an implicit tmpfs is not checked", "/tmp/x", "", nil, nil, 0}, {"the image's PATH replaces the default", "sh", "", []EnvVar{{Key: "PATH", Value: "/opt/bin"}}, nil, ExitNotFound}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { spec := RunSpec{RootFS: rootfs, Command: []string{tt.command}, Workdir: tt.workdir, Env: tt.env, Mounts: tt.mounts} err := CheckCommand(spec) var commandErr *CommandError switch { case tt.wantCode == 0 && err != nil: t.Errorf("got %v, want the command to pass", err) case tt.wantCode != 0 && (!errors.As(err, &commandErr) || commandErr.Code != tt.wantCode): t.Errorf("got %v, want exit code %d", err, tt.wantCode) } }) } }