package collect import ( "testing" "time" ) const zpoolStatusOK = ` pool: nas_pool_1_8_gb state: ONLINE scan: scrub repaired 0B in 03:12:41 with 0 errors on Sun Sep 7 03:36:42 2025 config: NAME STATE READ WRITE CKSUM nas_pool_1_8_gb ONLINE 0 0 0 mirror-0 ONLINE 0 0 0 ata-WDC_WD80EFZZ-68BTXN0_WD-AAA ONLINE 0 0 0 ata-WDC_WD80EFZZ-68BTXN0_WD-BBB ONLINE 0 0 0 errors: No known data errors ` const zpoolStatusDegraded = ` pool: tank state: DEGRADED status: One or more devices could not be opened. Sufficient replicas exist for the pool to continue functioning in a degraded state. action: Attach the missing device and online it using 'zpool online'. see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-2Q scan: scrub in progress since Sat Sep 13 01:00:00 2025 1.20T / 7.10T scanned at 1.02G/s, 500G / 7.10T issued at 430M/s 0B repaired, 6.88% done, 04:28:12 to go config: NAME STATE READ WRITE CKSUM tank DEGRADED 0 0 0 mirror-0 DEGRADED 0 0 0 ata-DISK_A ONLINE 0 0 3 1234567890123456789 UNAVAIL 0 0 0 was /dev/sdb1 logs nvme0n1p2 ONLINE 0 0 0 errors: Permanent errors have been detected in the following files: /tank/data/file.bin ` func TestParseZpoolStatusOK(t *testing.T) { p := Pool{Name: "nas_pool_1_8_gb"} parseZpoolStatus([]byte(zpoolStatusOK), &p) if p.Health != "ONLINE" { t.Errorf("health = %q", p.Health) } if p.Errors != "No known data errors" { t.Errorf("errors = %q", p.Errors) } if p.Scrubbing || p.ScanErrs != 0 { t.Errorf("scan flags wrong: %+v", p) } if p.LastScrub == nil { t.Fatalf("last scrub not parsed from %q", p.Scan) } want := time.Date(2025, 9, 7, 3, 36, 42, 0, time.Local) if !p.LastScrub.Equal(want) { t.Errorf("last scrub = %v, want %v", p.LastScrub, want) } if len(p.Vdevs) != 4 { t.Fatalf("vdevs = %d: %+v", len(p.Vdevs), p.Vdevs) } if p.Vdevs[0].Depth != 0 || p.Vdevs[1].Depth != 1 || p.Vdevs[2].Depth != 2 { t.Errorf("depths wrong: %+v", p.Vdevs) } if p.Vdevs[1].Name != "mirror-0" || p.Vdevs[1].State != "ONLINE" { t.Errorf("mirror vdev wrong: %+v", p.Vdevs[1]) } } func TestParseZpoolStatusDegraded(t *testing.T) { p := Pool{Name: "tank"} parseZpoolStatus([]byte(zpoolStatusDegraded), &p) if p.Health != "DEGRADED" { t.Errorf("health = %q", p.Health) } if !p.Scrubbing { t.Errorf("scrub in progress not detected: %q", p.Scan) } if p.LastScrub != nil { t.Errorf("last scrub must be nil while scrubbing") } if p.Status == "" || p.Action == "" { t.Errorf("status/action not captured: %q / %q", p.Status, p.Action) } if p.Errors == "" || p.Errors == "No known data errors" { t.Errorf("errors = %q", p.Errors) } var unavail, cksum, logs bool for _, v := range p.Vdevs { switch { case v.State == "UNAVAIL" && v.Note == "was /dev/sdb1": unavail = true case v.Name == "ata-DISK_A" && v.Cksum == 3: cksum = true case v.Name == "logs" && v.State == "": logs = true } } if !unavail || !cksum || !logs { t.Errorf("vdev parse: unavail=%v cksum=%v logs=%v %+v", unavail, cksum, logs, p.Vdevs) } } func TestParseZpoolList(t *testing.T) { out := "nas_pool_1_8_gb\t7992048291840\t3500000000000\t4492048291840\t43\tONLINE\t7\ntmp_pool\t1000000000000\t900000000000\t100000000000\t90\tONLINE\t-\n" pools := parseZpoolList([]byte(out)) if len(pools) != 2 { t.Fatalf("pools = %d", len(pools)) } if pools[0].CapPct != 43 || pools[0].FragPct != 7 || pools[0].Health != "ONLINE" { t.Errorf("pool0 = %+v", pools[0]) } if pools[1].CapPct != 90 || pools[1].FragPct != 0 { t.Errorf("pool1 = %+v", pools[1]) } } func TestParseZfsList(t *testing.T) { out := "nas_pool_1_8_gb\t100\t200\t50\t/nas_pool_1_8_gb\nnas_pool_1_8_gb/appdata\t10\t200\t10\t/nas_pool_1_8_gb/appdata\n" ds := parseZfsList([]byte(out)) if len(ds) != 2 || ds[1].Name != "nas_pool_1_8_gb/appdata" || ds[1].Avail != 200 { t.Errorf("datasets = %+v", ds) } }