amd-debug-tools 0.2.5__py3-none-any.whl → 0.2.6__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.
- amd_debug/__init__.py +9 -1
- amd_debug/bios.py +5 -3
- amd_debug/common.py +39 -0
- amd_debug/installer.py +2 -2
- amd_debug/prerequisites.py +24 -2
- amd_debug/pstate.py +5 -3
- amd_debug/s2idle.py +5 -3
- amd_debug/test_ttm.py +291 -0
- amd_debug/ttm.py +161 -0
- amd_debug/validator.py +20 -0
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.6.dist-info}/METADATA +6 -3
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.6.dist-info}/RECORD +23 -21
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.6.dist-info}/entry_points.txt +1 -0
- test_bios.py +26 -8
- test_common.py +91 -0
- test_kernel.py +2 -1
- test_launcher.py +7 -0
- test_prerequisites.py +77 -1
- test_s2idle.py +6 -4
- test_validator.py +63 -1
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.6.dist-info}/WHEEL +0 -0
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.6.dist-info}/top_level.txt +0 -0
amd_debug/__init__.py
CHANGED
|
@@ -23,8 +23,15 @@ def amd_pstate():
|
|
|
23
23
|
return pstate.main()
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def amd_ttm():
|
|
27
|
+
"""Launch the amd-ttm tool."""
|
|
28
|
+
from . import ttm # pylint: disable=import-outside-toplevel
|
|
29
|
+
|
|
30
|
+
return ttm.main()
|
|
31
|
+
|
|
32
|
+
|
|
26
33
|
def install_dep_superset():
|
|
27
|
-
"""Install all
|
|
34
|
+
"""Install all superset dependencies."""
|
|
28
35
|
from . import installer # pylint: disable=import-outside-toplevel
|
|
29
36
|
|
|
30
37
|
return installer.install_dep_superset()
|
|
@@ -36,6 +43,7 @@ def launch_tool(tool_name):
|
|
|
36
43
|
"amd_s2idle.py": amd_s2idle,
|
|
37
44
|
"amd_bios.py": amd_bios,
|
|
38
45
|
"amd_pstate.py": amd_pstate,
|
|
46
|
+
"amd_ttm.py": amd_ttm,
|
|
39
47
|
"install_deps.py": install_dep_superset,
|
|
40
48
|
}
|
|
41
49
|
if tool_name in tools:
|
amd_debug/bios.py
CHANGED
|
@@ -105,7 +105,9 @@ def parse_args():
|
|
|
105
105
|
action="store_true",
|
|
106
106
|
help="Enable tool debug logging",
|
|
107
107
|
)
|
|
108
|
-
|
|
108
|
+
parser.add_argument(
|
|
109
|
+
"--version", action="store_true", help="Show version information"
|
|
110
|
+
)
|
|
109
111
|
|
|
110
112
|
if len(sys.argv) == 1:
|
|
111
113
|
parser.print_help(sys.stderr)
|
|
@@ -122,7 +124,7 @@ def parse_args():
|
|
|
122
124
|
return args
|
|
123
125
|
|
|
124
126
|
|
|
125
|
-
def main() -> None|int:
|
|
127
|
+
def main() -> None | int:
|
|
126
128
|
"""Main function"""
|
|
127
129
|
args = parse_args()
|
|
128
130
|
ret = False
|
|
@@ -132,7 +134,7 @@ def main() -> None|int:
|
|
|
132
134
|
elif args.command == "parse":
|
|
133
135
|
app = AmdBios(args.input, args.tool_debug)
|
|
134
136
|
ret = app.run()
|
|
135
|
-
elif args.
|
|
137
|
+
elif args.version:
|
|
136
138
|
print(version())
|
|
137
139
|
show_log_info()
|
|
138
140
|
if ret is False:
|
amd_debug/common.py
CHANGED
|
@@ -226,6 +226,45 @@ def get_pretty_distro() -> str:
|
|
|
226
226
|
return distro
|
|
227
227
|
|
|
228
228
|
|
|
229
|
+
def bytes_to_gb(bytes_value):
|
|
230
|
+
"""Convert bytes to GB"""
|
|
231
|
+
return bytes_value * 4096 / (1024 * 1024 * 1024)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def gb_to_pages(gb_value):
|
|
235
|
+
"""Convert GB into bytes"""
|
|
236
|
+
return int(gb_value * (1024 * 1024 * 1024) / 4096)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def reboot():
|
|
240
|
+
"""Reboot the system"""
|
|
241
|
+
try:
|
|
242
|
+
import dbus # pylint: disable=import-outside-toplevel
|
|
243
|
+
|
|
244
|
+
bus = dbus.SystemBus()
|
|
245
|
+
obj = bus.get_object("org.freedesktop.login1", "/org/freedesktop/login1")
|
|
246
|
+
intf = dbus.Interface(obj, "org.freedesktop.login1.Manager")
|
|
247
|
+
intf.Reboot(True)
|
|
248
|
+
return True
|
|
249
|
+
except ImportError:
|
|
250
|
+
fatal_error("Missing dbus")
|
|
251
|
+
except dbus.exceptions.DBusException as e:
|
|
252
|
+
fatal_error({e})
|
|
253
|
+
return True
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def get_system_mem():
|
|
257
|
+
"""Get the total system memory in GB using /proc/meminfo"""
|
|
258
|
+
with open(os.path.join("/", "proc", "meminfo"), "r", encoding="utf-8") as f:
|
|
259
|
+
for line in f:
|
|
260
|
+
if line.startswith("MemTotal:"):
|
|
261
|
+
# MemTotal line format: "MemTotal: 16384516 kB"
|
|
262
|
+
# Extract the number and convert from kB to GB
|
|
263
|
+
mem_kb = int(line.split()[1])
|
|
264
|
+
return mem_kb / (1024 * 1024)
|
|
265
|
+
raise ValueError("Could not find MemTotal in /proc/meminfo")
|
|
266
|
+
|
|
267
|
+
|
|
229
268
|
def is_root() -> bool:
|
|
230
269
|
"""Check if the user is root"""
|
|
231
270
|
return os.geteuid() == 0
|
amd_debug/installer.py
CHANGED
|
@@ -446,8 +446,8 @@ def parse_args():
|
|
|
446
446
|
return parser.parse_args()
|
|
447
447
|
|
|
448
448
|
|
|
449
|
-
def install_dep_superset() -> None|int:
|
|
450
|
-
"""Install all python
|
|
449
|
+
def install_dep_superset() -> None | int:
|
|
450
|
+
"""Install all python superset dependencies"""
|
|
451
451
|
args = parse_args()
|
|
452
452
|
tool = Installer(tool_debug=args.tool_debug)
|
|
453
453
|
tool.set_requirements(
|
amd_debug/prerequisites.py
CHANGED
|
@@ -128,6 +128,28 @@ class PrerequisiteValidator(AmdTool):
|
|
|
128
128
|
if not self.db.get_last_prereq_ts():
|
|
129
129
|
self.run()
|
|
130
130
|
|
|
131
|
+
def capture_nvidia(self):
|
|
132
|
+
"""Capture the NVIDIA GPU state"""
|
|
133
|
+
p = os.path.join("/", "proc", "driver", "nvidia", "version")
|
|
134
|
+
if not os.path.exists(p):
|
|
135
|
+
return True
|
|
136
|
+
try:
|
|
137
|
+
self.db.record_debug_file(p)
|
|
138
|
+
except PermissionError:
|
|
139
|
+
self.db.record_prereq("NVIDIA GPU version not readable", "👀")
|
|
140
|
+
return True
|
|
141
|
+
p = os.path.join("/", "proc", "driver", "nvidia", "gpus")
|
|
142
|
+
if not os.path.exists(p):
|
|
143
|
+
return True
|
|
144
|
+
for root, _dirs, files in os.walk(p, topdown=False):
|
|
145
|
+
for f in files:
|
|
146
|
+
try:
|
|
147
|
+
self.db.record_debug(f"NVIDIA {f}")
|
|
148
|
+
self.db.record_debug_file(os.path.join(root, f))
|
|
149
|
+
except PermissionError:
|
|
150
|
+
self.db.record_prereq("NVIDIA GPU {f} not readable", "👀")
|
|
151
|
+
return True
|
|
152
|
+
|
|
131
153
|
def capture_edid(self):
|
|
132
154
|
"""Capture and decode the EDID data"""
|
|
133
155
|
edids = self.display.get_edid()
|
|
@@ -1227,9 +1249,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1227
1249
|
# ignore kernel warnings
|
|
1228
1250
|
taint &= ~BIT(9)
|
|
1229
1251
|
if taint != 0:
|
|
1230
|
-
self.db.record_prereq(f"Kernel is tainted: {taint}", "
|
|
1252
|
+
self.db.record_prereq(f"Kernel is tainted: {taint}", "🚦")
|
|
1231
1253
|
self.failures += [TaintedKernel()]
|
|
1232
|
-
return False
|
|
1233
1254
|
return True
|
|
1234
1255
|
|
|
1235
1256
|
def run(self):
|
|
@@ -1244,6 +1265,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1244
1265
|
self.capture_logind,
|
|
1245
1266
|
self.capture_pci_acpi,
|
|
1246
1267
|
self.capture_edid,
|
|
1268
|
+
self.capture_nvidia,
|
|
1247
1269
|
]
|
|
1248
1270
|
checks = []
|
|
1249
1271
|
|
amd_debug/pstate.py
CHANGED
|
@@ -293,18 +293,20 @@ def parse_args():
|
|
|
293
293
|
action="store_true",
|
|
294
294
|
help="Enable tool debug logging",
|
|
295
295
|
)
|
|
296
|
-
|
|
296
|
+
parser.add_argument(
|
|
297
|
+
"--version", action="store_true", help="Show version information"
|
|
298
|
+
)
|
|
297
299
|
if len(sys.argv) == 1:
|
|
298
300
|
parser.print_help(sys.stderr)
|
|
299
301
|
sys.exit(1)
|
|
300
302
|
return parser.parse_args()
|
|
301
303
|
|
|
302
304
|
|
|
303
|
-
def main() -> None|int:
|
|
305
|
+
def main() -> None | int:
|
|
304
306
|
"""Main function"""
|
|
305
307
|
args = parse_args()
|
|
306
308
|
ret = False
|
|
307
|
-
if args.
|
|
309
|
+
if args.version:
|
|
308
310
|
print(version())
|
|
309
311
|
return
|
|
310
312
|
elif args.command == "triage":
|
amd_debug/s2idle.py
CHANGED
|
@@ -385,7 +385,9 @@ def parse_args():
|
|
|
385
385
|
help="Enable tool debug logging",
|
|
386
386
|
)
|
|
387
387
|
|
|
388
|
-
|
|
388
|
+
parser.add_argument(
|
|
389
|
+
"--version", action="store_true", help="Show version information"
|
|
390
|
+
)
|
|
389
391
|
|
|
390
392
|
if len(sys.argv) == 1:
|
|
391
393
|
parser.print_help(sys.stderr)
|
|
@@ -394,7 +396,7 @@ def parse_args():
|
|
|
394
396
|
return parser.parse_args()
|
|
395
397
|
|
|
396
398
|
|
|
397
|
-
def main() -> None|int:
|
|
399
|
+
def main() -> None | int:
|
|
398
400
|
"""Main function"""
|
|
399
401
|
args = parse_args()
|
|
400
402
|
ret = False
|
|
@@ -427,7 +429,7 @@ def main() -> None|int:
|
|
|
427
429
|
args.logind,
|
|
428
430
|
args.bios_debug,
|
|
429
431
|
)
|
|
430
|
-
elif args.
|
|
432
|
+
elif args.version:
|
|
431
433
|
print(version())
|
|
432
434
|
return
|
|
433
435
|
else:
|
amd_debug/test_ttm.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
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.is_root", return_value=False)
|
|
203
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
204
|
+
def test_set_not_root(self, mock_print, _mock_is_root):
|
|
205
|
+
"""Test set() when not root"""
|
|
206
|
+
result = self.tool.set(2)
|
|
207
|
+
mock_print.assert_called_with("Root privileges required", "❌")
|
|
208
|
+
self.assertFalse(result)
|
|
209
|
+
|
|
210
|
+
@mock.patch("amd_debug.ttm.is_root", return_value=True)
|
|
211
|
+
@mock.patch("amd_debug.ttm.get_system_mem", return_value=8.0)
|
|
212
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
213
|
+
def test_set_gb_greater_than_total(self, mock_print, _mock_mem, _mock_is_root):
|
|
214
|
+
"""Test set() when gb_value > total system memory"""
|
|
215
|
+
result = self.tool.set(16)
|
|
216
|
+
mock_print.assert_any_call(
|
|
217
|
+
"16.00 GB is greater than total system memory (8.00 GB)", "❌"
|
|
218
|
+
)
|
|
219
|
+
self.assertFalse(result)
|
|
220
|
+
|
|
221
|
+
@mock.patch("amd_debug.ttm.is_root", return_value=True)
|
|
222
|
+
@mock.patch("amd_debug.ttm.get_system_mem", return_value=10.0)
|
|
223
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
224
|
+
@mock.patch("builtins.input", return_value="n")
|
|
225
|
+
def test_set_gb_exceeds_max_percentage_cancel(
|
|
226
|
+
self, _mock_input, mock_print, _mock_mem, mock_is_root
|
|
227
|
+
):
|
|
228
|
+
"""Test set() when gb_value exceeds max percentage and user cancels"""
|
|
229
|
+
result = self.tool.set(9.5)
|
|
230
|
+
self.assertFalse(result)
|
|
231
|
+
mock_print.assert_any_call("Operation cancelled.", "🚦")
|
|
232
|
+
|
|
233
|
+
@mock.patch("amd_debug.ttm.is_root", return_value=True)
|
|
234
|
+
@mock.patch("amd_debug.ttm.get_system_mem", return_value=10.0)
|
|
235
|
+
@mock.patch("amd_debug.ttm.gb_to_pages", return_value=20480)
|
|
236
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
237
|
+
@mock.patch("builtins.open", new_callable=mock.mock_open)
|
|
238
|
+
@mock.patch("builtins.input", return_value="y")
|
|
239
|
+
@mock.patch("amd_debug.ttm.maybe_reboot", return_value=True)
|
|
240
|
+
def test_set_success(
|
|
241
|
+
self,
|
|
242
|
+
_mock_reboot,
|
|
243
|
+
_mock_input,
|
|
244
|
+
mock_open,
|
|
245
|
+
mock_print,
|
|
246
|
+
_mock_gb_to_pages,
|
|
247
|
+
mock_mem,
|
|
248
|
+
mock_is_root,
|
|
249
|
+
):
|
|
250
|
+
"""Test set() success path"""
|
|
251
|
+
result = self.tool.set(5)
|
|
252
|
+
mock_open.assert_called_once_with(
|
|
253
|
+
"/etc/modprobe.d/ttm.conf", "w", encoding="utf-8"
|
|
254
|
+
)
|
|
255
|
+
mock_print.assert_any_call(
|
|
256
|
+
"Successfully set TTM pages limit to 20480 pages (5.00 GB)", "🐧"
|
|
257
|
+
)
|
|
258
|
+
self.assertTrue(result)
|
|
259
|
+
|
|
260
|
+
@mock.patch("os.path.exists", return_value=False)
|
|
261
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
262
|
+
def test_clear_file_not_exists(self, mock_print, _mock_exists):
|
|
263
|
+
"""Test clear() when config file does not exist"""
|
|
264
|
+
result = self.tool.clear()
|
|
265
|
+
mock_print.assert_called_with("/etc/modprobe.d/ttm.conf doesn't exist", "❌")
|
|
266
|
+
self.assertFalse(result)
|
|
267
|
+
|
|
268
|
+
@mock.patch("os.path.exists", return_value=True)
|
|
269
|
+
@mock.patch("amd_debug.ttm.is_root", return_value=False)
|
|
270
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
271
|
+
def test_clear_not_root(self, mock_print, _mock_is_root, _mock_exists):
|
|
272
|
+
"""Test clear() when not root"""
|
|
273
|
+
result = self.tool.clear()
|
|
274
|
+
mock_print.assert_called_with("Root privileges required", "❌")
|
|
275
|
+
self.assertFalse(result)
|
|
276
|
+
|
|
277
|
+
@mock.patch("os.path.exists", return_value=True)
|
|
278
|
+
@mock.patch("amd_debug.ttm.is_root", return_value=True)
|
|
279
|
+
@mock.patch("os.remove")
|
|
280
|
+
@mock.patch("amd_debug.ttm.print_color")
|
|
281
|
+
@mock.patch("amd_debug.ttm.maybe_reboot", return_value=True)
|
|
282
|
+
def test_clear_success(
|
|
283
|
+
self, _mock_reboot, mock_print, mock_remove, _mock_is_root, mock_exists
|
|
284
|
+
):
|
|
285
|
+
"""Test clear() success path"""
|
|
286
|
+
result = self.tool.clear()
|
|
287
|
+
mock_remove.assert_called_once_with("/etc/modprobe.d/ttm.conf")
|
|
288
|
+
mock_print.assert_any_call(
|
|
289
|
+
"Configuration /etc/modprobe.d/ttm.conf removed", "🐧"
|
|
290
|
+
)
|
|
291
|
+
self.assertTrue(result)
|
amd_debug/ttm.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""TTM configuration tool"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import argparse
|
|
7
|
+
from amd_debug.common import (
|
|
8
|
+
AmdTool,
|
|
9
|
+
bytes_to_gb,
|
|
10
|
+
gb_to_pages,
|
|
11
|
+
get_system_mem,
|
|
12
|
+
is_root,
|
|
13
|
+
print_color,
|
|
14
|
+
reboot,
|
|
15
|
+
version,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
TTM_PARAM_PATH = "/sys/module/ttm/parameters/pages_limit"
|
|
19
|
+
MODPROBE_CONF_PATH = "/etc/modprobe.d/ttm.conf"
|
|
20
|
+
# Maximum percentage of total system memory to allow for TTM
|
|
21
|
+
MAX_MEMORY_PERCENTAGE = 90
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def maybe_reboot() -> bool:
|
|
25
|
+
"""Prompt to reboot system"""
|
|
26
|
+
response = input("Would you like to reboot the system now? (y/n): ").strip().lower()
|
|
27
|
+
if response in ("y", "yes"):
|
|
28
|
+
return reboot()
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AmdTtmTool(AmdTool):
|
|
33
|
+
"""Class for handling TTM page configuration"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, logging):
|
|
36
|
+
log_prefix = "ttm" if logging else None
|
|
37
|
+
super().__init__(log_prefix)
|
|
38
|
+
|
|
39
|
+
def get(self) -> bool:
|
|
40
|
+
"""Read current page limit"""
|
|
41
|
+
try:
|
|
42
|
+
with open(TTM_PARAM_PATH, "r", encoding="utf-8") as f:
|
|
43
|
+
pages = int(f.read().strip())
|
|
44
|
+
gb_value = bytes_to_gb(pages)
|
|
45
|
+
print_color(
|
|
46
|
+
f"Current TTM pages limit: {pages} pages ({gb_value:.2f} GB)", "💻"
|
|
47
|
+
)
|
|
48
|
+
except FileNotFoundError:
|
|
49
|
+
print_color(f"Error: Could not find {TTM_PARAM_PATH}", "❌")
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
total = get_system_mem()
|
|
53
|
+
if total > 0:
|
|
54
|
+
print_color(f"Total system memory: {total:.2f} GB", "💻")
|
|
55
|
+
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
def set(self, gb_value) -> bool:
|
|
59
|
+
"""Set a new page limit"""
|
|
60
|
+
if not is_root():
|
|
61
|
+
print_color("Root privileges required", "❌")
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
# Check against system memory
|
|
65
|
+
total = get_system_mem()
|
|
66
|
+
if total > 0:
|
|
67
|
+
max_recommended_gb = total * MAX_MEMORY_PERCENTAGE / 100
|
|
68
|
+
|
|
69
|
+
if gb_value > total:
|
|
70
|
+
print_color(
|
|
71
|
+
f"{gb_value:.2f} GB is greater than total system memory ({total:.2f} GB)",
|
|
72
|
+
"❌",
|
|
73
|
+
)
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
if gb_value > max_recommended_gb:
|
|
77
|
+
print_color(
|
|
78
|
+
f"Warning: The requested value ({gb_value:.2f} GB) exceeds {MAX_MEMORY_PERCENTAGE}% of your system memory ({max_recommended_gb:.2f} GB).",
|
|
79
|
+
"🚦",
|
|
80
|
+
)
|
|
81
|
+
response = (
|
|
82
|
+
input(
|
|
83
|
+
"This could cause system instability. Continue anyway? (y/n): "
|
|
84
|
+
)
|
|
85
|
+
.strip()
|
|
86
|
+
.lower()
|
|
87
|
+
)
|
|
88
|
+
if response not in ("y", "yes"):
|
|
89
|
+
print_color("Operation cancelled.", "🚦")
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
pages = gb_to_pages(gb_value)
|
|
93
|
+
|
|
94
|
+
with open(MODPROBE_CONF_PATH, "w", encoding="utf-8") as f:
|
|
95
|
+
f.write(f"options ttm pages_limit={pages}\n")
|
|
96
|
+
print_color(
|
|
97
|
+
f"Successfully set TTM pages limit to {pages} pages ({gb_value:.2f} GB)",
|
|
98
|
+
"🐧",
|
|
99
|
+
)
|
|
100
|
+
print_color(f"Configuration written to {MODPROBE_CONF_PATH}", "🐧")
|
|
101
|
+
print_color("NOTE: You need to reboot for changes to take effect.", "○")
|
|
102
|
+
|
|
103
|
+
return maybe_reboot()
|
|
104
|
+
|
|
105
|
+
def clear(self) -> bool:
|
|
106
|
+
"""Clears the page limit"""
|
|
107
|
+
if not os.path.exists(MODPROBE_CONF_PATH):
|
|
108
|
+
print_color(f"{MODPROBE_CONF_PATH} doesn't exist", "❌")
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
if not is_root():
|
|
112
|
+
print_color("Root privileges required", "❌")
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
os.remove(MODPROBE_CONF_PATH)
|
|
116
|
+
print_color(f"Configuration {MODPROBE_CONF_PATH} removed", "🐧")
|
|
117
|
+
|
|
118
|
+
return maybe_reboot()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def parse_args():
|
|
122
|
+
"""Parse command line arguments."""
|
|
123
|
+
parser = argparse.ArgumentParser(description="Manage TTM pages limit")
|
|
124
|
+
parser.add_argument("--set", type=float, help="Set pages limit in GB")
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"--clear", action="store_true", help="Clear a previously set page limit"
|
|
127
|
+
)
|
|
128
|
+
parser.add_argument(
|
|
129
|
+
"--version", action="store_true", help="Show version information"
|
|
130
|
+
)
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"--tool-debug",
|
|
133
|
+
action="store_true",
|
|
134
|
+
help="Enable tool debug logging",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return parser.parse_args()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main() -> None | int:
|
|
141
|
+
"""Main function"""
|
|
142
|
+
|
|
143
|
+
args = parse_args()
|
|
144
|
+
tool = AmdTtmTool(args.tool_debug)
|
|
145
|
+
ret = False
|
|
146
|
+
|
|
147
|
+
if args.version:
|
|
148
|
+
print(version())
|
|
149
|
+
return
|
|
150
|
+
elif args.set is not None:
|
|
151
|
+
if args.set <= 0:
|
|
152
|
+
print("Error: GB value must be greater than 0")
|
|
153
|
+
return 1
|
|
154
|
+
ret = tool.set(args.set)
|
|
155
|
+
elif args.clear:
|
|
156
|
+
ret = tool.clear()
|
|
157
|
+
else:
|
|
158
|
+
ret = tool.get()
|
|
159
|
+
if ret is False:
|
|
160
|
+
return 1
|
|
161
|
+
return
|
amd_debug/validator.py
CHANGED
|
@@ -703,6 +703,22 @@ class SleepValidator(AmdTool):
|
|
|
703
703
|
else:
|
|
704
704
|
print_color("No RTC device found, please manually wake system", "🚦")
|
|
705
705
|
|
|
706
|
+
def toggle_nvidia(self, value):
|
|
707
|
+
"""Write to the NVIDIA suspend interface"""
|
|
708
|
+
p = os.path.join("/", "proc", "driver", "nvidia", "suspend")
|
|
709
|
+
if not os.path.exists(p):
|
|
710
|
+
return True
|
|
711
|
+
fd = os.open(p, os.O_WRONLY | os.O_SYNC)
|
|
712
|
+
try:
|
|
713
|
+
os.write(fd, value)
|
|
714
|
+
except OSError as e:
|
|
715
|
+
self.db.record_cycle_data(f"Failed to set {value} in NVIDIA {e}", "❌")
|
|
716
|
+
return False
|
|
717
|
+
finally:
|
|
718
|
+
os.close(fd)
|
|
719
|
+
self.db.record_debug(f"Wrote {value} to NVIDIA driver")
|
|
720
|
+
return True
|
|
721
|
+
|
|
706
722
|
@pm_debugging
|
|
707
723
|
def suspend_system(self):
|
|
708
724
|
"""Suspend the system using the dbus or sysfs interface"""
|
|
@@ -744,6 +760,8 @@ class SleepValidator(AmdTool):
|
|
|
744
760
|
self.db.record_cycle_data("Missing dbus", "❌")
|
|
745
761
|
return False
|
|
746
762
|
else:
|
|
763
|
+
if not self.toggle_nvidia(b"suspend"):
|
|
764
|
+
return False
|
|
747
765
|
old = get_wakeup_count()
|
|
748
766
|
p = os.path.join("/", "sys", "power", "state")
|
|
749
767
|
fd = os.open(p, os.O_WRONLY | os.O_SYNC)
|
|
@@ -757,6 +775,8 @@ class SleepValidator(AmdTool):
|
|
|
757
775
|
return False
|
|
758
776
|
finally:
|
|
759
777
|
os.close(fd)
|
|
778
|
+
if not self.toggle_nvidia(b"resume"):
|
|
779
|
+
return False
|
|
760
780
|
return True
|
|
761
781
|
|
|
762
782
|
def unlock_session(self):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amd-debug-tools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: debug tools for AMD systems
|
|
5
5
|
Author-email: Mario Limonciello <superm1@kernel.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -131,7 +131,7 @@ The following optional arguments are supported for this command:
|
|
|
131
131
|
If the tool is launched with an environment that can call `xdg-open`, the report
|
|
132
132
|
will be opened in a browser.
|
|
133
133
|
|
|
134
|
-
### `amd-s2idle version`
|
|
134
|
+
### `amd-s2idle --version`
|
|
135
135
|
This will print the version of the tool and exit.
|
|
136
136
|
|
|
137
137
|
### Debug output
|
|
@@ -164,7 +164,7 @@ The following optional arguments are supported for this command:
|
|
|
164
164
|
--input INPUT Optional input file to parse
|
|
165
165
|
--tool-debug Enable tool debug logging
|
|
166
166
|
|
|
167
|
-
### `amd-bios version`
|
|
167
|
+
### `amd-bios --version`
|
|
168
168
|
This will print the version of the tool and exit.
|
|
169
169
|
|
|
170
170
|
## amd-pstate
|
|
@@ -172,6 +172,9 @@ This will print the version of the tool and exit.
|
|
|
172
172
|
It will capture some state from the system as well as from the machine specific registers that
|
|
173
173
|
amd-pstate uses.
|
|
174
174
|
|
|
175
|
+
## amd-ttm
|
|
176
|
+
`amd-ttm` is a tool used for managing the TTM memory settings on AMD systems.
|
|
177
|
+
|
|
175
178
|
## Compatibility scripts
|
|
176
179
|
|
|
177
180
|
Compatibility scripts are provided for the previous names the tools went by:
|
|
@@ -1,45 +1,47 @@
|
|
|
1
1
|
launcher.py,sha256=M8kT9DtyZoQgZaKWDbSBu4jsS6tZF1gWko3sovNVyag,947
|
|
2
2
|
test_acpi.py,sha256=wtS43Rz95h7YEEJBeFa6Mswaeo4syBZrw4hY8i0YbJY,3117
|
|
3
3
|
test_batteries.py,sha256=nN5pfP5El7Whypq3HHEpW8bufdf5EWSTVGbayfNQYP4,3360
|
|
4
|
-
test_bios.py,sha256=
|
|
5
|
-
test_common.py,sha256=
|
|
4
|
+
test_bios.py,sha256=x_KLmQqGEbQhTugyWCHGXjGp2H1dCdhRz0kgw2Big8w,9276
|
|
5
|
+
test_common.py,sha256=EYbyObC9vIXquT3EbgQ_98V4Zw2ebUCY9cfS9VOoywE,19722
|
|
6
6
|
test_database.py,sha256=q5ZjI5u20f7ki6iCY5o1iPi0YOvPz1_W0LTDraU8mN4,10040
|
|
7
7
|
test_display.py,sha256=hHggv-zBthF1BlwWWSjzAm7BBw1DWcElwil5xAuz87g,5822
|
|
8
8
|
test_failures.py,sha256=H1UxXeVjhJs9-j9yas4vwAha676GX1Es7Kz8RN2B590,6845
|
|
9
9
|
test_installer.py,sha256=oDMCvaKqqAWjTggltacnasQ-s1gyUvXPDcNrCUGnux4,10216
|
|
10
|
-
test_kernel.py,sha256=
|
|
11
|
-
test_launcher.py,sha256=
|
|
12
|
-
test_prerequisites.py,sha256
|
|
10
|
+
test_kernel.py,sha256=2EXrLht5ZWdT4N5pb_F3zqZl9NEghjnDpcMGCMw3obI,7917
|
|
11
|
+
test_launcher.py,sha256=8g8CBTvLX64Us4RmHtRPSdpV5E2kQFaudBl7VIsxLhE,1733
|
|
12
|
+
test_prerequisites.py,sha256=-q6v80QXDMB_Mdek2KZTmKfKcRVZnHf8SBEhcT0RyIY,88498
|
|
13
13
|
test_pstate.py,sha256=a9oAJ9-LANX32XNQhplz6Y75VNYc__QqoSBKIrwvANg,6058
|
|
14
|
-
test_s2idle.py,sha256=
|
|
14
|
+
test_s2idle.py,sha256=FxsyujgX9Px3m56VzHNeA8yMTHmJiRLWxYt-fh1m5gw,33585
|
|
15
15
|
test_sleep_report.py,sha256=ANuxYi_C1oSKAi4xUU2wBu4SwJtcZA7VPpazBe3_WUQ,6922
|
|
16
|
-
test_validator.py,sha256
|
|
16
|
+
test_validator.py,sha256=RpjyzxDpExhLcSJfQ0UDuonr4sTFAfa7sTtY5g7tc_Q,36410
|
|
17
17
|
test_wake.py,sha256=6zi5GVFHQKU1sTWw3O5-aGriB9uu5713QLn4l2wjhpM,7152
|
|
18
|
-
amd_debug/__init__.py,sha256=
|
|
18
|
+
amd_debug/__init__.py,sha256=66Ya61av8RCws6bEY_vdujGmjBIZ6_UqfuWHgMNNOJY,1271
|
|
19
19
|
amd_debug/acpi.py,sha256=fkD3Sov8cRT5ryPlakRlT7Z9jiCLT9x_MPWxt3xU_tc,3161
|
|
20
20
|
amd_debug/battery.py,sha256=WN-6ys9PHCZIwg7PdwyBOa62GjBp8WKG0v1YZt5_W5s,3122
|
|
21
|
-
amd_debug/bios.py,sha256=
|
|
22
|
-
amd_debug/common.py,sha256=
|
|
21
|
+
amd_debug/bios.py,sha256=y1iwDqX-mXCkoUtHSi-XO9pN-oLfaqbAMzANGI12zHs,4041
|
|
22
|
+
amd_debug/common.py,sha256=fHrmSEVerVAE7KXjspf60eOAetUO7K9s1yhNyE2xi94,11598
|
|
23
23
|
amd_debug/database.py,sha256=GkRg3cmaNceyQ2_hy0MBAlMbnTDPHo2co2o4ObWpnQg,10621
|
|
24
24
|
amd_debug/display.py,sha256=5L9x9tI_UoulHpIvuxuVASRtdXta7UCW_JjTb5StEB0,953
|
|
25
25
|
amd_debug/failures.py,sha256=z4O4Q-akv3xYGssSZFCqE0cDE4P9F_aw1hxil3McoD4,22910
|
|
26
|
-
amd_debug/installer.py,sha256=
|
|
26
|
+
amd_debug/installer.py,sha256=6_Y0oHypW-oh_P8N9JW7fzbqidpsi5jphw9_8s5Qvso,14288
|
|
27
27
|
amd_debug/kernel.py,sha256=HpX-QRh8tgkvqKnExfo2JrYqfcbMY8GNgDrC2VVV0Oc,11638
|
|
28
|
-
amd_debug/prerequisites.py,sha256=
|
|
29
|
-
amd_debug/pstate.py,sha256=
|
|
28
|
+
amd_debug/prerequisites.py,sha256=zK-IXL52p_jomA1SsS-6btIHuaapNKW4sD-Fy0wBr68,51399
|
|
29
|
+
amd_debug/pstate.py,sha256=AOKCvUb0ngwHU2C59uSKrFwdLzEyn8r1w2DgWhZAMKM,9583
|
|
30
30
|
amd_debug/s2idle-hook,sha256=LLiaqPtGd0qetu9n6EYxKHZaIdHpVQDONdOuSc0pfFg,1695
|
|
31
|
-
amd_debug/s2idle.py,sha256=
|
|
31
|
+
amd_debug/s2idle.py,sha256=4cxHNfmvq11BE-AtkEthIqR-xrWmxW2LTa6oVDtGztY,13284
|
|
32
32
|
amd_debug/sleep_report.py,sha256=hhqu711AKtjeYF2xmGcejyCyyPtmq4-gC_hROUCrC0g,17317
|
|
33
|
-
amd_debug/
|
|
33
|
+
amd_debug/test_ttm.py,sha256=McNdEJZ14AeFw8fIxzul9ff8kr67dz1dPvK2fqlM4IA,11219
|
|
34
|
+
amd_debug/ttm.py,sha256=U7beRffgoXIPgUXet4ZMabEGo7b0qqKMluhBVMD7O04,4830
|
|
35
|
+
amd_debug/validator.py,sha256=X-cNFVvHWKzFgT4aR0Td3I2kwQRBOK4vQUk6L276VCQ,34153
|
|
34
36
|
amd_debug/wake.py,sha256=xT8WrFrN6voCmXWo5dsn4mQ7iR2QJxHrrYBd3EREG-Q,3936
|
|
35
37
|
amd_debug/bash/amd-s2idle,sha256=g_cle1ElCJpwE4wcLezL6y-BdasDKTnNMhrtzKLE9ks,1142
|
|
36
38
|
amd_debug/templates/html,sha256=JfGhpmHIB2C2GItdGI1kuC8uayqEVgrpQvAWAj35eZ4,14580
|
|
37
39
|
amd_debug/templates/md,sha256=r8X2aehnH2gzj0WHYTZ5K9wAqC5y39i_3nkDORSC0uM,787
|
|
38
40
|
amd_debug/templates/stdout,sha256=hyoOJ96K2dJfnWRWhyCuariLKbEHXvs9mstV_g5aMdI,469
|
|
39
41
|
amd_debug/templates/txt,sha256=nNdsvbPFOhGdL7VA-_4k5aN3nB-6ouGQt6AsWst7T3w,649
|
|
40
|
-
amd_debug_tools-0.2.
|
|
41
|
-
amd_debug_tools-0.2.
|
|
42
|
-
amd_debug_tools-0.2.
|
|
43
|
-
amd_debug_tools-0.2.
|
|
44
|
-
amd_debug_tools-0.2.
|
|
45
|
-
amd_debug_tools-0.2.
|
|
42
|
+
amd_debug_tools-0.2.6.dist-info/licenses/LICENSE,sha256=RBlZI6r3MRGzymI2VDX2iW__D2APDbMhu_Xg5t6BWeo,1066
|
|
43
|
+
amd_debug_tools-0.2.6.dist-info/METADATA,sha256=pMVZTIUIqoKEksnbkS2UCFJPnDRJ1k6sf3Jwj74mNxk,6971
|
|
44
|
+
amd_debug_tools-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
45
|
+
amd_debug_tools-0.2.6.dist-info/entry_points.txt,sha256=hIskDz6k0_6q1qpqWCpVFsca_djxAqkLrUAwzAyEGuE,144
|
|
46
|
+
amd_debug_tools-0.2.6.dist-info/top_level.txt,sha256=XYjxExbUTEtiIlag_5iQvZSVOC1EIxhKM4NLklReQ0k,234
|
|
47
|
+
amd_debug_tools-0.2.6.dist-info/RECORD,,
|
test_bios.py
CHANGED
|
@@ -33,8 +33,14 @@ class TestAmdBios(unittest.TestCase):
|
|
|
33
33
|
@patch("amd_debug.bios.minimum_kernel")
|
|
34
34
|
@patch("amd_debug.bios.AcpicaTracer")
|
|
35
35
|
@patch("amd_debug.bios.print_color")
|
|
36
|
+
@patch("subprocess.run")
|
|
36
37
|
def test_set_tracing_enable(
|
|
37
|
-
self,
|
|
38
|
+
self,
|
|
39
|
+
_mock_run,
|
|
40
|
+
_mock_print,
|
|
41
|
+
mock_acpica_tracer,
|
|
42
|
+
mock_minimum_kernel,
|
|
43
|
+
mock_relaunch_sudo,
|
|
38
44
|
):
|
|
39
45
|
"""Test enabling tracing"""
|
|
40
46
|
mock_minimum_kernel.return_value = True
|
|
@@ -53,8 +59,14 @@ class TestAmdBios(unittest.TestCase):
|
|
|
53
59
|
@patch("amd_debug.bios.minimum_kernel")
|
|
54
60
|
@patch("amd_debug.bios.AcpicaTracer")
|
|
55
61
|
@patch("amd_debug.bios.print_color")
|
|
62
|
+
@patch("subprocess.run")
|
|
56
63
|
def test_set_tracing_disable(
|
|
57
|
-
self,
|
|
64
|
+
self,
|
|
65
|
+
_mock_run,
|
|
66
|
+
_mock_print,
|
|
67
|
+
mock_acpica_tracer,
|
|
68
|
+
mock_minimum_kernel,
|
|
69
|
+
mock_relaunch_sudo,
|
|
58
70
|
):
|
|
59
71
|
"""Test disabling tracing"""
|
|
60
72
|
mock_minimum_kernel.return_value = True
|
|
@@ -71,7 +83,10 @@ class TestAmdBios(unittest.TestCase):
|
|
|
71
83
|
|
|
72
84
|
@patch("amd_debug.bios.sscanf_bios_args")
|
|
73
85
|
@patch("amd_debug.bios.print_color")
|
|
74
|
-
|
|
86
|
+
@patch("subprocess.run")
|
|
87
|
+
def test_analyze_kernel_log_line(
|
|
88
|
+
self, _mock_run, mock_print_color, mock_sscanf_bios_args
|
|
89
|
+
):
|
|
75
90
|
"""Test analyzing kernel log line"""
|
|
76
91
|
mock_sscanf_bios_args.return_value = "BIOS argument found"
|
|
77
92
|
|
|
@@ -85,8 +100,9 @@ class TestAmdBios(unittest.TestCase):
|
|
|
85
100
|
|
|
86
101
|
@patch("amd_debug.bios.sscanf_bios_args")
|
|
87
102
|
@patch("amd_debug.bios.print_color")
|
|
103
|
+
@patch("subprocess.run")
|
|
88
104
|
def test_analyze_kernel_log_line_no_bios_args(
|
|
89
|
-
self, mock_print_color, mock_sscanf_bios_args
|
|
105
|
+
self, _mock_run, mock_print_color, mock_sscanf_bios_args
|
|
90
106
|
):
|
|
91
107
|
"""Test analyzing kernel log line with no BIOS arguments"""
|
|
92
108
|
mock_sscanf_bios_args.return_value = None
|
|
@@ -140,12 +156,12 @@ class TestAmdBios(unittest.TestCase):
|
|
|
140
156
|
self.assertFalse(args.enable)
|
|
141
157
|
self.assertTrue(args.disable)
|
|
142
158
|
|
|
143
|
-
@patch("sys.argv", ["bios.py", "version"])
|
|
159
|
+
@patch("sys.argv", ["bios.py", "--version"])
|
|
144
160
|
def test_parse_args_version_command(self):
|
|
145
161
|
"""Test parse_args with version command"""
|
|
146
162
|
|
|
147
163
|
args = parse_args()
|
|
148
|
-
self.
|
|
164
|
+
self.assertTrue(args.version)
|
|
149
165
|
|
|
150
166
|
@patch("sys.argv", ["bios.py"])
|
|
151
167
|
@patch("argparse.ArgumentParser.print_help")
|
|
@@ -227,7 +243,7 @@ class TestAmdBios(unittest.TestCase):
|
|
|
227
243
|
self, _mock_print, mock_show_log_info, mock_version, mock_parse_args
|
|
228
244
|
):
|
|
229
245
|
"""Test main function with version command"""
|
|
230
|
-
mock_parse_args.return_value = argparse.Namespace(command=
|
|
246
|
+
mock_parse_args.return_value = argparse.Namespace(version=True, command=None)
|
|
231
247
|
mock_version.return_value = "1.0.0"
|
|
232
248
|
|
|
233
249
|
result = main()
|
|
@@ -241,7 +257,9 @@ class TestAmdBios(unittest.TestCase):
|
|
|
241
257
|
@patch("amd_debug.bios.show_log_info")
|
|
242
258
|
def test_main_invalid_command(self, mock_show_log_info, mock_parse_args):
|
|
243
259
|
"""Test main function with an invalid command"""
|
|
244
|
-
mock_parse_args.return_value = argparse.Namespace(
|
|
260
|
+
mock_parse_args.return_value = argparse.Namespace(
|
|
261
|
+
version=False, command="invalid"
|
|
262
|
+
)
|
|
245
263
|
|
|
246
264
|
result = main()
|
|
247
265
|
|
test_common.py
CHANGED
|
@@ -15,6 +15,7 @@ from platform import uname_result
|
|
|
15
15
|
|
|
16
16
|
from amd_debug.common import (
|
|
17
17
|
apply_prefix_wrapper,
|
|
18
|
+
bytes_to_gb,
|
|
18
19
|
Colors,
|
|
19
20
|
convert_string_to_bool,
|
|
20
21
|
colorize_choices,
|
|
@@ -22,12 +23,15 @@ from amd_debug.common import (
|
|
|
22
23
|
compare_file,
|
|
23
24
|
find_ip_version,
|
|
24
25
|
fatal_error,
|
|
26
|
+
gb_to_pages,
|
|
25
27
|
get_distro,
|
|
26
28
|
get_log_priority,
|
|
27
29
|
get_pretty_distro,
|
|
30
|
+
get_system_mem,
|
|
28
31
|
is_root,
|
|
29
32
|
minimum_kernel,
|
|
30
33
|
print_color,
|
|
34
|
+
reboot,
|
|
31
35
|
run_countdown,
|
|
32
36
|
systemd_in_use,
|
|
33
37
|
running_ssh,
|
|
@@ -442,3 +446,90 @@ class TestCommon(unittest.TestCase):
|
|
|
442
446
|
with patch("sys.exit") as mock_exit:
|
|
443
447
|
convert_string_to_bool("[unclosed_list")
|
|
444
448
|
mock_exit.assert_called_once_with("Invalid entry: [unclosed_list")
|
|
449
|
+
|
|
450
|
+
def test_bytes_to_gb(self):
|
|
451
|
+
"""Test bytes_to_gb conversion"""
|
|
452
|
+
# 4096 bytes should be 4096*4096/(1024*1024*1024) GB
|
|
453
|
+
self.assertAlmostEqual(bytes_to_gb(1), 4096 / (1024 * 1024 * 1024))
|
|
454
|
+
self.assertAlmostEqual(bytes_to_gb(0), 0)
|
|
455
|
+
self.assertAlmostEqual(bytes_to_gb(1024), 1024 * 4096 / (1024 * 1024 * 1024))
|
|
456
|
+
|
|
457
|
+
def test_gb_to_pages(self):
|
|
458
|
+
"""Test gb_to_pages conversion"""
|
|
459
|
+
# 1 GB should be int(1 * (1024*1024*1024) / 4096)
|
|
460
|
+
self.assertEqual(gb_to_pages(1), int((1024 * 1024 * 1024) / 4096))
|
|
461
|
+
self.assertEqual(gb_to_pages(0), 0)
|
|
462
|
+
self.assertEqual(gb_to_pages(2), int(2 * (1024 * 1024 * 1024) / 4096))
|
|
463
|
+
|
|
464
|
+
@patch(
|
|
465
|
+
"builtins.open",
|
|
466
|
+
new_callable=mock_open,
|
|
467
|
+
read_data="MemTotal: 16384516 kB\n",
|
|
468
|
+
)
|
|
469
|
+
@patch("os.path.join", return_value="/proc/meminfo")
|
|
470
|
+
def test_get_system_mem_valid(self, _mock_join, mock_file):
|
|
471
|
+
"""Test get_system_mem returns correct value"""
|
|
472
|
+
expected_gb = 16384516 / (1024 * 1024)
|
|
473
|
+
self.assertAlmostEqual(get_system_mem(), expected_gb)
|
|
474
|
+
mock_file.assert_called_once_with("/proc/meminfo", "r", encoding="utf-8")
|
|
475
|
+
|
|
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"""
|
|
499
|
+
|
|
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):
|
|
508
|
+
pass
|
|
509
|
+
|
|
510
|
+
class DummyBus: # pylint: disable=too-few-public-methods
|
|
511
|
+
"""Dummy bus"""
|
|
512
|
+
|
|
513
|
+
def get_object(self, *args, **kwargs):
|
|
514
|
+
"""Dummy get_object method"""
|
|
515
|
+
return DummyObj()
|
|
516
|
+
|
|
517
|
+
class DummyDBus:
|
|
518
|
+
"""Dummy dbus"""
|
|
519
|
+
|
|
520
|
+
class exceptions: # pylint: disable=invalid-name
|
|
521
|
+
"""Dummy exceptions"""
|
|
522
|
+
|
|
523
|
+
DBusException = DummyDBusException
|
|
524
|
+
|
|
525
|
+
def SystemBus(self): # pylint: disable=invalid-name
|
|
526
|
+
"""Dummy SystemBus method"""
|
|
527
|
+
return DummyBus()
|
|
528
|
+
|
|
529
|
+
def Interface(self, _obj, _name): # pylint: disable=invalid-name
|
|
530
|
+
"""Dummy Interface method"""
|
|
531
|
+
return DummyIntf()
|
|
532
|
+
|
|
533
|
+
with patch.dict("sys.modules", {"dbus": DummyDBus()}):
|
|
534
|
+
reboot()
|
|
535
|
+
self.assertTrue(mock_fatal_error.called)
|
test_kernel.py
CHANGED
|
@@ -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
|
@@ -326,7 +326,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
326
326
|
BIT(9) | 1
|
|
327
327
|
) # Kernel warnings ignored, other taint present
|
|
328
328
|
result = self.validator.check_taint()
|
|
329
|
-
self.
|
|
329
|
+
self.assertTrue(result)
|
|
330
330
|
self.assertTrue(
|
|
331
331
|
any(isinstance(f, TaintedKernel) for f in self.validator.failures)
|
|
332
332
|
)
|
|
@@ -1959,3 +1959,79 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
1959
1959
|
result = self.validator.check_dpia_pg_dmcub()
|
|
1960
1960
|
self.assertTrue(result)
|
|
1961
1961
|
self.mock_db.record_prereq.assert_not_called()
|
|
1962
|
+
|
|
1963
|
+
@patch("amd_debug.prerequisites.os.path.exists")
|
|
1964
|
+
def test_capture_nvidia_version_file_missing(self, mock_exists):
|
|
1965
|
+
"""Test capture_nvidia when /proc/driver/nvidia/version does not exist"""
|
|
1966
|
+
mock_exists.side_effect = lambda p: False if "version" in p else True
|
|
1967
|
+
result = self.validator.capture_nvidia()
|
|
1968
|
+
self.assertTrue(result)
|
|
1969
|
+
self.mock_db.record_debug_file.assert_not_called()
|
|
1970
|
+
self.mock_db.record_prereq.assert_not_called()
|
|
1971
|
+
|
|
1972
|
+
@patch("amd_debug.prerequisites.os.path.exists")
|
|
1973
|
+
def test_capture_nvidia_gpus_dir_missing(self, mock_exists):
|
|
1974
|
+
"""Test capture_nvidia when /proc/driver/nvidia/gpus does not exist"""
|
|
1975
|
+
|
|
1976
|
+
def exists_side_effect(path):
|
|
1977
|
+
if "version" in path:
|
|
1978
|
+
return True
|
|
1979
|
+
if "gpus" in path:
|
|
1980
|
+
return False
|
|
1981
|
+
return True
|
|
1982
|
+
|
|
1983
|
+
mock_exists.side_effect = exists_side_effect
|
|
1984
|
+
result = self.validator.capture_nvidia()
|
|
1985
|
+
self.assertTrue(result)
|
|
1986
|
+
self.mock_db.record_debug_file.assert_called_once_with(
|
|
1987
|
+
"/proc/driver/nvidia/version"
|
|
1988
|
+
)
|
|
1989
|
+
self.mock_db.record_prereq.assert_not_called()
|
|
1990
|
+
|
|
1991
|
+
@patch("amd_debug.prerequisites.os.walk")
|
|
1992
|
+
@patch("amd_debug.prerequisites.os.path.exists")
|
|
1993
|
+
def test_capture_nvidia_success(self, mock_exists, mock_walk):
|
|
1994
|
+
"""Test capture_nvidia when NVIDIA GPU files are present and readable"""
|
|
1995
|
+
mock_exists.side_effect = lambda p: True
|
|
1996
|
+
mock_walk.return_value = [
|
|
1997
|
+
("/proc/driver/nvidia/gpus/0000:01:00.0", [], ["info", "power"])
|
|
1998
|
+
]
|
|
1999
|
+
result = self.validator.capture_nvidia()
|
|
2000
|
+
self.assertTrue(result)
|
|
2001
|
+
self.mock_db.record_debug_file.assert_any_call("/proc/driver/nvidia/version")
|
|
2002
|
+
self.mock_db.record_debug.assert_any_call("NVIDIA info")
|
|
2003
|
+
self.mock_db.record_debug_file.assert_any_call(
|
|
2004
|
+
"/proc/driver/nvidia/gpus/0000:01:00.0/info"
|
|
2005
|
+
)
|
|
2006
|
+
self.mock_db.record_debug.assert_any_call("NVIDIA power")
|
|
2007
|
+
self.mock_db.record_debug_file.assert_any_call(
|
|
2008
|
+
"/proc/driver/nvidia/gpus/0000:01:00.0/power"
|
|
2009
|
+
)
|
|
2010
|
+
|
|
2011
|
+
@patch("amd_debug.prerequisites.os.walk")
|
|
2012
|
+
@patch("amd_debug.prerequisites.os.path.exists")
|
|
2013
|
+
def test_capture_nvidia_permission_error_on_version(self, mock_exists, mock_walk):
|
|
2014
|
+
"""Test capture_nvidia when PermissionError occurs reading version file"""
|
|
2015
|
+
mock_exists.side_effect = lambda p: True if "version" in p else False
|
|
2016
|
+
self.mock_db.record_debug_file.side_effect = PermissionError
|
|
2017
|
+
result = self.validator.capture_nvidia()
|
|
2018
|
+
self.assertTrue(result)
|
|
2019
|
+
self.mock_db.record_prereq.assert_called_with(
|
|
2020
|
+
"NVIDIA GPU version not readable", "👀"
|
|
2021
|
+
)
|
|
2022
|
+
|
|
2023
|
+
@patch("amd_debug.prerequisites.os.walk")
|
|
2024
|
+
@patch("amd_debug.prerequisites.os.path.exists")
|
|
2025
|
+
def test_capture_nvidia_permission_error_on_gpu_file(self, mock_exists, mock_walk):
|
|
2026
|
+
"""Test capture_nvidia when PermissionError occurs reading a GPU file"""
|
|
2027
|
+
mock_exists.side_effect = lambda p: True
|
|
2028
|
+
mock_walk.return_value = [
|
|
2029
|
+
("/proc/driver/nvidia/gpus/0000:01:00.0", [], ["info"])
|
|
2030
|
+
]
|
|
2031
|
+
self.mock_db.record_debug_file.side_effect = [None, PermissionError]
|
|
2032
|
+
result = self.validator.capture_nvidia()
|
|
2033
|
+
self.assertTrue(result)
|
|
2034
|
+
self.mock_db.record_debug.assert_any_call("NVIDIA info")
|
|
2035
|
+
self.mock_db.record_prereq.assert_called_with(
|
|
2036
|
+
"NVIDIA GPU {f} not readable", "👀"
|
|
2037
|
+
)
|
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")
|
test_validator.py
CHANGED
|
@@ -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
|
|
|
@@ -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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|