amd-debug-tools 0.2.2__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
@@ -290,13 +289,12 @@ class SleepValidator(AmdTool):
290
289
  sys_name = pnp.sys_name
291
290
 
292
291
  name = name.replace('"', "")
293
- devices.append(f"{name} [{sys_name}]: {wake_en}")
292
+ devices.append(f"{name}|{sys_name}|{wake_en}")
294
293
  devices.sort()
295
- self.db.record_debug("Possible wakeup sources:")
294
+ debug_str = "Wakeup Source|Linux Device|Status\n"
296
295
  for dev in devices:
297
- # set prefix if last device
298
- prefix = "│ " if dev != devices[-1] else "└─"
299
- self.db.record_debug(f"{prefix}{dev}")
296
+ debug_str += f"{dev}\n"
297
+ self.db.record_debug(debug_str)
300
298
 
301
299
  def capture_lid(self) -> None:
302
300
  """Capture lid state"""
@@ -319,6 +317,9 @@ class SleepValidator(AmdTool):
319
317
  actions = read_file(os.path.join(p, "actions"))
320
318
  message = f"{Headers.WokeFromIrq} {n} ({chip_name} {hw}-{name} {actions})"
321
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]
322
323
  except OSError:
323
324
  pass
324
325
  return True
@@ -436,7 +437,6 @@ class SleepValidator(AmdTool):
436
437
  continue
437
438
  self.db.record_debug(
438
439
  f"Woke up from input source {device} ({self.wakeup_count[device]}->{count})",
439
- "💤",
440
440
  )
441
441
  self.wakeup_count = wakeup_count
442
442
 
@@ -444,11 +444,10 @@ class SleepValidator(AmdTool):
444
444
  """Check for hardware sleep state"""
445
445
  # try from kernel 6.4's suspend stats interface first because it works
446
446
  # even with kernel lockdown
447
- if not self.hw_sleep_duration:
448
- p = os.path.join("/", "sys", "power", "suspend_stats", "last_hw_sleep")
449
- if os.path.exists(p):
450
- self.hw_sleep_duration = int(read_file(p)) / 10**6
451
- if not self.hw_sleep_duration:
447
+ p = os.path.join("/", "sys", "power", "suspend_stats", "last_hw_sleep")
448
+ if os.path.exists(p):
449
+ self.hw_sleep_duration = int(read_file(p)) / 10**6
450
+ if not os.path.exists(p) and not self.hw_sleep_duration:
452
451
  p = os.path.join("/", "sys", "kernel", "debug", "amd_pmc", "smu_fw_info")
453
452
  try:
454
453
  val = read_file(p)
@@ -472,7 +471,7 @@ class SleepValidator(AmdTool):
472
471
  self.db.record_debug(f"HW sleep statistics file {p} is missing")
473
472
  if not self.hw_sleep_duration:
474
473
  self.db.record_cycle_data("Did not reach hardware sleep state", "❌")
475
- return self.hw_sleep_duration
474
+ return self.hw_sleep_duration > 0
476
475
 
477
476
  def capture_command_line(self):
478
477
  """Capture the kernel command line to debug"""
@@ -546,16 +545,6 @@ class SleepValidator(AmdTool):
546
545
 
547
546
  def analyze_kernel_log(self):
548
547
  """Analyze one of the lines from the kernel log"""
549
- self.cycle_count = 0
550
- self.upep = False
551
- self.upep_microsoft = False
552
- self.wakeup_irqs = []
553
- self.idle_masks = []
554
- self.acpi_errors = []
555
- self.active_gpios = []
556
- self.notify_devices = []
557
- self.page_faults = []
558
- self.irq1_workaround = False
559
548
  self.kernel_log.process_callback(self._analyze_kernel_log_line)
560
549
 
561
550
  if self.cycle_count:
@@ -585,7 +574,6 @@ class SleepValidator(AmdTool):
585
574
  if bit_changed & BIT(bit):
586
575
  self.db.record_debug(
587
576
  f"Idle mask bit {bit} (0x{BIT(bit):x}) changed during suspend",
588
- "○",
589
577
  )
590
578
  if self.upep:
591
579
  if self.upep_microsoft:
@@ -646,9 +634,20 @@ class SleepValidator(AmdTool):
646
634
 
647
635
  def post(self):
648
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
+
649
648
  checks = [
650
- self.analyze_kernel_log,
651
649
  self.capture_wakeup_irq_data,
650
+ self.analyze_kernel_log,
652
651
  self.check_gpes,
653
652
  self.capture_lid,
654
653
  self.check_rtc_cmos,
@@ -663,10 +662,10 @@ class SleepValidator(AmdTool):
663
662
  check()
664
663
  self.db.record_cycle(
665
664
  self.requested_duration,
666
- self.active_gpios,
667
- self.wakeup_irqs,
668
- self.kernel_duration,
669
- 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),
670
669
  )
671
670
 
672
671
  def prep(self):
@@ -705,6 +704,22 @@ class SleepValidator(AmdTool):
705
704
  else:
706
705
  print_color("No RTC device found, please manually wake system", "🚦")
707
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
+
708
723
  @pm_debugging
709
724
  def suspend_system(self):
710
725
  """Suspend the system using the dbus or sysfs interface"""
@@ -746,17 +761,23 @@ class SleepValidator(AmdTool):
746
761
  self.db.record_cycle_data("Missing dbus", "❌")
747
762
  return False
748
763
  else:
764
+ if not self.toggle_nvidia(b"suspend"):
765
+ return False
749
766
  old = get_wakeup_count()
767
+ p = os.path.join("/", "sys", "power", "state")
768
+ fd = os.open(p, os.O_WRONLY | os.O_SYNC)
750
769
  try:
751
- p = os.path.join("/", "sys", "power", "state")
752
- with open(p, "w", encoding="utf-8") as w:
753
- w.write("mem")
770
+ os.write(fd, b"mem")
754
771
  except OSError as e:
755
772
  new = get_wakeup_count()
756
773
  self.db.record_cycle_data(
757
774
  f"Failed to set suspend state ({old} -> {new}): {e}", "❌"
758
775
  )
759
776
  return False
777
+ finally:
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):
@@ -780,6 +801,7 @@ class SleepValidator(AmdTool):
780
801
 
781
802
  def run(self, duration, count, wait, rand, logind):
782
803
  """Run the suspend test"""
804
+ min_duration = 4
783
805
  if not count:
784
806
  return True
785
807
 
@@ -787,8 +809,13 @@ class SleepValidator(AmdTool):
787
809
  self.logind = True
788
810
 
789
811
  if rand:
812
+ if duration <= min_duration:
813
+ print_color(f"Invalid max duration {duration}", "❌")
814
+ self.db.sync()
815
+ self.report_cycle()
816
+ return False
790
817
  print_color(
791
- f"Running {count} cycle random test with max duration of {duration}s and a max wait of {wait}s",
818
+ f"Running {count} cycle random test with min duration of {min_duration}s, max duration of {duration}s and a max wait of {wait}s",
792
819
  "🗣️",
793
820
  )
794
821
  elif count > 1:
@@ -799,7 +826,7 @@ class SleepValidator(AmdTool):
799
826
  )
800
827
  for i in range(1, count + 1):
801
828
  if rand:
802
- self.requested_duration = random.randint(1, duration)
829
+ self.requested_duration = random.randint(min_duration, duration)
803
830
  requested_wait = random.randint(1, wait)
804
831
  else:
805
832
  self.requested_duration = duration
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
launcher.py CHANGED
@@ -28,7 +28,6 @@ def main():
28
28
  f"Missing dependency: {e}\n"
29
29
  f"Run ./install_deps.py to install dependencies."
30
30
  )
31
- return False
32
31
 
33
32
 
34
33
  if __name__ == "__main__":
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")
@@ -194,7 +210,7 @@ class TestAmdBios(unittest.TestCase):
194
210
  mock_amd_bios.assert_called_once_with(None, True)
195
211
  mock_app.set_tracing.assert_called_once_with(True)
196
212
  mock_show_log_info.assert_called_once()
197
- self.assertTrue(result)
213
+ self.assertIsNone(result)
198
214
 
199
215
  @patch("amd_debug.bios.AmdBios")
200
216
  @patch("amd_debug.bios.parse_args")
@@ -217,7 +233,7 @@ class TestAmdBios(unittest.TestCase):
217
233
  mock_amd_bios.assert_called_once_with("test.log", True)
218
234
  mock_app.run.assert_called_once()
219
235
  mock_show_log_info.assert_called_once()
220
- self.assertTrue(result)
236
+ self.assertIsNone(result)
221
237
 
222
238
  @patch("amd_debug.bios.parse_args")
223
239
  @patch("amd_debug.bios.version")
@@ -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()
@@ -235,16 +251,18 @@ class TestAmdBios(unittest.TestCase):
235
251
  mock_parse_args.assert_called_once()
236
252
  mock_version.assert_called_once()
237
253
  mock_show_log_info.assert_called_once()
238
- self.assertEqual(result, False)
254
+ self.assertEqual(result, 1)
239
255
 
240
256
  @patch("amd_debug.bios.parse_args")
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
 
248
266
  mock_parse_args.assert_called_once()
249
267
  mock_show_log_info.assert_called_once()
250
- self.assertFalse(result)
268
+ self.assertEqual(result, 1)
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"""