test(aot): 添加字符串与函数解析等单元测试

pull/14/head
韩天峰 2 months ago
parent 7ccdd3add9
commit d55bb041f6
  1. 50
      tests/aot/basic/preprocessor-condition-stack.phpt
  2. 51
      tests/aot/basic/short-option-preprocess.phpt
  3. 51
      tests/aot/basic/target-name-normalize.phpt
  4. 29
      tests/aot/functions/compiler-name-normalize.phpt
  5. 102
      tests/aot/functions/cpp-signature-param-parser.phpt
  6. 48
      tests/aot/functions/doc-comment-tags.phpt
  7. 42
      tests/aot/functions/docblock-param-regex.phpt
  8. 43
      tests/aot/functions/file-extension-classifier.phpt
  9. 36
      tests/aot/functions/resource-version-normalize.phpt
  10. 30
      tests/aot/functions/unix-link-lib-flags.phpt
  11. 21
      tests/aot/string_method/camel-to-snake-regex.phpt
  12. 26
      tests/aot/string_method/negative-string-offset.phpt
  13. 42
      tests/aot/type_decl/numeric-literal-classifier.phpt

@ -0,0 +1,50 @@
--TEST--
preprocessor comment condition stack
--FILE--
<?php
function handle_preprocessor_conditions(array &$conds, array $comments): ?string
{
foreach ($comments as $comment) {
$text = trim($comment);
if (preg_match('/^#\s*if\s+(.+)$/', $text, $matches)) {
$conds[] = $matches[1];
} elseif (preg_match('/^#\s*ifdef\s+(.+)$/', $text, $matches)) {
$conds[] = "defined($matches[1])";
} elseif (preg_match('/^#\s*ifndef\s+(.+)$/', $text, $matches)) {
$conds[] = "!defined($matches[1])";
} elseif (preg_match('/^#\s*else$/', $text)) {
if (empty($conds)) {
return 'else-error';
}
$cond = array_pop($conds);
$conds[] = "!($cond)";
} elseif (preg_match('/^#\s*endif$/', $text)) {
if (empty($conds)) {
return 'endif-error';
}
array_pop($conds);
} elseif ($text !== '' && $text[0] === '#') {
return "unknown:$text";
}
}
return empty($conds) ? null : implode(' && ', $conds);
}
function main(): void
{
$conds = [];
var_dump(handle_preprocessor_conditions($conds, ['#ifdef PHP_WIN32', '#if PHP_VERSION_ID >= 80400']));
var_dump(handle_preprocessor_conditions($conds, ['#else']));
var_dump(handle_preprocessor_conditions($conds, ['#endif', '#endif']));
var_dump(handle_preprocessor_conditions($conds, ['#else']));
var_dump(handle_preprocessor_conditions($conds, ['#pragma once']));
}
?>
--EXPECT--
string(45) "defined(PHP_WIN32) && PHP_VERSION_ID >= 80400"
string(48) "defined(PHP_WIN32) && !(PHP_VERSION_ID >= 80400)"
NULL
string(10) "else-error"
string(20) "unknown:#pragma once"

@ -0,0 +1,51 @@
--TEST--
short option preprocessing splits compact short flag values
--FILE--
<?php
function preprocess_short_options(array $argv): array
{
$processed = [$argv[0]];
for ($i = 1; $i < count($argv); $i++) {
$arg = $argv[$i];
if (preg_match('/^-([a-zA-Z])(.+)$/', $arg, $matches)) {
$option = $matches[1];
$value = $matches[2];
$processed[] = "-{$option}";
$processed[] = $value;
} elseif (preg_match('/^-([a-zA-Z]{2,})$/', $arg, $matches)) {
$options = str_split($matches[1]);
foreach ($options as $opt) {
$processed[] = "-{$opt}";
}
} else {
$processed[] = $arg;
}
}
return $processed;
}
function main(): void
{
var_dump(preprocess_short_options(['compiler.php', '-Iinclude', '-abc', '--debug', 'project.yml']));
}
?>
--EXPECT--
array(7) {
[0]=>
string(12) "compiler.php"
[1]=>
string(2) "-I"
[2]=>
string(7) "include"
[3]=>
string(2) "-a"
[4]=>
string(2) "bc"
[5]=>
string(7) "--debug"
[6]=>
string(11) "project.yml"
}

@ -0,0 +1,51 @@
--TEST--
target name normalization with path separators and identifier validation
--FILE--
<?php
function normalize_target_name(string $name): array
{
$outputDir = '';
if (str_contains($name, '/') || str_contains($name, '\\')) {
$outputDir = dirname($name);
$name = basename($name);
}
$name = str_replace(['-', '*'], '_', $name);
$valid = preg_match('/^[a-zA-Z0-9_]+$/', $name) === 1;
return [$outputDir, $name, $valid];
}
function main(): void
{
var_dump(normalize_target_name('build/my-app'));
var_dump(normalize_target_name('C:\\tmp\\my*app'));
var_dump(normalize_target_name('bad.name'));
}
?>
--EXPECT--
array(3) {
[0]=>
string(5) "build"
[1]=>
string(6) "my_app"
[2]=>
bool(true)
}
array(3) {
[0]=>
string(1) "."
[1]=>
string(13) "C:\tmp\my_app"
[2]=>
bool(false)
}
array(3) {
[0]=>
string(0) ""
[1]=>
string(8) "bad.name"
[2]=>
bool(false)
}

@ -0,0 +1,29 @@
--TEST--
compiler command name normalization from command string
--FILE--
<?php
function normalize_compiler_name(string $compilerName): string
{
$firstToken = strtok(trim($compilerName), ' ');
if ($firstToken === false || $firstToken === '') {
return '';
}
$name = basename(str_replace('\\', '/', $firstToken));
$name = strtolower($name);
return preg_replace('/\.exe$/', '', $name);
}
function main(): void
{
var_dump(normalize_compiler_name(' C:\\LLVM\\bin\\CLANG++.EXE -O2'));
var_dump(normalize_compiler_name('/usr/bin/g++ -std=c++20'));
var_dump(normalize_compiler_name(''));
}
?>
--EXPECT--
string(7) "clang++"
string(3) "g++"
string(0) ""

@ -0,0 +1,102 @@
--TEST--
C++ signature parameter splitter with nested templates and defaults
--FILE--
<?php
function split_cpp_parameters(string $paramsStr): array
{
$params = [];
$current = '';
$depth = 0;
$length = strlen($paramsStr);
for ($i = 0; $i < $length; $i++) {
$char = $paramsStr[$i];
if ($char === '<' || $char === '(' || $char === '[') {
$depth++;
$current .= $char;
} elseif ($char === '>' || $char === ')' || $char === ']') {
$depth--;
$current .= $char;
} elseif ($char === ',' && $depth === 0) {
$params[] = $current;
$current = '';
} else {
$current .= $char;
}
}
if (!empty($current)) {
$params[] = $current;
}
return $params;
}
function parse_cpp_parameter(string $param): array
{
$param = trim($param);
$param = preg_replace('/\s*=\s*.*$/', '', $param);
if (preg_match('/^(.+?)\s+(\w+)\s*$/', $param, $matches)) {
return [
'type' => trim($matches[1]),
'name' => trim($matches[2]),
];
}
return [
'type' => $param,
'name' => '',
];
}
function parse_cpp_parameters(string $signature, string $funcName): array
{
$pattern = '/' . preg_quote($funcName, '/') . '\s*\((.*?)\)/s';
if (!preg_match($pattern, $signature, $matches)) {
return [];
}
$paramsStr = trim($matches[1]);
if (empty($paramsStr) || $paramsStr === 'void') {
return [];
}
return array_map('parse_cpp_parameter', split_cpp_parameters($paramsStr));
}
function main(): void
{
$signature = 'static std::vector<std::pair<int, std::string>> build(std::map<int, std::string> values, const char* name = "x", Callback<int,string> cb)';
var_dump(parse_cpp_parameters($signature, 'build'));
var_dump(parse_cpp_parameters('void reset(void)', 'reset'));
}
?>
--EXPECT--
array(3) {
[0]=>
array(2) {
["type"]=>
string(26) "std::map<int, std::string>"
["name"]=>
string(6) "values"
}
[1]=>
array(2) {
["type"]=>
string(11) "const char*"
["name"]=>
string(4) "name"
}
[2]=>
array(2) {
["type"]=>
string(20) "Callback<int,string>"
["name"]=>
string(2) "cb"
}
}
array(0) {
}

@ -0,0 +1,48 @@
--TEST--
doc comment tag extraction with preg_match_all set order
--FILE--
<?php
function parse_doc_tags(array $comments): array
{
$tags = [];
foreach ($comments as $comment) {
if (preg_match_all('/@(\w+)\s+(.*)$/m', $comment, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$tagName = $match[1];
$tagValue = trim($match[2]);
if (!isset($tags[$tagName])) {
$tags[$tagName] = [];
}
$tags[$tagName][] = $tagValue;
}
}
}
return $tags;
}
function main(): void
{
$comment = "summary\n@param int \$id\n@param string \$name user name\n@return bool";
var_dump(parse_doc_tags([$comment]));
}
?>
--EXPECT--
array(2) {
["param"]=>
array(2) {
[0]=>
string(7) "int $id"
[1]=>
string(22) "string $name user name"
}
["return"]=>
array(1) {
[0]=>
string(4) "bool"
}
}

@ -0,0 +1,42 @@
--TEST--
docblock param regex with recursive named groups
--FILE--
<?php
function parse_doc_param_name(string $value): array
{
$matches = [];
preg_match('/^\s*(?<type>[\w\|\\\\]+(?<parens>\((?<inparens>(?:(?&parens)|[^(){}[\]<>]*+))++\)|\{(?&inparens)\}|\[(?&inparens)\]|<(?&inparens)>)*+(?::(?&type))?)\s*(\.\.\.)?\$(?<name>\w+).*$/', $value, $matches);
return [
$matches['type'] ?? '',
$matches['name'] ?? '',
];
}
function main(): void
{
var_dump(parse_doc_param_name('callable(array<string, int>):mixed $handler callback'));
var_dump(parse_doc_param_name('int ...$items'));
var_dump(parse_doc_param_name('$missingType'));
}
?>
--EXPECT--
array(2) {
[0]=>
string(34) "callable(array<string, int>):mixed"
[1]=>
string(7) "handler"
}
array(2) {
[0]=>
string(3) "int"
[1]=>
string(5) "items"
}
array(2) {
[0]=>
string(0) ""
[1]=>
string(0) ""
}

@ -0,0 +1,43 @@
--TEST--
file extension classifier with pathinfo and in_array
--FILE--
<?php
function file_name_of(string $path): string
{
return pathinfo($path, PATHINFO_FILENAME);
}
function file_ext_of(string $path): string
{
return pathinfo($path, PATHINFO_EXTENSION);
}
function classify_source_file(string $file): string
{
$ext = file_ext_of($file);
if (in_array($ext, ['php', 'inc', 'phpt'])) {
return 'php:' . file_name_of($file);
}
if (in_array($ext, ['cc', 'cpp', 'cxx'])) {
return 'cpp:' . file_name_of($file);
}
if (in_array($ext, ['c', 'h', 'hpp'])) {
return 'native:' . file_name_of($file);
}
return 'other:' . $ext;
}
function main(): void
{
var_dump(classify_source_file('/tmp/src/main.php'));
var_dump(classify_source_file('lib/compiler.cxx'));
var_dump(classify_source_file('include/aot.hpp'));
var_dump(classify_source_file('README'));
}
?>
--EXPECT--
string(8) "php:main"
string(12) "cpp:compiler"
string(10) "native:aot"
string(6) "other:"

@ -0,0 +1,36 @@
--TEST--
resource version normalization with ltrim explode preg_replace and slice
--FILE--
<?php
function normalize_resource_version(string $version): string
{
$version = ltrim($version, 'vV');
if (str_contains($version, ',')) {
return $version;
}
$parts = explode('.', $version);
foreach ($parts as $i => $part) {
$parts[$i] = preg_replace('/[^0-9]/', '', $part) ?: '0';
}
while (count($parts) < 4) {
$parts[] = '0';
}
return implode(',', array_slice($parts, 0, 4));
}
function main(): void
{
var_dump(normalize_resource_version('v1.2.3'));
var_dump(normalize_resource_version('V10.beta.5-rc1.7.9'));
var_dump(normalize_resource_version('1,2,3,4'));
}
?>
--EXPECT--
string(7) "1,2,3,0"
string(9) "10,0,51,7"
string(7) "1,2,3,4"

@ -0,0 +1,30 @@
--TEST--
unix library paths normalized into linker flags
--FILE--
<?php
function make_unix_link_lib_flags(array $libraries, string $ext): string
{
$flags = [];
foreach ($libraries as $lib) {
$libName = basename($lib);
if (str_starts_with($libName, 'lib')) {
$libName = substr($libName, 3);
}
$libName = preg_replace('/\.(a|' . $ext . ')$/', '', $libName);
$flags[] = '-l' . $libName;
}
return implode(' ', $flags);
}
function main(): void
{
var_dump(make_unix_link_lib_flags(['/usr/lib/libssl.so', 'libcrypto.a', 'z'], 'so'));
var_dump(make_unix_link_lib_flags(['libcustom.dylib', '/opt/lib/libplain.txt'], 'dylib'));
}
?>
--EXPECT--
string(18) "-lssl -lcrypto -lz"
string(20) "-lcustom -lplain.txt"

@ -0,0 +1,21 @@
--TEST--
camel case to snake case with regex replacement
--FILE--
<?php
function camel_to_snake(string $name): string
{
return strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
}
function main(): void
{
var_dump(camel_to_snake('toStdOrderedMap'));
var_dump(camel_to_snake('HTMLParserValue'));
var_dump(camel_to_snake('simple'));
}
?>
--EXPECT--
string(18) "to_std_ordered_map"
string(16) "htmlparser_value"
string(6) "simple"

@ -0,0 +1,26 @@
--TEST--
negative string offset read
--FILE--
<?php
function format_kind(string $format): string
{
if ($format[0] == '.' and $format[-1] == 'f') {
return 'float:' . substr($format, 1, strlen($format) - 2);
} elseif (str_contains($format, '%')) {
return 'date:' . $format;
}
return 'other:' . $format[-1];
}
function main(): void
{
var_dump(format_kind('.3f'));
var_dump(format_kind('%Y-%m-%d'));
var_dump(format_kind('abc'));
}
?>
--EXPECT--
string(7) "float:3"
string(13) "date:%Y-%m-%d"
string(7) "other:c"

@ -0,0 +1,42 @@
--TEST--
numeric literal classifier style regex and digit extraction
--FILE--
<?php
function strip_numeric_underscores(string $value): string
{
return str_replace('_', '', $value);
}
function is_big_int_literal_string(string $rawValue): bool
{
$clean = strip_numeric_underscores($rawValue);
if (!preg_match('/^\d+$/', $clean)) {
return false;
}
return strlen(ltrim($clean, '0')) >= 19;
}
function is_decimal_literal_string(string $rawValue): bool
{
$clean = strip_numeric_underscores($rawValue);
if (!preg_match('/[\.eE]/', $clean)) {
return false;
}
$digits = preg_replace('/[^0-9]/', '', $clean);
return strlen(ltrim($digits, '0')) >= 16;
}
function main(): void
{
var_dump(is_big_int_literal_string('9_223_372_036_854_775_808'));
var_dump(is_big_int_literal_string('0x10000000000000000'));
var_dump(is_decimal_literal_string('123_456_789_012_345.6'));
var_dump(is_decimal_literal_string('123.45'));
}
?>
--EXPECT--
bool(true)
bool(false)
bool(true)
bool(false)
Loading…
Cancel
Save