diff --git a/tail.go b/tail.go index c962599..af5029b 100644 --- a/tail.go +++ b/tail.go @@ -2,11 +2,11 @@ // Copyright (c) 2015 HPE Software Inc. All rights reserved. // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. -//nxadm/tail provides a Go library that emulates the features of the BSD `tail` -//program. The library comes with full support for truncation/move detection as -//it is designed to work with log rotation tools. The library works on all -//operating systems supported by Go, including POSIX systems like Linux and -//*BSD, and MS Windows. Go 1.9 is the oldest compiler release supported. +// nxadm/tail provides a Go library that emulates the features of the BSD `tail` +// program. The library comes with full support for truncation/move detection as +// it is designed to work with log rotation tools. The library works on all +// operating systems supported by Go, including POSIX systems like Linux and +// *BSD, and MS Windows. Go 1.9 is the oldest compiler release supported. package tail import ( @@ -450,12 +450,25 @@ func (tail *Tail) sendLine(line string) bool { lines = util.PartitionString(line, tail.MaxLineSize) } + // This is a bit weird here, when a users requests stopAtEof we + // must keep sending all lines however <-tail.Dying() will return + // immediately at this point so the select below may not have + // chance to send the line if the reader side has is not yet ready. + // But if StopAtEOF was not set and it is a "normal" Kill then we + // should exit right away still thus the special logic here. + earlyExitChan := tail.Dying() + if tail.Err() == errStopAtEOF { + // Note that receive from a nil channel blocks forever so + // below we know it can only take the tail.Lines case. + earlyExitChan = nil + } + for _, line := range lines { tail.lineNum++ offset, _ := tail.Tell() select { case tail.Lines <- &Line{line, tail.lineNum, SeekInfo{Offset: offset}, now, nil}: - case <-tail.Dying(): + case <-earlyExitChan: return true } } diff --git a/tail_test.go b/tail_test.go index 7b9319e..cd2a0a2 100644 --- a/tail_test.go +++ b/tail_test.go @@ -622,6 +622,26 @@ func TestIncompleteLinesWithoutFollow(t *testing.T) { tail.Cleanup() } +func TestFollowUntilEof(t *testing.T) { + tailTest, cleanup := NewTailTest("incomplete-lines-no-follow", t) + defer cleanup() + filename := "test.txt" + config := Config{ + Follow: false, + } + tailTest.CreateFile(filename, "hello\nworld\n") + tail := tailTest.StartTail(filename, config) + + // StopAtEOF blocks until the read is done and in order to do so + // we have to drain the lines channel first which ReadLinesWithError does. + go tail.StopAtEOF() + tailTest.ReadLinesWithError(tail, []string{"hello", "world"}, false, errStopAtEOF) + + tailTest.RemoveFile(filename) + tail.Stop() + tail.Cleanup() +} + func reSeek(t *testing.T, poll bool) { var name string if poll { @@ -765,6 +785,14 @@ func (t TailTest) VerifyTailOutputUsingCursor(tail *Tail, lines []string, expect } func (t TailTest) ReadLines(tail *Tail, lines []string, useCursor bool) { + t.readLines(tail, lines, useCursor, nil) +} + +func (t TailTest) ReadLinesWithError(tail *Tail, lines []string, useCursor bool, err error) { + t.readLines(tail, lines, useCursor, err) +} + +func (t TailTest) readLines(tail *Tail, lines []string, useCursor bool, expectErr error) { cursor := 1 for _, line := range lines { @@ -773,8 +801,8 @@ func (t TailTest) ReadLines(tail *Tail, lines []string, useCursor bool) { if !ok { // tail.Lines is closed and empty. err := tail.Err() - if err != nil { - t.Fatalf("tail ended with error: %v", err) + if err != expectErr { + t.Fatalf("tail ended with unexpected error: %v", err) } t.Fatalf("tail ended early; expecting more: %v", lines[cursor:]) }