Commit 6fe55763 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent d7528830
...@@ -156,6 +156,116 @@ type delayInjectState struct { ...@@ -156,6 +156,116 @@ type delayInjectState struct {
} }
// Run runs f under tracetest environment.
//
// It is similar to Verify but f is ran only once.
// Run does not check for race conditions.
func Run(t testing.TB, f func(t *T)) {
run(t, f, nil)
}
// run serves Run and Verify: it creates T that wraps t, and runs f under T.
func run(t testing.TB, f func(t *T), delayInjectTab map[string]*delayInjectState) *T {
tT := &T{
_testing_TB: t,
streamTab: make(map[string]*_chan),
delayInjectTab: delayInjectTab,
}
// verify in the end that no events are left unchecked / unconsumed,
// e.g. sent to RxEvent, but not received. Nak them if they are and fail.
//
// NOTE this complements T.Fatal and friends, because a test might
// think it completes successfully, but leaves unconsumed events behind it.
defer func() {
nnak := tT.closeStreamTab()
if nnak != 0 {
t.Fatal()
}
}()
f(tT)
return tT
}
// Verify verifies a test system.
//
// It runs f under T environment, catching race conditions, deadlocks and
// unexpected events. f is rerun several times and should not alter its
// behaviour from run to run.
func Verify(t *testing.T, f func(t *T)) {
// run f once. This produces initial trace of events.
tT0 := run(t, f, nil)
// now, if f succeeds, verify f with injected delays.
if tT0.Failed() {
return
}
trace0 := tT0.tracev
if len(trace0) < 2 {
return
}
streams0 := streamsOfTrace(trace0)
// sort trace0 by time just in case - events might come from multiple
// CPUs simultaneously, and so for close events they might be added to
// tracev not in time order.
sort.Slice(trace0, func(i, j int) bool {
return trace0[i].t.Before(trace0[j].t)
})
// find out max(δt) in between events
var δtMax time.Duration
for i := 1; i < len(trace0); i++ {
δt := trace0[i].t.Sub(trace0[i-1].t)
if δt > δtMax {
δtMax = δt
}
}
// retest f with 10·δtMax delay injected at i'th event
delayT := 10*δtMax // TODO make sure it < deadTime
delayTmin := 10*time.Millisecond // make sure delayT ≥ 10ms
if delayT < delayTmin {
delayT = delayTmin
}
for i := 0; i < len(trace0); i++ {
// stream and on-stream sequence number for i'th global event
stream := trace0[i].stream
istream := -1
for j := 0; j <= i; j++ {
if trace0[j].stream == stream {
istream++
}
}
t.Run(fmt.Sprintf("delay@%d(=%s:%d)", i, stream, istream), func(t *testing.T) {
tT := run(t, f, map[string]*delayInjectState{
stream: &delayInjectState{
delayAt: istream,
delayT: delayT,
},
})
// verify that streams are the same from run to run
if tT.Failed() {
return
}
streams := streamsOfTrace(tT.tracev)
if !reflect.DeepEqual(streams, streams0) {
tT.Fatalf("streams are not the same as in the first run:\n"+
"first: %s\nnow: %s\ndiff:\n%s\n\n",
streams0, streams, pretty.Compare(streams0, streams))
}
})
}
}
// SetEventRouter tells t to which stream an event should go. // SetEventRouter tells t to which stream an event should go.
// //
// It should be called not more than once. // It should be called not more than once.
...@@ -214,8 +324,6 @@ func (t *T) RxEvent(event interface{}) { ...@@ -214,8 +324,6 @@ func (t *T) RxEvent(event interface{}) {
// XXX Chan.Close // XXX Chan.Close
// XXX Chan.Recv + RecvInto ? // XXX Chan.Recv + RecvInto ?
// xget1 gets 1 event in place and checks it has expected type // xget1 gets 1 event in place and checks it has expected type
// //
// if checks do not pass - fatal testing error is raised // if checks do not pass - fatal testing error is raised
...@@ -389,111 +497,6 @@ func (t *T) closeStreamTab() (nnak int) { ...@@ -389,111 +497,6 @@ func (t *T) closeStreamTab() (nnak int) {
// Run runs f under tracetest environment.
//
// It is similar to Verify but f is ran only once.
// Run does not check for race conditions.
func Run(t testing.TB, f func(t *T)) {
run(t, f, nil)
}
// run serves Run and Verify: it creates T that wraps t, and runs f under T.
func run(t testing.TB, f func(t *T), delayInjectTab map[string]*delayInjectState) *T {
tT := &T{
_testing_TB: t,
streamTab: make(map[string]*_chan),
delayInjectTab: delayInjectTab,
}
// verify in the end that no events are left unchecked / unconsumed,
// e.g. sent to RxEvent, but not received. Nak them if they are and fail.
//
// NOTE this complements T.Fatal and friends, because a test might
// think it completes successfully, but leaves unconsumed events behind it.
defer func() {
nnak := tT.closeStreamTab()
if nnak != 0 {
t.Fatal()
}
}()
f(tT)
return tT
}
// Verify verifies a test system.
//
// It runs f under T environment, catching race conditions, deadlocks and
// unexpected events. f is rerun several times and should not alter its
// behaviour from run to run.
func Verify(t *testing.T, f func(t *T)) {
// run f once. This produces initial trace of events.
tT0 := run(t, f, nil)
// now, if f succeeds, verify f with injected delays.
if tT0.Failed() {
return
}
trace0 := tT0.tracev
if len(trace0) < 2 {
return
}
streams0 := streamsOfTrace(trace0)
// sort trace0 by time just in case - events might come from multiple
// CPUs simultaneously, and so for close events they might be added to
// tracev not in time order.
sort.Slice(trace0, func(i, j int) bool {
return trace0[i].t.Before(trace0[j].t)
})
// find out max(δt) in between events
var δtMax time.Duration
for i := 1; i < len(trace0); i++ {
δt := trace0[i].t.Sub(trace0[i-1].t)
if δt > δtMax {
δtMax = δt
}
}
// retest f with 10·δtMax delay injected at i'th event
delayT := 10*δtMax // TODO make sure it < deadTime
delayTmin := 10*time.Millisecond // make sure delayT ≥ 10ms
if delayT < delayTmin {
delayT = delayTmin
}
for i := 0; i < len(trace0); i++ {
// stream and on-stream sequence number for i'th global event
stream := trace0[i].stream
istream := -1
for j := 0; j <= i; j++ {
if trace0[j].stream == stream {
istream++
}
}
t.Run(fmt.Sprintf("delay@%d(=%s:%d)", i, stream, istream), func(t *testing.T) {
tT := run(t, f, map[string]*delayInjectState{
stream: &delayInjectState{
delayAt: istream,
delayT: delayT,
},
})
// verify that streams are the same from run to run
if tT.Failed() {
return
}
streams := streamsOfTrace(tT.tracev)
if !reflect.DeepEqual(streams, streams0) {
tT.Fatalf("streams are not the same as in the first run:\n"+
"first: %s\nnow: %s\ndiff:\n%s\n\n",
streams0, streams, pretty.Compare(streams0, streams))
}
})
}
}
// chanForStream returns channel corresponding to stream. // chanForStream returns channel corresponding to stream.
// must be called under mu. // must be called under mu.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment