From 9106b6e3142a23162abe457c3646c534c9398d8e Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Tue, 3 Mar 2026 12:39:14 -0800 Subject: [PATCH 1/5] refactor: table-driven DIMM format parsing with tests Replace the cascading if-else in getDIMMParseInfo and switch in getDIMMSocketSlot with a unified dimmFormats table. Each entry holds pre-compiled regexes, match predicates, and extraction functions. - Fix regex bugs: [\d+] matched single char, not multi-digit numbers - Add descriptive dimmType names (dimmTypeInspurICX, dimmTypeQuantaGNR, etc.) - Add 66 characterization tests covering all 17 formats, ordering edge cases, and multi-digit regression cases Co-Authored-By: Claude Opus 4.6 --- internal/extract/dimm.go | 574 +++++++++++++++---------------- internal/extract/dimm_test.go | 612 ++++++++++++++++++++++++++++++++++ 2 files changed, 884 insertions(+), 302 deletions(-) create mode 100644 internal/extract/dimm_test.go diff --git a/internal/extract/dimm.go b/internal/extract/dimm.go index d235126c..ec94e4e5 100644 --- a/internal/extract/dimm.go +++ b/internal/extract/dimm.go @@ -291,336 +291,306 @@ func deriveDIMMInfoHPE(dimms [][]string, numSockets int, channelsPerSocket int) return derivedFields, nil } -/* -Get DIMM socket and slot from Bank Locator or Locator field from dmidecode. -This method is inherently unreliable/incomplete as each OEM can set -these fields as they see fit. -Returns None when there's no match. -*/ -func getDIMMSocketSlot(dimmType dimmType, reBankLoc *regexp.Regexp, reLoc *regexp.Regexp, bankLocator string, locator string) (socket int, slot int, err error) { - switch dimmType { - case dimmType0: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[3]) - } - return - case dimmType1: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[3]) +type dimmType int + +const ( + dimmTypeUNKNOWN dimmType = iota + dimmTypeInspurICX + dimmTypeQuantaGNR + dimmTypeGenericCPULetterDigit + dimmTypeMCFormat + dimmTypeNodeChannelDimm + dimmTypeSuperMicroSPR + dimmTypePNodeChannelDimm + dimmTypeNodeChannelDimmAlt + dimmTypeSKXSDP + dimmTypeICXSDP + dimmTypeNodeDIMM + dimmTypeGigabyteMilan + dimmTypeNUC + dimmTypeAlderLake + dimmTypeBirchstream + dimmTypeBirchstreamGNRAP + dimmTypeForestCity +) + +// Keep old names as aliases so tests written against the original constants still compile. +const ( + dimmType0 = dimmTypeInspurICX + dimmType1 = dimmTypeGenericCPULetterDigit + dimmType2 = dimmTypeMCFormat + dimmType3 = dimmTypeNodeChannelDimm + dimmType4 = dimmTypePNodeChannelDimm + dimmType5 = dimmTypeNodeChannelDimmAlt + dimmType6 = dimmTypeSKXSDP + dimmType7 = dimmTypeICXSDP + dimmType8 = dimmTypeNodeDIMM + dimmType9 = dimmTypeGigabyteMilan + dimmType10 = dimmTypeNUC + dimmType11 = dimmTypeAlderLake + dimmType12 = dimmTypeSuperMicroSPR + dimmType13 = dimmTypeBirchstream + dimmType14 = dimmTypeBirchstreamGNRAP + dimmType15 = dimmTypeForestCity + dimmType16 = dimmTypeQuantaGNR +) + +// dimmFormat defines how to identify and extract socket/slot for a DIMM format. +type dimmFormat struct { + name string + dType dimmType + bankLocPat *regexp.Regexp // nil if bank locator not used for matching + locPat *regexp.Regexp // nil if locator not used for matching + matchBoth bool // true = both patterns must match + // extractFunc extracts socket and slot from regex matches. + // bankLocMatch/locMatch are nil when the corresponding pattern is nil or didn't match. + extractFunc func(bankLocMatch, locMatch []string) (socket, slot int, err error) +} + +// dimmFormats is the ordered list of DIMM format definitions. Order matters: +// more specific patterns must appear before more general ones. +var dimmFormats = []dimmFormat{ + { + // Inspur ICX 2s system — must be before GenericCPULetterDigit to differentiate + name: "Inspur ICX", + dType: dimmTypeInspurICX, + locPat: regexp.MustCompile(`CPU([0-9])_C([0-9])D([0-9])`), + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) + slot, _ = strconv.Atoi(locMatch[3]) return - } - case dimmType2: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[3]) + }, + }, + { + // Quanta GNR — must match BOTH bank locator and locator. + // Explicitly excludes Dimm0; must be before NodeChannelDimmAlt (type5). + name: "Quanta GNR", + dType: dimmTypeQuantaGNR, + bankLocPat: regexp.MustCompile(`_Node(\d+)_Channel(\d+)_Dimm([1-2])\b`), + locPat: regexp.MustCompile(`CPU(\d+)_([A-Z])([1-2])\b`), + matchBoth: true, + extractFunc: func(bankLocMatch, _ []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(bankLocMatch[1]) + slot, _ = strconv.Atoi(bankLocMatch[3]) + slot -= 1 return - } - case dimmType3: - match := reBankLoc.FindStringSubmatch(bankLocator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[3]) + }, + }, + { + name: "Generic CPU_Letter_Digit", + dType: dimmTypeGenericCPULetterDigit, + locPat: regexp.MustCompile(`CPU([0-9])_([A-Z])([0-9])`), + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) + slot, _ = strconv.Atoi(locMatch[3]) return - } - case dimmType4: - match := reBankLoc.FindStringSubmatch(bankLocator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[4]) + }, + }, + { + name: "MC Format", + dType: dimmTypeMCFormat, + locPat: regexp.MustCompile(`CPU([0-9])_MC._DIMM_([A-Z])([0-9])`), + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) + slot, _ = strconv.Atoi(locMatch[3]) return - } - case dimmType5: - match := reBankLoc.FindStringSubmatch(bankLocator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[3]) + }, + }, + { + name: "NODE CHANNEL DIMM", + dType: dimmTypeNodeChannelDimm, + bankLocPat: regexp.MustCompile(`NODE ([0-9]) CHANNEL ([0-9]) DIMM ([0-9])`), + extractFunc: func(bankLocMatch, _ []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(bankLocMatch[1]) + slot, _ = strconv.Atoi(bankLocMatch[3]) return - } - case dimmType6: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) + }, + }, + { + // SuperMicro X13DET-B (SPR) / X11DPT-B (CLX). + // Must be before PNodeChannelDimm because that pattern also matches, but bank loc data is invalid. + name: "SuperMicro SPR", + dType: dimmTypeSuperMicroSPR, + locPat: regexp.MustCompile(`P([1,2])-DIMM([A-L])([1,2])`), + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) socket -= 1 - slot, _ = strconv.Atoi(match[3]) + slot, _ = strconv.Atoi(locMatch[3]) slot -= 1 return - } - case dimmType7: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[3]) + }, + }, + { + name: "P_Node_Channel_Dimm", + dType: dimmTypePNodeChannelDimm, + bankLocPat: regexp.MustCompile(`P([0-9])_Node([0-9])_Channel([0-9])_Dimm([0-9])`), + extractFunc: func(bankLocMatch, _ []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(bankLocMatch[1]) + slot, _ = strconv.Atoi(bankLocMatch[4]) + return + }, + }, + { + name: "_Node_Channel_Dimm", + dType: dimmTypeNodeChannelDimmAlt, + bankLocPat: regexp.MustCompile(`_Node([0-9])_Channel([0-9])_Dimm([0-9])`), + extractFunc: func(bankLocMatch, _ []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(bankLocMatch[1]) + slot, _ = strconv.Atoi(bankLocMatch[3]) + return + }, + }, + { + // SKX SDP: CPU[1-4]_DIMM_[A-Z][1-2] with NODE [1-8] + name: "SKX SDP", + dType: dimmTypeSKXSDP, + locPat: regexp.MustCompile(`CPU([1-4])_DIMM_([A-Z])([1-2])`), + bankLocPat: regexp.MustCompile(`NODE ([1-8])`), + matchBoth: true, + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) + socket -= 1 + slot, _ = strconv.Atoi(locMatch[3]) slot -= 1 return - } - case dimmType8: - match := reBankLoc.FindStringSubmatch(bankLocator) - if match != nil { - match2 := reLoc.FindStringSubmatch(locator) - if match2 != nil { - socket, _ = strconv.Atoi(match[1]) - socket -= 1 - slot, _ = strconv.Atoi(match2[2]) - slot -= 1 - return - } - } - case dimmType9: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[2]) + }, + }, + { + // ICX SDP: CPU[0-7]_DIMM_[A-Z][1-2] with NODE [0-9]+ + name: "ICX SDP", + dType: dimmTypeICXSDP, + locPat: regexp.MustCompile(`CPU([0-7])_DIMM_([A-Z])([1-2])`), + bankLocPat: regexp.MustCompile(`NODE ([0-9]+)`), + matchBoth: true, + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) + slot, _ = strconv.Atoi(locMatch[3]) + slot -= 1 return - } - case dimmType10: - match := reBankLoc.FindStringSubmatch(bankLocator) - if match != nil { - socket = 0 - slot, _ = strconv.Atoi(match[2]) + }, + }, + { + // NODE n + DIMM_Xn (both must match) + name: "NODE DIMM", + dType: dimmTypeNodeDIMM, + bankLocPat: regexp.MustCompile(`NODE ([1-9]\d*)`), + locPat: regexp.MustCompile(`DIMM_([A-Z])([1-9]\d*)`), + matchBoth: true, + extractFunc: func(bankLocMatch, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(bankLocMatch[1]) + socket -= 1 + slot, _ = strconv.Atoi(locMatch[2]) + slot -= 1 return - } - case dimmType11: - match := reLoc.FindStringSubmatch(locator) - if match != nil { + }, + }, + { + // Gigabyte Milan: DIMM_P[0-1]_[A-Z][0-1] + name: "Gigabyte Milan", + dType: dimmTypeGigabyteMilan, + locPat: regexp.MustCompile(`DIMM_P([0-1])_[A-Z]([0-1])`), + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) + slot, _ = strconv.Atoi(locMatch[2]) + return + }, + }, + { + // NUC: CHANNEL [A-D] DIMM[0-9] + name: "NUC SODIMM", + dType: dimmTypeNUC, + bankLocPat: regexp.MustCompile(`CHANNEL ([A-D]) DIMM([0-9])`), + extractFunc: func(bankLocMatch, _ []string) (socket, slot int, err error) { socket = 0 - slot, _ = strconv.Atoi(match[2]) + slot, _ = strconv.Atoi(bankLocMatch[2]) return - } - case dimmType12: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - socket = socket - 1 - slot, _ = strconv.Atoi(match[3]) - slot = slot - 1 + }, + }, + { + // Alder Lake Client Desktop: Controller[0-1]-Channel*-DIMM[0-1] + name: "Alder Lake", + dType: dimmTypeAlderLake, + locPat: regexp.MustCompile(`Controller([0-1]).*DIMM([0-1])`), + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket = 0 + slot, _ = strconv.Atoi(locMatch[2]) return - } - case dimmType13: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[3]) - slot = slot - 1 + }, + }, + { + // Birchstream: CPU[0-9]_DIMM_[A-H][1-2] + name: "Birchstream", + dType: dimmTypeBirchstream, + locPat: regexp.MustCompile(`CPU(\d)_DIMM_([A-H])([1-2])`), + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) + slot, _ = strconv.Atoi(locMatch[3]) + slot -= 1 return - } - case dimmType14: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) + }, + }, + { + // Birchstream Granite Rapids AP/X3: CPU[0-9]_DIMM_[A-L] (no slot digit) + name: "Birchstream GNR AP/X3", + dType: dimmTypeBirchstreamGNRAP, + locPat: regexp.MustCompile(`CPU(\d)_DIMM_([A-L])`), + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) slot = 0 return - } - case dimmType15: - match := reLoc.FindStringSubmatch(locator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[3]) - return - } - case dimmType16: - match := reBankLoc.FindStringSubmatch(bankLocator) - if match != nil { - socket, _ = strconv.Atoi(match[1]) - slot, _ = strconv.Atoi(match[3]) - slot -= 1 + }, + }, + { + // Forest City platform for SRF and GNR: CPU[0-9] CH[0-7]/D[0-1] + name: "Forest City SRF/GNR", + dType: dimmTypeForestCity, + locPat: regexp.MustCompile(`CPU(\d) CH([0-7])/D([0-1])`), + extractFunc: func(_, locMatch []string) (socket, slot int, err error) { + socket, _ = strconv.Atoi(locMatch[1]) + slot, _ = strconv.Atoi(locMatch[3]) return - } - } - err = fmt.Errorf("unrecognized bank locator and/or locator in dimm info: %s %s", bankLocator, locator) - return + }, + }, } -type dimmType int - -const ( - dimmTypeUNKNOWN = -1 - dimmType0 dimmType = iota - dimmType1 - dimmType2 - dimmType3 - dimmType4 - dimmType5 - dimmType6 - dimmType7 - dimmType8 - dimmType9 - dimmType10 - dimmType11 - dimmType12 - dimmType13 - dimmType14 - dimmType15 - dimmType16 -) - +// getDIMMParseInfo identifies the DIMM format from a representative bank locator and locator string. func getDIMMParseInfo(bankLocator string, locator string) (dt dimmType, reBankLoc *regexp.Regexp, reLoc *regexp.Regexp) { - dt = dimmTypeUNKNOWN - // Inspur ICX 2s system - // Needs to be before next regex pattern to differentiate - reLoc = regexp.MustCompile(`CPU([0-9])_C([0-9])D([0-9])`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType0 - return - } - - // explicitly exclude Dimm0 as the pattern we're looking for is Dimm1 or Dimm2 - // must match both Bank Locator and Locator to be considered dimmType16 - // seen on Quanta GNR - reBankLoc = regexp.MustCompile(`_Node([\d+])_Channel([\d+])_Dimm([1-2])\b`) - reLoc = regexp.MustCompile(`CPU([\d+])_([A-Z])([1-2])\b`) - if reBankLoc.FindStringSubmatch(bankLocator) != nil && reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType16 - return + for _, f := range dimmFormats { + bankLocOK := f.bankLocPat == nil || f.bankLocPat.FindStringSubmatch(bankLocator) != nil + locOK := f.locPat == nil || f.locPat.FindStringSubmatch(locator) != nil + if f.matchBoth { + if bankLocOK && locOK { + return f.dType, f.bankLocPat, f.locPat + } + } else if f.bankLocPat != nil && bankLocOK { + return f.dType, f.bankLocPat, f.locPat + } else if f.locPat != nil && locOK { + return f.dType, f.bankLocPat, f.locPat + } } + return dimmTypeUNKNOWN, nil, nil +} - reLoc = regexp.MustCompile(`CPU([0-9])_([A-Z])([0-9])`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType1 - return - } - reLoc = regexp.MustCompile(`CPU([0-9])_MC._DIMM_([A-Z])([0-9])`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType2 - return - } - reBankLoc = regexp.MustCompile(`NODE ([0-9]) CHANNEL ([0-9]) DIMM ([0-9])`) - if reBankLoc.FindStringSubmatch(bankLocator) != nil { - dt = dimmType3 - return - } - /* Added for SuperMicro X13DET-B (SPR). Must be before Type4 because Type4 matches, but data in BankLoc is invalid. - * Locator: P1-DIMMA1 - * Locator: P1-DIMMB1 - * Locator: P1-DIMMC1 - * ... - * Locator: P2-DIMMA1 - * ... - * Note: also matches SuperMicro X11DPT-B (CLX) - */ - reLoc = regexp.MustCompile(`P([1,2])-DIMM([A-L])([1,2])`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType12 - return - } - reBankLoc = regexp.MustCompile(`P([0-9])_Node([0-9])_Channel([0-9])_Dimm([0-9])`) - if reBankLoc.FindStringSubmatch(bankLocator) != nil { - dt = dimmType4 - return - } - reBankLoc = regexp.MustCompile(`_Node([0-9])_Channel([0-9])_Dimm([0-9])`) - if reBankLoc.FindStringSubmatch(bankLocator) != nil { - dt = dimmType5 - return - } - /* SKX SDP - * Locator: CPU1_DIMM_A1, Bank Locator: NODE 1 - * Locator: CPU1_DIMM_A2, Bank Locator: NODE 1 - */ - reLoc = regexp.MustCompile(`CPU([1-4])_DIMM_([A-Z])([1-2])`) - if reLoc.FindStringSubmatch(locator) != nil { - reBankLoc = regexp.MustCompile(`NODE ([1-8])`) - if reBankLoc.FindStringSubmatch(bankLocator) != nil { - dt = dimmType6 - return +// getDIMMSocketSlot extracts socket and slot from bank locator and locator strings +// using the format identified by getDIMMParseInfo. +func getDIMMSocketSlot(dt dimmType, reBankLoc *regexp.Regexp, reLoc *regexp.Regexp, bankLocator string, locator string) (socket int, slot int, err error) { + for _, f := range dimmFormats { + if f.dType != dt { + continue } - } - /* ICX SDP - * Locator: CPU0_DIMM_A1, Bank Locator: NODE 0 - * Locator: CPU0_DIMM_A2, Bank Locator: NODE 0 - */ - reLoc = regexp.MustCompile(`CPU([0-7])_DIMM_([A-Z])([1-2])`) - if reLoc.FindStringSubmatch(locator) != nil { - reBankLoc = regexp.MustCompile(`NODE ([0-9]+)`) - if reBankLoc.FindStringSubmatch(bankLocator) != nil { - dt = dimmType7 - return + var bankLocMatch, locMatch []string + if f.bankLocPat != nil { + bankLocMatch = reBankLoc.FindStringSubmatch(bankLocator) } - } - reBankLoc = regexp.MustCompile(`NODE ([1-9]\d*)`) - if reBankLoc.FindStringSubmatch(bankLocator) != nil { - reLoc = regexp.MustCompile(`DIMM_([A-Z])([1-9]\d*)`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType8 - return + if f.locPat != nil { + locMatch = reLoc.FindStringSubmatch(locator) } + if bankLocMatch != nil || locMatch != nil { + return f.extractFunc(bankLocMatch, locMatch) + } + break } - /* GIGABYTE MILAN - * Locator: DIMM_P0_A0, Bank Locator: BANK 0 - * Locator: DIMM_P0_A1, Bank Locator: BANK 1 - * Locator: DIMM_P0_B0, Bank Locator: BANK 0 - * ... - * Locator: DIMM_P1_I0, Bank Locator: BANK 0 - */ - reLoc = regexp.MustCompile(`DIMM_P([0-1])_[A-Z]([0-1])`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType9 - return - } - /* my NUC - * Locator: SODIMM0, Bank Locator: CHANNEL A DIMM0 - * Locator: SODIMM1, Bank Locator: CHANNEL B DIMM0 - */ - reBankLoc = regexp.MustCompile(`CHANNEL ([A-D]) DIMM([0-9])`) - if reBankLoc.FindStringSubmatch(bankLocator) != nil { - dt = dimmType10 - return - } - /* Alder Lake Client Desktop - * Locator: Controller0-ChannelA-DIMM0, Bank Locator: BANK 0 - * Locator: Controller1-ChannelA-DIMM0, Bank Locator: BANK 0 - */ - reLoc = regexp.MustCompile(`Controller([0-1]).*DIMM([0-1])`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType11 - return - } - /* BIRCHSTREAM - * LOCATOR BANK LOCATOR - * CPU0_DIMM_A1 BANK 0 - * CPU0_DIMM_A2 BANK 0 - * CPU0_DIMM_B1 BANK 1 - * CPU0_DIMM_B2 BANK 1 - * ... - * CPU0_DIMM_H2 BANK 7 - */ - reLoc = regexp.MustCompile(`CPU([\d])_DIMM_([A-H])([1-2])`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType13 - return - } - /* BIRCHSTREAM GRANITE RAPIDS AP/X3 - * LOCATOR BANK LOCATOR - * CPU0_DIMM_A BANK 0 - * CPU0_DIMM_B BANK 1 - * CPU0_DIMM_C BANK 2 - * CPU0_DIMM_D BANK 3 - * ... - * CPU0_DIMM_L BANK 11 - */ - reLoc = regexp.MustCompile(`CPU([\d])_DIMM_([A-L])`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType14 - return - } - /* FOREST CITY PLATFORM FOR SRF AND GNR - * LOCATOR BANK LOCATOR - * CPU0 CH0/D0 BANK 0 - * CPU0 CH0/D1 BANK 0 - * CPU0 CH1/D0 BANK 1 - * CPU0 CH1/D1 BANK 1 - * ... - * CPU0 CH7/D1 BANK 7 - */ - reLoc = regexp.MustCompile(`CPU([\d]) CH([0-7])/D([0-1])`) - if reLoc.FindStringSubmatch(locator) != nil { - dt = dimmType15 - return - } + err = fmt.Errorf("unrecognized bank locator and/or locator in dimm info: %s %s", bankLocator, locator) return } diff --git a/internal/extract/dimm_test.go b/internal/extract/dimm_test.go new file mode 100644 index 00000000..bd731dd7 --- /dev/null +++ b/internal/extract/dimm_test.go @@ -0,0 +1,612 @@ +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +package extract + +import ( + "testing" +) + +func TestGetDIMMParseInfo(t *testing.T) { + tests := []struct { + name string + bankLocator string + locator string + expectedType dimmType + }{ + // --- One positive case per dimmType --- + { + name: "dimmType0 - Inspur ICX", + bankLocator: "Not Specified", + locator: "CPU0_C0D0", + expectedType: dimmType0, + }, + { + name: "dimmType1 - Generic CPU_Letter_Digit", + bankLocator: "Not Specified", + locator: "CPU0_A0", + expectedType: dimmType1, + }, + { + name: "dimmType2 - MC format", + bankLocator: "Not Specified", + locator: "CPU0_MC0_DIMM_A0", + expectedType: dimmType2, + }, + { + name: "dimmType3 - NODE CHANNEL DIMM", + bankLocator: "NODE 0 CHANNEL 0 DIMM 0", + locator: "DIMM0", + expectedType: dimmType3, + }, + { + name: "dimmType4 - P_Node_Channel_Dimm", + bankLocator: "P0_Node0_Channel0_Dimm0", + locator: "DIMM0", + expectedType: dimmType4, + }, + { + name: "dimmType5 - _Node_Channel_Dimm", + bankLocator: "_Node0_Channel0_Dimm0", + locator: "DIMM0", + expectedType: dimmType5, + }, + { + name: "dimmType6 - SKX SDP (1-indexed)", + bankLocator: "NODE 1", + locator: "CPU1_DIMM_A1", + expectedType: dimmType6, + }, + { + name: "dimmType7 - ICX SDP (0-indexed)", + bankLocator: "NODE 0", + locator: "CPU0_DIMM_A1", + expectedType: dimmType7, + }, + { + name: "dimmType8 - NODE n + DIMM_Xn", + bankLocator: "NODE 1", + locator: "DIMM_A1", + expectedType: dimmType8, + }, + { + name: "dimmType9 - Gigabyte Milan DIMM_Pn_Xn", + bankLocator: "BANK 0", + locator: "DIMM_P0_A0", + expectedType: dimmType9, + }, + { + name: "dimmType10 - NUC SODIMM", + bankLocator: "CHANNEL A DIMM0", + locator: "SODIMM0", + expectedType: dimmType10, + }, + { + name: "dimmType11 - Alder Lake Controller", + bankLocator: "BANK 0", + locator: "Controller0-ChannelA-DIMM0", + expectedType: dimmType11, + }, + { + name: "dimmType12 - SuperMicro SPR P1-DIMMA1", + bankLocator: "Not Specified", + locator: "P1-DIMMA1", + expectedType: dimmType12, + }, + { + name: "dimmType13 - Birchstream CPU0_DIMM_A1", + bankLocator: "BANK 0", + locator: "CPU0_DIMM_A1", + expectedType: dimmType13, + }, + { + name: "dimmType14 - GNR AP/X3 CPU0_DIMM_A", + bankLocator: "BANK 0", + locator: "CPU0_DIMM_A", + expectedType: dimmType14, + }, + { + name: "dimmType15 - Forest City CPU0 CH0/D0", + bankLocator: "BANK 0", + locator: "CPU0 CH0/D0", + expectedType: dimmType15, + }, + { + name: "dimmType16 - Quanta GNR", + bankLocator: "_Node0_Channel0_Dimm1", + locator: "CPU0_A1", + expectedType: dimmType16, + }, + // --- Ordering-sensitive / ambiguous cases --- + { + name: "ordering: CPU0_C0D0 matches type0, not type1", + bankLocator: "Not Specified", + locator: "CPU0_C0D0", + expectedType: dimmType0, + }, + { + name: "ordering: P1-DIMMA1 matches type12, not type1", + bankLocator: "Not Specified", + locator: "P1-DIMMA1", + expectedType: dimmType12, + }, + { + name: "ordering: Quanta GNR matches type16, not type5", + bankLocator: "_Node0_Channel0_Dimm1", + locator: "CPU0_A1", + expectedType: dimmType16, + }, + { + // type16 requires Dimm[1-2]; Dimm0 doesn't match type16. + // CPU0_A1 matches type1's CPU([0-9])_([A-Z])([0-9]) before reaching type5's bank locator check. + name: "ordering: _Node0_Channel0_Dimm0 with CPU0_A1 falls to type1", + bankLocator: "_Node0_Channel0_Dimm0", + locator: "CPU0_A1", + expectedType: dimmType1, + }, + { + // type6 requires CPU[1-4] and NODE [1-8]; type7 requires CPU[0-7] and NODE [0-9]+ + name: "ordering: SKX SDP CPU1_DIMM_A1 NODE 1 matches type6", + bankLocator: "NODE 1", + locator: "CPU1_DIMM_A1", + expectedType: dimmType6, + }, + { + // CPU0 doesn't match type6's CPU[1-4], so falls to type7 + name: "ordering: ICX SDP CPU0_DIMM_A1 NODE 0 matches type7", + bankLocator: "NODE 0", + locator: "CPU0_DIMM_A1", + expectedType: dimmType7, + }, + { + // Birchstream CPU0_DIMM_A1 with non-NODE bank loc → not type6/7, falls to type13 + name: "ordering: Birchstream CPU0_DIMM_A1 BANK 0 matches type13, not type7", + bankLocator: "BANK 0", + locator: "CPU0_DIMM_A1", + expectedType: dimmType13, + }, + // --- Multi-digit values (regression tests for [\d+] bug fix) --- + { + name: "type16 - multi-digit node number", + bankLocator: "_Node10_Channel0_Dimm1", + locator: "CPU0_A1", + expectedType: dimmType16, + }, + { + name: "type16 - multi-digit channel number", + bankLocator: "_Node0_Channel12_Dimm2", + locator: "CPU10_B2", + expectedType: dimmType16, + }, + // --- Unknown / no match --- + { + name: "unknown format returns dimmTypeUNKNOWN", + bankLocator: "UNKNOWN", + locator: "UNKNOWN", + expectedType: dimmTypeUNKNOWN, + }, + { + name: "empty strings return dimmTypeUNKNOWN", + bankLocator: "", + locator: "", + expectedType: dimmTypeUNKNOWN, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dt, _, _ := getDIMMParseInfo(tt.bankLocator, tt.locator) + if dt != tt.expectedType { + t.Errorf("getDIMMParseInfo(%q, %q) = dimmType %d, want dimmType %d", + tt.bankLocator, tt.locator, dt, tt.expectedType) + } + }) + } +} + +func TestGetDIMMSocketSlot(t *testing.T) { + tests := []struct { + name string + bankLocator string + locator string + expectedSocket int + expectedSlot int + expectErr bool + }{ + // dimmType0: reLoc match[1]=socket, match[3]=slot + { + name: "type0 - Inspur ICX CPU0_C0D0", + bankLocator: "Not Specified", + locator: "CPU0_C0D0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type0 - Inspur ICX CPU1_C2D1", + bankLocator: "Not Specified", + locator: "CPU1_C2D1", + expectedSocket: 1, + expectedSlot: 1, + }, + // dimmType1: reLoc match[1]=socket, match[3]=slot + { + name: "type1 - CPU0_A0", + bankLocator: "Not Specified", + locator: "CPU0_A0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type1 - CPU1_B3", + bankLocator: "Not Specified", + locator: "CPU1_B3", + expectedSocket: 1, + expectedSlot: 3, + }, + // dimmType2: reLoc match[1]=socket, match[3]=slot + { + name: "type2 - CPU0_MC0_DIMM_A0", + bankLocator: "Not Specified", + locator: "CPU0_MC0_DIMM_A0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type2 - CPU1_MC1_DIMM_B2", + bankLocator: "Not Specified", + locator: "CPU1_MC1_DIMM_B2", + expectedSocket: 1, + expectedSlot: 2, + }, + // dimmType3: reBankLoc match[1]=socket, match[3]=slot + { + name: "type3 - NODE 0 CHANNEL 0 DIMM 0", + bankLocator: "NODE 0 CHANNEL 0 DIMM 0", + locator: "DIMM0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type3 - NODE 1 CHANNEL 3 DIMM 1", + bankLocator: "NODE 1 CHANNEL 3 DIMM 1", + locator: "DIMM1", + expectedSocket: 1, + expectedSlot: 1, + }, + // dimmType4: reBankLoc match[1]=socket, match[4]=slot + { + name: "type4 - P0_Node0_Channel0_Dimm0", + bankLocator: "P0_Node0_Channel0_Dimm0", + locator: "DIMM0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type4 - P1_Node1_Channel2_Dimm1", + bankLocator: "P1_Node1_Channel2_Dimm1", + locator: "DIMM1", + expectedSocket: 1, + expectedSlot: 1, + }, + // dimmType5: reBankLoc match[1]=socket, match[3]=slot + { + name: "type5 - _Node0_Channel0_Dimm0", + bankLocator: "_Node0_Channel0_Dimm0", + locator: "DIMM0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type5 - _Node1_Channel2_Dimm1", + bankLocator: "_Node1_Channel2_Dimm1", + locator: "DIMM1", + expectedSocket: 1, + expectedSlot: 1, + }, + // dimmType6: reLoc match[1]=socket-1, match[3]=slot-1 + { + name: "type6 - SKX SDP CPU1_DIMM_A1 NODE 1", + bankLocator: "NODE 1", + locator: "CPU1_DIMM_A1", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type6 - SKX SDP CPU2_DIMM_B2 NODE 2", + bankLocator: "NODE 2", + locator: "CPU2_DIMM_B2", + expectedSocket: 1, + expectedSlot: 1, + }, + // dimmType7: reLoc match[1]=socket, match[3]=slot-1 + { + name: "type7 - ICX SDP CPU0_DIMM_A1 NODE 0", + bankLocator: "NODE 0", + locator: "CPU0_DIMM_A1", + expectedSocket: 0, + expectedSlot: 0, + }, + { + // CPU0 doesn't match type6's CPU[1-4], so falls to type7's CPU[0-7] + name: "type7 - ICX SDP CPU0_DIMM_C2 NODE 0", + bankLocator: "NODE 0", + locator: "CPU0_DIMM_C2", + expectedSocket: 0, + expectedSlot: 1, + }, + // dimmType8: reBankLoc match[1]=socket-1, reLoc match[2]=slot-1 + { + name: "type8 - NODE 1 DIMM_A1", + bankLocator: "NODE 1", + locator: "DIMM_A1", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type8 - NODE 2 DIMM_B3", + bankLocator: "NODE 2", + locator: "DIMM_B3", + expectedSocket: 1, + expectedSlot: 2, + }, + // dimmType9: reLoc match[1]=socket, match[2]=slot + { + name: "type9 - Gigabyte Milan DIMM_P0_A0", + bankLocator: "BANK 0", + locator: "DIMM_P0_A0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type9 - Gigabyte Milan DIMM_P1_B1", + bankLocator: "BANK 0", + locator: "DIMM_P1_B1", + expectedSocket: 1, + expectedSlot: 1, + }, + // dimmType10: socket=0, reBankLoc match[2]=slot + { + name: "type10 - NUC CHANNEL A DIMM0", + bankLocator: "CHANNEL A DIMM0", + locator: "SODIMM0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type10 - NUC CHANNEL B DIMM1", + bankLocator: "CHANNEL B DIMM1", + locator: "SODIMM1", + expectedSocket: 0, + expectedSlot: 1, + }, + // dimmType11: socket=0, reLoc match[2]=slot + { + name: "type11 - Alder Lake Controller0-ChannelA-DIMM0", + bankLocator: "BANK 0", + locator: "Controller0-ChannelA-DIMM0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type11 - Alder Lake Controller1-ChannelA-DIMM1", + bankLocator: "BANK 0", + locator: "Controller1-ChannelA-DIMM1", + expectedSocket: 0, + expectedSlot: 1, + }, + // dimmType12: reLoc match[1]=socket-1, match[3]=slot-1 + { + name: "type12 - SuperMicro P1-DIMMA1", + bankLocator: "Not Specified", + locator: "P1-DIMMA1", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type12 - SuperMicro P2-DIMMB2", + bankLocator: "Not Specified", + locator: "P2-DIMMB2", + expectedSocket: 1, + expectedSlot: 1, + }, + // dimmType13: reLoc match[1]=socket, match[3]=slot-1 + { + name: "type13 - Birchstream CPU0_DIMM_A1", + bankLocator: "BANK 0", + locator: "CPU0_DIMM_A1", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type13 - Birchstream CPU1_DIMM_H2", + bankLocator: "BANK 7", + locator: "CPU1_DIMM_H2", + expectedSocket: 1, + expectedSlot: 1, + }, + // dimmType14: reLoc match[1]=socket, slot=0 + { + name: "type14 - GNR AP/X3 CPU0_DIMM_A", + bankLocator: "BANK 0", + locator: "CPU0_DIMM_A", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type14 - GNR AP/X3 CPU1_DIMM_L", + bankLocator: "BANK 11", + locator: "CPU1_DIMM_L", + expectedSocket: 1, + expectedSlot: 0, + }, + // dimmType15: reLoc match[1]=socket, match[3]=slot + { + name: "type15 - Forest City CPU0 CH0/D0", + bankLocator: "BANK 0", + locator: "CPU0 CH0/D0", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type15 - Forest City CPU1 CH7/D1", + bankLocator: "BANK 7", + locator: "CPU1 CH7/D1", + expectedSocket: 1, + expectedSlot: 1, + }, + // dimmType16: reBankLoc match[1]=socket, reLoc match[3]=slot-1 + { + name: "type16 - Quanta GNR _Node0_Channel0_Dimm1 CPU0_A1", + bankLocator: "_Node0_Channel0_Dimm1", + locator: "CPU0_A1", + expectedSocket: 0, + expectedSlot: 0, + }, + { + name: "type16 - Quanta GNR _Node1_Channel2_Dimm2 CPU1_B2", + bankLocator: "_Node1_Channel2_Dimm2", + locator: "CPU1_B2", + expectedSocket: 1, + expectedSlot: 1, + }, + // --- Multi-digit regression tests for [\d+] bug fix --- + { + // Socket comes from reBankLoc match[1] (Node number), slot from reBankLoc match[3] (Dimm-1) + name: "type16 - multi-digit node _Node10_Channel0_Dimm1 CPU0_A1", + bankLocator: "_Node10_Channel0_Dimm1", + locator: "CPU0_A1", + expectedSocket: 10, + expectedSlot: 0, + }, + // --- Error case --- + { + name: "unknown format returns error", + bankLocator: "UNKNOWN", + locator: "UNKNOWN", + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dt, reBankLoc, reLoc := getDIMMParseInfo(tt.bankLocator, tt.locator) + socket, slot, err := getDIMMSocketSlot(dt, reBankLoc, reLoc, tt.bankLocator, tt.locator) + if tt.expectErr { + if err == nil { + t.Errorf("expected error for (%q, %q), got socket=%d slot=%d", + tt.bankLocator, tt.locator, socket, slot) + } + return + } + if err != nil { + t.Errorf("unexpected error for (%q, %q): %v", tt.bankLocator, tt.locator, err) + return + } + if socket != tt.expectedSocket { + t.Errorf("getDIMMSocketSlot(%q, %q) socket = %d, want %d", + tt.bankLocator, tt.locator, socket, tt.expectedSocket) + } + if slot != tt.expectedSlot { + t.Errorf("getDIMMSocketSlot(%q, %q) slot = %d, want %d", + tt.bankLocator, tt.locator, slot, tt.expectedSlot) + } + }) + } +} + +func TestDeriveDIMMInfoOther(t *testing.T) { + // Helper to build a minimal DIMM row (only BankLocatorIdx and LocatorIdx matter, + // but SizeIdx is used by the caller; fill enough fields to avoid index panics). + makeDIMMRow := func(bankLoc, loc string) []string { + row := make([]string, DerivedSlotIdx+1) + row[BankLocatorIdx] = bankLoc + row[LocatorIdx] = loc + row[SizeIdx] = "8192 MB" + return row + } + + tests := []struct { + name string + dimms [][]string + channelsPerSocket int + expectedDerived []DerivedDIMMFields + expectErr bool + expectNil bool // nil result, no error (parse failure logged) + }{ + { + name: "type1 - two sockets, two channels each, one slot", + dimms: [][]string{ + makeDIMMRow("Not Specified", "CPU0_A0"), + makeDIMMRow("Not Specified", "CPU0_B0"), + makeDIMMRow("Not Specified", "CPU1_A0"), + makeDIMMRow("Not Specified", "CPU1_B0"), + }, + channelsPerSocket: 2, + expectedDerived: []DerivedDIMMFields{ + {Socket: "0", Channel: "0", Slot: "0"}, + {Socket: "0", Channel: "1", Slot: "0"}, + {Socket: "1", Channel: "0", Slot: "0"}, + {Socket: "1", Channel: "1", Slot: "0"}, + }, + }, + { + name: "type3 - NODE CHANNEL format", + dimms: [][]string{ + makeDIMMRow("NODE 0 CHANNEL 0 DIMM 0", "DIMM0"), + makeDIMMRow("NODE 0 CHANNEL 0 DIMM 1", "DIMM1"), + makeDIMMRow("NODE 0 CHANNEL 1 DIMM 0", "DIMM2"), + makeDIMMRow("NODE 1 CHANNEL 0 DIMM 0", "DIMM3"), + }, + channelsPerSocket: 2, + expectedDerived: []DerivedDIMMFields{ + {Socket: "0", Channel: "0", Slot: "0"}, + {Socket: "0", Channel: "0", Slot: "1"}, + {Socket: "0", Channel: "1", Slot: "0"}, + {Socket: "1", Channel: "0", Slot: "0"}, + }, + }, + { + name: "empty dimms returns error", + dimms: [][]string{}, + expectErr: true, + }, + { + name: "unknown format returns error", + dimms: [][]string{ + makeDIMMRow("UNKNOWN", "UNKNOWN"), + }, + channelsPerSocket: 2, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := deriveDIMMInfoOther(tt.dimms, tt.channelsPerSocket) + if tt.expectErr { + if err == nil { + t.Errorf("expected error, got result: %+v", result) + } + return + } + if tt.expectNil { + if result != nil { + t.Errorf("expected nil result, got: %+v", result) + } + return + } + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + if len(result) != len(tt.expectedDerived) { + t.Fatalf("result length = %d, want %d", len(result), len(tt.expectedDerived)) + } + for i, expected := range tt.expectedDerived { + if result[i] != expected { + t.Errorf("dimm[%d] = %+v, want %+v", i, result[i], expected) + } + } + }) + } +} From 3f21b72377daf5c537d3d96099436af4a415fb8b Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Tue, 3 Mar 2026 12:44:58 -0800 Subject: [PATCH 2/5] formatting Signed-off-by: Harper, Jason M --- internal/extract/dimm.go | 36 +++++++++++++++++------------------ internal/extract/dimm_test.go | 12 ++++++------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/internal/extract/dimm.go b/internal/extract/dimm.go index ec94e4e5..bb54daca 100644 --- a/internal/extract/dimm.go +++ b/internal/extract/dimm.go @@ -352,8 +352,8 @@ type dimmFormat struct { var dimmFormats = []dimmFormat{ { // Inspur ICX 2s system — must be before GenericCPULetterDigit to differentiate - name: "Inspur ICX", - dType: dimmTypeInspurICX, + name: "Inspur ICX", + dType: dimmTypeInspurICX, locPat: regexp.MustCompile(`CPU([0-9])_C([0-9])D([0-9])`), extractFunc: func(_, locMatch []string) (socket, slot int, err error) { socket, _ = strconv.Atoi(locMatch[1]) @@ -377,8 +377,8 @@ var dimmFormats = []dimmFormat{ }, }, { - name: "Generic CPU_Letter_Digit", - dType: dimmTypeGenericCPULetterDigit, + name: "Generic CPU_Letter_Digit", + dType: dimmTypeGenericCPULetterDigit, locPat: regexp.MustCompile(`CPU([0-9])_([A-Z])([0-9])`), extractFunc: func(_, locMatch []string) (socket, slot int, err error) { socket, _ = strconv.Atoi(locMatch[1]) @@ -387,8 +387,8 @@ var dimmFormats = []dimmFormat{ }, }, { - name: "MC Format", - dType: dimmTypeMCFormat, + name: "MC Format", + dType: dimmTypeMCFormat, locPat: regexp.MustCompile(`CPU([0-9])_MC._DIMM_([A-Z])([0-9])`), extractFunc: func(_, locMatch []string) (socket, slot int, err error) { socket, _ = strconv.Atoi(locMatch[1]) @@ -409,8 +409,8 @@ var dimmFormats = []dimmFormat{ { // SuperMicro X13DET-B (SPR) / X11DPT-B (CLX). // Must be before PNodeChannelDimm because that pattern also matches, but bank loc data is invalid. - name: "SuperMicro SPR", - dType: dimmTypeSuperMicroSPR, + name: "SuperMicro SPR", + dType: dimmTypeSuperMicroSPR, locPat: regexp.MustCompile(`P([1,2])-DIMM([A-L])([1,2])`), extractFunc: func(_, locMatch []string) (socket, slot int, err error) { socket, _ = strconv.Atoi(locMatch[1]) @@ -486,8 +486,8 @@ var dimmFormats = []dimmFormat{ }, { // Gigabyte Milan: DIMM_P[0-1]_[A-Z][0-1] - name: "Gigabyte Milan", - dType: dimmTypeGigabyteMilan, + name: "Gigabyte Milan", + dType: dimmTypeGigabyteMilan, locPat: regexp.MustCompile(`DIMM_P([0-1])_[A-Z]([0-1])`), extractFunc: func(_, locMatch []string) (socket, slot int, err error) { socket, _ = strconv.Atoi(locMatch[1]) @@ -508,8 +508,8 @@ var dimmFormats = []dimmFormat{ }, { // Alder Lake Client Desktop: Controller[0-1]-Channel*-DIMM[0-1] - name: "Alder Lake", - dType: dimmTypeAlderLake, + name: "Alder Lake", + dType: dimmTypeAlderLake, locPat: regexp.MustCompile(`Controller([0-1]).*DIMM([0-1])`), extractFunc: func(_, locMatch []string) (socket, slot int, err error) { socket = 0 @@ -519,8 +519,8 @@ var dimmFormats = []dimmFormat{ }, { // Birchstream: CPU[0-9]_DIMM_[A-H][1-2] - name: "Birchstream", - dType: dimmTypeBirchstream, + name: "Birchstream", + dType: dimmTypeBirchstream, locPat: regexp.MustCompile(`CPU(\d)_DIMM_([A-H])([1-2])`), extractFunc: func(_, locMatch []string) (socket, slot int, err error) { socket, _ = strconv.Atoi(locMatch[1]) @@ -531,8 +531,8 @@ var dimmFormats = []dimmFormat{ }, { // Birchstream Granite Rapids AP/X3: CPU[0-9]_DIMM_[A-L] (no slot digit) - name: "Birchstream GNR AP/X3", - dType: dimmTypeBirchstreamGNRAP, + name: "Birchstream GNR AP/X3", + dType: dimmTypeBirchstreamGNRAP, locPat: regexp.MustCompile(`CPU(\d)_DIMM_([A-L])`), extractFunc: func(_, locMatch []string) (socket, slot int, err error) { socket, _ = strconv.Atoi(locMatch[1]) @@ -542,8 +542,8 @@ var dimmFormats = []dimmFormat{ }, { // Forest City platform for SRF and GNR: CPU[0-9] CH[0-7]/D[0-1] - name: "Forest City SRF/GNR", - dType: dimmTypeForestCity, + name: "Forest City SRF/GNR", + dType: dimmTypeForestCity, locPat: regexp.MustCompile(`CPU(\d) CH([0-7])/D([0-1])`), extractFunc: func(_, locMatch []string) (socket, slot int, err error) { socket, _ = strconv.Atoi(locMatch[1]) diff --git a/internal/extract/dimm_test.go b/internal/extract/dimm_test.go index bd731dd7..3dc391f1 100644 --- a/internal/extract/dimm_test.go +++ b/internal/extract/dimm_test.go @@ -526,12 +526,12 @@ func TestDeriveDIMMInfoOther(t *testing.T) { } tests := []struct { - name string - dimms [][]string - channelsPerSocket int - expectedDerived []DerivedDIMMFields - expectErr bool - expectNil bool // nil result, no error (parse failure logged) + name string + dimms [][]string + channelsPerSocket int + expectedDerived []DerivedDIMMFields + expectErr bool + expectNil bool // nil result, no error (parse failure logged) }{ { name: "type1 - two sockets, two channels each, one slot", From 35441a6b8f1e3a964d70e11bd70104f988ed7b7d Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Tue, 3 Mar 2026 12:50:13 -0800 Subject: [PATCH 3/5] chore: use descriptive dimmType names in tests, remove old aliases Update all test references from dimmType0-16 to descriptive names (dimmTypeInspurICX, dimmTypeQuantaGNR, etc.) and remove the backward compatibility aliases from dimm.go. Co-Authored-By: Claude Opus 4.6 --- internal/extract/dimm.go | 21 ---- internal/extract/dimm_test.go | 226 +++++++++++++++++----------------- 2 files changed, 113 insertions(+), 134 deletions(-) diff --git a/internal/extract/dimm.go b/internal/extract/dimm.go index bb54daca..422fafff 100644 --- a/internal/extract/dimm.go +++ b/internal/extract/dimm.go @@ -314,27 +314,6 @@ const ( dimmTypeForestCity ) -// Keep old names as aliases so tests written against the original constants still compile. -const ( - dimmType0 = dimmTypeInspurICX - dimmType1 = dimmTypeGenericCPULetterDigit - dimmType2 = dimmTypeMCFormat - dimmType3 = dimmTypeNodeChannelDimm - dimmType4 = dimmTypePNodeChannelDimm - dimmType5 = dimmTypeNodeChannelDimmAlt - dimmType6 = dimmTypeSKXSDP - dimmType7 = dimmTypeICXSDP - dimmType8 = dimmTypeNodeDIMM - dimmType9 = dimmTypeGigabyteMilan - dimmType10 = dimmTypeNUC - dimmType11 = dimmTypeAlderLake - dimmType12 = dimmTypeSuperMicroSPR - dimmType13 = dimmTypeBirchstream - dimmType14 = dimmTypeBirchstreamGNRAP - dimmType15 = dimmTypeForestCity - dimmType16 = dimmTypeQuantaGNR -) - // dimmFormat defines how to identify and extract socket/slot for a DIMM format. type dimmFormat struct { name string diff --git a/internal/extract/dimm_test.go b/internal/extract/dimm_test.go index 3dc391f1..1e0b396e 100644 --- a/internal/extract/dimm_test.go +++ b/internal/extract/dimm_test.go @@ -16,167 +16,167 @@ func TestGetDIMMParseInfo(t *testing.T) { }{ // --- One positive case per dimmType --- { - name: "dimmType0 - Inspur ICX", + name: "Inspur ICX", bankLocator: "Not Specified", locator: "CPU0_C0D0", - expectedType: dimmType0, + expectedType: dimmTypeInspurICX, }, { - name: "dimmType1 - Generic CPU_Letter_Digit", + name: "Generic CPU_Letter_Digit", bankLocator: "Not Specified", locator: "CPU0_A0", - expectedType: dimmType1, + expectedType: dimmTypeGenericCPULetterDigit, }, { - name: "dimmType2 - MC format", + name: "MC format", bankLocator: "Not Specified", locator: "CPU0_MC0_DIMM_A0", - expectedType: dimmType2, + expectedType: dimmTypeMCFormat, }, { - name: "dimmType3 - NODE CHANNEL DIMM", + name: "NODE CHANNEL DIMM", bankLocator: "NODE 0 CHANNEL 0 DIMM 0", locator: "DIMM0", - expectedType: dimmType3, + expectedType: dimmTypeNodeChannelDimm, }, { - name: "dimmType4 - P_Node_Channel_Dimm", + name: "P_Node_Channel_Dimm", bankLocator: "P0_Node0_Channel0_Dimm0", locator: "DIMM0", - expectedType: dimmType4, + expectedType: dimmTypePNodeChannelDimm, }, { - name: "dimmType5 - _Node_Channel_Dimm", + name: "_Node_Channel_Dimm", bankLocator: "_Node0_Channel0_Dimm0", locator: "DIMM0", - expectedType: dimmType5, + expectedType: dimmTypeNodeChannelDimmAlt, }, { - name: "dimmType6 - SKX SDP (1-indexed)", + name: "SKX SDP (1-indexed)", bankLocator: "NODE 1", locator: "CPU1_DIMM_A1", - expectedType: dimmType6, + expectedType: dimmTypeSKXSDP, }, { - name: "dimmType7 - ICX SDP (0-indexed)", + name: "ICX SDP (0-indexed)", bankLocator: "NODE 0", locator: "CPU0_DIMM_A1", - expectedType: dimmType7, + expectedType: dimmTypeICXSDP, }, { - name: "dimmType8 - NODE n + DIMM_Xn", + name: "NODE n + DIMM_Xn", bankLocator: "NODE 1", locator: "DIMM_A1", - expectedType: dimmType8, + expectedType: dimmTypeNodeDIMM, }, { - name: "dimmType9 - Gigabyte Milan DIMM_Pn_Xn", + name: "Gigabyte Milan DIMM_Pn_Xn", bankLocator: "BANK 0", locator: "DIMM_P0_A0", - expectedType: dimmType9, + expectedType: dimmTypeGigabyteMilan, }, { - name: "dimmType10 - NUC SODIMM", + name: "NUC SODIMM", bankLocator: "CHANNEL A DIMM0", locator: "SODIMM0", - expectedType: dimmType10, + expectedType: dimmTypeNUC, }, { - name: "dimmType11 - Alder Lake Controller", + name: "Alder Lake Controller", bankLocator: "BANK 0", locator: "Controller0-ChannelA-DIMM0", - expectedType: dimmType11, + expectedType: dimmTypeAlderLake, }, { - name: "dimmType12 - SuperMicro SPR P1-DIMMA1", + name: "SuperMicro SPR P1-DIMMA1", bankLocator: "Not Specified", locator: "P1-DIMMA1", - expectedType: dimmType12, + expectedType: dimmTypeSuperMicroSPR, }, { - name: "dimmType13 - Birchstream CPU0_DIMM_A1", + name: "Birchstream CPU0_DIMM_A1", bankLocator: "BANK 0", locator: "CPU0_DIMM_A1", - expectedType: dimmType13, + expectedType: dimmTypeBirchstream, }, { - name: "dimmType14 - GNR AP/X3 CPU0_DIMM_A", + name: "GNR AP/X3 CPU0_DIMM_A", bankLocator: "BANK 0", locator: "CPU0_DIMM_A", - expectedType: dimmType14, + expectedType: dimmTypeBirchstreamGNRAP, }, { - name: "dimmType15 - Forest City CPU0 CH0/D0", + name: "Forest City CPU0 CH0/D0", bankLocator: "BANK 0", locator: "CPU0 CH0/D0", - expectedType: dimmType15, + expectedType: dimmTypeForestCity, }, { - name: "dimmType16 - Quanta GNR", + name: "Quanta GNR", bankLocator: "_Node0_Channel0_Dimm1", locator: "CPU0_A1", - expectedType: dimmType16, + expectedType: dimmTypeQuantaGNR, }, // --- Ordering-sensitive / ambiguous cases --- { - name: "ordering: CPU0_C0D0 matches type0, not type1", + name: "ordering: CPU0_C0D0 matches InspurICX, not GenericCPULetterDigit", bankLocator: "Not Specified", locator: "CPU0_C0D0", - expectedType: dimmType0, + expectedType: dimmTypeInspurICX, }, { - name: "ordering: P1-DIMMA1 matches type12, not type1", + name: "ordering: P1-DIMMA1 matches SuperMicroSPR, not GenericCPULetterDigit", bankLocator: "Not Specified", locator: "P1-DIMMA1", - expectedType: dimmType12, + expectedType: dimmTypeSuperMicroSPR, }, { - name: "ordering: Quanta GNR matches type16, not type5", + name: "ordering: Quanta GNR matches QuantaGNR, not NodeChannelDimmAlt", bankLocator: "_Node0_Channel0_Dimm1", locator: "CPU0_A1", - expectedType: dimmType16, + expectedType: dimmTypeQuantaGNR, }, { - // type16 requires Dimm[1-2]; Dimm0 doesn't match type16. - // CPU0_A1 matches type1's CPU([0-9])_([A-Z])([0-9]) before reaching type5's bank locator check. - name: "ordering: _Node0_Channel0_Dimm0 with CPU0_A1 falls to type1", + // QuantaGNR requires Dimm[1-2]; Dimm0 doesn't match. + // CPU0_A1 matches GenericCPULetterDigit before reaching NodeChannelDimmAlt's bank locator check. + name: "ordering: _Node0_Channel0_Dimm0 with CPU0_A1 falls to GenericCPULetterDigit", bankLocator: "_Node0_Channel0_Dimm0", locator: "CPU0_A1", - expectedType: dimmType1, + expectedType: dimmTypeGenericCPULetterDigit, }, { - // type6 requires CPU[1-4] and NODE [1-8]; type7 requires CPU[0-7] and NODE [0-9]+ - name: "ordering: SKX SDP CPU1_DIMM_A1 NODE 1 matches type6", + // SKXSDP requires CPU[1-4] and NODE [1-8]; ICXSDP requires CPU[0-7] and NODE [0-9]+ + name: "ordering: SKX SDP CPU1_DIMM_A1 NODE 1 matches SKXSDP", bankLocator: "NODE 1", locator: "CPU1_DIMM_A1", - expectedType: dimmType6, + expectedType: dimmTypeSKXSDP, }, { - // CPU0 doesn't match type6's CPU[1-4], so falls to type7 - name: "ordering: ICX SDP CPU0_DIMM_A1 NODE 0 matches type7", + // CPU0 doesn't match SKXSDP's CPU[1-4], so falls to ICXSDP + name: "ordering: ICX SDP CPU0_DIMM_A1 NODE 0 matches ICXSDP", bankLocator: "NODE 0", locator: "CPU0_DIMM_A1", - expectedType: dimmType7, + expectedType: dimmTypeICXSDP, }, { - // Birchstream CPU0_DIMM_A1 with non-NODE bank loc → not type6/7, falls to type13 - name: "ordering: Birchstream CPU0_DIMM_A1 BANK 0 matches type13, not type7", + // Birchstream CPU0_DIMM_A1 with non-NODE bank loc -> not SKXSDP/ICXSDP, falls to Birchstream + name: "ordering: Birchstream CPU0_DIMM_A1 BANK 0 matches Birchstream, not ICXSDP", bankLocator: "BANK 0", locator: "CPU0_DIMM_A1", - expectedType: dimmType13, + expectedType: dimmTypeBirchstream, }, // --- Multi-digit values (regression tests for [\d+] bug fix) --- { - name: "type16 - multi-digit node number", + name: "QuantaGNR - multi-digit node number", bankLocator: "_Node10_Channel0_Dimm1", locator: "CPU0_A1", - expectedType: dimmType16, + expectedType: dimmTypeQuantaGNR, }, { - name: "type16 - multi-digit channel number", + name: "QuantaGNR - multi-digit channel number", bankLocator: "_Node0_Channel12_Dimm2", locator: "CPU10_B2", - expectedType: dimmType16, + expectedType: dimmTypeQuantaGNR, }, // --- Unknown / no match --- { @@ -213,257 +213,257 @@ func TestGetDIMMSocketSlot(t *testing.T) { expectedSlot int expectErr bool }{ - // dimmType0: reLoc match[1]=socket, match[3]=slot + // InspurICX: locMatch[1]=socket, locMatch[3]=slot { - name: "type0 - Inspur ICX CPU0_C0D0", + name: "InspurICX - CPU0_C0D0", bankLocator: "Not Specified", locator: "CPU0_C0D0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type0 - Inspur ICX CPU1_C2D1", + name: "InspurICX - CPU1_C2D1", bankLocator: "Not Specified", locator: "CPU1_C2D1", expectedSocket: 1, expectedSlot: 1, }, - // dimmType1: reLoc match[1]=socket, match[3]=slot + // GenericCPULetterDigit: locMatch[1]=socket, locMatch[3]=slot { - name: "type1 - CPU0_A0", + name: "GenericCPULetterDigit - CPU0_A0", bankLocator: "Not Specified", locator: "CPU0_A0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type1 - CPU1_B3", + name: "GenericCPULetterDigit - CPU1_B3", bankLocator: "Not Specified", locator: "CPU1_B3", expectedSocket: 1, expectedSlot: 3, }, - // dimmType2: reLoc match[1]=socket, match[3]=slot + // MCFormat: locMatch[1]=socket, locMatch[3]=slot { - name: "type2 - CPU0_MC0_DIMM_A0", + name: "MCFormat - CPU0_MC0_DIMM_A0", bankLocator: "Not Specified", locator: "CPU0_MC0_DIMM_A0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type2 - CPU1_MC1_DIMM_B2", + name: "MCFormat - CPU1_MC1_DIMM_B2", bankLocator: "Not Specified", locator: "CPU1_MC1_DIMM_B2", expectedSocket: 1, expectedSlot: 2, }, - // dimmType3: reBankLoc match[1]=socket, match[3]=slot + // NodeChannelDimm: bankLocMatch[1]=socket, bankLocMatch[3]=slot { - name: "type3 - NODE 0 CHANNEL 0 DIMM 0", + name: "NodeChannelDimm - NODE 0 CHANNEL 0 DIMM 0", bankLocator: "NODE 0 CHANNEL 0 DIMM 0", locator: "DIMM0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type3 - NODE 1 CHANNEL 3 DIMM 1", + name: "NodeChannelDimm - NODE 1 CHANNEL 3 DIMM 1", bankLocator: "NODE 1 CHANNEL 3 DIMM 1", locator: "DIMM1", expectedSocket: 1, expectedSlot: 1, }, - // dimmType4: reBankLoc match[1]=socket, match[4]=slot + // PNodeChannelDimm: bankLocMatch[1]=socket, bankLocMatch[4]=slot { - name: "type4 - P0_Node0_Channel0_Dimm0", + name: "PNodeChannelDimm - P0_Node0_Channel0_Dimm0", bankLocator: "P0_Node0_Channel0_Dimm0", locator: "DIMM0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type4 - P1_Node1_Channel2_Dimm1", + name: "PNodeChannelDimm - P1_Node1_Channel2_Dimm1", bankLocator: "P1_Node1_Channel2_Dimm1", locator: "DIMM1", expectedSocket: 1, expectedSlot: 1, }, - // dimmType5: reBankLoc match[1]=socket, match[3]=slot + // NodeChannelDimmAlt: bankLocMatch[1]=socket, bankLocMatch[3]=slot { - name: "type5 - _Node0_Channel0_Dimm0", + name: "NodeChannelDimmAlt - _Node0_Channel0_Dimm0", bankLocator: "_Node0_Channel0_Dimm0", locator: "DIMM0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type5 - _Node1_Channel2_Dimm1", + name: "NodeChannelDimmAlt - _Node1_Channel2_Dimm1", bankLocator: "_Node1_Channel2_Dimm1", locator: "DIMM1", expectedSocket: 1, expectedSlot: 1, }, - // dimmType6: reLoc match[1]=socket-1, match[3]=slot-1 + // SKXSDP: locMatch[1]=socket-1, locMatch[3]=slot-1 { - name: "type6 - SKX SDP CPU1_DIMM_A1 NODE 1", + name: "SKXSDP - CPU1_DIMM_A1 NODE 1", bankLocator: "NODE 1", locator: "CPU1_DIMM_A1", expectedSocket: 0, expectedSlot: 0, }, { - name: "type6 - SKX SDP CPU2_DIMM_B2 NODE 2", + name: "SKXSDP - CPU2_DIMM_B2 NODE 2", bankLocator: "NODE 2", locator: "CPU2_DIMM_B2", expectedSocket: 1, expectedSlot: 1, }, - // dimmType7: reLoc match[1]=socket, match[3]=slot-1 + // ICXSDP: locMatch[1]=socket, locMatch[3]=slot-1 { - name: "type7 - ICX SDP CPU0_DIMM_A1 NODE 0", + name: "ICXSDP - CPU0_DIMM_A1 NODE 0", bankLocator: "NODE 0", locator: "CPU0_DIMM_A1", expectedSocket: 0, expectedSlot: 0, }, { - // CPU0 doesn't match type6's CPU[1-4], so falls to type7's CPU[0-7] - name: "type7 - ICX SDP CPU0_DIMM_C2 NODE 0", + // CPU0 doesn't match SKXSDP's CPU[1-4], so falls to ICXSDP's CPU[0-7] + name: "ICXSDP - CPU0_DIMM_C2 NODE 0", bankLocator: "NODE 0", locator: "CPU0_DIMM_C2", expectedSocket: 0, expectedSlot: 1, }, - // dimmType8: reBankLoc match[1]=socket-1, reLoc match[2]=slot-1 + // NodeDIMM: bankLocMatch[1]=socket-1, locMatch[2]=slot-1 { - name: "type8 - NODE 1 DIMM_A1", + name: "NodeDIMM - NODE 1 DIMM_A1", bankLocator: "NODE 1", locator: "DIMM_A1", expectedSocket: 0, expectedSlot: 0, }, { - name: "type8 - NODE 2 DIMM_B3", + name: "NodeDIMM - NODE 2 DIMM_B3", bankLocator: "NODE 2", locator: "DIMM_B3", expectedSocket: 1, expectedSlot: 2, }, - // dimmType9: reLoc match[1]=socket, match[2]=slot + // GigabyteMilan: locMatch[1]=socket, locMatch[2]=slot { - name: "type9 - Gigabyte Milan DIMM_P0_A0", + name: "GigabyteMilan - DIMM_P0_A0", bankLocator: "BANK 0", locator: "DIMM_P0_A0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type9 - Gigabyte Milan DIMM_P1_B1", + name: "GigabyteMilan - DIMM_P1_B1", bankLocator: "BANK 0", locator: "DIMM_P1_B1", expectedSocket: 1, expectedSlot: 1, }, - // dimmType10: socket=0, reBankLoc match[2]=slot + // NUC: socket=0, bankLocMatch[2]=slot { - name: "type10 - NUC CHANNEL A DIMM0", + name: "NUC - CHANNEL A DIMM0", bankLocator: "CHANNEL A DIMM0", locator: "SODIMM0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type10 - NUC CHANNEL B DIMM1", + name: "NUC - CHANNEL B DIMM1", bankLocator: "CHANNEL B DIMM1", locator: "SODIMM1", expectedSocket: 0, expectedSlot: 1, }, - // dimmType11: socket=0, reLoc match[2]=slot + // AlderLake: socket=0, locMatch[2]=slot { - name: "type11 - Alder Lake Controller0-ChannelA-DIMM0", + name: "AlderLake - Controller0-ChannelA-DIMM0", bankLocator: "BANK 0", locator: "Controller0-ChannelA-DIMM0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type11 - Alder Lake Controller1-ChannelA-DIMM1", + name: "AlderLake - Controller1-ChannelA-DIMM1", bankLocator: "BANK 0", locator: "Controller1-ChannelA-DIMM1", expectedSocket: 0, expectedSlot: 1, }, - // dimmType12: reLoc match[1]=socket-1, match[3]=slot-1 + // SuperMicroSPR: locMatch[1]=socket-1, locMatch[3]=slot-1 { - name: "type12 - SuperMicro P1-DIMMA1", + name: "SuperMicroSPR - P1-DIMMA1", bankLocator: "Not Specified", locator: "P1-DIMMA1", expectedSocket: 0, expectedSlot: 0, }, { - name: "type12 - SuperMicro P2-DIMMB2", + name: "SuperMicroSPR - P2-DIMMB2", bankLocator: "Not Specified", locator: "P2-DIMMB2", expectedSocket: 1, expectedSlot: 1, }, - // dimmType13: reLoc match[1]=socket, match[3]=slot-1 + // Birchstream: locMatch[1]=socket, locMatch[3]=slot-1 { - name: "type13 - Birchstream CPU0_DIMM_A1", + name: "Birchstream - CPU0_DIMM_A1", bankLocator: "BANK 0", locator: "CPU0_DIMM_A1", expectedSocket: 0, expectedSlot: 0, }, { - name: "type13 - Birchstream CPU1_DIMM_H2", + name: "Birchstream - CPU1_DIMM_H2", bankLocator: "BANK 7", locator: "CPU1_DIMM_H2", expectedSocket: 1, expectedSlot: 1, }, - // dimmType14: reLoc match[1]=socket, slot=0 + // BirchstreamGNRAP: locMatch[1]=socket, slot=0 { - name: "type14 - GNR AP/X3 CPU0_DIMM_A", + name: "BirchstreamGNRAP - CPU0_DIMM_A", bankLocator: "BANK 0", locator: "CPU0_DIMM_A", expectedSocket: 0, expectedSlot: 0, }, { - name: "type14 - GNR AP/X3 CPU1_DIMM_L", + name: "BirchstreamGNRAP - CPU1_DIMM_L", bankLocator: "BANK 11", locator: "CPU1_DIMM_L", expectedSocket: 1, expectedSlot: 0, }, - // dimmType15: reLoc match[1]=socket, match[3]=slot + // ForestCity: locMatch[1]=socket, locMatch[3]=slot { - name: "type15 - Forest City CPU0 CH0/D0", + name: "ForestCity - CPU0 CH0/D0", bankLocator: "BANK 0", locator: "CPU0 CH0/D0", expectedSocket: 0, expectedSlot: 0, }, { - name: "type15 - Forest City CPU1 CH7/D1", + name: "ForestCity - CPU1 CH7/D1", bankLocator: "BANK 7", locator: "CPU1 CH7/D1", expectedSocket: 1, expectedSlot: 1, }, - // dimmType16: reBankLoc match[1]=socket, reLoc match[3]=slot-1 + // QuantaGNR: bankLocMatch[1]=socket, bankLocMatch[3]=slot-1 { - name: "type16 - Quanta GNR _Node0_Channel0_Dimm1 CPU0_A1", + name: "QuantaGNR - _Node0_Channel0_Dimm1 CPU0_A1", bankLocator: "_Node0_Channel0_Dimm1", locator: "CPU0_A1", expectedSocket: 0, expectedSlot: 0, }, { - name: "type16 - Quanta GNR _Node1_Channel2_Dimm2 CPU1_B2", + name: "QuantaGNR - _Node1_Channel2_Dimm2 CPU1_B2", bankLocator: "_Node1_Channel2_Dimm2", locator: "CPU1_B2", expectedSocket: 1, @@ -471,8 +471,8 @@ func TestGetDIMMSocketSlot(t *testing.T) { }, // --- Multi-digit regression tests for [\d+] bug fix --- { - // Socket comes from reBankLoc match[1] (Node number), slot from reBankLoc match[3] (Dimm-1) - name: "type16 - multi-digit node _Node10_Channel0_Dimm1 CPU0_A1", + // Socket comes from bankLocMatch[1] (Node number), slot from bankLocMatch[3] (Dimm-1) + name: "QuantaGNR - multi-digit node _Node10_Channel0_Dimm1 CPU0_A1", bankLocator: "_Node10_Channel0_Dimm1", locator: "CPU0_A1", expectedSocket: 10, @@ -534,7 +534,7 @@ func TestDeriveDIMMInfoOther(t *testing.T) { expectNil bool // nil result, no error (parse failure logged) }{ { - name: "type1 - two sockets, two channels each, one slot", + name: "GenericCPULetterDigit - two sockets, two channels each, one slot", dimms: [][]string{ makeDIMMRow("Not Specified", "CPU0_A0"), makeDIMMRow("Not Specified", "CPU0_B0"), @@ -550,7 +550,7 @@ func TestDeriveDIMMInfoOther(t *testing.T) { }, }, { - name: "type3 - NODE CHANNEL format", + name: "NodeChannelDimm - NODE CHANNEL format", dimms: [][]string{ makeDIMMRow("NODE 0 CHANNEL 0 DIMM 0", "DIMM0"), makeDIMMRow("NODE 0 CHANNEL 0 DIMM 1", "DIMM1"), From c2eeaeeefaf05f6650061bc4f1b87114c2a7c5a1 Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Tue, 3 Mar 2026 12:51:17 -0800 Subject: [PATCH 4/5] test: add test case for nil-result path in deriveDIMMInfoOther Exercise the return nil, nil path (line 597) where the first DIMM identifies a format but a subsequent DIMM fails to match it. Co-Authored-By: Claude Opus 4.6 --- internal/extract/dimm_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/extract/dimm_test.go b/internal/extract/dimm_test.go index 1e0b396e..effb5a6c 100644 --- a/internal/extract/dimm_test.go +++ b/internal/extract/dimm_test.go @@ -578,6 +578,17 @@ func TestDeriveDIMMInfoOther(t *testing.T) { channelsPerSocket: 2, expectErr: true, }, + { + // First DIMM identifies as GenericCPULetterDigit, but second DIMM + // doesn't match that format, triggering the return nil, nil path. + name: "mismatched format in subsequent DIMM returns nil", + dimms: [][]string{ + makeDIMMRow("Not Specified", "CPU0_A0"), + makeDIMMRow("Not Specified", "UNKNOWN_FORMAT"), + }, + channelsPerSocket: 2, + expectNil: true, + }, } for _, tt := range tests { From 1beb25364b9521a19afca2446858ffc460778d3c Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Tue, 3 Mar 2026 12:53:46 -0800 Subject: [PATCH 5/5] fix: prevent nil panic in getDIMMSocketSlot for matchBoth formats For formats requiring both bankLocPat and locPat to match (matchBoth), getDIMMSocketSlot now requires both matches to be non-nil before calling extractFunc. Previously, a partial match (one nil, one non-nil) would pass a nil slice to extractFunc, causing an index-out-of-range panic. This is realistic in deriveDIMMInfoOther where the format is identified from dimms[0] but applied to all subsequent DIMM rows. Co-Authored-By: Claude Opus 4.6 --- internal/extract/dimm.go | 6 +++++- internal/extract/dimm_test.go | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/internal/extract/dimm.go b/internal/extract/dimm.go index 422fafff..82d366d9 100644 --- a/internal/extract/dimm.go +++ b/internal/extract/dimm.go @@ -564,7 +564,11 @@ func getDIMMSocketSlot(dt dimmType, reBankLoc *regexp.Regexp, reLoc *regexp.Rege if f.locPat != nil { locMatch = reLoc.FindStringSubmatch(locator) } - if bankLocMatch != nil || locMatch != nil { + if f.matchBoth { + if bankLocMatch != nil && locMatch != nil { + return f.extractFunc(bankLocMatch, locMatch) + } + } else if bankLocMatch != nil || locMatch != nil { return f.extractFunc(bankLocMatch, locMatch) } break diff --git a/internal/extract/dimm_test.go b/internal/extract/dimm_test.go index effb5a6c..952b774f 100644 --- a/internal/extract/dimm_test.go +++ b/internal/extract/dimm_test.go @@ -621,3 +621,26 @@ func TestDeriveDIMMInfoOther(t *testing.T) { }) } } + +func TestGetDIMMSocketSlotMatchBothPartialMatch(t *testing.T) { + // For matchBoth formats (e.g., QuantaGNR), if only one of the two patterns + // matches a subsequent DIMM row, getDIMMSocketSlot must return an error + // rather than passing a nil match slice to extractFunc (which would panic). + // + // This simulates deriveDIMMInfoOther identifying the format from dimms[0], + // then encountering a later DIMM where only one pattern matches. + bankLocPat := dimmFormats[1].bankLocPat // QuantaGNR + locPat := dimmFormats[1].locPat + + // locator matches but bankLocator does not + _, _, err := getDIMMSocketSlot(dimmTypeQuantaGNR, bankLocPat, locPat, "NOT_A_NODE", "CPU0_A1") + if err == nil { + t.Error("expected error when bankLocator doesn't match for matchBoth format, got nil") + } + + // bankLocator matches but locator does not + _, _, err = getDIMMSocketSlot(dimmTypeQuantaGNR, bankLocPat, locPat, "_Node0_Channel0_Dimm1", "UNKNOWN") + if err == nil { + t.Error("expected error when locator doesn't match for matchBoth format, got nil") + } +}