![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/dev/tests/static/testsuite/Magento/Test/Php/ |
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ declare(strict_types=1); namespace Magento\Test\Php; use Magento\Framework\App\Utility\Files; use Magento\TestFramework\CodingStandard\Tool\CodeMessDetector; use Magento\TestFramework\CodingStandard\Tool\CodeSniffer; use Magento\TestFramework\CodingStandard\Tool\CodeSniffer\Wrapper; use Magento\TestFramework\CodingStandard\Tool\CopyPasteDetector; use Magento\TestFramework\CodingStandard\Tool\PhpCompatibility; use Magento\TestFramework\CodingStandard\Tool\PhpStan; use Magento\TestFramework\Utility\AddedFiles; use Magento\TestFramework\Utility\FilesSearch; use PHPMD\TextUI\Command; /** * Set of tests for static code analysis, e.g. code style, code complexity, copy paste detecting, etc. */ class LiveCodeTest extends \PHPUnit\Framework\TestCase { /** * @var string */ protected static $reportDir = ''; /** * @var string */ protected static $pathToSource = ''; /** * Setup basics for all tests * * @return void */ public static function setUpBeforeClass(): void { self::$pathToSource = BP; self::$reportDir = self::$pathToSource . '/dev/tests/static/report'; if (!is_dir(self::$reportDir)) { mkdir(self::$reportDir); } } /** * Returns base folder for suite scope * * @return string */ private static function getBaseFilesFolder() { return __DIR__; } /** * Returns base directory for whitelisted files * * @return string */ private static function getChangedFilesBaseDir() { return __DIR__ . '/..'; } /** * Returns whitelist based on blacklist and git changed files * * @param array $fileTypes * @param string $changedFilesBaseDir * @param string $baseFilesFolder * @param string $whitelistFile * @return array */ public static function getWhitelist( $fileTypes = ['php'], $changedFilesBaseDir = '', $baseFilesFolder = '', $whitelistFile = '/_files/whitelist/common.txt' ) { $changedFiles = self::getChangedFilesList($changedFilesBaseDir); if (empty($changedFiles)) { return []; } $globPatternsFolder = ('' !== $baseFilesFolder) ? $baseFilesFolder : self::getBaseFilesFolder(); try { $directoriesToCheck = Files::init()->readLists($globPatternsFolder . $whitelistFile); } catch (\Exception $e) { // no directories matched white list return []; } $targetFiles = self::filterFiles($changedFiles, $fileTypes, $directoriesToCheck); return $targetFiles; } /** * This method loads list of changed files. * * List may be generated by: * - dev/tests/static/get_github_changes.php utility (allow to generate diffs between branches), * - CLI command "git diff --name-only > dev/tests/static/testsuite/Magento/Test/_files/changed_files_local.txt", * * If no generated changed files list found "git diff" will be used to find not committed changed * (tests should be invoked from target gir repo). * * Note: "static" modifier used for compatibility with legacy implementation of self::getWhitelist method * * @param string $changedFilesBaseDir Base dir with previously generated list files * @return string[] List of changed files */ private static function getChangedFilesList($changedFilesBaseDir) { return FilesSearch::getFilesFromListFile( $changedFilesBaseDir ?: self::getChangedFilesBaseDir(), 'changed_files*', function () { // if no list files, probably, this is the dev environment // phpcs:disable Generic.PHP.NoSilencedErrors,Magento2.Security.InsecureFunction @exec('git diff --name-only', $changedFiles); @exec('git diff --cached --name-only', $addedFiles); // phpcs:enable $changedFiles = array_unique(array_merge($changedFiles, $addedFiles)); return $changedFiles; } ); } /** * Filter list of files. * * File removed from list: * - if it not exists, * - if allowed types are specified and file has another type (extension), * - if allowed directories specified and file not located in one of them. * * Note: "static" modifier used for compatibility with legacy implementation of self::getWhitelist method * * @param string[] $files List of file paths to filter * @param string[] $allowedFileTypes List of allowed file extensions (pass empty array to allow all) * @param string[] $allowedDirectories List of allowed directories (pass empty array to allow all) * @return string[] Filtered file paths */ private static function filterFiles(array $files, array $allowedFileTypes, array $allowedDirectories) { if (empty($allowedFileTypes)) { $fileHasAllowedType = function () { return true; }; } else { $fileHasAllowedType = function ($file) use ($allowedFileTypes) { return in_array(pathinfo($file, PATHINFO_EXTENSION), $allowedFileTypes); }; } if (empty($allowedDirectories)) { $fileIsInAllowedDirectory = function () { return true; }; } else { $allowedDirectories = array_map('realpath', $allowedDirectories); usort( $allowedDirectories, function ($dir1, $dir2) { return strlen($dir1) - strlen($dir2); } ); $fileIsInAllowedDirectory = function ($file) use ($allowedDirectories) { foreach ($allowedDirectories as $directory) { if (strpos($file, $directory) === 0) { return true; } } return false; }; } $filtered = array_filter( $files, function ($file) use ($fileHasAllowedType, $fileIsInAllowedDirectory) { $file = realpath($file); if (false === $file) { return false; } return $fileHasAllowedType($file) && $fileIsInAllowedDirectory($file); } ); return $filtered; } /** * Retrieves full list of codebase paths without any files/folders filtered out * * @return array */ private function getFullWhitelist() { try { return Files::init()->readLists(__DIR__ . '/_files/whitelist/common.txt'); } catch (\Exception $e) { // nothing is whitelisted return []; } } /** * Returns whether a full scan was requested. * * This can be set in the `phpunit.xml` used to run these test cases, by setting the constant * `TESTCODESTYLE_IS_FULL_SCAN` to `1`, e.g.: * ```xml * <php> * <!-- TESTCODESTYLE_IS_FULL_SCAN - specify if full scan should be performed for test code style test --> * <const name="TESTCODESTYLE_IS_FULL_SCAN" value="0"/> * </php> * ``` * * @return bool */ private function isFullScan(): bool { return defined('TESTCODESTYLE_IS_FULL_SCAN') && TESTCODESTYLE_IS_FULL_SCAN === '1'; } /** * Test code quality using phpcs */ public function testCodeStyle() { $reportFile = self::$reportDir . '/phpcs_report.txt'; if (!file_exists($reportFile)) { touch($reportFile); } $codeSniffer = new CodeSniffer('Magento', $reportFile, new Wrapper()); $fileList = $this->isFullScan() ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml']); $ignoreList = Files::init()->readLists(__DIR__ . '/_files/phpcs/ignorelist/*.txt'); if ($ignoreList) { $ignoreListPattern = sprintf('#(%s)#i', implode('|', $ignoreList)); $fileList = array_filter( $fileList, function ($path) use ($ignoreListPattern) { return !preg_match($ignoreListPattern, $path); } ); } $result = $codeSniffer->run($fileList); $report = file_get_contents($reportFile); $this->assertEquals( 0, $result, "PHP Code Sniffer detected {$result} violation(s): " . PHP_EOL . $report ); } /** * Test code quality using phpmd */ public function testCodeMess() { $reportFile = self::$reportDir . '/phpmd_report.txt'; $codeMessDetector = new CodeMessDetector(realpath(__DIR__ . '/_files/phpmd/ruleset.xml'), $reportFile); if (!$codeMessDetector->canRun()) { $this->markTestSkipped('PHP Mess Detector is not available.'); } $fileList = self::getWhitelist(['php']); $ignoreList = Files::init()->readLists(__DIR__ . '/_files/phpmd/ignorelist/*.txt'); if ($ignoreList) { $ignoreListPattern = sprintf('#(%s)#i', implode('|', $ignoreList)); $fileList = array_filter( $fileList, function ($path) use ($ignoreListPattern) { return !preg_match($ignoreListPattern, $path); } ); } $result = $codeMessDetector->run($fileList); $output = ""; if (file_exists($reportFile)) { $output = file_get_contents($reportFile); } $this->assertEquals( Command::EXIT_SUCCESS, $result, "PHP Code Mess has found error(s):" . PHP_EOL . $output ); // delete empty reports if (file_exists($reportFile)) { unlink($reportFile); } } /** * Test code quality using phpcpd */ public function testCopyPaste() { $reportFile = self::$reportDir . '/phpcpd_report.xml'; $copyPasteDetector = new CopyPasteDetector($reportFile); if (!$copyPasteDetector->canRun()) { $this->markTestSkipped('PHP Copy/Paste Detector is not available.'); } $blackList = []; foreach (glob(__DIR__ . '/_files/phpcpd/blacklist/*.txt') as $list) { $blackList[] = file($list, FILE_IGNORE_NEW_LINES); } $blackList = array_merge([], ...$blackList); $copyPasteDetector->setBlackList($blackList); $result = $copyPasteDetector->run([BP]); $output = file_exists($reportFile) ? file_get_contents($reportFile) : ''; $this->assertTrue( $result, "PHP Copy/Paste Detector has found error(s):" . PHP_EOL . $output ); } /** * Tests whitelisted files for strict type declarations. */ public function testStrictTypes() { $changedFiles = AddedFiles::getAddedFilesList(self::getChangedFilesBaseDir()); try { $blackList = Files::init()->readLists( self::getBaseFilesFolder() . '/_files/blacklist/strict_type.txt' ); } catch (\Exception $e) { // nothing matched black list $blackList = []; } $toBeTestedFiles = array_diff( self::filterFiles($changedFiles, ['php'], []), $blackList ); $filesMissingStrictTyping = []; foreach ($toBeTestedFiles as $fileName) { $file = file_get_contents($fileName); if (strstr($file, 'strict_types=1') === false) { $filesMissingStrictTyping[] = $fileName; } } $this->assertCount( 0, $filesMissingStrictTyping, "Following files are missing strict type declaration:" . PHP_EOL . implode(PHP_EOL, $filesMissingStrictTyping) ); } /** * Test code quality using PHPStan * * @throws \Exception */ public function testPhpStan() { $reportFile = self::$reportDir . '/phpstan_report.txt'; $confFile = __DIR__ . '/_files/phpstan/phpstan.neon'; if (!file_exists($reportFile)) { touch($reportFile); } $fileList = self::getWhitelist(['php']); $blackList = Files::init()->readLists(__DIR__ . '/_files/phpstan/blacklist/*.txt'); if ($blackList) { $blackListPattern = sprintf('#(%s)#i', implode('|', $blackList)); $fileList = array_filter( $fileList, function ($path) use ($blackListPattern) { return !preg_match($blackListPattern, $path); } ); } $phpStan = new PhpStan($confFile, $reportFile); $exitCode = $phpStan->run($fileList); $report = file_get_contents($reportFile); $errorMessage = empty($report) ? 'PHPStan command run failed.' : 'PHPStan detected violation(s):' . PHP_EOL . $report; $this->assertEquals(0, $exitCode, $errorMessage); } /** * Tests whitelisted fixtures for reuse other fixtures. */ public function testFixtureReuse() { $changedFiles = self::getWhitelist(['php']); $toBeTestedFiles = self::filterFiles($changedFiles, ['php'], []); $filesWithIncorrectReuse = []; foreach ($toBeTestedFiles as $fileName) { //check only _files and Fixtures directory if (!preg_match('/integration.+\/(_files|Fixtures)/', $fileName)) { continue; } $file = str_replace(["\n", "\r"], '', file_get_contents($fileName)); if (preg_match('/(?<![\=\s*])\b(require|require_once|include)\b/', $file)) { $filesWithIncorrectReuse[] = $fileName; } } $this->assertEquals( 0, count($filesWithIncorrectReuse), "The following files incorrectly reuse fixtures:" . PHP_EOL . implode(PHP_EOL, $filesWithIncorrectReuse) . PHP_EOL . 'Please use Magento\TestFramework\Workaround\Override\Fixture\Resolver::requireDataFixture' ); } }