package wal import ( "git.crumpington.com/public/jldb/lib/errs" "git.crumpington.com/public/jldb/lib/testutil" "log" "math/rand" "reflect" "strings" "sync" "sync/atomic" "testing" "time" ) func TestSendRecvHarness(t *testing.T) { t.Parallel() (&SendRecvTestHarness{}).Run(t) } type SendRecvTestHarness struct{} func (h *SendRecvTestHarness) Run(t *testing.T) { val := reflect.ValueOf(h) typ := val.Type() for i := 0; i < typ.NumMethod(); i++ { method := typ.Method(i) if !strings.HasPrefix(method.Name, "Test") { continue } t.Run(method.Name, func(t *testing.T) { t.Parallel() pDir := t.TempDir() sDir := t.TempDir() config := Config{ SegMinCount: 8, SegMaxAgeSec: 1, } pWAL, err := Create(pDir, 1, config) if err != nil { t.Fatal(err) } defer pWAL.Close() sWAL, err := Create(sDir, 1, config) if err != nil { t.Fatal(err) } defer sWAL.Close() nw := testutil.NewNetwork() defer func() { nw.CloseServer() nw.CloseClient() }() val.MethodByName(method.Name).Call([]reflect.Value{ reflect.ValueOf(t), reflect.ValueOf(pWAL), reflect.ValueOf(sWAL), reflect.ValueOf(nw), }) }) } } func (h *SendRecvTestHarness) TestSimple( t *testing.T, pWAL *WAL, sWAL *WAL, nw *testutil.Network, ) { wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() if err := writeRandomWithEOF(pWAL, 5*time.Second); err != nil { panic(err) } }() // Send in the background. wg.Add(1) go func() { defer wg.Done() conn := nw.Accept() if err := pWAL.Send(conn, 8*time.Second); err != nil { log.Printf("Send error: %v", err) } }() // Recv in the background. wg.Add(1) go func() { defer wg.Done() conn := nw.Dial() if err := sWAL.Recv(conn, 8*time.Second); err != nil { log.Printf("Recv error: %v", err) } }() waitForEOF(t, sWAL) nw.CloseServer() nw.CloseClient() wg.Wait() checkWALsEqual(t, pWAL, sWAL) } func (h *SendRecvTestHarness) TestWriteThenRead( t *testing.T, pWAL *WAL, sWAL *WAL, nw *testutil.Network, ) { wg := sync.WaitGroup{} if err := writeRandomWithEOF(pWAL, 2*time.Second); err != nil { t.Fatal(err) } // Send in the background. wg.Add(1) go func() { defer wg.Done() conn := nw.Accept() if err := pWAL.Send(conn, 8*time.Second); err != nil { log.Printf("Send error: %v", err) } }() // Recv in the background. wg.Add(1) go func() { defer wg.Done() conn := nw.Dial() if err := sWAL.Recv(conn, 8*time.Second); err != nil { log.Printf("Recv error: %v", err) } }() waitForEOF(t, sWAL) nw.CloseServer() nw.CloseClient() wg.Wait() checkWALsEqual(t, pWAL, sWAL) } func (h *SendRecvTestHarness) TestNetworkFailures( t *testing.T, pWAL *WAL, sWAL *WAL, nw *testutil.Network, ) { recvDone := &atomic.Bool{} wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() writeRandomWithEOF(pWAL, 10*time.Second) }() // Send in the background. wg.Add(1) go func() { defer wg.Done() for { if recvDone.Load() { return } if conn := nw.Accept(); conn != nil { pWAL.Send(conn, 8*time.Second) } } }() // Recv in the background. wg.Add(1) go func() { defer wg.Done() for !recvDone.Load() { if conn := nw.Dial(); conn != nil { sWAL.Recv(conn, 8*time.Second) } } }() wg.Add(1) failureCount := 0 go func() { defer wg.Done() for { if recvDone.Load() { return } time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) failureCount++ if rand.Float64() < 0.5 { nw.CloseClient() } else { nw.CloseServer() } } }() waitForEOF(t, sWAL) recvDone.Store(true) wg.Wait() log.Printf("%d network failures.", failureCount) if failureCount < 10 { t.Fatal("Expected more failures.") } checkWALsEqual(t, pWAL, sWAL) } func (h *SendRecvTestHarness) TestSenderClose( t *testing.T, pWAL *WAL, sWAL *WAL, nw *testutil.Network, ) { wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() if err := writeRandomWithEOF(pWAL, 5*time.Second); !errs.Closed.Is(err) { panic(err) } }() // Close primary after some time. wg.Add(1) go func() { defer wg.Done() time.Sleep(time.Second) pWAL.Close() }() // Send in the background. wg.Add(1) go func() { defer wg.Done() conn := nw.Accept() if err := pWAL.Send(conn, 8*time.Second); err != nil { log.Printf("Send error: %v", err) } }() conn := nw.Dial() if err := sWAL.Recv(conn, 8*time.Second); !errs.Closed.Is(err) { t.Fatal(err) } nw.CloseServer() nw.CloseClient() wg.Wait() }