Last active
April 15, 2022 15:03
-
-
Save lovromazgon/4e06a8093bfd7d292feacceb90654e07 to your computer and use it in GitHub Desktop.
Test for bug in nxadm/tail, see https://github.com/nxadm/tail/issues/41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"os" | |
"sync" | |
"testing" | |
"time" | |
"github.com/nxadm/tail" | |
) | |
// TestTail runs three goroutines, one will continuously write to a file every | |
// 500 milliseconds until the context gets cancelled, the other two will each | |
// tail the same file using github.com/nxadm/tail and print the lines they read | |
// until the context gets cancelled. Contexts are configured so that the writing | |
// goroutine runs for 5 seconds, the first tailing goroutine runs for 2 seconds | |
// and the second tailing goroutine runs for 4 seconds. | |
// | |
// The expectation is that the second goroutine would keep on tailing the file | |
// after the first one stops, but that is not the case. | |
// | |
// The issue is (presumably) that both goroutines use the same underlying | |
// watcher and after one stops the watcher is removed. | |
func TestTail(t *testing.T) { | |
testFile := fmt.Sprintf("%s/%s", t.TempDir(), "test.txt") | |
f, err := os.Create(testFile) | |
if err != nil { | |
t.Fatal(err) | |
} | |
writeForever := func(ctx context.Context, wg *sync.WaitGroup) { | |
defer wg.Done() | |
i := 0 | |
for { | |
if ctx.Err() != nil { | |
return | |
} | |
line := fmt.Sprintf("line%d\n", i) | |
fmt.Printf("writing line %v", line) | |
_, err := f.Write([]byte(line)) | |
if err != nil { | |
panic(err) | |
} | |
i++ | |
time.Sleep(time.Millisecond * 500) | |
} | |
} | |
tailForever := func(ctx context.Context, wg *sync.WaitGroup, name string) { | |
defer wg.Done() | |
tl, err := tail.TailFile(f.Name(), tail.Config{Follow: true, ReOpen: true}) | |
if err != nil { | |
panic(err) | |
} | |
defer func() { | |
err := tl.Stop() | |
if err != nil { | |
panic(fmt.Errorf("%s: %w", name, err)) | |
} | |
// tl.Cleanup() | |
}() | |
for { | |
select { | |
case line, ok := <-tl.Lines: | |
if !ok { | |
panic(fmt.Errorf("%s: lines closed", name)) | |
} | |
if line.Err != nil { | |
panic(fmt.Errorf("%s: %w", name, line.Err)) | |
} | |
fmt.Printf("%s (%v): %s\n", name, ok, line.Text) | |
case <-ctx.Done(): | |
fmt.Printf("%s: --- done ---\n", name) | |
return // we are done | |
} | |
} | |
} | |
// run test for 5 seconds | |
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) | |
defer cancel() | |
var wg sync.WaitGroup | |
wg.Add(3) | |
go writeForever(ctx, &wg) | |
// first tail will run for 2 seconds | |
ctxTail, cancelTail := context.WithTimeout(ctx, time.Second*2) | |
defer cancelTail() | |
go tailForever(ctxTail, &wg, "first") | |
// second tail will run for 4 seconds | |
ctxTail, cancelTail = context.WithTimeout(ctx, time.Second*4) | |
defer cancelTail() | |
go tailForever(ctxTail, &wg, "second") | |
wg.Wait() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment