1717package container
1818
1919import (
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\n exit\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
88103func 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\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+ }
158212 }
159213
160214 testCase .Run (t )
0 commit comments