mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Improve test coverage of ProjectChecker
This commit is contained in:
parent
5a484901cd
commit
1d5759a35c
@ -19,6 +19,7 @@
|
||||
<file name="src/Psalm/CallMap.php" />
|
||||
<directory name="src/Psalm/Stubs" />
|
||||
<directory name="tests/stubs" />
|
||||
<directory name="tests/DummyProject" />
|
||||
<file name="vendor/phpunit/phpunit/src/Framework/TestCase.php" />
|
||||
<file name="src/Psalm/Diff/Differ.php" />
|
||||
</ignoreFiles>
|
||||
|
@ -14,8 +14,6 @@ use Psalm\Provider\ParserCacheProvider;
|
||||
use Psalm\Provider\Providers;
|
||||
use Psalm\Provider\StatementsProvider;
|
||||
use Psalm\Type;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Sabre\Event\Loop;
|
||||
|
||||
class ProjectChecker
|
||||
@ -255,7 +253,7 @@ class ProjectChecker
|
||||
$diff_files = null;
|
||||
$deleted_files = null;
|
||||
|
||||
$reference_cache = $this->file_reference_provider->loadReferenceCache();
|
||||
$reference_cache = $this->file_reference_provider->loadReferenceCache(true);
|
||||
|
||||
if ($is_diff
|
||||
&& $reference_cache
|
||||
@ -419,25 +417,14 @@ class ProjectChecker
|
||||
{
|
||||
$file_extensions = $config->getFileExtensions();
|
||||
|
||||
/** @var RecursiveDirectoryIterator */
|
||||
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_name));
|
||||
$iterator->rewind();
|
||||
$file_paths = $this->file_provider->getFilesInDir($dir_name, $file_extensions);
|
||||
|
||||
$files_to_scan = [];
|
||||
|
||||
while ($iterator->valid()) {
|
||||
if (!$iterator->isDot()) {
|
||||
$extension = $iterator->getExtension();
|
||||
if (in_array($extension, $file_extensions, true)) {
|
||||
$file_path = (string)$iterator->getRealPath();
|
||||
|
||||
if ($allow_non_project_files || $config->isInProjectDirs($file_path)) {
|
||||
$files_to_scan[$file_path] = $file_path;
|
||||
}
|
||||
}
|
||||
foreach ($file_paths as $file_path) {
|
||||
if ($allow_non_project_files || $config->isInProjectDirs($file_path)) {
|
||||
$files_to_scan[$file_path] = $file_path;
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
|
||||
$this->codebase->addFilesToAnalyze($files_to_scan);
|
||||
@ -451,26 +438,16 @@ class ProjectChecker
|
||||
private function getAllFiles(Config $config)
|
||||
{
|
||||
$file_extensions = $config->getFileExtensions();
|
||||
$file_names = [];
|
||||
$file_paths = [];
|
||||
|
||||
foreach ($config->getProjectDirectories() as $dir_name) {
|
||||
/** @var RecursiveDirectoryIterator */
|
||||
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_name));
|
||||
$iterator->rewind();
|
||||
|
||||
while ($iterator->valid()) {
|
||||
if (!$iterator->isDot()) {
|
||||
$extension = $iterator->getExtension();
|
||||
if (in_array($extension, $file_extensions, true)) {
|
||||
$file_names[] = (string)$iterator->getRealPath();
|
||||
}
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
$file_paths = array_merge(
|
||||
$file_paths,
|
||||
$this->file_provider->getFilesInDir($dir_name, $file_extensions)
|
||||
);
|
||||
}
|
||||
|
||||
return $file_names;
|
||||
return $file_paths;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -487,29 +464,18 @@ class ProjectChecker
|
||||
throw new \UnexpectedValueException('Parser cache provider cannot be null here');
|
||||
}
|
||||
|
||||
/** @var RecursiveDirectoryIterator */
|
||||
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_name));
|
||||
$iterator->rewind();
|
||||
|
||||
$diff_files = [];
|
||||
|
||||
$last_good_run = $this->parser_cache_provider->getLastGoodRun();
|
||||
|
||||
while ($iterator->valid()) {
|
||||
if (!$iterator->isDot()) {
|
||||
$extension = $iterator->getExtension();
|
||||
if (in_array($extension, $file_extensions, true)) {
|
||||
$file_path = (string)$iterator->getRealPath();
|
||||
$file_paths = $this->file_provider->getFilesInDir($dir_name, $file_extensions);
|
||||
|
||||
if ($config->isInProjectDirs($file_path)) {
|
||||
if ($this->file_provider->getModifiedTime($file_path) > $last_good_run) {
|
||||
$diff_files[] = $file_path;
|
||||
}
|
||||
}
|
||||
foreach ($file_paths as $file_path) {
|
||||
if ($config->isInProjectDirs($file_path)) {
|
||||
if ($this->file_provider->getModifiedTime($file_path) > $last_good_run) {
|
||||
$diff_files[] = $file_path;
|
||||
}
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
|
||||
return $diff_files;
|
||||
@ -526,7 +492,7 @@ class ProjectChecker
|
||||
$files_to_scan = [];
|
||||
|
||||
foreach ($file_list as $file_path) {
|
||||
if (!file_exists($file_path)) {
|
||||
if (!$this->file_provider->fileExists($file_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -43,4 +43,32 @@ class FileProvider
|
||||
{
|
||||
return file_exists($file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir_path
|
||||
* @param array<string> $file_extensions
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getFilesInDir($dir_path, array $file_extensions)
|
||||
{
|
||||
$file_paths = [];
|
||||
|
||||
/** @var \RecursiveDirectoryIterator */
|
||||
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir_path));
|
||||
$iterator->rewind();
|
||||
|
||||
while ($iterator->valid()) {
|
||||
if (!$iterator->isDot()) {
|
||||
$extension = $iterator->getExtension();
|
||||
if (in_array($extension, $file_extensions, true)) {
|
||||
$file_paths[] = (string)$iterator->getRealPath();
|
||||
}
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
|
||||
return $file_paths;
|
||||
}
|
||||
}
|
||||
|
@ -252,13 +252,14 @@ class FileReferenceProvider
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $force_reload
|
||||
* @return bool
|
||||
* @psalm-suppress MixedAssignment
|
||||
* @psalm-suppress MixedTypeCoercion
|
||||
*/
|
||||
public function loadReferenceCache()
|
||||
public function loadReferenceCache($force_reload = true)
|
||||
{
|
||||
if ($this->cache && !$this->loaded_from_cache) {
|
||||
if ($this->cache && (!$this->loaded_from_cache || $force_reload)) {
|
||||
$this->loaded_from_cache = true;
|
||||
|
||||
$file_references = $this->cache->getCachedFileReferences();
|
||||
|
@ -14,21 +14,21 @@ class ParserCacheProvider
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
protected $last_good_run = null;
|
||||
private $last_good_run = null;
|
||||
|
||||
/**
|
||||
* A map of filename hashes to contents hashes
|
||||
*
|
||||
* @var array<string, string>|null
|
||||
*/
|
||||
protected $existing_file_content_hashes = null;
|
||||
private $existing_file_content_hashes = null;
|
||||
|
||||
/**
|
||||
* A map of recently-added filename hashes to contents hashes
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $new_file_content_hashes = [];
|
||||
private $new_file_content_hashes = [];
|
||||
|
||||
/** @var bool */
|
||||
private $use_igbinary;
|
||||
@ -343,7 +343,11 @@ class ParserCacheProvider
|
||||
if ($this->last_good_run === null) {
|
||||
$cache_directory = Config::getInstance()->getCacheDirectory();
|
||||
|
||||
$this->last_good_run = filemtime($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME) ?: 0;
|
||||
if (file_exists($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME)) {
|
||||
$this->last_good_run = filemtime($cache_directory . DIRECTORY_SEPARATOR . self::GOOD_RUN_NAME);
|
||||
} else {
|
||||
$this->last_good_run = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->last_good_run;
|
||||
|
14
tests/DummyProject/Bar.php
Normal file
14
tests/DummyProject/Bar.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Foo;
|
||||
|
||||
class Bar
|
||||
{
|
||||
/** @var string */
|
||||
public $x;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->x = "hello";
|
||||
}
|
||||
}
|
11
tests/DummyProject/Bat.php
Normal file
11
tests/DummyProject/Bat.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Foo;
|
||||
|
||||
class Bat
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$a = new Bar();
|
||||
}
|
||||
}
|
311
tests/ProjectCheckerTest.php
Normal file
311
tests/ProjectCheckerTest.php
Normal file
@ -0,0 +1,311 @@
|
||||
<?php
|
||||
namespace Psalm\Tests;
|
||||
|
||||
use Psalm\Checker\FileChecker;
|
||||
use Psalm\Config;
|
||||
use Psalm\Context;
|
||||
|
||||
class ProjectCheckerTest extends TestCase
|
||||
{
|
||||
/** @var TestConfig */
|
||||
protected static $config;
|
||||
|
||||
/** @var \Psalm\Checker\ProjectChecker */
|
||||
protected $project_checker;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
self::$config = new TestConfig();
|
||||
|
||||
if (!defined('PSALM_VERSION')) {
|
||||
define('PSALM_VERSION', '2.0.0');
|
||||
}
|
||||
|
||||
if (!defined('PHP_PARSER_VERSION')) {
|
||||
define('PHP_PARSER_VERSION', '4.0.0');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setUp()
|
||||
{
|
||||
FileChecker::clearCache();
|
||||
$this->file_provider = new Provider\FakeFileProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @psalm-return array<mixed, string>
|
||||
*/
|
||||
public static function getAllIssues()
|
||||
{
|
||||
return array_filter(
|
||||
array_map(
|
||||
/**
|
||||
* @param string $file_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function ($file_name) {
|
||||
return substr($file_name, 0, -4);
|
||||
},
|
||||
scandir(dirname(__DIR__) . '/src/Psalm/Issue')
|
||||
),
|
||||
/**
|
||||
* @param string $issue_name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function ($issue_name) {
|
||||
return !empty($issue_name)
|
||||
&& $issue_name !== 'MethodIssue'
|
||||
&& $issue_name !== 'PropertyIssue'
|
||||
&& $issue_name !== 'ClassIssue'
|
||||
&& $issue_name !== 'CodeIssue';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config $config
|
||||
*
|
||||
* @return \Psalm\Checker\ProjectChecker
|
||||
*/
|
||||
private function getProjectCheckerWithConfig(Config $config)
|
||||
{
|
||||
return new \Psalm\Checker\ProjectChecker(
|
||||
$config,
|
||||
new \Psalm\Provider\Providers(
|
||||
$this->file_provider,
|
||||
new Provider\ParserInstanceCacheProvider(),
|
||||
new Provider\FileStorageInstanceCacheProvider(),
|
||||
new Provider\ClassLikeStorageInstanceCacheProvider(),
|
||||
new Provider\FakeFileReferenceCacheProvider()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCheck()
|
||||
{
|
||||
$this->project_checker = $this->getProjectCheckerWithConfig(
|
||||
Config::loadFromXML(
|
||||
(string)getcwd(),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<projectFiles>
|
||||
<directory name="tests/DummyProject" />
|
||||
</projectFiles>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
ob_start();
|
||||
$this->project_checker->check('tests/DummyProject');
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertSame('Scanning files...' . "\n" . 'Analyzing files...' . "\n", $output);
|
||||
|
||||
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());
|
||||
|
||||
$this->assertSame(
|
||||
'Psalm was able to infer types for 100.000% of analyzed code (2 files)',
|
||||
$this->project_checker->codebase->analyzer->getTypeInferenceSummary()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCheckAfterNoChange()
|
||||
{
|
||||
$this->project_checker = $this->getProjectCheckerWithConfig(
|
||||
Config::loadFromXML(
|
||||
(string)getcwd(),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<projectFiles>
|
||||
<directory name="tests/DummyProject" />
|
||||
</projectFiles>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
$this->project_checker->output_format = \Psalm\Checker\ProjectChecker::TYPE_JSON;
|
||||
|
||||
$this->project_checker->check('tests/DummyProject', true);
|
||||
\Psalm\IssueBuffer::finish($this->project_checker, true, microtime(true));
|
||||
|
||||
$this->assertSame(
|
||||
'Psalm was able to infer types for 100.000% of analyzed code (2 files)',
|
||||
$this->project_checker->codebase->analyzer->getTypeInferenceSummary()
|
||||
);
|
||||
|
||||
$this->project_checker->codebase->reloadFiles($this->project_checker, []);
|
||||
|
||||
$this->project_checker->check('tests/DummyProject', true);
|
||||
|
||||
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());
|
||||
|
||||
$this->assertSame(
|
||||
'No files analyzed',
|
||||
$this->project_checker->codebase->analyzer->getTypeInferenceSummary()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCheckAfterFileChange()
|
||||
{
|
||||
$this->project_checker = $this->getProjectCheckerWithConfig(
|
||||
Config::loadFromXML(
|
||||
(string)getcwd(),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<projectFiles>
|
||||
<directory name="tests/DummyProject" />
|
||||
</projectFiles>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
$this->project_checker->output_format = \Psalm\Checker\ProjectChecker::TYPE_JSON;
|
||||
|
||||
$this->project_checker->check('tests/DummyProject', true);
|
||||
\Psalm\IssueBuffer::finish($this->project_checker, true, microtime(true));
|
||||
|
||||
$this->assertSame(
|
||||
'Psalm was able to infer types for 100.000% of analyzed code (2 files)',
|
||||
$this->project_checker->codebase->analyzer->getTypeInferenceSummary()
|
||||
);
|
||||
|
||||
$bat_file_path = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'DummyProject' . DIRECTORY_SEPARATOR . 'Bat.php';
|
||||
|
||||
$bat_replacement_contents = '<?php
|
||||
|
||||
namespace Foo;
|
||||
|
||||
class Bat
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$a = new Bar();
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
$this->file_provider->registerFile($bat_file_path, $bat_replacement_contents);
|
||||
|
||||
$this->project_checker->codebase->reloadFiles($this->project_checker, []);
|
||||
|
||||
$this->project_checker->check('tests/DummyProject', true);
|
||||
|
||||
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());
|
||||
|
||||
$this->assertSame(
|
||||
'Psalm was able to infer types for 100.000% of analyzed code (1 file)',
|
||||
$this->project_checker->codebase->analyzer->getTypeInferenceSummary()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCheckDir()
|
||||
{
|
||||
$this->project_checker = $this->getProjectCheckerWithConfig(
|
||||
Config::loadFromXML(
|
||||
(string)getcwd(),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<projectFiles>
|
||||
<directory name="tests/DummyProject" />
|
||||
</projectFiles>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
ob_start();
|
||||
$this->project_checker->checkDir('tests/DummyProject');
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertSame('Scanning files...' . "\n" . 'Analyzing files...' . "\n", $output);
|
||||
|
||||
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());
|
||||
|
||||
$this->assertSame(
|
||||
'Psalm was able to infer types for 100.000% of analyzed code (2 files)',
|
||||
$this->project_checker->codebase->analyzer->getTypeInferenceSummary()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCheckPaths()
|
||||
{
|
||||
$this->project_checker = $this->getProjectCheckerWithConfig(
|
||||
Config::loadFromXML(
|
||||
(string)getcwd(),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<projectFiles>
|
||||
<directory name="tests/DummyProject" />
|
||||
</projectFiles>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
ob_start();
|
||||
$this->project_checker->checkPaths(['tests/DummyProject/Bar.php']);
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertSame('Scanning files...' . "\n" . 'Analyzing files...' . "\n", $output);
|
||||
|
||||
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());
|
||||
|
||||
$this->assertSame(
|
||||
'Psalm was able to infer types for 100.000% of analyzed code (1 file)',
|
||||
$this->project_checker->codebase->analyzer->getTypeInferenceSummary()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCheckFile()
|
||||
{
|
||||
$this->project_checker = $this->getProjectCheckerWithConfig(
|
||||
Config::loadFromXML(
|
||||
(string)getcwd(),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<projectFiles>
|
||||
<directory name="tests/DummyProject" />
|
||||
</projectFiles>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
ob_start();
|
||||
$this->project_checker->checkFile('tests/DummyProject/Bar.php');
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertSame('Scanning files...' . "\n" . 'Analyzing files...' . "\n", $output);
|
||||
|
||||
$this->assertSame(0, \Psalm\IssueBuffer::getErrorCount());
|
||||
|
||||
$this->assertSame(
|
||||
'Psalm was able to infer types for 100.000% of analyzed code (1 file)',
|
||||
$this->project_checker->codebase->analyzer->getTypeInferenceSummary()
|
||||
);
|
||||
}
|
||||
}
|
@ -74,4 +74,23 @@ class FakeFileProvider extends \Psalm\Provider\FileProvider
|
||||
$this->fake_files[$file_path] = $file_contents;
|
||||
$this->fake_file_times[$file_path] = microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir_path
|
||||
* @param array<string> $file_extensions
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getFilesInDir($dir_path, array $file_extensions)
|
||||
{
|
||||
$file_paths = parent::getFilesInDir($dir_path, $file_extensions);
|
||||
|
||||
foreach ($this->fake_files as $file_path => $_) {
|
||||
if (strpos(strtolower($file_path), strtolower($dir_path)) === 0) {
|
||||
$file_paths[] = $file_path;
|
||||
}
|
||||
}
|
||||
|
||||
return $file_paths;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,11 @@ class ParserInstanceCacheProvider extends \Psalm\Provider\ParserCacheProvider
|
||||
*/
|
||||
private $statements_cache_time = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $last_good_run = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
@ -89,4 +94,30 @@ class ParserInstanceCacheProvider extends \Psalm\Provider\ParserCacheProvider
|
||||
{
|
||||
$this->file_contents_cache[$file_path] = $file_contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLastGoodRun()
|
||||
{
|
||||
return $this->last_good_run;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $start_time
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processSuccessfulRun($start_time)
|
||||
{
|
||||
$this->last_good_run = (int) $start_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function canDiffFiles()
|
||||
{
|
||||
return $this->last_good_run > 0;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user