clock = new SystemClock(); $this->timer = new SystemTimer($this->clock); $this->fiberManager = new FiberManager($this->clock, $this->timer); }); it('creates a resolved promise', function () { $promise = AsyncPromise::resolve('test-value', $this->fiberManager); expect($promise->isResolved())->toBeTrue(); expect($promise->await())->toBe('test-value'); }); it('creates a rejected promise', function () { $exception = new \RuntimeException('test error'); $promise = AsyncPromise::reject($exception, $this->fiberManager); expect($promise->isResolved())->toBeTrue(); try { $promise->await(); throw new \Exception('Should have thrown'); } catch (\RuntimeException $e) { expect($e->getMessage())->toBe('test error'); } }); it('creates a promise from callable', function () { $promise = AsyncPromise::create( fn() => 'computed-value', $this->fiberManager ); // Give fiber time to execute usleep(10000); // 10ms expect($promise->await())->toBe('computed-value'); }); it('chains then callbacks', function () { $promise = AsyncPromise::resolve(5, $this->fiberManager) ->then(fn($value) => $value * 2) ->then(fn($value) => $value + 3); usleep(10000); // Give time for chaining expect($promise->await())->toBe(13); }); it('catches exceptions', function () { $promise = AsyncPromise::create( fn() => throw new \RuntimeException('error'), $this->fiberManager )->catch(fn($e) => 'caught: ' . $e->getMessage()); usleep(10000); expect($promise->await())->toBe('caught: error'); }); it('executes finally callback', function () { $finallyCalled = false; $promise = AsyncPromise::resolve('value', $this->fiberManager) ->finally(function () use (&$finallyCalled) { $finallyCalled = true; }); usleep(10000); $promise->await(); expect($finallyCalled)->toBeTrue(); }); it('waits for all promises', function () { $promise1 = AsyncPromise::resolve(1, $this->fiberManager); $promise2 = AsyncPromise::resolve(2, $this->fiberManager); $promise3 = AsyncPromise::resolve(3, $this->fiberManager); $allPromise = AsyncPromise::all( [$promise1, $promise2, $promise3], $this->fiberManager ); usleep(10000); $results = $allPromise->await(); expect($results)->toBe([1, 2, 3]); }); it('races promises and returns first', function () { $slowPromise = AsyncPromise::create(function () { usleep(100000); // 100ms return 'slow'; }, $this->fiberManager); $fastPromise = AsyncPromise::create(function () { // No sleep - immediate return return 'fast'; }, $this->fiberManager); $racePromise = AsyncPromise::race( [$slowPromise, $fastPromise], $this->fiberManager ); usleep(20000); // Wait a bit for fast to complete $result = $racePromise->await(); // Either fast wins or first to complete expect(['fast', 'slow'])->toContain($result); }); it('handles promise rejection in all', function () { $promise1 = AsyncPromise::resolve(1, $this->fiberManager); $promise2 = AsyncPromise::reject( new \RuntimeException('failed'), $this->fiberManager ); $allPromise = AsyncPromise::all( [$promise1, $promise2], $this->fiberManager ); usleep(10000); try { $allPromise->await(); throw new \Exception('Should have thrown'); } catch (\RuntimeException $e) { expect($e->getMessage())->toBe('failed'); } }); it('provides promise statistics', function () { $promise = AsyncPromise::create( fn() => 'value', $this->fiberManager ); usleep(10000); $stats = $promise->getStats(); expect($stats)->toHaveKey('resolved'); expect($stats)->toHaveKey('has_result'); expect($stats)->toHaveKey('has_exception'); }); it('chains multiple then callbacks with different return types', function () { $promise = AsyncPromise::resolve(10, $this->fiberManager) ->then(fn($x) => $x * 2) // 20 ->then(fn($x) => (string) $x) // "20" ->then(fn($x) => $x . '!') // "20!" ->then(fn($x) => strlen($x)); // 3 usleep(10000); expect($promise->await())->toBe(3); }); it('handles nested promises', function () { $innerPromise = AsyncPromise::create( fn() => 'inner', $this->fiberManager ); $outerPromise = AsyncPromise::create( fn() => $innerPromise->await() . '-outer', $this->fiberManager ); usleep(20000); expect($outerPromise->await())->toBe('inner-outer'); }); });