logs_test.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package container // import "github.com/docker/docker/integration/container"
  2. import (
  3. "bytes"
  4. "io"
  5. "strings"
  6. "testing"
  7. "time"
  8. containertypes "github.com/docker/docker/api/types/container"
  9. "github.com/docker/docker/daemon/logger/jsonfilelog"
  10. "github.com/docker/docker/daemon/logger/local"
  11. "github.com/docker/docker/integration/internal/container"
  12. "github.com/docker/docker/integration/internal/termtest"
  13. "github.com/docker/docker/pkg/stdcopy"
  14. "gotest.tools/v3/assert"
  15. "gotest.tools/v3/assert/cmp"
  16. "gotest.tools/v3/poll"
  17. "gotest.tools/v3/skip"
  18. )
  19. // Regression test for #35370
  20. // Makes sure that when following we don't get an EOF error when there are no logs
  21. func TestLogsFollowTailEmpty(t *testing.T) {
  22. // FIXME(vdemeester) fails on a e2e run on linux...
  23. skip.If(t, testEnv.IsRemoteDaemon)
  24. ctx := setupTest(t)
  25. apiClient := testEnv.APIClient()
  26. id := container.Run(ctx, t, apiClient, container.WithCmd("sleep", "100000"))
  27. logs, err := apiClient.ContainerLogs(ctx, id, containertypes.LogsOptions{ShowStdout: true, Tail: "2"})
  28. if logs != nil {
  29. defer logs.Close()
  30. }
  31. assert.Check(t, err)
  32. _, err = stdcopy.StdCopy(io.Discard, io.Discard, logs)
  33. assert.Check(t, err)
  34. }
  35. func TestLogs(t *testing.T) {
  36. drivers := []string{local.Name, jsonfilelog.Name}
  37. for _, logDriver := range drivers {
  38. t.Run("driver "+logDriver, func(t *testing.T) {
  39. testLogs(t, logDriver)
  40. })
  41. }
  42. }
  43. func testLogs(t *testing.T, logDriver string) {
  44. ctx := setupTest(t)
  45. apiClient := testEnv.APIClient()
  46. testCases := []struct {
  47. desc string
  48. logOps containertypes.LogsOptions
  49. expectedOut string
  50. expectedErr string
  51. tty bool
  52. }{
  53. // TTY, only one output stream
  54. {
  55. desc: "tty/stdout and stderr",
  56. tty: true,
  57. logOps: containertypes.LogsOptions{
  58. ShowStdout: true,
  59. ShowStderr: true,
  60. },
  61. expectedOut: "this is fineaccidents happen",
  62. },
  63. {
  64. desc: "tty/only stdout",
  65. tty: true,
  66. logOps: containertypes.LogsOptions{
  67. ShowStdout: true,
  68. ShowStderr: false,
  69. },
  70. expectedOut: "this is fineaccidents happen",
  71. },
  72. {
  73. desc: "tty/only stderr",
  74. tty: true,
  75. logOps: containertypes.LogsOptions{
  76. ShowStdout: false,
  77. ShowStderr: true,
  78. },
  79. expectedOut: "",
  80. },
  81. // Without TTY, both stdout and stderr
  82. {
  83. desc: "without tty/stdout and stderr",
  84. tty: false,
  85. logOps: containertypes.LogsOptions{
  86. ShowStdout: true,
  87. ShowStderr: true,
  88. },
  89. expectedOut: "this is fine",
  90. expectedErr: "accidents happen",
  91. },
  92. {
  93. desc: "without tty/only stdout",
  94. tty: false,
  95. logOps: containertypes.LogsOptions{
  96. ShowStdout: true,
  97. ShowStderr: false,
  98. },
  99. expectedOut: "this is fine",
  100. expectedErr: "",
  101. },
  102. {
  103. desc: "without tty/only stderr",
  104. tty: false,
  105. logOps: containertypes.LogsOptions{
  106. ShowStdout: false,
  107. ShowStderr: true,
  108. },
  109. expectedOut: "",
  110. expectedErr: "accidents happen",
  111. },
  112. }
  113. pollTimeout := time.Second * 10
  114. if testEnv.DaemonInfo.OSType == "windows" {
  115. pollTimeout = StopContainerWindowsPollTimeout
  116. }
  117. for _, tC := range testCases {
  118. tC := tC
  119. t.Run(tC.desc, func(t *testing.T) {
  120. t.Parallel()
  121. tty := tC.tty
  122. id := container.Run(ctx, t, apiClient,
  123. container.WithCmd("sh", "-c", "echo -n this is fine; echo -n accidents happen >&2"),
  124. container.WithTty(tty),
  125. container.WithLogDriver(logDriver),
  126. )
  127. defer apiClient.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
  128. poll.WaitOn(t, container.IsStopped(ctx, apiClient, id),
  129. poll.WithDelay(time.Millisecond*100),
  130. poll.WithTimeout(pollTimeout))
  131. logs, err := apiClient.ContainerLogs(ctx, id, tC.logOps)
  132. assert.NilError(t, err)
  133. defer logs.Close()
  134. var stdout, stderr bytes.Buffer
  135. if tty {
  136. // TTY, only one output stream
  137. _, err = io.Copy(&stdout, logs)
  138. } else {
  139. _, err = stdcopy.StdCopy(&stdout, &stderr, logs)
  140. }
  141. assert.NilError(t, err)
  142. stdoutStr := stdout.String()
  143. if tty && testEnv.DaemonInfo.OSType == "windows" {
  144. stdoutStr = stripEscapeCodes(t, stdoutStr)
  145. // Special case for Windows Server 2019
  146. // Check only that the raw output stream contains strings
  147. // that were printed to container's stdout and stderr.
  148. // This is a workaround for the backspace being outputted in an unexpected place
  149. // which breaks the parsed output: https://github.com/moby/moby/issues/43710
  150. if strings.Contains(testEnv.DaemonInfo.OperatingSystem, "Windows Server Version 1809") {
  151. if tC.logOps.ShowStdout {
  152. assert.Check(t, cmp.Contains(stdout.String(), "this is fine"))
  153. assert.Check(t, cmp.Contains(stdout.String(), "accidents happen"))
  154. } else {
  155. assert.DeepEqual(t, stdoutStr, "")
  156. }
  157. return
  158. }
  159. }
  160. assert.DeepEqual(t, stdoutStr, tC.expectedOut)
  161. assert.DeepEqual(t, stderr.String(), tC.expectedErr)
  162. })
  163. }
  164. }
  165. // This hack strips the escape codes that appear in the Windows TTY output and don't have
  166. // any effect on the text content.
  167. // This doesn't handle all escape sequences, only ones that were encountered during testing.
  168. func stripEscapeCodes(t *testing.T, input string) string {
  169. t.Logf("Stripping: %q\n", input)
  170. output, err := termtest.StripANSICommands(input)
  171. assert.NilError(t, err)
  172. return output
  173. }