Files
RetroArch/.github/workflows/Linux-samples-network.yml
T
Davey Hughes d67324c157 CI: faster slow steps, and ccache that hits (#19819)
* CI: run the qemu sinc kernel tests side by side

The four NEON sinc tests ran one after another under qemu-user on the
aarch64, aarch64+crc and armv7 legs, five to six minutes a leg, with
the runner's other CPUs idle. Each is one single-threaded qemu
process, so they now run at once, and each one's output is printed
when it is done. A failing test still fails the step.

On CI the step went from 300s to 102s on aarch64, 329s to 169s on
aarch64+crc and 318s to 249s on armv7, where one test alone sets most
of its length.

* tools/tsan-samples.sh: build each suite with every CPU

The suites were built one compile at a time. Built with -j on four
CPUs, the 68 directories in the list took 29s instead of 77s, and none
that builds without -j failed with it. The suites themselves still run
one at a time, so on CI the whole step went from 737s to 622s.

* views e2e: under Wine, each worker gets a prefix of its own

Cases run side by side in one prefix share its wineserver and moved
each other's pointer, so the Wine cases ran one at a time. Each worker
now takes a copy of the prepared prefix, and Wine runs four cases at
once like Xvfb.

On CI the D3D11 and D3D12 cases went from 1457s to 479s, all 120
passing; the pointer cases passed in three more runs on four CPUs.

* CI: one gl harness job per build, and one job for the wait checkers

The gl harness ran as 13 jobs, one per driver, API and sanitizer, but
the build depends only on the API and the sanitizer: each job set up a
runner, installed the same packages and built the same tree as two
others before its one or two minutes of harness. It now runs as 6
jobs, one per build, each running the harness on every driver that
build serves. Every driver runs even when one before it fails.

The three wait and teardown checkers, each under a minute of work,
ran as three jobs; they now share one, every checker running even
when one before it fails.

On CI these 16 jobs became 7, and 27.1 runner-minutes became 24.5.

* CI: MSYS2 installs Qt's base package only

The MSYS2 lanes installed about 40 Qt 6 modules, though configure
reports Qt support disabled on all four of them, so none of it is
built.

qt6-base still brings in the libraries configure does find (FFmpeg,
SDL2, FreeType, fontconfig, libass, glslang, SPIRV-Tools, OpenSSL).
The MinGW packages fetched drop from 198 (597MiB) to 152 (319MiB). On
CI the install steps of the four lanes went from 772s to 523s, and
configure found the same libraries on each.

* CI: sanitizer and debug builds of the tree use -g1

The samples moved to -g1 in 21635dd1, but every DEBUG=1 build of the
tree still took the Makefile's -O0 -g: the gl and Vulkan harness
sanitizer builds, pace-shadow, the TSan harness, the autoconf
sanitizer matrix and the C89 debug build. A sanitizer report needs
only line tables, which -g1 keeps.

Those jobs now set CFLAGS and CXXFLAGS to -O0 -g1 in the environment.
Where configure sees them it writes them into config.mk, and where
only make does, the Makefile's CFLAGS ?= takes them; either way they
replace DEBUG=1's -O0 -g and the Makefile's own flags are still
added, which they would not be from make's command line. The
threaded-video harness takes its compile line from the tree's, so its
run step sets the same. The one-off -O1 -g test compiles in the
workflows and the companion test scripts use -g1 as well. The Makefile
default is unchanged for local debug builds.

The ASan+UBSan DEBUG=1 build, from an empty cache on four CPUs here:
ccache 83MB -> 55MB, objects 530MB -> 424MB, 84s -> 80s. The gl
harness passed on that build, and an ASan report from a -g1 binary
still names the file and line.

* CI: print ccache's statistics in every job that keeps a cache

Only 7 of the 34 jobs that restore a ccache printed its statistics, so
for the rest a cold or missed cache was invisible. Each now prints
ccache -sv before saving, including the direct and preprocessed hit
counts, and a failure to print does not fail the job.

* Makefile, Makefile.webos: GIT_VERSION for version_git.c only

-DGIT_VERSION=<hash> went on every file's command line. ccache's direct
mode hashes the command line, so each new commit missed it for every
file, and CI's restored caches only ever hit after running the
preprocessor again: about 2.5% of hits were direct. Only version_git.c
reads the macro; the rest test HAVE_GIT_VERSION, which stays global.
Makefile.webos has its own compile rules and gets the same change.

On CI, with the cache of the commit before, the ASan+UBSan build had
418 of 418 hits direct.

* CI: the cross-arch ccache keys name their architecture

The retro-atomic-cross and crc32-cross legs shared one cache key per
commit, so only one leg's cache was saved ("Unable to reserve cache
... another job may be creating this cache") and the next run's legs
all restored whichever architecture won. Each leg now saves its own.

* CI: prune superseded ccache entries

Master pushes save each job's ccache under a key ending in the commit,
and only the newest per job is ever restored. Upstream's 10GB store
held 256 entries: 61 newest-per-job (4.24GB), 195 superseded (6.46GB),
and 2.3GB of other caches. Least recently used went first, so the
newest cache of a workflow that had not run for about four hours was
gone by its next run, which then built cold.

A workflow on each master push and hourly deletes every ccache entry
keyed on a commit that a newer one of the same job on the same branch
replaces. Other caches are left to GitHub's own eviction.

* CI: MXE builds keep a ccache

The two MXE cross builds compiled RetroArch from nothing on every run.
Their images ship ccache, so they now restore and save one like the
Linux jobs, with the compilers wrapped at configure time.

On CI, with the cache of the commit before, the compile went from 154s
to 18s on x64 and 166s to 11s on i686. A run with no cache at all is
slower than before, about 250s, as is any first build through ccache.

* CI: console builds keep a ccache

Switch, PS Vita, Wii, GameCube, DOS and the macOS PowerPC cross builds
compiled every file on every run. Their images run actions/cache but
ship no ccache, so the job installs it, and a small script reads CC
and CXX from the platform Makefile with the job's own variables and
runs make with both wrapped. The PowerPC legs each keep a cache of
their own. As in the Linux jobs, the cache is capped at 250MB, its
statistics are zeroed after the restore, and it is saved on master
pushes only. Installing ccache is retried and best effort: without it
the script runs plain make. The PS Vita and Wii Salamander builds,
which run first, are left as they were.

On CI, with the cache of the commit before:

  PS Vita   120s -> 4s     Switch           84s -> 2s
  Wii        50s -> 9s     GameCube         44s -> 9s
  DOS        49s -> 4s     macOS PowerPC   116s -> 2s

With no cache at all most took 5-30s longer than before; PS Vita took
148s longer.

PS2, RS90 and RetroFW are left out: their compiles did not go through
ccache this way. Miyoo is too: it hit every compile but went only from
50s to 38s, about what installing ccache costs.

* CI: webOS builds keep a ccache

The webOS job builds three configurations with make clean between
them, each from nothing. It now sets up ccache and wraps the compilers
like the console jobs.

On CI, with the cache of the commit before and before Makefile.webos
kept the commit hash to version_git.c, the three compiles went from
110s to 53s.
2026-10-13 20:10:01 +02:00

425 lines
22 KiB
YAML

name: CI Linux samples/network
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
samples-network:
name: Build and run samples/network
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential libmbedtls-dev libbearssl-dev"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
if [ ! -d /etc/apt/ra-orig ]; then
sudo mkdir /etc/apt/ra-orig
sudo cp -a /etc/apt/sources.list.d /etc/apt/ra-orig/
[ ! -f /etc/apt/sources.list ] || sudo cp -a /etc/apt/sources.list /etc/apt/ra-orig/
fi
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
# A refresh cut short or refused leaves apt no package lists: it
# is retried, without translations or command-not-found data, and
# the mirror is put back when the archive will not answer either.
upd="-o Acquire::Retries=3 -o Acquire::http::Timeout=30
-o Acquire::Languages=none
-o Acquire::IndexTargets::deb::CNF::DefaultEnabled=false
-o APT::Update::Error-Mode=any"
ok=0
for j in 1 2; do
sudo timeout 120 apt-get update -y $upd && { ok=1; break; }
done
if [ $ok = 0 ]; then
sudo rm -rf /etc/apt/sources.list.d
sudo cp -a /etc/apt/ra-orig/sources.list.d /etc/apt/
[ ! -f /etc/apt/ra-orig/sources.list ] || sudo cp -a /etc/apt/ra-orig/sources.list /etc/apt/
sudo rm -rf /etc/apt/ra-orig
sudo timeout 120 apt-get update -y $upd || true
fi
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: Build and run webdav_options_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/webdav_options
run: |
set -eu
# Includes network/cloud_sync/webdav.c and drives the OPTIONS
# capability probe as shipped over real-world header sets. The
# probe has to separate a WebDAV server from a plain HTTP
# server that answers OPTIONS, and the target it probes is a
# collection, on which PUT is not defined (RFC 4918 9.7.1) -
# so PUT's absence from Allow must not fail it. The previous
# probe did exactly that, rejecting mod_dav and Koofr
# (libretro/RetroArch#19486) while accepting a plain nginx
# that happened to list PUT. Covers the DAV header, Allow-only
# WebDAV methods, HTTP/2 lowercase field names, plain HTTP
# with and without PUT, empty and NULL header lists, and
# whole-token matching (PROPFINDX must not match PROPFIND).
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x webdav_options_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./webdav_options_test
echo "[pass] webdav_options_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_options_test
echo "[pass] webdav_options_test (TSan)"
- name: Build and run webdav_reauth_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/webdav_reauth
run: |
set -eu
# Includes network/cloud_sync/webdav.c and plays the server
# for every request type the driver sends (OPTIONS, GET, PUT,
# DELETE, MOVE, MKCOL). A server that keeps answering 401 with
# a Digest challenge - a wrong password - must get one retry
# and then a reported failure; the driver used to retry every
# challenge, so a mistyped password looped the first request
# of a sync forever. A single stale-nonce challenge must still
# be retried and succeed. A retried PUT must send the file:
# it used to re-read it from the end the first attempt left
# it at and upload uninitialised memory in place of the save.
# A malformed challenge (a value with no closing quote, a
# trailing unquoted parameter) must be refused, not crash the
# parser or send it reading past the end of the line.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x webdav_reauth_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./webdav_reauth_test
echo "[pass] webdav_reauth_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_reauth_test
echo "[pass] webdav_reauth_test (TSan)"
- name: Build and run webdav_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/webdav_backup
run: |
set -eu
# Includes network/cloud_sync/webdav.c and plays the server,
# request by request. With destructive sync off, an upload
# must COPY the server's file to deleted/<path>-<time> before
# the PUT replaces it - it used to overwrite it in place, so
# the setting protected against deletes but not overwrites.
# Covers the order (backup first, into an existing deleted/
# collection, named as a delete names it), 404 as "nothing to
# back up", a failed backup stopping the upload, destructive
# sync and the server manifest skipping the backup, and COPY
# and PUT each getting their own Digest retry.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x webdav_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./webdav_backup_test
echo "[pass] webdav_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_backup_test
echo "[pass] webdav_backup_test (TSan)"
- name: Build and run s3_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/s3_backup
run: |
set -eu
# Includes network/cloud_sync/s3.c and plays the server,
# request by request. The S3 driver never read the
# destructive sync setting: deletes removed objects for good
# and uploads overwrote them. With it off, both must first
# CopyObject the object to deleted/<path>-<time>. Covers the
# copy source for virtual-hosted and path-style endpoints,
# x-amz-copy-source being signed, 404 as "nothing to keep", a
# failed copy (including a 200 with an <Error> body) stopping
# the delete or upload, and destructive sync and the server
# manifest skipping the copy.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x s3_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./s3_backup_test
echo "[pass] s3_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./s3_backup_test
echo "[pass] s3_backup_test (TSan)"
- name: Build and run smb_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/smb_backup
run: |
set -eu
# Includes network/cloud_sync/smb.c and runs it against an
# in-memory libsmb2 share (fake_smb.c), checking what the
# share holds after each upload. The driver used to truncate
# the server file and then write, so a write failing part way
# left a truncated save; and with destructive sync off it
# overwrote the file without keeping the old copy a delete
# keeps. Uploads now go through <path>.rauploading and the
# replaced file is kept as <path>-<time> (non-destructive).
# Covers a failed write, backup and final rename each leaving
# the old file intact with no temporary file left, no backup
# for destructive sync, a new file or the manifest, an empty
# upload and the configured subdirectory.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x smb_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./smb_backup_test
echo "[pass] smb_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./smb_backup_test
echo "[pass] smb_backup_test (TSan)"
- name: Build and run nfs_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/nfs_backup
run: |
set -eu
# Includes network/cloud_sync/nfs.c and runs it against an
# in-memory export (fake_nfs.c) keeping net_nfs3.c's status
# contract. A download is written beside the local file and
# renamed over it once complete; only the server answering
# NOENT reads as "not there"; an upload is one RENAME over the
# old file, kept as <path>-<time>[-n] with destructive sync
# off. Covers failed opens and partial reads leaving the local
# save intact, unanswered stat/unlink failing instead of
# reporting a missing file or a delete, failed write, backup
# and final rename leaving the old file and no temporary, the
# manifest skipping the backup, refused over-long names, the
# subdirectory and a concurrent MKDIR.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x nfs_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./nfs_backup_test
echo "[pass] nfs_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./nfs_backup_test
echo "[pass] nfs_backup_test (TSan)"
- name: Build and run gdrive_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/gdrive_backup
run: |
set -eu
# Includes network/cloud_sync/google_drive.c and plays the
# Drive API, request by request. With destructive sync off, a
# delete kept the old file (renamed <name>-<time>) but an
# upload replaced its content outright. An upload to an
# existing file must now files.copy it to that name in the
# same folder before the PATCH. Covers the order and the copy
# request, a failed copy stopping the upload, a 401 on the
# copy and on the PATCH each refreshing the token once (the
# PATCH retry repeating only the PATCH, not the backup), and
# destructive sync, a new file and the manifest skipping it.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x gdrive_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./gdrive_backup_test
echo "[pass] gdrive_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./gdrive_backup_test
echo "[pass] gdrive_backup_test (TSan)"
- name: Build and run cheevos_login_test (bear ASan+UBSan, bear TSan, mbed ASan+UBSan)
shell: bash
working-directory: samples/network/cheevos_login
run: |
set -eu
# Regression guard for the RetroAchievements login transport
# (libretro/RetroArch#19562, #19457): drives net_http with the
# exact login shape cheevos_client.c produces, against a local
# mock server that validates every request byte-for-byte, over
# plain HTTP and over TLS with the real SSL backend linked in.
# Covers: login on a fresh TLS connection, trust-store loading
# from a realistic messy CA bundle (BearSSL), keep-alive pool
# reuse, one-shot replay of a request on a pooled connection
# the server closed while idle, and no replay once response
# bytes have been seen. Each TLS backend's non-blocking send
# must push a 12 MiB body past a server that stops reading for
# a second without ever blocking, and deliver every byte; and
# net_http must send the same upload a slice per update, plain
# and over TLS - it used to go out in one blocking send, a call
# of a second or more (spinning at 100% CPU on a plain socket).
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x cheevos_login_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check
echo "[pass] cheevos_login_test (BearSSL, ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 180 make check
echo "[pass] cheevos_login_test (BearSSL, TSan)"
make clean SSL_BACKEND=mbed SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=mbed SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check SSL_BACKEND=mbed
echo "[pass] cheevos_login_test (mbedTLS, ASan)"
# The built-in client, RetroArch's default backend.
make clean SSL_BACKEND=retro SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=retro SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check SSL_BACKEND=retro
echo "[pass] cheevos_login_test (built-in TLS, ASan)"
# The runner's libmbedtls-dev is 2.28, which has no TLS 1.3,
# so the leg above cannot see either TLS 1.3 failure. Mbed
# TLS 3.6.0 is the one release that shows both: it fails
# every handshake unless psa_crypto_init() was called, and it
# reports session tickets from mbedtls_ssl_read(). Library
# only, ~20 s to build. A download that still fails after
# the retries skips the leg rather than failing the job.
mbed_ver=3.6.0
mbed_sha=3ecf94fcfdaacafb757786a01b7538a61750ebd85c4b024f56ff8ba1490fcd38
mbed_tar="$RUNNER_TEMP/mbedtls-$mbed_ver.tar.bz2"
if curl -fsSL --retry 3 --retry-delay 5 --max-time 120 -o "$mbed_tar" \
"https://github.com/Mbed-TLS/mbedtls/releases/download/v$mbed_ver/mbedtls-$mbed_ver.tar.bz2"; then
echo "$mbed_sha $mbed_tar" | sha256sum -c -
tar -xjf "$mbed_tar" -C "$RUNNER_TEMP"
make -C "$RUNNER_TEMP/mbedtls-$mbed_ver" -j"$(nproc)" lib >/dev/null
make clean SSL_BACKEND=mbed SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=mbed SANITIZER=address,undefined \
MBEDTLS_DIR="$RUNNER_TEMP/mbedtls-$mbed_ver"
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check SSL_BACKEND=mbed
echo "[pass] cheevos_login_test (mbedTLS $mbed_ver TLS 1.3, ASan)"
else
echo "::warning::Mbed TLS $mbed_ver download failed; TLS 1.3 leg skipped"
fi
- name: Build and run net_http_semantics_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/net_http_semantics
run: |
set -eu
# HTTP/1.1 semantics of libretro-common/net/net_http.c against
# an in-process scripted server on 127.0.0.1, which logs which
# connection every request arrived on. Covers GET 200 framing
# and pool reuse, and responses without a body: HEAD, 204 and
# 304 on keep-alive used to sit waiting for a close that never
# came, a 1xx other than 100 was taken as the final status,
# and a 101 or stray bytes after a bodyless response left the
# socket in the pool. Connection and Transfer-Encoding are
# read as lists ("Keep-Alive, Close", "gzip, chunked"),
# HTTP/1.0 without keep-alive is not pooled, and a length
# after "chunked" does not reframe the body. Redirects: an
# absolute-path Location used to go out as "GET //foo", a
# 302 loop was followed forever (now 8 hops), a streamed PUT
# redirected by a 307 is rewound and resent whole, and a 3xx
# body no longer lands in the caller's sink. Bounds: a header
# block (1xx blocks included) over 64 KiB and a chunk size
# with no hex digits fail instead of growing the buffer or
# passing as the last chunk, a Content-Length buffer never
# grows past the body, and a pooled connection that dies
# after the status line is not replayed. A URL shorter than
# its scheme is rejected without an overread. Unit matrix for
# net_http_url_join() and net_http_urldecode(), including
# buffer bounds and in-place decoding; redirects resolved
# through it ("../", "?q", "//host", fragments dropped,
# %20 left encoded). The response header block: lookup and
# iteration, the legacy string_list accessor, and 64 header
# lines costing no per-line allocations. A Content-Length or
# "chunked" on a 1xx block does not frame the final response.
# A followed 3xx's body is dropped as it arrives, not
# buffered to its Content-Length, and bytes past a body's
# Content-Length end the connection, not the transfer.
# Every case has a
# deadline, so a hang fails the case instead of the job.
# net_http is lock-free by design (DNS cache and pool owned by
# the driving thread, resolvers publish with an atomic store);
# keep it that way.
if grep -nE '\b(slock|scond)_[a-z_]+\(' ../../../libretro-common/net/net_http.c; then
echo "::error title=net_http lock::net_http.c must not take locks"
exit 1
fi
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
./net_http_semantics_test
echo "[pass] net_http_semantics_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./net_http_semantics_test
echo "[pass] net_http_semantics_test (TSan)"
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}