17
17
package container
18
18
19
19
import (
20
- "bytes"
20
+ "errors"
21
+ "os"
21
22
"strings"
22
23
"testing"
24
+ "time"
23
25
24
26
"gotest.tools/v3/assert"
25
27
@@ -28,133 +30,185 @@ import (
28
30
"github.com/containerd/nerdctl/v2/pkg/testutil/test"
29
31
)
30
32
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
+ */
42
40
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
50
47
}
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
- }
64
48
65
- func TestAttach (t * testing.T ) {
66
- t .Parallel ()
49
+ testCase := nerdtest .Setup ()
67
50
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
+ }
69
54
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
+ }
71
70
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 })
74
80
75
- defer base . Cmd ( "container" , "rm" , "-f" , containerName ). AssertOK ()
76
- prepareContainerToAttach ( base , containerName )
81
+ return err
82
+ } )
77
83
78
- opts := []func (* testutil.Cmd ){
79
- testutil .WithStdin (testutil .NewDelayOnceReader (strings .NewReader ("expr 1 + 1\n exit\n " ))),
84
+ return cmd
80
85
}
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 )
86
101
}
87
102
88
103
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
+ }
90
110
91
- skipAttachForDocker ( t )
111
+ testCase := nerdtest . Setup ( )
92
112
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
+ }
95
116
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
+ }
98
132
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
+ }
103
160
}
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 )
108
163
}
109
164
110
165
// TestIssue3568 tests https://github.com/containerd/nerdctl/issues/3568
111
- func TestDetachAttachKeysForAutoRemovedContainer (t * testing.T ) {
166
+ func TestAttachForAutoRemovedContainer (t * testing.T ) {
112
167
testCase := nerdtest .Setup ()
113
168
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 )
156
187
},
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\n exit 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
+ }
158
212
}
159
213
160
214
testCase .Run (t )
0 commit comments