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  }