summaryrefslogtreecommitdiff
path: root/internal/ior_mode_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-06 17:32:24 +0200
committerPaul Buetow <paul@buetow.org>2026-03-06 17:32:24 +0200
commit1561987330cb898f5ff64383a9c78e7e6559f118 (patch)
tree69a823e8f98dce572566c97e6879c11c9d591bda /internal/ior_mode_test.go
parent96225fb6159212a8851043a08d781aba721b4e78 (diff)
parent110a193e04b81abb8d8e159abd73f9f6ed1acd7e (diff)
Merge branch 'feat/bubbletea-v2-migration'
Diffstat (limited to 'internal/ior_mode_test.go')
-rw-r--r--internal/ior_mode_test.go361
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()
},