Skip to content

Commit 166b078

Browse files
committed
Rewrite detach tests without unbuffer
Signed-off-by: apostasie <[email protected]>
1 parent 32f0782 commit 166b078

File tree

4 files changed

+278
-248
lines changed

4 files changed

+278
-248
lines changed

cmd/nerdctl/container/container_attach_linux_test.go

Lines changed: 159 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package container
1818

1919
import (
20-
"bytes"
20+
"errors"
21+
"os"
2122
"strings"
2223
"testing"
24+
"time"
2325

2426
"gotest.tools/v3/assert"
2527

@@ -28,133 +30,185 @@ import (
2830
"github.com/containerd/nerdctl/v2/pkg/testutil/test"
2931
)
3032

31-
// skipAttachForDocker should be called by attach-related tests that assert 'read detach keys' in stdout.
32-
func skipAttachForDocker(t *testing.T) {
33-
t.Helper()
34-
if testutil.GetTarget() == testutil.Docker {
35-
t.Skip("When detaching from a container, for a session started with 'docker attach'" +
36-
", it prints 'read escape sequence', but for one started with 'docker (run|start)', it prints nothing." +
37-
" However, the flag is called '--detach-keys' in all cases" +
38-
", so nerdctl prints 'read detach keys' for all cases" +
39-
", and that's why this test is skipped for Docker.")
40-
}
41-
}
33+
/*
34+
Important notes:
35+
- for both docker and nerdctl, you can run+detach of a container and exit 0, while the container would actually fail starting
36+
- nerdctl (not docker): on run, detach will race anything on stdin before the detach sequence from reaching the container
37+
- nerdctl AND docker: on attach ^
38+
- exit code variants: https://github.com/containerd/nerdctl/issues/3571
39+
*/
4240

43-
// prepareContainerToAttach spins up a container (entrypoint = shell) with `-it` and detaches from it
44-
// so that it can be re-attached to later.
45-
func prepareContainerToAttach(base *testutil.Base, containerName string) {
46-
opts := []func(*testutil.Cmd){
47-
testutil.WithStdin(testutil.NewDelayOnceReader(bytes.NewReader(
48-
[]byte{16, 17}, // ctrl+p,ctrl+q, see https://www.physics.udel.edu/~watson/scen103/ascii.html
49-
))),
41+
func TestAttach(t *testing.T) {
42+
// In nerdctl the detach return code from the container after attach is 0, but in docker the return code is 1.
43+
// This behaviour is reported in https://github.com/containerd/nerdctl/issues/3571
44+
ex := 0
45+
if nerdtest.IsDocker() {
46+
ex = 1
5047
}
51-
// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.
52-
// unbuffer(1) can be installed with `apt-get install expect`.
53-
//
54-
// "-p" is needed because we need unbuffer to read from stdin, and from [1]:
55-
// "Normally, unbuffer does not read from stdin. This simplifies use of unbuffer in some situations.
56-
// To use unbuffer in a pipeline, use the -p flag."
57-
//
58-
// [1] https://linux.die.net/man/1/unbuffer
59-
base.CmdWithHelper([]string{"unbuffer", "-p"}, "run", "-it", "--name", containerName, testutil.CommonImage).
60-
CmdOption(opts...).AssertOutContains("read detach keys")
61-
container := base.InspectContainer(containerName)
62-
assert.Equal(base.T, container.State.Running, true)
63-
}
6448

65-
func TestAttach(t *testing.T) {
66-
t.Parallel()
49+
testCase := nerdtest.Setup()
6750

68-
t.Skip("This test is very unstable and currently skipped. See https://github.com/containerd/nerdctl/issues/3558")
51+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
52+
helpers.Anyhow("rm", "-f", data.Identifier())
53+
}
6954

70-
skipAttachForDocker(t)
55+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
56+
cmd := helpers.Command("run", "--rm", "-it", "--name", data.Identifier(), testutil.CommonImage)
57+
cmd.WithPseudoTTY(func(f *os.File) error {
58+
_, err := f.Write([]byte{16, 17})
59+
return err
60+
})
61+
62+
cmd.Run(&test.Expected{
63+
ExitCode: 0,
64+
Errors: []error{errors.New("read detach keys")},
65+
Output: func(stdout string, info string, t *testing.T) {
66+
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
67+
},
68+
})
69+
}
7170

72-
base := testutil.NewBase(t)
73-
containerName := testutil.Identifier(t)
71+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
72+
// Run interactively and detach
73+
cmd := helpers.Command("attach", data.Identifier())
74+
cmd.WithPseudoTTY(func(f *os.File) error {
75+
_, _ = f.WriteString("echo mark${NON}mark\n")
76+
// Interestingly, and unlike with run, on attach, docker (like nerdctl) ALSO needs a pause so that the
77+
// container can read stdin before we detach
78+
time.Sleep(time.Second)
79+
_, err := f.Write([]byte{16, 17})
7480

75-
defer base.Cmd("container", "rm", "-f", containerName).AssertOK()
76-
prepareContainerToAttach(base, containerName)
81+
return err
82+
})
7783

78-
opts := []func(*testutil.Cmd){
79-
testutil.WithStdin(testutil.NewDelayOnceReader(strings.NewReader("expr 1 + 1\nexit\n"))),
84+
return cmd
8085
}
81-
// `unbuffer -p` returns 0 even if the underlying nerdctl process returns a non-zero exit code,
82-
// so the exit code cannot be easily tested here.
83-
base.CmdWithHelper([]string{"unbuffer", "-p"}, "attach", containerName).CmdOption(opts...).AssertOutContains("2")
84-
container := base.InspectContainer(containerName)
85-
assert.Equal(base.T, container.State.Running, false)
86+
87+
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
88+
return &test.Expected{
89+
ExitCode: ex,
90+
Errors: []error{errors.New("read detach keys")},
91+
Output: test.All(
92+
test.Contains("markmark"),
93+
func(stdout string, info string, t *testing.T) {
94+
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
95+
},
96+
),
97+
}
98+
}
99+
100+
testCase.Run(t)
86101
}
87102

88103
func TestAttachDetachKeys(t *testing.T) {
89-
t.Parallel()
104+
// In nerdctl the detach return code from the container after attach is 0, but in docker the return code is 1.
105+
// This behaviour is reported in https://github.com/containerd/nerdctl/issues/3571
106+
ex := 0
107+
if nerdtest.IsDocker() {
108+
ex = 1
109+
}
90110

91-
skipAttachForDocker(t)
111+
testCase := nerdtest.Setup()
92112

93-
base := testutil.NewBase(t)
94-
containerName := testutil.Identifier(t)
113+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
114+
helpers.Anyhow("rm", "-f", data.Identifier())
115+
}
95116

96-
defer base.Cmd("container", "rm", "-f", containerName).AssertOK()
97-
prepareContainerToAttach(base, containerName)
117+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
118+
cmd := helpers.Command("run", "--rm", "-it", "--detach-keys=ctrl-q", "--name", data.Identifier(), testutil.CommonImage)
119+
cmd.WithPseudoTTY(func(f *os.File) error {
120+
_, err := f.Write([]byte{17})
121+
return err
122+
})
123+
124+
cmd.Run(&test.Expected{
125+
ExitCode: 0,
126+
Errors: []error{errors.New("read detach keys")},
127+
Output: func(stdout string, info string, t *testing.T) {
128+
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
129+
},
130+
})
131+
}
98132

99-
opts := []func(*testutil.Cmd){
100-
testutil.WithStdin(testutil.NewDelayOnceReader(bytes.NewReader(
101-
[]byte{1, 2}, // https://www.physics.udel.edu/~watson/scen103/ascii.html
102-
))),
133+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
134+
// Run interactively and detach
135+
cmd := helpers.Command("attach", "--detach-keys=ctrl-a,ctrl-b", data.Identifier())
136+
cmd.WithPseudoTTY(func(f *os.File) error {
137+
_, _ = f.WriteString("echo mark${NON}mark\n")
138+
// Interestingly, and unlike with run, on attach, docker (like nerdctl) ALSO needs a pause so that the
139+
// container can read stdin before we detach
140+
time.Sleep(time.Second)
141+
_, err := f.Write([]byte{1, 2})
142+
143+
return err
144+
})
145+
146+
return cmd
147+
}
148+
149+
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
150+
return &test.Expected{
151+
ExitCode: ex,
152+
Errors: []error{errors.New("read detach keys")},
153+
Output: test.All(
154+
test.Contains("markmark"),
155+
func(stdout string, info string, t *testing.T) {
156+
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"))
157+
},
158+
),
159+
}
103160
}
104-
base.CmdWithHelper([]string{"unbuffer", "-p"}, "attach", "--detach-keys=ctrl-a,ctrl-b", containerName).
105-
CmdOption(opts...).AssertOutContains("read detach keys")
106-
container := base.InspectContainer(containerName)
107-
assert.Equal(base.T, container.State.Running, true)
161+
162+
testCase.Run(t)
108163
}
109164

110165
// TestIssue3568 tests https://github.com/containerd/nerdctl/issues/3568
111-
func TestDetachAttachKeysForAutoRemovedContainer(t *testing.T) {
166+
func TestAttachForAutoRemovedContainer(t *testing.T) {
112167
testCase := nerdtest.Setup()
113168

114-
testCase.SubTests = []*test.Case{
115-
{
116-
Description: "Issue #3568 - A container should be deleted when detaching and attaching a container started with the --rm option.",
117-
// In nerdctl the detach return code from the container is 0, but in docker the return code is 1.
118-
// This behaviour is reported in https://github.com/containerd/nerdctl/issues/3571 so this test is skipped for Docker.
119-
Require: test.Require(
120-
test.Not(nerdtest.Docker),
121-
),
122-
Setup: func(data test.Data, helpers test.Helpers) {
123-
cmd := helpers.Command("run", "--rm", "-it", "--detach-keys=ctrl-a,ctrl-b", "--name", data.Identifier(), testutil.CommonImage)
124-
// unbuffer(1) can be installed with `apt-get install expect`.
125-
//
126-
// "-p" is needed because we need unbuffer to read from stdin, and from [1]:
127-
// "Normally, unbuffer does not read from stdin. This simplifies use of unbuffer in some situations.
128-
// To use unbuffer in a pipeline, use the -p flag."
129-
//
130-
// [1] https://linux.die.net/man/1/unbuffer
131-
cmd.WithWrapper("unbuffer", "-p")
132-
cmd.WithStdin(testutil.NewDelayOnceReader(bytes.NewReader([]byte{1, 2}))) // https://www.physics.udel.edu/~watson/scen103/ascii.html
133-
cmd.Run(&test.Expected{
134-
ExitCode: 0,
135-
})
136-
},
137-
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
138-
cmd := helpers.Command("attach", data.Identifier())
139-
cmd.WithWrapper("unbuffer", "-p")
140-
cmd.WithStdin(testutil.NewDelayOnceReader(strings.NewReader("exit\n")))
141-
return cmd
142-
},
143-
Cleanup: func(data test.Data, helpers test.Helpers) {
144-
helpers.Anyhow("rm", "-f", data.Identifier())
145-
},
146-
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
147-
return &test.Expected{
148-
ExitCode: 0,
149-
Errors: []error{},
150-
Output: test.All(
151-
func(stdout string, info string, t *testing.T) {
152-
assert.Assert(t, !strings.Contains(helpers.Capture("ps", "-a"), data.Identifier()))
153-
},
154-
),
155-
}
169+
testCase.Description = "Issue #3568 - A container should be deleted when detaching and attaching a container started with the --rm option."
170+
171+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
172+
helpers.Anyhow("rm", "-f", data.Identifier())
173+
}
174+
175+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
176+
cmd := helpers.Command("run", "--rm", "-it", "--detach-keys=ctrl-a,ctrl-b", "--name", data.Identifier(), testutil.CommonImage)
177+
cmd.WithPseudoTTY(func(f *os.File) error {
178+
_, err := f.Write([]byte{1, 2})
179+
return err
180+
})
181+
182+
cmd.Run(&test.Expected{
183+
ExitCode: 0,
184+
Errors: []error{errors.New("read detach keys")},
185+
Output: func(stdout string, info string, t *testing.T) {
186+
assert.Assert(t, strings.Contains(helpers.Capture("inspect", "--format", "json", data.Identifier()), "\"Running\":true"), info)
156187
},
157-
},
188+
})
189+
}
190+
191+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
192+
// Run interactively and detach
193+
cmd := helpers.Command("attach", data.Identifier())
194+
cmd.WithPseudoTTY(func(f *os.File) error {
195+
_, err := f.WriteString("echo mark${NON}mark\nexit 42\n")
196+
return err
197+
})
198+
199+
return cmd
200+
}
201+
202+
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
203+
return &test.Expected{
204+
ExitCode: 42,
205+
Output: test.All(
206+
test.Contains("markmark"),
207+
func(stdout string, info string, t *testing.T) {
208+
assert.Assert(t, !strings.Contains(helpers.Capture("ps", "-a"), data.Identifier()))
209+
},
210+
),
211+
}
158212
}
159213

160214
testCase.Run(t)

0 commit comments

Comments
 (0)