Parses xFusion G5500 V7 iBMC diagnostic dump archives with: - FRU info (board serial, product name, component inventory) - IPMI sensor readings (temperature, voltage, power, fan, current) - CPU inventory (model, cores, threads, cache, serial) - Memory DIMMs (size, speed, type, serial, manufacturer) - GPU inventory from card_manage/card_info (serial, firmware, ECC counts) - OCP NIC detection (ConnectX-6 Lx with serial) - PSU inventory (4x 3000W, serial, firmware, voltage) - Storage: RAID controller firmware + physical drives (model, serial, endurance) - iBMC maintenance log events with severity mapping - Registers as vendor "xfusion" in the parser registry All 11 fixture tests pass against real G5500 V7 dump archive. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
220 lines
6.2 KiB
Go
220 lines
6.2 KiB
Go
package xfusion
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/parser"
|
|
)
|
|
|
|
// loadTestArchive extracts the given archive path for use in tests.
|
|
// Skips the test if the file is not found (CI environments without testdata).
|
|
func loadTestArchive(t *testing.T, path string) []parser.ExtractedFile {
|
|
t.Helper()
|
|
files, err := parser.ExtractArchive(path)
|
|
if err != nil {
|
|
t.Skipf("cannot load test archive %s: %v", path, err)
|
|
}
|
|
return files
|
|
}
|
|
|
|
func TestDetect_G5500V7(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
score := p.Detect(files)
|
|
if score < 80 {
|
|
t.Fatalf("expected Detect score >= 80, got %d", score)
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_BoardInfo(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
if result.Hardware == nil {
|
|
t.Fatal("Hardware is nil")
|
|
}
|
|
board := result.Hardware.BoardInfo
|
|
if board.SerialNumber != "210619KUGGXGS2000015" {
|
|
t.Errorf("BoardInfo.SerialNumber = %q, want 210619KUGGXGS2000015", board.SerialNumber)
|
|
}
|
|
if board.ProductName != "G5500 V7" {
|
|
t.Errorf("BoardInfo.ProductName = %q, want G5500 V7", board.ProductName)
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_CPUs(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
if len(result.Hardware.CPUs) != 2 {
|
|
t.Fatalf("expected 2 CPUs, got %d", len(result.Hardware.CPUs))
|
|
}
|
|
cpu1 := result.Hardware.CPUs[0]
|
|
if cpu1.Cores != 32 {
|
|
t.Errorf("CPU1 cores = %d, want 32", cpu1.Cores)
|
|
}
|
|
if cpu1.Threads != 64 {
|
|
t.Errorf("CPU1 threads = %d, want 64", cpu1.Threads)
|
|
}
|
|
if cpu1.SerialNumber == "" {
|
|
t.Error("CPU1 SerialNumber is empty")
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_Memory(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
// Only 2 DIMMs are populated (rest are "NO DIMM")
|
|
if len(result.Hardware.Memory) != 2 {
|
|
t.Fatalf("expected 2 populated DIMMs, got %d", len(result.Hardware.Memory))
|
|
}
|
|
dimm := result.Hardware.Memory[0]
|
|
if dimm.SizeMB != 65536 {
|
|
t.Errorf("DIMM0 SizeMB = %d, want 65536", dimm.SizeMB)
|
|
}
|
|
if dimm.Type != "DDR5" {
|
|
t.Errorf("DIMM0 Type = %q, want DDR5", dimm.Type)
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_GPUs(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
if len(result.Hardware.GPUs) != 8 {
|
|
t.Fatalf("expected 8 GPUs, got %d", len(result.Hardware.GPUs))
|
|
}
|
|
for _, gpu := range result.Hardware.GPUs {
|
|
if gpu.SerialNumber == "" {
|
|
t.Errorf("GPU slot %s has empty SerialNumber", gpu.Slot)
|
|
}
|
|
if gpu.Model == "" {
|
|
t.Errorf("GPU slot %s has empty Model", gpu.Slot)
|
|
}
|
|
if gpu.Firmware == "" {
|
|
t.Errorf("GPU slot %s has empty Firmware", gpu.Slot)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_NICs(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
if len(result.Hardware.NetworkCards) < 1 {
|
|
t.Fatal("expected at least 1 NIC (OCP CX6), got 0")
|
|
}
|
|
nic := result.Hardware.NetworkCards[0]
|
|
if nic.SerialNumber == "" {
|
|
t.Errorf("NIC SerialNumber is empty")
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_PSUs(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
if len(result.Hardware.PowerSupply) != 4 {
|
|
t.Fatalf("expected 4 PSUs, got %d", len(result.Hardware.PowerSupply))
|
|
}
|
|
for _, psu := range result.Hardware.PowerSupply {
|
|
if psu.WattageW != 3000 {
|
|
t.Errorf("PSU slot %s wattage = %d, want 3000", psu.Slot, psu.WattageW)
|
|
}
|
|
if psu.SerialNumber == "" {
|
|
t.Errorf("PSU slot %s has empty SerialNumber", psu.Slot)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_Storage(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
if len(result.Hardware.Storage) != 2 {
|
|
t.Fatalf("expected 2 storage devices, got %d", len(result.Hardware.Storage))
|
|
}
|
|
for _, disk := range result.Hardware.Storage {
|
|
if disk.SerialNumber == "" {
|
|
t.Errorf("disk slot %s has empty SerialNumber", disk.Slot)
|
|
}
|
|
if disk.Model == "" {
|
|
t.Errorf("disk slot %s has empty Model", disk.Slot)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_Sensors(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
if len(result.Sensors) < 20 {
|
|
t.Fatalf("expected at least 20 sensors, got %d", len(result.Sensors))
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_Events(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
if len(result.Events) < 5 {
|
|
t.Fatalf("expected at least 5 events, got %d", len(result.Events))
|
|
}
|
|
// All events should have real timestamps (not epoch 0)
|
|
for _, ev := range result.Events {
|
|
if ev.Timestamp.Year() <= 1970 {
|
|
t.Errorf("event has epoch timestamp: %v %s", ev.Timestamp, ev.Description)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParse_G5500V7_FRU(t *testing.T) {
|
|
files := loadTestArchive(t, "../../../../example/G5500V7_210619KUGGXGS2000015_20260318-1128.tar.gz")
|
|
p := &Parser{}
|
|
result, err := p.Parse(files)
|
|
if err != nil {
|
|
t.Fatalf("Parse: %v", err)
|
|
}
|
|
if len(result.FRU) < 3 {
|
|
t.Fatalf("expected at least 3 FRU entries, got %d", len(result.FRU))
|
|
}
|
|
// Check mainboard FRU serial
|
|
found := false
|
|
for _, f := range result.FRU {
|
|
if f.SerialNumber == "210619KUGGXGS2000015" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("mainboard serial 210619KUGGXGS2000015 not found in FRU")
|
|
}
|
|
}
|