Dashboard Editor & Layout: - Full-screen visual editor for reorganizing cards - Drag-and-drop cards between sections - Toggle card visibility with persistence to localStorage - Reset to default layout option Alerts System: - Threshold-based alerts for CPU, memory, temperature, disk, GPU - Alert manager with duration requirements - AlertsCard component with settings UI - API endpoints for alerts CRUD New Collectors: - Docker container monitoring with parallel stats fetching - Systemd service status via D-Bus - Historical metrics storage (1 hour at 1s intervals) PWA Support: - Service worker with offline caching - Web app manifest with SVG icons - iOS PWA meta tags Mobile Responsive: - Collapsible hamburger menu on mobile - Adaptive grid layouts for all screen sizes - Touch-friendly hover states - Safe area insets for notched devices UI Enhancements: - Light/dark theme toggle with persistence - Keyboard shortcuts (T=theme, R=refresh, ?=help) - Per-process expandable details in ProcessesCard - Sparkline charts for historical data Performance Fixes: - Buffered SSE channels to prevent blocking - Parallel Docker stats collection with timeout - D-Bus timeout for systemd collector Tests: - Unit tests for CPU, memory, network collectors - Alert manager tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
108 lines
2.7 KiB
Go
108 lines
2.7 KiB
Go
package collectors
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestMemoryCollector(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create mock /proc/meminfo
|
|
meminfoContent := `MemTotal: 32768000 kB
|
|
MemFree: 10240000 kB
|
|
MemAvailable: 20480000 kB
|
|
Buffers: 1024000 kB
|
|
Cached: 5120000 kB
|
|
SwapCached: 0 kB
|
|
Active: 8192000 kB
|
|
Inactive: 4096000 kB
|
|
SwapTotal: 8192000 kB
|
|
SwapFree: 6144000 kB
|
|
`
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "meminfo"), []byte(meminfoContent), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
collector := NewMemoryCollector(tmpDir)
|
|
stats, err := collector.Collect()
|
|
if err != nil {
|
|
t.Fatalf("Collect failed: %v", err)
|
|
}
|
|
|
|
// Verify values (kB to bytes)
|
|
expectedTotal := uint64(32768000 * 1024)
|
|
if stats.Total != expectedTotal {
|
|
t.Errorf("Expected Total=%d, got %d", expectedTotal, stats.Total)
|
|
}
|
|
|
|
expectedAvailable := uint64(20480000 * 1024)
|
|
if stats.Available != expectedAvailable {
|
|
t.Errorf("Expected Available=%d, got %d", expectedAvailable, stats.Available)
|
|
}
|
|
|
|
expectedCached := uint64(5120000 * 1024)
|
|
if stats.Cached != expectedCached {
|
|
t.Errorf("Expected Cached=%d, got %d", expectedCached, stats.Cached)
|
|
}
|
|
|
|
expectedSwapTotal := uint64(8192000 * 1024)
|
|
if stats.SwapTotal != expectedSwapTotal {
|
|
t.Errorf("Expected SwapTotal=%d, got %d", expectedSwapTotal, stats.SwapTotal)
|
|
}
|
|
|
|
expectedSwapUsed := uint64((8192000 - 6144000) * 1024)
|
|
if stats.SwapUsed != expectedSwapUsed {
|
|
t.Errorf("Expected SwapUsed=%d, got %d", expectedSwapUsed, stats.SwapUsed)
|
|
}
|
|
|
|
// Used should be Total - Available
|
|
expectedUsed := expectedTotal - expectedAvailable
|
|
if stats.Used != expectedUsed {
|
|
t.Errorf("Expected Used=%d, got %d", expectedUsed, stats.Used)
|
|
}
|
|
}
|
|
|
|
func TestMemoryCollector_MissingFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
collector := NewMemoryCollector(tmpDir)
|
|
|
|
stats, err := collector.Collect()
|
|
// Should return error or empty stats
|
|
if err == nil && stats.Total == 0 {
|
|
// This is acceptable - empty stats on missing file
|
|
return
|
|
}
|
|
if err != nil {
|
|
// This is also acceptable
|
|
return
|
|
}
|
|
t.Error("Expected either error or zero Total for missing meminfo")
|
|
}
|
|
|
|
func TestMemoryCollector_MalformedFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create malformed meminfo
|
|
meminfoContent := `MemTotal: invalid
|
|
MemFree: also invalid
|
|
`
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "meminfo"), []byte(meminfoContent), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
collector := NewMemoryCollector(tmpDir)
|
|
stats, err := collector.Collect()
|
|
|
|
// Should handle gracefully
|
|
if err != nil {
|
|
return // Error is acceptable
|
|
}
|
|
|
|
// If no error, values should be zero (failed to parse)
|
|
if stats.Total != 0 {
|
|
t.Errorf("Expected Total=0 for malformed input, got %d", stats.Total)
|
|
}
|
|
}
|