-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
quick.go
141 lines (131 loc) · 3.49 KB
/
quick.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Package testing provides utilities for testing implementations of Store
package testing
import (
"fmt"
"io"
"math/rand"
"reflect"
"testing"
"testing/quick"
"zenhack.net/go/irc-idler/irc"
"zenhack.net/go/irc-idler/storage"
// for casting reflect.Values to their actual values:
"unsafe"
)
// RandTest is a randomized black-box test for store implementations.
// It does the following:
//
// * Insert random values into the logs
// * Verify that reading back those values succeeds
// * Clear the logs and verify that they are actually empty.
//
// If any of the checks are unsuccessful, RandTest calls t.Fatal
//
// The function newStore should return a new (empty) store to test.
func RandTest(t *testing.T, newStore func() storage.Store) {
genValues := func(values []reflect.Value, r *rand.Rand) {
for i := range values {
values[i] = genStore(r)
}
}
cfg := &quick.Config{Values: genValues}
if err := quick.Check(checkFunc(newStore), cfg); err != nil {
t.Fatal(err)
}
}
func checkFunc(newStore func() storage.Store) func(m map[string][]*irc.Message) bool {
return func(m map[string][]*irc.Message) bool {
store := newStore()
fillStore(m, store)
if !checkFilled(m, store) {
return false
}
return checkClear(m, store)
}
}
func fillStore(m map[string][]*irc.Message, store storage.Store) {
for k, v := range m {
log, _ := store.GetChannel(k)
for _, msg := range v {
log.LogMessage(msg)
}
}
}
func checkClear(m map[string][]*irc.Message, store storage.Store) bool {
for k := range m {
log, _ := store.GetChannel(k)
log.Clear()
cursor, _ := log.Replay()
msg, err := cursor.Get()
if err != io.EOF {
fmt.Printf("Clear() did not clear the log for channel %q; "+
"Get() expected EOF but got (%q, %q)", k, msg, err)
cursor.Close()
return false
}
cursor.Close()
}
return true
}
func checkFilled(m map[string][]*irc.Message, store storage.Store) bool {
for k, v := range m {
log, err := store.GetChannel(k)
if err != nil {
panic(fmt.Sprintf("Getting channel %q: %q", k, err))
}
cursor, err := log.Replay()
if err != nil {
panic(fmt.Sprintf("Getting log for channel %q: %q", k, err))
}
for i, msg := range v {
loggedMsg, err := cursor.Get()
if err != nil {
fmt.Printf("Unexpected error getting log entry %d for channel %q: %q\n",
i, k, err)
cursor.Close()
return false
}
oldStr := loggedMsg.String()
newStr := msg.String()
if oldStr != newStr {
fmt.Printf(
"Mismatch at position %d in log for channel %q: "+
"expected %q but got %q.\n", i, k, newStr, oldStr)
cursor.Close()
return false
}
cursor.Next()
}
msg, err := cursor.Get()
if err == nil {
fmt.Printf("Unexpected message at position %d in log for channel "+
"%q: expected EOF but got %q.\n", len(v), k, msg.String())
cursor.Close()
return false
}
cursor.Close()
}
return true
}
func genStore(r *rand.Rand) reflect.Value {
numChannels := int(r.Uint32() % 50)
ret := make(map[string][]*irc.Message)
for i := 0; i < numChannels; i++ {
value, _ := quick.Value(reflect.TypeOf(""), r)
buf := []byte(value.String())
if len(buf) > 16 { // relatively arbitrary cap
buf = buf[:16]
}
ret[fmt.Sprintf("%x", buf)] = genChannel(r)
}
return reflect.ValueOf(ret)
}
func genChannel(r *rand.Rand) []*irc.Message {
numMessages := int(r.Float64() * 70)
ret := make([]*irc.Message, numMessages)
for i := range ret {
value := ret[i].Generate(r, 0)
ret[i] = (*irc.Message)(unsafe.Pointer(value.Pointer()))
}
return ret
}