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,189 @@ 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- }
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+ */
6440
6541func 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+ }
6748
68- t . Skip ( "This test is very unstable and currently skipped. See https://github.com/containerd/nerdctl/issues/3558" )
49+ testCase := nerdtest . Setup ( )
6950
70- skipAttachForDocker (t )
51+ testCase .Cleanup = func (data test.Data , helpers test.Helpers ) {
52+ helpers .Anyhow ("rm" , "-f" , data .Identifier ())
53+ }
7154
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+ }
7471
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+ }
7788
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+ }
80100 }
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 )
86103}
87104
88105func 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+ }
90112
91- skipAttachForDocker ( t )
113+ testCase := nerdtest . Setup ( )
92114
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+ }
95118
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+ }
98134
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
103150 }
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 )
108166}
109167
110168// TestIssue3568 tests https://github.com/containerd/nerdctl/issues/3568
111- func TestDetachAttachKeysForAutoRemovedContainer (t * testing.T ) {
169+ func TestAttachForAutoRemovedContainer (t * testing.T ) {
112170 testCase := nerdtest .Setup ()
113171
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 )
156191 },
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+ }
158216 }
159217
160218 testCase .Run (t )
0 commit comments