You are here

class ModuleBuilderModuleGenerationTestCase in Module Builder 8.3

Test case for Module Builder module generation.

This doesn't need a Drupal database, but relies on a dedicated Module Builder environment class, \ModuleBuilder\Environment\TestsSampleLocation, and a prepared file of processed hook definitions, in the sample_hook_definitions subfolder. This contains only the hook definition from system.api.php and node.api.php.

Hierarchy

Expanded class hierarchy of ModuleBuilderModuleGenerationTestCase

File

tests/module_builder.test, line 17
Contains tests for the Module builder module.

View source
class ModuleBuilderModuleGenerationTestCase extends DrupalUnitTestCase {

  /**
   * Implements getInfo().
   */
  public static function getInfo() {
    return array(
      'name' => t('Module generation'),
      'description' => t('Unit test for generating a module.'),
      'group' => t('Module Builder'),
    );
  }

  /**
   * Implements setUp().
   */
  function setUp() {
    parent::setUp();

    // Perform the work of module_builder_init().
    include_once dirname(__FILE__) . '/../Factory.php';

    // We rely on our own autoloader, but Drupal's core autoloaders are still
    // registered, and they will run first and crash -- so we remove them.
    // This is a workaround for https://www.drupal.org/node/1916328
    spl_autoload_unregister('drupal_autoload_class');
    spl_autoload_unregister('drupal_autoload_interface');
  }

  /**
   * Test generating module code.
   */
  function testModuleGeneration() {
    \DrupalCodeBuilder\Factory::setEnvironmentClass('TestsSampleLocation');

    // Get the hooks directory. WHY?
    $hooks_directory = \DrupalCodeBuilder\Factory::getEnvironment()
      ->getHooksDirectory();
    $mb_task_handler_report = \DrupalCodeBuilder\Factory::getTask('ReportHookData');
    $this
      ->assertTrue(is_object($mb_task_handler_report), "A task handler object was returned.");
    $hook_groups = $mb_task_handler_report
      ->listHookData();
    $this
      ->assertTrue(is_array($hook_groups) || !empty($hook_groups), "An non-empty array of hook data was returned.");

    // READY TO GO! MAKE SOME MODULES!
    $mb_task_handler_generate = \DrupalCodeBuilder\Factory::getTask('Generate', 'module');
    $this
      ->assertTrue(is_object($mb_task_handler_generate), "A task handler object was returned.");

    // Assemble module data.
    $module_name = $this
      ->randomName();
    $module_data = array(
      'base' => 'module',
      'root_name' => $module_name,
      'readable_name' => $this
        ->randomString(),
      'short_description' => $this
        ->randomString(),
      'hooks' => array(
        // These two hooks will go in the .module file.
        'hook_menu',
        'hook_node_info',
        // This goes in a tokens.inc file, and also has complex parameters.
        'hook_tokens',
        // This goes in the .install file.
        'hook_install',
      ),
      'requested_build' => array(
        'all' => TRUE,
      ),
    );
    $files = $this
      ->generateModuleFiles($module_data);
    $this
      ->assertTrue(isset($files["{$module_name}.module"]), "The files list has a .module file.");
    $this
      ->assertTrue(isset($files["{$module_name}.install"]), "The files list has a .install file.");
    $this
      ->assertTrue(isset($files["{$module_name}.info"]), "The files list has a .info file.");

    // Check the .module file.
    $module_file = $files["{$module_name}.module"];

    //debug($module_file);
    $this
      ->assertWellFormedPHP($module_file, "Module file parses as well-formed PHP.");
    $this
      ->assertFileHeader($module_file, "The module file contains the correct PHP open tag and file doc header");
    $this
      ->assertHookDocblock($module_file, 'hook_menu', "The module file contains the docblock for hook_menu().");
    $this
      ->assertHookImplementation($module_file, 'hook_menu', $module_name, "The module file contains a function declaration that implements hook_menu().");
    $this
      ->assertHookDocblock($module_file, 'hook_node_info', "The module file contains the docblock for hook_node_info().");
    $this
      ->assertHookImplementation($module_file, 'hook_node_info', $module_name, "The module file contains a function declaration that implements hook_node_info().");
    $this
      ->assertNoHookDocblock($module_file, 'hook_install', "The module file does not containt the docblock for hook_install().");

    // Check the .install file.
    $install_file = $files["{$module_name}.install"];
    $this
      ->assertWellFormedPHP($install_file, "Install file parses as well-formed PHP.");
    $this
      ->assertFileHeader($install_file, "The install file contains the correct PHP open tag and file doc header");
    $this
      ->assertHookDocblock($install_file, 'hook_install', "The install file contains the docblock for hook_install().");
    $this
      ->assertHookImplementation($install_file, 'hook_install', $module_name, "The instal file contains a function declaration that implements hook_install().");
    $this
      ->assertNoHookDocblock($install_file, 'hook_menu', "The install file does not contain the docblock for hook_menu().");
    $this
      ->assertNoHookDocblock($install_file, 'hook_node_info', "The install file does not contain the docblock for hook_node_info().");

    // Check the .tokens.inc file.
    $tokens_file = $files["{$module_name}.tokens.inc"];
    $this
      ->assertHookDocblock($tokens_file, 'hook_tokens', "The tokens file contains the docblock for hook_tokens().");
    $this
      ->assertHookImplementation($tokens_file, 'hook_tokens', $module_name, "The tokens file contains a function declaration that implements hook_tokens().");

    // Check the .info file.
    $info_file = $files["{$module_name}.info"];
    $this
      ->assertInfoLine($info_file, 'name', $module_data['readable_name'], "The info file declares the module name.");
    $this
      ->assertInfoLine($info_file, 'description', $module_data['short_description'], "The info file declares the module description.");
    $this
      ->assertInfoLine($info_file, 'core', "7.x", "The info file declares the core version.");

    // Create a module, specifying limited build.
    // It is crucial to create a new module name, as we eval() the generated
    // code!
    $module_name = $this
      ->randomName();
    $module_data = array(
      'base' => 'module',
      'root_name' => $module_name,
      'readable_name' => $this
        ->randomString(),
      'short_description' => $this
        ->randomString(),
      'hooks' => array(
        // These two hooks will go in the .module file.
        'hook_menu',
        'hook_node_info',
        // This goes in a tokens.inc file, and also has complex parameters.
        'hook_tokens',
        // This goes in the .install file.
        'hook_install',
      ),
      'requested_build' => array(
        'install' => TRUE,
      ),
    );
    $files = $this
      ->generateModuleFiles($module_data);
    $this
      ->assertEqual(count($files), 1, "Only one file is returned.");

    // Check the .install file.
    $install_file = $files["{$module_name}.install"];
    $this
      ->assertWellFormedPHP($install_file, "Install file parses as well-formed PHP.");
    $this
      ->assertFileHeader($install_file, "The install file contains the correct PHP open tag and file doc header");
    $this
      ->assertHookDocblock($install_file, 'hook_install', "The install file contains the docblock for hook_install().");
    $this
      ->assertHookImplementation($install_file, 'hook_install', $module_name, "The instal file contains a function declaration that implements hook_install().");
  }

  /**
   * Generate module files from a data array.
   *
   * @param $module_data
   *  An array of module data for the module generator.
   *
   * @param
   *  An array of files.
   */
  function generateModuleFiles($module_data) {
    \DrupalCodeBuilder\Factory::setEnvironmentClass('TestsSampleLocation');
    $mb_task_handler_generate = \DrupalCodeBuilder\Factory::getTask('Generate', 'module');
    $root_generator = $mb_task_handler_generate
      ->getRootGenerator();
    $component_data_info = $root_generator
      ->getComponentDataInfo();

    // Perform final processing on the component data.
    // This prepares data, for example expands options such as hook presets.
    $mb_task_handler_generate
      ->getRootGenerator()
      ->processComponentData($component_data_info, $module_data);
    $files = $mb_task_handler_generate
      ->generateComponent($module_data);
    return $files;
  }

  /**
   * Assert a string is correctly-formed PHP.
   *
   * @param $string
   *  The text of PHP to check. This is expected to begin with a '<?php' tag.
   * @param $message = NULL
   *  The assertion message.
   */
  function assertWellFormedPHP($code, $message = NULL) {
    if (!isset($message)) {
      $message = "String evaluates as correct PHP.";
    }

    // Code passed to eval() should not have an opening PHP tag, which our
    // module code does. However, we can close a PHP tag at the start and then
    // our opening tag is acceptable.
    // It should be safe to eval as all of the functions will be prefixed with
    // the module name (unless something has gone horribly wrong, in which case
    // case the test should fail anyway.)
    $eval = eval('?>' . $code);

    // eval() returns FALSE if there is a parse error.
    $this
      ->assertIdentical($eval, NULL, $message);
  }

  /**
   * Assert a string contains the correct file header.
   *
   * @param $string
   *  The text to check for a docblock.
   * @param $message = NULL
   *  The assertion message.
   */
  function assertFileHeader($string, $message = NULL) {
    $expected_string = "^<\\?php\n" . "\n" . "/\\*\\*\n" . " \\* @file.*\n" . " \\* .*\n" . " \\*/\n";
    $match = preg_match("[{$expected_string}]", $string);
    $this
      ->assertTrue($match, $message);
  }

  /**
   * Assert a string contains a PHP docblock for a hook.
   *
   * @param $string
   *  The text to check for a docblock.
   * @param $hook_name
   *  The full name of the hook, e.g. 'hook_menu'.
   * @param $message = NULL
   *  The assertion message.
   */
  function assertHookDocblock($string, $hook_name, $message = NULL) {
    $docblock = "/**\n" . " * Implements {$hook_name}().\n" . " */";
    $position = strstr($string, $docblock);
    $this
      ->assertTrue($position !== FALSE, $message);
  }

  /**
   * Assert a string does not contain a PHP docblock for a hook.
   *
   * @param $string
   *  The text to check for a docblock.
   * @param $hook_name
   *  The full name of the hook, e.g. 'hook_menu'.
   * @param $message = NULL
   *  The assertion message.
   */
  function assertNoHookDocblock($string, $hook_name, $message = NULL) {
    $docblock = "/**\n" . " * Implements {$hook_name}().\n" . " */";
    $position = strstr($string, $docblock);
    $this
      ->assertTrue($position === FALSE, $message);
  }

  /**
   * Assert a string contains a function declaration.
   *
   * @param $string
   *  The text to check for a function declaration.
   * @param $function_name
   *  The name of the function.
   * @param $message = NULL
   *  The assertion message.
   */
  function assertFunction($string, $function_name, $message = NULL) {
    $expected_regex = "^function {$function_name}\\((.*)\\)";
    $matches = array();
    $match = preg_match("[{$expected_regex}]m", $string, $matches);
    if (empty($message)) {
      $message = "The code string contains the function {$function_name}().";
    }
    $this
      ->assertTrue($match, $message);

    // Check the function parameters are properly formed.
    if (!empty($matches[1])) {
      $parameters = explode(', ', $matches[1]);
      $param_regex = '@
        ^
        ( \\w+ \\  ) ?  # type hint
        \\$ \\w+        # parameter name
        ( \\  = \\      # default value, one of:
          (
            \\d+ |             # numerical
            [[:upper:]]+ |    # constant
            \' .* \' |        # string
            array\\(\\)         # empty array
          )
        ) ?
        @x';
      foreach ($parameters as $parameter) {
        $match = preg_match($param_regex, $parameter);
        $this
          ->assertTrue($match, "Function parameter is correctly formed.");
      }
    }
  }

  /**
   * Assert a string contains a hook implementation function declaration.
   *
   * @param $code
   *  The code to check for a function declaration.
   * @param $hook_name
   *  The full name of the hook, e.g. 'hook_menu'.
   * @param $module_name
   *  The name of the implementing module.
   * @param $message = NULL
   *  The assertion message.
   */
  function assertHookImplementation($code, $hook_name, $module_name, $message = NULL) {
    $hook_short_name = substr($hook_name, 5);
    $function_name = $module_name . '_' . $hook_short_name;
    $this
      ->assertFunction($code, $function_name, $message);
  }

  /**
   * Assert a string contains a .info file property declaration.
   *
   * @param $string
   *  The text to check.
   * @param $property
   *  The property name, e.g. 'core'.
   * @param $value
   *  The value to check, e.g., '7.x'.
   * @param $message = NULL
   *  The assertion message.
   */
  function assertInfoLine($string, $property, $value, $message = NULL) {

    // Quote the value, as strings may contain regex characters.
    $value = preg_quote($value);
    $expected_regex = "^{$property} = {$value}\$";
    $match = preg_match("[{$expected_regex}]m", $string);
    $this
      ->assertTrue($match, $message);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ModuleBuilderModuleGenerationTestCase::assertFileHeader function Assert a string contains the correct file header.
ModuleBuilderModuleGenerationTestCase::assertFunction function Assert a string contains a function declaration.
ModuleBuilderModuleGenerationTestCase::assertHookDocblock function Assert a string contains a PHP docblock for a hook.
ModuleBuilderModuleGenerationTestCase::assertHookImplementation function Assert a string contains a hook implementation function declaration.
ModuleBuilderModuleGenerationTestCase::assertInfoLine function Assert a string contains a .info file property declaration.
ModuleBuilderModuleGenerationTestCase::assertNoHookDocblock function Assert a string does not contain a PHP docblock for a hook.
ModuleBuilderModuleGenerationTestCase::assertWellFormedPHP function Assert a string is correctly-formed PHP.
ModuleBuilderModuleGenerationTestCase::generateModuleFiles function Generate module files from a data array.
ModuleBuilderModuleGenerationTestCase::getInfo public static function Implements getInfo().
ModuleBuilderModuleGenerationTestCase::setUp function Implements setUp().
ModuleBuilderModuleGenerationTestCase::testModuleGeneration function Test generating module code.