feat(memory): flag RAM below rated speed (XMP/EXPO not enabled) — 0.40.0
tests / core (pull_request) Successful in 12s
tests / gui-smoke (pull_request) Successful in 27s

Inventory shows configured RAM speed + the rated speed when lower
('4800 MT/s (rated 5600)'); System Health flags it with the fix (enable
XMP/EXPO in BIOS). With the profile off dmidecode only reports the JEDEC base,
so the rated speed comes from dmidecode's max OR the part number, matched against
known DDR5 speed grades to avoid false positives. inventory.module_speed() shared
by both; needs dmidecode (root/launch elevation). +tests (incl. the user's
CMK..5600 kit → (4800, 5600)). Completes the underperforming-hardware trio with
PCIe gen + refresh rate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 17:00:02 +02:00
parent b006fa6b8d
commit 04e8d72bce
7 changed files with 110 additions and 4 deletions
+22
View File
@@ -10,6 +10,7 @@ from rigdoctor.core.health import (
INFO,
WARNING,
check_displays,
check_memory_speed,
check_pcie_links,
run_health_checks,
scan_journal_text,
@@ -97,5 +98,26 @@ class DisplayCheckTests(unittest.TestCase):
self.assertEqual(check_displays(), [])
class MemorySpeedCheckTests(unittest.TestCase):
def _dmi(self, configured, part):
return {"memory": [{"Configured Memory Speed": configured, "Speed": configured,
"Part Number": part}]}
def test_flags_unapplied_expo(self):
dmi = self._dmi("4800 MT/s", "CMK32GX5M2B5600Z36")
with mock.patch("rigdoctor.core.elevation.privileged", return_value=None), \
mock.patch("rigdoctor.core.inventory._dmidecode", return_value=dmi):
findings = check_memory_speed()
self.assertEqual(len(findings), 1)
self.assertEqual(findings[0].severity, INFO)
self.assertIn("5600", findings[0].title)
def test_no_flag_at_rated(self):
dmi = self._dmi("5600 MT/s", "CMK32GX5M2B5600Z36")
with mock.patch("rigdoctor.core.elevation.privileged", return_value=None), \
mock.patch("rigdoctor.core.inventory._dmidecode", return_value=dmi):
self.assertEqual(check_memory_speed(), [])
if __name__ == "__main__":
unittest.main()
+18
View File
@@ -54,5 +54,23 @@ class PcieLinkTests(unittest.TestCase):
self.assertEqual(inventory._nvme_link("sda"), "")
class MemorySpeedTests(unittest.TestCase):
def test_rated_speed_from_part_number(self):
self.assertEqual(inventory._rated_from_part("CMK32GX5M2B5600Z36"), 5600)
self.assertEqual(inventory._rated_from_part("F5-6000J3038F16G"), 6000)
self.assertIsNone(inventory._rated_from_part("NoSpeedHere"))
def test_detects_unapplied_expo(self):
# XMP/EXPO off: dmidecode only sees JEDEC 4800; the 5600 is in the part number.
m = {"Configured Memory Speed": "4800 MT/s", "Speed": "4800 MT/s",
"Part Number": "CMK32GX5M2B5600Z36"}
self.assertEqual(inventory.module_speed(m), (4800, 5600))
def test_at_rated_speed(self):
m = {"Configured Memory Speed": "5600 MT/s", "Speed": "5600 MT/s",
"Part Number": "CMK32GX5M2B5600Z36"}
self.assertEqual(inventory.module_speed(m), (5600, 5600))
if __name__ == "__main__":
unittest.main()