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 self::getFilesFromListFile( $changedFilesBaseDir, 'changed_files*', function () { // if no list files, probably, this is the dev environment @exec('git diff --name-only', $changedFiles); @exec('git diff --cached --name-only', $addedFiles); $changedFiles = array_unique(array_merge($changedFiles, $addedFiles)); return $changedFiles; } ); } /** * This method loads list of added files. * * @param string $changedFilesBaseDir * @return string[] */ private static function getAddedFilesList($changedFilesBaseDir) { return self::getFilesFromListFile( $changedFilesBaseDir, 'changed_files*.added.*', function () { // if no list files, probably, this is the dev environment @exec('git diff --cached --name-only', $addedFiles); return $addedFiles; } ); } /** * Read files from generated lists. * * @param string $listsBaseDir * @param string $listFilePattern * @param callable $noListCallback * @return string[] */ private static function getFilesFromListFile($listsBaseDir, $listFilePattern, $noListCallback) { $filesDefinedInList = []; $globFilesListPattern = ($listsBaseDir ?: self::getChangedFilesBaseDir()) . '/_files/' . $listFilePattern; $listFiles = glob($globFilesListPattern); if (count($listFiles)) { foreach ($listFiles as $listFile) { $filesDefinedInList = array_merge( $filesDefinedInList, file($listFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ); } } else { $filesDefinedInList = call_user_func($noListCallback); } array_walk( $filesDefinedInList, function (&$file) { $file = BP . '/' . $file; } ); $filesDefinedInList = array_values(array_unique($filesDefinedInList)); return $filesDefinedInList; } /** * 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 []; } } /** * Test code quality using phpcs */ public function testCodeStyle() { $isFullScan = defined('TESTCODESTYLE_IS_FULL_SCAN') && TESTCODESTYLE_IS_FULL_SCAN === '1'; $reportFile = self::$reportDir . '/phpcs_report.txt'; if (!file_exists($reportFile)) { touch($reportFile); } $codeSniffer = new CodeSniffer('Magento', $reportFile, new Wrapper()); $result = $codeSniffer->run($isFullScan ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml'])); $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.'); } $result = $codeMessDetector->run(self::getWhitelist(['php'])); $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 = array_merge($blackList, file($list, FILE_IGNORE_NEW_LINES)); } $copyPasteDetector->setBlackList($blackList); $result = $copyPasteDetector->run([BP]); $output = ""; if (file_exists($reportFile)) { $output = 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 = self::getAddedFilesList(''); 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->assertEquals( 0, count($filesMissingStrictTyping), "Following files are missing strict type declaration:" . PHP_EOL . implode(PHP_EOL, $filesMissingStrictTyping) ); } }