func benchmarkLogEntryDecoderDecode(b *testing.B, sz int) { var buf bytes.Buffer enc := raft.NewLogEntryEncoder(&buf) dec := raft.NewLogEntryDecoder(&buf) // Encode a single record and record its size. enc.Encode(&raft.LogEntry{Data: make([]byte, sz)}) b.SetBytes(int64(buf.Len())) // Encode all the records on the buffer first. buf.Reset() for i := 0; i < b.N; i++ { if err := enc.Encode(&raft.LogEntry{Data: make([]byte, sz)}); err != nil { b.Fatalf("encode: %s", err) } } b.ReportAllocs() // Decode from the buffer. b.ResetTimer() for i := 0; i < b.N; i++ { var entry raft.LogEntry if err := dec.Decode(&entry); err != nil { b.Fatal(err) } } b.StopTimer() runtime.GC() }
// Ensure the decoder returns EOF when no more data is available. func TestLogEntryDecoder_Decode_EOF(t *testing.T) { var e raft.LogEntry dec := raft.NewLogEntryDecoder(bytes.NewReader([]byte{})) if err := dec.Decode(&e); err != io.EOF { t.Fatalf("unexpected error: %s", err) } }
// Ensure the decoder returns an unexpected EOF when reading partial entries. func TestLogEntryDecoder_Decode_ErrUnexpectedEOF_Type(t *testing.T) { for i, tt := range []struct { buf []byte }{ {[]byte{0x10}}, // type flag only {[]byte{0x10, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0}}, // partial header {[]byte{0x10, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0}}, // full header, no data {[]byte{0x10, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5}}, // full header, partial data } { var e raft.LogEntry dec := raft.NewLogEntryDecoder(bytes.NewReader(tt.buf)) if err := dec.Decode(&e); err != io.ErrUnexpectedEOF { t.Errorf("%d. unexpected error: %s", i, err) } } }
// Ensure that log entries can be decoded from a reader. func TestLogEntryDecoder_Decode(t *testing.T) { buf := bytes.NewBuffer([]byte{0x10, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, 6}) // Create a blank entry and an expected result. entry := &raft.LogEntry{} // Decode the entry from the buffer. dec := raft.NewLogEntryDecoder(buf) if err := dec.Decode(entry); err != nil { t.Fatalf("unexpected error: %s", err) } if index := uint64(2); index != entry.Index { t.Fatalf("index: exp: %v, got: %v", index, entry.Index) } if term := uint64(3); term != entry.Term { t.Fatalf("term: exp: %v, got: %v", term, entry.Term) } if data := []byte{4, 5, 6}; !bytes.Equal(data, entry.Data) { t.Fatalf("data: exp: %x, got: %x", data, entry.Term) } }
// Ensure that random entries can be encoded and decoded correctly. func TestLogEntryEncodeDecode(t *testing.T) { f := func(entries []raft.LogEntry) bool { var buf bytes.Buffer enc := raft.NewLogEntryEncoder(&buf) dec := raft.NewLogEntryDecoder(&buf) // Encode entries. for _, e := range entries { if e.Type == 0xFF { buf.WriteByte(0xFF) continue } if err := enc.Encode(&e); err != nil { t.Fatalf("encode: %s", err) } } // Decode entries. for _, e := range entries { var entry raft.LogEntry if err := dec.Decode(&entry); err != nil { t.Fatalf("decode: %s", err) } else if entry.Type == 0xFF { if !reflect.DeepEqual(&entry, &raft.LogEntry{Type: 0xFF}) { t.Fatalf("invalid snapshot entry: %#v", &entry) } } else if !reflect.DeepEqual(e, entry) { t.Fatalf("mismatch:\n\nexp: %#v\n\ngot: %#v\n\n", e, entry) } } return true } if err := quick.Check(f, nil); err != nil { t.Error(err) } }
// Ensure a stream can be retrieved over HTTP. func TestHTTPHandler_HandleStream(t *testing.T) { n := NewInitNode() defer n.Close() // Connect to stream. resp, err := http.Get(n.Server.URL + "/stream?id=1&term=1") if err != nil { t.Fatalf("unexpected error: %s", err) } else if resp.StatusCode != http.StatusOK { t.Fatalf("unexpected status: %d", resp.StatusCode) } defer resp.Body.Close() // Ensure the stream is connected before applying a command. time.Sleep(10 * time.Millisecond) // Add an entry. if _, err := n.Log.Apply([]byte("xyz")); err != nil { t.Fatal(err) } // Move log's clock ahead & flush data. n.Log.Clock.Add(n.Log.HeartbeatInterval) n.Log.Flush() // Read entries from stream. var e raft.LogEntry dec := raft.NewLogEntryDecoder(resp.Body) // First entry should be the configuration. if err := dec.Decode(&e); err != nil { t.Fatalf("unexpected error: %s", err) } else if e.Type != 0xFE { t.Fatalf("expected configuration type: %d", e.Type) } // Next entry should be the snapshot. if err := dec.Decode(&e); err != nil { t.Fatalf("unexpected error: %s", err) } else if !reflect.DeepEqual(&e, &raft.LogEntry{Type: 0xFF, Data: nil}) { t.Fatalf("expected snapshot type: %d", e.Type) } // Read off the snapshot. var fsm FSM if err := fsm.Restore(resp.Body); err != nil { t.Fatalf("restore: %s", err) } // Read off the snapshot index. var index uint64 if err := binary.Read(resp.Body, binary.BigEndian, &index); err != nil { t.Fatalf("read snapshot index: %s", err) } else if index != 1 { t.Fatalf("unexpected snapshot index: %d", index) } // Next entry should be the command. if err := dec.Decode(&e); err != nil { t.Fatalf("unexpected error: %s", err) } else if !reflect.DeepEqual(&e, &raft.LogEntry{Index: 2, Term: 1, Data: []byte("xyz")}) { t.Fatalf("unexpected entry: %#v", &e) } }