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