Source file src/internal/trace/event_test.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package trace
     6  
     7  import (
     8  	"fmt"
     9  	"internal/diff"
    10  	"reflect"
    11  	"slices"
    12  	"testing"
    13  	"time"
    14  )
    15  
    16  func TestMakeEvent(t *testing.T) {
    17  	checkTime := func(t *testing.T, ev Event, want Time) {
    18  		t.Helper()
    19  		if ev.Time() != want {
    20  			t.Errorf("expected time to be %d, got %d", want, ev.Time())
    21  		}
    22  	}
    23  	checkValid := func(t *testing.T, err error, valid bool) bool {
    24  		t.Helper()
    25  		if valid && err == nil {
    26  			return true
    27  		}
    28  		if valid && err != nil {
    29  			t.Errorf("expected no error, got %v", err)
    30  		} else if !valid && err == nil {
    31  			t.Errorf("expected error, got %v", err)
    32  		}
    33  		return false
    34  	}
    35  	type stackType string
    36  	const (
    37  		schedStack stackType = "sched stack"
    38  		stStack    stackType = "state transition stack"
    39  	)
    40  	checkStack := func(t *testing.T, got Stack, want Stack, which stackType) {
    41  		t.Helper()
    42  		diff := diff.Diff("want", []byte(want.String()), "got", []byte(got.String()))
    43  		if len(diff) > 0 {
    44  			t.Errorf("unexpected %s: %s", which, diff)
    45  		}
    46  	}
    47  	stk1 := MakeStack([]StackFrame{
    48  		{PC: 1, Func: "foo", File: "foo.go", Line: 10},
    49  		{PC: 2, Func: "bar", File: "bar.go", Line: 20},
    50  	})
    51  	stk2 := MakeStack([]StackFrame{
    52  		{PC: 1, Func: "foo", File: "foo.go", Line: 10},
    53  		{PC: 2, Func: "bar", File: "bar.go", Line: 20},
    54  	})
    55  
    56  	t.Run("Metric", func(t *testing.T) {
    57  		tests := []struct {
    58  			name   string
    59  			metric string
    60  			val    uint64
    61  			stack  Stack
    62  			valid  bool
    63  		}{
    64  			{name: "gomaxprocs", metric: "/sched/gomaxprocs:threads", valid: true, val: 1, stack: NoStack},
    65  			{name: "gomaxprocs with stack", metric: "/sched/gomaxprocs:threads", valid: true, val: 1, stack: stk1},
    66  			{name: "heap objects", metric: "/memory/classes/heap/objects:bytes", valid: true, val: 2, stack: NoStack},
    67  			{name: "heap goal", metric: "/gc/heap/goal:bytes", valid: true, val: 3, stack: NoStack},
    68  			{name: "invalid metric", metric: "/test", valid: false, val: 4, stack: NoStack},
    69  		}
    70  		for i, test := range tests {
    71  			t.Run(test.name, func(t *testing.T) {
    72  				ev, err := MakeEvent(EventConfig[Metric]{
    73  					Kind:    EventMetric,
    74  					Time:    Time(42 + i),
    75  					Details: Metric{Name: test.metric, Value: Uint64Value(test.val)},
    76  					Stack:   test.stack,
    77  				})
    78  				if !checkValid(t, err, test.valid) {
    79  					return
    80  				}
    81  				checkTime(t, ev, Time(42+i))
    82  				checkStack(t, ev.Stack(), test.stack, schedStack)
    83  				got := ev.Metric()
    84  				if got.Name != test.metric {
    85  					t.Errorf("expected name to be %q, got %q", test.metric, got.Name)
    86  				}
    87  				if got.Value.Uint64() != test.val {
    88  					t.Errorf("expected value to be %d, got %d", test.val, got.Value.Uint64())
    89  				}
    90  			})
    91  		}
    92  	})
    93  
    94  	t.Run("Label", func(t *testing.T) {
    95  		ev, err := MakeEvent(EventConfig[Label]{
    96  			Kind:    EventLabel,
    97  			Time:    42,
    98  			Details: Label{Label: "test", Resource: MakeResourceID(GoID(23))},
    99  		})
   100  		if !checkValid(t, err, true) {
   101  			return
   102  		}
   103  		label := ev.Label()
   104  		if label.Label != "test" {
   105  			t.Errorf("expected label to be test, got %q", label.Label)
   106  		}
   107  		if label.Resource.Kind != ResourceGoroutine {
   108  			t.Errorf("expected label resource to be goroutine, got %d", label.Resource.Kind)
   109  		}
   110  		if label.Resource.id != 23 {
   111  			t.Errorf("expected label resource to be 23, got %d", label.Resource.id)
   112  		}
   113  		checkTime(t, ev, 42)
   114  	})
   115  
   116  	t.Run("Range", func(t *testing.T) {
   117  		tests := []struct {
   118  			kind  EventKind
   119  			name  string
   120  			scope ResourceID
   121  			valid bool
   122  		}{
   123  			{kind: EventRangeBegin, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true},
   124  			{kind: EventRangeActive, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true},
   125  			{kind: EventRangeEnd, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true},
   126  			{kind: EventMetric, name: "GC concurrent mark phase", scope: ResourceID{}, valid: false},
   127  			{kind: EventRangeBegin, name: "GC concurrent mark phase - INVALID", scope: ResourceID{}, valid: false},
   128  
   129  			{kind: EventRangeBegin, name: "GC incremental sweep", scope: MakeResourceID(ProcID(1)), valid: true},
   130  			{kind: EventRangeActive, name: "GC incremental sweep", scope: MakeResourceID(ProcID(2)), valid: true},
   131  			{kind: EventRangeEnd, name: "GC incremental sweep", scope: MakeResourceID(ProcID(3)), valid: true},
   132  			{kind: EventMetric, name: "GC incremental sweep", scope: MakeResourceID(ProcID(4)), valid: false},
   133  			{kind: EventRangeBegin, name: "GC incremental sweep - INVALID", scope: MakeResourceID(ProcID(5)), valid: false},
   134  
   135  			{kind: EventRangeBegin, name: "GC mark assist", scope: MakeResourceID(GoID(1)), valid: true},
   136  			{kind: EventRangeActive, name: "GC mark assist", scope: MakeResourceID(GoID(2)), valid: true},
   137  			{kind: EventRangeEnd, name: "GC mark assist", scope: MakeResourceID(GoID(3)), valid: true},
   138  			{kind: EventMetric, name: "GC mark assist", scope: MakeResourceID(GoID(4)), valid: false},
   139  			{kind: EventRangeBegin, name: "GC mark assist - INVALID", scope: MakeResourceID(GoID(5)), valid: false},
   140  
   141  			{kind: EventRangeBegin, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(1)), valid: true},
   142  			{kind: EventRangeActive, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(2)), valid: false},
   143  			{kind: EventRangeEnd, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(3)), valid: true},
   144  			{kind: EventMetric, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(4)), valid: false},
   145  			{kind: EventRangeBegin, name: "stop-the-world (for a good reason) - INVALID", scope: MakeResourceID(GoID(5)), valid: false},
   146  		}
   147  
   148  		for i, test := range tests {
   149  			name := fmt.Sprintf("%s/%s/%s", test.kind, test.name, test.scope)
   150  			t.Run(name, func(t *testing.T) {
   151  				ev, err := MakeEvent(EventConfig[Range]{
   152  					Time:    Time(42 + i),
   153  					Kind:    test.kind,
   154  					Details: Range{Name: test.name, Scope: test.scope},
   155  				})
   156  				if !checkValid(t, err, test.valid) {
   157  					return
   158  				}
   159  				got := ev.Range()
   160  				if got.Name != test.name {
   161  					t.Errorf("expected name to be %q, got %q", test.name, got.Name)
   162  				}
   163  				if ev.Kind() != test.kind {
   164  					t.Errorf("expected kind to be %s, got %s", test.kind, ev.Kind())
   165  				}
   166  				if got.Scope.String() != test.scope.String() {
   167  					t.Errorf("expected scope to be %s, got %s", test.scope.String(), got.Scope.String())
   168  				}
   169  				checkTime(t, ev, Time(42+i))
   170  			})
   171  		}
   172  	})
   173  
   174  	t.Run("GoroutineTransition", func(t *testing.T) {
   175  		const anotherG = 999 // indicates hat sched g is different from transition g
   176  		tests := []struct {
   177  			name    string
   178  			g       GoID
   179  			stack   Stack
   180  			stG     GoID
   181  			from    GoState
   182  			to      GoState
   183  			reason  string
   184  			stStack Stack
   185  			valid   bool
   186  		}{
   187  			{
   188  				name:    "EvGoCreate",
   189  				g:       anotherG,
   190  				stack:   stk1,
   191  				stG:     1,
   192  				from:    GoNotExist,
   193  				to:      GoRunnable,
   194  				reason:  "",
   195  				stStack: stk2,
   196  				valid:   true,
   197  			},
   198  			{
   199  				name:    "EvGoCreateBlocked",
   200  				g:       anotherG,
   201  				stack:   stk1,
   202  				stG:     2,
   203  				from:    GoNotExist,
   204  				to:      GoWaiting,
   205  				reason:  "",
   206  				stStack: stk2,
   207  				valid:   true,
   208  			},
   209  			{
   210  				name:    "EvGoCreateSyscall",
   211  				g:       anotherG,
   212  				stack:   NoStack,
   213  				stG:     3,
   214  				from:    GoNotExist,
   215  				to:      GoSyscall,
   216  				reason:  "",
   217  				stStack: NoStack,
   218  				valid:   true,
   219  			},
   220  			{
   221  				name:    "EvGoStart",
   222  				g:       anotherG,
   223  				stack:   NoStack,
   224  				stG:     4,
   225  				from:    GoRunnable,
   226  				to:      GoRunning,
   227  				reason:  "",
   228  				stStack: NoStack,
   229  				valid:   true,
   230  			},
   231  			{
   232  				name:    "EvGoDestroy",
   233  				g:       5,
   234  				stack:   NoStack,
   235  				stG:     5,
   236  				from:    GoRunning,
   237  				to:      GoNotExist,
   238  				reason:  "",
   239  				stStack: NoStack,
   240  				valid:   true,
   241  			},
   242  			{
   243  				name:    "EvGoDestroySyscall",
   244  				g:       6,
   245  				stack:   NoStack,
   246  				stG:     6,
   247  				from:    GoSyscall,
   248  				to:      GoNotExist,
   249  				reason:  "",
   250  				stStack: NoStack,
   251  				valid:   true,
   252  			},
   253  			{
   254  				name:    "EvGoStop",
   255  				g:       7,
   256  				stack:   stk1,
   257  				stG:     7,
   258  				from:    GoRunning,
   259  				to:      GoRunnable,
   260  				reason:  "preempted",
   261  				stStack: stk1,
   262  				valid:   true,
   263  			},
   264  			{
   265  				name:    "EvGoBlock",
   266  				g:       8,
   267  				stack:   stk1,
   268  				stG:     8,
   269  				from:    GoRunning,
   270  				to:      GoWaiting,
   271  				reason:  "blocked",
   272  				stStack: stk1,
   273  				valid:   true,
   274  			},
   275  			{
   276  				name:    "EvGoUnblock",
   277  				g:       9,
   278  				stack:   stk1,
   279  				stG:     anotherG,
   280  				from:    GoWaiting,
   281  				to:      GoRunnable,
   282  				reason:  "",
   283  				stStack: NoStack,
   284  				valid:   true,
   285  			},
   286  			// N.b. EvGoUnblock, EvGoSwitch and EvGoSwitchDestroy cannot be
   287  			// distinguished from each other in Event form, so MakeEvent only
   288  			// produces EvGoUnblock events for Waiting -> Runnable transitions.
   289  			{
   290  				name:    "EvGoSyscallBegin",
   291  				g:       10,
   292  				stack:   stk1,
   293  				stG:     10,
   294  				from:    GoRunning,
   295  				to:      GoSyscall,
   296  				reason:  "",
   297  				stStack: stk1,
   298  				valid:   true,
   299  			},
   300  			{
   301  				name:    "EvGoSyscallEnd",
   302  				g:       11,
   303  				stack:   NoStack,
   304  				stG:     11,
   305  				from:    GoSyscall,
   306  				to:      GoRunning,
   307  				reason:  "",
   308  				stStack: NoStack,
   309  				valid:   true,
   310  			},
   311  			{
   312  				name:    "EvGoSyscallEndBlocked",
   313  				g:       12,
   314  				stack:   NoStack,
   315  				stG:     12,
   316  				from:    GoSyscall,
   317  				to:      GoRunnable,
   318  				reason:  "",
   319  				stStack: NoStack,
   320  				valid:   true,
   321  			},
   322  			// TODO(felixge): Use coverage testsing to check if we need all these GoStatus/GoStatusStack cases
   323  			{
   324  				name:    "GoStatus Undetermined->Waiting",
   325  				g:       anotherG,
   326  				stack:   NoStack,
   327  				stG:     13,
   328  				from:    GoUndetermined,
   329  				to:      GoWaiting,
   330  				reason:  "",
   331  				stStack: NoStack,
   332  				valid:   true,
   333  			},
   334  			{
   335  				name:    "GoStatus Undetermined->Running",
   336  				g:       anotherG,
   337  				stack:   NoStack,
   338  				stG:     14,
   339  				from:    GoUndetermined,
   340  				to:      GoRunning,
   341  				reason:  "",
   342  				stStack: NoStack,
   343  				valid:   true,
   344  			},
   345  			{
   346  				name:    "GoStatusStack Undetermined->Waiting",
   347  				g:       anotherG,
   348  				stack:   stk1,
   349  				stG:     15,
   350  				from:    GoUndetermined,
   351  				to:      GoWaiting,
   352  				reason:  "",
   353  				stStack: stk1,
   354  				valid:   true,
   355  			},
   356  			{
   357  				name:    "GoStatusStack Undetermined->Runnable",
   358  				g:       anotherG,
   359  				stack:   stk1,
   360  				stG:     16,
   361  				from:    GoUndetermined,
   362  				to:      GoRunnable,
   363  				reason:  "",
   364  				stStack: stk1,
   365  				valid:   true,
   366  			},
   367  			{
   368  				name:    "GoStatus Runnable->Runnable",
   369  				g:       anotherG,
   370  				stack:   NoStack,
   371  				stG:     17,
   372  				from:    GoRunnable,
   373  				to:      GoRunnable,
   374  				reason:  "",
   375  				stStack: NoStack,
   376  				valid:   true,
   377  			},
   378  			{
   379  				name:    "GoStatus Runnable->Running",
   380  				g:       anotherG,
   381  				stack:   NoStack,
   382  				stG:     18,
   383  				from:    GoRunnable,
   384  				to:      GoRunning,
   385  				reason:  "",
   386  				stStack: NoStack,
   387  				valid:   true,
   388  			},
   389  			{
   390  				name:    "invalid NotExits->NotExists",
   391  				g:       anotherG,
   392  				stack:   stk1,
   393  				stG:     18,
   394  				from:    GoNotExist,
   395  				to:      GoNotExist,
   396  				reason:  "",
   397  				stStack: NoStack,
   398  				valid:   false,
   399  			},
   400  			{
   401  				name:    "invalid Running->Undetermined",
   402  				g:       anotherG,
   403  				stack:   stk1,
   404  				stG:     19,
   405  				from:    GoRunning,
   406  				to:      GoUndetermined,
   407  				reason:  "",
   408  				stStack: NoStack,
   409  				valid:   false,
   410  			},
   411  		}
   412  
   413  		for i, test := range tests {
   414  			t.Run(test.name, func(t *testing.T) {
   415  				st := MakeGoStateTransition(test.stG, test.from, test.to)
   416  				st.Stack = test.stStack
   417  				st.Reason = test.reason
   418  				ev, err := MakeEvent(EventConfig[StateTransition]{
   419  					Kind:      EventStateTransition,
   420  					Time:      Time(42 + i),
   421  					Goroutine: test.g,
   422  					Stack:     test.stack,
   423  					Details:   st,
   424  				})
   425  				if !checkValid(t, err, test.valid) {
   426  					return
   427  				}
   428  				checkStack(t, ev.Stack(), test.stack, schedStack)
   429  				if ev.Goroutine() != test.g {
   430  					t.Errorf("expected goroutine to be %d, got %d", test.g, ev.Goroutine())
   431  				}
   432  				got := ev.StateTransition()
   433  				if got.Resource.Goroutine() != test.stG {
   434  					t.Errorf("expected resource to be %d, got %d", test.stG, got.Resource.Goroutine())
   435  				}
   436  				from, to := got.Goroutine()
   437  				if from != test.from {
   438  					t.Errorf("from got=%s want=%s", from, test.from)
   439  				}
   440  				if to != test.to {
   441  					t.Errorf("to got=%s want=%s", to, test.to)
   442  				}
   443  				if got.Reason != test.reason {
   444  					t.Errorf("expected reason to be %s, got %s", test.reason, got.Reason)
   445  				}
   446  				checkStack(t, got.Stack, test.stStack, stStack)
   447  				checkTime(t, ev, Time(42+i))
   448  			})
   449  		}
   450  	})
   451  
   452  	t.Run("ProcTransition", func(t *testing.T) {
   453  		tests := []struct {
   454  			name      string
   455  			proc      ProcID
   456  			schedProc ProcID
   457  			from      ProcState
   458  			to        ProcState
   459  			valid     bool
   460  		}{
   461  			{name: "ProcStart", proc: 1, schedProc: 99, from: ProcIdle, to: ProcRunning, valid: true},
   462  			{name: "ProcStop", proc: 2, schedProc: 2, from: ProcRunning, to: ProcIdle, valid: true},
   463  			{name: "ProcSteal", proc: 3, schedProc: 99, from: ProcRunning, to: ProcIdle, valid: true},
   464  			{name: "ProcSteal lost info", proc: 4, schedProc: 99, from: ProcIdle, to: ProcIdle, valid: true},
   465  			{name: "ProcStatus", proc: 5, schedProc: 99, from: ProcUndetermined, to: ProcRunning, valid: true},
   466  		}
   467  		for i, test := range tests {
   468  			t.Run(test.name, func(t *testing.T) {
   469  				st := MakeProcStateTransition(test.proc, test.from, test.to)
   470  				ev, err := MakeEvent(EventConfig[StateTransition]{
   471  					Kind:    EventStateTransition,
   472  					Time:    Time(42 + i),
   473  					Proc:    test.schedProc,
   474  					Details: st,
   475  				})
   476  				if !checkValid(t, err, test.valid) {
   477  					return
   478  				}
   479  				checkTime(t, ev, Time(42+i))
   480  				gotSt := ev.StateTransition()
   481  				from, to := gotSt.Proc()
   482  				if from != test.from {
   483  					t.Errorf("from got=%s want=%s", from, test.from)
   484  				}
   485  				if to != test.to {
   486  					t.Errorf("to got=%s want=%s", to, test.to)
   487  				}
   488  				if ev.Proc() != test.schedProc {
   489  					t.Errorf("expected proc to be %d, got %d", test.schedProc, ev.Proc())
   490  				}
   491  				if gotSt.Resource.Proc() != test.proc {
   492  					t.Errorf("expected resource to be %d, got %d", test.proc, gotSt.Resource.Proc())
   493  				}
   494  			})
   495  		}
   496  	})
   497  
   498  	t.Run("Sync", func(t *testing.T) {
   499  		tests := []struct {
   500  			name    string
   501  			kind    EventKind
   502  			n       int
   503  			clock   *ClockSnapshot
   504  			batches map[string][]ExperimentalBatch
   505  			valid   bool
   506  		}{
   507  			{
   508  				name:  "invalid kind",
   509  				n:     1,
   510  				valid: false,
   511  			},
   512  			{
   513  				name:    "N",
   514  				kind:    EventSync,
   515  				n:       1,
   516  				batches: map[string][]ExperimentalBatch{},
   517  				valid:   true,
   518  			},
   519  			{
   520  				name:    "N+ClockSnapshot",
   521  				kind:    EventSync,
   522  				n:       1,
   523  				batches: map[string][]ExperimentalBatch{},
   524  				clock: &ClockSnapshot{
   525  					Trace: 1,
   526  					Wall:  time.Unix(59, 123456789),
   527  					Mono:  2,
   528  				},
   529  				valid: true,
   530  			},
   531  			{
   532  				name: "N+Batches",
   533  				kind: EventSync,
   534  				n:    1,
   535  				batches: map[string][]ExperimentalBatch{
   536  					"AllocFree": {{Thread: 1, Data: []byte{1, 2, 3}}},
   537  				},
   538  				valid: true,
   539  			},
   540  			{
   541  				name: "unknown experiment",
   542  				kind: EventSync,
   543  				n:    1,
   544  				batches: map[string][]ExperimentalBatch{
   545  					"does-not-exist": {{Thread: 1, Data: []byte{1, 2, 3}}},
   546  				},
   547  				valid: false,
   548  			},
   549  		}
   550  		for i, test := range tests {
   551  			t.Run(test.name, func(t *testing.T) {
   552  				ev, err := MakeEvent(EventConfig[Sync]{
   553  					Kind:    test.kind,
   554  					Time:    Time(42 + i),
   555  					Details: Sync{N: test.n, ClockSnapshot: test.clock, ExperimentalBatches: test.batches},
   556  				})
   557  				if !checkValid(t, err, test.valid) {
   558  					return
   559  				}
   560  				got := ev.Sync()
   561  				checkTime(t, ev, Time(42+i))
   562  				if got.N != test.n {
   563  					t.Errorf("expected N to be %d, got %d", test.n, got.N)
   564  				}
   565  				if test.clock != nil && got.ClockSnapshot == nil {
   566  					t.Fatalf("expected ClockSnapshot to be non-nil")
   567  				} else if test.clock == nil && got.ClockSnapshot != nil {
   568  					t.Fatalf("expected ClockSnapshot to be nil")
   569  				} else if test.clock != nil && got.ClockSnapshot != nil {
   570  					if got.ClockSnapshot.Trace != test.clock.Trace {
   571  						t.Errorf("expected ClockSnapshot.Trace to be %d, got %d", test.clock.Trace, got.ClockSnapshot.Trace)
   572  					}
   573  					if !got.ClockSnapshot.Wall.Equal(test.clock.Wall) {
   574  						t.Errorf("expected ClockSnapshot.Wall to be %s, got %s", test.clock.Wall, got.ClockSnapshot.Wall)
   575  					}
   576  					if got.ClockSnapshot.Mono != test.clock.Mono {
   577  						t.Errorf("expected ClockSnapshot.Mono to be %d, got %d", test.clock.Mono, got.ClockSnapshot.Mono)
   578  					}
   579  				}
   580  				if !reflect.DeepEqual(got.ExperimentalBatches, test.batches) {
   581  					t.Errorf("expected ExperimentalBatches to be %#v, got %#v", test.batches, got.ExperimentalBatches)
   582  				}
   583  			})
   584  		}
   585  	})
   586  
   587  	t.Run("Task", func(t *testing.T) {
   588  		tests := []struct {
   589  			name   string
   590  			kind   EventKind
   591  			id     TaskID
   592  			parent TaskID
   593  			typ    string
   594  			valid  bool
   595  		}{
   596  			{name: "no task", kind: EventTaskBegin, id: NoTask, parent: 1, typ: "type-0", valid: false},
   597  			{name: "invalid kind", kind: EventMetric, id: 1, parent: 2, typ: "type-1", valid: false},
   598  			{name: "EvUserTaskBegin", kind: EventTaskBegin, id: 2, parent: 3, typ: "type-2", valid: true},
   599  			{name: "EvUserTaskEnd", kind: EventTaskEnd, id: 3, parent: 4, typ: "type-3", valid: true},
   600  			{name: "no parent", kind: EventTaskBegin, id: 4, parent: NoTask, typ: "type-4", valid: true},
   601  		}
   602  
   603  		for i, test := range tests {
   604  			t.Run(test.name, func(t *testing.T) {
   605  				ev, err := MakeEvent(EventConfig[Task]{
   606  					Kind:    test.kind,
   607  					Time:    Time(42 + i),
   608  					Details: Task{ID: test.id, Parent: test.parent, Type: test.typ},
   609  				})
   610  				if !checkValid(t, err, test.valid) {
   611  					return
   612  				}
   613  				checkTime(t, ev, Time(42+i))
   614  				got := ev.Task()
   615  				if got.ID != test.id {
   616  					t.Errorf("expected ID to be %d, got %d", test.id, got.ID)
   617  				}
   618  				if got.Parent != test.parent {
   619  					t.Errorf("expected Parent to be %d, got %d", test.parent, got.Parent)
   620  				}
   621  				if got.Type != test.typ {
   622  					t.Errorf("expected Type to be %s, got %s", test.typ, got.Type)
   623  				}
   624  			})
   625  		}
   626  	})
   627  
   628  	t.Run("Region", func(t *testing.T) {
   629  		tests := []struct {
   630  			name  string
   631  			kind  EventKind
   632  			task  TaskID
   633  			typ   string
   634  			valid bool
   635  		}{
   636  			{name: "invalid kind", kind: EventMetric, task: 1, typ: "type-1", valid: false},
   637  			{name: "EvUserRegionBegin", kind: EventRegionBegin, task: 2, typ: "type-2", valid: true},
   638  			{name: "EvUserRegionEnd", kind: EventRegionEnd, task: 3, typ: "type-3", valid: true},
   639  		}
   640  
   641  		for i, test := range tests {
   642  			t.Run(test.name, func(t *testing.T) {
   643  				ev, err := MakeEvent(EventConfig[Region]{
   644  					Kind:    test.kind,
   645  					Time:    Time(42 + i),
   646  					Details: Region{Task: test.task, Type: test.typ},
   647  				})
   648  				if !checkValid(t, err, test.valid) {
   649  					return
   650  				}
   651  				checkTime(t, ev, Time(42+i))
   652  				got := ev.Region()
   653  				if got.Task != test.task {
   654  					t.Errorf("expected Task to be %d, got %d", test.task, got.Task)
   655  				}
   656  				if got.Type != test.typ {
   657  					t.Errorf("expected Type to be %s, got %s", test.typ, got.Type)
   658  				}
   659  			})
   660  		}
   661  	})
   662  
   663  	t.Run("Log", func(t *testing.T) {
   664  		tests := []struct {
   665  			name     string
   666  			kind     EventKind
   667  			task     TaskID
   668  			category string
   669  			message  string
   670  			valid    bool
   671  		}{
   672  			{name: "invalid kind", kind: EventMetric, task: 1, category: "category-1", message: "message-1", valid: false},
   673  			{name: "basic", kind: EventLog, task: 2, category: "category-2", message: "message-2", valid: true},
   674  		}
   675  
   676  		for i, test := range tests {
   677  			t.Run(test.name, func(t *testing.T) {
   678  				ev, err := MakeEvent(EventConfig[Log]{
   679  					Kind:    test.kind,
   680  					Time:    Time(42 + i),
   681  					Details: Log{Task: test.task, Category: test.category, Message: test.message},
   682  				})
   683  				if !checkValid(t, err, test.valid) {
   684  					return
   685  				}
   686  				checkTime(t, ev, Time(42+i))
   687  				got := ev.Log()
   688  				if got.Task != test.task {
   689  					t.Errorf("expected Task to be %d, got %d", test.task, got.Task)
   690  				}
   691  				if got.Category != test.category {
   692  					t.Errorf("expected Category to be %s, got %s", test.category, got.Category)
   693  				}
   694  				if got.Message != test.message {
   695  					t.Errorf("expected Message to be %s, got %s", test.message, got.Message)
   696  				}
   697  			})
   698  		}
   699  
   700  	})
   701  
   702  	t.Run("StackSample", func(t *testing.T) {
   703  		tests := []struct {
   704  			name  string
   705  			kind  EventKind
   706  			stack Stack
   707  			valid bool
   708  		}{
   709  			{name: "invalid kind", kind: EventMetric, stack: stk1, valid: false},
   710  			{name: "basic", kind: EventStackSample, stack: stk1, valid: true},
   711  		}
   712  
   713  		for i, test := range tests {
   714  			t.Run(test.name, func(t *testing.T) {
   715  				ev, err := MakeEvent(EventConfig[StackSample]{
   716  					Kind:  test.kind,
   717  					Time:  Time(42 + i),
   718  					Stack: test.stack,
   719  					// N.b. Details defaults to StackSample{}, so we can
   720  					// omit it here.
   721  				})
   722  				if !checkValid(t, err, test.valid) {
   723  					return
   724  				}
   725  				checkTime(t, ev, Time(42+i))
   726  				got := ev.Stack()
   727  				checkStack(t, got, test.stack, schedStack)
   728  			})
   729  		}
   730  
   731  	})
   732  }
   733  
   734  func TestMakeStack(t *testing.T) {
   735  	frames := []StackFrame{
   736  		{PC: 1, Func: "foo", File: "foo.go", Line: 10},
   737  		{PC: 2, Func: "bar", File: "bar.go", Line: 20},
   738  	}
   739  	got := slices.Collect(MakeStack(frames).Frames())
   740  	if len(got) != len(frames) {
   741  		t.Errorf("got=%d want=%d", len(got), len(frames))
   742  	}
   743  	for i := range got {
   744  		if got[i] != frames[i] {
   745  			t.Errorf("got=%v want=%v", got[i], frames[i])
   746  		}
   747  	}
   748  }
   749  
   750  func TestPanicEvent(t *testing.T) {
   751  	// Use a sync event for this because it doesn't have any extra metadata.
   752  	ev := syncEvent(nil, 0, 0)
   753  
   754  	mustPanic(t, func() {
   755  		_ = ev.Range()
   756  	})
   757  	mustPanic(t, func() {
   758  		_ = ev.Metric()
   759  	})
   760  	mustPanic(t, func() {
   761  		_ = ev.Log()
   762  	})
   763  	mustPanic(t, func() {
   764  		_ = ev.Task()
   765  	})
   766  	mustPanic(t, func() {
   767  		_ = ev.Region()
   768  	})
   769  	mustPanic(t, func() {
   770  		_ = ev.Label()
   771  	})
   772  	mustPanic(t, func() {
   773  		_ = ev.RangeAttributes()
   774  	})
   775  }
   776  
   777  func mustPanic(t *testing.T, f func()) {
   778  	defer func() {
   779  		if r := recover(); r == nil {
   780  			t.Fatal("failed to panic")
   781  		}
   782  	}()
   783  	f()
   784  }
   785  

View as plain text