ci: add run-tests composite action (#8159)

* ci: add run-tests composite action

* fixup! ci: add run-tests composite action

send sanitizer log messages to stderr

otherwise, they will break transmission-show tests

* fixup! ci: add run-tests composite action

fix windows, alpine breakage

* fixup! ci: add run-tests composite action

fix: sanitizer logging to stderr

* fixup! ci: add run-tests composite action

disable asan leak detection on macOS: the feature is unsupported there

* fixup! refactor: extract platform detection into its own composite action (#8158)

ensure bash is installed on alpine linux
This commit is contained in:
Charles Kerr
2026-01-21 08:24:46 -06:00
committed by GitHub
parent 62be679769
commit 83d49b3c7f
4 changed files with 308 additions and 23 deletions

View File

@@ -29,8 +29,9 @@ runs:
id: cache-key
shell: pwsh
run: |
$RepoRoot = if (Test-Path (Join-Path . 'src/release/windows/main.ps1')) { Join-Path . 'src' } else { (Get-Item .).FullName }
try {
$DepsHash = & (Join-Path . src release windows main.ps1) -Mode DepsHash -BuildArch ${{ inputs.arch }} -BuildPart ${{ inputs.type }}
$DepsHash = & (Join-Path $RepoRoot release windows main.ps1) -Mode DepsHash -BuildArch ${{ inputs.arch }} -BuildPart ${{ inputs.type }}
"hash=${DepsHash}" | Out-File $Env:GITHUB_OUTPUT -Append
} catch {
Write-Error ("{1}{0}{2}{0}{3}" -f [Environment]::NewLine, $_.ToString(), $_.InvocationInfo.PositionMessage, $_.ScriptStackTrace) -ErrorAction Continue
@@ -46,8 +47,9 @@ runs:
if: steps.restore-cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
$RepoRoot = if (Test-Path (Join-Path . 'src/release/windows/main.ps1')) { Join-Path . 'src' } else { (Get-Item .).FullName }
try {
& (Join-Path . src release windows main.ps1) -Mode Build -BuildArch ${{ inputs.arch }} -BuildPart ${{ inputs.type }}
& (Join-Path $RepoRoot release windows main.ps1) -Mode Build -BuildArch ${{ inputs.arch }} -BuildPart ${{ inputs.type }}
} catch {
Write-Error ("{1}{0}{2}{0}{3}" -f [Environment]::NewLine, $_.ToString(), $_.InvocationInfo.PositionMessage, $_.ScriptStackTrace) -ErrorAction Continue
exit 1

157
.github/actions/run-tests/action.yml vendored Normal file
View File

@@ -0,0 +1,157 @@
name: Run tests
description: Run ctest with crash-friendly settings and optional fallbacks
inputs:
setup-env:
description: "Call set-test-env before running tests"
required: false
default: "true"
build-dir:
description: "CTest build directory"
required: false
default: "obj"
build-config:
description: "CTest build configuration"
required: false
default: ""
parallel:
description: "Number of parallel jobs (auto if empty)"
required: false
default: ""
timeout:
description: "CTest timeout in seconds"
required: false
default: ""
extra-ctest-args:
description: "Additional ctest arguments"
required: false
default: ""
enable-sanitizers:
description: "Whether sanitizer options are enabled (affects fallback choices)"
required: false
default: "false"
enable-asan:
description: "Explicitly enable ASan options"
required: false
default: ""
enable-ubsan:
description: "Explicitly enable UBSan options"
required: false
default: ""
enable-lsan:
description: "Explicitly enable LSan options"
required: false
default: ""
extra-asan-options:
description: "Additional ASan options to append"
required: false
default: ""
extra-ubsan-options:
description: "Additional UBSan options to append"
required: false
default: ""
extra-lsan-options:
description: "Additional LSan options to append"
required: false
default: ""
use-catchsegv:
description: "Use catchsegv on Linux if available"
required: false
default: "true"
enable-macos-crash-reports:
description: "Attempt to collect macOS crash reports on failure"
required: false
default: "true"
runs:
using: composite
steps:
- name: Set test environment
if: ${{ inputs.setup-env == 'true' }}
uses: ./.github/actions/set-test-env
with:
enable-sanitizers: ${{ inputs.enable-sanitizers }}
enable-asan: ${{ inputs.enable-asan }}
enable-ubsan: ${{ inputs.enable-ubsan }}
enable-lsan: ${{ inputs.enable-lsan }}
extra-asan-options: ${{ inputs.extra-asan-options }}
extra-ubsan-options: ${{ inputs.extra-ubsan-options }}
extra-lsan-options: ${{ inputs.extra-lsan-options }}
- name: Detect platform
id: platform
uses: ./.github/actions/detect-platform
- name: Run ctest
shell: bash
env:
OS_FAMILY: ${{ steps.platform.outputs.os-family }}
run: |
set -euo pipefail
build_dir="${{ inputs.build-dir }}"
build_config="${{ inputs.build-config }}"
parallel="${{ inputs.parallel }}"
timeout="${{ inputs.timeout }}"
extra_args="${{ inputs.extra-ctest-args }}"
enable_sanitizers="${{ inputs.enable-sanitizers }}"
use_catchsegv="${{ inputs.use-catchsegv }}"
enable_macos_crash_reports="${{ inputs.enable-macos-crash-reports }}"
if [[ -z "$parallel" ]]; then
if command -v nproc >/dev/null 2>&1; then
parallel=$(nproc)
elif command -v sysctl >/dev/null 2>&1; then
parallel=$(sysctl -n hw.logicalcpu)
elif [[ -n "${NUMBER_OF_PROCESSORS:-}" ]]; then
parallel="$NUMBER_OF_PROCESSORS"
else
parallel=1
fi
fi
args=("--test-dir" "$build_dir" "-j" "$parallel" "--output-on-failure")
if [[ -n "$build_config" ]]; then
args+=("--build-config" "$build_config")
fi
if [[ -n "$timeout" ]]; then
args+=("--timeout" "$timeout")
fi
if [[ -n "$extra_args" ]]; then
args+=($extra_args)
fi
crash_marker=""
if [[ "${OS_FAMILY:-}" == "macos" && "$enable_macos_crash_reports" == "true" ]]; then
crash_marker="$RUNNER_TEMP/ctest-crash-marker"
touch "$crash_marker"
fi
run_ctest() {
if [[ "${OS_FAMILY:-}" == "linux" && "$enable_sanitizers" != "true" && "$use_catchsegv" == "true" ]] && command -v catchsegv >/dev/null 2>&1; then
catchsegv ctest "${args[@]}"
else
ctest "${args[@]}"
fi
}
set +e
run_ctest
status=$?
set -e
if [[ $status -ne 0 && "${OS_FAMILY:-}" == "macos" && "$enable_macos_crash_reports" == "true" ]]; then
echo "::group::macOS crash reports"
reports_found=0
for report_dir in "$HOME/Library/Logs/DiagnosticReports" "/Library/Logs/DiagnosticReports"; do
if [[ -d "$report_dir" ]]; then
while IFS= read -r report; do
reports_found=1
echo "--- $report ---"
tail -n 200 "$report" || true
done < <(find "$report_dir" -maxdepth 1 -type f \( -name "*.crash" -o -name "*.ips" \) -newer "$crash_marker" -print 2>/dev/null || true)
fi
done
if [[ $reports_found -eq 0 ]]; then
echo "No new crash reports found after test run."
fi
echo "::endgroup::"
fi
exit $status

98
.github/actions/set-test-env/action.yml vendored Normal file
View File

@@ -0,0 +1,98 @@
name: Set test environment
description: Configure environment variables for richer crash diagnostics in CI
inputs:
enable-sanitizers:
description: "Enable sanitizer runtime options"
required: false
default: "false"
enable-asan:
description: "Explicitly enable ASan options"
required: false
default: ""
enable-ubsan:
description: "Explicitly enable UBSan options"
required: false
default: ""
enable-lsan:
description: "Explicitly enable LSan options"
required: false
default: ""
extra-asan-options:
description: "Additional ASan options to append"
required: false
default: ""
extra-ubsan-options:
description: "Additional UBSan options to append"
required: false
default: ""
extra-lsan-options:
description: "Additional LSan options to append"
required: false
default: ""
runs:
using: composite
steps:
- name: Detect platform
id: platform
uses: ./.github/actions/detect-platform
- name: Configure test environment
shell: bash
env:
OS_FAMILY: ${{ steps.platform.outputs.os-family }}
run: |
set -euo pipefail
echo "CTEST_OUTPUT_ON_FAILURE=1" >> "$GITHUB_ENV"
echo "QT_QPA_PLATFORM=offscreen" >> "$GITHUB_ENV"
enable_sanitizers="${{ inputs.enable-sanitizers }}"
enable_asan="${{ inputs.enable-asan }}"
enable_ubsan="${{ inputs.enable-ubsan }}"
enable_lsan="${{ inputs.enable-lsan }}"
if [[ -z "$enable_asan" && -z "$enable_ubsan" && -z "$enable_lsan" ]]; then
enable_asan="$enable_sanitizers"
enable_ubsan="$enable_sanitizers"
enable_lsan="$enable_sanitizers"
fi
if command -v llvm-symbolizer >/dev/null 2>&1; then
echo "ASAN_SYMBOLIZER_PATH=$(command -v llvm-symbolizer)" >> "$GITHUB_ENV"
echo "LLVM_SYMBOLIZER_PATH=$(command -v llvm-symbolizer)" >> "$GITHUB_ENV"
elif command -v llvm-symbolizer-20 >/dev/null 2>&1; then
echo "ASAN_SYMBOLIZER_PATH=$(command -v llvm-symbolizer-20)" >> "$GITHUB_ENV"
echo "LLVM_SYMBOLIZER_PATH=$(command -v llvm-symbolizer-20)" >> "$GITHUB_ENV"
elif command -v llvm-symbolizer-19 >/dev/null 2>&1; then
echo "ASAN_SYMBOLIZER_PATH=$(command -v llvm-symbolizer-19)" >> "$GITHUB_ENV"
echo "LLVM_SYMBOLIZER_PATH=$(command -v llvm-symbolizer-19)" >> "$GITHUB_ENV"
fi
if [[ "$enable_asan" == "true" ]]; then
os_family="${OS_FAMILY:-unknown}"
detect_leaks_opt="detect_leaks=1"
if [[ "$os_family" == "macos" ]]; then
# don't run on unsupported platforms
detect_leaks_opt="detect_leaks=0"
fi
asan_opts="abort_on_error=1:allocator_may_return_null=1:check_initialization_order=1:${detect_leaks_opt}:halt_on_error=1:print_stacktrace=1:strict_string_checks=1:symbolize=1:verbosity=0"
if [[ -n "${{ inputs.extra-asan-options }}" ]]; then
asan_opts+="${asan_opts:+:}${{ inputs.extra-asan-options }}"
fi
echo "ASAN_OPTIONS=$asan_opts" >> "$GITHUB_ENV"
fi
if [[ "$enable_ubsan" == "true" ]]; then
ubsan_opts="halt_on_error=1:print_stacktrace=1:report_error_type=1:symbolize=1"
if [[ -n "${{ inputs.extra-ubsan-options }}" ]]; then
ubsan_opts+="${ubsan_opts:+:}${{ inputs.extra-ubsan-options }}"
fi
echo "UBSAN_OPTIONS=$ubsan_opts" >> "$GITHUB_ENV"
fi
if [[ "$enable_lsan" == "true" ]]; then
lsan_opts="verbosity=0:log_threads=0:print_suppressions=1"
if [[ -n "${{ inputs.extra-lsan-options }}" ]]; then
lsan_opts+="${lsan_opts:+:}${{ inputs.extra-lsan-options }}"
fi
echo "LSAN_OPTIONS=$lsan_opts" >> "$GITHUB_ENV"
fi

View File

@@ -166,7 +166,11 @@ jobs:
- name: Make
run: cmake --build obj --config Debug --target libtransmission-test transmission-show
- name: Test with sanitizers
run: cmake -E chdir obj ctest -j $(nproc) --build-config Debug --output-on-failure
uses: ./.github/actions/run-tests
with:
build-dir: obj
build-config: Debug
enable-sanitizers: true
sanitizer-tests-macos:
runs-on: macos-26
@@ -208,7 +212,11 @@ jobs:
- name: Make
run: cmake --build obj --config Debug --target libtransmission-test transmission-show
- name: Test with sanitizers
run: cmake -E chdir obj ctest -j $(nproc) --build-config Debug --output-on-failure
uses: ./.github/actions/run-tests
with:
build-dir: obj
build-config: Debug
enable-sanitizers: true
clang-tidy-libtransmission:
runs-on: ubuntu-24.04
@@ -385,8 +393,10 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
env:
TMPDIR: /private/tmp
QT_QPA_PLATFORM: offscreen
run: cmake -E chdir obj ctest -j $(sysctl -n hw.logicalcpu) --build-config RelWithDebInfo --output-on-failure
uses: ./.github/actions/run-tests
with:
build-dir: obj
build-config: RelWithDebInfo
- name: Install
run: cmake --install obj --config RelWithDebInfo --strip
- uses: actions/upload-artifact@v6
@@ -412,6 +422,7 @@ jobs:
apk update
apk add \
ca-certificates \
bash \
clang \
cmake \
crc32c-dev \
@@ -436,12 +447,11 @@ jobs:
- name: Get Source
uses: actions/checkout@v6
with:
path: src
submodules: recursive
- name: Configure
run: |
cmake \
-S src \
-S . \
-B obj \
-G Ninja \
-DCMAKE_C_COMPILER='clang' \
@@ -465,8 +475,10 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
env:
TMPDIR: /private/tmp
QT_QPA_PLATFORM: offscreen
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure
uses: ./.github/actions/run-tests
with:
build-dir: obj
build-config: RelWithDebInfo
- name: Install
run: cmake --install obj --config RelWithDebInfo --strip
- uses: actions/upload-artifact@v6
@@ -496,10 +508,9 @@ jobs:
- name: Get Source
uses: actions/checkout@v6
with:
path: src
submodules: recursive
- name: Prepare Build Deps
uses: ./src/.github/actions/prepare-deps-win32
uses: ./.github/actions/prepare-deps-win32
with:
arch: ${{ matrix.arch }}
type: Deps
@@ -507,7 +518,7 @@ jobs:
run: |
Import-VisualStudioVars -VisualStudioVersion 2022 -Architecture ${{ matrix.arch }}
cmake `
-S src `
-S . `
-B obj `
-G Ninja `
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
@@ -529,7 +540,11 @@ jobs:
cmake --build obj --config RelWithDebInfo
- name: Test
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure --timeout 600
uses: ./.github/actions/run-tests
with:
build-dir: obj
build-config: RelWithDebInfo
timeout: 600
- name: Install
run: cmake --install obj --config RelWithDebInfo
- name: Package
@@ -620,6 +635,12 @@ jobs:
- name: Get Dependencies (Qt)
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: brew install --formula qt
- name: Get Composite Actions
uses: actions/checkout@v6
with:
sparse-checkout: |
.github/actions
sparse-checkout-cone-mode: false
- name: Get Source
uses: actions/download-artifact@v7
with:
@@ -659,8 +680,10 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
env:
TMPDIR: /private/tmp
QT_QPA_PLATFORM: offscreen
run: cmake -E chdir obj ctest -j $(sysctl -n hw.logicalcpu) --build-config RelWithDebInfo --output-on-failure
uses: ./.github/actions/run-tests
with:
build-dir: obj
build-config: RelWithDebInfo
- name: Install
run: cmake --install obj --config RelWithDebInfo --strip
- uses: actions/upload-artifact@v6
@@ -726,9 +749,10 @@ jobs:
run: cmake --build obj --config RelWithDebInfo
- name: Test
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
env:
QT_QPA_PLATFORM: offscreen
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure
uses: ./.github/actions/run-tests
with:
build-dir: obj
build-config: RelWithDebInfo
- name: Install
run: cmake --install obj --config RelWithDebInfo --strip
- uses: actions/upload-artifact@v6
@@ -800,9 +824,10 @@ jobs:
run: cmake --build obj --config RelWithDebInfo
- name: Test
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
env:
QT_QPA_PLATFORM: offscreen
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure
uses: ./.github/actions/run-tests
with:
build-dir: obj
build-config: RelWithDebInfo
- name: Install
run: cmake --install obj --config RelWithDebInfo --strip
- uses: actions/upload-artifact@v6
@@ -870,7 +895,10 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
env:
TMPDIR: /private/tmp
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure
uses: ./.github/actions/run-tests
with:
build-dir: obj
build-config: RelWithDebInfo
- name: Install
run: cmake --install obj --config RelWithDebInfo --strip
- uses: actions/upload-artifact@v6