Skip to content

Commit bdc877a

Browse files
committed
added line num to Line struct
fixed zero line number bug and added unit tests removed unnecessary StopAtEOF
1 parent 37f4271 commit bdc877a

File tree

3 files changed

+133
-33
lines changed

3 files changed

+133
-33
lines changed

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: 15 additions & 10 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)
@@ -265,17 +269,18 @@ func (tail *Tail) tailFileSync() {
265269
}
266270
}
267271

272+
tail.lineNum++
268273
line, err := tail.readLine()
269274

270275
// Process `line` even if err is EOF.
271276
if err == nil {
272-
cooloff := !tail.sendLine(line)
277+
cooloff := !tail.sendLine(line, tail.lineNum)
273278
if cooloff {
274279
// Wait a second before seeking till the end of
275280
// file when rate limit is reached.
276281
msg := ("Too much log activity; waiting a second " +
277282
"before resuming tailing")
278-
tail.Lines <- &Line{msg, time.Now(), errors.New(msg)}
283+
tail.Lines <- &Line{msg, tail.lineNum, time.Now(), errors.New(msg)}
279284
select {
280285
case <-time.After(time.Second):
281286
case <-tail.Dying():
@@ -289,7 +294,7 @@ func (tail *Tail) tailFileSync() {
289294
} else if err == io.EOF {
290295
if !tail.Follow {
291296
if line != "" {
292-
tail.sendLine(line)
297+
tail.sendLine(line, tail.lineNum)
293298
}
294299
return
295300
}
@@ -404,7 +409,7 @@ func (tail *Tail) seekTo(pos SeekInfo) error {
404409

405410
// sendLine sends the line(s) to Lines channel, splitting longer lines
406411
// if necessary. Return false if rate limit is reached.
407-
func (tail *Tail) sendLine(line string) bool {
412+
func (tail *Tail) sendLine(line string, lineNum int) bool {
408413
now := time.Now()
409414
lines := []string{line}
410415

@@ -414,7 +419,7 @@ func (tail *Tail) sendLine(line string) bool {
414419
}
415420

416421
for _, line := range lines {
417-
tail.Lines <- &Line{line, now, nil}
422+
tail.Lines <- &Line{line, 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)