assertSame($loop, (new Kernel($loop))->loop()); } public function test_boots_and_shuts_down_registered_servers_with_hooks(): void { $loop = new StreamSelectLoop; $kernel = new Kernel($loop); $server = new RecordingServer; // Stop the kernel on the next tick so run() returns instead of blocking. $server->onBoot = fn (LoopInterface $l) => $l->futureTick(fn () => $kernel->stop()); $bootHook = false; $shutdownHook = false; $kernel ->register($server) ->onBoot(function () use (&$bootHook) { $bootHook = true; }) ->onShutdown(function () use (&$shutdownHook) { $shutdownHook = true; }) ->run(); $this->assertTrue($server->booted, 'server should have been booted'); $this->assertTrue($server->wasShutDown, 'server should have been shut down'); $this->assertTrue($bootHook, 'onBoot hook should have fired'); $this->assertTrue($shutdownHook, 'onShutdown hook should have fired'); $this->assertFalse($kernel->isRunning()); } public function test_stop_is_idempotent_and_safe_before_run(): void { $kernel = new Kernel(new StreamSelectLoop); $kernel->stop(); // never ran — must be a harmless no-op $kernel->stop(); $this->assertFalse($kernel->isRunning()); } public function test_a_failing_server_shutdown_does_not_block_the_others(): void { $loop = new StreamSelectLoop; $kernel = new Kernel($loop); $throwing = new class implements Server { public function name(): string { return 'throwing'; } public function boot(LoopInterface $loop): void {} public function shutdown(): void { throw new \RuntimeException('teardown failed'); } }; $good = new RecordingServer; $good->onBoot = fn (LoopInterface $l) => $l->futureTick(fn () => $kernel->stop()); $kernel->register($throwing)->register($good)->run(); $this->assertTrue($good->wasShutDown, 'a throwing teardown must not stop the others'); } }