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.
   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   * Tests finder
  19   *
  20   * @package    core
  21   * @category   test
  22   * @copyright  2012 Petr Skoda {@link http://skodak.org}
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  /**
  27   * Finds components and plugins with tests
  28   *
  29   * @package    core
  30   * @category   test
  31   * @copyright  2012 Petr Skoda {@link http://skodak.org}
  32   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  33   */
  34  class tests_finder {
  35  
  36      /**
  37       * Returns all the components with tests of the specified type
  38       * @param string $testtype The kind of test we are looking for
  39       * @return array
  40       */
  41      public static function get_components_with_tests($testtype) {
  42  
  43          // Get all the components
  44          $components = self::get_all_plugins_with_tests($testtype) + self::get_all_subsystems_with_tests($testtype);
  45  
  46          // Get all the directories having tests
  47          $directories = self::get_all_directories_with_tests($testtype);
  48  
  49          // Find any directory not covered by proper components
  50          $remaining = array_diff($directories, $components);
  51  
  52          // Add them to the list of components
  53          $components += $remaining;
  54  
  55          return $components;
  56      }
  57  
  58      /**
  59       * Returns all the plugins having tests
  60       * @param string $testtype The kind of test we are looking for
  61       * @return array  all the plugins having tests
  62       */
  63      private static function get_all_plugins_with_tests($testtype) {
  64          $pluginswithtests = array();
  65  
  66          $plugintypes = core_component::get_plugin_types();
  67          ksort($plugintypes);
  68          foreach ($plugintypes as $type => $unused) {
  69              $plugs = core_component::get_plugin_list($type);
  70              ksort($plugs);
  71              foreach ($plugs as $plug => $fullplug) {
  72                  // Look for tests recursively
  73                  if (self::directory_has_tests($fullplug, $testtype)) {
  74                      $pluginswithtests[$type . '_' . $plug] = $fullplug;
  75                  }
  76              }
  77          }
  78          return $pluginswithtests;
  79      }
  80  
  81      /**
  82       * Returns all the subsystems having tests
  83       *
  84       * Note we are hacking here the list of subsystems
  85       * to cover some well-known subsystems that are not properly
  86       * returned by the {@link get_core_subsystems()} function.
  87       *
  88       * @param string $testtype The kind of test we are looking for
  89       * @return array all the subsystems having tests
  90       */
  91      private static function get_all_subsystems_with_tests($testtype) {
  92          global $CFG;
  93  
  94          $subsystemswithtests = array();
  95  
  96          $subsystems = core_component::get_core_subsystems();
  97  
  98          // Hack the list a bit to cover some well-known ones
  99          $subsystems['backup'] = $CFG->dirroot.'/backup';
 100          $subsystems['db-dml'] = $CFG->dirroot.'/lib/dml';
 101          $subsystems['db-ddl'] = $CFG->dirroot.'/lib/ddl';
 102  
 103          ksort($subsystems);
 104          foreach ($subsystems as $subsys => $fullsubsys) {
 105              if ($fullsubsys === null) {
 106                  continue;
 107              }
 108              if (!is_dir($fullsubsys)) {
 109                  continue;
 110              }
 111              // Look for tests recursively
 112              if (self::directory_has_tests($fullsubsys, $testtype)) {
 113                  $subsystemswithtests['core_' . $subsys] = $fullsubsys;
 114              }
 115          }
 116          return $subsystemswithtests;
 117      }
 118  
 119      /**
 120       * Returns all the directories having tests
 121       *
 122       * @param string $testtype The kind of test we are looking for
 123       * @return array all directories having tests
 124       */
 125      private static function get_all_directories_with_tests($testtype) {
 126          global $CFG;
 127  
 128          // List of directories to exclude from test file searching.
 129          $excludedir = array('node_modules', 'vendor');
 130  
 131          // Get first level directories in which tests should be searched.
 132          $directoriestosearch = array();
 133          $alldirs = glob($CFG->dirroot . DIRECTORY_SEPARATOR . '*' , GLOB_ONLYDIR);
 134          foreach ($alldirs as $dir) {
 135              if (!in_array(basename($dir), $excludedir) && (filetype($dir) != 'link')) {
 136                  $directoriestosearch[] = $dir;
 137              }
 138          }
 139  
 140          // Search for tests in valid directories.
 141          $dirs = array();
 142          foreach ($directoriestosearch as $dir) {
 143              $dirite = new RecursiveDirectoryIterator($dir);
 144              $iteite = new RecursiveIteratorIterator($dirite);
 145              $regexp = self::get_regexp($testtype);
 146              $regite = new RegexIterator($iteite, $regexp);
 147              foreach ($regite as $path => $element) {
 148                  $key = dirname(dirname($path));
 149                  $value = trim(str_replace(DIRECTORY_SEPARATOR, '_', str_replace($CFG->dirroot, '', $key)), '_');
 150                  $dirs[$key] = $value;
 151              }
 152          }
 153          ksort($dirs);
 154          return array_flip($dirs);
 155      }
 156  
 157      /**
 158       * Returns if a given directory has tests (recursively)
 159       *
 160       * @param string $dir full path to the directory to look for phpunit tests
 161       * @param string $testtype phpunit|behat
 162       * @return bool if a given directory has tests (true) or no (false)
 163       */
 164      private static function directory_has_tests($dir, $testtype) {
 165          if (!is_dir($dir)) {
 166              return false;
 167          }
 168  
 169          $dirite = new RecursiveDirectoryIterator($dir);
 170          $iteite = new RecursiveIteratorIterator($dirite);
 171          $regexp = self::get_regexp($testtype);
 172          $regite = new RegexIterator($iteite, $regexp);
 173          $regite->rewind();
 174          if ($regite->valid()) {
 175              return true;
 176          }
 177          return false;
 178      }
 179  
 180  
 181      /**
 182       * Returns the regular expression to match by the test files
 183       * @param string $testtype
 184       * @return string
 185       */
 186      private static function get_regexp($testtype) {
 187  
 188          $sep = preg_quote(DIRECTORY_SEPARATOR, '|');
 189  
 190          switch ($testtype) {
 191              case 'phpunit':
 192                  $regexp = '|'.$sep.'tests'.$sep.'.*_test\.php$|';
 193                  break;
 194              case 'features':
 195                  $regexp = '|'.$sep.'tests'.$sep.'behat'.$sep.'.*\.feature$|';
 196                  break;
 197              case 'stepsdefinitions':
 198                  $regexp = '|'.$sep.'tests'.$sep.'behat'.$sep.'behat_.*\.php$|';
 199                  break;
 200              case 'behat':
 201                  $regexp = '!'.$sep.'tests'.$sep.'behat'.$sep.'(.*\.feature)|(behat_.*\.php)$!';
 202                  break;
 203          }
 204  
 205          return $regexp;
 206      }
 207  }