| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package trace |
| |
| import ( |
| "fmt" |
| "internal/diff" |
| "reflect" |
| "slices" |
| "testing" |
| "time" |
| ) |
| |
| func TestMakeEvent(t *testing.T) { |
| checkTime := func(t *testing.T, ev Event, want Time) { |
| t.Helper() |
| if ev.Time() != want { |
| t.Errorf("expected time to be %d, got %d", want, ev.Time()) |
| } |
| } |
| checkValid := func(t *testing.T, err error, valid bool) bool { |
| t.Helper() |
| if valid && err == nil { |
| return true |
| } |
| if valid && err != nil { |
| t.Errorf("expected no error, got %v", err) |
| } else if !valid && err == nil { |
| t.Errorf("expected error, got %v", err) |
| } |
| return false |
| } |
| type stackType string |
| const ( |
| schedStack stackType = "sched stack" |
| stStack stackType = "state transition stack" |
| ) |
| checkStack := func(t *testing.T, got Stack, want Stack, which stackType) { |
| t.Helper() |
| diff := diff.Diff("want", []byte(want.String()), "got", []byte(got.String())) |
| if len(diff) > 0 { |
| t.Errorf("unexpected %s: %s", which, diff) |
| } |
| } |
| stk1 := MakeStack([]StackFrame{ |
| {PC: 1, Func: "foo", File: "foo.go", Line: 10}, |
| {PC: 2, Func: "bar", File: "bar.go", Line: 20}, |
| }) |
| stk2 := MakeStack([]StackFrame{ |
| {PC: 1, Func: "foo", File: "foo.go", Line: 10}, |
| {PC: 2, Func: "bar", File: "bar.go", Line: 20}, |
| }) |
| |
| t.Run("Metric", func(t *testing.T) { |
| tests := []struct { |
| name string |
| metric string |
| val uint64 |
| stack Stack |
| valid bool |
| }{ |
| {name: "gomaxprocs", metric: "/sched/gomaxprocs:threads", valid: true, val: 1, stack: NoStack}, |
| {name: "gomaxprocs with stack", metric: "/sched/gomaxprocs:threads", valid: true, val: 1, stack: stk1}, |
| {name: "heap objects", metric: "/memory/classes/heap/objects:bytes", valid: true, val: 2, stack: NoStack}, |
| {name: "heap goal", metric: "/gc/heap/goal:bytes", valid: true, val: 3, stack: NoStack}, |
| {name: "invalid metric", metric: "/test", valid: false, val: 4, stack: NoStack}, |
| } |
| for i, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ev, err := MakeEvent(EventConfig[Metric]{ |
| Kind: EventMetric, |
| Time: Time(42 + i), |
| Details: Metric{Name: test.metric, Value: Uint64Value(test.val)}, |
| Stack: test.stack, |
| }) |
| if !checkValid(t, err, test.valid) { |
| return |
| } |
| checkTime(t, ev, Time(42+i)) |
| checkStack(t, ev.Stack(), test.stack, schedStack) |
| got := ev.Metric() |
| if got.Name != test.metric { |
| t.Errorf("expected name to be %q, got %q", test.metric, got.Name) |
| } |
| if got.Value.Uint64() != test.val { |
| t.Errorf("expected value to be %d, got %d", test.val, got.Value.Uint64()) |
| } |
| }) |
| } |
| }) |
| |
| t.Run("Label", func(t *testing.T) { |
| ev, err := MakeEvent(EventConfig[Label]{ |
| Kind: EventLabel, |
| Time: 42, |
| Details: Label{Label: "test", Resource: MakeResourceID(GoID(23))}, |
| }) |
| if !checkValid(t, err, true) { |
| return |
| } |
| label := ev.Label() |
| if label.Label != "test" { |
| t.Errorf("expected label to be test, got %q", label.Label) |
| } |
| if label.Resource.Kind != ResourceGoroutine { |
| t.Errorf("expected label resource to be goroutine, got %d", label.Resource.Kind) |
| } |
| if label.Resource.id != 23 { |
| t.Errorf("expected label resource to be 23, got %d", label.Resource.id) |
| } |
| checkTime(t, ev, 42) |
| }) |
| |
| t.Run("Range", func(t *testing.T) { |
| tests := []struct { |
| kind EventKind |
| name string |
| scope ResourceID |
| valid bool |
| }{ |
| {kind: EventRangeBegin, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true}, |
| {kind: EventRangeActive, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true}, |
| {kind: EventRangeEnd, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true}, |
| {kind: EventMetric, name: "GC concurrent mark phase", scope: ResourceID{}, valid: false}, |
| {kind: EventRangeBegin, name: "GC concurrent mark phase - INVALID", scope: ResourceID{}, valid: false}, |
| |
| {kind: EventRangeBegin, name: "GC incremental sweep", scope: MakeResourceID(ProcID(1)), valid: true}, |
| {kind: EventRangeActive, name: "GC incremental sweep", scope: MakeResourceID(ProcID(2)), valid: true}, |
| {kind: EventRangeEnd, name: "GC incremental sweep", scope: MakeResourceID(ProcID(3)), valid: true}, |
| {kind: EventMetric, name: "GC incremental sweep", scope: MakeResourceID(ProcID(4)), valid: false}, |
| {kind: EventRangeBegin, name: "GC incremental sweep - INVALID", scope: MakeResourceID(ProcID(5)), valid: false}, |
| |
| {kind: EventRangeBegin, name: "GC mark assist", scope: MakeResourceID(GoID(1)), valid: true}, |
| {kind: EventRangeActive, name: "GC mark assist", scope: MakeResourceID(GoID(2)), valid: true}, |
| {kind: EventRangeEnd, name: "GC mark assist", scope: MakeResourceID(GoID(3)), valid: true}, |
| {kind: EventMetric, name: "GC mark assist", scope: MakeResourceID(GoID(4)), valid: false}, |
| {kind: EventRangeBegin, name: "GC mark assist - INVALID", scope: MakeResourceID(GoID(5)), valid: false}, |
| |
| {kind: EventRangeBegin, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(1)), valid: true}, |
| {kind: EventRangeActive, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(2)), valid: false}, |
| {kind: EventRangeEnd, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(3)), valid: true}, |
| {kind: EventMetric, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(4)), valid: false}, |
| {kind: EventRangeBegin, name: "stop-the-world (for a good reason) - INVALID", scope: MakeResourceID(GoID(5)), valid: false}, |
| } |
| |
| for i, test := range tests { |
| name := fmt.Sprintf("%s/%s/%s", test.kind, test.name, test.scope) |
| t.Run(name, func(t *testing.T) { |
| ev, err := MakeEvent(EventConfig[Range]{ |
| Time: Time(42 + i), |
| Kind: test.kind, |
| Details: Range{Name: test.name, Scope: test.scope}, |
| }) |
| if !checkValid(t, err, test.valid) { |
| return |
| } |
| got := ev.Range() |
| if got.Name != test.name { |
| t.Errorf("expected name to be %q, got %q", test.name, got.Name) |
| } |
| if ev.Kind() != test.kind { |
| t.Errorf("expected kind to be %s, got %s", test.kind, ev.Kind()) |
| } |
| if got.Scope.String() != test.scope.String() { |
| t.Errorf("expected scope to be %s, got %s", test.scope.String(), got.Scope.String()) |
| } |
| checkTime(t, ev, Time(42+i)) |
| }) |
| } |
| }) |
| |
| t.Run("GoroutineTransition", func(t *testing.T) { |
| const anotherG = 999 // indicates hat sched g is different from transition g |
| tests := []struct { |
| name string |
| g GoID |
| stack Stack |
| stG GoID |
| from GoState |
| to GoState |
| reason string |
| stStack Stack |
| valid bool |
| }{ |
| { |
| name: "EvGoCreate", |
| g: anotherG, |
| stack: stk1, |
| stG: 1, |
| from: GoNotExist, |
| to: GoRunnable, |
| reason: "", |
| stStack: stk2, |
| valid: true, |
| }, |
| { |
| name: "EvGoCreateBlocked", |
| g: anotherG, |
| stack: stk1, |
| stG: 2, |
| from: GoNotExist, |
| to: GoWaiting, |
| reason: "", |
| stStack: stk2, |
| valid: true, |
| }, |
| { |
| name: "EvGoCreateSyscall", |
| g: anotherG, |
| stack: NoStack, |
| stG: 3, |
| from: GoNotExist, |
| to: GoSyscall, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| { |
| name: "EvGoStart", |
| g: anotherG, |
| stack: NoStack, |
| stG: 4, |
| from: GoRunnable, |
| to: GoRunning, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| { |
| name: "EvGoDestroy", |
| g: 5, |
| stack: NoStack, |
| stG: 5, |
| from: GoRunning, |
| to: GoNotExist, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| { |
| name: "EvGoDestroySyscall", |
| g: 6, |
| stack: NoStack, |
| stG: 6, |
| from: GoSyscall, |
| to: GoNotExist, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| { |
| name: "EvGoStop", |
| g: 7, |
| stack: stk1, |
| stG: 7, |
| from: GoRunning, |
| to: GoRunnable, |
| reason: "preempted", |
| stStack: stk1, |
| valid: true, |
| }, |
| { |
| name: "EvGoBlock", |
| g: 8, |
| stack: stk1, |
| stG: 8, |
| from: GoRunning, |
| to: GoWaiting, |
| reason: "blocked", |
| stStack: stk1, |
| valid: true, |
| }, |
| { |
| name: "EvGoUnblock", |
| g: 9, |
| stack: stk1, |
| stG: anotherG, |
| from: GoWaiting, |
| to: GoRunnable, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| // N.b. EvGoUnblock, EvGoSwitch and EvGoSwitchDestroy cannot be |
| // distinguished from each other in Event form, so MakeEvent only |
| // produces EvGoUnblock events for Waiting -> Runnable transitions. |
| { |
| name: "EvGoSyscallBegin", |
| g: 10, |
| stack: stk1, |
| stG: 10, |
| from: GoRunning, |
| to: GoSyscall, |
| reason: "", |
| stStack: stk1, |
| valid: true, |
| }, |
| { |
| name: "EvGoSyscallEnd", |
| g: 11, |
| stack: NoStack, |
| stG: 11, |
| from: GoSyscall, |
| to: GoRunning, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| { |
| name: "EvGoSyscallEndBlocked", |
| g: 12, |
| stack: NoStack, |
| stG: 12, |
| from: GoSyscall, |
| to: GoRunnable, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| // TODO(felixge): Use coverage testsing to check if we need all these GoStatus/GoStatusStack cases |
| { |
| name: "GoStatus Undetermined->Waiting", |
| g: anotherG, |
| stack: NoStack, |
| stG: 13, |
| from: GoUndetermined, |
| to: GoWaiting, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| { |
| name: "GoStatus Undetermined->Running", |
| g: anotherG, |
| stack: NoStack, |
| stG: 14, |
| from: GoUndetermined, |
| to: GoRunning, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| { |
| name: "GoStatusStack Undetermined->Waiting", |
| g: anotherG, |
| stack: stk1, |
| stG: 15, |
| from: GoUndetermined, |
| to: GoWaiting, |
| reason: "", |
| stStack: stk1, |
| valid: true, |
| }, |
| { |
| name: "GoStatusStack Undetermined->Runnable", |
| g: anotherG, |
| stack: stk1, |
| stG: 16, |
| from: GoUndetermined, |
| to: GoRunnable, |
| reason: "", |
| stStack: stk1, |
| valid: true, |
| }, |
| { |
| name: "GoStatus Runnable->Runnable", |
| g: anotherG, |
| stack: NoStack, |
| stG: 17, |
| from: GoRunnable, |
| to: GoRunnable, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| { |
| name: "GoStatus Runnable->Running", |
| g: anotherG, |
| stack: NoStack, |
| stG: 18, |
| from: GoRunnable, |
| to: GoRunning, |
| reason: "", |
| stStack: NoStack, |
| valid: true, |
| }, |
| { |
| name: "invalid NotExits->NotExists", |
| g: anotherG, |
| stack: stk1, |
| stG: 18, |
| from: GoNotExist, |
| to: GoNotExist, |
| reason: "", |
| stStack: NoStack, |
| valid: false, |
| }, |
| { |
| name: "invalid Running->Undetermined", |
| g: anotherG, |
| stack: stk1, |
| stG: 19, |
| from: GoRunning, |
| to: GoUndetermined, |
| reason: "", |
| stStack: NoStack, |
| valid: false, |
| }, |
| } |
| |
| for i, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| st := MakeGoStateTransition(test.stG, test.from, test.to) |
| st.Stack = test.stStack |
| st.Reason = test.reason |
| ev, err := MakeEvent(EventConfig[StateTransition]{ |
| Kind: EventStateTransition, |
| Time: Time(42 + i), |
| Goroutine: test.g, |
| Stack: test.stack, |
| Details: st, |
| }) |
| if !checkValid(t, err, test.valid) { |
| return |
| } |
| checkStack(t, ev.Stack(), test.stack, schedStack) |
| if ev.Goroutine() != test.g { |
| t.Errorf("expected goroutine to be %d, got %d", test.g, ev.Goroutine()) |
| } |
| got := ev.StateTransition() |
| if got.Resource.Goroutine() != test.stG { |
| t.Errorf("expected resource to be %d, got %d", test.stG, got.Resource.Goroutine()) |
| } |
| from, to := got.Goroutine() |
| if from != test.from { |
| t.Errorf("from got=%s want=%s", from, test.from) |
| } |
| if to != test.to { |
| t.Errorf("to got=%s want=%s", to, test.to) |
| } |
| if got.Reason != test.reason { |
| t.Errorf("expected reason to be %s, got %s", test.reason, got.Reason) |
| } |
| checkStack(t, got.Stack, test.stStack, stStack) |
| checkTime(t, ev, Time(42+i)) |
| }) |
| } |
| }) |
| |
| t.Run("ProcTransition", func(t *testing.T) { |
| tests := []struct { |
| name string |
| proc ProcID |
| schedProc ProcID |
| from ProcState |
| to ProcState |
| valid bool |
| }{ |
| {name: "ProcStart", proc: 1, schedProc: 99, from: ProcIdle, to: ProcRunning, valid: true}, |
| {name: "ProcStop", proc: 2, schedProc: 2, from: ProcRunning, to: ProcIdle, valid: true}, |
| {name: "ProcSteal", proc: 3, schedProc: 99, from: ProcRunning, to: ProcIdle, valid: true}, |
| {name: "ProcSteal lost info", proc: 4, schedProc: 99, from: ProcIdle, to: ProcIdle, valid: true}, |
| {name: "ProcStatus", proc: 5, schedProc: 99, from: ProcUndetermined, to: ProcRunning, valid: true}, |
| } |
| for i, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| st := MakeProcStateTransition(test.proc, test.from, test.to) |
| ev, err := MakeEvent(EventConfig[StateTransition]{ |
| Kind: EventStateTransition, |
| Time: Time(42 + i), |
| Proc: test.schedProc, |
| Details: st, |
| }) |
| if !checkValid(t, err, test.valid) { |
| return |
| } |
| checkTime(t, ev, Time(42+i)) |
| gotSt := ev.StateTransition() |
| from, to := gotSt.Proc() |
| if from != test.from { |
| t.Errorf("from got=%s want=%s", from, test.from) |
| } |
| if to != test.to { |
| t.Errorf("to got=%s want=%s", to, test.to) |
| } |
| if ev.Proc() != test.schedProc { |
| t.Errorf("expected proc to be %d, got %d", test.schedProc, ev.Proc()) |
| } |
| if gotSt.Resource.Proc() != test.proc { |
| t.Errorf("expected resource to be %d, got %d", test.proc, gotSt.Resource.Proc()) |
| } |
| }) |
| } |
| }) |
| |
| t.Run("Sync", func(t *testing.T) { |
| tests := []struct { |
| name string |
| kind EventKind |
| n int |
| clock *ClockSnapshot |
| batches map[string][]ExperimentalBatch |
| valid bool |
| }{ |
| { |
| name: "invalid kind", |
| n: 1, |
| valid: false, |
| }, |
| { |
| name: "N", |
| kind: EventSync, |
| n: 1, |
| batches: map[string][]ExperimentalBatch{}, |
| valid: true, |
| }, |
| { |
| name: "N+ClockSnapshot", |
| kind: EventSync, |
| n: 1, |
| batches: map[string][]ExperimentalBatch{}, |
| clock: &ClockSnapshot{ |
| Trace: 1, |
| Wall: time.Unix(59, 123456789), |
| Mono: 2, |
| }, |
| valid: true, |
| }, |
| { |
| name: "N+Batches", |
| kind: EventSync, |
| n: 1, |
| batches: map[string][]ExperimentalBatch{ |
| "AllocFree": {{Thread: 1, Data: []byte{1, 2, 3}}}, |
| }, |
| valid: true, |
| }, |
| { |
| name: "unknown experiment", |
| kind: EventSync, |
| n: 1, |
| batches: map[string][]ExperimentalBatch{ |
| "does-not-exist": {{Thread: 1, Data: []byte{1, 2, 3}}}, |
| }, |
| valid: false, |
| }, |
| } |
| for i, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ev, err := MakeEvent(EventConfig[Sync]{ |
| Kind: test.kind, |
| Time: Time(42 + i), |
| Details: Sync{N: test.n, ClockSnapshot: test.clock, ExperimentalBatches: test.batches}, |
| }) |
| if !checkValid(t, err, test.valid) { |
| return |
| } |
| got := ev.Sync() |
| checkTime(t, ev, Time(42+i)) |
| if got.N != test.n { |
| t.Errorf("expected N to be %d, got %d", test.n, got.N) |
| } |
| if test.clock != nil && got.ClockSnapshot == nil { |
| t.Fatalf("expected ClockSnapshot to be non-nil") |
| } else if test.clock == nil && got.ClockSnapshot != nil { |
| t.Fatalf("expected ClockSnapshot to be nil") |
| } else if test.clock != nil && got.ClockSnapshot != nil { |
| if got.ClockSnapshot.Trace != test.clock.Trace { |
| t.Errorf("expected ClockSnapshot.Trace to be %d, got %d", test.clock.Trace, got.ClockSnapshot.Trace) |
| } |
| if !got.ClockSnapshot.Wall.Equal(test.clock.Wall) { |
| t.Errorf("expected ClockSnapshot.Wall to be %s, got %s", test.clock.Wall, got.ClockSnapshot.Wall) |
| } |
| if got.ClockSnapshot.Mono != test.clock.Mono { |
| t.Errorf("expected ClockSnapshot.Mono to be %d, got %d", test.clock.Mono, got.ClockSnapshot.Mono) |
| } |
| } |
| if !reflect.DeepEqual(got.ExperimentalBatches, test.batches) { |
| t.Errorf("expected ExperimentalBatches to be %#v, got %#v", test.batches, got.ExperimentalBatches) |
| } |
| }) |
| } |
| }) |
| |
| t.Run("Task", func(t *testing.T) { |
| tests := []struct { |
| name string |
| kind EventKind |
| id TaskID |
| parent TaskID |
| typ string |
| valid bool |
| }{ |
| {name: "no task", kind: EventTaskBegin, id: NoTask, parent: 1, typ: "type-0", valid: false}, |
| {name: "invalid kind", kind: EventMetric, id: 1, parent: 2, typ: "type-1", valid: false}, |
| {name: "EvUserTaskBegin", kind: EventTaskBegin, id: 2, parent: 3, typ: "type-2", valid: true}, |
| {name: "EvUserTaskEnd", kind: EventTaskEnd, id: 3, parent: 4, typ: "type-3", valid: true}, |
| {name: "no parent", kind: EventTaskBegin, id: 4, parent: NoTask, typ: "type-4", valid: true}, |
| } |
| |
| for i, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ev, err := MakeEvent(EventConfig[Task]{ |
| Kind: test.kind, |
| Time: Time(42 + i), |
| Details: Task{ID: test.id, Parent: test.parent, Type: test.typ}, |
| }) |
| if !checkValid(t, err, test.valid) { |
| return |
| } |
| checkTime(t, ev, Time(42+i)) |
| got := ev.Task() |
| if got.ID != test.id { |
| t.Errorf("expected ID to be %d, got %d", test.id, got.ID) |
| } |
| if got.Parent != test.parent { |
| t.Errorf("expected Parent to be %d, got %d", test.parent, got.Parent) |
| } |
| if got.Type != test.typ { |
| t.Errorf("expected Type to be %s, got %s", test.typ, got.Type) |
| } |
| }) |
| } |
| }) |
| |
| t.Run("Region", func(t *testing.T) { |
| tests := []struct { |
| name string |
| kind EventKind |
| task TaskID |
| typ string |
| valid bool |
| }{ |
| {name: "invalid kind", kind: EventMetric, task: 1, typ: "type-1", valid: false}, |
| {name: "EvUserRegionBegin", kind: EventRegionBegin, task: 2, typ: "type-2", valid: true}, |
| {name: "EvUserRegionEnd", kind: EventRegionEnd, task: 3, typ: "type-3", valid: true}, |
| } |
| |
| for i, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ev, err := MakeEvent(EventConfig[Region]{ |
| Kind: test.kind, |
| Time: Time(42 + i), |
| Details: Region{Task: test.task, Type: test.typ}, |
| }) |
| if !checkValid(t, err, test.valid) { |
| return |
| } |
| checkTime(t, ev, Time(42+i)) |
| got := ev.Region() |
| if got.Task != test.task { |
| t.Errorf("expected Task to be %d, got %d", test.task, got.Task) |
| } |
| if got.Type != test.typ { |
| t.Errorf("expected Type to be %s, got %s", test.typ, got.Type) |
| } |
| }) |
| } |
| }) |
| |
| t.Run("Log", func(t *testing.T) { |
| tests := []struct { |
| name string |
| kind EventKind |
| task TaskID |
| category string |
| message string |
| valid bool |
| }{ |
| {name: "invalid kind", kind: EventMetric, task: 1, category: "category-1", message: "message-1", valid: false}, |
| {name: "basic", kind: EventLog, task: 2, category: "category-2", message: "message-2", valid: true}, |
| } |
| |
| for i, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ev, err := MakeEvent(EventConfig[Log]{ |
| Kind: test.kind, |
| Time: Time(42 + i), |
| Details: Log{Task: test.task, Category: test.category, Message: test.message}, |
| }) |
| if !checkValid(t, err, test.valid) { |
| return |
| } |
| checkTime(t, ev, Time(42+i)) |
| got := ev.Log() |
| if got.Task != test.task { |
| t.Errorf("expected Task to be %d, got %d", test.task, got.Task) |
| } |
| if got.Category != test.category { |
| t.Errorf("expected Category to be %s, got %s", test.category, got.Category) |
| } |
| if got.Message != test.message { |
| t.Errorf("expected Message to be %s, got %s", test.message, got.Message) |
| } |
| }) |
| } |
| |
| }) |
| |
| t.Run("StackSample", func(t *testing.T) { |
| tests := []struct { |
| name string |
| kind EventKind |
| stack Stack |
| valid bool |
| }{ |
| {name: "invalid kind", kind: EventMetric, stack: stk1, valid: false}, |
| {name: "basic", kind: EventStackSample, stack: stk1, valid: true}, |
| } |
| |
| for i, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ev, err := MakeEvent(EventConfig[StackSample]{ |
| Kind: test.kind, |
| Time: Time(42 + i), |
| Stack: test.stack, |
| // N.b. Details defaults to StackSample{}, so we can |
| // omit it here. |
| }) |
| if !checkValid(t, err, test.valid) { |
| return |
| } |
| checkTime(t, ev, Time(42+i)) |
| got := ev.Stack() |
| checkStack(t, got, test.stack, schedStack) |
| }) |
| } |
| |
| }) |
| } |
| |
| func TestMakeStack(t *testing.T) { |
| frames := []StackFrame{ |
| {PC: 1, Func: "foo", File: "foo.go", Line: 10}, |
| {PC: 2, Func: "bar", File: "bar.go", Line: 20}, |
| } |
| got := slices.Collect(MakeStack(frames).Frames()) |
| if len(got) != len(frames) { |
| t.Errorf("got=%d want=%d", len(got), len(frames)) |
| } |
| for i := range got { |
| if got[i] != frames[i] { |
| t.Errorf("got=%v want=%v", got[i], frames[i]) |
| } |
| } |
| } |
| |
| func TestPanicEvent(t *testing.T) { |
| // Use a sync event for this because it doesn't have any extra metadata. |
| ev := syncEvent(nil, 0, 0) |
| |
| mustPanic(t, func() { |
| _ = ev.Range() |
| }) |
| mustPanic(t, func() { |
| _ = ev.Metric() |
| }) |
| mustPanic(t, func() { |
| _ = ev.Log() |
| }) |
| mustPanic(t, func() { |
| _ = ev.Task() |
| }) |
| mustPanic(t, func() { |
| _ = ev.Region() |
| }) |
| mustPanic(t, func() { |
| _ = ev.Label() |
| }) |
| mustPanic(t, func() { |
| _ = ev.RangeAttributes() |
| }) |
| } |
| |
| func mustPanic(t *testing.T, f func()) { |
| defer func() { |
| if r := recover(); r == nil { |
| t.Fatal("failed to panic") |
| } |
| }() |
| f() |
| } |