Files
michaelschiemer/tests/debug/test_token_classification.php
Michael Schiemer 36ef2a1e2c
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
fix: Gitea Traefik routing and connection pool optimization
- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
2025-11-09 14:46:15 +01:00

913 lines
31 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use App\Framework\Tokenizer\PhpTokenizer;
use App\Framework\Tokenizer\ValueObjects\Token;
use App\Framework\Tokenizer\ValueObjects\TokenType;
/**
* Test script for token classification
* Tests various PHP structures and compares expected vs actual token types
*/
final class TokenClassificationTest
{
private PhpTokenizer $tokenizer;
public function __construct()
{
$this->tokenizer = new PhpTokenizer();
}
/**
* Run all test cases
*/
public function runAll(): void
{
$testCases = $this->getTestCases();
$total = count($testCases);
$passed = 0;
$failed = 0;
echo "=== Token Classification Test Suite ===\n\n";
echo "Running {$total} test cases...\n\n";
foreach ($testCases as $index => $testCase) {
echo str_repeat('=', 80) . "\n";
echo "Test Case " . ($index + 1) . ": {$testCase['name']}\n";
echo str_repeat('=', 80) . "\n\n";
$result = $this->runTestCase($testCase);
if ($result['passed']) {
$passed++;
echo "✅ PASSED\n\n";
} else {
$failed++;
echo "❌ FAILED\n\n";
}
}
echo str_repeat('=', 80) . "\n";
echo "Summary: {$passed}/{$total} passed, {$failed}/{$total} failed\n";
}
/**
* Run a single test case
*/
private function runTestCase(array $testCase): array
{
$code = $testCase['code'];
$expected = $testCase['expected'];
$expectedMetadata = $testCase['expectedMetadata'] ?? null;
echo "Code:\n";
$codeLines = explode("\n", $code);
foreach ($codeLines as $i => $line) {
echo sprintf(" %2d: %s\n", $i + 1, $line);
}
echo "\n";
// Tokenize the code
$tokens = $this->tokenizer->tokenize($code);
// Build actual token map
$actual = $this->buildTokenMap($tokens);
// Compare expected vs actual
$differences = $this->compareTokens($expected, $actual);
// Check metadata if expected
$metadataDifferences = [];
if ($expectedMetadata !== null) {
$metadataDifferences = $this->compareMetadata($tokens, $expectedMetadata);
}
// Display expected tokens
echo "Expected Tokens:\n";
foreach ($expected as $lineNum => $lineTokens) {
foreach ($lineTokens as $tokenValue => $expectedType) {
echo sprintf(" Line %d: '%s' → %s\n", $lineNum, $tokenValue, $expectedType);
}
}
echo "\n";
// Display actual tokens
echo "Actual Tokens:\n";
foreach ($actual as $lineNum => $lineTokens) {
foreach ($lineTokens as $tokenValue => $actualType) {
echo sprintf(" Line %d: '%s' → %s\n", $lineNum, $tokenValue, $actualType->value);
}
}
echo "\n";
// Display differences
if (empty($differences) && empty($metadataDifferences)) {
echo "Differences: None - All tokens and metadata match!\n";
return ['passed' => true, 'differences' => []];
}
echo "Differences:\n";
foreach ($differences as $diff) {
if ($diff['match']) {
echo sprintf(" ✅ Line %d: '%s' → %s (correct)\n",
$diff['line'],
$diff['token'],
$diff['expected']
);
} else {
echo sprintf(" ❌ Line %d: '%s' → Expected %s, got %s\n",
$diff['line'],
$diff['token'],
$diff['expected'],
$diff['actual']
);
}
}
// Display metadata differences
if (!empty($metadataDifferences)) {
echo "\nMetadata Differences:\n";
foreach ($metadataDifferences as $diff) {
if ($diff['match']) {
echo sprintf(" ✅ Line %d: '%s' → %s (correct)\n",
$diff['line'],
$diff['token'],
$diff['expected']
);
} else {
echo sprintf(" ❌ Line %d: '%s' → Expected %s, got %s\n",
$diff['line'],
$diff['token'],
$diff['expected'],
$diff['actual']
);
}
}
}
$allDifferences = array_merge($differences, $metadataDifferences);
return [
'passed' => empty(array_filter($allDifferences, fn($d) => !$d['match'])),
'differences' => $allDifferences
];
}
/**
* Build token map from TokenCollection
*/
private function buildTokenMap($tokens): array
{
$map = [];
foreach ($tokens as $token) {
$line = $token->line;
$value = trim($token->value);
// Skip whitespace-only tokens and PHP tags
if ($token->type === TokenType::WHITESPACE ||
$token->type === TokenType::PHP_TAG ||
empty($value)) {
continue;
}
if (!isset($map[$line])) {
$map[$line] = [];
}
// Use token value as key, token type as value
$map[$line][$value] = $token->type;
}
return $map;
}
/**
* Compare metadata for tokens
*/
private function compareMetadata($tokens, array $expectedMetadata): array
{
$differences = [];
// Build token lookup by line and value
$tokenLookup = [];
foreach ($tokens as $token) {
$line = $token->line;
$value = trim($token->value);
if (empty($value) || $token->type === TokenType::WHITESPACE || $token->type === TokenType::PHP_TAG) {
continue;
}
if (!isset($tokenLookup[$line])) {
$tokenLookup[$line] = [];
}
$tokenLookup[$line][$value] = $token;
}
// Compare expected metadata
foreach ($expectedMetadata as $lineNum => $lineMetadata) {
foreach ($lineMetadata as $tokenValue => $expectedMeta) {
$token = $tokenLookup[$lineNum][$tokenValue] ?? null;
if ($token === null) {
$differences[] = [
'line' => $lineNum,
'token' => $tokenValue,
'expected' => json_encode($expectedMeta),
'actual' => 'TOKEN_NOT_FOUND',
'match' => false
];
continue;
}
$actualMeta = $token->metadata;
// Check each expected metadata property
foreach ($expectedMeta as $property => $expectedValue) {
$actualValue = $actualMeta?->$property ?? null;
if ($property === 'isBuiltIn') {
$actualValue = $actualMeta?->isBuiltIn ?? false;
}
$match = $actualValue === $expectedValue;
if (!$match) {
$differences[] = [
'line' => $lineNum,
'token' => $tokenValue,
'expected' => "{$property}: " . ($expectedValue === true ? 'true' : ($expectedValue === false ? 'false' : $expectedValue)),
'actual' => "{$property}: " . ($actualValue === true ? 'true' : ($actualValue === false ? 'false' : ($actualValue ?? 'null'))),
'match' => false
];
}
}
}
}
return $differences;
}
/**
* Compare expected and actual tokens
*/
private function compareTokens(array $expected, array $actual): array
{
$differences = [];
$allLines = array_unique(array_merge(array_keys($expected), array_keys($actual)));
foreach ($allLines as $lineNum) {
$expectedTokens = $expected[$lineNum] ?? [];
$actualTokens = $actual[$lineNum] ?? [];
// Check all expected tokens
foreach ($expectedTokens as $tokenValue => $expectedTypeName) {
$expectedType = TokenType::tryFrom($expectedTypeName);
$actualType = $actualTokens[$tokenValue] ?? null;
$differences[] = [
'line' => $lineNum,
'token' => $tokenValue,
'expected' => $expectedTypeName,
'actual' => $actualType?->value ?? 'NOT_FOUND',
'match' => $actualType === $expectedType
];
}
// Check for unexpected tokens (optional - could be verbose)
foreach ($actualTokens as $tokenValue => $actualType) {
if (!isset($expectedTokens[$tokenValue])) {
// This token wasn't expected - could log as info
}
}
}
return $differences;
}
/**
* Get all test cases
*/
private function getTestCases(): array
{
return array_merge(
// Test Cases for Classes
$this->getClassTestCases(),
// Test Cases for Enums
$this->getEnumTestCases(),
// Test Cases for Attributes
$this->getAttributeTestCases(),
// Test Cases for Methods and Properties
$this->getMethodPropertyTestCases(),
// Test Cases for Metadata
$this->getMetadataTestCases()
);
}
/**
* Get test cases for classes
*/
private function getClassTestCases(): array
{
return [
[
'name' => 'Simple Class',
'code' => '<?php class MyClass {}',
'expected' => [
1 => [
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Final Class',
'code' => '<?php final class MyClass {}',
'expected' => [
1 => [
'final' => 'keyword_modifier',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Abstract Class',
'code' => '<?php abstract class MyClass {}',
'expected' => [
1 => [
'abstract' => 'keyword_modifier',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Readonly Class',
'code' => '<?php readonly class MyClass {}',
'expected' => [
1 => [
'readonly' => 'keyword_modifier',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Class with Extends',
'code' => '<?php class Child extends Parent {}',
'expected' => [
1 => [
'class' => 'keyword_other',
'Child' => 'class_name',
'extends' => 'keyword_other',
'Parent' => 'class_name',
],
],
],
[
'name' => 'Class with Implements',
'code' => '<?php class MyClass implements MyInterface {}',
'expected' => [
1 => [
'class' => 'keyword_other',
'MyClass' => 'class_name',
'implements' => 'keyword_other',
'MyInterface' => 'interface_name',
],
],
],
[
'name' => 'Class with Multiple Modifiers',
'code' => '<?php final readonly class MyClass {}',
'expected' => [
1 => [
'final' => 'keyword_modifier',
'readonly' => 'keyword_modifier',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Class with Extends and Implements',
'code' => '<?php class Child extends Parent implements MyInterface {}',
'expected' => [
1 => [
'class' => 'keyword_other',
'Child' => 'class_name',
'extends' => 'keyword_other',
'Parent' => 'class_name',
'implements' => 'keyword_other',
'MyInterface' => 'interface_name',
],
],
],
];
}
/**
* Get test cases for enums
*/
private function getEnumTestCases(): array
{
return [
[
'name' => 'Pure Enum',
'code' => '<?php enum Status {}',
'expected' => [
1 => [
'enum' => 'keyword_other',
'Status' => 'enum_name',
],
],
],
[
'name' => 'Backed Enum (String)',
'code' => '<?php enum Status: string {}',
'expected' => [
1 => [
'enum' => 'keyword_other',
'Status' => 'enum_name',
':' => 'operator',
'string' => 'keyword_type',
],
],
],
[
'name' => 'Backed Enum (Int)',
'code' => '<?php enum Status: int {}',
'expected' => [
1 => [
'enum' => 'keyword_other',
'Status' => 'enum_name',
':' => 'operator',
'int' => 'keyword_type',
],
],
],
[
'name' => 'Enum with Implements',
'code' => '<?php enum Status implements MyInterface {}',
'expected' => [
1 => [
'enum' => 'keyword_other',
'Status' => 'enum_name',
'implements' => 'keyword_other',
'MyInterface' => 'interface_name',
],
],
],
[
'name' => 'Final Enum',
'code' => '<?php final enum Status {}',
'expected' => [
1 => [
'final' => 'keyword_modifier',
'enum' => 'keyword_other',
'Status' => 'enum_name',
],
],
],
[
'name' => 'Backed Enum with Implements',
'code' => '<?php enum Status: string implements MyInterface {}',
'expected' => [
1 => [
'enum' => 'keyword_other',
'Status' => 'enum_name',
':' => 'operator',
'string' => 'keyword_type',
'implements' => 'keyword_other',
'MyInterface' => 'interface_name',
],
],
],
];
}
/**
* Get test cases for attributes
*/
private function getAttributeTestCases(): array
{
return [
[
'name' => 'Simple Attribute',
'code' => '<?php #[Route] class MyClass {}',
'expected' => [
1 => [
'#' => 'attribute',
'[' => 'bracket',
'Route' => 'attribute_name',
']' => 'bracket',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Attribute with Parameter',
'code' => '<?php #[Route(\'/api\')] class MyClass {}',
'expected' => [
1 => [
'#' => 'attribute',
'[' => 'bracket',
'Route' => 'attribute_name',
'(' => 'parenthesis',
'\'/api\'' => 'string_literal',
')' => 'parenthesis',
']' => 'bracket',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Attribute with Named Parameters',
'code' => '<?php #[Route(path: \'/api\', method: \'GET\')] class MyClass {}',
'expected' => [
1 => [
'#' => 'attribute',
'[' => 'bracket',
'Route' => 'attribute_name',
'(' => 'parenthesis',
'path' => 'attribute_name',
':' => 'operator',
'\'/api\'' => 'string_literal',
',' => 'punctuation',
'method' => 'attribute_name',
':' => 'operator',
'\'GET\'' => 'string_literal',
')' => 'parenthesis',
']' => 'bracket',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Multiple Attributes',
'code' => '<?php #[Route, Auth] class MyClass {}',
'expected' => [
1 => [
'#' => 'attribute',
'[' => 'bracket',
'Route' => 'attribute_name',
',' => 'punctuation',
'Auth' => 'attribute_name',
']' => 'bracket',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Attribute with Multiple Parameters',
'code' => '<?php #[Route(\'/api\', \'POST\')] class MyClass {}',
'expected' => [
1 => [
'#' => 'attribute',
'[' => 'bracket',
'Route' => 'attribute_name',
'(' => 'parenthesis',
'\'/api\'' => 'string_literal',
',' => 'punctuation',
'\'POST\'' => 'string_literal',
')' => 'parenthesis',
']' => 'bracket',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Attribute with Class Parameter',
'code' => '<?php #[Route(MyClass::class)] class MyClass {}',
'expected' => [
1 => [
'#' => 'attribute',
'[' => 'bracket',
'Route' => 'attribute_name',
'(' => 'parenthesis',
'MyClass' => 'class_name',
'::' => 'operator',
'class' => 'keyword_other',
')' => 'parenthesis',
']' => 'bracket',
'class' => 'keyword_other',
'MyClass' => 'class_name',
],
],
],
[
'name' => 'Attribute on Method',
'code' => '<?php class MyClass { #[Route] public function test() {} }',
'expected' => [
1 => [
'class' => 'keyword_other',
'MyClass' => 'class_name',
'{' => 'brace',
],
2 => [
'#' => 'attribute',
'[' => 'bracket',
'Route' => 'attribute_name',
']' => 'bracket',
'public' => 'keyword_modifier',
'function' => 'keyword_other',
'test' => 'method_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
],
];
}
/**
* Get test cases for methods and properties
*/
private function getMethodPropertyTestCases(): array
{
return [
[
'name' => 'Static Method Call',
'code' => '<?php MyClass::staticMethod();',
'expected' => [
1 => [
'MyClass' => 'class_name',
'::' => 'operator',
'staticMethod' => 'static_method_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
],
[
'name' => 'Instance Method Call',
'code' => '<?php $obj->instanceMethod();',
'expected' => [
1 => [
'$obj' => 'variable',
'->' => 'operator',
'instanceMethod' => 'instance_method_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
],
[
'name' => 'Property Access',
'code' => '<?php $obj->property;',
'expected' => [
1 => [
'$obj' => 'variable',
'->' => 'operator',
'property' => 'instance_property_name',
],
],
],
[
'name' => 'Static Property Access',
'code' => '<?php MyClass::$staticProperty;',
'expected' => [
1 => [
'MyClass' => 'class_name',
'::' => 'operator',
'$staticProperty' => 'variable',
],
],
],
[
'name' => 'Constructor Call',
'code' => '<?php new MyClass();',
'expected' => [
1 => [
'new' => 'keyword_other',
'MyClass' => 'constructor_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
],
[
'name' => 'Method Definition',
'code' => '<?php public function myMethod() {}',
'expected' => [
1 => [
'public' => 'keyword_modifier',
'function' => 'keyword_other',
'myMethod' => 'function_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
],
[
'name' => 'Method Definition in Class',
'code' => '<?php class MyClass { public function myMethod() {} }',
'expected' => [
1 => [
'class' => 'keyword_other',
'MyClass' => 'class_name',
'{' => 'brace',
],
2 => [
'public' => 'keyword_modifier',
'function' => 'keyword_other',
'myMethod' => 'method_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
],
[
'name' => 'Constructor Definition',
'code' => '<?php class MyClass { public function __construct() {} }',
'expected' => [
1 => [
'class' => 'keyword_other',
'MyClass' => 'class_name',
'{' => 'brace',
],
2 => [
'public' => 'keyword_modifier',
'function' => 'keyword_other',
'__construct' => 'constructor_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
],
[
'name' => 'Static Method Definition',
'code' => '<?php class MyClass { public static function staticMethod() {} }',
'expected' => [
1 => [
'class' => 'keyword_other',
'MyClass' => 'class_name',
'{' => 'brace',
],
2 => [
'public' => 'keyword_modifier',
'static' => 'keyword_modifier',
'function' => 'keyword_other',
'staticMethod' => 'method_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
],
[
'name' => 'Nullsafe Operator Method Call',
'code' => '<?php $obj?->method();',
'expected' => [
1 => [
'$obj' => 'variable',
'?->' => 'operator',
'method' => 'instance_method_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
],
];
}
/**
* Get test cases for metadata
*/
private function getMetadataTestCases(): array
{
return [
[
'name' => 'Built-in Function Call',
'code' => '<?php count($array);',
'expected' => [
1 => [
'count' => 'function_name',
'$array' => 'variable',
],
],
'expectedMetadata' => [
1 => [
'count' => [
'functionName' => 'count',
'isBuiltIn' => true,
],
],
],
],
[
'name' => 'Built-in Function strlen',
'code' => '<?php strlen($str);',
'expected' => [
1 => [
'strlen' => 'function_name',
'$str' => 'variable',
],
],
'expectedMetadata' => [
1 => [
'strlen' => [
'functionName' => 'strlen',
'isBuiltIn' => true,
],
],
],
],
[
'name' => 'Built-in Function array_map',
'code' => '<?php array_map($callback, $array);',
'expected' => [
1 => [
'array_map' => 'function_name',
'$callback' => 'variable',
'$array' => 'variable',
],
],
'expectedMetadata' => [
1 => [
'array_map' => [
'functionName' => 'array_map',
'isBuiltIn' => true,
],
],
],
],
[
'name' => 'User Function Call',
'code' => '<?php myFunction();',
'expected' => [
1 => [
'myFunction' => 'function_name',
'(' => 'parenthesis',
')' => 'parenthesis',
],
],
'expectedMetadata' => [
1 => [
'myFunction' => [
'functionName' => 'myFunction',
'isBuiltIn' => false,
],
],
],
],
[
'name' => 'Static Method Call with Metadata',
'code' => '<?php MyClass::staticMethod();',
'expected' => [
1 => [
'MyClass' => 'class_name',
'::' => 'operator',
'staticMethod' => 'static_method_name',
],
],
'expectedMetadata' => [
1 => [
'MyClass' => [
'className' => 'MyClass',
],
'staticMethod' => [
'functionName' => 'staticMethod',
'className' => 'MyClass',
],
],
],
],
[
'name' => 'Instance Method Call with Metadata',
'code' => '<?php $obj->instanceMethod();',
'expected' => [
1 => [
'$obj' => 'variable',
'->' => 'operator',
'instanceMethod' => 'instance_method_name',
],
],
'expectedMetadata' => [
1 => [
'instanceMethod' => [
'functionName' => 'instanceMethod',
],
],
],
],
];
}
}
// Run tests if executed directly
if (php_sapi_name() === 'cli') {
$test = new TokenClassificationTest();
$test->runAll();
}