Skip to content

Commit c7f3031

Browse files
laurazardvvoland
authored andcommitted
tests/run: fix flaky RunAttachTermination test
This test was just incorrect (and testing incorrect behavior): it was checking that `docker run` exited with a `context canceled` error after signalling the CLI/cancelling the command's context, but this was incorrect (and was fixed in 991b130 - which was when this test started failing). However, since this test assertion was happening inside of a goroutine, it would sometimes pass if this assertion didn't get to run before the test suite terminated. It was flaky because sometimes this assertion inside the goroutine did get to execute, but after the test finished execution, which is a big no-no. As an aside, assertions inside goroutines are generally bad, and `govet` even has a linter for this (but it only catches `t.Fatal` and `t.FailNow` calls and not `assert.Xx`. Signed-off-by: Laura Brehm <[email protected]> (cherry picked from commit eac8357) Signed-off-by: Paweł Gronowski <[email protected]>
1 parent a35c363 commit c7f3031

File tree

1 file changed

+103
-14
lines changed

1 file changed

+103
-14
lines changed

cli/command/container/run_test.go

+103-14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"io"
77
"net"
8-
"os/signal"
98
"syscall"
109
"testing"
1110
"time"
@@ -38,15 +37,85 @@ func TestRunLabel(t *testing.T) {
3837
assert.NilError(t, cmd.Execute())
3938
}
4039

41-
func TestRunAttachTermination(t *testing.T) {
40+
func TestRunAttach(t *testing.T) {
4241
p, tty, err := pty.Open()
4342
assert.NilError(t, err)
43+
defer func() {
44+
_ = tty.Close()
45+
_ = p.Close()
46+
}()
47+
48+
var conn net.Conn
49+
attachCh := make(chan struct{})
50+
fakeCLI := test.NewFakeCli(&fakeClient{
51+
createContainerFunc: func(_ *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ *specs.Platform, _ string) (container.CreateResponse, error) {
52+
return container.CreateResponse{
53+
ID: "id",
54+
}, nil
55+
},
56+
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
57+
server, client := net.Pipe()
58+
conn = server
59+
t.Cleanup(func() {
60+
_ = server.Close()
61+
})
62+
attachCh <- struct{}{}
63+
return types.NewHijackedResponse(client, types.MediaTypeRawStream), nil
64+
},
65+
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
66+
responseChan := make(chan container.WaitResponse, 1)
67+
errChan := make(chan error)
68+
69+
responseChan <- container.WaitResponse{
70+
StatusCode: 33,
71+
}
72+
return responseChan, errChan
73+
},
74+
// use new (non-legacy) wait API
75+
// see: 38591f20d07795aaef45d400df89ca12f29c603b
76+
Version: "1.30",
77+
}, func(fc *test.FakeCli) {
78+
fc.SetOut(streams.NewOut(tty))
79+
fc.SetIn(streams.NewIn(tty))
80+
})
81+
82+
cmd := NewRunCommand(fakeCLI)
83+
cmd.SetArgs([]string{"-it", "busybox"})
84+
cmd.SilenceUsage = true
85+
cmdErrC := make(chan error, 1)
86+
go func() {
87+
cmdErrC <- cmd.Execute()
88+
}()
89+
90+
// run command should attempt to attach to the container
91+
select {
92+
case <-time.After(5 * time.Second):
93+
t.Fatal("containerAttachFunc was not called before the 5 second timeout")
94+
case <-attachCh:
95+
}
96+
97+
// end stream from "container" so that we'll detach
98+
conn.Close()
99+
100+
select {
101+
case cmdErr := <-cmdErrC:
102+
assert.Equal(t, cmdErr, cli.StatusError{
103+
StatusCode: 33,
104+
})
105+
case <-time.After(2 * time.Second):
106+
t.Fatal("cmd did not return within timeout")
107+
}
108+
}
44109

110+
func TestRunAttachTermination(t *testing.T) {
111+
p, tty, err := pty.Open()
112+
assert.NilError(t, err)
45113
defer func() {
46114
_ = tty.Close()
47115
_ = p.Close()
48116
}()
49117

118+
var conn net.Conn
50119
killCh := make(chan struct{})
51120
attachCh := make(chan struct{})
52121
fakeCLI := test.NewFakeCli(&fakeClient{
@@ -61,42 +130,62 @@ func TestRunAttachTermination(t *testing.T) {
61130
},
62131
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
63132
server, client := net.Pipe()
133+
conn = server
64134
t.Cleanup(func() {
65135
_ = server.Close()
66136
})
67137
attachCh <- struct{}{}
68138
return types.NewHijackedResponse(client, types.MediaTypeRawStream), nil
69139
},
70-
Version: "1.36",
140+
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
141+
responseChan := make(chan container.WaitResponse, 1)
142+
errChan := make(chan error)
143+
144+
responseChan <- container.WaitResponse{
145+
StatusCode: 130,
146+
}
147+
return responseChan, errChan
148+
},
149+
// use new (non-legacy) wait API
150+
// see: 38591f20d07795aaef45d400df89ca12f29c603b
151+
Version: "1.30",
71152
}, func(fc *test.FakeCli) {
72153
fc.SetOut(streams.NewOut(tty))
73154
fc.SetIn(streams.NewIn(tty))
74155
})
75-
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM)
76-
defer cancel()
77-
78-
assert.Equal(t, fakeCLI.In().IsTerminal(), true)
79-
assert.Equal(t, fakeCLI.Out().IsTerminal(), true)
80156

81157
cmd := NewRunCommand(fakeCLI)
82158
cmd.SetArgs([]string{"-it", "busybox"})
83159
cmd.SilenceUsage = true
160+
cmdErrC := make(chan error, 1)
84161
go func() {
85-
assert.ErrorIs(t, cmd.ExecuteContext(ctx), context.Canceled)
162+
cmdErrC <- cmd.Execute()
86163
}()
87164

165+
// run command should attempt to attach to the container
88166
select {
89167
case <-time.After(5 * time.Second):
90-
t.Fatal("containerAttachFunc was not called before the 5 second timeout")
168+
t.Fatal("containerAttachFunc was not called before the timeout")
91169
case <-attachCh:
92170
}
93171

94-
assert.NilError(t, syscall.Kill(syscall.Getpid(), syscall.SIGTERM))
172+
assert.NilError(t, syscall.Kill(syscall.Getpid(), syscall.SIGINT))
173+
// end stream from "container" so that we'll detach
174+
conn.Close()
175+
95176
select {
96-
case <-time.After(5 * time.Second):
97-
cancel()
98-
t.Fatal("containerKillFunc was not called before the 5 second timeout")
99177
case <-killCh:
178+
case <-time.After(5 * time.Second):
179+
t.Fatal("containerKillFunc was not called before the timeout")
180+
}
181+
182+
select {
183+
case cmdErr := <-cmdErrC:
184+
assert.Equal(t, cmdErr, cli.StatusError{
185+
StatusCode: 130,
186+
})
187+
case <-time.After(2 * time.Second):
188+
t.Fatal("cmd did not return before the timeout")
100189
}
101190
}
102191

0 commit comments

Comments
 (0)