diff --git a/cmd/nerdctl/container/container_logs_test.go b/cmd/nerdctl/container/container_logs_test.go index ea2be65d6aa..f6eed127448 100644 --- a/cmd/nerdctl/container/container_logs_test.go +++ b/cmd/nerdctl/container/container_logs_test.go @@ -18,8 +18,10 @@ package container import ( "fmt" + "os" "os/exec" "runtime" + "strconv" "strings" "testing" "time" @@ -330,3 +332,81 @@ func TestTailFollowRotateLogs(t *testing.T) { } assert.Equal(t, true, len(tailLogs) > linesPerFile, logRun.Stderr()) } + +func TestLogsWithStartContainer(t *testing.T) { + testCase := nerdtest.Setup() + + // For windows we havent added support for dual logging so not adding the test. + testCase.Require = require.All(require.Not(require.Windows)) + + testCase.SubTests = []*test.Case{ + { + Description: "Test logs are directed correctly for container start of a interactive container", + Setup: func(data test.Data, helpers test.Helpers) { + cmd := helpers.Command("run", "-it", "--name", data.Identifier(), testutil.CommonImage) + cmd.WithPseudoTTY(func(f *os.File) error { + _, err := f.WriteString("echo foo\nexit\n") + return err + }) + cmd.Run(&test.Expected{ + ExitCode: 0, + }) + + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("start", "-a", data.Identifier()) + cmd.WithPseudoTTY(func(f *os.File) error { + _, err := f.WriteString("echo bar\nexit\n") + return err + }) + cmd.Run(&test.Expected{ + ExitCode: 0, + }) + cmd = helpers.Command("logs", data.Identifier()) + + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + assert.Assert(t, strings.Contains(stdout, "foo")) + assert.Assert(t, strings.Contains(stdout, "bar")) + }, + } + }, + }, + { + Description: "Test logs are captured after stopping and starting a non-interactive container and continue capturing new logs", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sh", "-c", "while true; do echo foo; sleep 1; done") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + helpers.Ensure("stop", data.Identifier()) + initialLogs := helpers.Capture("logs", data.Identifier()) + initialFooCount := strings.Count(initialLogs, "foo") + data.Set("initialFooCount", strconv.Itoa(initialFooCount)) + helpers.Ensure("start", data.Identifier()) + nerdtest.EnsureContainerStarted(helpers, data.Identifier()) + return helpers.Command("logs", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + finalLogsCount := strings.Count(stdout, "foo") + initialFooCount, _ := strconv.Atoi(data.Get("initialFooCount")) + assert.Assert(t, finalLogsCount > initialFooCount, "Expected 'foo' count to increase after restart", info) + }, + } + }, + }, + } + testCase.Run(t) +} diff --git a/pkg/taskutil/taskutil.go b/pkg/taskutil/taskutil.go index 35ac3d8effc..ca5f4d6c8a8 100644 --- a/pkg/taskutil/taskutil.go +++ b/pkg/taskutil/taskutil.go @@ -66,15 +66,16 @@ func NewTask(ctx context.Context, client *containerd.Client, container container var ioCreator cio.Creator if len(attachStreamOpt) != 0 { log.G(ctx).Debug("attaching output instead of using the log-uri") + // when attaching a TTY we use writee for stdio and binary for log persistence if flagT { in, err := consoleutil.NewDetachableStdin(con, detachKeys, closer) if err != nil { return nil, err } - ioCreator = cio.NewCreator(cio.WithStreams(in, con, nil), cio.WithTerminal) + ioCreator = cioutil.NewContainerIO(namespace, logURI, true, in, con, nil) } else { streams := processAttachStreamsOpt(attachStreamOpt) - ioCreator = cio.NewCreator(cio.WithStreams(streams.stdIn, streams.stdOut, streams.stdErr)) + ioCreator = cioutil.NewContainerIO(namespace, logURI, false, streams.stdIn, streams.stdOut, streams.stdErr) } } else if flagT && flagD {