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,189 @@ 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
- }
42
-
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
- ))),
50
- }
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
- }
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
+ */
64
40
65
41
func TestAttach (t * testing.T ) {
66
- t .Parallel ()
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
47
+ }
67
48
68
- t . Skip ( "This test is very unstable and currently skipped. See https://github.com/containerd/nerdctl/issues/3558" )
49
+ testCase := nerdtest . Setup ( )
69
50
70
- skipAttachForDocker (t )
51
+ testCase .Cleanup = func (data test.Data , helpers test.Helpers ) {
52
+ helpers .Anyhow ("rm" , "-f" , data .Identifier ())
53
+ }
71
54
72
- base := testutil .NewBase (t )
73
- containerName := testutil .Identifier (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
+ // ctrl+p and ctrl+q (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)
59
+ _ , err := f .Write ([]byte {16 , 17 })
60
+ return err
61
+ })
62
+
63
+ cmd .Run (& test.Expected {
64
+ ExitCode : 0 ,
65
+ Errors : []error {errors .New ("read detach keys" )},
66
+ Output : func (stdout string , info string , t * testing.T ) {
67
+ assert .Assert (t , strings .Contains (helpers .Capture ("inspect" , "--format" , "json" , data .Identifier ()), "\" Running\" :true" ))
68
+ },
69
+ })
70
+ }
74
71
75
- defer base .Cmd ("container" , "rm" , "-f" , containerName ).AssertOK ()
76
- prepareContainerToAttach (base , containerName )
72
+ testCase .Command = func (data test.Data , helpers test.Helpers ) test.TestableCommand {
73
+ // Run interactively and detach
74
+ cmd := helpers .Command ("attach" , data .Identifier ())
75
+ cmd .WithPseudoTTY (func (f * os.File ) error {
76
+ _ , _ = f .WriteString ("echo mark${NON}mark\n " )
77
+ // Interestingly, and unlike with run, on attach, docker (like nerdctl) ALSO needs a pause so that the
78
+ // container can read stdin before we detach
79
+ time .Sleep (time .Second )
80
+ // ctrl+p and ctrl+q (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)
81
+ _ , err := f .Write ([]byte {16 , 17 })
82
+
83
+ return err
84
+ })
85
+
86
+ return cmd
87
+ }
77
88
78
- opts := []func (* testutil.Cmd ){
79
- testutil .WithStdin (testutil .NewDelayOnceReader (strings .NewReader ("expr 1 + 1\n exit\n " ))),
89
+ testCase .Expected = func (data test.Data , helpers test.Helpers ) * test.Expected {
90
+ return & test.Expected {
91
+ ExitCode : ex ,
92
+ Errors : []error {errors .New ("read detach keys" )},
93
+ Output : test .All (
94
+ test .Contains ("markmark" ),
95
+ func (stdout string , info string , t * testing.T ) {
96
+ assert .Assert (t , strings .Contains (helpers .Capture ("inspect" , "--format" , "json" , data .Identifier ()), "\" Running\" :true" ))
97
+ },
98
+ ),
99
+ }
80
100
}
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 )
101
+
102
+ testCase .Run (t )
86
103
}
87
104
88
105
func TestAttachDetachKeys (t * testing.T ) {
89
- t .Parallel ()
106
+ // In nerdctl the detach return code from the container after attach is 0, but in docker the return code is 1.
107
+ // This behaviour is reported in https://github.com/containerd/nerdctl/issues/3571
108
+ ex := 0
109
+ if nerdtest .IsDocker () {
110
+ ex = 1
111
+ }
90
112
91
- skipAttachForDocker ( t )
113
+ testCase := nerdtest . Setup ( )
92
114
93
- base := testutil .NewBase (t )
94
- containerName := testutil .Identifier (t )
115
+ testCase .Cleanup = func (data test.Data , helpers test.Helpers ) {
116
+ helpers .Anyhow ("rm" , "-f" , data .Identifier ())
117
+ }
95
118
96
- defer base .Cmd ("container" , "rm" , "-f" , containerName ).AssertOK ()
97
- prepareContainerToAttach (base , containerName )
119
+ testCase .Setup = func (data test.Data , helpers test.Helpers ) {
120
+ cmd := helpers .Command ("run" , "--rm" , "-it" , "--detach-keys=ctrl-q" , "--name" , data .Identifier (), testutil .CommonImage )
121
+ cmd .WithPseudoTTY (func (f * os.File ) error {
122
+ _ , err := f .Write ([]byte {17 })
123
+ return err
124
+ })
125
+
126
+ cmd .Run (& test.Expected {
127
+ ExitCode : 0 ,
128
+ Errors : []error {errors .New ("read detach keys" )},
129
+ Output : func (stdout string , info string , t * testing.T ) {
130
+ assert .Assert (t , strings .Contains (helpers .Capture ("inspect" , "--format" , "json" , data .Identifier ()), "\" Running\" :true" ))
131
+ },
132
+ })
133
+ }
98
134
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
- ))),
135
+ testCase .Command = func (data test.Data , helpers test.Helpers ) test.TestableCommand {
136
+ // Run interactively and detach
137
+ cmd := helpers .Command ("attach" , "--detach-keys=ctrl-a,ctrl-b" , data .Identifier ())
138
+ cmd .WithPseudoTTY (func (f * os.File ) error {
139
+ _ , _ = f .WriteString ("echo mark${NON}mark\n " )
140
+ // Interestingly, and unlike with run, on attach, docker (like nerdctl) ALSO needs a pause so that the
141
+ // container can read stdin before we detach
142
+ time .Sleep (time .Second )
143
+ // ctrl+a and ctrl+b (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)
144
+ _ , err := f .Write ([]byte {1 , 2 })
145
+
146
+ return err
147
+ })
148
+
149
+ return cmd
103
150
}
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 )
151
+
152
+ testCase .Expected = func (data test.Data , helpers test.Helpers ) * test.Expected {
153
+ return & test.Expected {
154
+ ExitCode : ex ,
155
+ Errors : []error {errors .New ("read detach keys" )},
156
+ Output : test .All (
157
+ test .Contains ("markmark" ),
158
+ func (stdout string , info string , t * testing.T ) {
159
+ assert .Assert (t , strings .Contains (helpers .Capture ("inspect" , "--format" , "json" , data .Identifier ()), "\" Running\" :true" ))
160
+ },
161
+ ),
162
+ }
163
+ }
164
+
165
+ testCase .Run (t )
108
166
}
109
167
110
168
// TestIssue3568 tests https://github.com/containerd/nerdctl/issues/3568
111
- func TestDetachAttachKeysForAutoRemovedContainer (t * testing.T ) {
169
+ func TestAttachForAutoRemovedContainer (t * testing.T ) {
112
170
testCase := nerdtest .Setup ()
113
171
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
- }
172
+ testCase .Description = "Issue #3568 - A container should be deleted when detaching and attaching a container started with the --rm option."
173
+
174
+ testCase .Cleanup = func (data test.Data , helpers test.Helpers ) {
175
+ helpers .Anyhow ("rm" , "-f" , data .Identifier ())
176
+ }
177
+
178
+ testCase .Setup = func (data test.Data , helpers test.Helpers ) {
179
+ cmd := helpers .Command ("run" , "--rm" , "-it" , "--detach-keys=ctrl-a,ctrl-b" , "--name" , data .Identifier (), testutil .CommonImage )
180
+ cmd .WithPseudoTTY (func (f * os.File ) error {
181
+ // ctrl+a and ctrl+b (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)
182
+ _ , err := f .Write ([]byte {1 , 2 })
183
+ return err
184
+ })
185
+
186
+ cmd .Run (& test.Expected {
187
+ ExitCode : 0 ,
188
+ Errors : []error {errors .New ("read detach keys" )},
189
+ Output : func (stdout string , info string , t * testing.T ) {
190
+ assert .Assert (t , strings .Contains (helpers .Capture ("inspect" , "--format" , "json" , data .Identifier ()), "\" Running\" :true" ), info )
156
191
},
157
- },
192
+ })
193
+ }
194
+
195
+ testCase .Command = func (data test.Data , helpers test.Helpers ) test.TestableCommand {
196
+ // Run interactively and detach
197
+ cmd := helpers .Command ("attach" , data .Identifier ())
198
+ cmd .WithPseudoTTY (func (f * os.File ) error {
199
+ _ , err := f .WriteString ("echo mark${NON}mark\n exit 42\n " )
200
+ return err
201
+ })
202
+
203
+ return cmd
204
+ }
205
+
206
+ testCase .Expected = func (data test.Data , helpers test.Helpers ) * test.Expected {
207
+ return & test.Expected {
208
+ ExitCode : 42 ,
209
+ Output : test .All (
210
+ test .Contains ("markmark" ),
211
+ func (stdout string , info string , t * testing.T ) {
212
+ assert .Assert (t , ! strings .Contains (helpers .Capture ("ps" , "-a" ), data .Identifier ()))
213
+ },
214
+ ),
215
+ }
158
216
}
159
217
160
218
testCase .Run (t )
0 commit comments