mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-06-10 16:47:14 +00:00
These tests reach out to external services (httpbin, YouTube, ...) and flake on CI runners whenever those services are slow or unreachable. The previous shard runner script silently masked these failures; now that runs report them faithfully, default the filter to -*_Online. Override via workflow_dispatch + the gtest_filter input to include them when explicitly desired. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
355 lines
14 KiB
YAML
355 lines
14 KiB
YAML
name: test
|
|
|
|
on:
|
|
push:
|
|
pull_request:
|
|
workflow_dispatch:
|
|
inputs:
|
|
gtest_filter:
|
|
description: 'Google Test filter'
|
|
test_linux:
|
|
description: 'Test on Linux'
|
|
type: boolean
|
|
default: true
|
|
test_macos:
|
|
description: 'Test on MacOS'
|
|
type: boolean
|
|
default: true
|
|
test_windows:
|
|
description: 'Test on Windows'
|
|
type: boolean
|
|
default: true
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
# Exclude *_Online tests by default — they hit external services and flake on
|
|
# CI runners. Run with workflow_dispatch + a custom filter to include them.
|
|
GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '-*_Online' }}
|
|
|
|
jobs:
|
|
style-check:
|
|
runs-on: ubuntu-latest
|
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
continue-on-error: true
|
|
steps:
|
|
- name: checkout
|
|
uses: actions/checkout@v4
|
|
- name: run style check
|
|
run: |
|
|
clang-format --version
|
|
cd test && make style_check
|
|
|
|
build-and-test-on-32bit:
|
|
runs-on: ubuntu-latest
|
|
if: >
|
|
(github.event_name == 'push') ||
|
|
(github.event_name == 'pull_request' &&
|
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
|
strategy:
|
|
matrix:
|
|
config:
|
|
- arch_flags: -m32
|
|
arch_suffix: :i386
|
|
name: (32-bit)
|
|
steps:
|
|
- name: checkout
|
|
uses: actions/checkout@v4
|
|
- name: install libraries
|
|
run: |
|
|
sudo dpkg --add-architecture i386
|
|
sudo apt-get update
|
|
sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \
|
|
libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \
|
|
zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \
|
|
libzstd-dev${{ matrix.config.arch_suffix }}
|
|
- name: build and run tests
|
|
run: cd test && make test EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}"
|
|
|
|
ubuntu:
|
|
runs-on: ubuntu-latest
|
|
if: >
|
|
(github.event_name == 'push') ||
|
|
(github.event_name == 'pull_request' &&
|
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
tls_backend: [openssl, mbedtls, wolfssl]
|
|
# TODO: mbedTLS jobs hit pre-existing flaky failures (timing-sensitive
|
|
# ServerTest cases under ASAN+mbedTLS). Allow them to fail without
|
|
# blocking until the underlying flakiness is investigated.
|
|
continue-on-error: ${{ matrix.tls_backend == 'mbedtls' }}
|
|
name: ubuntu (${{ matrix.tls_backend }})
|
|
steps:
|
|
- name: checkout
|
|
uses: actions/checkout@v4
|
|
- name: install common libraries
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
|
|
- name: install OpenSSL
|
|
if: matrix.tls_backend == 'openssl'
|
|
run: sudo apt-get install -y libssl-dev
|
|
- name: install Mbed TLS
|
|
if: matrix.tls_backend == 'mbedtls'
|
|
run: sudo apt-get install -y libmbedtls-dev
|
|
- name: install wolfSSL
|
|
if: matrix.tls_backend == 'wolfssl'
|
|
run: sudo apt-get install -y libwolfssl-dev
|
|
- name: build and run tests (OpenSSL)
|
|
if: matrix.tls_backend == 'openssl'
|
|
run: cd test && make test_split && make test_openssl_parallel
|
|
env:
|
|
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
|
- name: build and run tests (Mbed TLS)
|
|
if: matrix.tls_backend == 'mbedtls'
|
|
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
|
- name: build and run tests (wolfSSL)
|
|
if: matrix.tls_backend == 'wolfssl'
|
|
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
|
- name: run fuzz test target
|
|
if: matrix.tls_backend == 'openssl'
|
|
run: cd test && make fuzz_test
|
|
- name: build and run WebSocket heartbeat test
|
|
if: matrix.tls_backend == 'openssl'
|
|
run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
|
|
- name: build and run ThreadPool test
|
|
run: cd test && make test_thread_pool && ./test_thread_pool
|
|
|
|
# Reproducer for https://github.com/yhirose/cpp-httplib/issues/2431.
|
|
# On Linux/glibc, getaddrinfo_with_timeout() schedules an asynchronous
|
|
# DNS lookup with getaddrinfo_a(GAI_NOWAIT) using a stack-local gaicb.
|
|
# When gai_suspend() hits the connection timeout, gai_cancel() is called
|
|
# but does not block; the resolver worker can later write back into the
|
|
# destroyed stack frame. To make the worker actually reach that write,
|
|
# the test job runs a loopback UDP responder (test/dns_test_fixture.py)
|
|
# that delays its reply past the test's 1s timeout, and uses an iptables
|
|
# NAT rule so glibc's lookups land on that fixture instead of a real
|
|
# nameserver. With ASAN's detect_stack_use_after_return enabled, the
|
|
# late write-back is reported as a stack-use-after-return.
|
|
issue-2431-repro:
|
|
runs-on: ubuntu-latest
|
|
if: >
|
|
(github.event_name == 'push') ||
|
|
(github.event_name == 'pull_request' &&
|
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
|
name: issue-2431 repro (Linux + ASAN)
|
|
# Bound the whole job in case anything in the test harness hangs
|
|
# unexpectedly. With the fixture in place a normal run is well under
|
|
# a minute either way (ASAN abort on broken HEAD, clean pass on fix).
|
|
timeout-minutes: 5
|
|
env:
|
|
DNS_FIXTURE_PORT: "15353"
|
|
DNS_FIXTURE_DELAY: "3"
|
|
steps:
|
|
- name: checkout
|
|
uses: actions/checkout@v4
|
|
- name: install libraries
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y libssl-dev zlib1g-dev libbrotli-dev \
|
|
libzstd-dev libcurl4-openssl-dev iptables util-linux iproute2
|
|
- name: start loopback DNS test fixture
|
|
run: |
|
|
# Force glibc through its DNS code path: Ubuntu's default
|
|
# nsswitch short-circuits to NOTFOUND through mdns4_minimal,
|
|
# which would skip the buggy code entirely.
|
|
sudo sed -i 's/^hosts:.*/hosts: dns/' /etc/nsswitch.conf
|
|
# Run the loopback fixture (delayed UDP responder).
|
|
python3 test/dns_test_fixture.py "$DNS_FIXTURE_PORT" "$DNS_FIXTURE_DELAY" \
|
|
>/tmp/dns_fixture.log 2>&1 &
|
|
echo $! | sudo tee /tmp/dns_fixture.pid >/dev/null
|
|
# Wait for the fixture to start listening.
|
|
for _ in $(seq 1 50); do
|
|
if ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT"; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT" \
|
|
|| { echo "fixture failed to start"; cat /tmp/dns_fixture.log; exit 1; }
|
|
# Send the test process's DNS lookups to the loopback fixture.
|
|
# NAT only the local OUTPUT chain; conntrack handles the reply path.
|
|
sudo iptables -t nat -I OUTPUT -p udp --dport 53 \
|
|
-j REDIRECT --to-port "$DNS_FIXTURE_PORT"
|
|
# Sanity check: a query must take at least the fixture delay
|
|
# and resolve to NXDOMAIN (proving traffic reaches the fixture).
|
|
start=$(date +%s)
|
|
getent hosts unresolvable-host.invalid >/dev/null 2>&1 || true
|
|
elapsed=$(( $(date +%s) - start ))
|
|
if [ "$elapsed" -lt 2 ]; then
|
|
echo "ERROR: lookup returned in ${elapsed}s; fixture not in path" >&2
|
|
exit 1
|
|
fi
|
|
echo "[ok] DNS lookups are routed to the test fixture (took ${elapsed}s)"
|
|
- name: build test binary
|
|
run: cd test && make test
|
|
- name: run GetAddrInfoAsyncCancelTest
|
|
run: |
|
|
cd test
|
|
ARCH=$(uname -m)
|
|
CPPHTTPLIB_TEST_ISSUE_2431=1 \
|
|
ASAN_OPTIONS=detect_stack_use_after_return=1 \
|
|
LSAN_OPTIONS=suppressions=lsan_suppressions.txt \
|
|
setarch "$ARCH" -R \
|
|
./test --gtest_filter='GetAddrInfoAsyncCancelTest.*'
|
|
- name: tear down test fixture
|
|
if: always()
|
|
run: |
|
|
sudo iptables -t nat -F OUTPUT || true
|
|
if [ -f /tmp/dns_fixture.pid ]; then
|
|
sudo kill "$(cat /tmp/dns_fixture.pid)" 2>/dev/null || true
|
|
fi
|
|
|
|
macos:
|
|
runs-on: macos-latest
|
|
if: >
|
|
(github.event_name == 'push') ||
|
|
(github.event_name == 'pull_request' &&
|
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
tls_backend: [openssl, mbedtls, wolfssl]
|
|
# See ubuntu job above.
|
|
continue-on-error: ${{ matrix.tls_backend == 'mbedtls' }}
|
|
name: macos (${{ matrix.tls_backend }})
|
|
steps:
|
|
- name: checkout
|
|
uses: actions/checkout@v4
|
|
- name: install Mbed TLS
|
|
if: matrix.tls_backend == 'mbedtls'
|
|
run: brew install mbedtls@3
|
|
- name: install wolfSSL
|
|
if: matrix.tls_backend == 'wolfssl'
|
|
run: brew install wolfssl
|
|
- name: build and run tests (OpenSSL)
|
|
if: matrix.tls_backend == 'openssl'
|
|
run: cd test && make test_split && make test_openssl_parallel
|
|
env:
|
|
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
|
- name: build and run tests (Mbed TLS)
|
|
if: matrix.tls_backend == 'mbedtls'
|
|
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
|
- name: build and run tests (wolfSSL)
|
|
if: matrix.tls_backend == 'wolfssl'
|
|
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
|
- name: run fuzz test target
|
|
if: matrix.tls_backend == 'openssl'
|
|
run: cd test && make fuzz_test
|
|
- name: build and run WebSocket heartbeat test
|
|
if: matrix.tls_backend == 'openssl'
|
|
run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
|
|
- name: build and run ThreadPool test
|
|
run: cd test && make test_thread_pool && ./test_thread_pool
|
|
|
|
windows:
|
|
runs-on: windows-latest
|
|
if: >
|
|
(github.event_name == 'push') ||
|
|
(github.event_name == 'pull_request' &&
|
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true')
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
config:
|
|
- with_ssl: false
|
|
compiled: false
|
|
run_tests: true
|
|
name: without SSL
|
|
- with_ssl: true
|
|
compiled: false
|
|
run_tests: true
|
|
name: with SSL
|
|
- with_ssl: false
|
|
compiled: true
|
|
run_tests: false
|
|
name: compiled
|
|
name: windows ${{ matrix.config.name }}
|
|
steps:
|
|
- name: Prepare Git for Checkout on Windows
|
|
run: |
|
|
git config --global core.autocrlf false
|
|
git config --global core.eol lf
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
- name: Export GitHub Actions cache environment variables
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
|
- name: Setup msbuild on windows
|
|
uses: microsoft/setup-msbuild@v2
|
|
- name: Cache vcpkg packages
|
|
id: vcpkg-cache
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: C:/vcpkg/installed
|
|
key: vcpkg-installed-windows-gtest-curl-zlib-brotli-zstd
|
|
- name: Install vcpkg dependencies
|
|
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
|
|
run: vcpkg install gtest curl zlib brotli zstd
|
|
- name: Install OpenSSL
|
|
if: ${{ matrix.config.with_ssl }}
|
|
run: choco install openssl
|
|
- name: Configure CMake ${{ matrix.config.name }}
|
|
run: >
|
|
cmake -B build -S .
|
|
-DCMAKE_BUILD_TYPE=Release
|
|
-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake
|
|
-DHTTPLIB_TEST=ON
|
|
-DHTTPLIB_COMPILE=${{ matrix.config.compiled && 'ON' || 'OFF' }}
|
|
-DHTTPLIB_USE_OPENSSL_IF_AVAILABLE=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
|
|
-DHTTPLIB_REQUIRE_ZLIB=ON
|
|
-DHTTPLIB_REQUIRE_BROTLI=ON
|
|
-DHTTPLIB_REQUIRE_ZSTD=ON
|
|
-DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
|
|
- name: Build ${{ matrix.config.name }}
|
|
run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine
|
|
- name: Run tests ${{ matrix.config.name }}
|
|
if: ${{ matrix.config.run_tests }}
|
|
shell: pwsh
|
|
working-directory: build/test
|
|
run: |
|
|
$shards = 4
|
|
$procs = @()
|
|
for ($i = 0; $i -lt $shards; $i++) {
|
|
$log = "shard_${i}.log"
|
|
$procs += Start-Process -FilePath ./Release/httplib-test.exe `
|
|
-ArgumentList "--gtest_color=yes","--gtest_filter=${{ github.event.inputs.gtest_filter || '-*_Online' }}" `
|
|
-NoNewWindow -PassThru -RedirectStandardOutput $log -RedirectStandardError "${log}.err" `
|
|
-Environment @{ GTEST_TOTAL_SHARDS="$shards"; GTEST_SHARD_INDEX="$i" }
|
|
}
|
|
$procs | Wait-Process
|
|
$failed = $false
|
|
for ($i = 0; $i -lt $shards; $i++) {
|
|
$log = "shard_${i}.log"
|
|
$proc = $procs[$i]
|
|
$hasPassed = Select-String -Path $log -Pattern "\[ PASSED \]" -Quiet
|
|
$hasFailed = Select-String -Path $log -Pattern "\[ FAILED \]" -Quiet
|
|
if ($hasPassed -and -not $hasFailed -and $proc.ExitCode -eq 0) {
|
|
$passed = (Select-String -Path $log -Pattern "\[ PASSED \]").Line
|
|
Write-Host "Shard ${i}: $passed"
|
|
} else {
|
|
Write-Host "=== Shard $i FAILED (exit=$($proc.ExitCode)) ==="
|
|
Get-Content $log
|
|
if (Test-Path "${log}.err") { Get-Content "${log}.err" }
|
|
$failed = $true
|
|
}
|
|
}
|
|
if ($failed) { exit 1 }
|
|
Write-Host "All shards passed."
|
|
|
|
env:
|
|
VCPKG_ROOT: "C:/vcpkg"
|
|
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|