test.yaml 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. name: test
  2. on:
  3. push:
  4. pull_request:
  5. workflow_dispatch:
  6. inputs:
  7. gtest_filter:
  8. description: 'Google Test filter'
  9. test_linux:
  10. description: 'Test on Linux'
  11. type: boolean
  12. default: true
  13. test_macos:
  14. description: 'Test on MacOS'
  15. type: boolean
  16. default: true
  17. test_windows:
  18. description: 'Test on Windows'
  19. type: boolean
  20. default: true
  21. concurrency:
  22. group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
  23. cancel-in-progress: true
  24. env:
  25. # Exclude *_Online tests by default — they hit external services and flake on
  26. # CI runners. Run with workflow_dispatch + a custom filter to include them.
  27. GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '-*_Online' }}
  28. jobs:
  29. style-check:
  30. runs-on: ubuntu-latest
  31. if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
  32. continue-on-error: true
  33. steps:
  34. - name: checkout
  35. uses: actions/checkout@v4
  36. - name: install clang-format
  37. run: |
  38. pipx install clang-format==23.1.0
  39. echo "$HOME/.local/bin" >> "$GITHUB_PATH"
  40. - name: run style check
  41. run: |
  42. clang-format --version
  43. cd test && make style_check
  44. build-and-test-on-32bit:
  45. runs-on: ubuntu-latest
  46. if: >
  47. (github.event_name == 'push') ||
  48. (github.event_name == 'pull_request' &&
  49. github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
  50. (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
  51. strategy:
  52. matrix:
  53. config:
  54. - arch_flags: -m32
  55. arch_suffix: :i386
  56. name: (32-bit)
  57. steps:
  58. - name: checkout
  59. uses: actions/checkout@v4
  60. - name: install libraries
  61. run: |
  62. sudo dpkg --add-architecture i386
  63. sudo apt-get update
  64. sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \
  65. libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \
  66. zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \
  67. libzstd-dev${{ matrix.config.arch_suffix }}
  68. - name: build and run tests
  69. run: cd test && make test EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}"
  70. ubuntu:
  71. runs-on: ubuntu-latest
  72. if: >
  73. (github.event_name == 'push') ||
  74. (github.event_name == 'pull_request' &&
  75. github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
  76. (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
  77. strategy:
  78. fail-fast: false
  79. matrix:
  80. tls_backend: [openssl, mbedtls, wolfssl]
  81. name: ubuntu (${{ matrix.tls_backend }})
  82. steps:
  83. - name: checkout
  84. uses: actions/checkout@v4
  85. - name: install common libraries
  86. run: |
  87. sudo apt-get update
  88. sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
  89. - name: install OpenSSL
  90. if: matrix.tls_backend == 'openssl'
  91. run: sudo apt-get install -y libssl-dev
  92. - name: install Mbed TLS
  93. if: matrix.tls_backend == 'mbedtls'
  94. run: sudo apt-get install -y libmbedtls-dev
  95. - name: install wolfSSL
  96. if: matrix.tls_backend == 'wolfssl'
  97. run: sudo apt-get install -y libwolfssl-dev
  98. - name: build and run tests (OpenSSL)
  99. if: matrix.tls_backend == 'openssl'
  100. run: cd test && make test_split && make test_openssl_parallel
  101. env:
  102. LSAN_OPTIONS: suppressions=lsan_suppressions.txt
  103. - name: build and run tests (Mbed TLS)
  104. if: matrix.tls_backend == 'mbedtls'
  105. run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
  106. - name: build and run tests (wolfSSL)
  107. if: matrix.tls_backend == 'wolfssl'
  108. run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
  109. - name: run fuzz test target
  110. if: matrix.tls_backend == 'openssl'
  111. run: cd test && make fuzz_test
  112. - name: build and run WebSocket heartbeat test
  113. if: matrix.tls_backend == 'openssl'
  114. run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
  115. - name: build and run WebSocket TLS thread safety test
  116. if: matrix.tls_backend == 'openssl'
  117. run: cd test && make test_websocket_thread_safety && ./test_websocket_thread_safety
  118. - name: build and run ThreadPool test
  119. run: cd test && make test_thread_pool && ./test_thread_pool
  120. # Ubuntu 26.04's apt ships Mbed TLS 3.6, giving 3.x coverage that
  121. # ubuntu-latest (24.04 = 2.28) and macOS (Homebrew = 4.x) no longer provide.
  122. # Uses the 26.04 public-preview image; fold into the main ubuntu matrix once
  123. # ubuntu-latest moves to 26.04.
  124. ubuntu-2604-mbedtls:
  125. runs-on: ubuntu-26.04
  126. if: >
  127. (github.event_name == 'push') ||
  128. (github.event_name == 'pull_request' &&
  129. github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
  130. (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
  131. name: ubuntu-26.04 (mbedtls 3.x)
  132. steps:
  133. - name: checkout
  134. uses: actions/checkout@v4
  135. - name: install common libraries
  136. run: |
  137. sudo apt-get update
  138. sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
  139. - name: install Mbed TLS
  140. run: sudo apt-get install -y libmbedtls-dev
  141. - name: build and run tests (Mbed TLS)
  142. run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
  143. # BoringSSL is Google's fork of OpenSSL. It has no API stability guarantee
  144. # and is not packaged by distros, so we build it from source. cpp-httplib
  145. # treats it as an OpenSSL backend variant via the OPENSSL_IS_BORINGSSL
  146. # macro (see httplib.h). This job is best-effort: continue-on-error keeps
  147. # upstream API drift from blocking PRs while still surfacing breakage.
  148. ubuntu-boringssl:
  149. runs-on: ubuntu-latest
  150. if: >
  151. (github.event_name == 'push') ||
  152. (github.event_name == 'pull_request' &&
  153. github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
  154. (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
  155. continue-on-error: true
  156. name: ubuntu (boringssl, best-effort)
  157. env:
  158. # Tracking HEAD keeps us honest about upstream churn. If breakage
  159. # becomes routine, replace HEAD with a 40-char commit SHA; the
  160. # resolve step uses the SHA directly when it matches that shape.
  161. BORINGSSL_REF: HEAD
  162. BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install
  163. steps:
  164. - name: checkout
  165. uses: actions/checkout@v4
  166. - name: install common libraries
  167. run: |
  168. sudo apt-get update
  169. sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
  170. - name: resolve BoringSSL commit
  171. id: boringssl-rev
  172. # Accept either a ref name (resolved via git ls-remote) or a full
  173. # 40-char SHA used directly. ls-remote does not list arbitrary
  174. # commit SHAs, so pinning requires the second path.
  175. run: |
  176. if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then
  177. sha="${BORINGSSL_REF}"
  178. echo "Using pinned BoringSSL SHA: ${sha}"
  179. else
  180. sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}')
  181. if [ -z "$sha" ]; then
  182. echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2
  183. exit 1
  184. fi
  185. echo "Resolved ${BORINGSSL_REF} -> ${sha}"
  186. fi
  187. echo "sha=${sha}" >> "$GITHUB_OUTPUT"
  188. - name: cache BoringSSL build
  189. id: boringssl-cache
  190. uses: actions/cache@v4
  191. with:
  192. path: ${{ env.BORINGSSL_PREFIX }}
  193. key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }}
  194. - name: build BoringSSL
  195. if: steps.boringssl-cache.outputs.cache-hit != 'true'
  196. run: |
  197. set -e
  198. git clone https://boringssl.googlesource.com/boringssl boringssl
  199. cd boringssl
  200. git checkout "${{ steps.boringssl-rev.outputs.sha }}"
  201. cmake -S . -B build \
  202. -DCMAKE_BUILD_TYPE=Release \
  203. -DBUILD_SHARED_LIBS=OFF \
  204. -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
  205. -DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}"
  206. cmake --build build -j"$(nproc)" --target install
  207. - name: build and run tests (BoringSSL)
  208. # Override OPENSSL_SUPPORT to point the existing OpenSSL Makefile path
  209. # at BoringSSL's prefix. BoringSSL defines OPENSSL_IS_BORINGSSL in
  210. # <openssl/base.h>, which httplib.h and test.cc use to switch on API
  211. # differences (e.g. SAN-only hostname verification, no CN fallback).
  212. #
  213. # BoringSSL's public headers (<openssl/stack.h>) use std::enable_if_t,
  214. # so consumers must compile with C++14 or later. cpp-httplib itself
  215. # supports C++11, but anyone pairing it with BoringSSL inherits this
  216. # constraint. EXTRA_CXXFLAGS appends after the Makefile's -std=c++11
  217. # and the later flag wins.
  218. run: |
  219. cd test
  220. BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -lpthread"
  221. make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
  222. make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
  223. env:
  224. LSAN_OPTIONS: suppressions=lsan_suppressions.txt
  225. # macOS counterpart of the BoringSSL job. Same best-effort posture; the
  226. # extra framework links cover the macOS Keychain integration that
  227. # httplib.h auto-enables for any TLS backend on macOS.
  228. macos-boringssl:
  229. runs-on: macos-latest
  230. if: >
  231. (github.event_name == 'push') ||
  232. (github.event_name == 'pull_request' &&
  233. github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
  234. (github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
  235. continue-on-error: true
  236. name: macos (boringssl, best-effort)
  237. env:
  238. BORINGSSL_REF: HEAD
  239. BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install
  240. steps:
  241. - name: checkout
  242. uses: actions/checkout@v4
  243. - name: resolve BoringSSL commit
  244. id: boringssl-rev
  245. # Accept either a ref name (resolved via git ls-remote) or a full
  246. # 40-char SHA used directly. ls-remote does not list arbitrary
  247. # commit SHAs, so pinning requires the second path.
  248. run: |
  249. if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then
  250. sha="${BORINGSSL_REF}"
  251. echo "Using pinned BoringSSL SHA: ${sha}"
  252. else
  253. sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}')
  254. if [ -z "$sha" ]; then
  255. echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2
  256. exit 1
  257. fi
  258. echo "Resolved ${BORINGSSL_REF} -> ${sha}"
  259. fi
  260. echo "sha=${sha}" >> "$GITHUB_OUTPUT"
  261. - name: cache BoringSSL build
  262. id: boringssl-cache
  263. uses: actions/cache@v4
  264. with:
  265. path: ${{ env.BORINGSSL_PREFIX }}
  266. key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }}
  267. - name: build BoringSSL
  268. if: steps.boringssl-cache.outputs.cache-hit != 'true'
  269. run: |
  270. set -e
  271. git clone https://boringssl.googlesource.com/boringssl boringssl
  272. cd boringssl
  273. git checkout "${{ steps.boringssl-rev.outputs.sha }}"
  274. cmake -S . -B build \
  275. -DCMAKE_BUILD_TYPE=Release \
  276. -DBUILD_SHARED_LIBS=OFF \
  277. -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
  278. -DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}"
  279. cmake --build build -j"$(sysctl -n hw.ncpu)" --target install
  280. - name: build and run tests (BoringSSL)
  281. run: |
  282. cd test
  283. # CoreFoundation/Security frameworks satisfy the Keychain integration
  284. # auto-enabled in httplib.h for macOS TLS builds.
  285. BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -framework CoreFoundation -framework Security"
  286. make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
  287. make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
  288. env:
  289. LSAN_OPTIONS: suppressions=lsan_suppressions.txt
  290. # Reproducer for https://github.com/yhirose/cpp-httplib/issues/2431.
  291. # On Linux/glibc, getaddrinfo_with_timeout() schedules an asynchronous
  292. # DNS lookup with getaddrinfo_a(GAI_NOWAIT) using a stack-local gaicb.
  293. # When gai_suspend() hits the connection timeout, gai_cancel() is called
  294. # but does not block; the resolver worker can later write back into the
  295. # destroyed stack frame. To make the worker actually reach that write,
  296. # the test job runs a loopback UDP responder (test/dns_test_fixture.py)
  297. # that delays its reply past the test's 1s timeout, and uses an iptables
  298. # NAT rule so glibc's lookups land on that fixture instead of a real
  299. # nameserver. With ASAN's detect_stack_use_after_return enabled, the
  300. # late write-back is reported as a stack-use-after-return.
  301. issue-2431-repro:
  302. runs-on: ubuntu-latest
  303. if: >
  304. (github.event_name == 'push') ||
  305. (github.event_name == 'pull_request' &&
  306. github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
  307. (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
  308. name: issue-2431 repro (Linux + ASAN)
  309. # Bound the whole job in case anything in the test harness hangs
  310. # unexpectedly. With the fixture in place a normal run is well under
  311. # a minute either way (ASAN abort on broken HEAD, clean pass on fix).
  312. timeout-minutes: 5
  313. env:
  314. DNS_FIXTURE_PORT: "15353"
  315. DNS_FIXTURE_DELAY: "3"
  316. steps:
  317. - name: checkout
  318. uses: actions/checkout@v4
  319. - name: install libraries
  320. run: |
  321. sudo apt-get update
  322. sudo apt-get install -y libssl-dev zlib1g-dev libbrotli-dev \
  323. libzstd-dev libcurl4-openssl-dev iptables util-linux iproute2
  324. - name: start loopback DNS test fixture
  325. run: |
  326. # Force glibc through its DNS code path: Ubuntu's default
  327. # nsswitch short-circuits to NOTFOUND through mdns4_minimal,
  328. # which would skip the buggy code entirely.
  329. sudo sed -i 's/^hosts:.*/hosts: dns/' /etc/nsswitch.conf
  330. # Run the loopback fixture (delayed UDP responder).
  331. python3 test/dns_test_fixture.py "$DNS_FIXTURE_PORT" "$DNS_FIXTURE_DELAY" \
  332. >/tmp/dns_fixture.log 2>&1 &
  333. echo $! | sudo tee /tmp/dns_fixture.pid >/dev/null
  334. # Wait for the fixture to start listening.
  335. for _ in $(seq 1 50); do
  336. if ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT"; then
  337. break
  338. fi
  339. sleep 0.1
  340. done
  341. ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT" \
  342. || { echo "fixture failed to start"; cat /tmp/dns_fixture.log; exit 1; }
  343. # Send the test process's DNS lookups to the loopback fixture.
  344. # NAT only the local OUTPUT chain; conntrack handles the reply path.
  345. sudo iptables -t nat -I OUTPUT -p udp --dport 53 \
  346. -j REDIRECT --to-port "$DNS_FIXTURE_PORT"
  347. # Sanity check: a query must take at least the fixture delay
  348. # and resolve to NXDOMAIN (proving traffic reaches the fixture).
  349. start=$(date +%s)
  350. getent hosts unresolvable-host.invalid >/dev/null 2>&1 || true
  351. elapsed=$(( $(date +%s) - start ))
  352. if [ "$elapsed" -lt 2 ]; then
  353. echo "ERROR: lookup returned in ${elapsed}s; fixture not in path" >&2
  354. exit 1
  355. fi
  356. echo "[ok] DNS lookups are routed to the test fixture (took ${elapsed}s)"
  357. - name: build test binary
  358. run: cd test && make test
  359. - name: run GetAddrInfoAsyncCancelTest
  360. run: |
  361. cd test
  362. ARCH=$(uname -m)
  363. CPPHTTPLIB_TEST_ISSUE_2431=1 \
  364. ASAN_OPTIONS=detect_stack_use_after_return=1 \
  365. LSAN_OPTIONS=suppressions=lsan_suppressions.txt \
  366. setarch "$ARCH" -R \
  367. ./test --gtest_filter='GetAddrInfoAsyncCancelTest.*'
  368. - name: tear down test fixture
  369. if: always()
  370. run: |
  371. sudo iptables -t nat -F OUTPUT || true
  372. if [ -f /tmp/dns_fixture.pid ]; then
  373. sudo kill "$(cat /tmp/dns_fixture.pid)" 2>/dev/null || true
  374. fi
  375. macos:
  376. runs-on: macos-latest
  377. if: >
  378. (github.event_name == 'push') ||
  379. (github.event_name == 'pull_request' &&
  380. github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
  381. (github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
  382. strategy:
  383. fail-fast: false
  384. matrix:
  385. tls_backend: [openssl, mbedtls, wolfssl]
  386. name: macos (${{ matrix.tls_backend }})
  387. steps:
  388. - name: checkout
  389. uses: actions/checkout@v4
  390. - name: install Mbed TLS
  391. if: matrix.tls_backend == 'mbedtls'
  392. run: brew install mbedtls
  393. - name: install wolfSSL
  394. if: matrix.tls_backend == 'wolfssl'
  395. run: brew install wolfssl
  396. - name: build and run tests (OpenSSL)
  397. if: matrix.tls_backend == 'openssl'
  398. run: cd test && make test_split && make test_openssl_parallel
  399. env:
  400. LSAN_OPTIONS: suppressions=lsan_suppressions.txt
  401. - name: build and run tests (Mbed TLS)
  402. if: matrix.tls_backend == 'mbedtls'
  403. run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
  404. - name: build and run tests (wolfSSL)
  405. if: matrix.tls_backend == 'wolfssl'
  406. run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
  407. - name: run fuzz test target
  408. if: matrix.tls_backend == 'openssl'
  409. run: cd test && make fuzz_test
  410. - name: build and run WebSocket heartbeat test
  411. if: matrix.tls_backend == 'openssl'
  412. run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
  413. - name: build and run WebSocket TLS thread safety test
  414. if: matrix.tls_backend == 'openssl'
  415. run: cd test && make test_websocket_thread_safety && ./test_websocket_thread_safety
  416. - name: build and run ThreadPool test
  417. run: cd test && make test_thread_pool && ./test_thread_pool
  418. ios-parse-check:
  419. runs-on: macos-latest
  420. if: >
  421. (github.event_name == 'push') ||
  422. (github.event_name == 'pull_request' &&
  423. github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
  424. (github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
  425. name: ios header parse check (not officially supported)
  426. steps:
  427. - name: checkout
  428. uses: actions/checkout@v4
  429. - name: install OpenSSL headers
  430. run: brew install openssl@3
  431. - name: verify header parses on iOS target
  432. run: |
  433. IOS_SDK=$(xcrun --sdk iphoneos --show-sdk-path)
  434. OPENSSL_INC=$(brew --prefix openssl@3)/include
  435. echo "Using iOS SDK: $IOS_SDK"
  436. echo '#include "httplib.h"' | clang++ \
  437. -isysroot "$IOS_SDK" \
  438. -target arm64-apple-ios16.0 \
  439. -std=c++11 \
  440. -DCPPHTTPLIB_OPENSSL_SUPPORT \
  441. -I"$OPENSSL_INC" \
  442. -I. -Wall -Wextra \
  443. -fsyntax-only -x c++ -
  444. - name: verify CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN is rejected on iOS
  445. run: |
  446. IOS_SDK=$(xcrun --sdk iphoneos --show-sdk-path)
  447. OPENSSL_INC=$(brew --prefix openssl@3)/include
  448. out=$(echo '#include "httplib.h"' | clang++ \
  449. -isysroot "$IOS_SDK" \
  450. -target arm64-apple-ios16.0 \
  451. -std=c++11 \
  452. -DCPPHTTPLIB_OPENSSL_SUPPORT \
  453. -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN \
  454. -I"$OPENSSL_INC" \
  455. -I. \
  456. -fsyntax-only -x c++ - 2>&1 || true)
  457. if echo "$out" | grep -q "only supported on macOS"; then
  458. echo "OK: #error fired as expected"
  459. else
  460. echo "FAIL: expected #error did not fire"
  461. echo "--- compiler output ---"
  462. echo "$out"
  463. exit 1
  464. fi
  465. windows:
  466. runs-on: windows-latest
  467. permissions:
  468. contents: read
  469. issues: write
  470. if: >
  471. (github.event_name == 'push') ||
  472. (github.event_name == 'pull_request' &&
  473. github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
  474. (github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true')
  475. strategy:
  476. fail-fast: false
  477. matrix:
  478. config:
  479. - with_ssl: false
  480. compiled: false
  481. run_tests: true
  482. name: without SSL
  483. - with_ssl: true
  484. compiled: false
  485. run_tests: true
  486. name: with SSL
  487. - with_ssl: false
  488. compiled: true
  489. run_tests: false
  490. name: compiled
  491. name: windows ${{ matrix.config.name }}
  492. steps:
  493. - name: Prepare Git for Checkout on Windows
  494. run: |
  495. git config --global core.autocrlf false
  496. git config --global core.eol lf
  497. - name: Checkout
  498. uses: actions/checkout@v4
  499. - name: Export GitHub Actions cache environment variables
  500. uses: actions/github-script@v7
  501. with:
  502. script: |
  503. core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
  504. core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
  505. - name: Setup msbuild on windows
  506. uses: microsoft/setup-msbuild@v2
  507. - name: Cache vcpkg packages
  508. id: vcpkg-cache
  509. uses: actions/cache@v4
  510. with:
  511. path: C:/vcpkg/installed
  512. key: vcpkg-installed-windows-gtest-curl-zlib-brotli-zstd
  513. - name: Install vcpkg dependencies
  514. if: steps.vcpkg-cache.outputs.cache-hit != 'true'
  515. run: vcpkg install gtest curl zlib brotli zstd
  516. - name: Install OpenSSL
  517. if: ${{ matrix.config.with_ssl }}
  518. shell: pwsh
  519. run: |
  520. # Chocolatey's openssl package hardcodes a versioned slproweb URL, and
  521. # slproweb keeps only the newest build of each branch. The package
  522. # therefore 404s on every OpenSSL release until someone respins it.
  523. # Read slproweb's own manifest instead: it is updated at the same time
  524. # as the downloads it points at, so the URL is always live.
  525. $ErrorActionPreference = 'Stop'
  526. $ProgressPreference = 'SilentlyContinue' # Invoke-WebRequest is slow with it
  527. $manifest = 'https://raw.githubusercontent.com/slproweb/opensslhashes/master/win32_openssl_hashes.json'
  528. $entry = (Invoke-RestMethod $manifest).files.PSObject.Properties.Value |
  529. Where-Object {
  530. $_.bits -eq 64 -and $_.arch -eq 'INTEL' -and
  531. -not $_.light -and $_.installer -eq 'exe' -and $_.basever -like '4.*'
  532. } |
  533. Sort-Object { [version]$_.basever } | Select-Object -Last 1
  534. if (-not $entry) { throw 'No 64-bit OpenSSL 4.x installer found in the manifest' }
  535. Write-Host "Installing OpenSSL $($entry.basever) from $($entry.url)"
  536. $installer = Join-Path $env:RUNNER_TEMP 'Win64OpenSSL.exe'
  537. Invoke-WebRequest $entry.url -OutFile $installer
  538. $actual = (Get-FileHash $installer -Algorithm SHA512).Hash.ToLower()
  539. if ($actual -ne $entry.sha512.ToLower()) {
  540. throw "SHA512 mismatch: expected $($entry.sha512), got $actual"
  541. }
  542. # Same silent flags the Chocolatey package used. The installer is Inno
  543. # Setup, so /DIR pins the location CMake already looks in. The inner
  544. # quotes matter: ArgumentList joins on spaces, so an unquoted /DIR
  545. # would install to C:\Program and only fail later, at load time.
  546. $dir = 'C:\Program Files\OpenSSL'
  547. $proc = Start-Process $installer -Wait -PassThru -ArgumentList `
  548. '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', '/SP-', "/DIR=`"$dir`""
  549. if ($proc.ExitCode -ne 0) { throw "Installer exited with $($proc.ExitCode)" }
  550. # Catch a misplaced install here rather than at link or load time.
  551. if (-not (Test-Path "$dir\lib\VC\x64\MD\libcrypto.lib")) {
  552. throw "OpenSSL import libraries missing under $dir"
  553. }
  554. if (-not (Get-ChildItem "$dir\bin\libcrypto-*.dll" -ErrorAction SilentlyContinue)) {
  555. throw "OpenSSL runtime DLLs missing under $dir\bin"
  556. }
  557. "$dir\bin" | Out-File $env:GITHUB_PATH -Append -Encoding utf8
  558. "OPENSSL_CONF=$dir\bin\openssl.cfg" | Out-File $env:GITHUB_ENV -Append -Encoding utf8
  559. - name: Configure CMake ${{ matrix.config.name }}
  560. run: >
  561. cmake -B build -S .
  562. -DCMAKE_BUILD_TYPE=Release
  563. -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake
  564. -DHTTPLIB_TEST=ON
  565. -DHTTPLIB_COMPILE=${{ matrix.config.compiled && 'ON' || 'OFF' }}
  566. -DHTTPLIB_USE_OPENSSL_IF_AVAILABLE=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
  567. -DHTTPLIB_REQUIRE_ZLIB=ON
  568. -DHTTPLIB_REQUIRE_BROTLI=ON
  569. -DHTTPLIB_REQUIRE_ZSTD=ON
  570. -DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
  571. - name: Build ${{ matrix.config.name }}
  572. run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine
  573. - name: Run tests ${{ matrix.config.name }}
  574. id: run_tests
  575. if: ${{ matrix.config.run_tests }}
  576. shell: pwsh
  577. working-directory: build/test
  578. run: |
  579. $shards = 4
  580. $procs = @()
  581. for ($i = 0; $i -lt $shards; $i++) {
  582. $log = "shard_${i}.log"
  583. $procs += Start-Process -FilePath ./Release/httplib-test.exe `
  584. -ArgumentList "--gtest_color=yes","--gtest_filter=${{ github.event.inputs.gtest_filter || '-*_Online' }}" `
  585. -NoNewWindow -PassThru -RedirectStandardOutput $log -RedirectStandardError "${log}.err" `
  586. -Environment @{ GTEST_TOTAL_SHARDS="$shards"; GTEST_SHARD_INDEX="$i" }
  587. }
  588. $procs | Wait-Process
  589. $failed = $false
  590. for ($i = 0; $i -lt $shards; $i++) {
  591. $log = "shard_${i}.log"
  592. $proc = $procs[$i]
  593. $hasPassed = Select-String -Path $log -Pattern "\[ PASSED \]" -Quiet
  594. $hasFailed = Select-String -Path $log -Pattern "\[ FAILED \]" -Quiet
  595. if ($hasPassed -and -not $hasFailed -and $proc.ExitCode -eq 0) {
  596. $passed = (Select-String -Path $log -Pattern "\[ PASSED \]").Line
  597. Write-Host "Shard ${i}: $passed"
  598. } else {
  599. Write-Host "=== Shard $i FAILED (exit=$($proc.ExitCode)) ==="
  600. Get-Content $log
  601. if (Test-Path "${log}.err") { Get-Content "${log}.err" }
  602. $failed = $true
  603. }
  604. }
  605. if ($failed) { exit 1 }
  606. Write-Host "All shards passed."
  607. - name: Report flaky failure on issue #2533
  608. if: >
  609. failure() && steps.run_tests.conclusion == 'failure'
  610. && matrix.config.name == 'without SSL'
  611. && github.event_name == 'push'
  612. continue-on-error: true
  613. shell: pwsh
  614. working-directory: build/test
  615. env:
  616. GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  617. run: |
  618. $summary = ""
  619. for ($i = 0; $i -lt 4; $i++) {
  620. $log = "shard_${i}.log"
  621. if (Test-Path $log) {
  622. $failedLines = Select-String -Path $log -Pattern "\[ FAILED \]"
  623. if ($failedLines) {
  624. $summary += "**Shard ${i}:**`n" + (($failedLines | ForEach-Object { $_.Line }) -join "`n") + "`n`n"
  625. }
  626. }
  627. }
  628. if (-not $summary) {
  629. Write-Host "No [ FAILED ] line in any shard log; not a test failure. Skipping the report."
  630. exit 0
  631. }
  632. $runUrl = "$($env:GITHUB_SERVER_URL)/$($env:GITHUB_REPOSITORY)/actions/runs/$($env:GITHUB_RUN_ID)"
  633. $body = "Reoccurred on push: $runUrl`n`nCommit: $($env:GITHUB_SHA)`n`n$summary"
  634. gh issue comment 2533 --repo $env:GITHUB_REPOSITORY --body $body
  635. env:
  636. VCPKG_ROOT: "C:/vcpkg"
  637. VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"