amd-debug-tools 0.2.5__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_kernel.py CHANGED
@@ -26,7 +26,7 @@ class TestKernelLog(unittest.TestCase):
26
26
  kernel_cmdline = "quiet splash"
27
27
  expected_output = ""
28
28
  with patch(
29
- "builtins.open", new_callable=mock_open, read_data=kernel_cmdline
29
+ "amd_debug.common.open", new_callable=mock_open, read_data=kernel_cmdline
30
30
  ) as _mock_file:
31
31
  result = get_kernel_command_line()
32
32
  self.assertEqual(result, expected_output)
@@ -35,7 +35,7 @@ class TestKernelLog(unittest.TestCase):
35
35
  kernel_cmdline = ""
36
36
  expected_output = ""
37
37
  with patch(
38
- "builtins.open", new_callable=mock_open, read_data=kernel_cmdline
38
+ "amd_debug.common.open", new_callable=mock_open, read_data=kernel_cmdline
39
39
  ) as _mock_file:
40
40
  result = get_kernel_command_line()
41
41
  self.assertEqual(result, expected_output)
@@ -44,7 +44,7 @@ class TestKernelLog(unittest.TestCase):
44
44
  kernel_cmdline = "quiet splash --debug=1"
45
45
  expected_output = "--debug=1"
46
46
  with patch(
47
- "builtins.open", new_callable=mock_open, read_data=kernel_cmdline
47
+ "amd_debug.common.open", new_callable=mock_open, read_data=kernel_cmdline
48
48
  ) as _mock_file:
49
49
  result = get_kernel_command_line()
50
50
  self.assertEqual(result, expected_output)
@@ -53,7 +53,7 @@ class TestKernelLog(unittest.TestCase):
53
53
  kernel_cmdline = "quiet splash initrd=foo modprobe.blacklist=foo"
54
54
  expected_output = "modprobe.blacklist=foo"
55
55
  with patch(
56
- "builtins.open", new_callable=mock_open, read_data=kernel_cmdline
56
+ "amd_debug.common.open", new_callable=mock_open, read_data=kernel_cmdline
57
57
  ) as _mock_file:
58
58
  result = get_kernel_command_line()
59
59
  self.assertEqual(result, expected_output)
@@ -114,7 +114,8 @@ class TestDmesgLogger(unittest.TestCase):
114
114
  """Test Dmesg logger functions"""
115
115
 
116
116
  @classmethod
117
- def setUpClass(cls):
117
+ @patch("subprocess.run")
118
+ def setUpClass(cls, _mock_run=None):
118
119
  logging.basicConfig(filename="/dev/null", level=logging.DEBUG)
119
120
 
120
121
  def test_dmesg_logger_initialization(self):
test_launcher.py CHANGED
@@ -52,3 +52,10 @@ class TestLauncher(unittest.TestCase):
52
52
  with patch("amd_debug.pstate.main") as mock_main:
53
53
  amd_debug.launch_tool("amd_pstate.py")
54
54
  mock_main.assert_called_once()
55
+
56
+ def test_launcher_amd_ttm(self):
57
+ """Test launching amd_ttm"""
58
+
59
+ with patch("amd_debug.ttm.main") as mock_main:
60
+ amd_debug.launch_tool("amd_ttm.py")
61
+ mock_main.assert_called_once()
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
@@ -326,7 +327,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
326
327
  BIT(9) | 1
327
328
  ) # Kernel warnings ignored, other taint present
328
329
  result = self.validator.check_taint()
329
- self.assertFalse(result)
330
+ self.assertTrue(result)
330
331
  self.assertTrue(
331
332
  any(isinstance(f, TaintedKernel) for f in self.validator.failures)
332
333
  )
@@ -436,6 +437,176 @@ class TestPrerequisiteValidator(unittest.TestCase):
436
437
  "This CPU model does not support hardware sleep over s2idle", "❌"
437
438
  )
438
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
+
439
610
  @patch("amd_debug.prerequisites.os.walk")
440
611
  @patch(
441
612
  "amd_debug.prerequisites.open",
@@ -1112,7 +1283,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
1112
1283
 
1113
1284
  @patch("amd_debug.prerequisites.os.path.exists")
1114
1285
  @patch(
1115
- "builtins.open",
1286
+ "amd_debug.prerequisites.open",
1116
1287
  new_callable=unittest.mock.mock_open,
1117
1288
  read_data="ignore_wake_value",
1118
1289
  )
@@ -1127,7 +1298,11 @@ class TestPrerequisiteValidator(unittest.TestCase):
1127
1298
  )
1128
1299
 
1129
1300
  @patch("amd_debug.prerequisites.os.path.exists")
1130
- @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
+ )
1131
1306
  def test_capture_disabled_pins_with_null_values(self, _mock_open, mock_path_exists):
1132
1307
  mock_path_exists.side_effect = (
1133
1308
  lambda path: "ignore_wake" in path or "ignore_interrupt" in path
@@ -1959,3 +2134,405 @@ class TestPrerequisiteValidator(unittest.TestCase):
1959
2134
  result = self.validator.check_dpia_pg_dmcub()
1960
2135
  self.assertTrue(result)
1961
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()