Commit 39b3d8c7 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent 4afad355
...@@ -92,38 +92,19 @@ func routeEvent(event interface{}) (stream string) { ...@@ -92,38 +92,19 @@ func routeEvent(event interface{}) (stream string) {
// verify calls tracetest.Verify on f with first preparing tracing setup and events delivery. // verify calls tracetest.Verify on f with first preparing tracing setup and events delivery.
// It also verifies that tracetest detects errors as expected. // It also verifies that tracetest detects errors as expected.
func verify(t *testing.T, f func(t *tracetest.T), targvExtra ...string) { func verify(t *testing.T, f func(t *tracetest.T), targvExtra ...string) {
if os.Getenv("TRACETEST_EX_VERIFY_IN_SUBPROCESS") == "1" { // XXX t.Helper() ?
verifyInSubprocess(t, func (t *testing.T) {
tracetest.Verify(t, func(t *tracetest.T) { tracetest.Verify(t, func(t *tracetest.T) {
// setup tracing to deliver trace events to t. // setup tracing to deliver trace events to t.
pg := setupTracing(t) pg := setupTracing(t)
t.Cleanup(pg.Done) defer pg.Done()
// tell t to which stream an event should go. // tell t to which stream an event should go.
t.SetEventRouter(routeEvent) t.SetEventRouter(routeEvent)
// run test code // run test code
f(t) f(t)
}) })
return }, targvExtra...)
}
// spawn the test in subprocess and verify its output
outOK, ok := testExpect[t.Name()]
if !ok {
panic(fmt.Sprintf("testExpect[%q] not defined", t.Name()))
}
argv := []string{"-test.run="+t.Name()}
argv = append(argv, targvExtra...)
cmd := exec.Command(os.Args[0], argv...)
cmd.Env = append(os.Environ(), "TRACETEST_EX_VERIFY_IN_SUBPROCESS=1")
bout, err := cmd.CombinedOutput()
out := string(bout)
if err != nil {
t.Log(out)
t.Fatal(err) // XXX check first it is not "exit code"
}
// XXX verify out
_ = outOK
} }
...@@ -162,26 +143,21 @@ func Test2ThreadsOK(t *testing.T) { ...@@ -162,26 +143,21 @@ func Test2ThreadsOK(t *testing.T) {
} }
// XXX // TestDeadlock demonstrates deadlock scenario hit by the checker.
func TestDeadlock(t *testing.T) { func TestDeadlock(t *testing.T) {
verify(t, func(t *tracetest.T) { verify(t, func(t *tracetest.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
defer wg.Wait() defer wg.Wait()
wg.Add(2) wg.Add(1)
go func() { // thread1 go func() { // thread1
defer wg.Done() defer wg.Done()
hi("T1·A") hi("T1·A")
}() }()
go func() { // thread2 // the checker expects something on stream "t2", but there is
defer wg.Done()
hi("T2·B")
}()
// the checker expects something on stream "t3", but there is
// no event sent there -> deadlock. // no event sent there -> deadlock.
t.Expect("t3", eventHi("zzz")) t.Expect("t2", eventHi("zzz"))
}, "-tracetest.deadtime=0.5s") }, "-tracetest.deadtime=0.5s")
} }
...@@ -190,24 +166,67 @@ func TestDeadlock(t *testing.T) { ...@@ -190,24 +166,67 @@ func TestDeadlock(t *testing.T) {
// -------- // --------
// testExpect maps <test name> -> expected output // verifyInSubprocess runs f in subprocess and verifies that its output matches testOutput.
var testExpect = map[string]string{ func verifyInSubprocess(t *testing.T, f func(t *testing.T), targvExtra ...string) {
"Test2ThreadsOK": "", t.Helper() // XXX ok?
"TestDeadlock": ` if os.Getenv("TRACETEST_EX_VERIFY_IN_SUBPROCESS") == "1" {
f(t)
return
}
// spawn the test in subprocess and verify its output
expectOK, ok := testExpectMap[t.Name()]
if !ok {
panic(fmt.Sprintf("testExpectMap[%q] not defined", t.Name()))
}
argv := []string{"-test.run="+t.Name()}
argv = append(argv, targvExtra...)
cmd := exec.Command(os.Args[0], argv...)
cmd.Env = append(os.Environ(), "TRACETEST_EX_VERIFY_IN_SUBPROCESS=1")
bout, err := cmd.CombinedOutput()
out := string(bout)
e, ok := err.(*exec.ExitError)
if !ok {
// e.g. could not respawn at all
t.Log(out)
t.Fatal(err)
}
ecode := e.ExitCode()
bad := ""
badf := func(format string, argv ...interface{}) {
bad += fmt.Sprintf(format+"\n", argv...)
}
if ecode != expectOK.exitCode {
badf("exit code: %d ; expected: %d", ecode, expectOK.exitCode)
}
if out != expectOK.outputRe { // XXX match with re
badf("unexpected output:\n%s\nwant: %s\n", out, expectOK.outputRe)
}
if bad != "" {
t.Fatal(bad)
}
}
// testExpect describes what result to expect from a test.
type testExpect struct {
exitCode int
outputRe string
}
// testExpectMap maps <test name> -> testExpect.
var testExpectMap = map[string]testExpect{
"Test2ThreadsOK": {0, ""},
"TestDeadlock": {1, `
--- FAIL: TestDeadlock (.+s) --- FAIL: TestDeadlock (.+s)
example_test.go:179: t3: recv: deadlock waiting for *tracetest_test.eventHi example_test.go:159: t2: recv: deadlock waiting for *tracetest_test.eventHi
example_test.go:179: test shutdown: #streams: 3, #(pending events): 2 example_test.go:159: test shutdown: #streams: 2, #(pending events): 1
t1 <- tracetest_test.eventHi T1·A t1 <- tracetest_test.eventHi T1·A
t2 <- tracetest_test.eventHi T2·B # t2
# t3
tracetest.go:175: t2: send: canceled (test failed)
.*
tracetest.go:175: t1: send: canceled (test failed) tracetest.go:175: t1: send: canceled (test failed)
.* ...
`},
FAIL
exit status 1
`,
} }
...@@ -484,11 +484,14 @@ type Streams interface { ...@@ -484,11 +484,14 @@ type Streams interface {
// ------ vvv = ok ------ // ------ vvv = ok ------
// _testing_TB is alias for testing.TB that is non-public when embedded into a struct.
type _testing_TB = testing.TB
// T is similar to testing.T and is passed by Verify to tested function. // T is similar to testing.T and is passed by Verify to tested function.
// //
// See top-level package documentation for details. // See top-level package documentation for details.
type T struct { type T struct {
testing.TB _testing_TB
mu sync.Mutex mu sync.Mutex
streamTab map[/*stream*/string]Chan // set to nil on test shutdown streamTab map[/*stream*/string]Chan // set to nil on test shutdown
...@@ -686,7 +689,7 @@ func (t *T) fatalfInNonMain(format string, argv ...interface{}) { ...@@ -686,7 +689,7 @@ func (t *T) fatalfInNonMain(format string, argv ...interface{}) {
func (t *T) FailNow() { func (t *T) FailNow() {
t.Helper() t.Helper()
_ = t.closeStreamTab() _ = t.closeStreamTab()
t.TB.FailNow() t._testing_TB.FailNow()
} }
func (t *T) Fatal(argv ...interface{}) { func (t *T) Fatal(argv ...interface{}) {
...@@ -784,7 +787,7 @@ func (t *T) closeStreamTab() (nnak int) { ...@@ -784,7 +787,7 @@ func (t *T) closeStreamTab() (nnak int) {
// Verify verifies a test system. // Verify verifies a test system.
// XXX // XXX
func Verify(t testing.TB, f func(t *T)) { func Verify(t testing.TB, f func(t *T)) {
tT := &T{TB: t, streamTab: make(map[string]Chan)} tT := &T{_testing_TB: t, streamTab: make(map[string]Chan)}
// XXX // XXX
// verify in the end that no events are left unchecked / unconsumed, // verify in the end that no events are left unchecked / unconsumed,
......
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