amd-debug-tools 0.2.4__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/kernel.py +2 -10
- amd_debug/prerequisites.py +57 -20
- 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.4.dist-info → amd_debug_tools-0.2.6.dist-info}/METADATA +6 -3
- {amd_debug_tools-0.2.4.dist-info → amd_debug_tools-0.2.6.dist-info}/RECORD +24 -22
- {amd_debug_tools-0.2.4.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 +3 -2
- test_launcher.py +7 -0
- test_prerequisites.py +115 -5
- test_s2idle.py +6 -4
- test_validator.py +63 -1
- {amd_debug_tools-0.2.4.dist-info → amd_debug_tools-0.2.6.dist-info}/WHEEL +0 -0
- {amd_debug_tools-0.2.4.dist-info → amd_debug_tools-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {amd_debug_tools-0.2.4.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/kernel.py
CHANGED
|
@@ -115,24 +115,16 @@ def sscanf_bios_args(line):
|
|
|
115
115
|
|
|
116
116
|
converted_args = []
|
|
117
117
|
arg_index = 0
|
|
118
|
-
for
|
|
118
|
+
for _specifier in format_specifiers:
|
|
119
119
|
if arg_index < len(arguments):
|
|
120
120
|
value = arguments[arg_index]
|
|
121
121
|
if value == "Unknown":
|
|
122
122
|
converted_args.append(-1)
|
|
123
|
-
|
|
123
|
+
else:
|
|
124
124
|
try:
|
|
125
125
|
converted_args.append(int(value, 16))
|
|
126
126
|
except ValueError:
|
|
127
127
|
return None
|
|
128
|
-
else: # Decimal conversion
|
|
129
|
-
try:
|
|
130
|
-
converted_args.append(int(value))
|
|
131
|
-
except ValueError:
|
|
132
|
-
try:
|
|
133
|
-
converted_args.append(int(value, 16))
|
|
134
|
-
except ValueError:
|
|
135
|
-
return None
|
|
136
128
|
arg_index += 1
|
|
137
129
|
else:
|
|
138
130
|
break
|
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()
|
|
@@ -1145,26 +1167,13 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1145
1167
|
found_iommu = True
|
|
1146
1168
|
debug_str += f"Found IOMMU {dev.sys_path}\n"
|
|
1147
1169
|
break
|
|
1170
|
+
|
|
1171
|
+
# User turned off IOMMU, no problems
|
|
1148
1172
|
if not found_iommu:
|
|
1149
1173
|
self.db.record_prereq("IOMMU disabled", "✅")
|
|
1150
1174
|
return True
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
data = f.read()
|
|
1154
|
-
if len(data) < 40:
|
|
1155
|
-
raise ValueError(
|
|
1156
|
-
"IVRS table appears too small to contain virtualization info."
|
|
1157
|
-
)
|
|
1158
|
-
virt_info = struct.unpack_from("I", data, 36)[0]
|
|
1159
|
-
debug_str += f"Virtualization info: 0x{virt_info:x}"
|
|
1160
|
-
found_dmar = (virt_info & 0x2) != 0
|
|
1161
|
-
self.db.record_debug(debug_str)
|
|
1162
|
-
if not found_dmar:
|
|
1163
|
-
self.db.record_prereq(
|
|
1164
|
-
"IOMMU is misconfigured: Pre-boot DMA protection not enabled", "❌"
|
|
1165
|
-
)
|
|
1166
|
-
self.failures += [DMArNotEnabled()]
|
|
1167
|
-
return False
|
|
1175
|
+
|
|
1176
|
+
# Look for MSFT0201 in DSDT/SSDT
|
|
1168
1177
|
for dev in self.pyudev.list_devices(subsystem="acpi"):
|
|
1169
1178
|
if "MSFT0201" in dev.sys_path:
|
|
1170
1179
|
found_acpi = True
|
|
@@ -1174,13 +1183,39 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1174
1183
|
)
|
|
1175
1184
|
self.failures += [MissingIommuACPI("MSFT0201")]
|
|
1176
1185
|
return False
|
|
1177
|
-
|
|
1186
|
+
|
|
1187
|
+
# Check that policy is bound to it
|
|
1178
1188
|
for dev in self.pyudev.list_devices(subsystem="platform"):
|
|
1179
1189
|
if "MSFT0201" in dev.sys_path:
|
|
1180
1190
|
p = os.path.join(dev.sys_path, "iommu")
|
|
1181
1191
|
if not os.path.exists(p):
|
|
1182
1192
|
self.failures += [MissingIommuPolicy("MSFT0201")]
|
|
1183
1193
|
return False
|
|
1194
|
+
|
|
1195
|
+
# Check pre-boot DMA
|
|
1196
|
+
p = os.path.join("/", "sys", "firmware", "acpi", "tables", "IVRS")
|
|
1197
|
+
with open(p, "rb") as f:
|
|
1198
|
+
data = f.read()
|
|
1199
|
+
if len(data) < 40:
|
|
1200
|
+
raise ValueError(
|
|
1201
|
+
"IVRS table appears too small to contain virtualization info."
|
|
1202
|
+
)
|
|
1203
|
+
virt_info = struct.unpack_from("I", data, 36)[0]
|
|
1204
|
+
debug_str += f"IVRS: Virtualization info: 0x{virt_info:x}\n"
|
|
1205
|
+
found_ivrs_dmar = (virt_info & 0x2) != 0
|
|
1206
|
+
|
|
1207
|
+
# check for MSFT0201 in IVRS (alternative to pre-boot DMA)
|
|
1208
|
+
target_bytes = "MSFT0201".encode("utf-8")
|
|
1209
|
+
found_ivrs_msft0201 = data.find(target_bytes) != -1
|
|
1210
|
+
debug_str += f"IVRS: Found MSFT0201: {found_ivrs_msft0201}"
|
|
1211
|
+
|
|
1212
|
+
self.db.record_debug(debug_str)
|
|
1213
|
+
if not found_ivrs_dmar and not found_ivrs_msft0201:
|
|
1214
|
+
self.db.record_prereq(
|
|
1215
|
+
"IOMMU is misconfigured: Pre-boot DMA protection not enabled", "❌"
|
|
1216
|
+
)
|
|
1217
|
+
self.failures += [DMArNotEnabled()]
|
|
1218
|
+
return False
|
|
1184
1219
|
self.db.record_prereq("IOMMU properly configured", "✅")
|
|
1185
1220
|
return True
|
|
1186
1221
|
|
|
@@ -1190,6 +1225,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1190
1225
|
return True
|
|
1191
1226
|
if self.cpu_model not in [0x74, 0x78]:
|
|
1192
1227
|
return True
|
|
1228
|
+
if not self.smu_version:
|
|
1229
|
+
return True
|
|
1193
1230
|
if version.parse(self.smu_version) > version.parse("76.60.0"):
|
|
1194
1231
|
return True
|
|
1195
1232
|
if version.parse(self.smu_version) < version.parse("76.18.0"):
|
|
@@ -1212,9 +1249,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1212
1249
|
# ignore kernel warnings
|
|
1213
1250
|
taint &= ~BIT(9)
|
|
1214
1251
|
if taint != 0:
|
|
1215
|
-
self.db.record_prereq(f"Kernel is tainted: {taint}", "
|
|
1252
|
+
self.db.record_prereq(f"Kernel is tainted: {taint}", "🚦")
|
|
1216
1253
|
self.failures += [TaintedKernel()]
|
|
1217
|
-
return False
|
|
1218
1254
|
return True
|
|
1219
1255
|
|
|
1220
1256
|
def run(self):
|
|
@@ -1229,6 +1265,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1229
1265
|
self.capture_logind,
|
|
1230
1266
|
self.capture_pci_acpi,
|
|
1231
1267
|
self.capture_edid,
|
|
1268
|
+
self.capture_nvidia,
|
|
1232
1269
|
]
|
|
1233
1270
|
checks = []
|
|
1234
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)
|