parser($filename)) { file_put_contents($filename.$postfix, $result); } } public static function processFolder($dir, $postfix='') { $directory = new \RecursiveDirectoryIterator($dir); $iterator = new \RecursiveIteratorIterator($directory); $pattern = '/^.+Test\.php$/i'; $regex = new \RegexIterator($iterator, $pattern, \RecursiveRegexIterator::GET_MATCH); foreach ($regex as $name => $object) { static::processFile($name, $postfix); } } public static function processTestFolders($dir, $postfix='') { $directories = FileHelper::get_all_test_folders($dir); foreach($directories as $dir) { static::processFolder($dir, $postfix); } } } class ConverterExpectedException { const BEFORE = 'before'; // if production regim const AFTER = 'after'; // if testing optimal position by fail public $REGIM; public function __construct($regim = ConverterExpectedException::BEFORE) { $this->REGIM = $regim; } public function parser($filename) { $is_replace = FALSE; /** @var \Pharborist\RootNode $tree */ $tree = Parser::parseFile($filename); /** @var \Pharborist\DocCommentNode $node */ foreach ($tree->find(Filter::isInstanceOf('\Pharborist\DocCommentNode')) as $node) { $doc_block = $node->getDocBlock(); $class_tags = $doc_block->getTagsByName('expectedException'); if($class_tags){ /** @var \Pharborist\Objects\ClassMethodNode $method_class */ $method_class = $node->parent(Filter::isInstanceOf('Pharborist\Objects\ClassMethodNode')); $line = LineDetectors::lineDetector($method_class, $doc_block, $filename); // For testing cases if ($line === NULL) { continue; } $this->createExceptionNode($class_tags, $doc_block, $method_class, $line); $this->clearDocComment($method_class); $is_replace = TRUE; } } if($is_replace){ $result = $this->removeArtefacts($tree); return $result; } return; } private function createExceptionNode($class_tags, &$doc_block, &$method_class, &$line) { $exception = $this->getExceptionPart($class_tags, $method_class); $message = $this->getMessagePart($doc_block); $statement = '$this->setExpectedException(' . $exception . $message . ');'; $algo = AlgoLogger::getAlgoInfo(); $algo = ''; $statement .= $algo; $new_node = Parser::parseSource($statement); $indentation = PharboristHelper::getIndentation($method_class, $line); $space_node = Parser::parseSource(PHP_EOL . $indentation); $insert = $this->REGIM == static::BEFORE ? 'insertBefore' : 'insertAfter'; $new_node->{$insert}($line); $space_node->{$insert}($line); } private function getExceptionPart($class_tags, &$method_class) { $exception_class = trim($class_tags[0]->getDescription(), '\\'); $parts = explode("\\", $exception_class); $Class = end($parts); $exception = "$Class::class"; if (count($parts) > 1) { PharboristHelper::addUseDeclaration($method_class, $exception_class); } else { $exception = "\\$exception"; } return $exception; } private function getMessagePart(&$doc_block) { $message = $doc_block->getTagsByName('expectedExceptionMessage'); /* * Result examples: * 'text text text' * 'text "text" text" * "text 'text' text" * "text \"text\" cann't text"; */ if ($message) { $message = $message[0]->getDescription(); $quote = "'"; if (strpos($message, "'") !== FALSE) { $quote = '"'; $message = str_replace('"', '\"', $message); } $message = ', ' . $quote . $message . $quote; } else { $message = ''; } return $message; } private function clearDocComment(&$method_class) { /** @var \Pharborist\DocCommentNode $doc_comment */ $doc_comment = $method_class->getDocComment(); $text = $doc_comment->getText(); $text = preg_replace('/\r/', "", $text); $text = preg_replace('/\n\s*\* \@expectedException.+?\n\s+\*\n/', "\n", $text); $text = preg_replace('/\n\s*\* \@expectedException.+?\n/', "\n", $text); $text = preg_replace('/\n\s*\* \@expectedExceptionMessage.+?\n\s+\*\n/', "\n", $text); $text = preg_replace('/\n\s*\* \@expectedExceptionMessage.+?\n/', "\n", $text); $text = preg_replace('/\n\s*\*\s*\n(\s*\*\/)/', "\n$1", $text); $doc_comment->setText($text); $method_class->setDocComment($doc_comment); } private function removeArtefacts($tree) { $result = $tree->getText(); $result = preg_replace('@\r@', '', $result); $result = preg_replace('@\*/\n +(?=\n)@', '*/', $result); return $result; } } class LineDetectors { public static function lineDetector(&$method_class, $doc_block, $filename) { AlgoLogger::$INFO["ALL"]++; AlgoLogger::$INFO['active'] = ''; AlgoLogger::$INFO['last_line'] = false; /** @var \Pharborist\NodeCollection $lines_of_code */ $lines_of_code = $method_class->find( Filter::any([ Filter::isInstanceOf('\Pharborist\ExpressionStatementNode'), Filter::isInstanceOf('\Pharborist\ControlStructures\ForNode'), Filter::isInstanceOf('\Pharborist\ControlStructures\ForeachNode'), Filter::isInstanceOf('\Pharborist\ControlStructures\WhileNode'), Filter::isInstanceOf('\Pharborist\ControlStructures\DoWhileNode'), Filter::isInstanceOf('\Pharborist\ControlStructures\IfNode'), // Filter::isInstanceOf('Pharborist\StatementNode'), // Filter::isNotHidden(), ]) ); $lines = $lines_of_code->toArray(); $index_line = -1; if($index_line == -1) { $index_line = static::lineDetectorByMap($lines, $method_class); } if($index_line == -1) { if($cover = static::getCover($doc_block)) { $index_line = static::lineDetectorByCover($lines, $cover); } } if($index_line == -1) { $index_line = static::lineDetectorByAssert($lines); } if($index_line == -1) { $index_line = count($lines) -1; AlgoLogger::logINFO("LAST"); // print "not detection $filename
"; } // if($index_line == count($lines)-1) AlgoLogger::$INFO['last_line'] = true; // $index_line = 0; $line = $lines[$index_line]; $line = static::correctorLine($line); return $line; } public static function lineDetectorByMap($lines, $method_class) { /** @var \Pharborist\StatementBlockNode $method_class */ $fullname = $method_class->getFullyQualifiedName(); switch($fullname){ case '\Drupal\Tests\migrate\Unit\RowTest::testSourceFreeze': $line = 2; break; case '\Drupal\KernelTests\Component\Utility\SafeMarkupKernelTest::testSafeMarkupUriWithExceptionUri': $line = 0; break; case '\Drupal\Tests\Component\DependencyInjection\ContainerTest::testResolveServicesAndParametersForInvalidArguments': $line = 0; break; case '\Drupal\Tests\Core\Config\ConfigTest::testSetIllegalOffsetValue': case '\Drupal\Tests\Core\Entity\ContentEntityBaseUnitTest::testRequiredValidation': case '\Drupal\Tests\Component\Plugin\Discovery\DiscoveryTraitTest::testDoGetDefinitionException': $line = count($lines)-1; break; default: $line = -1; } if ($line > -1){ AlgoLogger::logINFO("MAP"); } return $line; } public static function lineDetectorByCover($lines, $cover) { // ALGO COVER (method) foreach ($lines as $number_line => $node) { /** @var \Pharborist\ExpressionStatementNode $node */ if ($node instanceof \Pharborist\ExpressionStatementNode) { $expression = $node->getExpression(); if (method_exists($expression, 'getMethodName')) { $method_name = $expression->getMethodName()->getText(); if ($method_name == $cover) { AlgoLogger::logINFO("METHOD"); return $number_line; } } } } // ALGO COVER (body) foreach ($lines as $number_line => $node) { /** @var \Pharborist\ExpressionStatementNode $node */ if ($node instanceof \Pharborist\ExpressionStatementNode) { $expression = $node->getExpression(); $body = $expression->getText(); $is_method = strpos($body, $cover . "(") !== FALSE; // like 'get(' $is_operator = strpos($body, $cover . " ") !== FALSE; // like 'new' $is_object = $body == $cover; // like ..? don't know) if ($is_method || $is_operator || $is_object) { AlgoLogger::logINFO('BODY'); return $number_line; } } } return -1; } public static function lineDetectorByAssert($lines) { foreach($lines as $number_line => $node) { /** @var \Pharborist\ExpressionStatementNode $node */ if($node instanceof \Pharborist\ExpressionStatementNode) { $expression = $node->getExpression(); if (method_exists($expression, 'getMethodName')) { $method_name = $expression->getMethodName()->getText(); if (strpos($method_name, "assert") === 0) { $number_line -= 1; $prev_node = $lines[$number_line]; if ($prev_node && $prev_node instanceof \Pharborist\ExpressionStatementNode) { $prev_expression = $prev_node->getExpression(); if (method_exists($prev_expression, 'getLeftOperand')) { $left = $prev_expression->getLeftOperand(); if ($left->getText() == '$expected') { $number_line -= 1; } } } AlgoLogger::logINFO('ASSERT'); return $number_line; } } } } return -1; } public static function correctorLine($line) { $parrent = $line->parent()->parent(); if( $parrent instanceof \Pharborist\ControlStructures\ForNode || $parrent instanceof \Pharborist\ControlStructures\ForeachNode || $parrent instanceof \Pharborist\ControlStructures\WhileNode || $parrent instanceof \Pharborist\ControlStructures\DoWhileNode) { return $parrent; } return $line; } public static function getCover($doc_block){ $cover = $doc_block->getTagsByName('covers'); if ($cover) { $cover = trim($cover[0]->getContent(), ': '); switch($cover){ case '__construct': $cover = 'new'; break; } } return $cover; } } class PharboristHelper { public static function addUseDeclaration(&$method_class, $utext) { /** @var \Pharborist\RootNode $tree */ /** @var \Pharborist\Objects\ClassMethodNode $method_class */ $use_text = 'use ' . $utext . ';'; /** @var \Pharborist\Namespaces\NamespaceNode $namespaces */ $namespaces = $method_class->parents(Filter::isInstanceOf('\Pharborist\Namespaces\NamespaceNode'))->get(0); $uses = $namespaces->find(Filter::any([Filter::isInstanceOf('\Pharborist\Namespaces\UseDeclarationNode')])); foreach ($uses->toArray() as $use) { $text = $use->getStatement()->getText(); if ($text == $use_text) { return; } } if($uses) { $index_use = static::iIndex($use_text, $uses); } /** @var \Pharborist\Namespaces\UseDeclarationBlockNode $use_block */ $use_block = Parser::parseSnippet($use_text); $nl = Parser::parseSnippet("\n", '\Pharborist\WhitespaceNode'); if(isset($index_use)){ /** @var \Pharborist\Namespaces\UseDeclarationNode $child */ if ($index_use < 0) { $child = $uses->get(0)->parent()->parent(); $use_block->insertBefore($child); $nl->insertBefore($child); } else { $child = $uses->get($index_use)->parent()->parent(); $use_block->insertAfter($child); $nl->insertAfter($child); } } else{ // todo: find nice way to add 'use declaration' if namespace haven't any declaration $nl2 = Parser::parseSnippet("\n\n", '\Pharborist\WhitespaceNode'); $namespaces->find(Filter::isNewline())->get(1)->after($nl2); $namespaces->find(Filter::isNewline())->get(1)->after($use_block); } } public static function getIndentation($method_class, $line) { //$count_indent = $method_class->getColumnNumber() + 1; $count_indent = $line->getColumnNumber() - 1; $indentation = str_repeat(" ", $count_indent); return $indentation; } private static function get_use_array($use_text){ $use_text = trim($use_text, "use "); $use_text = trim($use_text, ';'); $use_text = trim($use_text, ' '); return explode('\\', $use_text); } public static function iIndex($use_text, $uses){ $new = static::get_use_array($use_text); $new_count = count($new); $max = 0; $find_index = -1; foreach ($uses->toArray() as $index => $use) { $local = 0; $old = static::get_use_array($use->getStatement()->getText()); $old_count = count($old); for ($i = 0, $len = min($new_count, $old_count); $i < $len; $i++) { $new_word = $new[$i]; $old_word = $old[$i]; if ($new_word == $old_word) { $local++; } else { break; } } if($local >= $max) { $level = $local + 1; if(($level != $new_count && $level != $old_count) || ($new_count == $old_count) || ($level == $old_count)){ if($new_word > $old_word) { $find_index = $index; } } else{ if($level == $new_count && $local > $max){ $find_index = $index -1; } } } if ($local > $max) { $max = $local; } } $find_index = min($find_index, count($uses->toArray())-1); return $find_index; } } class FileHelper { public static function get_all_test_folders($dir) { $directories = []; static::recursive($directories, $dir); return $directories; } public static function recursive(&$directories, $dir) { $odir = opendir($dir); while (($file = readdir($odir)) !== FALSE) { $next_dir = $dir.DIRECTORY_SEPARATOR.$file; if ($file != '.' && $file != '..' && $file != '.git' && is_dir($next_dir)) { if($file == "Tests" || $file == "tests"){ $directories[] = $next_dir; } else { static::recursive($directories, $next_dir); } } } closedir($odir); } public static function create_tester_env_by_patch($patch, $source, $tester) { $text = file_get_contents($patch); preg_match_all("@diff --git a(/core/.+?/[^/]+?\\.php) @", $text, $matches); foreach($matches[1] as $file) { static::create_test_files($source.$file, $tester); } } public static function create_test_files($source, $tester){ preg_match_all("@(/core/.+?/)([^/]+?\\.php)@", $source, $info); $folders = $info[1][0]; $file = $info[2][0]; $tester_site = $tester . '/site'; static::_mkdir($tester_site); $tester_site_folders = $tester_site . $folders; static::_mkdir($tester_site_folders); $tester_site_file = $tester_site_folders . $file; copy($source, $tester_site_file); $tester_plain = $tester . '/plain/'; static::_mkdir($tester_plain); $tester_plain_file = $tester_plain . $file; copy($source, $tester_plain_file); } public static function _mkdir($path){ if (!file_exists( $path)) { if(!mkdir($path, 0777, true)) { die('mkdir problem'); } } } } class AlgoLogger { public static $INFO = []; public static function getAlgoInfo() { $algo = "UNKNOWN"; switch(static::$INFO['active']) { case "METHOD": $algo = "ALGO COVER (method)"; break; case "BODY": $algo = "ALGO COVER (body)"; break; case "ASSERT": $algo = "ALGO ASSERT"; break; case "MAP": $algo = "ALGO MANUAL MAP"; break; case "LINE": $algo = "ALGO ONE"; break; case "LAST": $algo = "ALGO LAST"; break; } $algo = " // $algo"; return $algo; } public static function logINFO($algo) { if(!isset(static::$INFO[$algo])){ static::$INFO[$algo] = 0; } static::$INFO[$algo]++; static::$INFO['active'] = $algo; } }