*/ abstract class IntegrationTestCase extends TestCase { /** * Both sides answer immediately, so anything reaching this is a deadlock. * Far below the SDK's two-minute default, to fail rather than hang. */ private const TIMEOUT = 5; private ?Client $client = null; protected function clientBuilder(): ClientBuilder { return Client::builder() ->setClientInfo('integration-client', '1.0.0') ->setInitTimeout(self::TIMEOUT) ->setRequestTimeout(self::TIMEOUT); } /** * Spawn a fixture server and connect a client to it. * * The returned client has completed the handshake. * * @param string $fixture basename of a script in {@see Fixture} * @param array $env added to the server process environment */ protected function connect(string $fixture, ?ClientBuilder $client = null, array $env = []): Client { $script = __DIR__.'/Fixture/'.$fixture.'.php'; $this->client = ($client ?? $this->clientBuilder())->build(); try { $this->client->connect(new StdioTransport( command: \PHP_BINARY, args: [$script], // proc_open() replaces the environment rather than adding to it. env: [] === $env ? null : array_merge(getenv(), $env), )); } catch (ConnectionException $e) { // The transport discards the child's stderr, so a fixture dying on // startup arrives here as a bare timeout. $this->fail(\sprintf('Could not connect to fixture server "%s": %s. Run `%s %s` to see why.', $fixture, $e->getMessage(), \PHP_BINARY, $script)); } return $this->client; } protected function tearDown(): void { $this->client?->disconnect(); $this->client = null; } }