/** * Ensures the behat dir exists in moodledata * @param int $runprocess run process for which behat dir is returned. * @return string Full path */ public static function get_behat_dir($runprocess = 0) { global $CFG; // If not set then return empty string. if (!isset($CFG->behat_dataroot)) { return ""; } if (empty($runprocess)) { $behatdir = $CFG->behat_dataroot . '/behat'; } else { if (isset($CFG->behat_parallel_run[$runprocess - 1]['behat_dataroot'])) { $behatdir = $CFG->behat_parallel_run[$runprocess - 1]['behat_dataroot'] . '/behat'; } else { $behatdir = $CFG->behat_dataroot . $runprocess . '/behat'; } } if (!is_dir($behatdir)) { if (!mkdir($behatdir, $CFG->directorypermissions, true)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Directory ' . $behatdir . ' can not be created'); } } if (!is_writable($behatdir)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Directory ' . $behatdir . ' is not writable'); } return $behatdir; }
/** * Ensures the behat dir exists in maharadata * @return string Full path */ public static function get_behat_dir() { global $CFG; $behatdir = $CFG->behat_dataroot . '/behat'; if (!is_dir($behatdir)) { if (!mkdir($behatdir, $CFG->directorypermissions, true)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Directory ' . $behatdir . ' can not be created'); } } if (!is_writable($behatdir)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Directory ' . $behatdir . ' is not writable'); } return $behatdir; }
} closedir($dh); unset($dh); unset($file); } if (defined('BEHAT_UTIL')) { // Now we create dataroot directory structure for behat tests. testing_initdataroot($CFG->behat_dataroot, 'behat'); } else { behat_error(BEHAT_EXITCODE_INSTALL); } } if (!defined('BEHAT_UTIL') and !defined('BEHAT_TEST')) { // Somebody tries to access test site directly, tell them if not enabled. if (!file_exists($CFG->behat_dataroot . '/behat/test_environment_enabled.txt')) { behat_error(BEHAT_EXITCODE_CONFIG, 'Behat is configured but not enabled on this test site.'); } } // Constant used to inform that the behat test site is being used, // this includes all the processes executed by the behat CLI command like // the site reset, the steps executed by the browser drivers when simulating // a user session and a real session when browsing manually to $CFG->behat_wwwroot // like the browser driver does automatically. // Different from BEHAT_TEST as only this last one can perform CLI // actions like reset the site or use data generators. define('BEHAT_SITE_RUNNING', true); // Clean extra config.php settings. behat_clean_init_config(); // Now we can begin switching $CFG->X for $CFG->behat_X. $CFG->wwwroot = $CFG->behat_wwwroot; $CFG->prefix = $CFG->behat_prefix;
/** * Return blacklisted contexts or features for a theme, as defined in blacklist.json. * * @param string $theme themename * @param string $testtype test type (contexts|features) * @return array list of blacklisted contexts or features */ protected function get_blacklisted_tests_for_theme($theme, $testtype) { global $CFG; $themetestpath = $CFG->dirroot . DIRECTORY_SEPARATOR . "theme" . DIRECTORY_SEPARATOR . $theme . self::get_behat_tests_path(); if (file_exists($themetestpath . DIRECTORY_SEPARATOR . 'blacklist.json')) { // Blacklist file exist. Leave it for last to clear the feature and contexts. $blacklisttests = @json_decode(file_get_contents($themetestpath . DIRECTORY_SEPARATOR . 'blacklist.json'), true); if (empty($blacklisttests)) { behat_error(BEHAT_EXITCODE_REQUIREMENT, $themetestpath . DIRECTORY_SEPARATOR . 'blacklist.json is empty'); } // If features or contexts not defined then no problem. if (!isset($blacklisttests[$testtype])) { $blacklisttests[$testtype] = array(); } return $blacklisttests[$testtype]; } return array(); }
/** * Disables test mode * @throws coding_exception * @return void */ public static function stop_test_mode() { if (!defined('BEHAT_UTIL')) { throw new coding_exception('This method can be only used by Behat CLI tool'); } $testenvfile = self::get_test_file_path(); if (!self::is_test_mode_enabled()) { echo "Test environment was already disabled\n"; } else { if (!unlink($testenvfile)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Can not delete test environment file'); } } }
/** * Checks that the behat config vars are properly set. * * @return void Stops execution with error code if something goes wrong. */ function behat_check_config_vars() { global $CFG; // Verify prefix value. if (empty($CFG->behat_dbprefix)) { behat_error(BEHAT_EXITCODE_CONFIG, 'Define $CFG->behat_dbprefix in config.php'); } if (!empty($CFG->dbprefix) and $CFG->behat_dbprefix == $CFG->dbprefix) { behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dbprefix in config.php must be different from $CFG->dbprefix'); } if (!empty($CFG->phpunit_dbprefix) and $CFG->behat_dbprefix == $CFG->phpunit_dbprefix) { behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dbprefix in config.php must be different from $CFG->phpunit_dbprefix'); } // Verify behat wwwroot value. if (empty($CFG->behat_wwwroot)) { behat_error(BEHAT_EXITCODE_CONFIG, 'Define $CFG->behat_wwwroot in config.php'); } if (!empty($CFG->wwwroot) and $CFG->behat_wwwroot == $CFG->wwwroot) { behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_wwwroot in config.php must be different from $CFG->wwwroot'); } // Verify behat dataroot value. if (empty($CFG->behat_dataroot)) { behat_error(BEHAT_EXITCODE_CONFIG, 'Define $CFG->behat_dataroot in config.php'); } if (!file_exists($CFG->behat_dataroot)) { $permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777; umask(0); if (!mkdir($CFG->behat_dataroot, $permissions, true)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created'); } } $CFG->behat_dataroot = realpath($CFG->behat_dataroot); if (empty($CFG->behat_dataroot) or !is_dir($CFG->behat_dataroot) or !is_writable($CFG->behat_dataroot)) { behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dataroot in config.php must point to an existing writable directory'); } if (!empty($CFG->dataroot) and $CFG->behat_dataroot == realpath($CFG->dataroot)) { behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dataroot in config.php must be different from $CFG->dataroot'); } if (!empty($CFG->phpunit_dataroot) and $CFG->behat_dataroot == realpath($CFG->phpunit_dataroot)) { behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dataroot in config.php must be different from $CFG->phpunit_dataroot'); } }
/** * 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 is different from the $CFG->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 * @return void */ public static function update_config_file($component = '', $testsrunner = true) { global $CFG; // Behat must have a separate behat.yml to have access to the whole set of features and steps definitions. if ($testsrunner === true) { $configfilepath = behat_command::get_behat_dir() . '/behat.yml'; } else { // Alternative for steps definitions filtering, one for each user. $configfilepath = self::get_steps_list_config_filepath(); } // 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)) { // Standarizes separator (some dirs. comes with OS-dependant separator). $uniquekey = str_replace('\\', '/', $path); $featurespaths[$uniquekey] = $path; } } $features = array_values($featurespaths); } // Optionally include features from additional directories. if (!empty($CFG->behat_additionalfeatures)) { $features = array_merge($features, array_map("realpath", $CFG->behat_additionalfeatures)); } // 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; } } } // We don't want the deprecated steps definitions here. if (!$testsrunner) { unset($stepsdefinitions['behat_deprecated']); } // Behat config file specifing the main context class, // the required Behat extensions and Moodle test wwwroot. $contents = self::get_config_file_contents($features, $stepsdefinitions); // Stores the file. if (!file_put_contents($configfilepath, $contents)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $configfilepath . ' can not be created'); } }
if (!file_exists($CFG->behat_dataroot)) { if (!mkdir($CFG->behat_dataroot, $CFG->directorypermissions)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created'); } } if (!is_dir($CFG->behat_dataroot) || !is_writable($CFG->behat_dataroot)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory has no permissions or is not a directory'); } // Check that the directory does not contains other things. if (!file_exists("{$CFG->behat_dataroot}/behattestdir.txt")) { if ($dh = opendir($CFG->behat_dataroot)) { while (($file = readdir($dh)) !== false) { if ($file === 'behat' or $file === '.' or $file === '..' or $file === '.DS_Store') { continue; } behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dataroot directory is not empty, ensure this is the directory where you want to install behat test dataroot'); } closedir($dh); unset($dh); unset($file); } // Now we create dataroot directory structure for behat tests. testing_initdataroot($CFG->behat_dataroot, 'behat'); } // Overrides vars with behat-test ones. $vars = array('wwwroot', 'prefix', 'dataroot'); foreach ($vars as $var) { $CFG->{$var} = $CFG->{'behat_' . $var}; } // Clean $CFG extra values before performing any action. behat_clean_init_config();
/** * Print update progress as dots for updating feature file step list. * * @param Process $process process executing update step command. * @param string $featurestepfile feature step file in which steps will be saved. * @return int exitcode. */ function print_update_step_output($process, $featurestepfile) { $printedlength = 0; echo "Updating steps feature file for parallel behat runs" . PHP_EOL; // Show progress while running command. while ($process->isRunning()) { usleep(10000); $op = $process->getIncrementalOutput(); if (trim($op)) { echo "."; $printedlength++; if ($printedlength > 70) { $printedlength = 0; echo PHP_EOL; } } } // If any error then exit. $exitcode = $process->getExitCode(); // Output err. if ($exitcode != 0) { echo $process->getErrorOutput(); exit($exitcode); } // Extract features with step info and save it in file. $featuresteps = $process->getOutput(); $featuresteps = explode(PHP_EOL, $featuresteps); $realroot = realpath(__DIR__ . '/../../../../') . '/'; foreach ($featuresteps as $featurestep) { if (trim($featurestep)) { $step = explode("::", $featurestep); $step[0] = str_replace($realroot, '', $step[0]); $steps[$step[0]] = $step[1]; } } arsort($steps); if (!@file_put_contents($featurestepfile, json_encode($steps, JSON_PRETTY_PRINT))) { behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $featurestepfile . ' can not be created'); $exitcode = -1; } echo PHP_EOL . "Updated step count in " . $featurestepfile . PHP_EOL; return $exitcode; }
/** * Checks that the behat config vars are properly set. * * @return void Stops execution with error code if something goes wrong. */ function behat_check_config_vars() { global $CFG; // CFG->behat_prefix must be set and with value different than CFG->prefix and phpunit_prefix. if (empty($CFG->behat_prefix) || $CFG->behat_prefix == $CFG->prefix || !empty($CFG->phpunit_prefix) && $CFG->behat_prefix == $CFG->phpunit_prefix) { behat_error(BEHAT_EXITCODE_CONFIG, 'Define $CFG->behat_prefix in config.php with a value different than $CFG->prefix and $CFG->phpunit_prefix'); } // $CFG->behat_wwwroot must be different than CFG->wwwroot if it is set, it may not be set as // it can take the default value and we should also consider that will have the same value than // $CFG->wwwroot if $CFG->behat_switchcompletely is set. if (!empty($CFG->behat_wwwroot) && $CFG->behat_wwwroot == $CFG->wwwroot && empty($CFG->behat_switchcompletely)) { behat_error(BEHAT_EXITCODE_CONFIG, 'Define $CFG->behat_wwwroot in config.php with a value different than $CFG->wwwroot'); } // CFG->behat_dataroot must be set and with value different than CFG->dataroot and phpunit_dataroot. $CFG->dataroot = realpath($CFG->dataroot); if (!empty($CFG->behat_dataroot) && is_dir($CFG->behat_dataroot)) { $CFG->behat_dataroot = realpath($CFG->behat_dataroot); } if (empty($CFG->behat_dataroot) || $CFG->behat_dataroot == $CFG->dataroot || !empty($CFG->phpunit_dataroot) && is_dir($CFG->phpunit_dataroot) && $CFG->behat_dataroot == realpath($CFG->phpunit_dataroot)) { behat_error(BEHAT_EXITCODE_CONFIG, 'Define $CFG->behat_dataroot in config.php with a value different than $CFG->dataroot and $CFG->phpunit_dataroot'); } }
/** * Checks if behat is set up and working * * Uses notice() instead of behat_error() because is * also called from web interface * * It checks behat dependencies have been installed and runs * the behat help command to ensure it works as expected * * @param bool $checkphp Extra check for the PHP version * @return void */ public static function check_behat_setup($checkphp = false) { global $CFG; // We don't check the PHP version if $CFG->behat_switchcompletely has been enabled. // Here we are in CLI. if (empty($CFG->behat_switchcompletely) && $checkphp && version_compare(PHP_VERSION, '5.4.0', '<')) { behat_error(BEHAT_EXITCODE_REQUIREMENT, 'PHP 5.4 is required. See config-dist.php for possible alternatives'); } // Moodle setting. if (!self::are_behat_dependencies_installed()) { $msg = get_string('wrongbehatsetup', 'tool_behat'); // With HTML. $docslink = self::DOCS_URL . '#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. list($output, $code) = self::run(' --help'); if ($code != 0) { notice(get_string('wrongbehatsetup', 'tool_behat')); } // Checking behat dataroot existence otherwise notice about admin/tool/behat/cli/util.php. if (empty($CFG->behat_dataroot) || !is_dir($CFG->behat_dataroot) || !is_writable($CFG->behat_dataroot)) { notice(get_string('runclitool', 'tool_behat', 'php admin/tool/behat/cli/util.php')); } }
/** * 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 is different from the $CFG->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 * @param string $tags features files including tags. * @param bool $themesuitewithallfeatures if only theme specific features need to be included in the suite. * @param int $parallelruns number of parallel runs. * @param int $run current run for which config needs to be updated. * @return void */ public static function update_config_file($component = '', $testsrunner = true, $tags = '', $themesuitewithallfeatures = false, $parallelruns = 0, $run = 0) { global $CFG; // Behat must have a separate behat.yml to have access to the whole set of features and steps definitions. if ($testsrunner === true) { $configfilepath = behat_command::get_behat_dir() . '/behat.yml'; } else { // Alternative for steps definitions filtering, one for each user. $configfilepath = self::get_steps_list_config_filepath(); } $behatconfigutil = self::get_behat_config_util(); $behatconfigutil->set_theme_suite_to_include_core_features($themesuitewithallfeatures); $behatconfigutil->set_tag_for_feature_filter($tags); // Gets all the components with features, if running the tests otherwise not required. $features = array(); if ($testsrunner) { $features = $behatconfigutil->get_components_features(); } // Gets all the components with steps definitions. $stepsdefinitions = $behatconfigutil->get_components_contexts($component); // We don't want the deprecated steps definitions here. if (!$testsrunner) { unset($stepsdefinitions['behat_deprecated']); } // Get current run. if (empty($run) && $run !== false && !empty($CFG->behatrunprocess)) { $run = $CFG->behatrunprocess; } // Get number of parallel runs if not passed. if (empty($parallelruns) && $parallelruns !== false) { $parallelruns = self::get_parallel_test_runs(); } // Behat config file specifing the main context class, // the required Behat extensions and Moodle test wwwroot. $contents = $behatconfigutil->get_config_file_contents($features, $stepsdefinitions, $tags, $parallelruns, $run); // Stores the file. if (!file_put_contents($configfilepath, $contents)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $configfilepath . ' can not be created'); } }
/** * Checks if behat is set up and working * * Uses notice() instead of behat_error() because is * also called from web interface * * It checks behat dependencies have been installed and runs * the behat help command to ensure it works as expected * * @param bool $checkphp Extra check for the PHP version * @return int Error code or 0 if all ok */ public static function behat_setup_problem($checkphp = false) { global $CFG; // We don't check the PHP version if $CFG->behat_switchcompletely has been enabled. // Here we are in CLI. if (empty($CFG->behat_switchcompletely) && $checkphp && version_compare(PHP_VERSION, '5.4.0', '<')) { behat_error(BEHAT_EXITCODE_REQUIREMENT, 'PHP 5.4 is required. See config-dist.php for possible alternatives'); } $clibehaterrorstr = "Behat dependencies not installed. Ensure you ran the composer installer. " . self::DOCS_URL . "#Installation\n"; // Moodle setting. if (!self::are_behat_dependencies_installed()) { // With HTML. if (!CLI_SCRIPT) { $msg = get_string('wrongbehatsetup', 'tool_behat'); $docslink = self::DOCS_URL . '#Installation'; $docslink = html_writer::tag('a', $docslink, array('href' => $docslink, 'target' => '_blank')); $msg .= get_string('moreinfoin', 'tool_behat', $docslink); } else { $msg = $clibehaterrorstr; } self::output_msg($msg); return BEHAT_EXITCODE_COMPOSER; } // Behat test command. list($output, $code) = self::run(' --help'); if ($code != 0) { // Returning composer error code to avoid conflicts with behat and moodle error codes. if (!CLI_SCRIPT) { $msg = get_string('wrongbehatsetup', 'tool_behat'); } else { $msg = $clibehaterrorstr; } self::output_msg($msg); return BEHAT_EXITCODE_COMPOSER; } // Checking behat dataroot existence otherwise echo about admin/tool/behat/cli/init.php. if (empty($CFG->behat_dataroot) || !is_dir($CFG->behat_dataroot) || !is_writable($CFG->behat_dataroot)) { self::output_msg(get_string('runclitool', 'tool_behat', 'php admin/tool/behat/cli/init.php')); return BEHAT_EXITCODE_CONFIG; } return 0; }
/** * 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 plugin so it's * behat.yml is different from the $CFG->docroot one. * * @param string $plugin Restricts the obtained steps definitions to the specified plugin * @param string $testsrunner If the config file will be used to run tests * @return void */ public static function update_config_file($plugin = '', $testsrunner = true) { global $CFG; // Behat must have a separate behat.yml to have access to the whole set of features and steps definitions. if ($testsrunner === true) { $configfilepath = BehatCommand::get_behat_dir() . '/behat.yml'; } else { // Alternative for steps definitions filtering, one for each user. $configfilepath = self::get_steps_list_config_filepath(); } // Get core features $features = array(dirname(dirname(dirname(dirname(dirname(__DIR__))))) . '/test/behat/features'); // Gets all the plugins with features. $plugins = TestsFinder::get_plugins_with_tests('features'); if ($plugins) { foreach ($plugins as $pluginname => $path) { $path = self::clean_path($path) . self::get_behat_tests_path(); if (empty($featurespaths[$path]) && file_exists($path)) { // Standarizes separator (some dirs. comes with OS-dependant separator). $uniquekey = str_replace('\\', '/', $path); $featurespaths[$uniquekey] = $path; } } $features = array_merge($features, array_values($featurespaths)); } // Optionally include features from additional directories. if (!empty($CFG->behat_additionalfeatures)) { $features = array_merge($features, array_map("realpath", $CFG->behat_additionalfeatures)); } $stepsdefinitions = array(); // Find step definitions from core. They must be in the folder $MAHARA_ROOT/test/behat/stepdefinitions // The file name must be /^Behat[A-z0-9_]+\.php$/ $regite = new RegexIterator(new DirectoryIterator(get_mahararoot_dir() . '/test/behat/stepdefinitions'), '|^Behat[A-z0-9_\\-]+\\.php$|'); foreach ($regite as $file) { $key = $file->getBasename('.php'); $stepsdefinitions[$key] = $file->getPathname(); } // Gets all the plugins with steps definitions. $steps = self::get_plugins_steps_definitions(); if ($steps) { foreach ($steps as $key => $filepath) { if ($plugin === '' || $plugin === $key) { $stepsdefinitions[$key] = $filepath; } } } // We don't want the deprecated steps definitions here. if (!$testsrunner) { unset($stepsdefinitions['behat_deprecated']); } // Behat config file specifing the main context class, // the required Behat extensions and Mahara test wwwroot. $contents = self::get_config_file_contents($features, $stepsdefinitions); // Stores the file. if (!file_put_contents($configfilepath, $contents)) { behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $configfilepath . ' can not be created'); } }