amd-debug-tools 0.2.1__py3-none-any.whl → 0.2.2__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/common.py +24 -0
- amd_debug/failures.py +13 -1
- amd_debug/installer.py +34 -2
- amd_debug/prerequisites.py +87 -24
- amd_debug/s2idle.py +21 -7
- amd_debug/sleep_report.py +2 -2
- amd_debug/templates/md +0 -7
- amd_debug/validator.py +3 -5
- {amd_debug_tools-0.2.1.dist-info → amd_debug_tools-0.2.2.dist-info}/METADATA +4 -3
- {amd_debug_tools-0.2.1.dist-info → amd_debug_tools-0.2.2.dist-info}/RECORD +19 -19
- {amd_debug_tools-0.2.1.dist-info → amd_debug_tools-0.2.2.dist-info}/WHEEL +1 -1
- test_common.py +112 -0
- test_installer.py +8 -6
- test_prerequisites.py +194 -28
- test_s2idle.py +55 -20
- test_validator.py +3 -5
- {amd_debug_tools-0.2.1.dist-info → amd_debug_tools-0.2.2.dist-info}/entry_points.txt +0 -0
- {amd_debug_tools-0.2.1.dist-info → amd_debug_tools-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {amd_debug_tools-0.2.1.dist-info → amd_debug_tools-0.2.2.dist-info}/top_level.txt +0 -0
amd_debug/common.py
CHANGED
|
@@ -14,6 +14,7 @@ import struct
|
|
|
14
14
|
import subprocess
|
|
15
15
|
import re
|
|
16
16
|
import sys
|
|
17
|
+
from ast import literal_eval
|
|
17
18
|
from datetime import date, timedelta
|
|
18
19
|
|
|
19
20
|
|
|
@@ -278,6 +279,19 @@ def get_property_pyudev(properties, key, fallback=""):
|
|
|
278
279
|
return ""
|
|
279
280
|
|
|
280
281
|
|
|
282
|
+
def find_ip_version(base_path, kind, hw_ver) -> bool:
|
|
283
|
+
"""Determine if an IP version is present on the system"""
|
|
284
|
+
b = os.path.join(base_path, "ip_discovery", "die", "0", kind, "0")
|
|
285
|
+
for key, expected_value in hw_ver.items():
|
|
286
|
+
p = os.path.join(b, key)
|
|
287
|
+
if not os.path.exists(p):
|
|
288
|
+
return False
|
|
289
|
+
v = int(read_file(p))
|
|
290
|
+
if v != expected_value:
|
|
291
|
+
return False
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
|
|
281
295
|
def read_msr(msr, cpu):
|
|
282
296
|
"""Read a Model-Specific Register (MSR) value from the CPU."""
|
|
283
297
|
p = f"/dev/cpu/{cpu}/msr"
|
|
@@ -308,6 +322,16 @@ def running_ssh():
|
|
|
308
322
|
return "SSH_CLIENT" in os.environ or "SSH_TTY" in os.environ
|
|
309
323
|
|
|
310
324
|
|
|
325
|
+
def convert_string_to_bool(str_value) -> bool:
|
|
326
|
+
"""convert a string to a boolean value"""
|
|
327
|
+
try:
|
|
328
|
+
value = literal_eval(str_value)
|
|
329
|
+
except (SyntaxError, ValueError):
|
|
330
|
+
value = None
|
|
331
|
+
sys.exit(f"Invalid entry: {str_value}")
|
|
332
|
+
return bool(value)
|
|
333
|
+
|
|
334
|
+
|
|
311
335
|
def _git_describe() -> str:
|
|
312
336
|
"""Get the git description of the current commit"""
|
|
313
337
|
try:
|
amd_debug/failures.py
CHANGED
|
@@ -416,7 +416,7 @@ class LowHardwareSleepResidency(S0i3Failure):
|
|
|
416
416
|
super().__init__()
|
|
417
417
|
self.description = "System had low hardware sleep residency"
|
|
418
418
|
self.explanation = (
|
|
419
|
-
f"The system was asleep for {timedelta(seconds=duration)}, but only spent {percent:.2%} "
|
|
419
|
+
f"The system was asleep for {timedelta(seconds=duration)}, but only spent {percent/100:.2%} "
|
|
420
420
|
"of this time in a hardware sleep state. In sleep cycles that are at least "
|
|
421
421
|
"60 seconds long it's expected you spend above 90 percent of the cycle in "
|
|
422
422
|
"hardware sleep."
|
|
@@ -586,3 +586,15 @@ class RogAllyMcuPowerSave(S0i3Failure):
|
|
|
586
586
|
"The MCU powersave feature is disabled which will cause problems "
|
|
587
587
|
"with the controller after suspend/resume."
|
|
588
588
|
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
class DmcubTooOld(S0i3Failure):
|
|
592
|
+
"""DMCUB microcode is too old"""
|
|
593
|
+
|
|
594
|
+
def __init__(self, current, expected):
|
|
595
|
+
super().__init__()
|
|
596
|
+
self.description = "DMCUB microcode is too old"
|
|
597
|
+
self.explanation = (
|
|
598
|
+
f"The DMCUB microcode version {hex(current)} is older than the"
|
|
599
|
+
f"minimum suggested version {hex(expected)}."
|
|
600
|
+
)
|
amd_debug/installer.py
CHANGED
|
@@ -26,6 +26,7 @@ class Headers: # pylint: disable=too-few-public-methods
|
|
|
26
26
|
|
|
27
27
|
MissingIasl = "ACPI extraction tool `iasl` is missing"
|
|
28
28
|
MissingEdidDecode = "EDID decoding tool `edid-decode` is missing"
|
|
29
|
+
MissingDiEdidDecode = "EDID decoding tool `di-edid-decode` is missing"
|
|
29
30
|
MissingEthtool = "Ethtool is missing"
|
|
30
31
|
InstallAction = "Attempting to install"
|
|
31
32
|
MissingFwupd = "Firmware update library `fwupd` is missing"
|
|
@@ -35,6 +36,7 @@ class Headers: # pylint: disable=too-few-public-methods
|
|
|
35
36
|
MissingTabulate = "Data library `tabulate` is missing"
|
|
36
37
|
MissingJinja2 = "Template library `jinja2` is missing"
|
|
37
38
|
MissingSeaborn = "Data visualization library `seaborn` is missing"
|
|
39
|
+
UnknownDistro = "No distro installation support available, install manually"
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
class DistroPackage:
|
|
@@ -71,7 +73,8 @@ class DistroPackage:
|
|
|
71
73
|
return False
|
|
72
74
|
installer = ["pacman", "-Sy", self.arch]
|
|
73
75
|
else:
|
|
74
|
-
|
|
76
|
+
print_color(Headers.UnknownDistro, "👀")
|
|
77
|
+
return True
|
|
75
78
|
|
|
76
79
|
try:
|
|
77
80
|
subprocess.check_call(installer)
|
|
@@ -188,6 +191,18 @@ class EdidDecodePackage(DistroPackage):
|
|
|
188
191
|
)
|
|
189
192
|
|
|
190
193
|
|
|
194
|
+
class DisplayInfoPackage(DistroPackage):
|
|
195
|
+
"""display info package"""
|
|
196
|
+
|
|
197
|
+
def __init__(self):
|
|
198
|
+
super().__init__(
|
|
199
|
+
deb="libdisplay-info-bin",
|
|
200
|
+
rpm="libdisplay-info",
|
|
201
|
+
arch="libdisplay-info",
|
|
202
|
+
message=Headers.MissingDiEdidDecode,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
191
206
|
class FwupdPackage(DistroPackage):
|
|
192
207
|
"""Fwupd package"""
|
|
193
208
|
|
|
@@ -261,7 +276,19 @@ class Installer(AmdTool):
|
|
|
261
276
|
package = EthtoolPackage()
|
|
262
277
|
if not package.install():
|
|
263
278
|
return False
|
|
279
|
+
# can be satisified by either edid-decode or di-edid-decode
|
|
264
280
|
if "edid-decode" in self.requirements:
|
|
281
|
+
try:
|
|
282
|
+
di_edid = (
|
|
283
|
+
subprocess.call(
|
|
284
|
+
["di-edid-decode", "--help"],
|
|
285
|
+
stdout=subprocess.DEVNULL,
|
|
286
|
+
stderr=subprocess.DEVNULL,
|
|
287
|
+
)
|
|
288
|
+
== 255
|
|
289
|
+
)
|
|
290
|
+
except FileNotFoundError:
|
|
291
|
+
di_edid = False
|
|
265
292
|
try:
|
|
266
293
|
edid = (
|
|
267
294
|
subprocess.call(
|
|
@@ -271,7 +298,12 @@ class Installer(AmdTool):
|
|
|
271
298
|
)
|
|
272
299
|
except FileNotFoundError:
|
|
273
300
|
edid = False
|
|
274
|
-
if not edid:
|
|
301
|
+
if not di_edid and not edid:
|
|
302
|
+
# try to install di-edid-decode first
|
|
303
|
+
package = DisplayInfoPackage()
|
|
304
|
+
if package.install():
|
|
305
|
+
return True
|
|
306
|
+
# fall back to edid-decode instead
|
|
275
307
|
package = EdidDecodePackage()
|
|
276
308
|
if not package.install():
|
|
277
309
|
return False
|
amd_debug/prerequisites.py
CHANGED
|
@@ -26,6 +26,7 @@ from amd_debug.common import (
|
|
|
26
26
|
apply_prefix_wrapper,
|
|
27
27
|
BIT,
|
|
28
28
|
clear_temporary_message,
|
|
29
|
+
find_ip_version,
|
|
29
30
|
get_distro,
|
|
30
31
|
get_pretty_distro,
|
|
31
32
|
get_property_pyudev,
|
|
@@ -48,6 +49,7 @@ from amd_debug.failures import (
|
|
|
48
49
|
DevSlpDiskIssue,
|
|
49
50
|
DevSlpHostIssue,
|
|
50
51
|
DMArNotEnabled,
|
|
52
|
+
DmcubTooOld,
|
|
51
53
|
DmiNotSetup,
|
|
52
54
|
FadtWrong,
|
|
53
55
|
I2CHidBug,
|
|
@@ -134,20 +136,24 @@ class PrerequisiteValidator(AmdTool):
|
|
|
134
136
|
return True
|
|
135
137
|
|
|
136
138
|
for name, p in edids.items():
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
139
|
+
output = None
|
|
140
|
+
for cmd in ["di-edid-decode", "edid-decode"]:
|
|
141
|
+
try:
|
|
142
|
+
cmd = ["edid-decode", p]
|
|
143
|
+
output = subprocess.check_output(
|
|
144
|
+
cmd, stderr=subprocess.DEVNULL
|
|
145
|
+
).decode("utf-8")
|
|
146
|
+
break
|
|
147
|
+
except FileNotFoundError:
|
|
148
|
+
self.db.record_debug(f"{cmd} not installed")
|
|
149
|
+
except subprocess.CalledProcessError as e:
|
|
150
|
+
self.db.record_debug(
|
|
151
|
+
f"failed to capture edid with {cmd}: {e.output}"
|
|
152
|
+
)
|
|
153
|
+
if not output:
|
|
154
|
+
self.db.record_prereq("Failed to capture EDID table", "👀")
|
|
155
|
+
else:
|
|
156
|
+
self.db.record_debug(apply_prefix_wrapper(f"EDID for {name}:", output))
|
|
151
157
|
return True
|
|
152
158
|
|
|
153
159
|
def check_amdgpu(self):
|
|
@@ -395,6 +401,53 @@ class PrerequisiteValidator(AmdTool):
|
|
|
395
401
|
)
|
|
396
402
|
return True
|
|
397
403
|
|
|
404
|
+
def check_dpia_pg_dmcub(self):
|
|
405
|
+
"""Check if DMUB is new enough to PG DPIA when no USB4 present"""
|
|
406
|
+
usb4_found = False
|
|
407
|
+
for device in self.pyudev.list_devices(subsystem="pci", PCI_CLASS="C0340"):
|
|
408
|
+
usb4_found = True
|
|
409
|
+
break
|
|
410
|
+
if usb4_found:
|
|
411
|
+
self.db.record_debug("USB4 routers found, no need to check DMCUB version")
|
|
412
|
+
return True
|
|
413
|
+
# Check if matching DCN present
|
|
414
|
+
for device in self.pyudev.list_devices(subsystem="pci"):
|
|
415
|
+
current = None
|
|
416
|
+
klass = device.properties.get("PCI_CLASS")
|
|
417
|
+
if klass not in ["30000", "38000"]:
|
|
418
|
+
continue
|
|
419
|
+
pci_id = device.properties.get("PCI_ID")
|
|
420
|
+
if not pci_id.startswith("1002"):
|
|
421
|
+
continue
|
|
422
|
+
hw_ver = {"major": 3, "minor": 5, "revision": 0}
|
|
423
|
+
if not find_ip_version(device.sys_path, "DMU", hw_ver):
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
# DCN was found, lookup version from sysfs
|
|
427
|
+
p = os.path.join(device.sys_path, "fw_version", "dmcub_fw_version")
|
|
428
|
+
if os.path.exists(p):
|
|
429
|
+
current = int(read_file(p), 16)
|
|
430
|
+
|
|
431
|
+
# no sysfs, try to look for version from debugfs
|
|
432
|
+
if not current:
|
|
433
|
+
slot = device.properties["PCI_SLOT_NAME"]
|
|
434
|
+
p = os.path.join(
|
|
435
|
+
"/", "sys", "kernel", "debug", "dri", slot, "amdgpu_firmware_info"
|
|
436
|
+
)
|
|
437
|
+
contents = read_file(p)
|
|
438
|
+
for line in contents.split("\n"):
|
|
439
|
+
if not line.startswith("DMCUB"):
|
|
440
|
+
continue
|
|
441
|
+
current = int(line.split()[-1], 16)
|
|
442
|
+
if current:
|
|
443
|
+
expected = 0x09001B00
|
|
444
|
+
if current >= expected:
|
|
445
|
+
return True
|
|
446
|
+
self.db.record_prereq("DMCUB Firmware is outdated", "❌")
|
|
447
|
+
self.failures += [DmcubTooOld(current, expected)]
|
|
448
|
+
return False
|
|
449
|
+
return True
|
|
450
|
+
|
|
398
451
|
def check_usb4(self):
|
|
399
452
|
"""Check if the thunderbolt driver is loaded"""
|
|
400
453
|
slots = []
|
|
@@ -698,9 +751,13 @@ class PrerequisiteValidator(AmdTool):
|
|
|
698
751
|
interface = device.properties.get("INTERFACE")
|
|
699
752
|
cmd = ["ethtool", interface]
|
|
700
753
|
wol_supported = False
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
754
|
+
try:
|
|
755
|
+
output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode(
|
|
756
|
+
"utf-8"
|
|
757
|
+
)
|
|
758
|
+
except FileNotFoundError:
|
|
759
|
+
self.db.record_prereq(f"ethtool is missing", "👀")
|
|
760
|
+
return True
|
|
704
761
|
for line in output.split("\n"):
|
|
705
762
|
if "Supports Wake-on" in line:
|
|
706
763
|
val = line.split(":")[1].strip()
|
|
@@ -949,6 +1006,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
949
1006
|
stderr=subprocess.DEVNULL,
|
|
950
1007
|
)
|
|
951
1008
|
self.db.record_debug_file(f"{prefix}.dsl")
|
|
1009
|
+
except FileNotFoundError as e:
|
|
1010
|
+
self.db.record_prereq(f"Failed to capture ACPI table: {e}", "👀")
|
|
952
1011
|
except subprocess.CalledProcessError as e:
|
|
953
1012
|
self.db.record_prereq(
|
|
954
1013
|
f"Failed to capture ACPI table: {e.output}", "👀"
|
|
@@ -1099,13 +1158,16 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1099
1158
|
self.db.record_prereq("IOMMU disabled", "✅")
|
|
1100
1159
|
return True
|
|
1101
1160
|
debug_str += "DMA protection:\n"
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1161
|
+
p = os.path.join("/", "sys", "firmware", "acpi", "tables", "IVRS")
|
|
1162
|
+
with open(p, "rb") as f:
|
|
1163
|
+
data = f.read()
|
|
1164
|
+
if len(data) < 40:
|
|
1165
|
+
raise ValueError(
|
|
1166
|
+
"IVRS table appears too small to contain virtualization info."
|
|
1167
|
+
)
|
|
1168
|
+
virt_info = struct.unpack_from("I", data, 36)[0]
|
|
1169
|
+
debug_str += f"Virtualization info: 0x{virt_info:x}"
|
|
1170
|
+
found_dmar = (virt_info & 0x2) != 0
|
|
1109
1171
|
self.db.record_debug(debug_str)
|
|
1110
1172
|
if not found_dmar:
|
|
1111
1173
|
self.db.record_prereq(
|
|
@@ -1205,6 +1267,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1205
1267
|
self.check_smt,
|
|
1206
1268
|
self.check_iommu,
|
|
1207
1269
|
self.check_asus_rog_ally,
|
|
1270
|
+
self.check_dpia_pg_dmcub,
|
|
1208
1271
|
]
|
|
1209
1272
|
|
|
1210
1273
|
checks += [
|
amd_debug/s2idle.py
CHANGED
|
@@ -9,6 +9,7 @@ import sqlite3
|
|
|
9
9
|
|
|
10
10
|
from datetime import date, timedelta, datetime
|
|
11
11
|
from amd_debug.common import (
|
|
12
|
+
convert_string_to_bool,
|
|
12
13
|
colorize_choices,
|
|
13
14
|
is_root,
|
|
14
15
|
relaunch_sudo,
|
|
@@ -32,6 +33,7 @@ class Defaults:
|
|
|
32
33
|
since = date.today() - timedelta(days=60)
|
|
33
34
|
until = date.today() + timedelta(days=1)
|
|
34
35
|
format_choices = ["txt", "md", "html", "stdout"]
|
|
36
|
+
boolean_choices = ["true", "false"]
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
class Headers:
|
|
@@ -47,6 +49,7 @@ class Headers:
|
|
|
47
49
|
FormatDescription = "What format to output the report in"
|
|
48
50
|
MaxDurationDescription = "What is the maximum suspend cycle length (seconds)"
|
|
49
51
|
MaxWaitDescription = "What is the maximum time between suspend cycles (seconds)"
|
|
52
|
+
ReportDebugDescription = "Enable debug output in report (increased size)"
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
def display_report_file(fname, fmt) -> None:
|
|
@@ -85,7 +88,7 @@ def get_report_format() -> str:
|
|
|
85
88
|
return "html"
|
|
86
89
|
|
|
87
90
|
|
|
88
|
-
def prompt_report_arguments(since, until, fname, fmt) -> str:
|
|
91
|
+
def prompt_report_arguments(since, until, fname, fmt, report_debug) -> str:
|
|
89
92
|
"""Prompt user for report configuration"""
|
|
90
93
|
if not since:
|
|
91
94
|
default = Defaults.since
|
|
@@ -114,7 +117,16 @@ def prompt_report_arguments(since, until, fname, fmt) -> str:
|
|
|
114
117
|
fmt = get_report_format()
|
|
115
118
|
if fmt not in Defaults.format_choices:
|
|
116
119
|
sys.exit(f"Invalid format: {fmt}")
|
|
117
|
-
|
|
120
|
+
if report_debug is None:
|
|
121
|
+
inp = (
|
|
122
|
+
input(
|
|
123
|
+
f"{Headers.ReportDebugDescription} ({colorize_choices(Defaults.boolean_choices, "true")})? "
|
|
124
|
+
)
|
|
125
|
+
.lower()
|
|
126
|
+
.capitalize()
|
|
127
|
+
)
|
|
128
|
+
report_debug = True if not inp else convert_string_to_bool(inp)
|
|
129
|
+
return [since, until, get_report_file(fname, fmt), fmt, report_debug]
|
|
118
130
|
|
|
119
131
|
|
|
120
132
|
def prompt_test_arguments(duration, wait, count, rand) -> list:
|
|
@@ -157,7 +169,9 @@ def prompt_test_arguments(duration, wait, count, rand) -> list:
|
|
|
157
169
|
def report(since, until, fname, fmt, tool_debug, report_debug) -> bool:
|
|
158
170
|
"""Generate a report from previous sleep cycles"""
|
|
159
171
|
try:
|
|
160
|
-
since, until, fname, fmt = prompt_report_arguments(
|
|
172
|
+
since, until, fname, fmt, report_debug = prompt_report_arguments(
|
|
173
|
+
since, until, fname, fmt, report_debug
|
|
174
|
+
)
|
|
161
175
|
except KeyboardInterrupt:
|
|
162
176
|
sys.exit("\nReport generation cancelled")
|
|
163
177
|
try:
|
|
@@ -209,8 +223,8 @@ def run_test_cycle(
|
|
|
209
223
|
app = SleepValidator(tool_debug=debug, bios_debug=bios_debug)
|
|
210
224
|
try:
|
|
211
225
|
duration, wait, count = prompt_test_arguments(duration, wait, count, rand)
|
|
212
|
-
since, until, fname, fmt = prompt_report_arguments(
|
|
213
|
-
datetime.now().isoformat(), Defaults.until.isoformat(), fname, fmt
|
|
226
|
+
since, until, fname, fmt, report_debug = prompt_report_arguments(
|
|
227
|
+
datetime.now().isoformat(), Defaults.until.isoformat(), fname, fmt, True
|
|
214
228
|
)
|
|
215
229
|
except KeyboardInterrupt:
|
|
216
230
|
sys.exit("\nTest cancelled")
|
|
@@ -229,7 +243,7 @@ def run_test_cycle(
|
|
|
229
243
|
fname=fname,
|
|
230
244
|
fmt=fmt,
|
|
231
245
|
tool_debug=debug,
|
|
232
|
-
report_debug=
|
|
246
|
+
report_debug=report_debug,
|
|
233
247
|
)
|
|
234
248
|
app.run()
|
|
235
249
|
|
|
@@ -347,7 +361,7 @@ def parse_args():
|
|
|
347
361
|
)
|
|
348
362
|
report_cmd.add_argument(
|
|
349
363
|
"--report-debug",
|
|
350
|
-
action=
|
|
364
|
+
action=argparse.BooleanOptionalAction,
|
|
351
365
|
help="Include debug messages in report (WARNING: can significantly increase report size)",
|
|
352
366
|
)
|
|
353
367
|
|
amd_debug/sleep_report.py
CHANGED
|
@@ -432,8 +432,8 @@ class SleepReport(AmdTool):
|
|
|
432
432
|
|
|
433
433
|
characters = print_temporary_message("Building report, please wait...")
|
|
434
434
|
|
|
435
|
-
# Build charts in the page for html
|
|
436
|
-
if len(self.df.index) > 1 and
|
|
435
|
+
# Build charts in the page for html format
|
|
436
|
+
if len(self.df.index) > 1 and self.format == "html":
|
|
437
437
|
self.build_battery_chart()
|
|
438
438
|
self.build_hw_sleep_chart()
|
|
439
439
|
|
amd_debug/templates/md
CHANGED
amd_debug/validator.py
CHANGED
|
@@ -469,12 +469,10 @@ class SleepValidator(AmdTool):
|
|
|
469
469
|
)
|
|
470
470
|
return False
|
|
471
471
|
except FileNotFoundError:
|
|
472
|
-
self.db.
|
|
473
|
-
return False
|
|
472
|
+
self.db.record_debug(f"HW sleep statistics file {p} is missing")
|
|
474
473
|
if not self.hw_sleep_duration:
|
|
475
474
|
self.db.record_cycle_data("Did not reach hardware sleep state", "❌")
|
|
476
|
-
|
|
477
|
-
return self.hw_sleep_duration is not None
|
|
475
|
+
return self.hw_sleep_duration
|
|
478
476
|
|
|
479
477
|
def capture_command_line(self):
|
|
480
478
|
"""Capture the kernel command line to debug"""
|
|
@@ -674,7 +672,7 @@ class SleepValidator(AmdTool):
|
|
|
674
672
|
def prep(self):
|
|
675
673
|
"""Prepare the system for suspend testing"""
|
|
676
674
|
self.last_suspend = datetime.now()
|
|
677
|
-
self.kernel_log.seek_tail()
|
|
675
|
+
self.kernel_log.seek_tail(self.last_suspend)
|
|
678
676
|
self.db.start_cycle(self.last_suspend)
|
|
679
677
|
self.kernel_duration = 0
|
|
680
678
|
self.hw_sleep_duration = 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amd-debug-tools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: debug tools for AMD systems
|
|
5
5
|
Author-email: Mario Limonciello <superm1@kernel.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -125,8 +125,9 @@ The following optional arguments are supported for this command:
|
|
|
125
125
|
--format FORMAT Format of the report to produce (html, txt or md)
|
|
126
126
|
--report-file File to write the report to
|
|
127
127
|
--tool-debug Enable tool debug logging
|
|
128
|
-
--report-debug
|
|
129
|
-
|
|
128
|
+
--report-debug
|
|
129
|
+
--no-report-debug
|
|
130
|
+
Include debug messages in report (WARNING: can significantly increase report size)
|
|
130
131
|
If the tool is launched with an environment that can call `xdg-open`, the report
|
|
131
132
|
will be opened in a browser.
|
|
132
133
|
|
|
@@ -2,44 +2,44 @@ launcher.py,sha256=_Gs0W8tUB2wkTy5Nz4qEzG0VqQcnO7xuIQj0GwV_KbY,968
|
|
|
2
2
|
test_acpi.py,sha256=wtS43Rz95h7YEEJBeFa6Mswaeo4syBZrw4hY8i0YbJY,3117
|
|
3
3
|
test_batteries.py,sha256=nN5pfP5El7Whypq3HHEpW8bufdf5EWSTVGbayfNQYP4,3360
|
|
4
4
|
test_bios.py,sha256=GBAXE_rXd2G-JE0XJ8AvYcF9Me6LTyQQQ8h0Ib3cpxQ,8981
|
|
5
|
-
test_common.py,sha256=
|
|
5
|
+
test_common.py,sha256=fb16Oilh5ga6VgF-UgBj6azoYzZnPrS7KpECQ3nCwlg,16335
|
|
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
|
-
test_installer.py,sha256=
|
|
9
|
+
test_installer.py,sha256=oDMCvaKqqAWjTggltacnasQ-s1gyUvXPDcNrCUGnux4,10216
|
|
10
10
|
test_kernel.py,sha256=RW-eLbae02Bhwfu1cegqA1pTj6AS5IqD5lLe-6T0Rjo,7871
|
|
11
11
|
test_launcher.py,sha256=govYHL0Cpj9d5msteV5SfR7Covft31rJuzRkDeytHcY,1461
|
|
12
|
-
test_prerequisites.py,sha256=
|
|
12
|
+
test_prerequisites.py,sha256=4Ltxx7vGU91b9MX1AtdPLS8iyWJBCNuEKCuO0nnEb_g,83381
|
|
13
13
|
test_pstate.py,sha256=a9oAJ9-LANX32XNQhplz6Y75VNYc__QqoSBKIrwvANg,6058
|
|
14
|
-
test_s2idle.py,sha256
|
|
14
|
+
test_s2idle.py,sha256=-S3yymjprf3_LIgaPGWgzN9FmnyQG55dNc4xqWAF8Z8,32344
|
|
15
15
|
test_sleep_report.py,sha256=R3cUPPT9r9q4q93xk6NFvi4ySgT5laqidk2SASuTWIo,5878
|
|
16
|
-
test_validator.py,sha256=
|
|
16
|
+
test_validator.py,sha256=NeiX8kT5DiHiiB5lRSJkjIpV32UzaIrW5ljKLmFtBMk,30490
|
|
17
17
|
test_wake.py,sha256=6zi5GVFHQKU1sTWw3O5-aGriB9uu5713QLn4l2wjhpM,7152
|
|
18
18
|
amd_debug/__init__.py,sha256=aOtpIEKGLUStrh0e4qgilHW7HgF4Od-r9pOoZ87NwAM,1105
|
|
19
19
|
amd_debug/acpi.py,sha256=fkD3Sov8cRT5ryPlakRlT7Z9jiCLT9x_MPWxt3xU_tc,3161
|
|
20
20
|
amd_debug/battery.py,sha256=WN-6ys9PHCZIwg7PdwyBOa62GjBp8WKG0v1YZt5_W5s,3122
|
|
21
21
|
amd_debug/bios.py,sha256=wmPKDsTZeQqsHjWpv-YHdgRNlCtFdzHQ6jJf0H3hjN8,3971
|
|
22
|
-
amd_debug/common.py,sha256=
|
|
22
|
+
amd_debug/common.py,sha256=H9tIRlRFOMwe0d3f2-vXQeK2rJl5Z1WJzkpQM9ivpOc,10347
|
|
23
23
|
amd_debug/database.py,sha256=GkRg3cmaNceyQ2_hy0MBAlMbnTDPHo2co2o4ObWpnQg,10621
|
|
24
24
|
amd_debug/display.py,sha256=5L9x9tI_UoulHpIvuxuVASRtdXta7UCW_JjTb5StEB0,953
|
|
25
|
-
amd_debug/failures.py,sha256=
|
|
26
|
-
amd_debug/installer.py,sha256=
|
|
25
|
+
amd_debug/failures.py,sha256=Otv3YDu7Je4ljSifVmvjObGoOY4OvLIY20pw-v4Dqkw,22911
|
|
26
|
+
amd_debug/installer.py,sha256=r6r_nVWv8qYdrqAvnAzQhRiS5unBDOkXsqUfHvFK8uM,14249
|
|
27
27
|
amd_debug/kernel.py,sha256=xzAy-sDY5-sd4jxyU7EaBokS7YsvEjoWRuexaTJNRBc,11851
|
|
28
|
-
amd_debug/prerequisites.py,sha256=
|
|
28
|
+
amd_debug/prerequisites.py,sha256=bKJA9ztyapB8rxrNEgc-hxazw5Uh-sP5X0S7zplGA0c,50413
|
|
29
29
|
amd_debug/pstate.py,sha256=akGdJkIxBp0bx3AeGv6ictNxwv8m0j9vQ2IZB0Jx3dM,9518
|
|
30
30
|
amd_debug/s2idle-hook,sha256=LLiaqPtGd0qetu9n6EYxKHZaIdHpVQDONdOuSc0pfFg,1695
|
|
31
|
-
amd_debug/s2idle.py,sha256=
|
|
32
|
-
amd_debug/sleep_report.py,sha256=
|
|
33
|
-
amd_debug/validator.py,sha256=
|
|
31
|
+
amd_debug/s2idle.py,sha256=lzxLYZBcQllyqEMZfxYEUvQO3ArgVzwL5FHymzvZvSs,13281
|
|
32
|
+
amd_debug/sleep_report.py,sha256=zgwcmSk7S8GAmPtPZJGP29Mr5bcWUBxwNL8HBubKs6Q,15516
|
|
33
|
+
amd_debug/validator.py,sha256=fgG3D0k6DS9ArgzK1SuPUds1FNGoKf2asPUwWoyCsXE,33205
|
|
34
34
|
amd_debug/wake.py,sha256=xT8WrFrN6voCmXWo5dsn4mQ7iR2QJxHrrYBd3EREG-Q,3936
|
|
35
35
|
amd_debug/bash/amd-s2idle,sha256=g_cle1ElCJpwE4wcLezL6y-BdasDKTnNMhrtzKLE9ks,1142
|
|
36
36
|
amd_debug/templates/html,sha256=tnpqHDZF5FfhC6YNRUfOG6Vn9ZtISFr10kEXSB476Mw,14518
|
|
37
|
-
amd_debug/templates/md,sha256=
|
|
37
|
+
amd_debug/templates/md,sha256=r8X2aehnH2gzj0WHYTZ5K9wAqC5y39i_3nkDORSC0uM,787
|
|
38
38
|
amd_debug/templates/stdout,sha256=hyoOJ96K2dJfnWRWhyCuariLKbEHXvs9mstV_g5aMdI,469
|
|
39
39
|
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.
|
|
40
|
+
amd_debug_tools-0.2.2.dist-info/licenses/LICENSE,sha256=RBlZI6r3MRGzymI2VDX2iW__D2APDbMhu_Xg5t6BWeo,1066
|
|
41
|
+
amd_debug_tools-0.2.2.dist-info/METADATA,sha256=oZ5H5dL216bbAqtOF8VmvtqkqctPIf5iPibPCjjc1tQ,6877
|
|
42
|
+
amd_debug_tools-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
43
|
+
amd_debug_tools-0.2.2.dist-info/entry_points.txt,sha256=HC11T2up0pPfroAn6Pg5M2jOZXhkWIipToJ1YPTKqu8,116
|
|
44
|
+
amd_debug_tools-0.2.2.dist-info/top_level.txt,sha256=XYjxExbUTEtiIlag_5iQvZSVOC1EIxhKM4NLklReQ0k,234
|
|
45
|
+
amd_debug_tools-0.2.2.dist-info/RECORD,,
|
test_common.py
CHANGED
|
@@ -16,9 +16,11 @@ from platform import uname_result
|
|
|
16
16
|
from amd_debug.common import (
|
|
17
17
|
apply_prefix_wrapper,
|
|
18
18
|
Colors,
|
|
19
|
+
convert_string_to_bool,
|
|
19
20
|
colorize_choices,
|
|
20
21
|
check_lockdown,
|
|
21
22
|
compare_file,
|
|
23
|
+
find_ip_version,
|
|
22
24
|
fatal_error,
|
|
23
25
|
get_distro,
|
|
24
26
|
get_log_priority,
|
|
@@ -330,3 +332,113 @@ class TestCommon(unittest.TestCase):
|
|
|
330
332
|
default = "option1"
|
|
331
333
|
expected_output = f"{Colors.OK}{default}{Colors.ENDC}"
|
|
332
334
|
self.assertEqual(colorize_choices(choices, default), expected_output)
|
|
335
|
+
|
|
336
|
+
@patch("amd_debug.common.read_file")
|
|
337
|
+
@patch("os.path.exists")
|
|
338
|
+
def test_find_ip_version_found(self, mock_exists, mock_read_file):
|
|
339
|
+
"""Test find_ip_version returns True when expected value is found"""
|
|
340
|
+
base_path = "/foo"
|
|
341
|
+
kind = "bar"
|
|
342
|
+
hw_ver = {"baz": 42}
|
|
343
|
+
|
|
344
|
+
# Simulate file exists and value matches
|
|
345
|
+
def exists_side_effect(path):
|
|
346
|
+
return True
|
|
347
|
+
|
|
348
|
+
mock_exists.side_effect = exists_side_effect
|
|
349
|
+
mock_read_file.return_value = "42"
|
|
350
|
+
result = find_ip_version(base_path, kind, hw_ver)
|
|
351
|
+
self.assertTrue(result)
|
|
352
|
+
b = os.path.join(base_path, "ip_discovery", "die", "0", kind, "0")
|
|
353
|
+
expected_path = os.path.join(b, "baz")
|
|
354
|
+
mock_exists.assert_any_call(expected_path)
|
|
355
|
+
mock_read_file.assert_any_call(expected_path)
|
|
356
|
+
|
|
357
|
+
@patch("amd_debug.common.read_file")
|
|
358
|
+
@patch("os.path.exists")
|
|
359
|
+
def test_find_ip_version_not_found_due_to_missing_file(
|
|
360
|
+
self, mock_exists, mock_read_file
|
|
361
|
+
):
|
|
362
|
+
"""Test find_ip_version returns False if file does not exist"""
|
|
363
|
+
base_path = "/foo"
|
|
364
|
+
kind = "bar"
|
|
365
|
+
hw_ver = {"baz": 42}
|
|
366
|
+
# Simulate file does not exist
|
|
367
|
+
mock_exists.return_value = False
|
|
368
|
+
result = find_ip_version(base_path, kind, hw_ver)
|
|
369
|
+
self.assertFalse(result)
|
|
370
|
+
b = os.path.join(base_path, "ip_discovery", "die", "0", kind, "0")
|
|
371
|
+
expected_path = os.path.join(b, "baz")
|
|
372
|
+
mock_exists.assert_any_call(expected_path)
|
|
373
|
+
mock_read_file.assert_not_called()
|
|
374
|
+
|
|
375
|
+
@patch("amd_debug.common.read_file")
|
|
376
|
+
@patch("os.path.exists")
|
|
377
|
+
def test_find_ip_version_not_found_due_to_value_mismatch(
|
|
378
|
+
self, mock_exists, mock_read_file
|
|
379
|
+
):
|
|
380
|
+
"""Test find_ip_version returns False if value does not match"""
|
|
381
|
+
base_path = "/foo"
|
|
382
|
+
kind = "bar"
|
|
383
|
+
hw_ver = {"baz": 42}
|
|
384
|
+
# Simulate file exists but value does not match
|
|
385
|
+
mock_exists.return_value = True
|
|
386
|
+
mock_read_file.return_value = "99"
|
|
387
|
+
result = find_ip_version(base_path, kind, hw_ver)
|
|
388
|
+
self.assertFalse(result)
|
|
389
|
+
b = os.path.join(base_path, "ip_discovery", "die", "0", kind, "0")
|
|
390
|
+
expected_path = os.path.join(b, "baz")
|
|
391
|
+
mock_exists.assert_any_call(expected_path)
|
|
392
|
+
mock_read_file.assert_any_call(expected_path)
|
|
393
|
+
|
|
394
|
+
@patch("amd_debug.common.read_file")
|
|
395
|
+
@patch("os.path.exists")
|
|
396
|
+
def test_find_ip_version_multiple_keys(self, mock_exists, mock_read_file):
|
|
397
|
+
"""Test find_ip_version with multiple keys in hw_ver"""
|
|
398
|
+
base_path = "/foo"
|
|
399
|
+
kind = "bar"
|
|
400
|
+
hw_ver = {"baz": 42, "qux": 99}
|
|
401
|
+
|
|
402
|
+
# First key: file exists, value does not match
|
|
403
|
+
# Second key: file exists, value matches
|
|
404
|
+
def exists_side_effect(path):
|
|
405
|
+
return True
|
|
406
|
+
|
|
407
|
+
def read_file_side_effect(path):
|
|
408
|
+
if path.endswith("baz"):
|
|
409
|
+
return "0"
|
|
410
|
+
if path.endswith("qux"):
|
|
411
|
+
return "99"
|
|
412
|
+
return "0"
|
|
413
|
+
|
|
414
|
+
mock_exists.side_effect = exists_side_effect
|
|
415
|
+
mock_read_file.side_effect = read_file_side_effect
|
|
416
|
+
result = find_ip_version(base_path, kind, hw_ver)
|
|
417
|
+
self.assertFalse(result)
|
|
418
|
+
|
|
419
|
+
def test_convert_string_to_bool_true_values(self):
|
|
420
|
+
"""Test convert_string_to_bool returns True for truthy string values"""
|
|
421
|
+
self.assertTrue(convert_string_to_bool("True"))
|
|
422
|
+
self.assertTrue(convert_string_to_bool("1"))
|
|
423
|
+
self.assertTrue(convert_string_to_bool("'nonempty'"))
|
|
424
|
+
self.assertTrue(convert_string_to_bool('"nonempty"'))
|
|
425
|
+
|
|
426
|
+
def test_convert_string_to_bool_false_values(self):
|
|
427
|
+
"""Test convert_string_to_bool returns False for falsy string values"""
|
|
428
|
+
self.assertFalse(convert_string_to_bool("False"))
|
|
429
|
+
self.assertFalse(convert_string_to_bool("0"))
|
|
430
|
+
self.assertFalse(convert_string_to_bool("''"))
|
|
431
|
+
self.assertFalse(convert_string_to_bool('""'))
|
|
432
|
+
self.assertFalse(convert_string_to_bool("None"))
|
|
433
|
+
|
|
434
|
+
def test_convert_string_to_bool_invalid_syntax(self):
|
|
435
|
+
"""Test convert_string_to_bool exits on invalid syntax"""
|
|
436
|
+
with patch("sys.exit") as mock_exit:
|
|
437
|
+
convert_string_to_bool("not_a_bool")
|
|
438
|
+
mock_exit.assert_called_once_with("Invalid entry: not_a_bool")
|
|
439
|
+
|
|
440
|
+
def test_convert_string_to_bool_invalid_value(self):
|
|
441
|
+
"""Test convert_string_to_bool exits on invalid value"""
|
|
442
|
+
with patch("sys.exit") as mock_exit:
|
|
443
|
+
convert_string_to_bool("[unclosed_list")
|
|
444
|
+
mock_exit.assert_called_once_with("Invalid entry: [unclosed_list")
|
test_installer.py
CHANGED
|
@@ -192,7 +192,7 @@ class TestInstaller(unittest.TestCase):
|
|
|
192
192
|
"""Test install requirements function"""
|
|
193
193
|
self.installer.set_requirements("iasl", "ethtool")
|
|
194
194
|
ret = self.installer.install_dependencies()
|
|
195
|
-
self.
|
|
195
|
+
self.assertTrue(ret)
|
|
196
196
|
|
|
197
197
|
@patch("builtins.print")
|
|
198
198
|
@patch("amd_debug.installer.get_distro", return_value="ubuntu")
|
|
@@ -205,7 +205,9 @@ class TestInstaller(unittest.TestCase):
|
|
|
205
205
|
"""Test install requirements function for edid-decode on Ubuntu"""
|
|
206
206
|
self.installer.set_requirements("edid-decode")
|
|
207
207
|
ret = self.installer.install_dependencies()
|
|
208
|
-
_mock_check_call.assert_called_once_with(
|
|
208
|
+
_mock_check_call.assert_called_once_with(
|
|
209
|
+
["apt", "install", "libdisplay-info-bin"]
|
|
210
|
+
)
|
|
209
211
|
self.assertTrue(ret)
|
|
210
212
|
|
|
211
213
|
@patch("builtins.print")
|
|
@@ -229,7 +231,7 @@ class TestInstaller(unittest.TestCase):
|
|
|
229
231
|
self.installer.set_requirements("edid-decode")
|
|
230
232
|
ret = self.installer.install_dependencies()
|
|
231
233
|
_mock_check_call.assert_called_once_with(
|
|
232
|
-
["dnf", "install", "-y", "
|
|
234
|
+
["dnf", "install", "-y", "libdisplay-info"]
|
|
233
235
|
)
|
|
234
236
|
self.assertTrue(ret)
|
|
235
237
|
|
|
@@ -249,8 +251,8 @@ class TestInstaller(unittest.TestCase):
|
|
|
249
251
|
"""Test install requirements function for edid-decode on Arch"""
|
|
250
252
|
self.installer.set_requirements("edid-decode")
|
|
251
253
|
ret = self.installer.install_dependencies()
|
|
252
|
-
_mock_check_call.
|
|
253
|
-
self.
|
|
254
|
+
_mock_check_call.assert_called_once_with(["pacman", "-Sy", "libdisplay-info"])
|
|
255
|
+
self.assertTrue(ret)
|
|
254
256
|
|
|
255
257
|
@patch("builtins.print")
|
|
256
258
|
@patch("os.path.exists", return_value=False)
|
|
@@ -263,7 +265,7 @@ class TestInstaller(unittest.TestCase):
|
|
|
263
265
|
"""Test install requirements function for edid-decode on unsupported distro"""
|
|
264
266
|
self.installer.set_requirements("edid-decode")
|
|
265
267
|
ret = self.installer.install_dependencies()
|
|
266
|
-
self.
|
|
268
|
+
self.assertTrue(ret)
|
|
267
269
|
|
|
268
270
|
@patch("builtins.print")
|
|
269
271
|
@patch("os.path.exists", return_value=False)
|
test_prerequisites.py
CHANGED
|
@@ -8,7 +8,7 @@ This module contains unit tests for the prerequisite functions in the amd-debug-
|
|
|
8
8
|
import logging
|
|
9
9
|
import unittest
|
|
10
10
|
import subprocess
|
|
11
|
-
from unittest.mock import patch, MagicMock
|
|
11
|
+
from unittest.mock import patch, MagicMock, mock_open
|
|
12
12
|
|
|
13
13
|
from amd_debug.prerequisites import PrerequisiteValidator
|
|
14
14
|
from amd_debug.failures import *
|
|
@@ -180,20 +180,22 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
180
180
|
self.assertTrue(result)
|
|
181
181
|
self.mock_db.record_prereq.assert_called_with("IOMMU disabled", "✅")
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
@patch(
|
|
184
|
+
"amd_debug.prerequisites.open",
|
|
185
|
+
new_callable=unittest.mock.mock_open,
|
|
186
|
+
read_data=b"\x00" * 45,
|
|
187
|
+
)
|
|
188
|
+
def test_check_iommu_no_dma_protection(self, _mock_open):
|
|
184
189
|
"""Test check_iommu when DMA protection is not enabled"""
|
|
185
190
|
self.validator.cpu_family = 0x1A
|
|
186
191
|
self.validator.cpu_model = 0x20
|
|
187
192
|
iommu_device = MagicMock(sys_path="/sys/devices/iommu")
|
|
188
|
-
thunderbolt_device = MagicMock(sys_path="/sys/devices/thunderbolt")
|
|
189
193
|
self.mock_pyudev.list_devices.side_effect = [
|
|
190
194
|
[iommu_device],
|
|
191
|
-
[thunderbolt_device],
|
|
192
195
|
[],
|
|
193
196
|
[],
|
|
194
197
|
]
|
|
195
|
-
|
|
196
|
-
result = self.validator.check_iommu()
|
|
198
|
+
result = self.validator.check_iommu()
|
|
197
199
|
self.assertFalse(result)
|
|
198
200
|
self.assertTrue(
|
|
199
201
|
any(isinstance(f, DMArNotEnabled) for f in self.validator.failures)
|
|
@@ -202,20 +204,22 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
202
204
|
"IOMMU is misconfigured: Pre-boot DMA protection not enabled", "❌"
|
|
203
205
|
)
|
|
204
206
|
|
|
205
|
-
|
|
207
|
+
@patch(
|
|
208
|
+
"amd_debug.prerequisites.open",
|
|
209
|
+
new_callable=unittest.mock.mock_open,
|
|
210
|
+
read_data=b"\x00" * 36 + b"\xff" * 4,
|
|
211
|
+
)
|
|
212
|
+
def test_check_iommu_missing_acpi_device(self, _mock_open):
|
|
206
213
|
"""Test check_iommu when MSFT0201 ACPI device is missing"""
|
|
207
214
|
self.validator.cpu_family = 0x1A
|
|
208
215
|
self.validator.cpu_model = 0x20
|
|
209
216
|
iommu_device = MagicMock(sys_path="/sys/devices/iommu")
|
|
210
|
-
thunderbolt_device = MagicMock(sys_path="/sys/devices/thunderbolt")
|
|
211
217
|
self.mock_pyudev.list_devices.side_effect = [
|
|
212
218
|
[iommu_device],
|
|
213
|
-
[thunderbolt_device],
|
|
214
219
|
[],
|
|
215
220
|
[],
|
|
216
221
|
]
|
|
217
|
-
|
|
218
|
-
result = self.validator.check_iommu()
|
|
222
|
+
result = self.validator.check_iommu()
|
|
219
223
|
self.assertFalse(result)
|
|
220
224
|
self.assertTrue(
|
|
221
225
|
any(isinstance(f, MissingIommuACPI) for f in self.validator.failures)
|
|
@@ -224,47 +228,48 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
224
228
|
"IOMMU is misconfigured: missing MSFT0201 ACPI device", "❌"
|
|
225
229
|
)
|
|
226
230
|
|
|
227
|
-
|
|
231
|
+
@patch(
|
|
232
|
+
"amd_debug.prerequisites.open",
|
|
233
|
+
new_callable=unittest.mock.mock_open,
|
|
234
|
+
read_data=b"\x00" * 36 + b"\xff" * 4,
|
|
235
|
+
)
|
|
236
|
+
def test_check_iommu_missing_policy(self, _mock_open):
|
|
228
237
|
"""Test check_iommu when policy is not bound to MSFT0201"""
|
|
229
238
|
self.validator.cpu_family = 0x1A
|
|
230
239
|
self.validator.cpu_model = 0x20
|
|
231
240
|
iommu_device = MagicMock(sys_path="/sys/devices/iommu")
|
|
232
|
-
thunderbolt_device = MagicMock(sys_path="/sys/devices/thunderbolt")
|
|
233
241
|
acpi_device = MagicMock(sys_path="/sys/devices/acpi/MSFT0201")
|
|
234
242
|
platform_device = MagicMock(sys_path="/sys/devices/platform/MSFT0201")
|
|
235
243
|
self.mock_pyudev.list_devices.side_effect = [
|
|
236
244
|
[iommu_device],
|
|
237
|
-
[thunderbolt_device],
|
|
238
245
|
[acpi_device],
|
|
239
246
|
[platform_device],
|
|
240
247
|
]
|
|
241
|
-
|
|
242
|
-
"os.path.exists", return_value=False
|
|
243
|
-
):
|
|
244
|
-
result = self.validator.check_iommu()
|
|
248
|
+
result = self.validator.check_iommu()
|
|
245
249
|
self.assertFalse(result)
|
|
246
250
|
self.assertTrue(
|
|
247
251
|
any(isinstance(f, MissingIommuPolicy) for f in self.validator.failures)
|
|
248
252
|
)
|
|
249
253
|
|
|
250
|
-
|
|
254
|
+
@patch(
|
|
255
|
+
"amd_debug.prerequisites.open",
|
|
256
|
+
new_callable=unittest.mock.mock_open,
|
|
257
|
+
read_data=b"\x00" * 36 + b"\xff" * 4,
|
|
258
|
+
)
|
|
259
|
+
@patch("amd_debug.prerequisites.os.path.exists", return_value=True)
|
|
260
|
+
def test_check_iommu_properly_configured(self, _mock_open, _mock_exists):
|
|
251
261
|
"""Test check_iommu when IOMMU is properly configured"""
|
|
252
262
|
self.validator.cpu_family = 0x1A
|
|
253
263
|
self.validator.cpu_model = 0x20
|
|
254
264
|
iommu_device = MagicMock(sys_path="/sys/devices/iommu")
|
|
255
|
-
thunderbolt_device = MagicMock(sys_path="/sys/devices/thunderbolt")
|
|
256
265
|
acpi_device = MagicMock(sys_path="/sys/devices/acpi/MSFT0201")
|
|
257
266
|
platform_device = MagicMock(sys_path="/sys/devices/platform/MSFT0201")
|
|
258
267
|
self.mock_pyudev.list_devices.side_effect = [
|
|
259
268
|
[iommu_device],
|
|
260
|
-
[thunderbolt_device],
|
|
261
269
|
[acpi_device],
|
|
262
270
|
[platform_device],
|
|
263
271
|
]
|
|
264
|
-
|
|
265
|
-
"os.path.exists", return_value=True
|
|
266
|
-
):
|
|
267
|
-
result = self.validator.check_iommu()
|
|
272
|
+
result = self.validator.check_iommu()
|
|
268
273
|
self.assertTrue(result)
|
|
269
274
|
self.mock_db.record_prereq.assert_called_with("IOMMU properly configured", "✅")
|
|
270
275
|
|
|
@@ -1737,7 +1742,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
1737
1742
|
result = self.validator.capture_edid()
|
|
1738
1743
|
self.assertTrue(result)
|
|
1739
1744
|
self.mock_db.record_prereq.assert_called_with(
|
|
1740
|
-
"
|
|
1745
|
+
"Failed to capture EDID table", "👀"
|
|
1741
1746
|
)
|
|
1742
1747
|
|
|
1743
1748
|
@patch("amd_debug.prerequisites.subprocess.check_output")
|
|
@@ -1750,9 +1755,9 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
1750
1755
|
returncode=1, cmd="edid-decode", output=b"Error decoding EDID"
|
|
1751
1756
|
)
|
|
1752
1757
|
result = self.validator.capture_edid()
|
|
1753
|
-
self.
|
|
1758
|
+
self.assertTrue(result)
|
|
1754
1759
|
self.mock_db.record_prereq.assert_called_with(
|
|
1755
|
-
"Failed to capture EDID table
|
|
1760
|
+
"Failed to capture EDID table", "👀"
|
|
1756
1761
|
)
|
|
1757
1762
|
|
|
1758
1763
|
@patch("amd_debug.prerequisites.subprocess.check_output")
|
|
@@ -1767,3 +1772,164 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
1767
1772
|
self.mock_db.record_debug.assert_called_with(
|
|
1768
1773
|
apply_prefix_wrapper("EDID for Monitor1:", "Decoded EDID data")
|
|
1769
1774
|
)
|
|
1775
|
+
|
|
1776
|
+
@patch("amd_debug.prerequisites.find_ip_version", return_value=True)
|
|
1777
|
+
@patch("amd_debug.prerequisites.os.path.exists")
|
|
1778
|
+
@patch("amd_debug.prerequisites.read_file")
|
|
1779
|
+
def test_check_dpia_pg_dmcub_usb4_found(
|
|
1780
|
+
self, mock_read_file, mock_path_exists, mock_find_ip_version
|
|
1781
|
+
):
|
|
1782
|
+
"""Test check_dpia_pg_dmcub when USB4 routers are found"""
|
|
1783
|
+
usb4_device = MagicMock()
|
|
1784
|
+
self.mock_pyudev.list_devices.side_effect = [
|
|
1785
|
+
[usb4_device], # First call: USB4 present
|
|
1786
|
+
]
|
|
1787
|
+
result = self.validator.check_dpia_pg_dmcub()
|
|
1788
|
+
self.assertTrue(result)
|
|
1789
|
+
self.mock_db.record_debug.assert_called_with(
|
|
1790
|
+
"USB4 routers found, no need to check DMCUB version"
|
|
1791
|
+
)
|
|
1792
|
+
|
|
1793
|
+
@patch("amd_debug.prerequisites.find_ip_version", return_value=True)
|
|
1794
|
+
@patch("amd_debug.prerequisites.os.path.exists", return_value=True)
|
|
1795
|
+
@patch("amd_debug.prerequisites.read_file", return_value="0x90001B01")
|
|
1796
|
+
def test_check_dpia_pg_dmcub_dmcub_fw_version_new_enough(
|
|
1797
|
+
self, mock_read_file, mock_path_exists, mock_find_ip_version
|
|
1798
|
+
):
|
|
1799
|
+
"""Test check_dpia_pg_dmcub when DMCUB firmware version is new enough"""
|
|
1800
|
+
self.mock_pyudev.list_devices.side_effect = [
|
|
1801
|
+
[], # First call: no USB4
|
|
1802
|
+
[
|
|
1803
|
+
MagicMock(
|
|
1804
|
+
properties={
|
|
1805
|
+
"PCI_CLASS": "30000",
|
|
1806
|
+
"PCI_ID": "1002abcd",
|
|
1807
|
+
"PCI_SLOT_NAME": "0000:01:00.0",
|
|
1808
|
+
},
|
|
1809
|
+
sys_path="/sys/devices/pci0000:01/0000:01:00.0",
|
|
1810
|
+
)
|
|
1811
|
+
],
|
|
1812
|
+
]
|
|
1813
|
+
with patch("builtins.open", new_callable=mock_open, read_data="3") as mock_file:
|
|
1814
|
+
handlers = (
|
|
1815
|
+
mock_file.return_value,
|
|
1816
|
+
mock_open(read_data="5").return_value,
|
|
1817
|
+
mock_open(read_data="0").return_value,
|
|
1818
|
+
)
|
|
1819
|
+
mock_open.side_effect = handlers
|
|
1820
|
+
result = self.validator.check_dpia_pg_dmcub()
|
|
1821
|
+
self.assertTrue(result)
|
|
1822
|
+
self.mock_db.record_prereq.assert_not_called()
|
|
1823
|
+
|
|
1824
|
+
@patch("amd_debug.prerequisites.find_ip_version", return_value=True)
|
|
1825
|
+
@patch("amd_debug.prerequisites.os.path.exists", return_value=True)
|
|
1826
|
+
@patch("amd_debug.prerequisites.read_file", return_value="0x8001B00")
|
|
1827
|
+
def test_check_dpia_pg_dmcub_dmcub_fw_version_too_old(
|
|
1828
|
+
self, mock_read_file, mock_path_exists, mock_find_ip_version
|
|
1829
|
+
):
|
|
1830
|
+
"""Test check_dpia_pg_dmcub when DMCUB firmware version is too old"""
|
|
1831
|
+
self.mock_pyudev.list_devices.side_effect = [
|
|
1832
|
+
[], # First call: no USB4
|
|
1833
|
+
[
|
|
1834
|
+
MagicMock(
|
|
1835
|
+
properties={
|
|
1836
|
+
"PCI_CLASS": "30000",
|
|
1837
|
+
"PCI_ID": "1002abcd",
|
|
1838
|
+
"PCI_SLOT_NAME": "0000:01:00.0",
|
|
1839
|
+
},
|
|
1840
|
+
sys_path="/sys/devices/pci0000:01/0000:01:00.0",
|
|
1841
|
+
)
|
|
1842
|
+
],
|
|
1843
|
+
]
|
|
1844
|
+
result = self.validator.check_dpia_pg_dmcub()
|
|
1845
|
+
self.assertFalse(result)
|
|
1846
|
+
self.mock_db.record_prereq.assert_called_with(
|
|
1847
|
+
"DMCUB Firmware is outdated", "❌"
|
|
1848
|
+
)
|
|
1849
|
+
self.assertTrue(
|
|
1850
|
+
any(isinstance(f, DmcubTooOld) for f in self.validator.failures)
|
|
1851
|
+
)
|
|
1852
|
+
|
|
1853
|
+
@patch("amd_debug.prerequisites.find_ip_version", return_value=True)
|
|
1854
|
+
@patch("amd_debug.prerequisites.os.path.exists", return_value=False)
|
|
1855
|
+
@patch(
|
|
1856
|
+
"amd_debug.prerequisites.read_file",
|
|
1857
|
+
side_effect=[
|
|
1858
|
+
"", # sysfs read returns empty, so fallback to debugfs
|
|
1859
|
+
"DMCUB fw: 09001B00\nOther line\n", # debugfs read
|
|
1860
|
+
],
|
|
1861
|
+
)
|
|
1862
|
+
def test_check_dpia_pg_dmcub_debugfs_version_new_enough(
|
|
1863
|
+
self, mock_read_file, mock_path_exists, mock_find_ip_version
|
|
1864
|
+
):
|
|
1865
|
+
"""Test check_dpia_pg_dmcub when DMCUB version is found in debugfs and is new enough"""
|
|
1866
|
+
self.mock_pyudev.list_devices.side_effect = [
|
|
1867
|
+
[], # First call: no USB4
|
|
1868
|
+
[
|
|
1869
|
+
MagicMock(
|
|
1870
|
+
properties={
|
|
1871
|
+
"PCI_CLASS": "30000",
|
|
1872
|
+
"PCI_ID": "1002abcd",
|
|
1873
|
+
"PCI_SLOT_NAME": "0",
|
|
1874
|
+
},
|
|
1875
|
+
sys_path="/sys/devices/pci0000:01/0000:01:00.0",
|
|
1876
|
+
)
|
|
1877
|
+
],
|
|
1878
|
+
]
|
|
1879
|
+
result = self.validator.check_dpia_pg_dmcub()
|
|
1880
|
+
self.assertTrue(result)
|
|
1881
|
+
self.mock_db.record_prereq.assert_not_called()
|
|
1882
|
+
|
|
1883
|
+
@patch("amd_debug.prerequisites.find_ip_version", return_value=True)
|
|
1884
|
+
@patch("amd_debug.prerequisites.os.path.exists", return_value=False)
|
|
1885
|
+
@patch(
|
|
1886
|
+
"amd_debug.prerequisites.read_file",
|
|
1887
|
+
side_effect=[
|
|
1888
|
+
"DMCUB fw: 0x08001B00\nOther line\n", # debugfs read
|
|
1889
|
+
],
|
|
1890
|
+
)
|
|
1891
|
+
def test_check_dpia_pg_dmcub_debugfs_version_too_old(
|
|
1892
|
+
self, mock_read_file, mock_path_exists, mock_find_ip_version
|
|
1893
|
+
):
|
|
1894
|
+
"""Test check_dpia_pg_dmcub when DMCUB version is found in debugfs and is too old"""
|
|
1895
|
+
self.mock_pyudev.list_devices.side_effect = [
|
|
1896
|
+
[], # First call: no USB4
|
|
1897
|
+
[
|
|
1898
|
+
MagicMock(
|
|
1899
|
+
properties={
|
|
1900
|
+
"PCI_CLASS": "30000",
|
|
1901
|
+
"PCI_ID": "1002abcd",
|
|
1902
|
+
"PCI_SLOT_NAME": "0",
|
|
1903
|
+
},
|
|
1904
|
+
sys_path="/sys/devices/pci0000:01/0000:01:00.0",
|
|
1905
|
+
)
|
|
1906
|
+
],
|
|
1907
|
+
]
|
|
1908
|
+
result = self.validator.check_dpia_pg_dmcub()
|
|
1909
|
+
self.assertFalse(result)
|
|
1910
|
+
self.mock_db.record_prereq.assert_called_with(
|
|
1911
|
+
"DMCUB Firmware is outdated", "❌"
|
|
1912
|
+
)
|
|
1913
|
+
self.assertTrue(
|
|
1914
|
+
any(isinstance(f, DmcubTooOld) for f in self.validator.failures)
|
|
1915
|
+
)
|
|
1916
|
+
|
|
1917
|
+
@patch("amd_debug.prerequisites.find_ip_version", return_value=False)
|
|
1918
|
+
def test_check_dpia_pg_dmcub_no_matching_dcn(self, mock_find_ip_version):
|
|
1919
|
+
"""Test check_dpia_pg_dmcub when no matching DCN is found"""
|
|
1920
|
+
self.mock_pyudev.list_devices.side_effect = [
|
|
1921
|
+
[], # First call: no USB4
|
|
1922
|
+
[
|
|
1923
|
+
MagicMock(
|
|
1924
|
+
properties={
|
|
1925
|
+
"PCI_CLASS": "30000",
|
|
1926
|
+
"PCI_ID": "1002abcd",
|
|
1927
|
+
"PCI_SLOT_NAME": "0",
|
|
1928
|
+
},
|
|
1929
|
+
sys_path="/sys/devices/pci0000:01/0000:01:00.0",
|
|
1930
|
+
)
|
|
1931
|
+
],
|
|
1932
|
+
]
|
|
1933
|
+
result = self.validator.check_dpia_pg_dmcub()
|
|
1934
|
+
self.assertTrue(result)
|
|
1935
|
+
self.mock_db.record_prereq.assert_not_called()
|
test_s2idle.py
CHANGED
|
@@ -381,6 +381,7 @@ class TestTestFunction(unittest.TestCase):
|
|
|
381
381
|
"2023-02-01",
|
|
382
382
|
"report.html",
|
|
383
383
|
"html",
|
|
384
|
+
True,
|
|
384
385
|
)
|
|
385
386
|
|
|
386
387
|
mock_sleep_validator_instance = mock_sleep_validator.return_value
|
|
@@ -605,6 +606,7 @@ class TestReportFunction(unittest.TestCase):
|
|
|
605
606
|
"2023-02-01",
|
|
606
607
|
"report.html",
|
|
607
608
|
"html",
|
|
609
|
+
True,
|
|
608
610
|
)
|
|
609
611
|
mock_sleep_report_instance = mock_sleep_report.return_value
|
|
610
612
|
|
|
@@ -614,10 +616,12 @@ class TestReportFunction(unittest.TestCase):
|
|
|
614
616
|
fname=None,
|
|
615
617
|
fmt=None,
|
|
616
618
|
tool_debug=True,
|
|
617
|
-
report_debug=
|
|
619
|
+
report_debug=None,
|
|
618
620
|
)
|
|
619
621
|
|
|
620
|
-
mock_prompt_report_arguments.assert_called_once_with(
|
|
622
|
+
mock_prompt_report_arguments.assert_called_once_with(
|
|
623
|
+
None, None, None, None, None
|
|
624
|
+
)
|
|
621
625
|
mock_sleep_report.assert_called_once_with(
|
|
622
626
|
since="2023-01-01",
|
|
623
627
|
until="2023-02-01",
|
|
@@ -640,10 +644,12 @@ class TestReportFunction(unittest.TestCase):
|
|
|
640
644
|
fname=None,
|
|
641
645
|
fmt=None,
|
|
642
646
|
tool_debug=True,
|
|
643
|
-
report_debug=
|
|
647
|
+
report_debug=None,
|
|
644
648
|
)
|
|
645
649
|
|
|
646
|
-
mock_prompt_report_arguments.assert_called_once_with(
|
|
650
|
+
mock_prompt_report_arguments.assert_called_once_with(
|
|
651
|
+
None, None, None, None, None
|
|
652
|
+
)
|
|
647
653
|
|
|
648
654
|
@patch("amd_debug.s2idle.prompt_report_arguments")
|
|
649
655
|
@patch(
|
|
@@ -659,6 +665,7 @@ class TestReportFunction(unittest.TestCase):
|
|
|
659
665
|
"2023-02-01",
|
|
660
666
|
"report.html",
|
|
661
667
|
"html",
|
|
668
|
+
True,
|
|
662
669
|
)
|
|
663
670
|
|
|
664
671
|
result = report(
|
|
@@ -667,10 +674,12 @@ class TestReportFunction(unittest.TestCase):
|
|
|
667
674
|
fname=None,
|
|
668
675
|
fmt=None,
|
|
669
676
|
tool_debug=True,
|
|
670
|
-
report_debug=
|
|
677
|
+
report_debug=None,
|
|
671
678
|
)
|
|
672
679
|
|
|
673
|
-
mock_prompt_report_arguments.assert_called_once_with(
|
|
680
|
+
mock_prompt_report_arguments.assert_called_once_with(
|
|
681
|
+
None, None, None, None, None
|
|
682
|
+
)
|
|
674
683
|
mock_sleep_report.assert_called_once_with(
|
|
675
684
|
since="2023-01-01",
|
|
676
685
|
until="2023-02-01",
|
|
@@ -695,6 +704,7 @@ class TestReportFunction(unittest.TestCase):
|
|
|
695
704
|
"2023-02-01",
|
|
696
705
|
"report.html",
|
|
697
706
|
"html",
|
|
707
|
+
True,
|
|
698
708
|
)
|
|
699
709
|
|
|
700
710
|
result = report(
|
|
@@ -703,10 +713,12 @@ class TestReportFunction(unittest.TestCase):
|
|
|
703
713
|
fname=None,
|
|
704
714
|
fmt=None,
|
|
705
715
|
tool_debug=True,
|
|
706
|
-
report_debug=
|
|
716
|
+
report_debug=None,
|
|
707
717
|
)
|
|
708
718
|
|
|
709
|
-
mock_prompt_report_arguments.assert_called_once_with(
|
|
719
|
+
mock_prompt_report_arguments.assert_called_once_with(
|
|
720
|
+
None, None, None, None, None
|
|
721
|
+
)
|
|
710
722
|
mock_sleep_report.assert_called_once_with(
|
|
711
723
|
since="2023-01-01",
|
|
712
724
|
until="2023-02-01",
|
|
@@ -729,6 +741,7 @@ class TestReportFunction(unittest.TestCase):
|
|
|
729
741
|
"2023-02-01",
|
|
730
742
|
"report.html",
|
|
731
743
|
"html",
|
|
744
|
+
True,
|
|
732
745
|
)
|
|
733
746
|
mock_sleep_report_instance = mock_sleep_report.return_value
|
|
734
747
|
mock_sleep_report_instance.run.side_effect = ValueError("Invalid value")
|
|
@@ -739,10 +752,12 @@ class TestReportFunction(unittest.TestCase):
|
|
|
739
752
|
fname=None,
|
|
740
753
|
fmt=None,
|
|
741
754
|
tool_debug=True,
|
|
742
|
-
report_debug=
|
|
755
|
+
report_debug=None,
|
|
743
756
|
)
|
|
744
757
|
|
|
745
|
-
mock_prompt_report_arguments.assert_called_once_with(
|
|
758
|
+
mock_prompt_report_arguments.assert_called_once_with(
|
|
759
|
+
None, None, None, None, None
|
|
760
|
+
)
|
|
746
761
|
mock_sleep_report.assert_called_once_with(
|
|
747
762
|
since="2023-01-01",
|
|
748
763
|
until="2023-02-01",
|
|
@@ -758,14 +773,14 @@ class TestReportFunction(unittest.TestCase):
|
|
|
758
773
|
class TestPromptReportArguments(unittest.TestCase):
|
|
759
774
|
"""Test prompt_report_arguments function"""
|
|
760
775
|
|
|
761
|
-
@patch("builtins.input", side_effect=["2023-01-01", "2023-02-01", "html"])
|
|
776
|
+
@patch("builtins.input", side_effect=["2023-01-01", "2023-02-01", "html", "true"])
|
|
762
777
|
@patch("amd_debug.s2idle.get_report_file", return_value="report.html")
|
|
763
778
|
@patch("amd_debug.s2idle.get_report_format", return_value="html")
|
|
764
779
|
def test_prompt_report_arguments_success(
|
|
765
780
|
self, mock_get_report_format, mock_get_report_file, _mock_input
|
|
766
781
|
):
|
|
767
782
|
"""Test prompt_report_arguments with valid inputs"""
|
|
768
|
-
result = prompt_report_arguments(None, None, None, None)
|
|
783
|
+
result = prompt_report_arguments(None, None, None, None, None)
|
|
769
784
|
self.assertEqual(result[0], datetime(2023, 1, 1))
|
|
770
785
|
self.assertEqual(result[1], datetime(2023, 2, 1))
|
|
771
786
|
self.assertEqual(result[2], "report.html")
|
|
@@ -775,33 +790,36 @@ class TestPromptReportArguments(unittest.TestCase):
|
|
|
775
790
|
|
|
776
791
|
@patch(
|
|
777
792
|
"builtins.input",
|
|
778
|
-
side_effect=["invalid-date", "2023-01-01", "2023-02-01", "html"],
|
|
793
|
+
side_effect=["invalid-date", "2023-01-01", "2023-02-01", "html", "true"],
|
|
779
794
|
)
|
|
780
795
|
@patch("sys.exit")
|
|
781
796
|
def test_prompt_report_arguments_invalid_since_date(self, mock_exit, mock_input):
|
|
782
797
|
"""Test prompt_report_arguments with invalid 'since' date"""
|
|
783
798
|
mock_exit.side_effect = SystemExit
|
|
784
799
|
with self.assertRaises(SystemExit):
|
|
785
|
-
prompt_report_arguments(None, None, None, None)
|
|
800
|
+
prompt_report_arguments(None, None, None, None, None)
|
|
786
801
|
mock_exit.assert_called_once_with(
|
|
787
802
|
"Invalid date, use YYYY-MM-DD: Invalid isoformat string: 'invalid-date'"
|
|
788
803
|
)
|
|
789
804
|
|
|
790
805
|
@patch(
|
|
791
806
|
"builtins.input",
|
|
792
|
-
side_effect=["2023-01-01", "invalid-date", "2023-02-01", "html"],
|
|
807
|
+
side_effect=["2023-01-01", "invalid-date", "2023-02-01", "html", "true"],
|
|
793
808
|
)
|
|
794
809
|
@patch("sys.exit")
|
|
795
810
|
def test_prompt_report_arguments_invalid_until_date(self, mock_exit, mock_input):
|
|
796
811
|
"""Test prompt_report_arguments with invalid 'until' date"""
|
|
797
812
|
mock_exit.side_effect = SystemExit
|
|
798
813
|
with self.assertRaises(SystemExit):
|
|
799
|
-
prompt_report_arguments(None, None, None, None)
|
|
814
|
+
prompt_report_arguments(None, None, None, None, None)
|
|
800
815
|
mock_exit.assert_called_once_with(
|
|
801
816
|
"Invalid date, use YYYY-MM-DD: Invalid isoformat string: 'invalid-date'"
|
|
802
817
|
)
|
|
803
818
|
|
|
804
|
-
@patch(
|
|
819
|
+
@patch(
|
|
820
|
+
"builtins.input",
|
|
821
|
+
side_effect=["2023-01-01", "2023-02-01", "invalid-format", "true"],
|
|
822
|
+
)
|
|
805
823
|
@patch("amd_debug.s2idle.get_report_format", return_value="html")
|
|
806
824
|
@patch("sys.exit")
|
|
807
825
|
def test_prompt_report_arguments_invalid_format(
|
|
@@ -810,11 +828,27 @@ class TestPromptReportArguments(unittest.TestCase):
|
|
|
810
828
|
"""Test prompt_report_arguments with invalid format"""
|
|
811
829
|
mock_exit.side_effect = SystemExit
|
|
812
830
|
with self.assertRaises(SystemExit):
|
|
813
|
-
prompt_report_arguments(None, None, None, None)
|
|
831
|
+
prompt_report_arguments(None, None, None, None, None)
|
|
814
832
|
mock_exit.assert_called_once_with("Invalid format: invalid-format")
|
|
815
833
|
mock_get_report_format.assert_called_once()
|
|
816
834
|
|
|
817
|
-
@patch(
|
|
835
|
+
@patch(
|
|
836
|
+
"builtins.input",
|
|
837
|
+
side_effect=["2023-01-01", "2023-02-01", "html", "foo_the_bar"],
|
|
838
|
+
)
|
|
839
|
+
@patch("amd_debug.s2idle.get_report_format", return_value="html")
|
|
840
|
+
@patch("sys.exit")
|
|
841
|
+
def test_prompt_report_arguments_invalid_report(
|
|
842
|
+
self, mock_exit, mock_get_report_format, mock_input
|
|
843
|
+
):
|
|
844
|
+
"""Test prompt_report_arguments with invalid format"""
|
|
845
|
+
mock_exit.side_effect = SystemExit
|
|
846
|
+
with self.assertRaises(SystemExit):
|
|
847
|
+
prompt_report_arguments(None, None, None, None, None)
|
|
848
|
+
mock_exit.assert_called_once_with("Invalid entry: Foo_the_bar")
|
|
849
|
+
mock_get_report_format.assert_called_once()
|
|
850
|
+
|
|
851
|
+
@patch("builtins.input", side_effect=["", "", "", ""])
|
|
818
852
|
@patch(
|
|
819
853
|
"amd_debug.s2idle.get_report_file",
|
|
820
854
|
return_value="amd-s2idle-report-2023-01-01.html",
|
|
@@ -824,10 +858,11 @@ class TestPromptReportArguments(unittest.TestCase):
|
|
|
824
858
|
self, mock_get_report_format, mock_get_report_file, mock_input
|
|
825
859
|
):
|
|
826
860
|
"""Test prompt_report_arguments with default values"""
|
|
827
|
-
result = prompt_report_arguments(None, None, None, None)
|
|
861
|
+
result = prompt_report_arguments(None, None, None, None, None)
|
|
828
862
|
self.assertEqual(datetime.date(result[0]), Defaults.since)
|
|
829
863
|
self.assertEqual(datetime.date(result[1]), Defaults.until)
|
|
830
864
|
self.assertEqual(result[2], "amd-s2idle-report-2023-01-01.html")
|
|
831
865
|
self.assertEqual(result[3], "html")
|
|
866
|
+
self.assertEqual(result[4], True)
|
|
832
867
|
mock_get_report_file.assert_called_once_with(None, "html")
|
|
833
868
|
mock_get_report_format.assert_called()
|
test_validator.py
CHANGED
|
@@ -321,13 +321,11 @@ class TestValidator(unittest.TestCase):
|
|
|
321
321
|
self.validator.lockdown = False
|
|
322
322
|
with patch("os.path.exists", return_value=False), patch(
|
|
323
323
|
"amd_debug.validator.read_file", side_effect=FileNotFoundError
|
|
324
|
-
), patch.object(
|
|
325
|
-
self.validator.db, "record_cycle_data"
|
|
326
|
-
) as mock_record_cycle_data:
|
|
324
|
+
), patch.object(self.validator.db, "record_debug") as mock_record:
|
|
327
325
|
result = self.validator.capture_hw_sleep()
|
|
328
326
|
self.assertFalse(result)
|
|
329
|
-
|
|
330
|
-
"HW sleep statistics file missing"
|
|
327
|
+
mock_record.assert_called_once_with(
|
|
328
|
+
"HW sleep statistics file /sys/kernel/debug/amd_pmc/smu_fw_info is missing"
|
|
331
329
|
)
|
|
332
330
|
|
|
333
331
|
def test_capture_amdgpu_ips_status(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|