TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

170 lines
6.3 KiB

<?php
namespace TypePhp\Tests;
use PHPUnit\Framework\TestCase;
use TypePhp\Backend\CompilerBackend;
use TypePhp\Build\NativeBuilder;
final class ParallelCompileTest extends TestCase
{
private string $directory;
protected function setUp(): void
{
$this->directory = sys_get_temp_dir() . '/typephp_process_pool_' . bin2hex(random_bytes(6));
mkdir($this->directory, 0777, true);
}
protected function tearDown(): void
{
foreach (glob($this->directory . '/*') ?: [] as $file) {
@unlink($file);
}
@rmdir($this->directory);
}
public function testProcessPoolRunsCompilerTasksConcurrently(): void
{
$firstMarker = $this->directory . '/first.ready';
$secondMarker = $this->directory . '/second.ready';
$firstObject = $this->directory . '/first.o';
$secondObject = $this->directory . '/second.o';
$completed = [];
$builder = new NativeBuilder($this->createMock(CompilerBackend::class));
$result = $builder->dispatchProcessParallel(
[
$this->task('first.cc', $firstObject, $this->barrierCommand($firstMarker, $secondMarker, $firstObject)),
$this->task('second.cc', $secondObject, $this->barrierCommand($secondMarker, $firstMarker, $secondObject)),
],
2,
static function (
string $source,
string $object,
string $command,
array $output,
int $status,
bool $success,
int $count,
) use (&$completed): void {
$completed[$source] = [$object, $command, $output, $status, $success, $count];
},
);
self::assertSame([], $result['failures'], var_export($completed, true));
self::assertEqualsCanonicalizing([$firstObject, $secondObject], $result['objects']);
self::assertCount(2, $completed);
self::assertTrue($completed['first.cc'][4]);
self::assertTrue($completed['second.cc'][4]);
}
public function testProcessPoolCapturesCompilerFailureOutputAndStatus(): void
{
$object = $this->directory . '/failed.o';
$completion = null;
$command = $this->phpCommand("fwrite(STDERR, 'compiler-error'); exit(7);");
$builder = new NativeBuilder($this->createMock(CompilerBackend::class));
$result = $builder->dispatchProcessParallel(
[$this->task('failed.cc', $object, $command)],
4,
static function (
string $source,
string $object,
string $command,
array $output,
int $status,
bool $success,
int $count,
) use (&$completion): void {
$completion = compact('source', 'object', 'command', 'output', 'status', 'success', 'count');
},
);
self::assertSame([], $result['objects']);
self::assertSame(['failed.cc'], $result['failures']);
self::assertSame(7, $completion['status']);
self::assertFalse($completion['success']);
self::assertStringContainsString('compiler-error', implode("\n", $completion['output']));
}
public function testProcessPoolCanReserveOneLaneForSmallTasks(): void
{
$smallMarker = $this->directory . '/small.ready';
$largeObject = $this->directory . '/large.o';
$middleObject = $this->directory . '/middle.o';
$smallObject = $this->directory . '/small.o';
$builder = new NativeBuilder($this->createMock(CompilerBackend::class));
$result = $builder->dispatchProcessParallel(
[
$this->task(
'large.cc',
$largeObject,
$this->waitForFileCommand($smallMarker, $largeObject),
),
$this->task(
'middle.cc',
$middleObject,
$this->waitForFileCommand($largeObject, $middleObject),
),
$this->task(
'small.cc',
$smallObject,
$this->createFilesCommand([$smallMarker, $smallObject]),
),
],
2,
reserveSmallTaskLane: true,
);
self::assertSame([], $result['failures']);
self::assertEqualsCanonicalizing(
[$largeObject, $middleObject, $smallObject],
$result['objects'],
);
}
/** @return array{source: string, object: string, command: string} */
private function task(string $source, string $object, string $command): array
{
return compact('source', 'object', 'command');
}
private function barrierCommand(string $ownMarker, string $otherMarker, string $object): string
{
$code = 'file_put_contents(' . var_export($ownMarker, true) . ", 'ready');"
. '$deadline = microtime(true) + 5;'
. 'while (file_exists(' . var_export($otherMarker, true) . ') === false && microtime(true) < $deadline) {'
. ' usleep(10000); }'
. 'if (file_exists(' . var_export($otherMarker, true) . ') === false) { exit(9); }'
. 'file_put_contents(' . var_export($object, true) . ", 'object');";
return $this->phpCommand($code);
}
/** @param list<string> $files */
private function createFilesCommand(array $files): string
{
$code = '';
foreach ($files as $file) {
$code .= 'file_put_contents(' . var_export($file, true) . ", 'ready');";
}
return $this->phpCommand($code);
}
private function waitForFileCommand(string $dependency, string $object): string
{
$code = '$deadline = microtime(true) + 2;'
. 'while (file_exists(' . var_export($dependency, true) . ') === false && microtime(true) < $deadline) {'
. ' usleep(10000); }'
. 'if (file_exists(' . var_export($dependency, true) . ') === false) { exit(9); }'
. 'file_put_contents(' . var_export($object, true) . ", 'object');";
return $this->phpCommand($code);
}
private function phpCommand(string $code): string
{
return escapeshellarg(PHP_BINARY) . ' -n -r ' . escapeshellarg($code);
}
}