testo/fiber

Fiber/coroutine execution plugin for the Testo testing framework: run tests as cooperatively-scheduled plain fibers.

Maintainers

Package info

github.com/php-testo/fiber

pkg:composer/testo/fiber

Transparency log

Fund package maintenance!

Boosty

Statistics

Installs: 2 957

Dependents: 1

Suggesters: 0

Stars: 0

0.1.2 2026-08-10 12:39 UTC

This package is auto-updated.

Last update: 2026-08-10 12:41:15 UTC


README

TESTO

Fiber / coroutine plugin

Documentation Support on Boosty


Important

🪞 This is a read-only mirror.

Active development of the Testo project lives in php-testo/testo under plugin/fiber/. This repository is automatically synchronized from there on every release.

File issues and pull requests in the main monorepo, not here.

About

Runs tests as plain PHP fibers driven by Testo's own cooperative scheduler, so a test (or the code it exercises) may suspend with \Fiber::suspend() and be resumed, and a case's tests may be interleaved to shake out order-dependent races.

  • #[RunInFiber] — run a test (method) or a whole case (class) inside fibers, scheduled by Schedule::Solo (default), RoundRobin or Random.
  • Coroutine::spawn() / ->await() / Coroutine::concurrently() — add coroutines to the running test's schedule and wait for them; they interleave with the test body (and, under a class-level #[RunInFiber], with the case's other tests) at every suspension point.
#[RunInFiber]
public function pingPong(): void
{
    $server = Coroutine::spawn(fn(): string => $this->acceptAndEcho());
    $client = Coroutine::spawn(fn(): string => $this->connectAndSend('ping'));

    Assert::same($client->await(), 'pong');
    Assert::same($server->await(), 'ping');
}

The scope is structured: the test is not finished until every coroutine it spawned is. Coroutine failures always surface wrapped in a CompositeException — even a single one; if the test body fails, pending coroutines are cancelled with a CancelledException thrown into them, and an await cycle is broken with a DeadlockException at the guilty await().

Switching is cooperative and happens only at suspension points — there is no event loop and no preemption. This is for fiber-based/cooperative code and race hunting, not for real async I/O: awaiting a timer, socket or Future needs the Revolt event loop — use the testo/bridge-revolt #[RunInRevolt] attribute for that.

Everything lives under the Testo\Fiber\ namespace.

Install

composer require --dev testo/fiber

PHP Latest Version on Packagist License Total Downloads