tailfile_test.go 8.4 KB

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