Skip to content

Debug method #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions buildconfig/stubs/pygame/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ from pygame import (
surface as surface,
surflock as surflock,
sysfont as sysfont,
_debug as _debug
)

from .rect import Rect as Rect
Expand All @@ -51,6 +52,7 @@ from .math import Vector2 as Vector2, Vector3 as Vector3
from .cursors import Cursor as Cursor
from .bufferproxy import BufferProxy as BufferProxy
from .mask import Mask as Mask
from ._debug import print_debug_info as print_debug_info
from .event import Event as Event
from .font import Font as Font
from .mixer import Channel as Channel
Expand Down
7 changes: 7 additions & 0 deletions buildconfig/stubs/pygame/_debug.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from typing import Tuple, Union, Optional, Callable

ImportResult = Tuple[str, bool, Optional[Callable]]

def str_from_tuple(version_tuple: Union[Tuple[int, int, int], None]) -> str: ...
def attempt_import(module: str, function_name: str, output_str: str = "") -> ImportResult: ...
def print_debug_info(filename: Optional[str] = None) -> None: ...
19 changes: 19 additions & 0 deletions docs/reST/ref/pygame.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,25 @@ object instead of the module, which can be used to test for availability.

.. ## pygame.encode_file_path ##

.. function:: print_debug_info

| :sl:`retrieves useful information for debugging and issue-reporting purposes`
| :sg:`print_debug_info(filename=None) -> None`

Constructs a string containing details on the system, the python interpreter,
the pygame version, and the linked and compiled versions of the libraries that
pygame wraps. If ``filename`` is ``None``, then the string is printed into the
console. Otherwise, the debug string is written to the specified file.

.. note::
If ``pygame.freetype`` has not been initialized with :func:`pygame.init` or :func:`pygame.freetype.init`,
then the linked and compiled versions of FreeType will be "Unk" since this information is not
available before initialization.

.. versionadded:: 2.1.4

.. ## pygame.print_debug_info ##


:mod:`pygame.version`
=====================
Expand Down
5 changes: 5 additions & 0 deletions src_c/doc/pygame_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#define DOC_PYGAMEREGISTERQUIT "register_quit(callable) -> None\nregister a function to be called when pygame quits"
#define DOC_PYGAMEENCODESTRING "encode_string([obj [, encoding [, errors [, etype]]]]) -> bytes or None\nEncode a Unicode or bytes object"
#define DOC_PYGAMEENCODEFILEPATH "encode_file_path([obj [, etype]]) -> bytes or None\nEncode a Unicode or bytes object as a file system path"
#define DOC_PYGAMEPRINTDEBUGINFO "print_debug_info(filename=None) -> None\nretrieves useful information for debugging and issue-reporting purposes"
#define DOC_PYGAMEVERSION "small module containing version information"
#define DOC_PYGAMEVERSIONVER "ver = '1.2'\nversion number as a string"
#define DOC_PYGAMEVERSIONVERNUM "vernum = (1, 5, 3)\ntupled integers of the version"
Expand Down Expand Up @@ -69,6 +70,10 @@ pygame.encode_file_path
encode_file_path([obj [, etype]]) -> bytes or None
Encode a Unicode or bytes object as a file system path

pygame.print_debug_info
print_debug_info(filename=None) -> None
retrieves useful information for debugging and issue-reporting purposes

pygame.version
small module containing version information

Expand Down
8 changes: 7 additions & 1 deletion src_py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
language. The package is highly portable, with games running on
Windows, macOS, OS X, BeOS, FreeBSD, IRIX, and Linux."""

import sys
import os
import sys

# Choose Windows display driver
if os.name == "nt":
Expand Down Expand Up @@ -282,6 +282,12 @@ def Overlay(format, size): # pylint: disable=unused-argument
except (ImportError, OSError):
fastevent = MissingModule("fastevent", urgent=0)

try:
import pygame._debug
from pygame._debug import print_debug_info
except (ImportError, OSError):
debug = MissingModule("_debug", urgent=0)

# there's also a couple "internal" modules not needed
# by users, but putting them here helps "dependency finder"
# programs get everything they need (like py2exe)
Expand Down
161 changes: 161 additions & 0 deletions src_py/_debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""Debug functionality that allows for more useful issue reporting
"""

import sys
import traceback
import importlib
from typing import Tuple, Optional, Callable

ImportResult = Tuple[str, bool, Optional[Callable]]


def str_from_tuple(version_tuple):
"""Converts a tuple like (2, 0, 20) into a string joined by periods

Args:
version_tuple: tuple(version_major, version_minor, version_patch)

Returns:
str: "major.minor.patch"
"""
if version_tuple is None:
return "None"

strs = map(str, version_tuple)
return ".".join(strs)


def attempt_import(module, function_name, output_str=""):
"""Attempts to import function_name from module

Args:
module: string representing module name
function_name: string representing function name to be imported
output_str: optional string to prepend error messagess to if one occurs

Returns:
tuple(str, bool, Any):
[0]: output_str + error_message
[1]: True if successful, False if failed
[2]: if successful, the thing that was imported, else None
"""
try:
mod = importlib.import_module(module)
i = getattr(mod, function_name)
success = True
except (ImportError, AttributeError):
i = None
output_str += f"There was a problem with {module} import\n"
output_str += "A dummy value will be returned for the version\n"
output_str += traceback.format_exc() + "\n" + "=" * 20 + "\n"
success = False

return (output_str, success, i)


def print_debug_info(filename=None):
"""Gets debug information for reporting bugs. Prints to console
if filename is not specified, otherwise writes to that file
(note: if filename is not an empty file, it will overwrite whatever is
in there)

Args:
filename: string name of the file to save
"""
debug_str = ""

# keyword for compat with getters
def dummy_return(linked=True):
# pylint: disable=unused-argument
return (-1, -1, -1)

from pygame.display import get_driver, get_init as display_init
from pygame.base import get_sdl_version

debug_str, *mixer = attempt_import(
"pygame.mixer", "get_sdl_mixer_version", debug_str
)
if not mixer[0]:
get_sdl_mixer_version = dummy_return
else:
get_sdl_mixer_version = mixer[1]

debug_str, *font = attempt_import("pygame.font", "get_sdl_ttf_version", debug_str)
if not font[0]:
get_sdl_ttf_version = dummy_return
else:
get_sdl_ttf_version = font[1]

debug_str, *image = attempt_import(
"pygame.image", "get_sdl_image_version", debug_str
)
if not image[0]:
get_sdl_image_version = dummy_return
else:
get_sdl_image_version = image[1]

debug_str, *freetype = attempt_import("pygame.freetype", "get_version", debug_str)
if not freetype[0]:
ft_version = dummy_return
else:
ft_version = freetype[1]

from pygame.version import ver

import platform

debug_str += f"Platform:\t\t{platform.platform()}\n"

debug_str += f"System:\t\t\t{platform.system()}\n"

debug_str += f"System Version:\t\t{platform.version()}\n"

debug_str += f"Processor:\t\t{platform.processor()}\n"

debug_str += (
f"Architecture:\t\tBits: {platform.architecture()[0]}\t"
f"Linkage: {platform.architecture()[1]}\n"
)

if display_init():
debug_str += f"Driver:\t\t\t{get_driver()}\n\n"
else:
debug_str += "Driver:\t\t\tDisplay Not Initialized\n\n"

debug_str += f"Python:\t\t\t{platform.python_implementation()}\n"

debug_str += f"pygame version:\t\t{ver}\n"

debug_str += f"python version:\t\t{str_from_tuple(sys.version_info[0:3])}\n\n"

debug_str += (
f"SDL versions:\t\tLinked: {str_from_tuple(get_sdl_version())}\t"
f"Compiled: {str_from_tuple(get_sdl_version(linked = False))}\n"
)

debug_str += (
f"SDL Mixer versions:\tLinked: {str_from_tuple(get_sdl_mixer_version())}\t"
f"Compiled: {str_from_tuple(get_sdl_mixer_version(linked = False))}\n"
)

debug_str += (
f"SDL Font versions:\tLinked: {str_from_tuple(get_sdl_ttf_version())}\t"
f"Compiled: {str_from_tuple(get_sdl_ttf_version(linked = False))}\n"
)

debug_str += (
f"SDL Image versions:\tLinked: {str_from_tuple(get_sdl_image_version())}\t"
f"Compiled: {str_from_tuple(get_sdl_image_version(linked = False))}\n"
)

debug_str += (
f"Freetype versions:\tLinked: {str_from_tuple(ft_version())}\t"
f"Compiled: {str_from_tuple(ft_version(linked = False))}"
)

if filename is None:
print(debug_str)

else:
with open(filename, "w", encoding="utf8") as debugfile:
debugfile.write(debug_str)
23 changes: 23 additions & 0 deletions test/debug_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import unittest, unittest.mock
import io

import pygame


class DebugTest(unittest.TestCase):
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def assert_stdout(self, expected_output, mock_stdout):
pygame.print_debug_info()
self.assertEqual(mock_stdout.getvalue(), expected_output)

def test_print_debug(self):
import os

pygame.print_debug_info("temp_file.txt")
with open("temp_file.txt", "r") as temp_file:
text = temp_file.read()

self.assertNotEqual(text, "")
self.assert_stdout(text + "\n")

os.remove("temp_file.txt")