amd-debug-tools 0.2.5__tar.gz → 0.2.6__tar.gz

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.
Files changed (52) hide show
  1. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/PKG-INFO +6 -3
  2. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/README.md +5 -2
  3. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/pyproject.toml +1 -0
  4. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/__init__.py +9 -1
  5. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/bios.py +5 -3
  6. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/common.py +39 -0
  7. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/installer.py +2 -2
  8. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/prerequisites.py +24 -2
  9. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/pstate.py +5 -3
  10. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/s2idle.py +5 -3
  11. amd_debug_tools-0.2.6/src/amd_debug/test_ttm.py +291 -0
  12. amd_debug_tools-0.2.6/src/amd_debug/ttm.py +161 -0
  13. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/validator.py +20 -0
  14. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug_tools.egg-info/PKG-INFO +6 -3
  15. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug_tools.egg-info/SOURCES.txt +2 -0
  16. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug_tools.egg-info/entry_points.txt +1 -0
  17. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_bios.py +26 -8
  18. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_common.py +91 -0
  19. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_kernel.py +2 -1
  20. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_launcher.py +7 -0
  21. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_prerequisites.py +77 -1
  22. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_s2idle.py +6 -4
  23. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_validator.py +63 -1
  24. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/LICENSE +0 -0
  25. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/setup.cfg +0 -0
  26. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/acpi.py +0 -0
  27. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/bash/amd-s2idle +0 -0
  28. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/battery.py +0 -0
  29. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/database.py +0 -0
  30. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/display.py +0 -0
  31. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/failures.py +0 -0
  32. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/kernel.py +0 -0
  33. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/s2idle-hook +0 -0
  34. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/sleep_report.py +0 -0
  35. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/templates/html +0 -0
  36. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/templates/md +0 -0
  37. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/templates/stdout +0 -0
  38. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/templates/txt +0 -0
  39. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug/wake.py +0 -0
  40. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug_tools.egg-info/dependency_links.txt +0 -0
  41. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug_tools.egg-info/requires.txt +0 -0
  42. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/amd_debug_tools.egg-info/top_level.txt +0 -0
  43. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/launcher.py +0 -0
  44. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_acpi.py +0 -0
  45. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_batteries.py +0 -0
  46. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_database.py +0 -0
  47. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_display.py +0 -0
  48. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_failures.py +0 -0
  49. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_installer.py +0 -0
  50. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_pstate.py +0 -0
  51. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_sleep_report.py +0 -0
  52. {amd_debug_tools-0.2.5 → amd_debug_tools-0.2.6}/src/test_wake.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amd-debug-tools
3
- Version: 0.2.5
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:
@@ -107,7 +107,7 @@ The following optional arguments are supported for this command:
107
107
  If the tool is launched with an environment that can call `xdg-open`, the report
108
108
  will be opened in a browser.
109
109
 
110
- ### `amd-s2idle version`
110
+ ### `amd-s2idle --version`
111
111
  This will print the version of the tool and exit.
112
112
 
113
113
  ### Debug output
@@ -140,7 +140,7 @@ The following optional arguments are supported for this command:
140
140
  --input INPUT Optional input file to parse
141
141
  --tool-debug Enable tool debug logging
142
142
 
143
- ### `amd-bios version`
143
+ ### `amd-bios --version`
144
144
  This will print the version of the tool and exit.
145
145
 
146
146
  ## amd-pstate
@@ -148,6 +148,9 @@ This will print the version of the tool and exit.
148
148
  It will capture some state from the system as well as from the machine specific registers that
149
149
  amd-pstate uses.
150
150
 
151
+ ## amd-ttm
152
+ `amd-ttm` is a tool used for managing the TTM memory settings on AMD systems.
153
+
151
154
  ## Compatibility scripts
152
155
 
153
156
  Compatibility scripts are provided for the previous names the tools went by:
@@ -50,3 +50,4 @@ license = "MIT"
50
50
  amd-s2idle = "amd_debug:amd_s2idle"
51
51
  amd-bios = "amd_debug:amd_bios"
52
52
  amd-pstate = "amd_debug:amd_pstate"
53
+ amd-ttm = "amd_debug:amd_ttm"
@@ -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 supserset dependencies."""
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:
@@ -105,7 +105,9 @@ def parse_args():
105
105
  action="store_true",
106
106
  help="Enable tool debug logging",
107
107
  )
108
- subparsers.add_parser("version", help="Show version information")
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.command == "version":
137
+ elif args.version:
136
138
  print(version())
137
139
  show_log_info()
138
140
  if ret is False:
@@ -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
@@ -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 supserset dependencies"""
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(
@@ -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
 
@@ -293,18 +293,20 @@ def parse_args():
293
293
  action="store_true",
294
294
  help="Enable tool debug logging",
295
295
  )
296
- subparsers.add_parser("version", help="Show version information")
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.command == "version":
309
+ if args.version:
308
310
  print(version())
309
311
  return
310
312
  elif args.command == "triage":
@@ -385,7 +385,9 @@ def parse_args():
385
385
  help="Enable tool debug logging",
386
386
  )
387
387
 
388
- subparsers.add_parser("version", help="Show version information")
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.action == "version":
432
+ elif args.version:
431
433
  print(version())
432
434
  return
433
435
  else:
@@ -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)