@@ -20,12 +20,15 @@ import (
2020 "bytes"
2121 "errors"
2222 "io"
23+ "strconv"
2324 "strings"
2425 "testing"
26+ "time"
2527
2628 "gotest.tools/v3/assert"
2729
2830 "github.com/containerd/nerdctl/mod/tigron/expect"
31+ "github.com/containerd/nerdctl/mod/tigron/require"
2932 "github.com/containerd/nerdctl/mod/tigron/test"
3033 "github.com/containerd/nerdctl/mod/tigron/tig"
3134
@@ -77,3 +80,50 @@ func TestStartDetachKeys(t *testing.T) {
7780
7881 testCase .Run (t )
7982}
83+
84+ func TestStartWithCheckpoint (t * testing.T ) {
85+
86+ testCase := nerdtest .Setup ()
87+ testCase .Require = require .Not (nerdtest .Rootless )
88+
89+ testCase .Setup = func (data test.Data , helpers test.Helpers ) {
90+ // Use an in-memory tmpfs to model in-memory state without introducing extra processes
91+ // Single PID 1 shell: continuously increment a counter and write to /state/counter (tmpfs)
92+ helpers .Ensure ("run" , "-d" , "--name" , data .Identifier (), "--tmpfs" , "/state" , testutil .CommonImage ,
93+ "sh" , "-c" , `i=0; while true; do i=$((i+1)); printf "%d\n" "$i" >/state/counter; sleep 0.2; done` )
94+ // Give some time for the counter to increase before checkpoint to validate continuity after restore
95+ time .Sleep (1 * time .Second )
96+ helpers .Ensure ("checkpoint" , "create" , data .Identifier (), data .Identifier ()+ "-checkpoint" )
97+ }
98+
99+ testCase .Cleanup = func (data test.Data , helpers test.Helpers ) {
100+ helpers .Anyhow ("rm" , "-f" , data .Identifier ())
101+ }
102+
103+ testCase .Command = func (data test.Data , helpers test.Helpers ) test.TestableCommand {
104+ return helpers .Command ("start" , "--checkpoint" , data .Identifier ()+ "-checkpoint" , data .Identifier ())
105+ }
106+
107+ testCase .Expected = func (data test.Data , helpers test.Helpers ) * test.Expected {
108+ return & test.Expected {
109+ ExitCode : 0 ,
110+ Output : expect .All (
111+ func (_ string , t tig.T ) {
112+ // Validate in-memory state continuity via tmpfs: counter should not reset and must keep increasing
113+ // Short delay to allow the container to resume; if the counter had reset to 0, it could not reach >5 this fast
114+ time .Sleep (200 * time .Millisecond )
115+ c1Str := strings .TrimSpace (helpers .Capture ("exec" , data .Identifier (), "cat" , "/state/counter" ))
116+ var parseErrs []error
117+ c1 , err1 := strconv .Atoi (c1Str )
118+ if err1 != nil {
119+ parseErrs = append (parseErrs , err1 )
120+ }
121+ assert .Assert (t , len (parseErrs ) == 0 , "failed to parse counter values: %v" , parseErrs )
122+ assert .Assert (t , c1 > 5 , "tmpfs in-memory counter seems reset or too small: %d" , c1 )
123+ },
124+ ),
125+ }
126+ }
127+
128+ testCase .Run (t )
129+ }
0 commit comments