Files
logpile/internal/parser/vendors/xfusion/parser_test.go
2026-04-04 15:07:10 +03:00

333 lines
11 KiB
Go

package xfusion
import (
"strings"
"testing"
"git.mchus.pro/mchus/logpile/internal/models"
"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 TestDetect_ServerFileExportMarkers(t *testing.T) {
p := &Parser{}
score := p.Detect([]parser.ExtractedFile{
{Path: "dump_info/RTOSDump/versioninfo/app_revision.txt", Content: []byte("Product Name: G5500 V7")},
{Path: "dump_info/LogDump/netcard/netcard_info.txt", Content: []byte("2026-02-04 03:54:06 UTC")},
{Path: "dump_info/AppDump/card_manage/card_info", Content: []byte("OCP Card Info")},
})
if score < 70 {
t.Fatalf("expected Detect score >= 70 for xFusion file export markers, got %d", score)
}
}
func TestDetect_Negative(t *testing.T) {
p := &Parser{}
score := p.Detect([]parser.ExtractedFile{
{Path: "logs/messages.txt", Content: []byte("plain text")},
{Path: "inventory.json", Content: []byte(`{"vendor":"other"}`)},
})
if score != 0 {
t.Fatalf("expected Detect score 0 for non-xFusion input, 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_ServerFileExport_NetworkAdaptersAndFirmware(t *testing.T) {
p := &Parser{}
files := []parser.ExtractedFile{
{
Path: "dump_info/AppDump/card_manage/card_info",
Content: []byte(strings.TrimSpace(`
Pcie Card Info
Slot | Vender Id | Device Id | Sub Vender Id | Sub Device Id | Segment Number | Bus Number | Device Number | Function Number | Card Desc | Board Id | PCB Version | CPLD Version | Sub Card Bom Id | PartNum | SerialNumber | OriginalPartNum
1 | 0x15b3 | 0x101f | 0x1f24 | 0x2011 | 0x00 | 0x27 | 0x00 | 0x00 | MT2894 Family [ConnectX-6 Lx] | N/A | N/A | N/A | N/A | 0302Y238 | 02Y238X6RC000058 |
OCP Card Info
Slot | Vender Id | Device Id | Sub Vender Id | Sub Device Id | Segment Number | Bus Number | Device Number | Function Number | Card Desc | Board Id | PCB Version | CPLD Version | Sub Card Bom Id | PartNum | SerialNumber | OriginalPartNum
1 | 0x15b3 | 0x101f | 0x1f24 | 0x2011 | 0x00 | 0x27 | 0x00 | 0x00 | MT2894 Family [ConnectX-6 Lx] | N/A | N/A | N/A | N/A | 0302Y238 | 02Y238X6RC000058 |
`)),
},
{
Path: "dump_info/LogDump/netcard/netcard_info.txt",
Content: []byte(strings.TrimSpace(`
2026-02-04 03:54:06 UTC
ProductName :XC385
Manufacture :XFUSION
FirmwareVersion :26.39.2048
SlotId :1
Port0 BDF:0000:27:00.0
MacAddr:44:1A:4C:16:E8:03
ActualMac:44:1A:4C:16:E8:03
Port1 BDF:0000:27:00.1
MacAddr:00:00:00:00:00:00
ActualMac:44:1A:4C:16:E8:04
`)),
},
{
Path: "dump_info/RTOSDump/versioninfo/app_revision.txt",
Content: []byte(strings.TrimSpace(`
------------------- iBMC INFO -------------------
Active iBMC Version: (U68)3.08.05.85
Active iBMC Built: 16:46:26 Jan 4 2026
SDK Version: 13.16.30.16
SDK Built: 07:55:18 Dec 12 2025
Active BIOS Version: (U6216)01.02.08.17
Active BIOS Built: 00:00:00 Jan 05 2026
Product Name: G5500 V7
`)),
},
}
result, err := p.Parse(files)
if err != nil {
t.Fatalf("Parse: %v", err)
}
if result.Protocol != "ipmi" || result.SourceType != models.SourceTypeArchive {
t.Fatalf("unexpected source metadata: protocol=%q source_type=%q", result.Protocol, result.SourceType)
}
if result.Hardware == nil {
t.Fatal("Hardware is nil")
}
if len(result.Hardware.NetworkAdapters) != 1 {
t.Fatalf("expected 1 network adapter, got %d", len(result.Hardware.NetworkAdapters))
}
adapter := result.Hardware.NetworkAdapters[0]
if adapter.BDF != "0000:27:00.0" {
t.Fatalf("expected network adapter BDF 0000:27:00.0, got %q", adapter.BDF)
}
if adapter.Firmware != "26.39.2048" {
t.Fatalf("expected network adapter firmware 26.39.2048, got %q", adapter.Firmware)
}
if adapter.SerialNumber != "02Y238X6RC000058" {
t.Fatalf("expected network adapter serial from card_info, got %q", adapter.SerialNumber)
}
if len(adapter.MACAddresses) != 2 || adapter.MACAddresses[0] != "44:1A:4C:16:E8:03" || adapter.MACAddresses[1] != "44:1A:4C:16:E8:04" {
t.Fatalf("unexpected MAC addresses: %#v", adapter.MACAddresses)
}
fwByDevice := make(map[string]models.FirmwareInfo)
for _, fw := range result.Hardware.Firmware {
fwByDevice[fw.DeviceName] = fw
}
if fwByDevice["iBMC"].Version != "(U68)3.08.05.85" {
t.Fatalf("expected iBMC firmware from app_revision.txt, got %#v", fwByDevice["iBMC"])
}
if fwByDevice["BIOS"].Version != "(U6216)01.02.08.17" {
t.Fatalf("expected BIOS firmware from app_revision.txt, got %#v", fwByDevice["BIOS"])
}
if result.Hardware.BoardInfo.ProductName != "G5500 V7" {
t.Fatalf("expected board product fallback from app_revision.txt, got %q", result.Hardware.BoardInfo.ProductName)
}
}
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")
}
}