Skip to content

Add integration tools for every public ST API, with a runner and CI. v1.9.1 - #41

Merged
winlinvip merged 17 commits into
ossrs:srsfrom
winlinvip:tools
Oct 3, 2026
Merged

winlinvip merged 17 commits into
ossrs:srsfrom
winlinvip:tools

Conversation

@winlinvip

@winlinvip winlinvip commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Cover every function in ST's public API (public.h, installed as st.h) with integration tools in tools/. Each tool is a separate C program that links obj/libst.a through st.h, as SRS does. It runs a small real workload and exits non-zero on the first failed check. Features SRS uses are tested the way SRS uses them. The utests test each API by itself, and the tools test the APIs working together in one process.

This PR changes no ST library code. It only adds tools, a runner, and CI steps.

Runner

auto/tools.sh rebuilds the library with make -B <os>-debug, using the flags from EXTRA_CFLAGS and LDFLAGS. It then relinks every tool and runs it under both event systems: select, and the platform's own one, kqueue on macOS or epoll on Linux. It stops at the first failure.

./auto/tools.sh                                   # ST default, mmap stacks
EXTRA_CFLAGS=-DMALLOC_STACK ./auto/tools.sh       # the SRS build
EXTRA_CFLAGS="-DMALLOC_STACK -DMD_ASAN -fsanitize=address -fno-omit-frame-pointer" \
    LDFLAGS=-fsanitize=address ./auto/tools.sh    # ASAN

CI runs the tools after the utests in the ST default and SRS builds, and runs them with ASAN on Linux x64.

Tools

The new tools share the helpers in tools/tool.h. Each one runs in under 200 ms.

Tool Covers
lifecycle st_init, st_destroy, st_getfdlimit, st_set_primordial_stack, choosing the event system
thread create, join, exit, yield, interrupt, randomized stacks, switch callbacks, the DEBUG thread iterators
key coroutine specific data as SRS keeps its context id, destructors, the key limit
sync conditions and mutexes as SRS uses them in queues, fan-out, and locks, plus st_mutex_trylock
time st_utime, sleeps, the time cache, a custom clock
tcp a framed echo server and clients, the _resid and readv calls, timeouts, serialized accept, netfd data
udp one server socket that echoes to many peers told apart by address, as SRS serves WebRTC
unix Unix domain stream and datagram servers, socketpairs through st_netfd_open_socket
pipe the SRS signal pipe, a full pipe whose writer waits, a FIFO, and a file opened with st_open
poll st_poll on pipes and sockets at once, and st_netfd_poll
stress many loopback connections at once, create and exit cycles that reuse stacks, contention on one mutex and one condition

Existing tools

  • verify, backtrace, porting, and helloworld now fail on error, and each finishes in under half a second.
  • jmpbuf, pcs, and stack are removed. They did not link ST, and they did not check anything.
  • The Makefile now points obj at the current platform on every build. Before, after a Linux build in Docker, a macOS build left obj pointing at the Linux library, so the utests and tools failed to link.

Fixes found in review

  • lifecycle sets the primordial stack from the real main thread stack. Before, LeakSanitizer crashed at exit in the ASAN build.
  • time checks that every sleeping coroutine wakes, and that none wakes before its sleep ends. It no longer checks the order they wake in, because that order is not part of the API.

winlinvip and others added 14 commits October 3, 2026 09:50
…jmpbuf, pcs and stack studies, and repoint obj on every build.

Remove tools/jmpbuf, tools/pcs and tools/stack. They did not link ST and checked nothing: pcs and stack were bodies to step through in gdb, and jmpbuf printed a copy of the md.h jmpbuf table that could drift from it.

The remaining tools link obj/libst.a through st.h as SRS does, exit non-zero on failure, and each finishes in under half a second:

- tools/verify: checks every return code of init, create, mutex, condition variable, join and destroy instead of assert, that the signal comes after the full 100 ms sleep, and that the coroutine returns NULL. It used to sleep 1.5 s.
- tools/backtrace: main joins the coroutine and returns the result, instead of leaving through st_thread_exit. bar() checks that backtrace() returns bar, foo and start in order, so it unwinds from a coroutine stack. The __builtin_return_address fallback only requires foo, because GCC on aarch64 returns NULL for any level above 0.
- tools/porting: fails when the OS and CPU pair is not one md.h supports, or when longjmp returns to the wrong place.
- tools/helloworld: takes an optional count, 10 by default, sleeps 10 ms per line instead of 1 s, and fails on a bad count or a failed st_init or st_usleep.
- The verify, backtrace and helloworld Makefiles link libst.a once instead of twice.

Makefile: the obj link was only updated when libst.a was archived again. After a Linux build in Docker, darwin-debug-utest found the Darwin library up to date and left obj pointing at LINUX_*_DBG, so the utest and the tools failed to link on macOS. The phony obj-link target now points obj to the platform on every build.

All pass on macOS arm64, 184 utests and the four tools, and on Linux aarch64 in ossrs/srs:ubuntu20, 186 utests and the four tools, including a macOS build after a Linux one.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…tool under each event system.

auto/tools.sh rebuilds the library with make -B <os>-debug and EXTRA_CFLAGS from the environment, so a change of flags never links a stale library, and relinks each tool with LDFLAGS from the environment. It runs every folder in tools/, so a new tool is tested without editing a list, once with ST_TOOL_EVENTSYS=select and once with alt. It prints "<name> <eventsys> OK" per run, or the tool's output and "FAILED <name> <eventsys>", and exits 1 at the first failure. It works with macOS /bin/bash 3.2.

The four tools pass under both event systems in the ST default build and the SRS build (-DMALLOC_STACK) on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. A tool that exits 1 makes the runner exit 1.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…RS builds, and with ASAN on Linux x64.

Every utest matrix job (Linux x64, Linux aarch64, macOS arm64) now runs ./auto/tools.sh after the utests, once in the ST default build and once in the SRS build (EXTRA_CFLAGS=-DMALLOC_STACK). The Linux x64 job also runs it in the ASAN build, with -DMALLOC_STACK -DMD_ASAN -fsanitize=address -fno-omit-frame-pointer and LDFLAGS=-fsanitize=address, as SRS --sanitizer=on builds.

The steps pass on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN step also passes.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…y the new tools.

tools/tool.h holds the CHECK macro, tool_init() to set the event system from ST_TOOL_EVENTSYS (select or alt) then st_init and check st_get_eventsys and st_get_eventsys_name, and loopback TCP listener and client helpers on port 0.

tools/lifecycle runs the ST lifecycle as SRS does. It checks the event system is unset before st_set_eventsys, that an invalid one fails with EINVAL, and that setting it again before or after st_init fails with EBUSY. It sets the primordial stack from the stack limit as srs_set_primordial_stack does, and checks st_getfdlimit and RLIMIT_NOFILE after st_init as _st_io_init sets them: capped at FD_SETSIZE under select, and rlim_max, or rlim_cur when rlim_max is infinite, under kqueue and epoll. It interrupts and joins coroutines blocked in st_accept, st_read, st_cond_timedwait and st_usleep, each failing with EINTR at once, then calls st_destroy and exits 0.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes under 5 ms warm.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… yield, interrupt, and the switch callbacks.

tools/thread checks joinable coroutines that return a value or call st_thread_exit from a nested call, joined with and without a place for the value, 64 at once, and that a coroutine joining itself fails with EDEADLK. Detached coroutines run and exit by themselves, and joining one fails with EINVAL. st_thread_self is the primordial thread in main and the created handle in a coroutine. The default stack holds 16 KB of locals, and a custom stack that is not a page multiple holds 64 KB. Ready coroutines run in turn on st_thread_yield.

It stops a worker sleeping in st_usleep as SrsSTCoroutine does, with st_thread_interrupt then st_thread_join: the sleep fails with EINTR at once. An interrupt before the coroutine runs fails only its first sleep, and interrupting a coroutine that already finished does nothing. Coroutines run with st_randomize_stacks on and off, which returns the previous setting. st_set_switch_in_cb and st_set_switch_out_cb count the switches of each coroutine, return the previous callback, and stop counting when set to NULL. _st_show_thread_stack and _st_iterate_threads run on blocked, ready and running coroutines.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes under 15 ms warm.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…s context id, the destructors, and the key limit.

tools/key creates a key with a destructor, as SRS does for its context id, and one without. Before any key exists, and past the created ones, st_thread_getspecific returns NULL and st_thread_setspecific fails with EINVAL. Replacing a value frees the old one, setting the same value does not, setting NULL frees it, and a key without a destructor only drops the value.

Eight coroutines each set their own heap value, yield between each other, and still read their own; each value is freed when its coroutine exits, and the primordial thread's value is not touched. st_thread_setspecific2 sets a value on a new coroutine before it runs, and replaces the value of a blocked coroutine, which frees the old one, as SrsFastCoroutine::set_cid does. Destructors run before st_thread_join returns, whether the coroutine returns, calls st_thread_exit from a nested call, or is interrupted in st_usleep, and also for a detached coroutine. A new coroutine starts with no values. Keys are created up to st_key_getlimit(), and the next one fails with EAGAIN; every key is distinct, in range, and holds its own value.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes under 10 ms warm.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… uses them in its queues, fan-out and locks.

tools/sync runs a task queue like SrsAsyncCallWorker: a producer appends 300 tasks and signals, and three consumers wait in st_cond_wait while the queue is empty; every task is taken once, then a broadcast stops them. Each st_cond_signal wakes exactly one waiter, and a signal or broadcast with no waiter is not kept. A fan-out like a live stream broadcasts 10 messages to 16 waiting consumers, and each sees every message once. st_cond_timedwait times out with ETIME after at least the timeout, also for a zero timeout, and returns 0 when signaled in time.

A mutex guards a counter that 8 coroutines read, yield, and write back 50 times each: no update is lost and only one is ever inside. Unlocking a mutex that is free or owned by another coroutine fails with EPERM, and the owner locking it again fails with EDEADLK. st_mutex_trylock takes a free mutex and fails with EBUSY while another coroutine holds it. st_thread_interrupt wakes a coroutine in st_cond_wait or st_mutex_lock with EINTR, and the interrupted one does not get the mutex. Destroying a cond with a waiter, or a mutex that is held, held with a waiter, or handed to that waiter, fails with EBUSY; free ones are destroyed.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes under 20 ms warm.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… the time cache, and a custom clock.

tools/time checks that st_utime never goes back and moves across a sleep, and that st_usleep sleeps at least the given time by ST's clock and by the real monotonic clock, for 1, 5 and 10 ms; a zero sleep and st_sleep(0) return at once. Three coroutines started in another order wake in the order their sleeps end. st_utime_last_clock is set by st_init, moves after a sleep and after st_thread_yield, stays put without a switch, and never passes st_utime. st_time follows time(NULL) with the time cache off, keeps the cached value across a short sleep with it on, and st_timecache_set returns the previous setting.

st_set_utime_function works only before st_init, so a child forked before ST is set up sets a custom clock, 1000 hours ahead of the real one, then calls st_init. A coroutine sleeps 60 s on that clock; jumping the clock 60 s ahead wakes it at once in real time. After st_init, setting the clock fails with EINVAL, in the child and in the parent, which waits for the child before setting up ST.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes about 50 ms warm, most of it the sleeps.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… serves and connects, the residual counts, timeouts, and netfd data.

tools/tcp runs an echo over loopback: two coroutines accept on one listener after st_netfd_serialize_accept, which returns 0, and each accepted connection gets its own coroutine and a context kept as netfd specific data. Eight clients send 24 framed messages each, a header and a payload of up to 24000 bytes, and the server reads each with st_read_fully and echoes it with st_writev, up to 40 iovecs, more than ST's 16 local ones. The clients write with st_writev, st_write, st_write_resid and st_writev_resid, and read back with st_read_fully, st_read, st_read_resid, st_readv_resid and st_readv. The send buffers are small, so writes are partial, and TCP_NODELAY is set as SRS sets with tcp_nodelay. Every byte is checked, the message and byte counts match on both sides, the context destructor runs for every connection when it closes, and every coroutine joins; the acceptor still waiting in st_accept is interrupted.

At EOF, st_read_fully is short, st_read_resid and st_readv_resid keep what is left, and st_readv_resid moves past the filled iovecs. When the peer does not read, st_write_resid and st_writev_resid time out with ETIME part way, and the peer reads exactly the bytes they report as sent; st_write fails as a whole. st_read times out with ETIME, and st_connect does too on a backlog-1 listener that never accepts. st_netfd_setspecific frees a replaced value but not the same one; st_netfd_free runs the destructor and keeps the descriptor open, which works again through st_netfd_open_socket, and st_netfd_close closes it, so the peer reads EOF. All of it runs over IPv4 and IPv6, and prints SKIP ipv6 when ::1 cannot be bound.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes about 100 ms warm.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… to many peers told apart by address, as SRS serves WebRTC.

tools/udp runs one server coroutine on one loopback socket, as SRS's UDP mux does, and eight clients, each on its own socket, that send 32 datagrams each, a header and a payload of up to 4000 bytes, and wait for the echo. The server knows a peer only by its address: the first datagram from an address adds the peer, every later one from that address must carry the same client id, and at the end there is one peer per client with all its datagrams and bytes. The echo runs once with st_recvfrom and st_sendto, and once with st_recvmsg and st_sendmsg over several iovecs on both sides, as SRS's WebRTC path does. Every byte is checked on the server and the client, the sender of each echo is the server, the counts match on both sides, and every coroutine joins; the server, still waiting in its receive call, is stopped with st_thread_interrupt.

Datagrams queued before any read keep their sizes, one per receive, and a NULL sender is allowed. A buffer shorter than the datagram gets its start and drops the rest, and st_recvmsg reports it with MSG_TRUNC. With no sender, st_recvfrom and st_recvmsg time out with ETIME after at least the timeout, and a coroutine waiting in st_recvfrom is interrupted with EINTR. All of it runs over IPv4 and IPv6, and prints SKIP ipv6 when ::1 cannot be bound.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes about 50 ms warm.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…s on bound paths, and socketpairs through st_netfd_open_socket.

tools/unix runs a stream echo on a listener bound to a fixed path: one coroutine accepts eight clients that connect with st_connect, and each connection gets its own coroutine. The clients send 24 framed messages each, a header and a payload of up to 24000 bytes, with st_writev over up to 8 iovecs; the server reads them with st_read and echoes them with st_write, and the clients read the echo back with st_read or st_read_fully. Every byte is checked, the counts match on both sides, every connection sees its client close, and every coroutine joins; the acceptor still waiting in st_accept is interrupted. A connect to a path with no socket fails with ENOENT, and to a path whose listener is closed with ECONNREFUSED.

A datagram server on its own path echoes to four clients bound to their own paths, with st_recvfrom and st_sendto, 32 datagrams each of up to 2000 bytes, and knows each client by the path it sent from. Queued datagrams keep their boundaries, and with none st_recvfrom times out with ETIME. macOS gives a Unix datagram socket only 4096 bytes to receive into, and a send to a full one fails with ENOBUFS instead of waiting, so the tool enlarges SO_RCVBUF.

A stream socketpair opened with st_netfd_open_socket carries 512 KB each way while the writer waits for the reader, then times out an st_read with ETIME, carries an st_writev, and reads EOF after close. A datagram socketpair keeps the boundaries of st_write and st_read, and a coroutine waiting in st_read is interrupted with EINTR. The paths are removed by their literal names before and after use.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes about 20 ms warm on macOS and 30 ms on Linux.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…er fills it and waits, and a FIFO and a file opened with st_open.

tools/pipe runs the SRS signal pipe as SrsSignalManager does: a sigaction handler writes the signal number to a pipe, and a coroutine reads it from the read end opened with st_netfd_open, which makes it non-blocking. The process sends SIGHUP then SIGTERM to itself, and the coroutine reads both in order, reloads once, and quits. Then a child process sends them while every coroutine is blocked and ST waits in its event system; it sends SIGTERM only after the coroutine acknowledges the reload, so the order is kept.

A pipe between two coroutines carries 512 KB: the writer fills the pipe and waits until the reader reads it, every byte is checked, and the reader sees EOF when the writer closes. With nothing to read, st_read times out with ETIME; with no reader running, st_write_resid times out with ETIME part way, and the reader then gets exactly the bytes it reports as sent. With the read end closed and SIGPIPE ignored, st_write fails with EPIPE.

st_open always opens non-blocking, so opening a FIFO's write end with no reader fails with ENXIO; with the reader opened first, a writer coroutine sends 512 KB through the FIFO to the reader, which reads it all and then EOF. A regular file opened with st_open takes an st_write and an st_writev, reads back with st_read_fully and then EOF, and keeps O_APPEND; a missing path fails with ENOENT. The paths are removed by their literal names before and after use.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes about 25 ms warm on macOS and 35 ms on Linux.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…a worker that waits on all of them, and st_netfd_poll for reading and writing.

tools/poll polls two pipes, a Unix socketpair and a TCP connection together. With data on two of them and room on two write ends, st_poll returns at once and counts only those four, with POLLIN where data waits, POLLOUT where there is room, and 0 on the quiet ones; a TCP socket asked for both gets both, and only POLLOUT once drained. A worker keeps one pollfd array for its whole loop and waits on every channel with no timeout: for 16 bytes sent one at a time across the channels, it wakes once per byte, with exactly one descriptor ready, and reads it from the channel it came on, then quits on a stop byte.

With nothing ready, st_poll returns 0 after the timeout and leaves errno alone, as poll(2) does; st_netfd_poll turns that into -1 with ETIME. A coroutine waiting in either call is interrupted with EINTR at once, and one interrupted before it runs fails its first st_poll with EINTR without waiting, while the next one times out normally.

st_netfd_poll for POLLIN wakes when a byte arrives later on each channel, and the read then gets it. For POLLOUT, sockets and a pipe with room return at once; a pipe filled until EAGAIN times out with ETIME, and is writable again once a reader coroutine drains it. At EOF the read end is readable and the read returns 0. Every descriptor closes at the end, so nothing is left registered.

Loopback TCP may deliver a write a little after st_write returns, as on macOS, so the tool waits for the TCP byte before the poll that expects every ready descriptor, and the sender waits for the worker to read each byte before sending the next.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes about 40 ms warm on macOS, and 35 ms with select and 55 ms with epoll on Linux.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ce, coroutine create and exit cycles that reuse stacks, and contention on one mutex and one cond.

tools/stress opens 200 loopback TCP connections by default, each with a client coroutine and a server coroutine. The clients connect in batches the accept queue holds, and exchange nothing until every connection is up, so all of them are open at once. Each client then sends 8 messages, a 4-byte size and a payload of up to 4096 bytes, with st_writev, and reads the echo back with st_read_fully; the server reads with st_read_fully and echoes with st_write until the client closes. Every byte is checked, the message and byte counts match on both sides, and every coroutine joins.

Twenty cycles each create 200 coroutines, with the default stack and two custom sizes, so stacks are freed and reused across cycles. Each coroutine fills 4 KB of its stack with its own pattern, yields, and checks it; half exit with st_thread_exit and half return, and a quarter are detached. Every joinable one returns its own value, and every detached one finishes.

200 coroutines take one mutex 10 times each and yield inside it, and the counter shows no lost update and no two inside at once. Then they pass a token around through one cond with st_cond_broadcast, 3 times around, in order.

The count is an argument, for larger manual runs. A run needs about two descriptors per connection, so the tool raises the soft RLIMIT_NOFILE before st_init when it can, and fails with a clear message when st_getfdlimit is too small, such as 1024 for select.

It passes under both event systems in the ST default and SRS builds on macOS arm64, and on Linux aarch64 in ossrs/srs:ubuntu20, where the ASAN build also passes. Each run takes about 60 ms warm on macOS and 75 ms on Linux.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.01%. Comparing base (82be9d8) to head (bbaf45d).

Additional details and impacted files
@@           Coverage Diff           @@
##              srs      #41   +/-   ##
=======================================
  Coverage   96.01%   96.01%           
=======================================
  Files           8        8           
  Lines        1129     1129           
  Branches      287      287           
=======================================
  Hits         1084     1084           
  Partials       45       45           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

winlinvip and others added 2 commits October 3, 2026 12:33
…main thread stack, so LeakSanitizer does not crash at exit in the ASAN build.

The tool took the whole RLIMIT_STACK below a local as the main thread's stack, as SRS does. That is 8 MB on Linux, and most of it is unmapped. ST passes the range to ASAN on every switch back to the main thread, and with use-after-return fake stacks and leak detection on, LeakSanitizer reads it at exit and fails with "Tracer caught signal 11" every run, as in the linux-x64 CI job. SRS sets detect_leaks=0, so it never runs LeakSanitizer and never hits this.

The tool now reads the stack bounds with pthread_getattr_np on Linux and pthread_get_stackaddr_np on macOS, as the PrimordialStackTest utest does, and keeps leak detection on for st_destroy.

It passes all three tool runs on Ubuntu 24.04 aarch64 with gcc 13, including the ASAN build that failed before, and both runs on macOS arm64.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e before its sleep ends, instead of their wake order.

The check expected sleeps of 5, 10 and 15 ms to wake in that order, which holds only when each wake comes in its own scheduler pass. _st_vp_check_clock puts every expired sleeper at the head of the run queue, so sleeps that end in the same pass run in reverse. On a slow host the scheduler wakes late, and the order comes out reversed, as in the darwin-arm64 CI job; a 12 ms busy wait before the joins reproduces it every run.

Each sleeper now measures from st_utime_last_clock, the clock its deadline is based on, and must sleep at least its time; all of them must wake.

It passes with the 12 ms busy wait, and all tool runs pass on macOS arm64 and on Ubuntu 24.04 aarch64.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@winlinvip winlinvip changed the title Tools Add integration tools for every public ST API, with a runner and CI. Oct 3, 2026
@winlinvip winlinvip changed the title Add integration tools for every public ST API, with a runner and CI. Add integration tools for every public ST API, with a runner and CI. v1.9.1 Oct 3, 2026
@winlinvip
winlinvip merged commit e4e208c into ossrs:srs Oct 3, 2026
7 checks passed
winlinvip added a commit to ossrs/srs that referenced this pull request Oct 4, 2026
Sync State Threads to v1.9.1 and update the AI skills. No SRS media
server or proxy code changes; SRS builds only the ST library, which
changes only by coverage comments.

## State Threads (v1.9.0 → v1.9.1)

Synced into `trunk/3rdparty/st-srs` from
[state-threads#41](ossrs/state-threads#41) with
`./scripts/copy-from-st.sh`:

- `tools/` become integration tests that exit non-zero on failure. The
existing helloworld, verify, porting and backtrace tools are kept; the
jmpbuf, pcs and stack studies are removed.
- Eleven new tools, one per public API feature, tested the way SRS uses
them: lifecycle, thread, key, sync, time, tcp, udp, unix, pipe, poll and
stress, sharing helpers in `tools/tool.h`.
- `auto/tools.sh` builds the library with the given `EXTRA_CFLAGS` and
`LDFLAGS`, and runs every tool under the select and alternative event
systems.
- New utests for C++ exceptions on coroutine stacks, a signal
interrupting `st_connect`, and opening a closed descriptor, plus
portable socket helpers and harness fixes.
- `io.c`: gcovr exclusion comments only. `Makefile`: points `obj` to the
platform on every build, even when the library is up to date.

## Skills

- `srs-autopilot` (new): plan a complex task in a `tasks/` file and run
it with one subagent per task, each committing its task; report each
task's time, changes and tests, and review the commits on a separate
review branch.
- `srs-develop`: new Build and Test in Place rule; verification, TDD,
script and changelog rules split into lists; ST Step 3 lists utest,
`tools/` integration tests and the ASAN or Valgrind build; run Gate or
Full tiers in a subagent; test ST on Windows hosts locally and in WSL.
- `internal-codemap-for-srs` state-threads.md: lists all fifteen
integration tools and runs them with `auto/tools.sh` on macOS and Linux.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant