MDL-37046 behat: Split in different classes
authorDavid Monllao <davidm@moodle.com>
Mon, 14 Jan 2013 05:54:43 +0000 (13:54 +0800)
committerDavid Monllao <davidm@moodle.com>
Tue, 29 Jan 2013 00:40:38 +0000 (08:40 +0800)
admin/tool/behat/cli/util.php
admin/tool/behat/index.php
admin/tool/behat/locallib.php
admin/tool/behat/renderer.php
admin/tool/behat/tests/tool_behat_test.php
lib/behat/classes/behat_command.php [new file with mode: 0644]
lib/behat/classes/behat_config_manager.php [new file with mode: 0644]
lib/behat/classes/behat_util.php [new file with mode: 0644]

index 3f39442..5e580aa 100644 (file)
@@ -26,7 +26,7 @@ define('CLI_SCRIPT', true);
 
 require(__DIR__ . '/../../../../config.php');
 require_once($CFG->libdir . '/clilib.php');
-require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/behat/locallib.php');
+require_once($CFG->libdir . '/behat/behat_util.php');
 
 // CLI options.
 list($options, $unrecognized) = cli_get_params(
@@ -75,7 +75,7 @@ if ($options['enable']) {
     exit(0);
 }
 
-tool_behat::switchenvironment($action);
+behat_util::switchenvironment($action);
 
 mtrace(get_string('testenvironment' . $action, 'tool_behat'));
 
index c5e5605..f68ec13 100644 (file)
@@ -25,6 +25,7 @@
 require(__DIR__ . '/../../../config.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/behat/locallib.php');
+require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
 
 $filter = optional_param('filter', '', PARAM_ALPHANUMEXT);
 $type = optional_param('type', false, PARAM_ALPHAEXT);
@@ -39,7 +40,7 @@ $steps = tool_behat::stepsdefinitions($type, $component, $filter);
 $componentswithsteps = array('' => get_string('allavailablesteps', 'tool_behat'));
 
 // Complete the components list with the moodle steps definitions.
-$components = tool_behat::get_components_steps_definitions();
+$components = behat_config_manager::get_components_steps_definitions();
 if ($components) {
     foreach ($components as $component => $filepath) {
         // TODO Use a class static attribute instead of the class name.
index a3b8aa1..fd1ba30 100644 (file)
  */
 
 global $CFG;
-require_once($CFG->libdir . '/filestorage/file_exceptions.php');
-require_once($CFG->libdir . '/phpunit/bootstraplib.php');
-require_once($CFG->libdir . '/phpunit/classes/tests_finder.php');
-
+require_once($CFG->libdir . '/behat/classes/behat_command.php');
+require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
 require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/behat/steps_definitions_form.php');
 
 /**
@@ -38,12 +36,6 @@ require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/behat/steps_definitions_
  */
 class tool_behat {
 
-    /** @var array Steps types */
-    private static $steps_types = array('given', 'when', 'then');
-
-    /** @var string Docu url */
-    public static $docsurl = 'http://docs.moodle.org/dev/Acceptance_testing';
-
     /**
      * Lists the available steps definitions
      *
@@ -55,10 +47,10 @@ class tool_behat {
     public static function stepsdefinitions($type, $component, $filter) {
         global $CFG;
 
-        self::check_behat_setup();
+        behat_command::check_behat_setup();
 
         // The loaded steps depends on the component specified.
-        self::update_config_file($component, false);
+        behat_config_manager::update_config_file($component, false);
 
         // The Moodle\BehatExtension\HelpPrinter\MoodleDefinitionsPrinter will parse this search format.
         if ($type) {
@@ -73,7 +65,7 @@ class tool_behat {
 
         $currentcwd = getcwd();
         chdir($CFG->dirroot);
-        exec(self::get_behat_command() . ' --config="'.self::get_steps_list_config_filepath(). '" '.$filteroption, $steps, $code);
+        exec(behat_command::get_behat_command() . ' --config="'.behat_config_manager::get_steps_list_config_filepath(). '" '.$filteroption, $steps, $code);
         chdir($currentcwd);
 
         if ($steps) {
@@ -87,460 +79,4 @@ class tool_behat {
         return $stepshtml;
     }
 
-    /**
-     * Allows / disables the test environment to be accessed through the built-in server
-     *
-     * Built-in server must be started separately
-     *
-     * @param string $testenvironment enable|disable
-     */
-    public static function switchenvironment($testenvironment) {
-        if ($testenvironment == 'enable') {
-            self::start_test_mode();
-        } else if ($testenvironment == 'disable') {
-            self::stop_test_mode();
-        }
-    }
-
-    /**
-     * Updates a config file
-     *
-     * The tests runner and the steps definitions list uses different
-     * config files to avoid problems with concurrent executions.
-     *
-     * The steps definitions list can be filtered by component so it's
-     * behat.yml can be different from the dirroot one.
-     *
-     * @param string $component Restricts the obtained steps definitions to the specified component
-     * @param string $testsrunner If the config file will be used to run tests
-     * @throws file_exception
-     */
-    protected static function update_config_file($component = '', $testsrunner = true) {
-        global $CFG;
-
-        // Behat must run with the whole set of features and steps definitions.
-        if ($testsrunner === true) {
-            $prefix = '';
-            $configfilepath = $CFG->dirroot . '/behat.yml';
-
-            // Alternative for steps definitions filtering.
-        } else {
-            $configfilepath = self::get_steps_list_config_filepath();
-            $prefix = $CFG->dirroot .'/';
-        }
-
-        // Gets all the components with features.
-        $features = array();
-        $components = tests_finder::get_components_with_tests('features');
-        if ($components) {
-            foreach ($components as $componentname => $path) {
-                $path = self::clean_path($path) . self::get_behat_tests_path();
-                if (empty($featurespaths[$path]) && file_exists($path)) {
-                    $uniquekey = str_replace('\\', '/', $path);
-                    $featurespaths[$uniquekey] = $path;
-                }
-            }
-            $features = array_values($featurespaths);
-        }
-
-        // Gets all the components with steps definitions.
-        $stepsdefinitions = array();
-        $steps = self::get_components_steps_definitions();
-        if ($steps) {
-            foreach ($steps as $key => $filepath) {
-                if ($component == '' || $component === $key) {
-                    $stepsdefinitions[$key] = $filepath;
-                }
-            }
-        }
-
-        // Behat config file specifing the main context class,
-        // the required Behat extensions and Moodle test wwwroot.
-        $contents = self::get_config_file_contents($prefix, $features, $stepsdefinitions);
-
-        // Stores the file.
-        if (!file_put_contents($configfilepath, $contents)) {
-            throw new file_exception('cannotcreatefile', $configfilepath);
-        }
-
-    }
-
-    /**
-     * Behat config file specifing the main context class,
-     * the required Behat extensions and Moodle test wwwroot.
-     *
-     * @param string $prefix The filesystem prefix
-     * @param array $features The system feature files
-     * @param array $stepsdefinitions The system steps definitions
-     * @return string
-     */
-    protected static function get_config_file_contents($prefix, $features, $stepsdefinitions) {
-        global $CFG;
-
-        // We require here when we are sure behat dependencies are available.
-        require_once($CFG->dirroot . '/vendor/autoload.php');
-
-        $config = array(
-            'default' => array(
-                'paths' => array(
-                    'features' => $prefix . 'lib/behat/features',
-                    'bootstrap' => $prefix . 'lib/behat/features/bootstrap',
-                ),
-                'context' => array(
-                    'class' => 'behat_init_context'
-                ),
-                'extensions' => array(
-                    'Behat\MinkExtension\Extension' => array(
-                        'base_url' => $CFG->behat_wwwroot,
-                        'goutte' => null,
-                        'selenium2' => null
-                    ),
-                    'Moodle\BehatExtension\Extension' => array(
-                        'features' => $features,
-                        'steps_definitions' => $stepsdefinitions
-                    )
-                )
-            )
-        );
-
-        // In case user defined overrides respect them over our default ones.
-        if (!empty($CFG->behat_config)) {
-            $config = self::merge_config($config, $CFG->behat_config);
-        }
-
-        return Symfony\Component\Yaml\Yaml::dump($config, 10, 2);
-    }
-
-    /**
-     * Overrides default config with local config values
-     *
-     * array_merge does not merge completely the array's values
-     *
-     * @param mixed $config The node of the default config
-     * @param mixed $localconfig The node of the local config
-     * @return mixed The merge result
-     */
-    protected static function merge_config($config, $localconfig) {
-
-        if (!is_array($config) && !is_array($localconfig)) {
-            return $localconfig;
-        }
-
-        // Local overrides also deeper default values.
-        if (is_array($config) && !is_array($localconfig)) {
-            return $localconfig;
-        }
-
-        foreach ($localconfig as $key => $value) {
-
-            // If defaults are not as deep as local values let locals override.
-            if (!is_array($config)) {
-                unset($config);
-            }
-
-            // Add the param if it doesn't exists or merge branches.
-            if (empty($config[$key])) {
-                $config[$key] = $value;
-            } else {
-                $config[$key] = self::merge_config($config[$key], $localconfig[$key]);
-            }
-        }
-
-        return $config;
-    }
-
-    /**
-     * Gets the list of Moodle steps definitions
-     *
-     * Class name as a key and the filepath as value
-     *
-     * Externalized from update_config_file() to use
-     * it from the steps definitions web interface
-     *
-     * @return array
-     */
-    public static function get_components_steps_definitions() {
-
-        $components = tests_finder::get_components_with_tests('stepsdefinitions');
-        if (!$components) {
-            return false;
-        }
-
-        $stepsdefinitions = array();
-        foreach ($components as $componentname => $componentpath) {
-            $componentpath = self::clean_path($componentpath);
-            $diriterator = new DirectoryIterator($componentpath . self::get_behat_tests_path());
-            $regite = new RegexIterator($diriterator, '|behat_.*\.php$|');
-
-            // All behat_*.php inside self::get_behat_tests_path() are added as steps definitions files.
-            foreach ($regite as $file) {
-                $key = $file->getBasename('.php');
-                $stepsdefinitions[$key] = $file->getPathname();
-            }
-        }
-
-        return $stepsdefinitions;
-    }
-
-    /**
-     * Checks if $CFG->behat_wwwroot is available
-     *
-     * @return boolean
-     */
-    public static function is_server_running() {
-        global $CFG;
-
-        $request = new curl();
-        $request->get($CFG->behat_wwwroot);
-        return (true && !$request->get_errno());
-    }
-
-    /**
-     * Cleans the path returned by get_components_with_tests() to standarize it
-     *
-     * @see tests_finder::get_all_directories_with_tests() it returns the path including /tests/
-     * @param string $path
-     * @return string The string without the last /tests part
-     */
-    protected final static function clean_path($path) {
-
-        $path = rtrim($path, DIRECTORY_SEPARATOR);
-
-        $parttoremove = DIRECTORY_SEPARATOR . 'tests';
-
-        $substr = substr($path, strlen($path) - strlen($parttoremove));
-        if ($substr == $parttoremove) {
-            $path = substr($path, 0, strlen($path) - strlen($parttoremove));
-        }
-
-        return rtrim($path, DIRECTORY_SEPARATOR);
-    }
-
-    /**
-     * Checks whether the test database and dataroot is ready
-     * Stops execution if something went wrong
-     */
-    protected static function test_environment_problem() {
-        global $CFG;
-
-        // PHPUnit --diag returns nothing if the test environment is set up correctly.
-        exec('php ' . $CFG->dirroot . '/' . $CFG->admin . '/tool/phpunit/cli/util.php --diag', $output, $code);
-
-        // If something is not ready stop execution and display the CLI command output.
-        if ($code != 0) {
-            notice(get_string('phpunitenvproblem', 'tool_behat') . ': ' . implode(' ', $output));
-        }
-    }
-
-    /**
-     * Checks if behat is set up and working
-     *
-     * It checks behat dependencies have been installed and runs
-     * the behat help command to ensure it works as expected
-     * @param boolean $checkphp Extra check for the PHP version
-     */
-    protected static function check_behat_setup($checkphp = false) {
-        global $CFG;
-
-        // We don't check the PHP version if $CFG->behat_switchcompletely has been enabled.
-        if (empty($CFG->behat_switchcompletely) && $checkphp && version_compare(PHP_VERSION, '5.4.0', '<')) {
-            throw new Exception(get_string('wrongphpversion', 'tool_behat'));
-        }
-
-        // Moodle setting.
-        if (!self::are_behat_dependencies_installed()) {
-
-            $msg = get_string('wrongbehatsetup', 'tool_behat');
-
-            // With HTML.
-            $docslink = self::$docsurl . '#Installation';
-            if (!CLI_SCRIPT) {
-                $docslink = html_writer::tag('a', $docslink, array('href' => $docslink, 'target' => '_blank'));
-            }
-            $msg .= '. ' . get_string('moreinfoin', 'tool_behat') . ' ' . $docslink;
-            notice($msg);
-        }
-
-        // Behat test command.
-        $currentcwd = getcwd();
-        chdir($CFG->dirroot);
-        exec(self::get_behat_command() . ' --help', $output, $code);
-        chdir($currentcwd);
-
-        if ($code != 0) {
-            notice(get_string('wrongbehatsetup', 'tool_behat'));
-        }
-    }
-
-    /**
-     * Enables test mode
-     *
-     * Starts the test mode checking the composer installation and
-     * the phpunit test environment and updating the available
-     * features and steps definitions.
-     *
-     * Stores a file in dataroot/behat to allow Moodle to switch
-     * to the test environment when using cli-server (or $CFG->behat_switchcompletely)
-     *
-     * @throws file_exception
-     */
-    protected static function start_test_mode() {
-        global $CFG;
-
-        // Checks the behat set up and the PHP version.
-        self::check_behat_setup(true);
-
-        // Check that PHPUnit test environment is correctly set up.
-        self::test_environment_problem();
-
-        // Updates all the Moodle features and steps definitions.
-        self::update_config_file();
-
-        if (self::is_test_mode_enabled()) {
-            debugging('Test environment was already enabled');
-            return;
-        }
-
-        $behatdir = self::get_behat_dir();
-
-        $contents = '$CFG->behat_wwwroot, $CFG->phpunit_prefix and $CFG->phpunit_dataroot' .
-            ' are currently used as $CFG->wwwroot, $CFG->prefix and $CFG->dataroot';
-        $filepath = $behatdir . '/test_environment_enabled.txt';
-        if (!file_put_contents($filepath, $contents)) {
-            throw new file_exception('cannotcreatefile', $filepath);
-        }
-        chmod($filepath, $CFG->directorypermissions);
-    }
-
-    /**
-     * Disables test mode
-     * @throws file_exception
-     */
-    protected static function stop_test_mode() {
-
-        $testenvfile = self::get_test_filepath();
-
-        if (!self::is_test_mode_enabled()) {
-            debugging('Test environment was already disabled');
-        } else {
-            if (!unlink($testenvfile)) {
-                throw new file_exception('cannotdeletetestenvironmentfile');
-            }
-        }
-    }
-
-    /**
-     * Checks whether test environment is enabled or disabled
-     *
-     * To check is the current script is running in the test
-     * environment
-     *
-     * @see tool_behat::is_test_environment_running()
-     * @return bool
-     */
-    public static function is_test_mode_enabled() {
-
-        $testenvfile = self::get_test_filepath();
-        if (file_exists($testenvfile)) {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Returns true if Moodle is currently running with the test database and dataroot
-     * @return bool
-     */
-    public static function is_test_environment_running() {
-        global $CFG;
-
-        if (!empty($CFG->originaldataroot)) {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Has the site installed composer with --dev option
-     * @return boolean
-     */
-    public static function are_behat_dependencies_installed() {
-        if (!is_dir(__DIR__ . '/../../../vendor/behat')) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Returns the path to the file which specifies if test environment is enabled
-     *
-     * The file is in dataroot/behat/ but we need to
-     * know if test mode is running because then we swap
-     * it to phpunit_dataroot and we need the original value
-     *
-     * @return string
-     */
-    protected final static function get_test_filepath() {
-        global $CFG;
-
-        if (self::is_test_environment_running()) {
-            $prefix = $CFG->originaldataroot;
-        } else {
-            $prefix = $CFG->dataroot;
-        }
-
-        return $prefix . '/behat/test_environment_enabled.txt';
-    }
-
-
-    /**
-     * The relative path where components stores their behat tests
-     *
-     * @return string
-     */
-    protected final static function get_behat_tests_path() {
-        return DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'behat';
-    }
-
-    /**
-     * Ensures the behat dir exists in moodledata
-     * @throws file_exception
-     * @return string Full path
-     */
-    protected static function get_behat_dir() {
-        global $CFG;
-
-        $behatdir = $CFG->dataroot . '/behat';
-
-        if (!is_dir($behatdir)) {
-            if (!mkdir($behatdir, $CFG->directorypermissions, true)) {
-                throw new file_exception('storedfilecannotcreatefiledirs');
-            }
-        }
-
-        if (!is_writable($behatdir)) {
-            throw new file_exception('storedfilecannotcreatefiledirs');
-        }
-
-        return $behatdir;
-    }
-
-    /**
-     * Returns the executable path
-     * @return string
-     */
-    protected final static function get_behat_command() {
-        return 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'behat';
-    }
-
-    /**
-     * Returns the behat config file path used by the steps definition list
-     * @return string
-     */
-    protected static function get_steps_list_config_filepath() {
-        return self::get_behat_dir() . '/behat.yml';
-    }
-
 }
index e1cfc37..e927ca1 100644 (file)
@@ -33,6 +33,11 @@ defined('MOODLE_INTERNAL') || die;
  */
 class tool_behat_renderer extends plugin_renderer_base {
 
+    /**
+     * @var string Docs url
+     */
+    protected $docsurl = 'http://docs.moodle.org/dev/Acceptance_testing';
+
     /**
      * Renders the list of available steps according to the submitted filters
      *
@@ -49,11 +54,11 @@ class tool_behat_renderer extends plugin_renderer_base {
         $html .= $this->output->heading($title);
 
         // Info.
-        $installurl = tool_behat::$docsurl . '#Installation';
+        $installurl = $this->docsurl . '#Installation';
         $installlink = html_writer::tag('a', $installurl, array('href' => $installurl, 'target' => '_blank'));
-        $writetestsurl = tool_behat::$docsurl . '#Writting_features';
+        $writetestsurl = $this->docsurl . '#Writting_features';
         $writetestslink = html_writer::tag('a', $writetestsurl, array('href' => $writetestsurl, 'target' => '_blank'));
-        $writestepsurl = tool_behat::$docsurl . '#Adding_steps_definitions';
+        $writestepsurl = $this->docsurl . '#Adding_steps_definitions';
         $writestepslink = html_writer::tag('a', $writestepsurl, array('href' => $writestepsurl, 'target' => '_blank'));
         $infos = array(
             get_string('installinfo', 'tool_behat', $installlink),
index 61ae73b..00ebcce 100644 (file)
@@ -25,7 +25,9 @@
 defined('MOODLE_INTERNAL') || die();
 
 global $CFG;
-require_once($CFG->dirroot.'/' . $CFG->admin .'/tool/behat/locallib.php');
+require_once($CFG->dirroot . '/' . $CFG->admin .'/tool/behat/locallib.php');
+require_once($CFG->libdir . '/behat/classes/behat_util.php');
+require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
 
 /**
  * Allows access to internal methods without exposing them
@@ -34,7 +36,7 @@ require_once($CFG->dirroot.'/' . $CFG->admin .'/tool/behat/locallib.php');
  * @copyright  2012 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class testable_tool_behat extends tool_behat {
+class testable_behat_config_manager extends behat_config_manager {
 
     /**
      * Allow access to protected method
@@ -70,37 +72,37 @@ class testable_tool_behat extends tool_behat {
 class tool_behat_testcase extends advanced_testcase {
 
     /**
-     * test_switch_environment
+     * behat_util tests
      */
     public function test_switch_environment() {
 
         // Only run the tests if behat dependencies are installed.
         // We don't need to pre-check PHPUnit initialisation because we are running on it.
-        if (version_compare(PHP_VERSION, '5.4.0', '>=') && tool_behat::are_behat_dependencies_installed()) {
-             tool_behat::switchenvironment('enable');
-             $this->assertTrue(tool_behat::is_test_mode_enabled());
-             $this->assertFalse(tool_behat::is_test_environment_running());
+        if (version_compare(PHP_VERSION, '5.4.0', '>=') && behat_command::are_behat_dependencies_installed()) {
+             behat_util::switchenvironment('enable');
+             $this->assertTrue(behat_util::is_test_mode_enabled());
+             $this->assertFalse(behat_util::is_test_environment_running());
 
              // We trigger a debugging() if it's already enabled.
-             tool_behat::switchenvironment('enable');
+             behat_util::switchenvironment('enable');
              $this->assertDebuggingCalled();
 
-             tool_behat::switchenvironment('disable');
-             $this->assertFalse(tool_behat::is_test_mode_enabled());
-             $this->assertFalse(tool_behat::is_test_environment_running());
+             behat_util::switchenvironment('disable');
+             $this->assertFalse(behat_util::is_test_mode_enabled());
+             $this->assertFalse(behat_util::is_test_environment_running());
 
              // We trigger a debugging() if it's already enabled.
-             tool_behat::switchenvironment('disable');
+             behat_util::switchenvironment('disable');
              $this->assertDebuggingCalled();
 
              // Ensure all continues disabled.
-             $this->assertFalse(tool_behat::is_test_mode_enabled());
-             $this->assertFalse(tool_behat::is_test_environment_running());
+             $this->assertFalse(behat_util::is_test_mode_enabled());
+             $this->assertFalse(behat_util::is_test_environment_running());
         }
     }
 
     /**
-     * test_merge_configs
+     * behat_config_manager tests
      */
     public function test_merge_configs() {
 
@@ -130,7 +132,7 @@ class tool_behat_testcase extends advanced_testcase {
             )
         );
 
-        $array = testable_tool_behat::merge_config($array1, $array2);
+        $array = testable_behat_config_manager::merge_config($array1, $array2);
 
         // Overriddes are applied.
         $this->assertEquals('OVERRIDDEN1', $array['simple']);
@@ -155,7 +157,7 @@ class tool_behat_testcase extends advanced_testcase {
             'array' => 'one'
         );
 
-        $array = testable_tool_behat::merge_config($array1, $array2);
+        $array = testable_behat_config_manager::merge_config($array1, $array2);
 
         // Overrides applied.
         $this->assertNotEmpty($array['simple']);
@@ -168,7 +170,7 @@ class tool_behat_testcase extends advanced_testcase {
     }
 
     /**
-     * test_config_file_contents
+     * behat_config_manager tests
      */
     public function test_config_file_contents() {
         global $CFG;
@@ -188,7 +190,7 @@ class tool_behat_testcase extends advanced_testcase {
             'anoche' => '/cuando/yo/dormia'
         );
 
-        $contents = testable_tool_behat::get_config_file_contents('/i/am/a/prefix/', $features, $stepsdefinitions);
+        $contents = testable_behat_config_manager::get_config_file_contents('/i/am/a/prefix/', $features, $stepsdefinitions);
 
         $this->assertContains('features: /i/am/a/prefix/lib/behat/features', $contents);
         $this->assertContains('micarro: /me/lo/robaron', $contents);
diff --git a/lib/behat/classes/behat_command.php b/lib/behat/classes/behat_command.php
new file mode 100644 (file)
index 0000000..ece8039
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Behat command utils
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../filestorage/file_exceptions.php');
+
+/**
+ * Behat command related utils
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2013 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_command {
+
+    /**
+     * Ensures the behat dir exists in moodledata
+     * @throws file_exception
+     * @return string Full path
+     */
+    public static function get_behat_dir() {
+        global $CFG;
+
+        $behatdir = $CFG->dataroot . '/behat';
+
+        if (!is_dir($behatdir)) {
+            if (!mkdir($behatdir, $CFG->directorypermissions, true)) {
+                throw new file_exception('storedfilecannotcreatefiledirs');
+            }
+        }
+
+        if (!is_writable($behatdir)) {
+            throw new file_exception('storedfilecannotcreatefiledirs');
+        }
+
+        return $behatdir;
+    }
+
+    /**
+     * Returns the executable path
+     * @return string
+     */
+    public final static function get_behat_command() {
+        return 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'behat';
+    }
+
+    /**
+     * Checks if behat is set up and working
+     *
+     * It checks behat dependencies have been installed and runs
+     * the behat help command to ensure it works as expected
+     *
+     * @throw  Exception
+     * @param  boolean $checkphp Extra check for the PHP version
+     */
+    public static function check_behat_setup($checkphp = false) {
+        global $CFG;
+
+        // We don't check the PHP version if $CFG->behat_switchcompletely has been enabled.
+        if (empty($CFG->behat_switchcompletely) && $checkphp && version_compare(PHP_VERSION, '5.4.0', '<')) {
+            throw new Exception(get_string('wrongphpversion', 'tool_behat'));
+        }
+
+        // Moodle setting.
+        if (!self::are_behat_dependencies_installed()) {
+
+            $msg = get_string('wrongbehatsetup', 'tool_behat');
+
+            // With HTML.
+            $docslink = 'http://docs.moodle.org/dev/Acceptance_testing#Installation';
+            if (!CLI_SCRIPT) {
+                $docslink = html_writer::tag('a', $docslink, array('href' => $docslink, 'target' => '_blank'));
+            }
+            $msg .= '. ' . get_string('moreinfoin', 'tool_behat') . ' ' . $docslink;
+            notice($msg);
+        }
+
+        // Behat test command.
+        $currentcwd = getcwd();
+        chdir($CFG->dirroot);
+        exec(self::get_behat_command() . ' --help', $output, $code);
+        chdir($currentcwd);
+
+        if ($code != 0) {
+            notice(get_string('wrongbehatsetup', 'tool_behat'));
+        }
+    }
+
+    /**
+     * Has the site installed composer with --dev option
+     * @return boolean
+     */
+    public static function are_behat_dependencies_installed() {
+        if (!is_dir(__DIR__ . '/../../../vendor/behat')) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/lib/behat/classes/behat_config_manager.php b/lib/behat/classes/behat_config_manager.php
new file mode 100644 (file)
index 0000000..53bdbef
--- /dev/null
@@ -0,0 +1,261 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Utils to set Behat config
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/behat_command.php');
+require_once(__DIR__ . '/../../filestorage/file_exceptions.php');
+require_once(__DIR__ . '/../../testing/classes/tests_finder.php');
+
+/**
+ * Behat configuration manager
+ *
+ * Creates/updates Behat config files getting tests
+ * and steps from Moodle codebase
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_config_manager {
+
+    /**
+     * Updates a config file
+     *
+     * The tests runner and the steps definitions list uses different
+     * config files to avoid problems with concurrent executions.
+     *
+     * The steps definitions list can be filtered by component so it's
+     * behat.yml can be different from the dirroot one.
+     *
+     * @param string $component Restricts the obtained steps definitions to the specified component
+     * @param string $testsrunner If the config file will be used to run tests
+     * @throws file_exception
+     */
+    public static function update_config_file($component = '', $testsrunner = true) {
+        global $CFG;
+
+        // Behat must run with the whole set of features and steps definitions.
+        if ($testsrunner === true) {
+            $prefix = '';
+            $configfilepath = $CFG->dirroot . '/behat.yml';
+
+            // Alternative for steps definitions filtering.
+        } else {
+            $configfilepath = self::get_steps_list_config_filepath();
+            $prefix = $CFG->dirroot .'/';
+        }
+
+        // Gets all the components with features.
+        $features = array();
+        $components = tests_finder::get_components_with_tests('features');
+        if ($components) {
+            foreach ($components as $componentname => $path) {
+                $path = self::clean_path($path) . self::get_behat_tests_path();
+                if (empty($featurespaths[$path]) && file_exists($path)) {
+                    $uniquekey = str_replace('\\', '/', $path);
+                    $featurespaths[$uniquekey] = $path;
+                }
+            }
+            $features = array_values($featurespaths);
+        }
+
+        // Gets all the components with steps definitions.
+        $stepsdefinitions = array();
+        $steps = self::get_components_steps_definitions();
+        if ($steps) {
+            foreach ($steps as $key => $filepath) {
+                if ($component == '' || $component === $key) {
+                    $stepsdefinitions[$key] = $filepath;
+                }
+            }
+        }
+
+        // Behat config file specifing the main context class,
+        // the required Behat extensions and Moodle test wwwroot.
+        $contents = self::get_config_file_contents($prefix, $features, $stepsdefinitions);
+
+        // Stores the file.
+        if (!file_put_contents($configfilepath, $contents)) {
+            throw new file_exception('cannotcreatefile', $configfilepath);
+        }
+
+    }
+
+    /**
+     * Gets the list of Moodle steps definitions
+     *
+     * Class name as a key and the filepath as value
+     *
+     * Externalized from update_config_file() to use
+     * it from the steps definitions web interface
+     *
+     * @return array
+     */
+    public static function get_components_steps_definitions() {
+
+        $components = tests_finder::get_components_with_tests('stepsdefinitions');
+        if (!$components) {
+            return false;
+        }
+
+        $stepsdefinitions = array();
+        foreach ($components as $componentname => $componentpath) {
+            $componentpath = self::clean_path($componentpath);
+            $diriterator = new DirectoryIterator($componentpath . self::get_behat_tests_path());
+            $regite = new RegexIterator($diriterator, '|behat_.*\.php$|');
+
+            // All behat_*.php inside behat_config_manager::get_behat_tests_path() are added as steps definitions files.
+            foreach ($regite as $file) {
+                $key = $file->getBasename('.php');
+                $stepsdefinitions[$key] = $file->getPathname();
+            }
+        }
+
+        return $stepsdefinitions;
+    }
+
+    /**
+     * Returns the behat config file path used by the steps definition list
+     * @return string
+     */
+    public static function get_steps_list_config_filepath() {
+        return behat_command::get_behat_dir() . '/behat.yml';
+    }
+
+    /**
+     * Behat config file specifing the main context class,
+     * the required Behat extensions and Moodle test wwwroot.
+     *
+     * @param string $prefix The filesystem prefix
+     * @param array $features The system feature files
+     * @param array $stepsdefinitions The system steps definitions
+     * @return string
+     */
+    protected static function get_config_file_contents($prefix, $features, $stepsdefinitions) {
+        global $CFG;
+
+        // We require here when we are sure behat dependencies are available.
+        require_once($CFG->dirroot . '/vendor/autoload.php');
+
+        $config = array(
+            'default' => array(
+                'paths' => array(
+                    'features' => $prefix . 'lib/behat/features',
+                    'bootstrap' => $prefix . 'lib/behat/features/bootstrap',
+                ),
+                'context' => array(
+                    'class' => 'behat_init_context'
+                ),
+                'extensions' => array(
+                    'Behat\MinkExtension\Extension' => array(
+                        'base_url' => $CFG->behat_wwwroot,
+                        'goutte' => null,
+                        'selenium2' => null
+                    ),
+                    'Moodle\BehatExtension\Extension' => array(
+                        'features' => $features,
+                        'steps_definitions' => $stepsdefinitions
+                    )
+                )
+            )
+        );
+
+        // In case user defined overrides respect them over our default ones.
+        if (!empty($CFG->behat_config)) {
+            $config = self::merge_config($config, $CFG->behat_config);
+        }
+
+        return Symfony\Component\Yaml\Yaml::dump($config, 10, 2);
+    }
+
+    /**
+     * Overrides default config with local config values
+     *
+     * array_merge does not merge completely the array's values
+     *
+     * @param mixed $config The node of the default config
+     * @param mixed $localconfig The node of the local config
+     * @return mixed The merge result
+     */
+    protected static function merge_config($config, $localconfig) {
+
+        if (!is_array($config) && !is_array($localconfig)) {
+            return $localconfig;
+        }
+
+        // Local overrides also deeper default values.
+        if (is_array($config) && !is_array($localconfig)) {
+            return $localconfig;
+        }
+
+        foreach ($localconfig as $key => $value) {
+
+            // If defaults are not as deep as local values let locals override.
+            if (!is_array($config)) {
+                unset($config);
+            }
+
+            // Add the param if it doesn't exists or merge branches.
+            if (empty($config[$key])) {
+                $config[$key] = $value;
+            } else {
+                $config[$key] = self::merge_config($config[$key], $localconfig[$key]);
+            }
+        }
+
+        return $config;
+    }
+
+    /**
+     * Cleans the path returned by get_components_with_tests() to standarize it
+     *
+     * @see tests_finder::get_all_directories_with_tests() it returns the path including /tests/
+     * @param string $path
+     * @return string The string without the last /tests part
+     */
+    protected final static function clean_path($path) {
+
+        $path = rtrim($path, DIRECTORY_SEPARATOR);
+
+        $parttoremove = DIRECTORY_SEPARATOR . 'tests';
+
+        $substr = substr($path, strlen($path) - strlen($parttoremove));
+        if ($substr == $parttoremove) {
+            $path = substr($path, 0, strlen($path) - strlen($parttoremove));
+        }
+
+        return rtrim($path, DIRECTORY_SEPARATOR);
+    }
+
+    /**
+     * The relative path where components stores their behat tests
+     *
+     * @return string
+     */
+    protected final static function get_behat_tests_path() {
+        return DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'behat';
+    }
+
+}
diff --git a/lib/behat/classes/behat_util.php b/lib/behat/classes/behat_util.php
new file mode 100644 (file)
index 0000000..ec47b61
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Utils for behat-related stuff
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2012 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../testing/classes/util.php');
+
+require_once(__DIR__ . '/behat_command.php');
+require_once(__DIR__ . '/behat_config_manager.php');
+
+require_once(__DIR__ . '/../../filestorage/file_exceptions.php');
+require_once(__DIR__ . '/../../phpunit/bootstraplib.php');
+
+/**
+ * Init/reset utilities for Behat database and dataroot
+ *
+ * @package   core
+ * @category  test
+ * @copyright 2013 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_util extends testing_util {
+
+    /**
+     * Allows / disables the test environment to be accessed through the built-in server
+     *
+     * Built-in server must be started separately
+     *
+     * @param string $testenvironment enable|disable
+     */
+    public static function switchenvironment($testenvironment) {
+        if ($testenvironment == 'enable') {
+            self::start_test_mode();
+        } else if ($testenvironment == 'disable') {
+            self::stop_test_mode();
+        }
+    }
+
+    /**
+     * Checks if $CFG->behat_wwwroot is available
+     *
+     * @return boolean
+     */
+    public static function is_server_running() {
+        global $CFG;
+
+        $request = new curl();
+        $request->get($CFG->behat_wwwroot);
+        return (true && !$request->get_errno());
+    }
+
+    /**
+     * Checks whether the test database and dataroot is ready
+     * Stops execution if something went wrong
+     */
+    protected static function test_environment_problem() {
+        global $CFG;
+
+        // PHPUnit --diag returns nothing if the test environment is set up correctly.
+        exec('php ' . $CFG->dirroot . '/' . $CFG->admin . '/tool/phpunit/cli/util.php --diag', $output, $code);
+
+        // If something is not ready stop execution and display the CLI command output.
+        if ($code != 0) {
+            notice(get_string('phpunitenvproblem', 'tool_behat') . ': ' . implode(' ', $output));
+        }
+    }
+
+    /**
+     * Enables test mode
+     *
+     * Starts the test mode checking the composer installation and
+     * the phpunit test environment and updating the available
+     * features and steps definitions.
+     *
+     * Stores a file in dataroot/behat to allow Moodle to switch
+     * to the test environment when using cli-server (or $CFG->behat_switchcompletely)
+     *
+     * @throws file_exception
+     */
+    protected static function start_test_mode() {
+        global $CFG;
+
+        // Checks the behat set up and the PHP version.
+        behat_command::check_behat_setup(true);
+
+        // Check that PHPUnit test environment is correctly set up.
+        self::test_environment_problem();
+
+        // Updates all the Moodle features and steps definitions.
+        behat_config_manager::update_config_file();
+
+        if (self::is_test_mode_enabled()) {
+            debugging('Test environment was already enabled');
+            return;
+        }
+
+        $behatdir = behat_command::get_behat_dir();
+
+        $contents = '$CFG->behat_wwwroot, $CFG->phpunit_prefix and $CFG->phpunit_dataroot' .
+            ' are currently used as $CFG->wwwroot, $CFG->prefix and $CFG->dataroot';
+        $filepath = $behatdir . '/test_environment_enabled.txt';
+        if (!file_put_contents($filepath, $contents)) {
+            throw new file_exception('cannotcreatefile', $filepath);
+        }
+        chmod($filepath, $CFG->directorypermissions);
+    }
+
+    /**
+     * Disables test mode
+     * @throws file_exception
+     */
+    protected static function stop_test_mode() {
+
+        $testenvfile = self::get_test_filepath();
+
+        if (!self::is_test_mode_enabled()) {
+            debugging('Test environment was already disabled');
+        } else {
+            if (!unlink($testenvfile)) {
+                throw new file_exception('cannotdeletetestenvironmentfile');
+            }
+        }
+    }
+
+    /**
+     * Checks whether test environment is enabled or disabled
+     *
+     * To check is the current script is running in the test
+     * environment
+     *
+     * @see tool_behat::is_test_environment_running()
+     * @return bool
+     */
+    public static function is_test_mode_enabled() {
+
+        $testenvfile = self::get_test_filepath();
+        if (file_exists($testenvfile)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if Moodle is currently running with the test database and dataroot
+     * @return bool
+     */
+    public static function is_test_environment_running() {
+        global $CFG;
+
+        if (!empty($CFG->originaldataroot)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the path to the file which specifies if test environment is enabled
+     *
+     * The file is in dataroot/behat/ but we need to
+     * know if test mode is running because then we swap
+     * it to phpunit_dataroot and we need the original value
+     *
+     * @return string
+     */
+    protected final static function get_test_filepath() {
+        global $CFG;
+
+        if (self::is_test_environment_running()) {
+            $prefix = $CFG->originaldataroot;
+        } else {
+            $prefix = $CFG->dataroot;
+        }
+
+        return $prefix . '/behat/test_environment_enabled.txt';
+    }
+
+}