#!/usr/bin/env php -o -b DESCRIPTION Recursively find PHP files and run phpunit-skelgen to create unit test skeleton files. Files must use the .php extension and be PSR-0 compliant, contain a namespace and class name. Files ending in 'Interface.php' are ignored. OPTIONS -s , --source Source directory to scan for PHP files -t , --target Target for the generated UnitTest files. Default is current-working-dir/tests. -b , --bootstrap Specify a bootstrap file to be used by phpunit-skelgen for loading the classes. -v, --verbose Switch on verbose output -h, --help Prints this help HELP; $options = getopt('s:t::hb:v', array( 'source:', 'target::', 'bootstrap:', 'help', 'verbose', )); if (empty($options) || !is_null(optval($options, 'h', 'help'))) { echo "$help\n"; return; } // Extract the inputs $source = optval($options, 's', 'source'); $target = optval($options, 't', 'target'); $bootstrap = optval($options, 'b', 'bootstrap'); $verbose = !is_null(optval($options, 'v', 'verbose')); // Validate inputs if (empty($source) || !is_dir($source)) { echo "Source given '$source' is not a directory\n"; } if (empty($target)) { $target = './tests'; } if (!is_dir($target)) { mkdir($target, 0777, true); } $sourceDir = realpath($source); $targetDir = realpath($target); $binary = 'phpunit-skelgen.phar'; $skelgenBinary = trim(shell_exec("which $binary")); if (!file_exists($skelgenBinary)) { echo "Unable to locate $binary\n"; } // Scan and process the source directory $dirIterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($sourceDir) ); $sourcePathStrLength = strlen($sourceDir); $escapedBootstrap = $bootstrap ? '--bootstrap '.escapeshellarg("$bootstrap") : ''; foreach ($dirIterator as $filePath => $fileInfo) { // @var SplFileInfo $fileInfo if ( $fileInfo->isDir() || substr($filePath, -13) === 'Interface.php' ) { continue; } $targetFilePath = $targetDir . preg_replace( '/\.php$/i', 'Test.php', substr($filePath, $sourcePathStrLength) ); if (file_exists($targetFilePath)) { echo "Skip: Test file for '$filePath' already exists\n"; continue; } $fullClassName = extractFullClassNameFromFile($filePath); if ($fullClassName === false) { echo "Err: Class name could not be extracted from '$filePath'\n"; continue; } $targetFileName = basename($targetFilePath); $sourceDirPath = dirname($filePath); $targetDirPath = dirname($targetFilePath); $generatedFilePath = "$sourceDirPath/$targetFileName"; if (!is_dir($targetDirPath)) { mkdir($targetDirPath, 0777, true); } $escapedClassName = escapeshellarg($fullClassName); $escapedSourcePath = escapeshellarg($filePath); $result = shell_exec( "$skelgenBinary $escapedBootstrap --test -- $escapedClassName $escapedSourcePath" ); if ($verbose) { echo $result; } if (!file_exists($generatedFilePath)) { echo "Error: Failed to generate test file for '$fullClassName'\n"; continue; } rename($generatedFilePath, $targetFilePath); echo "OK: Test file successfully created for '$fullClassName'\n"; } echo "DONE.\n"; /* ---- FUNCTIONS ---- */ /** * Extract an option from the results of getopt specifying the * keys in order of preference. * * @param array $options Result from getopt('ab:c::', array('add', 'bacon', 'create')) * @param string $key1 First key to look for e.g. 'add' * @param string $key2 Second key to look for e.g. 'a' * @param string $keyn Other possible aliases... * @return mixed The value from options array or null otherwise */ function optval(array &$options, $key1, $key2, $keyn = null) { $args = func_get_args(); $options = array_shift($args); foreach ($args as $key) { if (isset($options[$key])) { return $options[$key]; } } return null; } /** * Conert to boolean by using normal PHP falsey equivalencies with * the addition of string 'false' which PHP usually considers truthy. * * @param mixed $val Value to convert to boolean * @return boolean Boolean equivalent */ function bool($val) { if ( $val === '' || $val === 'false' || $val === '0' || $val === 0 || $val === null || $val === array() || $val === 0.0 || $val === false ) { return false; } return true; } /** * Extract a fully qualfied (namespaced) classname from a php file. * This function assumes PSR-0 compliance. * * @param string $filePath Path to the php file. * @return string|false The resulting classname */ function extractFullClassNameFromFile($filePath) { if (!file_exists($filePath)) { return false; } $namespace = null; $classname = null; $cnMatches = null; $nsMatches = null; $file = file_get_contents($filePath); if (preg_match_all('/\n\s*(abstract\s|final\s)*class\s+(?[^\s;]+)\s*/i', $file, $cnMatches, PREG_PATTERN_ORDER)) { $classname = array_pop($cnMatches['name']); if (preg_match_all('/namespace\s+(?[^\s;]+)\s*;/i', $file, $nsMatches, PREG_PATTERN_ORDER)) { $namespace = array_pop($nsMatches['name']); } } if (empty($classname)) { return false; } return "$namespace\\$classname"; }