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