amd-debug-tools 0.2.5__py3-none-any.whl → 0.2.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
amd_debug/validator.py CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/python3
2
1
  # SPDX-License-Identifier: MIT
3
2
 
4
3
  import glob
@@ -318,6 +317,9 @@ class SleepValidator(AmdTool):
318
317
  actions = read_file(os.path.join(p, "actions"))
319
318
  message = f"{Headers.WokeFromIrq} {n} ({chip_name} {hw}-{name} {actions})"
320
319
  self.db.record_debug(message)
320
+ irq = int(n)
321
+ if irq and irq not in self.wakeup_irqs:
322
+ self.wakeup_irqs += [irq]
321
323
  except OSError:
322
324
  pass
323
325
  return True
@@ -435,7 +437,6 @@ class SleepValidator(AmdTool):
435
437
  continue
436
438
  self.db.record_debug(
437
439
  f"Woke up from input source {device} ({self.wakeup_count[device]}->{count})",
438
- "💤",
439
440
  )
440
441
  self.wakeup_count = wakeup_count
441
442
 
@@ -470,7 +471,7 @@ class SleepValidator(AmdTool):
470
471
  self.db.record_debug(f"HW sleep statistics file {p} is missing")
471
472
  if not self.hw_sleep_duration:
472
473
  self.db.record_cycle_data("Did not reach hardware sleep state", "❌")
473
- return self.hw_sleep_duration
474
+ return self.hw_sleep_duration > 0
474
475
 
475
476
  def capture_command_line(self):
476
477
  """Capture the kernel command line to debug"""
@@ -544,16 +545,6 @@ class SleepValidator(AmdTool):
544
545
 
545
546
  def analyze_kernel_log(self):
546
547
  """Analyze one of the lines from the kernel log"""
547
- self.cycle_count = 0
548
- self.upep = False
549
- self.upep_microsoft = False
550
- self.wakeup_irqs = []
551
- self.idle_masks = []
552
- self.acpi_errors = []
553
- self.active_gpios = []
554
- self.notify_devices = []
555
- self.page_faults = []
556
- self.irq1_workaround = False
557
548
  self.kernel_log.process_callback(self._analyze_kernel_log_line)
558
549
 
559
550
  if self.cycle_count:
@@ -583,7 +574,6 @@ class SleepValidator(AmdTool):
583
574
  if bit_changed & BIT(bit):
584
575
  self.db.record_debug(
585
576
  f"Idle mask bit {bit} (0x{BIT(bit):x}) changed during suspend",
586
- "○",
587
577
  )
588
578
  if self.upep:
589
579
  if self.upep_microsoft:
@@ -644,9 +634,20 @@ class SleepValidator(AmdTool):
644
634
 
645
635
  def post(self):
646
636
  """Post-process the suspend test results"""
637
+ self.cycle_count = 0
638
+ self.upep = False
639
+ self.upep_microsoft = False
640
+ self.wakeup_irqs = []
641
+ self.idle_masks = []
642
+ self.acpi_errors = []
643
+ self.active_gpios = []
644
+ self.notify_devices = []
645
+ self.page_faults = []
646
+ self.irq1_workaround = False
647
+
647
648
  checks = [
648
- self.analyze_kernel_log,
649
649
  self.capture_wakeup_irq_data,
650
+ self.analyze_kernel_log,
650
651
  self.check_gpes,
651
652
  self.capture_lid,
652
653
  self.check_rtc_cmos,
@@ -661,10 +662,10 @@ class SleepValidator(AmdTool):
661
662
  check()
662
663
  self.db.record_cycle(
663
664
  self.requested_duration,
664
- self.active_gpios,
665
- self.wakeup_irqs,
666
- self.kernel_duration,
667
- self.hw_sleep_duration,
665
+ ",".join(str(gpio) for gpio in self.active_gpios),
666
+ ",".join(str(irq) for irq in self.wakeup_irqs),
667
+ int(self.kernel_duration),
668
+ int(self.hw_sleep_duration),
668
669
  )
669
670
 
670
671
  def prep(self):
@@ -703,6 +704,22 @@ class SleepValidator(AmdTool):
703
704
  else:
704
705
  print_color("No RTC device found, please manually wake system", "🚦")
705
706
 
707
+ def toggle_nvidia(self, value):
708
+ """Write to the NVIDIA suspend interface"""
709
+ p = os.path.join("/", "proc", "driver", "nvidia", "suspend")
710
+ if not os.path.exists(p):
711
+ return True
712
+ fd = os.open(p, os.O_WRONLY | os.O_SYNC)
713
+ try:
714
+ os.write(fd, value)
715
+ except OSError as e:
716
+ self.db.record_cycle_data(f"Failed to set {value} in NVIDIA {e}", "❌")
717
+ return False
718
+ finally:
719
+ os.close(fd)
720
+ self.db.record_debug(f"Wrote {value} to NVIDIA driver")
721
+ return True
722
+
706
723
  @pm_debugging
707
724
  def suspend_system(self):
708
725
  """Suspend the system using the dbus or sysfs interface"""
@@ -744,6 +761,8 @@ class SleepValidator(AmdTool):
744
761
  self.db.record_cycle_data("Missing dbus", "❌")
745
762
  return False
746
763
  else:
764
+ if not self.toggle_nvidia(b"suspend"):
765
+ return False
747
766
  old = get_wakeup_count()
748
767
  p = os.path.join("/", "sys", "power", "state")
749
768
  fd = os.open(p, os.O_WRONLY | os.O_SYNC)
@@ -757,6 +776,8 @@ class SleepValidator(AmdTool):
757
776
  return False
758
777
  finally:
759
778
  os.close(fd)
779
+ if not self.toggle_nvidia(b"resume"):
780
+ return False
760
781
  return True
761
782
 
762
783
  def unlock_session(self):
amd_debug/wake.py CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/python3
2
1
  # SPDX-License-Identifier: MIT
3
2
 
4
3
  import os
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: amd-debug-tools
3
+ Version: 0.2.12
4
+ Summary: debug tools for AMD systems
5
+ Author-email: Mario Limonciello <superm1@kernel.org>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://web.git.kernel.org/pub/scm/linux/kernel/git/superm1/amd-debug-tools.git/
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: dbus-fast
14
+ Requires-Dist: pyudev
15
+ Requires-Dist: packaging
16
+ Requires-Dist: pandas
17
+ Requires-Dist: jinja2
18
+ Requires-Dist: tabulate
19
+ Requires-Dist: seaborn
20
+ Requires-Dist: cysystemd
21
+ Requires-Dist: Jinja2
22
+ Requires-Dist: matplotlib
23
+ Requires-Dist: seaborn
24
+ Dynamic: license-file
25
+
26
+ # Helpful tools for debugging AMD Zen systems
27
+ [![codecov](https://codecov.io/github/superm1/amd-debug-tools/graph/badge.svg?token=Z9WTBZADGT)](https://codecov.io/github/superm1/amd-debug-tools)
28
+ [![PyPI](https://img.shields.io/pypi/v/amd-debug-tools.svg)](https://pypi.org/project/amd-debug-tools/)
29
+
30
+ This repository hosts open tools that are useful for debugging issues on AMD systems.
31
+
32
+ ## Installation
33
+ ### Distro (Arch)
34
+ `amd-debug-tools` has been [packaged for Arch Linux](https://archlinux.org/packages/extra/any/amd-debug-tools/) (and derivatives). You can install it using:
35
+
36
+ pacman -Sy amd-debug-tools
37
+
38
+ ### Using a python wheel (Generic)
39
+ It is suggested to install tools in a virtual environment either using
40
+ `pipx` or `python3 -m venv`.
41
+
42
+ #### From PyPI
43
+ `amd-debug-tools` is distributed as a python wheel, which is a
44
+ binary package format for Python. To install from PyPI, run the following
45
+ command:
46
+
47
+ pipx install amd-debug-tools
48
+
49
+ ### From source
50
+ To build the package from source, you will need to the `python3-build`
51
+ package natively installed by your distribution package manager. Then you
52
+ can generate and install a wheel by running the following commands:
53
+
54
+ python3 -m build
55
+ pipx install dist/amd-debug-tools-*.whl
56
+
57
+ ### Ensuring path
58
+ If you have not used a `pipx` environment before, you may need to run the following command
59
+ to set up the environment:
60
+
61
+ pipx ensurepath
62
+
63
+ This will add the `pipx` environment to your path.
64
+
65
+ ## Running in-tree
66
+ Documentation about running directly from a git checkout is available [here](https://github.com/superm1/amd-debug-tools/blob/master/docs/in-tree.md).
67
+
68
+ ## Tools
69
+
70
+ Each tool has its own individual documentation page:
71
+ * [amd-s2idle](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-s2idle.md)
72
+ * [amd-bios](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-bios.md)
73
+ * [amd-pstate](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-pstate.md)
74
+ * [amd-ttm](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-ttm.md)
75
+
@@ -0,0 +1,47 @@
1
+ launcher.py,sha256=M8kT9DtyZoQgZaKWDbSBu4jsS6tZF1gWko3sovNVyag,947
2
+ test_acpi.py,sha256=1w5WjuTimgd6ru8FUi-dhOoX-7FsiVsAC42E6RZBcls,3125
3
+ test_batteries.py,sha256=nN5pfP5El7Whypq3HHEpW8bufdf5EWSTVGbayfNQYP4,3360
4
+ test_bios.py,sha256=x_KLmQqGEbQhTugyWCHGXjGp2H1dCdhRz0kgw2Big8w,9276
5
+ test_common.py,sha256=qOCouXyO-dhY_x_L8kyNyuP_c0bhHhlqPoc084_F6Xg,20757
6
+ test_database.py,sha256=HAC4M4dyZBxskNFMfZn2kro6uT2-j0exjnto8-0OOnk,10042
7
+ test_display.py,sha256=awC1-OEPG1aV34BH-MzrOWtAHHPfhCmnKRetas4AGNE,5813
8
+ test_failures.py,sha256=H1UxXeVjhJs9-j9yas4vwAha676GX1Es7Kz8RN2B590,6845
9
+ test_installer.py,sha256=QP0lXVPCOG-UOGwEWFPIV9-fnfkglsF8NEjXP7P6HEs,12691
10
+ test_kernel.py,sha256=QsggZOLEt78ID_l6JsHSffH4xzQaBSzSS0uCLaOxpAc,7949
11
+ test_launcher.py,sha256=8g8CBTvLX64Us4RmHtRPSdpV5E2kQFaudBl7VIsxLhE,1733
12
+ test_prerequisites.py,sha256=9OzUI3vZZmAu0yKBLjHRkjeJZufsi7AR8plDtN2ZBco,110642
13
+ test_pstate.py,sha256=a9oAJ9-LANX32XNQhplz6Y75VNYc__QqoSBKIrwvANg,6058
14
+ test_s2idle.py,sha256=BrNj4Bxov4LxpheKAeajCH3tJA_iW-Muy0vW2PRkoEk,34018
15
+ test_sleep_report.py,sha256=ANuxYi_C1oSKAi4xUU2wBu4SwJtcZA7VPpazBe3_WUQ,6922
16
+ test_ttm.py,sha256=QrCdRodQ_CD3nyDqKodb6yR158mgE0iIM5f1fV1Axu8,10515
17
+ test_validator.py,sha256=8uhhP_Piwe4sp0HE-ZthDJWG-QdNxWmE2ICWtcGsA1w,36498
18
+ test_wake.py,sha256=6zi5GVFHQKU1sTWw3O5-aGriB9uu5713QLn4l2wjhpM,7152
19
+ amd_debug/__init__.py,sha256=0VsbaXBas5gcwZ6vBbJg20-8qHNDo_BKFYzdb1Agfq4,1252
20
+ amd_debug/acpi.py,sha256=_lnnAwTnAb4g8AW2BXwF45UY-WUmXIDpthhFLE5ENGo,3142
21
+ amd_debug/battery.py,sha256=qd6bo1ssAPnEfPY2HCmJfFcwgYADrfoJkxFQWQyxKlU,3103
22
+ amd_debug/bios.py,sha256=3dfE5yVoW0aZUN5osGKOtYF-RLOzUcH3ryA4yHZZoJA,4022
23
+ amd_debug/common.py,sha256=OR0rCFkWWoGlRIdsPdJ0hBr9iQ_vXcjIAL3poZ9BTj8,12606
24
+ amd_debug/database.py,sha256=kkl1iKIvekpVMc8M0xzo0iX3rc4yDaREyjCuLSnGuCk,11052
25
+ amd_debug/display.py,sha256=XATsKPh2QDoJAbm5mAE1YNFdVuFEq3yS3dffEasEq8c,922
26
+ amd_debug/failures.py,sha256=sewb8L4D-p8HmbTIZVSlwbHdHJ3nO2zAG9otcwHpsqs,24382
27
+ amd_debug/installer.py,sha256=BEsjl_sh8cUsbPsnGfoq3BntkDwJdMgsKmR54dzxkKA,14288
28
+ amd_debug/kernel.py,sha256=w2y4syIMPd4OYXhPYrEmCDYnMBg9K16tK6iNwfzRVWU,11718
29
+ amd_debug/prerequisites.py,sha256=tI_D_iyqNBGb3OmdvNfY5X36zen9Q6klYSm-Oi-ZHIY,54842
30
+ amd_debug/pstate.py,sha256=fGA-pKS1mzIrOa9fIqd_q3Y9DvMhuWmInq5GPAGc21k,9564
31
+ amd_debug/s2idle-hook,sha256=LLiaqPtGd0qetu9n6EYxKHZaIdHpVQDONdOuSc0pfFg,1695
32
+ amd_debug/s2idle.py,sha256=73BngSl14f8MWiD8Rp4-QKSzu5-a3XxotDArJ_Y7c4E,13279
33
+ amd_debug/sleep_report.py,sha256=JQDtd_y-78gXQN5o7O7h-hKosEOWZeTmnZBSepdeCEE,17298
34
+ amd_debug/ttm.py,sha256=KoCY35jnU7Q2ZB0SO1nQmC_RsiCFwTWUzVmRbyUg0Lw,4660
35
+ amd_debug/validator.py,sha256=VygKoP30psFkLIrZUUCdvauD_xB43867h2BnI-o8ODE,34271
36
+ amd_debug/wake.py,sha256=x33z60sOaukTlZTABKpvLJZVRk-kIe_o8XljtV3Q38Q,3917
37
+ amd_debug/bash/amd-s2idle,sha256=g_cle1ElCJpwE4wcLezL6y-BdasDKTnNMhrtzKLE9ks,1142
38
+ amd_debug/templates/html,sha256=JfGhpmHIB2C2GItdGI1kuC8uayqEVgrpQvAWAj35eZ4,14580
39
+ amd_debug/templates/md,sha256=r8X2aehnH2gzj0WHYTZ5K9wAqC5y39i_3nkDORSC0uM,787
40
+ amd_debug/templates/stdout,sha256=hyoOJ96K2dJfnWRWhyCuariLKbEHXvs9mstV_g5aMdI,469
41
+ amd_debug/templates/txt,sha256=nNdsvbPFOhGdL7VA-_4k5aN3nB-6ouGQt6AsWst7T3w,649
42
+ amd_debug_tools-0.2.12.dist-info/licenses/LICENSE,sha256=RBlZI6r3MRGzymI2VDX2iW__D2APDbMhu_Xg5t6BWeo,1066
43
+ amd_debug_tools-0.2.12.dist-info/METADATA,sha256=-HT90JLmuyiEcusEKIvBj1rTQouT28VMGAJOYMHsujg,2773
44
+ amd_debug_tools-0.2.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
+ amd_debug_tools-0.2.12.dist-info/entry_points.txt,sha256=hIskDz6k0_6q1qpqWCpVFsca_djxAqkLrUAwzAyEGuE,144
46
+ amd_debug_tools-0.2.12.dist-info/top_level.txt,sha256=VvGkkY5I7O3HoLNrc2VfgjCA-to3PUjnnKd7juONaFw,243
47
+ amd_debug_tools-0.2.12.dist-info/RECORD,,
@@ -2,3 +2,4 @@
2
2
  amd-bios = amd_debug:amd_bios
3
3
  amd-pstate = amd_debug:amd_pstate
4
4
  amd-s2idle = amd_debug:amd_s2idle
5
+ amd-ttm = amd_debug:amd_ttm
@@ -14,5 +14,6 @@ test_prerequisites
14
14
  test_pstate
15
15
  test_s2idle
16
16
  test_sleep_report
17
+ test_ttm
17
18
  test_validator
18
19
  test_wake
test_acpi.py CHANGED
@@ -79,7 +79,7 @@ class TestAcpi(unittest.TestCase):
79
79
  def test_acpica_trace_no_acpi_debug(self):
80
80
  """Test AcpicaTracer class when ACPI tracing is not supported"""
81
81
  with patch("os.path.exists", return_value=False), patch(
82
- "builtins.open", mock_open(read_data="foo")
82
+ "amd_debug.common.open", mock_open(read_data="foo")
83
83
  ):
84
84
  tracer = AcpicaTracer()
85
85
  self.assertFalse(tracer.supported)
test_bios.py CHANGED
@@ -33,8 +33,14 @@ class TestAmdBios(unittest.TestCase):
33
33
  @patch("amd_debug.bios.minimum_kernel")
34
34
  @patch("amd_debug.bios.AcpicaTracer")
35
35
  @patch("amd_debug.bios.print_color")
36
+ @patch("subprocess.run")
36
37
  def test_set_tracing_enable(
37
- self, _mock_print, mock_acpica_tracer, mock_minimum_kernel, mock_relaunch_sudo
38
+ self,
39
+ _mock_run,
40
+ _mock_print,
41
+ mock_acpica_tracer,
42
+ mock_minimum_kernel,
43
+ mock_relaunch_sudo,
38
44
  ):
39
45
  """Test enabling tracing"""
40
46
  mock_minimum_kernel.return_value = True
@@ -53,8 +59,14 @@ class TestAmdBios(unittest.TestCase):
53
59
  @patch("amd_debug.bios.minimum_kernel")
54
60
  @patch("amd_debug.bios.AcpicaTracer")
55
61
  @patch("amd_debug.bios.print_color")
62
+ @patch("subprocess.run")
56
63
  def test_set_tracing_disable(
57
- self, _mock_print, mock_acpica_tracer, mock_minimum_kernel, mock_relaunch_sudo
64
+ self,
65
+ _mock_run,
66
+ _mock_print,
67
+ mock_acpica_tracer,
68
+ mock_minimum_kernel,
69
+ mock_relaunch_sudo,
58
70
  ):
59
71
  """Test disabling tracing"""
60
72
  mock_minimum_kernel.return_value = True
@@ -71,7 +83,10 @@ class TestAmdBios(unittest.TestCase):
71
83
 
72
84
  @patch("amd_debug.bios.sscanf_bios_args")
73
85
  @patch("amd_debug.bios.print_color")
74
- def test_analyze_kernel_log_line(self, mock_print_color, mock_sscanf_bios_args):
86
+ @patch("subprocess.run")
87
+ def test_analyze_kernel_log_line(
88
+ self, _mock_run, mock_print_color, mock_sscanf_bios_args
89
+ ):
75
90
  """Test analyzing kernel log line"""
76
91
  mock_sscanf_bios_args.return_value = "BIOS argument found"
77
92
 
@@ -85,8 +100,9 @@ class TestAmdBios(unittest.TestCase):
85
100
 
86
101
  @patch("amd_debug.bios.sscanf_bios_args")
87
102
  @patch("amd_debug.bios.print_color")
103
+ @patch("subprocess.run")
88
104
  def test_analyze_kernel_log_line_no_bios_args(
89
- self, mock_print_color, mock_sscanf_bios_args
105
+ self, _mock_run, mock_print_color, mock_sscanf_bios_args
90
106
  ):
91
107
  """Test analyzing kernel log line with no BIOS arguments"""
92
108
  mock_sscanf_bios_args.return_value = None
@@ -140,12 +156,12 @@ class TestAmdBios(unittest.TestCase):
140
156
  self.assertFalse(args.enable)
141
157
  self.assertTrue(args.disable)
142
158
 
143
- @patch("sys.argv", ["bios.py", "version"])
159
+ @patch("sys.argv", ["bios.py", "--version"])
144
160
  def test_parse_args_version_command(self):
145
161
  """Test parse_args with version command"""
146
162
 
147
163
  args = parse_args()
148
- self.assertEqual(args.command, "version")
164
+ self.assertTrue(args.version)
149
165
 
150
166
  @patch("sys.argv", ["bios.py"])
151
167
  @patch("argparse.ArgumentParser.print_help")
@@ -227,7 +243,7 @@ class TestAmdBios(unittest.TestCase):
227
243
  self, _mock_print, mock_show_log_info, mock_version, mock_parse_args
228
244
  ):
229
245
  """Test main function with version command"""
230
- mock_parse_args.return_value = argparse.Namespace(command="version")
246
+ mock_parse_args.return_value = argparse.Namespace(version=True, command=None)
231
247
  mock_version.return_value = "1.0.0"
232
248
 
233
249
  result = main()
@@ -241,7 +257,9 @@ class TestAmdBios(unittest.TestCase):
241
257
  @patch("amd_debug.bios.show_log_info")
242
258
  def test_main_invalid_command(self, mock_show_log_info, mock_parse_args):
243
259
  """Test main function with an invalid command"""
244
- mock_parse_args.return_value = argparse.Namespace(command="invalid")
260
+ mock_parse_args.return_value = argparse.Namespace(
261
+ version=False, command="invalid"
262
+ )
245
263
 
246
264
  result = main()
247
265
 
test_common.py CHANGED
@@ -4,17 +4,21 @@
4
4
  """
5
5
  This module contains unit tests for the common functions in the amd-debug-tools package.
6
6
  """
7
- from unittest.mock import patch, mock_open, call
7
+ from unittest.mock import patch, mock_open, call, Mock
8
8
 
9
+ import asyncio
10
+ import builtins
9
11
  import logging
10
12
  import tempfile
11
13
  import unittest
12
14
  import os
13
15
  from platform import uname_result
16
+ import sys
14
17
 
15
18
 
16
19
  from amd_debug.common import (
17
20
  apply_prefix_wrapper,
21
+ bytes_to_gb,
18
22
  Colors,
19
23
  convert_string_to_bool,
20
24
  colorize_choices,
@@ -22,12 +26,15 @@ from amd_debug.common import (
22
26
  compare_file,
23
27
  find_ip_version,
24
28
  fatal_error,
29
+ gb_to_pages,
25
30
  get_distro,
26
31
  get_log_priority,
27
32
  get_pretty_distro,
33
+ get_system_mem,
28
34
  is_root,
29
35
  minimum_kernel,
30
36
  print_color,
37
+ reboot,
31
38
  run_countdown,
32
39
  systemd_in_use,
33
40
  running_ssh,
@@ -45,6 +52,7 @@ color_dict = {
45
52
  "○": Colors.OK,
46
53
  "💤": Colors.OK,
47
54
  "💯": Colors.UNDERLINE,
55
+ "🚫": Colors.UNDERLINE,
48
56
  "🗣️": Colors.HEADER,
49
57
  }
50
58
 
@@ -442,3 +450,111 @@ class TestCommon(unittest.TestCase):
442
450
  with patch("sys.exit") as mock_exit:
443
451
  convert_string_to_bool("[unclosed_list")
444
452
  mock_exit.assert_called_once_with("Invalid entry: [unclosed_list")
453
+
454
+ def test_bytes_to_gb(self):
455
+ """Test bytes_to_gb conversion"""
456
+ # 4096 bytes should be 4096*4096/(1024*1024*1024) GB
457
+ self.assertAlmostEqual(bytes_to_gb(1), 4096 / (1024 * 1024 * 1024))
458
+ self.assertAlmostEqual(bytes_to_gb(0), 0)
459
+ self.assertAlmostEqual(bytes_to_gb(1024), 1024 * 4096 / (1024 * 1024 * 1024))
460
+
461
+ def test_gb_to_pages(self):
462
+ """Test gb_to_pages conversion"""
463
+ # 1 GB should be int(1 * (1024*1024*1024) / 4096)
464
+ self.assertEqual(gb_to_pages(1), int((1024 * 1024 * 1024) / 4096))
465
+ self.assertEqual(gb_to_pages(0), 0)
466
+ self.assertEqual(gb_to_pages(2), int(2 * (1024 * 1024 * 1024) / 4096))
467
+
468
+ @patch(
469
+ "builtins.open",
470
+ new_callable=mock_open,
471
+ read_data="MemTotal: 16384516 kB\n",
472
+ )
473
+ @patch("os.path.join", return_value="/proc/meminfo")
474
+ def test_get_system_mem_valid(self, _mock_join, mock_file):
475
+ """Test get_system_mem returns correct value"""
476
+ expected_gb = 16384516 / (1024 * 1024)
477
+ self.assertAlmostEqual(get_system_mem(), expected_gb)
478
+ mock_file.assert_called_once_with("/proc/meminfo", "r", encoding="utf-8")
479
+
480
+ def test_reboot_dbus_fast_success(self):
481
+ """Test reboot returns True when reboot_dbus_fast succeeds"""
482
+
483
+ # Create a mock loop that properly handles coroutines
484
+ def mock_run_until_complete(coro):
485
+ # Consume the coroutine to prevent the warning
486
+ try:
487
+ # Close the coroutine to prevent the warning
488
+ coro.close()
489
+ except (AttributeError, RuntimeError):
490
+ pass
491
+ return True
492
+
493
+ mock_loop = Mock()
494
+ mock_loop.run_until_complete.side_effect = mock_run_until_complete
495
+
496
+ with patch("amd_debug.common.asyncio.get_event_loop", return_value=mock_loop):
497
+ result = reboot()
498
+ self.assertTrue(result)
499
+ mock_loop.run_until_complete.assert_called_once()
500
+
501
+ @patch("asyncio.get_event_loop")
502
+ def test_reboot_dbus_fast_failure_and_dbus_success(self, mock_get_event_loop):
503
+ """Test reboot falls back to reboot_dbus when reboot_dbus_fast fails"""
504
+
505
+ # Create a mock loop that properly handles coroutines
506
+ def mock_run_until_complete(coro):
507
+ # Consume the coroutine to prevent the warning
508
+ try:
509
+ coro.close()
510
+ except (AttributeError, RuntimeError):
511
+ pass
512
+ return False
513
+
514
+ mock_loop = Mock()
515
+ mock_loop.run_until_complete.side_effect = mock_run_until_complete
516
+ mock_get_event_loop.return_value = mock_loop
517
+
518
+ # Mock the dbus module to avoid ImportError in CI
519
+ mock_dbus = Mock()
520
+ mock_bus = Mock()
521
+ mock_obj = Mock()
522
+ mock_intf = Mock()
523
+
524
+ mock_dbus.SystemBus.return_value = mock_bus
525
+ mock_bus.get_object.return_value = mock_obj
526
+ mock_obj.get_interface.return_value = mock_intf
527
+ mock_dbus.Interface = Mock(return_value=mock_intf)
528
+
529
+ with patch.dict("sys.modules", {"dbus": mock_dbus}):
530
+ result = reboot()
531
+ self.assertTrue(result)
532
+
533
+ @patch("asyncio.get_event_loop")
534
+ def test_reboot_dbus_fast_failure_and_dbus_failure(self, mock_get_event_loop):
535
+ """Test reboot returns False when both reboot_dbus_fast and reboot_dbus fail"""
536
+
537
+ # Create a mock loop that properly handles coroutines
538
+ def mock_run_until_complete(coro):
539
+ # Consume the coroutine to prevent the warning
540
+ try:
541
+ coro.close()
542
+ except (AttributeError, RuntimeError):
543
+ pass
544
+ return False
545
+
546
+ mock_loop = Mock()
547
+ mock_loop.run_until_complete.side_effect = mock_run_until_complete
548
+ mock_get_event_loop.return_value = mock_loop
549
+
550
+ # Mock the import to raise ImportError when dbus is imported
551
+ original_import = builtins.__import__
552
+
553
+ def mock_import(name, *args, **kwargs):
554
+ if name == "dbus":
555
+ raise ImportError("No module named 'dbus'")
556
+ return original_import(name, *args, **kwargs)
557
+
558
+ with patch("builtins.__import__", side_effect=mock_import):
559
+ result = reboot()
560
+ self.assertFalse(result)
test_database.py CHANGED
@@ -255,7 +255,7 @@ class TestSleepDatabase(unittest.TestCase):
255
255
  def test_get_last_prereq_ts_no_data(self):
256
256
  """Test getting the last prereq timestamp when no data exists"""
257
257
  result = self.db.get_last_prereq_ts()
258
- self.assertIsNone(result)
258
+ self.assertEqual(result, 0)
259
259
 
260
260
  def test_report_cycle_data(self):
261
261
  """Test reporting cycle data"""
test_display.py CHANGED
@@ -40,7 +40,7 @@ class TestDisplay(unittest.TestCase):
40
40
  display = Display()
41
41
 
42
42
  # Verify the EDID paths are correctly set
43
- expected_edid = {"card0": "/sys/devices/card0/edid"}
43
+ expected_edid = ["/sys/devices/card0/edid"]
44
44
  self.assertEqual(display.get_edid(), expected_edid)
45
45
  mock_context.assert_called_once()
46
46
 
@@ -54,7 +54,7 @@ class TestDisplay(unittest.TestCase):
54
54
  display = Display()
55
55
 
56
56
  # Verify the EDID dictionary is empty
57
- self.assertEqual(display.get_edid(), {})
57
+ self.assertEqual(display.get_edid(), [])
58
58
 
59
59
  @patch("amd_debug.display.Context")
60
60
  def test_device_without_card(self, mock_context):
@@ -70,7 +70,7 @@ class TestDisplay(unittest.TestCase):
70
70
  display = Display()
71
71
 
72
72
  # Verify the EDID dictionary is empty
73
- self.assertEqual(display.get_edid(), {})
73
+ self.assertEqual(display.get_edid(), [])
74
74
 
75
75
  @patch("amd_debug.display.Context")
76
76
  @patch("amd_debug.display.read_file")
@@ -94,7 +94,7 @@ class TestDisplay(unittest.TestCase):
94
94
  display = Display()
95
95
 
96
96
  # Verify the EDID dictionary is empty
97
- self.assertEqual(display.get_edid(), {})
97
+ self.assertEqual(display.get_edid(), [])
98
98
 
99
99
  @patch("amd_debug.display.Context")
100
100
  @patch("amd_debug.display.read_file")
@@ -116,7 +116,7 @@ class TestDisplay(unittest.TestCase):
116
116
  display = Display()
117
117
 
118
118
  # Verify the EDID dictionary is empty
119
- self.assertEqual(display.get_edid(), {})
119
+ self.assertEqual(display.get_edid(), [])
120
120
 
121
121
  @patch("amd_debug.display.Context")
122
122
  @patch("amd_debug.display.read_file")
@@ -140,4 +140,4 @@ class TestDisplay(unittest.TestCase):
140
140
  display = Display()
141
141
 
142
142
  # Verify the EDID dictionary is empty
143
- self.assertEqual(display.get_edid(), {})
143
+ self.assertEqual(display.get_edid(), [])
test_installer.py CHANGED
@@ -125,6 +125,29 @@ class TestInstaller(unittest.TestCase):
125
125
  )
126
126
  self.assertTrue(ret)
127
127
 
128
+ @patch("builtins.print")
129
+ @patch("amd_debug.installer.get_distro", return_value="fedora")
130
+ @patch("builtins.open", new_callable=mock_open, read_data="VARIANT_ID=kde\n")
131
+ @patch("os.execvp", return_value=None)
132
+ @patch("subprocess.check_call", return_value=0)
133
+ @patch("subprocess.call", return_value=1)
134
+ def test_install_iasl_fedora_kde(
135
+ self,
136
+ _mock_call,
137
+ _mock_check_call,
138
+ _mock_variant,
139
+ _mock_distro,
140
+ _fake_sudo,
141
+ _mock_print,
142
+ ):
143
+ """Test install requirements function on Fedora KDE"""
144
+ self.installer.set_requirements("iasl")
145
+ ret = self.installer.install_dependencies()
146
+ _mock_check_call.assert_called_once_with(
147
+ ["dnf", "install", "-y", "acpica-tools"]
148
+ )
149
+ self.assertTrue(ret)
150
+
128
151
  @patch("builtins.print")
129
152
  @patch("amd_debug.installer.get_distro", return_value="ubuntu")
130
153
  @patch("os.execvp", return_value=None)
@@ -162,6 +185,27 @@ class TestInstaller(unittest.TestCase):
162
185
  _mock_check_call.assert_called_once_with(["dnf", "install", "-y", "ethtool"])
163
186
  self.assertTrue(ret)
164
187
 
188
+ @patch("builtins.print")
189
+ @patch("amd_debug.installer.get_distro", return_value="fedora")
190
+ @patch("builtins.open", new_callable=mock_open, read_data="VARIANT_ID=kde\n")
191
+ @patch("os.execvp", return_value=None)
192
+ @patch("subprocess.check_call", return_value=0)
193
+ @patch("subprocess.call", return_value=1)
194
+ def test_install_ethtool_fedora_kde(
195
+ self,
196
+ _mock_call,
197
+ _mock_check_call,
198
+ _mock_variant,
199
+ _mock_distro,
200
+ _fake_sudo,
201
+ _mock_print,
202
+ ):
203
+ """Test install requirements function on Fedora KDE"""
204
+ self.installer.set_requirements("ethtool")
205
+ ret = self.installer.install_dependencies()
206
+ _mock_check_call.assert_called_once_with(["dnf", "install", "-y", "ethtool"])
207
+ self.assertTrue(ret)
208
+
165
209
  @patch("builtins.print")
166
210
  @patch("amd_debug.installer.get_distro", return_value="arch")
167
211
  @patch("os.execvp", return_value=None)
@@ -231,7 +275,30 @@ class TestInstaller(unittest.TestCase):
231
275
  self.installer.set_requirements("edid-decode")
232
276
  ret = self.installer.install_dependencies()
233
277
  _mock_check_call.assert_called_once_with(
234
- ["dnf", "install", "-y", "libdisplay-info"]
278
+ ["dnf", "install", "-y", "libdisplay-info-tools"]
279
+ )
280
+ self.assertTrue(ret)
281
+
282
+ @patch("builtins.print")
283
+ @patch("amd_debug.installer.get_distro", return_value="fedora")
284
+ @patch("builtins.open", new_callable=mock_open, read_data="VARIANT_ID=kde\n")
285
+ @patch("os.execvp", return_value=None)
286
+ @patch("subprocess.check_call", return_value=0)
287
+ @patch("subprocess.call", return_value=1)
288
+ def test_install_edid_decode_fedora_kde(
289
+ self,
290
+ _mock_call,
291
+ _mock_check_call,
292
+ _mock_variant,
293
+ _mock_distro,
294
+ _fake_sudo,
295
+ _mock_print,
296
+ ):
297
+ """Test install requirements function for edid-decode on Fedora KDE"""
298
+ self.installer.set_requirements("edid-decode")
299
+ ret = self.installer.install_dependencies()
300
+ _mock_check_call.assert_called_once_with(
301
+ ["dnf", "install", "-y", "libdisplay-info-tools"]
235
302
  )
236
303
  self.assertTrue(ret)
237
304