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_s2idle.py
CHANGED
|
@@ -117,9 +117,9 @@ class TestParseArgs(unittest.TestCase):
|
|
|
117
117
|
|
|
118
118
|
def test_version_command(self):
|
|
119
119
|
"""Test parse_args with version command"""
|
|
120
|
-
sys.argv = ["s2idle.py", "version"]
|
|
120
|
+
sys.argv = ["s2idle.py", "--version"]
|
|
121
121
|
args = parse_args()
|
|
122
|
-
self.
|
|
122
|
+
self.assertTrue(args.version)
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
class TestMainFunction(unittest.TestCase):
|
|
@@ -214,7 +214,7 @@ class TestMainFunction(unittest.TestCase):
|
|
|
214
214
|
"""Test main function with version action"""
|
|
215
215
|
sys.argv = ["s2idle.py", "version"]
|
|
216
216
|
with patch("amd_debug.s2idle.parse_args") as mock_parse_args:
|
|
217
|
-
mock_parse_args.return_value = argparse.Namespace(action=
|
|
217
|
+
mock_parse_args.return_value = argparse.Namespace(version=True, action=None)
|
|
218
218
|
mock_version.return_value = "1.0.0"
|
|
219
219
|
with patch("builtins.print") as mock_print:
|
|
220
220
|
result = main()
|
|
@@ -226,7 +226,9 @@ class TestMainFunction(unittest.TestCase):
|
|
|
226
226
|
"""Test main function with no action specified"""
|
|
227
227
|
sys.argv = ["s2idle.py"]
|
|
228
228
|
with patch("amd_debug.s2idle.parse_args") as mock_parse_args:
|
|
229
|
-
mock_parse_args.return_value = argparse.Namespace(
|
|
229
|
+
mock_parse_args.return_value = argparse.Namespace(
|
|
230
|
+
version=False, action=None
|
|
231
|
+
)
|
|
230
232
|
with self.assertRaises(SystemExit) as cm:
|
|
231
233
|
main()
|
|
232
234
|
self.assertEqual(cm.exception.code, "no action specified")
|
|
@@ -358,8 +360,10 @@ class TestTestFunction(unittest.TestCase):
|
|
|
358
360
|
@patch("amd_debug.s2idle.prompt_test_arguments")
|
|
359
361
|
@patch("amd_debug.s2idle.prompt_report_arguments")
|
|
360
362
|
@patch("amd_debug.s2idle.display_report_file")
|
|
363
|
+
@patch("amd_debug.s2idle.datetime")
|
|
361
364
|
def test_test_success(
|
|
362
365
|
self,
|
|
366
|
+
mock_datetime,
|
|
363
367
|
mock_display_report_file,
|
|
364
368
|
mock_prompt_report_arguments,
|
|
365
369
|
mock_prompt_test_arguments,
|
|
@@ -369,6 +373,11 @@ class TestTestFunction(unittest.TestCase):
|
|
|
369
373
|
mock_installer,
|
|
370
374
|
):
|
|
371
375
|
"""Test the test function when everything succeeds"""
|
|
376
|
+
from datetime import datetime
|
|
377
|
+
|
|
378
|
+
mock_datetime.now.return_value = datetime(2023, 2, 1, 0, 0, 0)
|
|
379
|
+
mock_datetime.side_effect = lambda *args, **kwargs: datetime(*args, **kwargs)
|
|
380
|
+
|
|
372
381
|
mock_installer_instance = mock_installer.return_value
|
|
373
382
|
mock_installer_instance.install_dependencies.return_value = True
|
|
374
383
|
|
|
@@ -414,9 +423,11 @@ class TestTestFunction(unittest.TestCase):
|
|
|
414
423
|
mock_sleep_validator_instance.run.assert_called_once_with(
|
|
415
424
|
duration=10, wait=5, count=3, rand=False, logind=False
|
|
416
425
|
)
|
|
426
|
+
from datetime import datetime
|
|
427
|
+
|
|
417
428
|
mock_sleep_report.assert_called_once_with(
|
|
418
429
|
since="2023-01-01",
|
|
419
|
-
until=
|
|
430
|
+
until=datetime(2023, 2, 1, 0, 0, 0),
|
|
420
431
|
fname="report.html",
|
|
421
432
|
fmt="html",
|
|
422
433
|
tool_debug=True,
|
|
@@ -600,12 +611,18 @@ class TestDisplayReportFile(unittest.TestCase):
|
|
|
600
611
|
self, mock_subprocess_call, mock_env_get, mock_is_root
|
|
601
612
|
):
|
|
602
613
|
"""Test display_report_file when format is html, user is root, and SUDO_USER is set"""
|
|
603
|
-
|
|
614
|
+
with self.assertRaises(SystemExit):
|
|
615
|
+
display_report_file("/tmp/report.html", "html")
|
|
604
616
|
mock_is_root.assert_called_once()
|
|
605
617
|
mock_env_get.assert_any_call("SUDO_USER")
|
|
606
|
-
mock_env_get.assert_any_call("XDG_SESSION_TYPE")
|
|
607
618
|
mock_subprocess_call.assert_called_once_with(
|
|
608
|
-
[
|
|
619
|
+
[
|
|
620
|
+
"systemd-run",
|
|
621
|
+
"--user",
|
|
622
|
+
"--machine=testuser@.host",
|
|
623
|
+
"xdg-open",
|
|
624
|
+
"/tmp/report.html",
|
|
625
|
+
]
|
|
609
626
|
)
|
|
610
627
|
|
|
611
628
|
@patch("amd_debug.s2idle.is_root", return_value=True)
|
test_ttm.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
This module contains unit tests for the ttm tool in the amd-debug-tools package.
|
|
6
|
+
"""
|
|
7
|
+
import unittest
|
|
8
|
+
import sys
|
|
9
|
+
import logging
|
|
10
|
+
from unittest import mock
|
|
11
|
+
|
|
12
|
+
from amd_debug.ttm import main, parse_args, AmdTtmTool, maybe_reboot
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestParseArgs(unittest.TestCase):
|
|
16
|
+
"""Test parse_args function"""
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def setUpClass(cls):
|
|
20
|
+
logging.basicConfig(filename="/dev/null", level=logging.DEBUG)
|
|
21
|
+
|
|
22
|
+
def setUp(self):
|
|
23
|
+
self.default_sys_argv = sys.argv
|
|
24
|
+
|
|
25
|
+
def tearDown(self):
|
|
26
|
+
sys.argv = self.default_sys_argv
|
|
27
|
+
|
|
28
|
+
@mock.patch.object(sys, "argv", new=["ttm", "--version"])
|
|
29
|
+
def test_parse_args_version(self):
|
|
30
|
+
"""Test version argument"""
|
|
31
|
+
args = parse_args()
|
|
32
|
+
self.assertTrue(args.version)
|
|
33
|
+
self.assertFalse(args.set)
|
|
34
|
+
self.assertFalse(args.clear)
|
|
35
|
+
|
|
36
|
+
@mock.patch.object(sys, "argv", new=["ttm", "--clear"])
|
|
37
|
+
def test_parse_args_clear(self):
|
|
38
|
+
"""Test clear argument"""
|
|
39
|
+
args = parse_args()
|
|
40
|
+
self.assertFalse(args.version)
|
|
41
|
+
self.assertFalse(args.set)
|
|
42
|
+
self.assertTrue(args.clear)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TestMainFunction(unittest.TestCase):
|
|
46
|
+
"""Test main() function logic"""
|
|
47
|
+
|
|
48
|
+
@mock.patch("amd_debug.ttm.parse_args")
|
|
49
|
+
@mock.patch("amd_debug.ttm.version", return_value="1.2.3")
|
|
50
|
+
@mock.patch("builtins.print")
|
|
51
|
+
def test_main_version(self, mock_print, _mock_version, mock_parse_args):
|
|
52
|
+
"""Test main function with version argument"""
|
|
53
|
+
mock_parse_args.return_value = mock.Mock(
|
|
54
|
+
version=True, set=None, clear=False, tool_debug=False
|
|
55
|
+
)
|
|
56
|
+
ret = main()
|
|
57
|
+
mock_print.assert_called_with("1.2.3")
|
|
58
|
+
self.assertIsNone(ret)
|
|
59
|
+
|
|
60
|
+
@mock.patch("amd_debug.ttm.parse_args")
|
|
61
|
+
@mock.patch("amd_debug.ttm.AmdTtmTool")
|
|
62
|
+
@mock.patch("builtins.print")
|
|
63
|
+
def test_main_set_invalid(self, mock_print, _mock_tool, mock_parse_args):
|
|
64
|
+
"""Test main function with invalid set argument"""
|
|
65
|
+
mock_parse_args.return_value = mock.Mock(
|
|
66
|
+
version=False, set=0, clear=False, tool_debug=False
|
|
67
|
+
)
|
|
68
|
+
ret = main()
|
|
69
|
+
mock_print.assert_called_with("Error: GB value must be greater than 0")
|
|
70
|
+
self.assertEqual(ret, 1)
|
|
71
|
+
|
|
72
|
+
@mock.patch("amd_debug.ttm.parse_args")
|
|
73
|
+
@mock.patch("amd_debug.ttm.AmdTtmTool")
|
|
74
|
+
def test_main_set_valid(self, mock_tool, mock_parse_args):
|
|
75
|
+
"""Test main function with set argument"""
|
|
76
|
+
instance = mock_tool.return_value
|
|
77
|
+
instance.set.return_value = True
|
|
78
|
+
mock_parse_args.return_value = mock.Mock(
|
|
79
|
+
version=False, set=2, clear=False, tool_debug=False
|
|
80
|
+
)
|
|
81
|
+
ret = main()
|
|
82
|
+
instance.set.assert_called_with(2)
|
|
83
|
+
self.assertIsNone(ret)
|
|
84
|
+
|
|
85
|
+
@mock.patch("amd_debug.ttm.parse_args")
|
|
86
|
+
@mock.patch("amd_debug.ttm.AmdTtmTool")
|
|
87
|
+
def test_main_set_failed(self, mock_tool, mock_parse_args):
|
|
88
|
+
instance = mock_tool.return_value
|
|
89
|
+
instance.set.return_value = False
|
|
90
|
+
mock_parse_args.return_value = mock.Mock(
|
|
91
|
+
version=False, set=2, clear=False, tool_debug=False
|
|
92
|
+
)
|
|
93
|
+
ret = main()
|
|
94
|
+
instance.set.assert_called_with(2)
|
|
95
|
+
self.assertEqual(ret, 1)
|
|
96
|
+
|
|
97
|
+
@mock.patch("amd_debug.ttm.parse_args")
|
|
98
|
+
@mock.patch("amd_debug.ttm.AmdTtmTool")
|
|
99
|
+
def test_main_clear_success(self, mock_tool, mock_parse_args):
|
|
100
|
+
"""Test main function with clear argument"""
|
|
101
|
+
instance = mock_tool.return_value
|
|
102
|
+
instance.clear.return_value = True
|
|
103
|
+
mock_parse_args.return_value = mock.Mock(
|
|
104
|
+
version=False, set=None, clear=True, tool_debug=False
|
|
105
|
+
)
|
|
106
|
+
ret = main()
|
|
107
|
+
instance.clear.assert_called_once()
|
|
108
|
+
self.assertIsNone(ret)
|
|
109
|
+
|
|
110
|
+
@mock.patch("amd_debug.ttm.parse_args")
|
|
111
|
+
@mock.patch("amd_debug.ttm.AmdTtmTool")
|
|
112
|
+
def test_main_clear_failed(self, mock_tool, mock_parse_args):
|
|
113
|
+
"""Test main function with clear argument failure"""
|
|
114
|
+
instance = mock_tool.return_value
|
|
115
|
+
instance.clear.return_value = False
|
|
116
|
+
mock_parse_args.return_value = mock.Mock(
|
|
117
|
+
version=False, set=None, clear=True, tool_debug=False
|
|
118
|
+
)
|
|
119
|
+
ret = main()
|
|
120
|
+
instance.clear.assert_called_once()
|
|
121
|
+
self.assertEqual(ret, 1)
|
|
122
|
+
|
|
123
|
+
@mock.patch("amd_debug.ttm.parse_args")
|
|
124
|
+
@mock.patch("amd_debug.ttm.AmdTtmTool")
|
|
125
|
+
def test_main_get_success(self, mock_tool, mock_parse_args):
|
|
126
|
+
"""Test main function with get argument"""
|
|
127
|
+
instance = mock_tool.return_value
|
|
128
|
+
instance.get.return_value = True
|
|
129
|
+
mock_parse_args.return_value = mock.Mock(
|
|
130
|
+
version=False, set=None, clear=False, tool_debug=False
|
|
131
|
+
)
|
|
132
|
+
ret = main()
|
|
133
|
+
instance.get.assert_called_once()
|
|
134
|
+
self.assertIsNone(ret)
|
|
135
|
+
|
|
136
|
+
@mock.patch("amd_debug.ttm.parse_args")
|
|
137
|
+
@mock.patch("amd_debug.ttm.AmdTtmTool")
|
|
138
|
+
def test_main_get_failed(self, mock_tool, mock_parse_args):
|
|
139
|
+
"""Test main function with get argument failure"""
|
|
140
|
+
instance = mock_tool.return_value
|
|
141
|
+
instance.get.return_value = False
|
|
142
|
+
mock_parse_args.return_value = mock.Mock(
|
|
143
|
+
version=False, set=None, clear=False, tool_debug=False
|
|
144
|
+
)
|
|
145
|
+
ret = main()
|
|
146
|
+
instance.get.assert_called_once()
|
|
147
|
+
self.assertEqual(ret, 1)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestMaybeReboot(unittest.TestCase):
|
|
151
|
+
"""Test maybe_reboot function"""
|
|
152
|
+
|
|
153
|
+
@mock.patch("builtins.input", return_value="y")
|
|
154
|
+
@mock.patch("amd_debug.ttm.reboot", return_value=True)
|
|
155
|
+
def test_maybe_reboot_yes(self, mock_reboot, _mock_input):
|
|
156
|
+
"""Test reboot confirmation and execution"""
|
|
157
|
+
result = maybe_reboot()
|
|
158
|
+
mock_reboot.assert_called_once()
|
|
159
|
+
self.assertTrue(result)
|
|
160
|
+
|
|
161
|
+
@mock.patch("builtins.input", return_value="n")
|
|
162
|
+
@mock.patch("amd_debug.ttm.reboot", return_value=True)
|
|
163
|
+
def test_maybe_reboot_no(self, mock_reboot, _mock_input):
|
|
164
|
+
"""Test reboot confirmation without execution"""
|
|
165
|
+
result = maybe_reboot()
|
|
166
|
+
mock_reboot.assert_not_called()
|
|
167
|
+
self.assertTrue(result)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TestAmdTtmTool(unittest.TestCase):
|
|
171
|
+
"""Unit tests for AmdTtmTool class"""
|
|
172
|
+
|
|
173
|
+
def setUp(self):
|
|
174
|
+
self.tool = AmdTtmTool(logging=False)
|
|
175
|
+
|
|
176
|
+
@mock.patch("builtins.open", new_callable=mock.mock_open, read_data="4096")
|
|
177
|
+
@mock.patch("amd_debug.ttm.bytes_to_gb", return_value=4.0)
|
|
178
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
179
|
+
@mock.patch("amd_debug.ttm.get_system_mem", return_value=16.0)
|
|
180
|
+
def test_get_success(self, _mock_mem, mock_print, _mock_bytes_to_gb, mock_open):
|
|
181
|
+
"""Test get() when TTM_PARAM_PATH exists"""
|
|
182
|
+
result = self.tool.get()
|
|
183
|
+
mock_open.assert_called_once_with(
|
|
184
|
+
"/sys/module/ttm/parameters/pages_limit", "r", encoding="utf-8"
|
|
185
|
+
)
|
|
186
|
+
mock_print.assert_any_call(
|
|
187
|
+
"Current TTM pages limit: 4096 pages (4.00 GB)", "💻"
|
|
188
|
+
)
|
|
189
|
+
mock_print.assert_any_call("Total system memory: 16.00 GB", "💻")
|
|
190
|
+
self.assertTrue(result)
|
|
191
|
+
|
|
192
|
+
@mock.patch("builtins.open", side_effect=FileNotFoundError)
|
|
193
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
194
|
+
def test_get_file_not_found(self, mock_print, _mock_open):
|
|
195
|
+
"""Test get() when TTM_PARAM_PATH does not exist"""
|
|
196
|
+
result = self.tool.get()
|
|
197
|
+
mock_print.assert_called_with(
|
|
198
|
+
"Error: Could not find /sys/module/ttm/parameters/pages_limit", "❌"
|
|
199
|
+
)
|
|
200
|
+
self.assertFalse(result)
|
|
201
|
+
|
|
202
|
+
@mock.patch("amd_debug.ttm.relaunch_sudo", return_value=True)
|
|
203
|
+
@mock.patch("amd_debug.ttm.get_system_mem", return_value=8.0)
|
|
204
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
205
|
+
def test_set_gb_greater_than_total(
|
|
206
|
+
self, mock_print, _mock_mem, _mock_relaunch_sudo
|
|
207
|
+
):
|
|
208
|
+
"""Test set() when gb_value > total system memory"""
|
|
209
|
+
result = self.tool.set(16)
|
|
210
|
+
mock_print.assert_any_call(
|
|
211
|
+
"16.00 GB is greater than total system memory (8.00 GB)", "❌"
|
|
212
|
+
)
|
|
213
|
+
self.assertFalse(result)
|
|
214
|
+
|
|
215
|
+
@mock.patch("amd_debug.ttm.relaunch_sudo", return_value=True)
|
|
216
|
+
@mock.patch("amd_debug.ttm.get_system_mem", return_value=10.0)
|
|
217
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
218
|
+
@mock.patch("builtins.input", return_value="n")
|
|
219
|
+
def test_set_gb_exceeds_max_percentage_cancel(
|
|
220
|
+
self, _mock_input, mock_print, _mock_mem, mock_relaunch_sudo
|
|
221
|
+
):
|
|
222
|
+
"""Test set() when gb_value exceeds max percentage and user cancels"""
|
|
223
|
+
result = self.tool.set(9.5)
|
|
224
|
+
self.assertFalse(result)
|
|
225
|
+
mock_print.assert_any_call("Operation cancelled.", "🚦")
|
|
226
|
+
|
|
227
|
+
@mock.patch("amd_debug.ttm.relaunch_sudo", return_value=True)
|
|
228
|
+
@mock.patch("amd_debug.ttm.get_system_mem", return_value=10.0)
|
|
229
|
+
@mock.patch("amd_debug.ttm.gb_to_pages", return_value=20480)
|
|
230
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
231
|
+
@mock.patch("builtins.open", new_callable=mock.mock_open)
|
|
232
|
+
@mock.patch("builtins.input", return_value="y")
|
|
233
|
+
@mock.patch("amd_debug.ttm.maybe_reboot", return_value=True)
|
|
234
|
+
def test_set_success(
|
|
235
|
+
self,
|
|
236
|
+
_mock_reboot,
|
|
237
|
+
_mock_input,
|
|
238
|
+
mock_open,
|
|
239
|
+
mock_print,
|
|
240
|
+
_mock_gb_to_pages,
|
|
241
|
+
_mock_mem,
|
|
242
|
+
_relaunch_sudo,
|
|
243
|
+
):
|
|
244
|
+
"""Test set() success path"""
|
|
245
|
+
result = self.tool.set(5)
|
|
246
|
+
mock_open.assert_called_once_with(
|
|
247
|
+
"/etc/modprobe.d/ttm.conf", "w", encoding="utf-8"
|
|
248
|
+
)
|
|
249
|
+
mock_print.assert_any_call(
|
|
250
|
+
"Successfully set TTM pages limit to 20480 pages (5.00 GB)", "🐧"
|
|
251
|
+
)
|
|
252
|
+
self.assertTrue(result)
|
|
253
|
+
|
|
254
|
+
@mock.patch("os.path.exists", return_value=False)
|
|
255
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
256
|
+
def test_clear_file_not_exists(self, mock_print, _mock_exists):
|
|
257
|
+
"""Test clear() when config file does not exist"""
|
|
258
|
+
result = self.tool.clear()
|
|
259
|
+
mock_print.assert_called_with("/etc/modprobe.d/ttm.conf doesn't exist", "❌")
|
|
260
|
+
self.assertFalse(result)
|
|
261
|
+
|
|
262
|
+
@mock.patch("os.path.exists", return_value=True)
|
|
263
|
+
@mock.patch("amd_debug.ttm.relaunch_sudo", return_value=True)
|
|
264
|
+
@mock.patch("os.remove")
|
|
265
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
266
|
+
@mock.patch("amd_debug.ttm.maybe_reboot", return_value=True)
|
|
267
|
+
def test_clear_success(
|
|
268
|
+
self, _mock_reboot, mock_print, mock_remove, _mock_relaunch_sudo, _mock_exists
|
|
269
|
+
):
|
|
270
|
+
"""Test clear() success path"""
|
|
271
|
+
result = self.tool.clear()
|
|
272
|
+
mock_remove.assert_called_once_with("/etc/modprobe.d/ttm.conf")
|
|
273
|
+
mock_print.assert_any_call(
|
|
274
|
+
"Configuration /etc/modprobe.d/ttm.conf removed", "🐧"
|
|
275
|
+
)
|
|
276
|
+
self.assertTrue(result)
|
test_validator.py
CHANGED
|
@@ -44,7 +44,7 @@ class TestValidatorHelpers(unittest.TestCase):
|
|
|
44
44
|
return "Test function executed"
|
|
45
45
|
|
|
46
46
|
# Mock /sys/power/pm_debug_messages existing and all ACPI existing
|
|
47
|
-
with patch("
|
|
47
|
+
with patch("amd_debug.validator.open", new_callable=mock_open, read_data="0") as mock_file:
|
|
48
48
|
handlers = (
|
|
49
49
|
mock_file.return_value,
|
|
50
50
|
mock_open(read_data="0").return_value,
|
|
@@ -57,7 +57,7 @@ class TestValidatorHelpers(unittest.TestCase):
|
|
|
57
57
|
|
|
58
58
|
# Mock /sys/power/pm_debug_messages missing
|
|
59
59
|
with patch(
|
|
60
|
-
"
|
|
60
|
+
"amd_debug.validator.open", side_effect=FileNotFoundError("not found")
|
|
61
61
|
) as mock_file:
|
|
62
62
|
with self.assertRaises(FileNotFoundError):
|
|
63
63
|
result = test_function()
|
|
@@ -71,7 +71,8 @@ class TestValidator(unittest.TestCase):
|
|
|
71
71
|
logging.basicConfig(filename="/dev/null", level=logging.DEBUG)
|
|
72
72
|
|
|
73
73
|
@patch("amd_debug.validator.SleepDatabase")
|
|
74
|
-
|
|
74
|
+
@patch("subprocess.run")
|
|
75
|
+
def setUp(self, _db_mock, _mock_run):
|
|
75
76
|
"""Set up a mock context for testing"""
|
|
76
77
|
self.validator = SleepValidator(tool_debug=True, bios_debug=False)
|
|
77
78
|
|
|
@@ -263,7 +264,7 @@ class TestValidator(unittest.TestCase):
|
|
|
263
264
|
|
|
264
265
|
# Validate debug messages
|
|
265
266
|
mock_record_debug.assert_called_once_with(
|
|
266
|
-
"Woke up from input source /sys/devices/input0 (3->5)"
|
|
267
|
+
"Woke up from input source /sys/devices/input0 (3->5)"
|
|
267
268
|
)
|
|
268
269
|
|
|
269
270
|
# Stop patches
|
|
@@ -556,7 +557,7 @@ class TestValidator(unittest.TestCase):
|
|
|
556
557
|
# Set attributes for record_cycle
|
|
557
558
|
self.validator.requested_duration = 60
|
|
558
559
|
self.validator.active_gpios = ["GPIO1"]
|
|
559
|
-
self.validator.wakeup_irqs = [5]
|
|
560
|
+
self.validator.wakeup_irqs = ["5"]
|
|
560
561
|
self.validator.kernel_duration = 1.5
|
|
561
562
|
self.validator.hw_sleep_duration = 1.0
|
|
562
563
|
|
|
@@ -579,10 +580,10 @@ class TestValidator(unittest.TestCase):
|
|
|
579
580
|
# Assert record_cycle was called with correct arguments
|
|
580
581
|
mock_record_cycle.assert_called_once_with(
|
|
581
582
|
self.validator.requested_duration,
|
|
582
|
-
self.validator.active_gpios,
|
|
583
|
-
self.validator.wakeup_irqs,
|
|
584
|
-
self.validator.kernel_duration,
|
|
585
|
-
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),
|
|
586
587
|
)
|
|
587
588
|
|
|
588
589
|
def test_program_wakealarm(self):
|
|
@@ -830,3 +831,64 @@ class TestValidator(unittest.TestCase):
|
|
|
830
831
|
)
|
|
831
832
|
mock_os_write.assert_called_once_with(3, b"mem")
|
|
832
833
|
mock_os_close.assert_called_once_with(3)
|
|
834
|
+
|
|
835
|
+
@patch("os.path.exists")
|
|
836
|
+
@patch("os.open")
|
|
837
|
+
@patch("os.write")
|
|
838
|
+
@patch("os.close")
|
|
839
|
+
def test_toggle_nvidia_file_not_exists(
|
|
840
|
+
self, mock_close, mock_write, mock_open, mock_exists
|
|
841
|
+
):
|
|
842
|
+
"""Test toggle_nvidia returns True if NVIDIA suspend file does not exist"""
|
|
843
|
+
mock_exists.return_value = False
|
|
844
|
+
result = self.validator.toggle_nvidia(b"suspend")
|
|
845
|
+
self.assertTrue(result)
|
|
846
|
+
mock_open.assert_not_called()
|
|
847
|
+
mock_write.assert_not_called()
|
|
848
|
+
mock_close.assert_not_called()
|
|
849
|
+
|
|
850
|
+
@patch("os.path.exists")
|
|
851
|
+
@patch("os.open")
|
|
852
|
+
@patch("os.write")
|
|
853
|
+
@patch("os.close")
|
|
854
|
+
def test_toggle_nvidia_success(
|
|
855
|
+
self, mock_close, mock_write, mock_open, mock_exists
|
|
856
|
+
):
|
|
857
|
+
"""Test toggle_nvidia writes value and returns True on success"""
|
|
858
|
+
mock_exists.return_value = True
|
|
859
|
+
mock_open.return_value = 42
|
|
860
|
+
mock_write.return_value = None
|
|
861
|
+
with patch.object(self.validator.db, "record_debug") as mock_record_debug:
|
|
862
|
+
result = self.validator.toggle_nvidia(b"suspend")
|
|
863
|
+
self.assertTrue(result)
|
|
864
|
+
mock_open.assert_called_once_with(
|
|
865
|
+
"/proc/driver/nvidia/suspend", os.O_WRONLY | os.O_SYNC
|
|
866
|
+
)
|
|
867
|
+
mock_write.assert_called_once_with(42, b"suspend")
|
|
868
|
+
mock_close.assert_called_once_with(42)
|
|
869
|
+
mock_record_debug.assert_called_once_with(
|
|
870
|
+
"Wrote b'suspend' to NVIDIA driver"
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
@patch("os.path.exists")
|
|
874
|
+
@patch("os.open")
|
|
875
|
+
@patch("os.write")
|
|
876
|
+
@patch("os.close")
|
|
877
|
+
def test_toggle_nvidia_oserror(
|
|
878
|
+
self, mock_close, mock_write, mock_open, mock_exists
|
|
879
|
+
):
|
|
880
|
+
"""Test toggle_nvidia handles OSError and returns False"""
|
|
881
|
+
mock_exists.return_value = True
|
|
882
|
+
mock_open.return_value = 99
|
|
883
|
+
mock_write.side_effect = OSError("write error")
|
|
884
|
+
with patch.object(
|
|
885
|
+
self.validator.db, "record_cycle_data"
|
|
886
|
+
) as mock_record_cycle_data:
|
|
887
|
+
result = self.validator.toggle_nvidia(b"resume")
|
|
888
|
+
self.assertFalse(result)
|
|
889
|
+
mock_open.assert_called_once_with(
|
|
890
|
+
"/proc/driver/nvidia/suspend", os.O_WRONLY | os.O_SYNC
|
|
891
|
+
)
|
|
892
|
+
mock_write.assert_called_once_with(99, b"resume")
|
|
893
|
+
mock_close.assert_called_once_with(99)
|
|
894
|
+
mock_record_cycle_data.assert_called_once()
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: amd-debug-tools
|
|
3
|
-
Version: 0.2.5
|
|
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: pyudev
|
|
14
|
-
Requires-Dist: packaging
|
|
15
|
-
Requires-Dist: pandas
|
|
16
|
-
Requires-Dist: jinja2
|
|
17
|
-
Requires-Dist: tabulate
|
|
18
|
-
Requires-Dist: seaborn
|
|
19
|
-
Requires-Dist: cysystemd
|
|
20
|
-
Requires-Dist: Jinja2
|
|
21
|
-
Requires-Dist: matplotlib
|
|
22
|
-
Requires-Dist: seaborn
|
|
23
|
-
Dynamic: license-file
|
|
24
|
-
|
|
25
|
-
# Helpful tools for debugging AMD Zen systems
|
|
26
|
-
[](https://codecov.io/github/superm1/amd-debug-tools)
|
|
27
|
-
[](https://pypi.org/project/amd-debug-tools/)
|
|
28
|
-
|
|
29
|
-
This repository hosts open tools that are useful for debugging issues on AMD systems.
|
|
30
|
-
|
|
31
|
-
## Installation
|
|
32
|
-
It is suggested to install tools in a virtual environment either using
|
|
33
|
-
`pipx` or `python3 -m venv`.
|
|
34
|
-
|
|
35
|
-
### From PyPI
|
|
36
|
-
`amd-debug-tools` is distributed as a python wheel, which is a
|
|
37
|
-
binary package format for Python. To install from PyPI, run the following
|
|
38
|
-
command:
|
|
39
|
-
|
|
40
|
-
pipx install amd-debug-tools
|
|
41
|
-
|
|
42
|
-
### From source
|
|
43
|
-
To build the package from source, you will need to the `python3-build`
|
|
44
|
-
package natively installed by your distribution package manager. Then you
|
|
45
|
-
can generate and install a wheel by running the following commands:
|
|
46
|
-
|
|
47
|
-
python3 -m build
|
|
48
|
-
pipx install dist/amd-debug-tools-*.whl
|
|
49
|
-
|
|
50
|
-
### Ensuring path
|
|
51
|
-
If you have not used a `pipx` environment before, you may need to run the following command
|
|
52
|
-
to set up the environment:
|
|
53
|
-
|
|
54
|
-
pipx ensurepath
|
|
55
|
-
|
|
56
|
-
This will add the `pipx` environment to your path.
|
|
57
|
-
|
|
58
|
-
### Running in tree
|
|
59
|
-
If you want to run the tools in tree, you need to make sure that distro dependencies
|
|
60
|
-
that would normally install into a venv are installed. This can be done by running:
|
|
61
|
-
|
|
62
|
-
./install_deps.py
|
|
63
|
-
|
|
64
|
-
After dependencies are installed, you can run the tools by running:
|
|
65
|
-
|
|
66
|
-
./amd_s2idle.py
|
|
67
|
-
./amd_bios.py
|
|
68
|
-
./amd_pstate.py
|
|
69
|
-
|
|
70
|
-
## amd-s2idle
|
|
71
|
-
`amd-s2idle` is a tool used for analyzing the entry and exit of the s2idle
|
|
72
|
-
state of a Linux system.
|
|
73
|
-
|
|
74
|
-
It is intended to use with Linux kernel 6.1 or later and works by hooking
|
|
75
|
-
into dynamic debugging messages and events that are generated by the kernel.
|
|
76
|
-
|
|
77
|
-
For analysis of power consumption issues it can be hooked into `systemd` to
|
|
78
|
-
run a command to capture data right before and after the system enters and
|
|
79
|
-
exits the s2idle state.
|
|
80
|
-
|
|
81
|
-
4 high level commands are supported.
|
|
82
|
-
|
|
83
|
-
### `amd-s2idle install`
|
|
84
|
-
This will install the systemd hook so that data will be captured before and
|
|
85
|
-
after the system enters and exits the s2idle state.
|
|
86
|
-
|
|
87
|
-
This will also install a bash completion script that can be used for other
|
|
88
|
-
commands.
|
|
89
|
-
|
|
90
|
-
**NOTE:** This command is only supported when run from a venv.
|
|
91
|
-
|
|
92
|
-
### `amd-s2idle uninstall`
|
|
93
|
-
This will uninstall the systemd hook and remove the bash completion script.
|
|
94
|
-
|
|
95
|
-
**NOTE:** This command is only supported when run from a venv.
|
|
96
|
-
|
|
97
|
-
### `amd-s2idle test`
|
|
98
|
-
This will run a suspend cycle with a timer based wakeup and capture relevant
|
|
99
|
-
data into a database and produce a report. This can also be used to run multiple cycles.
|
|
100
|
-
|
|
101
|
-
The following optional arguments are supported for this command:
|
|
102
|
-
|
|
103
|
-
--count COUNT Number of cycles to run
|
|
104
|
-
--duration DURATION Duration of the cycle in seconds
|
|
105
|
-
--wait WAIT Time to wait before starting the cycle in seconds
|
|
106
|
-
--format FORMAT Format of the report to produce (html, txt or md)
|
|
107
|
-
--report-file File to write the report to
|
|
108
|
-
--force Run a test cycle even if the system fails to pass prerequisite checks
|
|
109
|
-
--random Run sleep cycles for random durations and waits, using the --duration and --wait arguments as an upper bound
|
|
110
|
-
--logind Use logind to suspend the system
|
|
111
|
-
--tool-debug Enable debug logging
|
|
112
|
-
--bios-debug Enable BIOS debug logging instead of notify logging
|
|
113
|
-
|
|
114
|
-
If the tool is launched with an environment that can call `xdg-open`, the report
|
|
115
|
-
will be opened in a browser.
|
|
116
|
-
|
|
117
|
-
### `amd-s2idle report`
|
|
118
|
-
This will produce a report from the data captured by the `test` command
|
|
119
|
-
and/or from the systemd hook. The report will default to 60 days of data.
|
|
120
|
-
|
|
121
|
-
The following optional arguments are supported for this command:
|
|
122
|
-
|
|
123
|
-
--since SINCE Date to start the report from
|
|
124
|
-
--until UNTIL Date to end the report at
|
|
125
|
-
--format FORMAT Format of the report to produce (html, txt or md)
|
|
126
|
-
--report-file File to write the report to
|
|
127
|
-
--tool-debug Enable tool debug logging
|
|
128
|
-
--report-debug
|
|
129
|
-
--no-report-debug
|
|
130
|
-
Include debug messages in report (WARNING: can significantly increase report size)
|
|
131
|
-
If the tool is launched with an environment that can call `xdg-open`, the report
|
|
132
|
-
will be opened in a browser.
|
|
133
|
-
|
|
134
|
-
### `amd-s2idle version`
|
|
135
|
-
This will print the version of the tool and exit.
|
|
136
|
-
|
|
137
|
-
### Debug output
|
|
138
|
-
All commands support the `--tool-debug` argument which will enable extra debug output. This is often needed for debugging issues with a particular cycle.
|
|
139
|
-
|
|
140
|
-
**NOTE:** enabling debug output significantly increases the size of the report.
|
|
141
|
-
It's suggested that you use `--since` and `--until` to focus on the cycles that you are interested in.
|
|
142
|
-
|
|
143
|
-
## amd-bios
|
|
144
|
-
`amd-bios` is a a tool that can be used to enable or disable BIOS AML debug logging
|
|
145
|
-
-and to parse a kernel log that contains BIOS logs.
|
|
146
|
-
|
|
147
|
-
### `amd-bios trace`
|
|
148
|
-
Modify BIOS AML trace debug logging.
|
|
149
|
-
|
|
150
|
-
One of the following arguments must be set for this command:
|
|
151
|
-
|
|
152
|
-
--enable Enable BIOS AML tracing
|
|
153
|
-
--disable Disable BIOS AML tracing
|
|
154
|
-
|
|
155
|
-
The following optional arguments are supported for this command:
|
|
156
|
-
|
|
157
|
-
--tool-debug Enable tool debug logging
|
|
158
|
-
|
|
159
|
-
### `amd-bios parse`
|
|
160
|
-
Parses a kernel log that contains BIOS AML debug logging and produces a report.
|
|
161
|
-
|
|
162
|
-
The following optional arguments are supported for this command:
|
|
163
|
-
|
|
164
|
-
--input INPUT Optional input file to parse
|
|
165
|
-
--tool-debug Enable tool debug logging
|
|
166
|
-
|
|
167
|
-
### `amd-bios version`
|
|
168
|
-
This will print the version of the tool and exit.
|
|
169
|
-
|
|
170
|
-
## amd-pstate
|
|
171
|
-
`amd-pstate` is a tool used for identification of issues with amd-pstate.
|
|
172
|
-
It will capture some state from the system as well as from the machine specific registers that
|
|
173
|
-
amd-pstate uses.
|
|
174
|
-
|
|
175
|
-
## Compatibility scripts
|
|
176
|
-
|
|
177
|
-
Compatibility scripts are provided for the previous names the tools went by:
|
|
178
|
-
`amd_s2idle.py`, `amd_bios.py` and `amd_pstate.py`.
|
|
179
|
-
These allow cloning the repository and running the scripts without installing
|
|
180
|
-
the package.
|
|
181
|
-
|