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