Skip to content

Commit

Permalink
implement ipc
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Mar 17, 2024
1 parent b13615e commit 011ef95
Showing 1 changed file with 114 additions and 26 deletions.
140 changes: 114 additions & 26 deletions src/Framework/TestRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,34 +273,117 @@ function_exists('pcntl_fork')
;
}

// IPC inspired from https://github.com/barracudanetworks/forkdaemon-php
private const SOCKET_HEADER_SIZE = 4;

private function ipc_init()

Check failure on line 279 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

MissingReturnType

src/Framework/TestRunner.php:279:22: MissingReturnType: Method PHPUnit\Framework\TestRunner::ipc_init does not have a return type, expecting array<array-key, Socket> (see https://psalm.dev/050)

Check warning on line 279 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L279

Added line #L279 was not covered by tests
{
// windows needs AF_INET
$domain = strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' ? AF_INET : AF_UNIX;

Check failure on line 282 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

UndefinedConstant

src/Framework/TestRunner.php:282:63: UndefinedConstant: Const AF_INET is not defined (see https://psalm.dev/020)

Check failure on line 282 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

UndefinedConstant

src/Framework/TestRunner.php:282:73: UndefinedConstant: Const AF_UNIX is not defined (see https://psalm.dev/020)

Check warning on line 282 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L282

Added line #L282 was not covered by tests

// create a socket pair for IPC
$sockets = array();
if (socket_create_pair($domain, SOCK_STREAM, 0, $sockets) === false)

Check failure on line 286 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

UndefinedConstant

src/Framework/TestRunner.php:286:41: UndefinedConstant: Const SOCK_STREAM is not defined (see https://psalm.dev/020)

Check warning on line 286 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L285-L286

Added lines #L285 - L286 were not covered by tests
{
throw new \RuntimeException('socket_create_pair failed: ' . socket_strerror(socket_last_error()));

Check failure on line 288 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

MissingThrowsDocblock

src/Framework/TestRunner.php:288:13: MissingThrowsDocblock: RuntimeException is thrown but not caught - please either catch or add a @throws annotation (see https://psalm.dev/169)

Check warning on line 288 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L288

Added line #L288 was not covered by tests
}

return $sockets;

Check warning on line 291 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L291

Added line #L291 was not covered by tests
}

private function socket_receive($socket)

Check failure on line 294 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

MissingReturnType

src/Framework/TestRunner.php:294:22: MissingReturnType: Method PHPUnit\Framework\TestRunner::socket_receive does not have a return type (see https://psalm.dev/050)

Check failure on line 294 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

MissingParamType

src/Framework/TestRunner.php:294:37: MissingParamType: Parameter $socket has no provided type (see https://psalm.dev/154)

Check warning on line 294 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L294

Added line #L294 was not covered by tests
{
// initially read to the length of the header size, then
// expand to read more
$bytes_total = self::SOCKET_HEADER_SIZE;
$bytes_read = 0;
$have_header = false;
$socket_message = '';
while ($bytes_read < $bytes_total)

Check warning on line 302 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L298-L302

Added lines #L298 - L302 were not covered by tests
{
$read = @socket_read($socket, $bytes_total - $bytes_read);
if ($read === false)

Check warning on line 305 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L304-L305

Added lines #L304 - L305 were not covered by tests
{
throw new \RuntimeException('socket_receive error: ' . socket_strerror(socket_last_error()));

Check warning on line 307 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L307

Added line #L307 was not covered by tests
}

// blank socket_read means done
if ($read == '')

Check warning on line 311 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L311

Added line #L311 was not covered by tests
{
break;

Check warning on line 313 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L313

Added line #L313 was not covered by tests
}

$bytes_read += strlen($read);
$socket_message .= $read;

Check warning on line 317 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L316-L317

Added lines #L316 - L317 were not covered by tests

if (!$have_header && $bytes_read >= self::SOCKET_HEADER_SIZE)

Check warning on line 319 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L319

Added line #L319 was not covered by tests
{
$have_header = true;
list($bytes_total) = array_values(unpack('N', $socket_message));

Check failure on line 322 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

PossiblyUndefinedArrayOffset

src/Framework/TestRunner.php:322:22: PossiblyUndefinedArrayOffset: Possibly undefined array key (see https://psalm.dev/167)
$bytes_read = 0;
$socket_message = '';

Check warning on line 324 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L321-L324

Added lines #L321 - L324 were not covered by tests
}
}

return @unserialize($socket_message);

Check warning on line 328 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L328

Added line #L328 was not covered by tests
}

private function socket_send($socket, $message)

Check failure on line 331 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

MissingReturnType

src/Framework/TestRunner.php:331:22: MissingReturnType: Method PHPUnit\Framework\TestRunner::socket_send does not have a return type, expecting void (see https://psalm.dev/050)

Check failure on line 331 in src/Framework/TestRunner.php

View workflow job for this annotation

GitHub Actions / Type Checker

MissingParamType

src/Framework/TestRunner.php:331:34: MissingParamType: Parameter $socket has no provided type (see https://psalm.dev/154)

Check warning on line 331 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L331

Added line #L331 was not covered by tests
{
$serialized_message = @serialize($message);
if ($serialized_message == false)

Check warning on line 334 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L333-L334

Added lines #L333 - L334 were not covered by tests
{
throw new \RuntimeException('socket_send failed to serialize message');

Check warning on line 336 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L336

Added line #L336 was not covered by tests
}

$header = pack('N', strlen($serialized_message));
$data = $header . $serialized_message;
$bytes_left = strlen($data);
while ($bytes_left > 0)

Check warning on line 342 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L339-L342

Added lines #L339 - L342 were not covered by tests
{
$bytes_sent = @socket_write($socket, $data);
if ($bytes_sent === false)

Check warning on line 345 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L344-L345

Added lines #L344 - L345 were not covered by tests
{
throw new \RuntimeException('socket_send failed to write to socket');

Check warning on line 347 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L347

Added line #L347 was not covered by tests
}

$bytes_left -= $bytes_sent;
$data = substr($data, $bytes_sent);

Check warning on line 351 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L350-L351

Added lines #L350 - L351 were not covered by tests
}
}

private function runInFork(TestCase $test): void

Check warning on line 355 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L355

Added line #L355 was not covered by tests
{
if (socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets) === false) {
throw new \Exception('could not create socket pair');
}
list($socket_child, $socket_parent) = $this->ipc_init();

Check warning on line 357 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L357

Added line #L357 was not covered by tests

$pid = pcntl_fork();

Check warning on line 359 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L359

Added line #L359 was not covered by tests
// pcntl_fork may return NULL if the function is disabled in php.ini.
if ($pid === -1 || $pid === null) {

if ($pid === -1 ) {
throw new \Exception('could not fork');
} else if ($pid) {

Check warning on line 363 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L361-L363

Added lines #L361 - L363 were not covered by tests
// we are the parent

pcntl_waitpid($pid, $status); // protect against zombie children
socket_close($socket_parent);

Check warning on line 366 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L366

Added line #L366 was not covered by tests

// read child stdout, stderr
$result = $this->socket_receive($socket_child);

Check warning on line 369 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L369

Added line #L369 was not covered by tests

// read child output
$output = '';
while(($read = socket_read($sockets[1], 2048, PHP_BINARY_READ)) !== false) {
$output .= $read;
$stderr = '';
$stdout = '';
if (is_array($result) && array_key_exists('error', $result)) {
$stderr = $result['error'];

Check warning on line 374 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L371-L374

Added lines #L371 - L374 were not covered by tests
} else {
$stdout = $result;

Check warning on line 376 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L376

Added line #L376 was not covered by tests
}
socket_close($sockets[1]);

$php = AbstractPhpProcess::factory();
$php->processChildResult($test, $output, ''); // TODO stderr
$php->processChildResult($test, $stdout, $stderr);

Check warning on line 380 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L379-L380

Added lines #L379 - L380 were not covered by tests

} else {
// we are the child

socket_close($socket_child);

Check warning on line 385 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L385

Added line #L385 was not covered by tests

$offset = hrtime();
$dispatcher = Event\Facade::instance()->initForIsolation(
\PHPUnit\Event\Telemetry\HRTime::fromSecondsAndNanoseconds(
Expand All @@ -310,22 +393,27 @@ private function runInFork(TestCase $test): void
);

$test->setInIsolation(true);

Check warning on line 395 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L395

Added line #L395 was not covered by tests
$test->runBare();
try {
$test->run();
} catch (Throwable $e) {
$this->socket_send($socket_parent, ['error' => $e->getMessage()]);
exit();

Check warning on line 400 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L397-L400

Added lines #L397 - L400 were not covered by tests
}

// send result into parent
socket_write($sockets[0],
serialize(
[
'testResult' => $test->result(),
'codeCoverage' => CodeCoverage::instance()->isActive() ? CodeCoverage::instance()->codeCoverage() : null,
'numAssertions' => $test->numberOfAssertionsPerformed(),
'output' => !$test->expectsOutput() ? $output : '',
'events' => $dispatcher->flush(),
'passedTests' => PassedTests::instance()
]
)
$result = serialize(

Check warning on line 403 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L403

Added line #L403 was not covered by tests
[
'testResult' => $test->result(),
'codeCoverage' => CodeCoverage::instance()->isActive() ? CodeCoverage::instance()->codeCoverage() : null,
'numAssertions' => $test->numberOfAssertionsPerformed(),
'output' => !$test->expectsOutput() ? $test->output() : '',
'events' => $dispatcher->flush(),
'passedTests' => PassedTests::instance()

Check warning on line 410 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L405-L410

Added lines #L405 - L410 were not covered by tests
]
);
socket_close($sockets[0]);

// send result into parent
$this->socket_send($socket_parent, $result);
exit();

Check warning on line 416 in src/Framework/TestRunner.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestRunner.php#L415-L416

Added lines #L415 - L416 were not covered by tests
}
}

Expand Down

0 comments on commit 011ef95

Please sign in to comment.