* [Client] Implement setMaxRetries connection retries * [Client] Reject a negative retry count in Configuration * [Client] Use a positive init timeout in the retry test * [Client] Drop redundant docblock note on setMaxRetries * [Client] Clear the initialized flag on a failed connection * [Client] Trim comments to the non-obvious ones * [Client] Cover retries and timeout cleanup with integration tests The retry fixture counts the processes it was started as, which is what separates a real retry from a second call into the same server. The timeout fixture outlives the client's patience and then goes idle, so the next request runs on a connection the client already gave up on once.
93 lines
2.6 KiB
PHP
93 lines
2.6 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of the official PHP MCP SDK.
|
|
*
|
|
* A collaboration between Symfony and the PHP Foundation.
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Mcp\Tests\Integration;
|
|
|
|
use Mcp\Exception\ConnectionException;
|
|
use PHPUnit\Framework\Attributes\TestDox;
|
|
|
|
/**
|
|
* Connection retries, counted in processes rather than calls.
|
|
*
|
|
* A retry is only worth anything if it starts a fresh server, which is what the
|
|
* spawn counter pins down: the fixture bumps it on every start, so the test can
|
|
* tell a genuine second process from a second call into the same one.
|
|
*
|
|
* A failed attempt costs the init timeout, so these are the slowest tests here.
|
|
*
|
|
* @see Fixture/retry.php for the server under test
|
|
*/
|
|
final class RetryTest extends IntegrationTestCase
|
|
{
|
|
private string $counter;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$counter = tempnam(sys_get_temp_dir(), 'mcp-spawns');
|
|
|
|
if (false === $counter) {
|
|
$this->fail('Could not create the spawn counter file.');
|
|
}
|
|
|
|
$this->counter = $counter;
|
|
file_put_contents($this->counter, '0');
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
parent::tearDown();
|
|
|
|
@unlink($this->counter);
|
|
}
|
|
|
|
#[TestDox('a failed attempt is retried against a newly spawned server')]
|
|
public function testRetrySpawnsAnotherServer(): void
|
|
{
|
|
$client = $this->connect(
|
|
'retry',
|
|
$this->clientBuilder()->setInitTimeout(2)->setMaxRetries(1),
|
|
$this->environment(failing: 1),
|
|
);
|
|
|
|
$this->assertSame(2, $this->spawns());
|
|
$this->assertTrue($client->isConnected());
|
|
$this->assertSame('quick', $client->callTool('fast')->content[0]->text ?? null);
|
|
}
|
|
|
|
#[TestDox('no retries means the first failure is the last word')]
|
|
public function testRetriesCanBeDisabled(): void
|
|
{
|
|
$client = $this->clientBuilder()->setInitTimeout(2)->setMaxRetries(0)->build();
|
|
|
|
try {
|
|
$client->connect($this->transport('retry', $this->environment(failing: 1)));
|
|
$this->fail('Connecting should not have succeeded.');
|
|
} catch (ConnectionException) {
|
|
$this->assertSame(1, $this->spawns());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
private function environment(int $failing): array
|
|
{
|
|
return [
|
|
'MCP_SPAWN_COUNTER' => $this->counter,
|
|
'MCP_FAIL_SPAWNS' => (string) $failing,
|
|
];
|
|
}
|
|
|
|
private function spawns(): int
|
|
{
|
|
return (int) file_get_contents($this->counter);
|
|
}
|
|
}
|