test.yaml 24 KB

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