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] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 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   * Behat command utils
  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  defined('MOODLE_INTERNAL') || die();
  27  
  28  require_once (__DIR__ . '/../lib.php');
  29  
  30  /**
  31   * Behat command related utils
  32   *
  33   * @package    core
  34   * @category   test
  35   * @copyright  2013 David MonllaĆ³
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class behat_command {
  39  
  40      /**
  41       * Docs url
  42       */
  43      const DOCS_URL = 'https://moodledev.io/general/development/tools/behat';
  44  
  45      /**
  46       * Ensures the behat dir exists in moodledata
  47       *
  48       * @return string Full path
  49       */
  50      public static function get_parent_behat_dir() {
  51          global $CFG;
  52  
  53          // If not set then return empty string.
  54          if (!isset($CFG->behat_dataroot_parent)) {
  55              return "";
  56          }
  57  
  58          return $CFG->behat_dataroot_parent;
  59      }
  60  
  61      /**
  62       * Ensures the behat dir exists in moodledata
  63       * @param int $runprocess run process for which behat dir is returned.
  64       * @return string Full path
  65       */
  66      public static function get_behat_dir($runprocess = 0) {
  67          global $CFG;
  68  
  69          // If not set then return empty string.
  70          if (!isset($CFG->behat_dataroot)) {
  71              return "";
  72          }
  73  
  74          // If $CFG->behat_parallel_run starts with index 0 and $runprocess for parallel run starts with 1.
  75          if (!empty($runprocess) && isset($CFG->behat_parallel_run[$runprocess - 1]['behat_dataroot'])) {
  76              $behatdir = $CFG->behat_parallel_run[$runprocess - 1]['behat_dataroot'] . '/behat';;
  77          } else {
  78              $behatdir = $CFG->behat_dataroot . '/behat';
  79          }
  80  
  81          if (!is_dir($behatdir)) {
  82              if (!mkdir($behatdir, $CFG->directorypermissions, true)) {
  83                  behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Directory ' . $behatdir . ' can not be created');
  84              }
  85          }
  86  
  87          if (!is_writable($behatdir)) {
  88              behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Directory ' . $behatdir . ' is not writable');
  89          }
  90  
  91          return $behatdir;
  92      }
  93  
  94      /**
  95       * Returns the executable path
  96       *
  97       * Allows returning a customized command for cygwin when the
  98       * command is just displayed, when using exec(), system() and
  99       * friends we stay with DIRECTORY_SEPARATOR as they use the
 100       * normal cmd.exe (in Windows).
 101       *
 102       * @param  bool $custombyterm  If the provided command should depend on the terminal where it runs
 103       * @param bool $parallelrun If parallel run is installed.
 104       * @param bool $absolutepath return command with absolute path.
 105       * @return string
 106       */
 107      public final static function get_behat_command($custombyterm = false, $parallerun = false, $absolutepath = false) {
 108  
 109          $separator = DIRECTORY_SEPARATOR;
 110          $exec = 'behat';
 111  
 112          // Cygwin uses linux-style directory separators.
 113          if ($custombyterm && testing_is_cygwin()) {
 114              $separator = '/';
 115  
 116              // MinGW can not execute .bat scripts.
 117              if (!testing_is_mingw()) {
 118                  $exec = 'behat.bat';
 119              }
 120          }
 121  
 122          // If relative path then prefix relative path.
 123          if ($absolutepath) {
 124              $pathprefix = testing_cli_argument_path('/');
 125              if (!empty($pathprefix)) {
 126                  $pathprefix .= $separator;
 127              }
 128          } else {
 129              $pathprefix = '';
 130          }
 131  
 132          if (!$parallerun) {
 133              $command = $pathprefix . 'vendor' . $separator . 'bin' . $separator . $exec;
 134          } else {
 135              $command = 'php ' . $pathprefix . 'admin' . $separator . 'tool' . $separator . 'behat' . $separator . 'cli'
 136                  . $separator . 'run.php';
 137          }
 138          return $command;
 139      }
 140  
 141      /**
 142       * Runs behat command with provided options
 143       *
 144       * Execution continues when the process finishes
 145       *
 146       * @param  string $options  Defaults to '' so tests would be executed
 147       * @return array            CLI command outputs [0] => string, [1] => integer
 148       */
 149      public final static function run($options = '') {
 150          global $CFG;
 151  
 152          $currentcwd = getcwd();
 153          chdir($CFG->dirroot);
 154          exec(self::get_behat_command() . ' ' . $options, $output, $code);
 155          chdir($currentcwd);
 156  
 157          return array($output, $code);
 158      }
 159  
 160      /**
 161       * Checks if behat is set up and working
 162       *
 163       * Notifies failures both from CLI and web interface.
 164       *
 165       * It checks behat dependencies have been installed and runs
 166       * the behat help command to ensure it works as expected
 167       *
 168       * @return int Error code or 0 if all ok
 169       */
 170      public static function behat_setup_problem() {
 171          global $CFG;
 172  
 173          // Moodle setting.
 174          if (!self::are_behat_dependencies_installed()) {
 175  
 176              // Returning composer error code to avoid conflicts with behat and moodle error codes.
 177              self::output_msg(get_string('errorcomposer', 'tool_behat'));
 178              return TESTING_EXITCODE_COMPOSER;
 179          }
 180  
 181          // Behat test command.
 182          $dirrootconfigpath = $CFG->dirroot . DIRECTORY_SEPARATOR . 'behat.yml';
 183          if (file_exists($dirrootconfigpath)) {
 184              self::output_msg(get_string('warndirrootconfigfound', 'tool_behat', $dirrootconfigpath));
 185          }
 186          list($output, $code) = self::run(" --help");
 187  
 188          if ($code != 0) {
 189  
 190              // Returning composer error code to avoid conflicts with behat and moodle error codes.
 191              self::output_msg(get_string('errorbehatcommand', 'tool_behat', self::get_behat_command()));
 192              return TESTING_EXITCODE_COMPOSER;
 193          }
 194  
 195          // No empty values.
 196          if (empty($CFG->behat_dataroot) || empty($CFG->behat_prefix) || empty($CFG->behat_wwwroot)) {
 197              self::output_msg(get_string('errorsetconfig', 'tool_behat'));
 198              return BEHAT_EXITCODE_CONFIG;
 199  
 200          }
 201  
 202          // Not repeated values.
 203          // We only need to check this when the behat site is not running as
 204          // at this point, when it is running, all $CFG->behat_* vars have
 205          // already been copied to $CFG->dataroot, $CFG->prefix and $CFG->wwwroot.
 206          $phpunitprefix = empty($CFG->phpunit_prefix) ? '' : $CFG->phpunit_prefix;
 207          $behatdbname = empty($CFG->behat_dbname) ? $CFG->dbname : $CFG->behat_dbname;
 208          $phpunitdbname = empty($CFG->phpunit_dbname) ? $CFG->dbname : $CFG->phpunit_dbname;
 209          $behatdbhost = empty($CFG->behat_dbhost) ? $CFG->dbhost : $CFG->behat_dbhost;
 210          $phpunitdbhost = empty($CFG->phpunit_dbhost) ? $CFG->dbhost : $CFG->phpunit_dbhost;
 211  
 212          $samedataroot = $CFG->behat_dataroot == $CFG->dataroot;
 213          $samedataroot = $samedataroot || (!empty($CFG->phpunit_dataroot) && $CFG->phpunit_dataroot == $CFG->behat_dataroot);
 214          $samewwwroot = $CFG->behat_wwwroot == $CFG->wwwroot;
 215          $sameprefix = ($CFG->behat_prefix == $CFG->prefix && $behatdbname == $CFG->dbname && $behatdbhost == $CFG->dbhost);
 216          $sameprefix = $sameprefix || ($CFG->behat_prefix == $phpunitprefix && $behatdbname == $phpunitdbname &&
 217                  $behatdbhost == $phpunitdbhost);
 218          if (!defined('BEHAT_SITE_RUNNING') && ($samedataroot || $samewwwroot || $sameprefix)) {
 219              self::output_msg(get_string('erroruniqueconfig', 'tool_behat'));
 220              return BEHAT_EXITCODE_CONFIG;
 221          }
 222  
 223          // Checking behat dataroot existence otherwise echo about admin/tool/behat/cli/init.php.
 224          if (!empty($CFG->behat_dataroot)) {
 225              $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
 226          }
 227          if (empty($CFG->behat_dataroot) || !is_dir($CFG->behat_dataroot) || !is_writable($CFG->behat_dataroot)) {
 228              self::output_msg(get_string('errordataroot', 'tool_behat'));
 229              return BEHAT_EXITCODE_CONFIG;
 230          }
 231  
 232          return 0;
 233      }
 234  
 235      /**
 236       * Has the site installed composer.
 237       * @return bool
 238       */
 239      public static function are_behat_dependencies_installed() {
 240          if (!is_dir(__DIR__ . '/../../../vendor/behat')) {
 241              return false;
 242          }
 243          return true;
 244      }
 245  
 246      /**
 247       * Outputs a message.
 248       *
 249       * Used in CLI + web UI methods. Stops the
 250       * execution in web.
 251       *
 252       * @param string $msg
 253       * @return void
 254       */
 255      protected static function output_msg($msg) {
 256          global $CFG, $PAGE;
 257  
 258          // If we are using the web interface we want pretty messages.
 259          if (!CLI_SCRIPT) {
 260  
 261              $renderer = $PAGE->get_renderer('tool_behat');
 262              echo $renderer->render_error($msg);
 263  
 264              // Stopping execution.
 265              exit(1);
 266  
 267          } else {
 268  
 269              // We continue execution after this.
 270              $clibehaterrorstr = "Ensure you set \$CFG->behat_* vars in config.php " .
 271                  "and you ran admin/tool/behat/cli/init.php.\n" .
 272                  "More info in " . self::DOCS_URL;
 273  
 274              echo 'Error: ' . $msg . "\n\n" . $clibehaterrorstr;
 275          }
 276      }
 277  
 278  }