Merge pull request #489 from wbars/master

Add test for checking correctness of expectedArguments meta
This commit is contained in:
Maxim Kolmakov 2019-01-24 13:55:36 +01:00 committed by GitHub
commit dfb3c29e09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 314 additions and 45 deletions

View File

@ -90,7 +90,7 @@ namespace PHPSTORM_META {
expectedArguments(\DOMDocument::schemaValidateSource(), 1, LIBXML_SCHEMA_CREATE);
expectedArguments(\EvLoop::__construct(), 0, \Ev::FLAG_AUTO,\Ev::FLAG_NOENV,\Ev::FLAG_FORKCHECK,\Ev::FLAG_NOINOTIFY,\Ev::FLAG_SIGNALFD,\Ev::FLAG_NOSIGMASK); //todo support
expectedArguments(\Ev::run(), 0, \Ev::FLAG_AUTO,\Ev::FLAG_NOENV,\Ev::FLAG_FORKCHECK,\Ev::FLAG_NOINOTIFY,\Ev::FLAG_SIGNALFD,\Ev::FLAG_NOSIGMASK);
expectedArguments(\EvLoop::run(), 0, RUN_NOWAIT,RUN_ONCE);
expectedArguments(\EvLoop::run(), 0, \Ev::RUN_NOWAIT,\Ev::RUN_ONCE);
expectedArguments(\EvLoop::defaultLoop(), 0, \Ev::FLAG_AUTO,\Ev::FLAG_NOENV,\Ev::FLAG_FORKCHECK,\Ev::FLAG_NOINOTIFY,\Ev::FLAG_SIGNALFD,\Ev::FLAG_NOSIGMASK);
expectedArguments(\Event::pending(), 0, \Event::READ|\Event::WRITE|\Event::TIMEOUT|\Event::SIGNAL);
expectedArguments(\EventBase::loop(), 0, \EventBase::LOOP_ONCE, \EventBase::LOOP_NONBLOCK, \EventBase::NOLOCK, \EventBase::STARTUP_IOCP, \EventBase::NO_CACHE_TIME, \EventBase::EPOLL_USE_CHANGELIST);
@ -144,7 +144,7 @@ namespace PHPSTORM_META {
expectedArguments(\posix_access(), 1, POSIX_F_OK|POSIX_R_OK|POSIX_W_OK|POSIX_X_OK);
expectedArguments(\pspell_new(), 4, PSPELL_FAST,PSPELL_NORMAL,PSPELL_BAD_SPELLERS,PSPELL_RUN_TOGETHER);
expectedArguments(\pspell_new_personal(), 5, PSPELL_FAST,PSPELL_NORMAL,PSPELL_BAD_SPELLERS,PSPELL_RUN_TOGETHER);
expectedArguments(\SoapServer::setPersistence(), 0, OAP_PERSISTENCE_REQUEST,SOAP_PERSISTENCE_SESSION);
expectedArguments(\SoapServer::setPersistence(), 0, SOAP_PERSISTENCE_REQUEST,SOAP_PERSISTENCE_SESSION);
expectedArguments(\socket_recv(), 3, MSG_OOB|MSG_PEEK|MSG_WAITALL|MSG_DONTWAIT);
expectedArguments(\socket_send(), 3, MSG_OOB|MSG_EOR|MSG_EOF|MSG_DONTROUTE);
expectedArguments(\socket_recvfrom(), 3, MSG_OOB|MSG_PEEK|MSG_WAITALL|MSG_DONTWAIT);
@ -152,18 +152,18 @@ namespace PHPSTORM_META {
expectedArguments(\RecursiveIteratorIterator::__construct(), 1, \RecursiveIteratorIterator::LEAVES_ONLY,\RecursiveIteratorIterator::SELF_FIRST,\RecursiveIteratorIterator::CHILD_FIRST);
expectedArguments(\RecursiveIteratorIterator::__construct(), 2, \RecursiveIteratorIterator::CATCH_GET_CHILD);
expectedArguments(\RecursiveCachingIterator::__construct(), 1, \RecursiveCachingIterator::CALL_TOSTRING|\RecursiveCachingIterator::TOSTRING_USE_KEY|\RecursiveCachingIterator::TOSTRING_USE_CURRENT|\RecursiveCachingIterator::TOSTRING_USE_INNER);
expectedArguments(\RegexIterator::__construct(), 2, \RegexIterator::MATCH,\RegexIterator::GET_MATCH,\RegexIterator::ALL_MATCHES,\RegexIterator::SPLIT,REPLACE);
expectedArguments(\RecursiveCachingIterator::__construct(), 1, \CachingIterator::CALL_TOSTRING|\CachingIterator::TOSTRING_USE_KEY|\CachingIterator::TOSTRING_USE_CURRENT|\CachingIterator::TOSTRING_USE_INNER);
expectedArguments(\RegexIterator::__construct(), 2, \RegexIterator::MATCH,\RegexIterator::GET_MATCH,\RegexIterator::ALL_MATCHES,\RegexIterator::SPLIT,\RegexIterator::REPLACE);
expectedArguments(\RegexIterator::__construct(), 3, \RegexIterator::USE_KEY);
expectedArguments(\RegexIterator::__construct(), 4, \RegexIterator::USE_KEY);
expectedArguments(\RegexIterator::setMode(), 0, \RegexIterator::MATCH,\RegexIterator::GET_MATCH,\RegexIterator::ALL_MATCHES,\RegexIterator::SPLIT,\RegexIterator::REPLACE);
expectedArguments(\RegexIterator::setFlags(), 0, \RegexIterator::USE_KEY);
expectedArguments(\RecursiveRegexIterator::__construct(), 2, \RegexIterator::MATCH,\RegexIterator::GET_MATCH,\RegexIterator::ALL_MATCHES,\RegexIterator::SPLIT,REPLACE);
expectedArguments(\RecursiveRegexIterator::__construct(), 2, \RegexIterator::MATCH,\RegexIterator::GET_MATCH,\RegexIterator::ALL_MATCHES,\RegexIterator::SPLIT,\RegexIterator::REPLACE);
expectedArguments(\RecursiveRegexIterator::__construct(), 3, \RegexIterator::USE_KEY);
expectedArguments(\RecursiveRegexIterator::__construct(), 4, \RegexIterator::USE_KEY);
expectedArguments(\RecursiveTreeIterator::__construct(), 1, \RecursiveTreeIterator::BYPASS_KEY);
expectedArguments(\RecursiveTreeIterator::__construct(), 2, \CachingIterator::CATCH_GET_CHILD);
expectedArguments(\RecursiveTreeIterator::__construct(), 3, \RecursiveTreeIterator::SELF_FIRST);
expectedArguments(\RecursiveTreeIterator::__construct(), 3, \RecursiveIteratorIterator::SELF_FIRST);
expectedArguments(\ArrayObject::__construct(), 1, \ArrayObject::STD_PROP_LIST|\ArrayObject::ARRAY_AS_PROPS);
expectedArguments(\ArrayObject::__construct(), 1, \ArrayObject::STD_PROP_LIST|\ArrayObject::ARRAY_AS_PROPS);
expectedArguments(\ArrayIterator::__construct(), 1, \ArrayIterator::STD_PROP_LIST|\ArrayIterator::ARRAY_AS_PROPS);
@ -199,7 +199,7 @@ namespace PHPSTORM_META {
expectedArguments(\glob(), 1, GLOB_MARK|GLOB_NOSORT|GLOB_NOCHECK|GLOB_NOESCAPE|GLOB_BRACE|GLOB_ONLYDIR|GLOB_ERR);
expectedArguments(\count(), 1, COUNT_NORMAL,COUNT_RECURSIVE);
expectedArguments(\array_filter(), 2, ARRAY_FILTER_USE_KEY,ARRAY_FILTER_USE_BOTH);
expectedArguments(\svn_checkout(), 4, SVN_NON_RECURSIVE|SVN_IGNORE_EXTERNALS);
expectedArguments(\svn_checkout(), 4, SVN_NON_RECURSIVE);
expectedArguments(\svn_log(), 4, SVN_OMIT_MESSAGES|SVN_DISCOVER_CHANGED_PATHS|SVN_STOP_ON_COPY);
expectedArguments(\svn_status(), 1, \Svn::NON_RECURSIVE|\Svn::ALL|\Svn::SHOW_UPDATES|\Svn::NO_IGNORE|\Svn::IGNORE_EXTERNALS);
expectedArguments(\msg_receive(), 6, MSG_IPC_NOWAIT|MSG_EXCEPT|MSG_NOERROR);

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace StubTests\Parsers;
class ExpectedFunctionArgumentsInfo
{
/**
* @var \PhpParser\Node\Expr
*/
private $functionReference;
/**
* @var \PhpParser\Node\Expr[]
*/
private $expectedArguments;
/**
* ExpectedFunctionArgumentsInfo constructor.
* @param \PhpParser\Node\Expr $functionReference
* @param \PhpParser\Node\Expr[] $expectedArguments
*/
public function __construct(\PhpParser\Node\Expr $functionReference, array $expectedArguments)
{
$this->functionReference = $functionReference;
$this->expectedArguments = $expectedArguments;
}
/**
* @return \PhpParser\Node\Expr
*/
public function getFunctionReference(): \PhpParser\Node\Expr
{
return $this->functionReference;
}
/**
* @param \PhpParser\Node\Expr $functionReference
*/
public function setFunctionReference(\PhpParser\Node\Expr $functionReference): void
{
$this->functionReference = $functionReference;
}
/**
* @return \PhpParser\Node\Expr[]
*/
public function getExpectedArguments(): array
{
return $this->expectedArguments;
}
/**
* @param \PhpParser\Node\Expr[] $expectedArguments
*/
public function setExpectedArguments(array $expectedArguments): void
{
$this->expectedArguments = $expectedArguments;
}
public function __toString()
{
if (property_exists($this->functionReference, 'name')) {
return (string)$this->functionReference->name;
}
return implode(',', $this->functionReference->getAttributes());
}
}

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace StubTests\Parsers;
use PhpParser\NodeVisitorAbstract;
use RuntimeException;
use SplFileInfo;
class MetaExpectedArgumentsCollector extends NodeVisitorAbstract
{
private const EXPECTED_ARGUMENTS = 'expectedArguments';
/**
* @var ExpectedFunctionArgumentsInfo[]
*/
private $expectedArgumentsInfos;
public function __construct()
{
$this->expectedArgumentsInfos = array();
}
public function enterNode(\PhpParser\Node $node)
{
if ($node instanceof \PhpParser\Node\Expr\FuncCall) {
if ((string)$node->name === self::EXPECTED_ARGUMENTS) {
$args = $node->args;
if ($args < 3) throw new RuntimeException('Expected at least 3 arguments for expectedArguments call');
$expressions = array_slice(
array_map(function (\PhpParser\Node\Arg $arg) {
return $arg->value;
}, $args), 2);
$this->expectedArgumentsInfos[] = new ExpectedFunctionArgumentsInfo($args[0]->value, $this->unpackArguments($expressions));
}
}
}
/**
* @return ExpectedFunctionArgumentsInfo[]
*/
public function getExpectedArgumentsInfos(): array
{
return $this->expectedArgumentsInfos;
}
/**
* @param \PhpParser\Node\Expr[] $args
* @return \PhpParser\Node\Expr[]
*/
private function unpackArguments(array $expressions): array
{
$result = array();
foreach ($expressions as $expr) {
if ($expr instanceof \PhpParser\Node\Expr\BinaryOp\BitwiseOr) {
/** @noinspection SlowArrayOperationsInLoopInspection */
$result = array_merge($result, $this->unpackArguments(array($expr->left, $expr->right)));
} else {
$result[] = $expr;
}
}
return $result;
}
/**
* @return ExpectedFunctionArgumentsInfo[]
*/
public static function getMetaExpectedArguments(): array
{
$visitor = new MetaExpectedArgumentsCollector();
StubParser::processStubs($visitor, function (SplFileInfo $file) {
return $file->getFilename() === '.phpstorm.meta.php';
});
return $visitor->getExpectedArgumentsInfos();
}
}

View File

@ -6,6 +6,7 @@ namespace StubTests\Parsers;
use FilesystemIterator;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\NodeVisitorAbstract;
use PhpParser\ParserFactory;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
@ -19,30 +20,12 @@ class StubParser
public static function getPhpStormStubs(): StubsContainer
{
/** @noinspection PhpUnhandledExceptionInspection */
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$nameResolver = new NameResolver;
$stubs = new StubsContainer();
$visitor = new ASTVisitor($stubs);
$stubsIterator =
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(__DIR__ . '/../../', FilesystemIterator::SKIP_DOTS)
);
/** @var SplFileInfo $file */
foreach ($stubsIterator as $file) {
if (strpos($file->getRealPath(), 'vendor') || strpos($file->getRealPath(), '.git') ||
strpos($file->getRealPath(), 'tests') || strpos($file->getRealPath(), '.idea')) {
continue;
}
$code = file_get_contents($file->getRealPath());
$traverser = new NodeTraverser();
$traverser->addVisitor(new ParentConnector());
$traverser->addVisitor($nameResolver);
$traverser->addVisitor($visitor);
$traverser->traverse($parser->parse($code, new StubsParserErrorHandler()));
}
self::processStubs($visitor, function ($file) {
return true;
});
foreach ($stubs->getInterfaces() as $interface) {
$interface->parentInterfaces = $visitor->combineParentInterfaces($interface);
}
@ -53,4 +36,33 @@ class StubParser
}
return $stubs;
}
/**
* @param NodeVisitorAbstract $visitor
* @param callable $fileCondition
*/
public static function processStubs(NodeVisitorAbstract $visitor, callable $fileCondition): void
{
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$nameResolver = new NameResolver;
$stubsIterator =
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(__DIR__ . '/../../', FilesystemIterator::SKIP_DOTS)
);
/** @var SplFileInfo $file */
foreach ($stubsIterator as $file) {
if (!$fileCondition($file) ||
strpos($file->getRealPath(), 'vendor') || strpos($file->getRealPath(), '.git') ||
strpos($file->getRealPath(), 'tests') || strpos($file->getRealPath(), '.idea')) {
continue;
}
$code = file_get_contents($file->getRealPath());
$traverser = new NodeTraverser();
$traverser->addVisitor(new ParentConnector());
$traverser->addVisitor($nameResolver);
$traverser->addVisitor($visitor);
$traverser->traverse($parser->parse($code, new StubsParserErrorHandler()));
}
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace StubTests\TestData\Providers;
use StubTests\Model\StubsContainer;
use StubTests\Parsers\StubParser;
class PhpStormStubsSingleton
{
private static $phpstormStubs;
public static function getPhpStormStubs(): StubsContainer
{
if (self::$phpstormStubs === null) {
self::$phpstormStubs = StubParser::getPhpStormStubs();
}
return self::$phpstormStubs;
}
}

View File

@ -3,9 +3,6 @@ declare(strict_types=1);
namespace StubTests\TestData\Providers;
use StubTests\Model\StubsContainer;
use StubTests\Parsers\StubParser;
class StubsTestDataProviders
{
public static function stubClassConstantProvider()
@ -63,16 +60,3 @@ class StubsTestDataProviders
}
}
}
class PhpStormStubsSingleton
{
private static $phpstormStubs;
public static function getPhpStormStubs(): StubsContainer
{
if (self::$phpstormStubs === null) {
self::$phpstormStubs = StubParser::getPhpStormStubs();
}
return self::$phpstormStubs;
}
}

View File

@ -15,7 +15,7 @@ use StubTests\Model\PHPFunction;
use StubTests\Model\PHPInterface;
use StubTests\Model\PHPMethod;
use StubTests\Model\StubProblemType;
use StubTests\TestData\providers\PhpStormStubsSingleton;
use StubTests\TestData\Providers\PhpStormStubsSingleton;
class TestStubs extends TestCase
{

View File

@ -0,0 +1,113 @@
<?php
namespace StubTests;
use PHPUnit\Framework\TestCase;
use StubTests\Model\StubsContainer;
use StubTests\Parsers\ExpectedFunctionArgumentsInfo;
use StubTests\Parsers\MetaExpectedArgumentsCollector;
use StubTests\TestData\Providers\PhpStormStubsSingleton;
class TestStubsMetaExpectedArguments extends TestCase
{
/**
* @var ExpectedFunctionArgumentsInfo[]
*/
private static $expectedArguments;
private static $functionsFqns;
private static $methodsFqns;
private static $constantsFqns;
public static function setUpBeforeClass()
{
self::$expectedArguments = MetaExpectedArgumentsCollector::getMetaExpectedArguments();
$stubs = PhpStormStubsSingleton::getPhpStormStubs();
self::$functionsFqns = array_map(function (Model\PHPFunction $func) {
return self::toPresentableFqn((string)$func->name);
}, $stubs->getFunctions());
self::$methodsFqns = self::getMethodsFqns($stubs);;
self::$constantsFqns = self::getConstantsFqns($stubs);
}
private static function flatten(array $array): array
{
$return = array();
array_walk_recursive($array, function ($a) use (&$return) {
$return[$a] = $a;
});
return $return;
}
public static function getConstantsFqns(StubsContainer $stubs): array
{
$constants = array_map(function (\StubTests\Model\PHPConst $constant) {
return self::toPresentableFqn((string)$constant->name);
}, $stubs->getConstants());
foreach ($stubs->getClasses() as $class) {
foreach ($class->constants as $classConstant) {
$name = self::getClassMemberFqn($class->name, $classConstant->name);
$constants[$name] = $name;
}
}
return $constants;
}
public static function getMethodsFqns(StubsContainer $stubs): array
{
return self::flatten(
array_map(function (Model\PHPClass $class) {
return array_map(function (Model\PHPMethod $method) use ($class) {
return self::getClassMemberFqn($class->name, $method->name);
}, $class->methods);
}, $stubs->getClasses()));
}
public function testFunctionReferencesExists()
{
foreach (self::$expectedArguments as $argument) {
$expr = $argument->getFunctionReference();
if ($expr instanceof \PhpParser\Node\Expr\FuncCall) {
$fqn = self::toPresentableFqn($expr->name);
self::assertArrayHasKey($fqn, self::$functionsFqns, "Can't resolve function " . $fqn);
} else if ($expr instanceof \PhpParser\Node\Expr\StaticCall) {
if ((string)$expr->name !== '__construct') {
$fqn = self::getClassMemberFqn($expr->class, $expr->name);
self::assertArrayHasKey($fqn, self::$methodsFqns, "Can't resolve method " . $fqn);
}
} else {
self::fail('First argument should be function reference or method reference, got: ' . get_class($expr));
}
}
}
public function testConstantsExists()
{
foreach (self::$expectedArguments as $argument) {
$expectedArguments = $argument->getExpectedArguments();
self::assertNotEmpty($expectedArguments, 'Expected arguments should not be empty for ' . $argument);
foreach ($expectedArguments as $constantReference) {
if ($constantReference instanceof \PhpParser\Node\Expr\ClassConstFetch) {
$fqn = self::getClassMemberFqn($constantReference->class, $constantReference->name);
self::assertArrayHasKey($fqn, self::$constantsFqns, "Can't resolve class constant " . $fqn);
} else if ($constantReference instanceof \PhpParser\Node\Expr\ConstFetch) {
$fqn = self::toPresentableFqn((string)$constantReference->name);
self::assertArrayHasKey($fqn, self::$constantsFqns, "Can't resolve constant " . $fqn);
}
}
}
}
private static function getClassMemberFqn($className, $memberName): string
{
return self::toPresentableFqn($className) . '.' . $memberName;
}
private static function toPresentableFqn(string $name): string
{
if (strpos($name, '\\') === 0) {
return substr($name, 1);
}
return $name;
}
}