| 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: | |
| # Uses the macOS runner's pre-installed Homebrew so clang-format tracks | |
| # whatever version `brew install clang-format` currently resolves to on | |
| # the maintainer's own Mac, instead of a version pinned in this file. | |
| runs-on: macos-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: install clang-format | |
| run: | | |
| brew update | |
| brew install clang-format coreutils | |
| - 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] | |
| 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 WebSocket TLS thread safety test | |
| if: matrix.tls_backend == 'openssl' | |
| run: cd test && make test_websocket_thread_safety && ./test_websocket_thread_safety | |
| - name: build and run ThreadPool test | |
| run: cd test && make test_thread_pool && ./test_thread_pool | |
| # Ubuntu 26.04's apt ships Mbed TLS 3.6, giving 3.x coverage that | |
| # ubuntu-latest (24.04 = 2.28) and macOS (Homebrew = 4.x) no longer provide. | |
| # Uses the 26.04 public-preview image; fold into the main ubuntu matrix once | |
| # ubuntu-latest moves to 26.04. | |
| ubuntu-2604-mbedtls: | |
| runs-on: ubuntu-26.04 | |
| 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: ubuntu-26.04 (mbedtls 3.x) | |
| 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 Mbed TLS | |
| run: sudo apt-get install -y libmbedtls-dev | |
| - name: build and run tests (Mbed TLS) | |
| run: cd test && make test_split_mbedtls && make test_mbedtls_parallel | |
| # BoringSSL is Google's fork of OpenSSL. It has no API stability guarantee | |
| # and is not packaged by distros, so we build it from source. cpp-httplib | |
| # treats it as an OpenSSL backend variant via the OPENSSL_IS_BORINGSSL | |
| # macro (see httplib.h). This job is best-effort: continue-on-error keeps | |
| # upstream API drift from blocking PRs while still surfacing breakage. | |
| ubuntu-boringssl: | |
| 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') | |
| continue-on-error: true | |
| name: ubuntu (boringssl, best-effort) | |
| env: | |
| # Tracking HEAD keeps us honest about upstream churn. If breakage | |
| # becomes routine, replace HEAD with a 40-char commit SHA; the | |
| # resolve step uses the SHA directly when it matches that shape. | |
| BORINGSSL_REF: HEAD | |
| BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install | |
| 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: resolve BoringSSL commit | |
| id: boringssl-rev | |
| # Accept either a ref name (resolved via git ls-remote) or a full | |
| # 40-char SHA used directly. ls-remote does not list arbitrary | |
| # commit SHAs, so pinning requires the second path. | |
| run: | | |
| if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then | |
| sha="${BORINGSSL_REF}" | |
| echo "Using pinned BoringSSL SHA: ${sha}" | |
| else | |
| sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}') | |
| if [ -z "$sha" ]; then | |
| echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2 | |
| exit 1 | |
| fi | |
| echo "Resolved ${BORINGSSL_REF} -> ${sha}" | |
| fi | |
| echo "sha=${sha}" >> "$GITHUB_OUTPUT" | |
| - name: cache BoringSSL build | |
| id: boringssl-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.BORINGSSL_PREFIX }} | |
| key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }} | |
| - name: build BoringSSL | |
| if: steps.boringssl-cache.outputs.cache-hit != 'true' | |
| run: | | |
| set -e | |
| git clone https://boringssl.googlesource.com/boringssl boringssl | |
| cd boringssl | |
| git checkout "${{ steps.boringssl-rev.outputs.sha }}" | |
| cmake -S . -B build \ | |
| -DCMAKE_BUILD_TYPE=Release \ | |
| -DBUILD_SHARED_LIBS=OFF \ | |
| -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ | |
| -DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}" | |
| cmake --build build -j"$(nproc)" --target install | |
| - name: build and run tests (BoringSSL) | |
| # Override OPENSSL_SUPPORT to point the existing OpenSSL Makefile path | |
| # at BoringSSL's prefix. BoringSSL defines OPENSSL_IS_BORINGSSL in | |
| # <openssl/base.h>, which httplib.h and test.cc use to switch on API | |
| # differences (e.g. SAN-only hostname verification, no CN fallback). | |
| # | |
| # BoringSSL's public headers (<openssl/stack.h>) use std::enable_if_t, | |
| # so consumers must compile with C++14 or later. cpp-httplib itself | |
| # supports C++11, but anyone pairing it with BoringSSL inherits this | |
| # constraint. EXTRA_CXXFLAGS appends after the Makefile's -std=c++11 | |
| # and the later flag wins. | |
| run: | | |
| cd test | |
| BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -lpthread" | |
| make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17" | |
| make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17" | |
| env: | |
| LSAN_OPTIONS: suppressions=lsan_suppressions.txt | |
| # macOS counterpart of the BoringSSL job. Same best-effort posture; the | |
| # extra framework links cover the macOS Keychain integration that | |
| # httplib.h auto-enables for any TLS backend on macOS. | |
| macos-boringssl: | |
| 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') | |
| continue-on-error: true | |
| name: macos (boringssl, best-effort) | |
| env: | |
| BORINGSSL_REF: HEAD | |
| BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install | |
| steps: | |
| - name: checkout | |
| uses: actions/checkout@v4 | |
| - name: resolve BoringSSL commit | |
| id: boringssl-rev | |
| # Accept either a ref name (resolved via git ls-remote) or a full | |
| # 40-char SHA used directly. ls-remote does not list arbitrary | |
| # commit SHAs, so pinning requires the second path. | |
| run: | | |
| if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then | |
| sha="${BORINGSSL_REF}" | |
| echo "Using pinned BoringSSL SHA: ${sha}" | |
| else | |
| sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}') | |
| if [ -z "$sha" ]; then | |
| echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2 | |
| exit 1 | |
| fi | |
| echo "Resolved ${BORINGSSL_REF} -> ${sha}" | |
| fi | |
| echo "sha=${sha}" >> "$GITHUB_OUTPUT" | |
| - name: cache BoringSSL build | |
| id: boringssl-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.BORINGSSL_PREFIX }} | |
| key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }} | |
| - name: build BoringSSL | |
| if: steps.boringssl-cache.outputs.cache-hit != 'true' | |
| run: | | |
| set -e | |
| git clone https://boringssl.googlesource.com/boringssl boringssl | |
| cd boringssl | |
| git checkout "${{ steps.boringssl-rev.outputs.sha }}" | |
| cmake -S . -B build \ | |
| -DCMAKE_BUILD_TYPE=Release \ | |
| -DBUILD_SHARED_LIBS=OFF \ | |
| -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ | |
| -DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}" | |
| cmake --build build -j"$(sysctl -n hw.ncpu)" --target install | |
| - name: build and run tests (BoringSSL) | |
| run: | | |
| cd test | |
| # CoreFoundation/Security frameworks satisfy the Keychain integration | |
| # auto-enabled in httplib.h for macOS TLS builds. | |
| BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -framework CoreFoundation -framework Security" | |
| make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17" | |
| make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17" | |
| env: | |
| LSAN_OPTIONS: suppressions=lsan_suppressions.txt | |
| # 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] | |
| 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 | |
| - 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 WebSocket TLS thread safety test | |
| if: matrix.tls_backend == 'openssl' | |
| run: cd test && make test_websocket_thread_safety && ./test_websocket_thread_safety | |
| - name: build and run ThreadPool test | |
| run: cd test && make test_thread_pool && ./test_thread_pool | |
| ios-parse-check: | |
| 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') | |
| name: ios header parse check (not officially supported) | |
| steps: | |
| - name: checkout | |
| uses: actions/checkout@v4 | |
| - name: install OpenSSL headers | |
| run: brew install openssl@3 | |
| - name: verify header parses on iOS target | |
| run: | | |
| IOS_SDK=$(xcrun --sdk iphoneos --show-sdk-path) | |
| OPENSSL_INC=$(brew --prefix openssl@3)/include | |
| echo "Using iOS SDK: $IOS_SDK" | |
| echo '#include "httplib.h"' | clang++ \ | |
| -isysroot "$IOS_SDK" \ | |
| -target arm64-apple-ios16.0 \ | |
| -std=c++11 \ | |
| -DCPPHTTPLIB_OPENSSL_SUPPORT \ | |
| -I"$OPENSSL_INC" \ | |
| -I. -Wall -Wextra \ | |
| -fsyntax-only -x c++ - | |
| - name: verify CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN is rejected on iOS | |
| run: | | |
| IOS_SDK=$(xcrun --sdk iphoneos --show-sdk-path) | |
| OPENSSL_INC=$(brew --prefix openssl@3)/include | |
| out=$(echo '#include "httplib.h"' | clang++ \ | |
| -isysroot "$IOS_SDK" \ | |
| -target arm64-apple-ios16.0 \ | |
| -std=c++11 \ | |
| -DCPPHTTPLIB_OPENSSL_SUPPORT \ | |
| -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN \ | |
| -I"$OPENSSL_INC" \ | |
| -I. \ | |
| -fsyntax-only -x c++ - 2>&1 || true) | |
| if echo "$out" | grep -q "only supported on macOS"; then | |
| echo "OK: #error fired as expected" | |
| else | |
| echo "FAIL: expected #error did not fire" | |
| echo "--- compiler output ---" | |
| echo "$out" | |
| exit 1 | |
| fi | |
| windows: | |
| runs-on: windows-latest | |
| permissions: | |
| contents: read | |
| 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 }} | |
| shell: pwsh | |
| run: | | |
| # Chocolatey's openssl package hardcodes a versioned slproweb URL, and | |
| # slproweb keeps only the newest build of each branch. The package | |
| # therefore 404s on every OpenSSL release until someone respins it. | |
| # Read slproweb's own manifest instead: it is updated at the same time | |
| # as the downloads it points at, so the URL is always live. | |
| $ErrorActionPreference = 'Stop' | |
| $ProgressPreference = 'SilentlyContinue' # Invoke-WebRequest is slow with it | |
| $manifest = 'https://raw.githubusercontent.com/slproweb/opensslhashes/master/win32_openssl_hashes.json' | |
| $entry = (Invoke-RestMethod $manifest).files.PSObject.Properties.Value | | |
| Where-Object { | |
| $_.bits -eq 64 -and $_.arch -eq 'INTEL' -and | |
| -not $_.light -and $_.installer -eq 'exe' -and $_.basever -like '4.*' | |
| } | | |
| Sort-Object { [version]$_.basever } | Select-Object -Last 1 | |
| if (-not $entry) { throw 'No 64-bit OpenSSL 4.x installer found in the manifest' } | |
| Write-Host "Installing OpenSSL $($entry.basever) from $($entry.url)" | |
| $installer = Join-Path $env:RUNNER_TEMP 'Win64OpenSSL.exe' | |
| Invoke-WebRequest $entry.url -OutFile $installer | |
| $actual = (Get-FileHash $installer -Algorithm SHA512).Hash.ToLower() | |
| if ($actual -ne $entry.sha512.ToLower()) { | |
| throw "SHA512 mismatch: expected $($entry.sha512), got $actual" | |
| } | |
| # Same silent flags the Chocolatey package used. The installer is Inno | |
| # Setup, so /DIR pins the location CMake already looks in. The inner | |
| # quotes matter: ArgumentList joins on spaces, so an unquoted /DIR | |
| # would install to C:\Program and only fail later, at load time. | |
| $dir = 'C:\Program Files\OpenSSL' | |
| $proc = Start-Process $installer -Wait -PassThru -ArgumentList ` | |
| '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', '/SP-', "/DIR=`"$dir`"" | |
| if ($proc.ExitCode -ne 0) { throw "Installer exited with $($proc.ExitCode)" } | |
| # Catch a misplaced install here rather than at link or load time. | |
| if (-not (Test-Path "$dir\lib\VC\x64\MD\libcrypto.lib")) { | |
| throw "OpenSSL import libraries missing under $dir" | |
| } | |
| if (-not (Get-ChildItem "$dir\bin\libcrypto-*.dll" -ErrorAction SilentlyContinue)) { | |
| throw "OpenSSL runtime DLLs missing under $dir\bin" | |
| } | |
| "$dir\bin" | Out-File $env:GITHUB_PATH -Append -Encoding utf8 | |
| "OPENSSL_CONF=$dir\bin\openssl.cfg" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 | |
| - 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" |