package wal import ( "fmt" "math/rand" "os" "testing" "time" "git.crumpington.com/private/mdb/testconn" ) func TestShipping(t *testing.T) { run := func(name string, inner func( t *testing.T, wWALPath string, fWALPath string, w *Writer, nw *testconn.Network, )) { t.Run(name, func(t *testing.T) { wWALPath := randPath() + ".wal" fWALPath := randPath() + ".wal" w := NewWriterPrimary(wWALPath) defer w.Close() nw := testconn.NewNetwork() defer nw.CloseClient() defer nw.CloseServer() defer os.RemoveAll(wWALPath) defer os.RemoveAll(fWALPath) inner(t, wWALPath, fWALPath, w, nw) }) } run("simple", func(t *testing.T, wWALPath, fWALPath string, w *Writer, nw *testconn.Network) { // Write 100 entries in background. go func() { for i := 0; i < 100; i++ { time.Sleep(10 * time.Millisecond) w.Store("x", uint64(i+10), _b(fmt.Sprintf("data %d", i))) } }() // Run a sender in the background. go func() { f := NewFollower(wWALPath) conn := nw.Accept() f.SendWAL(conn) }() // Run the follower. go func() { w := NewWriterSecondary(fWALPath) conn := nw.Dial() w.RecvWAL(conn) }() time.Sleep(time.Second) // Wait for follower to get 100 entries, then close connection. f := NewFollower(fWALPath) defer f.Close() f.waitForSeqNum(100) if err := walsEqual(wWALPath, fWALPath); err != nil { t.Fatal(err) } }) run("net failures", func(t *testing.T, wWALPath, fWALPath string, w *Writer, nw *testconn.Network) { defer nw.CloseClient() defer nw.CloseServer() N := 4000 sleepTime := time.Millisecond go func() { for i := 0; i < N; i++ { time.Sleep(sleepTime) if rand.Float64() < 0.9 { w.Store(randString(), randID(), _b(randString())) } else { w.Delete(randString(), randID()) } } }() // Run a sender in the background. go func() { sender := NewFollower(wWALPath) f := NewFollower(fWALPath) defer f.Close() for f.MaxSeqNum() < uint64(N) { if conn := nw.Accept(); conn != nil { sender.SendWAL(conn) } } }() // Run the follower in the background. go func() { f := NewFollower(fWALPath) defer f.Close() w := NewWriterSecondary(fWALPath) for f.MaxSeqNum() < uint64(N) { if conn := nw.Dial(); conn != nil { w.RecvWAL(conn) } } }() // Disconnect the network randomly. go func() { f := NewFollower(fWALPath) defer f.Close() for f.MaxSeqNum() < uint64(N) { time.Sleep(time.Duration(rand.Intn(10 * int(sleepTime)))) if rand.Float64() < 0.5 { nw.CloseClient() } else { nw.CloseServer() } } }() f := NewFollower(fWALPath) defer f.Close() // Wait for follower to get 100 entries, then close connection. f.waitForSeqNum(uint64(N)) if err := walsEqual(wWALPath, fWALPath); err != nil { t.Fatal(err) } }) run("secondary too far behind", func(t *testing.T, wWALPath, fWALPath string, w *Writer, nw *testconn.Network) { // Write some entries to the primary. // MaxSeqNum will be 10. for i := 0; i < 10; i++ { w.Store(randString(), randID(), _b(randString())) } // Delete everything. w.DeleteBefore(-1) // Run a sender in the background. go func() { f := NewFollower(wWALPath) defer f.Close() conn := nw.Accept() f.SendWAL(conn) }() // Run the follower. go func() { w := NewWriterSecondary(fWALPath) defer w.Close() conn := nw.Dial() w.RecvWAL(conn) }() time.Sleep(time.Second) f := NewFollower(fWALPath) defer f.Close() if f.MaxSeqNum() != 0 { t.Fatal(f.MaxSeqNum()) } }) } func TestShippingEncoding(t *testing.T) { recs := []Record{ {SeqNum: 10, Collection: "x", ID: 44, Store: true, Data: _b("Hello")}, {SeqNum: 24, Collection: "abc", ID: 3, Store: true, Data: _b("x")}, {SeqNum: 81, Collection: "qrs", ID: 102, Store: false}, } buf := make([]byte, recHeaderSize) for _, rec := range recs { encodeRecordHeader(rec, buf) out, colLen, dataLen := decodeRecHeader(buf) if out.SeqNum != rec.SeqNum { t.Fatal(out, rec) } if out.ID != rec.ID { t.Fatal(out, rec) } if out.Store != rec.Store { t.Fatal(out, rec) } if colLen != len(rec.Collection) { t.Fatal(out, rec) } if dataLen != len(rec.Data) { t.Fatal(out, rec) } } }