Skip to content

Commit 9a4e353

Browse files
committed
added line num to Line struct
fixed zero line number bug and added unit tests removed unnecessary StopAtEOF include line num count for split lines
1 parent 37f4271 commit 9a4e353

3 files changed

Lines changed: 130 additions & 30 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
default: test
22

33
test: *.go
4-
go test -v -race ./...
4+
go test -v -race -timeout 30s ./...
55

66
fmt:
77
gofmt -w .

tail.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"github.com/hpcloud/tail/ratelimiter"
1919
"github.com/hpcloud/tail/util"
2020
"github.com/hpcloud/tail/watch"
21-
"gopkg.in/tomb.v1"
21+
tomb "gopkg.in/tomb.v1"
2222
)
2323

2424
var (
@@ -27,13 +27,14 @@ var (
2727

2828
type Line struct {
2929
Text string
30+
Num int
3031
Time time.Time
3132
Err error // Error from tail
3233
}
3334

3435
// NewLine returns a Line with present time.
35-
func NewLine(text string) *Line {
36-
return &Line{text, time.Now(), nil}
36+
func NewLine(text string, lineNum int) *Line {
37+
return &Line{text, lineNum, time.Now(), nil}
3738
}
3839

3940
// SeekInfo represents arguments to `os.Seek`
@@ -78,8 +79,9 @@ type Tail struct {
7879
Lines chan *Line
7980
Config
8081

81-
file *os.File
82-
reader *bufio.Reader
82+
file *os.File
83+
reader *bufio.Reader
84+
lineNum int
8385

8486
watcher watch.FileWatcher
8587
changes *watch.FileChanges
@@ -186,6 +188,8 @@ func (tail *Tail) closeFile() {
186188

187189
func (tail *Tail) reopen() error {
188190
tail.closeFile()
191+
// reset line number
192+
tail.lineNum = 0
189193
for {
190194
var err error
191195
tail.file, err = OpenFile(tail.Filename)
@@ -275,7 +279,7 @@ func (tail *Tail) tailFileSync() {
275279
// file when rate limit is reached.
276280
msg := ("Too much log activity; waiting a second " +
277281
"before resuming tailing")
278-
tail.Lines <- &Line{msg, time.Now(), errors.New(msg)}
282+
tail.Lines <- &Line{msg, tail.lineNum, time.Now(), errors.New(msg)}
279283
select {
280284
case <-time.After(time.Second):
281285
case <-tail.Dying():
@@ -414,7 +418,8 @@ func (tail *Tail) sendLine(line string) bool {
414418
}
415419

416420
for _, line := range lines {
417-
tail.Lines <- &Line{line, now, nil}
421+
tail.lineNum++
422+
tail.Lines <- &Line{line, tail.lineNum, now, nil}
418423
}
419424

420425
if tail.Config.RateLimiter != nil {

tail_test.go

Lines changed: 117 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ func TestStopAtEOF(t *testing.T) {
106106
if line.Text != "hello" {
107107
t.Errorf("Expected to get 'hello', got '%s' instead", line.Text)
108108
}
109+
if line.Num != 1 {
110+
t.Errorf("Expected to get 1, got %d instead", line.Num)
111+
}
109112

110113
tailTest.VerifyTailOutput(tail, []string{"there", "world"}, false)
111114
tail.StopAtEOF()
@@ -134,6 +137,7 @@ func TestOver4096ByteLine(t *testing.T) {
134137
tailTest.RemoveFile("test.txt")
135138
tailTest.Cleanup(tail, true)
136139
}
140+
137141
func TestOver4096ByteLineWithSetMaxLineSize(t *testing.T) {
138142
tailTest := NewTailTest("Over4096ByteLineMaxLineSize", t)
139143
testString := strings.Repeat("a", 4097)
@@ -219,6 +223,40 @@ func TestReOpenPolling(t *testing.T) {
219223
reOpen(t, true)
220224
}
221225

226+
func TestReOpenWithCursor(t *testing.T) {
227+
delay := 300 * time.Millisecond // account for POLL_DURATION
228+
tailTest := NewTailTest("reopen-cursor", t)
229+
tailTest.CreateFile("test.txt", "hello\nworld\n")
230+
tail := tailTest.StartTail(
231+
"test.txt",
232+
Config{Follow: true, ReOpen: true, Poll: true})
233+
content := []string{"hello", "world", "more", "data", "endofworld"}
234+
go tailTest.VerifyTailOutputUsingCursor(tail, content, false)
235+
236+
// deletion must trigger reopen
237+
<-time.After(delay)
238+
tailTest.RemoveFile("test.txt")
239+
<-time.After(delay)
240+
tailTest.CreateFile("test.txt", "hello\nworld\nmore\ndata\n")
241+
242+
// rename must trigger reopen
243+
<-time.After(delay)
244+
tailTest.RenameFile("test.txt", "test.txt.rotated")
245+
<-time.After(delay)
246+
tailTest.CreateFile("test.txt", "hello\nworld\nmore\ndata\nendofworld\n")
247+
248+
// Delete after a reasonable delay, to give tail sufficient time
249+
// to read all lines.
250+
<-time.After(delay)
251+
tailTest.RemoveFile("test.txt")
252+
<-time.After(delay)
253+
254+
// Do not bother with stopping as it could kill the tomb during
255+
// the reading of data written above. Timings can vary based on
256+
// test environment.
257+
tailTest.Cleanup(tail, false)
258+
}
259+
222260
// The use of polling file watcher could affect file rotation
223261
// (detected via renames), so test these explicitly.
224262

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

271+
func TestReSeekWithCursor(t *testing.T) {
272+
tailTest := NewTailTest("reseek-cursor", t)
273+
tailTest.CreateFile("test.txt", "a really long string goes here\nhello\nworld\n")
274+
tail := tailTest.StartTail(
275+
"test.txt",
276+
Config{Follow: true, ReOpen: false, Poll: false})
277+
278+
go tailTest.VerifyTailOutputUsingCursor(tail, []string{
279+
"a really long string goes here", "hello", "world", "but", "not", "me"}, false)
280+
281+
// truncate now
282+
<-time.After(100 * time.Millisecond)
283+
tailTest.TruncateFile("test.txt", "skip\nme\nplease\nbut\nnot\nme\n")
284+
285+
// Delete after a reasonable delay, to give tail sufficient time
286+
// to read all lines.
287+
<-time.After(100 * time.Millisecond)
288+
tailTest.RemoveFile("test.txt")
289+
290+
// Do not bother with stopping as it could kill the tomb during
291+
// the reading of data written above. Timings can vary based on
292+
// test environment.
293+
tailTest.Cleanup(tail, false)
294+
}
295+
233296
func TestRateLimiting(t *testing.T) {
234297
tailTest := NewTailTest("rate-limiting", t)
235298
tailTest.CreateFile("test.txt", "hello\nworld\nagain\nextra\n")
@@ -266,7 +329,10 @@ func TestTell(t *testing.T) {
266329
Location: &SeekInfo{0, os.SEEK_SET}}
267330
tail := tailTest.StartTail("test.txt", config)
268331
// read noe line
269-
<-tail.Lines
332+
line := <-tail.Lines
333+
if line.Num != 1 {
334+
tailTest.Errorf("expected line to have number 1 but got %d", line.Num)
335+
}
270336
offset, err := tail.Tell()
271337
if err != nil {
272338
tailTest.Errorf("Tell return error: %s", err.Error())
@@ -285,6 +351,9 @@ func TestTell(t *testing.T) {
285351
tailTest.Fatalf("mismatch; expected world or again, but got %s",
286352
l.Text)
287353
}
354+
if l.Num < 1 || l.Num > 2 {
355+
tailTest.Errorf("expected line number to be between 1 and 2 but got %d", l.Num)
356+
}
288357
break
289358
}
290359
tailTest.RemoveFile("test.txt")
@@ -518,7 +587,7 @@ func (t TailTest) StartTail(name string, config Config) *Tail {
518587

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

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")
601+
func (t TailTest) VerifyTailOutputUsingCursor(tail *Tail, lines []string, expectEOF bool) {
602+
defer close(t.done)
603+
t.ReadLines(tail, lines, true)
604+
// It is important to do this if only EOF is expected
605+
// otherwise we could block on <-tail.Lines
606+
if expectEOF {
607+
line, ok := <-tail.Lines
608+
if ok {
609+
t.Fatalf("more content from tail: %+v", line)
545610
}
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)
611+
}
612+
}
613+
614+
func (t TailTest) ReadLines(tail *Tail, lines []string, useCursor bool) {
615+
cursor := 1
616+
617+
for _, line := range lines {
618+
for {
619+
tailedLine, ok := <-tail.Lines
620+
if !ok {
621+
// tail.Lines is closed and empty.
622+
err := tail.Err()
623+
if err != nil {
624+
t.Fatalf("tail ended with error: %v", err)
625+
}
626+
t.Fatalf("tail ended early; expecting more: %v", lines[cursor:])
627+
}
628+
if tailedLine == nil {
629+
t.Fatalf("tail.Lines returned nil; not possible")
630+
}
631+
632+
if useCursor && tailedLine.Num < cursor {
633+
// skip lines up until cursor
634+
continue
635+
}
636+
637+
// Note: not checking .Err as the `lines` argument is designed
638+
// to match error strings as well.
639+
if tailedLine.Text != line {
640+
t.Fatalf(
641+
"unexpected line/err from tail: "+
642+
"expecting <<%s>>>, but got <<<%s>>>",
643+
line, tailedLine.Text)
644+
}
645+
646+
cursor++
647+
break
553648
}
554649
}
555650
}

0 commit comments

Comments
 (0)