tailfile_test.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. package tailfile // import "github.com/docker/docker/pkg/tailfile"
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "fmt"
  7. "io"
  8. "os"
  9. "strings"
  10. "testing"
  11. "gotest.tools/v3/assert"
  12. )
  13. func TestTailFile(t *testing.T) {
  14. f, err := os.CreateTemp("", "tail-test")
  15. if err != nil {
  16. t.Fatal(err)
  17. }
  18. defer f.Close()
  19. defer os.RemoveAll(f.Name())
  20. testFile := []byte(`first line
  21. second line
  22. third line
  23. fourth line
  24. fifth line
  25. next first line
  26. next second line
  27. next third line
  28. next fourth line
  29. next fifth line
  30. last first line
  31. next first line
  32. next second line
  33. next third line
  34. next fourth line
  35. next fifth line
  36. next first line
  37. next second line
  38. next third line
  39. next fourth line
  40. next fifth line
  41. last second line
  42. last third line
  43. last fourth line
  44. last fifth line
  45. truncated line`)
  46. if _, err := f.Write(testFile); err != nil {
  47. t.Fatal(err)
  48. }
  49. if _, err := f.Seek(0, io.SeekStart); err != nil {
  50. t.Fatal(err)
  51. }
  52. expected := []string{"last fourth line", "last fifth line"}
  53. res, err := TailFile(f, 2)
  54. if err != nil {
  55. t.Fatal(err)
  56. }
  57. if len(res) != len(expected) {
  58. t.Fatalf("\nexpected:\n%s\n\nactual:\n%s", expected, res)
  59. }
  60. for i, l := range res {
  61. if expected[i] != string(l) {
  62. t.Fatalf("Expected line %q, got %q", expected[i], l)
  63. }
  64. }
  65. }
  66. func TestTailFileManyLines(t *testing.T) {
  67. f, err := os.CreateTemp("", "tail-test")
  68. if err != nil {
  69. t.Fatal(err)
  70. }
  71. defer f.Close()
  72. defer os.RemoveAll(f.Name())
  73. testFile := []byte(`first line
  74. second line
  75. truncated line`)
  76. if _, err := f.Write(testFile); err != nil {
  77. t.Fatal(err)
  78. }
  79. if _, err := f.Seek(0, io.SeekStart); err != nil {
  80. t.Fatal(err)
  81. }
  82. expected := []string{"first line", "second line"}
  83. res, err := TailFile(f, 10000)
  84. if err != nil {
  85. t.Fatal(err)
  86. }
  87. if len(expected) != len(res) {
  88. t.Fatalf("\nexpected:\n%s\n\nactual:\n%s", expected, res)
  89. }
  90. for i, l := range res {
  91. if expected[i] != string(l) {
  92. t.Fatalf("Expected line %s, got %s", expected[i], l)
  93. }
  94. }
  95. }
  96. func TestTailEmptyFile(t *testing.T) {
  97. f, err := os.CreateTemp("", "tail-test")
  98. if err != nil {
  99. t.Fatal(err)
  100. }
  101. defer f.Close()
  102. defer os.RemoveAll(f.Name())
  103. res, err := TailFile(f, 10000)
  104. if err != nil {
  105. t.Fatal(err)
  106. }
  107. if len(res) != 0 {
  108. t.Fatal("Must be empty slice from empty file")
  109. }
  110. }
  111. func TestTailNegativeN(t *testing.T) {
  112. f, err := os.CreateTemp("", "tail-test")
  113. if err != nil {
  114. t.Fatal(err)
  115. }
  116. defer f.Close()
  117. defer os.RemoveAll(f.Name())
  118. testFile := []byte(`first line
  119. second line
  120. truncated line`)
  121. if _, err := f.Write(testFile); err != nil {
  122. t.Fatal(err)
  123. }
  124. if _, err := f.Seek(0, io.SeekStart); err != nil {
  125. t.Fatal(err)
  126. }
  127. if _, err := TailFile(f, -1); err != ErrNonPositiveLinesNumber {
  128. t.Fatalf("Expected ErrNonPositiveLinesNumber, got %v", err)
  129. }
  130. if _, err := TailFile(f, 0); err != ErrNonPositiveLinesNumber {
  131. t.Fatalf("Expected ErrNonPositiveLinesNumber, got %s", err)
  132. }
  133. }
  134. func BenchmarkTail(b *testing.B) {
  135. f, err := os.CreateTemp("", "tail-test")
  136. if err != nil {
  137. b.Fatal(err)
  138. }
  139. defer f.Close()
  140. defer os.RemoveAll(f.Name())
  141. for i := 0; i < 10000; i++ {
  142. if _, err := f.Write([]byte("tailfile pretty interesting line\n")); err != nil {
  143. b.Fatal(err)
  144. }
  145. }
  146. b.ResetTimer()
  147. for i := 0; i < b.N; i++ {
  148. if _, err := TailFile(f, 1000); err != nil {
  149. b.Fatal(err)
  150. }
  151. }
  152. }
  153. func TestNewTailReader(t *testing.T) {
  154. t.Parallel()
  155. ctx := context.Background()
  156. for dName, delim := range map[string][]byte{
  157. "no delimiter": {},
  158. "single byte delimiter": {'\n'},
  159. "2 byte delimiter": []byte(";\n"),
  160. "4 byte delimiter": []byte("####"),
  161. "8 byte delimiter": []byte("########"),
  162. "12 byte delimiter": []byte("############"),
  163. } {
  164. t.Run(dName, func(t *testing.T) {
  165. delim := delim
  166. t.Parallel()
  167. s1 := "Hello world."
  168. s2 := "Today is a fine day."
  169. s3 := "So long, and thanks for all the fish!"
  170. s4 := strings.Repeat("a", blockSize/2) // same as block size
  171. s5 := strings.Repeat("a", blockSize) // just to make sure
  172. s6 := strings.Repeat("a", blockSize*2) // bigger than block size
  173. s7 := strings.Repeat("a", blockSize-1) // single line same as block
  174. s8 := `{"log":"Don't panic!\n","stream":"stdout","time":"2018-04-04T20:28:44.7207062Z"}`
  175. jsonTest := make([]string, 0, 20)
  176. for i := 0; i < 20; i++ {
  177. jsonTest = append(jsonTest, s8)
  178. }
  179. for _, test := range []struct {
  180. desc string
  181. data []string
  182. }{
  183. {desc: "one small entry", data: []string{s1}},
  184. {desc: "several small entries", data: []string{s1, s2, s3}},
  185. {desc: "various sizes", data: []string{s1, s2, s3, s4, s5, s1, s2, s3, s7, s6}},
  186. {desc: "multiple lines with one more than block", data: []string{s5, s5, s5, s5, s5}},
  187. {desc: "multiple lines much bigger than block", data: []string{s6, s6, s6, s6, s6}},
  188. {desc: "multiple lines same as block", data: []string{s4, s4, s4, s4, s4}},
  189. {desc: "single line same as block", data: []string{s7}},
  190. {desc: "single line half block", data: []string{s4}},
  191. {desc: "single line twice block", data: []string{s6}},
  192. {desc: "json encoded values", data: jsonTest},
  193. {desc: "no lines", data: []string{}},
  194. {desc: "same length as delimiter", data: []string{strings.Repeat("a", len(delim))}},
  195. } {
  196. t.Run(test.desc, func(t *testing.T) {
  197. test := test
  198. t.Parallel()
  199. maxLen := len(test.data)
  200. if maxLen > 10 {
  201. maxLen = 10
  202. }
  203. s := strings.Join(test.data, string(delim))
  204. if len(test.data) > 0 {
  205. s += string(delim)
  206. }
  207. for i := 1; i <= maxLen; i++ {
  208. t.Run(fmt.Sprintf("%d lines", i), func(t *testing.T) {
  209. i := i
  210. t.Parallel()
  211. r := strings.NewReader(s)
  212. tr, lines, err := NewTailReaderWithDelimiter(ctx, r, i, delim)
  213. if len(delim) == 0 {
  214. assert.Assert(t, err != nil)
  215. assert.Assert(t, lines == 0)
  216. return
  217. }
  218. assert.NilError(t, err)
  219. assert.Check(t, lines == i, "%d -- %d", lines, i)
  220. b, err := io.ReadAll(tr)
  221. assert.NilError(t, err)
  222. expectLines := test.data[len(test.data)-i:]
  223. assert.Check(t, len(expectLines) == i)
  224. expect := strings.Join(expectLines, string(delim)) + string(delim)
  225. assert.Check(t, string(b) == expect, "\n%v\n%v", b, []byte(expect))
  226. })
  227. }
  228. t.Run("request more lines than available", func(t *testing.T) {
  229. t.Parallel()
  230. r := strings.NewReader(s)
  231. tr, lines, err := NewTailReaderWithDelimiter(ctx, r, len(test.data)*2, delim)
  232. if len(delim) == 0 {
  233. assert.Assert(t, err != nil)
  234. assert.Assert(t, lines == 0)
  235. return
  236. }
  237. if len(test.data) == 0 {
  238. assert.Assert(t, err == ErrNonPositiveLinesNumber, err)
  239. return
  240. }
  241. assert.NilError(t, err)
  242. assert.Check(t, lines == len(test.data), "%d -- %d", lines, len(test.data))
  243. b, err := io.ReadAll(tr)
  244. assert.NilError(t, err)
  245. assert.Check(t, bytes.Equal(b, []byte(s)), "\n%v\n%v", b, []byte(s))
  246. })
  247. })
  248. }
  249. })
  250. }
  251. t.Run("truncated last line", func(t *testing.T) {
  252. t.Run("more than available", func(t *testing.T) {
  253. tail, nLines, err := NewTailReader(ctx, strings.NewReader("a\nb\nextra"), 3)
  254. assert.NilError(t, err)
  255. assert.Check(t, nLines == 2, nLines)
  256. rdr := bufio.NewReader(tail)
  257. data, _, err := rdr.ReadLine()
  258. assert.NilError(t, err)
  259. assert.Check(t, string(data) == "a", string(data))
  260. data, _, err = rdr.ReadLine()
  261. assert.NilError(t, err)
  262. assert.Check(t, string(data) == "b", string(data))
  263. _, _, err = rdr.ReadLine()
  264. assert.Assert(t, err == io.EOF, err)
  265. })
  266. })
  267. t.Run("truncated last line", func(t *testing.T) {
  268. t.Run("exact", func(t *testing.T) {
  269. tail, nLines, err := NewTailReader(ctx, strings.NewReader("a\nb\nextra"), 2)
  270. assert.NilError(t, err)
  271. assert.Check(t, nLines == 2, nLines)
  272. rdr := bufio.NewReader(tail)
  273. data, _, err := rdr.ReadLine()
  274. assert.NilError(t, err)
  275. assert.Check(t, string(data) == "a", string(data))
  276. data, _, err = rdr.ReadLine()
  277. assert.NilError(t, err)
  278. assert.Check(t, string(data) == "b", string(data))
  279. _, _, err = rdr.ReadLine()
  280. assert.Assert(t, err == io.EOF, err)
  281. })
  282. })
  283. t.Run("truncated last line", func(t *testing.T) {
  284. t.Run("one line", func(t *testing.T) {
  285. tail, nLines, err := NewTailReader(ctx, strings.NewReader("a\nb\nextra"), 1)
  286. assert.NilError(t, err)
  287. assert.Check(t, nLines == 1, nLines)
  288. rdr := bufio.NewReader(tail)
  289. data, _, err := rdr.ReadLine()
  290. assert.NilError(t, err)
  291. assert.Check(t, string(data) == "b", string(data))
  292. _, _, err = rdr.ReadLine()
  293. assert.Assert(t, err == io.EOF, err)
  294. })
  295. })
  296. }