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.
- amd_debug/__init__.py +9 -2
- amd_debug/acpi.py +0 -1
- amd_debug/battery.py +0 -1
- amd_debug/bios.py +5 -4
- amd_debug/common.py +70 -3
- amd_debug/database.py +22 -5
- amd_debug/display.py +2 -3
- amd_debug/failures.py +43 -2
- amd_debug/installer.py +4 -5
- amd_debug/kernel.py +10 -7
- amd_debug/prerequisites.py +116 -16
- amd_debug/pstate.py +5 -4
- amd_debug/s2idle.py +22 -16
- amd_debug/sleep_report.py +1 -2
- amd_debug/ttm.py +157 -0
- amd_debug/validator.py +40 -19
- amd_debug/wake.py +0 -1
- amd_debug_tools-0.2.12.dist-info/METADATA +75 -0
- amd_debug_tools-0.2.12.dist-info/RECORD +47 -0
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.12.dist-info}/entry_points.txt +1 -0
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.12.dist-info}/top_level.txt +1 -0
- test_acpi.py +1 -1
- test_bios.py +26 -8
- test_common.py +117 -1
- test_database.py +1 -1
- test_display.py +6 -6
- test_installer.py +68 -1
- test_kernel.py +6 -5
- test_launcher.py +7 -0
- test_prerequisites.py +580 -3
- test_s2idle.py +25 -8
- test_ttm.py +276 -0
- test_validator.py +71 -9
- amd_debug_tools-0.2.5.dist-info/METADATA +0 -181
- amd_debug_tools-0.2.5.dist-info/RECORD +0 -45
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.12.dist-info}/WHEEL +0 -0
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.12.dist-info}/licenses/LICENSE +0 -0
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
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(
|
|
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()
|