Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save hassankhan/6196491 to your computer and use it in GitHub Desktop.
Save hassankhan/6196491 to your computer and use it in GitHub Desktop.

Revisions

  1. @mertenvg mertenvg created this gist Dec 14, 2012.
    226 changes: 226 additions & 0 deletions project-phpunit-skelgen.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,226 @@
    #!/usr/bin/env php
    <?php

    $help = <<<'HELP'
    EXAMPLES
    ./project-phpunit-skelgen.php -s <dir> -o <dir> -b <file>
    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 <dir>, --source <dir>
    Source directory to scan for PHP files
    -t <dir>, --target <dir>
    Target for the generated UnitTest files. Default is current-working-dir/tests.
    -b <file>, --bootstrap <file>
    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+(?<name>[^\s;]+)\s*/i', $file, $cnMatches, PREG_PATTERN_ORDER)) {
    $classname = array_pop($cnMatches['name']);

    if (preg_match_all('/namespace\s+(?<name>[^\s;]+)\s*;/i', $file, $nsMatches, PREG_PATTERN_ORDER)) {
    $namespace = array_pop($nsMatches['name']);
    }
    }

    if (empty($classname)) {
    return false;
    }

    return "$namespace\\$classname";
    }