Skip to content

Instantly share code, notes, and snippets.

@lovromazgon
Last active April 15, 2022 15:03
Show Gist options
  • Save lovromazgon/4e06a8093bfd7d292feacceb90654e07 to your computer and use it in GitHub Desktop.
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
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