|
@@ -14,27 +14,120 @@
|
|
|
|
|
|
|
|
package testutil
|
|
package testutil
|
|
|
|
|
|
|
|
-import "sync"
|
|
|
|
|
|
|
+import (
|
|
|
|
|
+ "errors"
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+ "sync"
|
|
|
|
|
+ "time"
|
|
|
|
|
+)
|
|
|
|
|
|
|
|
type Action struct {
|
|
type Action struct {
|
|
|
Name string
|
|
Name string
|
|
|
Params []interface{}
|
|
Params []interface{}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-type Recorder struct {
|
|
|
|
|
|
|
+type Recorder interface {
|
|
|
|
|
+ // Record publishes an Action (e.g., function call) which will
|
|
|
|
|
+ // be reflected by Wait() or Chan()
|
|
|
|
|
+ Record(a Action)
|
|
|
|
|
+ // Wait waits until at least n Actions are availble or returns with error
|
|
|
|
|
+ Wait(n int) ([]Action, error)
|
|
|
|
|
+ // Action returns immediately available Actions
|
|
|
|
|
+ Action() []Action
|
|
|
|
|
+ // Chan returns the channel for actions published by Record
|
|
|
|
|
+ Chan() <-chan Action
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// RecorderBuffered appends all Actions to a slice
|
|
|
|
|
+type RecorderBuffered struct {
|
|
|
sync.Mutex
|
|
sync.Mutex
|
|
|
actions []Action
|
|
actions []Action
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (r *Recorder) Record(a Action) {
|
|
|
|
|
|
|
+func (r *RecorderBuffered) Record(a Action) {
|
|
|
r.Lock()
|
|
r.Lock()
|
|
|
r.actions = append(r.actions, a)
|
|
r.actions = append(r.actions, a)
|
|
|
r.Unlock()
|
|
r.Unlock()
|
|
|
}
|
|
}
|
|
|
-func (r *Recorder) Action() []Action {
|
|
|
|
|
|
|
+func (r *RecorderBuffered) Action() []Action {
|
|
|
r.Lock()
|
|
r.Lock()
|
|
|
cpy := make([]Action, len(r.actions))
|
|
cpy := make([]Action, len(r.actions))
|
|
|
copy(cpy, r.actions)
|
|
copy(cpy, r.actions)
|
|
|
r.Unlock()
|
|
r.Unlock()
|
|
|
return cpy
|
|
return cpy
|
|
|
}
|
|
}
|
|
|
|
|
+func (r *RecorderBuffered) Wait(n int) (acts []Action, err error) {
|
|
|
|
|
+ // legacy racey behavior
|
|
|
|
|
+ WaitSchedule()
|
|
|
|
|
+ acts = r.Action()
|
|
|
|
|
+ if len(acts) < n {
|
|
|
|
|
+ err = newLenErr(n, len(r.actions))
|
|
|
|
|
+ }
|
|
|
|
|
+ return acts, err
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (r *RecorderBuffered) Chan() <-chan Action {
|
|
|
|
|
+ ch := make(chan Action)
|
|
|
|
|
+ go func() {
|
|
|
|
|
+ acts := r.Action()
|
|
|
|
|
+ for i := range acts {
|
|
|
|
|
+ ch <- acts[i]
|
|
|
|
|
+ }
|
|
|
|
|
+ close(ch)
|
|
|
|
|
+ }()
|
|
|
|
|
+ return ch
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// RecorderStream writes all Actions to an unbuffered channel
|
|
|
|
|
+type recorderStream struct {
|
|
|
|
|
+ ch chan Action
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func NewRecorderStream() Recorder {
|
|
|
|
|
+ return &recorderStream{ch: make(chan Action)}
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (r *recorderStream) Record(a Action) {
|
|
|
|
|
+ r.ch <- a
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (r *recorderStream) Action() (acts []Action) {
|
|
|
|
|
+ for {
|
|
|
|
|
+ select {
|
|
|
|
|
+ case act := <-r.ch:
|
|
|
|
|
+ acts = append(acts, act)
|
|
|
|
|
+ default:
|
|
|
|
|
+ return acts
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return acts
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (r *recorderStream) Chan() <-chan Action {
|
|
|
|
|
+ return r.ch
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (r *recorderStream) Wait(n int) ([]Action, error) {
|
|
|
|
|
+ acts := make([]Action, n)
|
|
|
|
|
+ timeoutC := time.After(5 * time.Second)
|
|
|
|
|
+ for i := 0; i < n; i++ {
|
|
|
|
|
+ select {
|
|
|
|
|
+ case acts[i] = <-r.ch:
|
|
|
|
|
+ case <-timeoutC:
|
|
|
|
|
+ acts = acts[:i]
|
|
|
|
|
+ return acts, newLenErr(n, i)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // extra wait to catch any Action spew
|
|
|
|
|
+ select {
|
|
|
|
|
+ case act := <-r.ch:
|
|
|
|
|
+ acts = append(acts, act)
|
|
|
|
|
+ case <-time.After(10 * time.Millisecond):
|
|
|
|
|
+ }
|
|
|
|
|
+ return acts, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func newLenErr(expected int, actual int) error {
|
|
|
|
|
+ s := fmt.Sprintf("len(actions) = %d, expected >= %d", actual, expected)
|
|
|
|
|
+ return errors.New(s)
|
|
|
|
|
+}
|