Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion internal/animate/ease.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,30 @@ func Clamp01(t float64) float64 {
}

func Spring(current, target float64, velocity *float64, dt float64) float64 {
if math.IsNaN(target) || math.IsInf(target, 0) {
target = 0
}
if math.IsNaN(current) || math.IsInf(current, 0) {
current = target
}
if velocity == nil {
return target
}
if math.IsNaN(*velocity) || math.IsInf(*velocity, 0) {
*velocity = 0
}
if dt <= 0 || math.IsNaN(dt) || math.IsInf(dt, 0) {
return current
}
force := stiffness * (target - current)
*velocity += force * dt
*velocity *= math.Exp(-damping * dt)
if *velocity < 0.0001 && *velocity > -0.0001 {
*velocity = 0
}
return current + *velocity*dt
res := current + *velocity*dt
if math.IsNaN(res) || math.IsInf(res, 0) {
return target
Comment thread
programmersd21 marked this conversation as resolved.
}
return res
}
28 changes: 28 additions & 0 deletions internal/animate/ease_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,31 @@ func TestSpringStableAtUITickInterval(t *testing.T) {
t.Errorf("Spring oscillated negative at dt=0.13 (min %f) — value would render as 0 B/s", minVal)
}
}

func TestSpringNaNAndInfGuards(t *testing.T) {
var vel float64
// Target is NaN
val := Spring(100, math.NaN(), &vel, 0.13)
if math.IsNaN(val) || math.IsInf(val, 0) {
t.Errorf("Spring with NaN target returned %f", val)
}

// Current is NaN
val = Spring(math.NaN(), 100, &vel, 0.13)
if math.IsNaN(val) || math.IsInf(val, 0) {
t.Errorf("Spring with NaN current returned %f", val)
}

// Velocity is NaN
vel = math.NaN()
val = Spring(100, 100, &vel, 0.13)
if math.IsNaN(val) || math.IsInf(val, 0) || math.IsNaN(vel) {
t.Errorf("Spring with NaN velocity returned val=%f, vel=%f", val, vel)
}
Comment on lines +85 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Strengthen assertion on velocity reset when starting from NaN

For the NaN velocity case, the test should assert that vel is reset to 0, not just that it’s non-NaN. Since the implementation now explicitly sets non-finite velocity to zero, add an assertion like if vel != 0 { t.Errorf("expected velocity reset to 0, got %f", vel) } to verify the exact expected behavior.

Suggested change
// Velocity is NaN
vel = math.NaN()
val = Spring(100, 100, &vel, 0.13)
if math.IsNaN(val) || math.IsInf(val, 0) || math.IsNaN(vel) {
t.Errorf("Spring with NaN velocity returned val=%f, vel=%f", val, vel)
}
// Velocity is NaN
vel = math.NaN()
val = Spring(100, 100, &vel, 0.13)
if math.IsNaN(val) || math.IsInf(val, 0) {
t.Errorf("Spring with NaN velocity returned non-finite val=%f", val)
}
if vel != 0 {
t.Errorf("Spring with NaN velocity expected velocity reset to 0, got %f", vel)
}


// dt is NaN
val = Spring(100, 100, &vel, math.NaN())
if math.IsNaN(val) || math.IsInf(val, 0) {
t.Errorf("Spring with NaN dt returned %f", val)
}
}
7 changes: 7 additions & 0 deletions internal/history/persist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ func TestSaveLoad(t *testing.T) {
}

func TestLoadMissing(t *testing.T) {
dir := t.TempDir()
orig := statsPath
statsPath = func() (string, error) {
return filepath.Join(dir, "nonexistent_stats.json"), nil
}
defer func() { statsPath = orig }()

tracker := NewTracker()
err := tracker.Load()
if err == nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/ui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

case pingMsg:
m.pingLatency = time.Duration(msg)
return m, nil
return m, m.pingTick()

case sampleMsg:
if msg.Err != nil {
Expand Down Expand Up @@ -502,7 +502,7 @@ func FormatBps(bps float64, unit UnitMode) string {
}

func FormatBpsExt(bps float64, unit UnitMode, bits bool) string {
if bps < 0 {
if bps < 0 || math.IsNaN(bps) || math.IsInf(bps, 0) {
bps = 0
}
if bits {
Expand Down
13 changes: 9 additions & 4 deletions internal/ui/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,15 @@ func TestFormatBpsExt(t *testing.T) {
}

func TestFormatBpsExt_EdgeCases(t *testing.T) {
// Test NaN and Inf - should not crash
_ = FormatBpsExt(math.NaN(), UnitAuto, false)
_ = FormatBpsExt(math.Inf(1), UnitAuto, false)
_ = FormatBpsExt(math.Inf(-1), UnitAuto, false)
if got := FormatBpsExt(math.NaN(), UnitAuto, false); got != "0 B/s" {
t.Errorf("FormatBpsExt(NaN) = %q; want '0 B/s'", got)
}
if got := FormatBpsExt(math.Inf(1), UnitAuto, false); got != "0 B/s" {
t.Errorf("FormatBpsExt(+Inf) = %q; want '0 B/s'", got)
}
if got := FormatBpsExt(math.Inf(-1), UnitAuto, false); got != "0 B/s" {
t.Errorf("FormatBpsExt(-Inf) = %q; want '0 B/s'", got)
}
}

func TestFormatBpsFixedWidth(t *testing.T) {
Expand Down
12 changes: 12 additions & 0 deletions internal/ui/views.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ func max(a, b int) int {
}

func maxf(a, b float64) float64 {
if math.IsNaN(a) {
if math.IsNaN(b) {
return 0
}
return b
}
if math.IsNaN(b) {
return a
}
if a > b {
return a
}
Expand Down Expand Up @@ -96,6 +105,9 @@ func formatInterval(d time.Duration) string {
}

func formatBytes(b float64) string {
if b < 0 || math.IsNaN(b) || math.IsInf(b, 0) {
b = 0
}
const (
KB = 1024.0
MB = 1024 * KB
Expand Down
Loading