moby/integration/container/logs_test.go
Paweł Gronowski 2ec3e14c0f test: Add tests for logging
1. Add integration tests for the ContainerLogs API call
Each test handle a distinct case of ContainerLogs output.
- Muxed stream, when container is started without tty
- Single stream, when container is started with tty

2. Add unit test for LogReader suite that tests concurrent logging
It checks that there are no race conditions when logging concurrently
from multiple goroutines.

Co-authored-by: Cory Snider <csnider@mirantis.com>
Signed-off-by: Cory Snider <csnider@mirantis.com>
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2022-06-10 09:26:17 +02:00

190 lines
5 KiB
Go

package container // import "github.com/docker/docker/integration/container"
import (
"bytes"
"context"
"io"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/daemon/logger/jsonfilelog"
"github.com/docker/docker/daemon/logger/local"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/integration/internal/termtest"
"github.com/docker/docker/pkg/stdcopy"
"gotest.tools/v3/assert"
"gotest.tools/v3/assert/cmp"
"gotest.tools/v3/poll"
"gotest.tools/v3/skip"
)
// Regression test for #35370
// Makes sure that when following we don't get an EOF error when there are no logs
func TestLogsFollowTailEmpty(t *testing.T) {
// FIXME(vdemeester) fails on a e2e run on linux...
skip.If(t, testEnv.IsRemoteDaemon)
defer setupTest(t)()
client := testEnv.APIClient()
ctx := context.Background()
id := container.Run(ctx, t, client, container.WithCmd("sleep", "100000"))
logs, err := client.ContainerLogs(ctx, id, types.ContainerLogsOptions{ShowStdout: true, Tail: "2"})
if logs != nil {
defer logs.Close()
}
assert.Check(t, err)
_, err = stdcopy.StdCopy(io.Discard, io.Discard, logs)
assert.Check(t, err)
}
func TestLogs(t *testing.T) {
drivers := []string{local.Name, jsonfilelog.Name}
for _, logDriver := range drivers {
t.Run("driver "+logDriver, func(t *testing.T) {
testLogs(t, logDriver)
})
}
}
func testLogs(t *testing.T, logDriver string) {
defer setupTest(t)()
client := testEnv.APIClient()
ctx := context.Background()
testCases := []struct {
desc string
logOps types.ContainerLogsOptions
expectedOut string
expectedErr string
tty bool
}{
// TTY, only one output stream
{
desc: "tty/stdout and stderr",
tty: true,
logOps: types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
},
expectedOut: "this is fineaccidents happen",
},
{
desc: "tty/only stdout",
tty: true,
logOps: types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: false,
},
expectedOut: "this is fineaccidents happen",
},
{
desc: "tty/only stderr",
tty: true,
logOps: types.ContainerLogsOptions{
ShowStdout: false,
ShowStderr: true,
},
expectedOut: "",
},
// Without TTY, both stdout and stderr
{
desc: "without tty/stdout and stderr",
tty: false,
logOps: types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
},
expectedOut: "this is fine",
expectedErr: "accidents happen",
},
{
desc: "without tty/only stdout",
tty: false,
logOps: types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: false,
},
expectedOut: "this is fine",
expectedErr: "",
},
{
desc: "without tty/only stderr",
tty: false,
logOps: types.ContainerLogsOptions{
ShowStdout: false,
ShowStderr: true,
},
expectedOut: "",
expectedErr: "accidents happen",
},
}
for _, tC := range testCases {
tC := tC
t.Run(tC.desc, func(t *testing.T) {
t.Parallel()
tty := tC.tty
id := container.Run(ctx, t, client,
container.WithCmd("sh", "-c", "echo -n this is fine; echo -n accidents happen >&2"),
container.WithTty(tty),
container.WithLogDriver(logDriver),
)
defer client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true})
poll.WaitOn(t, container.IsStopped(ctx, client, id), poll.WithDelay(time.Millisecond*100))
logs, err := client.ContainerLogs(ctx, id, tC.logOps)
assert.NilError(t, err)
defer logs.Close()
var stdout, stderr bytes.Buffer
if tty {
// TTY, only one output stream
_, err = io.Copy(&stdout, logs)
} else {
_, err = stdcopy.StdCopy(&stdout, &stderr, logs)
}
assert.NilError(t, err)
stdoutStr := stdout.String()
if tty && testEnv.OSType == "windows" {
stdoutStr = stripEscapeCodes(t, stdoutStr)
// Special case for Windows Server 2019
// Check only that the raw output stream contains strings
// that were printed to container's stdout and stderr.
// This is a workaround for the backspace being outputted in an unexpected place
// which breaks the parsed output: https://github.com/moby/moby/issues/43710
if strings.Contains(testEnv.DaemonInfo.OperatingSystem, "Windows Server Version 1809") {
if tC.logOps.ShowStdout {
assert.Check(t, cmp.Contains(stdout.String(), "this is fine"))
assert.Check(t, cmp.Contains(stdout.String(), "accidents happen"))
} else {
assert.DeepEqual(t, stdoutStr, "")
}
return
}
}
assert.DeepEqual(t, stdoutStr, tC.expectedOut)
assert.DeepEqual(t, stderr.String(), tC.expectedErr)
})
}
}
// This hack strips the escape codes that appear in the Windows TTY output and don't have
// any effect on the text content.
// This doesn't handle all escape sequences, only ones that were encountered during testing.
func stripEscapeCodes(t *testing.T, input string) string {
t.Logf("Stripping: %q\n", input)
output, err := termtest.StripANSICommands(input)
assert.NilError(t, err)
return output
}