test.yaml 26 KB

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