mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 11:31:46 +00:00
test: add tests for utility functions and heap implementations (#380)
Add unit tests for utility functions in cmd/status/view_test.go: - formatRate: MB/s rate formatting with adaptive precision - shorten: string truncation with ellipsis - humanBytesShort: byte formatting with binary units Add unit tests for heap implementations in cmd/analyze/heap_test.go: - entryHeap: min-heap for dirEntry (basic ops, empty, single element) - largeFileHeap: min-heap for fileEntry (basic ops, top N pattern)
This commit is contained in:
153
cmd/analyze/heap_test.go
Normal file
153
cmd/analyze/heap_test.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEntryHeap(t *testing.T) {
|
||||
t.Run("basic heap operations", func(t *testing.T) {
|
||||
h := &entryHeap{}
|
||||
heap.Init(h)
|
||||
|
||||
// Push entries with varying sizes.
|
||||
heap.Push(h, dirEntry{Name: "medium", Size: 500})
|
||||
heap.Push(h, dirEntry{Name: "small", Size: 100})
|
||||
heap.Push(h, dirEntry{Name: "large", Size: 1000})
|
||||
|
||||
if h.Len() != 3 {
|
||||
t.Errorf("Len() = %d, want 3", h.Len())
|
||||
}
|
||||
|
||||
// Min-heap: smallest should come out first.
|
||||
first := heap.Pop(h).(dirEntry)
|
||||
if first.Name != "small" || first.Size != 100 {
|
||||
t.Errorf("first Pop() = %v, want {small, 100}", first)
|
||||
}
|
||||
|
||||
second := heap.Pop(h).(dirEntry)
|
||||
if second.Name != "medium" || second.Size != 500 {
|
||||
t.Errorf("second Pop() = %v, want {medium, 500}", second)
|
||||
}
|
||||
|
||||
third := heap.Pop(h).(dirEntry)
|
||||
if third.Name != "large" || third.Size != 1000 {
|
||||
t.Errorf("third Pop() = %v, want {large, 1000}", third)
|
||||
}
|
||||
|
||||
if h.Len() != 0 {
|
||||
t.Errorf("Len() after all pops = %d, want 0", h.Len())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty heap", func(t *testing.T) {
|
||||
h := &entryHeap{}
|
||||
heap.Init(h)
|
||||
|
||||
if h.Len() != 0 {
|
||||
t.Errorf("empty heap Len() = %d, want 0", h.Len())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("single element", func(t *testing.T) {
|
||||
h := &entryHeap{}
|
||||
heap.Init(h)
|
||||
|
||||
heap.Push(h, dirEntry{Name: "only", Size: 42})
|
||||
popped := heap.Pop(h).(dirEntry)
|
||||
|
||||
if popped.Name != "only" || popped.Size != 42 {
|
||||
t.Errorf("Pop() = %v, want {only, 42}", popped)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("equal sizes maintain stability", func(t *testing.T) {
|
||||
h := &entryHeap{}
|
||||
heap.Init(h)
|
||||
|
||||
heap.Push(h, dirEntry{Name: "a", Size: 100})
|
||||
heap.Push(h, dirEntry{Name: "b", Size: 100})
|
||||
heap.Push(h, dirEntry{Name: "c", Size: 100})
|
||||
|
||||
// All have same size, heap property still holds.
|
||||
for i := 0; i < 3; i++ {
|
||||
popped := heap.Pop(h).(dirEntry)
|
||||
if popped.Size != 100 {
|
||||
t.Errorf("Pop() size = %d, want 100", popped.Size)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLargeFileHeap(t *testing.T) {
|
||||
t.Run("basic heap operations", func(t *testing.T) {
|
||||
h := &largeFileHeap{}
|
||||
heap.Init(h)
|
||||
|
||||
// Push entries with varying sizes.
|
||||
heap.Push(h, fileEntry{Name: "medium.bin", Size: 500})
|
||||
heap.Push(h, fileEntry{Name: "small.txt", Size: 100})
|
||||
heap.Push(h, fileEntry{Name: "large.iso", Size: 1000})
|
||||
|
||||
if h.Len() != 3 {
|
||||
t.Errorf("Len() = %d, want 3", h.Len())
|
||||
}
|
||||
|
||||
// Min-heap: smallest should come out first.
|
||||
first := heap.Pop(h).(fileEntry)
|
||||
if first.Name != "small.txt" || first.Size != 100 {
|
||||
t.Errorf("first Pop() = %v, want {small.txt, 100}", first)
|
||||
}
|
||||
|
||||
second := heap.Pop(h).(fileEntry)
|
||||
if second.Name != "medium.bin" || second.Size != 500 {
|
||||
t.Errorf("second Pop() = %v, want {medium.bin, 500}", second)
|
||||
}
|
||||
|
||||
third := heap.Pop(h).(fileEntry)
|
||||
if third.Name != "large.iso" || third.Size != 1000 {
|
||||
t.Errorf("third Pop() = %v, want {large.iso, 1000}", third)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("top N largest pattern", func(t *testing.T) {
|
||||
// This is how the heap is used in practice: keep top N largest.
|
||||
h := &largeFileHeap{}
|
||||
heap.Init(h)
|
||||
maxSize := 3
|
||||
|
||||
files := []fileEntry{
|
||||
{Name: "a", Size: 50},
|
||||
{Name: "b", Size: 200},
|
||||
{Name: "c", Size: 30},
|
||||
{Name: "d", Size: 150},
|
||||
{Name: "e", Size: 300},
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
heap.Push(h, f)
|
||||
if h.Len() > maxSize {
|
||||
heap.Pop(h) // Remove smallest to keep only top N.
|
||||
}
|
||||
}
|
||||
|
||||
if h.Len() != maxSize {
|
||||
t.Errorf("Len() = %d, want %d", h.Len(), maxSize)
|
||||
}
|
||||
|
||||
// Extract remaining (should be 3 largest: 150, 200, 300).
|
||||
var sizes []int64
|
||||
for h.Len() > 0 {
|
||||
sizes = append(sizes, heap.Pop(h).(fileEntry).Size)
|
||||
}
|
||||
|
||||
// Min-heap pops in ascending order.
|
||||
want := []int64{150, 200, 300}
|
||||
for i, s := range sizes {
|
||||
if s != want[i] {
|
||||
t.Errorf("sizes[%d] = %d, want %d", i, s, want[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
114
cmd/status/view_test.go
Normal file
114
cmd/status/view_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFormatRate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input float64
|
||||
want string
|
||||
}{
|
||||
// Below threshold (< 0.01).
|
||||
{"zero", 0, "0 MB/s"},
|
||||
{"tiny", 0.001, "0 MB/s"},
|
||||
{"just under threshold", 0.009, "0 MB/s"},
|
||||
|
||||
// Small rates (0.01 to < 1) — 2 decimal places.
|
||||
{"at threshold", 0.01, "0.01 MB/s"},
|
||||
{"small rate", 0.5, "0.50 MB/s"},
|
||||
{"just under 1", 0.99, "0.99 MB/s"},
|
||||
|
||||
// Medium rates (1 to < 10) — 1 decimal place.
|
||||
{"exactly 1", 1.0, "1.0 MB/s"},
|
||||
{"medium rate", 5.5, "5.5 MB/s"},
|
||||
{"just under 10", 9.9, "9.9 MB/s"},
|
||||
|
||||
// Large rates (>= 10) — no decimal places.
|
||||
{"exactly 10", 10.0, "10 MB/s"},
|
||||
{"large rate", 100.5, "100 MB/s"},
|
||||
{"very large", 1000.0, "1000 MB/s"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := formatRate(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("formatRate(%v) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShorten(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
maxLen int
|
||||
want string
|
||||
}{
|
||||
// No truncation needed.
|
||||
{"empty string", "", 10, ""},
|
||||
{"shorter than max", "hello", 10, "hello"},
|
||||
{"exactly at max", "hello", 5, "hello"},
|
||||
|
||||
// Truncation needed.
|
||||
{"one over max", "hello!", 5, "hell…"},
|
||||
{"much longer", "hello world", 5, "hell…"},
|
||||
|
||||
// Edge cases.
|
||||
{"maxLen 1", "hello", 1, "…"},
|
||||
{"maxLen 2", "hello", 2, "h…"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := shorten(tt.input, tt.maxLen)
|
||||
if got != tt.want {
|
||||
t.Errorf("shorten(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHumanBytesShort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input uint64
|
||||
want string
|
||||
}{
|
||||
// Zero and small values.
|
||||
{"zero", 0, "0"},
|
||||
{"one byte", 1, "1"},
|
||||
{"999 bytes", 999, "999"},
|
||||
|
||||
// Kilobyte boundaries.
|
||||
{"exactly 1KB", 1 << 10, "1K"},
|
||||
{"just under 1KB", (1 << 10) - 1, "1023"},
|
||||
{"1.5KB rounds to 2K", 1536, "2K"},
|
||||
{"999KB", 999 << 10, "999K"},
|
||||
|
||||
// Megabyte boundaries.
|
||||
{"exactly 1MB", 1 << 20, "1M"},
|
||||
{"just under 1MB", (1 << 20) - 1, "1024K"},
|
||||
{"500MB", 500 << 20, "500M"},
|
||||
|
||||
// Gigabyte boundaries.
|
||||
{"exactly 1GB", 1 << 30, "1G"},
|
||||
{"just under 1GB", (1 << 30) - 1, "1024M"},
|
||||
{"100GB", 100 << 30, "100G"},
|
||||
|
||||
// Terabyte boundaries.
|
||||
{"exactly 1TB", 1 << 40, "1T"},
|
||||
{"just under 1TB", (1 << 40) - 1, "1024G"},
|
||||
{"2TB", 2 << 40, "2T"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := humanBytesShort(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("humanBytesShort(%d) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user