Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Data generators for acceptance testing.
  19   *
  20   * @package   core
  21   * @category  test
  22   * @copyright 2012 David MonllaĆ³
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
  27  
  28  require_once (__DIR__ . '/../../behat/behat_base.php');
  29  
  30  use Behat\Gherkin\Node\TableNode as TableNode;
  31  use Behat\Behat\Tester\Exception\PendingException as PendingException;
  32  
  33  /**
  34   * Class to set up quickly a Given environment.
  35   *
  36   * The entry point is the Behat steps:
  37   *     the following "entity types" exist:
  38   *       | test | data |
  39   *
  40   * Entity type will either look like "users" or "activities" for core entities, or
  41   * "mod_forum > subscription" or "core_message > message" for entities belonging
  42   * to components.
  43   *
  44   * Generally, you only need to specify properties relevant to your test,
  45   * and everything else gets set to sensible defaults.
  46   *
  47   * The actual generation of entities is done by {@link behat_generator_base}.
  48   * There is one subclass for each component, e.g. {@link behat_core_generator}
  49   * or {@link behat_mod_quiz_generator}. To see the types of entity
  50   * that can be created for each component, look at the arrays returned
  51   * by the get_creatable_entities() method in each class.
  52   *
  53   * @package   core
  54   * @category  test
  55   * @copyright 2012 David MonllaĆ³
  56   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  57   */
  58  class behat_data_generators extends behat_base {
  59  
  60      /**
  61       * Convert legacy entity names to the new component-specific form.
  62       *
  63       * In the past, there was no support for plugins, and everything that
  64       * could be created was handled by the core generator. Now, we can
  65       * support plugins, and so some thing should probably be moved.
  66       *
  67       * For example, in the future we should probably add
  68       * 'message contacts' => 'core_message > contact'] to
  69       * this array, and move generation of message contact
  70       * from core to core_message.
  71       *
  72       * @var array old entity type => new entity type.
  73       */
  74      protected $movedentitytypes = [
  75      ];
  76  
  77      /**
  78       * Creates the specified elements.
  79       *
  80       * See the class comment for an overview.
  81       *
  82       * @Given /^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/
  83       *
  84       * @param string    $entitytype The name of the type entity to add
  85       * @param TableNode $data
  86       */
  87      public function the_following_entities_exist($entitytype, TableNode $data) {
  88          if (isset($this->movedentitytypes[$entitytype])) {
  89              $entitytype = $this->movedentitytypes[$entitytype];
  90          }
  91          list($component, $entity) = $this->parse_entity_type($entitytype);
  92          $this->get_instance_for_component($component)->generate_items($entity, $data);
  93      }
  94  
  95      /**
  96       * Create multiple entities of one entity type.
  97       *
  98       * @Given :count :entitytype exist with the following data:
  99       *
 100       * @param   string $entitytype The name of the type entity to add
 101       * @param   int $count
 102       * @param   TableNode $data
 103       */
 104      public function the_following_repeated_entities_exist(string $entitytype, int $count, TableNode $data): void {
 105          $rows = $data->getRowsHash();
 106  
 107          $tabledata = [array_keys($rows)];
 108          for ($current = 1; $current < $count + 1; $current++) {
 109              $rowdata = [];
 110              foreach ($rows as $fieldname => $fieldtemplate) {
 111                  $rowdata[$fieldname] = str_replace('[count]', $current, $fieldtemplate);
 112              }
 113              $tabledata[] = $rowdata;
 114          }
 115  
 116          if (isset($this->movedentitytypes[$entitytype])) {
 117              $entitytype = $this->movedentitytypes[$entitytype];
 118          }
 119          list($component, $entity) = $this->parse_entity_type($entitytype);
 120          $this->get_instance_for_component($component)->generate_items($entity, new TableNode($tabledata), false);
 121      }
 122  
 123      /**
 124       * Creates the specified (singular) element.
 125       *
 126       * See the class comment for an overview.
 127       *
 128       * @Given the following :entitytype exists:
 129       *
 130       * @param string    $entitytype The name of the type entity to add
 131       * @param TableNode $data
 132       */
 133      public function the_following_entity_exists($entitytype, TableNode $data) {
 134          if (isset($this->movedentitytypes[$entitytype])) {
 135              $entitytype = $this->movedentitytypes[$entitytype];
 136          }
 137          list($component, $entity) = $this->parse_entity_type($entitytype);
 138          $this->get_instance_for_component($component)->generate_items($entity, $data, true);
 139      }
 140  
 141      /**
 142       * Parse a full entity type like 'users' or 'mod_forum > subscription'.
 143       *
 144       * E.g. parsing 'course' gives ['core', 'course'] and
 145       * parsing 'core_message > message' gives ['core_message', 'message'].
 146       *
 147       * @param string $entitytype the entity type
 148       * @return string[] with two elements, component and entity type.
 149       */
 150      protected function parse_entity_type(string $entitytype): array {
 151          $dividercount = substr_count($entitytype, ' > ');
 152          if ($dividercount === 0) {
 153              return ['core', $entitytype];
 154          } else if ($dividercount === 1) {
 155              list($component, $type) = explode(' > ', $entitytype);
 156              if ($component === 'core') {
 157                  throw new coding_exception('Do not specify the component "core > ..." for entity types.');
 158              }
 159              return [$component, $type];
 160          } else {
 161              throw new coding_exception('The entity type must be in the form ' .
 162                      '"{entity-type}" for core entities, or "{component} > {entity-type}" ' .
 163                      'for entities belonging to other components. ' .
 164                      'For example "users" or "mod_forum > subscriptions".');
 165          }
 166      }
 167  
 168      /**
 169       * Get an instance of the appropriate subclass of this class for a given component.
 170       *
 171       * @param string $component The name of the component to generate entities for.
 172       * @return behat_generator_base the subclass of this class for the requested component.
 173       */
 174      protected function get_instance_for_component(string $component): behat_generator_base {
 175          global $CFG;
 176  
 177          // Ensure the generator class is loaded.
 178          require_once($CFG->libdir . '/behat/classes/behat_generator_base.php');
 179          if ($component === 'core') {
 180              $lib = $CFG->libdir . '/behat/classes/behat_core_generator.php';
 181          } else {
 182              $dir = core_component::get_component_directory($component);
 183              $lib = $dir . '/tests/generator/behat_' . $component . '_generator.php';
 184              if (!$dir || !is_readable($lib)) {
 185                  throw new coding_exception("Component {$component} does not support " .
 186                          "behat generators yet. Missing {$lib}.");
 187              }
 188          }
 189          require_once($lib);
 190  
 191          // Create an instance.
 192          $componentclass = "behat_{$component}_generator";
 193          if (!class_exists($componentclass)) {
 194              throw new PendingException($component .
 195                      ' does not yet support the Behat data generator mechanism. Class ' .
 196                      $componentclass . ' not found in file ' . $lib . '.');
 197          }
 198          $instance = new $componentclass($component);
 199          return $instance;
 200      }
 201  
 202      /**
 203       * Get all entities that can be created in all components using the_following_entities_exist()
 204       *
 205       * @return array
 206       * @throws coding_exception
 207       */
 208      public function get_all_entities(): array {
 209          global $CFG;
 210          // Ensure the generator class is loaded.
 211          require_once($CFG->libdir . '/behat/classes/behat_generator_base.php');
 212          $componenttypes = core_component::get_component_list();
 213          $coregenerator = $this->get_instance_for_component('core');
 214          $pluginswithentities = ['core' => array_keys($coregenerator->get_available_generators())];
 215          foreach ($componenttypes as $components) {
 216              foreach ($components as $component => $componentdir) {
 217                  try {
 218                      $plugingenerator = $this->get_instance_for_component($component);
 219                      $entities = array_keys($plugingenerator->get_available_generators());
 220                      if (!empty($entities)) {
 221                          $pluginswithentities[$component] = $entities;
 222                      }
 223                  } catch (Exception $e) {
 224                      // The component has no generator, skip it.
 225                      continue;
 226                  }
 227              }
 228          }
 229          return $pluginswithentities;
 230      }
 231  
 232      /**
 233       * Get the required fields for a specific creatable entity.
 234       *
 235       * @param string $entitytype
 236       * @return mixed
 237       * @throws coding_exception
 238       */
 239      public function get_entity(string $entitytype): array {
 240          [$component, $entity] = $this->parse_entity_type($entitytype);
 241          $generator = $this->get_instance_for_component($component);
 242          $entities = $generator->get_available_generators();
 243          if (!array_key_exists($entity, $entities)) {
 244              throw new coding_exception('No generator for ' . $entity . ' in component ' . $component);
 245          }
 246          return $entities[$entity];
 247      }
 248  }