You are here

Tests.php in Drupal 7 to 8/9 Module Upgrader 8

File

src/Plugin/DMU/Converter/Tests.php
View source
<?php

namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Converter;

use Drupal\drupalmoduleupgrader\ConverterBase;
use Drupal\drupalmoduleupgrader\TargetInterface;
use Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter;
use Pharborist\DocCommentNode;
use Pharborist\Filter;
use Pharborist\Objects\ClassMemberNode;
use Pharborist\Objects\ClassMethodCallNode;
use Pharborist\Objects\ClassNode;
use Pharborist\Parser;
use Pharborist\RootNode;
use Pharborist\Types\StringNode;
use Pharborist\WhitespaceNode;

/**
 * @Converter(
 *  id = "tests",
 *  description = @Translation("Modifies test classes.")
 * )
 */
class Tests extends ConverterBase {
  private $target;

  /**
   * {@inheritdoc}
   */
  public function isExecutable(TargetInterface $target) {
    foreach ([
      'DrupalTestCase',
      'DrupalWebTestCase',
    ] as $parent_class) {
      if ($target
        ->getIndexer('class')
        ->getQuery()
        ->condition('parent', $parent_class)
        ->countQuery()
        ->execute()) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function convert(TargetInterface $target) {
    $this->target = $target;
    $mapping = [
      'DrupalWebTestCase' => 'convertWeb',
      'AJAXTestCase' => 'convertAjax',
    ];
    foreach ($mapping as $parent_class => $convert_method) {
      $test_files = $target
        ->getIndexer('class')
        ->getQuery([
        'file',
      ])
        ->condition('parent', $parent_class)
        ->execute()
        ->fetchCol();
      foreach ($test_files as $test_file) {

        /** @var \Pharborist\Objects\Classnode[] $tests */
        $tests = $target
          ->open($test_file)
          ->find(Filter::isInstanceOf('\\Pharborist\\Objects\\SingleInheritanceNode'))
          ->toArray();
        foreach ($tests as $test) {
          if ((string) $test
            ->getExtends() === $parent_class) {
            $this
              ->{$convert_method}($test);
          }
        }
      }
    }
  }

  /**
   * Converts a single web test.
   *
   * @param \Pharborist\Objects\ClassNode $test
   */
  public function convertWeb(ClassNode $test) {
    $test
      ->setExtends('\\Drupal\\simpletest\\WebTestBase');
    $this
      ->convertInfo($test);
    $this
      ->setModules($test);
    $this
      ->setProfile($test);
    $this
      ->move($test);
  }

  /**
   * Converts the test's getInfo() method to an annotation.
   *
   * @param \Pharborist\Objects\ClassNode $test
   */
  private function convertInfo(ClassNode $test) {
    $info = $this
      ->extractInfo($test);
    if ($info) {
      $comment = '';
      $comment .= $info['description'] . "\n\n";
      $comment .= '@group ' . $this->target
        ->id();
      if (isset($info['dependencies'])) {
        $comment .= "\n";
        foreach ($info['dependencies'] as $module) {
          $comment .= '@requires module . ' . $module . "\n";
        }
      }
      $test
        ->setDocComment(DocCommentNode::create($comment));
    }
    else {
      $this->log
        ->error('Could not get info for test {class}.', [
        'class' => $test
          ->getName(),
      ]);
    }
  }

  /**
   * Extracts the return value of the test's getInfo() method, if there's no
   * logic in the method.
   *
   * @param \Pharborist\Objects\ClassNode $test
   *
   * @return array|null
   */
  private function extractInfo(ClassNode $test) {
    if ($test
      ->hasMethod('getInfo')) {
      $info = $test
        ->getMethod('getInfo');
      if (!$info
        ->is(new ContainsLogicFilter())) {
        return eval($info
          ->getBody()
          ->getText());
      }
    }
  }

  /**
   * Sets the test's $modules property.
   *
   * @param \Pharborist\Objects\ClassNode $test
   */
  private function setModules(ClassNode $test) {
    $modules = $this
      ->extractModules($test);
    if ($modules) {

      // @todo Use ClassNode::createProperty() when #124 lands in Pharborist
      $property = Parser::parseSnippet('class Foo { public static $modules = ["' . implode('", "', $modules) . '"]; }')
        ->getBody()
        ->firstChild()
        ->remove();
      $test
        ->appendProperty($property);
    }
  }

  /**
   * Extracts every module required by a web test by scanning its calls
   * to parent::setUp().
   *
   * @param \Pharborist\Objects\ClassNode $test
   *
   * @return string[]
   *   Array of modules set up by this module.
   */
  private function extractModules(ClassNode $test) {
    $modules = [];
    $test
      ->find(Filter::isClassMethodCall('parent', 'setUp'))
      ->filter(function (ClassMethodCallNode $call) {
      return sizeof($call
        ->getArguments()) > 0;
    })
      ->each(function (ClassMethodCallNode $call) use (&$modules) {
      foreach ($call
        ->getArguments() as $argument) {
        if ($argument instanceof StringNode) {
          $modules[] = $argument
            ->toValue();
        }
      }
      $call
        ->clearArguments();
    });
    return array_unique($modules);
  }

  /**
   * Sets the test's $profile property.
   *
   * @param \Pharborist\Objects\ClassNode $test
   */
  private function setProfile(ClassNode $test) {
    if (!$test
      ->hasProperty('profile')) {
      $test
        ->appendProperty(ClassMemberNode::create('profile', StringNode::create("'standard'"), 'protected'));
    }
  }
  public function move(ClassNode $test) {
    $ns = 'Drupal\\' . $this->target
      ->id() . '\\Tests';
    RootNode::create($ns)
      ->getNamespace($ns)
      ->append($test
      ->remove());
    WhitespaceNode::create("\n\n")
      ->insertBefore($test);
    $this
      ->writeClass($this->target, $test);
  }

  /**
   * Converts a single Ajax test.
   *
   * @param \Pharborist\Objects\ClassNode $test
   */
  public function convertAjax(ClassNode $test) {
    $test
      ->setExtends('\\Drupal\\system\\Tests\\Ajax\\AjaxTestBase');
    $this
      ->setModules($test);
    $this
      ->move($test);
  }

}

Classes

Namesort descending Description
Tests Plugin annotation @Converter( id = "tests", description = @Translation("Modifies test classes.") )