amd-debug-tools 0.2.7__py3-none-any.whl → 0.2.9__py3-none-any.whl

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

Potentially problematic release.


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

@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: amd-debug-tools
3
+ Version: 0.2.9
4
+ Summary: debug tools for AMD systems
5
+ Author-email: Mario Limonciello <superm1@kernel.org>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://web.git.kernel.org/pub/scm/linux/kernel/git/superm1/amd-debug-tools.git/
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: dbus-fast
14
+ Requires-Dist: pyudev
15
+ Requires-Dist: packaging
16
+ Requires-Dist: pandas
17
+ Requires-Dist: jinja2
18
+ Requires-Dist: tabulate
19
+ Requires-Dist: seaborn
20
+ Requires-Dist: cysystemd
21
+ Requires-Dist: Jinja2
22
+ Requires-Dist: matplotlib
23
+ Requires-Dist: seaborn
24
+ Dynamic: license-file
25
+
26
+ # Helpful tools for debugging AMD Zen systems
27
+ [![codecov](https://codecov.io/github/superm1/amd-debug-tools/graph/badge.svg?token=Z9WTBZADGT)](https://codecov.io/github/superm1/amd-debug-tools)
28
+ [![PyPI](https://img.shields.io/pypi/v/amd-debug-tools.svg)](https://pypi.org/project/amd-debug-tools/)
29
+
30
+ This repository hosts open tools that are useful for debugging issues on AMD systems.
31
+
32
+ ## Installation
33
+ ### Distro (Arch)
34
+ `amd-debug-tools` has been [packaged for Arch Linux](https://archlinux.org/packages/extra/any/amd-debug-tools/) (and derivatives). You can install it using:
35
+
36
+ pacman -Sy amd-debug-tools
37
+
38
+ ### Using a python wheel (Generic)
39
+ It is suggested to install tools in a virtual environment either using
40
+ `pipx` or `python3 -m venv`.
41
+
42
+ #### From PyPI
43
+ `amd-debug-tools` is distributed as a python wheel, which is a
44
+ binary package format for Python. To install from PyPI, run the following
45
+ command:
46
+
47
+ pipx install amd-debug-tools
48
+
49
+ ### From source
50
+ To build the package from source, you will need to the `python3-build`
51
+ package natively installed by your distribution package manager. Then you
52
+ can generate and install a wheel by running the following commands:
53
+
54
+ python3 -m build
55
+ pipx install dist/amd-debug-tools-*.whl
56
+
57
+ ### Ensuring path
58
+ If you have not used a `pipx` environment before, you may need to run the following command
59
+ to set up the environment:
60
+
61
+ pipx ensurepath
62
+
63
+ This will add the `pipx` environment to your path.
64
+
65
+ ## Running in-tree
66
+ Documentation about running directly from a git checkout is available [here](https://github.com/superm1/amd-debug-tools/blob/master/docs/in-tree.md).
67
+
68
+ ## Tools
69
+
70
+ Each tool has its own individual documentation page:
71
+ * [amd-s2idle](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-s2idle.md)
72
+ * [amd-bios](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-bios.md)
73
+ * [amd-pstate](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-pstate.md)
74
+ * [amd-ttm](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-ttm.md)
75
+
@@ -0,0 +1,47 @@
1
+ launcher.py,sha256=M8kT9DtyZoQgZaKWDbSBu4jsS6tZF1gWko3sovNVyag,947
2
+ test_acpi.py,sha256=wtS43Rz95h7YEEJBeFa6Mswaeo4syBZrw4hY8i0YbJY,3117
3
+ test_batteries.py,sha256=nN5pfP5El7Whypq3HHEpW8bufdf5EWSTVGbayfNQYP4,3360
4
+ test_bios.py,sha256=x_KLmQqGEbQhTugyWCHGXjGp2H1dCdhRz0kgw2Big8w,9276
5
+ test_common.py,sha256=qOCouXyO-dhY_x_L8kyNyuP_c0bhHhlqPoc084_F6Xg,20757
6
+ test_database.py,sha256=HAC4M4dyZBxskNFMfZn2kro6uT2-j0exjnto8-0OOnk,10042
7
+ test_display.py,sha256=awC1-OEPG1aV34BH-MzrOWtAHHPfhCmnKRetas4AGNE,5813
8
+ test_failures.py,sha256=H1UxXeVjhJs9-j9yas4vwAha676GX1Es7Kz8RN2B590,6845
9
+ test_installer.py,sha256=tZpGUOY4ACgXU-cs1Z5RHSPY5INa3HhLmmuB2H4QrOc,10222
10
+ test_kernel.py,sha256=2EXrLht5ZWdT4N5pb_F3zqZl9NEghjnDpcMGCMw3obI,7917
11
+ test_launcher.py,sha256=8g8CBTvLX64Us4RmHtRPSdpV5E2kQFaudBl7VIsxLhE,1733
12
+ test_prerequisites.py,sha256=SJk9y5zXdBmsNbtPMbBOHzkFsnuQF0j3BS6tdZ3ccTo,95944
13
+ test_pstate.py,sha256=a9oAJ9-LANX32XNQhplz6Y75VNYc__QqoSBKIrwvANg,6058
14
+ test_s2idle.py,sha256=FxsyujgX9Px3m56VzHNeA8yMTHmJiRLWxYt-fh1m5gw,33585
15
+ test_sleep_report.py,sha256=ANuxYi_C1oSKAi4xUU2wBu4SwJtcZA7VPpazBe3_WUQ,6922
16
+ test_ttm.py,sha256=QrCdRodQ_CD3nyDqKodb6yR158mgE0iIM5f1fV1Axu8,10515
17
+ test_validator.py,sha256=bfFFB2V3hrZtxTruwrvcUyZjoh-dcDa2TPt-l6pAfTA,36476
18
+ test_wake.py,sha256=6zi5GVFHQKU1sTWw3O5-aGriB9uu5713QLn4l2wjhpM,7152
19
+ amd_debug/__init__.py,sha256=0VsbaXBas5gcwZ6vBbJg20-8qHNDo_BKFYzdb1Agfq4,1252
20
+ amd_debug/acpi.py,sha256=_lnnAwTnAb4g8AW2BXwF45UY-WUmXIDpthhFLE5ENGo,3142
21
+ amd_debug/battery.py,sha256=qd6bo1ssAPnEfPY2HCmJfFcwgYADrfoJkxFQWQyxKlU,3103
22
+ amd_debug/bios.py,sha256=3dfE5yVoW0aZUN5osGKOtYF-RLOzUcH3ryA4yHZZoJA,4022
23
+ amd_debug/common.py,sha256=OR0rCFkWWoGlRIdsPdJ0hBr9iQ_vXcjIAL3poZ9BTj8,12606
24
+ amd_debug/database.py,sha256=kkl1iKIvekpVMc8M0xzo0iX3rc4yDaREyjCuLSnGuCk,11052
25
+ amd_debug/display.py,sha256=XATsKPh2QDoJAbm5mAE1YNFdVuFEq3yS3dffEasEq8c,922
26
+ amd_debug/failures.py,sha256=Sa2GEwhn8PImcIbZkHEPA2oCbuayTx9JheLaXBscz4I,23280
27
+ amd_debug/installer.py,sha256=JNlqGWNgF5pYxzy2VJiphmP4gXg99F0f4TBy2xk1Tpg,14275
28
+ amd_debug/kernel.py,sha256=w2y4syIMPd4OYXhPYrEmCDYnMBg9K16tK6iNwfzRVWU,11718
29
+ amd_debug/prerequisites.py,sha256=f55y-DUCMQHPWSQRjJcP-J8qvGaNKbXUIBbyhOzUB1U,52632
30
+ amd_debug/pstate.py,sha256=fGA-pKS1mzIrOa9fIqd_q3Y9DvMhuWmInq5GPAGc21k,9564
31
+ amd_debug/s2idle-hook,sha256=LLiaqPtGd0qetu9n6EYxKHZaIdHpVQDONdOuSc0pfFg,1695
32
+ amd_debug/s2idle.py,sha256=tj1_5g2adTScWfgaFAlH4NVjAse2jSCgOhpaOeWBYzk,13381
33
+ amd_debug/sleep_report.py,sha256=JQDtd_y-78gXQN5o7O7h-hKosEOWZeTmnZBSepdeCEE,17298
34
+ amd_debug/ttm.py,sha256=KoCY35jnU7Q2ZB0SO1nQmC_RsiCFwTWUzVmRbyUg0Lw,4660
35
+ amd_debug/validator.py,sha256=OEpaIAnkZg_GF_MeU48YNj8Wcj45vEgT7edqgflsChM,34151
36
+ amd_debug/wake.py,sha256=x33z60sOaukTlZTABKpvLJZVRk-kIe_o8XljtV3Q38Q,3917
37
+ amd_debug/bash/amd-s2idle,sha256=g_cle1ElCJpwE4wcLezL6y-BdasDKTnNMhrtzKLE9ks,1142
38
+ amd_debug/templates/html,sha256=JfGhpmHIB2C2GItdGI1kuC8uayqEVgrpQvAWAj35eZ4,14580
39
+ amd_debug/templates/md,sha256=r8X2aehnH2gzj0WHYTZ5K9wAqC5y39i_3nkDORSC0uM,787
40
+ amd_debug/templates/stdout,sha256=hyoOJ96K2dJfnWRWhyCuariLKbEHXvs9mstV_g5aMdI,469
41
+ amd_debug/templates/txt,sha256=nNdsvbPFOhGdL7VA-_4k5aN3nB-6ouGQt6AsWst7T3w,649
42
+ amd_debug_tools-0.2.9.dist-info/licenses/LICENSE,sha256=RBlZI6r3MRGzymI2VDX2iW__D2APDbMhu_Xg5t6BWeo,1066
43
+ amd_debug_tools-0.2.9.dist-info/METADATA,sha256=4rIp2I7Oipo1o3hCRtw4CoCf3IliJjyJcUV6SnYhkrE,2772
44
+ amd_debug_tools-0.2.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
+ amd_debug_tools-0.2.9.dist-info/entry_points.txt,sha256=hIskDz6k0_6q1qpqWCpVFsca_djxAqkLrUAwzAyEGuE,144
46
+ amd_debug_tools-0.2.9.dist-info/top_level.txt,sha256=VvGkkY5I7O3HoLNrc2VfgjCA-to3PUjnnKd7juONaFw,243
47
+ amd_debug_tools-0.2.9.dist-info/RECORD,,
test_common.py CHANGED
@@ -4,13 +4,16 @@
4
4
  """
5
5
  This module contains unit tests for the common functions in the amd-debug-tools package.
6
6
  """
7
- from unittest.mock import patch, mock_open, call
7
+ from unittest.mock import patch, mock_open, call, Mock
8
8
 
9
+ import asyncio
10
+ import builtins
9
11
  import logging
10
12
  import tempfile
11
13
  import unittest
12
14
  import os
13
15
  from platform import uname_result
16
+ import sys
14
17
 
15
18
 
16
19
  from amd_debug.common import (
@@ -49,6 +52,7 @@ color_dict = {
49
52
  "○": Colors.OK,
50
53
  "💤": Colors.OK,
51
54
  "💯": Colors.UNDERLINE,
55
+ "🚫": Colors.UNDERLINE,
52
56
  "🗣️": Colors.HEADER,
53
57
  }
54
58
 
@@ -473,63 +477,84 @@ class TestCommon(unittest.TestCase):
473
477
  self.assertAlmostEqual(get_system_mem(), expected_gb)
474
478
  mock_file.assert_called_once_with("/proc/meminfo", "r", encoding="utf-8")
475
479
 
476
- @patch("builtins.open", new_callable=mock_open, read_data="NoMemHere: 1234\n")
477
- @patch("os.path.join", return_value="/proc/meminfo")
478
- def test_get_system_mem_missing(self, _mock_join, _mock_file):
479
- """Test get_system_mem raises ValueError if MemTotal is missing"""
480
- with self.assertRaises(ValueError):
481
- get_system_mem()
482
-
483
- @patch("amd_debug.common.fatal_error")
484
- def test_reboot_importerror(self, mock_fatal_error):
485
- """Test reboot handles ImportError"""
486
- with patch.dict("sys.modules", {"dbus": None}):
487
- reboot()
488
- mock_fatal_error.assert_called_once_with("Missing dbus")
489
-
490
- @patch("amd_debug.common.fatal_error")
491
- def test_reboot_dbus_exception(self, mock_fatal_error):
492
- """Test reboot handles dbus.exceptions.DBusException"""
493
-
494
- class DummyDBusException(Exception):
495
- """Dummy exception"""
496
-
497
- class DummyIntf:
498
- """Dummy interface"""
480
+ def test_reboot_dbus_fast_success(self):
481
+ """Test reboot returns True when reboot_dbus_fast succeeds"""
499
482
 
500
- def Reboot(self, _arg): # pylint: disable=invalid-name
501
- """Dummy Reboot method"""
502
- raise DummyDBusException("fail")
503
-
504
- class DummyObj: # pylint: disable=too-few-public-methods
505
- """Dummy object"""
506
-
507
- def __init__(self):
483
+ # Create a mock loop that properly handles coroutines
484
+ def mock_run_until_complete(coro):
485
+ # Consume the coroutine to prevent the warning
486
+ try:
487
+ # Close the coroutine to prevent the warning
488
+ coro.close()
489
+ except (AttributeError, RuntimeError):
508
490
  pass
491
+ return True
509
492
 
510
- class DummyBus: # pylint: disable=too-few-public-methods
511
- """Dummy bus"""
493
+ mock_loop = Mock()
494
+ mock_loop.run_until_complete.side_effect = mock_run_until_complete
512
495
 
513
- def get_object(self, *args, **kwargs):
514
- """Dummy get_object method"""
515
- return DummyObj()
496
+ with patch("amd_debug.common.asyncio.get_event_loop", return_value=mock_loop):
497
+ result = reboot()
498
+ self.assertTrue(result)
499
+ mock_loop.run_until_complete.assert_called_once()
516
500
 
517
- class DummyDBus:
518
- """Dummy dbus"""
501
+ @patch("asyncio.get_event_loop")
502
+ def test_reboot_dbus_fast_failure_and_dbus_success(self, mock_get_event_loop):
503
+ """Test reboot falls back to reboot_dbus when reboot_dbus_fast fails"""
519
504
 
520
- class exceptions: # pylint: disable=invalid-name
521
- """Dummy exceptions"""
505
+ # Create a mock loop that properly handles coroutines
506
+ def mock_run_until_complete(coro):
507
+ # Consume the coroutine to prevent the warning
508
+ try:
509
+ coro.close()
510
+ except (AttributeError, RuntimeError):
511
+ pass
512
+ return False
513
+
514
+ mock_loop = Mock()
515
+ mock_loop.run_until_complete.side_effect = mock_run_until_complete
516
+ mock_get_event_loop.return_value = mock_loop
517
+
518
+ # Mock the dbus module to avoid ImportError in CI
519
+ mock_dbus = Mock()
520
+ mock_bus = Mock()
521
+ mock_obj = Mock()
522
+ mock_intf = Mock()
523
+
524
+ mock_dbus.SystemBus.return_value = mock_bus
525
+ mock_bus.get_object.return_value = mock_obj
526
+ mock_obj.get_interface.return_value = mock_intf
527
+ mock_dbus.Interface = Mock(return_value=mock_intf)
528
+
529
+ with patch.dict("sys.modules", {"dbus": mock_dbus}):
530
+ result = reboot()
531
+ self.assertTrue(result)
532
+
533
+ @patch("asyncio.get_event_loop")
534
+ def test_reboot_dbus_fast_failure_and_dbus_failure(self, mock_get_event_loop):
535
+ """Test reboot returns False when both reboot_dbus_fast and reboot_dbus fail"""
536
+
537
+ # Create a mock loop that properly handles coroutines
538
+ def mock_run_until_complete(coro):
539
+ # Consume the coroutine to prevent the warning
540
+ try:
541
+ coro.close()
542
+ except (AttributeError, RuntimeError):
543
+ pass
544
+ return False
522
545
 
523
- DBusException = DummyDBusException
546
+ mock_loop = Mock()
547
+ mock_loop.run_until_complete.side_effect = mock_run_until_complete
548
+ mock_get_event_loop.return_value = mock_loop
524
549
 
525
- def SystemBus(self): # pylint: disable=invalid-name
526
- """Dummy SystemBus method"""
527
- return DummyBus()
550
+ # Mock the import to raise ImportError when dbus is imported
551
+ original_import = builtins.__import__
528
552
 
529
- def Interface(self, _obj, _name): # pylint: disable=invalid-name
530
- """Dummy Interface method"""
531
- return DummyIntf()
553
+ def mock_import(name, *args, **kwargs):
554
+ if name == "dbus":
555
+ raise ImportError("No module named 'dbus'")
556
+ return original_import(name, *args, **kwargs)
532
557
 
533
- with patch.dict("sys.modules", {"dbus": DummyDBus()}):
534
- reboot()
535
- self.assertTrue(mock_fatal_error.called)
558
+ with patch("builtins.__import__", side_effect=mock_import):
559
+ result = reboot()
560
+ self.assertFalse(result)
test_database.py CHANGED
@@ -255,7 +255,7 @@ class TestSleepDatabase(unittest.TestCase):
255
255
  def test_get_last_prereq_ts_no_data(self):
256
256
  """Test getting the last prereq timestamp when no data exists"""
257
257
  result = self.db.get_last_prereq_ts()
258
- self.assertIsNone(result)
258
+ self.assertEqual(result, 0)
259
259
 
260
260
  def test_report_cycle_data(self):
261
261
  """Test reporting cycle data"""
test_display.py CHANGED
@@ -40,7 +40,7 @@ class TestDisplay(unittest.TestCase):
40
40
  display = Display()
41
41
 
42
42
  # Verify the EDID paths are correctly set
43
- expected_edid = {"card0": "/sys/devices/card0/edid"}
43
+ expected_edid = ["/sys/devices/card0/edid"]
44
44
  self.assertEqual(display.get_edid(), expected_edid)
45
45
  mock_context.assert_called_once()
46
46
 
@@ -54,7 +54,7 @@ class TestDisplay(unittest.TestCase):
54
54
  display = Display()
55
55
 
56
56
  # Verify the EDID dictionary is empty
57
- self.assertEqual(display.get_edid(), {})
57
+ self.assertEqual(display.get_edid(), [])
58
58
 
59
59
  @patch("amd_debug.display.Context")
60
60
  def test_device_without_card(self, mock_context):
@@ -70,7 +70,7 @@ class TestDisplay(unittest.TestCase):
70
70
  display = Display()
71
71
 
72
72
  # Verify the EDID dictionary is empty
73
- self.assertEqual(display.get_edid(), {})
73
+ self.assertEqual(display.get_edid(), [])
74
74
 
75
75
  @patch("amd_debug.display.Context")
76
76
  @patch("amd_debug.display.read_file")
@@ -94,7 +94,7 @@ class TestDisplay(unittest.TestCase):
94
94
  display = Display()
95
95
 
96
96
  # Verify the EDID dictionary is empty
97
- self.assertEqual(display.get_edid(), {})
97
+ self.assertEqual(display.get_edid(), [])
98
98
 
99
99
  @patch("amd_debug.display.Context")
100
100
  @patch("amd_debug.display.read_file")
@@ -116,7 +116,7 @@ class TestDisplay(unittest.TestCase):
116
116
  display = Display()
117
117
 
118
118
  # Verify the EDID dictionary is empty
119
- self.assertEqual(display.get_edid(), {})
119
+ self.assertEqual(display.get_edid(), [])
120
120
 
121
121
  @patch("amd_debug.display.Context")
122
122
  @patch("amd_debug.display.read_file")
@@ -140,4 +140,4 @@ class TestDisplay(unittest.TestCase):
140
140
  display = Display()
141
141
 
142
142
  # Verify the EDID dictionary is empty
143
- self.assertEqual(display.get_edid(), {})
143
+ self.assertEqual(display.get_edid(), [])
test_installer.py CHANGED
@@ -231,7 +231,7 @@ class TestInstaller(unittest.TestCase):
231
231
  self.installer.set_requirements("edid-decode")
232
232
  ret = self.installer.install_dependencies()
233
233
  _mock_check_call.assert_called_once_with(
234
- ["dnf", "install", "-y", "libdisplay-info"]
234
+ ["dnf", "install", "-y", "libdisplay-info-tools"]
235
235
  )
236
236
  self.assertTrue(ret)
237
237
 
test_prerequisites.py CHANGED
@@ -2035,3 +2035,180 @@ class TestPrerequisiteValidator(unittest.TestCase):
2035
2035
  self.mock_db.record_prereq.assert_called_with(
2036
2036
  "NVIDIA GPU {f} not readable", "👀"
2037
2037
  )
2038
+
2039
+ @patch("amd_debug.prerequisites.os.walk")
2040
+ @patch(
2041
+ "builtins.open",
2042
+ new_callable=unittest.mock.mock_open,
2043
+ read_data=b"C1 state info",
2044
+ )
2045
+ def test_capture_cstates_single_file(self, mock_open, mock_walk):
2046
+ """Test capture_cstates with a single cpuidle file"""
2047
+ mock_walk.return_value = [
2048
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1"]),
2049
+ ]
2050
+ self.validator.capture_cstates()
2051
+ self.mock_db.record_debug.assert_called_with(
2052
+ "ACPI C-state information\n└─/sys/bus/cpu/devices/cpu0/cpuidle/state1: C1 state info"
2053
+ )
2054
+
2055
+ @patch("amd_debug.prerequisites.os.walk")
2056
+ @patch("builtins.open", new_callable=mock_open)
2057
+ def test_capture_cstates_multiple_files(self, mock_open_func, mock_walk):
2058
+ """Test capture_cstates with multiple cpuidle files"""
2059
+ # Setup mock file reads for two files
2060
+ file_contents = {
2061
+ "/sys/bus/cpu/devices/cpu0/cpuidle/state1": b"C1 info",
2062
+ "/sys/bus/cpu/devices/cpu0/cpuidle/state2": b"C2 info",
2063
+ }
2064
+
2065
+ def side_effect(path, mode="rb"):
2066
+ mock_file = mock_open(read_data=file_contents[path])()
2067
+ return mock_file
2068
+
2069
+ mock_open_func.side_effect = side_effect
2070
+ mock_walk.return_value = [
2071
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1", "state2"]),
2072
+ ]
2073
+ self.validator.capture_cstates()
2074
+ # The prefix logic is based on order, so check for both lines
2075
+ debug_call = self.mock_db.record_debug.call_args[0][0]
2076
+ self.assertIn("/sys/bus/cpu/devices/cpu0/cpuidle/state1: C1 info", debug_call)
2077
+ self.assertIn("/sys/bus/cpu/devices/cpu0/cpuidle/state2: C2 info", debug_call)
2078
+ self.assertTrue(debug_call.startswith("ACPI C-state information\n"))
2079
+
2080
+ @patch("amd_debug.prerequisites.os.walk")
2081
+ @patch("builtins.open", new_callable=mock_open, read_data=b"")
2082
+ def test_capture_cstates_empty_files(self, _mock_open, mock_walk):
2083
+ """Test capture_cstates with empty cpuidle files"""
2084
+ mock_walk.return_value = [
2085
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1"]),
2086
+ ]
2087
+ self.validator.capture_cstates()
2088
+ self.mock_db.record_debug.assert_called_with(
2089
+ "ACPI C-state information\n└─/sys/bus/cpu/devices/cpu0/cpuidle/state1: "
2090
+ )
2091
+
2092
+ @patch("amd_debug.prerequisites.os.walk")
2093
+ @patch("builtins.open", side_effect=PermissionError)
2094
+ def test_capture_cstates_permission_error(self, _mock_open, mock_walk):
2095
+ """Test capture_cstates when reading cpuidle files raises PermissionError"""
2096
+ mock_walk.return_value = [
2097
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1"]),
2098
+ ]
2099
+ with self.assertRaises(PermissionError):
2100
+ self.validator.capture_cstates()
2101
+ self.mock_db.record_debug.assert_not_called()
2102
+
2103
+ @patch("amd_debug.prerequisites.os.walk")
2104
+ def test_capture_cstates_no_files(self, mock_walk):
2105
+ """Test capture_cstates when no cpuidle files are present"""
2106
+ mock_walk.return_value = [
2107
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], []),
2108
+ ]
2109
+ self.validator.capture_cstates()
2110
+ self.mock_db.record_debug.assert_called_with("ACPI C-state information\n")
2111
+
2112
+ @patch("amd_debug.prerequisites.read_file")
2113
+ def test_check_pinctrl_amd_driver_loaded_with_missing_file_error(
2114
+ self, mock_read_file
2115
+ ):
2116
+ """Test check_pinctrl_amd when the driver is loaded but debug file is missing"""
2117
+ mock_read_file.side_effect = FileNotFoundError
2118
+ self.mock_pyudev.list_devices.return_value = [
2119
+ MagicMock(properties={"DRIVER": "amd_gpio"})
2120
+ ]
2121
+
2122
+ result = self.validator.check_pinctrl_amd()
2123
+ self.assertTrue(result)
2124
+ self.mock_db.record_prereq.assert_called_with(
2125
+ "GPIO debugfs not available", "👀"
2126
+ )
2127
+
2128
+ def test_check_amdgpu_no_devices(self):
2129
+ """Test check_amdgpu when no PCI devices are found"""
2130
+ self.mock_pyudev.list_devices.return_value = []
2131
+ result = self.validator.check_amdgpu()
2132
+ self.assertFalse(result)
2133
+ self.mock_db.record_prereq.assert_called_with("Integrated GPU not found", "❌")
2134
+ self.assertTrue(any(isinstance(f, MissingGpu) for f in self.validator.failures))
2135
+
2136
+ def test_check_amdgpu_non_amd_devices(self):
2137
+ """Test check_amdgpu when PCI devices are present but not AMD GPUs"""
2138
+ self.mock_pyudev.list_devices.return_value = [
2139
+ MagicMock(
2140
+ properties={
2141
+ "PCI_CLASS": "30000",
2142
+ "PCI_ID": "8086abcd",
2143
+ "DRIVER": "i915",
2144
+ }
2145
+ ),
2146
+ ]
2147
+ result = self.validator.check_amdgpu()
2148
+ self.assertFalse(result)
2149
+ self.mock_db.record_prereq.assert_called_with("Integrated GPU not found", "❌")
2150
+ self.assertTrue(any(isinstance(f, MissingGpu) for f in self.validator.failures))
2151
+
2152
+ def test_check_amdgpu_driver_not_loaded(self):
2153
+ """Test check_amdgpu when AMD GPU is present but driver is not loaded"""
2154
+ self.mock_pyudev.list_devices.return_value = [
2155
+ MagicMock(
2156
+ properties={"PCI_CLASS": "20000", "PCI_ID": "1111abcd", "DRIVER": None}
2157
+ ),
2158
+ MagicMock(
2159
+ properties={"PCI_CLASS": "30000", "PCI_ID": "1002abcd", "DRIVER": None}
2160
+ ),
2161
+ ]
2162
+ result = self.validator.check_amdgpu()
2163
+ self.assertFalse(result)
2164
+ self.mock_db.record_prereq.assert_called_with(
2165
+ "GPU driver `amdgpu` not loaded", "❌"
2166
+ )
2167
+ self.assertTrue(
2168
+ any(isinstance(f, MissingAmdgpu) for f in self.validator.failures)
2169
+ )
2170
+
2171
+ def test_check_amdgpu_driver_loaded(self):
2172
+ """Test check_amdgpu when AMD GPU is present and driver is loaded"""
2173
+ self.mock_pyudev.list_devices.return_value = [
2174
+ MagicMock(
2175
+ properties={
2176
+ "PCI_CLASS": "30000",
2177
+ "PCI_ID": "1002abcd",
2178
+ "DRIVER": "amdgpu",
2179
+ "PCI_SLOT_NAME": "0000:01:00.0",
2180
+ }
2181
+ ),
2182
+ ]
2183
+ result = self.validator.check_amdgpu()
2184
+ self.assertTrue(result)
2185
+ self.mock_db.record_prereq.assert_called_with(
2186
+ "GPU driver `amdgpu` bound to 0000:01:00.0", "✅"
2187
+ )
2188
+
2189
+ def test_check_amdgpu_multiple_devices_mixed(self):
2190
+ """Test check_amdgpu with multiple devices, one with driver loaded, one without"""
2191
+ self.mock_pyudev.list_devices.return_value = [
2192
+ MagicMock(
2193
+ properties={
2194
+ "PCI_CLASS": "30000",
2195
+ "PCI_ID": "1002abcd",
2196
+ "DRIVER": "amdgpu",
2197
+ "PCI_SLOT_NAME": "0000:01:00.0",
2198
+ }
2199
+ ),
2200
+ MagicMock(
2201
+ properties={"PCI_CLASS": "30000", "PCI_ID": "1002abcd", "DRIVER": None}
2202
+ ),
2203
+ ]
2204
+ result = self.validator.check_amdgpu()
2205
+ self.assertFalse(result)
2206
+ self.mock_db.record_prereq.assert_any_call(
2207
+ "GPU driver `amdgpu` bound to 0000:01:00.0", "✅"
2208
+ )
2209
+ self.mock_db.record_prereq.assert_any_call(
2210
+ "GPU driver `amdgpu` not loaded", "❌"
2211
+ )
2212
+ self.assertTrue(
2213
+ any(isinstance(f, MissingAmdgpu) for f in self.validator.failures)
2214
+ )
test_validator.py CHANGED
@@ -264,7 +264,7 @@ class TestValidator(unittest.TestCase):
264
264
 
265
265
  # Validate debug messages
266
266
  mock_record_debug.assert_called_once_with(
267
- "Woke up from input source /sys/devices/input0 (3->5)", "💤"
267
+ "Woke up from input source /sys/devices/input0 (3->5)"
268
268
  )
269
269
 
270
270
  # Stop patches
@@ -557,7 +557,7 @@ class TestValidator(unittest.TestCase):
557
557
  # Set attributes for record_cycle
558
558
  self.validator.requested_duration = 60
559
559
  self.validator.active_gpios = ["GPIO1"]
560
- self.validator.wakeup_irqs = [5]
560
+ self.validator.wakeup_irqs = ["5"]
561
561
  self.validator.kernel_duration = 1.5
562
562
  self.validator.hw_sleep_duration = 1.0
563
563
 
@@ -580,10 +580,10 @@ class TestValidator(unittest.TestCase):
580
580
  # Assert record_cycle was called with correct arguments
581
581
  mock_record_cycle.assert_called_once_with(
582
582
  self.validator.requested_duration,
583
- self.validator.active_gpios,
584
- self.validator.wakeup_irqs,
585
- self.validator.kernel_duration,
586
- self.validator.hw_sleep_duration,
583
+ ",".join(str(gpio) for gpio in self.validator.active_gpios),
584
+ ",".join(str(irq) for irq in self.validator.wakeup_irqs),
585
+ int(self.validator.kernel_duration),
586
+ int(self.validator.hw_sleep_duration),
587
587
  )
588
588
 
589
589
  def test_program_wakealarm(self):