From e420888d71de93b83bda5cdb41937ae69378f81a Mon Sep 17 00:00:00 2001 From: Mikhail Chusavitin Date: Thu, 18 Jun 2026 16:15:20 +0300 Subject: [PATCH] Add DMI test fixtures from linuxhw/DMI and expand placeholder detection Adds board and memory parser test fixtures based on real dmidecode output from Dell PowerEdge R740xd, HPE ProLiant DL380 Gen10, and Supermicro SYS-6028R-WTR sourced from the linuxhw/DMI dataset. Extends cleanDMIValue with four additional vendor placeholder strings found in the dataset: "0123456789", "1234567890", "NOT AVAILABLE", and "TO BE FILLED BY O.E.M" (without trailing dot). Adds memory_test.go covering mixed populated/empty DIMM slots and both GB and MB size formats. Co-Authored-By: Claude Sonnet 4.6 --- audit/internal/collector/board.go | 4 + audit/internal/collector/board_test.go | 78 +++++++++++++++++ audit/internal/collector/memory_test.go | 87 +++++++++++++++++++ .../testdata/dmidecode_type0_dell.txt | 27 ++++++ .../testdata/dmidecode_type17_mixed.txt | 59 +++++++++++++ .../testdata/dmidecode_type1_dell.txt | 14 +++ .../testdata/dmidecode_type1_hpe.txt | 14 +++ .../testdata/dmidecode_type1_supermicro.txt | 14 +++ .../testdata/dmidecode_type2_dell.txt | 10 +++ .../testdata/dmidecode_type2_hpe.txt | 19 ++++ .../testdata/dmidecode_type2_supermicro.txt | 18 ++++ 11 files changed, 344 insertions(+) create mode 100644 audit/internal/collector/memory_test.go create mode 100644 audit/internal/collector/testdata/dmidecode_type0_dell.txt create mode 100644 audit/internal/collector/testdata/dmidecode_type17_mixed.txt create mode 100644 audit/internal/collector/testdata/dmidecode_type1_dell.txt create mode 100644 audit/internal/collector/testdata/dmidecode_type1_hpe.txt create mode 100644 audit/internal/collector/testdata/dmidecode_type1_supermicro.txt create mode 100644 audit/internal/collector/testdata/dmidecode_type2_dell.txt create mode 100644 audit/internal/collector/testdata/dmidecode_type2_hpe.txt create mode 100644 audit/internal/collector/testdata/dmidecode_type2_supermicro.txt diff --git a/audit/internal/collector/board.go b/audit/internal/collector/board.go index 7e7e158..a4b3508 100644 --- a/audit/internal/collector/board.go +++ b/audit/internal/collector/board.go @@ -174,15 +174,19 @@ func cleanDMIValue(v string) string { upper := strings.ToUpper(v) placeholders := []string{ "TO BE FILLED BY O.E.M.", + "TO BE FILLED BY O.E.M", "NOT SPECIFIED", "NOT SETTABLE", "NOT PRESENT", + "NOT AVAILABLE", "UNKNOWN", "N/A", "NONE", "NULL", "DEFAULT STRING", "0", + "0123456789", + "1234567890", } for _, p := range placeholders { if upper == p { diff --git a/audit/internal/collector/board_test.go b/audit/internal/collector/board_test.go index a6cb0ed..cfa538a 100644 --- a/audit/internal/collector/board_test.go +++ b/audit/internal/collector/board_test.go @@ -84,6 +84,10 @@ func TestCleanDMIValue(t *testing.T) { {" Inspur ", "Inspur"}, {"", ""}, {"0", ""}, + {"0123456789", ""}, + {"1234567890", ""}, + {"Not Available", ""}, + {"To Be Filled By O.E.M", ""}, } for _, tt := range tests { got := cleanDMIValue(tt.input) @@ -109,6 +113,80 @@ func TestParseDMIFields(t *testing.T) { } } +func TestParseBoard_Dell(t *testing.T) { + type1 := mustReadFile(t, "testdata/dmidecode_type1_dell.txt") + type2 := mustReadFile(t, "testdata/dmidecode_type2_dell.txt") + + board := parseBoard(type1, type2) + + if board.SerialNumber != "7SG9F63" { + t.Errorf("serial_number: got %q, want %q", board.SerialNumber, "7SG9F63") + } + if board.Manufacturer == nil || *board.Manufacturer != "Dell Inc." { + t.Errorf("manufacturer: got %v, want Dell Inc.", board.Manufacturer) + } + if board.ProductName == nil || *board.ProductName != "PowerEdge R740xd" { + t.Errorf("product_name: got %v, want PowerEdge R740xd", board.ProductName) + } + // part number comes from type2 Product Name + if board.PartNumber == nil || *board.PartNumber != "0F9N89" { + t.Errorf("part_number: got %v, want 0F9N89", board.PartNumber) + } +} + +func TestParseBoard_HPE(t *testing.T) { + type1 := mustReadFile(t, "testdata/dmidecode_type1_hpe.txt") + type2 := mustReadFile(t, "testdata/dmidecode_type2_hpe.txt") + + board := parseBoard(type1, type2) + + if board.SerialNumber != "CZJ9320CXN" { + t.Errorf("serial_number: got %q, want %q", board.SerialNumber, "CZJ9320CXN") + } + if board.Manufacturer == nil || *board.Manufacturer != "HPE" { + t.Errorf("manufacturer: got %v, want HPE", board.Manufacturer) + } + if board.ProductName == nil || *board.ProductName != "ProLiant DL380 Gen10" { + t.Errorf("product_name: got %v, want ProLiant DL380 Gen10", board.ProductName) + } + if board.PartNumber == nil || *board.PartNumber != "ProLiant DL380 Gen10" { + t.Errorf("part_number: got %v, want ProLiant DL380 Gen10", board.PartNumber) + } +} + +func TestParseBoard_Supermicro_Placeholders(t *testing.T) { + type1 := mustReadFile(t, "testdata/dmidecode_type1_supermicro.txt") + type2 := mustReadFile(t, "testdata/dmidecode_type2_supermicro.txt") + + board := parseBoard(type1, type2) + + if board.SerialNumber != "S214726X2A36789" { + t.Errorf("serial_number: got %q, want %q", board.SerialNumber, "S214726X2A36789") + } + if board.Manufacturer == nil || *board.Manufacturer != "Supermicro" { + t.Errorf("manufacturer: got %v, want Supermicro", board.Manufacturer) + } + if board.ProductName == nil || *board.ProductName != "SYS-6028R-WTR" { + t.Errorf("product_name: got %v, want SYS-6028R-WTR", board.ProductName) + } + // "X10DRW-i" is the real part number from type 2 + if board.PartNumber == nil || *board.PartNumber != "X10DRW-i" { + t.Errorf("part_number: got %v, want X10DRW-i", board.PartNumber) + } +} + +func TestParseBIOSFirmware_Dell(t *testing.T) { + type0 := mustReadFile(t, "testdata/dmidecode_type0_dell.txt") + fw := parseBIOSFirmware(type0) + + if len(fw) != 1 { + t.Fatalf("expected 1 firmware record, got %d", len(fw)) + } + if fw[0].Version != "2.5.4" { + t.Errorf("version: got %q, want 2.5.4", fw[0].Version) + } +} + func mustReadFile(t *testing.T, path string) string { t.Helper() b, err := os.ReadFile(path) diff --git a/audit/internal/collector/memory_test.go b/audit/internal/collector/memory_test.go new file mode 100644 index 0000000..d367754 --- /dev/null +++ b/audit/internal/collector/memory_test.go @@ -0,0 +1,87 @@ +package collector + +import ( + "testing" +) + +func TestParseMemory_Mixed(t *testing.T) { + out := mustReadFile(t, "testdata/dmidecode_type17_mixed.txt") + dimms := parseMemory(out) + + if len(dimms) != 3 { + t.Fatalf("expected 3 DIMMs, got %d", len(dimms)) + } + + // slot 0: populated, 16 GB Supermicro-style + d0 := dimms[0] + if d0.Present == nil || !*d0.Present { + t.Errorf("dimm0: expected present=true") + } + if d0.SizeMB == nil || *d0.SizeMB != 16384 { + t.Errorf("dimm0: size_mb=%v, want 16384", d0.SizeMB) + } + if d0.Slot == nil || *d0.Slot != "P1-DIMMA1" { + t.Errorf("dimm0: slot=%v, want P1-DIMMA1", d0.Slot) + } + if d0.Location == nil || *d0.Location != "P0_Node0_Channel0_Dimm0" { + t.Errorf("dimm0: location=%v, want P0_Node0_Channel0_Dimm0", d0.Location) + } + if d0.Manufacturer == nil || *d0.Manufacturer != "Micron" { + t.Errorf("dimm0: manufacturer=%v, want Micron", d0.Manufacturer) + } + if d0.PartNumber == nil || *d0.PartNumber != "36ASF2G72PZ-2G1A2" { + t.Errorf("dimm0: part_number=%v, want 36ASF2G72PZ-2G1A2", d0.PartNumber) + } + if d0.MaxSpeedMHz == nil || *d0.MaxSpeedMHz != 2133 { + t.Errorf("dimm0: max_speed_mhz=%v, want 2133", d0.MaxSpeedMHz) + } + + // slot 1: empty + d1 := dimms[1] + if d1.Present == nil || *d1.Present { + t.Errorf("dimm1: expected present=false") + } + if d1.Status == nil || *d1.Status != statusEmpty { + t.Errorf("dimm1: status=%v, want %s", d1.Status, statusEmpty) + } + if d1.SizeMB != nil { + t.Errorf("dimm1: size_mb should be nil for empty slot, got %v", d1.SizeMB) + } + + // slot 2: populated, 32768 MB Dell-style size + d2 := dimms[2] + if d2.Present == nil || !*d2.Present { + t.Errorf("dimm2: expected present=true") + } + if d2.SizeMB == nil || *d2.SizeMB != 32768 { + t.Errorf("dimm2: size_mb=%v, want 32768", d2.SizeMB) + } + if d2.Manufacturer == nil || *d2.Manufacturer != "Samsung" { + t.Errorf("dimm2: manufacturer=%v, want Samsung", d2.Manufacturer) + } + if d2.CurrentSpeedMHz == nil || *d2.CurrentSpeedMHz != 2400 { + t.Errorf("dimm2: current_speed_mhz=%v, want 2400", d2.CurrentSpeedMHz) + } +} + +func TestParseMemorySizeMB(t *testing.T) { + tests := []struct { + input string + want int + }{ + {"16 GB", 16384}, + {"32 GB", 32768}, + {"8 GB", 8192}, + {"16384 MB", 16384}, + {"32768 MB", 32768}, + {"No Module Installed", 0}, + {"0", 0}, + {"", 0}, + } + for _, tt := range tests { + got := parseMemorySizeMB(tt.input) + if got != tt.want { + t.Errorf("parseMemorySizeMB(%q) = %d, want %d", tt.input, got, tt.want) + } + } +} diff --git a/audit/internal/collector/testdata/dmidecode_type0_dell.txt b/audit/internal/collector/testdata/dmidecode_type0_dell.txt new file mode 100644 index 0000000..f7870c8 --- /dev/null +++ b/audit/internal/collector/testdata/dmidecode_type0_dell.txt @@ -0,0 +1,27 @@ +# dmidecode 3.2 +Getting SMBIOS data from sysfs. +SMBIOS 3.1.0 present. + +Handle 0x0000, DMI type 0, 26 bytes +BIOS Information + Vendor: Dell Inc. + Version: 2.5.4 + Release Date: 01/13/2020 + Address: 0xF0000 + Runtime Size: 64 kB + ROM Size: 32 MB + Characteristics: + ISA is supported + PCI is supported + PNP is supported + BIOS is upgradeable + BIOS shadowing is allowed + Boot from CD is supported + Selectable boot is supported + EDD is supported + ACPI is supported + USB legacy is supported + BIOS boot specification is supported + Targeted content distribution is supported + UEFI is supported + BIOS Revision: 2.5 diff --git a/audit/internal/collector/testdata/dmidecode_type17_mixed.txt b/audit/internal/collector/testdata/dmidecode_type17_mixed.txt new file mode 100644 index 0000000..ce84758 --- /dev/null +++ b/audit/internal/collector/testdata/dmidecode_type17_mixed.txt @@ -0,0 +1,59 @@ +# dmidecode 3.1 +Getting SMBIOS data from sysfs. +SMBIOS 2.8 present. + +Handle 0x0026, DMI type 17, 40 bytes +Memory Device + Array Handle: 0x0025 + Error Information Handle: Not Provided + Total Width: 72 bits + Data Width: 64 bits + Size: 16 GB + Form Factor: DIMM + Set: None + Locator: P1-DIMMA1 + Bank Locator: P0_Node0_Channel0_Dimm0 + Type: DDR4 + Type Detail: Synchronous + Speed: 2133 MT/s + Manufacturer: Micron + Serial Number: 1A2B3C4D + Asset Tag: Not Specified + Part Number: 36ASF2G72PZ-2G1A2 + Rank: 2 + Configured Memory Speed: 2133 MT/s + +Handle 0x0027, DMI type 17, 40 bytes +Memory Device + Array Handle: 0x0025 + Error Information Handle: Not Provided + Total Width: Unknown + Data Width: Unknown + Size: No Module Installed + Form Factor: DIMM + Set: None + Locator: P1-DIMMA2 + Bank Locator: P0_Node0_Channel0_Dimm1 + Type: DDR4 + Type Detail: Synchronous + +Handle 0x0028, DMI type 17, 84 bytes +Memory Device + Array Handle: 0x0025 + Error Information Handle: Not Provided + Total Width: 72 bits + Data Width: 64 bits + Size: 32768 MB + Form Factor: DIMM + Set: 1 + Locator: A1 + Bank Locator: Not Specified + Type: DDR4 + Type Detail: Synchronous Registered (Buffered) + Speed: 2933 MT/s + Manufacturer: Samsung + Serial Number: 5E6F7A8B + Asset Tag: Not Specified + Part Number: M393A4K40CB2-CVF + Rank: 2 + Configured Memory Speed: 2400 MT/s diff --git a/audit/internal/collector/testdata/dmidecode_type1_dell.txt b/audit/internal/collector/testdata/dmidecode_type1_dell.txt new file mode 100644 index 0000000..3054ae5 --- /dev/null +++ b/audit/internal/collector/testdata/dmidecode_type1_dell.txt @@ -0,0 +1,14 @@ +# dmidecode 3.2 +Getting SMBIOS data from sysfs. +SMBIOS 3.1.0 present. + +Handle 0x0100, DMI type 1, 27 bytes +System Information + Manufacturer: Dell Inc. + Product Name: PowerEdge R740xd + Version: Not Specified + Serial Number: 7SG9F63 + UUID: b1c2d3e4-f5a6-7890-bcde-f12345678901 + Wake-up Type: Power Switch + SKU Number: SKU=NotProvided;ModelName=PowerEdge R740xd + Family: PowerEdge diff --git a/audit/internal/collector/testdata/dmidecode_type1_hpe.txt b/audit/internal/collector/testdata/dmidecode_type1_hpe.txt new file mode 100644 index 0000000..4ef14aa --- /dev/null +++ b/audit/internal/collector/testdata/dmidecode_type1_hpe.txt @@ -0,0 +1,14 @@ +# dmidecode 3.3 +Getting SMBIOS data from sysfs. +SMBIOS 3.1.0 present. + +Handle 0x008E, DMI type 1, 27 bytes +System Information + Manufacturer: HPE + Product Name: ProLiant DL380 Gen10 + Version: Not Specified + Serial Number: CZJ9320CXN + UUID: c2d3e4f5-a6b7-8901-cdef-012345678902 + Wake-up Type: Power Switch + SKU Number: 868703-B21 + Family: ProLiant diff --git a/audit/internal/collector/testdata/dmidecode_type1_supermicro.txt b/audit/internal/collector/testdata/dmidecode_type1_supermicro.txt new file mode 100644 index 0000000..a92f8ed --- /dev/null +++ b/audit/internal/collector/testdata/dmidecode_type1_supermicro.txt @@ -0,0 +1,14 @@ +# dmidecode 3.1 +Getting SMBIOS data from sysfs. +SMBIOS 2.8 present. + +Handle 0x0001, DMI type 1, 27 bytes +System Information + Manufacturer: Supermicro + Product Name: SYS-6028R-WTR + Version: 0123456789 + Serial Number: S214726X2A36789 + UUID: d3e4f5a6-b7c8-9012-def0-123456789003 + Wake-up Type: Power Switch + SKU Number: Default string + Family: Default string diff --git a/audit/internal/collector/testdata/dmidecode_type2_dell.txt b/audit/internal/collector/testdata/dmidecode_type2_dell.txt new file mode 100644 index 0000000..8cbe979 --- /dev/null +++ b/audit/internal/collector/testdata/dmidecode_type2_dell.txt @@ -0,0 +1,10 @@ +# dmidecode 3.2 +Getting SMBIOS data from sysfs. +SMBIOS 3.1.0 present. + +Handle 0x0200, DMI type 2, 8 bytes +Base Board Information + Manufacturer: Dell Inc. + Product Name: 0F9N89 + Version: A00 + Serial Number: 7SG9F63 diff --git a/audit/internal/collector/testdata/dmidecode_type2_hpe.txt b/audit/internal/collector/testdata/dmidecode_type2_hpe.txt new file mode 100644 index 0000000..a41b9fb --- /dev/null +++ b/audit/internal/collector/testdata/dmidecode_type2_hpe.txt @@ -0,0 +1,19 @@ +# dmidecode 3.3 +Getting SMBIOS data from sysfs. +SMBIOS 3.1.0 present. + +Handle 0x00A4, DMI type 2, 15 bytes +Base Board Information + Manufacturer: HPE + Product Name: ProLiant DL380 Gen10 + Version: Not Specified + Serial Number: CZJ9320CXN + Asset Tag: CZJ9320CXN + Features: + Board is a hosting board + Board is removable + Board is replaceable + Location In Chassis: Not Specified + Chassis Handle: 0x0000 + Type: Motherboard + Contained Object Handles: 0 diff --git a/audit/internal/collector/testdata/dmidecode_type2_supermicro.txt b/audit/internal/collector/testdata/dmidecode_type2_supermicro.txt new file mode 100644 index 0000000..b28f7af --- /dev/null +++ b/audit/internal/collector/testdata/dmidecode_type2_supermicro.txt @@ -0,0 +1,18 @@ +# dmidecode 3.1 +Getting SMBIOS data from sysfs. +SMBIOS 2.8 present. + +Handle 0x0002, DMI type 2, 15 bytes +Base Board Information + Manufacturer: Supermicro + Product Name: X10DRW-i + Version: 1.02 + Serial Number: S214726X2A36789 + Asset Tag: Default string + Features: + Board is a hosting board + Board is replaceable + Location In Chassis: Default string + Chassis Handle: 0x0003 + Type: Motherboard + Contained Object Handles: 0