amd-debug-tools 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of amd-debug-tools might be problematic. Click here for more details.

test_common.py CHANGED
@@ -16,9 +16,11 @@ from platform import uname_result
16
16
  from amd_debug.common import (
17
17
  apply_prefix_wrapper,
18
18
  Colors,
19
+ convert_string_to_bool,
19
20
  colorize_choices,
20
21
  check_lockdown,
21
22
  compare_file,
23
+ find_ip_version,
22
24
  fatal_error,
23
25
  get_distro,
24
26
  get_log_priority,
@@ -330,3 +332,113 @@ class TestCommon(unittest.TestCase):
330
332
  default = "option1"
331
333
  expected_output = f"{Colors.OK}{default}{Colors.ENDC}"
332
334
  self.assertEqual(colorize_choices(choices, default), expected_output)
335
+
336
+ @patch("amd_debug.common.read_file")
337
+ @patch("os.path.exists")
338
+ def test_find_ip_version_found(self, mock_exists, mock_read_file):
339
+ """Test find_ip_version returns True when expected value is found"""
340
+ base_path = "/foo"
341
+ kind = "bar"
342
+ hw_ver = {"baz": 42}
343
+
344
+ # Simulate file exists and value matches
345
+ def exists_side_effect(path):
346
+ return True
347
+
348
+ mock_exists.side_effect = exists_side_effect
349
+ mock_read_file.return_value = "42"
350
+ result = find_ip_version(base_path, kind, hw_ver)
351
+ self.assertTrue(result)
352
+ b = os.path.join(base_path, "ip_discovery", "die", "0", kind, "0")
353
+ expected_path = os.path.join(b, "baz")
354
+ mock_exists.assert_any_call(expected_path)
355
+ mock_read_file.assert_any_call(expected_path)
356
+
357
+ @patch("amd_debug.common.read_file")
358
+ @patch("os.path.exists")
359
+ def test_find_ip_version_not_found_due_to_missing_file(
360
+ self, mock_exists, mock_read_file
361
+ ):
362
+ """Test find_ip_version returns False if file does not exist"""
363
+ base_path = "/foo"
364
+ kind = "bar"
365
+ hw_ver = {"baz": 42}
366
+ # Simulate file does not exist
367
+ mock_exists.return_value = False
368
+ result = find_ip_version(base_path, kind, hw_ver)
369
+ self.assertFalse(result)
370
+ b = os.path.join(base_path, "ip_discovery", "die", "0", kind, "0")
371
+ expected_path = os.path.join(b, "baz")
372
+ mock_exists.assert_any_call(expected_path)
373
+ mock_read_file.assert_not_called()
374
+
375
+ @patch("amd_debug.common.read_file")
376
+ @patch("os.path.exists")
377
+ def test_find_ip_version_not_found_due_to_value_mismatch(
378
+ self, mock_exists, mock_read_file
379
+ ):
380
+ """Test find_ip_version returns False if value does not match"""
381
+ base_path = "/foo"
382
+ kind = "bar"
383
+ hw_ver = {"baz": 42}
384
+ # Simulate file exists but value does not match
385
+ mock_exists.return_value = True
386
+ mock_read_file.return_value = "99"
387
+ result = find_ip_version(base_path, kind, hw_ver)
388
+ self.assertFalse(result)
389
+ b = os.path.join(base_path, "ip_discovery", "die", "0", kind, "0")
390
+ expected_path = os.path.join(b, "baz")
391
+ mock_exists.assert_any_call(expected_path)
392
+ mock_read_file.assert_any_call(expected_path)
393
+
394
+ @patch("amd_debug.common.read_file")
395
+ @patch("os.path.exists")
396
+ def test_find_ip_version_multiple_keys(self, mock_exists, mock_read_file):
397
+ """Test find_ip_version with multiple keys in hw_ver"""
398
+ base_path = "/foo"
399
+ kind = "bar"
400
+ hw_ver = {"baz": 42, "qux": 99}
401
+
402
+ # First key: file exists, value does not match
403
+ # Second key: file exists, value matches
404
+ def exists_side_effect(path):
405
+ return True
406
+
407
+ def read_file_side_effect(path):
408
+ if path.endswith("baz"):
409
+ return "0"
410
+ if path.endswith("qux"):
411
+ return "99"
412
+ return "0"
413
+
414
+ mock_exists.side_effect = exists_side_effect
415
+ mock_read_file.side_effect = read_file_side_effect
416
+ result = find_ip_version(base_path, kind, hw_ver)
417
+ self.assertFalse(result)
418
+
419
+ def test_convert_string_to_bool_true_values(self):
420
+ """Test convert_string_to_bool returns True for truthy string values"""
421
+ self.assertTrue(convert_string_to_bool("True"))
422
+ self.assertTrue(convert_string_to_bool("1"))
423
+ self.assertTrue(convert_string_to_bool("'nonempty'"))
424
+ self.assertTrue(convert_string_to_bool('"nonempty"'))
425
+
426
+ def test_convert_string_to_bool_false_values(self):
427
+ """Test convert_string_to_bool returns False for falsy string values"""
428
+ self.assertFalse(convert_string_to_bool("False"))
429
+ self.assertFalse(convert_string_to_bool("0"))
430
+ self.assertFalse(convert_string_to_bool("''"))
431
+ self.assertFalse(convert_string_to_bool('""'))
432
+ self.assertFalse(convert_string_to_bool("None"))
433
+
434
+ def test_convert_string_to_bool_invalid_syntax(self):
435
+ """Test convert_string_to_bool exits on invalid syntax"""
436
+ with patch("sys.exit") as mock_exit:
437
+ convert_string_to_bool("not_a_bool")
438
+ mock_exit.assert_called_once_with("Invalid entry: not_a_bool")
439
+
440
+ def test_convert_string_to_bool_invalid_value(self):
441
+ """Test convert_string_to_bool exits on invalid value"""
442
+ with patch("sys.exit") as mock_exit:
443
+ convert_string_to_bool("[unclosed_list")
444
+ mock_exit.assert_called_once_with("Invalid entry: [unclosed_list")
test_installer.py CHANGED
@@ -192,7 +192,7 @@ class TestInstaller(unittest.TestCase):
192
192
  """Test install requirements function"""
193
193
  self.installer.set_requirements("iasl", "ethtool")
194
194
  ret = self.installer.install_dependencies()
195
- self.assertFalse(ret)
195
+ self.assertTrue(ret)
196
196
 
197
197
  @patch("builtins.print")
198
198
  @patch("amd_debug.installer.get_distro", return_value="ubuntu")
@@ -205,7 +205,9 @@ class TestInstaller(unittest.TestCase):
205
205
  """Test install requirements function for edid-decode on Ubuntu"""
206
206
  self.installer.set_requirements("edid-decode")
207
207
  ret = self.installer.install_dependencies()
208
- _mock_check_call.assert_called_once_with(["apt", "install", "edid-decode"])
208
+ _mock_check_call.assert_called_once_with(
209
+ ["apt", "install", "libdisplay-info-bin"]
210
+ )
209
211
  self.assertTrue(ret)
210
212
 
211
213
  @patch("builtins.print")
@@ -229,7 +231,7 @@ class TestInstaller(unittest.TestCase):
229
231
  self.installer.set_requirements("edid-decode")
230
232
  ret = self.installer.install_dependencies()
231
233
  _mock_check_call.assert_called_once_with(
232
- ["dnf", "install", "-y", "edid-decode"]
234
+ ["dnf", "install", "-y", "libdisplay-info"]
233
235
  )
234
236
  self.assertTrue(ret)
235
237
 
@@ -249,8 +251,8 @@ class TestInstaller(unittest.TestCase):
249
251
  """Test install requirements function for edid-decode on Arch"""
250
252
  self.installer.set_requirements("edid-decode")
251
253
  ret = self.installer.install_dependencies()
252
- _mock_check_call.assert_not_called() # edid-decode is not supported on Arch
253
- self.assertFalse(ret)
254
+ _mock_check_call.assert_called_once_with(["pacman", "-Sy", "libdisplay-info"])
255
+ self.assertTrue(ret)
254
256
 
255
257
  @patch("builtins.print")
256
258
  @patch("os.path.exists", return_value=False)
@@ -263,7 +265,7 @@ class TestInstaller(unittest.TestCase):
263
265
  """Test install requirements function for edid-decode on unsupported distro"""
264
266
  self.installer.set_requirements("edid-decode")
265
267
  ret = self.installer.install_dependencies()
266
- self.assertFalse(ret)
268
+ self.assertTrue(ret)
267
269
 
268
270
  @patch("builtins.print")
269
271
  @patch("os.path.exists", return_value=False)
test_prerequisites.py CHANGED
@@ -8,7 +8,7 @@ This module contains unit tests for the prerequisite functions in the amd-debug-
8
8
  import logging
9
9
  import unittest
10
10
  import subprocess
11
- from unittest.mock import patch, MagicMock
11
+ from unittest.mock import patch, MagicMock, mock_open
12
12
 
13
13
  from amd_debug.prerequisites import PrerequisiteValidator
14
14
  from amd_debug.failures import *
@@ -180,20 +180,22 @@ class TestPrerequisiteValidator(unittest.TestCase):
180
180
  self.assertTrue(result)
181
181
  self.mock_db.record_prereq.assert_called_with("IOMMU disabled", "✅")
182
182
 
183
- def test_check_iommu_no_dma_protection(self):
183
+ @patch(
184
+ "amd_debug.prerequisites.open",
185
+ new_callable=unittest.mock.mock_open,
186
+ read_data=b"\x00" * 45,
187
+ )
188
+ def test_check_iommu_no_dma_protection(self, _mock_open):
184
189
  """Test check_iommu when DMA protection is not enabled"""
185
190
  self.validator.cpu_family = 0x1A
186
191
  self.validator.cpu_model = 0x20
187
192
  iommu_device = MagicMock(sys_path="/sys/devices/iommu")
188
- thunderbolt_device = MagicMock(sys_path="/sys/devices/thunderbolt")
189
193
  self.mock_pyudev.list_devices.side_effect = [
190
194
  [iommu_device],
191
- [thunderbolt_device],
192
195
  [],
193
196
  [],
194
197
  ]
195
- with patch("amd_debug.prerequisites.read_file", return_value="0"):
196
- result = self.validator.check_iommu()
198
+ result = self.validator.check_iommu()
197
199
  self.assertFalse(result)
198
200
  self.assertTrue(
199
201
  any(isinstance(f, DMArNotEnabled) for f in self.validator.failures)
@@ -202,20 +204,22 @@ class TestPrerequisiteValidator(unittest.TestCase):
202
204
  "IOMMU is misconfigured: Pre-boot DMA protection not enabled", "❌"
203
205
  )
204
206
 
205
- def test_check_iommu_missing_acpi_device(self):
207
+ @patch(
208
+ "amd_debug.prerequisites.open",
209
+ new_callable=unittest.mock.mock_open,
210
+ read_data=b"\x00" * 36 + b"\xff" * 4,
211
+ )
212
+ def test_check_iommu_missing_acpi_device(self, _mock_open):
206
213
  """Test check_iommu when MSFT0201 ACPI device is missing"""
207
214
  self.validator.cpu_family = 0x1A
208
215
  self.validator.cpu_model = 0x20
209
216
  iommu_device = MagicMock(sys_path="/sys/devices/iommu")
210
- thunderbolt_device = MagicMock(sys_path="/sys/devices/thunderbolt")
211
217
  self.mock_pyudev.list_devices.side_effect = [
212
218
  [iommu_device],
213
- [thunderbolt_device],
214
219
  [],
215
220
  [],
216
221
  ]
217
- with patch("amd_debug.prerequisites.read_file", return_value="1"):
218
- result = self.validator.check_iommu()
222
+ result = self.validator.check_iommu()
219
223
  self.assertFalse(result)
220
224
  self.assertTrue(
221
225
  any(isinstance(f, MissingIommuACPI) for f in self.validator.failures)
@@ -224,47 +228,48 @@ class TestPrerequisiteValidator(unittest.TestCase):
224
228
  "IOMMU is misconfigured: missing MSFT0201 ACPI device", "❌"
225
229
  )
226
230
 
227
- def test_check_iommu_missing_policy(self):
231
+ @patch(
232
+ "amd_debug.prerequisites.open",
233
+ new_callable=unittest.mock.mock_open,
234
+ read_data=b"\x00" * 36 + b"\xff" * 4,
235
+ )
236
+ def test_check_iommu_missing_policy(self, _mock_open):
228
237
  """Test check_iommu when policy is not bound to MSFT0201"""
229
238
  self.validator.cpu_family = 0x1A
230
239
  self.validator.cpu_model = 0x20
231
240
  iommu_device = MagicMock(sys_path="/sys/devices/iommu")
232
- thunderbolt_device = MagicMock(sys_path="/sys/devices/thunderbolt")
233
241
  acpi_device = MagicMock(sys_path="/sys/devices/acpi/MSFT0201")
234
242
  platform_device = MagicMock(sys_path="/sys/devices/platform/MSFT0201")
235
243
  self.mock_pyudev.list_devices.side_effect = [
236
244
  [iommu_device],
237
- [thunderbolt_device],
238
245
  [acpi_device],
239
246
  [platform_device],
240
247
  ]
241
- with patch("amd_debug.prerequisites.read_file", return_value="1"), patch(
242
- "os.path.exists", return_value=False
243
- ):
244
- result = self.validator.check_iommu()
248
+ result = self.validator.check_iommu()
245
249
  self.assertFalse(result)
246
250
  self.assertTrue(
247
251
  any(isinstance(f, MissingIommuPolicy) for f in self.validator.failures)
248
252
  )
249
253
 
250
- def test_check_iommu_properly_configured(self):
254
+ @patch(
255
+ "amd_debug.prerequisites.open",
256
+ new_callable=unittest.mock.mock_open,
257
+ read_data=b"\x00" * 36 + b"\xff" * 4,
258
+ )
259
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
260
+ def test_check_iommu_properly_configured(self, _mock_open, _mock_exists):
251
261
  """Test check_iommu when IOMMU is properly configured"""
252
262
  self.validator.cpu_family = 0x1A
253
263
  self.validator.cpu_model = 0x20
254
264
  iommu_device = MagicMock(sys_path="/sys/devices/iommu")
255
- thunderbolt_device = MagicMock(sys_path="/sys/devices/thunderbolt")
256
265
  acpi_device = MagicMock(sys_path="/sys/devices/acpi/MSFT0201")
257
266
  platform_device = MagicMock(sys_path="/sys/devices/platform/MSFT0201")
258
267
  self.mock_pyudev.list_devices.side_effect = [
259
268
  [iommu_device],
260
- [thunderbolt_device],
261
269
  [acpi_device],
262
270
  [platform_device],
263
271
  ]
264
- with patch("amd_debug.prerequisites.read_file", return_value="1"), patch(
265
- "os.path.exists", return_value=True
266
- ):
267
- result = self.validator.check_iommu()
272
+ result = self.validator.check_iommu()
268
273
  self.assertTrue(result)
269
274
  self.mock_db.record_prereq.assert_called_with("IOMMU properly configured", "✅")
270
275
 
@@ -495,9 +500,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
495
500
  result = self.validator.map_acpi_path()
496
501
  self.assertTrue(result)
497
502
  self.mock_db.record_debug.assert_called_with(
498
- "ACPI name: ACPI path [driver]\n"
499
- "│ device1: mocked_path [driver]\n"
500
- "└─device2: mocked_path [driver]\n"
503
+ "ACPI name | ACPI path | Kernel driver\ndevice1 | mocked_path | driver\ndevice2 | mocked_path | driver\n"
501
504
  )
502
505
 
503
506
  @patch("amd_debug.prerequisites.os.path.exists")
@@ -540,7 +543,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
540
543
  result = self.validator.map_acpi_path()
541
544
  self.assertTrue(result)
542
545
  self.mock_db.record_debug.assert_called_with(
543
- "ACPI name: ACPI path [driver]\n└─device1: mocked_path [None]\n"
546
+ "ACPI name | ACPI path | Kernel driver\ndevice1 | mocked_path | None\n"
544
547
  )
545
548
 
546
549
  @patch("amd_debug.prerequisites.read_file")
@@ -564,8 +567,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
564
567
 
565
568
  self.validator.capture_pci_acpi()
566
569
  self.mock_db.record_debug.assert_called_with(
567
- "PCI devices\n"
568
- "└─0000:00:1f.0 : Intel Corporation ISA bridge [1234abcd] : mocked_acpi_path\n"
570
+ "PCI Slot | Vendor | Class | ID | ACPI path\n└─0000:00:1f.0 | Intel Corporation | ISA bridge | 1234abcd | mocked_acpi_path\n"
569
571
  )
570
572
 
571
573
  @patch("amd_debug.prerequisites.read_file")
@@ -588,8 +590,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
588
590
 
589
591
  self.validator.capture_pci_acpi()
590
592
  self.mock_db.record_debug.assert_called_with(
591
- "PCI devices\n"
592
- "└─0000:01:00.0 : NVIDIA Corporation VGA compatible controller [5678efgh]\n"
593
+ "PCI Slot | Vendor | Class | ID | ACPI path\n└─0000:01:00.0 | NVIDIA Corporation | VGA compatible controller | 5678efgh | \n"
593
594
  )
594
595
 
595
596
  @patch("amd_debug.prerequisites.read_file")
@@ -623,9 +624,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
623
624
 
624
625
  self.validator.capture_pci_acpi()
625
626
  self.mock_db.record_debug.assert_called_with(
626
- "PCI devices\n"
627
- "│ 0000:00:1f.0 : Intel Corporation ISA bridge [1234abcd] : mocked_acpi_path\n"
628
- "└─0000:01:00.0 : NVIDIA Corporation VGA compatible controller [5678efgh] : mocked_acpi_path\n"
627
+ "PCI Slot | Vendor | Class | ID | ACPI path\n│ 0000:00:1f.0 | Intel Corporation | ISA bridge | 1234abcd | mocked_acpi_path\n└─0000:01:00.0 | NVIDIA Corporation | VGA compatible controller | 5678efgh | mocked_acpi_path\n"
629
628
  )
630
629
 
631
630
  def test_capture_pci_acpi_no_devices(self):
@@ -633,7 +632,9 @@ class TestPrerequisiteValidator(unittest.TestCase):
633
632
  self.mock_pyudev.list_devices.return_value = []
634
633
 
635
634
  self.validator.capture_pci_acpi()
636
- self.mock_db.record_debug.assert_called_with("PCI devices\n")
635
+ self.mock_db.record_debug.assert_called_with(
636
+ "PCI Slot | Vendor | Class | ID | ACPI path\n"
637
+ )
637
638
 
638
639
  @patch("amd_debug.prerequisites.read_file")
639
640
  def test_check_aspm_default_policy(self, mock_read_file):
@@ -1001,7 +1002,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
1001
1002
  "MockVendor MockProduct (MockFamily)", "💻"
1002
1003
  )
1003
1004
  self.mock_db.record_debug.assert_called_with(
1004
- "DMI data:\nchassis_type: Desktop\n"
1005
+ "DMI|value\nchassis_type| Desktop\n"
1005
1006
  )
1006
1007
 
1007
1008
  @patch("amd_debug.prerequisites.os.walk")
@@ -1029,7 +1030,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
1029
1030
  self.mock_db.record_prereq.assert_called_with(
1030
1031
  "MockVendor MockProduct (MockFamily)", "💻"
1031
1032
  )
1032
- self.mock_db.record_debug.assert_called_with("DMI data:\n")
1033
+ self.mock_db.record_debug.assert_called_with("DMI|value\n")
1033
1034
 
1034
1035
  @patch("amd_debug.prerequisites.os.walk")
1035
1036
  @patch("amd_debug.prerequisites.read_file")
@@ -1705,9 +1706,6 @@ class TestPrerequisiteValidator(unittest.TestCase):
1705
1706
  ]
1706
1707
  result = self.validator.check_storage()
1707
1708
  self.assertTrue(result)
1708
- self.mock_db.record_debug.assert_called_with(
1709
- "New enough kernel to avoid NVME check"
1710
- )
1711
1709
 
1712
1710
  def test_check_storage_no_kernel_log(self):
1713
1711
  """Test check_storage when kernel log is unavailable"""
@@ -1737,7 +1735,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
1737
1735
  result = self.validator.capture_edid()
1738
1736
  self.assertTrue(result)
1739
1737
  self.mock_db.record_prereq.assert_called_with(
1740
- "edid-decode not installed, unable to decode EDID", "👀"
1738
+ "Failed to capture EDID table", "👀"
1741
1739
  )
1742
1740
 
1743
1741
  @patch("amd_debug.prerequisites.subprocess.check_output")
@@ -1750,9 +1748,9 @@ class TestPrerequisiteValidator(unittest.TestCase):
1750
1748
  returncode=1, cmd="edid-decode", output=b"Error decoding EDID"
1751
1749
  )
1752
1750
  result = self.validator.capture_edid()
1753
- self.assertFalse(result)
1751
+ self.assertTrue(result)
1754
1752
  self.mock_db.record_prereq.assert_called_with(
1755
- "Failed to capture EDID table: b'Error decoding EDID'", "👀"
1753
+ "Failed to capture EDID table", "👀"
1756
1754
  )
1757
1755
 
1758
1756
  @patch("amd_debug.prerequisites.subprocess.check_output")
@@ -1767,3 +1765,164 @@ class TestPrerequisiteValidator(unittest.TestCase):
1767
1765
  self.mock_db.record_debug.assert_called_with(
1768
1766
  apply_prefix_wrapper("EDID for Monitor1:", "Decoded EDID data")
1769
1767
  )
1768
+
1769
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1770
+ @patch("amd_debug.prerequisites.os.path.exists")
1771
+ @patch("amd_debug.prerequisites.read_file")
1772
+ def test_check_dpia_pg_dmcub_usb4_found(
1773
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1774
+ ):
1775
+ """Test check_dpia_pg_dmcub when USB4 routers are found"""
1776
+ usb4_device = MagicMock()
1777
+ self.mock_pyudev.list_devices.side_effect = [
1778
+ [usb4_device], # First call: USB4 present
1779
+ ]
1780
+ result = self.validator.check_dpia_pg_dmcub()
1781
+ self.assertTrue(result)
1782
+ self.mock_db.record_debug.assert_called_with(
1783
+ "USB4 routers found, no need to check DMCUB version"
1784
+ )
1785
+
1786
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1787
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1788
+ @patch("amd_debug.prerequisites.read_file", return_value="0x90001B01")
1789
+ def test_check_dpia_pg_dmcub_dmcub_fw_version_new_enough(
1790
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1791
+ ):
1792
+ """Test check_dpia_pg_dmcub when DMCUB firmware version is new enough"""
1793
+ self.mock_pyudev.list_devices.side_effect = [
1794
+ [], # First call: no USB4
1795
+ [
1796
+ MagicMock(
1797
+ properties={
1798
+ "PCI_CLASS": "30000",
1799
+ "PCI_ID": "1002abcd",
1800
+ "PCI_SLOT_NAME": "0000:01:00.0",
1801
+ },
1802
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1803
+ )
1804
+ ],
1805
+ ]
1806
+ with patch("builtins.open", new_callable=mock_open, read_data="3") as mock_file:
1807
+ handlers = (
1808
+ mock_file.return_value,
1809
+ mock_open(read_data="5").return_value,
1810
+ mock_open(read_data="0").return_value,
1811
+ )
1812
+ mock_open.side_effect = handlers
1813
+ result = self.validator.check_dpia_pg_dmcub()
1814
+ self.assertTrue(result)
1815
+ self.mock_db.record_prereq.assert_not_called()
1816
+
1817
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1818
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
1819
+ @patch("amd_debug.prerequisites.read_file", return_value="0x8001B00")
1820
+ def test_check_dpia_pg_dmcub_dmcub_fw_version_too_old(
1821
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1822
+ ):
1823
+ """Test check_dpia_pg_dmcub when DMCUB firmware version is too old"""
1824
+ self.mock_pyudev.list_devices.side_effect = [
1825
+ [], # First call: no USB4
1826
+ [
1827
+ MagicMock(
1828
+ properties={
1829
+ "PCI_CLASS": "30000",
1830
+ "PCI_ID": "1002abcd",
1831
+ "PCI_SLOT_NAME": "0000:01:00.0",
1832
+ },
1833
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1834
+ )
1835
+ ],
1836
+ ]
1837
+ result = self.validator.check_dpia_pg_dmcub()
1838
+ self.assertFalse(result)
1839
+ self.mock_db.record_prereq.assert_called_with(
1840
+ "DMCUB Firmware is outdated", "❌"
1841
+ )
1842
+ self.assertTrue(
1843
+ any(isinstance(f, DmcubTooOld) for f in self.validator.failures)
1844
+ )
1845
+
1846
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1847
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=False)
1848
+ @patch(
1849
+ "amd_debug.prerequisites.read_file",
1850
+ side_effect=[
1851
+ "", # sysfs read returns empty, so fallback to debugfs
1852
+ "DMCUB fw: 09001B00\nOther line\n", # debugfs read
1853
+ ],
1854
+ )
1855
+ def test_check_dpia_pg_dmcub_debugfs_version_new_enough(
1856
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1857
+ ):
1858
+ """Test check_dpia_pg_dmcub when DMCUB version is found in debugfs and is new enough"""
1859
+ self.mock_pyudev.list_devices.side_effect = [
1860
+ [], # First call: no USB4
1861
+ [
1862
+ MagicMock(
1863
+ properties={
1864
+ "PCI_CLASS": "30000",
1865
+ "PCI_ID": "1002abcd",
1866
+ "PCI_SLOT_NAME": "0",
1867
+ },
1868
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1869
+ )
1870
+ ],
1871
+ ]
1872
+ result = self.validator.check_dpia_pg_dmcub()
1873
+ self.assertTrue(result)
1874
+ self.mock_db.record_prereq.assert_not_called()
1875
+
1876
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=True)
1877
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=False)
1878
+ @patch(
1879
+ "amd_debug.prerequisites.read_file",
1880
+ side_effect=[
1881
+ "DMCUB fw: 0x08001B00\nOther line\n", # debugfs read
1882
+ ],
1883
+ )
1884
+ def test_check_dpia_pg_dmcub_debugfs_version_too_old(
1885
+ self, mock_read_file, mock_path_exists, mock_find_ip_version
1886
+ ):
1887
+ """Test check_dpia_pg_dmcub when DMCUB version is found in debugfs and is too old"""
1888
+ self.mock_pyudev.list_devices.side_effect = [
1889
+ [], # First call: no USB4
1890
+ [
1891
+ MagicMock(
1892
+ properties={
1893
+ "PCI_CLASS": "30000",
1894
+ "PCI_ID": "1002abcd",
1895
+ "PCI_SLOT_NAME": "0",
1896
+ },
1897
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1898
+ )
1899
+ ],
1900
+ ]
1901
+ result = self.validator.check_dpia_pg_dmcub()
1902
+ self.assertFalse(result)
1903
+ self.mock_db.record_prereq.assert_called_with(
1904
+ "DMCUB Firmware is outdated", "❌"
1905
+ )
1906
+ self.assertTrue(
1907
+ any(isinstance(f, DmcubTooOld) for f in self.validator.failures)
1908
+ )
1909
+
1910
+ @patch("amd_debug.prerequisites.find_ip_version", return_value=False)
1911
+ def test_check_dpia_pg_dmcub_no_matching_dcn(self, mock_find_ip_version):
1912
+ """Test check_dpia_pg_dmcub when no matching DCN is found"""
1913
+ self.mock_pyudev.list_devices.side_effect = [
1914
+ [], # First call: no USB4
1915
+ [
1916
+ MagicMock(
1917
+ properties={
1918
+ "PCI_CLASS": "30000",
1919
+ "PCI_ID": "1002abcd",
1920
+ "PCI_SLOT_NAME": "0",
1921
+ },
1922
+ sys_path="/sys/devices/pci0000:01/0000:01:00.0",
1923
+ )
1924
+ ],
1925
+ ]
1926
+ result = self.validator.check_dpia_pg_dmcub()
1927
+ self.assertTrue(result)
1928
+ self.mock_db.record_prereq.assert_not_called()