From 3ebaec895a0e6918a547a5c30b471740b0344da2 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 1 Mar 2024 11:47:15 -0500 Subject: [PATCH 001/234] Lay the groundwork for testing with libretro.py --- cmake/FindPythonModules.cmake | 364 ++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 6 + 2 files changed, 370 insertions(+) create mode 100644 cmake/FindPythonModules.cmake diff --git a/cmake/FindPythonModules.cmake b/cmake/FindPythonModules.cmake new file mode 100644 index 00000000..ff715f66 --- /dev/null +++ b/cmake/FindPythonModules.cmake @@ -0,0 +1,364 @@ +############################################################################## +# @file FindPythonModules.cmake +# @brief Find Python modules. +# +# @par Input/Output variables: +# +# +# @tp @b PythonModules_DIR @endtp +# +# +#
List of directories where Python modules are installed.
+ +# @par Input variables: +# +# +# @tp @b PythonModules_FIND_COMPONENTS @endtp +# +# +# +# @tp @b PythonModules_FIND_OPTIONAL_COMPONENTS @endtp +# +# +# +# @tp @b PYTHON_EXECUTABLE @endtp +# +# +# +# @tp @b PYTHONPATH @endtp +# +# +#
The @c COMPONENTS argument(s) of the find_package() command specifies +# the names of the Python modules to look for.
The @c OPTIONAL_COMPONENTS argument(s) of the find_package() command +# specifies the names of the Python modules which are not necessarily +# required, but should be searched as well.
Path to the Python interpreter. Should be set by first looking +# for the Python interpreter, i.e., find_packages(PythonInterp). +# If set, this module first tries to execute the Python interpreter, +# import the respective Python module, and then derive the search path +# from the @c __file__ attribute of the loaded Python module. +# Otherwise, or if this fails, it looks either for a package +# @c __init__.py file inside a subdirectory named after the specified +# Python module or a @c .py module file in each directory listed in +# the @c PYTHONPATH.
Search path for Python modules. If this CMake variable is undefined, +# the corresponding environment variable is used instead if set. +# Only absolute paths in the @c PYTHONPATH are considered.
+# +# @par Output variables: +# +# +# @tp @b PythonModules_FOUND @endtp +# +# +# +# @tp @b PythonModules_<module>_FOUND @endtp +# +# +# +# @tp @b PythonModules_<module>_PATH @endtp +# +# +# +# @tp @b PythonModules_<module> @endtp +# +# +# +# @tp @b PythonModules_PYTHONPATH @endtp +# +# +#
Whether all specified Python modules were found.
Whether the Python module <module%gt; was found.
Absolute path of the directory containing the Python module named <module%gt;.
Import target for module named <module>. The location of the +# target is @c PythonModules_<module>_PATH.
The @c PYTHONPATH setting required for the found Python module(s), i.e., +# The directories that have to be added to the Python search path. +# To support the use of this CMake module more than once with different +# arguments to the find_package() command, e.g., with and without the +# @c REQUIRED argument, the directories containing the found Python +# modules are appended to any existing directories in +# @c PythonModules_PYTHONPATH if not already listed.
+# +# @ingroup CMakeFindModules +############################################################################## + +#============================================================================= +# Copyright 2011-2012 University of Pennsylvania +# Copyright 2013-2016 Andreas Schuh +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +include (CMakeParseArguments) + +# ---------------------------------------------------------------------------- +## @brief Find Python module. +# +# If the @c PYTHON_EXECUTABLE variable is set, this function at first tries +# to launch the Python interpreter, import the named Python module, and then +# determines the search path for the Python module from the @c __file__ +# attribute of the loaded module. Otherwise, or if this fails, it looks for +# the Python module in the directories listed in the @c PYTHONPATH variable +# if defined. If this variable is not defined, the @c PYTHONPATH environment +# variable is used instead. +# +# @param [in] CACHEVAR Name of CMake cache variable which stores path of +# directory of the Python module. If not set or if +# the cache entry contains space characters only or +# ends in the string NOTFOUND, this function looks for +# the specified Python module. Otherwise, it does nothing +# and leaves the cache entry unmodified. +# @param [in] ARGN The remaining arguments are parsed and the following +# options extract: +# @par +# +# +# @tp @b NAME module @endtp +# +# +# +# @tp @b PYTHON_EXECUTABLE python @endtp +# +# +# +# @tp @b PATH dir1 [dir2...] @endtp +# +# +# +# @tp @b NO_PYTHONPATH @endtp +# +# +# +# @tp @b NO_DEFAULT_PATH @endtp +# +# +#
Name of the Python module.
Full path of the Python interpreter executable. If not specified +# the global PYTHON_EXECUTABLE CMake variable/cache entry is used.
Directories where to look for Python module.
Do not consider the @c PYTHONPATH environment variable.
Do not look in any default path such as the directories listed by the +# @c PYTHONPATH environment variable.
+# +# @returns Sets the named cache variable of type @c PATH to the absolute path +# of the directory containing the specified Python @p MODULE if found, +# or the string "<MODULE%gt;-NOTFOUND" otherwise. +function (basis_find_python_module CACHEVAR) + # do nothing if path of module already known from previous run + if (DEFINED ${CACHEVAR} AND NOT ${CACHEVAR} MATCHES "NOTFOUND$") + return () + endif () + # parse arguments + CMAKE_PARSE_ARGUMENTS ( + ARGN + "NO_DEFAULT_PATH;NO_PYTHONPATH" + "PYTHON_EXECUTABLE;NAME" + "PATH" + ${ARGN} + ) + if (NOT ARGN_NAME) + message ("basis_find_python_module(): Missing NAME argument!") + endif () + if (ARGN_UNPARSED_ARGUMENTS) + message ("basis_find_python_module(): Invalid arguments: ${ARGN_UNPARSED_ARGUMENTS}") + endif () + if (NOT ARGN_PYTHON_EXECUTABLE) + set (ARGN_PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}") + endif () + if (ARGN_NO_DEFAULT_PATH) + set (ARGN_NO_PYTHONPATH TRUE) + set (ARGN_PYTHON_EXECUTABLE) + endif () + # set initial value of cache entry + if (${CACHEVAR} MATCHES "^ *$") + set (${CACHEVAR} "${ARGN_NAME}-NOTFOUND" CACHE PATH "Directory containing ${ARGN_NAME} Python module/package." FORCE) + else () + set (${CACHEVAR} "${ARGN_NAME}-NOTFOUND" CACHE PATH "Directory containing ${ARGN_NAME} Python module/package.") + endif () + # 1. search specified paths + foreach (P ${ARGN_PATH}) # ignore empty entries + if (IS_ABSOLUTE "${P}") + if (EXISTS "${P}/${ARGN_NAME}.py" OR EXISTS "${P}/${ARGN_NAME}/__init__.py" OR + EXISTS "${P}/${ARGN_NAME}.pyc" OR EXISTS "${P}/${ARGN_NAME}/__init__.pyc") + set_property (CACHE ${CACHEVAR} PROPERTY VALUE "${P}") + return () + endif () + endif () + endforeach () + # 2. get __file__ attribute of module loaded in Python + if (ARGN_PYTHON_EXECUTABLE) + set (IMPORT_SITE_ERROR FALSE) + # 2a. try it with -E option -- the preferred way to run Python + execute_process ( + COMMAND "${ARGN_PYTHON_EXECUTABLE}" -E -c "import ${ARGN_NAME}; print ${ARGN_NAME}.__file__" + RESULT_VARIABLE STATUS + OUTPUT_VARIABLE P + ERROR_VARIABLE ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (ERROR MATCHES "'import site' failed|ImportError: No module named site") + set (IMPORT_SITE_ERROR TRUE) + endif () + # 2b. try it without -E option + if (NOT STATUS EQUAL 0) + execute_process ( + COMMAND "${ARGN_PYTHON_EXECUTABLE}" -c "import ${ARGN_NAME}; print ${ARGN_NAME}.__file__" + RESULT_VARIABLE STATUS + OUTPUT_VARIABLE P + ERROR_VARIABLE ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (ERROR MATCHES "'import site' failed|ImportError: No module named site") + set (IMPORT_SITE_ERROR TRUE) + endif () + if (NOT STATUS EQUAL 0 AND ERROR MATCHES "ImportError: No module named site") + set (PYTHONHOME "$ENV{PYTHONHOME}") + unset (ENV{PYTHONHOME}) + execute_process ( + COMMAND "${ARGN_PYTHON_EXECUTABLE}" -c "import ${ARGN_NAME}; print ${ARGN_NAME}.__file__" + RESULT_VARIABLE STATUS + OUTPUT_VARIABLE P + ERROR_VARIABLE ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (ERROR MATCHES "'import site' failed|ImportError: No module named site") + set (IMPORT_SITE_ERROR TRUE) + endif () + set (ENV{PYTHONHOME} "${PYTHONHOME}") + endif () + endif () + if (STATUS EQUAL 0) + if (P MATCHES "__init__\\.pyc?$") + get_filename_component (P "${P}" PATH) + get_filename_component (P "${P}" PATH) + else () + get_filename_component (P "${P}" PATH) + endif () + set_property (CACHE ${CACHEVAR} PROPERTY VALUE "${P}") + return () + elseif (IMPORT_SITE_ERROR) + message (WARNING "Import of site module failed when running Python interpreter ${ARGN_PYTHON_EXECUTABLE}" + " with and without -E option. Make sure that the Python interpreter is installed properly" + " and that the PYTHONHOME environment variable is either not set (recommended) or at" + " least set correctly for this Python installation. Maybe you need to enable this Python" + " version first somehow if more than one version of Python is installed on your system?" + " Otherwise, set PYTHON_EXECUTABLE to the right Python interpreter executable (python).") + endif () + endif () + # 3. search PYTHONPATH + if (NOT ARGN_NO_PYTHONPATH) + string (REPLACE ":" ";" PYTHONPATH "$ENV{PYTHONPATH}") + foreach (P ${PYTHONPATH}) # ignore empty entries + if (IS_ABSOLUTE "${P}") + if (EXISTS "${P}/${ARGN_NAME}.py" OR EXISTS "${P}/${ARGN_NAME}/__init__.py" OR + EXISTS "${P}/${ARGN_NAME}.pyc" OR EXISTS "${P}/${ARGN_NAME}/__init__.pyc") + set_property (CACHE ${CACHEVAR} PROPERTY VALUE "${P}") + return () + endif () + endif () + endforeach () + endif () +endfunction () + +# ---------------------------------------------------------------------------- +# find Python modules +if (NOT PythonModules_FIND_COMPONENTS AND NOT PythonModules_FIND_OPTIONAL_COMPONENTS) + message (FATAL_ERROR "PythonModules: No (OPTIONAL_)COMPONENTS (i.e., Python module names) specified!") +endif () +if (NOT DEFINED PythonModules_PYTHONPATH) + set (PythonModules_PYTHONPATH) # PYTHONPATH of all found modules +endif () +# helper macro +macro (_PythonModules_find_python_modules ALL_FOUND) + set (${ALL_FOUND} TRUE) + foreach (_PythonModules_MODULE ${ARGN}) + set (_PythonModules_VAR "PythonModules_${_PythonModules_MODULE}_PATH") + set (_PythonModules_TGT "PythonModules_${_PythonModules_MODULE}") + if (PythonModules_DIR) + basis_find_python_module ( + ${_PythonModules_VAR} + NAME "${_PythonModules_MODULE}" + PATH "${PythonModules_DIR}" + NO_DEFAULT_PATH + ) + else () + basis_find_python_module ( + ${_PythonModules_VAR} + NAME "${_PythonModules_MODULE}" + PATH "${PythonModules_DIR}" + ) + endif () + mark_as_advanced (${_PythonModules_VAR}) + if (${_PythonModules_VAR} MATCHES "(^ *|NOTFOUND)$") + set (${ALL_FOUND} FALSE) + set (PythonModules_${_PythonModules_MODULE}_FOUND FALSE) + else () + if (NOT TARGET ${_PythonModules_TGT}) + add_library (${_PythonModules_TGT} UNKNOWN IMPORTED) + set_target_properties ( + ${_PythonModules_TGT} + PROPERTIES + BASIS_TYPE "SCRIPT_LIBRARY" + IMPORTED_LOCATION "${${_PythonModules_VAR}}" + ) + endif () + set (PythonModules_${_PythonModules_MODULE}_FOUND TRUE) + list (APPEND PythonModules_PYTHONPATH "${${_PythonModules_VAR}}") + endif () + endforeach () +endmacro () +# optional first, as PythonModules_FOUND shall be reset to TRUE afterwards +_PythonModules_find_python_modules (PythonModules_FOUND ${PythonModules_FIND_OPTIONAL_COMPONENTS}) +_PythonModules_find_python_modules (PythonModules_FOUND ${PythonModules_FIND_COMPONENTS}) +# remove duplicate paths in PYTHONPATH +if (PythonModules_PYTHONPATH) + list (REMOVE_DUPLICATES PythonModules_PYTHONPATH) +endif () + +# ---------------------------------------------------------------------------- +# handle standard QUIET and REQUIRED arguments +if (NOT PythonModules_FOUND) + # list of modules that were not found + set (_PythonModules_MISSING) + foreach (_PythonModules_MODULE ${PythonModules_FIND_COMPONENTS}) + if (NOT PythonModules_${_PythonModules_MODULE}_FOUND) + list (APPEND _PythonModules_MISSING "${_PythonModules_MODULE}") + endif () + endforeach () + # hint on how to help finding the Python modules + set (_PythonModules_HINT) + if (PYTHON_EXECUTABLE) + set (_PythonModules_HINT "Check if executing ${PYTHON_EXECUTABLE} -c \"import \" works") + else () + set (_PythonModules_HINT "Set PYTHON_EXECUTABLE, e.g., by searching for Python interpreter first") + endif () + if (_PythonModules_HINT) + set (_PythonModules_HINT "${_PythonModules_HINT} or set") + else () + set (_PythonModules_HINT "Set") + endif () + set (_PythonModules_HINT "${_PythonModules_HINT} the PythonModules_DIR variable. Alternatively,") + set (_PythonModules_HINT "${_PythonModules_HINT} set the PythonModules__PATH variable(s)") + set (_PythonModules_HINT "${_PythonModules_HINT} instead or the PYTHONPATH environment variable.") + set (_PythonModules_HINT "${_PythonModules_HINT} Unset PythonModules_DIR if you chose an alternative") + set (_PythonModules_HINT "${_PythonModules_HINT} option and before rerunning CMake again.") + # error message + string (REPLACE ";" ", " ${_PythonModules_MISSING} "${_PythonModules_MISSING}") + message (FATAL_ERROR "Could NOT find the following Python modules:\n${_PythonModules_MISSING}\n${_PythonModules_HINT}") +endif () + +# ---------------------------------------------------------------------------- +# common _DIR variable +if (NOT PythonModules_DIR) + set ( + PythonModules_DIR + "${PythonModules_PYTHONPATH}" + CACHE PATH + "Directory or list of directories separated by ; of installed Python modules." + FORCE + ) +endif () + +# ---------------------------------------------------------------------------- +# clean up +unset (_PythonModules_MODULE) +unset (_PythonModules_VAR) +unset (_PythonModules_VARS) \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d9c6c038..23a85855 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,10 @@ find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter) +find_package(PythonModules COMPONENTS libretro) + +# TODO: Make this required once libretro.py is on PyPI +if (PythonModules_libretro_PATH) + message(STATUS "Found libretro.py: ${PythonModules_libretro_PATH}") +endif() if (NOT RETROARCH) find_program( From 7999dbca9f5e10059adb89c1567140d94eb13f34 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 1 Mar 2024 11:51:40 -0500 Subject: [PATCH 002/234] Update the pr-target-branch-action --- .github/workflows/wrong-branch.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wrong-branch.yaml b/.github/workflows/wrong-branch.yaml index 15874c0a..4d9649d7 100644 --- a/.github/workflows/wrong-branch.yaml +++ b/.github/workflows/wrong-branch.yaml @@ -13,7 +13,7 @@ jobs: issues: write steps: - name: Verify Target Branch - uses: Vankka/pr-target-branch-action@v2 + uses: Vankka/pr-target-branch-action@v2.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From 7b0804353c9e24c91800d795d080d3637d5070a8 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 1 Mar 2024 11:47:15 -0500 Subject: [PATCH 003/234] Lay the groundwork for testing with libretro.py --- cmake/FindPythonModules.cmake | 364 ++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 6 + 2 files changed, 370 insertions(+) create mode 100644 cmake/FindPythonModules.cmake diff --git a/cmake/FindPythonModules.cmake b/cmake/FindPythonModules.cmake new file mode 100644 index 00000000..ff715f66 --- /dev/null +++ b/cmake/FindPythonModules.cmake @@ -0,0 +1,364 @@ +############################################################################## +# @file FindPythonModules.cmake +# @brief Find Python modules. +# +# @par Input/Output variables: +# +# +# @tp @b PythonModules_DIR @endtp +# +# +#
List of directories where Python modules are installed.
+ +# @par Input variables: +# +# +# @tp @b PythonModules_FIND_COMPONENTS @endtp +# +# +# +# @tp @b PythonModules_FIND_OPTIONAL_COMPONENTS @endtp +# +# +# +# @tp @b PYTHON_EXECUTABLE @endtp +# +# +# +# @tp @b PYTHONPATH @endtp +# +# +#
The @c COMPONENTS argument(s) of the find_package() command specifies +# the names of the Python modules to look for.
The @c OPTIONAL_COMPONENTS argument(s) of the find_package() command +# specifies the names of the Python modules which are not necessarily +# required, but should be searched as well.
Path to the Python interpreter. Should be set by first looking +# for the Python interpreter, i.e., find_packages(PythonInterp). +# If set, this module first tries to execute the Python interpreter, +# import the respective Python module, and then derive the search path +# from the @c __file__ attribute of the loaded Python module. +# Otherwise, or if this fails, it looks either for a package +# @c __init__.py file inside a subdirectory named after the specified +# Python module or a @c .py module file in each directory listed in +# the @c PYTHONPATH.
Search path for Python modules. If this CMake variable is undefined, +# the corresponding environment variable is used instead if set. +# Only absolute paths in the @c PYTHONPATH are considered.
+# +# @par Output variables: +# +# +# @tp @b PythonModules_FOUND @endtp +# +# +# +# @tp @b PythonModules_<module>_FOUND @endtp +# +# +# +# @tp @b PythonModules_<module>_PATH @endtp +# +# +# +# @tp @b PythonModules_<module> @endtp +# +# +# +# @tp @b PythonModules_PYTHONPATH @endtp +# +# +#
Whether all specified Python modules were found.
Whether the Python module <module%gt; was found.
Absolute path of the directory containing the Python module named <module%gt;.
Import target for module named <module>. The location of the +# target is @c PythonModules_<module>_PATH.
The @c PYTHONPATH setting required for the found Python module(s), i.e., +# The directories that have to be added to the Python search path. +# To support the use of this CMake module more than once with different +# arguments to the find_package() command, e.g., with and without the +# @c REQUIRED argument, the directories containing the found Python +# modules are appended to any existing directories in +# @c PythonModules_PYTHONPATH if not already listed.
+# +# @ingroup CMakeFindModules +############################################################################## + +#============================================================================= +# Copyright 2011-2012 University of Pennsylvania +# Copyright 2013-2016 Andreas Schuh +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +include (CMakeParseArguments) + +# ---------------------------------------------------------------------------- +## @brief Find Python module. +# +# If the @c PYTHON_EXECUTABLE variable is set, this function at first tries +# to launch the Python interpreter, import the named Python module, and then +# determines the search path for the Python module from the @c __file__ +# attribute of the loaded module. Otherwise, or if this fails, it looks for +# the Python module in the directories listed in the @c PYTHONPATH variable +# if defined. If this variable is not defined, the @c PYTHONPATH environment +# variable is used instead. +# +# @param [in] CACHEVAR Name of CMake cache variable which stores path of +# directory of the Python module. If not set or if +# the cache entry contains space characters only or +# ends in the string NOTFOUND, this function looks for +# the specified Python module. Otherwise, it does nothing +# and leaves the cache entry unmodified. +# @param [in] ARGN The remaining arguments are parsed and the following +# options extract: +# @par +# +# +# @tp @b NAME module @endtp +# +# +# +# @tp @b PYTHON_EXECUTABLE python @endtp +# +# +# +# @tp @b PATH dir1 [dir2...] @endtp +# +# +# +# @tp @b NO_PYTHONPATH @endtp +# +# +# +# @tp @b NO_DEFAULT_PATH @endtp +# +# +#
Name of the Python module.
Full path of the Python interpreter executable. If not specified +# the global PYTHON_EXECUTABLE CMake variable/cache entry is used.
Directories where to look for Python module.
Do not consider the @c PYTHONPATH environment variable.
Do not look in any default path such as the directories listed by the +# @c PYTHONPATH environment variable.
+# +# @returns Sets the named cache variable of type @c PATH to the absolute path +# of the directory containing the specified Python @p MODULE if found, +# or the string "<MODULE%gt;-NOTFOUND" otherwise. +function (basis_find_python_module CACHEVAR) + # do nothing if path of module already known from previous run + if (DEFINED ${CACHEVAR} AND NOT ${CACHEVAR} MATCHES "NOTFOUND$") + return () + endif () + # parse arguments + CMAKE_PARSE_ARGUMENTS ( + ARGN + "NO_DEFAULT_PATH;NO_PYTHONPATH" + "PYTHON_EXECUTABLE;NAME" + "PATH" + ${ARGN} + ) + if (NOT ARGN_NAME) + message ("basis_find_python_module(): Missing NAME argument!") + endif () + if (ARGN_UNPARSED_ARGUMENTS) + message ("basis_find_python_module(): Invalid arguments: ${ARGN_UNPARSED_ARGUMENTS}") + endif () + if (NOT ARGN_PYTHON_EXECUTABLE) + set (ARGN_PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}") + endif () + if (ARGN_NO_DEFAULT_PATH) + set (ARGN_NO_PYTHONPATH TRUE) + set (ARGN_PYTHON_EXECUTABLE) + endif () + # set initial value of cache entry + if (${CACHEVAR} MATCHES "^ *$") + set (${CACHEVAR} "${ARGN_NAME}-NOTFOUND" CACHE PATH "Directory containing ${ARGN_NAME} Python module/package." FORCE) + else () + set (${CACHEVAR} "${ARGN_NAME}-NOTFOUND" CACHE PATH "Directory containing ${ARGN_NAME} Python module/package.") + endif () + # 1. search specified paths + foreach (P ${ARGN_PATH}) # ignore empty entries + if (IS_ABSOLUTE "${P}") + if (EXISTS "${P}/${ARGN_NAME}.py" OR EXISTS "${P}/${ARGN_NAME}/__init__.py" OR + EXISTS "${P}/${ARGN_NAME}.pyc" OR EXISTS "${P}/${ARGN_NAME}/__init__.pyc") + set_property (CACHE ${CACHEVAR} PROPERTY VALUE "${P}") + return () + endif () + endif () + endforeach () + # 2. get __file__ attribute of module loaded in Python + if (ARGN_PYTHON_EXECUTABLE) + set (IMPORT_SITE_ERROR FALSE) + # 2a. try it with -E option -- the preferred way to run Python + execute_process ( + COMMAND "${ARGN_PYTHON_EXECUTABLE}" -E -c "import ${ARGN_NAME}; print ${ARGN_NAME}.__file__" + RESULT_VARIABLE STATUS + OUTPUT_VARIABLE P + ERROR_VARIABLE ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (ERROR MATCHES "'import site' failed|ImportError: No module named site") + set (IMPORT_SITE_ERROR TRUE) + endif () + # 2b. try it without -E option + if (NOT STATUS EQUAL 0) + execute_process ( + COMMAND "${ARGN_PYTHON_EXECUTABLE}" -c "import ${ARGN_NAME}; print ${ARGN_NAME}.__file__" + RESULT_VARIABLE STATUS + OUTPUT_VARIABLE P + ERROR_VARIABLE ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (ERROR MATCHES "'import site' failed|ImportError: No module named site") + set (IMPORT_SITE_ERROR TRUE) + endif () + if (NOT STATUS EQUAL 0 AND ERROR MATCHES "ImportError: No module named site") + set (PYTHONHOME "$ENV{PYTHONHOME}") + unset (ENV{PYTHONHOME}) + execute_process ( + COMMAND "${ARGN_PYTHON_EXECUTABLE}" -c "import ${ARGN_NAME}; print ${ARGN_NAME}.__file__" + RESULT_VARIABLE STATUS + OUTPUT_VARIABLE P + ERROR_VARIABLE ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (ERROR MATCHES "'import site' failed|ImportError: No module named site") + set (IMPORT_SITE_ERROR TRUE) + endif () + set (ENV{PYTHONHOME} "${PYTHONHOME}") + endif () + endif () + if (STATUS EQUAL 0) + if (P MATCHES "__init__\\.pyc?$") + get_filename_component (P "${P}" PATH) + get_filename_component (P "${P}" PATH) + else () + get_filename_component (P "${P}" PATH) + endif () + set_property (CACHE ${CACHEVAR} PROPERTY VALUE "${P}") + return () + elseif (IMPORT_SITE_ERROR) + message (WARNING "Import of site module failed when running Python interpreter ${ARGN_PYTHON_EXECUTABLE}" + " with and without -E option. Make sure that the Python interpreter is installed properly" + " and that the PYTHONHOME environment variable is either not set (recommended) or at" + " least set correctly for this Python installation. Maybe you need to enable this Python" + " version first somehow if more than one version of Python is installed on your system?" + " Otherwise, set PYTHON_EXECUTABLE to the right Python interpreter executable (python).") + endif () + endif () + # 3. search PYTHONPATH + if (NOT ARGN_NO_PYTHONPATH) + string (REPLACE ":" ";" PYTHONPATH "$ENV{PYTHONPATH}") + foreach (P ${PYTHONPATH}) # ignore empty entries + if (IS_ABSOLUTE "${P}") + if (EXISTS "${P}/${ARGN_NAME}.py" OR EXISTS "${P}/${ARGN_NAME}/__init__.py" OR + EXISTS "${P}/${ARGN_NAME}.pyc" OR EXISTS "${P}/${ARGN_NAME}/__init__.pyc") + set_property (CACHE ${CACHEVAR} PROPERTY VALUE "${P}") + return () + endif () + endif () + endforeach () + endif () +endfunction () + +# ---------------------------------------------------------------------------- +# find Python modules +if (NOT PythonModules_FIND_COMPONENTS AND NOT PythonModules_FIND_OPTIONAL_COMPONENTS) + message (FATAL_ERROR "PythonModules: No (OPTIONAL_)COMPONENTS (i.e., Python module names) specified!") +endif () +if (NOT DEFINED PythonModules_PYTHONPATH) + set (PythonModules_PYTHONPATH) # PYTHONPATH of all found modules +endif () +# helper macro +macro (_PythonModules_find_python_modules ALL_FOUND) + set (${ALL_FOUND} TRUE) + foreach (_PythonModules_MODULE ${ARGN}) + set (_PythonModules_VAR "PythonModules_${_PythonModules_MODULE}_PATH") + set (_PythonModules_TGT "PythonModules_${_PythonModules_MODULE}") + if (PythonModules_DIR) + basis_find_python_module ( + ${_PythonModules_VAR} + NAME "${_PythonModules_MODULE}" + PATH "${PythonModules_DIR}" + NO_DEFAULT_PATH + ) + else () + basis_find_python_module ( + ${_PythonModules_VAR} + NAME "${_PythonModules_MODULE}" + PATH "${PythonModules_DIR}" + ) + endif () + mark_as_advanced (${_PythonModules_VAR}) + if (${_PythonModules_VAR} MATCHES "(^ *|NOTFOUND)$") + set (${ALL_FOUND} FALSE) + set (PythonModules_${_PythonModules_MODULE}_FOUND FALSE) + else () + if (NOT TARGET ${_PythonModules_TGT}) + add_library (${_PythonModules_TGT} UNKNOWN IMPORTED) + set_target_properties ( + ${_PythonModules_TGT} + PROPERTIES + BASIS_TYPE "SCRIPT_LIBRARY" + IMPORTED_LOCATION "${${_PythonModules_VAR}}" + ) + endif () + set (PythonModules_${_PythonModules_MODULE}_FOUND TRUE) + list (APPEND PythonModules_PYTHONPATH "${${_PythonModules_VAR}}") + endif () + endforeach () +endmacro () +# optional first, as PythonModules_FOUND shall be reset to TRUE afterwards +_PythonModules_find_python_modules (PythonModules_FOUND ${PythonModules_FIND_OPTIONAL_COMPONENTS}) +_PythonModules_find_python_modules (PythonModules_FOUND ${PythonModules_FIND_COMPONENTS}) +# remove duplicate paths in PYTHONPATH +if (PythonModules_PYTHONPATH) + list (REMOVE_DUPLICATES PythonModules_PYTHONPATH) +endif () + +# ---------------------------------------------------------------------------- +# handle standard QUIET and REQUIRED arguments +if (NOT PythonModules_FOUND) + # list of modules that were not found + set (_PythonModules_MISSING) + foreach (_PythonModules_MODULE ${PythonModules_FIND_COMPONENTS}) + if (NOT PythonModules_${_PythonModules_MODULE}_FOUND) + list (APPEND _PythonModules_MISSING "${_PythonModules_MODULE}") + endif () + endforeach () + # hint on how to help finding the Python modules + set (_PythonModules_HINT) + if (PYTHON_EXECUTABLE) + set (_PythonModules_HINT "Check if executing ${PYTHON_EXECUTABLE} -c \"import \" works") + else () + set (_PythonModules_HINT "Set PYTHON_EXECUTABLE, e.g., by searching for Python interpreter first") + endif () + if (_PythonModules_HINT) + set (_PythonModules_HINT "${_PythonModules_HINT} or set") + else () + set (_PythonModules_HINT "Set") + endif () + set (_PythonModules_HINT "${_PythonModules_HINT} the PythonModules_DIR variable. Alternatively,") + set (_PythonModules_HINT "${_PythonModules_HINT} set the PythonModules__PATH variable(s)") + set (_PythonModules_HINT "${_PythonModules_HINT} instead or the PYTHONPATH environment variable.") + set (_PythonModules_HINT "${_PythonModules_HINT} Unset PythonModules_DIR if you chose an alternative") + set (_PythonModules_HINT "${_PythonModules_HINT} option and before rerunning CMake again.") + # error message + string (REPLACE ";" ", " ${_PythonModules_MISSING} "${_PythonModules_MISSING}") + message (FATAL_ERROR "Could NOT find the following Python modules:\n${_PythonModules_MISSING}\n${_PythonModules_HINT}") +endif () + +# ---------------------------------------------------------------------------- +# common _DIR variable +if (NOT PythonModules_DIR) + set ( + PythonModules_DIR + "${PythonModules_PYTHONPATH}" + CACHE PATH + "Directory or list of directories separated by ; of installed Python modules." + FORCE + ) +endif () + +# ---------------------------------------------------------------------------- +# clean up +unset (_PythonModules_MODULE) +unset (_PythonModules_VAR) +unset (_PythonModules_VARS) \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d9c6c038..23a85855 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,10 @@ find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter) +find_package(PythonModules COMPONENTS libretro) + +# TODO: Make this required once libretro.py is on PyPI +if (PythonModules_libretro_PATH) + message(STATUS "Found libretro.py: ${PythonModules_libretro_PATH}") +endif() if (NOT RETROARCH) find_program( From 4a9f51b4f7744ca6ae51818bf8013f76e93f50d0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 1 Mar 2024 11:51:40 -0500 Subject: [PATCH 004/234] Update the pr-target-branch-action --- .github/workflows/wrong-branch.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wrong-branch.yaml b/.github/workflows/wrong-branch.yaml index 15874c0a..4d9649d7 100644 --- a/.github/workflows/wrong-branch.yaml +++ b/.github/workflows/wrong-branch.yaml @@ -13,7 +13,7 @@ jobs: issues: write steps: - name: Verify Target Branch - uses: Vankka/pr-target-branch-action@v2 + uses: Vankka/pr-target-branch-action@v2.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From d6b6addfd12016afb66b71718cd8575c8d61d727 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 1 Mar 2024 13:24:19 -0500 Subject: [PATCH 005/234] Add a basic libretro.py test --- test/CMakeLists.txt | 68 +++++++++++++++++++++++++++++++++++++++ test/cmake/Basics.cmake | 4 +++ test/python/core_loads.py | 6 ++++ 3 files changed, 78 insertions(+) create mode 100644 test/cmake/Basics.cmake create mode 100644 test/python/core_loads.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 23a85855..e76eda58 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -244,6 +244,73 @@ function(add_emutest_test) endif() endfunction() +function(add_python_test) + set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NO_SKIP_ERROR_SCREEN DISABLED) + set(oneValueArgs NAME CONTENT TEST_SCRIPT) + set(multiValueArgs CORE_OPTION PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION) + cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") + + set(TEST_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/${RETRO_TEST_SCRIPT}") + add_test( + NAME "${RETRO_NAME}" + COMMAND ${Python3_EXECUTABLE} + "${TEST_SCRIPT}" + "$" + "${RETRO_CONTENT}" + ) + + list(APPEND ENVIROMENT "RETRO_CORE=${PythonModules_libretro_PATH}") + list(APPEND REQUIRED_FILES "$") + list(APPEND REQUIRED_FILES "${TEST_SCRIPT}") + if (RETRO_CONTENT) + list(APPEND REQUIRED_FILES "${RETRO_CONTENT}") + endif() + + if (NOT RETRO_NO_SKIP_ERROR_SCREEN) + list(APPEND ENVIRONMENT MELONDSDS_SKIP_ERROR_SCREEN=1) + endif() + + list(APPEND ENVIRONMENT ${RETRO_CORE_OPTION}) # Not an omission, this is already a list + + macro(expose_system_file SYSFILE) + if (RETRO_${SYSFILE}) + list(APPEND REQUIRED_FILES "${${SYSFILE}}") + list(APPEND ENVIRONMENT "${SYSFILE}=${${SYSFILE}}") + endif() + endmacro() + + expose_system_file(ARM7_BIOS) + expose_system_file(ARM9_BIOS) + expose_system_file(ARM7_DSI_BIOS) + expose_system_file(ARM9_DSI_BIOS) + expose_system_file(NDS_FIRMWARE) + expose_system_file(DSI_FIRMWARE) + expose_system_file(DSI_NAND) + + set_tests_properties("${RETRO_NAME}" PROPERTIES LABELS "libretro.py") + set_tests_properties("${RETRO_NAME}" PROPERTIES ENVIRONMENT "${ENVIRONMENT}") + set_tests_properties("${RETRO_NAME}" PROPERTIES REQUIRED_FILES "${REQUIRED_FILES}") + if (RETRO_PASS_REGULAR_EXPRESSION) + set_tests_properties("${RETRO_NAME}" PROPERTIES PASS_REGULAR_EXPRESSION "${RETRO_PASS_REGULAR_EXPRESSION}") + endif() + + if (RETRO_FAIL_REGULAR_EXPRESSION) + set_tests_properties("${RETRO_NAME}" PROPERTIES FAIL_REGULAR_EXPRESSION "${RETRO_FAIL_REGULAR_EXPRESSION}") + endif() + + if (RETRO_SKIP_REGULAR_EXPRESSION) + set_tests_properties("${RETRO_NAME}" PROPERTIES SKIP_REGULAR_EXPRESSION "${RETRO_SKIP_REGULAR_EXPRESSION}") + endif() + + if (RETRO_WILL_FAIL) + set_tests_properties("${RETRO_NAME}" PROPERTIES WILL_FAIL TRUE) + endif() + + if (RETRO_DISABLED) + set_tests_properties("${RETRO_NAME}" PROPERTIES DISABLED TRUE) + endif() +endfunction() + FetchContent_Declare( godmode9i URL "https://github.com/DS-Homebrew/GodMode9i/releases/download/v3.4.1/GodMode9i.7z" @@ -757,4 +824,5 @@ add_emutest_test( # - Core loads NDS game with GBA game in slot-2 and save data, and can save GBA data # (will need to find a test case besides Pokemon) +include(cmake/Basics.cmake) include(cmake/TestHomebrewSDCards.cmake) \ No newline at end of file diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake new file mode 100644 index 00000000..67aadcd6 --- /dev/null +++ b/test/cmake/Basics.cmake @@ -0,0 +1,4 @@ +add_python_test( + NAME "libretro.py loads melonDS DS" + TEST_SCRIPT "core_loads.py" +) \ No newline at end of file diff --git a/test/python/core_loads.py b/test/python/core_loads.py new file mode 100644 index 00000000..a949b587 --- /dev/null +++ b/test/python/core_loads.py @@ -0,0 +1,6 @@ +from sys import argv +from libretro import Core + +core = Core(argv[1]) + +assert core is not None From d32012c495e3f4e94ac21fb5f795dd4d1afd6b82 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 1 Mar 2024 17:33:56 -0500 Subject: [PATCH 006/234] Rename a test --- test/cmake/Basics.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 67aadcd6..72f0247f 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -1,4 +1,4 @@ add_python_test( - NAME "libretro.py loads melonDS DS" + NAME "melonDS DS loads" TEST_SCRIPT "core_loads.py" ) \ No newline at end of file From 7ca25996aa8883b6c7232ce60eb0e68a45989b1a Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 1 Mar 2024 17:34:13 -0500 Subject: [PATCH 007/234] Use the correct path for test scripts --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e76eda58..ca4db9e5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -250,7 +250,7 @@ function(add_python_test) set(multiValueArgs CORE_OPTION PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION) cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") - set(TEST_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/${RETRO_TEST_SCRIPT}") + set(TEST_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/python/${RETRO_TEST_SCRIPT}") add_test( NAME "${RETRO_NAME}" COMMAND ${Python3_EXECUTABLE} From 854bea3dbd0b5f80193413ea237a61393623e334 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 1 Mar 2024 17:34:33 -0500 Subject: [PATCH 008/234] Add some extra features to Python tests --- test/CMakeLists.txt | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ca4db9e5..a3c2c615 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -246,8 +246,8 @@ endfunction() function(add_python_test) set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NO_SKIP_ERROR_SCREEN DISABLED) - set(oneValueArgs NAME CONTENT TEST_SCRIPT) - set(multiValueArgs CORE_OPTION PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION) + set(oneValueArgs NAME CONTENT TEST_SCRIPT SKIP_RETURN_CODE TIMEOUT) + set(multiValueArgs CORE_OPTION DEPENDS PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION LABELS) cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") set(TEST_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/python/${RETRO_TEST_SCRIPT}") @@ -287,7 +287,7 @@ function(add_python_test) expose_system_file(DSI_FIRMWARE) expose_system_file(DSI_NAND) - set_tests_properties("${RETRO_NAME}" PROPERTIES LABELS "libretro.py") + set_tests_properties("${RETRO_NAME}" PROPERTIES LABELS "libretro.py;${RETRO_LABELS}") set_tests_properties("${RETRO_NAME}" PROPERTIES ENVIRONMENT "${ENVIRONMENT}") set_tests_properties("${RETRO_NAME}" PROPERTIES REQUIRED_FILES "${REQUIRED_FILES}") if (RETRO_PASS_REGULAR_EXPRESSION) @@ -309,6 +309,20 @@ function(add_python_test) if (RETRO_DISABLED) set_tests_properties("${RETRO_NAME}" PROPERTIES DISABLED TRUE) endif() + + if (RETRO_SKIP_RETURN_CODE) + set_tests_properties("${RETRO_NAME}" PROPERTIES SKIP_RETURN_CODE "${RETRO_SKIP_RETURN_CODE}") + endif() + + if (RETRO_DEPENDS) + set_tests_properties("${RETRO_NAME}" PROPERTIES DEPENDS "${RETRO_DEPENDS}") + endif() + + if (RETRO_TIMEOUT) + set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT "${RETRO_TIMEOUT}") + else() + set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT 10) + endif() endfunction() FetchContent_Declare( From fe8d1741d33052c134bd8181f434badce004ca9b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 1 Mar 2024 17:34:46 -0500 Subject: [PATCH 009/234] Add some extra tests --- test/cmake/Basics.cmake | 10 ++++++++++ test/python/core_sets_callbacks.py | 11 +++++++++++ test/python/core_system_info_valid.py | 9 +++++++++ 3 files changed, 30 insertions(+) create mode 100644 test/python/core_sets_callbacks.py create mode 100644 test/python/core_system_info_valid.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 72f0247f..2f72acb0 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -1,4 +1,14 @@ add_python_test( NAME "melonDS DS loads" TEST_SCRIPT "core_loads.py" +) + +add_python_test( + NAME "Core system info is valid" + TEST_SCRIPT "core_system_info_valid.py" +) + +add_python_test( + NAME "Core sets frontend callbacks" + TEST_SCRIPT "core_sets_callbacks.py" ) \ No newline at end of file diff --git a/test/python/core_sets_callbacks.py b/test/python/core_sets_callbacks.py new file mode 100644 index 00000000..a0509b9c --- /dev/null +++ b/test/python/core_sets_callbacks.py @@ -0,0 +1,11 @@ +from sys import argv +from libretro import Core + +core = Core(argv[1]) + +core.set_video_refresh(lambda data, width, height, pitch: None) +core.set_audio_sample(lambda left, right: None) +core.set_audio_sample_batch(lambda data, frames: 0) +core.set_input_poll(lambda: None) +core.set_input_state(lambda port, device, index, id: 0) +core.set_environment(lambda cmd, data: False) \ No newline at end of file diff --git a/test/python/core_system_info_valid.py b/test/python/core_system_info_valid.py new file mode 100644 index 00000000..488162ae --- /dev/null +++ b/test/python/core_system_info_valid.py @@ -0,0 +1,9 @@ +from sys import argv +from libretro import Core + +core = Core(argv[1]) + +system_info = core.get_system_info() + +assert system_info is not None +assert b"melonDS DS" == system_info.library_name From df0d595ca715ca530e5a035681ea8dbd16f60f73 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 4 Mar 2024 13:26:07 -0500 Subject: [PATCH 010/234] Fix a typo --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a3c2c615..d8b1ef27 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -259,7 +259,7 @@ function(add_python_test) "${RETRO_CONTENT}" ) - list(APPEND ENVIROMENT "RETRO_CORE=${PythonModules_libretro_PATH}") + list(APPEND ENVIRONMENT "RETRO_CORE=${PythonModules_libretro_PATH}") list(APPEND REQUIRED_FILES "$") list(APPEND REQUIRED_FILES "${TEST_SCRIPT}") if (RETRO_CONTENT) From 54e75e2146cc78483e3ba9885cec4aa060e2ddb0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 4 Mar 2024 16:42:00 -0500 Subject: [PATCH 011/234] Add and update some test scripts --- test/python/basics/core_exposes_ram.py | 14 ++++++++++++++ test/python/basics/core_exposes_sram.py | 14 ++++++++++++++ test/python/basics/core_generates_audio.py | 11 +++++++++++ test/python/basics/core_generates_video.py | 16 ++++++++++++++++ test/python/basics/core_get_proc_address.py | 17 +++++++++++++++++ test/python/{ => basics}/core_loads.py | 0 test/python/basics/core_logs_output.py | 8 ++++++++ test/python/basics/core_run_frame.py | 5 +++++ test/python/basics/core_run_frames.py | 6 ++++++ test/python/{ => basics}/core_sets_callbacks.py | 0 .../{ => basics}/core_system_info_valid.py | 0 11 files changed, 91 insertions(+) create mode 100644 test/python/basics/core_exposes_ram.py create mode 100644 test/python/basics/core_exposes_sram.py create mode 100644 test/python/basics/core_generates_audio.py create mode 100644 test/python/basics/core_generates_video.py create mode 100644 test/python/basics/core_get_proc_address.py rename test/python/{ => basics}/core_loads.py (100%) create mode 100644 test/python/basics/core_logs_output.py create mode 100644 test/python/basics/core_run_frame.py create mode 100644 test/python/basics/core_run_frames.py rename test/python/{ => basics}/core_sets_callbacks.py (100%) rename test/python/{ => basics}/core_system_info_valid.py (100%) diff --git a/test/python/basics/core_exposes_ram.py b/test/python/basics/core_exposes_ram.py new file mode 100644 index 00000000..efb11b7c --- /dev/null +++ b/test/python/basics/core_exposes_ram.py @@ -0,0 +1,14 @@ +from sys import argv +from libretro import default_session, RETRO_MEMORY_SYSTEM_RAM + +with default_session(argv[1]) as session: + size = session.core.get_memory_size(RETRO_MEMORY_SYSTEM_RAM) + + assert size is not None + assert size > 0 + + data = session.core.get_memory_data(RETRO_MEMORY_SYSTEM_RAM) + + assert data + assert data is not None + assert data.value diff --git a/test/python/basics/core_exposes_sram.py b/test/python/basics/core_exposes_sram.py new file mode 100644 index 00000000..27d04211 --- /dev/null +++ b/test/python/basics/core_exposes_sram.py @@ -0,0 +1,14 @@ +from sys import argv +from libretro import default_session, RETRO_MEMORY_SAVE_RAM + +with default_session(argv[1]) as session: + size = session.core.get_memory_size(RETRO_MEMORY_SAVE_RAM) + + assert size is not None + assert size > 0 + + data = session.core.get_memory_data(RETRO_MEMORY_SAVE_RAM) + + assert data + assert data is not None + assert data.value diff --git a/test/python/basics/core_generates_audio.py b/test/python/basics/core_generates_audio.py new file mode 100644 index 00000000..44c6b8ac --- /dev/null +++ b/test/python/basics/core_generates_audio.py @@ -0,0 +1,11 @@ +from sys import argv +from libretro import default_session + +with default_session(argv[1]) as session: + for i in range(300): + session.core.run() + + assert session.audio.buffer is not None + assert len(session.audio.buffer) > 0 + + assert any(b != 0 for b in session.audio.buffer) \ No newline at end of file diff --git a/test/python/basics/core_generates_video.py b/test/python/basics/core_generates_video.py new file mode 100644 index 00000000..1e092542 --- /dev/null +++ b/test/python/basics/core_generates_video.py @@ -0,0 +1,16 @@ +from array import array +from sys import argv +from libretro import default_session + +with default_session(argv[1]) as session: + for i in range(10): + session.core.run() + + assert session.video.frame is not None + + frame1 = array(session.video.frame) + + for i in range(60): + session.core.run() + + assert session.video.frame != frame1 diff --git a/test/python/basics/core_get_proc_address.py b/test/python/basics/core_get_proc_address.py new file mode 100644 index 00000000..dcdc2745 --- /dev/null +++ b/test/python/basics/core_get_proc_address.py @@ -0,0 +1,17 @@ +from ctypes import * +from sys import argv +from libretro import default_session, retro_get_proc_address_t + +with default_session(argv[1]) as env: + proc_address_callback = env.proc_address_callback + assert proc_address_callback is not None + + get_proc_address: retro_get_proc_address_t = proc_address_callback.get_proc_address + assert get_proc_address is not None + + add_integers = get_proc_address("libretropy_add_integers") + assert add_integers is not None + + add_integers_callable = cast(add_integers, CFUNCTYPE(c_int, c_int, c_int)) + + assert add_integers_callable(1, 2) == 3 diff --git a/test/python/core_loads.py b/test/python/basics/core_loads.py similarity index 100% rename from test/python/core_loads.py rename to test/python/basics/core_loads.py diff --git a/test/python/basics/core_logs_output.py b/test/python/basics/core_logs_output.py new file mode 100644 index 00000000..77d43025 --- /dev/null +++ b/test/python/basics/core_logs_output.py @@ -0,0 +1,8 @@ +from sys import argv +from libretro import default_session + + +with default_session(argv[1]) as env: + assert env.log is not None + assert env.log.entries is not None + assert len(env.log.entries) > 0 diff --git a/test/python/basics/core_run_frame.py b/test/python/basics/core_run_frame.py new file mode 100644 index 00000000..5a62e405 --- /dev/null +++ b/test/python/basics/core_run_frame.py @@ -0,0 +1,5 @@ +from sys import argv +from libretro import default_session + +with default_session(argv[1]) as session: + session.core.run() diff --git a/test/python/basics/core_run_frames.py b/test/python/basics/core_run_frames.py new file mode 100644 index 00000000..36136812 --- /dev/null +++ b/test/python/basics/core_run_frames.py @@ -0,0 +1,6 @@ +from sys import argv +from libretro import default_session + +with default_session(argv[1]) as session: + for i in range(300): + session.core.run() diff --git a/test/python/core_sets_callbacks.py b/test/python/basics/core_sets_callbacks.py similarity index 100% rename from test/python/core_sets_callbacks.py rename to test/python/basics/core_sets_callbacks.py diff --git a/test/python/core_system_info_valid.py b/test/python/basics/core_system_info_valid.py similarity index 100% rename from test/python/core_system_info_valid.py rename to test/python/basics/core_system_info_valid.py From 496b4cbb9434310084e3f5ff0d7103627310c9d4 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 4 Mar 2024 17:32:47 -0500 Subject: [PATCH 012/234] Add some tests --- test/cmake/Basics.cmake | 90 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 2f72acb0..60b9f573 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -1,14 +1,96 @@ add_python_test( NAME "melonDS DS loads" - TEST_SCRIPT "core_loads.py" + TEST_SCRIPT "basics/core_loads.py" ) add_python_test( NAME "Core system info is valid" - TEST_SCRIPT "core_system_info_valid.py" + TEST_SCRIPT "basics/core_system_info_valid.py" ) add_python_test( NAME "Core sets frontend callbacks" - TEST_SCRIPT "core_sets_callbacks.py" -) \ No newline at end of file + TEST_SCRIPT "basics/core_sets_callbacks.py" +) + +add_python_test( + NAME "Core implements get_proc_address" + TEST_SCRIPT "basics/core_get_proc_address.py" +) + +#add_python_test( +# NAME "Core runs init and deinit" +# TEST_SCRIPT "" +#) + +#add_python_test( +# NAME "Core loads and unloads without content" +# TEST_SCRIPT "" +#) + +#add_python_test( +# NAME "Core loads and unloads content" +# TEST_SCRIPT "" +#) +# +#add_python_test( +# NAME "Core loads and unloads subsystem content" +# TEST_SCRIPT "" +#) +# + +add_python_test( + NAME "Core runs for one frame" + TEST_SCRIPT "basics/core_run_frame.py" +) + +add_python_test( + NAME "Core runs for multiple frames" + TEST_SCRIPT "basics/core_run_frames.py" +) + +add_python_test( + NAME "Core generates audio" + TEST_SCRIPT "basics/core_generates_audio.py" +) + +add_python_test( + NAME "Core generates video" + TEST_SCRIPT "basics/core_generates_video.py" +) + +#add_python_test( +# NAME "Core accepts button input" +# TEST_SCRIPT "basics/core_accepts_button_input.py" +#) + +# +#add_python_test( +# NAME "Core accepts analog input" +# TEST_SCRIPT "" +#) +# +#add_python_test( +# NAME "Core accepts pointer input" +# TEST_SCRIPT "" +#) + +add_python_test( + NAME "Core exposes emulated RAM" + TEST_SCRIPT "basics/core_exposes_ram.py" +) + +add_python_test( + NAME "Core exposes emulated SRAM" + TEST_SCRIPT "basics/core_exposes_sram.py" +) + +add_python_test( + NAME "Core logs output" + TEST_SCRIPT "basics/core_logs_output.py" +) + +#add_python_test( +# NAME "Core accepts microphone input" +# TEST_SCRIPT "" +#) \ No newline at end of file From 275db2a0517248b3fe75fd6cafb857ad7b7bc80e Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 4 Mar 2024 17:33:22 -0500 Subject: [PATCH 013/234] Use `RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK` - For tests, primarily --- src/libretro/environment.cpp | 2 ++ src/libretro/libretro.cpp | 10 ++++++++++ src/libretro/libretro.hpp | 1 + 3 files changed, 13 insertions(+) diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index e8d84676..4b8f0f2e 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -715,6 +715,8 @@ PUBLIC_SYMBOL void retro_set_environment(retro_environment_t cb) { retro::debug("retro_set_environment({})", fmt::ptr(cb)); } + environment(RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK, (void*) MelonDsDs::GetProcAddress); + retro::_supports_bitmasks |= environment(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, nullptr); retro::_supportsPowerStatus |= environment(RETRO_ENVIRONMENT_GET_DEVICE_POWER, nullptr); diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index 5dcfe28b..06a0daed 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -335,4 +335,14 @@ void Platform::WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writ MelonDsDs::Core.WriteFirmware(firmware, writeoffset, writelen); } +extern "C" int libretropy_add_integers(int a, int b) { + return a + b; +} + +retro_proc_address_t MelonDsDs::GetProcAddress(const char* sym) noexcept { + if (sym == "libretropy_add_integers") + return reinterpret_cast(libretropy_add_integers); + + return nullptr; +} diff --git a/src/libretro/libretro.hpp b/src/libretro/libretro.hpp index a75bc287..dcae3e8a 100644 --- a/src/libretro/libretro.hpp +++ b/src/libretro/libretro.hpp @@ -39,6 +39,7 @@ namespace MelonDsDs { void HardwareContextReset() noexcept; void HardwareContextDestroyed() noexcept; bool UpdateOptionVisibility() noexcept; + retro_proc_address_t GetProcAddress(const char* sym) noexcept; } #endif //MELONDS_DS_LIBRETRO_HPP From 8a4b683079e82563c84328f035e35c4c01260181 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 4 Mar 2024 17:58:03 -0500 Subject: [PATCH 014/234] Remove an unused environment variable --- test/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d8b1ef27..2a3c194c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -259,7 +259,6 @@ function(add_python_test) "${RETRO_CONTENT}" ) - list(APPEND ENVIRONMENT "RETRO_CORE=${PythonModules_libretro_PATH}") list(APPEND REQUIRED_FILES "$") list(APPEND REQUIRED_FILES "${TEST_SCRIPT}") if (RETRO_CONTENT) From c3e092275bc3af67509a64760a1cc7244bc97c55 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 4 Mar 2024 19:25:14 -0500 Subject: [PATCH 015/234] Implement some more test scripts --- test/cmake/Basics.cmake | 28 ++++++++++--------- .../basics/core_loads_unloads_with_content.py | 6 ++++ .../core_loads_unloads_without_content.py | 6 ++++ test/python/basics/core_run_init_deinit.py | 5 ++++ 4 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 test/python/basics/core_loads_unloads_with_content.py create mode 100644 test/python/basics/core_loads_unloads_without_content.py create mode 100644 test/python/basics/core_run_init_deinit.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 60b9f573..f25ebe5f 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -18,23 +18,25 @@ add_python_test( TEST_SCRIPT "basics/core_get_proc_address.py" ) -#add_python_test( -# NAME "Core runs init and deinit" -# TEST_SCRIPT "" -#) +add_python_test( + NAME "Core runs init and deinit" + TEST_SCRIPT "basics/core_run_init_deinit.py" +) -#add_python_test( -# NAME "Core loads and unloads without content" -# TEST_SCRIPT "" -#) +add_python_test( + NAME "Core loads and unloads without content" + TEST_SCRIPT "basics/core_loads_unloads_without_content.py" +) + +add_python_test( + NAME "Core loads and unloads with content" + TEST_SCRIPT "basics/core_loads_unloads_with_content.py" + CONTENT "${NDS_ROM}" +) -#add_python_test( -# NAME "Core loads and unloads content" -# TEST_SCRIPT "" -#) # #add_python_test( -# NAME "Core loads and unloads subsystem content" +# NAME "Core loads and unloads with subsystem content" # TEST_SCRIPT "" #) # diff --git a/test/python/basics/core_loads_unloads_with_content.py b/test/python/basics/core_loads_unloads_with_content.py new file mode 100644 index 00000000..1ea58fdc --- /dev/null +++ b/test/python/basics/core_loads_unloads_with_content.py @@ -0,0 +1,6 @@ +from sys import argv +from libretro import default_session + +with default_session(argv[1], argv[2]) as session: + for i in range(10): + session.core.run() diff --git a/test/python/basics/core_loads_unloads_without_content.py b/test/python/basics/core_loads_unloads_without_content.py new file mode 100644 index 00000000..c3940c6d --- /dev/null +++ b/test/python/basics/core_loads_unloads_without_content.py @@ -0,0 +1,6 @@ +from sys import argv +from libretro import default_session + +with default_session(argv[1]) as session: + for i in range(10): + session.core.run() diff --git a/test/python/basics/core_run_init_deinit.py b/test/python/basics/core_run_init_deinit.py new file mode 100644 index 00000000..1665795e --- /dev/null +++ b/test/python/basics/core_run_init_deinit.py @@ -0,0 +1,5 @@ +from sys import argv +from libretro import default_session + +with default_session(argv[1]) as session: + pass From 00320851166a77ac55ac451fbe2c466d9e410ed4 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 5 Mar 2024 16:42:36 -0500 Subject: [PATCH 016/234] Print directly to stderr (without fmt) if no log interface is available --- src/libretro/environment.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index 4b8f0f2e..1dbcf0bd 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -295,16 +295,17 @@ constexpr uint32_t GetLogColor(retro_log_level level) noexcept { #endif void retro::fmt_log(retro_log_level level, fmt::string_view fmt, fmt::format_args args) noexcept { - if (_log) { - fmt::basic_memory_buffer buffer; - fmt::vformat_to(std::back_inserter(buffer), fmt, args); - // We can't pass the va_list directly to the libretro callback, - // so we have to construct the string and print that + fmt::basic_memory_buffer buffer; + fmt::vformat_to(std::back_inserter(buffer), fmt, args); + // We can't pass the va_list directly to the libretro callback, + // so we have to construct the string and print that - if (buffer[buffer.size() - 1] == '\n') - buffer[buffer.size() - 1] = '\0'; + if (buffer[buffer.size() - 1] == '\n') + buffer[buffer.size() - 1] = '\0'; - buffer.push_back('\0'); + buffer.push_back('\0'); + + if (_log) { _log(level, "%s\n", buffer.data()); #ifdef TRACY_ENABLE if (tracy::ProfilerAvailable()) { @@ -312,7 +313,7 @@ void retro::fmt_log(retro_log_level level, fmt::string_view fmt, fmt::format_arg } #endif } else { - fmt::vprint(stderr, fmt, args); + fprintf(stderr, "%s\n", buffer.data()); } } From 06c0585554bfa664bc9db8b330f84c4d5a77946a Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 5 Mar 2024 16:47:45 -0500 Subject: [PATCH 017/234] Check info->path for nullptr --- src/libretro/libretro.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index 06a0daed..ab90c585 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -87,7 +87,7 @@ PUBLIC_SYMBOL bool retro_load_game(const struct retro_game_info *info) { ZoneScopedN(TracyFunction); if (info) { ZoneText(info->path, strlen(info->path)); - retro::debug("retro_load_game(\"{}\", {})", info->path, info->size); + retro::debug("retro_load_game(\"{}\", {})", info->path ? info->path : "", info->size); } else { retro::debug("retro_load_game()"); From b642690df9353ec2c0148bd87a1274382ffa9bba Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 5 Mar 2024 16:47:54 -0500 Subject: [PATCH 018/234] Log when we can't get the log interface --- src/libretro/environment.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index 1dbcf0bd..392f3cbd 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -714,6 +714,8 @@ PUBLIC_SYMBOL void retro_set_environment(retro_environment_t cb) { if (environment(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log_callback)) { retro::_log = log_callback.log; retro::debug("retro_set_environment({})", fmt::ptr(cb)); + } else { + retro::warn("Failed to get log interface"); } environment(RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK, (void*) MelonDsDs::GetProcAddress); From 327dd78d9e76a4df868ffab0ad2bef619233f858 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 5 Mar 2024 16:58:45 -0500 Subject: [PATCH 019/234] Fix an incorrect format specifier - Mixed up stdio and fmt --- src/libretro/environment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index 392f3cbd..5e0e30b4 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -458,7 +458,7 @@ bool retro::set_message(const struct retro_message_ext& message) { return false; } - fmt_log(message.level, "%s", fmt::make_format_args(message.msg)); + fmt_log(message.level, "{}", fmt::make_format_args(message.msg)); return true; } default: From 1c32a2538896fab49313a9c77c2a5c7ccf902e07 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 5 Mar 2024 17:11:06 -0500 Subject: [PATCH 020/234] Set some default values in `CoreConfig` --- src/libretro/config/config.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libretro/config/config.hpp b/src/libretro/config/config.hpp index f8ecf6be..c305a0fb 100644 --- a/src/libretro/config/config.hpp +++ b/src/libretro/config/config.hpp @@ -441,12 +441,12 @@ namespace MelonDsDs { unsigned _flushDelay = 120; // TODO: Make configurable unsigned _numberOfScreenLayouts = 1; std::array _screenLayouts; - unsigned _screenGap; - unsigned _hybridRatio; + unsigned _screenGap = 0; + unsigned _hybridRatio = 2; HybridSideScreenDisplay _smallScreenLayout; unsigned _cursorSize = 2.0f; MelonDsDs::CursorMode _cursorMode = CursorMode::Always; - unsigned _cursorTimeout; + unsigned _cursorTimeout = 3; MelonDsDs::TouchMode _touchMode; MelonDsDs::ConsoleType _consoleType; MelonDsDs::BootMode _bootMode; From a346fdb7266982f7417ffc2a33ba1a5200ba636d Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 6 Mar 2024 18:09:41 -0500 Subject: [PATCH 021/234] Flesh out some tests --- test/CMakeLists.txt | 7 +- test/cmake/Basics.cmake | 267 +++++++++++++++--- test/python/__init__.py | 0 test/python/basics/__init__.py | 0 .../basics/core_accepts_button_input.py | 26 ++ .../python/basics/core_api_version_correct.py | 6 + test/python/basics/core_generates_video.py | 17 +- test/python/basics/core_logs_output.py | 12 +- test/python/basics/core_region_correct.py | 7 + .../core_registers_no_content_support.py | 7 + .../basics/core_system_av_info_correct.py | 10 + test/python/prelude.py | 29 ++ 12 files changed, 331 insertions(+), 57 deletions(-) create mode 100644 test/python/__init__.py create mode 100644 test/python/basics/__init__.py create mode 100644 test/python/basics/core_accepts_button_input.py create mode 100644 test/python/basics/core_api_version_correct.py create mode 100644 test/python/basics/core_region_correct.py create mode 100644 test/python/basics/core_registers_no_content_support.py create mode 100644 test/python/basics/core_system_av_info_correct.py create mode 100644 test/python/prelude.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2a3c194c..093f1d86 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -246,21 +246,19 @@ endfunction() function(add_python_test) set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NO_SKIP_ERROR_SCREEN DISABLED) - set(oneValueArgs NAME CONTENT TEST_SCRIPT SKIP_RETURN_CODE TIMEOUT) + set(oneValueArgs NAME CONTENT TEST_MODULE SKIP_RETURN_CODE TIMEOUT) set(multiValueArgs CORE_OPTION DEPENDS PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION LABELS) cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") - set(TEST_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/python/${RETRO_TEST_SCRIPT}") add_test( NAME "${RETRO_NAME}" COMMAND ${Python3_EXECUTABLE} - "${TEST_SCRIPT}" + -m "${RETRO_TEST_MODULE}" "$" "${RETRO_CONTENT}" ) list(APPEND REQUIRED_FILES "$") - list(APPEND REQUIRED_FILES "${TEST_SCRIPT}") if (RETRO_CONTENT) list(APPEND REQUIRED_FILES "${RETRO_CONTENT}") endif() @@ -289,6 +287,7 @@ function(add_python_test) set_tests_properties("${RETRO_NAME}" PROPERTIES LABELS "libretro.py;${RETRO_LABELS}") set_tests_properties("${RETRO_NAME}" PROPERTIES ENVIRONMENT "${ENVIRONMENT}") set_tests_properties("${RETRO_NAME}" PROPERTIES REQUIRED_FILES "${REQUIRED_FILES}") + set_tests_properties("${RETRO_NAME}" PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/python") if (RETRO_PASS_REGULAR_EXPRESSION) set_tests_properties("${RETRO_NAME}" PROPERTIES PASS_REGULAR_EXPRESSION "${RETRO_PASS_REGULAR_EXPRESSION}") endif() diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index f25ebe5f..9921d6ea 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -1,98 +1,275 @@ +# Basic Functionality + add_python_test( - NAME "melonDS DS loads" - TEST_SCRIPT "basics/core_loads.py" + NAME "Core loads" + TEST_MODULE basics.core_loads ) add_python_test( - NAME "Core system info is valid" - TEST_SCRIPT "basics/core_system_info_valid.py" + NAME "Core sets frontend callbacks" + TEST_MODULE basics.core_sets_callbacks ) add_python_test( - NAME "Core sets frontend callbacks" - TEST_SCRIPT "basics/core_sets_callbacks.py" + NAME "Core API version is 1" + TEST_MODULE basics.core_api_version_correct ) add_python_test( - NAME "Core implements get_proc_address" - TEST_SCRIPT "basics/core_get_proc_address.py" + NAME "Core system info is correct" + TEST_MODULE basics.core_system_info_valid ) add_python_test( - NAME "Core runs init and deinit" - TEST_SCRIPT "basics/core_run_init_deinit.py" + NAME "Core system AV info is correct" + TEST_MODULE basics.core_system_av_info_correct ) add_python_test( - NAME "Core loads and unloads without content" - TEST_SCRIPT "basics/core_loads_unloads_without_content.py" + NAME "Core region is correct" + TEST_MODULE basics.core_region_correct ) add_python_test( - NAME "Core loads and unloads with content" - TEST_SCRIPT "basics/core_loads_unloads_with_content.py" + NAME "Core runs init and deinit" + TEST_MODULE "basics.core_run_init_deinit" CONTENT "${NDS_ROM}" ) -# -#add_python_test( -# NAME "Core loads and unloads with subsystem content" -# TEST_SCRIPT "" -#) -# +add_python_test( + NAME "Core loads and unloads with content" + TEST_MODULE "basics.core_loads_unloads_with_content" + CONTENT "${NDS_ROM}" +) add_python_test( NAME "Core runs for one frame" - TEST_SCRIPT "basics/core_run_frame.py" + TEST_MODULE "basics.core_run_frame" ) add_python_test( NAME "Core runs for multiple frames" - TEST_SCRIPT "basics/core_run_frames.py" + TEST_MODULE "basics.core_run_frames" +) + +add_python_test( + NAME "Core resets emulator state" + TEST_MODULE "" ) add_python_test( NAME "Core generates audio" - TEST_SCRIPT "basics/core_generates_audio.py" + TEST_MODULE "basics.core_generates_audio" ) add_python_test( NAME "Core generates video" - TEST_SCRIPT "basics/core_generates_video.py" + TEST_MODULE "basics.core_generates_video" +) + +add_python_test( + NAME "Core accepts button input" + TEST_MODULE "basics.core_accepts_button_input" +) + +add_python_test( + NAME "Core accepts analog input" + TEST_MODULE "" ) -#add_python_test( -# NAME "Core accepts button input" -# TEST_SCRIPT "basics/core_accepts_button_input.py" -#) +add_python_test( + NAME "Core accepts pointer input" + TEST_MODULE "" +) + +add_python_test( + NAME "Core saves state" + TEST_MODULE "basics.core_saves_state" +) -# -#add_python_test( -# NAME "Core accepts analog input" -# TEST_SCRIPT "" -#) -# -#add_python_test( -# NAME "Core accepts pointer input" -# TEST_SCRIPT "" -#) +add_python_test( + NAME "Core saves and loads state" + TEST_MODULE "basics.core_saves_and_loads_state" +) add_python_test( NAME "Core exposes emulated RAM" - TEST_SCRIPT "basics/core_exposes_ram.py" + TEST_MODULE "basics.core_exposes_ram" ) add_python_test( NAME "Core exposes emulated SRAM" - TEST_SCRIPT "basics/core_exposes_sram.py" + TEST_MODULE "basics.core_exposes_sram" +) + +add_python_test( + NAME "Core applies cheats" + TEST_MODULE "" +) + +# Environment Calls +# (these also serve as good tests for libretro.py itself) + +add_python_test( + NAME "Core rotates the screen" + TEST_MODULE "" +) + +add_python_test( + NAME "Core can send messages (API V0)" + TEST_MODULE "" +) + +add_python_test( + NAME "Core can shut down internally" + TEST_MODULE "" +) + +add_python_test( + NAME "Core gets the frontend's system directory" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sets pixel format to XRGB8888" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sets input descriptors" + TEST_MODULE "" +) + +add_python_test( + NAME "Core can fetch option values" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sets options (V0 API)" + TEST_MODULE "" +) + +add_python_test( + NAME "Core tests for option updates" + TEST_MODULE "" +) + +add_python_test( + NAME "Core registers support for no-content mode" + TEST_MODULE "basics.core_registers_no_content_support" + CONTENT "${NDS_ROM}" +) + +add_python_test( + NAME "Core loads and unloads without content" + TEST_MODULE "basics.core_loads_unloads_without_content" ) add_python_test( NAME "Core logs output" - TEST_SCRIPT "basics/core_logs_output.py" + TEST_MODULE "basics.core_logs_output" + ARM7_BIOS + ARM9_BIOS + NDS_FIRMWARE +) + +add_python_test( + NAME "Core gets the frontend's save directory" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sets retro_get_proc_address_interface" + TEST_MODULE "basics.core_get_proc_address" + CONTENT "${NDS_ROM}" +) + +add_python_test( + NAME "Core defines subsystems" + TEST_MODULE "" +) + +add_python_test( + NAME "Core loads and unloads with subsystem content" + TEST_MODULE "" +) + +add_python_test( + NAME "Core defines controller info" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sets geometry at runtime" + TEST_MODULE "" +) + +add_python_test( + NAME "Core registers support for achievements" + TEST_MODULE "" ) -#add_python_test( -# NAME "Core accepts microphone input" -# TEST_SCRIPT "" -#) \ No newline at end of file +add_python_test( + NAME "Core gets VFS interface" + TEST_MODULE "" +) + +add_python_test( + NAME "Core gets input bitmask support" + TEST_MODULE "" +) + +add_python_test( + NAME "Core gets options API version" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sets options (V1 API)" + TEST_MODULE "" +) + +add_python_test( + NAME "Core can set options visibility" + TEST_MODULE "" +) + +add_python_test( + NAME "Core gets message API version" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sends messages (API V1)" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sets content info overrides" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sets options (V2 API)" + TEST_MODULE "" +) + +add_python_test( + NAME "Core sets options visibility display update callback" + TEST_MODULE "" +) + +add_python_test( + NAME "Core accepts microphone input" + TEST_MODULE "" +) + +add_python_test( + NAME "Core queries device power state" + TEST_MODULE "" +) + + + + diff --git a/test/python/__init__.py b/test/python/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/basics/__init__.py b/test/python/basics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/basics/core_accepts_button_input.py b/test/python/basics/core_accepts_button_input.py new file mode 100644 index 00000000..b6d80ece --- /dev/null +++ b/test/python/basics/core_accepts_button_input.py @@ -0,0 +1,26 @@ +import itertools +from sys import argv + +import libretro.callback.input +from libretro import default_session +from libretro.callback.input import JoypadState + + +def generate_input(): + yield from itertools.repeat(0, 180) + + yield JoypadState(a=True) + + yield from itertools.repeat(0) + + +with default_session(argv[1]) as session: + + for i in range(180): + session.core.run() + + # TODO: Take screenshot + # TODO: Emulate button press + + for i in range(180): + session.core.run() diff --git a/test/python/basics/core_api_version_correct.py b/test/python/basics/core_api_version_correct.py new file mode 100644 index 00000000..a8f280e4 --- /dev/null +++ b/test/python/basics/core_api_version_correct.py @@ -0,0 +1,6 @@ +from sys import argv +from libretro import Core + +core = Core(argv[1]) + +assert core.api_version() == 1 diff --git a/test/python/basics/core_generates_video.py b/test/python/basics/core_generates_video.py index 1e092542..e30eaa7e 100644 --- a/test/python/basics/core_generates_video.py +++ b/test/python/basics/core_generates_video.py @@ -1,16 +1,25 @@ from array import array from sys import argv +from typing import cast + from libretro import default_session +from libretro.callback.video import SoftwareVideoState with default_session(argv[1]) as session: - for i in range(10): + video = cast(SoftwareVideoState, session.video) + + assert isinstance(video, SoftwareVideoState) + + for i in range(60): session.core.run() - assert session.video.frame is not None + assert video.frame is not None - frame1 = array(session.video.frame) + frame1 = array(video.frame.typecode, video.frame) for i in range(60): session.core.run() - assert session.video.frame != frame1 + frame2 = array(video.frame.typecode, video.frame) + + assert frame1 != frame2 diff --git a/test/python/basics/core_logs_output.py b/test/python/basics/core_logs_output.py index 77d43025..d3ef559a 100644 --- a/test/python/basics/core_logs_output.py +++ b/test/python/basics/core_logs_output.py @@ -1,8 +1,12 @@ from sys import argv +from typing import cast from libretro import default_session +from libretro.callback.log import StandardLogger +import prelude -with default_session(argv[1]) as env: - assert env.log is not None - assert env.log.entries is not None - assert len(env.log.entries) > 0 +with default_session(argv[1], system_dir=prelude.core_system_dir) as session: + log: StandardLogger = cast(StandardLogger, session.log) + assert log is not None + assert log.records is not None + assert len(log.records) > 0 diff --git a/test/python/basics/core_region_correct.py b/test/python/basics/core_region_correct.py new file mode 100644 index 00000000..72bd6288 --- /dev/null +++ b/test/python/basics/core_region_correct.py @@ -0,0 +1,7 @@ +from sys import argv +from libretro import Core +from libretro.defs import Region + +core = Core(argv[1]) + +assert core.get_region() == Region.NTSC diff --git a/test/python/basics/core_registers_no_content_support.py b/test/python/basics/core_registers_no_content_support.py new file mode 100644 index 00000000..8897192d --- /dev/null +++ b/test/python/basics/core_registers_no_content_support.py @@ -0,0 +1,7 @@ +from sys import argv +from libretro import default_session + +import prelude + +with default_session(argv[1], argv[2]) as env: + assert env.support_no_game is True diff --git a/test/python/basics/core_system_av_info_correct.py b/test/python/basics/core_system_av_info_correct.py new file mode 100644 index 00000000..01a79654 --- /dev/null +++ b/test/python/basics/core_system_av_info_correct.py @@ -0,0 +1,10 @@ +from sys import argv + +from libretro import default_session + +with default_session(argv[1]) as session: + av_info = session.core.get_system_av_info() + + assert av_info is not None + assert av_info.timing.sample_rate != 0 + assert av_info.geometry.base_width != 0 diff --git a/test/python/prelude.py b/test/python/prelude.py new file mode 100644 index 00000000..67e15861 --- /dev/null +++ b/test/python/prelude.py @@ -0,0 +1,29 @@ +import os +import shutil +import tempfile + +SYSTEM_FILES = ("ARM7_BIOS", "ARM9_BIOS", "ARM7_DSI_BIOS", "ARM9_DSI_BIOS", "NDS_FIRMWARE", "DSI_FIRMWARE", "DSI_NAND") +testdir = tempfile.TemporaryDirectory(".libretro") +system_dir = os.path.join(testdir.name, "system") +save_dir = os.path.join(testdir.name, "savefiles") +savestate_directory = os.path.join(testdir.name, "states") +core_system_dir = os.path.join(system_dir, "melonDS DS") +core_save_dir = os.path.join(save_dir, "melonDS DS") + +print("Test dir:", testdir.name) +print("System dir:", system_dir) +print("Save dir:", save_dir) +print("Savestate dir:", savestate_directory) +print("Core system dir:", core_system_dir) + +os.makedirs(core_system_dir, exist_ok=True) +os.makedirs(core_save_dir, exist_ok=True) + +for _f in SYSTEM_FILES: + if _f in os.environ: + basename = os.path.basename(os.environ[_f]) + targetpath = os.path.join(core_system_dir, basename) + print(f"Copying {os.environ[_f]} to {targetpath}") + shutil.copyfile(os.environ[_f], targetpath) + +options_string = os.getenv("RETRO_CORE_OPTIONS") From 10457432f22ab9fff11a3411b7b77249e4e824d4 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 6 Mar 2024 20:11:00 -0500 Subject: [PATCH 022/234] Require the test suite to run with optimizations off so `assert`s are kept --- test/python/prelude.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/python/prelude.py b/test/python/prelude.py index 67e15861..472b08a7 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -2,6 +2,9 @@ import shutil import tempfile +if not __debug__: + raise RuntimeError("The melonDS DS test suite should not be run with -O") + SYSTEM_FILES = ("ARM7_BIOS", "ARM9_BIOS", "ARM7_DSI_BIOS", "ARM9_DSI_BIOS", "NDS_FIRMWARE", "DSI_FIRMWARE", "DSI_NAND") testdir = tempfile.TemporaryDirectory(".libretro") system_dir = os.path.join(testdir.name, "system") From b71fa4a7c9fdff680968432931f532c9492d9872 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 6 Mar 2024 20:11:17 -0500 Subject: [PATCH 023/234] Ensure that `core.get_memory` works --- test/python/basics/core_exposes_ram.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/python/basics/core_exposes_ram.py b/test/python/basics/core_exposes_ram.py index efb11b7c..9fedf968 100644 --- a/test/python/basics/core_exposes_ram.py +++ b/test/python/basics/core_exposes_ram.py @@ -12,3 +12,9 @@ assert data assert data is not None assert data.value + + memory = session.core.get_memory(RETRO_MEMORY_SYSTEM_RAM) + + assert memory is not None + assert len(memory) == size + assert id(memory) == data.value From 35dff56284a8fa65c62c463f1f8f64f366600179 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 6 Mar 2024 20:11:55 -0500 Subject: [PATCH 024/234] Write some more tests --- test/cmake/Basics.cmake | 30 +++++++++++-------- test/python/basics/core_applies_cheats.py | 19 ++++++++++++ test/python/basics/core_resets.py | 11 +++++++ .../basics/core_saves_and_loads_state.py | 25 ++++++++++++++++ test/python/basics/core_saves_state.py | 15 ++++++++++ 5 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 test/python/basics/core_applies_cheats.py create mode 100644 test/python/basics/core_resets.py create mode 100644 test/python/basics/core_saves_and_loads_state.py create mode 100644 test/python/basics/core_saves_state.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 9921d6ea..e2eac74e 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -32,39 +32,42 @@ add_python_test( add_python_test( NAME "Core runs init and deinit" - TEST_MODULE "basics.core_run_init_deinit" + TEST_MODULE basics.core_run_init_deinit CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core loads and unloads with content" - TEST_MODULE "basics.core_loads_unloads_with_content" + TEST_MODULE basics.core_loads_unloads_with_content CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core runs for one frame" - TEST_MODULE "basics.core_run_frame" + TEST_MODULE basics.core_run_frame + CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core runs for multiple frames" - TEST_MODULE "basics.core_run_frames" + TEST_MODULE basics.core_run_frames + CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core resets emulator state" - TEST_MODULE "" + TEST_MODULE basics.core_resets + CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core generates audio" - TEST_MODULE "basics.core_generates_audio" + TEST_MODULE basics.core_generates_audio ) add_python_test( NAME "Core generates video" - TEST_MODULE "basics.core_generates_video" + TEST_MODULE basics.core_generates_video ) add_python_test( @@ -84,27 +87,30 @@ add_python_test( add_python_test( NAME "Core saves state" - TEST_MODULE "basics.core_saves_state" + TEST_MODULE basics.core_saves_state + CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core saves and loads state" - TEST_MODULE "basics.core_saves_and_loads_state" + TEST_MODULE basics.core_saves_and_loads_state + CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core exposes emulated RAM" - TEST_MODULE "basics.core_exposes_ram" + TEST_MODULE basics.core_exposes_ram ) add_python_test( NAME "Core exposes emulated SRAM" - TEST_MODULE "basics.core_exposes_sram" + TEST_MODULE basics.core_exposes_sram ) add_python_test( NAME "Core applies cheats" - TEST_MODULE "" + TEST_MODULE basics.core_applies_cheats + CONTENT "${NDS_ROM}" ) # Environment Calls diff --git a/test/python/basics/core_applies_cheats.py b/test/python/basics/core_applies_cheats.py new file mode 100644 index 00000000..92b0fce5 --- /dev/null +++ b/test/python/basics/core_applies_cheats.py @@ -0,0 +1,19 @@ +from sys import argv +from libretro import default_session, RETRO_MEMORY_SYSTEM_RAM + +with default_session(argv[1]) as session: + memory = session.core.get_memory(RETRO_MEMORY_SYSTEM_RAM) + + assert memory is not None + + session.core.cheat_set(0, True, b'02000000-DEADBEEF') + # A trivial cheat code with one instruction: 0XXXXXXX-YYYYYYYY, + # where XXXXXXX (in this case, 02000000) is the address to write to + # and YYYYYYYY (in this case, DEADBEEF) is the value to write. + + assert memory[0:4].tobytes() == b'\xde\xad\xbe\xef' + + session.core.cheat_set(0, False, b'02000000-CAFEBABE') + + assert memory[0:4].tobytes() == b'\xde\xad\xbe\xef' + # Passing False to enabled shouldn't apply the cheat diff --git a/test/python/basics/core_resets.py b/test/python/basics/core_resets.py new file mode 100644 index 00000000..ea95b709 --- /dev/null +++ b/test/python/basics/core_resets.py @@ -0,0 +1,11 @@ +from sys import argv +from libretro import default_session + +with default_session(argv[1], argv[2]) as session: + for i in range(10): + session.core.run() + + session.core.reset() + + for i in range(10): + session.core.run() diff --git a/test/python/basics/core_saves_and_loads_state.py b/test/python/basics/core_saves_and_loads_state.py new file mode 100644 index 00000000..a5f4e33f --- /dev/null +++ b/test/python/basics/core_saves_and_loads_state.py @@ -0,0 +1,25 @@ +from sys import argv +from libretro import default_session + +import prelude + +with default_session(argv[1], argv[2]) as session: + for i in range(30): + session.core.run() + + size = session.core.serialize_size() + buffer = bytearray(size) + state_saved = session.core.serialize(buffer) + + assert state_saved + assert any(buffer) + + for i in range(30): + session.core.run() + + new_size = session.core.serialize_size() + assert new_size == size + + state_loaded = session.core.unserialize(buffer) + + assert state_loaded diff --git a/test/python/basics/core_saves_state.py b/test/python/basics/core_saves_state.py new file mode 100644 index 00000000..44da198e --- /dev/null +++ b/test/python/basics/core_saves_state.py @@ -0,0 +1,15 @@ +from sys import argv +from libretro import default_session + +import prelude + +with default_session(argv[1], argv[2]) as session: + for i in range(10): + session.core.run() + + size = session.core.serialize_size() + buffer = bytearray(size) + state_saved = session.core.serialize(buffer) + + assert state_saved + assert any(buffer) From 5b0ed4a8d81aefcb014980a2de64a1cd7a35e56d Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 6 Mar 2024 20:13:05 -0500 Subject: [PATCH 025/234] Add a comment --- test/python/basics/core_applies_cheats.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/python/basics/core_applies_cheats.py b/test/python/basics/core_applies_cheats.py index 92b0fce5..84f4c91a 100644 --- a/test/python/basics/core_applies_cheats.py +++ b/test/python/basics/core_applies_cheats.py @@ -10,6 +10,7 @@ # A trivial cheat code with one instruction: 0XXXXXXX-YYYYYYYY, # where XXXXXXX (in this case, 02000000) is the address to write to # and YYYYYYYY (in this case, DEADBEEF) is the value to write. + # See https://mgba-emu.github.io/gbatek/#dscartcheatactionreplayds for more details assert memory[0:4].tobytes() == b'\xde\xad\xbe\xef' From 9f432f7b001330d0ff52cd69a721ebbbf0fa0bee Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 6 Mar 2024 21:24:10 -0500 Subject: [PATCH 026/234] Implement or update some tests --- src/libretro/CMakeLists.txt | 2 ++ src/libretro/core/test.cpp | 31 ++++++++++++++++++ src/libretro/core/test.hpp | 25 +++++++++++++++ src/libretro/environment.cpp | 1 + src/libretro/libretro.cpp | 14 +------- src/libretro/libretro.hpp | 1 - test/cmake/Basics.cmake | 32 +++++++++++-------- .../core_defines_content_info_overrides.py | 10 ++++++ .../basics/core_defines_controller_info.py | 9 ++++++ .../basics/core_defines_input_descriptors.py | 11 +++++++ test/python/basics/core_defines_subsystems.py | 10 ++++++ test/python/basics/core_get_proc_address.py | 10 +++--- test/python/basics/core_logs_output.py | 3 +- .../core_registers_achievement_support.py | 6 ++++ test/python/basics/core_sets_pixel_format.py | 8 +++++ test/python/prelude.py | 3 ++ 16 files changed, 142 insertions(+), 34 deletions(-) create mode 100644 src/libretro/core/test.cpp create mode 100644 src/libretro/core/test.hpp create mode 100644 test/python/basics/core_defines_content_info_overrides.py create mode 100644 test/python/basics/core_defines_controller_info.py create mode 100644 test/python/basics/core_defines_input_descriptors.py create mode 100644 test/python/basics/core_defines_subsystems.py create mode 100644 test/python/basics/core_registers_achievement_support.py create mode 100644 test/python/basics/core_sets_pixel_format.py diff --git a/src/libretro/CMakeLists.txt b/src/libretro/CMakeLists.txt index 7dff6ac8..595d523e 100644 --- a/src/libretro/CMakeLists.txt +++ b/src/libretro/CMakeLists.txt @@ -30,6 +30,8 @@ add_library(melondsds_libretro MODULE core/core.cpp core/core.hpp core/tasks.cpp + core/test.cpp + core/test.hpp environment.cpp environment.hpp exceptions.cpp diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp new file mode 100644 index 00000000..f684420f --- /dev/null +++ b/src/libretro/core/test.cpp @@ -0,0 +1,31 @@ +/* + Copyright 2024 Jesse Talavera + + melonDS DS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS DS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS DS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "test.hpp" + +#include + +extern "C" int libretropy_add_integers(int a, int b) { + return a + b; +} + +retro_proc_address_t MelonDsDs::GetProcAddress(const char* sym) noexcept { + if (string_is_equal(sym, "libretropy_add_integers")) + return reinterpret_cast(libretropy_add_integers); + + return nullptr; +} + diff --git a/src/libretro/core/test.hpp b/src/libretro/core/test.hpp new file mode 100644 index 00000000..d3806eae --- /dev/null +++ b/src/libretro/core/test.hpp @@ -0,0 +1,25 @@ +/* + Copyright 2024 Jesse Talavera + + melonDS DS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS DS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS DS. If not, see http://www.gnu.org/licenses/. +*/ + +#pragma once + +#include + +namespace MelonDsDs +{ + // test functions for the test suite + retro_proc_address_t GetProcAddress(const char* sym) noexcept; +} diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index 5e0e30b4..5f215c7f 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -37,6 +37,7 @@ #include "info.hpp" #include "libretro.hpp" #include "config/config.hpp" +#include "core/test.hpp" #include "tracy.hpp" #include "version.hpp" diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index ab90c585..d3a3def6 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -333,16 +333,4 @@ void Platform::WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writ ZoneScopedN(TracyFunction); MelonDsDs::Core.WriteFirmware(firmware, writeoffset, writelen); -} - -extern "C" int libretropy_add_integers(int a, int b) { - return a + b; -} - -retro_proc_address_t MelonDsDs::GetProcAddress(const char* sym) noexcept { - if (sym == "libretropy_add_integers") - return reinterpret_cast(libretropy_add_integers); - - return nullptr; -} - +} \ No newline at end of file diff --git a/src/libretro/libretro.hpp b/src/libretro/libretro.hpp index dcae3e8a..a75bc287 100644 --- a/src/libretro/libretro.hpp +++ b/src/libretro/libretro.hpp @@ -39,7 +39,6 @@ namespace MelonDsDs { void HardwareContextReset() noexcept; void HardwareContextDestroyed() noexcept; bool UpdateOptionVisibility() noexcept; - retro_proc_address_t GetProcAddress(const char* sym) noexcept; } #endif //MELONDS_DS_LIBRETRO_HPP diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index e2eac74e..38d81cd8 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -138,12 +138,14 @@ add_python_test( add_python_test( NAME "Core sets pixel format to XRGB8888" - TEST_MODULE "" + TEST_MODULE basics.core_sets_pixel_format + CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core sets input descriptors" - TEST_MODULE "" + TEST_MODULE basics.core_defines_input_descriptors + NDS_SYSFILES ) add_python_test( @@ -163,21 +165,19 @@ add_python_test( add_python_test( NAME "Core registers support for no-content mode" - TEST_MODULE "basics.core_registers_no_content_support" + TEST_MODULE basics.core_registers_no_content_support CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core loads and unloads without content" - TEST_MODULE "basics.core_loads_unloads_without_content" + TEST_MODULE basics.core_loads_unloads_without_content ) add_python_test( NAME "Core logs output" - TEST_MODULE "basics.core_logs_output" - ARM7_BIOS - ARM9_BIOS - NDS_FIRMWARE + TEST_MODULE basics.core_logs_output + NDS_SYSFILES ) add_python_test( @@ -187,13 +187,14 @@ add_python_test( add_python_test( NAME "Core sets retro_get_proc_address_interface" - TEST_MODULE "basics.core_get_proc_address" - CONTENT "${NDS_ROM}" + TEST_MODULE basics.core_get_proc_address + NDS_SYSFILES ) add_python_test( NAME "Core defines subsystems" - TEST_MODULE "" + TEST_MODULE basics.core_defines_subsystems + NDS_SYSFILES ) add_python_test( @@ -203,7 +204,8 @@ add_python_test( add_python_test( NAME "Core defines controller info" - TEST_MODULE "" + TEST_MODULE basics.core_defines_controller_info + NDS_SYSFILES ) add_python_test( @@ -213,7 +215,8 @@ add_python_test( add_python_test( NAME "Core registers support for achievements" - TEST_MODULE "" + TEST_MODULE basics.core_registers_achievement_support + NDS_SYSFILES ) add_python_test( @@ -253,7 +256,8 @@ add_python_test( add_python_test( NAME "Core sets content info overrides" - TEST_MODULE "" + TEST_MODULE basics.core_defines_content_info_overrides + NDS_SYSFILES ) add_python_test( diff --git a/test/python/basics/core_defines_content_info_overrides.py b/test/python/basics/core_defines_content_info_overrides.py new file mode 100644 index 00000000..9bf66aff --- /dev/null +++ b/test/python/basics/core_defines_content_info_overrides.py @@ -0,0 +1,10 @@ +from libretro import default_session + +import prelude + +with default_session(prelude.core_path) as session: + overrides = session.content_info_overrides + + assert overrides is not None + assert len(overrides) > 0 + assert all(o.extensions for o in overrides) diff --git a/test/python/basics/core_defines_controller_info.py b/test/python/basics/core_defines_controller_info.py new file mode 100644 index 00000000..e91c8473 --- /dev/null +++ b/test/python/basics/core_defines_controller_info.py @@ -0,0 +1,9 @@ +from libretro import default_session + +import prelude + +with default_session(prelude.core_path) as session: + info = session.controller_info + + assert info is not None + assert info.num_types > 0 diff --git a/test/python/basics/core_defines_input_descriptors.py b/test/python/basics/core_defines_input_descriptors.py new file mode 100644 index 00000000..f554ce8c --- /dev/null +++ b/test/python/basics/core_defines_input_descriptors.py @@ -0,0 +1,11 @@ +from ctypes import * +from libretro import default_session, retro_get_proc_address_t + +import prelude + +with default_session(prelude.core_path) as session: + descriptors = session.input_descriptors + + assert descriptors is not None + assert len(descriptors) > 0 + assert all(d.description for d in descriptors) diff --git a/test/python/basics/core_defines_subsystems.py b/test/python/basics/core_defines_subsystems.py new file mode 100644 index 00000000..6937d06d --- /dev/null +++ b/test/python/basics/core_defines_subsystems.py @@ -0,0 +1,10 @@ +from libretro import default_session + +import prelude + +with default_session(prelude.core_path) as session: + subsystems = session.subsystems + + assert subsystems is not None + assert len(subsystems) > 0 + assert all(s.desc for s in subsystems) diff --git a/test/python/basics/core_get_proc_address.py b/test/python/basics/core_get_proc_address.py index dcdc2745..14f0deec 100644 --- a/test/python/basics/core_get_proc_address.py +++ b/test/python/basics/core_get_proc_address.py @@ -1,17 +1,19 @@ from ctypes import * -from sys import argv from libretro import default_session, retro_get_proc_address_t -with default_session(argv[1]) as env: - proc_address_callback = env.proc_address_callback +import prelude + +with default_session(prelude.core_path) as session: + proc_address_callback = session.proc_address_callback assert proc_address_callback is not None get_proc_address: retro_get_proc_address_t = proc_address_callback.get_proc_address assert get_proc_address is not None - add_integers = get_proc_address("libretropy_add_integers") + add_integers = get_proc_address(b"libretropy_add_integers") assert add_integers is not None add_integers_callable = cast(add_integers, CFUNCTYPE(c_int, c_int, c_int)) + assert add_integers_callable is not None assert add_integers_callable(1, 2) == 3 diff --git a/test/python/basics/core_logs_output.py b/test/python/basics/core_logs_output.py index d3ef559a..4cfb8c01 100644 --- a/test/python/basics/core_logs_output.py +++ b/test/python/basics/core_logs_output.py @@ -1,11 +1,10 @@ -from sys import argv from typing import cast from libretro import default_session from libretro.callback.log import StandardLogger import prelude -with default_session(argv[1], system_dir=prelude.core_system_dir) as session: +with default_session(prelude.core_path, system_dir=prelude.core_system_dir) as session: log: StandardLogger = cast(StandardLogger, session.log) assert log is not None assert log.records is not None diff --git a/test/python/basics/core_registers_achievement_support.py b/test/python/basics/core_registers_achievement_support.py new file mode 100644 index 00000000..e780cadb --- /dev/null +++ b/test/python/basics/core_registers_achievement_support.py @@ -0,0 +1,6 @@ +from libretro import default_session + +import prelude + +with default_session(prelude.core_path, prelude.content_path) as env: + assert env.support_achievements is True diff --git a/test/python/basics/core_sets_pixel_format.py b/test/python/basics/core_sets_pixel_format.py new file mode 100644 index 00000000..967ddfb5 --- /dev/null +++ b/test/python/basics/core_sets_pixel_format.py @@ -0,0 +1,8 @@ +from sys import argv +from libretro import default_session +from libretro.defs import PixelFormat + +import prelude + +with default_session(prelude.core_path) as session: + assert session.video.pixel_format == PixelFormat.XRGB8888 \ No newline at end of file diff --git a/test/python/prelude.py b/test/python/prelude.py index 472b08a7..26fe94c2 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -1,5 +1,6 @@ import os import shutil +import sys import tempfile if not __debug__: @@ -30,3 +31,5 @@ shutil.copyfile(os.environ[_f], targetpath) options_string = os.getenv("RETRO_CORE_OPTIONS") +core_path = sys.argv[1] +content_path = sys.argv[2] if len(sys.argv) > 2 else None From fd699ea3b8779091a925bca72bd5c4776d3a403f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 6 Mar 2024 22:05:03 -0500 Subject: [PATCH 027/234] Implement or update some tests --- src/libretro/core/test.cpp | 20 ++++++++++++++++ test/cmake/Basics.cmake | 6 +++-- .../python/basics/core_gets_save_directory.py | 23 +++++++++++++++++++ .../basics/core_gets_system_directory.py | 23 +++++++++++++++++++ test/python/prelude.py | 1 + 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 test/python/basics/core_gets_save_directory.py create mode 100644 test/python/basics/core_gets_system_directory.py diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp index f684420f..e473e41a 100644 --- a/src/libretro/core/test.cpp +++ b/src/libretro/core/test.cpp @@ -18,14 +18,34 @@ #include +#include "environment.hpp" + extern "C" int libretropy_add_integers(int a, int b) { return a + b; } +extern "C" const char* libretropy_get_system_directory() { + const char* path = nullptr; + bool ok = retro::environment(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &path); + return ok ? path : nullptr; +} + +extern "C" const char* libretropy_get_save_directory() { + const char* path = nullptr; + bool ok = retro::environment(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &path); + return ok ? path : nullptr; +} + retro_proc_address_t MelonDsDs::GetProcAddress(const char* sym) noexcept { if (string_is_equal(sym, "libretropy_add_integers")) return reinterpret_cast(libretropy_add_integers); + if (string_is_equal(sym, "libretropy_get_system_directory")) + return reinterpret_cast(libretropy_get_system_directory); + + if (string_is_equal(sym, "libretropy_get_save_directory")) + return reinterpret_cast(libretropy_get_save_directory); + return nullptr; } diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 38d81cd8..43ce7b15 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -133,7 +133,8 @@ add_python_test( add_python_test( NAME "Core gets the frontend's system directory" - TEST_MODULE "" + TEST_MODULE basics.core_gets_system_directory + CONTENT "${NDS_ROM}" ) add_python_test( @@ -182,7 +183,8 @@ add_python_test( add_python_test( NAME "Core gets the frontend's save directory" - TEST_MODULE "" + TEST_MODULE basics.core_gets_save_directory + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_gets_save_directory.py b/test/python/basics/core_gets_save_directory.py new file mode 100644 index 00000000..7733980e --- /dev/null +++ b/test/python/basics/core_gets_save_directory.py @@ -0,0 +1,23 @@ +from ctypes import * +from libretro import default_session, retro_get_proc_address_t + +import prelude + +with default_session(prelude.core_path, save_dir=prelude.save_directory) as session: + proc_address_callback = session.proc_address_callback + assert proc_address_callback is not None + + get_proc_address: retro_get_proc_address_t = proc_address_callback.get_proc_address + assert get_proc_address is not None + + get_save_directory = get_proc_address(b"libretropy_get_save_directory") + assert get_save_directory is not None + + get_save_directory_callable = cast(get_save_directory, CFUNCTYPE(c_char_p)) + + assert get_save_directory_callable is not None + + save_directory_pointer = get_save_directory_callable() + assert isinstance(save_directory_pointer, c_char_p) + + assert string_at(save_directory_pointer) == session.save_dir diff --git a/test/python/basics/core_gets_system_directory.py b/test/python/basics/core_gets_system_directory.py new file mode 100644 index 00000000..4dfe1cad --- /dev/null +++ b/test/python/basics/core_gets_system_directory.py @@ -0,0 +1,23 @@ +from ctypes import * +from libretro import default_session, retro_get_proc_address_t + +import prelude + +with default_session(prelude.core_path, system_dir=prelude.system_dir) as session: + proc_address_callback = session.proc_address_callback + assert proc_address_callback is not None + + get_proc_address: retro_get_proc_address_t = proc_address_callback.get_proc_address + assert get_proc_address is not None + + get_system_directory = get_proc_address(b"libretropy_get_system_directory") + assert get_system_directory is not None + + get_system_directory_callable = cast(get_system_directory, CFUNCTYPE(c_char_p)) + + assert get_system_directory_callable is not None + + system_directory_pointer = get_system_directory_callable() + assert isinstance(system_directory_pointer, c_char_p) + + assert string_at(system_directory_pointer) == session.system_dir diff --git a/test/python/prelude.py b/test/python/prelude.py index 26fe94c2..8720427c 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -10,6 +10,7 @@ testdir = tempfile.TemporaryDirectory(".libretro") system_dir = os.path.join(testdir.name, "system") save_dir = os.path.join(testdir.name, "savefiles") +save_directory = save_dir savestate_directory = os.path.join(testdir.name, "states") core_system_dir = os.path.join(system_dir, "melonDS DS") core_save_dir = os.path.join(save_dir, "melonDS DS") From fc032b9ef60de28330ded1de8b80f494d039f5de Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 8 Mar 2024 12:09:23 -0500 Subject: [PATCH 028/234] Log some extra info for some tests --- test/python/basics/core_api_version_correct.py | 7 +++++-- test/python/basics/core_region_correct.py | 7 +++++-- test/python/basics/core_system_info_valid.py | 13 +++++++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/test/python/basics/core_api_version_correct.py b/test/python/basics/core_api_version_correct.py index a8f280e4..548f4fea 100644 --- a/test/python/basics/core_api_version_correct.py +++ b/test/python/basics/core_api_version_correct.py @@ -1,6 +1,9 @@ -from sys import argv from libretro import Core -core = Core(argv[1]) +import prelude + +core = Core(prelude.core_path) + +print(f"api_version: {core.api_version()}") assert core.api_version() == 1 diff --git a/test/python/basics/core_region_correct.py b/test/python/basics/core_region_correct.py index 72bd6288..4adbb08e 100644 --- a/test/python/basics/core_region_correct.py +++ b/test/python/basics/core_region_correct.py @@ -1,7 +1,10 @@ -from sys import argv from libretro import Core from libretro.defs import Region -core = Core(argv[1]) +import prelude + +core = Core(prelude.core_path) + +print(f"region: {core.get_region()!r}") assert core.get_region() == Region.NTSC diff --git a/test/python/basics/core_system_info_valid.py b/test/python/basics/core_system_info_valid.py index 488162ae..3ff102b6 100644 --- a/test/python/basics/core_system_info_valid.py +++ b/test/python/basics/core_system_info_valid.py @@ -1,9 +1,18 @@ -from sys import argv from libretro import Core -core = Core(argv[1]) +import prelude + +core = Core(prelude.core_path) system_info = core.get_system_info() assert system_info is not None + +print(f"library_name: {system_info.library_name}") +print(f"library_version: {system_info.library_version}") +print(f"valid_extensions: {system_info.valid_extensions}") +print(f"need_fullpath: {system_info.need_fullpath}") +print(f"block_extract: {system_info.block_extract}") + assert b"melonDS DS" == system_info.library_name + From 78f831c248be905b1f9d329cc986297e07171f96 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 8 Mar 2024 15:49:05 -0500 Subject: [PATCH 029/234] Clean up some tests --- test/python/basics/core_accepts_button_input.py | 16 ++++++---------- .../core_defines_content_info_overrides.py | 7 +++++-- test/python/basics/core_defines_subsystems.py | 5 ++++- .../python/basics/core_system_av_info_correct.py | 9 +++++---- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/test/python/basics/core_accepts_button_input.py b/test/python/basics/core_accepts_button_input.py index b6d80ece..de6239b4 100644 --- a/test/python/basics/core_accepts_button_input.py +++ b/test/python/basics/core_accepts_button_input.py @@ -1,10 +1,9 @@ import itertools -from sys import argv - -import libretro.callback.input from libretro import default_session from libretro.callback.input import JoypadState +import prelude + def generate_input(): yield from itertools.repeat(0, 180) @@ -14,13 +13,10 @@ def generate_input(): yield from itertools.repeat(0) -with default_session(argv[1]) as session: +with default_session(prelude.core_path, system_dir=prelude.system_dir, input_state=generate_input) as session: - for i in range(180): + for i in range(360): session.core.run() - # TODO: Take screenshot - # TODO: Emulate button press - - for i in range(180): - session.core.run() + assert False + # TODO: Assert that I'm in the main menu diff --git a/test/python/basics/core_defines_content_info_overrides.py b/test/python/basics/core_defines_content_info_overrides.py index 9bf66aff..c98a14b3 100644 --- a/test/python/basics/core_defines_content_info_overrides.py +++ b/test/python/basics/core_defines_content_info_overrides.py @@ -1,10 +1,13 @@ -from libretro import default_session +from pprint import pprint +import libretro import prelude -with default_session(prelude.core_path) as session: +session: libretro.Session +with prelude.noload_session() as session: overrides = session.content_info_overrides + pprint(overrides) assert overrides is not None assert len(overrides) > 0 assert all(o.extensions for o in overrides) diff --git a/test/python/basics/core_defines_subsystems.py b/test/python/basics/core_defines_subsystems.py index 6937d06d..99b82ee1 100644 --- a/test/python/basics/core_defines_subsystems.py +++ b/test/python/basics/core_defines_subsystems.py @@ -1,10 +1,13 @@ +import libretro.session from libretro import default_session +from pprint import pprint import prelude -with default_session(prelude.core_path) as session: +with default_session(prelude.core_path, libretro.session.DoNotLoad) as session: subsystems = session.subsystems + pprint(subsystems) assert subsystems is not None assert len(subsystems) > 0 assert all(s.desc for s in subsystems) diff --git a/test/python/basics/core_system_av_info_correct.py b/test/python/basics/core_system_av_info_correct.py index 01a79654..41d7242b 100644 --- a/test/python/basics/core_system_av_info_correct.py +++ b/test/python/basics/core_system_av_info_correct.py @@ -1,9 +1,10 @@ -from sys import argv +from libretro import retro_system_av_info, Session -from libretro import default_session +import prelude -with default_session(argv[1]) as session: - av_info = session.core.get_system_av_info() +session: Session +with prelude.session() as session: + av_info: retro_system_av_info = session.core.get_system_av_info() assert av_info is not None assert av_info.timing.sample_rate != 0 From 5fa89159a2fafc96e129d820f3ae2734aef93b3a Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sat, 9 Mar 2024 10:37:32 -0500 Subject: [PATCH 030/234] Fix some tests --- test/CMakeLists.txt | 12 ++++++++++++ .../basics/core_loads_unloads_with_content.py | 11 ++++++----- test/python/basics/core_run_frame.py | 10 ++++++---- test/python/basics/core_run_frames.py | 8 +++++--- test/python/basics/core_run_init_deinit.py | 8 +++++--- test/python/prelude.py | 13 +++++++++++++ 6 files changed, 47 insertions(+), 15 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 093f1d86..6f1f4b3a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -276,6 +276,18 @@ function(add_python_test) endif() endmacro() + if(${RETRO_NDS_SYSFILES}) + if (NOT RETRO_ARM7_BIOS) + set(RETRO_ARM7_BIOS 1) + endif() + if (NOT RETRO_ARM9_BIOS) + set(RETRO_ARM9_BIOS 1) + endif() + if (NOT RETRO_NDS_FIRMWARE) + set(RETRO_NDS_FIRMWARE 1) + endif() + endif() + expose_system_file(ARM7_BIOS) expose_system_file(ARM9_BIOS) expose_system_file(ARM7_DSI_BIOS) diff --git a/test/python/basics/core_loads_unloads_with_content.py b/test/python/basics/core_loads_unloads_with_content.py index 1ea58fdc..4930a1dc 100644 --- a/test/python/basics/core_loads_unloads_with_content.py +++ b/test/python/basics/core_loads_unloads_with_content.py @@ -1,6 +1,7 @@ -from sys import argv -from libretro import default_session +from libretro import Session -with default_session(argv[1], argv[2]) as session: - for i in range(10): - session.core.run() +import prelude + +session: Session +with prelude.session() as session: + pass diff --git a/test/python/basics/core_run_frame.py b/test/python/basics/core_run_frame.py index 5a62e405..2c11e607 100644 --- a/test/python/basics/core_run_frame.py +++ b/test/python/basics/core_run_frame.py @@ -1,5 +1,7 @@ -from sys import argv -from libretro import default_session +from libretro import Session -with default_session(argv[1]) as session: - session.core.run() +import prelude + +session: Session +with prelude.session() as session: + session.core.run() \ No newline at end of file diff --git a/test/python/basics/core_run_frames.py b/test/python/basics/core_run_frames.py index 36136812..77332e10 100644 --- a/test/python/basics/core_run_frames.py +++ b/test/python/basics/core_run_frames.py @@ -1,6 +1,8 @@ -from sys import argv -from libretro import default_session +from libretro import Session -with default_session(argv[1]) as session: +import prelude + +session: Session +with prelude.session() as session: for i in range(300): session.core.run() diff --git a/test/python/basics/core_run_init_deinit.py b/test/python/basics/core_run_init_deinit.py index 1665795e..2ebdb6c9 100644 --- a/test/python/basics/core_run_init_deinit.py +++ b/test/python/basics/core_run_init_deinit.py @@ -1,5 +1,7 @@ -from sys import argv -from libretro import default_session +from libretro import Session -with default_session(argv[1]) as session: +import prelude + +session: Session +with prelude.noload_session() as session: pass diff --git a/test/python/prelude.py b/test/python/prelude.py index 8720427c..42579d29 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -3,6 +3,8 @@ import sys import tempfile +import libretro + if not __debug__: raise RuntimeError("The melonDS DS test suite should not be run with -O") @@ -34,3 +36,14 @@ options_string = os.getenv("RETRO_CORE_OPTIONS") core_path = sys.argv[1] content_path = sys.argv[2] if len(sys.argv) > 2 else None + +default_dirs = { + "system_dir": system_dir, + "save_dir": save_dir, +} + +def session() -> libretro.Session: + return libretro.default_session(core_path, content_path, **default_dirs) + +def noload_session() -> libretro.Session: + return libretro.default_session(core_path, libretro.session.DoNotLoad, **default_dirs) \ No newline at end of file From be77fbfc065b083792f1d8fd670a2477e6c29510 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 10 Mar 2024 15:57:49 -0400 Subject: [PATCH 031/234] Ignore venvs --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index eeaeaf1a..7b21fae4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ out.txt # Act .actrc + +# Python +venv* \ No newline at end of file From f58dbbf64197401e1332879bd74bd779144cf28e Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 10 Mar 2024 15:58:52 -0400 Subject: [PATCH 032/234] Ensure that `add_python_test` allows setting `NDS_SYSFILES` and `DSI_SYSFILES` --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6f1f4b3a..8245c4c4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -245,7 +245,7 @@ function(add_emutest_test) endfunction() function(add_python_test) - set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NO_SKIP_ERROR_SCREEN DISABLED) + set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NDS_SYSFILES DSI_SYSFILES NO_SKIP_ERROR_SCREEN DISABLED) set(oneValueArgs NAME CONTENT TEST_MODULE SKIP_RETURN_CODE TIMEOUT) set(multiValueArgs CORE_OPTION DEPENDS PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION LABELS) cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") From 17b44712a104497a34059d956bc35f02644a1d63 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 10 Mar 2024 18:06:03 -0400 Subject: [PATCH 033/234] Bump the required Python version to 3.11 --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8245c4c4..2959cd18 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,4 @@ -find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter) +find_package(Python3 3.11 REQUIRED) find_package(PythonModules COMPONENTS libretro) # TODO: Make this required once libretro.py is on PyPI From e1f57b1746b21c1ba61b1dd14fc787bfeb63cc9c Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 10 Mar 2024 19:45:49 -0400 Subject: [PATCH 034/234] Fail the test if a ctypes exception is unhandled - Ideally libretro.py would hold onto the exception until it received control --- test/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2959cd18..0f7903a1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -304,6 +304,7 @@ function(add_python_test) set_tests_properties("${RETRO_NAME}" PROPERTIES PASS_REGULAR_EXPRESSION "${RETRO_PASS_REGULAR_EXPRESSION}") endif() + set_tests_properties("${RETRO_NAME}" PROPERTIES FAIL_REGULAR_EXPRESSION "Exception ignored on calling ctypes callback function") if (RETRO_FAIL_REGULAR_EXPRESSION) set_tests_properties("${RETRO_NAME}" PROPERTIES FAIL_REGULAR_EXPRESSION "${RETRO_FAIL_REGULAR_EXPRESSION}") endif() From 23a249b5ee979ac93d25b343087dff763abb9cac Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 10 Mar 2024 20:55:23 -0400 Subject: [PATCH 035/234] Fix some tests --- test/cmake/Basics.cmake | 13 ++++++++++--- test/python/basics/core_generates_audio.py | 17 +++++++++++------ test/python/basics/core_sends_messages_v0.py | 0 test/python/basics/core_sends_messages_v1.py | 0 4 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 test/python/basics/core_sends_messages_v0.py create mode 100644 test/python/basics/core_sends_messages_v1.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 43ce7b15..edf95025 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -23,6 +23,7 @@ add_python_test( add_python_test( NAME "Core system AV info is correct" TEST_MODULE basics.core_system_av_info_correct + CONTENT "${NDS_ROM}" ) add_python_test( @@ -63,6 +64,7 @@ add_python_test( add_python_test( NAME "Core generates audio" TEST_MODULE basics.core_generates_audio + CONTENT "${NDS_ROM}" ) add_python_test( @@ -100,11 +102,13 @@ add_python_test( add_python_test( NAME "Core exposes emulated RAM" TEST_MODULE basics.core_exposes_ram + CONTENT "${NDS_ROM}" ) add_python_test( NAME "Core exposes emulated SRAM" TEST_MODULE basics.core_exposes_sram + CONTENT "${NDS_ROM}" ) add_python_test( @@ -123,7 +127,8 @@ add_python_test( add_python_test( NAME "Core can send messages (API V0)" - TEST_MODULE "" + TEST_MODULE basics.core_sends_messages_v0 + NDS_SYSFILES ) add_python_test( @@ -233,7 +238,8 @@ add_python_test( add_python_test( NAME "Core gets options API version" - TEST_MODULE "" + TEST_MODULE basics.core_gets_options_version + NDS_SYSFILES ) add_python_test( @@ -253,7 +259,8 @@ add_python_test( add_python_test( NAME "Core sends messages (API V1)" - TEST_MODULE "" + TEST_MODULE basics.core_sends_messages_v1 + NDS_SYSFILES ) add_python_test( diff --git a/test/python/basics/core_generates_audio.py b/test/python/basics/core_generates_audio.py index 44c6b8ac..31fa5dd4 100644 --- a/test/python/basics/core_generates_audio.py +++ b/test/python/basics/core_generates_audio.py @@ -1,11 +1,16 @@ -from sys import argv -from libretro import default_session +from typing import cast +from libretro import Session +from libretro.api import ArrayAudioState -with default_session(argv[1]) as session: +import prelude + +session: Session +with prelude.session() as session: + audio = cast(ArrayAudioState, session.audio) for i in range(300): session.core.run() - assert session.audio.buffer is not None - assert len(session.audio.buffer) > 0 + assert audio.buffer is not None + assert len(audio.buffer) > 0 - assert any(b != 0 for b in session.audio.buffer) \ No newline at end of file + assert any(b != 0 for b in audio.buffer) diff --git a/test/python/basics/core_sends_messages_v0.py b/test/python/basics/core_sends_messages_v0.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/basics/core_sends_messages_v1.py b/test/python/basics/core_sends_messages_v1.py new file mode 100644 index 00000000..e69de29b From 71ce09069a44626e3022af04800673241a71422e Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 10 Mar 2024 20:56:20 -0400 Subject: [PATCH 036/234] Add a comment to a test --- test/python/basics/core_generates_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/basics/core_generates_audio.py b/test/python/basics/core_generates_audio.py index 31fa5dd4..3b024212 100644 --- a/test/python/basics/core_generates_audio.py +++ b/test/python/basics/core_generates_audio.py @@ -12,5 +12,5 @@ assert audio.buffer is not None assert len(audio.buffer) > 0 - assert any(b != 0 for b in audio.buffer) + # Assert that we're not just being given silence From ecf60f03cea270d56453b8a19d0eff9acab2eb96 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 10 Mar 2024 20:58:31 -0400 Subject: [PATCH 037/234] Fix basics.core_resets --- test/python/basics/core_resets.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/python/basics/core_resets.py b/test/python/basics/core_resets.py index ea95b709..1d0ed32f 100644 --- a/test/python/basics/core_resets.py +++ b/test/python/basics/core_resets.py @@ -1,11 +1,13 @@ -from sys import argv -from libretro import default_session +from libretro import Session -with default_session(argv[1], argv[2]) as session: - for i in range(10): +import prelude + +session: Session +with prelude.session() as session: + for i in range(60): session.core.run() session.core.reset() - for i in range(10): + for i in range(60): session.core.run() From 482143b6424cc00df48014c4cfc0552f04ffb3cc Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 10 Mar 2024 21:02:37 -0400 Subject: [PATCH 038/234] Fix basics.core_generates_video --- test/cmake/Basics.cmake | 1 + test/python/basics/core_generates_video.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index edf95025..4b8009ac 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -70,6 +70,7 @@ add_python_test( add_python_test( NAME "Core generates video" TEST_MODULE basics.core_generates_video + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_generates_video.py b/test/python/basics/core_generates_video.py index e30eaa7e..917edf0c 100644 --- a/test/python/basics/core_generates_video.py +++ b/test/python/basics/core_generates_video.py @@ -1,11 +1,13 @@ from array import array -from sys import argv from typing import cast -from libretro import default_session -from libretro.callback.video import SoftwareVideoState +from libretro import Session +from libretro.api.video import SoftwareVideoState -with default_session(argv[1]) as session: +import prelude + +session: Session +with prelude.session() as session: video = cast(SoftwareVideoState, session.video) assert isinstance(video, SoftwareVideoState) From b7f8348ac7197b10b8727d038987321502cec8e7 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 11 Mar 2024 12:48:11 -0400 Subject: [PATCH 039/234] Fix FindPythonModules's use of `print` - Compatibility with Python 2 is no longer an issue --- cmake/FindPythonModules.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/FindPythonModules.cmake b/cmake/FindPythonModules.cmake index ff715f66..cd940ce3 100644 --- a/cmake/FindPythonModules.cmake +++ b/cmake/FindPythonModules.cmake @@ -188,7 +188,7 @@ function (basis_find_python_module CACHEVAR) set (IMPORT_SITE_ERROR FALSE) # 2a. try it with -E option -- the preferred way to run Python execute_process ( - COMMAND "${ARGN_PYTHON_EXECUTABLE}" -E -c "import ${ARGN_NAME}; print ${ARGN_NAME}.__file__" + COMMAND "${ARGN_PYTHON_EXECUTABLE}" -E -c "import ${ARGN_NAME}; print(${ARGN_NAME}.__file__)" RESULT_VARIABLE STATUS OUTPUT_VARIABLE P ERROR_VARIABLE ERROR @@ -200,7 +200,7 @@ function (basis_find_python_module CACHEVAR) # 2b. try it without -E option if (NOT STATUS EQUAL 0) execute_process ( - COMMAND "${ARGN_PYTHON_EXECUTABLE}" -c "import ${ARGN_NAME}; print ${ARGN_NAME}.__file__" + COMMAND "${ARGN_PYTHON_EXECUTABLE}" -c "import ${ARGN_NAME}; print(${ARGN_NAME}.__file__)" RESULT_VARIABLE STATUS OUTPUT_VARIABLE P ERROR_VARIABLE ERROR @@ -213,7 +213,7 @@ function (basis_find_python_module CACHEVAR) set (PYTHONHOME "$ENV{PYTHONHOME}") unset (ENV{PYTHONHOME}) execute_process ( - COMMAND "${ARGN_PYTHON_EXECUTABLE}" -c "import ${ARGN_NAME}; print ${ARGN_NAME}.__file__" + COMMAND "${ARGN_PYTHON_EXECUTABLE}" -c "import ${ARGN_NAME}; print(${ARGN_NAME}.__file__)" RESULT_VARIABLE STATUS OUTPUT_VARIABLE P ERROR_VARIABLE ERROR From f0928d3b1577c16dc2a1da8b81cf5e1dfcd23751 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 11 Mar 2024 12:48:53 -0400 Subject: [PATCH 040/234] Use FindPython instead of FindPython3 --- test/CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0f7903a1..e993901a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,9 @@ -find_package(Python3 3.11 REQUIRED) find_package(PythonModules COMPONENTS libretro) +find_package(Python 3.11 REQUIRED) + +# FindPython sets "Python_EXECUTABLE", but FindPythonModules wants "PYTHON_EXECUTABLE" (note the caps) +set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") + # TODO: Make this required once libretro.py is on PyPI if (PythonModules_libretro_PATH) @@ -252,7 +256,7 @@ function(add_python_test) add_test( NAME "${RETRO_NAME}" - COMMAND ${Python3_EXECUTABLE} + COMMAND ${Python_EXECUTABLE} -m "${RETRO_TEST_MODULE}" "$" "${RETRO_CONTENT}" From 87a248165a25f4fb1713d1268ce991e61633e702 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 09:07:54 -0400 Subject: [PATCH 041/234] Fix some tests --- test/python/basics/core_defines_controller_info.py | 11 ++++++++--- test/python/basics/core_region_correct.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/python/basics/core_defines_controller_info.py b/test/python/basics/core_defines_controller_info.py index e91c8473..a2742a31 100644 --- a/test/python/basics/core_defines_controller_info.py +++ b/test/python/basics/core_defines_controller_info.py @@ -1,9 +1,14 @@ -from libretro import default_session +from pprint import pprint +from libretro import Session import prelude -with default_session(prelude.core_path) as session: +session: Session +with prelude.session() as session: info = session.controller_info + pprint(info) + assert info is not None - assert info.num_types > 0 + assert len(info) > 0 + assert all(len(i) for i in info) diff --git a/test/python/basics/core_region_correct.py b/test/python/basics/core_region_correct.py index 4adbb08e..83585d7b 100644 --- a/test/python/basics/core_region_correct.py +++ b/test/python/basics/core_region_correct.py @@ -1,5 +1,5 @@ from libretro import Core -from libretro.defs import Region +from libretro.api.system import Region import prelude From e6630f003f9e00cbfe347e022e0ad7fe3d914c8f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 16:20:55 -0400 Subject: [PATCH 042/234] Fix core_logs_output.py --- test/python/basics/core_logs_output.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/python/basics/core_logs_output.py b/test/python/basics/core_logs_output.py index 4cfb8c01..6c2409e3 100644 --- a/test/python/basics/core_logs_output.py +++ b/test/python/basics/core_logs_output.py @@ -1,10 +1,11 @@ from typing import cast -from libretro import default_session -from libretro.callback.log import StandardLogger +from libretro import Session +from libretro.api.log import StandardLogger import prelude -with default_session(prelude.core_path, system_dir=prelude.core_system_dir) as session: +session: Session +with prelude.session() as session: log: StandardLogger = cast(StandardLogger, session.log) assert log is not None assert log.records is not None From 684f78184568bd306def0b9d286da801c6469763 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 16:23:40 -0400 Subject: [PATCH 043/234] Make the prelude session functions more flexible --- test/python/prelude.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/python/prelude.py b/test/python/prelude.py index 42579d29..06855b67 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -35,15 +35,15 @@ options_string = os.getenv("RETRO_CORE_OPTIONS") core_path = sys.argv[1] -content_path = sys.argv[2] if len(sys.argv) > 2 else None +content_path = sys.argv[2] if len(sys.argv) > 2 and len(sys.argv[2]) > 0 else None -default_dirs = { +default_args = { "system_dir": system_dir, "save_dir": save_dir, } -def session() -> libretro.Session: - return libretro.default_session(core_path, content_path, **default_dirs) +def session(**kwargs) -> libretro.Session: + return libretro.default_session(core_path, content_path, **(default_args | kwargs)) -def noload_session() -> libretro.Session: - return libretro.default_session(core_path, libretro.session.DoNotLoad, **default_dirs) \ No newline at end of file +def noload_session(**kwargs) -> libretro.Session: + return libretro.default_session(core_path, libretro.session.DoNotLoad, **(default_args | kwargs)) \ No newline at end of file From c6d51a111a29fe5d6d54a58a5be81482adfd5182 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 17:27:18 -0400 Subject: [PATCH 044/234] Implement some new tests --- test/cmake/Basics.cmake | 9 ++++--- test/python/basics/core_defines_options_v0.py | 26 ++++++++++++++++++ test/python/basics/core_defines_options_v1.py | 26 ++++++++++++++++++ test/python/basics/core_defines_options_v2.py | 27 +++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 test/python/basics/core_defines_options_v0.py create mode 100644 test/python/basics/core_defines_options_v1.py create mode 100644 test/python/basics/core_defines_options_v2.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 4b8009ac..a38f1f99 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -162,7 +162,8 @@ add_python_test( add_python_test( NAME "Core sets options (V0 API)" - TEST_MODULE "" + TEST_MODULE basics.core_defines_options_v0 + CONTENT "${NDS_ROM}" ) add_python_test( @@ -245,7 +246,8 @@ add_python_test( add_python_test( NAME "Core sets options (V1 API)" - TEST_MODULE "" + TEST_MODULE basics.core_defines_options_v1 + CONTENT "${NDS_ROM}" ) add_python_test( @@ -272,7 +274,8 @@ add_python_test( add_python_test( NAME "Core sets options (V2 API)" - TEST_MODULE "" + TEST_MODULE basics.core_defines_options_v2 + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_defines_options_v0.py b/test/python/basics/core_defines_options_v0.py new file mode 100644 index 00000000..2468435f --- /dev/null +++ b/test/python/basics/core_defines_options_v0.py @@ -0,0 +1,26 @@ +from collections.abc import Mapping + +from libretro import Session +from libretro.api.options import retro_core_option_v2_definition, StandardOptionState + +import prelude + +definitions: Mapping[bytes, retro_core_option_v2_definition] +session: Session +with prelude.session(options=StandardOptionState(version=0)) as session: + assert session.options is not None + assert session.options.version == 0 + + assert session.options.categories is None + + assert session.options.definitions is not None + assert len(session.options.definitions) > 0 + assert all(d.key for d in session.options.definitions.values()) + assert all(not d.category_key for d in session.options.definitions.values()) + definitions = session.options.definitions + +# Ensure that the frontend's copy of this data is still alive +del session +assert definitions is not None +assert len(definitions) > 0 +assert all(v.key for v in definitions.values()) \ No newline at end of file diff --git a/test/python/basics/core_defines_options_v1.py b/test/python/basics/core_defines_options_v1.py new file mode 100644 index 00000000..c912ef02 --- /dev/null +++ b/test/python/basics/core_defines_options_v1.py @@ -0,0 +1,26 @@ +from collections.abc import Mapping +from libretro import Session +from libretro.api.options import retro_core_option_v2_definition, StandardOptionState + +import prelude + +definitions: Mapping[bytes, retro_core_option_v2_definition] +session: Session +with prelude.session(options=StandardOptionState(version=1)) as session: + assert session.options is not None + assert session.options.version == 1 + + assert session.options.categories is None + + assert session.options.definitions is not None + assert len(session.options.definitions) > 0 + assert all(d.key for d in session.options.definitions.values()) + assert all(not d.category_key for d in session.options.definitions.values()) + + definitions = session.options.definitions + +# Ensure that the frontend's copy of this data is still alive +del session +assert definitions is not None +assert len(definitions) > 0 +assert all(v.key for v in definitions.values()) diff --git a/test/python/basics/core_defines_options_v2.py b/test/python/basics/core_defines_options_v2.py new file mode 100644 index 00000000..3e57946a --- /dev/null +++ b/test/python/basics/core_defines_options_v2.py @@ -0,0 +1,27 @@ +from collections.abc import Mapping + +from libretro import Session +from libretro.api.options import retro_core_option_v2_definition + +import prelude + +definitions: Mapping[bytes, retro_core_option_v2_definition] +session: Session +with prelude.session() as session: + assert session.options is not None + assert session.options.version == 2 + + assert session.options.definitions is not None + assert len(session.options.definitions) > 0 + assert all(v.key for v in session.options.definitions.values()) + definitions = session.options.definitions + + assert session.options.categories is not None + assert len(session.options.categories) > 0 + assert all(c.key for c in session.options.categories.values()) + +# Ensure that the frontend's copy of this data is still alive +del session +assert definitions is not None +assert len(definitions) > 0 +assert all(v.key for v in definitions.values()) \ No newline at end of file From 585d2bd2a36f09070daa81afd42775f928a3f77c Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 18:31:11 -0400 Subject: [PATCH 045/234] Implement a new test --- test/cmake/Basics.cmake | 5 +++-- .../core_sets_options_visibility_update_callback.py | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 test/python/basics/core_sets_options_visibility_update_callback.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index a38f1f99..47d0bd43 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -279,8 +279,9 @@ add_python_test( ) add_python_test( - NAME "Core sets options visibility display update callback" - TEST_MODULE "" + NAME "Core sets options visibility update callback" + TEST_MODULE basics.core_sets_options_visibility_update_callback + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_sets_options_visibility_update_callback.py b/test/python/basics/core_sets_options_visibility_update_callback.py new file mode 100644 index 00000000..85a7b215 --- /dev/null +++ b/test/python/basics/core_sets_options_visibility_update_callback.py @@ -0,0 +1,8 @@ +from libretro import Session + +import prelude + +session: Session +with prelude.session() as session: + assert session.options.update_display_callback + assert session.options.update_display_callback.callback From ea49e1086a2a794dc559a8343508dd2984cdc96c Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 19:40:45 -0400 Subject: [PATCH 046/234] Mark some functions as `extern "C"` --- src/libretro/libretro.cpp | 6 +++--- src/libretro/libretro.hpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index d3a3def6..04d1db32 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -260,7 +260,7 @@ PUBLIC_SYMBOL size_t retro_get_memory_size(unsigned type) { return MelonDsDs::Core.GetMemorySize(type); } -void MelonDsDs::HardwareContextReset() noexcept { +extern "C" void MelonDsDs::HardwareContextReset() noexcept { try { Core.ResetRenderState(); } @@ -285,11 +285,11 @@ void MelonDsDs::HardwareContextReset() noexcept { } } -void MelonDsDs::HardwareContextDestroyed() noexcept { +extern "C" void MelonDsDs::HardwareContextDestroyed() noexcept { Core.DestroyRenderState(); } -bool MelonDsDs::UpdateOptionVisibility() noexcept { +extern "C" bool MelonDsDs::UpdateOptionVisibility() noexcept { return Core.UpdateOptionVisibility(); } diff --git a/src/libretro/libretro.hpp b/src/libretro/libretro.hpp index a75bc287..8f2630a9 100644 --- a/src/libretro/libretro.hpp +++ b/src/libretro/libretro.hpp @@ -36,9 +36,9 @@ using NdsCart = melonDS::NDSCart::CartCommon; using GbaCart = melonDS::GBACart::CartCommon; namespace MelonDsDs { - void HardwareContextReset() noexcept; - void HardwareContextDestroyed() noexcept; - bool UpdateOptionVisibility() noexcept; + extern "C" void HardwareContextReset() noexcept; + extern "C" void HardwareContextDestroyed() noexcept; + extern "C" bool UpdateOptionVisibility() noexcept; } #endif //MELONDS_DS_LIBRETRO_HPP From b68f75b1adcbcca08d1b4abfbb9669c12bd5b034 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 20:10:23 -0400 Subject: [PATCH 047/234] Log the core options version --- src/libretro/environment.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index 5f215c7f..03f70893 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -141,6 +141,8 @@ bool retro::set_core_options(const retro_core_options_v2& options) noexcept { if (!retro::environment(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version)) version = 0; + retro::debug("Frontend reports core options version: {}", version); + if (version >= 2) { if (retro::environment(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, (void *) &options)) { retro::debug("V2 core options set successfully"); From 29f3bf345733d40ad48232ee31261eee51b17130 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 20:11:09 -0400 Subject: [PATCH 048/234] Update a test --- test/cmake/Basics.cmake | 7 ++++--- .../basics/core_sets_options_visibility.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 test/python/basics/core_sets_options_visibility.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 47d0bd43..91125160 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -214,7 +214,7 @@ add_python_test( add_python_test( NAME "Core defines controller info" TEST_MODULE basics.core_defines_controller_info - NDS_SYSFILES + CONTENT "${NDS_ROM}" ) add_python_test( @@ -251,8 +251,9 @@ add_python_test( ) add_python_test( - NAME "Core can set options visibility" - TEST_MODULE "" + NAME "Core hides options when the frontend updates them" + TEST_MODULE basics.core_sets_options_visibility + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_sets_options_visibility.py b/test/python/basics/core_sets_options_visibility.py new file mode 100644 index 00000000..98bbe594 --- /dev/null +++ b/test/python/basics/core_sets_options_visibility.py @@ -0,0 +1,19 @@ +from libretro import Session + +import prelude + +session: Session +with prelude.session() as session: + assert session.options.update_display_callback + assert session.options.update_display_callback.callback + + assert 'melonds_console_mode' in session.options.variables + assert 'melonds_dsi_nand_path' in session.options.variables + + assert session.options.variables['melonds_console_mode'] == b'ds' + assert session.options.visibility['melonds_ds_battery_ok_threshold'] + + session.options.variables['melonds_console_mode'] = b'dsi' + + assert session.options.variables['melonds_console_mode'] == b'dsi' + assert not session.options.visibility['melonds_ds_battery_ok_threshold'] From f8733dafa53b0386d0454a15c8dfe9ab3c085bb3 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 20:25:17 -0400 Subject: [PATCH 049/234] Avoid conflicting with kernel32's GetProcAddress --- src/libretro/core/test.cpp | 2 +- src/libretro/core/test.hpp | 3 ++- src/libretro/environment.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp index e473e41a..ab5d4260 100644 --- a/src/libretro/core/test.cpp +++ b/src/libretro/core/test.cpp @@ -36,7 +36,7 @@ extern "C" const char* libretropy_get_save_directory() { return ok ? path : nullptr; } -retro_proc_address_t MelonDsDs::GetProcAddress(const char* sym) noexcept { +extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) noexcept { if (string_is_equal(sym, "libretropy_add_integers")) return reinterpret_cast(libretropy_add_integers); diff --git a/src/libretro/core/test.hpp b/src/libretro/core/test.hpp index d3806eae..a2018426 100644 --- a/src/libretro/core/test.hpp +++ b/src/libretro/core/test.hpp @@ -21,5 +21,6 @@ namespace MelonDsDs { // test functions for the test suite - retro_proc_address_t GetProcAddress(const char* sym) noexcept; + // named to not conflict with Windows' GetProcAddress + extern "C" retro_proc_address_t GetRetroProcAddress(const char* sym) noexcept; } diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index 03f70893..84465f9f 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -721,7 +721,7 @@ PUBLIC_SYMBOL void retro_set_environment(retro_environment_t cb) { retro::warn("Failed to get log interface"); } - environment(RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK, (void*) MelonDsDs::GetProcAddress); + environment(RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK, (void*) MelonDsDs::GetRetroProcAddress); retro::_supports_bitmasks |= environment(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, nullptr); retro::_supportsPowerStatus |= environment(RETRO_ENVIRONMENT_GET_DEVICE_POWER, nullptr); From 8a8f1c60d526292b0a397f6e634ca0efa2f59529 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 20:26:59 -0400 Subject: [PATCH 050/234] Register a `get_proc_address` correctly --- src/libretro/environment.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index 84465f9f..4b7077f5 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -721,7 +721,8 @@ PUBLIC_SYMBOL void retro_set_environment(retro_environment_t cb) { retro::warn("Failed to get log interface"); } - environment(RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK, (void*) MelonDsDs::GetRetroProcAddress); + retro_get_proc_address_interface get_proc_address {MelonDsDs::GetRetroProcAddress}; + environment(RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK, &get_proc_address); retro::_supports_bitmasks |= environment(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, nullptr); retro::_supportsPowerStatus |= environment(RETRO_ENVIRONMENT_GET_DEVICE_POWER, nullptr); From efb2699063645d681ea35899c702e0255ed0522e Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 20:27:49 -0400 Subject: [PATCH 051/234] Fix core_get_proc_address --- test/cmake/Basics.cmake | 2 +- test/python/basics/core_get_proc_address.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 91125160..432dc461 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -197,7 +197,7 @@ add_python_test( add_python_test( NAME "Core sets retro_get_proc_address_interface" TEST_MODULE basics.core_get_proc_address - NDS_SYSFILES + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_get_proc_address.py b/test/python/basics/core_get_proc_address.py index 14f0deec..e33f1551 100644 --- a/test/python/basics/core_get_proc_address.py +++ b/test/python/basics/core_get_proc_address.py @@ -1,16 +1,18 @@ from ctypes import * -from libretro import default_session, retro_get_proc_address_t + +from libretro import Session +from libretro.api.proc import retro_get_proc_address_t import prelude -with default_session(prelude.core_path) as session: +session: Session +with prelude.session() as session: proc_address_callback = session.proc_address_callback assert proc_address_callback is not None + assert proc_address_callback.get_proc_address is not None - get_proc_address: retro_get_proc_address_t = proc_address_callback.get_proc_address - assert get_proc_address is not None - - add_integers = get_proc_address(b"libretropy_add_integers") + add_integers = session.get_proc_address(b"libretropy_add_integers") + print(add_integers) assert add_integers is not None add_integers_callable = cast(add_integers, CFUNCTYPE(c_int, c_int, c_int)) From bce0c42bd27959276c506191775f2df142d7c459 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 20:28:07 -0400 Subject: [PATCH 052/234] Remove a stray include --- test/python/basics/core_get_proc_address.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/python/basics/core_get_proc_address.py b/test/python/basics/core_get_proc_address.py index e33f1551..2ad58c46 100644 --- a/test/python/basics/core_get_proc_address.py +++ b/test/python/basics/core_get_proc_address.py @@ -1,7 +1,6 @@ from ctypes import * from libretro import Session -from libretro.api.proc import retro_get_proc_address_t import prelude From d3d301f3e40d906888c971e0af993091c10e0b5e Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 20:36:42 -0400 Subject: [PATCH 053/234] Simplify a test --- test/python/basics/core_get_proc_address.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/python/basics/core_get_proc_address.py b/test/python/basics/core_get_proc_address.py index 2ad58c46..79b8b9b4 100644 --- a/test/python/basics/core_get_proc_address.py +++ b/test/python/basics/core_get_proc_address.py @@ -10,11 +10,8 @@ assert proc_address_callback is not None assert proc_address_callback.get_proc_address is not None - add_integers = session.get_proc_address(b"libretropy_add_integers") - print(add_integers) + add_integers = session.get_proc_address(b"libretropy_add_integers", CFUNCTYPE(c_int, c_int, c_int)) assert add_integers is not None - add_integers_callable = cast(add_integers, CFUNCTYPE(c_int, c_int, c_int)) - - assert add_integers_callable is not None - assert add_integers_callable(1, 2) == 3 + assert add_integers is not None + assert add_integers(1, 2) == 3 From 801adaa6ec5ae3b60fa5a7dd181670c53e6381ac Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 12 Mar 2024 23:54:16 -0400 Subject: [PATCH 054/234] Fix a test --- test/cmake/Basics.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 432dc461..dd01a4ec 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -225,7 +225,7 @@ add_python_test( add_python_test( NAME "Core registers support for achievements" TEST_MODULE basics.core_registers_achievement_support - NDS_SYSFILES + CONTENT "${NDS_ROM}" ) add_python_test( From fd5bbbb5125d4ac3a17a96763f7fdebe4b86e7ec Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 08:28:32 -0400 Subject: [PATCH 055/234] Warn about a failed log interface if the envcall returns true without a callback --- src/libretro/environment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index 4b7077f5..fc6f51de 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -714,7 +714,7 @@ PUBLIC_SYMBOL void retro_set_environment(retro_environment_t cb) { environment(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes); retro_log_callback log_callback = {nullptr}; - if (environment(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log_callback)) { + if (environment(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log_callback) && log_callback.log) { retro::_log = log_callback.log; retro::debug("retro_set_environment({})", fmt::ptr(cb)); } else { From f4be2463fb6be294f88c9338c8a2e28dd3a271d2 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 08:29:05 -0400 Subject: [PATCH 056/234] Simplify some tests --- .../python/basics/core_gets_save_directory.py | 23 +++++-------------- .../basics/core_gets_system_directory.py | 23 +++++-------------- .../core_registers_achievement_support.py | 8 +++---- 3 files changed, 16 insertions(+), 38 deletions(-) diff --git a/test/python/basics/core_gets_save_directory.py b/test/python/basics/core_gets_save_directory.py index 7733980e..83b98d3c 100644 --- a/test/python/basics/core_gets_save_directory.py +++ b/test/python/basics/core_gets_save_directory.py @@ -1,23 +1,12 @@ from ctypes import * -from libretro import default_session, retro_get_proc_address_t +from libretro import Session import prelude -with default_session(prelude.core_path, save_dir=prelude.save_directory) as session: - proc_address_callback = session.proc_address_callback - assert proc_address_callback is not None - - get_proc_address: retro_get_proc_address_t = proc_address_callback.get_proc_address - assert get_proc_address is not None - - get_save_directory = get_proc_address(b"libretropy_get_save_directory") +session: Session +with prelude.session() as session: + get_save_directory = session.get_proc_address(b"libretropy_get_save_directory", CFUNCTYPE(c_char_p)) assert get_save_directory is not None - get_save_directory_callable = cast(get_save_directory, CFUNCTYPE(c_char_p)) - - assert get_save_directory_callable is not None - - save_directory_pointer = get_save_directory_callable() - assert isinstance(save_directory_pointer, c_char_p) - - assert string_at(save_directory_pointer) == session.save_dir + save_directory = get_save_directory() + assert save_directory == session.save_dir diff --git a/test/python/basics/core_gets_system_directory.py b/test/python/basics/core_gets_system_directory.py index 4dfe1cad..c5afdb9f 100644 --- a/test/python/basics/core_gets_system_directory.py +++ b/test/python/basics/core_gets_system_directory.py @@ -1,23 +1,12 @@ from ctypes import * -from libretro import default_session, retro_get_proc_address_t +from libretro import Session import prelude -with default_session(prelude.core_path, system_dir=prelude.system_dir) as session: - proc_address_callback = session.proc_address_callback - assert proc_address_callback is not None - - get_proc_address: retro_get_proc_address_t = proc_address_callback.get_proc_address - assert get_proc_address is not None - - get_system_directory = get_proc_address(b"libretropy_get_system_directory") +session: Session +with prelude.session() as session: + get_system_directory = session.get_proc_address(b"libretropy_get_system_directory", CFUNCTYPE(c_char_p)) assert get_system_directory is not None - get_system_directory_callable = cast(get_system_directory, CFUNCTYPE(c_char_p)) - - assert get_system_directory_callable is not None - - system_directory_pointer = get_system_directory_callable() - assert isinstance(system_directory_pointer, c_char_p) - - assert string_at(system_directory_pointer) == session.system_dir + system_directory = get_system_directory() + assert system_directory == session.system_dir diff --git a/test/python/basics/core_registers_achievement_support.py b/test/python/basics/core_registers_achievement_support.py index e780cadb..86db6ea6 100644 --- a/test/python/basics/core_registers_achievement_support.py +++ b/test/python/basics/core_registers_achievement_support.py @@ -1,6 +1,6 @@ -from libretro import default_session - +from libretro import Session import prelude -with default_session(prelude.core_path, prelude.content_path) as env: - assert env.support_achievements is True +session: Session +with prelude.session() as session: + assert session.support_achievements From 1754b7d920642dfa9c396cb6a2fac183f76728b6 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 08:41:01 -0400 Subject: [PATCH 057/234] Ensure that the core sets input descriptors --- test/cmake/Basics.cmake | 2 +- test/python/basics/core_defines_input_descriptors.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index dd01a4ec..9eea0ed0 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -152,7 +152,7 @@ add_python_test( add_python_test( NAME "Core sets input descriptors" TEST_MODULE basics.core_defines_input_descriptors - NDS_SYSFILES + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_defines_input_descriptors.py b/test/python/basics/core_defines_input_descriptors.py index f554ce8c..06f13bb9 100644 --- a/test/python/basics/core_defines_input_descriptors.py +++ b/test/python/basics/core_defines_input_descriptors.py @@ -1,9 +1,9 @@ -from ctypes import * -from libretro import default_session, retro_get_proc_address_t +from libretro import Session import prelude -with default_session(prelude.core_path) as session: +session: Session +with prelude.session() as session: descriptors = session.input_descriptors assert descriptors is not None From ad005841b75825cc322598966c0eca20c0c6431c Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 08:43:24 -0400 Subject: [PATCH 058/234] Fix core_registers_no_content_support --- test/python/basics/core_registers_no_content_support.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/python/basics/core_registers_no_content_support.py b/test/python/basics/core_registers_no_content_support.py index 8897192d..0fad10a1 100644 --- a/test/python/basics/core_registers_no_content_support.py +++ b/test/python/basics/core_registers_no_content_support.py @@ -1,7 +1,7 @@ -from sys import argv -from libretro import default_session +from libretro import Session import prelude -with default_session(argv[1], argv[2]) as env: - assert env.support_no_game is True +session: Session +with prelude.session() as session: + assert session.support_no_game is True From 50fb22a9f7484801b241f06dda676fc92e113598 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 08:56:03 -0400 Subject: [PATCH 059/234] Fix savestate tests --- test/python/basics/core_saves_and_loads_state.py | 8 +++++--- test/python/basics/core_saves_state.py | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/test/python/basics/core_saves_and_loads_state.py b/test/python/basics/core_saves_and_loads_state.py index a5f4e33f..2bd75143 100644 --- a/test/python/basics/core_saves_and_loads_state.py +++ b/test/python/basics/core_saves_and_loads_state.py @@ -1,13 +1,15 @@ -from sys import argv -from libretro import default_session +from libretro import Session import prelude -with default_session(argv[1], argv[2]) as session: +session: Session +with prelude.session() as session: for i in range(30): session.core.run() size = session.core.serialize_size() + assert size > 0 + buffer = bytearray(size) state_saved = session.core.serialize(buffer) diff --git a/test/python/basics/core_saves_state.py b/test/python/basics/core_saves_state.py index 44da198e..e8034589 100644 --- a/test/python/basics/core_saves_state.py +++ b/test/python/basics/core_saves_state.py @@ -1,13 +1,15 @@ -from sys import argv -from libretro import default_session +from libretro import Session import prelude -with default_session(argv[1], argv[2]) as session: +session: Session +with prelude.session() as session: for i in range(10): session.core.run() size = session.core.serialize_size() + assert size > 0 + buffer = bytearray(size) state_saved = session.core.serialize(buffer) From 0d757151b1168d1ddb4d3c6c78bb05c4f20bcfac Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 09:15:37 -0400 Subject: [PATCH 060/234] Fix RAM tests --- test/python/basics/core_exposes_ram.py | 18 ++++++++++++++---- test/python/basics/core_exposes_sram.py | 9 ++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/test/python/basics/core_exposes_ram.py b/test/python/basics/core_exposes_ram.py index 9fedf968..02a7976b 100644 --- a/test/python/basics/core_exposes_ram.py +++ b/test/python/basics/core_exposes_ram.py @@ -1,7 +1,12 @@ -from sys import argv -from libretro import default_session, RETRO_MEMORY_SYSTEM_RAM +from ctypes import * -with default_session(argv[1]) as session: +from libretro import Session +from libretro.h import RETRO_MEMORY_SYSTEM_RAM + +import prelude + +session: Session +with prelude.session() as session: size = session.core.get_memory_size(RETRO_MEMORY_SYSTEM_RAM) assert size is not None @@ -17,4 +22,9 @@ assert memory is not None assert len(memory) == size - assert id(memory) == data.value + + # Let's ensure that we can write to the memory + memory[0:5] = b'hello' + mem_ptr = cast(data, POINTER(c_ubyte)) + assert bytes(mem_ptr[0:5]) == b'hello' + diff --git a/test/python/basics/core_exposes_sram.py b/test/python/basics/core_exposes_sram.py index 27d04211..bc717bbe 100644 --- a/test/python/basics/core_exposes_sram.py +++ b/test/python/basics/core_exposes_sram.py @@ -1,7 +1,10 @@ -from sys import argv -from libretro import default_session, RETRO_MEMORY_SAVE_RAM +from libretro import Session +from libretro.h import RETRO_MEMORY_SAVE_RAM -with default_session(argv[1]) as session: +import prelude + +session: Session +with prelude.session() as session: size = session.core.get_memory_size(RETRO_MEMORY_SAVE_RAM) assert size is not None From 2cb4e55b8a5661901e512bdb78a06155bdfe22e3 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 09:22:59 -0400 Subject: [PATCH 061/234] Allow dashe to separate cheat tokens --- src/libretro/core/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libretro/core/core.hpp b/src/libretro/core/core.hpp index e5aadb5c..418d4e6a 100644 --- a/src/libretro/core/core.hpp +++ b/src/libretro/core/core.hpp @@ -143,7 +143,7 @@ namespace MelonDsDs { bool _syncClock = false; std::unique_ptr _messageScreen = nullptr; // TODO: Switch to compile time regular expressions (see https://compile-time.re) - std::regex _cheatSyntax { "^\\s*[0-9A-Fa-f]{8}([+\\s]*[0-9A-Fa-f]{8})*$", REGEX_OPTIONS }; + std::regex _cheatSyntax { "^\\s*[0-9A-Fa-f]{8}([+\\s-]*[0-9A-Fa-f]{8})*$", REGEX_OPTIONS }; std::regex _tokenSyntax { "[0-9A-Fa-f]{8}", REGEX_OPTIONS }; // This object is meant to be stored in a placement-new'd byte array, // so having this flag lets us detect if the core has been initialized From 5ea67dec7d89d6025f55d40f88f605ef2c1cdfed Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 09:32:23 -0400 Subject: [PATCH 062/234] Fix the cheat test --- test/python/basics/core_applies_cheats.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/python/basics/core_applies_cheats.py b/test/python/basics/core_applies_cheats.py index 84f4c91a..d88d5962 100644 --- a/test/python/basics/core_applies_cheats.py +++ b/test/python/basics/core_applies_cheats.py @@ -1,20 +1,24 @@ -from sys import argv -from libretro import default_session, RETRO_MEMORY_SYSTEM_RAM +from libretro import Session +from libretro.h import RETRO_MEMORY_SYSTEM_RAM -with default_session(argv[1]) as session: +import prelude + +session: Session +with prelude.session() as session: memory = session.core.get_memory(RETRO_MEMORY_SYSTEM_RAM) assert memory is not None - session.core.cheat_set(0, True, b'02000000-DEADBEEF') + session.core.cheat_set(0, True, b'02000000 DEADBEEF') # A trivial cheat code with one instruction: 0XXXXXXX-YYYYYYYY, # where XXXXXXX (in this case, 02000000) is the address to write to # and YYYYYYYY (in this case, DEADBEEF) is the value to write. # See https://mgba-emu.github.io/gbatek/#dscartcheatactionreplayds for more details - assert memory[0:4].tobytes() == b'\xde\xad\xbe\xef' + # Slicing reads the bytes in sequence, not as a word + assert memory[0:4].tobytes() == b'\xef\xbe\xad\xde' - session.core.cheat_set(0, False, b'02000000-CAFEBABE') + session.core.cheat_set(0, False, b'02000000 CAFEBABE') - assert memory[0:4].tobytes() == b'\xde\xad\xbe\xef' + assert memory[0:4].tobytes() == b'\xef\xbe\xad\xde' # Passing False to enabled shouldn't apply the cheat From 8d987bb7a2b68eb907d323e83ebb3213099028ce Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 12:26:02 -0400 Subject: [PATCH 063/234] Add some new tests --- test/cmake/Basics.cmake | 5 +++-- test/python/basics/core_gets_message_version.py | 12 ++++++++++++ test/python/basics/core_gets_options_version.py | 13 +++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 test/python/basics/core_gets_message_version.py create mode 100644 test/python/basics/core_gets_options_version.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 9eea0ed0..c91ff3f7 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -241,7 +241,7 @@ add_python_test( add_python_test( NAME "Core gets options API version" TEST_MODULE basics.core_gets_options_version - NDS_SYSFILES + CONTENT "${NDS_ROM}" ) add_python_test( @@ -258,7 +258,8 @@ add_python_test( add_python_test( NAME "Core gets message API version" - TEST_MODULE "" + TEST_MODULE basics.core_gets_message_version + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_gets_message_version.py b/test/python/basics/core_gets_message_version.py new file mode 100644 index 00000000..c34e915c --- /dev/null +++ b/test/python/basics/core_gets_message_version.py @@ -0,0 +1,12 @@ +from ctypes import * + +from libretro import Session + +import prelude + +session: Session +with prelude.session() as session: + get_message_version = session.get_proc_address(b"libretropy_get_message_version", CFUNCTYPE(c_uint)) + + assert get_message_version is not None + assert get_message_version() == 1 diff --git a/test/python/basics/core_gets_options_version.py b/test/python/basics/core_gets_options_version.py new file mode 100644 index 00000000..329611b7 --- /dev/null +++ b/test/python/basics/core_gets_options_version.py @@ -0,0 +1,13 @@ +from ctypes import * + +from libretro import Session +from libretro.api.options import StandardOptionState + +import prelude + +session: Session +with prelude.session(options=StandardOptionState(1)) as session: + get_options_version = session.get_proc_address(b"libretropy_get_options_version", CFUNCTYPE(c_uint)) + + assert get_options_version is not None + assert get_options_version() == 1 From d3cc7707127ded865dee8c893221ea5818aec9fa Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 12:31:59 -0400 Subject: [PATCH 064/234] Add some test utility functions --- src/libretro/core/test.cpp | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp index ab5d4260..ccae0531 100644 --- a/src/libretro/core/test.cpp +++ b/src/libretro/core/test.cpp @@ -24,6 +24,45 @@ extern "C" int libretropy_add_integers(int a, int b) { return a + b; } +extern "C" bool libretropy_send_message(const char* message) { + return retro::set_error_message("{}", message); +} + +extern "C" const char* libretropy_get_option(const char* key) { + retro_variable var { key, nullptr }; + bool ok = retro::get_variable(&var); + + return ok ? var.value : nullptr; +} + +extern "C" unsigned libretropy_get_options_version() { + unsigned version = 0; + retro::environment(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version); + + return version; +} + +extern "C" unsigned libretropy_get_message_version() { + unsigned version = 0; + retro::environment(RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION, &version); + + return version; +} + +extern "C" bool libretropy_get_input_bitmasks() { + bool ok = false; + retro::environment(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, &ok); + + return ok; +} + +extern "C" uint64_t libretropy_get_input_device_capabilities() { + uint64_t caps = 0; + retro::environment(RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES, &caps); + + return caps; +} + extern "C" const char* libretropy_get_system_directory() { const char* path = nullptr; bool ok = retro::environment(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &path); @@ -46,6 +85,24 @@ extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) if (string_is_equal(sym, "libretropy_get_save_directory")) return reinterpret_cast(libretropy_get_save_directory); + if (string_is_equal(sym, "libretropy_send_message")) + return reinterpret_cast(libretropy_send_message); + + if (string_is_equal(sym, "libretropy_get_option")) + return reinterpret_cast(libretropy_get_option); + + if (string_is_equal(sym, "libretropy_get_options_version")) + return reinterpret_cast(libretropy_get_options_version); + + if (string_is_equal(sym, "libretropy_get_message_version")) + return reinterpret_cast(libretropy_get_message_version); + + if (string_is_equal(sym, "libretropy_get_input_bitmasks")) + return reinterpret_cast(libretropy_get_input_bitmasks); + + if (string_is_equal(sym, "libretropy_get_input_device_capabilities")) + return reinterpret_cast(libretropy_get_input_device_capabilities); + return nullptr; } From efe050f884194124cb049887115091a231aba03b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 12:42:40 -0400 Subject: [PATCH 065/234] Whoops, input bitmasks aren't queried like that --- src/libretro/core/test.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp index ccae0531..adc78333 100644 --- a/src/libretro/core/test.cpp +++ b/src/libretro/core/test.cpp @@ -51,9 +51,8 @@ extern "C" unsigned libretropy_get_message_version() { extern "C" bool libretropy_get_input_bitmasks() { bool ok = false; - retro::environment(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, &ok); - return ok; + return retro::environment(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, &ok); } extern "C" uint64_t libretropy_get_input_device_capabilities() { From a1b4342be6ceb5e21ba67edc454b95768670e568 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 12:44:04 -0400 Subject: [PATCH 066/234] Add/implement some tests --- test/cmake/Basics.cmake | 9 ++++++++- .../python/basics/core_gets_input_bitmask_support.py | 12 ++++++++++++ test/python/basics/core_gets_input_capabilities.py | 12 ++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 test/python/basics/core_gets_input_bitmask_support.py create mode 100644 test/python/basics/core_gets_input_capabilities.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index c91ff3f7..310e7ad9 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -182,6 +182,12 @@ add_python_test( TEST_MODULE basics.core_loads_unloads_without_content ) +add_python_test( + NAME "Core gets input device capabilities" + TEST_MODULE basics.core_gets_input_capabilities + CONTENT "${NDS_ROM}" +) + add_python_test( NAME "Core logs output" TEST_MODULE basics.core_logs_output @@ -235,7 +241,8 @@ add_python_test( add_python_test( NAME "Core gets input bitmask support" - TEST_MODULE "" + TEST_MODULE basics.core_gets_input_bitmask_support + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_gets_input_bitmask_support.py b/test/python/basics/core_gets_input_bitmask_support.py new file mode 100644 index 00000000..d0b247e7 --- /dev/null +++ b/test/python/basics/core_gets_input_bitmask_support.py @@ -0,0 +1,12 @@ +from ctypes import * + +from libretro import Session + +import prelude + +session: Session +with prelude.session() as session: + get_input_bitmasks = session.get_proc_address("libretropy_get_input_bitmasks", CFUNCTYPE(c_bool)) + + assert get_input_bitmasks is not None + assert get_input_bitmasks() diff --git a/test/python/basics/core_gets_input_capabilities.py b/test/python/basics/core_gets_input_capabilities.py new file mode 100644 index 00000000..38dfd806 --- /dev/null +++ b/test/python/basics/core_gets_input_capabilities.py @@ -0,0 +1,12 @@ +from ctypes import * + +from libretro import Session + +import prelude + +session: Session +with prelude.session() as session: + get_input_caps = session.get_proc_address("libretropy_get_input_device_capabilities", CFUNCTYPE(c_uint64)) + + assert get_input_caps is not None + assert get_input_caps() > 0 From 5731607e2ec496d611532a247cf3303df99381cc Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 12:49:26 -0400 Subject: [PATCH 067/234] Add a test --- test/cmake/Basics.cmake | 5 +++-- test/python/basics/core_gets_option.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 test/python/basics/core_gets_option.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 310e7ad9..22410e8e 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -156,8 +156,9 @@ add_python_test( ) add_python_test( - NAME "Core can fetch option values" - TEST_MODULE "" + NAME "Core can get option values" + TEST_MODULE basics.core_gets_option + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_gets_option.py b/test/python/basics/core_gets_option.py new file mode 100644 index 00000000..3d6b73b6 --- /dev/null +++ b/test/python/basics/core_gets_option.py @@ -0,0 +1,14 @@ +from ctypes import * + +from libretro import Session +import prelude + +session: Session +with prelude.session() as session: + get_option = session.get_proc_address("libretropy_get_option", CFUNCTYPE(c_char_p, c_char_p)) + + assert get_option is not None + assert get_option(b"melonds_touch_mode") == b'auto' + + session.options.variables[b"melonds_touch_mode"] = b'touch' + assert get_option(b"melonds_touch_mode") == b'touch' From a7d6c6c8d8b3a7a40bd1178c2abb6fe45efa4b7f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 16:19:58 -0400 Subject: [PATCH 068/234] Add a test --- test/cmake/Basics.cmake | 3 ++- .../python/basics/core_gets_option_updates.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 test/python/basics/core_gets_option_updates.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 22410e8e..d881cabb 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -169,7 +169,8 @@ add_python_test( add_python_test( NAME "Core tests for option updates" - TEST_MODULE "" + TEST_MODULE basics.core_gets_option_updates + CONTENT "${NDS_ROM}" ) add_python_test( diff --git a/test/python/basics/core_gets_option_updates.py b/test/python/basics/core_gets_option_updates.py new file mode 100644 index 00000000..3fbf3904 --- /dev/null +++ b/test/python/basics/core_gets_option_updates.py @@ -0,0 +1,19 @@ +from libretro import Session +import prelude + +session: Session +with prelude.session() as session: + assert not session.options.variable_updated + + assert session.options.variables['melonds_audio_interpolation'] == b'disabled' + assert not session.options.variable_updated + # Assert that checking an option in the frontend doesn't mark variables as updated + + session.options.variables['melonds_audio_interpolation'] = b'linear' + assert session.options.variable_updated + # Assert that setting an option in the frontend marks variables as updated + + session.core.run() + + assert not session.options.variable_updated + # Assert that running the core clears the updated flag From 81e86cf9a1fe34c73103d309eeafd16b3c642322 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 23:38:01 -0400 Subject: [PATCH 069/234] Fix button and pointer tests --- test/cmake/Basics.cmake | 8 ++- .../basics/core_accepts_button_input.py | 30 +++++++--- .../basics/core_accepts_pointer_input.py | 55 +++++++++++++++++++ 3 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 test/python/basics/core_accepts_pointer_input.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index d881cabb..9742b128 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -75,7 +75,9 @@ add_python_test( add_python_test( NAME "Core accepts button input" - TEST_MODULE "basics.core_accepts_button_input" + TEST_MODULE basics.core_accepts_button_input + NDS_SYSFILES # This test needs the NDS system menu + TIMEOUT 30 ) add_python_test( @@ -85,7 +87,9 @@ add_python_test( add_python_test( NAME "Core accepts pointer input" - TEST_MODULE "" + TEST_MODULE basics.core_accepts_pointer_input + NDS_SYSFILES # This test needs the NDS system menu + CORE_OPTION melonds_show_cursor=always ) add_python_test( diff --git a/test/python/basics/core_accepts_button_input.py b/test/python/basics/core_accepts_button_input.py index de6239b4..332a553d 100644 --- a/test/python/basics/core_accepts_button_input.py +++ b/test/python/basics/core_accepts_button_input.py @@ -1,22 +1,38 @@ +from array import array import itertools -from libretro import default_session -from libretro.callback.input import JoypadState +from typing import cast + +from libretro import Session +from libretro.api.input import JoypadState +from libretro.api.video import SoftwareVideoState + +from wand.image import Image import prelude def generate_input(): - yield from itertools.repeat(0, 180) + yield from itertools.repeat(0, 240) yield JoypadState(a=True) yield from itertools.repeat(0) -with default_session(prelude.core_path, system_dir=prelude.system_dir, input_state=generate_input) as session: +session: Session +with prelude.session(input_state=generate_input) as session: + video = cast(SoftwareVideoState, session.video) + for i in range(240): + session.core.run() - for i in range(360): + frame1 = array(video.frame.typecode, video.frame) + for i in range(240): session.core.run() - assert False - # TODO: Assert that I'm in the main menu + frame2 = array(video.frame.typecode, video.frame) + + screen1 = Image(blob=frame1.tobytes(), format="BGRA", width=256, height=192*2, depth=8) + screen2 = Image(blob=frame2.tobytes(), format="BGRA", width=256, height=192*2, depth=8) + + assert screen1.get_image_distortion(screen2, 'absolute') > 50000 + # Assert that the two screenshots differ by more than 50000 pixels diff --git a/test/python/basics/core_accepts_pointer_input.py b/test/python/basics/core_accepts_pointer_input.py new file mode 100644 index 00000000..af2e5eb7 --- /dev/null +++ b/test/python/basics/core_accepts_pointer_input.py @@ -0,0 +1,55 @@ +import math +import time +from array import array +import itertools +from typing import cast +from math import sin, cos, tau, pi + +import wand.display +from libretro import Session +from libretro.api.input import Point +from libretro.api.input.pointer import Pointer +from libretro.api.video import SoftwareVideoState + +from wand.image import Image + +import prelude + + +def generate_circle_points(points: int, radius: int = 1, offset: float = 0): + for i in range(points): + angle = (pi * 2 * i / points) + offset + yield Point(x=int(cos(angle) * radius), y=int(sin(angle) * radius)) + + +def generate_input(): + circle = tuple(generate_circle_points(180, 0x6fff, pi/2)) + yield from iter(circle) + # Circle the pointer around the screen, starting from near the bottom + + yield from itertools.repeat(circle[0], 120) + # Hold the pointer in place for a bit + + yield Pointer(circle[0].x, circle[0].y, True) + # Touch the screen at the current pointer position + + yield from itertools.repeat(circle[0]) + + +session: Session +with prelude.session(input_state=generate_input) as session: + video = cast(SoftwareVideoState, session.video) + for i in range(180): + session.core.run() + + frame1 = array(video.frame.typecode, video.frame) + + for i in range(240): + session.core.run() + + frame2 = array(video.frame.typecode, video.frame) + + screen1 = Image(blob=frame1.tobytes(), format="BGRA", width=256, height=192*2, depth=8) + screen2 = Image(blob=frame2.tobytes(), format="BGRA", width=256, height=192*2, depth=8) + + assert screen1.get_image_distortion(screen2, 'absolute') > 50000 From bf6366e98be3238301a0a9da1ea8b1455cb036bf Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 13 Mar 2024 23:38:14 -0400 Subject: [PATCH 070/234] Pass options to the session --- test/python/prelude.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/python/prelude.py b/test/python/prelude.py index 06855b67..307763f2 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -37,13 +37,21 @@ core_path = sys.argv[1] content_path = sys.argv[2] if len(sys.argv) > 2 and len(sys.argv[2]) > 0 else None +options = { + k.lower().encode(): v.encode() + for k, v in os.environ.items() if k.lower().startswith("melonds_") +} + default_args = { "system_dir": system_dir, "save_dir": save_dir, + "options": options, } + def session(**kwargs) -> libretro.Session: return libretro.default_session(core_path, content_path, **(default_args | kwargs)) + def noload_session(**kwargs) -> libretro.Session: return libretro.default_session(core_path, libretro.session.DoNotLoad, **(default_args | kwargs)) \ No newline at end of file From c28f2697b7a023e1d354e64475877fc4bfa333a9 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 14 Mar 2024 08:59:10 -0400 Subject: [PATCH 071/234] Fix the message interface tests --- test/python/basics/core_sends_messages_v0.py | 23 ++++++++++++++++++++ test/python/basics/core_sends_messages_v1.py | 23 ++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/test/python/basics/core_sends_messages_v0.py b/test/python/basics/core_sends_messages_v0.py index e69de29b..b17e570c 100644 --- a/test/python/basics/core_sends_messages_v0.py +++ b/test/python/basics/core_sends_messages_v0.py @@ -0,0 +1,23 @@ +import logging +import typing +from ctypes import * + +from libretro import Session + +import prelude +from libretro.api.message import LoggerMessageInterface + +session: Session +with prelude.session(message=LoggerMessageInterface(0, logging.getLogger('libretro'))) as session: + message = typing.cast(LoggerMessageInterface, session.message) + assert message is not None + assert message.version == 0 + + send_message = session.get_proc_address(b"libretropy_send_message", CFUNCTYPE(c_bool, c_char_p)) + assert send_message is not None + + assert send_message(b"Hello, world!") + + assert message.messages is not None + assert len(message.messages) > 0 + assert any(m.msg == b"Hello, world!" for m in message.messages) diff --git a/test/python/basics/core_sends_messages_v1.py b/test/python/basics/core_sends_messages_v1.py index e69de29b..735919e5 100644 --- a/test/python/basics/core_sends_messages_v1.py +++ b/test/python/basics/core_sends_messages_v1.py @@ -0,0 +1,23 @@ +import logging +import typing +from ctypes import * + +from libretro import Session + +import prelude +from libretro.api.message import LoggerMessageInterface + +session: Session +with prelude.session(message=LoggerMessageInterface(1, logging.getLogger('libretro'))) as session: + message = typing.cast(LoggerMessageInterface, session.message) + assert message is not None + assert message.version == 1 + + send_message = session.get_proc_address(b"libretropy_send_message", CFUNCTYPE(c_bool, c_char_p)) + assert send_message is not None + + assert send_message(b"Hello, world!") + + assert message.message_exts is not None + assert len(message.message_exts) > 0 + assert any(m.msg == b"Hello, world!" for m in message.message_exts) From f0891f76693d10943ad2a3ce895f8cc6e110c77a Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 14 Mar 2024 15:59:13 -0400 Subject: [PATCH 072/234] Implement a shutdown test, mostly --- test/cmake/Basics.cmake | 9 +++-- test/python/basics/core_can_shut_down.py | 42 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 test/python/basics/core_can_shut_down.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 9742b128..bc408899 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -137,8 +137,13 @@ add_python_test( ) add_python_test( - NAME "Core can shut down internally" - TEST_MODULE "" + NAME "Core can shut down" + TEST_MODULE basics.core_can_shut_down + NDS_SYSFILES + TIMEOUT 30 + DISABLED + # Failure is expected, as the frontend will shut down + # (But segfaults or timeouts are still actual failures) ) add_python_test( diff --git a/test/python/basics/core_can_shut_down.py b/test/python/basics/core_can_shut_down.py new file mode 100644 index 00000000..84fe5357 --- /dev/null +++ b/test/python/basics/core_can_shut_down.py @@ -0,0 +1,42 @@ +import itertools +from typing import cast + +from libretro import Session +from libretro.api.input import DeviceIdJoypad +from libretro.api.video import SoftwareVideoState + +import prelude + + +def generate_input(): + # Wait for intro to finish + yield from itertools.repeat(None, 240) + + # Go to NDS main menu and wait for it to appear + yield DeviceIdJoypad.A + yield from itertools.repeat(None, 59) + + # Select the options menu and wait for the cursor to move + yield DeviceIdJoypad.DOWN + yield from itertools.repeat(None, 29) + + # Go to the options menu and wait for it to appear + yield DeviceIdJoypad.A + yield from itertools.repeat(None, 179) + + # Exit the options menu and wait for the window to appear + yield DeviceIdJoypad.B + yield from itertools.repeat(None, 29) + + # Confirm the exit (exiting the NDS options menu shuts down the console) + yield DeviceIdJoypad.A + yield from itertools.repeat(None) + + +session: Session +with prelude.session(input_state=generate_input) as session: + video = cast(SoftwareVideoState, session.video) + for i in range(600): + session.core.run() + + assert session.is_shutdown From e99007cf1baf770870cc17ec16200fd4f98e6a5f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 14 Mar 2024 17:16:34 -0400 Subject: [PATCH 073/234] Handle the frontend not providing a suitable VFS --- src/libretro/environment.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index fc6f51de..176bdc74 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -731,6 +731,8 @@ PUBLIC_SYMBOL void retro_set_environment(retro_environment_t cb) { retro::_message_interface_version = UINT_MAX; } + retro::debug("Frontend report a message API version {}", retro::_message_interface_version); + if (const char* save_dir = nullptr; environment(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &save_dir) && save_dir) { // First copy the returned path into the buffer we'll use... strlcpy(_saveDir, save_dir, sizeof(_saveDir)); @@ -783,19 +785,14 @@ PUBLIC_SYMBOL void retro_set_environment(retro_environment_t cb) { environment(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*) MelonDsDs::subsystems); - retro_vfs_interface_info vfs { .required_interface_version = 1, .iface = nullptr }; + retro_vfs_interface_info vfs { .required_interface_version = PATH_REQUIRED_VFS_VERSION, .iface = nullptr }; if (environment(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs)) { - if (vfs.required_interface_version >= PATH_REQUIRED_VFS_VERSION) { - path_vfs_init(&vfs); - } - - if (vfs.required_interface_version >= FILESTREAM_REQUIRED_VFS_VERSION) { - filestream_vfs_init(&vfs); - } - - if (vfs.required_interface_version >= DIRENT_REQUIRED_VFS_VERSION) { - dirent_vfs_init(&vfs); - } + debug("Requested VFS interface version {}, got {}", PATH_REQUIRED_VFS_VERSION, vfs.required_interface_version); + path_vfs_init(&vfs); + filestream_vfs_init(&vfs); + dirent_vfs_init(&vfs); + } else { + warn("Could not get VFS interface {}, falling back to libretro-common defaults", PATH_REQUIRED_VFS_VERSION); } yes = true; From 439a111915cc3cba5bd887bd6b0cb874f61adc8f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 14 Mar 2024 17:18:21 -0400 Subject: [PATCH 074/234] Support the `DSI_SYSFILES` test option --- test/CMakeLists.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e993901a..cf5898f9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -292,6 +292,21 @@ function(add_python_test) endif() endif() + if(${RETRO_DSI_SYSFILES}) + if (NOT RETRO_ARM7_DSI_BIOS) + set(RETRO_ARM7_DSI_BIOS 1) + endif() + if (NOT RETRO_ARM9_DSI_BIOS) + set(RETRO_ARM9_DSI_BIOS 1) + endif() + if (NOT RETRO_DSI_FIRMWARE) + set(RETRO_DSI_FIRMWARE 1) + endif() + if (NOT RETRO_DSI_NAND) + set(RETRO_DSI_NAND 1) + endif() + endif() + expose_system_file(ARM7_BIOS) expose_system_file(ARM9_BIOS) expose_system_file(ARM7_DSI_BIOS) From d257cdc9da2c9b75a6d4e0181658208d67a4d4f9 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 19 Mar 2024 19:42:42 -0400 Subject: [PATCH 075/234] Ensure that the path defined in the core options is relative to the system directory --- src/libretro/config/config.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libretro/config/config.cpp b/src/libretro/config/config.cpp index ec4adf97..bfa1d2c7 100644 --- a/src/libretro/config/config.cpp +++ b/src/libretro/config/config.cpp @@ -892,6 +892,7 @@ bool MelonDsDs::RegisterCoreOptions() noexcept { retro::debug("Found a DSi NAND image at \"{}\"", dsiNandPaths[i]); string_view path = dsiNandPaths[i]; path.remove_prefix(sysdir->size() + 1); + retro_assert(!path_is_absolute(path.data())); dsiNandPathOption->values[i].value = path.data(); dsiNandPathOption->values[i].label = nullptr; } From 9e5ce5a3f05c1d213fa0b753ac2667a241338082 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 19 Mar 2024 19:43:28 -0400 Subject: [PATCH 076/234] Load and copy the NDS ARM BIOS files when DSI_SYSFILES is set --- test/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cf5898f9..d352c396 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -293,6 +293,12 @@ function(add_python_test) endif() if(${RETRO_DSI_SYSFILES}) + if (NOT RETRO_ARM7_BIOS) + set(RETRO_ARM7_BIOS 1) + endif() + if (NOT RETRO_ARM9_BIOS) + set(RETRO_ARM9_BIOS 1) + endif() if (NOT RETRO_ARM7_DSI_BIOS) set(RETRO_ARM7_DSI_BIOS 1) endif() From 534738fc57663f718392044d65df223eb82a1a64 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 19 Mar 2024 19:44:13 -0400 Subject: [PATCH 077/234] Implement a test for the VFS --- test/cmake/Basics.cmake | 5 ++++- test/python/basics/core_gets_vfs_interface.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/python/basics/core_gets_vfs_interface.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index bc408899..dac01a3d 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -247,7 +247,10 @@ add_python_test( add_python_test( NAME "Core gets VFS interface" - TEST_MODULE "" + TEST_MODULE basics.core_gets_vfs_interface + DSI_SYSFILES + CORE_OPTION melonds_console_mode=dsi + # We use DSi mode because it use the file system more often ) add_python_test( diff --git a/test/python/basics/core_gets_vfs_interface.py b/test/python/basics/core_gets_vfs_interface.py new file mode 100644 index 00000000..7716d457 --- /dev/null +++ b/test/python/basics/core_gets_vfs_interface.py @@ -0,0 +1,12 @@ +from libretro import Session + +import prelude +from libretro.api.vfs import StandardFileSystemInterface +from libretro.api.vfs.history import HistoryFileSystemInterface + +vfs = HistoryFileSystemInterface(StandardFileSystemInterface()) +session: Session +with prelude.session(vfs=vfs) as session: + assert session.vfs is vfs, f"Expected the session to use the vfs interface we provided, found {session.vfs} instead" + assert vfs.history is not None, "Expected the vfs interface to have a history, found None instead" + assert len(vfs.history) > 0, "Expected the vfs interface to have a non-empty history" From b302017c0bd03739f32bd0b1710f3ebeec360688 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 19 Mar 2024 19:48:58 -0400 Subject: [PATCH 078/234] Set the correct default DSi NAND option - I wasn't stripping the sysdir prefix before --- src/libretro/config/config.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libretro/config/config.cpp b/src/libretro/config/config.cpp index bfa1d2c7..6fb2aa45 100644 --- a/src/libretro/config/config.cpp +++ b/src/libretro/config/config.cpp @@ -889,15 +889,15 @@ bool MelonDsDs::RegisterCoreOptions() noexcept { memset(dsiNandPathOption->values, 0, sizeof(dsiNandPathOption->values)); int length = std::min((int)dsiNandPaths.size(), (int)RETRO_NUM_CORE_OPTION_VALUES_MAX - 1); for (int i = 0; i < length; ++i) { - retro::debug("Found a DSi NAND image at \"{}\"", dsiNandPaths[i]); string_view path = dsiNandPaths[i]; path.remove_prefix(sysdir->size() + 1); + retro::debug("Found a DSi NAND image at \"{}\", presenting it in the options as \"{}\"", dsiNandPaths[i], path); retro_assert(!path_is_absolute(path.data())); dsiNandPathOption->values[i].value = path.data(); dsiNandPathOption->values[i].label = nullptr; } - dsiNandPathOption->default_value = dsiNandPaths[0].c_str(); + dsiNandPathOption->default_value = dsiNandPathOption->values[0].value; } if (!firmware.empty()) { From 466bc54fabf2f2b31445bdae3cbc39fa062269c5 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 20 Mar 2024 08:54:00 -0400 Subject: [PATCH 079/234] Move some tests around --- test/CMakeLists.txt | 486 +------------------------------------- test/cmake/Basics.cmake | 23 +- test/cmake/Booting.cmake | 219 +++++++++++++++++ test/cmake/Errors.cmake | 43 ++++ test/cmake/Firmware.cmake | 118 +++++++++ test/cmake/Reset.cmake | 74 ++++++ test/cmake/Video.cmake | 10 + 7 files changed, 486 insertions(+), 487 deletions(-) create mode 100644 test/cmake/Booting.cmake create mode 100644 test/cmake/Errors.cmake create mode 100644 test/cmake/Firmware.cmake create mode 100644 test/cmake/Reset.cmake create mode 100644 test/cmake/Video.cmake diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d352c396..c8ec5e8e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -381,485 +381,6 @@ add_emutest_test( TEST_SCRIPT "loads-core.lua" ) -## Loading invalid data #################################################### - -add_emutest_test( - NAME "Loading invalid ROM does not cause a crash" - CONTENT "$" - TEST_SCRIPT "no-crash-on-invalid-rom.lua" - WILL_FAIL -) - -# See https://github.com/JesseTG/melonds-ds/issues/70 -add_retroarch_test( - NAME "Core unloads with threaded software rendering" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_sysfile_mode=builtin" - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_threaded_renderer=enabled" -) - -# See https://github.com/JesseTG/melonds-ds/issues/62 -add_emutest_test( - NAME "Core resets when using built-in system files and direct boot (NDS)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - CORE_OPTION melonds_boot_mode="direct" - CORE_OPTION melonds_show_cursor="disabled" - CORE_OPTION melonds_sysfile_mode="builtin" - CORE_OPTION melonds_console_mode="ds" -) - -add_emutest_test( - NAME "Core resets when using native system files and direct boot (NDS)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - ARM7_BIOS - ARM9_BIOS - NDS_FIRMWARE - CORE_OPTION melonds_firmware_nds_path='melonDS DS/${NDS_FIRMWARE_NAME}' - CORE_OPTION melonds_boot_mode="direct" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" - CORE_OPTION melonds_console_mode="ds" -) - -add_emutest_test( - NAME "Core resets when using native system files and native boot (NDS)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - ARM7_BIOS - ARM9_BIOS - NDS_FIRMWARE - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_boot_mode="native" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" -) - -add_emutest_test( - NAME "Core resets when using native system files and direct boot (DSi)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_boot_mode="direct" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" -) - -add_emutest_test( - NAME "Core resets when using native system files and native boot (DSi)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_boot_mode="native" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" -) - -# See https://github.com/JesseTG/melonds-ds/issues/51 -add_retroarch_test( - NAME "Core exposes config options to frontend" - CONTENT "${NDS_ROM}" - FAIL_REGULAR_EXPRESSION "GET_VARIABLE: melonds_[a-z_]+ - Not implemented" -) - -### Preventing Unneeded Loads - -add_retroarch_test( - NAME "Core doesn't try to load native BIOS if using FreeBIOS (NDS)" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=builtin" - PASS_REGULAR_EXPRESSION "Not loading native ARM BIOS files" -) - -### With Content - -add_retroarch_test( - NAME "Core falls back to FreeBIOS if ARM7 BIOS is missing (NDS)" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=direct" - ARM9_BIOS - PASS_REGULAR_EXPRESSION "Falling back to FreeBIOS" -) - -add_retroarch_test( - NAME "Core falls back to FreeBIOS if ARM9 BIOS is missing (NDS)" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=direct" - ARM7_BIOS - PASS_REGULAR_EXPRESSION "Falling back to FreeBIOS" -) - -add_retroarch_test( - NAME "Core falls back to built-in firmware if native firmware is missing (NDS)" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" - ARM7_BIOS - ARM9_BIOS - PASS_REGULAR_EXPRESSION "Falling back to built-in firmware" -) - - -## Direct Boot of NDS game #################################################### - -add_retroarch_test( - NAME "Direct NDS boot with built-in system files succeeds" - CONTENT "${NDS_ROM}" - MAX_FRAMES 180 - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=builtin" -) - -add_retroarch_test( - NAME "Direct NDS boot with native system files succeeds" - CONTENT "${NDS_ROM}" - MAX_FRAMES 180 - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" - ARM7_BIOS - ARM9_BIOS - NDS_FIRMWARE - FAIL_REGULAR_EXPRESSION "Failed to load ARM[79]" - FAIL_REGULAR_EXPRESSION "Failed to load the required firmware" -) - -add_retroarch_test( - NAME "Direct NDS boot with native BIOS and non-bootable firmware succeeds" - CONTENT "${NDS_ROM}" - MAX_FRAMES 180 - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${DSI_FIRMWARE_NAME}" - ARM7_BIOS - ARM9_BIOS - DSI_FIRMWARE - FAIL_REGULAR_EXPRESSION "Failed to load ARM[79]" - FAIL_REGULAR_EXPRESSION "Failed to load the required firmware" -) - -## Boot to firmware #################################################### - -add_retroarch_test( - NAME "NDS boot with no content, native BIOS, and bootable firmware succeeds" - MAX_FRAMES 180 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=native" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" - NDS_FIRMWARE - ARM7_BIOS - ARM9_BIOS -) - -add_retroarch_test( - NAME "NDS boot with no content and built-in system files fails" - MAX_FRAMES 180 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=builtin" - WILL_FAIL -) - -add_retroarch_test( - NAME "NDS boot with no content, native BIOS, and non-bootable firmware fails" - MAX_FRAMES 180 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=builtin" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${DSI_FIRMWARE_NAME}" - ARM7_BIOS - ARM9_BIOS - DSI_FIRMWARE - WILL_FAIL -) - -## DSi boot #################################################### - -add_retroarch_test( - NAME "DSi boot to menu with no NAND image fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=/notfound" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - WILL_FAIL -) - -add_retroarch_test( - NAME "DSi boot to menu with no NDS BIOS fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND - WILL_FAIL -) - -add_retroarch_test( - NAME "DSi boot to menu with no DSi BIOS fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - DSI_FIRMWARE - DSI_NAND - WILL_FAIL -) - -add_retroarch_test( - NAME "DSi boot to menu with NDS firmware fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_NAND - WILL_FAIL -) - -add_retroarch_test( - NAME "DSi boot to menu with no firmware fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_NAND - WILL_FAIL -) - -add_retroarch_test( - NAME "DSi boot to menu with all system files succeeds" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND -) - -add_retroarch_test( - NAME "Direct DSi boot to NDS game with no NAND image fails" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=/notfound" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - WILL_FAIL -) - -add_retroarch_test( - NAME "Direct DSi boot to NDS game with no NDS BIOS image fails" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND - WILL_FAIL -) - -add_retroarch_test( - NAME "Direct DSi boot to NDS game with no DSi BIOS image fails" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - DSI_FIRMWARE - DSI_NAND - WILL_FAIL -) - -add_retroarch_test( - NAME "Direct DSi boot to NDS game with all system files succeeds" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND -) - -### Using wfcsettings.bin - -# See https://github.com/JesseTG/melonds-ds/issues/59 -add_retroarch_test( - NAME "Native firmware is not overwritten by contents of wfcsettings.bin" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=native" - ARM7_BIOS - ARM9_BIOS - NDS_FIRMWARE - REQUIRE_FILE_SIZE_UNCHANGED "system/melonDS DS/${NDS_FIRMWARE_NAME}" -) - -add_retroarch_test( - NAME "Core saves wi-fi settings to wfcsettings.bin when using built-in firmware" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_firmware_nds_path=/builtin" - ARM7_BIOS - ARM9_BIOS - REQUIRE_FILE_CREATED "system/melonDS DS/wfcsettings.bin" -) - -### Overwriting Firmware - -add_emutest_test( - NAME "NDS firmware is not overwritten when switching from DS to DSi mode" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "firmware-not-overwritten.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="ds" - CORE_OPTION melonds_boot_directly="false" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" -) - -add_emutest_test( - NAME "NDS firmware is not overwritten when switching from DSi to NDS mode" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "firmware-not-overwritten.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_boot_directly="false" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" -) - -add_emutest_test( - NAME "DSi firmware is not overwritten when switching from NDS to DSi mode" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "firmware-not-overwritten.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_boot_directly="false" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" -) - -add_emutest_test( - NAME "DSi firmware is not overwritten when switching from DSi to NDS mode" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "firmware-not-overwritten.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_boot_directly="false" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" -) - # TODO: Write the following test capabilities: # - Copying a system file to a particular location # - Select a GBA ROM @@ -875,4 +396,9 @@ add_emutest_test( # (will need to find a test case besides Pokemon) include(cmake/Basics.cmake) -include(cmake/TestHomebrewSDCards.cmake) \ No newline at end of file +include(cmake/Booting.cmake) +include(cmake/Errors.cmake) +include(cmake/Firmware.cmake) +include(cmake/Reset.cmake) +include(cmake/TestHomebrewSDCards.cmake) +include(cmake/Video.cmake) \ No newline at end of file diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index dac01a3d..7d50ad11 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -82,7 +82,8 @@ add_python_test( add_python_test( NAME "Core accepts analog input" - TEST_MODULE "" + TEST_MODULE basics.core_accepts_analog_input + NDS_SYSFILES # This test needs the NDS system menu ) add_python_test( @@ -127,8 +128,11 @@ add_python_test( add_python_test( NAME "Core rotates the screen" - TEST_MODULE "" -) + TEST_MODULE basics.core_rotates_screen + NDS_SYSFILES + DISABLED +) # TODO: Implement this test +# TODO: Set the screen layout sequence add_python_test( NAME "Core can send messages (API V0)" @@ -226,7 +230,8 @@ add_python_test( add_python_test( NAME "Core loads and unloads with subsystem content" TEST_MODULE "" -) + DISABLED +) # TODO: Implement this test add_python_test( NAME "Core defines controller info" @@ -237,7 +242,8 @@ add_python_test( add_python_test( NAME "Core sets geometry at runtime" TEST_MODULE "" -) + DISABLED +) # TODO: Implement this test add_python_test( NAME "Core registers support for achievements" @@ -295,6 +301,7 @@ add_python_test( NDS_SYSFILES ) +# See https://github.com/JesseTG/melonds-ds/issues/51 add_python_test( NAME "Core sets options (V2 API)" TEST_MODULE basics.core_defines_options_v2 @@ -310,12 +317,14 @@ add_python_test( add_python_test( NAME "Core accepts microphone input" TEST_MODULE "" -) + DISABLED +) # TODO: Implement this test add_python_test( NAME "Core queries device power state" TEST_MODULE "" -) + DISABLED +) # TODO: Implement this test diff --git a/test/cmake/Booting.cmake b/test/cmake/Booting.cmake new file mode 100644 index 00000000..003b628b --- /dev/null +++ b/test/cmake/Booting.cmake @@ -0,0 +1,219 @@ +## Direct Boot of NDS game #################################################### + +add_retroarch_test( + NAME "Direct NDS boot with built-in system files succeeds" + CONTENT "${NDS_ROM}" + MAX_FRAMES 180 + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=builtin" +) + +add_retroarch_test( + NAME "Direct NDS boot with native system files succeeds" + CONTENT "${NDS_ROM}" + MAX_FRAMES 180 + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + ARM7_BIOS + ARM9_BIOS + NDS_FIRMWARE + FAIL_REGULAR_EXPRESSION "Failed to load ARM[79]" + FAIL_REGULAR_EXPRESSION "Failed to load the required firmware" +) + +add_retroarch_test( + NAME "Direct NDS boot with native BIOS and non-bootable firmware succeeds" + CONTENT "${NDS_ROM}" + MAX_FRAMES 180 + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${DSI_FIRMWARE_NAME}" + ARM7_BIOS + ARM9_BIOS + DSI_FIRMWARE + FAIL_REGULAR_EXPRESSION "Failed to load ARM[79]" + FAIL_REGULAR_EXPRESSION "Failed to load the required firmware" +) + +## Boot to firmware #################################################### + +add_retroarch_test( + NAME "NDS boot with no content, native BIOS, and bootable firmware succeeds" + MAX_FRAMES 180 + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_boot_mode=native" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + NDS_FIRMWARE + ARM7_BIOS + ARM9_BIOS +) + +add_retroarch_test( + NAME "NDS boot with no content and built-in system files fails" + MAX_FRAMES 180 + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=builtin" + WILL_FAIL +) + +add_retroarch_test( + NAME "NDS boot with no content, native BIOS, and non-bootable firmware fails" + MAX_FRAMES 180 + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=builtin" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${DSI_FIRMWARE_NAME}" + ARM7_BIOS + ARM9_BIOS + DSI_FIRMWARE + WILL_FAIL +) + +## DSi boot #################################################### + +add_retroarch_test( + NAME "DSi boot to menu with no NAND image fails" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=/notfound" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + WILL_FAIL +) + +add_retroarch_test( + NAME "DSi boot to menu with no NDS BIOS fails" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + DSI_NAND + WILL_FAIL +) + +add_retroarch_test( + NAME "DSi boot to menu with no DSi BIOS fails" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + DSI_FIRMWARE + DSI_NAND + WILL_FAIL +) + +add_retroarch_test( + NAME "DSi boot to menu with NDS firmware fails" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + NDS_FIRMWARE + DSI_NAND + WILL_FAIL +) + +add_retroarch_test( + NAME "DSi boot to menu with no firmware fails" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_NAND + WILL_FAIL +) + +add_retroarch_test( + NAME "DSi boot to menu with all system files succeeds" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + DSI_NAND +) + +add_retroarch_test( + NAME "Direct DSi boot to NDS game with no NAND image fails" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=/notfound" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + WILL_FAIL +) + +add_retroarch_test( + NAME "Direct DSi boot to NDS game with no NDS BIOS image fails" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + DSI_NAND + WILL_FAIL +) + +add_retroarch_test( + NAME "Direct DSi boot to NDS game with no DSi BIOS image fails" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + DSI_FIRMWARE + DSI_NAND + WILL_FAIL +) + +add_retroarch_test( + NAME "Direct DSi boot to NDS game with all system files succeeds" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + DSI_NAND +) \ No newline at end of file diff --git a/test/cmake/Errors.cmake b/test/cmake/Errors.cmake new file mode 100644 index 00000000..204c715b --- /dev/null +++ b/test/cmake/Errors.cmake @@ -0,0 +1,43 @@ +### With Content + +add_retroarch_test( + NAME "Core falls back to FreeBIOS if ARM7 BIOS is missing (NDS)" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_boot_mode=direct" + ARM9_BIOS + PASS_REGULAR_EXPRESSION "Falling back to FreeBIOS" +) + +add_retroarch_test( + NAME "Core falls back to FreeBIOS if ARM9 BIOS is missing (NDS)" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_boot_mode=direct" + ARM7_BIOS + PASS_REGULAR_EXPRESSION "Falling back to FreeBIOS" +) + +add_retroarch_test( + NAME "Core falls back to built-in firmware if native firmware is missing (NDS)" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + ARM7_BIOS + ARM9_BIOS + PASS_REGULAR_EXPRESSION "Falling back to built-in firmware" +) + +add_emutest_test( + NAME "Loading invalid ROM does not cause a crash" + CONTENT "$" + TEST_SCRIPT "no-crash-on-invalid-rom.lua" + WILL_FAIL +) diff --git a/test/cmake/Firmware.cmake b/test/cmake/Firmware.cmake new file mode 100644 index 00000000..e3115c99 --- /dev/null +++ b/test/cmake/Firmware.cmake @@ -0,0 +1,118 @@ +### Preventing Unneeded Loads +add_retroarch_test( + NAME "Core doesn't try to load native BIOS if using FreeBIOS (NDS)" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=builtin" + PASS_REGULAR_EXPRESSION "Not loading native ARM BIOS files" +) + + + +# See https://github.com/JesseTG/melonds-ds/issues/59 +add_retroarch_test( + NAME "Native firmware is not overwritten by contents of wfcsettings.bin" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_boot_mode=native" + ARM7_BIOS + ARM9_BIOS + NDS_FIRMWARE + REQUIRE_FILE_SIZE_UNCHANGED "system/melonDS DS/${NDS_FIRMWARE_NAME}" +) + +add_retroarch_test( + NAME "Core saves wi-fi settings to wfcsettings.bin when using built-in firmware" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_firmware_nds_path=/builtin" + ARM7_BIOS + ARM9_BIOS + REQUIRE_FILE_CREATED "system/melonDS DS/wfcsettings.bin" +) + +### Overwriting Firmware + +add_emutest_test( + NAME "NDS firmware is not overwritten when switching from DS to DSi mode" + CONTENT "${NDS_ROM}" + TEST_SCRIPT "firmware-not-overwritten.lua" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + NDS_FIRMWARE + DSI_FIRMWARE + DSI_NAND + CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION melonds_console_mode="ds" + CORE_OPTION melonds_boot_directly="false" + CORE_OPTION melonds_sysfile_mode="native" + CORE_OPTION melonds_show_cursor="disabled" +) + +add_emutest_test( + NAME "NDS firmware is not overwritten when switching from DSi to NDS mode" + CONTENT "${NDS_ROM}" + TEST_SCRIPT "firmware-not-overwritten.lua" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + NDS_FIRMWARE + DSI_FIRMWARE + DSI_NAND + CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION melonds_console_mode="dsi" + CORE_OPTION melonds_boot_directly="false" + CORE_OPTION melonds_sysfile_mode="native" + CORE_OPTION melonds_show_cursor="disabled" +) + +add_emutest_test( + NAME "DSi firmware is not overwritten when switching from NDS to DSi mode" + CONTENT "${NDS_ROM}" + TEST_SCRIPT "firmware-not-overwritten.lua" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + NDS_FIRMWARE + DSI_FIRMWARE + DSI_NAND + CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION melonds_console_mode="dsi" + CORE_OPTION melonds_boot_directly="false" + CORE_OPTION melonds_sysfile_mode="native" + CORE_OPTION melonds_show_cursor="disabled" +) + +add_emutest_test( + NAME "DSi firmware is not overwritten when switching from DSi to NDS mode" + CONTENT "${NDS_ROM}" + TEST_SCRIPT "firmware-not-overwritten.lua" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + NDS_FIRMWARE + DSI_FIRMWARE + DSI_NAND + CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION melonds_console_mode="dsi" + CORE_OPTION melonds_boot_directly="false" + CORE_OPTION melonds_sysfile_mode="native" + CORE_OPTION melonds_show_cursor="disabled" +) \ No newline at end of file diff --git a/test/cmake/Reset.cmake b/test/cmake/Reset.cmake new file mode 100644 index 00000000..1e1d09fc --- /dev/null +++ b/test/cmake/Reset.cmake @@ -0,0 +1,74 @@ + +# See https://github.com/JesseTG/melonds-ds/issues/62 +add_emutest_test( + NAME "Core resets when using built-in system files and direct boot (NDS)" + CONTENT "${NDS_ROM}" + TEST_SCRIPT "no-hang-on-reboot.lua" + CORE_OPTION melonds_boot_mode="direct" + CORE_OPTION melonds_show_cursor="disabled" + CORE_OPTION melonds_sysfile_mode="builtin" + CORE_OPTION melonds_console_mode="ds" +) + +add_emutest_test( + NAME "Core resets when using native system files and direct boot (NDS)" + CONTENT "${NDS_ROM}" + TEST_SCRIPT "no-hang-on-reboot.lua" + ARM7_BIOS + ARM9_BIOS + NDS_FIRMWARE + CORE_OPTION melonds_firmware_nds_path='melonDS DS/${NDS_FIRMWARE_NAME}' + CORE_OPTION melonds_boot_mode="direct" + CORE_OPTION melonds_sysfile_mode="native" + CORE_OPTION melonds_show_cursor="disabled" + CORE_OPTION melonds_console_mode="ds" +) + +add_emutest_test( + NAME "Core resets when using native system files and native boot (NDS)" + CONTENT "${NDS_ROM}" + TEST_SCRIPT "no-hang-on-reboot.lua" + ARM7_BIOS + ARM9_BIOS + NDS_FIRMWARE + CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION melonds_boot_mode="native" + CORE_OPTION melonds_sysfile_mode="native" + CORE_OPTION melonds_show_cursor="disabled" +) + +add_emutest_test( + NAME "Core resets when using native system files and direct boot (DSi)" + CONTENT "${NDS_ROM}" + TEST_SCRIPT "no-hang-on-reboot.lua" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + DSI_NAND + CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION melonds_console_mode="dsi" + CORE_OPTION melonds_boot_mode="direct" + CORE_OPTION melonds_sysfile_mode="native" + CORE_OPTION melonds_show_cursor="disabled" +) + +add_emutest_test( + NAME "Core resets when using native system files and native boot (DSi)" + CONTENT "${NDS_ROM}" + TEST_SCRIPT "no-hang-on-reboot.lua" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + DSI_NAND + CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION melonds_console_mode="dsi" + CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION melonds_boot_mode="native" + CORE_OPTION melonds_sysfile_mode="native" + CORE_OPTION melonds_show_cursor="disabled" +) \ No newline at end of file diff --git a/test/cmake/Video.cmake b/test/cmake/Video.cmake new file mode 100644 index 00000000..a303a73c --- /dev/null +++ b/test/cmake/Video.cmake @@ -0,0 +1,10 @@ +# See https://github.com/JesseTG/melonds-ds/issues/70 +add_retroarch_test( + NAME "Core unloads with threaded software rendering" + CONTENT "${NDS_ROM}" + MAX_FRAMES 6 + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_sysfile_mode=builtin" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_threaded_renderer=enabled" +) \ No newline at end of file From 1dff30e5bcb6dd8fcc5e9a01d80024157a4698b7 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 20 Mar 2024 09:55:27 -0400 Subject: [PATCH 080/234] Pass the formatted text directly to the log callback --- src/libretro/environment.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libretro/environment.cpp b/src/libretro/environment.cpp index 176bdc74..0ea6e4ff 100644 --- a/src/libretro/environment.cpp +++ b/src/libretro/environment.cpp @@ -306,10 +306,11 @@ void retro::fmt_log(retro_log_level level, fmt::string_view fmt, fmt::format_arg if (buffer[buffer.size() - 1] == '\n') buffer[buffer.size() - 1] = '\0'; + buffer.push_back('\n'); buffer.push_back('\0'); if (_log) { - _log(level, "%s\n", buffer.data()); + _log(level, buffer.data()); #ifdef TRACY_ENABLE if (tracy::ProfilerAvailable()) { TracyMessageCS(buffer.data(), buffer.size() - 1, GetLogColor(level), 8); @@ -717,7 +718,8 @@ PUBLIC_SYMBOL void retro_set_environment(retro_environment_t cb) { if (environment(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log_callback) && log_callback.log) { retro::_log = log_callback.log; retro::debug("retro_set_environment({})", fmt::ptr(cb)); - } else { + } else if (!retro::_log) { + // retro_set_environment might be called multiple times with different callbacks retro::warn("Failed to get log interface"); } From 59e3cf6ecffd1edba87a77237586c8d750ec9a33 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 20 Mar 2024 10:01:30 -0400 Subject: [PATCH 081/234] Fix a log test --- test/python/basics/core_logs_output.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/python/basics/core_logs_output.py b/test/python/basics/core_logs_output.py index 6c2409e3..33e66196 100644 --- a/test/python/basics/core_logs_output.py +++ b/test/python/basics/core_logs_output.py @@ -1,12 +1,12 @@ from typing import cast from libretro import Session -from libretro.api.log import StandardLogger +from libretro.api.log import UnformattedLogger import prelude session: Session with prelude.session() as session: - log: StandardLogger = cast(StandardLogger, session.log) + log: UnformattedLogger = cast(UnformattedLogger, session.log) assert log is not None assert log.records is not None - assert len(log.records) > 0 + assert len(log.records) > 0 \ No newline at end of file From ad4ffaf9d938bcd985f2d58030f9ebeb0bd002d8 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 20 Mar 2024 13:14:24 -0400 Subject: [PATCH 082/234] Implement another test --- test/cmake/Basics.cmake | 10 +++------- test/python/basics/core_gets_power_state.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 test/python/basics/core_gets_power_state.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 7d50ad11..38869df3 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -322,10 +322,6 @@ add_python_test( add_python_test( NAME "Core queries device power state" - TEST_MODULE "" - DISABLED -) # TODO: Implement this test - - - - + TEST_MODULE basics.core_gets_power_state + CONTENT "${NDS_ROM}" +) diff --git a/test/python/basics/core_gets_power_state.py b/test/python/basics/core_gets_power_state.py new file mode 100644 index 00000000..6a9f41b8 --- /dev/null +++ b/test/python/basics/core_gets_power_state.py @@ -0,0 +1,16 @@ +from ctypes import * + +from libretro import Session + +import prelude +from libretro.api.power import retro_device_power, PowerState + +power = retro_device_power(PowerState.DISCHARGING, 3540, 52) +session: Session +with prelude.session(device_power=power) as session: + get_power = session.get_proc_address(b"libretropy_get_power", CFUNCTYPE(bool, POINTER(retro_device_power))) + + assert get_power is not None + returned_power = retro_device_power() + assert get_power(byref(returned_power)) + assert power == returned_power, f"{power} != {returned_power}" From 1385f47a57d2f3e9e1e5502319eba3f08cb49bf2 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 20 Mar 2024 22:01:00 -0400 Subject: [PATCH 083/234] Remove a redundant `assert` --- test/python/basics/core_get_proc_address.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/python/basics/core_get_proc_address.py b/test/python/basics/core_get_proc_address.py index 79b8b9b4..e38e52f2 100644 --- a/test/python/basics/core_get_proc_address.py +++ b/test/python/basics/core_get_proc_address.py @@ -11,7 +11,5 @@ assert proc_address_callback.get_proc_address is not None add_integers = session.get_proc_address(b"libretropy_add_integers", CFUNCTYPE(c_int, c_int, c_int)) - assert add_integers is not None - assert add_integers is not None assert add_integers(1, 2) == 3 From ad4c360e8dec2703ba95d1ca16e5b203822d85d5 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 20 Mar 2024 22:18:45 -0400 Subject: [PATCH 084/234] Move some tests to libretro.py --- src/libretro/core/core.hpp | 2 + src/libretro/core/test.cpp | 54 +++++++++++++++ src/libretro/libretro.cpp | 2 +- test/cmake/Booting.cmake | 14 ++-- test/cmake/Errors.cmake | 67 +++++++++---------- test/python/errors/__init__.py | 0 .../errors/core_falls_back_to_freebios.py | 27 ++++++++ 7 files changed, 122 insertions(+), 44 deletions(-) create mode 100644 test/python/errors/__init__.py create mode 100644 test/python/errors/core_falls_back_to_freebios.py diff --git a/src/libretro/core/core.hpp b/src/libretro/core/core.hpp index 418d4e6a..eee08efc 100644 --- a/src/libretro/core/core.hpp +++ b/src/libretro/core/core.hpp @@ -93,6 +93,8 @@ namespace MelonDsDs { void WriteGbaSave(std::span savedata, uint32_t writeoffset, uint32_t writelen) noexcept; void WriteFirmware(const melonDS::Firmware& firmware, uint32_t writeoffset, uint32_t writelen) noexcept; bool UpdateOptionVisibility() noexcept; + + const melonDS::NDS* GetConsole() const noexcept { return Console.get(); } private: static constexpr auto REGEX_OPTIONS = std::regex_constants::ECMAScript | std::regex_constants::optimize; [[gnu::cold]] void ApplyConfig(const CoreConfig& config) noexcept; diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp index adc78333..3eefe4d4 100644 --- a/src/libretro/core/test.cpp +++ b/src/libretro/core/test.cpp @@ -18,8 +18,15 @@ #include +#include "core.hpp" #include "environment.hpp" +namespace MelonDsDs +{ + // sshhh...don't tell anyone + extern CoreState& Core; +} + extern "C" int libretropy_add_integers(int a, int b) { return a + b; } @@ -74,6 +81,38 @@ extern "C" const char* libretropy_get_save_directory() { return ok ? path : nullptr; } +extern "C" bool libretropy_get_power(retro_device_power* power) { + return retro::environment(RETRO_ENVIRONMENT_GET_DEVICE_POWER, power); +} + +extern "C" bool melondsds_console_exists() { + using namespace MelonDsDs; + const melonDS::NDS* console = Core.GetConsole(); + + return console != nullptr; +} + +extern "C" bool melondsds_arm7_bios_native() { + using namespace MelonDsDs; + const melonDS::NDS* console = Core.GetConsole(); + + return console ? console->IsLoadedARM7BIOSKnownNative() : false; +} + +extern "C" bool melondsds_arm9_bios_native() { + using namespace MelonDsDs; + const melonDS::NDS* console = Core.GetConsole(); + + return console ? console->IsLoadedARM7BIOSKnownNative() : false; +} + +extern "C" bool melondsds_firmware_native() { + using namespace MelonDsDs; + const melonDS::NDS* console = Core.GetConsole(); + + return console ? console->GetFirmware().GetHeader().Identifier != melonDS::GENERATED_FIRMWARE_IDENTIFIER : false; +} + extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) noexcept { if (string_is_equal(sym, "libretropy_add_integers")) return reinterpret_cast(libretropy_add_integers); @@ -102,6 +141,21 @@ extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) if (string_is_equal(sym, "libretropy_get_input_device_capabilities")) return reinterpret_cast(libretropy_get_input_device_capabilities); + if (string_is_equal(sym, "libretropy_get_power")) + return reinterpret_cast(libretropy_get_power); + + if (string_is_equal(sym, "melondsds_console_exists")) + return reinterpret_cast(melondsds_console_exists); + + if (string_is_equal(sym, "melondsds_arm7_bios_native")) + return reinterpret_cast(melondsds_arm7_bios_native); + + if (string_is_equal(sym, "melondsds_arm9_bios_native")) + return reinterpret_cast(melondsds_arm9_bios_native); + + if (string_is_equal(sym, "melondsds_firmware_native")) + return reinterpret_cast(melondsds_firmware_native); + return nullptr; } diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index 04d1db32..a81a452b 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -62,7 +62,7 @@ using retro::task::TaskSpec; namespace MelonDsDs { // Aligned with CoreState to prevent undefined behavior alignas(CoreState) static std::array CoreStateBuffer; - static CoreState& Core = *reinterpret_cast(CoreStateBuffer.data()); + CoreState& Core = *reinterpret_cast(CoreStateBuffer.data()); } PUBLIC_SYMBOL void retro_init(void) { diff --git a/test/cmake/Booting.cmake b/test/cmake/Booting.cmake index 003b628b..37d2f235 100644 --- a/test/cmake/Booting.cmake +++ b/test/cmake/Booting.cmake @@ -1,12 +1,12 @@ ## Direct Boot of NDS game #################################################### -add_retroarch_test( - NAME "Direct NDS boot with built-in system files succeeds" - CONTENT "${NDS_ROM}" - MAX_FRAMES 180 - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=builtin" +add_python_test( + NAME "Direct NDS boot with built-in system files succeeds" + TEST_MODULE basics.core_run_frames + CONTENT "${NDS_ROM}" + CORE_OPTION melonds_boot_mode=direct + CORE_OPTION melonds_console_mode=ds + CORE_OPTION melonds_sysfile_mode=builtin ) add_retroarch_test( diff --git a/test/cmake/Errors.cmake b/test/cmake/Errors.cmake index 204c715b..4e0e6ab4 100644 --- a/test/cmake/Errors.cmake +++ b/test/cmake/Errors.cmake @@ -1,43 +1,38 @@ -### With Content - -add_retroarch_test( - NAME "Core falls back to FreeBIOS if ARM7 BIOS is missing (NDS)" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=direct" - ARM9_BIOS - PASS_REGULAR_EXPRESSION "Falling back to FreeBIOS" +add_python_test( + NAME "Core falls back to FreeBIOS if ARM7 BIOS is missing (NDS)" + CONTENT "${NDS_ROM}" + TEST_MODULE errors.core_falls_back_to_freebios + CORE_OPTION melonds_console_mode=ds + CORE_OPTION melonds_sysfile_mode=native + CORE_OPTION melonds_boot_mode=direct + ARM9_BIOS ) -add_retroarch_test( - NAME "Core falls back to FreeBIOS if ARM9 BIOS is missing (NDS)" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=direct" - ARM7_BIOS - PASS_REGULAR_EXPRESSION "Falling back to FreeBIOS" +add_python_test( + NAME "Core falls back to FreeBIOS if ARM9 BIOS is missing (NDS)" + CONTENT "${NDS_ROM}" + TEST_MODULE errors.core_falls_back_to_freebios + CORE_OPTION melonds_console_mode=ds + CORE_OPTION melonds_sysfile_mode=native + CORE_OPTION melonds_boot_mode=direct + ARM7_BIOS ) -add_retroarch_test( - NAME "Core falls back to built-in firmware if native firmware is missing (NDS)" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" - ARM7_BIOS - ARM9_BIOS - PASS_REGULAR_EXPRESSION "Falling back to built-in firmware" +add_python_test( + NAME "Core falls back to built-in firmware if native firmware is missing (NDS)" + CONTENT "${NDS_ROM}" + TEST_MODULE errors.core_falls_back_to_freebios + CORE_OPTION melonds_console_mode=ds + CORE_OPTION melonds_sysfile_mode=native + CORE_OPTION melonds_boot_mode=direct + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + ARM7_BIOS + ARM9_BIOS ) -add_emutest_test( - NAME "Loading invalid ROM does not cause a crash" - CONTENT "$" - TEST_SCRIPT "no-crash-on-invalid-rom.lua" - WILL_FAIL +add_python_test( + NAME "Loading invalid ROM does not cause a crash" + TEST_MODULE basics.core_loads_unloads_with_content + CONTENT "$" + WILL_FAIL ) diff --git a/test/python/errors/__init__.py b/test/python/errors/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/errors/core_falls_back_to_freebios.py b/test/python/errors/core_falls_back_to_freebios.py new file mode 100644 index 00000000..a62c8d9d --- /dev/null +++ b/test/python/errors/core_falls_back_to_freebios.py @@ -0,0 +1,27 @@ +from ctypes import c_bool, CFUNCTYPE + +from libretro import Session + +import prelude + +session: Session +with prelude.session() as session: + proc_address_callback = session.proc_address_callback + assert proc_address_callback is not None + assert proc_address_callback.get_proc_address is not None + + console_exists = session.get_proc_address("melondsds_console_exists", CFUNCTYPE(c_bool)) + assert console_exists is not None + assert console_exists() + + arm7_native = session.get_proc_address("melondsds_arm7_bios_native", CFUNCTYPE(c_bool)) + assert arm7_native is not None + assert not arm7_native() + + arm9_native = session.get_proc_address("melondsds_arm9_bios_native", CFUNCTYPE(c_bool)) + assert arm9_native is not None + assert not arm9_native() + + firmware_native = session.get_proc_address("melondsds_firmware_native", CFUNCTYPE(c_bool)) + assert firmware_native is not None + assert not firmware_native() From 84c16f281358fcb747beab797ab9fe81f0306d39 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 10:03:34 -0400 Subject: [PATCH 085/234] Write a test for mic support --- test/cmake/Basics.cmake | 7 +-- .../basics/core_accepts_microphone_input.py | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 test/python/basics/core_accepts_microphone_input.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 38869df3..bae513aa 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -316,9 +316,10 @@ add_python_test( add_python_test( NAME "Core accepts microphone input" - TEST_MODULE "" - DISABLED -) # TODO: Implement this test + TEST_MODULE basics.core_accepts_microphone_input + CONTENT "${MICRECORD_NDS}" + CORE_OPTION melonds_boot_mode=direct +) add_python_test( NAME "Core queries device power state" diff --git a/test/python/basics/core_accepts_microphone_input.py b/test/python/basics/core_accepts_microphone_input.py new file mode 100644 index 00000000..80792397 --- /dev/null +++ b/test/python/basics/core_accepts_microphone_input.py @@ -0,0 +1,49 @@ +from collections.abc import Iterator +from itertools import count, repeat +from math import sin +from typing import cast + +from libretro import Session +from libretro.api import ArrayAudioState +from libretro.api.input import JoypadState + +import prelude +from libretro.api.video import SoftwareVideoState + + +def generate_input() -> Iterator[int]: + yield from repeat(None, 6) + + yield JoypadState(a=True) + + yield from repeat(None, 90) + + yield JoypadState(a=True) + + yield from repeat(None) + + +def generate_sine_wave() -> Iterator[int]: + for i in count(): + yield int(sin(i * 440) * 30000) + + +kwargs = { + "input_state": generate_input, + "mic_interface": generate_sine_wave, + "options": { + "melonds_mic_input_active": "always" + } +} + +session: Session +with prelude.session(**kwargs) as session: + audio = cast(ArrayAudioState, session.audio) + video = cast(SoftwareVideoState, session.video) + for i in range(300): + session.core.run() + + assert audio.buffer is not None + assert len(audio.buffer) > 0 + assert any(b != 0 for b in audio.buffer) + # Assert that we're not just being given silence From 716a88d3560c475fd98cf069c4ef5ec941d4cc2a Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 10:03:46 -0400 Subject: [PATCH 086/234] Write a test for mic support --- test/nds/micrecord.nds | Bin 0 -> 135744 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/nds/micrecord.nds diff --git a/test/nds/micrecord.nds b/test/nds/micrecord.nds new file mode 100644 index 0000000000000000000000000000000000000000..111b3a97a6bd22a0722d748614aaed8a7f4ebfd3 GIT binary patch literal 135744 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Ê&OTq0Z&91E8Wmjagx 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 Date: Thu, 21 Mar 2024 10:28:35 -0400 Subject: [PATCH 087/234] Fix another bug with falling back --- CHANGELOG.md | 7 +++++++ src/libretro/config/console.cpp | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aabf91fb..1cf875a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0), and this project roughly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- Fixed a bug where native BIOS images would be used + when the core was supposed to fall back to built-in system files. + ## [1.1.1] - 2024-02-29 ### Fixed diff --git a/src/libretro/config/console.cpp b/src/libretro/config/console.cpp index 8b1e7d3e..09b7030b 100644 --- a/src/libretro/config/console.cpp +++ b/src/libretro/config/console.cpp @@ -199,7 +199,8 @@ static melonDS::NDSArgs MelonDsDs::GetNdsArgs( firmware = make_optional(static_cast(ConsoleType::DS)); } - if (config.SysfileMode() == SysfileMode::BuiltIn) { + bool isFirmwareGenerated = firmware->GetHeader().Identifier == melonDS::GENERATED_FIRMWARE_IDENTIFIER; + if (isFirmwareGenerated) { retro::debug("Not loading native ARM BIOS files"); } @@ -208,8 +209,7 @@ static melonDS::NDSArgs MelonDsDs::GetNdsArgs( ApplyCommonArgs(config, ndsargs); // Try to load the ARM7 and ARM9 BIOS files (but don't bother with the ARM9 BIOS if the ARM7 BIOS failed) - bool bios7Loaded = (config.SysfileMode() == SysfileMode::Native) && LoadBios( - config.Bios7Path(), BiosType::Arm7, ndsargs.ARM7BIOS); + bool bios7Loaded = !isFirmwareGenerated && LoadBios(config.Bios7Path(), BiosType::Arm7, ndsargs.ARM7BIOS); bool bios9Loaded = bios7Loaded && LoadBios(config.Bios9Path(), BiosType::Arm9, ndsargs.ARM9BIOS); if (config.SysfileMode() == SysfileMode::Native && !(bios7Loaded && bios9Loaded)) { From 731db65c5715163ff92f131c93d66506c827c898 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 10:28:53 -0400 Subject: [PATCH 088/234] Set the path to the test ROM --- test/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c8ec5e8e..e910e6fe 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -370,6 +370,8 @@ FetchContent_GetProperties(godmode9i) FetchContent_MakeAvailable(godmode9i) set(GODMODE9I_ROM "${godmode9i_SOURCE_DIR}/GodMode9i.nds") +set(MICRECORD_NDS "${CMAKE_CURRENT_SOURCE_DIR}/nds/micrecord.nds") + add_retroarch_test( NAME "RetroArch loads melonDS DS" CONTENT "${NDS_ROM}" From a1b8882fe50096a43f3f42e2222986e75ad54b7f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 13:07:20 -0400 Subject: [PATCH 089/234] Move some tests to libretro.py --- test/cmake/Booting.cmake | 337 +++++++++++++++++++-------------------- 1 file changed, 162 insertions(+), 175 deletions(-) diff --git a/test/cmake/Booting.cmake b/test/cmake/Booting.cmake index 37d2f235..6dc34e5b 100644 --- a/test/cmake/Booting.cmake +++ b/test/cmake/Booting.cmake @@ -9,211 +9,198 @@ add_python_test( CORE_OPTION melonds_sysfile_mode=builtin ) -add_retroarch_test( - NAME "Direct NDS boot with native system files succeeds" - CONTENT "${NDS_ROM}" - MAX_FRAMES 180 - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" - ARM7_BIOS - ARM9_BIOS - NDS_FIRMWARE - FAIL_REGULAR_EXPRESSION "Failed to load ARM[79]" - FAIL_REGULAR_EXPRESSION "Failed to load the required firmware" +add_python_test( + NAME "Direct NDS boot with native system files succeeds" + TEST_MODULE basics.core_run_frames + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + NDS_SYSFILES ) -add_retroarch_test( - NAME "Direct NDS boot with native BIOS and non-bootable firmware succeeds" - CONTENT "${NDS_ROM}" - MAX_FRAMES 180 - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${DSI_FIRMWARE_NAME}" - ARM7_BIOS - ARM9_BIOS - DSI_FIRMWARE - FAIL_REGULAR_EXPRESSION "Failed to load ARM[79]" - FAIL_REGULAR_EXPRESSION "Failed to load the required firmware" +add_python_test( + NAME "Direct NDS boot with native BIOS and non-bootable firmware succeeds" + TEST_MODULE basics.core_run_frames + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${DSI_FIRMWARE_NAME}" + ARM7_BIOS + ARM9_BIOS + DSI_FIRMWARE ) ## Boot to firmware #################################################### -add_retroarch_test( - NAME "NDS boot with no content, native BIOS, and bootable firmware succeeds" - MAX_FRAMES 180 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=native" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" - NDS_FIRMWARE - ARM7_BIOS - ARM9_BIOS +add_python_test( + NAME "NDS boot with no content, native BIOS, and bootable firmware succeeds" + TEST_MODULE basics.core_run_frames + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_boot_mode=native" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + NDS_SYSFILES ) -add_retroarch_test( - NAME "NDS boot with no content and built-in system files fails" - MAX_FRAMES 180 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=builtin" - WILL_FAIL +add_python_test( + NAME "NDS boot with no content and built-in system files fails" + TEST_MODULE basics.core_run_frames + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=builtin" + WILL_FAIL ) -add_retroarch_test( - NAME "NDS boot with no content, native BIOS, and non-bootable firmware fails" - MAX_FRAMES 180 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=builtin" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${DSI_FIRMWARE_NAME}" - ARM7_BIOS - ARM9_BIOS - DSI_FIRMWARE - WILL_FAIL +add_python_test( + NAME "NDS boot with no content, native BIOS, and non-bootable firmware fails" + TEST_MODULE basics.core_run_frames + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=builtin" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${DSI_FIRMWARE_NAME}" + ARM7_BIOS + ARM9_BIOS + DSI_FIRMWARE + WILL_FAIL ) ## DSi boot #################################################### -add_retroarch_test( - NAME "DSi boot to menu with no NAND image fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=/notfound" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - WILL_FAIL +add_python_test( + NAME "DSi boot to menu with no NAND image fails" + TEST_MODULE basics.core_run_frames + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=/notfound" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + WILL_FAIL ) -add_retroarch_test( - NAME "DSi boot to menu with no NDS BIOS fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND - WILL_FAIL +add_python_test( + NAME "DSi boot to menu with no NDS BIOS fails" + TEST_MODULE basics.core_run_frames + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + DSI_NAND + WILL_FAIL ) -add_retroarch_test( - NAME "DSi boot to menu with no DSi BIOS fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - DSI_FIRMWARE - DSI_NAND - WILL_FAIL +add_python_test( + NAME "DSi boot to menu with no DSi BIOS fails" + TEST_MODULE basics.core_run_frames + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + DSI_FIRMWARE + DSI_NAND + WILL_FAIL ) -add_retroarch_test( - NAME "DSi boot to menu with NDS firmware fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_NAND - WILL_FAIL +add_python_test( + NAME "DSi boot to menu with NDS firmware fails" + TEST_MODULE basics.core_run_frames + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + NDS_FIRMWARE + DSI_NAND + WILL_FAIL ) -add_retroarch_test( - NAME "DSi boot to menu with no firmware fails" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_NAND - WILL_FAIL +add_python_test( + NAME "DSi boot to menu with no firmware fails" + TEST_MODULE basics.core_run_frames + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_NAND + WILL_FAIL ) -add_retroarch_test( - NAME "DSi boot to menu with all system files succeeds" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND +add_python_test( + NAME "DSi boot to menu with all system files succeeds" + TEST_MODULE basics.core_run_frames + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + DSI_NAND ) -add_retroarch_test( - NAME "Direct DSi boot to NDS game with no NAND image fails" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=/notfound" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - WILL_FAIL +add_python_test( + NAME "Direct DSi boot to NDS game with no NAND image fails" + TEST_MODULE basics.core_run_frames + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=/notfound" + ARM7_BIOS + ARM9_BIOS + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + WILL_FAIL ) -add_retroarch_test( - NAME "Direct DSi boot to NDS game with no NDS BIOS image fails" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND - WILL_FAIL +add_python_test( + NAME "Direct DSi boot to NDS game with no NDS BIOS image fails" + TEST_MODULE basics.core_run_frames + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_DSI_BIOS + ARM9_DSI_BIOS + DSI_FIRMWARE + DSI_NAND + WILL_FAIL ) -add_retroarch_test( - NAME "Direct DSi boot to NDS game with no DSi BIOS image fails" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - DSI_FIRMWARE - DSI_NAND - WILL_FAIL +add_python_test( + NAME "Direct DSi boot to NDS game with no DSi BIOS image fails" + TEST_MODULE basics.core_run_frames + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + ARM7_BIOS + ARM9_BIOS + DSI_FIRMWARE + DSI_NAND + WILL_FAIL ) -add_retroarch_test( - NAME "Direct DSi boot to NDS game with all system files succeeds" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=dsi" - CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND +add_python_test( + NAME "Direct DSi boot to NDS game with all system files succeeds" + TEST_MODULE basics.core_run_frames + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + DSI_SYSFILES ) \ No newline at end of file From 83e282418f5b0d54db15a93955d445abe9d70ee2 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 14:56:04 -0400 Subject: [PATCH 090/234] Add prelude.wfcsettings_path --- test/python/prelude.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/python/prelude.py b/test/python/prelude.py index 307763f2..39a4436c 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -16,6 +16,7 @@ savestate_directory = os.path.join(testdir.name, "states") core_system_dir = os.path.join(system_dir, "melonDS DS") core_save_dir = os.path.join(save_dir, "melonDS DS") +wfcsettings_path = os.path.join(core_system_dir, "wfcsettings.bin") print("Test dir:", testdir.name) print("System dir:", system_dir) From fabde5aa6e750608d6b22cf41291319fa333d4c0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 15:01:51 -0400 Subject: [PATCH 091/234] Move more tests to libretro.py --- test/cmake/Firmware.cmake | 152 ++++++------------ test/python/firmware/__init__.py | 0 .../python/firmware/core_saves_wfcsettings.py | 14 ++ ...overwritten_when_switching_console_mode.py | 55 +++++++ .../native_bios_not_loaded_with_freebios.py | 17 ++ .../firmware/nds_firmware_not_overwritten.py | 22 +++ 6 files changed, 154 insertions(+), 106 deletions(-) create mode 100644 test/python/firmware/__init__.py create mode 100644 test/python/firmware/core_saves_wfcsettings.py create mode 100644 test/python/firmware/firmware_not_overwritten_when_switching_console_mode.py create mode 100644 test/python/firmware/native_bios_not_loaded_with_freebios.py create mode 100644 test/python/firmware/nds_firmware_not_overwritten.py diff --git a/test/cmake/Firmware.cmake b/test/cmake/Firmware.cmake index e3115c99..bd31d1ac 100644 --- a/test/cmake/Firmware.cmake +++ b/test/cmake/Firmware.cmake @@ -1,118 +1,58 @@ ### Preventing Unneeded Loads -add_retroarch_test( - NAME "Core doesn't try to load native BIOS if using FreeBIOS (NDS)" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_sysfile_mode=builtin" - PASS_REGULAR_EXPRESSION "Not loading native ARM BIOS files" +add_python_test( + NAME "Core doesn't try to load native BIOS if using FreeBIOS (NDS)" + TEST_MODULE firmware.native_bios_not_loaded_with_freebios + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_sysfile_mode=builtin" ) - +### Ensuring firmware is not overwritten # See https://github.com/JesseTG/melonds-ds/issues/59 -add_retroarch_test( - NAME "Native firmware is not overwritten by contents of wfcsettings.bin" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION "melonds_sysfile_mode=native" - CORE_OPTION "melonds_boot_mode=native" - ARM7_BIOS - ARM9_BIOS - NDS_FIRMWARE - REQUIRE_FILE_SIZE_UNCHANGED "system/melonDS DS/${NDS_FIRMWARE_NAME}" +add_python_test( + NAME "Native firmware is not overwritten by contents of wfcsettings.bin" + TEST_MODULE firmware.nds_firmware_not_overwritten + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION "melonds_sysfile_mode=native" + CORE_OPTION "melonds_boot_mode=native" + NDS_SYSFILES ) -add_retroarch_test( - NAME "Core saves wi-fi settings to wfcsettings.bin when using built-in firmware" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_firmware_nds_path=/builtin" - ARM7_BIOS - ARM9_BIOS - REQUIRE_FILE_CREATED "system/melonDS DS/wfcsettings.bin" +add_python_test( + NAME "Core saves wi-fi settings to wfcsettings.bin when using built-in firmware" + TEST_MODULE firmware.core_saves_wfcsettings + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_firmware_nds_path=/builtin" + CORE_OPTION "melonds_sysfile_mode=builtin" ) -### Overwriting Firmware - -add_emutest_test( - NAME "NDS firmware is not overwritten when switching from DS to DSi mode" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "firmware-not-overwritten.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="ds" - CORE_OPTION melonds_boot_directly="false" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" +add_python_test( + NAME "Firmware images aren't overwritten when switching from DS to DSi mode" + TEST_MODULE firmware.firmware_not_overwritten_when_switching_console_mode + CONTENT "${NDS_ROM}" + NDS_SYSFILES + DSI_SYSFILES + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_boot_directly=false" + CORE_OPTION "melonds_sysfile_mode=native" ) -add_emutest_test( - NAME "NDS firmware is not overwritten when switching from DSi to NDS mode" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "firmware-not-overwritten.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_boot_directly="false" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" +add_python_test( + NAME "Firmware images aren't overwritten when switching from DSi to DS mode" + TEST_MODULE firmware.firmware_not_overwritten_when_switching_console_mode + CONTENT "${NDS_ROM}" + NDS_SYSFILES + DSI_SYSFILES + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_boot_directly=false" + CORE_OPTION "melonds_sysfile_mode=native" ) - -add_emutest_test( - NAME "DSi firmware is not overwritten when switching from NDS to DSi mode" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "firmware-not-overwritten.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_boot_directly="false" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" -) - -add_emutest_test( - NAME "DSi firmware is not overwritten when switching from DSi to NDS mode" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "firmware-not-overwritten.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - NDS_FIRMWARE - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_boot_directly="false" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" -) \ No newline at end of file diff --git a/test/python/firmware/__init__.py b/test/python/firmware/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/firmware/core_saves_wfcsettings.py b/test/python/firmware/core_saves_wfcsettings.py new file mode 100644 index 00000000..75024ef2 --- /dev/null +++ b/test/python/firmware/core_saves_wfcsettings.py @@ -0,0 +1,14 @@ +import os + +from libretro import Session + +import prelude + +assert not os.access(prelude.wfcsettings_path, os.F_OK), f"{prelude.wfcsettings_path} should not exist yet" + +session: Session +with prelude.session() as session: + for i in range(300): + session.core.run() + + assert os.access(prelude.wfcsettings_path, os.F_OK), f"{prelude.wfcsettings_path} should exist by now" diff --git a/test/python/firmware/firmware_not_overwritten_when_switching_console_mode.py b/test/python/firmware/firmware_not_overwritten_when_switching_console_mode.py new file mode 100644 index 00000000..ba0c1a87 --- /dev/null +++ b/test/python/firmware/firmware_not_overwritten_when_switching_console_mode.py @@ -0,0 +1,55 @@ +import os + +from libretro import Session + +import prelude + +nds_firmware_path = os.environ["NDS_FIRMWARE"] +nds_firmware_basename = os.path.basename(nds_firmware_path) +dsi_firmware_path = os.environ["DSI_FIRMWARE"] +dsi_firmware_basename = os.path.basename(dsi_firmware_path) + +with open(nds_firmware_path, "rb") as firmware_file: + nds_firmware = firmware_file.read() + +with open(dsi_firmware_path, "rb") as firmware_file: + dsi_firmware = firmware_file.read() + +assert nds_firmware != dsi_firmware, "DS and DSi firmware are the same" + +session: Session +with prelude.session() as session: + for i in range(30): + session.core.run() + + match prelude.options[b'melonds_console_mode']: + case 'ds' | b'ds': + print("Switching to DSi mode") + session.options.variables['melonds_console_mode'] = b'dsi' + case 'dsi' | b'dsi': + print("Switching to DS mode") + session.options.variables['melonds_console_mode'] = b'ds' + case _ as mode: + raise ValueError(f"Unknown console mode {mode}") + + session.core.reset() + + for i in range(30): + session.core.run() + +nds_firmware_path_local = os.path.join(prelude.core_system_dir, nds_firmware_basename) +dsi_firmware_path_local = os.path.join(prelude.core_system_dir, dsi_firmware_basename) + +with open(nds_firmware_path_local, "rb") as nds_firmware_file_local: + nds_firmware_after = nds_firmware_file_local.read() + +with open(dsi_firmware_path_local, "rb") as dsi_firmware_file_local: + dsi_firmware_after = dsi_firmware_file_local.read() + +assert len(nds_firmware_after) == len(nds_firmware), \ + f"DS firmware size changed from {len(nds_firmware)} to {len(nds_firmware_after)}" + +assert len(dsi_firmware_after) == len(dsi_firmware), \ + f"DSi firmware size changed from {len(dsi_firmware)} to {len(dsi_firmware_after)}" + +assert nds_firmware_after != dsi_firmware_after, "Firmware was overwritten after switching console mode" diff --git a/test/python/firmware/native_bios_not_loaded_with_freebios.py b/test/python/firmware/native_bios_not_loaded_with_freebios.py new file mode 100644 index 00000000..705427d4 --- /dev/null +++ b/test/python/firmware/native_bios_not_loaded_with_freebios.py @@ -0,0 +1,17 @@ +from libretro import Session +from libretro.api.vfs import StandardFileSystemInterface, HistoryFileSystemInterface, VfsOperation, VfsOperationType + +import prelude + +vfs = HistoryFileSystemInterface(StandardFileSystemInterface()) +session: Session +with prelude.session(vfs=vfs) as session: + for i in range(300): + session.core.run() + + op: VfsOperation + for op in filter(lambda f: f.operation == VfsOperationType.OPEN, vfs.history): + path = op.args[0] + assert isinstance(path, bytes), f"Expected path to be bytes, found {type(path).__name__} instead" + assert not path.endswith(b'bios7.bin'), f"{path} should not have been opened" + assert not path.endswith(b'bios9.bin'), f"{path} should not have been opened" diff --git a/test/python/firmware/nds_firmware_not_overwritten.py b/test/python/firmware/nds_firmware_not_overwritten.py new file mode 100644 index 00000000..7d442783 --- /dev/null +++ b/test/python/firmware/nds_firmware_not_overwritten.py @@ -0,0 +1,22 @@ +import os + +from libretro import Session + +import prelude + +nds_firmware_path = os.environ["NDS_FIRMWARE"] +nds_firmware_basename = os.path.basename(nds_firmware_path) +test_nds_firmware_path = os.path.join(prelude.core_system_dir, nds_firmware_basename) + +original_nds_firmware_size = os.stat(nds_firmware_path).st_size +assert original_nds_firmware_size > 0, f"{nds_firmware_path} is empty" + +session: Session +with prelude.session() as session: + for i in range(300): + session.core.run() + + test_nds_firmware_size = os.stat(test_nds_firmware_path).st_size + + assert original_nds_firmware_size == test_nds_firmware_size, \ + f"Expected firmware to be {original_nds_firmware_size} bytes after saving, found {test_nds_firmware_size} bytes" From 43b9eabf6f78c86f92003c98e9995ab3fb10f578 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 15:03:59 -0400 Subject: [PATCH 092/234] Remove some unneeded Lua scripts --- test/lua/firmware-not-overwritten.lua | 90 --------------------------- test/lua/no-crash-on-invalid-rom.lua | 7 --- 2 files changed, 97 deletions(-) delete mode 100644 test/lua/firmware-not-overwritten.lua delete mode 100644 test/lua/no-crash-on-invalid-rom.lua diff --git a/test/lua/firmware-not-overwritten.lua b/test/lua/firmware-not-overwritten.lua deleted file mode 100644 index ce180333..00000000 --- a/test/lua/firmware-not-overwritten.lua +++ /dev/null @@ -1,90 +0,0 @@ -local preamble = dofile(os.getenv("PREAMBLE")) - -local nds_firmware, nds_err = io.open(os.getenv("NDS_FIRMWARE"), "rb") -if nds_firmware == nil or nds_err ~= nil then - print(get_logs()) - error("Failed to open NDS firmware: " .. nds_err) -end - -local dsi_firmware, dsi_err = io.open(os.getenv("DSI_FIRMWARE"), "rb") -if dsi_firmware == nil or dsi_err ~= nil then - print(get_logs()) - error("Failed to open DSi firmware: " .. dsi_err) -end - --- Need to check the size of the firmware files --- because go-lua doesn't support file:read() -local nds_firmware_size = nds_firmware:seek("end") -local dsi_firmware_size = dsi_firmware:seek("end") - -nds_firmware:close() -dsi_firmware:close() - -if nds_firmware_size == nil then - error("Failed to read NDS firmware") -end - -if dsi_firmware_size == nil then - error("Failed to read DSi firmware") -end - -if nds_firmware_size == dsi_firmware_size then - error("NDS firmware and DSI firmware are the same size, this test is pointless") -end - -load_core(corepath) -load_game(rompath) - -set_options_string(preamble.options_string) - -for _ = 1, 30 do - run() -end - -local initial_mode = get_option("melonds_console_mode") - -if initial_mode == nil then - print(get_logs()) - error("melonds_console_mode is nil (is this version of emutest too old?)") -end - -if initial_mode == "ds" then - print("Switching from NDS to DSi mode") - set_option("melonds_console_mode", "dsi") -else - print("Switching from DSi to NDS mode") - set_option("melonds_console_mode", "ds") -end - -print("Resetting core") - -reset() - -for _ = 1, 30 do - run() -end - -nds_firmware = io.open(os.getenv("NDS_FIRMWARE"), "rb") -dsi_firmware = io.open(os.getenv("DSI_FIRMWARE"), "rb") - -local nds_firmware_size_after, nds_err_after = nds_firmware:seek("end") -if nds_err_after ~= nil then - print(get_logs()) - error("Failed to open NDS firmware: " .. nds_err_after) -end - -local dsi_firmware_size_after, dsi_err_after = dsi_firmware:seek("end") -if dsi_err_after ~= nil then - print(get_logs()) - error("Failed to open DSi firmware: " .. dsi_err_after) -end - -nds_firmware:close() -dsi_firmware:close() - -if nds_firmware_size_after == dsi_firmware_size_after then - print(get_logs()) - error("Firmware was overwritten") -end - -print(get_logs()) \ No newline at end of file diff --git a/test/lua/no-crash-on-invalid-rom.lua b/test/lua/no-crash-on-invalid-rom.lua deleted file mode 100644 index ce4f129c..00000000 --- a/test/lua/no-crash-on-invalid-rom.lua +++ /dev/null @@ -1,7 +0,0 @@ -local preamble = dofile(os.getenv("PREAMBLE")) - -set_options_string(preamble.options_string) - -load_core(corepath) -print(get_logs()) -load_game(rompath) From 14639694820748c001e5e9f5465dc5238f62be50 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 15:32:59 -0400 Subject: [PATCH 093/234] Migrate more tests to libretro.py --- test/cmake/Reset.cmake | 110 +++++++++++-------------- test/python/reset/__init__.py | 0 test/python/reset/no_hang_on_reboot.py | 37 +++++++++ 3 files changed, 83 insertions(+), 64 deletions(-) create mode 100644 test/python/reset/__init__.py create mode 100644 test/python/reset/no_hang_on_reboot.py diff --git a/test/cmake/Reset.cmake b/test/cmake/Reset.cmake index 1e1d09fc..40ce4530 100644 --- a/test/cmake/Reset.cmake +++ b/test/cmake/Reset.cmake @@ -1,74 +1,56 @@ - # See https://github.com/JesseTG/melonds-ds/issues/62 -add_emutest_test( - NAME "Core resets when using built-in system files and direct boot (NDS)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - CORE_OPTION melonds_boot_mode="direct" - CORE_OPTION melonds_show_cursor="disabled" - CORE_OPTION melonds_sysfile_mode="builtin" - CORE_OPTION melonds_console_mode="ds" +add_python_test( + NAME "Core resets when using built-in system files and direct boot (NDS)" + TEST_MODULE reset.no_hang_on_reboot + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_show_cursor=disabled" + CORE_OPTION "melonds_sysfile_mode=builtin" ) -add_emutest_test( - NAME "Core resets when using native system files and direct boot (NDS)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - ARM7_BIOS - ARM9_BIOS - NDS_FIRMWARE - CORE_OPTION melonds_firmware_nds_path='melonDS DS/${NDS_FIRMWARE_NAME}' - CORE_OPTION melonds_boot_mode="direct" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" - CORE_OPTION melonds_console_mode="ds" +add_python_test( + NAME "Core resets when using native system files and direct boot (NDS)" + TEST_MODULE reset.no_hang_on_reboot + CONTENT "${NDS_ROM}" + NDS_SYSFILES + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION "melonds_sysfile_mode=native" ) -add_emutest_test( - NAME "Core resets when using native system files and native boot (NDS)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - ARM7_BIOS - ARM9_BIOS - NDS_FIRMWARE - CORE_OPTION melonds_firmware_nds_path="melonDS DS/${NDS_FIRMWARE_NAME}" - CORE_OPTION melonds_boot_mode="native" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" +add_python_test( + NAME "Core resets when using native system files and native boot (NDS)" + TEST_MODULE reset.no_hang_on_reboot + CONTENT "${NDS_ROM}" + NDS_SYSFILES + CORE_OPTION "melonds_boot_mode=native" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_firmware_nds_path=melonDS DS/${NDS_FIRMWARE_NAME}" + CORE_OPTION "melonds_sysfile_mode=native" ) -add_emutest_test( - NAME "Core resets when using native system files and direct boot (DSi)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_boot_mode="direct" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" +add_python_test( + NAME "Core resets when using native system files and direct boot (DSi)" + TEST_MODULE reset.no_hang_on_reboot + CONTENT "${NDS_ROM}" + DSI_SYSFILES + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_sysfile_mode=native" ) -add_emutest_test( - NAME "Core resets when using native system files and native boot (DSi)" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "no-hang-on-reboot.lua" - ARM7_BIOS - ARM9_BIOS - ARM7_DSI_BIOS - ARM9_DSI_BIOS - DSI_FIRMWARE - DSI_NAND - CORE_OPTION melonds_firmware_dsi_path="melonDS DS/${DSI_FIRMWARE_NAME}" - CORE_OPTION melonds_console_mode="dsi" - CORE_OPTION melonds_dsi_nand_path="melonDS DS/${DSI_NAND_NAME}" - CORE_OPTION melonds_boot_mode="native" - CORE_OPTION melonds_sysfile_mode="native" - CORE_OPTION melonds_show_cursor="disabled" +add_python_test( + NAME "Core resets when using native system files and native boot (DSi)" + TEST_MODULE reset.no_hang_on_reboot + CONTENT "${NDS_ROM}" + DSI_SYSFILES + CORE_OPTION "melonds_boot_mode=native" + CORE_OPTION "melonds_console_mode=dsi" + CORE_OPTION "melonds_dsi_nand_path=melonDS DS/${DSI_NAND_NAME}" + CORE_OPTION "melonds_firmware_dsi_path=melonDS DS/${DSI_FIRMWARE_NAME}" + CORE_OPTION "melonds_sysfile_mode=native" ) \ No newline at end of file diff --git a/test/python/reset/__init__.py b/test/python/reset/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/reset/no_hang_on_reboot.py b/test/python/reset/no_hang_on_reboot.py new file mode 100644 index 00000000..a744ce05 --- /dev/null +++ b/test/python/reset/no_hang_on_reboot.py @@ -0,0 +1,37 @@ +from array import array +from typing import cast + +from libretro import Session +from libretro.api.video import SoftwareVideoState + +import prelude + +options = { + b"melonds_show_cursor": b"disabled" +} + +WHITE = (0xFF, 0xFF, 0xFF, 0xFF) + +session: Session +with prelude.session(options=options) as session: + session.core.run() + + video = cast(SoftwareVideoState, session.video) + + # Very first frame should be all white + blank_frame = array(video.frame.typecode, video.frame) + assert all(byte == 0xFF for byte in blank_frame), "Screen is not blank" + + for i in range(300): + session.core.run() + + after_frame = array(video.frame.typecode, video.frame) + assert blank_frame != after_frame, "Screen is still blank after 300 frames" + + session.core.reset() + + for i in range(300): + session.core.run() + + after_reset_frame = array(video.frame.typecode, video.frame) + assert blank_frame != after_reset_frame, "Screen is still blank after resetting and running for 300 frames" From b37403ba726343a4dbe630ae48c00d874e802882 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 15:33:39 -0400 Subject: [PATCH 094/234] Remove an unused script --- test/lua/no-hang-on-reboot.lua | 52 ---------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 test/lua/no-hang-on-reboot.lua diff --git a/test/lua/no-hang-on-reboot.lua b/test/lua/no-hang-on-reboot.lua deleted file mode 100644 index f57b292f..00000000 --- a/test/lua/no-hang-on-reboot.lua +++ /dev/null @@ -1,52 +0,0 @@ -local preamble = dofile(os.getenv("PREAMBLE")) - -set_options_string(preamble.options_string) - -load_core(corepath) -load_game(rompath) - -print("Loaded core and ROM") - -run() - -print("Ran first frame") - --- Take the CRC of whatever a blank white screen looks like -local blank_white_crc = get_fb_crc() - -for _ = 1, 300 do - run() -end - -print("Ran for 300 frames") - --- If the CRC is still the same, then the screen is still white -local non_blank_crc = get_fb_crc() -if (non_blank_crc == blank_white_crc) then - print(get_logs()) - error("Screen is still white after 300 frames") -end - -print("About to reset core") -reset() - -print("Reset core") -print("About to run for 300 more frames") - -for i = 1, 300 do - run() -end - -print("Ran for 300 frames after reset") - -local reset_crc = get_fb_crc() -if (reset_crc == blank_white_crc) then - print(get_logs()) - error("Emulator is stuck on blank white screen") -end - -unload_game() - -print("Unloaded game") - -print(get_logs()) \ No newline at end of file From f225eb2a555bc272493a10390b601304b3083122 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 15:51:02 -0400 Subject: [PATCH 095/234] List `FindPythonModules.cmake` as vendored --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index f92131ec..47acea9a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ cmake/toolchain/** linguist-vendored cmake/GitInfo.cmake linguist-vendored cmake/FindOpenGLES.cmake linguist-vendored +cmake/FindPythonModules.cmake linguist-vendored LICENSE text \ No newline at end of file From ccff012b1a470f4a4e7fdcf32d8fdb62336b0fd9 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 17:06:18 -0400 Subject: [PATCH 096/234] Migrate more tests to libretro.py --- test/cmake/TestHomebrewSDCards.cmake | 70 +++++++++---------- test/lua/homebrew-sdcard-exists.lua | 13 ---- test/lua/homebrew-sdcard-sync-exists.lua | 14 ---- test/python/prelude.py | 2 + test/python/save/__init__.py | 0 test/python/save/homebrew_sd_card_exists.py | 15 ++++ .../save/homebrew_sd_card_sync_exists.py | 14 ++++ 7 files changed, 66 insertions(+), 62 deletions(-) delete mode 100644 test/lua/homebrew-sdcard-exists.lua delete mode 100644 test/lua/homebrew-sdcard-sync-exists.lua create mode 100644 test/python/save/__init__.py create mode 100644 test/python/save/homebrew_sd_card_exists.py create mode 100644 test/python/save/homebrew_sd_card_sync_exists.py diff --git a/test/cmake/TestHomebrewSDCards.cmake b/test/cmake/TestHomebrewSDCards.cmake index e41e2aa4..b0445144 100644 --- a/test/cmake/TestHomebrewSDCards.cmake +++ b/test/cmake/TestHomebrewSDCards.cmake @@ -1,68 +1,68 @@ -add_emutest_test( +add_python_test( NAME "Homebrew SD card image is not created if loading a retail ROM" CONTENT "${NDS_ROM}" - TEST_SCRIPT "homebrew-sdcard-exists.lua" - CORE_OPTION melonds_console_mode="ds" - CORE_OPTION melonds_homebrew_sdcard="enabled" - CORE_OPTION melonds_homebrew_sync_sdcard_to_host="disabled" + TEST_MODULE save.homebrew_sd_card_exists + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_homebrew_sdcard=enabled" + CORE_OPTION "melonds_homebrew_sync_sdcard_to_host=disabled" WILL_FAIL ) -add_emutest_test( +add_python_test( NAME "Homebrew SD card image is not created if disabled and loading a retail ROM" CONTENT "${NDS_ROM}" - TEST_SCRIPT "homebrew-sdcard-exists.lua" - CORE_OPTION melonds_console_mode="ds" - CORE_OPTION melonds_homebrew_sdcard="disabled" - CORE_OPTION melonds_homebrew_sync_sdcard_to_host="disabled" + TEST_MODULE save.homebrew_sd_card_exists + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_homebrew_sdcard=disabled" + CORE_OPTION "melonds_homebrew_sync_sdcard_to_host=disabled" WILL_FAIL ) -add_emutest_test( +add_python_test( NAME "Homebrew SD card image is created if enabled and loading a homebrew ROM" CONTENT "${GODMODE9I_ROM}" - TEST_SCRIPT "homebrew-sdcard-exists.lua" - CORE_OPTION melonds_console_mode="ds" - CORE_OPTION melonds_homebrew_sdcard="enabled" - CORE_OPTION melonds_homebrew_sync_sdcard_to_host="disabled" + TEST_MODULE save.homebrew_sd_card_exists + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_homebrew_sdcard=enabled" + CORE_OPTION "melonds_homebrew_sync_sdcard_to_host=disabled" ) -add_emutest_test( +add_python_test( NAME "Homebrew SD card image is not created if disabled and loading a homebrew ROM" CONTENT "${GODMODE9I_ROM}" - TEST_SCRIPT "homebrew-sdcard-exists.lua" - CORE_OPTION melonds_console_mode="ds" - CORE_OPTION melonds_homebrew_sdcard="disabled" - CORE_OPTION melonds_homebrew_sync_sdcard_to_host="disabled" + TEST_MODULE save.homebrew_sd_card_exists + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_homebrew_sdcard=disabled" + CORE_OPTION "melonds_homebrew_sync_sdcard_to_host=disabled" WILL_FAIL ) -add_emutest_test( +add_python_test( NAME "Homebrew SD card sync folder is not created if SD card is enabled and loading a retail ROM" CONTENT "${NDS_ROM}" - TEST_SCRIPT "homebrew-sdcard-sync-exists.lua" - CORE_OPTION melonds_console_mode="ds" - CORE_OPTION melonds_homebrew_sdcard="enabled" - CORE_OPTION melonds_homebrew_sync_sdcard_to_host="enabled" + TEST_MODULE save.homebrew_sd_card_sync_exists + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_homebrew_sdcard=enabled" + CORE_OPTION "melonds_homebrew_sync_sdcard_to_host=enabled" WILL_FAIL ) -add_emutest_test( +add_python_test( NAME "Homebrew SD card sync folder is not created if SD card sync is disabled and loading a retail ROM" CONTENT "${NDS_ROM}" - TEST_SCRIPT "homebrew-sdcard-sync-exists.lua" - CORE_OPTION melonds_console_mode="ds" - CORE_OPTION melonds_homebrew_sdcard="enabled" - CORE_OPTION melonds_homebrew_sync_sdcard_to_host="disabled" + TEST_MODULE save.homebrew_sd_card_sync_exists + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_homebrew_sdcard=enabled" + CORE_OPTION "melonds_homebrew_sync_sdcard_to_host=disabled" WILL_FAIL ) -add_emutest_test( +add_python_test( NAME "Homebrew SD card sync folder is not created if SD card sync is disabled and loading a homebrew ROM" CONTENT "${GODMODE9I_ROM}" - TEST_SCRIPT "homebrew-sdcard-sync-exists.lua" - CORE_OPTION melonds_console_mode="ds" - CORE_OPTION melonds_homebrew_sdcard="enabled" - CORE_OPTION melonds_homebrew_sync_sdcard_to_host="disabled" + TEST_MODULE save.homebrew_sd_card_sync_exists + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_homebrew_sdcard=enabled" + CORE_OPTION "melonds_homebrew_sync_sdcard_to_host=disabled" WILL_FAIL ) \ No newline at end of file diff --git a/test/lua/homebrew-sdcard-exists.lua b/test/lua/homebrew-sdcard-exists.lua deleted file mode 100644 index c116a257..00000000 --- a/test/lua/homebrew-sdcard-exists.lua +++ /dev/null @@ -1,13 +0,0 @@ -local preamble = dofile(os.getenv("PREAMBLE")) - -set_options_string(preamble.options_string) - -load_core(corepath) -load_game(rompath) -run() - -local homebrew_sd_card, sdcard_err = io.open(preamble.core_save_dir .. "/dldi_sd_card.bin", "rb") -if homebrew_sd_card == nil or sdcard_err ~= nil then - print(get_logs()) - error("Failed to open homebrew SD card: " .. sdcard_err) -end diff --git a/test/lua/homebrew-sdcard-sync-exists.lua b/test/lua/homebrew-sdcard-sync-exists.lua deleted file mode 100644 index 52a31e3a..00000000 --- a/test/lua/homebrew-sdcard-sync-exists.lua +++ /dev/null @@ -1,14 +0,0 @@ -local preamble = dofile(os.getenv("PREAMBLE")) - -set_options_string(preamble.options_string) - -load_core(corepath) -load_game(rompath) -run() - -local cmake = os.getenv("CMAKE") -local ok, reason, code = os.execute(string.format('"%s" -E chdir "%s" true', cmake, preamble.core_save_dir .. "/dldi_sd_card")) -if ok ~= true then - print(get_logs()) - error("Failed to open homebrew SD card sync directory") -end diff --git a/test/python/prelude.py b/test/python/prelude.py index 39a4436c..01971de7 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -17,6 +17,8 @@ core_system_dir = os.path.join(system_dir, "melonDS DS") core_save_dir = os.path.join(save_dir, "melonDS DS") wfcsettings_path = os.path.join(core_system_dir, "wfcsettings.bin") +dldi_sd_card_path = os.path.join(core_save_dir, "dldi_sd_card.bin") +dldi_sd_card_sync_path = os.path.join(core_save_dir, "dldi_sd_card") print("Test dir:", testdir.name) print("System dir:", system_dir) diff --git a/test/python/save/__init__.py b/test/python/save/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/save/homebrew_sd_card_exists.py b/test/python/save/homebrew_sd_card_exists.py new file mode 100644 index 00000000..ab87394e --- /dev/null +++ b/test/python/save/homebrew_sd_card_exists.py @@ -0,0 +1,15 @@ +import os +import stat + +from libretro import Session + +import prelude + +session: Session +with prelude.session() as session: + session.core.run() + + dldi_sdcard_stat = os.stat(prelude.dldi_sd_card_path) + + assert stat.S_ISREG(dldi_sdcard_stat.st_mode), "dldi_sd_card.bin should be a regular file" + assert dldi_sdcard_stat.st_size > 0, "dldi_sd_card.bin should exist and be non-empty" diff --git a/test/python/save/homebrew_sd_card_sync_exists.py b/test/python/save/homebrew_sd_card_sync_exists.py new file mode 100644 index 00000000..7367c5dd --- /dev/null +++ b/test/python/save/homebrew_sd_card_sync_exists.py @@ -0,0 +1,14 @@ +import os +import stat + +from libretro import Session + +import prelude + +session: Session +with prelude.session() as session: + session.core.run() + + dldi_sdcard_stat = os.stat(prelude.dldi_sd_card_sync_path) + + assert stat.S_ISDIR(dldi_sdcard_stat.st_mode), "dldi_sd_card should be a directory" From 688e6fa11e1dfd6cce1c7accdacd8f46871340e5 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 20:34:13 -0400 Subject: [PATCH 097/234] Migrate the last test to libretro.py --- test/cmake/Video.cmake | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/cmake/Video.cmake b/test/cmake/Video.cmake index a303a73c..761305f1 100644 --- a/test/cmake/Video.cmake +++ b/test/cmake/Video.cmake @@ -1,10 +1,10 @@ # See https://github.com/JesseTG/melonds-ds/issues/70 -add_retroarch_test( - NAME "Core unloads with threaded software rendering" - CONTENT "${NDS_ROM}" - MAX_FRAMES 6 - CORE_OPTION "melonds_boot_mode=direct" - CORE_OPTION "melonds_sysfile_mode=builtin" - CORE_OPTION "melonds_console_mode=ds" - CORE_OPTION "melonds_threaded_renderer=enabled" +add_python_test( + NAME "Core unloads with threaded software rendering" + TEST_MODULE basics.core_run_frames + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_boot_mode=direct" + CORE_OPTION "melonds_sysfile_mode=builtin" + CORE_OPTION "melonds_console_mode=ds" + CORE_OPTION "melonds_threaded_renderer=enabled" ) \ No newline at end of file From 77ed079bdce4520f2b281218cd47013f4a2663a7 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 21 Mar 2024 20:45:07 -0400 Subject: [PATCH 098/234] Remove obsolete test infrastructure --- test/CMakeLists.txt | 201 +-------------------------------------- test/README.md | 27 ++---- test/lua/loads-core.lua | 11 --- test/lua/preamble.lua | 40 -------- test/python/retroarch.py | 133 -------------------------- 5 files changed, 15 insertions(+), 397 deletions(-) delete mode 100644 test/lua/loads-core.lua delete mode 100644 test/lua/preamble.lua delete mode 100644 test/python/retroarch.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e910e6fe..44579a4b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,36 +1,19 @@ -find_package(PythonModules COMPONENTS libretro) find_package(Python 3.11 REQUIRED) # FindPython sets "Python_EXECUTABLE", but FindPythonModules wants "PYTHON_EXECUTABLE" (note the caps) set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") +find_package(PythonModules COMPONENTS libretro wand) # TODO: Make this required once libretro.py is on PyPI if (PythonModules_libretro_PATH) message(STATUS "Found libretro.py: ${PythonModules_libretro_PATH}") endif() -if (NOT RETROARCH) - find_program( - RETROARCH - retroarch - HINTS "C:/Program Files/RetroArch" "C:/Program Files/RetroArch-Win64" - REQUIRED - ) -endif() -message(STATUS "RETROARCH: ${RETROARCH}") - -if (NOT EMUTEST) - find_program( - EMUTEST - emutest - HINTS "$ENV{GOPATH}/bin" "$ENV{USERPROFILE}/go/bin" - REQUIRED - ) +if (PythonModules_wand_PATH) + message(STATUS "Found wand: ${PythonModules_wand_PATH}") endif() -message(STATUS "EMUTEST: ${EMUTEST}") - -cmake_path(GET RETROARCH PARENT_PATH RETROARCH_DIR) +## TODO: Write a requirements.txt for all test dependencies if (NOT NDS_ROM) message(WARNING "NDS_ROM must be set to the path of an NDS ROM") @@ -41,7 +24,7 @@ endif() function(find_bios BIOS_NAME BIOS_FILE EXPECTED_BIOS_SIZES KNOWN_BIOS_HASHES) if (NOT ${BIOS_NAME}) - find_file(${BIOS_NAME} ${BIOS_FILE} HINTS "${RETROARCH_DIR}/system" REQUIRED) + find_file(${BIOS_NAME} ${BIOS_FILE} REQUIRED) endif() file(SIZE ${${BIOS_NAME}} BIOS_SIZE) @@ -85,169 +68,6 @@ cmake_path(GET DSI_NAND FILENAME DSI_NAND_NAME) include(CMakePrintHelpers) -function(add_retroarch_test) - set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NO_SKIP_ERROR_SCREEN DISABLED) - set(oneValueArgs NAME MAX_FRAMES CONTENT REQUIRE_FILE_SIZE_UNCHANGED REQUIRE_FILE_CREATED) - set(multiValueArgs CORE_OPTION PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION) - cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") - - if (NOT RETRO_MAX_FRAMES) - set(RETRO_MAX_FRAMES 30) - endif() - - add_test( - NAME "${RETRO_NAME}" - COMMAND ${Python3_EXECUTABLE} - "${CMAKE_CURRENT_SOURCE_DIR}/python/retroarch.py" - --libretro "$" - --max-frames=${RETRO_MAX_FRAMES} - "${RETRO_CONTENT}" - ) - - list(APPEND REQUIRED_FILES "$") - if (RETRO_CONTENT) - list(APPEND REQUIRED_FILES "${RETRO_CONTENT}") - endif() - - if (NOT RETRO_NO_SKIP_ERROR_SCREEN) - list(APPEND ENVIRONMENT MELONDSDS_SKIP_ERROR_SCREEN=1) - endif() - - if (RETRO_REQUIRE_FILE_SIZE_UNCHANGED) - list(APPEND ENVIRONMENT "REQUIRE_FILE_SIZE_UNCHANGED=${RETRO_REQUIRE_FILE_SIZE_UNCHANGED}") - endif() - - if (RETRO_REQUIRE_FILE_CREATED) - list(APPEND ENVIRONMENT "REQUIRE_FILE_CREATED=${RETRO_REQUIRE_FILE_CREATED}") - endif() - - list(APPEND ENVIRONMENT "RETROARCH=${RETROARCH}") - list(APPEND ENVIRONMENT ${RETRO_CORE_OPTION}) # Not an omission, this is already a list - - macro(expose_system_file SYSFILE) - if (RETRO_${SYSFILE}) - list(APPEND REQUIRED_FILES "${${SYSFILE}}") - list(APPEND ENVIRONMENT "${SYSFILE}=${${SYSFILE}}") - endif() - endmacro() - - expose_system_file(ARM7_BIOS) - expose_system_file(ARM9_BIOS) - expose_system_file(ARM7_DSI_BIOS) - expose_system_file(ARM9_DSI_BIOS) - expose_system_file(NDS_FIRMWARE) - expose_system_file(DSI_FIRMWARE) - expose_system_file(DSI_NAND) - - set_tests_properties("${RETRO_NAME}" PROPERTIES LABELS "RetroArch") - set_tests_properties("${RETRO_NAME}" PROPERTIES ENVIRONMENT "${ENVIRONMENT}") - set_tests_properties("${RETRO_NAME}" PROPERTIES REQUIRED_FILES "${REQUIRED_FILES}") - if (RETRO_PASS_REGULAR_EXPRESSION) - set_tests_properties("${RETRO_NAME}" PROPERTIES PASS_REGULAR_EXPRESSION "${RETRO_PASS_REGULAR_EXPRESSION}") - endif() - - if (RETRO_FAIL_REGULAR_EXPRESSION) - set_tests_properties("${RETRO_NAME}" PROPERTIES FAIL_REGULAR_EXPRESSION "${RETRO_FAIL_REGULAR_EXPRESSION}") - endif() - - if (RETRO_SKIP_REGULAR_EXPRESSION) - set_tests_properties("${RETRO_NAME}" PROPERTIES SKIP_REGULAR_EXPRESSION "${RETRO_SKIP_REGULAR_EXPRESSION}") - endif() - - if (RETRO_WILL_FAIL) - set_tests_properties("${RETRO_NAME}" PROPERTIES WILL_FAIL TRUE) - endif() - - if (RETRO_DISABLED) - set_tests_properties("${RETRO_NAME}" PROPERTIES DISABLED TRUE) - endif() -endfunction() - -function(add_emutest_test) - set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NO_SKIP_ERROR_SCREEN DISABLED) - set(oneValueArgs NAME TEST_SCRIPT CONTENT) - set(multiValueArgs CORE_OPTION PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION) - cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") - - add_test( - NAME "${RETRO_NAME}" - COMMAND ${EMUTEST} - -L "$" - -r "${RETRO_CONTENT}" - -t "${CMAKE_CURRENT_SOURCE_DIR}/lua/${RETRO_TEST_SCRIPT}" - ) - - list(APPEND REQUIRED_FILES "${CMAKE_CURRENT_SOURCE_DIR}/lua/preamble.lua") - list(APPEND REQUIRED_FILES "$") - if (RETRO_CONTENT) - list(APPEND REQUIRED_FILES "${RETRO_CONTENT}") - endif() - - - if (WIN32) - cmake_path(CONVERT "$ENV{USERPROFILE}\\.emutest" TO_CMAKE_PATH_LIST EMUTEST_HOME) - # emutest hardcodes a single root directory - else() - set(EMUTEST_HOME "$ENV{HOME}/.emutest") - endif() - if (NOT RETRO_NO_SKIP_ERROR_SCREEN) - list(APPEND ENVIRONMENT MELONDSDS_SKIP_ERROR_SCREEN=1) - endif() - - list(APPEND ENVIRONMENT "EMUTEST_HOME=${EMUTEST_HOME}") - list(APPEND ENVIRONMENT "PREAMBLE=${CMAKE_CURRENT_SOURCE_DIR}/lua/preamble.lua") - list(JOIN RETRO_CORE_OPTION "\n" RETRO_CORE_OPTIONS) - list(APPEND ENVIRONMENT "RETRO_CORE_OPTIONS=${RETRO_CORE_OPTIONS}") - - macro(expose_system_file SYSFILE) - if (RETRO_${SYSFILE}) - cmake_path(GET ${SYSFILE} FILENAME filename) - # emutest doesn't use standard Lua; it uses a Go port that happens to not support regexes. - # So we have to pass in the basename separately. - list(APPEND REQUIRED_FILES "${${SYSFILE}}") - list(APPEND ENVIRONMENT "${SYSFILE}=${${SYSFILE}}") - list(APPEND ENVIRONMENT "${SYSFILE}_NAME=${filename}") - endif() - endmacro() - - list(APPEND ENVIRONMENT "CMAKE=${CMAKE_COMMAND}") - - expose_system_file(ARM7_BIOS) - expose_system_file(ARM9_BIOS) - expose_system_file(ARM7_DSI_BIOS) - expose_system_file(ARM9_DSI_BIOS) - expose_system_file(NDS_FIRMWARE) - expose_system_file(DSI_FIRMWARE) - expose_system_file(DSI_NAND) - - set_tests_properties("${RETRO_NAME}" PROPERTIES LABELS "emutest") - set_tests_properties("${RETRO_NAME}" PROPERTIES ENVIRONMENT "${ENVIRONMENT}") - set_tests_properties("${RETRO_NAME}" PROPERTIES REQUIRED_FILES "${REQUIRED_FILES}") - set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT 30) - - # emutest only supports a pre-defined base directory, so we can only run one emutest test at a time - set_tests_properties("${RETRO_NAME}" PROPERTIES RESOURCE_LOCK "homedir") - if (RETRO_PASS_REGULAR_EXPRESSION) - set_tests_properties("${RETRO_NAME}" PROPERTIES PASS_REGULAR_EXPRESSION "${RETRO_PASS_REGULAR_EXPRESSION}") - endif() - - if (RETRO_FAIL_REGULAR_EXPRESSION) - set_tests_properties("${RETRO_NAME}" PROPERTIES FAIL_REGULAR_EXPRESSION "${RETRO_FAIL_REGULAR_EXPRESSION}") - endif() - - if (RETRO_SKIP_REGULAR_EXPRESSION) - set_tests_properties("${RETRO_NAME}" PROPERTIES SKIP_REGULAR_EXPRESSION "${RETRO_SKIP_REGULAR_EXPRESSION}") - endif() - - if (RETRO_WILL_FAIL) - set_tests_properties("${RETRO_NAME}" PROPERTIES WILL_FAIL TRUE) - endif() - - if (RETRO_DISABLED) - set_tests_properties("${RETRO_NAME}" PROPERTIES DISABLED TRUE) - endif() -endfunction() - function(add_python_test) set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NDS_SYSFILES DSI_SYSFILES NO_SKIP_ERROR_SCREEN DISABLED) set(oneValueArgs NAME CONTENT TEST_MODULE SKIP_RETURN_CODE TIMEOUT) @@ -372,17 +192,6 @@ set(GODMODE9I_ROM "${godmode9i_SOURCE_DIR}/GodMode9i.nds") set(MICRECORD_NDS "${CMAKE_CURRENT_SOURCE_DIR}/nds/micrecord.nds") -add_retroarch_test( - NAME "RetroArch loads melonDS DS" - CONTENT "${NDS_ROM}" -) - -add_emutest_test( - NAME "emutest loads melonDS DS" - CONTENT "${NDS_ROM}" - TEST_SCRIPT "loads-core.lua" -) - # TODO: Write the following test capabilities: # - Copying a system file to a particular location # - Select a GBA ROM diff --git a/test/README.md b/test/README.md index 306e17f4..b14c7a9f 100644 --- a/test/README.md +++ b/test/README.md @@ -17,13 +17,15 @@ through the steps described in the [main README](../README.md#building). Once you do that, you'll need to obtain the following dependencies: -- [RetroArch][retroarch] -- [Emutest][emutest] -- [Go][go], to install Emutest (since it doesn't offer prebuilt binaries) -- [Python 3][python] -- A Nintendo DS ROM +- [Python][python] 3.11 or later. +- A Nintendo DS ROM, preferably retail. + The tests don't assume any particular ROM. - The set of Nintendo DS/DSi system files described in the [main README](../README.md#installing-nintendo-ds-bios) +## Configuring the Python Environment + +TODO: Describe how to install the Python dependencies. + ## Configuring the Tests Once you have all of the above, @@ -39,14 +41,10 @@ with the following variables defined on the command line: - `DSI_FIRMWARE`: Set to the location of your DSi firmware image. - `DSI_NAND`: Set to the location of your DSi NAND image. - `NDS_ROM`: Set to the location of your NDS ROM image. -- `RETROARCH`: Set to the location of the RetroArch executable. - Not necessary if it's on your `PATH`. -- `EMU_TEST`: Set to the location of the Emutest executable. - Not necessary if it's on your `PATH`. > [!NOTE] > The test suite will not modify these files; -> they will be copied into appropriate directories before each test. +> they will be copied into a temporary directory before each test. Here's an example: @@ -61,9 +59,7 @@ cmake -B build \ -DNDS_FIRMWARE="$SYSTEM_PATH/firmware.bin" \ -DDSI_FIRMWARE="$SYSTEM_PATH/dsi_firmware.bin" \ -DDSI_NAND="$SYSTEM_PATH/dsi_nand.bin" \ - -DNDS_ROM="$ROM_PATH/your_nds_rom.nds" \ - -DRETROARCH="$RETROARCH_PATH/retroarch" \ - -DEMU_TEST="$EMUTEST_PATH/emutest" + -DNDS_ROM="$ROM_PATH/your_nds_rom.nds" ``` You may want to put these variables in a script or an IDE configuration. @@ -89,7 +85,4 @@ ctest --test-dir build # Run the tests. (CTest is included with CMake) > If you can't reproduce a bug that should cause a test to fail, > try a different firmware image. -[emutest]: https://github.com/kivutar/emutest -[go]: https://go.dev -[python]: https://www.python.org -[retroarch]: https://www.retroarch.com \ No newline at end of file +[python]: https://www.python.org \ No newline at end of file diff --git a/test/lua/loads-core.lua b/test/lua/loads-core.lua deleted file mode 100644 index cfe6e57d..00000000 --- a/test/lua/loads-core.lua +++ /dev/null @@ -1,11 +0,0 @@ -local preamble = dofile(os.getenv("PREAMBLE")) -load_core(corepath) -load_game(rompath) - -for _ = 1, 30 do - run() -end - -unload_game() - -print(get_logs()) \ No newline at end of file diff --git a/test/lua/preamble.lua b/test/lua/preamble.lua deleted file mode 100644 index 78807063..00000000 --- a/test/lua/preamble.lua +++ /dev/null @@ -1,40 +0,0 @@ -local SYSTEM_FILES = {"ARM7_BIOS", "ARM9_BIOS", "ARM7_DSI_BIOS", "ARM9_DSI_BIOS", "NDS_FIRMWARE", "DSI_FIRMWARE", "DSI_NAND"} -local emutest_dir = os.getenv("EMUTEST_HOME") -local cmake = os.getenv("CMAKE") -local system_dir = emutest_dir .. "/system" -local save_dir = emutest_dir .. "/savefiles" -local savestate_directory = emutest_dir .. "/states" -local core_system_dir = system_dir .. "/melonDS DS" -local core_save_dir = save_dir .. "/melonDS DS" - -print("Test dir:", emutest_dir) -print("System dir:", system_dir) -print("Save dir:", save_dir) -print("Savestate dir:", savestate_directory) -print("Core system dir:", core_system_dir) - --- We can assume the presence of CMake, --- otherwise how would we be running the test suite? -os.execute(string.format('"%s" -E rm -rf "%s" "%s" "%s"', cmake, system_dir, save_dir, savestate_directory)) -os.execute(string.format('"%s" -E make_directory "%s" "%s" "%s"', cmake, core_system_dir, save_dir, savestate_directory)) - -for _, f in ipairs(SYSTEM_FILES) do - local sysfile = os.getenv(f) - if sysfile ~= nil and sysfile ~= "" then - local sysname = os.getenv(f .. "_NAME") - assert(sysname ~= nil and sysname ~= "") - local cmd = string.format('"%s" -E copy "%s" "%s/%s"', cmake, sysfile, core_system_dir, sysname) - -- TODO: For the ARM BIOS files, hardcode the destination filename to "bios7.bin", etc. - os.execute(cmd) - end -end - -local preamble = {} - -preamble.options_string = os.getenv("RETRO_CORE_OPTIONS") -preamble.system_dir = system_dir -preamble.save_dir = save_dir -preamble.core_system_dir = core_system_dir -preamble.core_save_dir = core_save_dir - -return preamble \ No newline at end of file diff --git a/test/python/retroarch.py b/test/python/retroarch.py deleted file mode 100644 index 9ddee9c1..00000000 --- a/test/python/retroarch.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python - -import platform -import os.path -import subprocess -import shutil -import sys -import tempfile - -SYSTEM_FILES = ("ARM7_BIOS", "ARM9_BIOS", "ARM7_DSI_BIOS", "ARM9_DSI_BIOS", "NDS_FIRMWARE", "DSI_FIRMWARE", "DSI_NAND") -retroarch_path = os.environ["RETROARCH"] - -assert retroarch_path is not None -assert os.path.isabs(retroarch_path) -assert os.path.isfile(retroarch_path) - -# Create a temporary directory, so that we don't mess with the user's config. -# (Also, this helps us parallelize tests.) -tempdir = tempfile.mkdtemp() -print("Test dir:", tempdir) - - -system_dir = os.path.join(tempdir, "system") -save_dir = os.path.join(tempdir, "saves") -log_dir = os.path.join(tempdir, "logs") -screenshot_dir = os.path.join(tempdir, "screenshots") -savestate_directory = os.path.join(tempdir, "states") -config_dir = os.path.join(tempdir, "config") -core_option_dir = os.path.join(config_dir, "melonDS DS") -core_option_path = os.path.join(core_option_dir, "melonDS DS.opt") -core_system_dir = os.path.join(system_dir, "melonDS DS") - -os.makedirs(core_system_dir, exist_ok=True) -os.makedirs(save_dir, exist_ok=True) -os.makedirs(log_dir, exist_ok=True) -os.makedirs(screenshot_dir, exist_ok=True) -os.makedirs(savestate_directory, exist_ok=True) -os.makedirs(config_dir, exist_ok=True) -os.makedirs(core_option_dir, exist_ok=True) - -for s in SYSTEM_FILES: - if s in os.environ: - sysfile = os.environ[s] - assert os.path.exists(sysfile) - shutil.copy2(sysfile, os.path.join(core_system_dir, os.path.basename(sysfile))) - # TODO: For the ARM BIOS files, hardcode the destination filename to "bios7.bin", etc. - -# Create a bare-bones RetroArch config. -# RetroArch itself will add defaults for any missing options. -config_path = os.path.join(tempdir, "retroarch.cfg") -config = { - "audio_driver": "null", - "audio_enable": "false", - "camera_driver": "null", - "frontend_log_level": "0", - "gamemode_enable": "false", - "libretro_log_level": "0", - "input_max_users": "8", - "input_pause_toggle": "nul", - "log_to_file": "true", - "log_dir": log_dir, - "log_to_file_timestamp": "false", - "log_verbosity": "true", - "menu_driver": "rgui", - "microphone_driver": "null", - "microphone_enable": "false", - "midi_driver": "null", - "network_cmd_enable": "false", - "pause_nonactive": "false", - "rgui_config_directory": config_dir, - "system_directory": system_dir, - "savefile_directory": save_dir, - "savestate_directory": savestate_directory, - "suspend_screensaver_enable": "false", - "video_driver": "glcore" if platform.system() == "Linux" else "sdl2", -} - - -def retroarch(): - with open(core_option_path, "x") as core_config, open(config_path, "x") as retroarch_config: - # Need to create a config file here so RetroArch will use it as the root directory. - for k, v in config.items(): - retroarch_config.write(f"{k} = \"{v}\"\n") - - for e in sorted(os.environ): - key = e.lower() - if key.startswith("melonds_"): - core_config.write(f"{key} = \"{os.environ[e]}\"\n") - elif key.startswith("retroarch_"): - retroarch_config.write(f"{key.removeprefix('retroarch_')} = \"{os.environ[e]}\"\n") - - logpath = os.path.join(tempdir, "logs", "retroarch.log") - - with subprocess.Popen((retroarch_path, f"--config={config_path}", "--verbose", f"--log-file={logpath}", *sys.argv[1:]), cwd=tempdir, text=True) as process: - print(process.args) - result = process.wait(30) - - with open(logpath, "r") as f: - print(f.read()) - - print("End of log file") - return result - - -if __name__ == "__main__": - require_file_size_unchanged = os.environ.get("REQUIRE_FILE_SIZE_UNCHANGED", None) - required_file_size: int | None = None - if require_file_size_unchanged is not None: - # If there's a file whose size we must ensure didn't change... - require_file_size_unchanged = os.path.join(tempdir, require_file_size_unchanged) - stat = os.stat(require_file_size_unchanged) - assert stat is not None - required_file_size = stat.st_size - - require_file_created = os.environ.get("REQUIRE_FILE_CREATED", None) - if require_file_created is not None: - # If there's a file that we must ensure is newly-created... - require_file_created = os.path.join(tempdir, require_file_created) - assert not os.access(require_file_created, os.F_OK) - - returnCode = retroarch() - - if require_file_size_unchanged is not None: - # If there's a file whose size we must ensure didn't change... - stat = os.stat(require_file_size_unchanged) - assert stat is not None - assert stat.st_size == required_file_size - - if require_file_created is not None: - # If there's a file that we must ensure is newly-created... - assert os.access(require_file_created, os.F_OK) - - sys.exit(returnCode) From c20a97ad356c7d2f644e485285a1fbdc800180ca Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 25 Mar 2024 20:00:47 -0400 Subject: [PATCH 099/234] Update some test scripts --- test/CMakeLists.txt | 2 +- .../basics/core_accepts_button_input.py | 19 +++++--------- .../basics/core_accepts_microphone_input.py | 2 -- .../basics/core_accepts_pointer_input.py | 26 +++++++------------ test/python/basics/core_generates_video.py | 20 +++++++------- test/python/basics/core_region_correct.py | 2 +- .../basics/core_system_av_info_correct.py | 4 +-- test/python/reset/no_hang_on_reboot.py | 13 +++++----- 8 files changed, 37 insertions(+), 51 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 44579a4b..f49e5755 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,7 +3,7 @@ find_package(Python 3.11 REQUIRED) # FindPython sets "Python_EXECUTABLE", but FindPythonModules wants "PYTHON_EXECUTABLE" (note the caps) set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") -find_package(PythonModules COMPONENTS libretro wand) +find_package(PythonModules COMPONENTS libretro PIL) # TODO: Make this required once libretro.py is on PyPI if (PythonModules_libretro_PATH) diff --git a/test/python/basics/core_accepts_button_input.py b/test/python/basics/core_accepts_button_input.py index 332a553d..76f454ed 100644 --- a/test/python/basics/core_accepts_button_input.py +++ b/test/python/basics/core_accepts_button_input.py @@ -1,12 +1,9 @@ -from array import array import itertools from typing import cast from libretro import Session from libretro.api.input import JoypadState -from libretro.api.video import SoftwareVideoState - -from wand.image import Image +from libretro.api.video import PillowVideoDriver import prelude @@ -21,18 +18,16 @@ def generate_input(): session: Session with prelude.session(input_state=generate_input) as session: - video = cast(SoftwareVideoState, session.video) + video = cast(PillowVideoDriver, session.video) for i in range(240): session.core.run() - frame1 = array(video.frame.typecode, video.frame) + frame1 = video.get_frame() for i in range(240): session.core.run() - frame2 = array(video.frame.typecode, video.frame) - - screen1 = Image(blob=frame1.tobytes(), format="BGRA", width=256, height=192*2, depth=8) - screen2 = Image(blob=frame2.tobytes(), format="BGRA", width=256, height=192*2, depth=8) + frame2 = video.get_frame() - assert screen1.get_image_distortion(screen2, 'absolute') > 50000 - # Assert that the two screenshots differ by more than 50000 pixels + # The logo screen (frame1) has a white pixel in the top left corner, + # whereas the main menu screen doesn't + assert frame1.getpixel((0, 0)) != frame2.getpixel((0, 0)) diff --git a/test/python/basics/core_accepts_microphone_input.py b/test/python/basics/core_accepts_microphone_input.py index 80792397..f5d0209a 100644 --- a/test/python/basics/core_accepts_microphone_input.py +++ b/test/python/basics/core_accepts_microphone_input.py @@ -8,7 +8,6 @@ from libretro.api.input import JoypadState import prelude -from libretro.api.video import SoftwareVideoState def generate_input() -> Iterator[int]: @@ -39,7 +38,6 @@ def generate_sine_wave() -> Iterator[int]: session: Session with prelude.session(**kwargs) as session: audio = cast(ArrayAudioState, session.audio) - video = cast(SoftwareVideoState, session.video) for i in range(300): session.core.run() diff --git a/test/python/basics/core_accepts_pointer_input.py b/test/python/basics/core_accepts_pointer_input.py index af2e5eb7..d1706e7b 100644 --- a/test/python/basics/core_accepts_pointer_input.py +++ b/test/python/basics/core_accepts_pointer_input.py @@ -1,17 +1,10 @@ -import math -import time -from array import array import itertools from typing import cast -from math import sin, cos, tau, pi +from math import sin, cos, pi -import wand.display from libretro import Session -from libretro.api.input import Point -from libretro.api.input.pointer import Pointer -from libretro.api.video import SoftwareVideoState - -from wand.image import Image +from libretro.api.input import Point, Pointer +from libretro.api.video import PillowVideoDriver import prelude @@ -38,18 +31,17 @@ def generate_input(): session: Session with prelude.session(input_state=generate_input) as session: - video = cast(SoftwareVideoState, session.video) + video = cast(PillowVideoDriver, session.video) for i in range(180): session.core.run() - frame1 = array(video.frame.typecode, video.frame) + frame1 = video.get_frame() for i in range(240): session.core.run() - frame2 = array(video.frame.typecode, video.frame) - - screen1 = Image(blob=frame1.tobytes(), format="BGRA", width=256, height=192*2, depth=8) - screen2 = Image(blob=frame2.tobytes(), format="BGRA", width=256, height=192*2, depth=8) + frame2 = video.get_frame() - assert screen1.get_image_distortion(screen2, 'absolute') > 50000 + # The logo screen (frame1) has a white pixel in the top left corner, + # whereas the main menu screen doesn't + assert frame1.getpixel((0, 0)) != frame2.getpixel((0, 0)) diff --git a/test/python/basics/core_generates_video.py b/test/python/basics/core_generates_video.py index 917edf0c..d5bb1887 100644 --- a/test/python/basics/core_generates_video.py +++ b/test/python/basics/core_generates_video.py @@ -1,27 +1,27 @@ -from array import array from typing import cast from libretro import Session -from libretro.api.video import SoftwareVideoState +from libretro.api.video import PillowVideoDriver import prelude session: Session with prelude.session() as session: - video = cast(SoftwareVideoState, session.video) + video = cast(PillowVideoDriver, session.video) - assert isinstance(video, SoftwareVideoState) + assert isinstance(video, PillowVideoDriver) - for i in range(60): + for i in range(70): session.core.run() - assert video.frame is not None + frame1 = video.get_frame() + assert frame1 is not None - frame1 = array(video.frame.typecode, video.frame) - - for i in range(60): + for i in range(70): session.core.run() - frame2 = array(video.frame.typecode, video.frame) + frame2 = video.get_frame() + assert frame2 is not None + assert frame1.size == frame2.size assert frame1 != frame2 diff --git a/test/python/basics/core_region_correct.py b/test/python/basics/core_region_correct.py index 83585d7b..6cf432a7 100644 --- a/test/python/basics/core_region_correct.py +++ b/test/python/basics/core_region_correct.py @@ -1,5 +1,5 @@ from libretro import Core -from libretro.api.system import Region +from libretro.api.av import Region import prelude diff --git a/test/python/basics/core_system_av_info_correct.py b/test/python/basics/core_system_av_info_correct.py index 41d7242b..a2589a27 100644 --- a/test/python/basics/core_system_av_info_correct.py +++ b/test/python/basics/core_system_av_info_correct.py @@ -1,10 +1,10 @@ -from libretro import retro_system_av_info, Session +from libretro import Session import prelude session: Session with prelude.session() as session: - av_info: retro_system_av_info = session.core.get_system_av_info() + av_info = session.core.get_system_av_info() assert av_info is not None assert av_info.timing.sample_rate != 0 diff --git a/test/python/reset/no_hang_on_reboot.py b/test/python/reset/no_hang_on_reboot.py index a744ce05..3dd10e82 100644 --- a/test/python/reset/no_hang_on_reboot.py +++ b/test/python/reset/no_hang_on_reboot.py @@ -2,7 +2,7 @@ from typing import cast from libretro import Session -from libretro.api.video import SoftwareVideoState +from libretro.api.video import PillowVideoDriver import prelude @@ -16,16 +16,17 @@ with prelude.session(options=options) as session: session.core.run() - video = cast(SoftwareVideoState, session.video) + video = cast(PillowVideoDriver, session.video) # Very first frame should be all white - blank_frame = array(video.frame.typecode, video.frame) - assert all(byte == 0xFF for byte in blank_frame), "Screen is not blank" + blank_frame = video.get_frame() + blank_colors = blank_frame.getcolors() + assert blank_colors is not None and len(blank_colors) == 1, f"Expected an all-white frame, got {blank_colors}" for i in range(300): session.core.run() - after_frame = array(video.frame.typecode, video.frame) + after_frame = video.get_frame() assert blank_frame != after_frame, "Screen is still blank after 300 frames" session.core.reset() @@ -33,5 +34,5 @@ for i in range(300): session.core.run() - after_reset_frame = array(video.frame.typecode, video.frame) + after_reset_frame = video.get_frame() assert blank_frame != after_reset_frame, "Screen is still blank after resetting and running for 300 frames" From cdf28ecd746a69e1e308d495005f524d63230815 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 25 Mar 2024 20:19:35 -0400 Subject: [PATCH 100/234] Implement a test for setting the geometry --- test/cmake/Basics.cmake | 6 +- test/python/basics/core_sets_geometry.py | 77 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 test/python/basics/core_sets_geometry.py diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index bae513aa..93fecd69 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -241,9 +241,9 @@ add_python_test( add_python_test( NAME "Core sets geometry at runtime" - TEST_MODULE "" - DISABLED -) # TODO: Implement this test + TEST_MODULE basics.core_sets_geometry + CONTENT "${NDS_ROM}" +) add_python_test( NAME "Core registers support for achievements" diff --git a/test/python/basics/core_sets_geometry.py b/test/python/basics/core_sets_geometry.py new file mode 100644 index 00000000..4e113fc2 --- /dev/null +++ b/test/python/basics/core_sets_geometry.py @@ -0,0 +1,77 @@ +import itertools +from typing import cast + +from libretro import Session +from libretro.api.input import JoypadState +from libretro.api.video import PillowVideoDriver + +import prelude + +options = { + b"melonds_number_of_screen_layouts": b"2", + b"melonds_screen_layout1": b"top-bottom", + b"melonds_screen_layout2": b"left-right", +} + + +def generate_input(): + # Wait a little while... + yield from itertools.repeat(None, 10) + + # Cycle to the next screen layout + yield JoypadState(r2=True) + + yield from itertools.repeat(None) + + +session: Session +with prelude.session(input_state=generate_input) as session: + video = cast(PillowVideoDriver, session.video) + for i in range(10): + session.core.run() + + frame1 = video.get_frame() + framebuffer1 = video.get_frame_max() + geometry1 = video.geometry + + assert frame1 is not None + assert framebuffer1 is not None + assert geometry1 is not None + + assert frame1.size != framebuffer1.size, \ + f"Frame size should not be the same as framebuffer size ({frame1.size})" + + assert frame1.width < framebuffer1.width, \ + f"Frame width ({frame1.width}) shouldn't exceed framebuffer width ({framebuffer1.width})" + + assert frame1.height < framebuffer1.height, \ + f"Frame height ({frame1.height}) shouldn't exceed framebuffer height ({framebuffer1.height})" + + assert frame1.width == geometry1.base_width, \ + f"Frame width ({frame1.width}) should match geometry base width ({geometry1.base_width})" + + assert frame1.height == geometry1.base_height, \ + f"Frame height ({frame1.height}) should match geometry base height ({geometry1.height})" + + assert framebuffer1.width == geometry1.max_width, \ + f"Framebuffer width ({framebuffer1.width}) should match geometry max width ({geometry1.max_width})" + + assert framebuffer1.height == geometry1.max_height, \ + f"Framebuffer height ({framebuffer1.height}) should match geometry max height ({geometry1.max_height})" + + for i in range(20): + session.core.run() + + # Now that we've changed the screen layout, let's make sure we changed the geometry + + frame2 = video.get_frame() + framebuffer2 = video.get_frame_max() + geometry2 = video.geometry + + assert frame2 is not None + assert framebuffer2 is not None + assert geometry2 is not None + + assert geometry1 != geometry2, f"Geometry should have changed from {geometry1} after switching screen layout" + assert framebuffer1.size == framebuffer2.size, \ + f"Framebuffer size changed from {framebuffer1.size} to {framebuffer2.size}, but it shouldn't have" From 86ebcd38082180bacc61a0a79b3cc48ffec85385 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 25 Mar 2024 20:25:19 -0400 Subject: [PATCH 101/234] Implement a test for setting the pixel format --- test/python/basics/core_sets_pixel_format.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/python/basics/core_sets_pixel_format.py b/test/python/basics/core_sets_pixel_format.py index 967ddfb5..c4a4f124 100644 --- a/test/python/basics/core_sets_pixel_format.py +++ b/test/python/basics/core_sets_pixel_format.py @@ -1,8 +1,12 @@ -from sys import argv -from libretro import default_session -from libretro.defs import PixelFormat +from libretro import default_session, Session +from libretro.api.video import PixelFormat, PillowVideoDriver import prelude -with default_session(prelude.core_path) as session: - assert session.video.pixel_format == PixelFormat.XRGB8888 \ No newline at end of file +video = PillowVideoDriver() + +assert video.pixel_format != PixelFormat.XRGB8888 + +session: Session +with prelude.session() as session: + assert session.video.pixel_format == PixelFormat.XRGB8888 From a941fb398c2b6fd5127d99332756e5133fa1f470 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 25 Mar 2024 21:51:58 -0400 Subject: [PATCH 102/234] Implement a test for shutting down the core --- test/cmake/Basics.cmake | 1 - test/python/basics/core_can_shut_down.py | 22 ++++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 93fecd69..0c811d23 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -145,7 +145,6 @@ add_python_test( TEST_MODULE basics.core_can_shut_down NDS_SYSFILES TIMEOUT 30 - DISABLED # Failure is expected, as the frontend will shut down # (But segfaults or timeouts are still actual failures) ) diff --git a/test/python/basics/core_can_shut_down.py b/test/python/basics/core_can_shut_down.py index 84fe5357..1b70c90a 100644 --- a/test/python/basics/core_can_shut_down.py +++ b/test/python/basics/core_can_shut_down.py @@ -1,11 +1,11 @@ import itertools from typing import cast -from libretro import Session +from libretro import Session, CoreShutDownException from libretro.api.input import DeviceIdJoypad -from libretro.api.video import SoftwareVideoState import prelude +from libretro.api.video import PillowVideoDriver def generate_input(): @@ -35,8 +35,22 @@ def generate_input(): session: Session with prelude.session(input_state=generate_input) as session: - video = cast(SoftwareVideoState, session.video) + video = cast(PillowVideoDriver, session.video) + for i in range(600): session.core.run() + # The core should shut down at some point during the test + + assert False, "session.core should've raised CoreShutDownException and exited the runtime context" + +# noinspection PyUnreachableCode +assert session is not None +assert session.is_shutdown +assert session.is_exited + +try: + session.core.run() +except CoreShutDownException as e: + pass - assert session.is_shutdown +print("Core shut down successfully") From f35311fcd6d198659118622818396ec1a92bcae2 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 25 Mar 2024 21:58:02 -0400 Subject: [PATCH 103/234] Fix a test for no-content load --- test/cmake/Basics.cmake | 2 ++ test/python/basics/core_loads_unloads_without_content.py | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 0c811d23..29ce396e 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -194,6 +194,8 @@ add_python_test( add_python_test( NAME "Core loads and unloads without content" TEST_MODULE basics.core_loads_unloads_without_content + NDS_SYSFILES + # If we don't have content, then we need bootable firmware ) add_python_test( diff --git a/test/python/basics/core_loads_unloads_without_content.py b/test/python/basics/core_loads_unloads_without_content.py index c3940c6d..fca2f919 100644 --- a/test/python/basics/core_loads_unloads_without_content.py +++ b/test/python/basics/core_loads_unloads_without_content.py @@ -1,6 +1,8 @@ -from sys import argv -from libretro import default_session +from libretro import Session -with default_session(argv[1]) as session: +import prelude + +session: Session +with prelude.session() as session: for i in range(10): session.core.run() From c77f86ada96ec0795271251212e89320f653aa8b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 25 Mar 2024 22:03:23 -0400 Subject: [PATCH 104/234] Clean up soe test options - Allow tests to be skipped if they need OpenGL but it's unavailable - Make CONTENT a multi-valued arg --- test/CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f49e5755..d9a98a48 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -69,9 +69,9 @@ cmake_path(GET DSI_NAND FILENAME DSI_NAND_NAME) include(CMakePrintHelpers) function(add_python_test) - set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NDS_SYSFILES DSI_SYSFILES NO_SKIP_ERROR_SCREEN DISABLED) - set(oneValueArgs NAME CONTENT TEST_MODULE SKIP_RETURN_CODE TIMEOUT) - set(multiValueArgs CORE_OPTION DEPENDS PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION LABELS) + set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NDS_SYSFILES DSI_SYSFILES NO_SKIP_ERROR_SCREEN DISABLED REQUIRES_OPENGL) + set(oneValueArgs NAME TEST_MODULE SKIP_RETURN_CODE TIMEOUT) + set(multiValueArgs CONTENT CORE_OPTION DEPENDS PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION LABELS) cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") add_test( @@ -164,6 +164,9 @@ function(add_python_test) if (RETRO_DISABLED) set_tests_properties("${RETRO_NAME}" PROPERTIES DISABLED TRUE) + elseif(RETRO_REQUIRES_OPENGL AND NOT HAVE_OPENGL) + message(WARNING "Test '${RETRO_NAME}' requires OpenGL, but the core wasn't built with it. Disabling.") + set_tests_properties("${RETRO_NAME}" PROPERTIES DISABLED TRUE) endif() if (RETRO_SKIP_RETURN_CODE) From 99af7c4c9b0ff5a8a31a0a560ca4d89a8eb7e5f2 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 25 Mar 2024 22:06:21 -0400 Subject: [PATCH 105/234] Move option lists to multiple lines --- test/CMakeLists.txt | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d9a98a48..65dce738 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -69,9 +69,38 @@ cmake_path(GET DSI_NAND FILENAME DSI_NAND_NAME) include(CMakePrintHelpers) function(add_python_test) - set(options WILL_FAIL ARM7_BIOS ARM9_BIOS ARM7_DSI_BIOS ARM9_DSI_BIOS NDS_FIRMWARE DSI_FIRMWARE DSI_NAND NDS_SYSFILES DSI_SYSFILES NO_SKIP_ERROR_SCREEN DISABLED REQUIRES_OPENGL) - set(oneValueArgs NAME TEST_MODULE SKIP_RETURN_CODE TIMEOUT) - set(multiValueArgs CONTENT CORE_OPTION DEPENDS PASS_REGULAR_EXPRESSION FAIL_REGULAR_EXPRESSION SKIP_REGULAR_EXPRESSION LABELS) + set(options + ARM7_BIOS + ARM7_DSI_BIOS + ARM9_BIOS + ARM9_DSI_BIOS + DISABLED + DSI_FIRMWARE + DSI_NAND + DSI_SYSFILES + NDS_FIRMWARE + NDS_SYSFILES + NO_SKIP_ERROR_SCREEN + REQUIRES_OPENGL + WILL_FAIL + ) + + set(oneValueArgs + NAME + SKIP_RETURN_CODE + TEST_MODULE + TIMEOUT + ) + + set(multiValueArgs + CONTENT + CORE_OPTION + DEPENDS + FAIL_REGULAR_EXPRESSION + LABELS + PASS_REGULAR_EXPRESSION + SKIP_REGULAR_EXPRESSION + ) cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") add_test( From 3d872f075824581f0ef3e6fa3d229a73408e053d Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 26 Mar 2024 08:58:26 -0400 Subject: [PATCH 106/234] Update some tests --- test/python/basics/core_defines_controller_info.py | 10 ++++++++-- test/python/basics/core_defines_options_v0.py | 4 ++-- test/python/basics/core_defines_options_v1.py | 4 ++-- test/python/basics/core_defines_subsystems.py | 14 ++++++++++---- test/python/basics/core_gets_options_version.py | 4 ++-- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/test/python/basics/core_defines_controller_info.py b/test/python/basics/core_defines_controller_info.py index a2742a31..08c93362 100644 --- a/test/python/basics/core_defines_controller_info.py +++ b/test/python/basics/core_defines_controller_info.py @@ -1,14 +1,20 @@ +from collections.abc import Sequence from pprint import pprint from libretro import Session +from libretro.api.input.info import retro_controller_info import prelude +info: Sequence[retro_controller_info] session: Session with prelude.session() as session: info = session.controller_info - pprint(info) - assert info is not None assert len(info) > 0 assert all(len(i) for i in info) + + +# Testing this _after_ unloading the core to ensure the data is still valid +pprint(info) +pprint(info[0]) diff --git a/test/python/basics/core_defines_options_v0.py b/test/python/basics/core_defines_options_v0.py index 2468435f..c2a95d10 100644 --- a/test/python/basics/core_defines_options_v0.py +++ b/test/python/basics/core_defines_options_v0.py @@ -1,13 +1,13 @@ from collections.abc import Mapping from libretro import Session -from libretro.api.options import retro_core_option_v2_definition, StandardOptionState +from libretro.api.options import retro_core_option_v2_definition, DictOptionDriver import prelude definitions: Mapping[bytes, retro_core_option_v2_definition] session: Session -with prelude.session(options=StandardOptionState(version=0)) as session: +with prelude.session(options=DictOptionDriver(version=0)) as session: assert session.options is not None assert session.options.version == 0 diff --git a/test/python/basics/core_defines_options_v1.py b/test/python/basics/core_defines_options_v1.py index c912ef02..9dc55e4d 100644 --- a/test/python/basics/core_defines_options_v1.py +++ b/test/python/basics/core_defines_options_v1.py @@ -1,12 +1,12 @@ from collections.abc import Mapping from libretro import Session -from libretro.api.options import retro_core_option_v2_definition, StandardOptionState +from libretro.api.options import retro_core_option_v2_definition, DictOptionDriver import prelude definitions: Mapping[bytes, retro_core_option_v2_definition] session: Session -with prelude.session(options=StandardOptionState(version=1)) as session: +with prelude.session(options=DictOptionDriver(version=1)) as session: assert session.options is not None assert session.options.version == 1 diff --git a/test/python/basics/core_defines_subsystems.py b/test/python/basics/core_defines_subsystems.py index 99b82ee1..44f000da 100644 --- a/test/python/basics/core_defines_subsystems.py +++ b/test/python/basics/core_defines_subsystems.py @@ -1,13 +1,19 @@ -import libretro.session -from libretro import default_session +from collections.abc import Sequence from pprint import pprint +from libretro import Session +from libretro.api.content import retro_subsystem_info + import prelude -with default_session(prelude.core_path, libretro.session.DoNotLoad) as session: +subsystems: Sequence[retro_subsystem_info] +session: Session +with prelude.noload_session() as session: subsystems = session.subsystems - pprint(subsystems) assert subsystems is not None assert len(subsystems) > 0 assert all(s.desc for s in subsystems) + +# Testing this _after_ unloading the core to ensure the data is still valid +pprint(subsystems) diff --git a/test/python/basics/core_gets_options_version.py b/test/python/basics/core_gets_options_version.py index 329611b7..e69cd22b 100644 --- a/test/python/basics/core_gets_options_version.py +++ b/test/python/basics/core_gets_options_version.py @@ -1,12 +1,12 @@ from ctypes import * from libretro import Session -from libretro.api.options import StandardOptionState +from libretro.api.options import DictOptionDriver import prelude session: Session -with prelude.session(options=StandardOptionState(1)) as session: +with prelude.session(options=DictOptionDriver(1)) as session: get_options_version = session.get_proc_address(b"libretropy_get_options_version", CFUNCTYPE(c_uint)) assert get_options_version is not None From 0ea969271bbae2bd7b9a53968af1d89c2e103c0f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 26 Mar 2024 15:41:51 -0400 Subject: [PATCH 107/234] Add some in-core test functions --- src/libretro/core/test.cpp | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp index 3eefe4d4..239ab585 100644 --- a/src/libretro/core/test.cpp +++ b/src/libretro/core/test.cpp @@ -113,6 +113,46 @@ extern "C" bool melondsds_firmware_native() { return console ? console->GetFirmware().GetHeader().Identifier != melonDS::GENERATED_FIRMWARE_IDENTIFIER : false; } +extern "C" size_t melondsds_gba_rom_length() { + using namespace MelonDsDs; + const melonDS::NDS* console = Core.GetConsole(); + + if (!(console && console->GetGBACart())) + return 0; + + return console->GetGBACart()->GetROMLength(); +} + +extern "C" const uint8_t* melondsds_gba_rom() { + using namespace MelonDsDs; + const melonDS::NDS* console = Core.GetConsole(); + + if (!(console && console->GetGBACart())) + return nullptr; + + return console->GetGBACart()->GetROM(); +} + +extern "C" size_t melondsds_gba_sram_length() { + using namespace MelonDsDs; + const melonDS::NDS* console = Core.GetConsole(); + + if (!(console && console->GetGBACart())) + return 0; + + return console->GetGBACart()->GetSaveMemoryLength(); +} + +extern "C" const uint8_t* melondsds_gba_sram() { + using namespace MelonDsDs; + const melonDS::NDS* console = Core.GetConsole(); + + if (!(console && console->GetGBACart())) + return nullptr; + + return console->GetGBACart()->GetSaveMemory(); +} + extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) noexcept { if (string_is_equal(sym, "libretropy_add_integers")) return reinterpret_cast(libretropy_add_integers); @@ -156,6 +196,18 @@ extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) if (string_is_equal(sym, "melondsds_firmware_native")) return reinterpret_cast(melondsds_firmware_native); + if (string_is_equal(sym, "melondsds_gba_rom_length")) + return reinterpret_cast(melondsds_gba_rom_length); + + if (string_is_equal(sym, "melondsds_gba_rom")) + return reinterpret_cast(melondsds_gba_rom); + + if (string_is_equal(sym, "melondsds_gba_sram_length")) + return reinterpret_cast(melondsds_gba_sram_length); + + if (string_is_equal(sym, "melondsds_gba_sram")) + return reinterpret_cast(melondsds_gba_sram); + return nullptr; } From 8d3e41603fa1079f5e3c5699aa7691ef82368bd0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 26 Mar 2024 15:43:16 -0400 Subject: [PATCH 108/234] Add a test for loading subsystems --- .github/workflows/main.yaml | 2 + .github/workflows/test.yaml | 6 +++ test/CMakeLists.txt | 48 +++++++++++++++++++-- test/cmake/Basics.cmake | 9 ++-- test/python/basics/core_loads_subsystems.py | 40 +++++++++++++++++ test/python/prelude.py | 18 +++++++- 6 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 test/python/basics/core_loads_subsystems.py diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 2f3d69b1..b3ca69c1 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -174,6 +174,8 @@ jobs: NDS_FIRMWARE: ${{ secrets.NDS_FIRMWARE }} DSI_FIRMWARE: ${{ secrets.DSI_FIRMWARE }} NDS_ROM: ${{ secrets.NDS_ROM }} + GBA_ROM: ${{ secrets.GBA_ROM }} + GBA_SRAM: ${{ secrets.GBA_SRAM }} create-release: name: Create Release diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 32cb19c9..191597f1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,6 +20,10 @@ on: required: true NDS_ROM: required: true + GBA_ROM: + required: true + GBA_SRAM: + required: true DSI_NAND_ARCHIVE: required: true DSI_NAND: @@ -94,6 +98,8 @@ jobs: -DDSI_FIRMWARE="${{ env.TESTFILE_DIR }}/${{ secrets.DSI_FIRMWARE }}" \ -DDSI_NAND="${{ env.TESTFILE_DIR }}/${{ secrets.DSI_NAND }}" \ -DNDS_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_ROM }}" \ + -DGBA_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_ROM }}" \ + -DGBA_SRAM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_SRAM }}" \ ${{ inputs.cmake-args }} - name: Run Test Suite (Linux) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 65dce738..428e8865 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,6 +22,20 @@ else() message(DEBUG "NDS_ROM: ${NDS_ROM}") endif() +if (NOT GBA_ROM) + message(WARNING "GBA_ROM must be set to the path of a GBA ROM") + set(GBA_ROM "GBA_ROM-NOTFOUND" CACHE FILEPATH "Path to a GBA ROM" FORCE) +else() + message(DEBUG "GBA_ROM: ${GBA_ROM}") +endif() + +if (NOT GBA_SRAM) + message(WARNING "GBA_SRAM must be set to the path of a GBA save file") + set(GBA_SRAM "GBA_SRAM-NOTFOUND" CACHE FILEPATH "Path to a GBA save file" FORCE) +else() + message(DEBUG "GBA_SRAM: ${GBA_SRAM}") +endif() + function(find_bios BIOS_NAME BIOS_FILE EXPECTED_BIOS_SIZES KNOWN_BIOS_HASHES) if (NOT ${BIOS_NAME}) find_file(${BIOS_NAME} ${BIOS_FILE} REQUIRED) @@ -88,6 +102,7 @@ function(add_python_test) set(oneValueArgs NAME SKIP_RETURN_CODE + SUBSYSTEM TEST_MODULE TIMEOUT ) @@ -103,23 +118,50 @@ function(add_python_test) ) cmake_parse_arguments(PARSE_ARGV 0 RETRO "${options}" "${oneValueArgs}" "${multiValueArgs}") + list(LENGTH RETRO_CONTENT RETRO_CONTENT_LENGTH) + if (RETRO_CONTENT_LENGTH GREATER 0) + list(GET RETRO_CONTENT 0 NDS_CONTENT) + endif() + + if (RETRO_CONTENT_LENGTH GREATER 1) + list(GET RETRO_CONTENT 1 GBA_CONTENT) + endif() + + if (RETRO_CONTENT_LENGTH GREATER 2) + list(GET RETRO_CONTENT 2 GBA_SRAM_CONTENT) + endif() + add_test( NAME "${RETRO_NAME}" COMMAND ${Python_EXECUTABLE} -m "${RETRO_TEST_MODULE}" "$" - "${RETRO_CONTENT}" + "${NDS_CONTENT}" + "${GBA_CONTENT}" + "${GBA_SRAM_CONTENT}" ) list(APPEND REQUIRED_FILES "$") - if (RETRO_CONTENT) - list(APPEND REQUIRED_FILES "${RETRO_CONTENT}") + if (NDS_CONTENT) + list(APPEND REQUIRED_FILES "${NDS_CONTENT}") + endif() + + if (GBA_CONTENT) + list(APPEND REQUIRED_FILES "${GBA_CONTENT}") + endif() + + if (GBA_SRAM_CONTENT) + list(APPEND REQUIRED_FILES "${GBA_SRAM_CONTENT}") endif() if (NOT RETRO_NO_SKIP_ERROR_SCREEN) list(APPEND ENVIRONMENT MELONDSDS_SKIP_ERROR_SCREEN=1) endif() + if (RETRO_SUBSYSTEM) + list(APPEND ENVIRONMENT SUBSYSTEM=${RETRO_SUBSYSTEM}) + endif() + list(APPEND ENVIRONMENT ${RETRO_CORE_OPTION}) # Not an omission, this is already a list macro(expose_system_file SYSFILE) diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 29ce396e..0be8447d 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -230,9 +230,12 @@ add_python_test( add_python_test( NAME "Core loads and unloads with subsystem content" - TEST_MODULE "" - DISABLED -) # TODO: Implement this test + TEST_MODULE basics.core_loads_subsystems + SUBSYSTEM gba + CONTENT "${NDS_ROM}" + CONTENT "${GBA_ROM}" + CONTENT "${GBA_SRAM}" +) add_python_test( NAME "Core defines controller info" diff --git a/test/python/basics/core_loads_subsystems.py b/test/python/basics/core_loads_subsystems.py new file mode 100644 index 00000000..dca07ffc --- /dev/null +++ b/test/python/basics/core_loads_subsystems.py @@ -0,0 +1,40 @@ +from ctypes import CFUNCTYPE, c_bool, c_size_t, c_uint8, c_size_t, POINTER + +from libretro import Session + +import prelude + +assert prelude.subsystem is not None + +session: Session +with prelude.session() as session: + subsystems = session.subsystems + + assert subsystems is not None + subsystem: bytes = prelude.subsystem.encode() + idents = [bytes(s.ident) for s in subsystems] + assert subsystem in idents, f"Subsystem {subsystem} not found in {idents}" + + proc_address_callback = session.proc_address_callback + assert proc_address_callback is not None + assert proc_address_callback.get_proc_address is not None + + gba_rom_length = session.get_proc_address(b"melondsds_gba_rom_length", CFUNCTYPE(c_size_t)) + assert gba_rom_length is not None, "Core needs to define melondsds_gba_rom_length" + assert gba_rom_length() > 0, "GBA ROM not installed" + + gba_rom = session.get_proc_address(b"melondsds_gba_rom", CFUNCTYPE(POINTER(c_uint8))) + assert gba_rom is not None, "Core needs to define melondsds_gba_rom" + + rom = gba_rom() + assert rom, "GBA ROM not loaded" + + gba_sram_length = session.get_proc_address(b"melondsds_gba_sram_length", CFUNCTYPE(c_size_t)) + assert gba_sram_length is not None, "Core needs to define melondsds_gba_sram_length" + assert gba_sram_length() > 0, "GBA SRAM not installed" + + gba_sram = session.get_proc_address(b"melondsds_gba_sram", CFUNCTYPE(POINTER(c_uint8))) + assert gba_sram is not None, "Core needs to define melondsds_gba_sram" + + sram = gba_sram() + assert sram, "GBA SRAM not loaded" diff --git a/test/python/prelude.py b/test/python/prelude.py index 01971de7..c04c67b1 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -4,6 +4,7 @@ import tempfile import libretro +from libretro.api.content import SubsystemContent if not __debug__: raise RuntimeError("The melonDS DS test suite should not be run with -O") @@ -37,8 +38,10 @@ shutil.copyfile(os.environ[_f], targetpath) options_string = os.getenv("RETRO_CORE_OPTIONS") +subsystem = os.getenv("SUBSYSTEM") core_path = sys.argv[1] -content_path = sys.argv[2] if len(sys.argv) > 2 and len(sys.argv[2]) > 0 else None +content_path = sys.argv[2] if len(sys.argv) > 2 and sys.argv[2] else None +content_paths = tuple(s for s in sys.argv[2:] if s) if len(sys.argv) > 2 else () options = { k.lower().encode(): v.encode() @@ -53,7 +56,18 @@ def session(**kwargs) -> libretro.Session: - return libretro.default_session(core_path, content_path, **(default_args | kwargs)) + content = None + match content_paths: + case [] | None: + pass + case [path]: + content = path + case [*paths]: + content = SubsystemContent(subsystem, paths) + case _: + raise TypeError(f"Unexpected content_paths {type(content_paths).__name__}") + + return libretro.default_session(core_path, content, **(default_args | kwargs)) def noload_session(**kwargs) -> libretro.Session: From 5969b082c56d7fa214185b7bc696336741798c0e Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 26 Mar 2024 15:43:53 -0400 Subject: [PATCH 109/234] Fix an incorrect if() --- test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 428e8865..89189b46 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,8 +10,8 @@ if (PythonModules_libretro_PATH) message(STATUS "Found libretro.py: ${PythonModules_libretro_PATH}") endif() -if (PythonModules_wand_PATH) - message(STATUS "Found wand: ${PythonModules_wand_PATH}") +if (PythonModules_PIL_PATH) + message(STATUS "Found PIL (aka Pillow): ${PythonModules_PIL_PATH}") endif() ## TODO: Write a requirements.txt for all test dependencies From 2e3e95291b8be5e307e8790cade161b0a57c12b2 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 7 Apr 2024 21:04:08 -0400 Subject: [PATCH 110/234] Fix several tests --- test/CMakeLists.txt | 2 +- test/cmake/Basics.cmake | 1 + .../basics/core_accepts_button_input.py | 19 +++++------- .../basics/core_accepts_microphone_input.py | 20 +++--------- .../basics/core_accepts_pointer_input.py | 10 +++--- test/python/basics/core_can_shut_down.py | 9 ++---- .../core_defines_content_info_overrides.py | 2 +- .../basics/core_defines_controller_info.py | 5 --- test/python/basics/core_defines_options_v0.py | 5 ++- test/python/basics/core_defines_options_v1.py | 8 ++--- test/python/basics/core_defines_subsystems.py | 8 +---- test/python/basics/core_generates_audio.py | 5 ++- test/python/basics/core_generates_video.py | 11 +++---- .../basics/core_gets_options_version.py | 6 +--- test/python/basics/core_gets_power_state.py | 2 +- test/python/basics/core_gets_vfs_interface.py | 9 ++---- test/python/basics/core_logs_output.py | 7 ++--- test/python/basics/core_sends_messages_v0.py | 10 +++--- test/python/basics/core_sends_messages_v1.py | 7 ++--- test/python/basics/core_sets_geometry.py | 15 ++++----- test/python/basics/core_sets_pixel_format.py | 3 +- .../native_bios_not_loaded_with_freebios.py | 4 +-- test/python/prelude.py | 31 ++++++++++++++++--- test/python/reset/no_hang_on_reboot.py | 12 +++---- 24 files changed, 89 insertions(+), 122 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 89189b46..751b8cd4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -251,7 +251,7 @@ function(add_python_test) if (RETRO_TIMEOUT) set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT "${RETRO_TIMEOUT}") else() - set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT 10) + set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT 30) endif() endfunction() diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 0be8447d..e38d7008 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -53,6 +53,7 @@ add_python_test( NAME "Core runs for multiple frames" TEST_MODULE basics.core_run_frames CONTENT "${NDS_ROM}" + TIMEOUT 30 ) add_python_test( diff --git a/test/python/basics/core_accepts_button_input.py b/test/python/basics/core_accepts_button_input.py index 76f454ed..3af1ed6a 100644 --- a/test/python/basics/core_accepts_button_input.py +++ b/test/python/basics/core_accepts_button_input.py @@ -1,32 +1,27 @@ -import itertools +from itertools import repeat from typing import cast -from libretro import Session -from libretro.api.input import JoypadState -from libretro.api.video import PillowVideoDriver +from libretro import JoypadState, PillowVideoDriver import prelude def generate_input(): - yield from itertools.repeat(0, 240) - + yield from repeat(0, 240) yield JoypadState(a=True) - - yield from itertools.repeat(0) + yield from repeat(0) -session: Session -with prelude.session(input_state=generate_input) as session: +with prelude.builder().with_video(PillowVideoDriver).with_input(generate_input).build() as session: video = cast(PillowVideoDriver, session.video) for i in range(240): session.core.run() - frame1 = video.get_frame() + frame1 = video.frame for i in range(240): session.core.run() - frame2 = video.get_frame() + frame2 = video.frame # The logo screen (frame1) has a white pixel in the top left corner, # whereas the main menu screen doesn't diff --git a/test/python/basics/core_accepts_microphone_input.py b/test/python/basics/core_accepts_microphone_input.py index f5d0209a..782e2ed6 100644 --- a/test/python/basics/core_accepts_microphone_input.py +++ b/test/python/basics/core_accepts_microphone_input.py @@ -3,22 +3,16 @@ from math import sin from typing import cast -from libretro import Session -from libretro.api import ArrayAudioState -from libretro.api.input import JoypadState +from libretro import Session, ArrayAudioDriver, JoypadState import prelude def generate_input() -> Iterator[int]: yield from repeat(None, 6) - yield JoypadState(a=True) - yield from repeat(None, 90) - yield JoypadState(a=True) - yield from repeat(None) @@ -27,17 +21,13 @@ def generate_sine_wave() -> Iterator[int]: yield int(sin(i * 440) * 30000) -kwargs = { - "input_state": generate_input, - "mic_interface": generate_sine_wave, - "options": { - "melonds_mic_input_active": "always" - } +options = { + "melonds_mic_input_active": "always" } session: Session -with prelude.session(**kwargs) as session: - audio = cast(ArrayAudioState, session.audio) +with prelude.builder().with_input(generate_input).with_mic(generate_sine_wave).with_options(options).build() as session: + audio = cast(ArrayAudioDriver, session.audio) for i in range(300): session.core.run() diff --git a/test/python/basics/core_accepts_pointer_input.py b/test/python/basics/core_accepts_pointer_input.py index d1706e7b..9e97fee2 100644 --- a/test/python/basics/core_accepts_pointer_input.py +++ b/test/python/basics/core_accepts_pointer_input.py @@ -2,9 +2,7 @@ from typing import cast from math import sin, cos, pi -from libretro import Session -from libretro.api.input import Point, Pointer -from libretro.api.video import PillowVideoDriver +from libretro import Session, Point, Pointer, PillowVideoDriver import prelude @@ -30,17 +28,17 @@ def generate_input(): session: Session -with prelude.session(input_state=generate_input) as session: +with prelude.builder().with_input(generate_input).with_video(PillowVideoDriver).build() as session: video = cast(PillowVideoDriver, session.video) for i in range(180): session.core.run() - frame1 = video.get_frame() + frame1 = video.frame for i in range(240): session.core.run() - frame2 = video.get_frame() + frame2 = video.frame # The logo screen (frame1) has a white pixel in the top left corner, # whereas the main menu screen doesn't diff --git a/test/python/basics/core_can_shut_down.py b/test/python/basics/core_can_shut_down.py index 1b70c90a..5434ae2f 100644 --- a/test/python/basics/core_can_shut_down.py +++ b/test/python/basics/core_can_shut_down.py @@ -1,11 +1,8 @@ import itertools -from typing import cast -from libretro import Session, CoreShutDownException -from libretro.api.input import DeviceIdJoypad +from libretro import DeviceIdJoypad, CoreShutDownException import prelude -from libretro.api.video import PillowVideoDriver def generate_input(): @@ -33,9 +30,7 @@ def generate_input(): yield from itertools.repeat(None) -session: Session -with prelude.session(input_state=generate_input) as session: - video = cast(PillowVideoDriver, session.video) +with prelude.builder().with_input(generate_input).build() as session: for i in range(600): session.core.run() diff --git a/test/python/basics/core_defines_content_info_overrides.py b/test/python/basics/core_defines_content_info_overrides.py index c98a14b3..b75d3732 100644 --- a/test/python/basics/core_defines_content_info_overrides.py +++ b/test/python/basics/core_defines_content_info_overrides.py @@ -4,7 +4,7 @@ import prelude session: libretro.Session -with prelude.noload_session() as session: +with prelude.session() as session: overrides = session.content_info_overrides pprint(overrides) diff --git a/test/python/basics/core_defines_controller_info.py b/test/python/basics/core_defines_controller_info.py index 08c93362..396c6faf 100644 --- a/test/python/basics/core_defines_controller_info.py +++ b/test/python/basics/core_defines_controller_info.py @@ -1,12 +1,7 @@ -from collections.abc import Sequence from pprint import pprint -from libretro import Session -from libretro.api.input.info import retro_controller_info import prelude -info: Sequence[retro_controller_info] -session: Session with prelude.session() as session: info = session.controller_info diff --git a/test/python/basics/core_defines_options_v0.py b/test/python/basics/core_defines_options_v0.py index c2a95d10..97ae7e20 100644 --- a/test/python/basics/core_defines_options_v0.py +++ b/test/python/basics/core_defines_options_v0.py @@ -1,13 +1,12 @@ from collections.abc import Mapping -from libretro import Session -from libretro.api.options import retro_core_option_v2_definition, DictOptionDriver +from libretro import Session, retro_core_option_v2_definition, DictOptionDriver import prelude definitions: Mapping[bytes, retro_core_option_v2_definition] session: Session -with prelude.session(options=DictOptionDriver(version=0)) as session: +with prelude.builder().with_options(0).build() as session: assert session.options is not None assert session.options.version == 0 diff --git a/test/python/basics/core_defines_options_v1.py b/test/python/basics/core_defines_options_v1.py index 9dc55e4d..edbe7120 100644 --- a/test/python/basics/core_defines_options_v1.py +++ b/test/python/basics/core_defines_options_v1.py @@ -1,12 +1,8 @@ -from collections.abc import Mapping -from libretro import Session -from libretro.api.options import retro_core_option_v2_definition, DictOptionDriver +from libretro import DictOptionDriver import prelude -definitions: Mapping[bytes, retro_core_option_v2_definition] -session: Session -with prelude.session(options=DictOptionDriver(version=1)) as session: +with prelude.builder().with_options(DictOptionDriver(version=1)).build() as session: assert session.options is not None assert session.options.version == 1 diff --git a/test/python/basics/core_defines_subsystems.py b/test/python/basics/core_defines_subsystems.py index 44f000da..18a0046f 100644 --- a/test/python/basics/core_defines_subsystems.py +++ b/test/python/basics/core_defines_subsystems.py @@ -1,14 +1,8 @@ -from collections.abc import Sequence from pprint import pprint -from libretro import Session -from libretro.api.content import retro_subsystem_info - import prelude -subsystems: Sequence[retro_subsystem_info] -session: Session -with prelude.noload_session() as session: +with prelude.session() as session: subsystems = session.subsystems assert subsystems is not None diff --git a/test/python/basics/core_generates_audio.py b/test/python/basics/core_generates_audio.py index 3b024212..b2323e9e 100644 --- a/test/python/basics/core_generates_audio.py +++ b/test/python/basics/core_generates_audio.py @@ -1,12 +1,11 @@ from typing import cast -from libretro import Session -from libretro.api import ArrayAudioState +from libretro import Session, ArrayAudioDriver import prelude session: Session with prelude.session() as session: - audio = cast(ArrayAudioState, session.audio) + audio = cast(ArrayAudioDriver, session.audio) for i in range(300): session.core.run() diff --git a/test/python/basics/core_generates_video.py b/test/python/basics/core_generates_video.py index d5bb1887..7cd2e192 100644 --- a/test/python/basics/core_generates_video.py +++ b/test/python/basics/core_generates_video.py @@ -1,26 +1,25 @@ from typing import cast -from libretro import Session -from libretro.api.video import PillowVideoDriver +from libretro import Session, PillowVideoDriver import prelude session: Session -with prelude.session() as session: +with prelude.builder().with_video(PillowVideoDriver).build() as session: video = cast(PillowVideoDriver, session.video) - assert isinstance(video, PillowVideoDriver) + assert isinstance(video, PillowVideoDriver), f"Expected PillowVideoDriver, got {type(video).__name__}" for i in range(70): session.core.run() - frame1 = video.get_frame() + frame1 = video.frame assert frame1 is not None for i in range(70): session.core.run() - frame2 = video.get_frame() + frame2 = video.frame assert frame2 is not None assert frame1.size == frame2.size diff --git a/test/python/basics/core_gets_options_version.py b/test/python/basics/core_gets_options_version.py index e69cd22b..bc0621e9 100644 --- a/test/python/basics/core_gets_options_version.py +++ b/test/python/basics/core_gets_options_version.py @@ -1,12 +1,8 @@ from ctypes import * -from libretro import Session -from libretro.api.options import DictOptionDriver - import prelude -session: Session -with prelude.session(options=DictOptionDriver(1)) as session: +with prelude.builder().with_options(1).build() as session: get_options_version = session.get_proc_address(b"libretropy_get_options_version", CFUNCTYPE(c_uint)) assert get_options_version is not None diff --git a/test/python/basics/core_gets_power_state.py b/test/python/basics/core_gets_power_state.py index 6a9f41b8..e114b4d1 100644 --- a/test/python/basics/core_gets_power_state.py +++ b/test/python/basics/core_gets_power_state.py @@ -7,7 +7,7 @@ power = retro_device_power(PowerState.DISCHARGING, 3540, 52) session: Session -with prelude.session(device_power=power) as session: +with prelude.builder().with_power(power).build() as session: get_power = session.get_proc_address(b"libretropy_get_power", CFUNCTYPE(bool, POINTER(retro_device_power))) assert get_power is not None diff --git a/test/python/basics/core_gets_vfs_interface.py b/test/python/basics/core_gets_vfs_interface.py index 7716d457..b84d3977 100644 --- a/test/python/basics/core_gets_vfs_interface.py +++ b/test/python/basics/core_gets_vfs_interface.py @@ -1,12 +1,9 @@ -from libretro import Session - import prelude -from libretro.api.vfs import StandardFileSystemInterface -from libretro.api.vfs.history import HistoryFileSystemInterface +from libretro import StandardFileSystemInterface, HistoryFileSystemInterface vfs = HistoryFileSystemInterface(StandardFileSystemInterface()) -session: Session -with prelude.session(vfs=vfs) as session: + +with prelude.builder().with_vfs(vfs).build() as session: assert session.vfs is vfs, f"Expected the session to use the vfs interface we provided, found {session.vfs} instead" assert vfs.history is not None, "Expected the vfs interface to have a history, found None instead" assert len(vfs.history) > 0, "Expected the vfs interface to have a non-empty history" diff --git a/test/python/basics/core_logs_output.py b/test/python/basics/core_logs_output.py index 33e66196..c4e6b6c3 100644 --- a/test/python/basics/core_logs_output.py +++ b/test/python/basics/core_logs_output.py @@ -1,12 +1,11 @@ from typing import cast -from libretro import Session -from libretro.api.log import UnformattedLogger +from libretro import Session, UnformattedLogDriver import prelude session: Session with prelude.session() as session: - log: UnformattedLogger = cast(UnformattedLogger, session.log) + log: UnformattedLogDriver = cast(UnformattedLogDriver, session.log) assert log is not None assert log.records is not None - assert len(log.records) > 0 \ No newline at end of file + assert len(log.records) > 0 diff --git a/test/python/basics/core_sends_messages_v0.py b/test/python/basics/core_sends_messages_v0.py index b17e570c..ad1a70bc 100644 --- a/test/python/basics/core_sends_messages_v0.py +++ b/test/python/basics/core_sends_messages_v0.py @@ -2,16 +2,14 @@ import typing from ctypes import * -from libretro import Session - import prelude -from libretro.api.message import LoggerMessageInterface +from libretro import LoggerMessageInterface -session: Session -with prelude.session(message=LoggerMessageInterface(0, logging.getLogger('libretro'))) as session: +driver = LoggerMessageInterface(0, logging.getLogger('libretro')) +with prelude.builder().with_message(driver).build() as session: message = typing.cast(LoggerMessageInterface, session.message) assert message is not None - assert message.version == 0 + assert message.version == 0, f"Expected version 0, got {message.version}" send_message = session.get_proc_address(b"libretropy_send_message", CFUNCTYPE(c_bool, c_char_p)) assert send_message is not None diff --git a/test/python/basics/core_sends_messages_v1.py b/test/python/basics/core_sends_messages_v1.py index 735919e5..ef5207dc 100644 --- a/test/python/basics/core_sends_messages_v1.py +++ b/test/python/basics/core_sends_messages_v1.py @@ -2,13 +2,12 @@ import typing from ctypes import * -from libretro import Session +from libretro import LoggerMessageInterface import prelude -from libretro.api.message import LoggerMessageInterface -session: Session -with prelude.session(message=LoggerMessageInterface(1, logging.getLogger('libretro'))) as session: +driver = LoggerMessageInterface(1, logging.getLogger('libretro')) +with prelude.builder().with_message(driver).build() as session: message = typing.cast(LoggerMessageInterface, session.message) assert message is not None assert message.version == 1 diff --git a/test/python/basics/core_sets_geometry.py b/test/python/basics/core_sets_geometry.py index 4e113fc2..84281d3a 100644 --- a/test/python/basics/core_sets_geometry.py +++ b/test/python/basics/core_sets_geometry.py @@ -1,9 +1,7 @@ import itertools from typing import cast -from libretro import Session -from libretro.api.input import JoypadState -from libretro.api.video import PillowVideoDriver +from libretro import JoypadState, PillowVideoDriver import prelude @@ -24,14 +22,13 @@ def generate_input(): yield from itertools.repeat(None) -session: Session -with prelude.session(input_state=generate_input) as session: +with prelude.builder().with_input(generate_input).with_video(PillowVideoDriver).build() as session: video = cast(PillowVideoDriver, session.video) for i in range(10): session.core.run() - frame1 = video.get_frame() - framebuffer1 = video.get_frame_max() + frame1 = video.frame + framebuffer1 = video.frame_max geometry1 = video.geometry assert frame1 is not None @@ -64,8 +61,8 @@ def generate_input(): # Now that we've changed the screen layout, let's make sure we changed the geometry - frame2 = video.get_frame() - framebuffer2 = video.get_frame_max() + frame2 = video.frame + framebuffer2 = video.frame_max geometry2 = video.geometry assert frame2 is not None diff --git a/test/python/basics/core_sets_pixel_format.py b/test/python/basics/core_sets_pixel_format.py index c4a4f124..0a4cc226 100644 --- a/test/python/basics/core_sets_pixel_format.py +++ b/test/python/basics/core_sets_pixel_format.py @@ -1,5 +1,4 @@ -from libretro import default_session, Session -from libretro.api.video import PixelFormat, PillowVideoDriver +from libretro import Session, PixelFormat, PillowVideoDriver import prelude diff --git a/test/python/firmware/native_bios_not_loaded_with_freebios.py b/test/python/firmware/native_bios_not_loaded_with_freebios.py index 705427d4..5f6cba55 100644 --- a/test/python/firmware/native_bios_not_loaded_with_freebios.py +++ b/test/python/firmware/native_bios_not_loaded_with_freebios.py @@ -1,11 +1,11 @@ from libretro import Session -from libretro.api.vfs import StandardFileSystemInterface, HistoryFileSystemInterface, VfsOperation, VfsOperationType +from libretro import StandardFileSystemInterface, HistoryFileSystemInterface, VfsOperation, VfsOperationType import prelude vfs = HistoryFileSystemInterface(StandardFileSystemInterface()) session: Session -with prelude.session(vfs=vfs) as session: +with prelude.builder().with_vfs(vfs).build() as session: for i in range(300): session.core.run() diff --git a/test/python/prelude.py b/test/python/prelude.py index c04c67b1..16a2d2d9 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -4,7 +4,10 @@ import tempfile import libretro -from libretro.api.content import SubsystemContent +from libretro import SubsystemContent, SessionBuilder +from libretro.driver.path import DefaultPathDriver +from libretro.driver.video import PillowVideoDriver +from libretro.builder import SessionBuilder if not __debug__: raise RuntimeError("The melonDS DS test suite should not be run with -O") @@ -12,6 +15,8 @@ SYSTEM_FILES = ("ARM7_BIOS", "ARM9_BIOS", "ARM7_DSI_BIOS", "ARM9_DSI_BIOS", "NDS_FIRMWARE", "DSI_FIRMWARE", "DSI_NAND") testdir = tempfile.TemporaryDirectory(".libretro") system_dir = os.path.join(testdir.name, "system") +assets_dir = os.path.join(testdir.name, "assets") +playlist_dir = os.path.join(testdir.name, "playlist") save_dir = os.path.join(testdir.name, "savefiles") save_directory = save_dir savestate_directory = os.path.join(testdir.name, "states") @@ -55,7 +60,7 @@ } -def session(**kwargs) -> libretro.Session: +def builder(**kwargs) -> SessionBuilder: content = None match content_paths: case [] | None: @@ -67,8 +72,26 @@ def session(**kwargs) -> libretro.Session: case _: raise TypeError(f"Unexpected content_paths {type(content_paths).__name__}") - return libretro.default_session(core_path, content, **(default_args | kwargs)) + return ( + libretro + .defaults(core_path) + .with_content(content) + .with_paths( + DefaultPathDriver( + corepath=core_path, + system=system_dir, + assets=assets_dir, + save=save_dir, + playlist=playlist_dir, + ) + ) + .with_options(options) + ) + + +def session(**kwargs) -> libretro.Session: + return builder(**kwargs).build() def noload_session(**kwargs) -> libretro.Session: - return libretro.default_session(core_path, libretro.session.DoNotLoad, **(default_args | kwargs)) \ No newline at end of file + return builder(**kwargs).with_content_driver(None).build() \ No newline at end of file diff --git a/test/python/reset/no_hang_on_reboot.py b/test/python/reset/no_hang_on_reboot.py index 3dd10e82..dd143f7f 100644 --- a/test/python/reset/no_hang_on_reboot.py +++ b/test/python/reset/no_hang_on_reboot.py @@ -1,8 +1,6 @@ -from array import array from typing import cast -from libretro import Session -from libretro.api.video import PillowVideoDriver +from libretro import Session, PillowVideoDriver import prelude @@ -13,20 +11,20 @@ WHITE = (0xFF, 0xFF, 0xFF, 0xFF) session: Session -with prelude.session(options=options) as session: +with prelude.builder().with_options(options).with_video(PillowVideoDriver).build() as session: session.core.run() video = cast(PillowVideoDriver, session.video) # Very first frame should be all white - blank_frame = video.get_frame() + blank_frame = video.frame blank_colors = blank_frame.getcolors() assert blank_colors is not None and len(blank_colors) == 1, f"Expected an all-white frame, got {blank_colors}" for i in range(300): session.core.run() - after_frame = video.get_frame() + after_frame = video.frame assert blank_frame != after_frame, "Screen is still blank after 300 frames" session.core.reset() @@ -34,5 +32,5 @@ for i in range(300): session.core.run() - after_reset_frame = video.get_frame() + after_reset_frame = video.frame assert blank_frame != after_reset_frame, "Screen is still blank after resetting and running for 300 frames" From be0a42e173da62355f7d1b7e0be2ad50c39d142b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 8 Apr 2024 11:28:55 -0400 Subject: [PATCH 111/234] Revert the default test timeout to 10 seconds --- test/CMakeLists.txt | 2 +- test/cmake/Basics.cmake | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 751b8cd4..89189b46 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -251,7 +251,7 @@ function(add_python_test) if (RETRO_TIMEOUT) set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT "${RETRO_TIMEOUT}") else() - set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT 30) + set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT 10) endif() endfunction() diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index e38d7008..0be8447d 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -53,7 +53,6 @@ add_python_test( NAME "Core runs for multiple frames" TEST_MODULE basics.core_run_frames CONTENT "${NDS_ROM}" - TIMEOUT 30 ) add_python_test( From de155da93411ad4801b10ad0e47d11023c2894dd Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 8 Apr 2024 16:01:56 -0400 Subject: [PATCH 112/234] Fix GBA SRAM --- CHANGELOG.md | 1 + src/libretro/core/core.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cf875a9..430529de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project roughly adheres to [Semantic Versioning](https://semver.org/spe - Fixed a bug where native BIOS images would be used when the core was supposed to fall back to built-in system files. +- Fixed a bug where GBA SRAM wouldn't be loaded. ## [1.1.1] - 2024-02-29 diff --git a/src/libretro/core/core.cpp b/src/libretro/core/core.cpp index 8087621d..568e4beb 100644 --- a/src/libretro/core/core.cpp +++ b/src/libretro/core/core.cpp @@ -667,7 +667,7 @@ void MelonDsDs::CoreState::InitContent(unsigned type, std::span 2 && game[2].path != nullptr && game[2].size > 0) { + if (game.size() > 2 && game[2].path != nullptr) { // If we got a GBA SRAM file... _gbaSaveInfo = game[2]; } From a615eff0e4b3bb584645b1fb023b62f55d7f3202 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 1 May 2024 18:16:39 -0400 Subject: [PATCH 113/234] Log some OpenGL info --- src/libretro/config/types.hpp | 2 ++ src/libretro/format.cpp | 54 ++++++++++++++++++++++++++++++++++ src/libretro/format.hpp | 5 ++++ src/libretro/render/opengl.cpp | 10 +++++++ 4 files changed, 71 insertions(+) diff --git a/src/libretro/config/types.hpp b/src/libretro/config/types.hpp index e575a298..5f4132a6 100644 --- a/src/libretro/config/types.hpp +++ b/src/libretro/config/types.hpp @@ -156,6 +156,8 @@ namespace MelonDsDs { Relative, Absolute, }; + + enum class FormattedGLEnum {}; } #endif // MELONDSDS_CONFIG_TYPES_HPP diff --git a/src/libretro/format.cpp b/src/libretro/format.cpp index 3f29cfd1..398a8a16 100644 --- a/src/libretro/format.cpp +++ b/src/libretro/format.cpp @@ -15,11 +15,65 @@ */ #include "format.hpp" +#if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) +#include "PlatformOGLPrivate.h" +#endif using namespace melonDS; using FirmwareConsoleType = Firmware::FirmwareConsoleType; using DSi_NAND::ConsoleRegion; +#if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) +auto fmt::formatter::format(MelonDsDs::FormattedGLEnum e, format_context& ctx) const -> decltype(ctx.out()) { + string_view name = ""; + switch ((int)e) { + case GL_NO_ERROR: + name = "GL_NO_ERROR"; + break; + case GL_INVALID_VALUE: + name = "GL_INVALID_VALUE"; + break; + case GL_INVALID_OPERATION: + name = "GL_INVALID_OPERATION"; + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + name = "GL_INVALID_FRAMEBUFFER_OPERATION"; + break; + case GL_OUT_OF_MEMORY: + name = "GL_OUT_OF_MEMORY"; + break; + case GL_INVALID_ENUM: + name = "GL_INVALID_ENUM"; + break; + case GL_FRAMEBUFFER_COMPLETE: + name = "GL_FRAMEBUFFER_COMPLETE"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + name = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + name = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + name = "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + name = "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"; + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + name = "GL_FRAMEBUFFER_UNSUPPORTED"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + name = "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: + name = "GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS"; + break; + } + return formatter::format(name, ctx); +} +#endif + auto fmt::formatter::format(MelonDsDs::BiosType c, format_context& ctx) const -> decltype(ctx.out()) { string_view name = "unknown"; switch (c) { diff --git a/src/libretro/format.hpp b/src/libretro/format.hpp index c33a28a1..5b4799eb 100644 --- a/src/libretro/format.hpp +++ b/src/libretro/format.hpp @@ -36,6 +36,11 @@ #include "config/config.hpp" namespace fmt { + template<> + struct formatter : formatter { + auto format(MelonDsDs::FormattedGLEnum e, format_context& ctx) const -> decltype(ctx.out()); + }; + template<> struct formatter : formatter { auto format(MelonDsDs::BiosType c, format_context& ctx) const -> decltype(ctx.out()); diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index 113a8dbb..8ea6a5be 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -30,6 +30,7 @@ #include "../core/core.hpp" #include "exceptions.hpp" +#include "format.hpp" #include "screenlayout.hpp" #include "tracy.hpp" @@ -168,6 +169,9 @@ MelonDsDs::OpenGLRenderState::OpenGLRenderState() { retro_assert(hw_render.debug_context); #endif + uintptr_t framebuffer = hw_render.get_current_framebuffer(); + retro::debug("OpenGL context requested. Current framebuffer: {}", framebuffer); + gl_query_core_context_set(hw_render.context_type == RETRO_HW_CONTEXT_OPENGL_CORE); } @@ -204,6 +208,12 @@ void MelonDsDs::OpenGLRenderState::ContextReset(melonDS::NDS& nds, const CoreCon glsm_ctl(GLSM_CTL_STATE_CONTEXT_RESET, nullptr); TracyGpuContext; // Must be called AFTER the function pointers are bound! + uintptr_t fbo = glsm_get_current_framebuffer(); + retro::debug("Current OpenGL framebuffer: {}", fbo); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + retro::debug("Framebuffer status: {}", static_cast(status)); + // Initialize global OpenGL resources (e.g. VAOs) and get config info (e.g. limits) glsm_ctl(GLSM_CTL_STATE_SETUP, nullptr); From 12d600438a5433ccdd4ef54827c8c071ebc78db5 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 21 May 2024 10:14:44 -0400 Subject: [PATCH 114/234] Update the tests to match updates to the libretro.py API --- .../basics/core_accepts_microphone_input.py | 2 +- test/python/basics/core_can_shut_down.py | 4 +-- .../basics/core_defines_controller_info.py | 2 +- test/python/basics/core_defines_subsystems.py | 2 +- test/python/basics/core_generates_audio.py | 2 +- test/python/basics/core_generates_video.py | 4 +-- .../python/basics/core_gets_option_updates.py | 2 +- .../python/basics/core_gets_save_directory.py | 2 +- test/python/basics/core_loads_subsystems.py | 3 +- .../core_loads_unloads_without_content.py | 2 +- .../core_registers_achievement_support.py | 2 +- test/python/basics/core_resets.py | 9 ++--- test/python/basics/core_run_frame.py | 5 +-- test/python/basics/core_run_frames.py | 5 +-- .../basics/core_saves_and_loads_state.py | 7 ++-- test/python/basics/core_saves_state.py | 5 +-- .../python/firmware/core_saves_wfcsettings.py | 2 +- ...overwritten_when_switching_console_mode.py | 6 ++-- .../native_bios_not_loaded_with_freebios.py | 2 +- .../firmware/nds_firmware_not_overwritten.py | 2 +- test/python/opengl/__init__.py | 0 test/python/opengl/core_loads_unloads.py | 33 +++++++++++++++++++ test/python/prelude.py | 5 +-- test/python/reset/no_hang_on_reboot.py | 4 +-- test/python/save/homebrew_sd_card_exists.py | 2 +- .../save/homebrew_sd_card_sync_exists.py | 2 +- 26 files changed, 66 insertions(+), 50 deletions(-) create mode 100644 test/python/opengl/__init__.py create mode 100644 test/python/opengl/core_loads_unloads.py diff --git a/test/python/basics/core_accepts_microphone_input.py b/test/python/basics/core_accepts_microphone_input.py index 782e2ed6..1e56825f 100644 --- a/test/python/basics/core_accepts_microphone_input.py +++ b/test/python/basics/core_accepts_microphone_input.py @@ -29,7 +29,7 @@ def generate_sine_wave() -> Iterator[int]: with prelude.builder().with_input(generate_input).with_mic(generate_sine_wave).with_options(options).build() as session: audio = cast(ArrayAudioDriver, session.audio) for i in range(300): - session.core.run() + session.run() assert audio.buffer is not None assert len(audio.buffer) > 0 diff --git a/test/python/basics/core_can_shut_down.py b/test/python/basics/core_can_shut_down.py index 5434ae2f..4403c1f1 100644 --- a/test/python/basics/core_can_shut_down.py +++ b/test/python/basics/core_can_shut_down.py @@ -33,7 +33,7 @@ def generate_input(): with prelude.builder().with_input(generate_input).build() as session: for i in range(600): - session.core.run() + session.run() # The core should shut down at some point during the test assert False, "session.core should've raised CoreShutDownException and exited the runtime context" @@ -44,7 +44,7 @@ def generate_input(): assert session.is_exited try: - session.core.run() + session.run() except CoreShutDownException as e: pass diff --git a/test/python/basics/core_defines_controller_info.py b/test/python/basics/core_defines_controller_info.py index 396c6faf..22c94187 100644 --- a/test/python/basics/core_defines_controller_info.py +++ b/test/python/basics/core_defines_controller_info.py @@ -3,7 +3,7 @@ import prelude with prelude.session() as session: - info = session.controller_info + info = session.environment.controller_info assert info is not None assert len(info) > 0 diff --git a/test/python/basics/core_defines_subsystems.py b/test/python/basics/core_defines_subsystems.py index 18a0046f..ce1f2d41 100644 --- a/test/python/basics/core_defines_subsystems.py +++ b/test/python/basics/core_defines_subsystems.py @@ -3,7 +3,7 @@ import prelude with prelude.session() as session: - subsystems = session.subsystems + subsystems = session.environment.subsystems assert subsystems is not None assert len(subsystems) > 0 diff --git a/test/python/basics/core_generates_audio.py b/test/python/basics/core_generates_audio.py index b2323e9e..2c08d509 100644 --- a/test/python/basics/core_generates_audio.py +++ b/test/python/basics/core_generates_audio.py @@ -7,7 +7,7 @@ with prelude.session() as session: audio = cast(ArrayAudioDriver, session.audio) for i in range(300): - session.core.run() + session.run() assert audio.buffer is not None assert len(audio.buffer) > 0 diff --git a/test/python/basics/core_generates_video.py b/test/python/basics/core_generates_video.py index 7cd2e192..0dea0c65 100644 --- a/test/python/basics/core_generates_video.py +++ b/test/python/basics/core_generates_video.py @@ -13,13 +13,13 @@ for i in range(70): session.core.run() - frame1 = video.frame + frame1 = video.screenshot assert frame1 is not None for i in range(70): session.core.run() - frame2 = video.frame + frame2 = video.screenshot assert frame2 is not None assert frame1.size == frame2.size diff --git a/test/python/basics/core_gets_option_updates.py b/test/python/basics/core_gets_option_updates.py index 3fbf3904..5440ad80 100644 --- a/test/python/basics/core_gets_option_updates.py +++ b/test/python/basics/core_gets_option_updates.py @@ -13,7 +13,7 @@ assert session.options.variable_updated # Assert that setting an option in the frontend marks variables as updated - session.core.run() + session.run() assert not session.options.variable_updated # Assert that running the core clears the updated flag diff --git a/test/python/basics/core_gets_save_directory.py b/test/python/basics/core_gets_save_directory.py index 83b98d3c..3ddc511e 100644 --- a/test/python/basics/core_gets_save_directory.py +++ b/test/python/basics/core_gets_save_directory.py @@ -9,4 +9,4 @@ assert get_save_directory is not None save_directory = get_save_directory() - assert save_directory == session.save_dir + assert save_directory == session.environment.path.save_dir diff --git a/test/python/basics/core_loads_subsystems.py b/test/python/basics/core_loads_subsystems.py index dca07ffc..aad1d431 100644 --- a/test/python/basics/core_loads_subsystems.py +++ b/test/python/basics/core_loads_subsystems.py @@ -8,7 +8,7 @@ session: Session with prelude.session() as session: - subsystems = session.subsystems + subsystems = session.environment.subsystems assert subsystems is not None subsystem: bytes = prelude.subsystem.encode() @@ -29,6 +29,7 @@ rom = gba_rom() assert rom, "GBA ROM not loaded" + gba_sram_length = session.get_proc_address(b"melondsds_gba_sram_length", CFUNCTYPE(c_size_t)) assert gba_sram_length is not None, "Core needs to define melondsds_gba_sram_length" assert gba_sram_length() > 0, "GBA SRAM not installed" diff --git a/test/python/basics/core_loads_unloads_without_content.py b/test/python/basics/core_loads_unloads_without_content.py index fca2f919..612da74b 100644 --- a/test/python/basics/core_loads_unloads_without_content.py +++ b/test/python/basics/core_loads_unloads_without_content.py @@ -5,4 +5,4 @@ session: Session with prelude.session() as session: for i in range(10): - session.core.run() + session.run() diff --git a/test/python/basics/core_registers_achievement_support.py b/test/python/basics/core_registers_achievement_support.py index 86db6ea6..37c2c2d8 100644 --- a/test/python/basics/core_registers_achievement_support.py +++ b/test/python/basics/core_registers_achievement_support.py @@ -3,4 +3,4 @@ session: Session with prelude.session() as session: - assert session.support_achievements + assert session.environment.support_achievements diff --git a/test/python/basics/core_resets.py b/test/python/basics/core_resets.py index 1d0ed32f..78d09ef5 100644 --- a/test/python/basics/core_resets.py +++ b/test/python/basics/core_resets.py @@ -1,13 +1,10 @@ -from libretro import Session - import prelude -session: Session with prelude.session() as session: for i in range(60): - session.core.run() + session.run() - session.core.reset() + session.reset() for i in range(60): - session.core.run() + session.run() diff --git a/test/python/basics/core_run_frame.py b/test/python/basics/core_run_frame.py index 2c11e607..fefeadf8 100644 --- a/test/python/basics/core_run_frame.py +++ b/test/python/basics/core_run_frame.py @@ -1,7 +1,4 @@ -from libretro import Session - import prelude -session: Session with prelude.session() as session: - session.core.run() \ No newline at end of file + session.run() diff --git a/test/python/basics/core_run_frames.py b/test/python/basics/core_run_frames.py index 77332e10..cad9845c 100644 --- a/test/python/basics/core_run_frames.py +++ b/test/python/basics/core_run_frames.py @@ -1,8 +1,5 @@ -from libretro import Session - import prelude -session: Session with prelude.session() as session: for i in range(300): - session.core.run() + session.run() diff --git a/test/python/basics/core_saves_and_loads_state.py b/test/python/basics/core_saves_and_loads_state.py index 2bd75143..4ef3a436 100644 --- a/test/python/basics/core_saves_and_loads_state.py +++ b/test/python/basics/core_saves_and_loads_state.py @@ -1,11 +1,8 @@ -from libretro import Session - import prelude -session: Session with prelude.session() as session: for i in range(30): - session.core.run() + session.run() size = session.core.serialize_size() assert size > 0 @@ -17,7 +14,7 @@ assert any(buffer) for i in range(30): - session.core.run() + session.run() new_size = session.core.serialize_size() assert new_size == size diff --git a/test/python/basics/core_saves_state.py b/test/python/basics/core_saves_state.py index e8034589..19801825 100644 --- a/test/python/basics/core_saves_state.py +++ b/test/python/basics/core_saves_state.py @@ -1,11 +1,8 @@ -from libretro import Session - import prelude -session: Session with prelude.session() as session: for i in range(10): - session.core.run() + session.run() size = session.core.serialize_size() assert size > 0 diff --git a/test/python/firmware/core_saves_wfcsettings.py b/test/python/firmware/core_saves_wfcsettings.py index 75024ef2..513b6395 100644 --- a/test/python/firmware/core_saves_wfcsettings.py +++ b/test/python/firmware/core_saves_wfcsettings.py @@ -9,6 +9,6 @@ session: Session with prelude.session() as session: for i in range(300): - session.core.run() + session.run() assert os.access(prelude.wfcsettings_path, os.F_OK), f"{prelude.wfcsettings_path} should exist by now" diff --git a/test/python/firmware/firmware_not_overwritten_when_switching_console_mode.py b/test/python/firmware/firmware_not_overwritten_when_switching_console_mode.py index ba0c1a87..2bb71154 100644 --- a/test/python/firmware/firmware_not_overwritten_when_switching_console_mode.py +++ b/test/python/firmware/firmware_not_overwritten_when_switching_console_mode.py @@ -20,7 +20,7 @@ session: Session with prelude.session() as session: for i in range(30): - session.core.run() + session.run() match prelude.options[b'melonds_console_mode']: case 'ds' | b'ds': @@ -32,10 +32,10 @@ case _ as mode: raise ValueError(f"Unknown console mode {mode}") - session.core.reset() + session.reset() for i in range(30): - session.core.run() + session.run() nds_firmware_path_local = os.path.join(prelude.core_system_dir, nds_firmware_basename) dsi_firmware_path_local = os.path.join(prelude.core_system_dir, dsi_firmware_basename) diff --git a/test/python/firmware/native_bios_not_loaded_with_freebios.py b/test/python/firmware/native_bios_not_loaded_with_freebios.py index 5f6cba55..7aa07420 100644 --- a/test/python/firmware/native_bios_not_loaded_with_freebios.py +++ b/test/python/firmware/native_bios_not_loaded_with_freebios.py @@ -7,7 +7,7 @@ session: Session with prelude.builder().with_vfs(vfs).build() as session: for i in range(300): - session.core.run() + session.run() op: VfsOperation for op in filter(lambda f: f.operation == VfsOperationType.OPEN, vfs.history): diff --git a/test/python/firmware/nds_firmware_not_overwritten.py b/test/python/firmware/nds_firmware_not_overwritten.py index 7d442783..9bbe24af 100644 --- a/test/python/firmware/nds_firmware_not_overwritten.py +++ b/test/python/firmware/nds_firmware_not_overwritten.py @@ -14,7 +14,7 @@ session: Session with prelude.session() as session: for i in range(300): - session.core.run() + session.run() test_nds_firmware_size = os.stat(test_nds_firmware_path).st_size diff --git a/test/python/opengl/__init__.py b/test/python/opengl/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/python/opengl/core_loads_unloads.py b/test/python/opengl/core_loads_unloads.py new file mode 100644 index 00000000..b0b5f11d --- /dev/null +++ b/test/python/opengl/core_loads_unloads.py @@ -0,0 +1,33 @@ +from typing import cast +import time +from libretro import ModernGlVideoDriver + +from PIL import Image +import prelude + +def dd(): + return ModernGlVideoDriver(window="pyglet") + +with prelude.builder().with_video(ModernGlVideoDriver).build() as session: + video = cast(ModernGlVideoDriver, session.video) + + assert isinstance(video, ModernGlVideoDriver), f"Expected ModernGlVideoDriver, got {type(video).__name__}" + + for i in range(70): + session.run() + + frame1 = video.screenshot + assert frame1 is not None + + for i in range(360 * 2): + session.run() + + frame2 = video.screenshot + assert frame2 is not None + + geometry = video.geometry + size = (geometry.base_width, geometry.base_height) + image = Image.frombuffer("RGB", size, frame2) + #image.show() + assert len(frame1) == len(frame2) + assert frame1 != frame2 diff --git a/test/python/prelude.py b/test/python/prelude.py index 16a2d2d9..f06ded72 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -4,10 +4,7 @@ import tempfile import libretro -from libretro import SubsystemContent, SessionBuilder -from libretro.driver.path import DefaultPathDriver -from libretro.driver.video import PillowVideoDriver -from libretro.builder import SessionBuilder +from libretro import SubsystemContent, SessionBuilder, DefaultPathDriver, PillowVideoDriver if not __debug__: raise RuntimeError("The melonDS DS test suite should not be run with -O") diff --git a/test/python/reset/no_hang_on_reboot.py b/test/python/reset/no_hang_on_reboot.py index dd143f7f..1f106b95 100644 --- a/test/python/reset/no_hang_on_reboot.py +++ b/test/python/reset/no_hang_on_reboot.py @@ -12,7 +12,7 @@ session: Session with prelude.builder().with_options(options).with_video(PillowVideoDriver).build() as session: - session.core.run() + session.run() video = cast(PillowVideoDriver, session.video) @@ -22,7 +22,7 @@ assert blank_colors is not None and len(blank_colors) == 1, f"Expected an all-white frame, got {blank_colors}" for i in range(300): - session.core.run() + session.run() after_frame = video.frame assert blank_frame != after_frame, "Screen is still blank after 300 frames" diff --git a/test/python/save/homebrew_sd_card_exists.py b/test/python/save/homebrew_sd_card_exists.py index ab87394e..e1e9da73 100644 --- a/test/python/save/homebrew_sd_card_exists.py +++ b/test/python/save/homebrew_sd_card_exists.py @@ -7,7 +7,7 @@ session: Session with prelude.session() as session: - session.core.run() + session.run() dldi_sdcard_stat = os.stat(prelude.dldi_sd_card_path) diff --git a/test/python/save/homebrew_sd_card_sync_exists.py b/test/python/save/homebrew_sd_card_sync_exists.py index 7367c5dd..2ce2cc5a 100644 --- a/test/python/save/homebrew_sd_card_sync_exists.py +++ b/test/python/save/homebrew_sd_card_sync_exists.py @@ -7,7 +7,7 @@ session: Session with prelude.session() as session: - session.core.run() + session.run() dldi_sdcard_stat = os.stat(prelude.dldi_sd_card_sync_path) From c201d1b0c19b0ef1a2b0711264935c7f1480a6a2 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 21 May 2024 10:14:54 -0400 Subject: [PATCH 115/234] Add a symbol I'd missed --- src/libretro/format.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libretro/format.cpp b/src/libretro/format.cpp index 398a8a16..56b127f2 100644 --- a/src/libretro/format.cpp +++ b/src/libretro/format.cpp @@ -48,6 +48,9 @@ auto fmt::formatter::format(MelonDsDs::FormattedGLEn case GL_FRAMEBUFFER_COMPLETE: name = "GL_FRAMEBUFFER_COMPLETE"; break; + case GL_FRAMEBUFFER_UNDEFINED: + name = "GL_FRAMEBUFFER_UNDEFINED"; + break; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: name = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; break; From 178cfd989849df7e2321f356e8d0294877301cdd Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 21 May 2024 10:15:38 -0400 Subject: [PATCH 116/234] Check some OpenGL-related info --- src/libretro/render/opengl.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index 8ea6a5be..757d78d0 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -208,11 +208,21 @@ void MelonDsDs::OpenGLRenderState::ContextReset(melonDS::NDS& nds, const CoreCon glsm_ctl(GLSM_CTL_STATE_CONTEXT_RESET, nullptr); TracyGpuContext; // Must be called AFTER the function pointers are bound! + const char *vendor = (const char*)glGetString(GL_VENDOR); + const char *rendererName = (const char*)glGetString(GL_RENDERER); + const char *version = (const char*)glGetString(GL_VERSION); + + retro::info("OpenGL version: {}", version); + retro::info("OpenGL vendor: {}", vendor); + retro::info("OpenGL renderer: {}", rendererName); + uintptr_t fbo = glsm_get_current_framebuffer(); + retro_assert(glIsFramebuffer(fbo) == GL_TRUE); retro::debug("Current OpenGL framebuffer: {}", fbo); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); retro::debug("Framebuffer status: {}", static_cast(status)); + retro_assert(status == GL_FRAMEBUFFER_COMPLETE); // Initialize global OpenGL resources (e.g. VAOs) and get config info (e.g. limits) glsm_ctl(GLSM_CTL_STATE_SETUP, nullptr); @@ -257,6 +267,8 @@ void MelonDsDs::OpenGLRenderState::SetUpCoreOpenGlState(const CoreConfig& config retro::debug("OpenGL debugging extensions are available"); } + // TODO: Check gl_check_capability for GL_CAPS_VAO and GL_CAPS_FBO + if (!melonDS::OpenGL::BuildShaderProgram(embedded_melondsds_vertex_shader, embedded_melondsds_fragment_shader, shader.data(), SHADER_PROGRAM_NAME)) throw shader_compilation_failed_exception("Failed to compile melonDS DS shaders."); From e242226576715e67782bde024e9a26ce11d4edc6 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 22 May 2024 13:57:12 -0400 Subject: [PATCH 117/234] Update some test scripts to match libretro.py's changes --- .../basics/core_accepts_button_input.py | 17 ++++----- .../basics/core_accepts_pointer_input.py | 16 ++++---- test/python/basics/core_generates_video.py | 24 +++++------- test/python/basics/core_sets_geometry.py | 38 ++++--------------- test/python/basics/core_sets_pixel_format.py | 4 +- test/python/prelude.py | 2 +- test/python/reset/no_hang_on_reboot.py | 21 +++++----- 7 files changed, 45 insertions(+), 77 deletions(-) diff --git a/test/python/basics/core_accepts_button_input.py b/test/python/basics/core_accepts_button_input.py index 3af1ed6a..4c0daf5e 100644 --- a/test/python/basics/core_accepts_button_input.py +++ b/test/python/basics/core_accepts_button_input.py @@ -1,8 +1,6 @@ from itertools import repeat -from typing import cast - -from libretro import JoypadState, PillowVideoDriver +from libretro import JoypadState import prelude @@ -12,17 +10,16 @@ def generate_input(): yield from repeat(0) -with prelude.builder().with_video(PillowVideoDriver).with_input(generate_input).build() as session: - video = cast(PillowVideoDriver, session.video) +with prelude.builder().with_input(generate_input).build() as session: for i in range(240): - session.core.run() + session.run() - frame1 = video.frame + frame1 = session.video.screenshot() for i in range(240): - session.core.run() + session.run() - frame2 = video.frame + frame2 = session.video.screenshot() # The logo screen (frame1) has a white pixel in the top left corner, # whereas the main menu screen doesn't - assert frame1.getpixel((0, 0)) != frame2.getpixel((0, 0)) + assert frame1.data[0:4] != frame2.data[0:4] diff --git a/test/python/basics/core_accepts_pointer_input.py b/test/python/basics/core_accepts_pointer_input.py index 9e97fee2..fb0915ab 100644 --- a/test/python/basics/core_accepts_pointer_input.py +++ b/test/python/basics/core_accepts_pointer_input.py @@ -1,8 +1,7 @@ import itertools -from typing import cast from math import sin, cos, pi -from libretro import Session, Point, Pointer, PillowVideoDriver +from libretro import Session, Point, Pointer import prelude @@ -28,18 +27,17 @@ def generate_input(): session: Session -with prelude.builder().with_input(generate_input).with_video(PillowVideoDriver).build() as session: - video = cast(PillowVideoDriver, session.video) +with prelude.builder().with_input(generate_input).build() as session: for i in range(180): - session.core.run() + session.run() - frame1 = video.frame + frame1 = session.video.screenshot() for i in range(240): - session.core.run() + session.run() - frame2 = video.frame + frame2 = session.video.screenshot() # The logo screen (frame1) has a white pixel in the top left corner, # whereas the main menu screen doesn't - assert frame1.getpixel((0, 0)) != frame2.getpixel((0, 0)) + assert frame1.data[0:4] != frame2.data[0:4] diff --git a/test/python/basics/core_generates_video.py b/test/python/basics/core_generates_video.py index 0dea0c65..acd775db 100644 --- a/test/python/basics/core_generates_video.py +++ b/test/python/basics/core_generates_video.py @@ -1,26 +1,22 @@ from typing import cast -from libretro import Session, PillowVideoDriver +from libretro import Session, Screenshot import prelude session: Session -with prelude.builder().with_video(PillowVideoDriver).build() as session: - video = cast(PillowVideoDriver, session.video) - - assert isinstance(video, PillowVideoDriver), f"Expected PillowVideoDriver, got {type(video).__name__}" - +with prelude.builder().build() as session: for i in range(70): - session.core.run() + session.run() - frame1 = video.screenshot - assert frame1 is not None + frame1 = session.video.screenshot() + assert isinstance(frame1, Screenshot) for i in range(70): - session.core.run() + session.run() - frame2 = video.screenshot - assert frame2 is not None + frame2 = session.video.screenshot() + assert isinstance(frame2, Screenshot) - assert frame1.size == frame2.size - assert frame1 != frame2 + assert (frame1.width, frame1.height) == (frame2.width, frame2.height) + assert frame1.data != frame2.data diff --git a/test/python/basics/core_sets_geometry.py b/test/python/basics/core_sets_geometry.py index 84281d3a..7cdda251 100644 --- a/test/python/basics/core_sets_geometry.py +++ b/test/python/basics/core_sets_geometry.py @@ -1,7 +1,7 @@ import itertools from typing import cast -from libretro import JoypadState, PillowVideoDriver +from libretro import JoypadState import prelude @@ -22,53 +22,31 @@ def generate_input(): yield from itertools.repeat(None) -with prelude.builder().with_input(generate_input).with_video(PillowVideoDriver).build() as session: - video = cast(PillowVideoDriver, session.video) +with prelude.builder().with_input(generate_input).build() as session: for i in range(10): - session.core.run() + session.run() - frame1 = video.frame - framebuffer1 = video.frame_max - geometry1 = video.geometry + frame1 = session.video.screenshot() + geometry1 = session.video.geometry assert frame1 is not None - assert framebuffer1 is not None assert geometry1 is not None - assert frame1.size != framebuffer1.size, \ - f"Frame size should not be the same as framebuffer size ({frame1.size})" - - assert frame1.width < framebuffer1.width, \ - f"Frame width ({frame1.width}) shouldn't exceed framebuffer width ({framebuffer1.width})" - - assert frame1.height < framebuffer1.height, \ - f"Frame height ({frame1.height}) shouldn't exceed framebuffer height ({framebuffer1.height})" - assert frame1.width == geometry1.base_width, \ f"Frame width ({frame1.width}) should match geometry base width ({geometry1.base_width})" assert frame1.height == geometry1.base_height, \ f"Frame height ({frame1.height}) should match geometry base height ({geometry1.height})" - assert framebuffer1.width == geometry1.max_width, \ - f"Framebuffer width ({framebuffer1.width}) should match geometry max width ({geometry1.max_width})" - - assert framebuffer1.height == geometry1.max_height, \ - f"Framebuffer height ({framebuffer1.height}) should match geometry max height ({geometry1.max_height})" - for i in range(20): - session.core.run() + session.run() # Now that we've changed the screen layout, let's make sure we changed the geometry - frame2 = video.frame - framebuffer2 = video.frame_max - geometry2 = video.geometry + frame2 = session.video.screenshot() + geometry2 = session.video.geometry assert frame2 is not None - assert framebuffer2 is not None assert geometry2 is not None assert geometry1 != geometry2, f"Geometry should have changed from {geometry1} after switching screen layout" - assert framebuffer1.size == framebuffer2.size, \ - f"Framebuffer size changed from {framebuffer1.size} to {framebuffer2.size}, but it shouldn't have" diff --git a/test/python/basics/core_sets_pixel_format.py b/test/python/basics/core_sets_pixel_format.py index 0a4cc226..ecd04d36 100644 --- a/test/python/basics/core_sets_pixel_format.py +++ b/test/python/basics/core_sets_pixel_format.py @@ -1,8 +1,8 @@ -from libretro import Session, PixelFormat, PillowVideoDriver +from libretro import Session, PixelFormat, ArrayVideoDriver import prelude -video = PillowVideoDriver() +video = ArrayVideoDriver() assert video.pixel_format != PixelFormat.XRGB8888 diff --git a/test/python/prelude.py b/test/python/prelude.py index f06ded72..d28a7a35 100644 --- a/test/python/prelude.py +++ b/test/python/prelude.py @@ -4,7 +4,7 @@ import tempfile import libretro -from libretro import SubsystemContent, SessionBuilder, DefaultPathDriver, PillowVideoDriver +from libretro import SubsystemContent, SessionBuilder, DefaultPathDriver if not __debug__: raise RuntimeError("The melonDS DS test suite should not be run with -O") diff --git a/test/python/reset/no_hang_on_reboot.py b/test/python/reset/no_hang_on_reboot.py index 1f106b95..666c2fdd 100644 --- a/test/python/reset/no_hang_on_reboot.py +++ b/test/python/reset/no_hang_on_reboot.py @@ -1,6 +1,7 @@ -from typing import cast +import itertools -from libretro import Session, PillowVideoDriver +from libretro import Session +from PIL import Image import prelude @@ -8,23 +9,21 @@ b"melonds_show_cursor": b"disabled" } -WHITE = (0xFF, 0xFF, 0xFF, 0xFF) - session: Session -with prelude.builder().with_options(options).with_video(PillowVideoDriver).build() as session: +with prelude.builder().with_options(options).build() as session: session.run() - video = cast(PillowVideoDriver, session.video) - # Very first frame should be all white - blank_frame = video.frame - blank_colors = blank_frame.getcolors() + blank_frame = session.video.screenshot() + blank_framebuffer = blank_frame.data + + blank_colors = set(itertools.batched(blank_framebuffer, 4)) assert blank_colors is not None and len(blank_colors) == 1, f"Expected an all-white frame, got {blank_colors}" for i in range(300): session.run() - after_frame = video.frame + after_frame = session.video.screenshot() assert blank_frame != after_frame, "Screen is still blank after 300 frames" session.core.reset() @@ -32,5 +31,5 @@ for i in range(300): session.core.run() - after_reset_frame = video.frame + after_reset_frame = session.video.screenshot() assert blank_frame != after_reset_frame, "Screen is still blank after resetting and running for 300 frames" From 59cd2904eee9129e0d33c1fc1e770b0fe5696703 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 26 May 2024 17:42:41 -0400 Subject: [PATCH 118/234] Write a test script for analog controls --- src/libretro/core/core.hpp | 1 + src/libretro/core/test.cpp | 16 +++++++++ .../basics/core_accepts_analog_input.py | 33 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 test/python/basics/core_accepts_analog_input.py diff --git a/src/libretro/core/core.hpp b/src/libretro/core/core.hpp index eee08efc..23749e70 100644 --- a/src/libretro/core/core.hpp +++ b/src/libretro/core/core.hpp @@ -95,6 +95,7 @@ namespace MelonDsDs { bool UpdateOptionVisibility() noexcept; const melonDS::NDS* GetConsole() const noexcept { return Console.get(); } + const InputState& GetInputState() const noexcept { return _inputState; } private: static constexpr auto REGEX_OPTIONS = std::regex_constants::ECMAScript | std::regex_constants::optimize; [[gnu::cold]] void ApplyConfig(const CoreConfig& config) noexcept; diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp index 239ab585..20e05536 100644 --- a/src/libretro/core/test.cpp +++ b/src/libretro/core/test.cpp @@ -153,6 +153,16 @@ extern "C" const uint8_t* melondsds_gba_sram() { return console->GetGBACart()->GetSaveMemory(); } +extern "C" int melondsds_analog_cursor_x() { + using namespace MelonDsDs; + return Core.GetInputState().JoystickTouchPosition().x; +} + +extern "C" int melondsds_analog_cursor_y() { + using namespace MelonDsDs; + return Core.GetInputState().JoystickTouchPosition().y; +} + extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) noexcept { if (string_is_equal(sym, "libretropy_add_integers")) return reinterpret_cast(libretropy_add_integers); @@ -208,6 +218,12 @@ extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) if (string_is_equal(sym, "melondsds_gba_sram")) return reinterpret_cast(melondsds_gba_sram); + if (string_is_equal(sym, "melondsds_analog_cursor_x")) + return reinterpret_cast(melondsds_analog_cursor_x); + + if (string_is_equal(sym, "melondsds_analog_cursor_y")) + return reinterpret_cast(melondsds_analog_cursor_y); + return nullptr; } diff --git a/test/python/basics/core_accepts_analog_input.py b/test/python/basics/core_accepts_analog_input.py new file mode 100644 index 00000000..cd2d5d67 --- /dev/null +++ b/test/python/basics/core_accepts_analog_input.py @@ -0,0 +1,33 @@ +import itertools +from collections.abc import Callable +from ctypes import c_int, CFUNCTYPE + +from libretro import AnalogState + +import prelude + +options = { + b"melonds_show_cursor": b"enabled" +} + +def generate_input(): + yield from itertools.repeat(0, 2) + yield from itertools.repeat(AnalogState(rstick=(2000, 150)), 25) + yield from itertools.repeat(0) + + +with prelude.builder().with_input(generate_input).with_options(options).build() as session: + cursor_x: Callable[[], int] = session.get_proc_address(b"melondsds_analog_cursor_x", CFUNCTYPE(c_int)) + assert cursor_x is not None + cursor_y: Callable[[], int] = session.get_proc_address(b"melondsds_analog_cursor_y", CFUNCTYPE(c_int)) + assert cursor_y is not None + + initial_cursor_pos = cursor_x(), cursor_y() + + for i in range(360): + session.run() + + current_cursor_pos = cursor_x(), cursor_y() + + assert initial_cursor_pos != current_cursor_pos, f"Cursor didn't move from initial position of {initial_cursor_pos}" + From 019b7ae7dadd73af792bf56887aad9303b81ae98 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 28 May 2024 17:23:47 -0400 Subject: [PATCH 119/234] Remove some code that was supposed to be temporary --- test/python/opengl/core_loads_unloads.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/python/opengl/core_loads_unloads.py b/test/python/opengl/core_loads_unloads.py index b0b5f11d..91000a10 100644 --- a/test/python/opengl/core_loads_unloads.py +++ b/test/python/opengl/core_loads_unloads.py @@ -1,13 +1,9 @@ from typing import cast -import time from libretro import ModernGlVideoDriver from PIL import Image import prelude -def dd(): - return ModernGlVideoDriver(window="pyglet") - with prelude.builder().with_video(ModernGlVideoDriver).build() as session: video = cast(ModernGlVideoDriver, session.video) From 6120e4f247e696d78c5305c622031ec44194e97f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 28 May 2024 17:35:10 -0400 Subject: [PATCH 120/234] Log the screen layout that's being switched to --- src/libretro/format.cpp | 45 +++++++++++++++++++++++++++++++++++++++++ src/libretro/format.hpp | 6 ++++++ src/libretro/input.cpp | 4 +++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/libretro/format.cpp b/src/libretro/format.cpp index 56b127f2..4598e940 100644 --- a/src/libretro/format.cpp +++ b/src/libretro/format.cpp @@ -402,3 +402,48 @@ auto fmt::formatter::format(Platform::FileMode mode, format_ return formatter>::format(bits, ctx); } + +auto fmt::formatter::format(MelonDsDs::ScreenLayout layout, format_context& ctx) const -> decltype(ctx.out()) { + string_view name; + + switch (layout) { + case MelonDsDs::ScreenLayout::TopBottom: + name = "TopBottom"; + break; + case MelonDsDs::ScreenLayout::BottomTop: + name = "BottomTop"; + break; + case MelonDsDs::ScreenLayout::LeftRight: + name = "LeftRight"; + break; + case MelonDsDs::ScreenLayout::RightLeft: + name = "RightLeft"; + break; + case MelonDsDs::ScreenLayout::TopOnly: + name = "TopOnly"; + break; + case MelonDsDs::ScreenLayout::BottomOnly: + name = "BottomOnly"; + break; + case MelonDsDs::ScreenLayout::HybridTop: + name = "HybridTop"; + break; + case MelonDsDs::ScreenLayout::HybridBottom: + name = "HybridBottom"; + break; + case MelonDsDs::ScreenLayout::TurnLeft: + name = "TurnLeft"; + break; + case MelonDsDs::ScreenLayout::TurnRight: + name = "TurnRight"; + break; + case MelonDsDs::ScreenLayout::UpsideDown: + name = "UpsideDown"; + break; + default: + name = ""; + break; + } + + return formatter::format(name, ctx); +} \ No newline at end of file diff --git a/src/libretro/format.hpp b/src/libretro/format.hpp index 5b4799eb..10a1185c 100644 --- a/src/libretro/format.hpp +++ b/src/libretro/format.hpp @@ -34,6 +34,7 @@ #include #include "config/config.hpp" +#include "config/types.hpp" namespace fmt { template<> @@ -90,6 +91,11 @@ namespace fmt { struct formatter : formatter> { auto format(melonDS::Platform::FileMode mode, format_context& ctx) const -> decltype(ctx.out()); }; + + template<> + struct formatter : formatter { + auto format(MelonDsDs::ScreenLayout layout, format_context& ctx) const -> decltype(ctx.out()); + }; } #endif //MELONDS_DS_FORMAT_HPP diff --git a/src/libretro/input.cpp b/src/libretro/input.cpp index d9c79e63..bff1c656 100644 --- a/src/libretro/input.cpp +++ b/src/libretro/input.cpp @@ -24,6 +24,7 @@ #include "config/config.hpp" #include "environment.hpp" +#include "format.hpp" #include "libretro.hpp" #include "math.hpp" #include "screenlayout.hpp" @@ -132,7 +133,8 @@ void MelonDsDs::HandleInput(melonDS::NDS& nds, InputState& inputState, ScreenLay if (inputState.CycleLayoutPressed()) { // If the user wants to change the active screen layout... screenLayout.NextLayout(); // ...update the screen layout to the next in the sequence. - retro::debug("Switched to screen layout {} of {}", screenLayout.LayoutIndex() + 1, screenLayout.NumberOfLayouts()); + retro::debug("Switched to screen layout {} of {} ({})", screenLayout.LayoutIndex() + 1, screenLayout.NumberOfLayouts(), screenLayout.Layout()); + // Add 1 to the index because we present the layout index as 1-based to the user. } } From d052228231f5e69d8e5d703891da52349d1591f0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 29 May 2024 14:30:18 -0400 Subject: [PATCH 121/234] Add a test for rotating the screen --- src/libretro/core/core.hpp | 1 + src/libretro/core/test.cpp | 9 +++ test/cmake/Basics.cmake | 4 +- test/python/basics/core_rotates_screen.py | 75 +++++++++++++++++++++++ 4 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 test/python/basics/core_rotates_screen.py diff --git a/src/libretro/core/core.hpp b/src/libretro/core/core.hpp index 23749e70..68345374 100644 --- a/src/libretro/core/core.hpp +++ b/src/libretro/core/core.hpp @@ -96,6 +96,7 @@ namespace MelonDsDs { const melonDS::NDS* GetConsole() const noexcept { return Console.get(); } const InputState& GetInputState() const noexcept { return _inputState; } + const ScreenLayoutData& GetScreenLayoutData() const noexcept { return _screenLayout; } private: static constexpr auto REGEX_OPTIONS = std::regex_constants::ECMAScript | std::regex_constants::optimize; [[gnu::cold]] void ApplyConfig(const CoreConfig& config) noexcept; diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp index 20e05536..f1c7a535 100644 --- a/src/libretro/core/test.cpp +++ b/src/libretro/core/test.cpp @@ -163,6 +163,12 @@ extern "C" int melondsds_analog_cursor_y() { return Core.GetInputState().JoystickTouchPosition().y; } +extern "C" int melondsds_screen_layout() { + using namespace MelonDsDs; + return static_cast(Core.GetScreenLayoutData().Layout()); +} + + extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) noexcept { if (string_is_equal(sym, "libretropy_add_integers")) return reinterpret_cast(libretropy_add_integers); @@ -224,6 +230,9 @@ extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) if (string_is_equal(sym, "melondsds_analog_cursor_y")) return reinterpret_cast(melondsds_analog_cursor_y); + if (string_is_equal(sym, "melondsds_screen_layout")) + return reinterpret_cast(melondsds_screen_layout); + return nullptr; } diff --git a/test/cmake/Basics.cmake b/test/cmake/Basics.cmake index 0be8447d..aa7a1a91 100644 --- a/test/cmake/Basics.cmake +++ b/test/cmake/Basics.cmake @@ -130,9 +130,7 @@ add_python_test( NAME "Core rotates the screen" TEST_MODULE basics.core_rotates_screen NDS_SYSFILES - DISABLED -) # TODO: Implement this test -# TODO: Set the screen layout sequence +) add_python_test( NAME "Core can send messages (API V0)" diff --git a/test/python/basics/core_rotates_screen.py b/test/python/basics/core_rotates_screen.py new file mode 100644 index 00000000..61401166 --- /dev/null +++ b/test/python/basics/core_rotates_screen.py @@ -0,0 +1,75 @@ +import itertools +from ctypes import CFUNCTYPE, c_int + +from libretro import JoypadState, Rotation +import prelude + +options = { + b"melonds_number_of_screen_layouts": b"2", + b"melonds_screen_layout1": b"top-bottom", + b"melonds_screen_layout2": b"rotate-left", + b"melonds_screen_gap": b"0", +} + + +def generate_input(): + # Wait a little while... + yield from itertools.repeat(None, 10) + + # Cycle to the next screen layout + yield JoypadState(r2=True) + + yield from itertools.repeat(None) + + +with prelude.builder().with_input(generate_input).with_options(options).build() as session: + screen_layout = session.get_proc_address(b"melondsds_screen_layout", CFUNCTYPE(c_int)) + assert screen_layout is not None, "melondsds_screen_layout not defined" + + for i in range(10): + session.run() + + assert session.video.rotation == Rotation.NONE, "Core rotated earlier than expected" + + layout1 = screen_layout() + assert layout1 == 0, f"Expected screen layout 0 (TopBottom), got {layout1}" + + frame1 = session.video.screenshot() + geometry1 = session.video.geometry + + assert frame1 is not None + assert geometry1 is not None + + assert frame1.width == geometry1.base_width, \ + f"Frame width ({frame1.width}) should match geometry base width ({geometry1.base_width})" + + assert frame1.height == geometry1.base_height, \ + f"Frame height ({frame1.height}) should match geometry base height ({geometry1.height})" + + for i in range(200): + session.run() + + assert session.video.rotation == Rotation.NINETY, f"Expected core rotation of 90 degrees, got {session.video.rotation}" + + layout2 = screen_layout() + assert layout2 == 8, f"Expected screen layout 8 (TurnLeft), got {layout2}" + + frame2 = session.video.screenshot() + geometry2 = session.video.geometry + + assert frame2 is not None + assert geometry2 is not None + + assert geometry1 != geometry2, f"Geometry should have changed from {geometry1}" + + assert frame2.width == geometry2.base_width, \ + f"Frame width ({frame2.width}) should match geometry base width ({geometry2.base_width})" + + assert frame2.height == geometry2.base_height, \ + f"Frame height ({frame2.height}) should match geometry base height ({geometry2.height})" + + assert abs(geometry1.aspect_ratio - (1.0 / geometry2.aspect_ratio)) < 0.0001, \ + f"Expected aspect ratio of {1.0 / geometry1.aspect_ratio}, got {geometry2.aspect_ratio}" + + assert frame1.data != frame2.data, "Frame data should have changed to reflect the rotation" + From c022a76570724e3c73f23650f57210e9e0e9f0e8 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 29 May 2024 17:37:26 -0400 Subject: [PATCH 122/234] Add some OpenGL tests --- test/cmake/Video.cmake | 17 ++++++++++++++++- test/python/opengl/core_loads_unloads.py | 12 +++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/test/cmake/Video.cmake b/test/cmake/Video.cmake index 761305f1..060bdd03 100644 --- a/test/cmake/Video.cmake +++ b/test/cmake/Video.cmake @@ -7,4 +7,19 @@ add_python_test( CORE_OPTION "melonds_sysfile_mode=builtin" CORE_OPTION "melonds_console_mode=ds" CORE_OPTION "melonds_threaded_renderer=enabled" -) \ No newline at end of file +) + +add_python_test( + NAME "Core loads and unloads with the OpenGL renderer" + TEST_MODULE opengl.core_loads_unloads + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_render_mode=opengl" + REQUIRES_OPENGL +) + +add_python_test( + NAME "Core runs for multiple frames with OpenGL and software rendering" + TEST_MODULE opengl.core_loads_unloads + CONTENT "${NDS_ROM}" + REQUIRES_OPENGL +) diff --git a/test/python/opengl/core_loads_unloads.py b/test/python/opengl/core_loads_unloads.py index 91000a10..daf37230 100644 --- a/test/python/opengl/core_loads_unloads.py +++ b/test/python/opengl/core_loads_unloads.py @@ -1,7 +1,6 @@ from typing import cast from libretro import ModernGlVideoDriver -from PIL import Image import prelude with prelude.builder().with_video(ModernGlVideoDriver).build() as session: @@ -12,18 +11,17 @@ for i in range(70): session.run() - frame1 = video.screenshot + frame1 = video.screenshot() assert frame1 is not None - for i in range(360 * 2): + for i in range(360): session.run() - frame2 = video.screenshot + frame2 = video.screenshot() assert frame2 is not None geometry = video.geometry size = (geometry.base_width, geometry.base_height) - image = Image.frombuffer("RGB", size, frame2) - #image.show() - assert len(frame1) == len(frame2) + + assert len(frame1.data) == len(frame2.data) assert frame1 != frame2 From 6e46a00da95bf0e667e3adecd8fab21c18b8b96b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 29 May 2024 18:40:03 -0400 Subject: [PATCH 123/234] Rename a test --- test/cmake/Video.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cmake/Video.cmake b/test/cmake/Video.cmake index 060bdd03..d01382ed 100644 --- a/test/cmake/Video.cmake +++ b/test/cmake/Video.cmake @@ -10,7 +10,7 @@ add_python_test( ) add_python_test( - NAME "Core loads and unloads with the OpenGL renderer" + NAME "Core runs for multiple frames with OpenGL" TEST_MODULE opengl.core_loads_unloads CONTENT "${NDS_ROM}" CORE_OPTION "melonds_render_mode=opengl" From 40e0d1c276307ad35d12b004d45193b331d30454 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 29 May 2024 18:40:43 -0400 Subject: [PATCH 124/234] Add another OpenGL test --- src/libretro/core/core.hpp | 1 + src/libretro/core/test.cpp | 19 +++++++++++++ test/cmake/Video.cmake | 8 ++++++ .../opengl/core_falls_back_to_software.py | 28 +++++++++++++++++++ 4 files changed, 56 insertions(+) create mode 100644 test/python/opengl/core_falls_back_to_software.py diff --git a/src/libretro/core/core.hpp b/src/libretro/core/core.hpp index 68345374..32120d7e 100644 --- a/src/libretro/core/core.hpp +++ b/src/libretro/core/core.hpp @@ -96,6 +96,7 @@ namespace MelonDsDs { const melonDS::NDS* GetConsole() const noexcept { return Console.get(); } const InputState& GetInputState() const noexcept { return _inputState; } + std::optional GetRenderMode() const noexcept { return _renderState.GetRenderMode(); } const ScreenLayoutData& GetScreenLayoutData() const noexcept { return _screenLayout; } private: static constexpr auto REGEX_OPTIONS = std::regex_constants::ECMAScript | std::regex_constants::optimize; diff --git a/src/libretro/core/test.cpp b/src/libretro/core/test.cpp index f1c7a535..d920bc4a 100644 --- a/src/libretro/core/test.cpp +++ b/src/libretro/core/test.cpp @@ -168,6 +168,19 @@ extern "C" int melondsds_screen_layout() { return static_cast(Core.GetScreenLayoutData().Layout()); } +extern "C" bool melondsds_is_opengl_renderer() { + using namespace MelonDsDs; + auto mode = Core.GetRenderMode(); + + return mode && *mode == RenderMode::OpenGl; +} + +extern "C" bool melondsds_is_software_renderer() { + using namespace MelonDsDs; + auto mode = Core.GetRenderMode(); + + return mode && *mode == RenderMode::Software; +} extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) noexcept { if (string_is_equal(sym, "libretropy_add_integers")) @@ -233,6 +246,12 @@ extern "C" retro_proc_address_t MelonDsDs::GetRetroProcAddress(const char* sym) if (string_is_equal(sym, "melondsds_screen_layout")) return reinterpret_cast(melondsds_screen_layout); + if (string_is_equal(sym, "melondsds_is_opengl_renderer")) + return reinterpret_cast(melondsds_is_opengl_renderer); + + if (string_is_equal(sym, "melondsds_is_software_renderer")) + return reinterpret_cast(melondsds_is_software_renderer); + return nullptr; } diff --git a/test/cmake/Video.cmake b/test/cmake/Video.cmake index d01382ed..666555b5 100644 --- a/test/cmake/Video.cmake +++ b/test/cmake/Video.cmake @@ -23,3 +23,11 @@ add_python_test( CONTENT "${NDS_ROM}" REQUIRES_OPENGL ) + +add_python_test( + NAME "Core falls back to software renderer if OpenGL is unavailable" + TEST_MODULE opengl.core_falls_back_to_software + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_render_mode=opengl" + REQUIRES_OPENGL +) diff --git a/test/python/opengl/core_falls_back_to_software.py b/test/python/opengl/core_falls_back_to_software.py new file mode 100644 index 00000000..5776ed50 --- /dev/null +++ b/test/python/opengl/core_falls_back_to_software.py @@ -0,0 +1,28 @@ +from ctypes import CFUNCTYPE, c_bool +from typing import cast +from libretro import ArrayVideoDriver + +import prelude + +options = { + b"melonds_render_mode": b"opengl", +} + +with prelude.builder().with_video(ArrayVideoDriver).with_options(options).build() as session: + video = cast(ArrayVideoDriver, session.video) + assert isinstance(video, ArrayVideoDriver), f"Expected ArrayVideoDriver, got {type(video).__name__}" + + is_opengl_renderer = session.get_proc_address(b"melondsds_is_opengl_renderer", CFUNCTYPE(c_bool)) + assert is_opengl_renderer is not None, "melondsds_is_opengl_renderer not defined in the core" + + is_software_renderer = session.get_proc_address(b"melondsds_is_software_renderer", CFUNCTYPE(c_bool)) + assert is_software_renderer is not None, "melondsds_is_software_renderer not defined in the core" + + assert is_software_renderer() + assert not is_opengl_renderer() + + for i in range(10): + session.run() + + assert is_software_renderer() + assert not is_opengl_renderer() From 7a63bff2b17f68a34a4417515591ea47f9812e82 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 29 May 2024 20:02:55 -0400 Subject: [PATCH 125/234] Fix an incorrect format string --- src/libretro/render/opengl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index 757d78d0..fb57a584 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -137,7 +137,7 @@ std::unique_ptr MelonDsDs::OpenGLRenderState::New( try { return std::make_unique(); } catch (const opengl_not_initialized_exception& e) { - retro::debug("OpenGL context could not be initialized: %s", e.what()); + retro::debug("OpenGL context could not be initialized: {}", e.what()); return nullptr; } } From 93aa6f402eb60954ce52bff4c052ac4810c89ebf Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 30 May 2024 11:49:43 -0400 Subject: [PATCH 126/234] Add a formatter for `RenderMode` --- src/libretro/format.cpp | 20 +++++++++++++++++++- src/libretro/format.hpp | 6 ++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/libretro/format.cpp b/src/libretro/format.cpp index 4598e940..b660f0ae 100644 --- a/src/libretro/format.cpp +++ b/src/libretro/format.cpp @@ -446,4 +446,22 @@ auto fmt::formatter::format(MelonDsDs::ScreenLayout lay } return formatter::format(name, ctx); -} \ No newline at end of file +} + +auto fmt::formatter::format(MelonDsDs::RenderMode mode, format_context& ctx) const -> decltype(ctx.out()) { + string_view name; + + switch (mode) { + case MelonDsDs::RenderMode::Software: + name = "Software"; + break; + case MelonDsDs::RenderMode::OpenGl: + name = "OpenGL"; + break; + default: + name = ""; + break; + } + + return formatter::format(name, ctx); +} diff --git a/src/libretro/format.hpp b/src/libretro/format.hpp index 10a1185c..d9386439 100644 --- a/src/libretro/format.hpp +++ b/src/libretro/format.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -96,6 +97,11 @@ namespace fmt { struct formatter : formatter { auto format(MelonDsDs::ScreenLayout layout, format_context& ctx) const -> decltype(ctx.out()); }; + + template<> + struct formatter : formatter { + auto format(MelonDsDs::RenderMode mode, format_context& ctx) const -> decltype(ctx.out()); + }; } #endif //MELONDS_DS_FORMAT_HPP From 700fb8dd5824d3205cf67d5b378df95a8ebf6d0f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 30 May 2024 14:31:19 -0400 Subject: [PATCH 127/234] Add some extra logging --- src/libretro/core/core.cpp | 3 +++ src/libretro/render/opengl.cpp | 1 + src/libretro/render/render.cpp | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libretro/core/core.cpp b/src/libretro/core/core.cpp index 568e4beb..c3412db3 100644 --- a/src/libretro/core/core.cpp +++ b/src/libretro/core/core.cpp @@ -139,6 +139,7 @@ void MelonDsDs::CoreState::Run() noexcept { if (retro::is_variable_updated()) [[unlikely]] { // If any settings have changed... + retro::debug("At least one setting has changed; updating now"); ParseConfig(Config); ApplyConfig(Config); UpdateConsole(Config, nds); @@ -414,6 +415,7 @@ void MelonDsDs::CoreState::StartConsole() { retro_assert(Console != nullptr); // This function should only be called if the console is initialized + retro::debug(TracyFunction); _renderState.UpdateRenderer(Config, *Console); { @@ -652,6 +654,7 @@ void MelonDsDs::CoreState::ApplyConfig(const CoreConfig& config) noexcept { // If this isn't the first time we're setting the renderer... if (oldRenderer != newRenderer) { // If we're switching renderer modes... + retro::debug("Switching render mode from {} to {}", *oldRenderer, *newRenderer); retro_system_av_info av = GetSystemAvInfo(*newRenderer); retro::set_system_av_info(av); } diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index fb57a584..f3c593dd 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -205,6 +205,7 @@ void MelonDsDs::OpenGLRenderState::ContextReset(melonDS::NDS& nds, const CoreCon retro::debug(TracyFunction); // Initialize all OpenGL function pointers + retro::debug("Initializing OpenGL function pointers"); glsm_ctl(GLSM_CTL_STATE_CONTEXT_RESET, nullptr); TracyGpuContext; // Must be called AFTER the function pointers are bound! diff --git a/src/libretro/render/render.cpp b/src/libretro/render/render.cpp index d6ddf93d..ca99ae7e 100644 --- a/src/libretro/render/render.cpp +++ b/src/libretro/render/render.cpp @@ -110,7 +110,7 @@ void MelonDsDs::RenderStateWrapper::UpdateRenderer(const CoreConfig& config, mel #if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) if (auto* glRender = dynamic_cast(_renderState.get()); glRender && !nds.GPU.GetRenderer3D().Accelerated) { // If we're configured to use the OpenGL renderer, and we aren't already... - + retro::debug("Initializing OpenGL renderer"); if (auto renderer = melonDS::GLRenderer::New()) { nds.GPU.SetRenderer3D(std::move(renderer)); glRender->RequestRefresh(); From 17b763a9c735dbcaf68c9416e90479053f386bee Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 30 May 2024 14:31:40 -0400 Subject: [PATCH 128/234] Add an extra test case --- test/cmake/Video.cmake | 7 +++++ .../core_switch_software_to_opengl_runtime.py | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 test/python/opengl/core_switch_software_to_opengl_runtime.py diff --git a/test/cmake/Video.cmake b/test/cmake/Video.cmake index 666555b5..8ac50a47 100644 --- a/test/cmake/Video.cmake +++ b/test/cmake/Video.cmake @@ -31,3 +31,10 @@ add_python_test( CORE_OPTION "melonds_render_mode=opengl" REQUIRES_OPENGL ) + +add_python_test( + NAME "Core can switch from software to OpenGL mode at runtime" + TEST_MODULE opengl.core_switch_software_to_opengl_runtime + CONTENT "${NDS_ROM}" + REQUIRES_OPENGL +) diff --git a/test/python/opengl/core_switch_software_to_opengl_runtime.py b/test/python/opengl/core_switch_software_to_opengl_runtime.py new file mode 100644 index 00000000..c6f3eafd --- /dev/null +++ b/test/python/opengl/core_switch_software_to_opengl_runtime.py @@ -0,0 +1,26 @@ +from ctypes import CFUNCTYPE, c_bool + +import prelude + +options = { + b"melonds_render_mode": b"software", +} + +with prelude.builder().with_options(options).build() as session: + is_opengl_renderer = session.get_proc_address(b"melondsds_is_opengl_renderer", CFUNCTYPE(c_bool)) + assert is_opengl_renderer is not None, "melondsds_is_opengl_renderer not defined in the core" + + is_software_renderer = session.get_proc_address(b"melondsds_is_software_renderer", CFUNCTYPE(c_bool)) + assert is_software_renderer is not None, "melondsds_is_software_renderer not defined in the core" + + assert is_software_renderer() + + for i in range(3): + session.run() + + session.options.variables["melonds_render_mode"] = b"opengl" + + for i in range(3): + session.run() + + assert is_opengl_renderer() From 2dbf2ebc47db6fa472fcd9282e089d4517cffad7 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 30 May 2024 14:59:50 -0400 Subject: [PATCH 129/234] Add some more test cases --- test/cmake/Video.cmake | 21 ++++++++++++ ...tch_opengl_to_software_and_back_runtime.py | 33 +++++++++++++++++++ .../core_switch_opengl_to_software_runtime.py | 26 +++++++++++++++ ...tch_software_to_opengl_and_back_runtime.py | 33 +++++++++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 test/python/opengl/core_switch_opengl_to_software_and_back_runtime.py create mode 100644 test/python/opengl/core_switch_opengl_to_software_runtime.py create mode 100644 test/python/opengl/core_switch_software_to_opengl_and_back_runtime.py diff --git a/test/cmake/Video.cmake b/test/cmake/Video.cmake index 8ac50a47..f4db7ee5 100644 --- a/test/cmake/Video.cmake +++ b/test/cmake/Video.cmake @@ -38,3 +38,24 @@ add_python_test( CONTENT "${NDS_ROM}" REQUIRES_OPENGL ) + +add_python_test( + NAME "Core can switch from OpenGL to software mode at runtime" + TEST_MODULE opengl.core_switch_opengl_to_software_runtime + CONTENT "${NDS_ROM}" + REQUIRES_OPENGL +) + +add_python_test( + NAME "Core can switch from software to OpenGL mode and back at runtime" + TEST_MODULE opengl.core_switch_software_to_opengl_and_back_runtime + CONTENT "${NDS_ROM}" + REQUIRES_OPENGL +) + +add_python_test( + NAME "Core can switch from OpenGL to software mode and back at runtime" + TEST_MODULE opengl.core_switch_opengl_to_software_and_back_runtime + CONTENT "${NDS_ROM}" + REQUIRES_OPENGL +) diff --git a/test/python/opengl/core_switch_opengl_to_software_and_back_runtime.py b/test/python/opengl/core_switch_opengl_to_software_and_back_runtime.py new file mode 100644 index 00000000..722bb28b --- /dev/null +++ b/test/python/opengl/core_switch_opengl_to_software_and_back_runtime.py @@ -0,0 +1,33 @@ +from ctypes import CFUNCTYPE, c_bool + +import prelude + +options = { + b"melonds_render_mode": b"opengl", +} + +with prelude.builder().with_options(options).build() as session: + is_opengl_renderer = session.get_proc_address(b"melondsds_is_opengl_renderer", CFUNCTYPE(c_bool)) + assert is_opengl_renderer is not None, "melondsds_is_opengl_renderer not defined in the core" + + is_software_renderer = session.get_proc_address(b"melondsds_is_software_renderer", CFUNCTYPE(c_bool)) + assert is_software_renderer is not None, "melondsds_is_software_renderer not defined in the core" + + assert is_opengl_renderer() + + for i in range(3): + session.run() + + session.options.variables["melonds_render_mode"] = b"software" + + for i in range(3): + session.run() + + assert is_software_renderer() + + session.options.variables["melonds_render_mode"] = b"opengl" + + for i in range(3): + session.run() + + assert is_opengl_renderer() diff --git a/test/python/opengl/core_switch_opengl_to_software_runtime.py b/test/python/opengl/core_switch_opengl_to_software_runtime.py new file mode 100644 index 00000000..c59efd6c --- /dev/null +++ b/test/python/opengl/core_switch_opengl_to_software_runtime.py @@ -0,0 +1,26 @@ +from ctypes import CFUNCTYPE, c_bool + +import prelude + +options = { + b"melonds_render_mode": b"opengl", +} + +with prelude.builder().with_options(options).build() as session: + is_opengl_renderer = session.get_proc_address(b"melondsds_is_opengl_renderer", CFUNCTYPE(c_bool)) + assert is_opengl_renderer is not None, "melondsds_is_opengl_renderer not defined in the core" + + is_software_renderer = session.get_proc_address(b"melondsds_is_software_renderer", CFUNCTYPE(c_bool)) + assert is_software_renderer is not None, "melondsds_is_software_renderer not defined in the core" + + assert is_opengl_renderer() + + for i in range(3): + session.run() + + session.options.variables["melonds_render_mode"] = b"software" + + for i in range(3): + session.run() + + assert is_software_renderer() diff --git a/test/python/opengl/core_switch_software_to_opengl_and_back_runtime.py b/test/python/opengl/core_switch_software_to_opengl_and_back_runtime.py new file mode 100644 index 00000000..dcd618f8 --- /dev/null +++ b/test/python/opengl/core_switch_software_to_opengl_and_back_runtime.py @@ -0,0 +1,33 @@ +from ctypes import CFUNCTYPE, c_bool + +import prelude + +options = { + b"melonds_render_mode": b"software", +} + +with prelude.builder().with_options(options).build() as session: + is_opengl_renderer = session.get_proc_address(b"melondsds_is_opengl_renderer", CFUNCTYPE(c_bool)) + assert is_opengl_renderer is not None, "melondsds_is_opengl_renderer not defined in the core" + + is_software_renderer = session.get_proc_address(b"melondsds_is_software_renderer", CFUNCTYPE(c_bool)) + assert is_software_renderer is not None, "melondsds_is_software_renderer not defined in the core" + + assert is_software_renderer() + + for i in range(3): + session.run() + + session.options.variables["melonds_render_mode"] = b"opengl" + + for i in range(3): + session.run() + + assert is_opengl_renderer() + + session.options.variables["melonds_render_mode"] = b"software" + + for i in range(3): + session.run() + + assert is_software_renderer() From df2ca2309a4718a718b78dbeb00554ffad032d3b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 30 May 2024 15:06:42 -0400 Subject: [PATCH 130/234] Lengthen the timeout for two test cases --- test/cmake/Video.cmake | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/cmake/Video.cmake b/test/cmake/Video.cmake index f4db7ee5..9c833ebd 100644 --- a/test/cmake/Video.cmake +++ b/test/cmake/Video.cmake @@ -44,7 +44,11 @@ add_python_test( TEST_MODULE opengl.core_switch_opengl_to_software_runtime CONTENT "${NDS_ROM}" REQUIRES_OPENGL + TIMEOUT 30 ) +# This test is slower for some reason; +# there must be a lot of overhead +# in the OpenGL context creation add_python_test( NAME "Core can switch from software to OpenGL mode and back at runtime" @@ -58,4 +62,15 @@ add_python_test( TEST_MODULE opengl.core_switch_opengl_to_software_and_back_runtime CONTENT "${NDS_ROM}" REQUIRES_OPENGL + TIMEOUT 30 ) + +# See https://github.com/JesseTG/melonds-ds/issues/155 +add_python_test( + NAME "Core does not crash at in-core error screen when using OpenGL" + TEST_MODULE "" + CONTENT "${NDS_ROM}" + CORE_OPTION "melonds_render_mode=opengl" + REQUIRES_OPENGL + DISABLED +) # TODO: Implement From dc0ca964f2bb278fadaf411d2977208ca78acaf9 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 31 May 2024 12:07:45 -0400 Subject: [PATCH 131/234] Fix #155 --- src/libretro/core/core.cpp | 4 ++-- test/cmake/Video.cmake | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libretro/core/core.cpp b/src/libretro/core/core.cpp index c3412db3..21d92e28 100644 --- a/src/libretro/core/core.cpp +++ b/src/libretro/core/core.cpp @@ -316,6 +316,8 @@ bool MelonDsDs::CoreState::InitErrorScreen(const config_exception& e) noexcept { retro::task::reset(); _messageScreen = std::make_unique(e); Config.SetConfiguredRenderer(RenderMode::Software); + _renderState.Apply(Config); + _screenLayout.Apply(Config, _renderState); _screenLayout.Update(); retro::error("Error screen initialized"); return true; @@ -323,8 +325,6 @@ bool MelonDsDs::CoreState::InitErrorScreen(const config_exception& e) noexcept { void MelonDsDs::CoreState::RenderErrorScreen() noexcept { assert(_messageScreen != nullptr); - - _screenLayout.Update(); _renderState.Render(*_messageScreen, Config, _screenLayout); } diff --git a/test/cmake/Video.cmake b/test/cmake/Video.cmake index 9c833ebd..410dbfbb 100644 --- a/test/cmake/Video.cmake +++ b/test/cmake/Video.cmake @@ -68,9 +68,8 @@ add_python_test( # See https://github.com/JesseTG/melonds-ds/issues/155 add_python_test( NAME "Core does not crash at in-core error screen when using OpenGL" - TEST_MODULE "" - CONTENT "${NDS_ROM}" + TEST_MODULE basics.core_run_frames CORE_OPTION "melonds_render_mode=opengl" REQUIRES_OPENGL - DISABLED -) # TODO: Implement + NO_SKIP_ERROR_SCREEN +) From f8a56ee55ce0e20517fd01a5834d09377ad434ed Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 31 May 2024 12:19:25 -0400 Subject: [PATCH 132/234] Add a CHANGELOG entry for the recent bug fix --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 430529de..4b6717ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project roughly adheres to [Semantic Versioning](https://semver.org/spe - Fixed a bug where native BIOS images would be used when the core was supposed to fall back to built-in system files. - Fixed a bug where GBA SRAM wouldn't be loaded. +- Fixed a bug where the core would crash when trying to load the error screen + while using the OpenGL renderer. ## [1.1.1] - 2024-02-29 From ea8599c12aceaefcb0b7862f17c758c374da85d8 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 31 May 2024 15:31:34 -0400 Subject: [PATCH 133/234] Add `requirements.txt` --- test/CMakeLists.txt | 1 - test/requirements.txt | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 test/requirements.txt diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 89189b46..5f6e2ca7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -13,7 +13,6 @@ endif() if (PythonModules_PIL_PATH) message(STATUS "Found PIL (aka Pillow): ${PythonModules_PIL_PATH}") endif() -## TODO: Write a requirements.txt for all test dependencies if (NOT NDS_ROM) message(WARNING "NDS_ROM must be set to the path of an NDS ROM") diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 00000000..c575978e --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,5 @@ +# melonDS DS doesn't support OpenGL on macOS, +# so there's no need to install OpenGL dependencies +libretro.py[opengl]==0.1.0 ; sys_platform != "darwin" +libretro.py==0.1.0 ; sys_platform == "darwin" +Pillow==10.3.0 \ No newline at end of file From c5f2baaea4b129cbec493324441c47dde2ff774b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 31 May 2024 15:54:25 -0400 Subject: [PATCH 134/234] Add `CreatePythonVenv.cmake` - Taken from https://github.com/HarryMills/CMakeVenvGeneration --- .gitattributes | 1 + cmake/CreatePythonVenv.cmake | 57 ++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 cmake/CreatePythonVenv.cmake diff --git a/.gitattributes b/.gitattributes index 47acea9a..21423484 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,4 +2,5 @@ cmake/toolchain/** linguist-vendored cmake/GitInfo.cmake linguist-vendored cmake/FindOpenGLES.cmake linguist-vendored cmake/FindPythonModules.cmake linguist-vendored +cmake/CreatePythonVenv.cmake linguist-vendored LICENSE text \ No newline at end of file diff --git a/cmake/CreatePythonVenv.cmake b/cmake/CreatePythonVenv.cmake new file mode 100644 index 00000000..a98c1324 --- /dev/null +++ b/cmake/CreatePythonVenv.cmake @@ -0,0 +1,57 @@ +function(CreatePythonVenv venv_dest venv_name out_venv_executable) + # Creates a virtual environment within the binary directory, with the given input name. Returns the path to the + # virtual environments python executable for use else where. Optional argument of path to a requirements.txt, if + # given will attempt to install/update within virtual environment. + ## ARGUMENTS + # venv_dest - Input the destination path for the virtual environment to be created within + # venv_name - Input name of the virtual environment to create in the binary directory + # out_venv_executable - Variable to be set in the parent scope, holds path to virtual environment executable + ## OPTIONAL + # requirements_path - Optional input of path to requirements.txt + + # create variable for venv path from input + SET(VENV_PATH "${venv_dest}/${venv_name}") + + # if path doesn't exist, create the virtual environment + if (NOT EXISTS ${VENV_PATH}) + MESSAGE(STATUS "Creating Python virtual environment named: " ${venv_name}) + FIND_PACKAGE(Python3 COMPONENTS Interpreter) + EXECUTE_PROCESS(COMMAND ${Python3_EXECUTABLE} "-m" "venv" ${venv_name} + WORKING_DIRECTORY ${venv_dest}) + endif() + + # check if virtual environment was made successfully/already exists (path exists) + if (EXISTS ${VENV_PATH}) + # unset python executable + UNSET(Python3_EXECUTABLE) + + # make cmake find the python executable in virtual environment instead + SET(ENV{VIRTUAL_ENV} ${VENV_PATH}) + SET(Python3_FIND_VIRTUALENV FIRST) + SET(Python3_FIND_REGISTRY NEVER) + SET(CMAKE_FIND_FRAMEWORK NEVER) + FIND_PACKAGE(Python3 COMPONENTS Interpreter Development) + + # make a regex that can be used to check whether a path is within the venv + # need to replace escape all special regex characters in the path + string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" VENV_PATH_AS_REGEX "${VENV_PATH}") + SET(IN_VENV_REGEX "${VENV_PATH_AS_REGEX}/+") + + # If python executable found in the venv path + if (Python3_FOUND AND Python3_EXECUTABLE MATCHES "${IN_VENV_REGEX}") + # set return variable in parent scope + SET(venv_executable "${Python3_EXECUTABLE}") + SET(${out_venv_executable} "${venv_executable}" PARENT_SCOPE) + + # if given path to requirements.txt try install/update them + if (DEFINED ARGV3) + MESSAGE(STATUS "Checking/installing python requirements") + EXECUTE_PROCESS(COMMAND ${venv_executable} -m pip install --upgrade pip setuptools -q) + EXECUTE_PROCESS(COMMAND ${venv_executable} -m pip install --upgrade -r "${ARGV3}" -q) + MESSAGE(STATUS "Python requirements updated") + endif() + else() + MESSAGE(WARNING "Python virtual environment creation failed.") + endif() + endif() +endfunction() \ No newline at end of file From e87a8ddb6bbb4cad1c35c323e69e9ce68e1dd7e5 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 31 May 2024 19:05:06 -0400 Subject: [PATCH 135/234] Update to libretro.py 0.1.2 --- test/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/requirements.txt b/test/requirements.txt index c575978e..2cfe54d9 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,5 +1,5 @@ # melonDS DS doesn't support OpenGL on macOS, # so there's no need to install OpenGL dependencies -libretro.py[opengl]==0.1.0 ; sys_platform != "darwin" -libretro.py==0.1.0 ; sys_platform == "darwin" +libretro.py[opengl]==0.1.2 ; sys_platform != "darwin" +libretro.py==0.1.2 ; sys_platform == "darwin" Pillow==10.3.0 \ No newline at end of file From 13bf099e762de74ec4f62f8482d06cbc5f0c12f0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 31 May 2024 19:14:19 -0400 Subject: [PATCH 136/234] Create and use an internal venv by default --- test/CMakeLists.txt | 19 +++++++++++++------ test/README.md | 11 +++++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5f6e2ca7..8e2f122c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,11 +1,18 @@ -find_package(Python 3.11 REQUIRED) +option(MELONDSDS_INTERNAL_VENV "Use an internal Python virtual environment for the test suite; disable if using your own." ON) -# FindPython sets "Python_EXECUTABLE", but FindPythonModules wants "PYTHON_EXECUTABLE" (note the caps) -set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") +if (MELONDSDS_INTERNAL_VENV) + include(../cmake/CreatePythonVenv.cmake) + CreatePythonVenv("${CMAKE_BINARY_DIR}" "venv" VENV "${CMAKE_CURRENT_LIST_DIR}/requirements.txt") + set(PYTHON_EXECUTABLE "${VENV}") +else() + find_package(Python 3.11 REQUIRED) + # FindPython sets "Python_EXECUTABLE", but FindPythonModules wants "PYTHON_EXECUTABLE" (note the caps) + set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") +endif() -find_package(PythonModules COMPONENTS libretro PIL) +# NOTE: These are modules, not package names (i.e. these names get imported as-is) +find_package(PythonModules REQUIRED COMPONENTS libretro PIL) -# TODO: Make this required once libretro.py is on PyPI if (PythonModules_libretro_PATH) message(STATUS "Found libretro.py: ${PythonModules_libretro_PATH}") endif() @@ -132,7 +139,7 @@ function(add_python_test) add_test( NAME "${RETRO_NAME}" - COMMAND ${Python_EXECUTABLE} + COMMAND ${PYTHON_EXECUTABLE} -m "${RETRO_TEST_MODULE}" "$" "${NDS_CONTENT}" diff --git a/test/README.md b/test/README.md index b14c7a9f..0c4fa0e2 100644 --- a/test/README.md +++ b/test/README.md @@ -22,9 +22,16 @@ Once you do that, you'll need to obtain the following dependencies: The tests don't assume any particular ROM. - The set of Nintendo DS/DSi system files described in the [main README](../README.md#installing-nintendo-ds-bios) -## Configuring the Python Environment +### Optional: Configuring the Python Environment -TODO: Describe how to install the Python dependencies. +By default, CMake finds Python and configures its own internal Python environment in the build directory. +You can use your own virtual environment or the system Python installation with the following steps: + +```bash +# If you want to use a venv, activate it beforehand. +pip install -r test/requirements.txt # Install the test framework's dependencies +cmake -B build -DMELONDSDS_INTERNAL_VENV=OFF +``` ## Configuring the Tests From a93fd1e5b6890f9a2b7356ce6900b787d6fdb83a Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 12:26:01 -0400 Subject: [PATCH 137/234] Set the default test timeout to 30 seconds --- test/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8e2f122c..f30f52cf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,6 +88,8 @@ cmake_path(GET DSI_NAND FILENAME DSI_NAND_NAME) include(CMakePrintHelpers) +set(DEFAULT_TIMEOUT 30) + function(add_python_test) set(options ARM7_BIOS @@ -257,7 +259,7 @@ function(add_python_test) if (RETRO_TIMEOUT) set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT "${RETRO_TIMEOUT}") else() - set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT 10) + set_tests_properties("${RETRO_NAME}" PROPERTIES TIMEOUT "${DEFAULT_TIMEOUT}") endif() endfunction() From 226c6f0bf1d5fd1b0da6494015376e4789d417e7 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 12:31:55 -0400 Subject: [PATCH 138/234] Update to libretro.py 0.1.3 --- test/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/requirements.txt b/test/requirements.txt index 2cfe54d9..bd33dd3f 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,5 +1,5 @@ # melonDS DS doesn't support OpenGL on macOS, # so there's no need to install OpenGL dependencies -libretro.py[opengl]==0.1.2 ; sys_platform != "darwin" -libretro.py==0.1.2 ; sys_platform == "darwin" +libretro.py[opengl]==0.1.3 ; sys_platform != "darwin" +libretro.py==0.1.3 ; sys_platform == "darwin" Pillow==10.3.0 \ No newline at end of file From 38472db1f53a197a1ce0d599d3f70a3500c86f25 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 12:39:39 -0400 Subject: [PATCH 139/234] Update test suite dependencies - Install Python - Don't install RetroArch, Go, or emutest --- .github/actions/test-deps/action.yml | 29 +++++++--------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/.github/actions/test-deps/action.yml b/.github/actions/test-deps/action.yml index 14b801cd..cada43d5 100644 --- a/.github/actions/test-deps/action.yml +++ b/.github/actions/test-deps/action.yml @@ -21,23 +21,18 @@ inputs: runs: using: composite steps: - - name: Add RetroArch PPA (Linux) - if: ${{ runner.os == 'Linux' }} - shell: bash - run: sudo add-apt-repository --yes ppa:libretro/stable - # Skip this step on act due to https://github.com/nektos/act/issues/1849 - name: Install Dependencies (Linux x86_64/Android) if: ${{ !env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} uses: awalsh128/cache-apt-pkgs-action@latest with: - packages: cmake p7zip-full xvfb xdg-utils x11-xserver-utils retroarch + packages: cmake p7zip-full xvfb xdg-utils x11-xserver-utils version: 1.0 - name: Install Dependencies (Linux x86_64/Android + act) if: ${{ env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} shell: bash - run: sudo apt-get update -qy && sudo apt-get install -qy cmake p7zip-full xvfb xdg-utils x11-xserver-utils retroarch + run: sudo apt-get update -qy && sudo apt-get install -qy cmake p7zip-full xvfb xdg-utils x11-xserver-utils - name: Install MSYS2 Dependencies (Windows) uses: msys2/setup-msys2@v2 @@ -47,12 +42,11 @@ runs: update: true install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake - - name: Install RetroArch (Windows) - if: ${{ runner.os == 'Windows' }} - shell: powershell - run: | - Invoke-WebRequest -Uri https://buildbot.libretro.com/stable/1.16.0/windows/x86_64/RetroArch.7z -OutFile RetroArch.7z - 7z x -o"$Env:ProgramFiles" RetroArch.7z + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' - name: Download Test Files uses: actions/checkout@v4 @@ -65,12 +59,3 @@ runs: working-directory: "${{ inputs.testfile-dir }}" shell: bash run: 7z x "${{ inputs.dsi-nand-archive }}" - - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version: '^1.21.0' - - - name: Install Emutest - shell: bash - run: go install github.com/kivutar/emutest@latest \ No newline at end of file From 1c3cb6464c8c3ff21aeaa60d08532e0375e26c5a Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 12:47:52 -0400 Subject: [PATCH 140/234] Log more detais about the created venv --- cmake/CreatePythonVenv.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/CreatePythonVenv.cmake b/cmake/CreatePythonVenv.cmake index a98c1324..7c4ec4c4 100644 --- a/cmake/CreatePythonVenv.cmake +++ b/cmake/CreatePythonVenv.cmake @@ -14,8 +14,8 @@ function(CreatePythonVenv venv_dest venv_name out_venv_executable) # if path doesn't exist, create the virtual environment if (NOT EXISTS ${VENV_PATH}) - MESSAGE(STATUS "Creating Python virtual environment named: " ${venv_name}) FIND_PACKAGE(Python3 COMPONENTS Interpreter) + MESSAGE(STATUS "Creating Python virtual environment at ${VENV_PATH} with ${Python3_EXECUTABLE}") EXECUTE_PROCESS(COMMAND ${Python3_EXECUTABLE} "-m" "venv" ${venv_name} WORKING_DIRECTORY ${venv_dest}) endif() From 12343d49de16411ef8344d95f66dbcf659f88284 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 12:49:44 -0400 Subject: [PATCH 141/234] Echo the output of the venv's creation --- cmake/CreatePythonVenv.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/CreatePythonVenv.cmake b/cmake/CreatePythonVenv.cmake index 7c4ec4c4..7e7ed8e8 100644 --- a/cmake/CreatePythonVenv.cmake +++ b/cmake/CreatePythonVenv.cmake @@ -17,7 +17,8 @@ function(CreatePythonVenv venv_dest venv_name out_venv_executable) FIND_PACKAGE(Python3 COMPONENTS Interpreter) MESSAGE(STATUS "Creating Python virtual environment at ${VENV_PATH} with ${Python3_EXECUTABLE}") EXECUTE_PROCESS(COMMAND ${Python3_EXECUTABLE} "-m" "venv" ${venv_name} - WORKING_DIRECTORY ${venv_dest}) + WORKING_DIRECTORY ${venv_dest} + COMMAND_ECHO STDOUT) endif() # check if virtual environment was made successfully/already exists (path exists) From f6ac3500ad50b9b48d2415d92ffaea8bd63c0d3d Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 12:57:25 -0400 Subject: [PATCH 142/234] Try ECHO_OUTPUT_VARIABLE instead --- cmake/CreatePythonVenv.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/CreatePythonVenv.cmake b/cmake/CreatePythonVenv.cmake index 7e7ed8e8..af28f272 100644 --- a/cmake/CreatePythonVenv.cmake +++ b/cmake/CreatePythonVenv.cmake @@ -18,7 +18,7 @@ function(CreatePythonVenv venv_dest venv_name out_venv_executable) MESSAGE(STATUS "Creating Python virtual environment at ${VENV_PATH} with ${Python3_EXECUTABLE}") EXECUTE_PROCESS(COMMAND ${Python3_EXECUTABLE} "-m" "venv" ${venv_name} WORKING_DIRECTORY ${venv_dest} - COMMAND_ECHO STDOUT) + ECHO_OUTPUT_VARIABLE ECHO_ERROR_VARIABLE) endif() # check if virtual environment was made successfully/already exists (path exists) From 700686ce4167257232902b33ae2695e1ccb05d53 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 17:50:47 -0400 Subject: [PATCH 143/234] Don't use an internal venv - This way we can cache the Python dependencies --- .github/actions/test-deps/action.yml | 7 +++++++ .github/workflows/test.yaml | 1 + 2 files changed, 8 insertions(+) diff --git a/.github/actions/test-deps/action.yml b/.github/actions/test-deps/action.yml index cada43d5..5c80221a 100644 --- a/.github/actions/test-deps/action.yml +++ b/.github/actions/test-deps/action.yml @@ -48,6 +48,13 @@ runs: python-version: '3.12' cache: 'pip' + - name: Install Python Dependencies + shell: bash + run: | + python -m venv ~/.venv/melondsds + source activate ~/.venv/melondsds/bin/activate + pip install -r "${{ github.workspace }}/test/requirements.txt" + - name: Download Test Files uses: actions/checkout@v4 with: diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 191597f1..3839126e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -100,6 +100,7 @@ jobs: -DNDS_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_ROM }}" \ -DGBA_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_ROM }}" \ -DGBA_SRAM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_SRAM }}" \ + -DMELONDSDS_INTERNAL_VENV=OFF \ ${{ inputs.cmake-args }} - name: Run Test Suite (Linux) From 36a419bb8062b01f1ff3897f59069836313d47bd Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 18:00:26 -0400 Subject: [PATCH 144/234] Fix an incorrect `source` - Whoops --- .github/actions/test-deps/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/test-deps/action.yml b/.github/actions/test-deps/action.yml index 5c80221a..091c5549 100644 --- a/.github/actions/test-deps/action.yml +++ b/.github/actions/test-deps/action.yml @@ -52,7 +52,7 @@ runs: shell: bash run: | python -m venv ~/.venv/melondsds - source activate ~/.venv/melondsds/bin/activate + source ~/.venv/melondsds/bin/activate pip install -r "${{ github.workspace }}/test/requirements.txt" - name: Download Test Files From 4dfe5a595c9e35b03fe3a5428fdb0eb39a8171e0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 18:17:13 -0400 Subject: [PATCH 145/234] Log some extra info provided by CMake --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f7f3d34..46bce1b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,10 @@ project("melonDS DS" LANGUAGES C CXX) message(STATUS "Configuring a ${CMAKE_BUILD_TYPE} build.") +if (APPLE) + message(STATUS "CMAKE_OSX_ARCHITECTURES: ${CMAKE_OSX_ARCHITECTURES}") +endif () +message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) include(CheckSymbolExists) From 3b81455a56a34a8038010ba1e6afcb5b48a6f6be Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 18:21:36 -0400 Subject: [PATCH 146/234] Activate the Python venv when configuring the test suite --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3839126e..f6cc8671 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -85,6 +85,7 @@ jobs: - name: Configure Test Suite working-directory: "${{ env.BUILD_DIR }}" run: | + source ~/.venv/melondsds/bin/activate cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DMELONDS_REPOSITORY_URL="${{ vars.MELONDS_REPOSITORY_URL }}" \ From e6f4908da53f0b43a07c0161562fcaa3a0d04bad Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 18:33:22 -0400 Subject: [PATCH 147/234] Accommodate GitHub's changes to macOS runners - macOS runners now default to ARM-based Macs --- .github/workflows/main.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index b3ca69c1..451289f0 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -52,7 +52,7 @@ jobs: runs-on: macos-latest target: macos-x86_64 lib-ext: dylib - cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="x86_64" -DENABLE_OGLRENDERER=OFF $EXTRA_CMAKE_ARGS + cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="x86_64" -DENABLE_OGLRENDERER=OFF -DCMAKE_APPLE_SILICON_PROCESSOR="x86_64" $EXTRA_CMAKE_ARGS # Disabled OpenGL on macOS due to https://github.com/JesseTG/melonds-ds/issues/12 macos-arm64: @@ -64,7 +64,7 @@ jobs: runs-on: macos-latest target: macos-arm64 lib-ext: dylib - cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="arm64" -DENABLE_OGLRENDERER=OFF $EXTRA_CMAKE_ARGS + cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="arm64" -DENABLE_OGLRENDERER=OFF -DCMAKE_APPLE_SILICON_PROCESSOR="arm64" $EXTRA_CMAKE_ARGS # Disabled OpenGL on macOS due to https://github.com/JesseTG/melonds-ds/issues/12 linux-x86_64: From 357dc891e4aa645379fd56a27536e0eb07fa3dfa Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 19:51:36 -0400 Subject: [PATCH 148/234] List more details about the chosen Python installation --- .github/workflows/test.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f6cc8671..6eff5cee 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -84,8 +84,11 @@ jobs: - name: Configure Test Suite working-directory: "${{ env.BUILD_DIR }}" + shell: bash run: | source ~/.venv/melondsds/bin/activate + ls -halR ~/.venv + which -a python cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DMELONDS_REPOSITORY_URL="${{ vars.MELONDS_REPOSITORY_URL }}" \ @@ -101,6 +104,7 @@ jobs: -DNDS_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_ROM }}" \ -DGBA_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_ROM }}" \ -DGBA_SRAM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_SRAM }}" \ + -DPython_EXECUTABLE:FILEPATH=~/.venv/melondsds/bin/python -DMELONDSDS_INTERNAL_VENV=OFF \ ${{ inputs.cmake-args }} From 5107f3043009a80e0b5d8d03b1149d48ad104f9b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 19:58:39 -0400 Subject: [PATCH 149/234] Set the Python executable correctly --- .github/workflows/test.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6eff5cee..5ad408b7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -87,8 +87,6 @@ jobs: shell: bash run: | source ~/.venv/melondsds/bin/activate - ls -halR ~/.venv - which -a python cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DMELONDS_REPOSITORY_URL="${{ vars.MELONDS_REPOSITORY_URL }}" \ @@ -104,8 +102,8 @@ jobs: -DNDS_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_ROM }}" \ -DGBA_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_ROM }}" \ -DGBA_SRAM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_SRAM }}" \ - -DPython_EXECUTABLE:FILEPATH=~/.venv/melondsds/bin/python -DMELONDSDS_INTERNAL_VENV=OFF \ + -DPython_EXECUTABLE:FILEPATH=~/.venv/melondsds/bin/python \ ${{ inputs.cmake-args }} - name: Run Test Suite (Linux) From 86cc1ae5ef6ca2e365cb3079b37d1a3c7c8814db Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 20:37:52 -0400 Subject: [PATCH 150/234] Set `cache-dependency-path` --- .github/actions/test-deps/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/test-deps/action.yml b/.github/actions/test-deps/action.yml index 091c5549..300ef781 100644 --- a/.github/actions/test-deps/action.yml +++ b/.github/actions/test-deps/action.yml @@ -47,6 +47,7 @@ runs: with: python-version: '3.12' cache: 'pip' + cache-dependency-path: "${{ github.workspace }}/test/requirements.txt" - name: Install Python Dependencies shell: bash From 83b18090d142c43ff8be56e594099df2f73e5ca6 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 20:38:50 -0400 Subject: [PATCH 151/234] Call the `pip` inside the venv explicitly --- .github/actions/test-deps/action.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/test-deps/action.yml b/.github/actions/test-deps/action.yml index 300ef781..540b0faa 100644 --- a/.github/actions/test-deps/action.yml +++ b/.github/actions/test-deps/action.yml @@ -53,8 +53,7 @@ runs: shell: bash run: | python -m venv ~/.venv/melondsds - source ~/.venv/melondsds/bin/activate - pip install -r "${{ github.workspace }}/test/requirements.txt" + ~/.venv/melondsds/bin/pip install -r "${{ github.workspace }}/test/requirements.txt" - name: Download Test Files uses: actions/checkout@v4 From e73f25d286560489c8d257553fca699bfe297334 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 20:54:54 -0400 Subject: [PATCH 152/234] Don't use a venv on GitHub Actions --- .github/actions/test-deps/action.yml | 3 +-- .github/workflows/test.yaml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/actions/test-deps/action.yml b/.github/actions/test-deps/action.yml index 540b0faa..7e0afab1 100644 --- a/.github/actions/test-deps/action.yml +++ b/.github/actions/test-deps/action.yml @@ -52,8 +52,7 @@ runs: - name: Install Python Dependencies shell: bash run: | - python -m venv ~/.venv/melondsds - ~/.venv/melondsds/bin/pip install -r "${{ github.workspace }}/test/requirements.txt" + pip install -r "${{ github.workspace }}/test/requirements.txt" - name: Download Test Files uses: actions/checkout@v4 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5ad408b7..d10a633a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -102,8 +102,7 @@ jobs: -DNDS_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_ROM }}" \ -DGBA_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_ROM }}" \ -DGBA_SRAM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_SRAM }}" \ - -DMELONDSDS_INTERNAL_VENV=OFF \ - -DPython_EXECUTABLE:FILEPATH=~/.venv/melondsds/bin/python \ + -DMELONDSDS_INTERNAL_VENV=OFF ${{ inputs.cmake-args }} - name: Run Test Suite (Linux) From 3d75542bc81311c31c3ff34fd1ca4c145804c279 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sun, 2 Jun 2024 20:59:18 -0400 Subject: [PATCH 153/234] Whoops, forgot to remove the `source` call --- .github/workflows/test.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d10a633a..71d20abe 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -86,7 +86,6 @@ jobs: working-directory: "${{ env.BUILD_DIR }}" shell: bash run: | - source ~/.venv/melondsds/bin/activate cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DMELONDS_REPOSITORY_URL="${{ vars.MELONDS_REPOSITORY_URL }}" \ From e29a9ab208d74b90d5338ee0745dfecfc84d9cc4 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 3 Jun 2024 13:59:48 -0400 Subject: [PATCH 154/234] Verbose logging for pip --- .github/actions/test-deps/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/test-deps/action.yml b/.github/actions/test-deps/action.yml index 7e0afab1..fb83dd87 100644 --- a/.github/actions/test-deps/action.yml +++ b/.github/actions/test-deps/action.yml @@ -52,7 +52,7 @@ runs: - name: Install Python Dependencies shell: bash run: | - pip install -r "${{ github.workspace }}/test/requirements.txt" + pip install -v -r "${{ github.workspace }}/test/requirements.txt" - name: Download Test Files uses: actions/checkout@v4 From 0e7b9009cac64733db8cbc6d7ecffccd15c3977f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 3 Jun 2024 14:04:21 -0400 Subject: [PATCH 155/234] Simplify requirements.txt --- test/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/requirements.txt b/test/requirements.txt index bd33dd3f..c20c58ab 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,5 +1,5 @@ # melonDS DS doesn't support OpenGL on macOS, # so there's no need to install OpenGL dependencies +libretro.py==0.1.3 libretro.py[opengl]==0.1.3 ; sys_platform != "darwin" -libretro.py==0.1.3 ; sys_platform == "darwin" Pillow==10.3.0 \ No newline at end of file From fd6d86577df6ded2d609a7c725f60c734c63d892 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 3 Jun 2024 20:12:37 -0400 Subject: [PATCH 156/234] Install `libopengl0` on the Linux test runners - Should allow PyOpenGL to be imported safely --- .github/actions/test-deps/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/test-deps/action.yml b/.github/actions/test-deps/action.yml index fb83dd87..39562e04 100644 --- a/.github/actions/test-deps/action.yml +++ b/.github/actions/test-deps/action.yml @@ -26,13 +26,13 @@ runs: if: ${{ !env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} uses: awalsh128/cache-apt-pkgs-action@latest with: - packages: cmake p7zip-full xvfb xdg-utils x11-xserver-utils - version: 1.0 + packages: cmake libopengl0 libopengl-dev p7zip-full xvfb xdg-utils x11-xserver-utils + version: 1.1 - name: Install Dependencies (Linux x86_64/Android + act) if: ${{ env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} shell: bash - run: sudo apt-get update -qy && sudo apt-get install -qy cmake p7zip-full xvfb xdg-utils x11-xserver-utils + run: sudo apt-get update -qy && sudo apt-get install -qy cmake libopengl0 libopengl-dev p7zip-full xvfb xdg-utils x11-xserver-utils - name: Install MSYS2 Dependencies (Windows) uses: msys2/setup-msys2@v2 From d2d1ff586206e6ce5d48ecced17dbcffa76cd33e Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 3 Jun 2024 20:13:17 -0400 Subject: [PATCH 157/234] Log info when trying to import a module in FindPythonModules.cmake --- cmake/FindPythonModules.cmake | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/cmake/FindPythonModules.cmake b/cmake/FindPythonModules.cmake index cd940ce3..95753346 100644 --- a/cmake/FindPythonModules.cmake +++ b/cmake/FindPythonModules.cmake @@ -176,6 +176,7 @@ function (basis_find_python_module CACHEVAR) # 1. search specified paths foreach (P ${ARGN_PATH}) # ignore empty entries if (IS_ABSOLUTE "${P}") + message(DEBUG "Looking for ${ARGN_NAME} in ${P}") if (EXISTS "${P}/${ARGN_NAME}.py" OR EXISTS "${P}/${ARGN_NAME}/__init__.py" OR EXISTS "${P}/${ARGN_NAME}.pyc" OR EXISTS "${P}/${ARGN_NAME}/__init__.pyc") set_property (CACHE ${CACHEVAR} PROPERTY VALUE "${P}") @@ -187,6 +188,7 @@ function (basis_find_python_module CACHEVAR) if (ARGN_PYTHON_EXECUTABLE) set (IMPORT_SITE_ERROR FALSE) # 2a. try it with -E option -- the preferred way to run Python + message(DEBUG "Trying to import ${ARGN_NAME} using Python interpreter ${ARGN_PYTHON_EXECUTABLE}") execute_process ( COMMAND "${ARGN_PYTHON_EXECUTABLE}" -E -c "import ${ARGN_NAME}; print(${ARGN_NAME}.__file__)" RESULT_VARIABLE STATUS @@ -234,19 +236,26 @@ function (basis_find_python_module CACHEVAR) endif () set_property (CACHE ${CACHEVAR} PROPERTY VALUE "${P}") return () - elseif (IMPORT_SITE_ERROR) - message (WARNING "Import of site module failed when running Python interpreter ${ARGN_PYTHON_EXECUTABLE}" - " with and without -E option. Make sure that the Python interpreter is installed properly" - " and that the PYTHONHOME environment variable is either not set (recommended) or at" - " least set correctly for this Python installation. Maybe you need to enable this Python" - " version first somehow if more than one version of Python is installed on your system?" - " Otherwise, set PYTHON_EXECUTABLE to the right Python interpreter executable (python).") + else() + if (NOT STATUS EQUAL 0) + message(WARNING "${ERROR}") + endif () + + if (IMPORT_SITE_ERROR) + message (WARNING "Import of site module failed when running Python interpreter ${ARGN_PYTHON_EXECUTABLE}" + " with and without -E option. Make sure that the Python interpreter is installed properly" + " and that the PYTHONHOME environment variable is either not set (recommended) or at" + " least set correctly for this Python installation. Maybe you need to enable this Python" + " version first somehow if more than one version of Python is installed on your system?" + " Otherwise, set PYTHON_EXECUTABLE to the right Python interpreter executable (python).") + endif() endif () endif () # 3. search PYTHONPATH if (NOT ARGN_NO_PYTHONPATH) string (REPLACE ":" ";" PYTHONPATH "$ENV{PYTHONPATH}") foreach (P ${PYTHONPATH}) # ignore empty entries + message(DEBUG "Looking for ${ARGN_NAME} in ${P}") if (IS_ABSOLUTE "${P}") if (EXISTS "${P}/${ARGN_NAME}.py" OR EXISTS "${P}/${ARGN_NAME}/__init__.py" OR EXISTS "${P}/${ARGN_NAME}.pyc" OR EXISTS "${P}/${ARGN_NAME}/__init__.pyc") From d5df5dd49fc4584ce202737428fa74ee4a682bc0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 3 Jun 2024 20:13:56 -0400 Subject: [PATCH 158/234] Update libretro.py --- test/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/requirements.txt b/test/requirements.txt index c20c58ab..97173b96 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,5 +1,5 @@ # melonDS DS doesn't support OpenGL on macOS, # so there's no need to install OpenGL dependencies -libretro.py==0.1.3 -libretro.py[opengl]==0.1.3 ; sys_platform != "darwin" +libretro.py==0.1.6 +libretro.py[opengl]==0.1.6 ; sys_platform != "darwin" Pillow==10.3.0 \ No newline at end of file From 157f95ac7b0420b0cf69bc1bde6fcd848a6b14f3 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 3 Jun 2024 20:26:38 -0400 Subject: [PATCH 159/234] Re-enable the test suite on the Windows runners --- .github/workflows/main.yaml | 49 +++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 451289f0..94669f80 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -129,29 +129,30 @@ jobs: cmake-args: --toolchain ./cmake/toolchain/ios.toolchain.cmake -DPLATFORM=TVOS -DDEPLOYMENT_TARGET=14 $EXTRA_CMAKE_ARGS # Disabled OpenGL on tvOS due to https://github.com/JesseTG/melonds-ds/issues/23 - # Temporarily disabled until I finish writing that test program; - # emutest is too limiting - # test-windows: - # name: Test Suite (Windows) - # uses: ./.github/workflows/test.yaml - # needs: [ windows ] - # with: - # archive-name: melondsds_libretro-win32-x86_64 - # target: win32 - # runs-on: windows-latest - # shell: msys2 {0} - # secrets: - # TESTFILE_REPO_TOKEN: ${{ secrets.TESTFILE_REPO_TOKEN }} - # TESTFILE_REPO: ${{ secrets.TESTFILE_REPO }} - # DSI_NAND_ARCHIVE: ${{ secrets.DSI_NAND_ARCHIVE }} - # DSI_NAND: ${{ secrets.DSI_NAND }} - # ARM7_BIOS: ${{ secrets.ARM7_BIOS }} - # ARM9_BIOS: ${{ secrets.ARM9_BIOS }} - # ARM7_DSI_BIOS: ${{ secrets.ARM7_DSI_BIOS }} - # ARM9_DSI_BIOS: ${{ secrets.ARM9_DSI_BIOS }} - # NDS_FIRMWARE: ${{ secrets.NDS_FIRMWARE }} - # DSI_FIRMWARE: ${{ secrets.DSI_FIRMWARE }} - # NDS_ROM: ${{ secrets.NDS_ROM }} + test-windows: + name: Test Suite (Windows) + uses: ./.github/workflows/test.yaml + needs: [ windows ] + if: github.event_name == 'push' + with: + archive-name: melondsds_libretro-win32-x86_64 + target: win32 + runs-on: windows-latest + shell: msys2 {0} + secrets: + TESTFILE_REPO_TOKEN: ${{ secrets.TESTFILE_REPO_TOKEN }} + TESTFILE_REPO: ${{ secrets.TESTFILE_REPO }} + DSI_NAND_ARCHIVE: ${{ secrets.DSI_NAND_ARCHIVE }} + DSI_NAND: ${{ secrets.DSI_NAND }} + ARM7_BIOS: ${{ secrets.ARM7_BIOS }} + ARM9_BIOS: ${{ secrets.ARM9_BIOS }} + ARM7_DSI_BIOS: ${{ secrets.ARM7_DSI_BIOS }} + ARM9_DSI_BIOS: ${{ secrets.ARM9_DSI_BIOS }} + NDS_FIRMWARE: ${{ secrets.NDS_FIRMWARE }} + DSI_FIRMWARE: ${{ secrets.DSI_FIRMWARE }} + NDS_ROM: ${{ secrets.NDS_ROM }} + GBA_ROM: ${{ secrets.GBA_ROM }} + GBA_SRAM: ${{ secrets.GBA_SRAM }} test-linux-x86_64: name: Test Suite (Linux x86_64) @@ -179,7 +180,7 @@ jobs: create-release: name: Create Release - needs: [ windows, macos-x86_64, macos-arm64, linux-x86_64, linux-aarch64, android, ios, tvos, test-linux-x86_64 ] + needs: [ windows, macos-x86_64, macos-arm64, linux-x86_64, linux-aarch64, android, ios, tvos, test-linux-x86_64, test-windows ] if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: From ba09d6a41149dc617dcad80f9e44b3492311dc76 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 3 Jun 2024 20:52:38 -0400 Subject: [PATCH 160/234] Don't set the `MELONDS_` CMake variables --- .github/workflows/test.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 71d20abe..1d070d6f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -88,8 +88,6 @@ jobs: run: | cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ - -DMELONDS_REPOSITORY_URL="${{ vars.MELONDS_REPOSITORY_URL }}" \ - -DMELONDS_REPOSITORY_TAG="${{ vars.MELONDS_REPOSITORY_TAG }}" \ -DBUILD_TESTING=ON \ -DARM7_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM7_BIOS }}" \ -DARM9_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM9_BIOS }}" \ From af2ba9f73803c9216e3a5c88424c4992a8e7ebef Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 3 Jun 2024 23:08:05 -0400 Subject: [PATCH 161/234] Turn off `FETCHCONTENT_QUIET` in the test suite --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1d070d6f..77d05190 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -88,6 +88,7 @@ jobs: run: | cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ + -DFETCHCONTENT_QUIET=OFF \ -DBUILD_TESTING=ON \ -DARM7_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM7_BIOS }}" \ -DARM9_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM9_BIOS }}" \ From cf5c244039472c65f31244ead1f6c7f629dd03c4 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 19:11:46 -0400 Subject: [PATCH 162/234] Add `.git` to the melonDS dependency's URL - I wonder if this is somehow making `git` choke on the Windows runner? --- cmake/FetchDependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FetchDependencies.cmake b/cmake/FetchDependencies.cmake index a82e4e88..fd19b459 100644 --- a/cmake/FetchDependencies.cmake +++ b/cmake/FetchDependencies.cmake @@ -37,7 +37,7 @@ function(fetch_dependency name default_url default_tag) FetchContent_GetProperties(${name}) endfunction() -fetch_dependency(melonDS "https://github.com/melonDS-emu/melonDS" "d48e5f2") +fetch_dependency(melonDS "https://github.com/melonDS-emu/melonDS.git" "d48e5f2") fetch_dependency("libretro-common" "https://github.com/libretro/libretro-common.git" "fce57fd") fetch_dependency("embed-binaries" "https://github.com/andoalon/embed-binaries.git" "21f28ca") fetch_dependency(glm "https://github.com/g-truc/glm" "33b0eb9") From 316fed16b6607111f94f16e77a9dd57d54ae178c Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 19:32:03 -0400 Subject: [PATCH 163/234] Clone `libretro-common` first --- cmake/FetchDependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FetchDependencies.cmake b/cmake/FetchDependencies.cmake index fd19b459..842de042 100644 --- a/cmake/FetchDependencies.cmake +++ b/cmake/FetchDependencies.cmake @@ -37,8 +37,8 @@ function(fetch_dependency name default_url default_tag) FetchContent_GetProperties(${name}) endfunction() -fetch_dependency(melonDS "https://github.com/melonDS-emu/melonDS.git" "d48e5f2") fetch_dependency("libretro-common" "https://github.com/libretro/libretro-common.git" "fce57fd") +fetch_dependency(melonDS "https://github.com/melonDS-emu/melonDS.git" "d48e5f2") fetch_dependency("embed-binaries" "https://github.com/andoalon/embed-binaries.git" "21f28ca") fetch_dependency(glm "https://github.com/g-truc/glm" "33b0eb9") fetch_dependency(libslirp "https://github.com/JesseTG/libslirp-mirror.git" "44e7877") From 64e30a5ced5fbc471e7dd7af4b9873e0ecb7b390 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 19:44:36 -0400 Subject: [PATCH 164/234] Revert the last two changes --- cmake/FetchDependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FetchDependencies.cmake b/cmake/FetchDependencies.cmake index 842de042..a82e4e88 100644 --- a/cmake/FetchDependencies.cmake +++ b/cmake/FetchDependencies.cmake @@ -37,8 +37,8 @@ function(fetch_dependency name default_url default_tag) FetchContent_GetProperties(${name}) endfunction() +fetch_dependency(melonDS "https://github.com/melonDS-emu/melonDS" "d48e5f2") fetch_dependency("libretro-common" "https://github.com/libretro/libretro-common.git" "fce57fd") -fetch_dependency(melonDS "https://github.com/melonDS-emu/melonDS.git" "d48e5f2") fetch_dependency("embed-binaries" "https://github.com/andoalon/embed-binaries.git" "21f28ca") fetch_dependency(glm "https://github.com/g-truc/glm" "33b0eb9") fetch_dependency(libslirp "https://github.com/JesseTG/libslirp-mirror.git" "44e7877") From 71a9d391323c3336304e4678c2401b892d0c401f Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 19:49:51 -0400 Subject: [PATCH 165/234] Remove the `set-extra-args` job - Originally intended for canary builds against standalone melonDS's `master` branch - I changed my mind about doing that, as API compatibility isn't guaranteed anyway --- .github/workflows/main.yaml | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 94669f80..d42827de 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -17,22 +17,9 @@ on: - ".gitignore" - "*.gitlab-ci.yml" jobs: - set-extra-args: - name: Set Extra CMake Arguments - runs-on: ubuntu-latest - steps: - - name: Set Canary CMake Args - run: | - # Set CMake arguments that are only relevant in certain builds; - # in this case, scheduled builds will always use the latest upstream master - if [[ $GITHUB_EVENT_NAME = 'workflow_dispatch' || $GITHUB_EVENT_NAME = 'schedule' ]]; then - echo "EXTRA_CMAKE_ARGS='-DMELONDS_REPOSITORY_URL=https://github.com/melonDS-emu/melonDS.git -DMELONDS_REPOSITORY_TAG=master'" >> "$GITHUB_ENV" - fi - windows: name: Windows (x86_64) uses: ./.github/workflows/build.yaml - needs: [ set-extra-args ] with: archive-name: melondsds_libretro-win32-x86_64 runs-on: windows-latest @@ -41,92 +28,84 @@ jobs: lib-ext: dll shell: msys2 {0} test-suite: true - cmake-args: -DENABLE_SCCACHE=ON -DSCCACHE="C:/Users/runneradmin/.cargo/bin/sccache.exe" $EXTRA_CMAKE_ARGS + cmake-args: -DENABLE_SCCACHE=ON -DSCCACHE="C:/Users/runneradmin/.cargo/bin/sccache.exe" macos-x86_64: name: macOS (x86_64) uses: ./.github/workflows/build.yaml - needs: [ set-extra-args ] with: archive-name: melondsds_libretro-macos-x86_64 runs-on: macos-latest target: macos-x86_64 lib-ext: dylib - cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="x86_64" -DENABLE_OGLRENDERER=OFF -DCMAKE_APPLE_SILICON_PROCESSOR="x86_64" $EXTRA_CMAKE_ARGS + cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="x86_64" -DENABLE_OGLRENDERER=OFF -DCMAKE_APPLE_SILICON_PROCESSOR="x86_64" # Disabled OpenGL on macOS due to https://github.com/JesseTG/melonds-ds/issues/12 macos-arm64: name: macOS (arm64) uses: ./.github/workflows/build.yaml - needs: [ set-extra-args ] with: archive-name: melondsds_libretro-macos-arm64 runs-on: macos-latest target: macos-arm64 lib-ext: dylib - cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="arm64" -DENABLE_OGLRENDERER=OFF -DCMAKE_APPLE_SILICON_PROCESSOR="arm64" $EXTRA_CMAKE_ARGS + cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="arm64" -DENABLE_OGLRENDERER=OFF -DCMAKE_APPLE_SILICON_PROCESSOR="arm64" # Disabled OpenGL on macOS due to https://github.com/JesseTG/melonds-ds/issues/12 linux-x86_64: name: Linux (x86_64) uses: ./.github/workflows/build.yaml - needs: [ set-extra-args ] with: archive-name: melondsds_libretro-linux-x86_64 target: linux-x86_64 runs-on: ubuntu-latest lib-ext: so test-suite: true - cmake-args: $EXTRA_CMAKE_ARGS linux-aarch64: name: Linux (arm64) uses: ./.github/workflows/build.yaml - needs: [ set-extra-args ] with: archive-name: melondsds_libretro-linux-arm64 target: linux-aarch64 runs-on: ubuntu-latest lib-ext: so - cmake-args: -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config $EXTRA_CMAKE_ARGS + cmake-args: -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config android: name: Android uses: ./.github/workflows/build.yaml - needs: [ set-extra-args ] with: lib-ext: so target: android core-name: melondsds_libretro_android archive-name: melondsds_libretro-android - cmake-args: -DENABLE_OGLRENDERER=OFF -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=24 -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK/build/cmake/android.toolchain.cmake" $EXTRA_CMAKE_ARGS + cmake-args: -DENABLE_OGLRENDERER=OFF -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=24 -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK/build/cmake/android.toolchain.cmake" runs-on: ubuntu-latest # Disabled OpenGL on Android due to https://github.com/JesseTG/melonds-ds/issues/23 ios: name: iOS uses: ./.github/workflows/build.yaml - needs: [ set-extra-args ] with: runs-on: macos-latest target: ios archive-name: melondsds_libretro-ios info-dir: info lib-ext: dylib - cmake-args: --toolchain ./cmake/toolchain/ios.toolchain.cmake -DPLATFORM=OS64 -DDEPLOYMENT_TARGET=14 $EXTRA_CMAKE_ARGS + cmake-args: --toolchain ./cmake/toolchain/ios.toolchain.cmake -DPLATFORM=OS64 -DDEPLOYMENT_TARGET=14 # Disabled OpenGL on iOS due to https://github.com/JesseTG/melonds-ds/issues/23 tvos: name: tvOS uses: ./.github/workflows/build.yaml - needs: [ set-extra-args ] with: runs-on: macos-latest target: tvos archive-name: melondsds_libretro-tvos info-dir: info lib-ext: dylib - cmake-args: --toolchain ./cmake/toolchain/ios.toolchain.cmake -DPLATFORM=TVOS -DDEPLOYMENT_TARGET=14 $EXTRA_CMAKE_ARGS + cmake-args: --toolchain ./cmake/toolchain/ios.toolchain.cmake -DPLATFORM=TVOS -DDEPLOYMENT_TARGET=14 # Disabled OpenGL on tvOS due to https://github.com/JesseTG/melonds-ds/issues/23 test-windows: From cdb35c299a3f5dad8f8f3dd8983ead918c1122f4 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 20:30:38 -0400 Subject: [PATCH 166/234] Consolidate the build, test, and dependency workflows --- .github/actions/deps/action.yml | 55 ---------- .github/actions/test-deps/action.yml | 67 ------------ .github/workflows/build.yaml | 146 ++++++++++++++++++++++++--- .github/workflows/main.yaml | 80 ++++++--------- .github/workflows/test.yaml | 115 --------------------- 5 files changed, 160 insertions(+), 303 deletions(-) delete mode 100644 .github/actions/deps/action.yml delete mode 100644 .github/actions/test-deps/action.yml delete mode 100644 .github/workflows/test.yaml diff --git a/.github/actions/deps/action.yml b/.github/actions/deps/action.yml deleted file mode 100644 index 3229c1a8..00000000 --- a/.github/actions/deps/action.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Install Dependencies -description: Install Dependencies -inputs: - target: - description: Platform target - required: true - -runs: - using: composite - steps: - - name: Install MSYS2 Dependencies (Windows) - uses: msys2/setup-msys2@v2 - if: ${{ runner.os == 'Windows' }} - with: - msystem: MINGW64 - update: true - install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake - - - name: Install Dependencies (macOS) - if: ${{ runner.os == 'macOS' }} - shell: bash - run: brew install pkg-config cmake ninja ccache - - # Skip this step on act due to https://github.com/nektos/act/issues/1849 - - name: Install Dependencies (Linux x86_64/Android) - if: ${{ !env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} - uses: awalsh128/cache-apt-pkgs-action@6460a33c29e99ddc3c9f61ebd04653105b260906 - with: - packages: cmake ninja-build libepoxy-dev ccache - version: 1.1 - - - name: Install Dependencies (Linux x86_64/Android + act) - if: ${{ env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} - shell: bash - run: sudo apt-get update -qy && sudo apt-get install -qy libepoxy-dev cmake ninja-build libepoxy-dev ccache - - - name: Install Cross-Compile Support (Linux aarch64) - if: ${{ runner.os == 'Linux' && contains(inputs.target, 'aarch64') }} - uses: cyberjunk/gha-ubuntu-cross@v4 - with: - arch: arm64 - - - name: Install Dependencies (Linux aarch64) - if: ${{ runner.os == 'Linux' && contains(inputs.target, 'aarch64') }} - uses: awalsh128/cache-apt-pkgs-action@6460a33c29e99ddc3c9f61ebd04653105b260906 - with: - packages: cmake ninja-build libepoxy-dev:arm64 ccache - version: 1.1 - - - name: Set Up CCache - uses: hendrikmuhs/ccache-action@v1.2.11 - with: - key: ${{ inputs.target }}-${{ matrix.build-type }} - variant: ${{ runner.os == 'Windows' && 'sccache' || 'ccache' }} # Equivalent to (runner.os == 'Windows') ? 'sccache' : 'ccache' - # Using sccache on Windows due to https://github.com/hendrikmuhs/ccache-action/issues/112 diff --git a/.github/actions/test-deps/action.yml b/.github/actions/test-deps/action.yml deleted file mode 100644 index 39562e04..00000000 --- a/.github/actions/test-deps/action.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Install Test Suite Dependencies -description: Install Test Suite Dependencies -inputs: - target: - description: Platform target - required: true - testfile-dir: - description: Directory containing the test files - required: false - default: "testfiles" - testfile-repo: - description: Repository containing the test files - required: true - testfile-repo-token: - description: Token for the repository containing the test files - required: true - dsi-nand-archive: - description: Name of the DSI NAND archive - required: true - -runs: - using: composite - steps: - # Skip this step on act due to https://github.com/nektos/act/issues/1849 - - name: Install Dependencies (Linux x86_64/Android) - if: ${{ !env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: cmake libopengl0 libopengl-dev p7zip-full xvfb xdg-utils x11-xserver-utils - version: 1.1 - - - name: Install Dependencies (Linux x86_64/Android + act) - if: ${{ env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} - shell: bash - run: sudo apt-get update -qy && sudo apt-get install -qy cmake libopengl0 libopengl-dev p7zip-full xvfb xdg-utils x11-xserver-utils - - - name: Install MSYS2 Dependencies (Windows) - uses: msys2/setup-msys2@v2 - if: ${{ runner.os == 'Windows' }} - with: - msystem: MINGW64 - update: true - install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake - - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - cache: 'pip' - cache-dependency-path: "${{ github.workspace }}/test/requirements.txt" - - - name: Install Python Dependencies - shell: bash - run: | - pip install -v -r "${{ github.workspace }}/test/requirements.txt" - - - name: Download Test Files - uses: actions/checkout@v4 - with: - repository: "${{ inputs.testfile-repo }}" - token: "${{ inputs.testfile-repo-token }}" - path: "${{ inputs.testfile-dir }}" - - - name: Prepare Test Files - working-directory: "${{ inputs.testfile-dir }}" - shell: bash - run: 7z x "${{ inputs.dsi-nand-archive }}" diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9bb57122..142fffa7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,6 +1,33 @@ name: Build (Internal) on: workflow_call: + secrets: + TESTFILE_REPO_TOKEN: + required: false + TESTFILE_REPO: + required: false + ARM7_BIOS: + required: false + ARM9_BIOS: + required: false + ARM7_DSI_BIOS: + required: false + ARM9_DSI_BIOS: + required: false + NDS_FIRMWARE: + required: false + DSI_FIRMWARE: + required: false + NDS_ROM: + required: false + GBA_ROM: + required: false + GBA_SRAM: + required: false + DSI_NAND_ARCHIVE: + required: false + DSI_NAND: + required: false inputs: cmake-args: description: Additional arguments to pass to CMake. @@ -60,6 +87,7 @@ on: env: BUILD_DIR: "${{ github.workspace }}/${{ inputs.build-dir }}" DIST_DIR: "${{ github.workspace }}/${{ inputs.dist-dir }}" + TESTFILE_DIR: "${{ github.workspace }}/testfiles" jobs: build: strategy: @@ -81,21 +109,111 @@ jobs: with: build-directory: ${{ env.BUILD_DIR }} - - name: Install Dependencies - uses: ./.github/actions/deps + - name: Install MSYS2 Dependencies (Windows) + uses: msys2/setup-msys2@v2 + if: ${{ runner.os == 'Windows' }} + with: + msystem: MINGW64 + update: true + install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake + + - name: Install Dependencies (macOS) + if: ${{ runner.os == 'macOS' }} + shell: bash + run: brew install pkg-config cmake ninja ccache + + # Skip this step on act due to https://github.com/nektos/act/issues/1849 + - name: Install Dependencies (Linux x86_64/Android) + if: ${{ !env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} + uses: awalsh128/cache-apt-pkgs-action@6460a33c29e99ddc3c9f61ebd04653105b260906 with: - target: ${{ inputs.target }} + packages: ccache cmake libepoxy-dev libopengl0 libopengl-dev ninja-build p7zip-full x11-xserver-utils xdg-utils xvfb + version: 1.2 + + - name: Install Dependencies (Linux x86_64/Android + act) + if: ${{ env.ACT && runner.os == 'Linux' && !contains(inputs.target, 'aarch64') }} + shell: bash + run: sudo apt-get update -qy && sudo apt-get install -qy ccache cmake libepoxy-dev libopengl0 libopengl-dev ninja-build p7zip-full x11-xserver-utils xdg-utils xvfb + + - name: Install Cross-Compile Support (Linux aarch64) + if: ${{ runner.os == 'Linux' && contains(inputs.target, 'aarch64') }} + uses: cyberjunk/gha-ubuntu-cross@v4 + with: + arch: arm64 + + - name: Install Dependencies (Linux aarch64) + if: ${{ runner.os == 'Linux' && contains(inputs.target, 'aarch64') }} + uses: awalsh128/cache-apt-pkgs-action@6460a33c29e99ddc3c9f61ebd04653105b260906 + with: + packages: cmake ninja-build libepoxy-dev:arm64 ccache + version: 1.1 + + - name: Install CCache + uses: hendrikmuhs/ccache-action@v1.2.11 + with: + key: ${{ inputs.target }}-${{ matrix.build-type }} + variant: ${{ runner.os == 'Windows' && 'sccache' || 'ccache' }} # Equivalent to (runner.os == 'Windows') ? 'sccache' : 'ccache' + # Using sccache on Windows due to https://github.com/hendrikmuhs/ccache-action/issues/112 + + - name: Install Python + if: ${{ inputs.test-suite }} + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: "${{ github.workspace }}/test/requirements.txt" + + - name: Install Python Dependencies + if: ${{ inputs.test-suite }} + shell: bash + run: | + pip install -v -r "${{ github.workspace }}/test/requirements.txt" + + - name: Download Test Files + if: ${{ inputs.test-suite }} + uses: actions/checkout@v4 + with: + repository: "${{ secrets.TESTFILE_REPO }}" + token: "${{ secrets.TESTFILE_REPO_TOKEN }}" + path: "${{ inputs.testfile-dir }}" + + - name: Prepare Test Files + if: ${{ inputs.test-suite }} + working-directory: "${{ inputs.testfile-dir }}" + shell: bash + run: 7z x "${{ secrets.DSI_NAND_ARCHIVE }}" - name: Create build environment run: mkdir -vp "${{ env.BUILD_DIR }}" - name: Configure working-directory: "${{ env.BUILD_DIR }}" + if: ${{ ! inputs.test-suite }} run: | cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ ${{ inputs.cmake-args }} + - name: Configure (Test Suite) + working-directory: "${{ env.BUILD_DIR }}" + if: ${{ ! inputs.test-suite }} + run: | + cmake "${{ github.workspace }}" \ + -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ + -DBUILD_TESTING=ON \ + -DARM7_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM7_BIOS }}" \ + -DARM9_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM9_BIOS }}" \ + -DARM7_DSI_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM7_DSI_BIOS }}" \ + -DARM9_DSI_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM9_DSI_BIOS }}" \ + -DNDS_FIRMWARE="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_FIRMWARE }}" \ + -DDSI_FIRMWARE="${{ env.TESTFILE_DIR }}/${{ secrets.DSI_FIRMWARE }}" \ + -DDSI_NAND="${{ env.TESTFILE_DIR }}/${{ secrets.DSI_NAND }}" \ + -DNDS_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_ROM }}" \ + -DGBA_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_ROM }}" \ + -DGBA_SRAM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_SRAM }}" \ + -DMELONDSDS_INTERNAL_VENV=OFF + ${{ inputs.cmake-args }} + - name: Build working-directory: "${{ env.BUILD_DIR }}" run: cmake --build "${{ env.BUILD_DIR }}" --parallel @@ -117,17 +235,13 @@ jobs: if-no-files-found: error path: "${{ env.DIST_DIR }}" - - name: Remove Extra Artifacts - if: ${{ matrix.build-type == 'Debug' && inputs.test-suite }} - run: | - rm -rf "${{ env.BUILD_DIR }}/_deps" "${{ env.BUILD_DIR }}/src/libretro/CMakeFiles" - # Remove various files that we won't need to pass to the test suite + - name: Run Test Suite (Linux) + if: ${{ inputs.test-suite && runner.os == 'Linux' }} + working-directory: "${{ env.BUILD_DIR }}" + run: xvfb-run ctest --extra-verbose --exclude-regex example --output-on-failure - - name: Upload CMake Build Directory - uses: actions/upload-artifact@v3 - if: ${{ matrix.build-type == 'Debug' && inputs.test-suite }} - with: - name: "zzz-not-for-players-cmake-build-${{ inputs.archive-name }}-${{ matrix.build-type }}" - # So no one tries to download this on nightly.link - if-no-files-found: error - path: "${{ env.BUILD_DIR }}" \ No newline at end of file + - name: Run Test Suite (Windows) + if: ${{ inputs.test-suite && runner.os == 'Windows' }} + working-directory: "${{ env.BUILD_DIR }}" + shell: msys2 {0} + run: ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index d42827de..163c330d 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -29,6 +29,20 @@ jobs: shell: msys2 {0} test-suite: true cmake-args: -DENABLE_SCCACHE=ON -DSCCACHE="C:/Users/runneradmin/.cargo/bin/sccache.exe" + secrets: + TESTFILE_REPO_TOKEN: ${{ secrets.TESTFILE_REPO_TOKEN }} + TESTFILE_REPO: ${{ secrets.TESTFILE_REPO }} + DSI_NAND_ARCHIVE: ${{ secrets.DSI_NAND_ARCHIVE }} + DSI_NAND: ${{ secrets.DSI_NAND }} + ARM7_BIOS: ${{ secrets.ARM7_BIOS }} + ARM9_BIOS: ${{ secrets.ARM9_BIOS }} + ARM7_DSI_BIOS: ${{ secrets.ARM7_DSI_BIOS }} + ARM9_DSI_BIOS: ${{ secrets.ARM9_DSI_BIOS }} + NDS_FIRMWARE: ${{ secrets.NDS_FIRMWARE }} + DSI_FIRMWARE: ${{ secrets.DSI_FIRMWARE }} + NDS_ROM: ${{ secrets.NDS_ROM }} + GBA_ROM: ${{ secrets.GBA_ROM }} + GBA_SRAM: ${{ secrets.GBA_SRAM }} macos-x86_64: name: macOS (x86_64) @@ -61,6 +75,20 @@ jobs: runs-on: ubuntu-latest lib-ext: so test-suite: true + secrets: + TESTFILE_REPO_TOKEN: ${{ secrets.TESTFILE_REPO_TOKEN }} + TESTFILE_REPO: ${{ secrets.TESTFILE_REPO }} + DSI_NAND_ARCHIVE: ${{ secrets.DSI_NAND_ARCHIVE }} + DSI_NAND: ${{ secrets.DSI_NAND }} + ARM7_BIOS: ${{ secrets.ARM7_BIOS }} + ARM9_BIOS: ${{ secrets.ARM9_BIOS }} + ARM7_DSI_BIOS: ${{ secrets.ARM7_DSI_BIOS }} + ARM9_DSI_BIOS: ${{ secrets.ARM9_DSI_BIOS }} + NDS_FIRMWARE: ${{ secrets.NDS_FIRMWARE }} + DSI_FIRMWARE: ${{ secrets.DSI_FIRMWARE }} + NDS_ROM: ${{ secrets.NDS_ROM }} + GBA_ROM: ${{ secrets.GBA_ROM }} + GBA_SRAM: ${{ secrets.GBA_SRAM }} linux-aarch64: name: Linux (arm64) @@ -108,58 +136,9 @@ jobs: cmake-args: --toolchain ./cmake/toolchain/ios.toolchain.cmake -DPLATFORM=TVOS -DDEPLOYMENT_TARGET=14 # Disabled OpenGL on tvOS due to https://github.com/JesseTG/melonds-ds/issues/23 - test-windows: - name: Test Suite (Windows) - uses: ./.github/workflows/test.yaml - needs: [ windows ] - if: github.event_name == 'push' - with: - archive-name: melondsds_libretro-win32-x86_64 - target: win32 - runs-on: windows-latest - shell: msys2 {0} - secrets: - TESTFILE_REPO_TOKEN: ${{ secrets.TESTFILE_REPO_TOKEN }} - TESTFILE_REPO: ${{ secrets.TESTFILE_REPO }} - DSI_NAND_ARCHIVE: ${{ secrets.DSI_NAND_ARCHIVE }} - DSI_NAND: ${{ secrets.DSI_NAND }} - ARM7_BIOS: ${{ secrets.ARM7_BIOS }} - ARM9_BIOS: ${{ secrets.ARM9_BIOS }} - ARM7_DSI_BIOS: ${{ secrets.ARM7_DSI_BIOS }} - ARM9_DSI_BIOS: ${{ secrets.ARM9_DSI_BIOS }} - NDS_FIRMWARE: ${{ secrets.NDS_FIRMWARE }} - DSI_FIRMWARE: ${{ secrets.DSI_FIRMWARE }} - NDS_ROM: ${{ secrets.NDS_ROM }} - GBA_ROM: ${{ secrets.GBA_ROM }} - GBA_SRAM: ${{ secrets.GBA_SRAM }} - - test-linux-x86_64: - name: Test Suite (Linux x86_64) - uses: ./.github/workflows/test.yaml - needs: [ linux-x86_64 ] - if: github.event_name == 'push' - with: - archive-name: melondsds_libretro-linux-x86_64 - target: linux-x86_64 - runs-on: ubuntu-latest - secrets: - TESTFILE_REPO_TOKEN: ${{ secrets.TESTFILE_REPO_TOKEN }} - TESTFILE_REPO: ${{ secrets.TESTFILE_REPO }} - DSI_NAND_ARCHIVE: ${{ secrets.DSI_NAND_ARCHIVE }} - DSI_NAND: ${{ secrets.DSI_NAND }} - ARM7_BIOS: ${{ secrets.ARM7_BIOS }} - ARM9_BIOS: ${{ secrets.ARM9_BIOS }} - ARM7_DSI_BIOS: ${{ secrets.ARM7_DSI_BIOS }} - ARM9_DSI_BIOS: ${{ secrets.ARM9_DSI_BIOS }} - NDS_FIRMWARE: ${{ secrets.NDS_FIRMWARE }} - DSI_FIRMWARE: ${{ secrets.DSI_FIRMWARE }} - NDS_ROM: ${{ secrets.NDS_ROM }} - GBA_ROM: ${{ secrets.GBA_ROM }} - GBA_SRAM: ${{ secrets.GBA_SRAM }} - create-release: name: Create Release - needs: [ windows, macos-x86_64, macos-arm64, linux-x86_64, linux-aarch64, android, ios, tvos, test-linux-x86_64, test-windows ] + needs: [ windows, macos-x86_64, macos-arm64, linux-x86_64, linux-aarch64, android, ios, tvos ] if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: @@ -196,6 +175,7 @@ jobs: with: name: melondsds_libretro.info path: artifact/melondsds_libretro-linux-x86_64-Release/cores/melondsds_libretro.info + # The .info file doesn't vary by platform, so we only need to upload one - name: Create Release if: "${{ steps.changelog.outputs.version != steps.newest-tag.outputs.version }}" uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index 77d05190..00000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,115 +0,0 @@ -name: Test Suite (Internal) -on: - workflow_call: - secrets: - TESTFILE_REPO_TOKEN: - required: true - TESTFILE_REPO: - required: true - ARM7_BIOS: - required: true - ARM9_BIOS: - required: true - ARM7_DSI_BIOS: - required: true - ARM9_DSI_BIOS: - required: true - NDS_FIRMWARE: - required: true - DSI_FIRMWARE: - required: true - NDS_ROM: - required: true - GBA_ROM: - required: true - GBA_SRAM: - required: true - DSI_NAND_ARCHIVE: - required: true - DSI_NAND: - required: true - inputs: - cmake-args: - description: Additional arguments to pass to CMake. - type: string - required: false - runs-on: - description: The platform on which the workflow will be run. Passed directly to jobs..runs-on. - type: string - required: true - shell: - description: The shell to use for running commands. Passed directly to jobs..defaults.run.shell. - type: string - required: false - default: bash - build-dir: - description: The directory in which to build the project. - type: string - required: false - default: "build" - archive-name: - description: The name of the archive to upload. - type: string - required: true - target: - description: The platform target. - type: string - required: true -env: - BUILD_DIR: "${{ github.workspace }}/${{ inputs.build-dir }}" - TESTFILE_DIR: "${{ github.workspace }}/testfiles" -jobs: - test: - runs-on: ${{ inputs.runs-on }} - defaults: - run: - shell: ${{ inputs.shell }} - steps: - - name: Check Out Source - uses: actions/checkout@v4 - - - name: Install Dependencies - uses: ./.github/actions/test-deps - with: - target: ${{ inputs.target }} - testfile-repo: ${{ secrets.TESTFILE_REPO }} - testfile-repo-token: ${{ secrets.TESTFILE_REPO_TOKEN }} - dsi-nand-archive: ${{ secrets.DSI_NAND_ARCHIVE }} - - - name: Download Artifact - uses: actions/download-artifact@v3 - with: - name: "zzz-not-for-players-cmake-build-${{ inputs.archive-name }}-Debug" - path: "${{ env.BUILD_DIR }}" - - - name: Configure Test Suite - working-directory: "${{ env.BUILD_DIR }}" - shell: bash - run: | - cmake "${{ github.workspace }}" \ - -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ - -DFETCHCONTENT_QUIET=OFF \ - -DBUILD_TESTING=ON \ - -DARM7_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM7_BIOS }}" \ - -DARM9_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM9_BIOS }}" \ - -DARM7_DSI_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM7_DSI_BIOS }}" \ - -DARM9_DSI_BIOS="${{ env.TESTFILE_DIR }}/${{ secrets.ARM9_DSI_BIOS }}" \ - -DNDS_FIRMWARE="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_FIRMWARE }}" \ - -DDSI_FIRMWARE="${{ env.TESTFILE_DIR }}/${{ secrets.DSI_FIRMWARE }}" \ - -DDSI_NAND="${{ env.TESTFILE_DIR }}/${{ secrets.DSI_NAND }}" \ - -DNDS_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_ROM }}" \ - -DGBA_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_ROM }}" \ - -DGBA_SRAM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_SRAM }}" \ - -DMELONDSDS_INTERNAL_VENV=OFF - ${{ inputs.cmake-args }} - - - name: Run Test Suite (Linux) - if: ${{ runner.os == 'Linux' }} - working-directory: "${{ env.BUILD_DIR }}" - run: xvfb-run ctest --extra-verbose --exclude-regex example --output-on-failure - - - name: Run Test Suite (Windows) - if: ${{ runner.os == 'Windows' }} - working-directory: "${{ env.BUILD_DIR }}" - shell: msys2 {0} - run: ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From fc012400c8222388863061a0ae5fb6576336dff0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 20:35:56 -0400 Subject: [PATCH 167/234] Fix an incorrect `if` --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 142fffa7..9a9af96e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -196,7 +196,7 @@ jobs: - name: Configure (Test Suite) working-directory: "${{ env.BUILD_DIR }}" - if: ${{ ! inputs.test-suite }} + if: ${{ inputs.test-suite }} run: | cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ From 715f5d0e89bfc8ecffb9f5265b228695870d2bef Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 21:02:43 -0400 Subject: [PATCH 168/234] Forgot a backslash --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9a9af96e..7ebc2aba 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -211,7 +211,7 @@ jobs: -DNDS_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.NDS_ROM }}" \ -DGBA_ROM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_ROM }}" \ -DGBA_SRAM="${{ env.TESTFILE_DIR }}/${{ secrets.GBA_SRAM }}" \ - -DMELONDSDS_INTERNAL_VENV=OFF + -DMELONDSDS_INTERNAL_VENV=OFF \ ${{ inputs.cmake-args }} - name: Build From b8817251700d2142e39c0128d3ddf40a9db2d41a Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 21:02:52 -0400 Subject: [PATCH 169/234] Clarify a step's name --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7ebc2aba..9e23e4c1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -194,7 +194,7 @@ jobs: -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ ${{ inputs.cmake-args }} - - name: Configure (Test Suite) + - name: Configure (With Test Suite) working-directory: "${{ env.BUILD_DIR }}" if: ${{ inputs.test-suite }} run: | From 7302a448203a78f15fe35c4fd170fe75be77beb3 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 21:12:42 -0400 Subject: [PATCH 170/234] Force bash for the configure step --- .github/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9e23e4c1..a749f71a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -189,6 +189,7 @@ jobs: - name: Configure working-directory: "${{ env.BUILD_DIR }}" if: ${{ ! inputs.test-suite }} + shell: bash run: | cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ @@ -197,6 +198,7 @@ jobs: - name: Configure (With Test Suite) working-directory: "${{ env.BUILD_DIR }}" if: ${{ inputs.test-suite }} + shell: bash run: | cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ From c4beabb1af1d2077dc631b3e2ecde08eb2f536e7 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 21:14:50 -0400 Subject: [PATCH 171/234] What's going on here? --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a749f71a..95eeccc1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -200,6 +200,7 @@ jobs: if: ${{ inputs.test-suite }} shell: bash run: | + ls -hal "${{ github.workspace }}" cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DBUILD_TESTING=ON \ From 24305bcb4dd68ec7e5b6d7554473996a0913fcc0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 21:23:24 -0400 Subject: [PATCH 172/234] Whoops, I screwed up the variables --- .github/workflows/build.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 95eeccc1..f74fbf1c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -175,11 +175,11 @@ jobs: with: repository: "${{ secrets.TESTFILE_REPO }}" token: "${{ secrets.TESTFILE_REPO_TOKEN }}" - path: "${{ inputs.testfile-dir }}" + path: "testfiles" - name: Prepare Test Files if: ${{ inputs.test-suite }} - working-directory: "${{ inputs.testfile-dir }}" + working-directory: "${{ github.workspace }}/testfiles" shell: bash run: 7z x "${{ secrets.DSI_NAND_ARCHIVE }}" @@ -200,7 +200,6 @@ jobs: if: ${{ inputs.test-suite }} shell: bash run: | - ls -hal "${{ github.workspace }}" cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ -DBUILD_TESTING=ON \ From d9308c29246802a2f45446a01c9ec3f831ddb5d9 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 22:05:24 -0400 Subject: [PATCH 173/234] Don't force `bash` --- .github/workflows/build.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f74fbf1c..dee9d072 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -189,7 +189,6 @@ jobs: - name: Configure working-directory: "${{ env.BUILD_DIR }}" if: ${{ ! inputs.test-suite }} - shell: bash run: | cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ @@ -198,7 +197,6 @@ jobs: - name: Configure (With Test Suite) working-directory: "${{ env.BUILD_DIR }}" if: ${{ inputs.test-suite }} - shell: bash run: | cmake "${{ github.workspace }}" \ -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ From b327a8885fb74a6363420ccd2c92003807703992 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 22:25:55 -0400 Subject: [PATCH 174/234] Install `mingw-w64-x86_64-mesa` on the Windows runners --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index dee9d072..b5a1f1aa 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -115,7 +115,7 @@ jobs: with: msystem: MINGW64 update: true - install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake + install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-mesa - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} From 25646589a25be4d23297b5cf2ddce1e5825199ca Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 22:43:39 -0400 Subject: [PATCH 175/234] Follow the moderngl instructions - https://moderngl.readthedocs.io/en/5.8.2/install/installation.html#using-with-mesa-3d-on-windows --- .github/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b5a1f1aa..5dae17c4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -244,4 +244,6 @@ jobs: if: ${{ inputs.test-suite && runner.os == 'Windows' }} working-directory: "${{ env.BUILD_DIR }}" shell: msys2 {0} + env: + GLCONTEXT_WIN_LIBGL: "C:\msys64\mingw64\bin\opengl32.dll" run: ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 9ed7be74fda44e68f3ab24fafdf4d29bc7e34d53 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 4 Jun 2024 22:44:44 -0400 Subject: [PATCH 176/234] Forward slashes - Motherfucker --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5dae17c4..acae1b0b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -245,5 +245,5 @@ jobs: working-directory: "${{ env.BUILD_DIR }}" shell: msys2 {0} env: - GLCONTEXT_WIN_LIBGL: "C:\msys64\mingw64\bin\opengl32.dll" + GLCONTEXT_WIN_LIBGL: "C:/msys64/mingw64/bin/opengl32.dll" run: ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 322637aeeaf8d46dea8b46eae3edd1a313a22112 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 09:23:09 -0400 Subject: [PATCH 177/234] Print the env --- .github/workflows/build.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index acae1b0b..13aa23a7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -246,4 +246,6 @@ jobs: shell: msys2 {0} env: GLCONTEXT_WIN_LIBGL: "C:/msys64/mingw64/bin/opengl32.dll" - run: ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file + run: | + env | sort + ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 2504c7b3dd1e1185e29d9a9fd9a7d93122382f20 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 09:36:14 -0400 Subject: [PATCH 178/234] Don't print the env --- .github/workflows/build.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 13aa23a7..acae1b0b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -246,6 +246,4 @@ jobs: shell: msys2 {0} env: GLCONTEXT_WIN_LIBGL: "C:/msys64/mingw64/bin/opengl32.dll" - run: | - env | sort - ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file + run: ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From c2f810a2487778796a04828eab4b2aa71056b8b8 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 10:05:10 -0400 Subject: [PATCH 179/234] List `Python3_ROOT_DIR` --- .github/workflows/build.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index acae1b0b..c252183b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -246,4 +246,6 @@ jobs: shell: msys2 {0} env: GLCONTEXT_WIN_LIBGL: "C:/msys64/mingw64/bin/opengl32.dll" - run: ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file + run: | + ls -hal $Python3_ROOT_DIR + ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 6193fc5eda24a1ca776059b61f512144d011f237 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 10:39:36 -0400 Subject: [PATCH 180/234] Log more environment info --- .github/workflows/build.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c252183b..03baa313 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -247,5 +247,6 @@ jobs: env: GLCONTEXT_WIN_LIBGL: "C:/msys64/mingw64/bin/opengl32.dll" run: | - ls -hal $Python3_ROOT_DIR + ls -hal "$Python3_ROOT_DIR/DLLs" "$Python3_ROOT_DIR/libs" "$Python3_ROOT_DIR/Lib" "$Python3_ROOT_DIR/Scripts" + echo $PATH ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From b619be61b238fa0684a0d7e5ab69bd2440b93fcf Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 10:48:38 -0400 Subject: [PATCH 181/234] Log more environment info --- .github/workflows/build.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 03baa313..98b3e20c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -247,6 +247,7 @@ jobs: env: GLCONTEXT_WIN_LIBGL: "C:/msys64/mingw64/bin/opengl32.dll" run: | - ls -hal "$Python3_ROOT_DIR/DLLs" "$Python3_ROOT_DIR/libs" "$Python3_ROOT_DIR/Lib" "$Python3_ROOT_DIR/Scripts" + ls -hal /mingw64/bin + ls -hal /c/msys64/mingw64/bin echo $PATH ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 6bf3b41c2fb28ae96f741110455d145910651497 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 12:36:16 -0400 Subject: [PATCH 182/234] Set the PATH to ensure the relevant DLLs are available to moderngl --- .github/workflows/build.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 98b3e20c..beb03e5f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -247,7 +247,5 @@ jobs: env: GLCONTEXT_WIN_LIBGL: "C:/msys64/mingw64/bin/opengl32.dll" run: | - ls -hal /mingw64/bin - ls -hal /c/msys64/mingw64/bin - echo $PATH + export PATH="/mingw64/bin:$PATH" ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 46d16a623703e508e45ce42e5661acbfced2ecb1 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 13:02:20 -0400 Subject: [PATCH 183/234] Try using the default Python version --- .github/workflows/build.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index beb03e5f..54b02d22 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -115,7 +115,7 @@ jobs: with: msystem: MINGW64 update: true - install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-mesa + install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-mesa mingw-w64-x86_64-python - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} @@ -159,7 +159,6 @@ jobs: if: ${{ inputs.test-suite }} uses: actions/setup-python@v5 with: - python-version: '3.12' cache: 'pip' cache-dependency-path: "${{ github.workspace }}/test/requirements.txt" From eaa103d952064019bf0c6f8823cf5182f19467cd Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 13:06:36 -0400 Subject: [PATCH 184/234] No, that didn't work --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 54b02d22..c5d9fe8e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -159,6 +159,7 @@ jobs: if: ${{ inputs.test-suite }} uses: actions/setup-python@v5 with: + python-version: '3.12' cache: 'pip' cache-dependency-path: "${{ github.workspace }}/test/requirements.txt" From 3c28df8f6014d9b71d335856dc6facd70e4fa9bf Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 13:10:21 -0400 Subject: [PATCH 185/234] Try a different path --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c5d9fe8e..1e044c5e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -245,7 +245,7 @@ jobs: working-directory: "${{ env.BUILD_DIR }}" shell: msys2 {0} env: - GLCONTEXT_WIN_LIBGL: "C:/msys64/mingw64/bin/opengl32.dll" + GLCONTEXT_WIN_LIBGL: "C:/mingw64/bin/opengl32.dll" run: | export PATH="/mingw64/bin:$PATH" ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 48b0c5a1e0eef7dedd3e4ca4986e4c096d50b91e Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 13:27:00 -0400 Subject: [PATCH 186/234] Try Python 3.11 instead --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1e044c5e..5e1047f8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -159,7 +159,7 @@ jobs: if: ${{ inputs.test-suite }} uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.11' cache: 'pip' cache-dependency-path: "${{ github.workspace }}/test/requirements.txt" From af25e8c60f9345b9974278b4abd5b5aa73168da8 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 19:05:32 -0400 Subject: [PATCH 187/234] Bump libretro.py to 0.1.7 --- test/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/requirements.txt b/test/requirements.txt index 97173b96..61b40974 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,5 +1,5 @@ # melonDS DS doesn't support OpenGL on macOS, # so there's no need to install OpenGL dependencies -libretro.py==0.1.6 -libretro.py[opengl]==0.1.6 ; sys_platform != "darwin" +libretro.py==0.1.7 +libretro.py[opengl]==0.1.7 ; sys_platform != "darwin" Pillow==10.3.0 \ No newline at end of file From 6e50af0669b4a373f8c7f80b42734b23b612d61a Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 5 Jun 2024 19:54:34 -0400 Subject: [PATCH 188/234] Bump libretro.py to 0.1.8 --- test/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/requirements.txt b/test/requirements.txt index 61b40974..4d327cf5 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,5 +1,5 @@ # melonDS DS doesn't support OpenGL on macOS, # so there's no need to install OpenGL dependencies -libretro.py==0.1.7 -libretro.py[opengl]==0.1.7 ; sys_platform != "darwin" +libretro.py==0.1.8 +libretro.py[opengl]==0.1.8 ; sys_platform != "darwin" Pillow==10.3.0 \ No newline at end of file From cc226f48b4d48f806d7d6655a2a268e998a04a3d Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 7 Jun 2024 17:06:03 -0400 Subject: [PATCH 189/234] Update Python dependencies --- test/python/reset/no_hang_on_reboot.py | 9 +++++++-- test/requirements.txt | 7 ++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/test/python/reset/no_hang_on_reboot.py b/test/python/reset/no_hang_on_reboot.py index 666c2fdd..70e83156 100644 --- a/test/python/reset/no_hang_on_reboot.py +++ b/test/python/reset/no_hang_on_reboot.py @@ -1,4 +1,9 @@ -import itertools +import sys + +if sys.version_info >= (3, 12): + from itertools import batched +else: + from more_itertools import batched from libretro import Session from PIL import Image @@ -17,7 +22,7 @@ blank_frame = session.video.screenshot() blank_framebuffer = blank_frame.data - blank_colors = set(itertools.batched(blank_framebuffer, 4)) + blank_colors = set(batched(blank_framebuffer, 4)) assert blank_colors is not None and len(blank_colors) == 1, f"Expected an all-white frame, got {blank_colors}" for i in range(300): diff --git a/test/requirements.txt b/test/requirements.txt index 4d327cf5..cd1b1250 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,5 +1,6 @@ # melonDS DS doesn't support OpenGL on macOS, # so there's no need to install OpenGL dependencies -libretro.py==0.1.8 -libretro.py[opengl]==0.1.8 ; sys_platform != "darwin" -Pillow==10.3.0 \ No newline at end of file +libretro.py==0.1.9 +libretro.py[opengl]==0.1.9 ; sys_platform != "darwin" +Pillow==10.3.0 +more-itertools==10.2.* ; python_version < '3.12' From cc953b82271d94e86555fef2cdb2db5b8d755fdc Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 7 Jun 2024 17:33:14 -0400 Subject: [PATCH 190/234] Update Python dependencies --- test/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/requirements.txt b/test/requirements.txt index cd1b1250..67e813ec 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,6 +1,6 @@ # melonDS DS doesn't support OpenGL on macOS, # so there's no need to install OpenGL dependencies -libretro.py==0.1.9 -libretro.py[opengl]==0.1.9 ; sys_platform != "darwin" +libretro.py==0.1.10 +libretro.py[opengl]==0.1.10 ; sys_platform != "darwin" Pillow==10.3.0 more-itertools==10.2.* ; python_version < '3.12' From 189f00e566901e313785142869047bacca85c7d9 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 7 Jun 2024 18:07:44 -0400 Subject: [PATCH 191/234] Log info about `$$GLCONTEXT_WIN_LIBGL` --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5e1047f8..87e1b319 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -248,4 +248,5 @@ jobs: GLCONTEXT_WIN_LIBGL: "C:/mingw64/bin/opengl32.dll" run: | export PATH="/mingw64/bin:$PATH" + stat "$GLCONTEXT_WIN_LIBGL" ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From ad1df30ad1d63277f32ee1e5417b56261b719ca6 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 7 Jun 2024 18:15:07 -0400 Subject: [PATCH 192/234] Try a different path - Print all environment variables, too --- .github/workflows/build.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 87e1b319..4902dcf5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -245,8 +245,7 @@ jobs: working-directory: "${{ env.BUILD_DIR }}" shell: msys2 {0} env: - GLCONTEXT_WIN_LIBGL: "C:/mingw64/bin/opengl32.dll" + GLCONTEXT_WIN_LIBGL: "D:/a/_temp/msys64/mingw64/bin/opengl32.dll" run: | - export PATH="/mingw64/bin:$PATH" - stat "$GLCONTEXT_WIN_LIBGL" + env | sort ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 38d170759f820d197a04e41c1cb6af6e796e25e7 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 7 Jun 2024 18:31:32 -0400 Subject: [PATCH 193/234] Don't hardcode the temp path --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4902dcf5..4574205b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -245,7 +245,7 @@ jobs: working-directory: "${{ env.BUILD_DIR }}" shell: msys2 {0} env: - GLCONTEXT_WIN_LIBGL: "D:/a/_temp/msys64/mingw64/bin/opengl32.dll" + GLCONTEXT_WIN_LIBGL: "${{ env.RUNNER_TEMP }}/msys64/mingw64/bin/opengl32.dll" run: | env | sort ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 28f868bdcfbaec914743f672ebbbcf3e0f69a311 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 7 Jun 2024 18:34:41 -0400 Subject: [PATCH 194/234] Increase the default test timeout to 60 seconds --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f30f52cf..928d9d1e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,7 +88,7 @@ cmake_path(GET DSI_NAND FILENAME DSI_NAND_NAME) include(CMakePrintHelpers) -set(DEFAULT_TIMEOUT 30) +set(DEFAULT_TIMEOUT 60) # In seconds function(add_python_test) set(options From cbb686ac9bece26ee814e77fa4ce1c1cffa294f6 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 7 Jun 2024 18:43:02 -0400 Subject: [PATCH 195/234] Stop logging the environment --- .github/workflows/build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4574205b..bd32939e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -247,5 +247,4 @@ jobs: env: GLCONTEXT_WIN_LIBGL: "${{ env.RUNNER_TEMP }}/msys64/mingw64/bin/opengl32.dll" run: | - env | sort ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 7ef00f101c0425aac8d6979b39d25a9566222f21 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 7 Jun 2024 18:49:54 -0400 Subject: [PATCH 196/234] Set `GLCONTEXT_WIN_LIBGL` in the `run` block - `$RUNNER_TEMP` isn't yet defined when the `env` block is being evaluated --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bd32939e..96c2dfda 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -244,7 +244,7 @@ jobs: if: ${{ inputs.test-suite && runner.os == 'Windows' }} working-directory: "${{ env.BUILD_DIR }}" shell: msys2 {0} - env: - GLCONTEXT_WIN_LIBGL: "${{ env.RUNNER_TEMP }}/msys64/mingw64/bin/opengl32.dll" run: | + export GLCONTEXT_WIN_LIBGL="$RUNNER_TEMP/msys64/mingw64/bin/opengl32.dll" + # Needed by glcontext (which is a dependency for libretro.py's OpenGL driver) ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file From 7e7d4b5d772b63bdc1a928105ca18326159bd35b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Sat, 8 Jun 2024 16:42:15 -0400 Subject: [PATCH 197/234] Don't write extra-verbose test output --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 96c2dfda..fbffa6dd 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -247,4 +247,4 @@ jobs: run: | export GLCONTEXT_WIN_LIBGL="$RUNNER_TEMP/msys64/mingw64/bin/opengl32.dll" # Needed by glcontext (which is a dependency for libretro.py's OpenGL driver) - ctest --extra-verbose --exclude-regex example --output-on-failure \ No newline at end of file + ctest --exclude-regex example --output-on-failure \ No newline at end of file From d5264a26dd6bcd7244f128e74aae27664ee87d6d Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 12:20:52 -0400 Subject: [PATCH 198/234] Add some extra logging --- src/libretro/render/opengl.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index f3c593dd..94a52e46 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -226,12 +226,16 @@ void MelonDsDs::OpenGLRenderState::ContextReset(melonDS::NDS& nds, const CoreCon retro_assert(status == GL_FRAMEBUFFER_COMPLETE); // Initialize global OpenGL resources (e.g. VAOs) and get config info (e.g. limits) + retro::debug("Setting up GL state"); glsm_ctl(GLSM_CTL_STATE_SETUP, nullptr); + retro::debug("Set up GL state"); // Start using global OpenGL structures { TracyGpuZone("GLSM_CTL_STATE_BIND"); + retro::debug("Binding GL state"); glsm_ctl(GLSM_CTL_STATE_BIND, nullptr); + retro::debug("Bound GL state"); } // HACK: Makes the core resilient to context loss by cleaning up the stale OpenGL renderer @@ -244,6 +248,7 @@ void MelonDsDs::OpenGLRenderState::ContextReset(melonDS::NDS& nds, const CoreCon } renderer->SetRenderSettings(config.BetterPolygonSplitting(), config.ScaleFactor()); nds.GPU.SetRenderer3D(std::move(renderer)); + retro::debug("Installed OpenGL renderer"); SetUpCoreOpenGlState(config); _contextInitialized = true; From 21fa8a6b1f8e0785b0093e11fa2bc7daef4ca5f7 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 13:26:09 -0400 Subject: [PATCH 199/234] Disable Python's stdout/stderr buffering - Less likely that some log lines will be lost --- .github/workflows/build.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fbffa6dd..66ba86e4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -238,12 +238,16 @@ jobs: - name: Run Test Suite (Linux) if: ${{ inputs.test-suite && runner.os == 'Linux' }} working-directory: "${{ env.BUILD_DIR }}" + env: + PYTHONUNBUFFERED: 1 run: xvfb-run ctest --extra-verbose --exclude-regex example --output-on-failure - name: Run Test Suite (Windows) if: ${{ inputs.test-suite && runner.os == 'Windows' }} working-directory: "${{ env.BUILD_DIR }}" shell: msys2 {0} + env: + PYTHONUNBUFFERED: 1 run: | export GLCONTEXT_WIN_LIBGL="$RUNNER_TEMP/msys64/mingw64/bin/opengl32.dll" # Needed by glcontext (which is a dependency for libretro.py's OpenGL driver) From 984c122490ef3c7844ade5ffe20a9839a6222e67 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 13:46:30 -0400 Subject: [PATCH 200/234] Add another log line --- src/libretro/render/opengl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index 94a52e46..def47d98 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -244,6 +244,7 @@ void MelonDsDs::OpenGLRenderState::ContextReset(melonDS::NDS& nds, const CoreCon nds.GPU.GPU3D.SetCurrentRenderer(std::make_unique()); auto renderer = melonDS::GLRenderer::New(); if (!renderer) { + retro::error("Failed to initialize OpenGL renderer!"); throw opengl_not_initialized_exception(); } renderer->SetRenderSettings(config.BetterPolygonSplitting(), config.ScaleFactor()); From 822520f893d1c27aaeb33aa1ba7fc8f3d90126a1 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 14:21:23 -0400 Subject: [PATCH 201/234] No --extra-verbose output on the Linux test runner --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 66ba86e4..b24ed2df 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -240,7 +240,7 @@ jobs: working-directory: "${{ env.BUILD_DIR }}" env: PYTHONUNBUFFERED: 1 - run: xvfb-run ctest --extra-verbose --exclude-regex example --output-on-failure + run: xvfb-run ctest --exclude-regex example --output-on-failure - name: Run Test Suite (Windows) if: ${{ inputs.test-suite && runner.os == 'Windows' }} From 6cb74c949da33833c102153bbb02e891065746c8 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 14:58:20 -0400 Subject: [PATCH 202/234] Update to libretro.py 0.1.11 --- test/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/requirements.txt b/test/requirements.txt index 67e813ec..1e1806d2 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,6 +1,6 @@ # melonDS DS doesn't support OpenGL on macOS, # so there's no need to install OpenGL dependencies -libretro.py==0.1.10 -libretro.py[opengl]==0.1.10 ; sys_platform != "darwin" +libretro.py==0.1.11 +libretro.py[opengl]==0.1.11 ; sys_platform != "darwin" Pillow==10.3.0 more-itertools==10.2.* ; python_version < '3.12' From ae620675afcb4b2e67824b0cef43bcaf30c51b38 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 15:20:13 -0400 Subject: [PATCH 203/234] Try MESA_DEBUG on the Windows runner --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b24ed2df..360a2ed5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -248,6 +248,7 @@ jobs: shell: msys2 {0} env: PYTHONUNBUFFERED: 1 + MESA_DEBUG: 1 run: | export GLCONTEXT_WIN_LIBGL="$RUNNER_TEMP/msys64/mingw64/bin/opengl32.dll" # Needed by glcontext (which is a dependency for libretro.py's OpenGL driver) From 9c120ef28edacc685535f4fcd39677c7ef3bd39c Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 16:29:41 -0400 Subject: [PATCH 204/234] Try D3D12_DEBUG on the Windows runner - Source: https://docs.mesa3d.org/drivers/d3d12.html --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 360a2ed5..a0940168 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -249,6 +249,7 @@ jobs: env: PYTHONUNBUFFERED: 1 MESA_DEBUG: 1 + D3D12_DEBUG: verbose,debuglayer,gpuvalidator run: | export GLCONTEXT_WIN_LIBGL="$RUNNER_TEMP/msys64/mingw64/bin/opengl32.dll" # Needed by glcontext (which is a dependency for libretro.py's OpenGL driver) From f4362667d9c3eaebfe203c2870d47a51507286eb Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 16:46:37 -0400 Subject: [PATCH 205/234] Increase the default test timeout to 3 minutes --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 928d9d1e..7137a098 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,7 +88,7 @@ cmake_path(GET DSI_NAND FILENAME DSI_NAND_NAME) include(CMakePrintHelpers) -set(DEFAULT_TIMEOUT 60) # In seconds +set(DEFAULT_TIMEOUT 180) # In seconds function(add_python_test) set(options From e3ba6e512c6ac02540b251b6170b2bdb9e3f164b Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 16:57:31 -0400 Subject: [PATCH 206/234] Add another debug log call --- src/libretro/libretro.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index a81a452b..4e4fa5e1 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -100,6 +100,7 @@ PUBLIC_SYMBOL bool retro_load_game(const struct retro_game_info *info) { PUBLIC_SYMBOL void retro_get_system_av_info(struct retro_system_av_info *info) { ZoneScopedN(TracyFunction); + retro::debug(TracyFunction); retro_assert(info != nullptr); From 448ee3864ac903977698cdfecbf22ad41a1dd2a0 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 17:00:00 -0400 Subject: [PATCH 207/234] Revert test timeout to 60 seconds --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7137a098..928d9d1e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,7 +88,7 @@ cmake_path(GET DSI_NAND FILENAME DSI_NAND_NAME) include(CMakePrintHelpers) -set(DEFAULT_TIMEOUT 180) # In seconds +set(DEFAULT_TIMEOUT 60) # In seconds function(add_python_test) set(options From 30e85f0f4b78efec32d700cf010bb6e5f50a54fe Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 17:08:54 -0400 Subject: [PATCH 208/234] Add another log --- src/libretro/libretro.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index 4e4fa5e1..d17adb58 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -105,6 +105,8 @@ PUBLIC_SYMBOL void retro_get_system_av_info(struct retro_system_av_info *info) { retro_assert(info != nullptr); *info = MelonDsDs::Core.GetSystemAvInfo(); + + retro::debug("retro_get_system_av_info finished"); } PUBLIC_SYMBOL [[gnu::hot]] void retro_run(void) { From 4698b281420267caf023bae7c3a190a8d316b753 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 17:38:31 -0400 Subject: [PATCH 209/234] Add more logging calls --- src/libretro/render/opengl.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index def47d98..b4411625 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -247,15 +247,19 @@ void MelonDsDs::OpenGLRenderState::ContextReset(melonDS::NDS& nds, const CoreCon retro::error("Failed to initialize OpenGL renderer!"); throw opengl_not_initialized_exception(); } + retro::debug("Constructed OpenGL renderer"); renderer->SetRenderSettings(config.BetterPolygonSplitting(), config.ScaleFactor()); + retro::debug("Applied OpenGL renderer settings"); nds.GPU.SetRenderer3D(std::move(renderer)); retro::debug("Installed OpenGL renderer"); SetUpCoreOpenGlState(config); + retro::debug("Initialized core OpenGL state"); _contextInitialized = true; // Stop using OpenGL structures glsm_ctl(GLSM_CTL_STATE_UNBIND, nullptr); // Always succeeds + retro::debug("Unbound GL state"); retro::debug("OpenGL context reset successfully."); } From 42431e8288cbf9a04cf006a784afc7114ceb65c8 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 17:56:24 -0400 Subject: [PATCH 210/234] Add another log call --- src/libretro/core/core.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libretro/core/core.cpp b/src/libretro/core/core.cpp index 21d92e28..df454dab 100644 --- a/src/libretro/core/core.cpp +++ b/src/libretro/core/core.cpp @@ -656,7 +656,12 @@ void MelonDsDs::CoreState::ApplyConfig(const CoreConfig& config) noexcept { // If we're switching renderer modes... retro::debug("Switching render mode from {} to {}", *oldRenderer, *newRenderer); retro_system_av_info av = GetSystemAvInfo(*newRenderer); - retro::set_system_av_info(av); + if (retro::set_system_av_info(av)) { + retro::info("Updated system AV info for new renderer"); + } + else { + retro::warn("Failed to update system AV info for new renderer"); + } } _renderState.UpdateRenderer(Config, *Console); From aac373bc66e0653390db83104c2ae2fbf09b8460 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Mon, 10 Jun 2024 18:29:38 -0400 Subject: [PATCH 211/234] Add another log call --- src/libretro/render/render.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libretro/render/render.cpp b/src/libretro/render/render.cpp index ca99ae7e..1b691842 100644 --- a/src/libretro/render/render.cpp +++ b/src/libretro/render/render.cpp @@ -112,6 +112,7 @@ void MelonDsDs::RenderStateWrapper::UpdateRenderer(const CoreConfig& config, mel // If we're configured to use the OpenGL renderer, and we aren't already... retro::debug("Initializing OpenGL renderer"); if (auto renderer = melonDS::GLRenderer::New()) { + retro::debug("Initialized OpenGL renderer."); nds.GPU.SetRenderer3D(std::move(renderer)); glRender->RequestRefresh(); } else { From e1c0ce2126238cb41e57dc17ab79b4b5cb48a3c1 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 11 Jun 2024 17:17:22 -0400 Subject: [PATCH 212/234] What if I don't set `GLCONTEXT_WIN_LIBGL`? --- .github/workflows/build.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a0940168..4f10925a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -251,6 +251,4 @@ jobs: MESA_DEBUG: 1 D3D12_DEBUG: verbose,debuglayer,gpuvalidator run: | - export GLCONTEXT_WIN_LIBGL="$RUNNER_TEMP/msys64/mingw64/bin/opengl32.dll" - # Needed by glcontext (which is a dependency for libretro.py's OpenGL driver) ctest --exclude-regex example --output-on-failure \ No newline at end of file From 5d77516f9e16c9ded77d4594b7b7adc2235bb373 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 11 Jun 2024 18:13:58 -0400 Subject: [PATCH 213/234] Is `opengl32.dll` where I expect it? --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4f10925a..d27268ea 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -251,4 +251,5 @@ jobs: MESA_DEBUG: 1 D3D12_DEBUG: verbose,debuglayer,gpuvalidator run: | + ls C:/Windows/System32/opengl32.dll ctest --exclude-regex example --output-on-failure \ No newline at end of file From baa47660b5cf3fc866f3c1e9455ecf5595474618 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 11 Jun 2024 18:22:04 -0400 Subject: [PATCH 214/234] Use the system opengl32.dll for glcontext --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d27268ea..39d0177d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -250,6 +250,6 @@ jobs: PYTHONUNBUFFERED: 1 MESA_DEBUG: 1 D3D12_DEBUG: verbose,debuglayer,gpuvalidator + GLCONTEXT_WIN_LIBGL: "C:/Windows/System32/opengl32.dll" run: | - ls C:/Windows/System32/opengl32.dll ctest --exclude-regex example --output-on-failure \ No newline at end of file From 0c4adc8bfd56ea6486943ff3691bf1f1a9af4059 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 11 Jun 2024 18:58:29 -0400 Subject: [PATCH 215/234] Install the OpenGL compatibility pack - The test suite probably works on my VM because of this package --- .github/workflows/build.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 39d0177d..179b87b2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -117,6 +117,16 @@ jobs: update: true install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-mesa mingw-w64-x86_64-python + - name: Install winget + uses: Cyberboss/install-winget@v1 + if: ${{ runner.os == 'Windows' }} + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install OpenGL Compatibility Pack (Windows) + if: ${{ runner.os == 'Windows' }} + run: winget install 9NQPSL29BFFF --disable-interactivity --accept-source-agreements + - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} shell: bash From 69c330202ad2f0c240637d87053f2351dfc4d761 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 11 Jun 2024 19:18:18 -0400 Subject: [PATCH 216/234] What now? --- .github/workflows/build.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 179b87b2..4e9ae368 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -125,7 +125,9 @@ jobs: - name: Install OpenGL Compatibility Pack (Windows) if: ${{ runner.os == 'Windows' }} - run: winget install 9NQPSL29BFFF --disable-interactivity --accept-source-agreements + run: | + echo $Env:PATH + winget install 9NQPSL29BFFF --disable-interactivity --accept-source-agreements - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} From 3022de7c43b101e97b4e668fe5f9a368cb897cbe Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 11 Jun 2024 19:24:01 -0400 Subject: [PATCH 217/234] Sigh --- .github/workflows/build.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4e9ae368..178774e1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -118,13 +118,14 @@ jobs: install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-mesa mingw-w64-x86_64-python - name: Install winget - uses: Cyberboss/install-winget@v1 + uses: Cyberboss/install-winget@v1.0.6 if: ${{ runner.os == 'Windows' }} with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install OpenGL Compatibility Pack (Windows) if: ${{ runner.os == 'Windows' }} + shell: powershell run: | echo $Env:PATH winget install 9NQPSL29BFFF --disable-interactivity --accept-source-agreements From b9c9f18ee213b1a23d781944832a2c11502a5ec3 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Tue, 11 Jun 2024 19:38:18 -0400 Subject: [PATCH 218/234] Add `--accept-package-agreements` --- .github/workflows/build.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 178774e1..c54e0da2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -127,8 +127,7 @@ jobs: if: ${{ runner.os == 'Windows' }} shell: powershell run: | - echo $Env:PATH - winget install 9NQPSL29BFFF --disable-interactivity --accept-source-agreements + winget install 9NQPSL29BFFF --disable-interactivity --accept-source-agreements --accept-package-agreements - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} From 6a3ab8d24cfe40aa8be7b590041993271bcbcde4 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 08:41:57 -0400 Subject: [PATCH 219/234] Try this --- .github/workflows/build.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c54e0da2..d6a6fc2e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -127,7 +127,8 @@ jobs: if: ${{ runner.os == 'Windows' }} shell: powershell run: | - winget install 9NQPSL29BFFF --disable-interactivity --accept-source-agreements --accept-package-agreements + winget source update + winget install 9NQPSL29BFFF --source=msstore --disable-interactivity --accept-source-agreements --accept-package-agreements - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} From cb31074a3e98d8a5216b4c47cc3067d18ac1704d Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 10:00:57 -0400 Subject: [PATCH 220/234] Don't use winget --- .github/workflows/build.yaml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d6a6fc2e..39d0177d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -117,19 +117,6 @@ jobs: update: true install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-mesa mingw-w64-x86_64-python - - name: Install winget - uses: Cyberboss/install-winget@v1.0.6 - if: ${{ runner.os == 'Windows' }} - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install OpenGL Compatibility Pack (Windows) - if: ${{ runner.os == 'Windows' }} - shell: powershell - run: | - winget source update - winget install 9NQPSL29BFFF --source=msstore --disable-interactivity --accept-source-agreements --accept-package-agreements - - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} shell: bash From 1326268d3d1be3f134482efc6bee4fbe55a52658 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 10:08:25 -0400 Subject: [PATCH 221/234] Don't use some dependencies --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 39d0177d..d0837830 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -115,7 +115,7 @@ jobs: with: msystem: MINGW64 update: true - install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-mesa mingw-w64-x86_64-python + install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} From 9470714c71cab06c2a2ae869e92248e60d24b83c Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 10:34:06 -0400 Subject: [PATCH 222/234] Enable the test suite on macOS --- .github/workflows/main.yaml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 163c330d..e2c95083 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -52,8 +52,23 @@ jobs: runs-on: macos-latest target: macos-x86_64 lib-ext: dylib + test-suite: true cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="x86_64" -DENABLE_OGLRENDERER=OFF -DCMAKE_APPLE_SILICON_PROCESSOR="x86_64" # Disabled OpenGL on macOS due to https://github.com/JesseTG/melonds-ds/issues/12 + secrets: + TESTFILE_REPO_TOKEN: ${{ secrets.TESTFILE_REPO_TOKEN }} + TESTFILE_REPO: ${{ secrets.TESTFILE_REPO }} + DSI_NAND_ARCHIVE: ${{ secrets.DSI_NAND_ARCHIVE }} + DSI_NAND: ${{ secrets.DSI_NAND }} + ARM7_BIOS: ${{ secrets.ARM7_BIOS }} + ARM9_BIOS: ${{ secrets.ARM9_BIOS }} + ARM7_DSI_BIOS: ${{ secrets.ARM7_DSI_BIOS }} + ARM9_DSI_BIOS: ${{ secrets.ARM9_DSI_BIOS }} + NDS_FIRMWARE: ${{ secrets.NDS_FIRMWARE }} + DSI_FIRMWARE: ${{ secrets.DSI_FIRMWARE }} + NDS_ROM: ${{ secrets.NDS_ROM }} + GBA_ROM: ${{ secrets.GBA_ROM }} + GBA_SRAM: ${{ secrets.GBA_SRAM }} macos-arm64: name: macOS (arm64) @@ -63,8 +78,23 @@ jobs: runs-on: macos-latest target: macos-arm64 lib-ext: dylib + test-suite: true cmake-args: -DCMAKE_OSX_ARCHITECTURES:STRING="arm64" -DENABLE_OGLRENDERER=OFF -DCMAKE_APPLE_SILICON_PROCESSOR="arm64" # Disabled OpenGL on macOS due to https://github.com/JesseTG/melonds-ds/issues/12 + secrets: + TESTFILE_REPO_TOKEN: ${{ secrets.TESTFILE_REPO_TOKEN }} + TESTFILE_REPO: ${{ secrets.TESTFILE_REPO }} + DSI_NAND_ARCHIVE: ${{ secrets.DSI_NAND_ARCHIVE }} + DSI_NAND: ${{ secrets.DSI_NAND }} + ARM7_BIOS: ${{ secrets.ARM7_BIOS }} + ARM9_BIOS: ${{ secrets.ARM9_BIOS }} + ARM7_DSI_BIOS: ${{ secrets.ARM7_DSI_BIOS }} + ARM9_DSI_BIOS: ${{ secrets.ARM9_DSI_BIOS }} + NDS_FIRMWARE: ${{ secrets.NDS_FIRMWARE }} + DSI_FIRMWARE: ${{ secrets.DSI_FIRMWARE }} + NDS_ROM: ${{ secrets.NDS_ROM }} + GBA_ROM: ${{ secrets.GBA_ROM }} + GBA_SRAM: ${{ secrets.GBA_SRAM }} linux-x86_64: name: Linux (x86_64) From db793e14f95a88b51449aa6894d0a4445ea575b3 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 10:36:00 -0400 Subject: [PATCH 223/234] Revert "Don't use winget" This reverts commit cb31074a3e98d8a5216b4c47cc3067d18ac1704d. --- .github/workflows/build.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d0837830..78663219 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -117,6 +117,19 @@ jobs: update: true install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake + - name: Install winget + uses: Cyberboss/install-winget@v1.0.6 + if: ${{ runner.os == 'Windows' }} + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install OpenGL Compatibility Pack (Windows) + if: ${{ runner.os == 'Windows' }} + shell: powershell + run: | + winget source update + winget install 9NQPSL29BFFF --source=msstore --disable-interactivity --accept-source-agreements --accept-package-agreements + - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} shell: bash From 0aa03455a5f7a4a31471e73e4abbc13e321059ac Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 12:32:25 -0400 Subject: [PATCH 224/234] Verbose winget logging --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 78663219..f351af17 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -128,7 +128,7 @@ jobs: shell: powershell run: | winget source update - winget install 9NQPSL29BFFF --source=msstore --disable-interactivity --accept-source-agreements --accept-package-agreements + winget install 9NQPSL29BFFF --source=msstore --disable-interactivity --accept-source-agreements --accept-package-agreements --verbose - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} From c1cdd067e8ca872ae5c61cf90a23371d2995d188 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 12:33:27 -0400 Subject: [PATCH 225/234] Whoops, forgot to actually run the tests --- .github/workflows/build.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f351af17..0db23adb 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -248,6 +248,13 @@ jobs: if-no-files-found: error path: "${{ env.DIST_DIR }}" + - name: Run Test Suite (macOS) + if: ${{ inputs.test-suite && runner.os == 'macOS' }} + working-directory: "${{ env.BUILD_DIR }}" + env: + PYTHONUNBUFFERED: 1 + run: ctest --exclude-regex example --output-on-failure + - name: Run Test Suite (Linux) if: ${{ inputs.test-suite && runner.os == 'Linux' }} working-directory: "${{ env.BUILD_DIR }}" From b5002adaaab51d39f247a7425372873582032118 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 12:38:08 -0400 Subject: [PATCH 226/234] Use macOS 13 for x64 macOS test runs --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index e2c95083..10ea5746 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -49,7 +49,7 @@ jobs: uses: ./.github/workflows/build.yaml with: archive-name: melondsds_libretro-macos-x86_64 - runs-on: macos-latest + runs-on: macos-13 target: macos-x86_64 lib-ext: dylib test-suite: true From c44a0f6252117595643e1b1284c1296160f509e9 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 12:49:44 -0400 Subject: [PATCH 227/234] Use Mesa instead of winget --- .github/workflows/build.yaml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0db23adb..917f399f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -117,18 +117,9 @@ jobs: update: true install: git pkgconf mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake - - name: Install winget - uses: Cyberboss/install-winget@v1.0.6 + - name: Install Mesa if: ${{ runner.os == 'Windows' }} - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install OpenGL Compatibility Pack (Windows) - if: ${{ runner.os == 'Windows' }} - shell: powershell - run: | - winget source update - winget install 9NQPSL29BFFF --source=msstore --disable-interactivity --accept-source-agreements --accept-package-agreements --verbose + uses: ssciwr/setup-mesa-dist-win@v2 - name: Install Dependencies (macOS) if: ${{ runner.os == 'macOS' }} From 87a3dfbc0160026439e04c01802fec915687d13e Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Wed, 12 Jun 2024 16:34:22 -0400 Subject: [PATCH 228/234] Release 1.1.2 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b6717ce..5019b406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0), and this project roughly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.1.2] - 2024-06-12 ### Fixed From 923332a42dc252dfe2d50bdd093b10b381354227 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 13 Jun 2024 13:32:31 -0400 Subject: [PATCH 229/234] Don't filter workflow runs by branch --- .github/workflows/main.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 10ea5746..4853b70c 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -3,9 +3,6 @@ name: Build Artifacts on: workflow_dispatch: push: - branches: - - main - - dev paths-ignore: - "*.gitlab-ci.yml" - "*.gitignore" From d30d2b6c3008f2a8c6935092b135c5e02e59ea97 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Thu, 13 Jun 2024 13:28:24 -0400 Subject: [PATCH 230/234] Explicitly request an OpenGL Core 3.2 context --- src/libretro/render/opengl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index b4411625..9e1d2241 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -148,9 +148,9 @@ MelonDsDs::OpenGLRenderState::OpenGLRenderState() { glsm_ctx_params_t params = {}; // MelonDS DS wants an opengl 3.1 context, so glcore is required for mesa compatibility - params.context_type = RETRO_HW_CONTEXT_OPENGL; + params.context_type = RETRO_HW_CONTEXT_OPENGL_CORE; params.major = 3; - params.minor = 1; + params.minor = 2; params.context_reset = HardwareContextReset; params.context_destroy = HardwareContextDestroyed; params.environ_cb = retro::environment; From 5816985f22b81647a7373266439c7f1de305d770 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 14 Jun 2024 11:33:41 -0400 Subject: [PATCH 231/234] Consolidate some logging calls and remove an `assert` --- src/libretro/render/opengl.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index 9e1d2241..944c359c 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -219,11 +219,8 @@ void MelonDsDs::OpenGLRenderState::ContextReset(melonDS::NDS& nds, const CoreCon uintptr_t fbo = glsm_get_current_framebuffer(); retro_assert(glIsFramebuffer(fbo) == GL_TRUE); - retro::debug("Current OpenGL framebuffer: {}", fbo); - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - retro::debug("Framebuffer status: {}", static_cast(status)); - retro_assert(status == GL_FRAMEBUFFER_COMPLETE); + retro::debug("Current OpenGL framebuffer: id={}, status={}", fbo, static_cast(status)); // Initialize global OpenGL resources (e.g. VAOs) and get config info (e.g. limits) retro::debug("Setting up GL state"); From a73780fcf7715569e59eb39f76f748e514a41fbb Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 14 Jun 2024 15:14:17 -0400 Subject: [PATCH 232/234] Fix an incorrect comment and `info` field --- melondsds_libretro.info.in | 2 +- src/libretro/render/opengl.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/melondsds_libretro.info.in b/melondsds_libretro.info.in index 006c0121..30bd2678 100644 --- a/melondsds_libretro.info.in +++ b/melondsds_libretro.info.in @@ -25,7 +25,7 @@ is_experimental = "false" libretro_saves = "true" load_subsystem = "true" needs_fullpath = "false" -required_hw_api = "OpenGL Core >= 3.1" +required_hw_api = "OpenGL Core >= 3.2" savestate = "true" savestate_features = "serialized" single_purpose = "false" diff --git a/src/libretro/render/opengl.cpp b/src/libretro/render/opengl.cpp index 944c359c..702bf998 100644 --- a/src/libretro/render/opengl.cpp +++ b/src/libretro/render/opengl.cpp @@ -147,7 +147,8 @@ MelonDsDs::OpenGLRenderState::OpenGLRenderState() { retro::debug(TracyFunction); glsm_ctx_params_t params = {}; - // MelonDS DS wants an opengl 3.1 context, so glcore is required for mesa compatibility + // MelonDS needs at least OpenGL 3.2 for OpenGL renderer + // (it doesn't use the legacy fixed-function pipeline) params.context_type = RETRO_HW_CONTEXT_OPENGL_CORE; params.major = 3; params.minor = 2; From 8046cb439abb8d07a006b5a19d85c6342f395fd3 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 14 Jun 2024 15:16:03 -0400 Subject: [PATCH 233/234] Update the changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5019b406..fc590fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0), and this project roughly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- Fixed a crash that would occur when attempting to use the OpenGL renderer on some GPUs. [#203](https://github.com/JesseTG/melonds-ds/issues/203) + ## [1.1.2] - 2024-06-12 ### Fixed From a29c41595acee67e41c347fadb736f9ea2ab4b05 Mon Sep 17 00:00:00 2001 From: Jesse Talavera Date: Fri, 14 Jun 2024 17:51:14 -0400 Subject: [PATCH 234/234] Release 1.1.3 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc590fdc..9d4a55da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0), and this project roughly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.1.3] - 2024-06-14 ### Fixed