pact-php
pact-php copied to clipboard
phpunit exits with code 137 and pact-files are not written
I'm using pact in combination with phpunit. Sometimes phpunit is exiting with exit code 137
. Also, the pact files are not written to the configured folder.
Is that a known error?
There are 2 instances of the mock-service used in one phpunit test, which are running parallel, because there are 2 APIs mocked.
Here again with an error 137 in GitHub workflows:
Result of the workflow:
Time: 00:17.983, Memory: 72.50 MB
OK (5 tests, 10 assertions)
Error: Process completed with exit code 137.
The used pact test-case for that tests:
<?php
declare(strict_types=1);
namespace App\Tests\Pact;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use ApiPlatform\Symfony\Bundle\Test\Client;
use PhpPact\Consumer\InteractionBuilder;
use PhpPact\Consumer\Model\ConsumerRequest;
use PhpPact\Consumer\Model\ProviderResponse;
use PhpPact\Standalone\MockService\MockServer;
use PhpPact\Standalone\MockService\MockServerConfig;
use function Safe\exec;
use function Safe\fwrite;
/** @psalm-suppress MissingConstructor */
class PactTestCase extends ApiTestCase
{
public const PROVIDER_NAME = 'a-service';
public const MOCK_SERVER_PORT = 9999;
public const CONSUMER_NAME = 'another-service';
private ?Client $currentClient = null;
private int $mockServerPID;
private InteractionBuilder $interactionBuilder;
private string $mockServerUrl;
public function stopPactMockServer(): void
{
// sometimes, this is ending in a exit code 137 of phpunit
$killCounter = 0;
fwrite(STDOUT, \sprintf('kill pid %s', $this->mockServerPID));
do {
try {
exec(\sprintf('kill %s || true', $this->mockServerPID));
\sleep(1);
$killCounter++;
fwrite(STDOUT, '.');
} catch (\Throwable) {
}
} while (\file_exists('/proc/' . $this->mockServerPID) && $killCounter < 30);
if (\file_exists('/proc/' . $this->mockServerPID)) {
throw new \RuntimeException(\sprintf(
'Cannot kill mock-server process PID %s',
$this->mockServerPID,
));
}
}
/** @psalm-suppress MethodSignatureMismatch */
public function getClient(): Client
{
if (null === $this->currentClient) {
$this->currentClient = self::createClient();
}
return $this->currentClient;
}
protected function getInteractionBuilder(): InteractionBuilder
{
return $this->interactionBuilder;
}
protected function getPactMockServerUrl(): string
{
return $this->mockServerUrl;
}
protected function createInteraction(
string $name,
string $providerName,
ConsumerRequest $consumerRequest,
ProviderResponse $providerResponse,
string $consumerName,
int $port,
): InteractionBuilder {
$interactionBuilder =
new InteractionBuilder($this->getPactMockServerConfiguration($providerName, $consumerName, $port));
$interactionBuilder
->uponReceiving($name)
->with($consumerRequest)
->willRespondWith($providerResponse);
$this->interactionBuilder = $interactionBuilder;
return $interactionBuilder;
}
protected function tearDown(): void
{
$this->stopPactMockServer();
parent::tearDown();
}
protected function startPactMockServer(string $providerName, string $consumerName, int $port): void
{
$this->mockServerPID = $this->getPactMockServer($providerName, $consumerName, $port)->start();
}
/** @psalm-suppress UndefinedInterfaceMethod */
protected function getPactMockServerConfiguration(
string $providerName,
string $consumerName,
int $port,
): MockServerConfig {
$config = new MockServerConfig();
$config->setHost('localhost');
$config->setPort($port);
$config->setConsumer($consumerName);
$config->setProvider($providerName);
$config->setCors(true);
$config->setHealthCheckTimeout(30);
$config->setHealthCheckRetrySec(1);
$config->setPactDir(\dirname(__DIR__) . '/../pacts');
$config->setPactFileWriteMode('merge');
$config->setLog(\dirname(__DIR__) . '/../var/log/test/pact');
$config->setLogLevel('DEBUG');
$this->mockServerUrl = "http://{$config->getHost()}:{$config->getPort()}";
return $config;
}
protected function getPactMockServer(string $providerName, string $consumerName, int $port): MockServer
{
return new MockServer($this->getPactMockServerConfiguration($providerName, $consumerName, $port));
}
}
A test class using the PactTestCase class:
<?php
declare(strict_types=1);
namespace App\Tests\Pact\Tests;
use App\Tests\Pact\DataRetrieverDummy;
use App\Tests\Pact\PactTestCase;
use PhpPact\Consumer\Matcher\Matcher;
use PhpPact\Consumer\Model\ConsumerRequest;
use PhpPact\Consumer\Model\ProviderResponse;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class RetrievePimPreProcessingBrandDataTest extends PactTestCase
{
/** @phpstan-ignore-next-line */
private HttpClientInterface $client;
protected function setUp(): void
{
self::bootKernel();
$container = static::getContainer();
$this->client = $container->get(HttpClientInterface::class);
$this->startPactMockServer(
PactTestCase::PROVIDER_NAME,
PactTestCase::CONSUMER_NAME,
PactTestCase::MOCK_SERVER_PORT,
);
}
public function testRetrieveData(): void
{
$matcher = new Matcher();
$request = new ConsumerRequest();
$request
->setMethod('GET')
->setPath('/the_index/_search');
$response = new ProviderResponse();
$response
->setStatus(200)
->setBody([
'took' => $matcher->integer(),
'timed_out' => $matcher->boolean(),
'_shards' => [
'total' => $matcher->integer(),
'successfull' => $matcher->integer(),
'skipped' => $matcher->integer(),
'failed' => $matcher->integer(),
],
'hits' => [
'total' => [
'value' => $matcher->integer(1),
'relation' => $matcher->like('eq'),
],
'max_score' => $matcher->integer(),
'hits' => $matcher->eachLike([
'_index' => $matcher->like('the_index'),
'_type' => $matcher->like('_doc'),
'_score' => $matcher->integer(),
'_source' => [
'external_key' => $matcher->like('any-key'),
'external_id' => $matcher->like('522'),
'name' => $matcher->like('Franz'),
'ulid' => $matcher->like('7KVFK80BKTAXAA7EF2Q1QQJEND'),
],
]),
],
]);
$this->createInteraction(
'Retrieve data from ppp (/ppp_none_brands/_search)',
PactTestCase::PROVIDER_NAME,
$request,
$response,
PactTestCase::CONSUMER_NAME,
PactTestCase::MOCK_SERVER_PORT,
);
$drd = new DataRetrieverDummy();
/** @var string $path */
$path = $request->getPath();
$result = $drd->retrieveData('http://localhost:9999' . $path);
$this->getInteractionBuilder()->verify();
self::assertEquals(1, $result['hits']['total']['value']);
self::assertEquals(
'any-key',
$result['hits']['hits'][0]['_source']['external_key'],
);
}
}
Is this a known error? Can anyone help to solve that issue?