3proxy/tests/cases/units.py
Vladimir Dubrovin 4e1455b419 Fibers: stacks a thread switches to and back
src/fiber.c, not used yet: a fiber runs a function on a stack of its
own, yielding back to the thread which resumed it, for a thread to
serve many clients with code written as if each had a thread.

The switch is our own on x86-64 (System V), aarch64, i386, arm (ARM or
Thumb code, with or without a floating point unit) and mips o32 (with
soft, 32 or 64 bit floating point registers, position independent or
not), Windows' fibers on Windows. It saves the registers a function
has to keep and swaps the stack. Elsewhere there are no fibers
(WITH_FIBERS is not defined), nor where x86-64 Linux checks returns
against a shadow stack, which fiberusable() tells: each client has a
thread there.

Stacks are mapped with a guard page below them. A fiber whose function
returned is started again with another, its stack kept. Switches are
annotated for AddressSanitizer.

tests/fibertest.c tests them: yielding and resuming, the integer and
floating point registers a function keeps across switches both ways, a
thousand fibers resumed at random each keeping its stack, recursion
deep in a stack, fibers sleeping on the event loop's timers. The units
case, which was the loop case, runs it with looptest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:51:40 +03:00

33 lines
1.3 KiB
Python

"""The event loop and fibers on their own: tests/looptest.c and
tests/fibertest.c, built by CMake next to the 3proxy binary, each run with
a few seeds. Skipped where they were not built (the Makefiles do not build
them)."""
import os
import subprocess
def run(t):
here = os.path.dirname(os.path.abspath(t.binary))
for unit in ("looptest", "fibertest"):
path = os.path.join(here, unit + (".exe" if os.name == "nt" else ""))
if not os.path.exists(path):
t.skip(f"{unit} is built by CMake only")
continue
for seed in ("1", "2", "3"):
try:
done = subprocess.run([path, seed], stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, timeout=120)
except subprocess.TimeoutExpired:
t.fail(f"{unit} {seed} finishes")
continue
out = done.stdout.decode(errors="replace")
for line in out.splitlines():
if line.startswith("ok "):
t.ok(f"{unit} {seed}: {line[3:]}")
elif line.startswith("FAIL "):
label, _, what = line[5:].partition(": ")
t.fail(f"{unit} {seed}: {label}", actual=what)
if done.returncode and "FAIL " not in out:
t.fail(f"{unit} {seed} exits cleanly", 0, done.returncode)