logs_test.go 5.2 KB

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