diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-06 17:32:24 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-06 17:32:24 +0200 |
| commit | 1561987330cb898f5ff64383a9c78e7e6559f118 (patch) | |
| tree | 69a823e8f98dce572566c97e6879c11c9d591bda /internal/ior_mode_test.go | |
| parent | 96225fb6159212a8851043a08d781aba721b4e78 (diff) | |
| parent | 110a193e04b81abb8d8e159abd73f9f6ed1acd7e (diff) | |
Merge branch 'feat/bubbletea-v2-migration'
Diffstat (limited to 'internal/ior_mode_test.go')
| -rw-r--r-- | internal/ior_mode_test.go | 361 |
1 files changed, 308 insertions, 53 deletions
diff --git a/internal/ior_mode_test.go b/internal/ior_mode_test.go index bbca555..48b2c36 100644 --- a/internal/ior_mode_test.go +++ b/internal/ior_mode_test.go @@ -1,7 +1,9 @@ package internal import ( + "bytes" "context" + "encoding/json" "errors" "testing" "time" @@ -11,7 +13,7 @@ import ( ) func TestShouldRunTraceMode(t *testing.T) { - base := flags.Flags{} + base := flags.Config{} if shouldRunTraceMode(base) { t.Fatalf("expected default mode to use TUI") @@ -23,27 +25,27 @@ func TestShouldRunTraceMode(t *testing.T) { t.Fatalf("expected plain mode to use trace mode") } - withFlamegraph := base - withFlamegraph.FlamegraphEnable = true - if !shouldRunTraceMode(withFlamegraph) { - t.Fatalf("expected flamegraph mode to use trace mode") - } - withPprof := base withPprof.PprofEnable = true - if !shouldRunTraceMode(withPprof) { - t.Fatalf("expected pprof mode to use trace mode") + if shouldRunTraceMode(withPprof) { + t.Fatalf("expected pprof flag alone to keep TUI mode") + } + + withTestFlames := base + withTestFlames.TestFlames = true + if shouldRunTraceMode(withTestFlames) { + t.Fatalf("expected --testflames to stay in TUI mode") } - withLive := base - withLive.LiveFlamegraph = true - if !shouldRunTraceMode(withLive) { - t.Fatalf("expected live mode to use trace mode") + withTestLiveFlames := base + withTestLiveFlames.TestLiveFlames = true + if shouldRunTraceMode(withTestLiveFlames) { + t.Fatalf("expected --testliveflames to stay in TUI mode") } } func TestShouldAutoStopByDuration(t *testing.T) { - base := flags.Flags{} + base := flags.Config{} if shouldAutoStopByDuration(base) { t.Fatalf("expected default TUI mode not to auto-stop by duration") } @@ -54,45 +56,46 @@ func TestShouldAutoStopByDuration(t *testing.T) { t.Fatalf("expected plain mode to auto-stop by duration") } - withFlamegraph := base - withFlamegraph.FlamegraphEnable = true - if !shouldAutoStopByDuration(withFlamegraph) { - t.Fatalf("expected flamegraph mode to auto-stop by duration") - } - withPprof := base withPprof.PprofEnable = true - if !shouldAutoStopByDuration(withPprof) { - t.Fatalf("expected pprof mode to auto-stop by duration") + if shouldAutoStopByDuration(withPprof) { + t.Fatalf("expected pprof flag alone not to auto-stop by duration") } - withLive := base - withLive.LiveFlamegraph = true - if !shouldAutoStopByDuration(withLive) { - t.Fatalf("expected live mode to auto-stop by duration") - } } func TestDispatchRunUsesTraceModeWhenRequested(t *testing.T) { origRunTrace := runTraceFn origRunTUI := runTUIFn + origRunTUITestFlames := runTUITestFlamesFn + origRunTUITestLiveFlames := runTUITestLiveFlamesFn defer func() { runTraceFn = origRunTrace runTUIFn = origRunTUI + runTUITestFlamesFn = origRunTUITestFlames + runTUITestLiveFlamesFn = origRunTUITestLiveFlames }() traceCalled := false tuiCalled := false - runTraceFn = func() error { + runTraceFn = func(flags.Config) error { traceCalled = true return nil } - runTUIFn = func(tui.TraceStarter) error { + runTUIFn = func(flags.Config, tui.TraceStarter) error { tuiCalled = true return nil } + runTUITestFlamesFn = func(flags.Config, tui.TraceStarter) error { + t.Fatalf("runTUITestFlamesFn should not be called in trace mode") + return nil + } + runTUITestLiveFlamesFn = func(flags.Config, tui.TraceStarter) error { + t.Fatalf("runTUITestLiveFlamesFn should not be called in trace mode") + return nil + } - cfg := flags.Flags{PlainMode: true} + cfg := flags.Config{PlainMode: true} if err := dispatchRun(cfg); err != nil { t.Fatalf("dispatchRun returned error: %v", err) } @@ -104,16 +107,63 @@ func TestDispatchRunUsesTraceModeWhenRequested(t *testing.T) { } } +func TestDispatchRunUsesTUIWhenOnlyPprofEnabled(t *testing.T) { + origRunTrace := runTraceFn + origRunTUI := runTUIFn + origRunTUITestFlames := runTUITestFlamesFn + origRunTUITestLiveFlames := runTUITestLiveFlamesFn + defer func() { + runTraceFn = origRunTrace + runTUIFn = origRunTUI + runTUITestFlamesFn = origRunTUITestFlames + runTUITestLiveFlamesFn = origRunTUITestLiveFlames + }() + + traceCalled := false + tuiCalled := false + runTraceFn = func(flags.Config) error { + traceCalled = true + return nil + } + runTUIFn = func(flags.Config, tui.TraceStarter) error { + tuiCalled = true + return nil + } + runTUITestFlamesFn = func(flags.Config, tui.TraceStarter) error { + t.Fatalf("runTUITestFlamesFn should not be called for regular TUI mode") + return nil + } + runTUITestLiveFlamesFn = func(flags.Config, tui.TraceStarter) error { + t.Fatalf("runTUITestLiveFlamesFn should not be called for regular TUI mode") + return nil + } + + cfg := flags.Config{PprofEnable: true} + if err := dispatchRun(cfg); err != nil { + t.Fatalf("dispatchRun returned error: %v", err) + } + if traceCalled { + t.Fatalf("did not expect runTraceFn when only -pprof is enabled") + } + if !tuiCalled { + t.Fatalf("expected runTUIFn to be called") + } +} + func TestDispatchRunUsesTUIStarterWhenNotPlain(t *testing.T) { origRunTraceWithContext := runTraceWithContextFn origRunTUI := runTUIFn + origRunTUITestFlames := runTUITestFlamesFn + origRunTUITestLiveFlames := runTUITestLiveFlamesFn defer func() { runTraceWithContextFn = origRunTraceWithContext runTUIFn = origRunTUI + runTUITestFlamesFn = origRunTUITestFlames + runTUITestLiveFlamesFn = origRunTUITestLiveFlames }() traceDone := make(chan struct{}, 1) - runTraceWithContextFn = func(_ context.Context, started chan<- struct{}, configure func(*eventLoop)) error { + runTraceWithContextFn = func(_ context.Context, _ flags.Config, started chan<- struct{}, configure func(*eventLoop)) error { if configure != nil { configure(&eventLoop{}) } @@ -123,7 +173,7 @@ func TestDispatchRunUsesTUIStarterWhenNotPlain(t *testing.T) { } tuiCalled := false - runTUIFn = func(starter tui.TraceStarter) error { + runTUIFn = func(_ flags.Config, starter tui.TraceStarter) error { tuiCalled = true if starter == nil { t.Fatalf("expected non-nil starter") @@ -133,8 +183,16 @@ func TestDispatchRunUsesTUIStarterWhenNotPlain(t *testing.T) { } return nil } + runTUITestFlamesFn = func(flags.Config, tui.TraceStarter) error { + t.Fatalf("runTUITestFlamesFn should not be called for normal starter path") + return nil + } + runTUITestLiveFlamesFn = func(flags.Config, tui.TraceStarter) error { + t.Fatalf("runTUITestLiveFlamesFn should not be called for normal starter path") + return nil + } - cfg := flags.Flags{} + cfg := flags.Config{} if err := dispatchRun(cfg); err != nil { t.Fatalf("dispatchRun returned error: %v", err) } @@ -149,61 +207,209 @@ func TestDispatchRunUsesTUIStarterWhenNotPlain(t *testing.T) { } } -func TestDispatchRunRejectsLiveAndFlamegraph(t *testing.T) { +func TestDispatchRunUsesTestFlamesModeWhenRequested(t *testing.T) { + origRunTrace := runTraceFn + origRunTUI := runTUIFn + origRunTUITestFlames := runTUITestFlamesFn + origRunTUITestLiveFlames := runTUITestLiveFlamesFn + defer func() { + runTraceFn = origRunTrace + runTUIFn = origRunTUI + runTUITestFlamesFn = origRunTUITestFlames + runTUITestLiveFlamesFn = origRunTUITestLiveFlames + }() + + traceCalled := false + regularTUICalled := false + testFlamesCalled := false + runTraceFn = func(flags.Config) error { + traceCalled = true + return nil + } + runTUIFn = func(flags.Config, tui.TraceStarter) error { + regularTUICalled = true + return nil + } + runTUITestFlamesFn = func(_ flags.Config, starter tui.TraceStarter) error { + testFlamesCalled = true + if starter == nil { + t.Fatalf("expected non-nil starter for test flames mode") + } + return starter(context.Background()) + } + runTUITestLiveFlamesFn = func(flags.Config, tui.TraceStarter) error { + t.Fatalf("runTUITestLiveFlamesFn should not be called for --testflames") + return nil + } + + cfg := flags.Config{TestFlames: true} + if err := dispatchRun(cfg); err != nil { + t.Fatalf("dispatchRun returned error: %v", err) + } + if traceCalled { + t.Fatalf("did not expect runTraceFn for test flames mode") + } + if regularTUICalled { + t.Fatalf("did not expect runTUIFn for test flames mode") + } + if !testFlamesCalled { + t.Fatalf("expected runTUITestFlamesFn to be called") + } +} + +func TestDispatchRunUsesTestLiveFlamesModeWhenRequested(t *testing.T) { origRunTrace := runTraceFn origRunTUI := runTUIFn + origRunTUITestFlames := runTUITestFlamesFn + origRunTUITestLiveFlames := runTUITestLiveFlamesFn defer func() { runTraceFn = origRunTrace runTUIFn = origRunTUI + runTUITestFlamesFn = origRunTUITestFlames + runTUITestLiveFlamesFn = origRunTUITestLiveFlames }() - runTraceFn = func() error { - t.Fatalf("runTraceFn should not be called for invalid flag combos") + traceCalled := false + regularTUICalled := false + testLiveFlamesCalled := false + runTraceFn = func(flags.Config) error { + traceCalled = true + return nil + } + runTUIFn = func(flags.Config, tui.TraceStarter) error { + regularTUICalled = true return nil } - runTUIFn = func(tui.TraceStarter) error { - t.Fatalf("runTUIFn should not be called for invalid flag combos") + runTUITestFlamesFn = func(flags.Config, tui.TraceStarter) error { + t.Fatalf("runTUITestFlamesFn should not be called for --testliveflames") return nil } + runTUITestLiveFlamesFn = func(_ flags.Config, starter tui.TraceStarter) error { + testLiveFlamesCalled = true + if starter == nil { + t.Fatalf("expected non-nil starter for test live flames mode") + } + return starter(context.Background()) + } + + cfg := flags.Config{TestLiveFlames: true} + if err := dispatchRun(cfg); err != nil { + t.Fatalf("dispatchRun returned error: %v", err) + } + if traceCalled { + t.Fatalf("did not expect runTraceFn for test live flames mode") + } + if regularTUICalled { + t.Fatalf("did not expect runTUIFn for test live flames mode") + } + if !testLiveFlamesCalled { + t.Fatalf("expected runTUITestLiveFlamesFn to be called") + } +} - cfg := flags.Flags{LiveFlamegraph: true, FlamegraphEnable: true} - err := dispatchRun(cfg) +func TestValidateRunConfigRejectsTestFlamesWithTraceFlags(t *testing.T) { + cfg := flags.Config{TestFlames: true, PlainMode: true} + err := validateRunConfig(cfg) if err == nil { - t.Fatalf("expected error for -live with -flamegraph") + t.Fatalf("expected error for --testflames with trace-mode flags") } - if err.Error() != "-live and -flamegraph are mutually exclusive" { + if err.Error() != "--testflames cannot be combined with -plain" { t.Fatalf("unexpected error: %v", err) } } -func TestValidateRunConfigRejectsIorWatchWithoutIor(t *testing.T) { - cfg := flags.Flags{IorWatchInterval: time.Second} +func TestValidateRunConfigRejectsTestLiveFlamesWithTraceFlags(t *testing.T) { + cfg := flags.Config{TestLiveFlames: true, PlainMode: true} err := validateRunConfig(cfg) if err == nil { - t.Fatalf("expected error for -iorWatchInterval without -ior") + t.Fatalf("expected error for --testliveflames with trace-mode flags") } - if err.Error() != "-iorWatchInterval requires -ior" { + if err.Error() != "--testliveflames cannot be combined with -plain" { t.Fatalf("unexpected error: %v", err) } } -func TestValidateRunConfigRejectsNegativeIorWatchInterval(t *testing.T) { - cfg := flags.Flags{IorWatchInterval: -time.Second} +func TestValidateRunConfigRejectsBothTestModes(t *testing.T) { + cfg := flags.Config{TestFlames: true, TestLiveFlames: true} err := validateRunConfig(cfg) if err == nil { - t.Fatalf("expected error for negative -iorWatchInterval") + t.Fatalf("expected error when both test flame modes are enabled") } - if err.Error() != "-iorWatchInterval must be >= 0" { + if err.Error() != "--testflames and --testliveflames are mutually exclusive" { t.Fatalf("unexpected error: %v", err) } } +func TestBuildTestFlamesRuntimeSeedsLiveTrie(t *testing.T) { + cfg := flags.NewFlags() + _, streamBuf, liveTrie := buildTestFlamesRuntime(cfg) + if streamBuf == nil { + t.Fatalf("expected stream buffer in test flames runtime") + } + if liveTrie == nil { + t.Fatalf("expected live trie in test flames runtime") + } + if liveTrie.Version() == 0 { + t.Fatalf("expected seeded live trie version to be non-zero") + } + + payload, _ := liveTrie.SnapshotJSON() + var snap map[string]any + if err := json.Unmarshal(payload, &snap); err != nil { + t.Fatalf("decode snapshot: %v", err) + } + total, ok := snap["t"].(float64) + if !ok || total <= 0 { + t.Fatalf("expected seeded snapshot total > 0, got %v", snap["t"]) + } +} + +func TestBuildTestLiveFlamesRuntimeContinuouslyUpdatesLiveTrie(t *testing.T) { + cfg := flags.NewFlags() + cfg.LiveInterval = 15 * time.Millisecond + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, streamBuf, liveTrie := buildTestLiveFlamesRuntime(ctx, cfg) + if streamBuf == nil { + t.Fatalf("expected stream buffer in test live flames runtime") + } + if liveTrie == nil { + t.Fatalf("expected live trie in test live flames runtime") + } + + initialVersion := liveTrie.Version() + if initialVersion == 0 { + t.Fatalf("expected seeded live trie version to be non-zero") + } + initialSnapshot, _ := liveTrie.SnapshotJSON() + + sawUpdate := false + deadline := time.Now().Add(300 * time.Millisecond) + for time.Now().Before(deadline) { + if liveTrie.Version() <= initialVersion { + time.Sleep(10 * time.Millisecond) + continue + } + currentSnapshot, _ := liveTrie.SnapshotJSON() + if !bytes.Equal(initialSnapshot, currentSnapshot) { + sawUpdate = true + break + } + time.Sleep(10 * time.Millisecond) + } + if !sawUpdate { + t.Fatalf("expected test live flames snapshot shape to change over time (version > %d)", initialVersion) + } +} + func TestRunTraceWithContextRequiresRoot(t *testing.T) { origGetEUID := getEUID defer func() { getEUID = origGetEUID }() getEUID = func() int { return 1000 } - err := runTraceWithContext(context.Background(), nil, nil) + err := runTraceWithContext(context.Background(), flags.NewFlags(), nil, nil) if !errors.Is(err, errRootPrivilegesRequired) { t.Fatalf("expected root-required error, got %v", err) } @@ -211,7 +417,10 @@ func TestRunTraceWithContextRequiresRoot(t *testing.T) { func TestTuiTraceStarterFromRunTracePropagatesError(t *testing.T) { starter := tuiTraceStarterFromRunTrace( - func(context.Context, chan<- struct{}, func(*eventLoop)) error { return errors.New("startup failed") }, + flags.NewFlags(), + func(context.Context, flags.Config, chan<- struct{}, func(*eventLoop)) error { + return errors.New("startup failed") + }, ) err := starter(context.Background()) @@ -220,9 +429,55 @@ func TestTuiTraceStarterFromRunTracePropagatesError(t *testing.T) { } } +func TestTuiTraceStarterFromRunTraceUsesContextFilters(t *testing.T) { + base := flags.NewFlags() + base.PidFilter = 11 + base.TidFilter = 12 + + var gotCfg flags.Config + starter := tuiTraceStarterFromRunTrace( + base, + func(_ context.Context, cfg flags.Config, started chan<- struct{}, _ func(*eventLoop)) error { + gotCfg = cfg + close(started) + return nil + }, + ) + + ctx := tui.ContextWithTraceFilters(context.Background(), 2222, 3333) + if err := starter(ctx); err != nil { + t.Fatalf("starter returned error: %v", err) + } + if gotCfg.PidFilter != 2222 { + t.Fatalf("expected pid filter from context, got %d", gotCfg.PidFilter) + } + if gotCfg.TidFilter != 3333 { + t.Fatalf("expected tid filter from context, got %d", gotCfg.TidFilter) + } +} + +func TestProfilingFilesForMode(t *testing.T) { + cpu, mem, execTrace, duration := profilingFilesForMode(false) + if cpu != "ior.cpuprofile" || mem != "ior.memprofile" { + t.Fatalf("unexpected trace-mode profiling file names: cpu=%q mem=%q", cpu, mem) + } + if execTrace != "" || duration != 0 { + t.Fatalf("expected trace-mode execution tracing to be disabled, got trace=%q duration=%s", execTrace, duration) + } + + cpu, mem, execTrace, duration = profilingFilesForMode(true) + if cpu != "ior-tui-cpu.prof" || mem != "ior-tui-mem.prof" || execTrace != "ior-tui-trace.out" { + t.Fatalf("unexpected TUI profiling file names: cpu=%q mem=%q trace=%q", cpu, mem, execTrace) + } + if duration != 10*time.Second { + t.Fatalf("expected 10s TUI execution trace duration, got %s", duration) + } +} + func TestTuiTraceStarterFromRunTraceRespectsCancel(t *testing.T) { starter := tuiTraceStarterFromRunTrace( - func(ctx context.Context, _ chan<- struct{}, _ func(*eventLoop)) error { + flags.NewFlags(), + func(ctx context.Context, _ flags.Config, _ chan<- struct{}, _ func(*eventLoop)) error { <-ctx.Done() return ctx.Err() }, |
