amd-debug-tools 0.2.2__py3-none-any.whl → 0.2.12__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.
test_prerequisites.py CHANGED
@@ -8,6 +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
+ import struct
11
12
  from unittest.mock import patch, MagicMock, mock_open
12
13
 
13
14
  from amd_debug.prerequisites import PrerequisiteValidator
@@ -143,6 +144,16 @@ class TestPrerequisiteValidator(unittest.TestCase):
143
144
  result = self.validator.check_port_pm_override()
144
145
  self.assertTrue(result)
145
146
 
147
+ @patch("amd_debug.prerequisites.version.parse")
148
+ def test_check_port_pm_override_smu_version_missing(self, mock_version_parse):
149
+ """Test check_port_pm_override with SMU version undefined"""
150
+ self.validator.cpu_family = 0x19
151
+ self.validator.cpu_model = 0x74
152
+ mock_version_parse.side_effect = lambda v: v if isinstance(v, str) else None
153
+ self.validator.smu_version = ""
154
+ result = self.validator.check_port_pm_override()
155
+ self.assertTrue(result)
156
+
146
157
  @patch("amd_debug.prerequisites.version.parse")
147
158
  def test_check_port_pm_override_smu_version_too_low(self, mock_version_parse):
148
159
  """Test check_port_pm_override with SMU version < 76.18.0"""
@@ -185,15 +196,18 @@ class TestPrerequisiteValidator(unittest.TestCase):
185
196
  new_callable=unittest.mock.mock_open,
186
197
  read_data=b"\x00" * 45,
187
198
  )
188
- def test_check_iommu_no_dma_protection(self, _mock_open):
189
- """Test check_iommu when DMA protection is not enabled"""
199
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
200
+ def test_check_iommu_no_dma_protection_no_msft0201(self, _mock_open, _mock_exists):
201
+ """Test check_iommu when DMA protection is not enabled and no MSFT0201 in IVRS"""
190
202
  self.validator.cpu_family = 0x1A
191
203
  self.validator.cpu_model = 0x20
192
204
  iommu_device = MagicMock(sys_path="/sys/devices/iommu")
205
+ acpi_device = MagicMock(sys_path="/sys/devices/acpi/MSFT0201")
206
+ platform_device = MagicMock(sys_path="/sys/devices/platform/MSFT0201")
193
207
  self.mock_pyudev.list_devices.side_effect = [
194
208
  [iommu_device],
195
- [],
196
- [],
209
+ [acpi_device],
210
+ [platform_device],
197
211
  ]
198
212
  result = self.validator.check_iommu()
199
213
  self.assertFalse(result)
@@ -204,6 +218,27 @@ class TestPrerequisiteValidator(unittest.TestCase):
204
218
  "IOMMU is misconfigured: Pre-boot DMA protection not enabled", "❌"
205
219
  )
206
220
 
221
+ @patch(
222
+ "amd_debug.prerequisites.open",
223
+ new_callable=unittest.mock.mock_open,
224
+ read_data=b"\x00" * 45 + "MSFT0201".encode("utf-8"),
225
+ )
226
+ @patch("amd_debug.prerequisites.os.path.exists", return_value=True)
227
+ def test_check_iommu_no_dma_protection_BUT_msft0201(self, _mock_open, _mock_exists):
228
+ """Test check_iommu when DMA protection is not enabled BUT MSFT0201 is in IVRS"""
229
+ self.validator.cpu_family = 0x1A
230
+ self.validator.cpu_model = 0x20
231
+ iommu_device = MagicMock(sys_path="/sys/devices/iommu")
232
+ acpi_device = MagicMock(sys_path="/sys/devices/acpi/MSFT0201")
233
+ platform_device = MagicMock(sys_path="/sys/devices/platform/MSFT0201")
234
+ self.mock_pyudev.list_devices.side_effect = [
235
+ [iommu_device],
236
+ [acpi_device],
237
+ [platform_device],
238
+ ]
239
+ result = self.validator.check_iommu()
240
+ self.assertTrue(result)
241
+
207
242
  @patch(
208
243
  "amd_debug.prerequisites.open",
209
244
  new_callable=unittest.mock.mock_open,
@@ -292,7 +327,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
292
327
  BIT(9) | 1
293
328
  ) # Kernel warnings ignored, other taint present
294
329
  result = self.validator.check_taint()
295
- self.assertFalse(result)
330
+ self.assertTrue(result)
296
331
  self.assertTrue(
297
332
  any(isinstance(f, TaintedKernel) for f in self.validator.failures)
298
333
  )
@@ -402,6 +437,176 @@ class TestPrerequisiteValidator(unittest.TestCase):
402
437
  "This CPU model does not support hardware sleep over s2idle", "❌"
403
438
  )
404
439
 
440
+ @patch("builtins.open", new_callable=mock_open)
441
+ @patch("amd_debug.prerequisites.read_file")
442
+ @patch("amd_debug.prerequisites.os.path.exists")
443
+ def test_check_cpu_limited_cores_single_ccd(
444
+ self, mock_path_exists, mock_read_file, mock_file_open
445
+ ):
446
+ """Test check_cpu with artificially limited CPUs on single-CCD system"""
447
+ self.validator.cpu_family = 0x19
448
+ self.validator.cpu_model = 0x74
449
+ mock_read_file.return_value = "7" # kernel_max = 7 (8 cores)
450
+ mock_path_exists.return_value = True
451
+ # Simulate finding socket level at subleaf 1
452
+ # First call: subleaf 0, level_type = 1 (not socket)
453
+ # Second call: subleaf 1, level_type = 4 (socket level)
454
+ # Third call: read cpu_count from subleaf 1
455
+ mock_file_open.return_value.read.side_effect = [
456
+ struct.pack("4I", 0, 0, 0x100, 0), # subleaf 0: level_type = 1 (thread)
457
+ struct.pack(
458
+ "4I", 0, 0, 0x400, 0
459
+ ), # subleaf 1: level_type = 4 (socket) - FOUND
460
+ struct.pack("4I", 0, 16, 0, 0), # subleaf 1: cpu_count = 16
461
+ ]
462
+ result = self.validator.check_cpu()
463
+ self.assertFalse(result)
464
+ self.assertTrue(
465
+ any(isinstance(f, LimitedCores) for f in self.validator.failures)
466
+ )
467
+ self.mock_db.record_prereq.assert_called_with(
468
+ "The kernel has been limited to 8 CPU cores, but the system has 16 cores",
469
+ "❌",
470
+ )
471
+
472
+ @patch("builtins.open", new_callable=mock_open)
473
+ @patch("amd_debug.prerequisites.read_file")
474
+ @patch("amd_debug.prerequisites.os.path.exists")
475
+ def test_check_cpu_limited_cores_multi_ccd(
476
+ self, mock_path_exists, mock_read_file, mock_file_open
477
+ ):
478
+ """Test check_cpu with multi-CCD system (tests socket-level counting)"""
479
+ self.validator.cpu_family = 0x19
480
+ self.validator.cpu_model = 0x74
481
+ mock_read_file.return_value = "15" # kernel_max = 15 (16 cores)
482
+ mock_path_exists.return_value = True
483
+ # Simulate multi-CCD: iterate through levels to find socket
484
+ # subleaf 0: level_type = 1 (thread)
485
+ # subleaf 1: level_type = 2 (core)
486
+ # subleaf 2: level_type = 3 (complex/CCD)
487
+ # subleaf 3: level_type = 4 (socket) - FOUND
488
+ mock_file_open.return_value.read.side_effect = [
489
+ struct.pack("4I", 0, 0, 0x100, 0), # subleaf 0: level_type = 1 (thread)
490
+ struct.pack("4I", 0, 0, 0x200, 0), # subleaf 1: level_type = 2 (core)
491
+ struct.pack(
492
+ "4I", 0, 0, 0x300, 0
493
+ ), # subleaf 2: level_type = 3 (complex/CCD)
494
+ struct.pack(
495
+ "4I", 0, 0, 0x400, 0
496
+ ), # subleaf 3: level_type = 4 (socket) - FOUND
497
+ struct.pack("4I", 0, 32, 0, 0), # subleaf 3: cpu_count = 32
498
+ ]
499
+ result = self.validator.check_cpu()
500
+ self.assertFalse(result)
501
+ self.assertTrue(
502
+ any(isinstance(f, LimitedCores) for f in self.validator.failures)
503
+ )
504
+ self.mock_db.record_prereq.assert_called_with(
505
+ "The kernel has been limited to 16 CPU cores, but the system has 32 cores",
506
+ "❌",
507
+ )
508
+
509
+ @patch("builtins.open", new_callable=mock_open)
510
+ @patch("amd_debug.prerequisites.read_file")
511
+ @patch("amd_debug.prerequisites.os.path.exists")
512
+ def test_check_cpu_not_limited(
513
+ self, mock_path_exists, mock_read_file, mock_file_open
514
+ ):
515
+ """Test check_cpu when CPUs are not artificially limited"""
516
+ self.validator.cpu_family = 0x19
517
+ self.validator.cpu_model = 0x74
518
+ mock_read_file.return_value = "31" # kernel_max = 31 (32 cores)
519
+ mock_path_exists.return_value = True
520
+ # Simulate finding socket level
521
+ mock_file_open.return_value.read.side_effect = [
522
+ struct.pack("4I", 0, 0, 0x100, 0), # subleaf 0: level_type = 1
523
+ struct.pack("4I", 0, 0, 0x400, 0), # subleaf 1: level_type = 4 (socket)
524
+ struct.pack("4I", 0, 16, 0, 0), # subleaf 1: cpu_count = 16
525
+ ]
526
+ result = self.validator.check_cpu()
527
+ self.assertTrue(result)
528
+ self.mock_db.record_debug.assert_called_with("CPU core count: 16 max: 32")
529
+
530
+ @patch("builtins.open", new_callable=mock_open)
531
+ @patch("amd_debug.prerequisites.read_file")
532
+ @patch("amd_debug.prerequisites.os.path.exists")
533
+ def test_check_cpu_socket_level_at_boundary(
534
+ self, mock_path_exists, mock_read_file, mock_file_open
535
+ ):
536
+ """Test check_cpu when socket level is found at the last checked subleaf"""
537
+ self.validator.cpu_family = 0x19
538
+ self.validator.cpu_model = 0x74
539
+ mock_read_file.return_value = "15" # kernel_max = 15 (16 cores)
540
+ mock_path_exists.return_value = True
541
+ # Socket level found at subleaf 4 (last iteration)
542
+ mock_file_open.return_value.read.side_effect = [
543
+ struct.pack("4I", 0, 0, 0x100, 0), # subleaf 0: level_type = 1
544
+ struct.pack("4I", 0, 0, 0x200, 0), # subleaf 1: level_type = 2
545
+ struct.pack("4I", 0, 0, 0x300, 0), # subleaf 2: level_type = 3
546
+ struct.pack("4I", 0, 0, 0x000, 0), # subleaf 3: level_type = 0
547
+ struct.pack("4I", 0, 0, 0x400, 0), # subleaf 4: level_type = 4 (socket)
548
+ struct.pack("4I", 0, 16, 0, 0), # subleaf 4: cpu_count = 16
549
+ ]
550
+ result = self.validator.check_cpu()
551
+ self.assertTrue(result)
552
+ self.mock_db.record_prereq.assert_called_with(
553
+ "Unable to discover CPU topology, didn't find socket level", "🚦"
554
+ )
555
+
556
+ @patch("builtins.open", new_callable=mock_open)
557
+ @patch("amd_debug.prerequisites.read_file")
558
+ @patch("amd_debug.prerequisites.os.path.exists")
559
+ def test_check_cpu_socket_level_first_subleaf(
560
+ self, mock_path_exists, mock_read_file, mock_file_open
561
+ ):
562
+ """Test check_cpu when socket level is found at first subleaf"""
563
+ self.validator.cpu_family = 0x19
564
+ self.validator.cpu_model = 0x74
565
+ mock_read_file.return_value = "7" # kernel_max = 7 (8 cores)
566
+ mock_path_exists.return_value = True
567
+ # Socket level found immediately at subleaf 0
568
+ mock_file_open.return_value.read.side_effect = [
569
+ struct.pack(
570
+ "4I", 0, 0, 0x400, 0
571
+ ), # subleaf 0: level_type = 4 (socket) - FOUND
572
+ struct.pack("4I", 0, 8, 0, 0), # subleaf 0: cpu_count = 8
573
+ ]
574
+ result = self.validator.check_cpu()
575
+ self.assertTrue(result)
576
+ self.mock_db.record_debug.assert_called_with("CPU core count: 8 max: 8")
577
+
578
+ @patch("builtins.open", side_effect=FileNotFoundError)
579
+ @patch("amd_debug.prerequisites.read_file")
580
+ @patch("amd_debug.prerequisites.os.path.exists")
581
+ def test_check_cpu_cpuid_file_not_found(
582
+ self, mock_path_exists, mock_read_file, mock_file_open
583
+ ):
584
+ """Test check_cpu when cpuid kernel module is not loaded"""
585
+ self.validator.cpu_family = 0x19
586
+ self.validator.cpu_model = 0x74
587
+ mock_read_file.return_value = "7"
588
+ mock_path_exists.return_value = False
589
+ result = self.validator.check_cpu()
590
+ self.assertFalse(result)
591
+ self.mock_db.record_prereq.assert_called_with(
592
+ "Unable to check CPU topology: cpuid kernel module not loaded", "❌"
593
+ )
594
+
595
+ @patch("builtins.open", side_effect=PermissionError)
596
+ @patch("amd_debug.prerequisites.read_file")
597
+ @patch("amd_debug.prerequisites.os.path.exists")
598
+ def test_check_cpu_cpuid_permission_error(
599
+ self, mock_path_exists, mock_read_file, mock_file_open
600
+ ):
601
+ """Test check_cpu when there is a permission error accessing cpuid"""
602
+ self.validator.cpu_family = 0x19
603
+ self.validator.cpu_model = 0x74
604
+ mock_read_file.return_value = "7"
605
+ mock_path_exists.return_value = True
606
+ result = self.validator.check_cpu()
607
+ self.assertTrue(result)
608
+ self.mock_db.record_prereq.assert_called_with("CPUID checks unavailable", "🚦")
609
+
405
610
  @patch("amd_debug.prerequisites.os.walk")
406
611
  @patch(
407
612
  "amd_debug.prerequisites.open",
@@ -500,9 +705,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
500
705
  result = self.validator.map_acpi_path()
501
706
  self.assertTrue(result)
502
707
  self.mock_db.record_debug.assert_called_with(
503
- "ACPI name: ACPI path [driver]\n"
504
- "│ device1: mocked_path [driver]\n"
505
- "└─device2: mocked_path [driver]\n"
708
+ "ACPI name | ACPI path | Kernel driver\ndevice1 | mocked_path | driver\ndevice2 | mocked_path | driver\n"
506
709
  )
507
710
 
508
711
  @patch("amd_debug.prerequisites.os.path.exists")
@@ -545,7 +748,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
545
748
  result = self.validator.map_acpi_path()
546
749
  self.assertTrue(result)
547
750
  self.mock_db.record_debug.assert_called_with(
548
- "ACPI name: ACPI path [driver]\n└─device1: mocked_path [None]\n"
751
+ "ACPI name | ACPI path | Kernel driver\ndevice1 | mocked_path | None\n"
549
752
  )
550
753
 
551
754
  @patch("amd_debug.prerequisites.read_file")
@@ -569,8 +772,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
569
772
 
570
773
  self.validator.capture_pci_acpi()
571
774
  self.mock_db.record_debug.assert_called_with(
572
- "PCI devices\n"
573
- "└─0000:00:1f.0 : Intel Corporation ISA bridge [1234abcd] : mocked_acpi_path\n"
775
+ "PCI Slot | Vendor | Class | ID | ACPI path\n└─0000:00:1f.0 | Intel Corporation | ISA bridge | 1234abcd | mocked_acpi_path\n"
574
776
  )
575
777
 
576
778
  @patch("amd_debug.prerequisites.read_file")
@@ -593,8 +795,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
593
795
 
594
796
  self.validator.capture_pci_acpi()
595
797
  self.mock_db.record_debug.assert_called_with(
596
- "PCI devices\n"
597
- "└─0000:01:00.0 : NVIDIA Corporation VGA compatible controller [5678efgh]\n"
798
+ "PCI Slot | Vendor | Class | ID | ACPI path\n└─0000:01:00.0 | NVIDIA Corporation | VGA compatible controller | 5678efgh | \n"
598
799
  )
599
800
 
600
801
  @patch("amd_debug.prerequisites.read_file")
@@ -628,9 +829,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
628
829
 
629
830
  self.validator.capture_pci_acpi()
630
831
  self.mock_db.record_debug.assert_called_with(
631
- "PCI devices\n"
632
- "│ 0000:00:1f.0 : Intel Corporation ISA bridge [1234abcd] : mocked_acpi_path\n"
633
- "└─0000:01:00.0 : NVIDIA Corporation VGA compatible controller [5678efgh] : mocked_acpi_path\n"
832
+ "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"
634
833
  )
635
834
 
636
835
  def test_capture_pci_acpi_no_devices(self):
@@ -638,7 +837,9 @@ class TestPrerequisiteValidator(unittest.TestCase):
638
837
  self.mock_pyudev.list_devices.return_value = []
639
838
 
640
839
  self.validator.capture_pci_acpi()
641
- self.mock_db.record_debug.assert_called_with("PCI devices\n")
840
+ self.mock_db.record_debug.assert_called_with(
841
+ "PCI Slot | Vendor | Class | ID | ACPI path\n"
842
+ )
642
843
 
643
844
  @patch("amd_debug.prerequisites.read_file")
644
845
  def test_check_aspm_default_policy(self, mock_read_file):
@@ -802,8 +1003,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
802
1003
  self.assertFalse(result)
803
1004
  self.assertTrue(any(isinstance(f, FadtWrong) for f in self.validator.failures))
804
1005
 
805
- @patch("amd_debug.prerequisites.os.path.exists", return_value=False)
806
- def test_check_fadt_file_not_found(self, mock_path_exists):
1006
+ def test_check_fadt_file_not_found(self):
807
1007
  """Test check_fadt when FADT file is not found"""
808
1008
  self.mock_kernel_log.match_line.return_value = False
809
1009
  result = self.validator.check_fadt()
@@ -1006,7 +1206,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
1006
1206
  "MockVendor MockProduct (MockFamily)", "💻"
1007
1207
  )
1008
1208
  self.mock_db.record_debug.assert_called_with(
1009
- "DMI data:\nchassis_type: Desktop\n"
1209
+ "DMI|value\nchassis_type| Desktop\n"
1010
1210
  )
1011
1211
 
1012
1212
  @patch("amd_debug.prerequisites.os.walk")
@@ -1034,7 +1234,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
1034
1234
  self.mock_db.record_prereq.assert_called_with(
1035
1235
  "MockVendor MockProduct (MockFamily)", "💻"
1036
1236
  )
1037
- self.mock_db.record_debug.assert_called_with("DMI data:\n")
1237
+ self.mock_db.record_debug.assert_called_with("DMI|value\n")
1038
1238
 
1039
1239
  @patch("amd_debug.prerequisites.os.walk")
1040
1240
  @patch("amd_debug.prerequisites.read_file")
@@ -1083,7 +1283,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
1083
1283
 
1084
1284
  @patch("amd_debug.prerequisites.os.path.exists")
1085
1285
  @patch(
1086
- "builtins.open",
1286
+ "amd_debug.prerequisites.open",
1087
1287
  new_callable=unittest.mock.mock_open,
1088
1288
  read_data="ignore_wake_value",
1089
1289
  )
@@ -1098,7 +1298,11 @@ class TestPrerequisiteValidator(unittest.TestCase):
1098
1298
  )
1099
1299
 
1100
1300
  @patch("amd_debug.prerequisites.os.path.exists")
1101
- @patch("builtins.open", new_callable=unittest.mock.mock_open, read_data="(null)")
1301
+ @patch(
1302
+ "amd_debug.prerequisites.open",
1303
+ new_callable=unittest.mock.mock_open,
1304
+ read_data="(null)",
1305
+ )
1102
1306
  def test_capture_disabled_pins_with_null_values(self, _mock_open, mock_path_exists):
1103
1307
  mock_path_exists.side_effect = (
1104
1308
  lambda path: "ignore_wake" in path or "ignore_interrupt" in path
@@ -1710,9 +1914,6 @@ class TestPrerequisiteValidator(unittest.TestCase):
1710
1914
  ]
1711
1915
  result = self.validator.check_storage()
1712
1916
  self.assertTrue(result)
1713
- self.mock_db.record_debug.assert_called_with(
1714
- "New enough kernel to avoid NVME check"
1715
- )
1716
1917
 
1717
1918
  def test_check_storage_no_kernel_log(self):
1718
1919
  """Test check_storage when kernel log is unavailable"""
@@ -1933,3 +2134,405 @@ class TestPrerequisiteValidator(unittest.TestCase):
1933
2134
  result = self.validator.check_dpia_pg_dmcub()
1934
2135
  self.assertTrue(result)
1935
2136
  self.mock_db.record_prereq.assert_not_called()
2137
+
2138
+ @patch("amd_debug.prerequisites.os.path.exists")
2139
+ def test_capture_nvidia_version_file_missing(self, mock_exists):
2140
+ """Test capture_nvidia when /proc/driver/nvidia/version does not exist"""
2141
+ mock_exists.side_effect = lambda p: False if "version" in p else True
2142
+ result = self.validator.capture_nvidia()
2143
+ self.assertTrue(result)
2144
+ self.mock_db.record_debug_file.assert_not_called()
2145
+ self.mock_db.record_prereq.assert_not_called()
2146
+
2147
+ @patch("amd_debug.prerequisites.os.path.exists")
2148
+ def test_capture_nvidia_gpus_dir_missing(self, mock_exists):
2149
+ """Test capture_nvidia when /proc/driver/nvidia/gpus does not exist"""
2150
+
2151
+ def exists_side_effect(path):
2152
+ if "version" in path:
2153
+ return True
2154
+ if "gpus" in path:
2155
+ return False
2156
+ return True
2157
+
2158
+ mock_exists.side_effect = exists_side_effect
2159
+ result = self.validator.capture_nvidia()
2160
+ self.assertTrue(result)
2161
+ self.mock_db.record_debug_file.assert_called_once_with(
2162
+ "/proc/driver/nvidia/version"
2163
+ )
2164
+ self.mock_db.record_prereq.assert_not_called()
2165
+
2166
+ @patch("amd_debug.prerequisites.os.walk")
2167
+ @patch("amd_debug.prerequisites.os.path.exists")
2168
+ def test_capture_nvidia_success(self, mock_exists, mock_walk):
2169
+ """Test capture_nvidia when NVIDIA GPU files are present and readable"""
2170
+ mock_exists.side_effect = lambda p: True
2171
+ mock_walk.return_value = [
2172
+ ("/proc/driver/nvidia/gpus/0000:01:00.0", [], ["info", "power"])
2173
+ ]
2174
+ result = self.validator.capture_nvidia()
2175
+ self.assertTrue(result)
2176
+ self.mock_db.record_debug_file.assert_any_call("/proc/driver/nvidia/version")
2177
+ self.mock_db.record_debug.assert_any_call("NVIDIA info")
2178
+ self.mock_db.record_debug_file.assert_any_call(
2179
+ "/proc/driver/nvidia/gpus/0000:01:00.0/info"
2180
+ )
2181
+ self.mock_db.record_debug.assert_any_call("NVIDIA power")
2182
+ self.mock_db.record_debug_file.assert_any_call(
2183
+ "/proc/driver/nvidia/gpus/0000:01:00.0/power"
2184
+ )
2185
+
2186
+ @patch("amd_debug.prerequisites.os.walk")
2187
+ @patch("amd_debug.prerequisites.os.path.exists")
2188
+ def test_capture_nvidia_permission_error_on_version(self, mock_exists, mock_walk):
2189
+ """Test capture_nvidia when PermissionError occurs reading version file"""
2190
+ mock_exists.side_effect = lambda p: True if "version" in p else False
2191
+ self.mock_db.record_debug_file.side_effect = PermissionError
2192
+ result = self.validator.capture_nvidia()
2193
+ self.assertTrue(result)
2194
+ self.mock_db.record_prereq.assert_called_with(
2195
+ "NVIDIA GPU version not readable", "👀"
2196
+ )
2197
+
2198
+ @patch("amd_debug.prerequisites.os.walk")
2199
+ @patch("amd_debug.prerequisites.os.path.exists")
2200
+ def test_capture_nvidia_permission_error_on_gpu_file(self, mock_exists, mock_walk):
2201
+ """Test capture_nvidia when PermissionError occurs reading a GPU file"""
2202
+ mock_exists.side_effect = lambda p: True
2203
+ mock_walk.return_value = [
2204
+ ("/proc/driver/nvidia/gpus/0000:01:00.0", [], ["info"])
2205
+ ]
2206
+ self.mock_db.record_debug_file.side_effect = [None, PermissionError]
2207
+ result = self.validator.capture_nvidia()
2208
+ self.assertTrue(result)
2209
+ self.mock_db.record_debug.assert_any_call("NVIDIA info")
2210
+ self.mock_db.record_prereq.assert_called_with(
2211
+ "NVIDIA GPU {f} not readable", "👀"
2212
+ )
2213
+
2214
+ @patch("amd_debug.prerequisites.os.walk")
2215
+ @patch(
2216
+ "builtins.open",
2217
+ new_callable=unittest.mock.mock_open,
2218
+ read_data=b"C1 state info",
2219
+ )
2220
+ def test_capture_cstates_single_file(self, mock_open, mock_walk):
2221
+ """Test capture_cstates with a single cpuidle file"""
2222
+ mock_walk.return_value = [
2223
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1"]),
2224
+ ]
2225
+ self.validator.capture_cstates()
2226
+ self.mock_db.record_debug.assert_called_with(
2227
+ "ACPI C-state information\n└─/sys/bus/cpu/devices/cpu0/cpuidle/state1: C1 state info"
2228
+ )
2229
+
2230
+ @patch("amd_debug.prerequisites.os.walk")
2231
+ @patch("builtins.open", new_callable=mock_open)
2232
+ def test_capture_cstates_multiple_files(self, mock_open_func, mock_walk):
2233
+ """Test capture_cstates with multiple cpuidle files"""
2234
+ # Setup mock file reads for two files
2235
+ file_contents = {
2236
+ "/sys/bus/cpu/devices/cpu0/cpuidle/state1": b"C1 info",
2237
+ "/sys/bus/cpu/devices/cpu0/cpuidle/state2": b"C2 info",
2238
+ }
2239
+
2240
+ def side_effect(path, mode="rb"):
2241
+ mock_file = mock_open(read_data=file_contents[path])()
2242
+ return mock_file
2243
+
2244
+ mock_open_func.side_effect = side_effect
2245
+ mock_walk.return_value = [
2246
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1", "state2"]),
2247
+ ]
2248
+ self.validator.capture_cstates()
2249
+ # The prefix logic is based on order, so check for both lines
2250
+ debug_call = self.mock_db.record_debug.call_args[0][0]
2251
+ self.assertIn("/sys/bus/cpu/devices/cpu0/cpuidle/state1: C1 info", debug_call)
2252
+ self.assertIn("/sys/bus/cpu/devices/cpu0/cpuidle/state2: C2 info", debug_call)
2253
+ self.assertTrue(debug_call.startswith("ACPI C-state information\n"))
2254
+
2255
+ @patch("amd_debug.prerequisites.os.walk")
2256
+ @patch("builtins.open", new_callable=mock_open, read_data=b"")
2257
+ def test_capture_cstates_empty_files(self, _mock_open, mock_walk):
2258
+ """Test capture_cstates with empty cpuidle files"""
2259
+ mock_walk.return_value = [
2260
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1"]),
2261
+ ]
2262
+ self.validator.capture_cstates()
2263
+ self.mock_db.record_debug.assert_called_with(
2264
+ "ACPI C-state information\n└─/sys/bus/cpu/devices/cpu0/cpuidle/state1: "
2265
+ )
2266
+
2267
+ @patch("amd_debug.prerequisites.os.walk")
2268
+ @patch("amd_debug.prerequisites.open", side_effect=PermissionError)
2269
+ def test_capture_cstates_permission_error(self, _mock_open, mock_walk):
2270
+ """Test capture_cstates when reading cpuidle files raises PermissionError"""
2271
+ mock_walk.return_value = [
2272
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1"]),
2273
+ ]
2274
+ with self.assertRaises(PermissionError):
2275
+ self.validator.capture_cstates()
2276
+ self.mock_db.record_debug.assert_not_called()
2277
+
2278
+ @patch("amd_debug.prerequisites.os.walk")
2279
+ def test_capture_cstates_no_files(self, mock_walk):
2280
+ """Test capture_cstates when no cpuidle files are present"""
2281
+ mock_walk.return_value = [
2282
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], []),
2283
+ ]
2284
+ self.validator.capture_cstates()
2285
+ self.mock_db.record_debug.assert_called_with("ACPI C-state information\n")
2286
+
2287
+ @patch("amd_debug.prerequisites.read_file")
2288
+ def test_check_pinctrl_amd_driver_loaded_with_missing_file_error(
2289
+ self, mock_read_file
2290
+ ):
2291
+ """Test check_pinctrl_amd when the driver is loaded but debug file is missing"""
2292
+ mock_read_file.side_effect = FileNotFoundError
2293
+ self.mock_pyudev.list_devices.return_value = [
2294
+ MagicMock(properties={"DRIVER": "amd_gpio"})
2295
+ ]
2296
+
2297
+ result = self.validator.check_pinctrl_amd()
2298
+ self.assertTrue(result)
2299
+ self.mock_db.record_prereq.assert_called_with(
2300
+ "GPIO debugfs not available", "👀"
2301
+ )
2302
+
2303
+ def test_check_amdgpu_no_devices(self):
2304
+ """Test check_amdgpu when no PCI devices are found"""
2305
+ self.mock_pyudev.list_devices.return_value = []
2306
+ result = self.validator.check_amdgpu()
2307
+ self.assertFalse(result)
2308
+ self.mock_db.record_prereq.assert_called_with("Integrated GPU not found", "❌")
2309
+ self.assertTrue(any(isinstance(f, MissingGpu) for f in self.validator.failures))
2310
+
2311
+ def test_check_amdgpu_non_amd_devices(self):
2312
+ """Test check_amdgpu when PCI devices are present but not AMD GPUs"""
2313
+ self.mock_pyudev.list_devices.return_value = [
2314
+ MagicMock(
2315
+ properties={
2316
+ "PCI_CLASS": "30000",
2317
+ "PCI_ID": "8086abcd",
2318
+ "DRIVER": "i915",
2319
+ }
2320
+ ),
2321
+ ]
2322
+ result = self.validator.check_amdgpu()
2323
+ self.assertFalse(result)
2324
+ self.mock_db.record_prereq.assert_called_with("Integrated GPU not found", "❌")
2325
+ self.assertTrue(any(isinstance(f, MissingGpu) for f in self.validator.failures))
2326
+
2327
+ def test_check_amdgpu_driver_not_loaded(self):
2328
+ """Test check_amdgpu when AMD GPU is present but driver is not loaded"""
2329
+ self.mock_pyudev.list_devices.return_value = [
2330
+ MagicMock(
2331
+ properties={"PCI_CLASS": "20000", "PCI_ID": "1111abcd", "DRIVER": None}
2332
+ ),
2333
+ MagicMock(
2334
+ properties={"PCI_CLASS": "30000", "PCI_ID": "1002abcd", "DRIVER": None}
2335
+ ),
2336
+ ]
2337
+ result = self.validator.check_amdgpu()
2338
+ self.assertFalse(result)
2339
+ self.mock_db.record_prereq.assert_called_with(
2340
+ "GPU driver `amdgpu` not loaded", "❌"
2341
+ )
2342
+ self.assertTrue(
2343
+ any(isinstance(f, MissingAmdgpu) for f in self.validator.failures)
2344
+ )
2345
+
2346
+ def test_check_amdgpu_driver_loaded(self):
2347
+ """Test check_amdgpu when AMD GPU is present and driver is loaded"""
2348
+ self.mock_pyudev.list_devices.return_value = [
2349
+ MagicMock(
2350
+ properties={
2351
+ "PCI_CLASS": "30000",
2352
+ "PCI_ID": "1002abcd",
2353
+ "DRIVER": "amdgpu",
2354
+ "PCI_SLOT_NAME": "0000:01:00.0",
2355
+ }
2356
+ ),
2357
+ ]
2358
+ result = self.validator.check_amdgpu()
2359
+ self.assertTrue(result)
2360
+ self.mock_db.record_prereq.assert_called_with(
2361
+ "GPU driver `amdgpu` bound to 0000:01:00.0", "✅"
2362
+ )
2363
+
2364
+ def test_check_amdgpu_multiple_devices_mixed(self):
2365
+ """Test check_amdgpu with multiple devices, one with driver loaded, one without"""
2366
+ self.mock_pyudev.list_devices.return_value = [
2367
+ MagicMock(
2368
+ properties={
2369
+ "PCI_CLASS": "30000",
2370
+ "PCI_ID": "1002abcd",
2371
+ "DRIVER": "amdgpu",
2372
+ "PCI_SLOT_NAME": "0000:01:00.0",
2373
+ }
2374
+ ),
2375
+ MagicMock(
2376
+ properties={"PCI_CLASS": "30000", "PCI_ID": "1002abcd", "DRIVER": None}
2377
+ ),
2378
+ ]
2379
+ result = self.validator.check_amdgpu()
2380
+ self.assertFalse(result)
2381
+ self.mock_db.record_prereq.assert_any_call(
2382
+ "GPU driver `amdgpu` bound to 0000:01:00.0", "✅"
2383
+ )
2384
+ self.mock_db.record_prereq.assert_any_call(
2385
+ "GPU driver `amdgpu` not loaded", "❌"
2386
+ )
2387
+ self.assertTrue(
2388
+ any(isinstance(f, MissingAmdgpu) for f in self.validator.failures)
2389
+ )
2390
+
2391
+ @patch("amd_debug.prerequisites.os.path.exists")
2392
+ @patch("amd_debug.prerequisites.os.readlink")
2393
+ def test_check_isp4_no_devices(self, _mock_readlink, _mock_path_exists):
2394
+ """Test check_isp4 when no ISP4 camera devices are found"""
2395
+ self.mock_pyudev.list_devices.return_value = []
2396
+ result = self.validator.check_isp4()
2397
+ self.assertTrue(result)
2398
+ self.mock_db.record_prereq.assert_not_called()
2399
+
2400
+ @patch("amd_debug.prerequisites.os.path.exists")
2401
+ @patch("amd_debug.prerequisites.os.readlink")
2402
+ def test_check_isp4_device_without_path(self, _mock_readlink, mock_path_exists):
2403
+ """Test check_isp4 when ACPI device exists but path file is missing"""
2404
+ mock_path_exists.return_value = False
2405
+ self.mock_pyudev.list_devices.return_value = [
2406
+ MagicMock(sys_path="/sys/devices/acpi/OMNI5C10:00", sys_name="OMNI5C10:00")
2407
+ ]
2408
+ result = self.validator.check_isp4()
2409
+ self.assertTrue(result)
2410
+ self.mock_db.record_prereq.assert_not_called()
2411
+
2412
+ @patch("amd_debug.prerequisites.os.path.exists")
2413
+ @patch("amd_debug.prerequisites.os.readlink")
2414
+ def test_check_isp4_driver_not_bound(self, _mock_readlink, mock_path_exists):
2415
+ """Test check_isp4 when ISP4 driver is not bound to the device"""
2416
+ mock_path_exists.side_effect = lambda p: "path" in p
2417
+ self.mock_pyudev.list_devices.return_value = [
2418
+ MagicMock(sys_path="/sys/devices/acpi/OMNI5C10:00", sys_name="OMNI5C10:00")
2419
+ ]
2420
+ result = self.validator.check_isp4()
2421
+ self.assertFalse(result)
2422
+ self.mock_db.record_prereq.assert_called_with(
2423
+ "ISP4 platform camera driver 'amd-isp4' not bound to OMNI5C10:00", "❌"
2424
+ )
2425
+ self.assertTrue(
2426
+ any(
2427
+ isinstance(f, MissingIsp4PlatformDriver)
2428
+ for f in self.validator.failures
2429
+ )
2430
+ )
2431
+
2432
+ @patch("amd_debug.prerequisites.os.path.exists")
2433
+ @patch(
2434
+ "amd_debug.prerequisites.os.readlink",
2435
+ return_value="/sys/devices/platform/drivers/amd-isp4",
2436
+ )
2437
+ def test_check_isp4_driver_bound_but_module_not_loaded(
2438
+ self, _mock_readlink, mock_path_exists
2439
+ ):
2440
+ """Test check_isp4 when driver is bound but amd_capture module is not loaded"""
2441
+
2442
+ def exists_side_effect(path):
2443
+ if "path" in path or "driver" in path:
2444
+ return True
2445
+ if "amd_capture" in path:
2446
+ return False
2447
+ return True
2448
+
2449
+ mock_path_exists.side_effect = exists_side_effect
2450
+ self.mock_pyudev.list_devices.return_value = [
2451
+ MagicMock(sys_path="/sys/devices/acpi/OMNI5C10:00", sys_name="OMNI5C10:00")
2452
+ ]
2453
+ result = self.validator.check_isp4()
2454
+ self.assertFalse(result)
2455
+ self.mock_db.record_prereq.assert_any_call(
2456
+ "ISP4 platform camera driver 'amd-isp4' bound to OMNI5C10:00", "✅"
2457
+ )
2458
+ self.mock_db.record_prereq.assert_any_call(
2459
+ "Camera driver module 'amd_capture' not loaded", "❌"
2460
+ )
2461
+ self.assertTrue(
2462
+ any(isinstance(f, MissingAmdCaptureModule) for f in self.validator.failures)
2463
+ )
2464
+
2465
+ @patch("amd_debug.prerequisites.os.path.exists")
2466
+ @patch(
2467
+ "amd_debug.prerequisites.os.readlink",
2468
+ return_value="/sys/devices/platform/drivers/amd-isp4",
2469
+ )
2470
+ def test_check_isp4_fully_configured(self, _mock_readlink, mock_path_exists):
2471
+ """Test check_isp4 when ISP4 is fully configured with driver and module"""
2472
+ mock_path_exists.return_value = True
2473
+ self.mock_pyudev.list_devices.return_value = [
2474
+ MagicMock(sys_path="/sys/devices/acpi/OMNI5C10:00", sys_name="OMNI5C10:00")
2475
+ ]
2476
+ result = self.validator.check_isp4()
2477
+ self.assertTrue(result)
2478
+ self.mock_db.record_prereq.assert_any_call(
2479
+ "ISP4 platform camera driver 'amd-isp4' bound to OMNI5C10:00", "✅"
2480
+ )
2481
+ self.mock_db.record_prereq.assert_any_call(
2482
+ "Camera driver module 'amd_capture' loaded", "✅"
2483
+ )
2484
+
2485
+ @patch("amd_debug.prerequisites.os.path.exists")
2486
+ @patch(
2487
+ "amd_debug.prerequisites.os.readlink",
2488
+ return_value="/sys/devices/platform/drivers/other-driver",
2489
+ )
2490
+ def test_check_isp4_wrong_driver(self, _mock_readlink, mock_path_exists):
2491
+ """Test check_isp4 when wrong driver is bound to the device"""
2492
+ mock_path_exists.side_effect = lambda p: "path" in p or "driver" in p
2493
+ self.mock_pyudev.list_devices.return_value = [
2494
+ MagicMock(sys_path="/sys/devices/acpi/OMNI5C10:00", sys_name="OMNI5C10:00")
2495
+ ]
2496
+ result = self.validator.check_isp4()
2497
+ self.assertFalse(result)
2498
+ self.mock_db.record_prereq.assert_called_with(
2499
+ "ISP4 platform camera driver 'amd-isp4' not bound to OMNI5C10:00", "❌"
2500
+ )
2501
+ self.assertTrue(
2502
+ any(
2503
+ isinstance(f, MissingIsp4PlatformDriver)
2504
+ for f in self.validator.failures
2505
+ )
2506
+ )
2507
+
2508
+ @patch("amd_debug.prerequisites.os.path.exists")
2509
+ @patch(
2510
+ "amd_debug.prerequisites.os.readlink",
2511
+ return_value="/sys/devices/platform/drivers/amd-isp4",
2512
+ )
2513
+ def test_check_isp4_multiple_devices(self, _mock_readlink, mock_path_exists):
2514
+ """Test check_isp4 with multiple ISP4 camera devices"""
2515
+ mock_path_exists.return_value = True
2516
+ self.mock_pyudev.list_devices.return_value = [
2517
+ MagicMock(sys_path="/sys/devices/acpi/OMNI5C10:00", sys_name="OMNI5C10:00"),
2518
+ MagicMock(sys_path="/sys/devices/acpi/OMNI5C10:01", sys_name="OMNI5C10:01"),
2519
+ ]
2520
+ result = self.validator.check_isp4()
2521
+ self.assertTrue(result)
2522
+ self.mock_db.record_prereq.assert_any_call(
2523
+ "ISP4 platform camera driver 'amd-isp4' bound to OMNI5C10:00", "✅"
2524
+ )
2525
+ self.mock_db.record_prereq.assert_any_call(
2526
+ "ISP4 platform camera driver 'amd-isp4' bound to OMNI5C10:01", "✅"
2527
+ )
2528
+
2529
+ @patch("amd_debug.prerequisites.os.path.exists")
2530
+ def test_check_isp4_device_not_starting_with_omni5c10(self, mock_path_exists):
2531
+ """Test check_isp4 when ACPI devices don't match OMNI5C10 pattern"""
2532
+ mock_path_exists.return_value = True
2533
+ self.mock_pyudev.list_devices.return_value = [
2534
+ MagicMock(sys_path="/sys/devices/acpi/OTHER:00", sys_name="OTHER:00")
2535
+ ]
2536
+ result = self.validator.check_isp4()
2537
+ self.assertTrue(result)
2538
+ self.mock_db.record_prereq.assert_not_called()