package main import ( "encoding/json" "os" "path/filepath" "strings" "testing" "time" ) func TestApplySMARTJSONReadsNVMeSelfTestHistory(t *testing.T) { disk := DiskMetrics{Path: "/dev/nvme0n1"} output := []byte(`{ "model_name":"ADATA LEGEND 900", "smart_status":{"passed":true}, "power_on_time":{"hours":960}, "nvme_self_test_log":{ "current_self_test_operation":{"value":0,"string":"No self-test in progress"}, "table":[{ "self_test_code":{"value":1,"string":"Short"}, "self_test_result":{"value":0,"string":"Completed without error"}, "power_on_hours":959 }] } }`) before := time.Now().Add(-2 * time.Hour) applySMARTJSON(&disk, output) after := time.Now() if disk.SelfTestNeverRun { t.Fatal("завершённый NVMe self-test ошибочно отмечен как никогда не запускавшийся") } if disk.LastSelfTest != "Short · Completed without error" { t.Fatalf("неожиданный результат self-test: %q", disk.LastSelfTest) } parsed, err := time.Parse(time.RFC3339, disk.LastSelfTestAt) if err != nil || parsed.Before(before) || parsed.After(after) { t.Fatalf("неверно вычислено время self-test: %q, err=%v", disk.LastSelfTestAt, err) } if len(disk.AttentionReasons) != 0 || disk.SMARTStatus != "Исправен" { t.Fatalf("исправный NVMe требует внимания: %+v", disk) } } func TestIsPhysicalDiskName(t *testing.T) { tests := map[string]bool{ "sda": true, "sdb": true, "nvme0n1": true, "loop0": false, "dm-0": false, "zram0": false, "md0": false, } for name, expected := range tests { if actual := isPhysicalDiskName(name); actual != expected { t.Fatalf("isPhysicalDiskName(%q) = %v, ожидалось %v", name, actual, expected) } } } func TestReadDiskIOStats(t *testing.T) { path := filepath.Join(t.TempDir(), "diskstats") data := " 8 0 sda 100 2 300 40 50 3 700 80 4 90 120 0 0 0 0 0 0\n 7 0 loop0 999 0 999 0 999 0 999 0 0 0 0\n" if err := os.WriteFile(path, []byte(data), 0o600); err != nil { t.Fatal(err) } stats := readDiskIOStats(path) disk, ok := stats["sda"] if !ok || disk.reads != 100 || disk.readSectors != 300 || disk.writes != 50 || disk.writeSectors != 700 || disk.inFlight != 4 || disk.ioMS != 90 || disk.weightedMS != 120 { t.Fatalf("unexpected stats: %+v", disk) } if _, exists := stats["loop0"]; exists { t.Fatal("virtual device must be ignored") } } func TestSumFilesystemUsage(t *testing.T) { device := lsblkDevice{Children: []lsblkDevice{ {FSUsed: json.RawMessage(`100`), FSAvail: json.RawMessage(`300`)}, {FSUsed: json.RawMessage(`200`), FSAvail: json.RawMessage(`400`)}, }} used, total, ok := sumFilesystemUsage(device) if !ok || used != 300 || total != 1000 { t.Fatalf("неожиданное использование диска: used=%d total=%d ok=%v", used, total, ok) } } func TestApplyATAWearAndReason(t *testing.T) { var smart smartctlJSON attribute := struct { Name string `json:"name"` Value int `json:"value"` Threshold int `json:"thresh"` WhenFailed string `json:"when_failed"` Raw struct { String string `json:"string"` Value int64 `json:"value"` } `json:"raw"` }{Name: "SSD_Life_Left", Value: 92, WhenFailed: "FAILING_NOW"} smart.ATAAttributes.Table = append(smart.ATAAttributes.Table, attribute) disk := DiskMetrics{} applyATAAttributes(&disk, smart) if disk.WearUsedPercent == nil || *disk.WearUsedPercent != 8 { t.Fatalf("неожиданный износ: %v", disk.WearUsedPercent) } if len(disk.AttentionReasons) != 1 { t.Fatalf("причина предупреждения не добавлена: %+v", disk) } } func TestHistoricalATAFailureDoesNotRequireAttention(t *testing.T) { var smart smartctlJSON attribute := struct { Name string `json:"name"` Value int `json:"value"` Threshold int `json:"thresh"` WhenFailed string `json:"when_failed"` Raw struct { String string `json:"string"` Value int64 `json:"value"` } `json:"raw"` }{Name: "Airflow_Temperature_Cel", WhenFailed: "In_the_past"} attribute.Raw.String = "46 (Min/Max 26/47 #15)" smart.ATAAttributes.Table = append(smart.ATAAttributes.Table, attribute) disk := DiskMetrics{} applyATAAttributes(&disk, smart) applySMARTExitStatus(&disk, 32|64|128) if len(disk.AttentionReasons) != 0 { t.Fatalf("историческая ошибка стала активной: %+v", disk.AttentionReasons) } if len(disk.SMARTHistory) != 4 { t.Fatalf("история SMART потеряна: %+v", disk.SMARTHistory) } } func TestSMARTErrorLogGrowthRequiresAttention(t *testing.T) { old := DiskMetrics{SMARTErrorCount: 2} current := DiskMetrics{SMARTErrorCount: 3} changes := diskCounterChanges(old, current) if len(changes) != 1 || !strings.Contains(changes[0], "2 → 3") { t.Fatalf("рост журнала не обнаружен: %+v", changes) } }