lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/tracing/tracetest/example_test.go (about) 1 // Copyright (C) 2018-2021 Nexedi SA and Contributors. 2 // Kirill Smelkov <kirr@nexedi.com> 3 // 4 // This program is free software: you can Use, Study, Modify and Redistribute 5 // it under the terms of the GNU General Public License version 3, or (at your 6 // option) any later version, as published by the Free Software Foundation. 7 // 8 // You can also Link and Combine this program with other software covered by 9 // the terms of any of the Free Software licenses or any of the Open Source 10 // Initiative approved licenses and Convey the resulting work. Corresponding 11 // source of such a combination shall include the source code for all other 12 // software used. 13 // 14 // This program is distributed WITHOUT ANY WARRANTY; without even the implied 15 // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 // 17 // See COPYING file for full licensing terms. 18 // See https://www.nexedi.com/licensing for rationale and options. 19 20 // Package tracetest_test demonstrates how to use package tracetest. 21 // 22 // It also serves as set of testcases for tracetest itself. 23 package tracetest_test 24 25 //go:generate gotrace gen . 26 27 import ( 28 "fmt" 29 "os" 30 "os/exec" 31 "regexp" 32 "strings" 33 "sync" 34 "testing" 35 "time" 36 37 "lab.nexedi.com/kirr/go123/tracing" 38 "lab.nexedi.com/kirr/go123/tracing/tracetest" 39 ) 40 41 // hi and hello are functions that emit "(Hi|Hello), <who>" and can be traced. 42 43 //trace:event traceHi(who string) 44 //trace:event traceHello(who string) 45 func hi(who string) { 46 traceHi(who) 47 fmt.Println("Hi,", who) 48 } 49 func hello(who string) { 50 traceHello(who) 51 fmt.Println("Hello,", who) 52 } 53 54 // we use tracing to attach probes to hi and hello, and emit corresponding 55 // eventHi and eventHello to tracetest.T from there. 56 type eventHi string 57 type eventHello string 58 func setupTracing(t *tracetest.T) *tracing.ProbeGroup { 59 pg := &tracing.ProbeGroup{} 60 tracing.Lock() 61 traceHi_Attach(pg, func(who string) { 62 t.RxEvent(eventHi(who)) 63 }) 64 traceHello_Attach(pg, func(who string) { 65 t.RxEvent(eventHello(who)) 66 }) 67 tracing.Unlock() 68 // NOTE pg.Done must be invoked by caller when setup tracing is no longer needed. 69 return pg 70 } 71 72 // routeEvent tells to which stream an event should go. 73 // Here, in example, we use the convention that who comes as "<threadID>·..." 74 // and we route the event to stream that corresponds to threadID. 75 func routeEvent(event interface{}) (stream string) { 76 who := "" 77 switch ev := event.(type) { 78 default: 79 panic(fmt.Sprintf("unexpected event type %T", event)) 80 case eventHi: 81 who = string(ev) 82 case eventHello: 83 who = string(ev) 84 } 85 86 i := strings.Index(who, "·") 87 if i == -1 { 88 panic(fmt.Sprintf("who does not have threadID: %q", who)) 89 } 90 91 return strings.ToLower(who[:i]) 92 } 93 94 // verify calls tracetest.Verify on f with first preparing tracing setup and events delivery. 95 // It also verifies that tracetest detects errors as expected. 96 func verify(t *testing.T, f func(t *tracetest.T), targvExtra ...string) { 97 t.Helper() 98 verifyInSubprocess(t, func (t *testing.T) { 99 tracetest.Verify(t, func(t *tracetest.T) { 100 // setup tracing to deliver trace events to t. 101 pg := setupTracing(t) 102 defer pg.Done() 103 // tell t to which stream an event should go. 104 t.SetEventRouter(routeEvent) 105 106 // run test code 107 f(t) 108 }) 109 }, targvExtra...) 110 } 111 112 113 // Test2ThreadsOK demonstrates verifying 2 threads that execute independently. 114 // There is no concurrency problem here. 115 func Test2ThreadsOK(t *testing.T) { 116 verify(t, func(t *tracetest.T) { 117 var wg sync.WaitGroup 118 defer wg.Wait() 119 wg.Add(2) 120 121 go func() { // thread1 122 defer wg.Done() 123 hi("T1·A") 124 hello("T1·B") 125 }() 126 127 go func() { // thread2 128 defer wg.Done() 129 hello("T2·C") 130 hi("T2·D") 131 }() 132 133 // assert that events come as expected 134 // NOTE in checks t2 vs t1 order does not matter 135 t.Expect("t2", eventHello("T2·C")) 136 t.Expect("t2", eventHi("T2·D")) 137 t.Expect("t1", eventHi("T1·A")) 138 t.Expect("t1", eventHello("T1·B")) 139 }) 140 } 141 142 // TestDeadlock demonstrates deadlock detection. 143 // XXX also test for wrong decomposition XXX or is it also covered by this test as well? 144 func TestDeadlock(t *testing.T) { 145 verify(t, func(t *tracetest.T) { 146 var wg sync.WaitGroup 147 defer wg.Wait() 148 wg.Add(1) 149 150 go func() { // thread1 151 defer wg.Done() 152 hi("T1·A") 153 }() 154 155 // the checker expects something on stream "t2", but there is 156 // no event sent there -> deadlock. 157 t.Expect("t2", eventHi("zzz")) 158 }, "-tracetest.deadtime=0.5s") 159 } 160 161 // TestRace demonstrates detection of logical race. 162 func TestRace(t *testing.T) { 163 verify(t, func(t *tracetest.T) { 164 var wg sync.WaitGroup 165 defer wg.Wait() 166 wg.Add(2) 167 168 // 2 threads should synchronize with each other and do step A before B. 169 // They do not properly synchronize though, and just happen to 170 // usually emit events in expected order due to sleep in T2. 171 // Tracetest detects that. 172 go func() { // thread1 173 defer wg.Done() 174 hi("x·A") 175 }() 176 177 go func() { // thread2 178 defer wg.Done() 179 time.Sleep(100*time.Millisecond) 180 hi("x·B") 181 }() 182 183 t.Expect("x", eventHi("x·A")) 184 t.Expect("x", eventHi("x·B")) 185 }) 186 } 187 188 189 // other tests (mainly to verify tracetest itself) 190 191 // TestDeadlockExtra demonstrates deadlock detection when there is extra event 192 // not consumed by main checker. 193 func TestDeadlockExtra(t *testing.T) { 194 verify(t, func(t *tracetest.T) { 195 var wg sync.WaitGroup 196 defer wg.Wait() 197 wg.Add(1) 198 199 go func() { // thread 1 200 defer wg.Done() 201 hi("T1·A") 202 hi("T1·Extra") 203 }() 204 205 t.Expect("t1", eventHi("T1·A")) 206 }, "-tracetest.deadtime=0.5s") 207 } 208 209 // TestExpectType demonstrates Expect asserting with "unexpected event type". 210 func TestExpectType(t *testing.T) { 211 verify(t, func(t *tracetest.T) { 212 var wg sync.WaitGroup 213 defer wg.Wait() 214 wg.Add(1) 215 216 go func() { // thread 1 217 defer wg.Done() 218 hi("T1·A") 219 }() 220 221 t.Expect("t1", eventHello("T1·A")) 222 }) 223 } 224 225 // TestExpectValue demonstrates Expect asserting with "unexpected event value". 226 func TestExpectValue(t *testing.T) { 227 verify(t, func(t *tracetest.T) { 228 var wg sync.WaitGroup 229 defer wg.Wait() 230 wg.Add(1) 231 232 go func() { // thread 1 233 defer wg.Done() 234 hi("T1·A") 235 }() 236 237 t.Expect("t1", eventHi("T1·B")) 238 }) 239 } 240 241 242 243 // ---------------------------------------- 244 245 // verifyInSubprocess runs f in subprocess and verifies that its output matches testExpectMap[t.Name]. 246 func verifyInSubprocess(t *testing.T, f func(t *testing.T), targvExtra ...string) { 247 t.Helper() 248 if os.Getenv("TRACETEST_EX_VERIFY_IN_SUBPROCESS") == "1" { 249 f(t) 250 return 251 } 252 253 // spawn the test in subprocess and verify its output 254 expectOK, ok := testExpectMap[t.Name()] 255 if !ok { 256 panic(fmt.Sprintf("testExpectMap[%q] not defined", t.Name())) 257 } 258 outputOK := regexp.QuoteMeta(expectOK.output) 259 // empty line -> kind of "<BLANKLINE>" 260 for { 261 __ := strings.ReplaceAll(outputOK, "\n\n", "\n\\s*\n") 262 if __ == outputOK { 263 break 264 } 265 outputOK = __ 266 } 267 outputOK = strings.ReplaceAll(outputOK, "<TIME>", ".+s") 268 outputOK = strings.ReplaceAll(outputOK, "<LINE>", "[0-9]+") 269 outputRe := regexp.MustCompile(outputOK) 270 argv := []string{"-test.run="+t.Name()} 271 argv = append(argv, targvExtra...) 272 cmd := exec.Command(os.Args[0], argv...) 273 cmd.Env = append(os.Environ(), "TRACETEST_EX_VERIFY_IN_SUBPROCESS=1") 274 bout, err := cmd.CombinedOutput() // NOTE `go test` itself combines everything to stdout only 275 out := string(bout) 276 ecode := 0 277 if testing.Verbose() { 278 t.Logf("stdout:\n%s\n", out) 279 } 280 if err != nil { 281 e, ok := err.(*exec.ExitError) 282 if !ok { 283 // e.g. could not respawn at all 284 t.Fatal(err) 285 } 286 ecode = e.ExitCode() 287 } 288 289 bad := "" 290 badf := func(format string, argv ...interface{}) { 291 bad += fmt.Sprintf(format+"\n", argv...) 292 } 293 294 if ecode != expectOK.exitCode { 295 badf("exit code: %d ; expected: %d", ecode, expectOK.exitCode) 296 } 297 298 if !outputRe.MatchString(out) { 299 badf("unexpected output:\n%s\nwant: ~\n%s\n", out, expectOK.output) 300 } 301 302 if bad != "" { 303 t.Fatal(bad) 304 } 305 } 306 307 // testExpect describes what result to expect from a test. 308 type testExpect struct { 309 exitCode int 310 output string 311 } 312 // testExpectMap maps <test name> -> testExpect. 313 var testExpectMap = map[string]testExpect{ 314 "Test2ThreadsOK": {0, ""}, 315 316 "TestDeadlock": {1, 317 `--- FAIL: TestDeadlock (<TIME>) 318 example_test.go:157: t2: recv: deadlock waiting for *tracetest_test.eventHi 319 example_test.go:157: test shutdown: #streams: 2, #(pending events): 1 320 t1 <- tracetest_test.eventHi T1·A 321 # t2 322 323 tracetest.go:<LINE>: chan.go:<LINE>: t1: send: deadlock 324 `}, 325 326 "TestRace": {1, 327 ` --- FAIL: TestRace/delay@0(=x:0) (<TIME>) 328 example_test.go:183: x: expect: tracetest_test.eventHi: 329 want: x·A 330 have: x·B 331 diff: 332 -"x·A" 333 +"x·B" 334 `}, 335 336 "TestDeadlockExtra": {1, 337 `Hi, T1·A 338 --- FAIL: TestDeadlockExtra (<TIME>) 339 tracetest.go:<LINE>: test shutdown: #streams: 1, #(pending events): 1 340 t1 <- tracetest_test.eventHi T1·Extra 341 342 tracetest.go:<LINE>: chan.go:<LINE>: t1: send: deadlock 343 `}, 344 345 "TestExpectType": {1, 346 `--- FAIL: TestExpectType (<TIME>) 347 example_test.go:221: t1: expect: tracetest_test.eventHello: got tracetest_test.eventHi T1·A 348 example_test.go:221: test shutdown: #streams: 1, #(pending events): 0 349 # t1 350 351 tracetest.go:<LINE>: chan.go:<LINE>: t1: send: unexpected event type 352 `}, 353 354 "TestExpectValue": {1, 355 `--- FAIL: TestExpectValue (<TIME>) 356 example_test.go:237: t1: expect: tracetest_test.eventHi: 357 want: T1·B 358 have: T1·A 359 diff: 360 -"T1·B" 361 +"T1·A" 362 363 example_test.go:237: test shutdown: #streams: 1, #(pending events): 0 364 # t1 365 366 tracetest.go:<LINE>: chan.go:<LINE>: t1: send: unexpected event data 367 `}, 368 }