2017-03-15 21:10:29 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types = 1);
|
|
|
|
|
|
|
|
namespace LanguageServer\Tests;
|
|
|
|
|
|
|
|
use Exception;
|
2017-05-01 23:50:42 +00:00
|
|
|
use LanguageServer\Definition;
|
2017-03-15 21:10:29 +00:00
|
|
|
use LanguageServer\Index\Index;
|
2017-04-10 19:30:46 +00:00
|
|
|
use LanguageServer\ParserKind;
|
2017-03-15 21:10:29 +00:00
|
|
|
use LanguageServer\ParserResourceFactory;
|
|
|
|
use LanguageServer\PhpDocument;
|
2017-05-01 23:50:42 +00:00
|
|
|
use phpDocumentor\Reflection\DocBlock;
|
2017-03-15 21:10:29 +00:00
|
|
|
use phpDocumentor\Reflection\DocBlockFactory;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
use LanguageServer\ClientHandler;
|
|
|
|
use LanguageServer\Protocol\Message;
|
|
|
|
use AdvancedJsonRpc;
|
|
|
|
use RecursiveDirectoryIterator;
|
|
|
|
use RecursiveIteratorIterator;
|
|
|
|
use Sabre\Event\Loop;
|
2017-04-10 19:30:46 +00:00
|
|
|
use Microsoft\PhpParser as Tolerant;
|
2017-03-15 21:10:29 +00:00
|
|
|
|
|
|
|
class ValidationTest extends TestCase
|
|
|
|
{
|
|
|
|
public function frameworkErrorProvider() {
|
|
|
|
$frameworks = glob(__DIR__ . "/../../validation/frameworks/*", GLOB_ONLYDIR);
|
|
|
|
|
|
|
|
$testProviderArray = array();
|
|
|
|
foreach ($frameworks as $frameworkDir) {
|
|
|
|
$frameworkName = basename($frameworkDir);
|
2017-04-13 16:11:01 +00:00
|
|
|
if ($frameworkName !== "broken") {
|
2017-04-16 22:11:14 +00:00
|
|
|
// continue;
|
2017-04-13 16:11:01 +00:00
|
|
|
}
|
2017-05-01 23:50:42 +00:00
|
|
|
|
2017-03-15 21:10:29 +00:00
|
|
|
$iterator = new RecursiveDirectoryIterator(__DIR__ . "/../../validation/frameworks/" . $frameworkName);
|
|
|
|
|
|
|
|
foreach (new RecursiveIteratorIterator($iterator) as $file) {
|
2017-05-01 23:50:42 +00:00
|
|
|
if (strpos(\strrev((string)$file), \strrev(".php")) === 0) {
|
2017-03-28 20:58:48 +00:00
|
|
|
if ($file->getSize() < 100000) {
|
2017-03-24 17:30:07 +00:00
|
|
|
$testProviderArray[$frameworkName . "::" . $file->getBasename()] = [$file->getPathname(), $frameworkName];
|
|
|
|
}
|
2017-03-15 21:10:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (count($testProviderArray) === 0) {
|
|
|
|
throw new Exception("ERROR: Validation testsuite frameworks not found - run `git submodule update --init --recursive` to download.");
|
|
|
|
}
|
|
|
|
return $testProviderArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-04-11 02:39:25 +00:00
|
|
|
* @group validation
|
2017-03-15 21:10:29 +00:00
|
|
|
* @dataProvider frameworkErrorProvider
|
2017-05-01 23:50:42 +00:00
|
|
|
* @param $testCaseFile
|
|
|
|
* @param $frameworkName
|
2017-03-15 21:10:29 +00:00
|
|
|
*/
|
2017-05-01 23:50:42 +00:00
|
|
|
public function testFrameworkErrors($testCaseFile, $frameworkName) {
|
2017-03-15 21:10:29 +00:00
|
|
|
$fileContents = file_get_contents($testCaseFile);
|
2017-05-01 23:50:42 +00:00
|
|
|
|
2017-03-15 21:10:29 +00:00
|
|
|
$parser = ParserResourceFactory::getParser();
|
|
|
|
$docBlockFactory = DocBlockFactory::createInstance();
|
|
|
|
$index = new Index;
|
|
|
|
$definitionResolver = ParserResourceFactory::getDefinitionResolver($index);
|
|
|
|
|
|
|
|
$directory = __DIR__ . "/output/$frameworkName/";
|
|
|
|
$outFile = $directory . basename($testCaseFile);
|
|
|
|
|
|
|
|
try {
|
|
|
|
$document = new PhpDocument($testCaseFile, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver);
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
if (!file_exists($dir = __DIR__ . "/output")) {
|
|
|
|
mkdir($dir);
|
|
|
|
}
|
|
|
|
if (!file_exists($directory)) {
|
|
|
|
mkdir($directory);
|
|
|
|
}
|
|
|
|
file_put_contents($outFile, $fileContents);
|
|
|
|
$this->fail((string)$e);
|
|
|
|
}
|
|
|
|
|
2017-03-28 20:58:48 +00:00
|
|
|
$this->assertNotNull($document->getStmts());
|
|
|
|
|
2017-03-15 21:10:29 +00:00
|
|
|
if (file_exists($outFile)) {
|
|
|
|
unlink($outFile);
|
|
|
|
}
|
2017-04-11 19:54:20 +00:00
|
|
|
}
|
|
|
|
|
2017-04-10 19:30:46 +00:00
|
|
|
/**
|
2017-04-11 02:39:25 +00:00
|
|
|
* @group validation
|
2017-04-10 19:30:46 +00:00
|
|
|
* @dataProvider frameworkErrorProvider
|
2017-05-01 23:50:42 +00:00
|
|
|
* @param $testCaseFile
|
|
|
|
* @param $frameworkName
|
2017-04-10 19:30:46 +00:00
|
|
|
*/
|
|
|
|
public function testDefinitionErrors($testCaseFile, $frameworkName) {
|
2017-05-01 23:50:42 +00:00
|
|
|
echo PHP_EOL . realpath($testCaseFile) . PHP_EOL;
|
|
|
|
|
2017-04-10 19:30:46 +00:00
|
|
|
$fileContents = file_get_contents($testCaseFile);
|
2017-05-01 23:50:42 +00:00
|
|
|
[$expectedDefinitions, $expectedReferences] = $this->getExpectedDefinitionsAndReferences($testCaseFile, $frameworkName, $fileContents);
|
|
|
|
[$actualDefinitions, $actualReferences] = $this->getActualDefinitionsAndReferences($testCaseFile, $fileContents);
|
2017-04-16 22:11:14 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
$this->filterSkippedReferences($expectedReferences);
|
|
|
|
$this->filterSkippedReferences($actualReferences);
|
2017-04-10 19:30:46 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
$expectedValues = $this->getValuesFromDefinitionsAndReferences($expectedDefinitions, $expectedReferences);
|
|
|
|
$actualValues = $this->getValuesFromDefinitionsAndReferences($actualDefinitions, $actualReferences);
|
|
|
|
|
|
|
|
foreach ($expectedValues as $name => $expectedValue) {
|
|
|
|
$actualValue = $actualValues[$name];
|
|
|
|
|
|
|
|
if ($name === 'references') {
|
|
|
|
try {
|
|
|
|
$this->assertArraySubset($expectedValue, $actualValue, false, 'references don\'t match.');
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
$this->assertEquals($expectedValue, $actualValue, 'references don\'t match.');
|
|
|
|
}
|
|
|
|
continue;
|
2017-04-10 21:20:45 +00:00
|
|
|
}
|
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
$this->assertEquals($expectedValue, $actualValue, "$name did not match.");
|
|
|
|
}
|
|
|
|
}
|
2017-04-20 00:52:37 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
/**
|
|
|
|
* @param $filename
|
|
|
|
* @param $fileContents
|
|
|
|
* @return array<Definition[], string[][]>
|
|
|
|
*/
|
|
|
|
private function getExpectedDefinitionsAndReferences($filename, $frameworkName, $fileContents) {
|
|
|
|
// $outputFile = $filename . '.expected';
|
|
|
|
// if (file_exists($outputFile)) {
|
|
|
|
// return json_decode(file_get_contents($outputFile));
|
|
|
|
// }
|
2017-04-20 00:52:37 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
global $parserKind;
|
|
|
|
$parserKind = ParserKind::PHP_PARSER;
|
2017-04-11 19:54:20 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
$index = new Index();
|
|
|
|
$parser = ParserResourceFactory::getParser();
|
|
|
|
$docBlockFactory = DocBlockFactory::createInstance();
|
|
|
|
$definitionResolver = ParserResourceFactory::getDefinitionResolver($index);
|
2017-04-11 19:54:20 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
try {
|
|
|
|
$document = new PhpDocument($filename, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver);
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
$this->markTestSkipped('Baseline parser failed: '. $e->getTraceAsString());
|
|
|
|
}
|
2017-04-12 19:51:15 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
if ($document->getStmts() === null) {
|
|
|
|
$this->markTestSkipped('Baseline parser failed: null AST');
|
|
|
|
}
|
2017-04-12 19:51:15 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
$defsAndRefs = [$document->getDefinitions(), $index->references];
|
|
|
|
// if ($frameworkName === 'broken') {
|
|
|
|
// file_put_contents($outputFile, json_encode($defsAndRefs, JSON_PRETTY_PRINT));
|
|
|
|
// }
|
|
|
|
// }
|
2017-04-16 22:11:14 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
return $defsAndRefs;
|
|
|
|
}
|
2017-04-16 22:11:14 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
private function getActualDefinitionsAndReferences($filename, $fileContents) {
|
|
|
|
global $parserKind;
|
|
|
|
$parserKind = ParserKind::TOLERANT_PHP_PARSER;
|
|
|
|
|
|
|
|
$index = new Index();
|
|
|
|
$parser = ParserResourceFactory::getParser();
|
|
|
|
$docBlockFactory = DocBlockFactory::createInstance();
|
|
|
|
$definitionResolver = ParserResourceFactory::getDefinitionResolver($index);
|
|
|
|
|
|
|
|
$document = new PhpDocument($filename, $fileContents, $index, $parser, $docBlockFactory, $definitionResolver);
|
|
|
|
|
|
|
|
return [$document->getDefinitions(), $index->references];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $expectedDefinitions
|
|
|
|
* @param $expectedReferences
|
|
|
|
* @return array|\array[]
|
|
|
|
* @internal param $propertyNames
|
|
|
|
*/
|
|
|
|
private function getValuesFromDefinitionsAndReferences($expectedDefinitions, $expectedReferences): array
|
|
|
|
{
|
|
|
|
// TODO - use reflection to read these properties
|
|
|
|
$propertyNames = ['extends', 'isGlobal', 'isStatic', 'canBeInstantiated', 'symbolInformation', 'type', 'documentation'];
|
|
|
|
|
|
|
|
$expectedValues = [];
|
|
|
|
foreach ($expectedDefinitions as $expectedDefinition) {
|
|
|
|
$fqn = $expectedDefinition->fqn;
|
|
|
|
$expectedValues['$def->fqn'][] = $fqn;
|
|
|
|
|
|
|
|
foreach ($propertyNames as $propertyName) {
|
|
|
|
if ($propertyName === 'symbolInformation') {
|
|
|
|
unset($expectedDefinition->$propertyName->location->range);
|
|
|
|
} elseif ($propertyName === 'extends') {
|
|
|
|
$expectedDefinition->$propertyName = $expectedDefinition->$propertyName ?? [];
|
2017-04-16 22:11:14 +00:00
|
|
|
}
|
2017-05-01 23:50:42 +00:00
|
|
|
$expectedValues['$def->' . $propertyName][$fqn] = $expectedDefinition->$propertyName;
|
2017-04-10 19:30:46 +00:00
|
|
|
}
|
2017-05-01 23:50:42 +00:00
|
|
|
}
|
2017-04-10 19:30:46 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
$expectedValues['references'] = $expectedReferences;
|
|
|
|
return $expectedValues;
|
|
|
|
}
|
2017-04-11 19:54:20 +00:00
|
|
|
|
2017-05-01 23:50:42 +00:00
|
|
|
private function filterSkippedReferences(&$references)
|
|
|
|
{
|
|
|
|
$skipped = [
|
|
|
|
'false', 'true', 'null', 'FALSE', 'TRUE', 'NULL',
|
|
|
|
'__', // magic constants are treated as normal constants
|
|
|
|
'Exception', 'Error', // catch exception types missing from old definition resolver
|
|
|
|
'Trait', // use Trait references are missing from old definition resolve
|
|
|
|
'->tableAlias', '->realField', '->field', '->first_name', '->last_name', '->quoteMatch', '->idCol', '->timeCol', '->dataCol',
|
|
|
|
'pathToUri', 'uriToPath' // group function use declarations are broken in old definition resolver
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ($references as $key=>$value) {
|
|
|
|
foreach ($skipped as $s) {
|
|
|
|
if (strpos($key, $s) !== false) {
|
|
|
|
unset($references[$key]);
|
|
|
|
}
|
|
|
|
}
|
2017-04-10 19:30:46 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-15 21:10:29 +00:00
|
|
|
}
|