diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index 4ce15489b7211..f84ca1b153a97 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -7,6 +7,7 @@ import logging import os import re +import shlex from typing import Optional logger = logging.getLogger(__name__) @@ -234,6 +235,7 @@ class JobDetails: pypi_packages: list[str] = dataclasses.field(default_factory=list) setup_gage_sdk_path: str = "" binutils_strings: str = "strings" + ctest_args: str = "" def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]: data = { @@ -303,6 +305,7 @@ def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]: "pypi-packages": my_shlex_join(self.pypi_packages), "setup-ngage-sdk-path": self.setup_gage_sdk_path, "binutils-strings": self.binutils_strings, + "ctest-args": self.ctest_args, } return {k: v for k, v in data.items() if v != ""} @@ -318,7 +321,7 @@ def escape(s): return " ".join(escape(s)) -def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDetails: +def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool, ctest_args: list[str]) -> JobDetails: job = JobDetails( name=spec.name, key=key, @@ -487,6 +490,8 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta job.shared_lib = SharedLibType.SO_0 job.static_lib = StaticLibType.A fpic = True + job.cmake_arguments.append("-DSDLTEST_GDB=ON") + job.apt_packages.append("gdb") if spec.more_hard_deps: # Some distros prefer to make important dependencies # mandatory, so that SDL won't start up but lack expected @@ -828,6 +833,7 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta "-DCMAKE_C_COMPILER_LAUNCHER=ccache", "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache", )) + job.ctest_args = shlex.join(ctest_args) if not build_parallel: job.cmake_build_arguments.append("-j1") if job.cflags or job.cppflags: @@ -850,9 +856,14 @@ def tf(b): return job -def spec_to_platform(spec: JobSpec, key: str, enable_artifacts: bool, trackmem_symbol_names: bool) -> dict[str, str|bool]: +def spec_to_platform(spec: JobSpec, key: str, enable_artifacts: bool, trackmem_symbol_names: bool, ctest_args:list[str]) -> dict[str, str|bool]: logger.info("spec=%r", spec) - job = spec_to_job(spec, key=key, trackmem_symbol_names=trackmem_symbol_names) + job = spec_to_job( + spec, + key=key, + trackmem_symbol_names=trackmem_symbol_names, + ctest_args=ctest_args, + ) logger.info("job=%r", job) platform = job.to_workflow(enable_artifacts=enable_artifacts) logger.info("platform=%r", platform) @@ -881,6 +892,7 @@ def main(): ) filters = [] + ctest_args = [] if args.commit_message_file: with open(args.commit_message_file, "r") as f: commit_message = f.read() @@ -893,6 +905,9 @@ def main(): if re.search(r"\[sdl-ci-(full-)?trackmem(-symbol-names)?]", commit_message, flags=re.M): args.trackmem_symbol_names = True + for m in re.finditer(r"\[sdl-ci-ctest-args? (.*)]", commit_message, flags=re.M): + ctest_args.extend(shlex.split(m.group(1))) + if not filters: filters.append("*") @@ -900,7 +915,7 @@ def main(): all_level_platforms = {} - all_platforms = {key: spec_to_platform(spec, key=key, enable_artifacts=args.enable_artifacts, trackmem_symbol_names=args.trackmem_symbol_names) for key, spec in JOB_SPECS.items()} + all_platforms = {key: spec_to_platform(spec, key=key, enable_artifacts=args.enable_artifacts, trackmem_symbol_names=args.trackmem_symbol_names, ctest_args=ctest_args) for key, spec in JOB_SPECS.items()} for level_i, level_keys in enumerate(all_level_keys, 1): level_key = f"level{level_i}" diff --git a/.github/workflows/generic.yml b/.github/workflows/generic.yml index 183146679b3fe..82fcbaf15666d 100644 --- a/.github/workflows/generic.yml +++ b/.github/workflows/generic.yml @@ -242,7 +242,7 @@ jobs: ${{ matrix.platform.pretest-cmd }} set -eu export SDL_TESTS_QUICK=1 - ctest -VV --test-dir build/ -j2 + ctest --test-dir build/ ${{ matrix.platform.ctest-args || '-VV -j2' }} - name: "Build test apk's (CMake)" id: apks if: ${{ always() && steps.build.outcome == 'success' && matrix.platform.android-apks != '' }} diff --git a/docs/README-contributing.md b/docs/README-contributing.md index 02a37bf756620..b926e1d0deee5 100644 --- a/docs/README-contributing.md +++ b/docs/README-contributing.md @@ -95,6 +95,7 @@ Its behaviour can be influenced slightly by including SDL-specific tags in your - `[sdl-ci-filter GLOB]` limits the platforms for which to run ci. - `[sdl-ci-artifacts]` forces SDL artifacts, which can then be downloaded from the summary page. - `[sdl-ci-trackmem-symbol-names]` makes sure the final report generated by `--trackmem` contains symbol names. +- `[sdl-ci-ctest-args]` allows overriding the ctest arguments. Adding `--repeat-until-fail N` and `-R NAME` is useful. ## Contributing to the documentation diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a77c5aa685c00..81caee0d7414e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -54,6 +54,12 @@ if(WIN32) else() set_property(TARGET sdlprocdump PROPERTY EXCLUDE_FROM_ALL "1") endif() +else() + option(SDLTEST_GDB "Run tests using headless gdb" OFF) + find_program(GDB_BIN NAMES "gdb") + if(SDLTEST_GDB AND GDB_BIN) + set(CMAKE_TEST_LAUNCHER "${GDB_BIN};-batch;-nx;-x;${CMAKE_CURRENT_SOURCE_DIR}/unix/gdbcmds.txt;--args") + endif() endif() if(EMSCRIPTEN) diff --git a/test/unix/gdbcmds.txt b/test/unix/gdbcmds.txt new file mode 100644 index 0000000000000..c7a8ff9f08d02 --- /dev/null +++ b/test/unix/gdbcmds.txt @@ -0,0 +1,36 @@ +# Script to run a headless gdb printing a stacktrace on a fatal signal +# Use it as: `gdb -batch -nx -x gdbcmds.txt --args $PROGRAM_AND_ARGS` +# Configure CMake with -DSDLTEST_GDB=ON to use with within CTest. + +set pagination off +set confirm off +set verbose off + +# Only stop on real crash signals +handle SIGSEGV stop print nopass +handle SIGABRT stop print nopass +handle SIGILL stop print nopass +handle SIGFPE stop print nopass +handle SIGBUS stop print nopass + +# Ignore SIGPIPE and SIGTERM signals +# (SIGTERM is used by testthread) +handle SIGPIPE nostop noprint pass +handle SIGTERM nostop noprint pass + +define hook-stop + if !$_isvoid($_siginfo) + printf "\n=== Crash detected (signal %d) ===\n", $_siginfo.si_signo + bt full + quit 1 + end +end + +run + +# Normal exit path +if !$_isvoid($_exitcode) + quit $_exitcode +end + +quit 0