Add some higher level tests and benchmarks
This commit is contained in:
parent
5ea0bbfc87
commit
0c0a814a4e
|
@ -0,0 +1,166 @@
|
|||
package journal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testingCommon interface {
|
||||
TempDir() string
|
||||
Fatalf(string, ...any)
|
||||
}
|
||||
|
||||
// testConnector spawns a Conn with a local Unix datagram socket that checks
|
||||
// incoming datagrams for well-formedness but otherwise discards them.
|
||||
func testConnector(t testingCommon) *Conn {
|
||||
sockPath := filepath.Join(t.TempDir(), "test-socket")
|
||||
sock, err := net.ListenUnixgram("unixgram", &net.UnixAddr{
|
||||
Name: sockPath,
|
||||
Net: "unixgram",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("testConnector: ListenUnix: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, 1<<16 /*enough for our tests */)
|
||||
for {
|
||||
n, err := sock.Read(buf)
|
||||
switch err {
|
||||
case nil:
|
||||
case io.EOF:
|
||||
return
|
||||
default:
|
||||
t.Fatalf("testConnector: Read: %v", err)
|
||||
}
|
||||
ok, pos, desc := checkWellFormedProto(buf[:n])
|
||||
if !ok {
|
||||
t.Fatalf("received malformed data at pos 0x%x: %s\n%s", pos, desc, hex.Dump(buf[:n]))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := Connect(sockPath)
|
||||
if err != nil {
|
||||
t.Fatalf("testConnector: DialUnix: %v", err)
|
||||
}
|
||||
conn.ErrHandler = func(err error) {
|
||||
t.Fatalf("testConnector: connection error: %v", err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func checkWellFormedProto(buf []byte) (ok bool, pos int, desc string) {
|
||||
for pos < len(buf) {
|
||||
// grab attribute name up to '=' or '\n'
|
||||
off := bytes.IndexAny(buf[pos:], "=\n")
|
||||
if off == -1 {
|
||||
return false, pos, "unterminated key"
|
||||
}
|
||||
key := string(buf[pos : pos+off])
|
||||
if err := AttrKeyValid(key); err != nil {
|
||||
return false, pos, err.Error()
|
||||
}
|
||||
pos += off
|
||||
|
||||
// for KEY=VALUE, the value is terminated by a newline
|
||||
if buf[pos] == '=' {
|
||||
pos++
|
||||
off = bytes.IndexByte(buf[pos:], '\n')
|
||||
if off == -1 {
|
||||
return false, pos, "unterminated value"
|
||||
}
|
||||
pos += off // consume value
|
||||
pos++ // consume trailing newline
|
||||
continue
|
||||
}
|
||||
|
||||
// otherwise, expect an 8-bit little-endian length
|
||||
pos++ // consume newline after key
|
||||
if pos+8 > len(buf) {
|
||||
return false, pos, "value length too short"
|
||||
}
|
||||
vlen := binary.LittleEndian.Uint64(buf[pos:])
|
||||
pos += 8
|
||||
if vlen > uint64(len(buf)) /* protect against overflow */ ||
|
||||
uint64(pos)+vlen+1 > uint64(len(buf)) {
|
||||
return false, pos, "value length too long"
|
||||
}
|
||||
pos += int(vlen)
|
||||
if buf[pos] != '\n' {
|
||||
return false, pos, "value not terminated by newline"
|
||||
}
|
||||
pos++
|
||||
}
|
||||
return true, pos, ""
|
||||
}
|
||||
|
||||
// TestConcurrentEntries is best run with the race detector, and tries to pick
|
||||
// up any faults that might occur when concurrent goroutines write into the same
|
||||
// Conn.
|
||||
func TestConcurrentEntries(t *testing.T) {
|
||||
c := testConnector(t)
|
||||
|
||||
// attributes which will be common to all EntryErr calls
|
||||
attr := make([]Attr, 0, 10 /* enough capacity to avoid realloc on append; might trigger data races */)
|
||||
attr = append(attr, Attr{
|
||||
Key: AttrKey{
|
||||
key: "HELLO",
|
||||
},
|
||||
Value: []byte("world"),
|
||||
})
|
||||
|
||||
// spawn goroutines
|
||||
start := make(chan struct{})
|
||||
for i := 0; i < 16; i++ {
|
||||
go func() {
|
||||
<-start
|
||||
for j := 0; j < 100; j++ {
|
||||
err := c.EntryErr(PriDebug, "message "+strconv.Itoa(i)+"."+strconv.Itoa(j), attr)
|
||||
if err != nil {
|
||||
t.Fatalf("message %d.%d error: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// try to get all the goroutines to start at roughly the same time
|
||||
close(start)
|
||||
}
|
||||
|
||||
// BenchmarkEntry is a benchmark for the common Entry function.
|
||||
func BenchmarkEntry(b *testing.B) {
|
||||
c := testConnector(b)
|
||||
|
||||
// add some common attributes
|
||||
c.Common = make([]Attr, 0, 10)
|
||||
c.Common = append(c.Common, Attr{
|
||||
Key: AttrKey{
|
||||
key: "COMMON_ATTR",
|
||||
},
|
||||
Value: []byte("abc123\n"),
|
||||
})
|
||||
|
||||
// attributes which will be common to all EntryErr calls
|
||||
attr := make([]Attr, 0, 10)
|
||||
attr = append(attr, Attr{
|
||||
Key: AttrKey{
|
||||
key: "HELLO",
|
||||
},
|
||||
Value: []byte("world"),
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := c.EntryErr(PriDebug, "hello world!", attr)
|
||||
if err != nil {
|
||||
b.Fatalf("message %d: error %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue