parent
f22499bdf9
commit
b1dcaaa37e
10 changed files with 488 additions and 0 deletions
@ -0,0 +1,140 @@ |
|||||||
|
#!/usr/bin/env php |
||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
const DEFAULT_TEST_DIR = 'tests'; |
||||||
|
|
||||||
|
$root = dirname(__DIR__); |
||||||
|
$artifactSuffixes = [ |
||||||
|
'.php', |
||||||
|
'.exp', |
||||||
|
'.log', |
||||||
|
'.out', |
||||||
|
'.diff', |
||||||
|
'.sh', |
||||||
|
'.mem', |
||||||
|
'.clean.php', |
||||||
|
'.skip.php', |
||||||
|
]; |
||||||
|
$binarySuffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : ''; |
||||||
|
|
||||||
|
$dryRun = false; |
||||||
|
$paths = []; |
||||||
|
|
||||||
|
foreach (array_slice($argv, 1) as $arg) { |
||||||
|
if ($arg === '--dry-run' || $arg === '-n') { |
||||||
|
$dryRun = true; |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($arg === '--help' || $arg === '-h') { |
||||||
|
printUsage($argv[0]); |
||||||
|
exit(0); |
||||||
|
} |
||||||
|
$paths[] = $arg; |
||||||
|
} |
||||||
|
|
||||||
|
if (!$paths) { |
||||||
|
$paths[] = DEFAULT_TEST_DIR; |
||||||
|
} |
||||||
|
|
||||||
|
$phptFiles = []; |
||||||
|
foreach ($paths as $path) { |
||||||
|
$absolute = resolvePath($root, $path); |
||||||
|
if (is_file($absolute)) { |
||||||
|
if (str_ends_with($absolute, '.phpt')) { |
||||||
|
$phptFiles[$absolute] = true; |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (!is_dir($absolute)) { |
||||||
|
fwrite(STDERR, "Path not found: {$path}\n"); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
$iterator = new RecursiveIteratorIterator( |
||||||
|
new RecursiveDirectoryIterator($absolute, FilesystemIterator::SKIP_DOTS) |
||||||
|
); |
||||||
|
foreach ($iterator as $file) { |
||||||
|
$filePath = $file->getPathname(); |
||||||
|
if (str_ends_with($filePath, '.phpt')) { |
||||||
|
$phptFiles[$filePath] = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$artifacts = []; |
||||||
|
foreach (array_keys($phptFiles) as $phptFile) { |
||||||
|
$base = substr($phptFile, 0, -5); |
||||||
|
foreach ($artifactSuffixes as $suffix) { |
||||||
|
$artifact = $base . $suffix; |
||||||
|
if (is_file($artifact)) { |
||||||
|
$artifacts[$artifact] = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$binary = $root . DIRECTORY_SEPARATOR . str_replace('-', '_', basename($base)) . $binarySuffix; |
||||||
|
if (is_file($binary) && is_executable($binary)) { |
||||||
|
$artifacts[$binary] = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$artifacts = array_keys($artifacts); |
||||||
|
sort($artifacts); |
||||||
|
|
||||||
|
$deleted = 0; |
||||||
|
foreach ($artifacts as $artifact) { |
||||||
|
$displayPath = relativePath($root, $artifact); |
||||||
|
if ($dryRun) { |
||||||
|
echo $displayPath, PHP_EOL; |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (!unlink($artifact)) { |
||||||
|
fwrite(STDERR, "Failed to remove: {$displayPath}\n"); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
++$deleted; |
||||||
|
} |
||||||
|
|
||||||
|
if ($dryRun) { |
||||||
|
printf("Found %d PHPT artifact file(s).\n", count($artifacts)); |
||||||
|
} else { |
||||||
|
printf("Removed %d PHPT artifact file(s).\n", $deleted); |
||||||
|
} |
||||||
|
|
||||||
|
function printUsage(string $script): void |
||||||
|
{ |
||||||
|
echo <<<USAGE |
||||||
|
Usage: |
||||||
|
php {$script} [--dry-run] [path ...] |
||||||
|
|
||||||
|
Examples: |
||||||
|
php {$script} |
||||||
|
php {$script} tests/aot/symfony |
||||||
|
php {$script} --dry-run tests/aot/basic/001.phpt |
||||||
|
|
||||||
|
Only files generated from an existing .phpt are removed. This includes adjacent |
||||||
|
.php/.log/.out/.diff files and the AOT test binary generated in the project root. |
||||||
|
|
||||||
|
USAGE; |
||||||
|
} |
||||||
|
|
||||||
|
function resolvePath(string $root, string $path): string |
||||||
|
{ |
||||||
|
if ($path === '') { |
||||||
|
return $root; |
||||||
|
} |
||||||
|
if ($path[0] === '/') { |
||||||
|
return $path; |
||||||
|
} |
||||||
|
return $root . DIRECTORY_SEPARATOR . $path; |
||||||
|
} |
||||||
|
|
||||||
|
function relativePath(string $root, string $path): string |
||||||
|
{ |
||||||
|
$prefix = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
||||||
|
if (str_starts_with($path, $prefix)) { |
||||||
|
return substr($path, strlen($prefix)); |
||||||
|
} |
||||||
|
return $path; |
||||||
|
} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: array_push with unpacked static method result |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class FunctionCallParser |
||||||
|
{ |
||||||
|
public static function parse(string $expr): array |
||||||
|
{ |
||||||
|
$calls = []; |
||||||
|
|
||||||
|
foreach (explode(',', $expr) as $part) { |
||||||
|
$part = trim($part); |
||||||
|
if ($part === '') { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
$calls[] = strtoupper($part); |
||||||
|
if (str_contains($part, '+')) { |
||||||
|
array_push($calls, ...self::parse(str_replace('+', ',', $part))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $calls; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(FunctionCallParser::parse('foo,bar+baz')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(4) { |
||||||
|
[0]=> |
||||||
|
string(3) "FOO" |
||||||
|
[1]=> |
||||||
|
string(7) "BAR+BAZ" |
||||||
|
[2]=> |
||||||
|
string(3) "BAR" |
||||||
|
[3]=> |
||||||
|
string(3) "BAZ" |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: conditional first-class callable stored in property |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class DeferredCallback |
||||||
|
{ |
||||||
|
private mixed $callback = null; |
||||||
|
|
||||||
|
public function set(?callable $callback): void |
||||||
|
{ |
||||||
|
$this->callback = null !== $callback ? $callback(...) : null; |
||||||
|
} |
||||||
|
|
||||||
|
public function run(string $value): ?string |
||||||
|
{ |
||||||
|
if (null === $callback = $this->callback) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return $callback($value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$callback = new DeferredCallback(); |
||||||
|
$callback->set(static fn (string $value): string => strtoupper($value)); |
||||||
|
var_dump($callback->run('symfony')); |
||||||
|
|
||||||
|
$callback->set(null); |
||||||
|
var_dump($callback->run('symfony')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(7) "SYMFONY" |
||||||
|
NULL |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony ErrorHandler pattern: dynamic static property name with multidimensional isset |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class FinalMemberRegistry |
||||||
|
{ |
||||||
|
private static array $finalConstants = [ |
||||||
|
'ParentClass' => [ |
||||||
|
'STATUS' => 'ParentClass::STATUS', |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
private static array $finalProperties = [ |
||||||
|
'ParentClass' => [ |
||||||
|
'name' => 'ParentClass::$name', |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
public static function lookup(string $type, string $class, string $name): ?string |
||||||
|
{ |
||||||
|
if (isset(self::${$type}[$class][$name])) { |
||||||
|
return self::${$type}[$class][$name]; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(FinalMemberRegistry::lookup('finalConstants', 'ParentClass', 'STATUS')); |
||||||
|
var_dump(FinalMemberRegistry::lookup('finalProperties', 'ParentClass', 'name')); |
||||||
|
var_dump(FinalMemberRegistry::lookup('finalProperties', 'ParentClass', 'missing')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(19) "ParentClass::STATUS" |
||||||
|
string(18) "ParentClass::$name" |
||||||
|
NULL |
||||||
@ -0,0 +1,39 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Process pattern: environment union with array_diff_ukey callback |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function mergeEnv(array $env, array $defaults, bool $windows): array |
||||||
|
{ |
||||||
|
$result = ['PATH' => '/bin']; |
||||||
|
$result += $windows ? array_diff_ukey($env, $result, 'strcasecmp') : $env; |
||||||
|
$result += $windows ? array_diff_ukey($defaults, $result, 'strcasecmp') : $defaults; |
||||||
|
|
||||||
|
return $result; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(mergeEnv(['Path' => '/usr/bin', 'APP_ENV' => 'dev'], ['HOME' => '/tmp'], true)); |
||||||
|
var_dump(mergeEnv(['Path' => '/usr/bin', 'APP_ENV' => 'dev'], ['HOME' => '/tmp'], false)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(3) { |
||||||
|
["PATH"]=> |
||||||
|
string(4) "/bin" |
||||||
|
["APP_ENV"]=> |
||||||
|
string(3) "dev" |
||||||
|
["HOME"]=> |
||||||
|
string(4) "/tmp" |
||||||
|
} |
||||||
|
array(4) { |
||||||
|
["PATH"]=> |
||||||
|
string(4) "/bin" |
||||||
|
["Path"]=> |
||||||
|
string(8) "/usr/bin" |
||||||
|
["APP_ENV"]=> |
||||||
|
string(3) "dev" |
||||||
|
["HOME"]=> |
||||||
|
string(4) "/tmp" |
||||||
|
} |
||||||
@ -0,0 +1,41 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: late static binding with new static and static return type |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class BaseFactory |
||||||
|
{ |
||||||
|
public string $name = 'base'; |
||||||
|
|
||||||
|
public static function create(string $name): static |
||||||
|
{ |
||||||
|
$instance = new static(); |
||||||
|
$instance->name = $name; |
||||||
|
|
||||||
|
return $instance; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ChildFactory extends BaseFactory |
||||||
|
{ |
||||||
|
public string $extra = 'child'; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$base = BaseFactory::create('root'); |
||||||
|
$child = ChildFactory::create('leaf'); |
||||||
|
|
||||||
|
var_dump($base::class); |
||||||
|
var_dump($base->name); |
||||||
|
var_dump($child::class); |
||||||
|
var_dump($child->name); |
||||||
|
var_dump($child->extra); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(11) "BaseFactory" |
||||||
|
string(4) "root" |
||||||
|
string(12) "ChildFactory" |
||||||
|
string(4) "leaf" |
||||||
|
string(5) "child" |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony AssetMapper pattern: new expression as constructor default |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class Compressor |
||||||
|
{ |
||||||
|
public function compress(string $value): string |
||||||
|
{ |
||||||
|
return 'gz:'.$value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class GzipAsset |
||||||
|
{ |
||||||
|
public function __construct( |
||||||
|
private readonly Compressor $compressor = new Compressor(), |
||||||
|
) { |
||||||
|
} |
||||||
|
|
||||||
|
public function encode(string $value): string |
||||||
|
{ |
||||||
|
return $this->compressor->compress($value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump((new GzipAsset())->encode('asset')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(8) "gz:asset" |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: nullsafe attribute newInstance chain |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS)] |
||||||
|
class TaggedItem |
||||||
|
{ |
||||||
|
public function __construct(public int $priority = 0) |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[TaggedItem(priority: 20)] |
||||||
|
class TaggedService |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
class UntaggedService |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
function priorityOf(string $class): ?int |
||||||
|
{ |
||||||
|
return ((new ReflectionClass($class))->getAttributes(TaggedItem::class)[0] ?? null)?->newInstance()->priority; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(priorityOf(TaggedService::class)); |
||||||
|
var_dump(priorityOf(UntaggedService::class)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(20) |
||||||
|
NULL |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Process pattern: nested isset on commandline string offset |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function normalizeCommand(array $commandline): array |
||||||
|
{ |
||||||
|
if (isset($commandline[0][0]) && strlen($commandline[0]) === strcspn($commandline[0], ':/\\')) { |
||||||
|
$commandline[0] = 'resolved:'.$commandline[0]; |
||||||
|
} |
||||||
|
|
||||||
|
return $commandline; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(normalizeCommand(['php', '-v'])); |
||||||
|
var_dump(normalizeCommand(['/usr/bin/php', '-v'])); |
||||||
|
var_dump(normalizeCommand(['', '-v'])); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(12) "resolved:php" |
||||||
|
[1]=> |
||||||
|
string(2) "-v" |
||||||
|
} |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(12) "/usr/bin/php" |
||||||
|
[1]=> |
||||||
|
string(2) "-v" |
||||||
|
} |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(0) "" |
||||||
|
[1]=> |
||||||
|
string(2) "-v" |
||||||
|
} |
||||||
@ -0,0 +1,39 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: static property array spread merge assignment |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class CasterRegistry |
||||||
|
{ |
||||||
|
private static array $casters = [ |
||||||
|
'default' => 'base', |
||||||
|
]; |
||||||
|
|
||||||
|
public static function add(array $casters): void |
||||||
|
{ |
||||||
|
self::$casters = [...self::$casters, ...$casters]; |
||||||
|
} |
||||||
|
|
||||||
|
public static function all(): array |
||||||
|
{ |
||||||
|
return self::$casters; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
CasterRegistry::add(['a' => 'A']); |
||||||
|
CasterRegistry::add(['default' => 'override', 'b' => 'B']); |
||||||
|
|
||||||
|
var_dump(CasterRegistry::all()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(3) { |
||||||
|
["default"]=> |
||||||
|
string(8) "override" |
||||||
|
["a"]=> |
||||||
|
string(1) "A" |
||||||
|
["b"]=> |
||||||
|
string(1) "B" |
||||||
|
} |
||||||
Loading…
Reference in new issue