-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexitcode_test.go
More file actions
173 lines (157 loc) · 4.45 KB
/
Copy pathexitcode_test.go
File metadata and controls
173 lines (157 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package main
import (
"bytes"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"testing"
)
func TestFatalfExitsCbFailure(t *testing.T) {
orig := osExit
var code int
var calls int
osExit = func(c int) {
code = c
calls++
}
t.Cleanup(func() { osExit = orig })
fatalf("boom: %v", errors.New("x"))
if calls != 1 {
t.Fatalf("osExit called %d times, want 1", calls)
}
if code != exitCbFailure {
t.Fatalf("got exit code %d, want %d", code, exitCbFailure)
}
}
func TestExitAndInterruptConstants(t *testing.T) {
if exitUsage != 2 {
t.Errorf("exitUsage = %d, want 2", exitUsage)
}
if exitCbFailure != 120 {
t.Errorf("exitCbFailure = %d, want 120", exitCbFailure)
}
if exitInterrupted != 130 {
t.Errorf("exitInterrupted = %d, want 130", exitInterrupted)
}
}
func TestExitCodeDefault(t *testing.T) {
// osExit is the indirection every exit-code decision uses in unit tests.
// A leaked stub would make later tests silently swallow real exits.
if reflect.ValueOf(osExit).Pointer() != reflect.ValueOf(os.Exit).Pointer() {
t.Error("osExit does not default to os.Exit")
}
}
func TestSubprocessExitCodes(t *testing.T) {
if _, err := exec.LookPath("go"); err != nil {
t.Skip("go not on PATH:", err)
}
cbPath := buildTestCb(t)
workDir := t.TempDir()
tests := []struct {
name string
args []string
workDir string
want int
}{
{
name: "unknown subcommand exits 2",
args: []string{"bogus-subcommand"},
workDir: filepath.Join(workDir, "unknown"),
want: exitUsage,
},
{
name: "unexpose with no args exits 120",
args: []string{"unexpose"},
workDir: filepath.Join(workDir, "unexpose"),
want: exitCbFailure,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := os.MkdirAll(tt.workDir, 0755); err != nil {
t.Fatalf("mkdir workdir: %v", err)
}
runExitTest(t, cbPath, tt.args, tt.want, tt.workDir)
})
}
}
// buildTestCb builds a temporary cb binary for the subprocess exit-code
// tests. It requires `go` on PATH and a writable module/build cache. It
// also relies on registryPath() resolving relative to the spawned
// executable's directory, so the test runs without a real
// container-bin.toml and falls back to defaultRegistry(). If
// registryPath() ever changes to a user-profile location, this test could
// start creating container-bin.mutation.lock in the developer's real
// registry.
func buildTestCb(t *testing.T) string {
t.Helper()
dir := t.TempDir()
testBin := "cb_test"
cbBin := "cb"
if runtime.GOOS == "windows" {
testBin += ".exe"
cbBin += ".exe"
}
testPath := filepath.Join(dir, testBin)
cbPath := filepath.Join(dir, cbBin)
build := exec.Command("go", "build", "-o", testPath, ".")
if out, err := build.CombinedOutput(); err != nil {
t.Fatalf("go build: %v\n%s", err, out)
}
// main() only exposes the management CLI when argv[0] is "cb",
// "container-bin", or starts with "cb-v". Build the artifact as cb_test,
// then create an executable named cb in the same temp directory.
if err := os.Link(testPath, cbPath); err != nil {
if err := copyTestFile(testPath, cbPath); err != nil {
t.Fatalf("create cb copy: %v", err)
}
}
return cbPath
}
// copyTestFile is this test's own hardlink fallback. The production copy now
// lives unexported inside internal/registry, whose only caller is InstallShims;
// duplicating seventeen lines here is preferable to widening that package's
// exported surface for a single test consumer.
func copyTestFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return err
}
_, cpErr := io.Copy(out, in)
closeErr := out.Close()
if cpErr != nil {
return cpErr
}
return closeErr
}
func runExitTest(t *testing.T, cbPath string, args []string, want int, workDir string) {
t.Helper()
cmd := exec.Command(cbPath, args...)
cmd.Dir = workDir
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil && !isExitError(err) {
t.Fatalf("run %v: %v\nstderr: %s", args, err, stderr.String())
}
if cmd.ProcessState == nil {
t.Fatal("no process state")
}
if got := cmd.ProcessState.ExitCode(); got != want {
t.Fatalf("run %v exit code = %d, want %d\nstdout: %s\nstderr: %s", args, got, want, stdout.String(), stderr.String())
}
}
func isExitError(err error) bool {
_, ok := err.(*exec.ExitError)
return ok
}