github.com/admpub/tail@v1.1.0/tail_test.go (about) 1 // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 4 // TODO: 5 // * repeat all the tests with Poll:true 6 7 package tail 8 9 import ( 10 _ "fmt" 11 "io/ioutil" 12 "os" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/admpub/tail/ratelimiter" 18 "github.com/admpub/tail/watch" 19 ) 20 21 func init() { 22 // Clear the temporary test directory 23 err := os.RemoveAll(".test") 24 if err != nil { 25 panic(err) 26 } 27 } 28 29 func TestMain(m *testing.M) { 30 // Use a smaller poll duration for faster test runs. Keep it below 31 // 100ms (which value is used as common delays for tests) 32 watch.POLL_DURATION = 5 * time.Millisecond 33 os.Exit(m.Run()) 34 } 35 36 func TestMustExist(t *testing.T) { 37 tail, err := TailFile("/no/such/file", Config{Follow: true, MustExist: true}) 38 if err == nil { 39 t.Error("MustExist:true is violated") 40 tail.Stop() 41 } 42 tail, err = TailFile("/no/such/file", Config{Follow: true, MustExist: false}) 43 if err != nil { 44 t.Error("MustExist:false is violated") 45 } 46 tail.Stop() 47 _, err = TailFile("README.md", Config{Follow: true, MustExist: true}) 48 if err != nil { 49 t.Error("MustExist:true on an existing file is violated") 50 } 51 tail.Cleanup() 52 } 53 54 func TestWaitsForFileToExist(t *testing.T) { 55 tailTest := NewTailTest("waits-for-file-to-exist", t) 56 tail := tailTest.StartTail("test.txt", Config{}) 57 go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false) 58 59 <-time.After(100 * time.Millisecond) 60 tailTest.CreateFile("test.txt", "hello\nworld\n") 61 tailTest.Cleanup(tail, true) 62 } 63 64 func TestWaitsForFileToExistRelativePath(t *testing.T) { 65 tailTest := NewTailTest("waits-for-file-to-exist-relative", t) 66 67 oldWD, err := os.Getwd() 68 if err != nil { 69 tailTest.Fatal(err) 70 } 71 os.Chdir(tailTest.path) 72 defer os.Chdir(oldWD) 73 74 tail, err := TailFile("test.txt", Config{}) 75 if err != nil { 76 tailTest.Fatal(err) 77 } 78 79 go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false) 80 81 <-time.After(100 * time.Millisecond) 82 if err := ioutil.WriteFile("test.txt", []byte("hello\nworld\n"), 0600); err != nil { 83 tailTest.Fatal(err) 84 } 85 tailTest.Cleanup(tail, true) 86 } 87 88 func TestStop(t *testing.T) { 89 tail, err := TailFile("_no_such_file", Config{Follow: true, MustExist: false}) 90 if err != nil { 91 t.Error("MustExist:false is violated") 92 } 93 if tail.Stop() != nil { 94 t.Error("Should be stoped successfully") 95 } 96 tail.Cleanup() 97 } 98 99 func TestStopAtEOF(t *testing.T) { 100 tailTest := NewTailTest("maxlinesize", t) 101 tailTest.CreateFile("test.txt", "hello\nthere\nworld\n") 102 tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil}) 103 104 // read "hello" 105 line := <-tail.Lines 106 if line.Text != "hello" { 107 t.Errorf("Expected to get 'hello', got '%s' instead", line.Text) 108 } 109 110 tailTest.VerifyTailOutput(tail, []string{"there", "world"}, false) 111 tail.StopAtEOF() 112 tailTest.Cleanup(tail, true) 113 } 114 115 func TestMaxLineSizeFollow(t *testing.T) { 116 // As last file line does not end with newline, it will not be present in tail's output 117 maxLineSize(t, true, "hello\nworld\nfin\nhe", []string{"hel", "lo", "wor", "ld", "fin"}) 118 } 119 120 func TestMaxLineSizeNoFollow(t *testing.T) { 121 maxLineSize(t, false, "hello\nworld\nfin\nhe", []string{"hel", "lo", "wor", "ld", "fin", "he"}) 122 } 123 124 func TestOver4096ByteLine(t *testing.T) { 125 tailTest := NewTailTest("Over4096ByteLine", t) 126 testString := strings.Repeat("a", 4097) 127 tailTest.CreateFile("test.txt", "test\n"+testString+"\nhello\nworld\n") 128 tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil}) 129 go tailTest.VerifyTailOutput(tail, []string{"test", testString, "hello", "world"}, false) 130 131 // Delete after a reasonable delay, to give tail sufficient time 132 // to read all lines. 133 <-time.After(100 * time.Millisecond) 134 tailTest.RemoveFile("test.txt") 135 tailTest.Cleanup(tail, true) 136 } 137 func TestOver4096ByteLineWithSetMaxLineSize(t *testing.T) { 138 tailTest := NewTailTest("Over4096ByteLineMaxLineSize", t) 139 testString := strings.Repeat("a", 4097) 140 tailTest.CreateFile("test.txt", "test\n"+testString+"\nhello\nworld\n") 141 tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil, MaxLineSize: 4097}) 142 go tailTest.VerifyTailOutput(tail, []string{"test", testString, "hello", "world"}, false) 143 144 // Delete after a reasonable delay, to give tail sufficient time 145 // to read all lines. 146 <-time.After(100 * time.Millisecond) 147 tailTest.RemoveFile("test.txt") 148 tailTest.Cleanup(tail, true) 149 } 150 151 func TestLocationFull(t *testing.T) { 152 tailTest := NewTailTest("location-full", t) 153 tailTest.CreateFile("test.txt", "hello\nworld\n") 154 tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: nil}) 155 go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false) 156 157 // Delete after a reasonable delay, to give tail sufficient time 158 // to read all lines. 159 <-time.After(100 * time.Millisecond) 160 tailTest.RemoveFile("test.txt") 161 tailTest.Cleanup(tail, true) 162 } 163 164 func TestLocationFullDontFollow(t *testing.T) { 165 tailTest := NewTailTest("location-full-dontfollow", t) 166 tailTest.CreateFile("test.txt", "hello\nworld\n") 167 tail := tailTest.StartTail("test.txt", Config{Follow: false, Location: nil}) 168 go tailTest.VerifyTailOutput(tail, []string{"hello", "world"}, false) 169 170 // Add more data only after reasonable delay. 171 <-time.After(100 * time.Millisecond) 172 tailTest.AppendFile("test.txt", "more\ndata\n") 173 <-time.After(100 * time.Millisecond) 174 175 tailTest.Cleanup(tail, true) 176 } 177 178 func TestLocationEnd(t *testing.T) { 179 tailTest := NewTailTest("location-end", t) 180 tailTest.CreateFile("test.txt", "hello\nworld\n") 181 tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: &SeekInfo{0, os.SEEK_END}}) 182 go tailTest.VerifyTailOutput(tail, []string{"more", "data"}, false) 183 184 <-time.After(100 * time.Millisecond) 185 tailTest.AppendFile("test.txt", "more\ndata\n") 186 187 // Delete after a reasonable delay, to give tail sufficient time 188 // to read all lines. 189 <-time.After(100 * time.Millisecond) 190 tailTest.RemoveFile("test.txt") 191 tailTest.Cleanup(tail, true) 192 } 193 194 func TestLocationMiddle(t *testing.T) { 195 // Test reading from middle. 196 tailTest := NewTailTest("location-middle", t) 197 tailTest.CreateFile("test.txt", "hello\nworld\n") 198 tail := tailTest.StartTail("test.txt", Config{Follow: true, Location: &SeekInfo{-6, os.SEEK_END}}) 199 go tailTest.VerifyTailOutput(tail, []string{"world", "more", "data"}, false) 200 201 <-time.After(100 * time.Millisecond) 202 tailTest.AppendFile("test.txt", "more\ndata\n") 203 204 // Delete after a reasonable delay, to give tail sufficient time 205 // to read all lines. 206 <-time.After(100 * time.Millisecond) 207 tailTest.RemoveFile("test.txt") 208 tailTest.Cleanup(tail, true) 209 } 210 211 // The use of polling file watcher could affect file rotation 212 // (detected via renames), so test these explicitly. 213 214 func TestReOpenInotify(t *testing.T) { 215 reOpen(t, false) 216 } 217 218 func TestReOpenPolling(t *testing.T) { 219 reOpen(t, true) 220 } 221 222 // The use of polling file watcher could affect file rotation 223 // (detected via renames), so test these explicitly. 224 225 func TestReSeekInotify(t *testing.T) { 226 reSeek(t, false) 227 } 228 229 func TestReSeekPolling(t *testing.T) { 230 reSeek(t, true) 231 } 232 233 func TestRateLimiting(t *testing.T) { 234 tailTest := NewTailTest("rate-limiting", t) 235 tailTest.CreateFile("test.txt", "hello\nworld\nagain\nextra\n") 236 config := Config{ 237 Follow: true, 238 RateLimiter: ratelimiter.NewLeakyBucket(2, time.Second)} 239 leakybucketFull := "Too much log activity; waiting a second before resuming tailing" 240 tail := tailTest.StartTail("test.txt", config) 241 242 // TODO: also verify that tail resumes after the cooloff period. 243 go tailTest.VerifyTailOutput(tail, []string{ 244 "hello", "world", "again", 245 leakybucketFull, 246 "more", "data", 247 leakybucketFull}, false) 248 249 // Add more data only after reasonable delay. 250 <-time.After(1200 * time.Millisecond) 251 tailTest.AppendFile("test.txt", "more\ndata\n") 252 253 // Delete after a reasonable delay, to give tail sufficient time 254 // to read all lines. 255 <-time.After(100 * time.Millisecond) 256 tailTest.RemoveFile("test.txt") 257 258 tailTest.Cleanup(tail, true) 259 } 260 261 func TestTell(t *testing.T) { 262 tailTest := NewTailTest("tell-position", t) 263 tailTest.CreateFile("test.txt", "hello\nworld\nagain\nmore\n") 264 config := Config{ 265 Follow: false, 266 Location: &SeekInfo{0, os.SEEK_SET}} 267 tail := tailTest.StartTail("test.txt", config) 268 // read noe line 269 <-tail.Lines 270 offset, err := tail.Tell() 271 if err != nil { 272 tailTest.Errorf("Tell return error: %s", err.Error()) 273 } 274 tail.Done() 275 // tail.close() 276 277 config = Config{ 278 Follow: false, 279 Location: &SeekInfo{offset, os.SEEK_SET}} 280 tail = tailTest.StartTail("test.txt", config) 281 for l := range tail.Lines { 282 // it may readed one line in the chan(tail.Lines), 283 // so it may lost one line. 284 if l.Text != "world" && l.Text != "again" { 285 tailTest.Fatalf("mismatch; expected world or again, but got %s", 286 l.Text) 287 } 288 break 289 } 290 tailTest.RemoveFile("test.txt") 291 tail.Done() 292 tail.Cleanup() 293 } 294 295 func TestBlockUntilExists(t *testing.T) { 296 tailTest := NewTailTest("block-until-file-exists", t) 297 config := Config{ 298 Follow: true, 299 } 300 tail := tailTest.StartTail("test.txt", config) 301 go func() { 302 time.Sleep(100 * time.Millisecond) 303 tailTest.CreateFile("test.txt", "hello world\n") 304 }() 305 for l := range tail.Lines { 306 if l.Text != "hello world" { 307 tailTest.Fatalf("mismatch; expected hello world, but got %s", 308 l.Text) 309 } 310 break 311 } 312 tailTest.RemoveFile("test.txt") 313 tail.Stop() 314 tail.Cleanup() 315 } 316 317 func maxLineSize(t *testing.T, follow bool, fileContent string, expected []string) { 318 tailTest := NewTailTest("maxlinesize", t) 319 tailTest.CreateFile("test.txt", fileContent) 320 tail := tailTest.StartTail("test.txt", Config{Follow: follow, Location: nil, MaxLineSize: 3}) 321 go tailTest.VerifyTailOutput(tail, expected, false) 322 323 // Delete after a reasonable delay, to give tail sufficient time 324 // to read all lines. 325 <-time.After(100 * time.Millisecond) 326 tailTest.RemoveFile("test.txt") 327 tailTest.Cleanup(tail, true) 328 } 329 330 func reOpen(t *testing.T, poll bool) { 331 var name string 332 var delay time.Duration 333 if poll { 334 name = "reopen-polling" 335 delay = 300 * time.Millisecond // account for POLL_DURATION 336 } else { 337 name = "reopen-inotify" 338 delay = 100 * time.Millisecond 339 } 340 tailTest := NewTailTest(name, t) 341 tailTest.CreateFile("test.txt", "hello\nworld\n") 342 tail := tailTest.StartTail( 343 "test.txt", 344 Config{Follow: true, ReOpen: true, Poll: poll}) 345 content := []string{"hello", "world", "more", "data", "endofworld"} 346 go tailTest.VerifyTailOutput(tail, content, false) 347 348 if poll { 349 // deletion must trigger reopen 350 <-time.After(delay) 351 tailTest.RemoveFile("test.txt") 352 <-time.After(delay) 353 tailTest.CreateFile("test.txt", "more\ndata\n") 354 } else { 355 // In inotify mode, fsnotify is currently unable to deliver notifications 356 // about deletion of open files, so we are not testing file deletion. 357 // (see https://github.com/fsnotify/fsnotify/issues/194 for details). 358 <-time.After(delay) 359 tailTest.AppendToFile("test.txt", "more\ndata\n") 360 } 361 362 // rename must trigger reopen 363 <-time.After(delay) 364 tailTest.RenameFile("test.txt", "test.txt.rotated") 365 <-time.After(delay) 366 tailTest.CreateFile("test.txt", "endofworld\n") 367 368 // Delete after a reasonable delay, to give tail sufficient time 369 // to read all lines. 370 <-time.After(delay) 371 tailTest.RemoveFile("test.txt") 372 <-time.After(delay) 373 374 // Do not bother with stopping as it could kill the tomb during 375 // the reading of data written above. Timings can vary based on 376 // test environment. 377 tailTest.Cleanup(tail, false) 378 } 379 380 func TestInotify_WaitForCreateThenMove(t *testing.T) { 381 tailTest := NewTailTest("wait-for-create-then-reopen", t) 382 os.Remove(tailTest.path + "/test.txt") // Make sure the file does NOT exist. 383 384 tail := tailTest.StartTail( 385 "test.txt", 386 Config{Follow: true, ReOpen: true, Poll: false}) 387 388 content := []string{"hello", "world", "endofworld"} 389 go tailTest.VerifyTailOutput(tail, content, false) 390 391 time.Sleep(50 * time.Millisecond) 392 tailTest.CreateFile("test.txt", "hello\nworld\n") 393 time.Sleep(50 * time.Millisecond) 394 tailTest.RenameFile("test.txt", "test.txt.rotated") 395 time.Sleep(50 * time.Millisecond) 396 tailTest.CreateFile("test.txt", "endofworld\n") 397 time.Sleep(50 * time.Millisecond) 398 tailTest.RemoveFile("test.txt.rotated") 399 tailTest.RemoveFile("test.txt") 400 401 // Do not bother with stopping as it could kill the tomb during 402 // the reading of data written above. Timings can vary based on 403 // test environment. 404 tailTest.Cleanup(tail, false) 405 } 406 407 func reSeek(t *testing.T, poll bool) { 408 var name string 409 if poll { 410 name = "reseek-polling" 411 } else { 412 name = "reseek-inotify" 413 } 414 tailTest := NewTailTest(name, t) 415 tailTest.CreateFile("test.txt", "a really long string goes here\nhello\nworld\n") 416 tail := tailTest.StartTail( 417 "test.txt", 418 Config{Follow: true, ReOpen: false, Poll: poll}) 419 420 go tailTest.VerifyTailOutput(tail, []string{ 421 "a really long string goes here", "hello", "world", "h311o", "w0r1d", "endofworld"}, false) 422 423 // truncate now 424 <-time.After(100 * time.Millisecond) 425 tailTest.TruncateFile("test.txt", "h311o\nw0r1d\nendofworld\n") 426 427 // Delete after a reasonable delay, to give tail sufficient time 428 // to read all lines. 429 <-time.After(100 * time.Millisecond) 430 tailTest.RemoveFile("test.txt") 431 432 // Do not bother with stopping as it could kill the tomb during 433 // the reading of data written above. Timings can vary based on 434 // test environment. 435 tailTest.Cleanup(tail, false) 436 } 437 438 // Test library 439 440 type TailTest struct { 441 Name string 442 path string 443 done chan struct{} 444 *testing.T 445 } 446 447 func NewTailTest(name string, t *testing.T) TailTest { 448 tt := TailTest{name, ".test/" + name, make(chan struct{}), t} 449 err := os.MkdirAll(tt.path, os.ModeTemporary|0700) 450 if err != nil { 451 tt.Fatal(err) 452 } 453 454 return tt 455 } 456 457 func (t TailTest) CreateFile(name string, contents string) { 458 err := ioutil.WriteFile(t.path+"/"+name, []byte(contents), 0600) 459 if err != nil { 460 t.Fatal(err) 461 } 462 } 463 464 func (t TailTest) AppendToFile(name string, contents string) { 465 err := ioutil.WriteFile(t.path+"/"+name, []byte(contents), 0600|os.ModeAppend) 466 if err != nil { 467 t.Fatal(err) 468 } 469 } 470 471 func (t TailTest) RemoveFile(name string) { 472 err := os.Remove(t.path + "/" + name) 473 if err != nil { 474 t.Fatal(err) 475 } 476 } 477 478 func (t TailTest) RenameFile(oldname string, newname string) { 479 oldname = t.path + "/" + oldname 480 newname = t.path + "/" + newname 481 err := os.Rename(oldname, newname) 482 if err != nil { 483 t.Fatal(err) 484 } 485 } 486 487 func (t TailTest) AppendFile(name string, contents string) { 488 f, err := os.OpenFile(t.path+"/"+name, os.O_APPEND|os.O_WRONLY, 0600) 489 if err != nil { 490 t.Fatal(err) 491 } 492 defer f.Close() 493 _, err = f.WriteString(contents) 494 if err != nil { 495 t.Fatal(err) 496 } 497 } 498 499 func (t TailTest) TruncateFile(name string, contents string) { 500 f, err := os.OpenFile(t.path+"/"+name, os.O_TRUNC|os.O_WRONLY, 0600) 501 if err != nil { 502 t.Fatal(err) 503 } 504 defer f.Close() 505 _, err = f.WriteString(contents) 506 if err != nil { 507 t.Fatal(err) 508 } 509 } 510 511 func (t TailTest) StartTail(name string, config Config) *Tail { 512 tail, err := TailFile(t.path+"/"+name, config) 513 if err != nil { 514 t.Fatal(err) 515 } 516 return tail 517 } 518 519 func (t TailTest) VerifyTailOutput(tail *Tail, lines []string, expectEOF bool) { 520 defer close(t.done) 521 t.ReadLines(tail, lines) 522 // It is important to do this if only EOF is expected 523 // otherwise we could block on <-tail.Lines 524 if expectEOF { 525 line, ok := <-tail.Lines 526 if ok { 527 t.Fatalf("more content from tail: %+v", line) 528 } 529 } 530 } 531 532 func (t TailTest) ReadLines(tail *Tail, lines []string) { 533 for idx, line := range lines { 534 tailedLine, ok := <-tail.Lines 535 if !ok { 536 // tail.Lines is closed and empty. 537 err := tail.Err() 538 if err != nil { 539 t.Fatalf("tail ended with error: %v", err) 540 } 541 t.Fatalf("tail ended early; expecting more: %v", lines[idx:]) 542 } 543 if tailedLine == nil { 544 t.Fatalf("tail.Lines returned nil; not possible") 545 } 546 // Note: not checking .Err as the `lines` argument is designed 547 // to match error strings as well. 548 if tailedLine.Text != line { 549 t.Fatalf( 550 "unexpected line/err from tail: "+ 551 "expecting <<%s>>>, but got <<<%s>>>", 552 line, tailedLine.Text) 553 } 554 } 555 } 556 557 func (t TailTest) Cleanup(tail *Tail, stop bool) { 558 <-t.done 559 if stop { 560 tail.Stop() 561 } 562 tail.Cleanup() 563 } 564 565 var testFile = `first line 566 second line 567 long third line third line third line third line third line 568 fourth line 569 six line 570 next first line 571 next second line 572 next third line 573 next fourth line 574 next fifth line 575 next six line 576 last first line 577 next second line 578 next first line 579 next second line 580 next third line 581 n 582 next fifth line 583 next second line 584 next third line 585 next fourth line 586 next fifth line 587 next six line 588 last second line 589 long last third line third line third line third line third line 590 last third line 591 last fourth line 592 last fifth line` 593 594 func TestLastLines(t *testing.T) { 595 tailTest := NewTailTest("ReadLast10Lines", t) 596 tailTest.CreateFile("test.txt", testFile) 597 config := Config{ 598 Follow: false, 599 MustExist: true, 600 LastLines: 3} 601 602 tail := tailTest.StartTail("test.txt", config) 603 expected := []string{"last third line", "last fourth line", "last fifth line"} 604 // line := <-tail.Lines 605 // for line := range tail.Lines { 606 // fmt.Println(line.Text,1) 607 // } 608 tailTest.VerifyTailOutput(tail, expected, false) 609 } 610 611 func TestLastLinesSmallPage(t *testing.T) { 612 tailTest := NewTailTest("TestLastLinesSmallPage", t) 613 tailTest.CreateFile("test.txt", testFile) 614 config := Config{ 615 Follow: false, 616 MustExist: true, 617 PageSize: 8, 618 LastLines: 3} 619 620 tail := tailTest.StartTail("test.txt", config) 621 expected := []string{"last third line", "last fourth line", "last fifth line"} 622 tailTest.VerifyTailOutput(tail, expected, false) 623 } 624 625 func TestLastLinesMore(t *testing.T) { 626 tailTest := NewTailTest("TestLastLinesMore", t) 627 tailTest.CreateFile("test.txt", testFile) 628 config := Config{ 629 Follow: false, 630 MustExist: true, 631 LastLines: 99} 632 633 tail := tailTest.StartTail("test.txt", config) 634 expected := strings.Split(testFile, "\n") 635 tailTest.VerifyTailOutput(tail, expected, false) 636 } 637 638 func TestLastLinesMoreSmallPage(t *testing.T) { 639 tailTest := NewTailTest("TestLastLinesMoreSmallPage", t) 640 tailTest.CreateFile("test.txt", testFile) 641 config := Config{ 642 Follow: false, 643 MustExist: true, 644 PageSize: 8, 645 LastLines: 99} 646 647 tail := tailTest.StartTail("test.txt", config) 648 expected := strings.Split(testFile, "\n") 649 tailTest.VerifyTailOutput(tail, expected, false) 650 } 651 652 func TestLastLinesLastNewLine(t *testing.T) { 653 tailTest := NewTailTest("TestLastLinesLastNewLine", t) 654 testFile = testFile + "\n" 655 tailTest.CreateFile("test.txt", testFile) 656 config := Config{ 657 Follow: false, 658 MustExist: true, 659 LastLines: 3} 660 661 tail := tailTest.StartTail("test.txt", config) 662 expected := []string{"last third line", "last fourth line", "last fifth line"} 663 tailTest.VerifyTailOutput(tail, expected, false) 664 } 665 666 func TestSkipFirstLines(t *testing.T) { 667 tailTest := NewTailTest("TestSkipFirstLines", t) 668 tailTest.CreateFile("test.txt", "one\ntwo\nthree\nfour\nfive\nsix\n\neight\n") 669 config := Config{ 670 Follow: false, 671 MustExist: true, 672 FromLine: 6} 673 674 tail := tailTest.StartTail("test.txt", config) 675 expected := []string{"six", "", "eight"} 676 677 tailTest.VerifyTailOutput(tail, expected, false) 678 } 679 680 func TestSkipFirstLinesMore(t *testing.T) { 681 tailTest := NewTailTest("TestSkipFirstLinesMore", t) 682 tailTest.CreateFile("test.txt", "one\ntwo\nthree\nfour\nfive\nsix\n\neight\n") 683 config := Config{ 684 Follow: false, 685 MustExist: true, 686 FromLine: 99} 687 688 tail := tailTest.StartTail("test.txt", config) 689 expected := []string{} 690 691 tailTest.VerifyTailOutput(tail, expected, false) 692 } 693 694 func TestSkipFirstLinesSmallPage(t *testing.T) { 695 tailTest := NewTailTest("TestSkipFirstLines", t) 696 tailTest.CreateFile("test.txt", "one\ntwo\nthree\nfour\nfive\nsix\n\neight\n") 697 config := Config{ 698 Follow: false, 699 MustExist: true, 700 PageSize: 8, 701 FromLine: 6} 702 703 tail := tailTest.StartTail("test.txt", config) 704 expected := []string{"six", "", "eight"} 705 706 tailTest.VerifyTailOutput(tail, expected, false) 707 } 708 709 func TestSkipFirstLinesEmpty(t *testing.T) { 710 tailTest := NewTailTest("TestSkipFirstLinesEmpty", t) 711 tailTest.CreateFile("test.txt", "") 712 config := Config{ 713 Follow: false, 714 MustExist: true, 715 FromLine: 9} 716 717 tail := tailTest.StartTail("test.txt", config) 718 expected := []string{} 719 720 tailTest.VerifyTailOutput(tail, expected, false) 721 } 722 723 func TestSkipFirstReOpen(t *testing.T) { 724 tailTest := NewTailTest("TestSkipFirstReOpen", t) 725 tailTest.CreateFile("test.txt", "one\ntwo\nthree\nfour\nfive\nsix\n\neight\n") 726 config := Config{ 727 ReOpen: true, 728 Follow: true, 729 MustExist: true, 730 Poll: true, 731 SeekOnReOpen: true, 732 FromLine: 5} 733 734 tail := tailTest.StartTail("test.txt", config) 735 expected := []string{"five", "six", "", "eight"} 736 737 <-time.After(300 * time.Millisecond) 738 tailTest.ReadLines(tail, expected) 739 740 tailTest.RemoveFile("test.txt") 741 tailTest.CreateFile("test.txt", "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\n") 742 <-time.After(300 * time.Millisecond) 743 expected = []string{"five", "six", "seven", "eight", "nine"} 744 tailTest.VerifyTailOutput(tail, expected, false) 745 }