Skip to content

Instantly share code, notes, and snippets.

@sun
Created July 2, 2014 12:59
Show Gist options
  • Save sun/121c93db423d2c7b0911 to your computer and use it in GitHub Desktop.
Save sun/121c93db423d2c7b0911 to your computer and use it in GitHub Desktop.
Rewrite D8 tests to convert getInfo() into PHPDoc
<?php
use Drupal\Core\DrupalKernel;
use Drupal\simpletest\TestDiscovery;
use Sun\StaticReflection\ReflectionClass;
use Sun\StaticReflection\ReflectionDocComment;
use Symfony\Component\HttpFoundation\Request;
$autoloader = require __DIR__ . '/../vendor/autoload.php';
if (!isset($autoloader->getPrefixesPsr4()['Sun\\StaticReflection\\'])) {
if (file_exists($dir = dirname(__DIR__) . '/vendor/sun/staticreflection/src')) {
$autoloader->addPsr4('Sun\\StaticReflection\\', $dir);
}
else {
$autoloader->addPsr4('Sun\\StaticReflection\\', 'h:/git/staticreflection/src');
}
}
$request = Request::createFromGlobals();
$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
$kernel->boot();
restore_error_handler();
$container = \Drupal::getContainer();
$tests = $container->get('test_discovery')->findAllClassFiles();
#$tests = array_slice($tests, 0, 50, TRUE);
#$tests = array_slice($tests, 4, 1, TRUE);
#$tests = preg_grep('@/BlockConfigSchemaTest.php$|/ConfigExportImportUITest.php$|/ActionLocalTasksTest.php$|/XssTest.php$@', $tests);
$count = 0;
foreach ($tests as $classname => $pathname) {
if ($classname === 'Drupal\Tests\UnitTestCase' || $classname === 'Drupal\simpletest\TestBase') {
continue;
}
$rc = new \ReflectionClass($classname);
$is_phpunit = $rc->isSubclassOf('PHPUnit_Framework_TestCase');
$is_simple = $rc->isSubclassOf('Drupal\simpletest\TestBase');
if (!$is_phpunit && !$is_simple) {
continue;
}
if (!$rc->hasMethod('getInfo')) {
continue;
}
$rm = $rc->getMethod('getInfo');
if ($rm->class !== $classname) {
if ($rm->class !== 'Drupal\Tests\UnitTestCase' && $rm->class !== 'Drupal\simpletest\TestBase') {
throw new \LogicException(sprintf('Base class %s in %s implements getInfo().', $classname, $pathname));
}
continue;
}
if ($rc->isAbstract()) {
throw new \LogicException(sprintf('Abstract class %s in %s implements getInfo().', $classname, $pathname));
}
if (++$count % 10 != 0) {
// continue;
}
echo $classname, "\n";
$file = file_get_contents($pathname);
// Retrieve legacy getInfo().
$info = $classname::getInfo();
// Remove getInfo() method from file content (including possible doc comment
// and newlines to next method).
#$file = preg_replace('@(?: +/\*\*.+?)?^[^\n]+function getInfo[^}]+\}\s+^@ms', '', $file);
$docblock_length = $rm->getDocComment() ? count(explode("\n", $rm->getDocComment())) : 0;
$lines = file($pathname);
$method = array_slice($lines,
$rm->getStartLine() - 1 - $docblock_length,
$rm->getEndLine() + 1 - $rm->getStartLine() + $docblock_length
);
$method = implode('', $method);
$file = preg_replace('`' . preg_quote($method, '`') . '\n*`', '', $file);
// Retrieve current class doc comment block.
$docblock = $rc->getDocComment();
if (!$docblock) {
throw new \LogicException(sprintf('Missing class PHPDoc comment on %s in %s.', $classname, $pathname));
}
// Retrieve PHPDoc summary and (optional) description + annotations.
$clean = ReflectionDocComment::getPlainDocComment($docblock);
preg_match('/(?P<summary>.+?)(?=\z|\n\n|^@)\n*(?P<description>.*?)(?=\z|^@)(?P<annotations>.*)?/ms', $clean, $matches);
// echo var_dump($matches), "\n";
// continue;
// Normalize summaries.
if (!empty($info['description'])) {
$info['description'] = format_summary($info['description']);
}
if (!empty($matches['summary'])) {
$matches['summary'] = format_summary(trim($matches['summary'], "\n"));
}
// PHPDoc summary is often much more accurate.
if (!empty($info['description']) && !empty($matches['summary'])) {
similar_text($matches['summary'], $info['description'], $similarity);
if ($similarity > 80) {
$info['description'] = $matches['summary'];
}
}
$classparts = explode('\\', $classname);
$annotations = ReflectionDocComment::parseAnnotations($clean);
// Rebuild new doc comment block from scratch for PHPUnit tests.
if ($is_phpunit) {
if (isset($annotations['coversDefaultClass'])) {
$new = '@coversDefaultClass ' . $annotations['coversDefaultClass'][0] . "\n";
}
// Fix bogus @covers tags.
elseif (isset($annotations['covers'])) {
$new = '@coversDefaultClass ' . $annotations['covers'][0] . "\n";
}
// Fix bogus @coversClass tags.
elseif (isset($annotations['coversClass'])) {
$new = '@coversDefaultClass ' . $annotations['coversClass'][0] . "\n";
}
// Fix bogus @see tags:
// 1. wrongly used instead of @coversDefaultClass (must contain backslash)
// 2. totally bogus instead of @group
elseif (isset($annotations['see']) && $sees = preg_grep('@(?:\w+\\\\)+@', $annotations['see'])) {
$new = '@coversDefaultClass ' . array_shift($sees) . "\n";
}
elseif (!empty($info['description'])) {
$new = $info['description'] . "\n\n";
}
else {
$new = $matches['summary'] . "\n\n";
}
if (strpos($classname, 'Drupal\\Tests\\') === 0) {
// e.g. Drupal\Tests\Component\{group}\...
$new .= '@group ' . $classparts[3] . "\n";
}
else {
// e.g. Drupal\{group}\Tests\...
$new .= '@group ' . $classparts[1] . "\n";
}
if (!empty($matches['description'])) {
$new .= "\n" . trim($matches['description'], "\n") . "\n";
}
}
// Build new doc comment block, using legacy description, group, and dependencies.
else {
if (!empty($info['description'])) {
$new = $info['description'] . "\n\n";
}
else {
$new = $matches['summary'] . "\n\n";
}
if (!empty($matches['description'])) {
$new .= trim($matches['description'], "\n") . "\n\n";
}
// Use originating module as @group instead of (partially bogus) getInfo()
// 'group' definitions.
// $new .= '@group ' . $info['group'] . "\n";
if ($classparts[1] == 'system' && $classparts[3] != 'System') {
// e.g. Drupal\system\Tests\{group}\...
$new .= '@group ' . $classparts[3] . "\n";
}
else {
// e.g. Drupal\{group}\Tests\...
$new .= '@group ' . $classparts[1] . "\n";
}
if (isset($info['dependencies'])) {
foreach ($info['dependencies'] as $module) {
$new .= "@requires module $module\n";
}
}
if (isset($matches['annotations'])) {
$new .= $matches['annotations'];
}
}
// echo var_dump($new), "\n";
// continue;
// Reformat as doc comment block.
$new = explode("\n", $new);
foreach ($new as &$line) {
$line = ' *' . ($line !== '' ? ' ' : '') . $line;
}
$new = "/**\n" . implode("\n", $new) . "/";
// Replace the doc comment block.
$file = strtr($file, array($docblock => $new));
file_put_contents($pathname, $file);
}
function format_summary($string) {
$string = preg_replace(['@^Test @', '@^Checks? @', '@([^.])$@'], ['Tests ', 'Tests ', '$1.'], $string);
return wordwrap($string, 80 - 3);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment