use ReadString instead of ReadLine

ReadLine is a low-level primitive, and we don't do a good job of
splitting the lines as they are being read. best to read the entire
line and then split it in one go.

with this change, there is one test failure that will be resolved
next:

  --- FAIL: TestMaxLineSize (0.10 seconds)
         tail_test.go:410: tail ended early; expecting more: [he]
This commit is contained in:
Sridhar Ratnakumar 2014-05-16 18:01:10 -07:00
parent c7e3c77bef
commit 929590016a
2 changed files with 17 additions and 23 deletions

32
tail.go
View File

@ -13,6 +13,7 @@ import (
"launchpad.net/tomb" "launchpad.net/tomb"
"log" "log"
"os" "os"
"strings"
"time" "time"
) )
@ -167,26 +168,18 @@ func (tail *Tail) reopen() error {
return nil return nil
} }
func (tail *Tail) readLine() ([]byte, error) { func (tail *Tail) readLine() (string, error) {
line, isPrefix, err := tail.reader.ReadLine() line, err := tail.reader.ReadString('\n')
if err != nil { if err != nil {
// Note ReadString "returns the data read before the error" in
// case of an error, including EOF, so we return it as is. The
// caller is expected to process it if err is EOF.
return line, err return line, err
} }
// If MaxLineSize is set, we don't have to join the parts (let line = strings.TrimRight(line, "\n")
// Go's reader split them).
if !isPrefix || tail.MaxLineSize > 0 {
return line, err
}
// Join lines from next read "if the line was too long for the return line, err
// buffer". XXX: why not use ReadBytes('\n') or Scanner?
buf := append([]byte(nil), line...)
for isPrefix && err == nil {
line, isPrefix, err = tail.reader.ReadLine()
buf = append(buf, line...)
}
return buf, err
} }
func (tail *Tail) tailFileSync() { func (tail *Tail) tailFileSync() {
@ -222,7 +215,7 @@ func (tail *Tail) tailFileSync() {
switch err { switch err {
case nil: case nil:
if line != nil { if true {
cooloff := !tail.sendLine(line) cooloff := !tail.sendLine(line)
if cooloff { if cooloff {
// Wait a second before seeking till the end of // Wait a second before seeking till the end of
@ -337,14 +330,13 @@ func (tail *Tail) seekEnd() error {
// sendLine sends the line(s) to Lines channel, splitting longer lines // sendLine sends the line(s) to Lines channel, splitting longer lines
// if necessary. Return false if rate limit is reached. // if necessary. Return false if rate limit is reached.
func (tail *Tail) sendLine(line []byte) bool { func (tail *Tail) sendLine(line string) bool {
now := time.Now() now := time.Now()
lines := []string{string(line)} lines := []string{line}
// Split longer lines // Split longer lines
if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize { if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
lines = util.PartitionString( lines = util.PartitionString(line, tail.MaxLineSize)
string(line), tail.MaxLineSize)
} }
for _, line := range lines { for _, line := range lines {

View File

@ -85,7 +85,7 @@ func TestOver4096ByteLine(_t *testing.T) {
func TestOver4096ByteLineWithSetMaxLineSize(_t *testing.T) { func TestOver4096ByteLineWithSetMaxLineSize(_t *testing.T) {
t := NewTailTest("Over4096ByteLineMaxLineSize", _t) t := NewTailTest("Over4096ByteLineMaxLineSize", _t)
testString := strings.Repeat("a", 4097) testString := strings.Repeat("a", 4097)
t.CreateFile("test.txt", "test\r\n"+testString+"\r\nhello\r\nworld\r\n") t.CreateFile("test.txt", "test\n"+testString+"\nhello\nworld\n")
tail := t.StartTail("test.txt", Config{Follow: true, Location: nil, MaxLineSize: 4097}) tail := t.StartTail("test.txt", Config{Follow: true, Location: nil, MaxLineSize: 4097})
go t.VerifyTailOutput(tail, []string{"test", testString, "hello", "world"}) go t.VerifyTailOutput(tail, []string{"test", testString, "hello", "world"})
@ -93,7 +93,7 @@ func TestOver4096ByteLineWithSetMaxLineSize(_t *testing.T) {
// to read all lines. // to read all lines.
<-time.After(100 * time.Millisecond) <-time.After(100 * time.Millisecond)
t.RemoveFile("test.txt") t.RemoveFile("test.txt")
tail.Stop() // tail.Stop()
Cleanup() Cleanup()
} }
@ -415,7 +415,9 @@ func (t TailTest) VerifyTailOutput(tail *Tail, lines []string) {
// Note: not checking .Err as the `lines` argument is designed // Note: not checking .Err as the `lines` argument is designed
// to match error strings as well. // to match error strings as well.
if tailedLine.Text != line { if tailedLine.Text != line {
t.Fatalf("unexpected line/err from tail: expecting ```%s```, but got ```%s```", t.Fatalf(
"unexpected line/err from tail: "+
"expecting <<%s>>>, but got <<<%s>>>",
line, tailedLine.Text) line, tailedLine.Text)
} }
} }