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