Skip to content

Commit

Permalink
added line num to Line struct
Browse files Browse the repository at this point in the history
fixed zero line number bug and added unit tests

removed unnecessary StopAtEOF

include line num count for split lines
  • Loading branch information
mezzi committed Feb 27, 2019
1 parent 37f4271 commit 9a4e353
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
default: test

test: *.go
go test -v -race ./...
go test -v -race -timeout 30s ./...

fmt:
gofmt -w .
Expand Down
19 changes: 12 additions & 7 deletions tail.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"github.com/hpcloud/tail/ratelimiter"
"github.com/hpcloud/tail/util"
"github.com/hpcloud/tail/watch"
"gopkg.in/tomb.v1"
tomb "gopkg.in/tomb.v1"
)

var (
Expand All @@ -27,13 +27,14 @@ var (

type Line struct {
Text string
Num int
Time time.Time
Err error // Error from tail
}

// NewLine returns a Line with present time.
func NewLine(text string) *Line {
return &Line{text, time.Now(), nil}
func NewLine(text string, lineNum int) *Line {
return &Line{text, lineNum, time.Now(), nil}
}

// SeekInfo represents arguments to `os.Seek`
Expand Down Expand Up @@ -78,8 +79,9 @@ type Tail struct {
Lines chan *Line
Config

file *os.File
reader *bufio.Reader
file *os.File
reader *bufio.Reader
lineNum int

watcher watch.FileWatcher
changes *watch.FileChanges
Expand Down Expand Up @@ -186,6 +188,8 @@ func (tail *Tail) closeFile() {

func (tail *Tail) reopen() error {
tail.closeFile()
// reset line number
tail.lineNum = 0
for {
var err error
tail.file, err = OpenFile(tail.Filename)
Expand Down Expand Up @@ -275,7 +279,7 @@ func (tail *Tail) tailFileSync() {
// file when rate limit is reached.
msg := ("Too much log activity; waiting a second " +
"before resuming tailing")
tail.Lines <- &Line{msg, time.Now(), errors.New(msg)}
tail.Lines <- &Line{msg, tail.lineNum, time.Now(), errors.New(msg)}
select {
case <-time.After(time.Second):
case <-tail.Dying():
Expand Down Expand Up @@ -414,7 +418,8 @@ func (tail *Tail) sendLine(line string) bool {
}

for _, line := range lines {
tail.Lines <- &Line{line, now, nil}
tail.lineNum++
tail.Lines <- &Line{line, tail.lineNum, now, nil}
}

if tail.Config.RateLimiter != nil {
Expand Down
139 changes: 117 additions & 22 deletions tail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ func TestStopAtEOF(t *testing.T) {
if line.Text != "hello" {
t.Errorf("Expected to get 'hello', got '%s' instead", line.Text)
}
if line.Num != 1 {
t.Errorf("Expected to get 1, got %d instead", line.Num)
}

tailTest.VerifyTailOutput(tail, []string{"there", "world"}, false)
tail.StopAtEOF()
Expand Down Expand Up @@ -134,6 +137,7 @@ func TestOver4096ByteLine(t *testing.T) {
tailTest.RemoveFile("test.txt")
tailTest.Cleanup(tail, true)
}

func TestOver4096ByteLineWithSetMaxLineSize(t *testing.T) {
tailTest := NewTailTest("Over4096ByteLineMaxLineSize", t)
testString := strings.Repeat("a", 4097)
Expand Down Expand Up @@ -219,6 +223,40 @@ func TestReOpenPolling(t *testing.T) {
reOpen(t, true)
}

func TestReOpenWithCursor(t *testing.T) {
delay := 300 * time.Millisecond // account for POLL_DURATION
tailTest := NewTailTest("reopen-cursor", t)
tailTest.CreateFile("test.txt", "hello\nworld\n")
tail := tailTest.StartTail(
"test.txt",
Config{Follow: true, ReOpen: true, Poll: true})
content := []string{"hello", "world", "more", "data", "endofworld"}
go tailTest.VerifyTailOutputUsingCursor(tail, content, false)

// deletion must trigger reopen
<-time.After(delay)
tailTest.RemoveFile("test.txt")
<-time.After(delay)
tailTest.CreateFile("test.txt", "hello\nworld\nmore\ndata\n")

// rename must trigger reopen
<-time.After(delay)
tailTest.RenameFile("test.txt", "test.txt.rotated")
<-time.After(delay)
tailTest.CreateFile("test.txt", "hello\nworld\nmore\ndata\nendofworld\n")

// Delete after a reasonable delay, to give tail sufficient time
// to read all lines.
<-time.After(delay)
tailTest.RemoveFile("test.txt")
<-time.After(delay)

// Do not bother with stopping as it could kill the tomb during
// the reading of data written above. Timings can vary based on
// test environment.
tailTest.Cleanup(tail, false)
}

// The use of polling file watcher could affect file rotation
// (detected via renames), so test these explicitly.

Expand All @@ -230,6 +268,31 @@ func TestReSeekPolling(t *testing.T) {
reSeek(t, true)
}

func TestReSeekWithCursor(t *testing.T) {
tailTest := NewTailTest("reseek-cursor", t)
tailTest.CreateFile("test.txt", "a really long string goes here\nhello\nworld\n")
tail := tailTest.StartTail(
"test.txt",
Config{Follow: true, ReOpen: false, Poll: false})

go tailTest.VerifyTailOutputUsingCursor(tail, []string{
"a really long string goes here", "hello", "world", "but", "not", "me"}, false)

// truncate now
<-time.After(100 * time.Millisecond)
tailTest.TruncateFile("test.txt", "skip\nme\nplease\nbut\nnot\nme\n")

// Delete after a reasonable delay, to give tail sufficient time
// to read all lines.
<-time.After(100 * time.Millisecond)
tailTest.RemoveFile("test.txt")

// Do not bother with stopping as it could kill the tomb during
// the reading of data written above. Timings can vary based on
// test environment.
tailTest.Cleanup(tail, false)
}

func TestRateLimiting(t *testing.T) {
tailTest := NewTailTest("rate-limiting", t)
tailTest.CreateFile("test.txt", "hello\nworld\nagain\nextra\n")
Expand Down Expand Up @@ -266,7 +329,10 @@ func TestTell(t *testing.T) {
Location: &SeekInfo{0, os.SEEK_SET}}
tail := tailTest.StartTail("test.txt", config)
// read noe line
<-tail.Lines
line := <-tail.Lines
if line.Num != 1 {
tailTest.Errorf("expected line to have number 1 but got %d", line.Num)
}
offset, err := tail.Tell()
if err != nil {
tailTest.Errorf("Tell return error: %s", err.Error())
Expand All @@ -285,6 +351,9 @@ func TestTell(t *testing.T) {
tailTest.Fatalf("mismatch; expected world or again, but got %s",
l.Text)
}
if l.Num < 1 || l.Num > 2 {
tailTest.Errorf("expected line number to be between 1 and 2 but got %d", l.Num)
}
break
}
tailTest.RemoveFile("test.txt")
Expand Down Expand Up @@ -518,7 +587,7 @@ func (t TailTest) StartTail(name string, config Config) *Tail {

func (t TailTest) VerifyTailOutput(tail *Tail, lines []string, expectEOF bool) {
defer close(t.done)
t.ReadLines(tail, lines)
t.ReadLines(tail, lines, false)
// It is important to do this if only EOF is expected
// otherwise we could block on <-tail.Lines
if expectEOF {
Expand All @@ -529,27 +598,53 @@ func (t TailTest) VerifyTailOutput(tail *Tail, lines []string, expectEOF bool) {
}
}

func (t TailTest) ReadLines(tail *Tail, lines []string) {
for idx, line := range lines {
tailedLine, ok := <-tail.Lines
if !ok {
// tail.Lines is closed and empty.
err := tail.Err()
if err != nil {
t.Fatalf("tail ended with error: %v", err)
}
t.Fatalf("tail ended early; expecting more: %v", lines[idx:])
}
if tailedLine == nil {
t.Fatalf("tail.Lines returned nil; not possible")
func (t TailTest) VerifyTailOutputUsingCursor(tail *Tail, lines []string, expectEOF bool) {
defer close(t.done)
t.ReadLines(tail, lines, true)
// It is important to do this if only EOF is expected
// otherwise we could block on <-tail.Lines
if expectEOF {
line, ok := <-tail.Lines
if ok {
t.Fatalf("more content from tail: %+v", line)
}
// Note: not checking .Err as the `lines` argument is designed
// to match error strings as well.
if tailedLine.Text != line {
t.Fatalf(
"unexpected line/err from tail: "+
"expecting <<%s>>>, but got <<<%s>>>",
line, tailedLine.Text)
}
}

func (t TailTest) ReadLines(tail *Tail, lines []string, useCursor bool) {
cursor := 1

for _, line := range lines {
for {
tailedLine, ok := <-tail.Lines
if !ok {
// tail.Lines is closed and empty.
err := tail.Err()
if err != nil {
t.Fatalf("tail ended with error: %v", err)
}
t.Fatalf("tail ended early; expecting more: %v", lines[cursor:])
}
if tailedLine == nil {
t.Fatalf("tail.Lines returned nil; not possible")
}

if useCursor && tailedLine.Num < cursor {
// skip lines up until cursor
continue
}

// Note: not checking .Err as the `lines` argument is designed
// to match error strings as well.
if tailedLine.Text != line {
t.Fatalf(
"unexpected line/err from tail: "+
"expecting <<%s>>>, but got <<<%s>>>",
line, tailedLine.Text)
}

cursor++
break
}
}
}
Expand Down

0 comments on commit 9a4e353

Please sign in to comment.