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  }