first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
<?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/>.
/**
* Backend generic code.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Backend generic code for all tool_generator commands.
*
* @abstract
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class tool_generator_backend {
/**
* @var int Lowest (smallest) size index
*/
const MIN_SIZE = 0;
/**
* @var int Highest (largest) size index
*/
const MAX_SIZE = 5;
/**
* @var int Default size index
*/
const DEFAULT_SIZE = 3;
/**
* @var bool True if we want a fixed dataset or false to generate random data
*/
protected $fixeddataset;
/**
* @var int|bool Maximum number of bytes for file.
*/
protected $filesizelimit;
/**
* @var bool True if displaying progress
*/
protected $progress;
/**
* @var int Epoch time at which current step (current set of dots) started
*/
protected $starttime;
/**
* @var int Size code (index in the above arrays)
*/
protected $size;
/**
* @var progress_bar progressbar
*/
protected $progressbar;
/**
* @var string Part of the language string.
*/
protected $langstring;
/**
* @var string Module for the language string.
*/
protected $module;
/**
* @var string|object|array|int Optional language string parameters.
*/
protected $aparam;
/**
* Generic generator class
*
* @param int $size Size as numeric index
* @param bool $fixeddataset To use fixed or random data
* @param int|bool $filesizelimit The max number of bytes for a generated file
* @param bool $progress True if progress information should be displayed
* @throws coding_exception If parameters are invalid
*/
public function __construct($size, $fixeddataset = false, $filesizelimit = false, $progress = true) {
// Check parameter.
if ($size < self::MIN_SIZE || $size > self::MAX_SIZE) {
throw new coding_exception('Invalid size');
}
// Set parameters.
$this->size = $size;
$this->fixeddataset = $fixeddataset;
$this->filesizelimit = $filesizelimit;
$this->progress = $progress;
}
/**
* Converts a size name into the numeric constant.
*
* @param string $sizename Size name e.g. 'L'
* @return int Numeric version
* @throws coding_exception If the size name is not known
*/
public static function size_for_name($sizename) {
for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
if ($sizename == get_string('shortsize_' . $size, 'tool_generator')) {
return $size;
}
}
throw new coding_exception("Unknown size name '$sizename'");
}
/**
* Displays information as part of progress.
*
* @param string $langstring Part of langstring (after progress_)
* @param mixed $a Optional lang string parameters
* @param bool $leaveopen If true, doesn't close LI tag (ready for dots)
* @param string $module module for language string
*/
public function log(string $langstring, $a = null, bool $leaveopen = false, string $module = 'tool_generator'): void {
if (!$this->progress) {
return;
}
$this->langstring = $langstring;
$this->module = $module;
$this->aparam = $a;
$this->starttime = microtime(true);
$this->progressbar = new progress_bar();
$this->progressbar->create();
}
/**
* Outputs dots. There is up to one dot per second. Once a minute, it
* displays a percentage.
*
* @param int $number Number of completed items
* @param int $total Total number of items to complete
*/
public function dot(int $number, int $total): void {
if (!$this->progress) {
return;
}
$now = time();
// Update time limit so PHP doesn't time out.
if (!CLI_SCRIPT) {
core_php_time_limit::raise(120);
}
$status = get_string('progress_' . $this->langstring, $this->module, $number);
$this->progressbar->update($number, $total, $status);
}
/**
* Ends a log string that was started using log function with $leaveopen.
*/
public function end_log(): void {
if (!$this->progress) {
return;
}
$status = get_string('progress_' . $this->langstring, $this->module, $this->aparam);
$done = get_string('done', 'tool_generator', round(microtime(true) - $this->starttime, 1));
$this->progressbar->update_full(100, $status . ' - ' . $done);
}
}
@@ -0,0 +1,621 @@
<?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/>.
/**
* tool_generator course backend code.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Backend code for the 'make large course' tool.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_generator_course_backend extends tool_generator_backend {
/**
* @var array Number of sections in course
*/
private static $paramsections = array(1, 10, 100, 500, 1000, 2000);
/**
* @var array Number of assignments in course
*/
private static $paramassignments = array(1, 10, 100, 500, 1000, 2000);
/**
* @var array Number of Page activities in course
*/
private static $parampages = array(1, 50, 200, 1000, 5000, 10000);
/**
* @var array Number of students enrolled in course
*/
private static $paramusers = array(1, 100, 1000, 10000, 50000, 100000);
/**
* Total size of small files: 1KB, 1MB, 10MB, 100MB, 1GB, 2GB.
*
* @var array Number of small files created in a single file activity
*/
private static $paramsmallfilecount = array(1, 64, 128, 1024, 16384, 32768);
/**
* @var array Size of small files (to make the totals into nice numbers)
*/
private static $paramsmallfilesize = array(1024, 16384, 81920, 102400, 65536, 65536);
/**
* Total size of big files: 8KB, 8MB, 80MB, 800MB, 8GB, 16GB.
*
* @var array Number of big files created as individual file activities
*/
private static $parambigfilecount = array(1, 2, 5, 10, 10, 10);
/**
* @var array Size of each large file
*/
private static $parambigfilesize = array(8192, 4194304, 16777216, 83886080,
858993459, 1717986918);
/**
* @var array Number of forum discussions
*/
private static $paramforumdiscussions = array(1, 10, 100, 500, 1000, 2000);
/**
* @var array Number of forum posts per discussion
*/
private static $paramforumposts = array(2, 2, 5, 10, 10, 10);
/**
* @var array Number of assignments in course
*/
private static $paramactivities = array(1, 10, 100, 500, 1000, 2000);
/**
* @var string Course shortname
*/
private $shortname;
/**
* @var string Course fullname.
*/
private $fullname = "";
/**
* @var string Course summary.
*/
private $summary = "";
/**
* @var string Course summary format, defaults to FORMAT_HTML.
*/
private $summaryformat = FORMAT_HTML;
/**
* @var testing_data_generator Data generator
*/
protected $generator;
/**
* @var stdClass Course object
*/
private $course;
/**
* @var array Array from test user number (1...N) to userid in database
*/
private $userids;
/**
* @var array $additionalmodules
*/
private $additionalmodules;
/**
* Constructs object ready to create course.
*
* @param string $shortname Course shortname
* @param int $size Size as numeric index
* @param bool $fixeddataset To use fixed or random data
* @param int|bool $filesizelimit The max number of bytes for a generated file
* @param bool $progress True if progress information should be displayed
* @param array $additionalmodules potential additional modules to be added (quiz, bigbluebutton...)
*/
public function __construct(
$shortname,
$size,
$fixeddataset = false,
$filesizelimit = false,
$progress = true,
$fullname = null,
$summary = null,
$summaryformat = FORMAT_HTML,
$additionalmodules = []
) {
// Set parameters.
$this->shortname = $shortname;
// We can't allow fullname to be set to an empty string.
if (empty($fullname)) {
$this->fullname = get_string(
'fullname',
'tool_generator',
array(
'size' => get_string('shortsize_' . $size, 'tool_generator')
)
);
} else {
$this->fullname = $fullname;
}
// Summary, on the other hand, should be empty-able.
if (!is_null($summary)) {
$this->summary = $summary;
$this->summaryformat = $summaryformat;
}
$this->additionalmodules = $additionalmodules;
parent::__construct($size, $fixeddataset, $filesizelimit, $progress);
}
/**
* Returns the relation between users and course sizes.
*
* @return array
*/
public static function get_users_per_size() {
return self::$paramusers;
}
/**
* Gets a list of size choices supported by this backend.
*
* @return array List of size (int) => text description for display
*/
public static function get_size_choices() {
$options = array();
for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
$options[$size] = get_string('coursesize_' . $size, 'tool_generator');
}
return $options;
}
/**
* Checks that a shortname is available (unused).
*
* @param string $shortname Proposed course shortname
* @return string An error message if the name is unavailable or '' if OK
*/
public static function check_shortname_available($shortname) {
global $DB;
$fullname = $DB->get_field('course', 'fullname',
array('shortname' => $shortname), IGNORE_MISSING);
if ($fullname !== false) {
// I wanted to throw an exception here but it is not possible to
// use strings from moodle.php in exceptions, and I didn't want
// to duplicate the string in tool_generator, so I changed this to
// not use exceptions.
return get_string('shortnametaken', 'moodle', $fullname);
}
return '';
}
/**
* Runs the entire 'make' process.
*
* @return int Course id
*/
public function make() {
global $DB, $CFG, $USER;
require_once($CFG->dirroot . '/lib/phpunit/classes/util.php');
raise_memory_limit(MEMORY_EXTRA);
if ($this->progress && !CLI_SCRIPT) {
echo html_writer::start_tag('ul');
}
$entirestart = microtime(true);
// Get generator.
$this->generator = phpunit_util::get_data_generator();
// Make course.
$this->course = $this->create_course();
$this->create_assignments();
$this->create_pages();
$this->create_small_files();
$this->create_big_files();
// Create users as late as possible to reduce regarding in the gradebook.
$this->create_users();
$this->create_forum();
// Let plugins hook into user settings navigation.
$pluginsfunction = get_plugins_with_function('course_backend_generator_create_activity');
foreach ($pluginsfunction as $plugintype => $plugins) {
foreach ($plugins as $pluginname => $pluginfunction) {
if (in_array($pluginname, $this->additionalmodules)) {
$pluginfunction($this, $this->generator, $this->course->id, self::$paramactivities[$this->size]);
}
}
}
// We are checking 'enroladminnewcourse' setting to decide to enrol admins or not.
if (!empty($CFG->creatornewroleid) && !empty($CFG->enroladminnewcourse) && is_siteadmin($USER->id)) {
// Deal with course creators - enrol them internally with default role.
enrol_try_internal_enrol($this->course->id, $USER->id, $CFG->creatornewroleid);
}
// Log total time.
$this->log('coursecompleted', round(microtime(true) - $entirestart, 1));
$this->end_log();
if ($this->progress && !CLI_SCRIPT) {
echo html_writer::end_tag('ul');
}
return $this->course->id;
}
/**
* Creates the actual course.
*
* @return stdClass Course record
*/
private function create_course() {
$this->log('createcourse', $this->shortname);
$courserecord = array(
'shortname' => $this->shortname,
'fullname' => $this->fullname,
'numsections' => self::$paramsections[$this->size],
'startdate' => usergetmidnight(time())
);
if (strlen($this->summary) > 0) {
$courserecord['summary'] = $this->summary;
$courserecord['summary_format'] = $this->summaryformat;
}
$return = $this->generator->create_course($courserecord, array('createsections' => true));
$this->end_log();
return $return;
}
/**
* Creates a number of user accounts and enrols them on the course.
* Note: Existing user accounts that were created by this system are
* reused if available.
*/
private function create_users() {
global $DB;
// Work out total number of users.
$count = self::$paramusers[$this->size];
// Get existing users in order. We will 'fill up holes' in this up to
// the required number.
$this->log('checkaccounts', $count);
$this->end_log();
$nextnumber = 1;
$rs = $DB->get_recordset_select('user', $DB->sql_like('username', '?'),
array('tool_generator_%'), 'username', 'id, username');
foreach ($rs as $rec) {
// Extract number from username.
$matches = array();
if (!preg_match('~^tool_generator_([0-9]{6})$~', $rec->username, $matches)) {
continue;
}
$number = (int)$matches[1];
// Create missing users in range up to this.
if ($number != $nextnumber) {
$this->create_user_accounts($nextnumber, min($number - 1, $count));
} else {
$this->userids[$number] = (int)$rec->id;
}
// Stop if we've got enough users.
$nextnumber = $number + 1;
if ($number >= $count) {
break;
}
}
$rs->close();
// Create users from end of existing range.
if ($nextnumber <= $count) {
$this->create_user_accounts($nextnumber, $count);
}
// Assign all users to course.
$this->log('enrol', $count, true);
$enrolplugin = enrol_get_plugin('manual');
$instances = enrol_get_instances($this->course->id, true);
foreach ($instances as $instance) {
if ($instance->enrol === 'manual') {
break;
}
}
if ($instance->enrol !== 'manual') {
throw new coding_exception('No manual enrol plugin in course');
}
$role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
for ($number = 1; $number <= $count; $number++) {
// Enrol user.
$enrolplugin->enrol_user($instance, $this->userids[$number], $role->id);
$this->dot($number, $count);
}
// Sets the pointer at the beginning to be aware of the users we use.
reset($this->userids);
$this->end_log();
}
/**
* Creates user accounts with a numeric range.
*
* @param int $first Number of first user
* @param int $last Number of last user
*/
private function create_user_accounts($first, $last) {
global $CFG;
$count = $last - $first + 1;
$this->log('createusers', $count, true);
$done = 0;
for ($number = $first; $number <= $last; $number++, $done++) {
// Work out username with 6-digit number.
$textnumber = (string)$number;
while (strlen($textnumber) < 6) {
$textnumber = '0' . $textnumber;
}
$username = 'tool_generator_' . $textnumber;
// Create user account.
$record = array('username' => $username, 'idnumber' => $number);
// We add a user password if it has been specified.
if (!empty($CFG->tool_generator_users_password)) {
$record['password'] = $CFG->tool_generator_users_password;
}
$user = $this->generator->create_user($record);
$this->userids[$number] = (int)$user->id;
$this->dot($done, $count);
}
$this->end_log();
}
/**
* Creates a number of Assignment activities.
*/
private function create_assignments() {
// Set up generator.
$assigngenerator = $this->generator->get_plugin_generator('mod_assign');
// Create assignments.
$number = self::$paramassignments[$this->size];
$this->log('createassignments', $number, true);
for ($i = 0; $i < $number; $i++) {
$record = array('course' => $this->course);
$options = array('section' => $this->get_target_section());
$assigngenerator->create_instance($record, $options);
$this->dot($i, $number);
}
$this->end_log();
}
/**
* Creates a number of Page activities.
*/
private function create_pages() {
// Set up generator.
$pagegenerator = $this->generator->get_plugin_generator('mod_page');
// Create pages.
$number = self::$parampages[$this->size];
$this->log('createpages', $number, true);
for ($i = 0; $i < $number; $i++) {
$record = array('course' => $this->course);
$options = array('section' => $this->get_target_section());
$pagegenerator->create_instance($record, $options);
$this->dot($i, $number);
}
$this->end_log();
}
/**
* Creates one resource activity with a lot of small files.
*/
private function create_small_files() {
$count = self::$paramsmallfilecount[$this->size];
$this->log('createsmallfiles', $count, true);
// Create resource with default textfile only.
$resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
$record = array('course' => $this->course,
'name' => get_string('smallfiles', 'tool_generator'));
$options = array('section' => 0);
$resource = $resourcegenerator->create_instance($record, $options);
// Add files.
$fs = get_file_storage();
$context = context_module::instance($resource->cmid);
$filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/');
for ($i = 0; $i < $count; $i++) {
$filerecord['filename'] = 'smallfile' . $i . '.dat';
// Generate random binary data (different for each file so it
// doesn't compress unrealistically).
$data = random_bytes($this->limit_filesize(self::$paramsmallfilesize[$this->size]));
$fs->create_file_from_string($filerecord, $data);
$this->dot($i, $count);
}
$this->end_log();
}
/**
* Creates a number of resource activities with one big file each.
*/
private function create_big_files() {
// Work out how many files and how many blocks to use (up to 64KB).
$count = self::$parambigfilecount[$this->size];
$filesize = $this->limit_filesize(self::$parambigfilesize[$this->size]);
$blocks = ceil($filesize / 65536);
$blocksize = floor($filesize / $blocks);
$this->log('createbigfiles', $count, true);
// Prepare temp area.
$tempfolder = make_temp_directory('tool_generator');
$tempfile = $tempfolder . '/' . rand();
// Create resources and files.
$fs = get_file_storage();
$resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
for ($i = 0; $i < $count; $i++) {
// Create resource.
$record = array('course' => $this->course,
'name' => get_string('bigfile', 'tool_generator', $i));
$options = array('section' => $this->get_target_section());
$resource = $resourcegenerator->create_instance($record, $options);
// Write file.
$handle = fopen($tempfile, 'w');
if (!$handle) {
throw new coding_exception('Failed to open temporary file');
}
for ($j = 0; $j < $blocks; $j++) {
$data = random_bytes($blocksize);
fwrite($handle, $data);
$this->dot($i * $blocks + $j, $count * $blocks);
}
fclose($handle);
// Add file.
$context = context_module::instance($resource->cmid);
$filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/',
'filename' => 'bigfile' . $i . '.dat');
$fs->create_file_from_pathname($filerecord, $tempfile);
}
unlink($tempfile);
$this->end_log();
}
/**
* Creates one forum activity with a bunch of posts.
*/
private function create_forum() {
global $DB;
$discussions = self::$paramforumdiscussions[$this->size];
$posts = self::$paramforumposts[$this->size];
$totalposts = $discussions * $posts;
$this->log('createforum', $totalposts, true);
// Create empty forum.
$forumgenerator = $this->generator->get_plugin_generator('mod_forum');
$record = array('course' => $this->course,
'name' => get_string('pluginname', 'forum'));
$options = array('section' => 0);
$forum = $forumgenerator->create_instance($record, $options);
// Add discussions and posts.
$sofar = 0;
for ($i = 0; $i < $discussions; $i++) {
$record = array('forum' => $forum->id, 'course' => $this->course->id,
'userid' => $this->get_target_user());
$discussion = $forumgenerator->create_discussion($record);
$parentid = $DB->get_field('forum_posts', 'id', array('discussion' => $discussion->id), MUST_EXIST);
$sofar++;
for ($j = 0; $j < $posts - 1; $j++, $sofar++) {
$record = array('discussion' => $discussion->id,
'userid' => $this->get_target_user(), 'parent' => $parentid);
$forumgenerator->create_post($record);
$this->dot($sofar, $totalposts);
}
}
$this->end_log();
}
/**
* Gets a section number.
*
* Depends on $this->fixeddataset.
*
* @return int A section number from 1 to the number of sections
*/
public function get_target_section() {
if (!$this->fixeddataset) {
$key = rand(1, self::$paramsections[$this->size]);
} else {
// Using section 1.
$key = 1;
}
return $key;
}
/**
* Gets a user id.
*
* Depends on $this->fixeddataset.
*
* @return int A user id for a random created user
*/
private function get_target_user() {
if (!$this->fixeddataset) {
$userid = $this->userids[rand(1, self::$paramusers[$this->size])];
} else if ($userid = current($this->userids)) {
// Moving pointer to the next user.
next($this->userids);
} else {
// Returning to the beginning if we reached the end.
$userid = reset($this->userids);
}
return $userid;
}
/**
* Restricts the binary file size if necessary
*
* @param int $length The total length
* @return int The limited length if a limit was specified.
*/
private function limit_filesize($length) {
// Limit to $this->filesizelimit.
if (is_numeric($this->filesizelimit) && $length > $this->filesizelimit) {
$length = floor($this->filesizelimit);
}
return $length;
}
}
@@ -0,0 +1,60 @@
<?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/>.
namespace tool_generator\form;
defined('MOODLE_INTERNAL') || die();
use moodleform;
global $CFG;
require_once($CFG->dirroot . '/lib/formslib.php');
/**
* Form for importting a testing scenario feature file.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class featureimport extends moodleform {
public function definition(): void {
$mform = &$this->_form;
// File upload.
$mform->addElement(
'filepicker',
'featurefile',
get_string('testscenario_file', 'tool_generator'),
null,
['accepted_types' => ['.feature']]
);
$mform->addRule('featurefile', null, 'required');
$this->add_action_buttons(false, get_string('import'));
}
/**
* Get the feature file contents.
* @return string|null the feature file contents or null if not found.
*/
public function get_feature_contents(): ?string {
$result = $this->get_file_content('featurefile');
if (!$result) {
return null;
}
return $result;
}
}
@@ -0,0 +1,123 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
namespace tool_generator\local\testscenario;
use stdClass;
/**
* Class with a scenario feature parsed.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class parsedfeature {
/** @var int the number of steps. */
private int $stepcount = 0;
/** @var bool if the parser is ok or fail. */
private bool $isvalid = true;
/** @var stdClass[] the list of scenarios with all the steps.
*
* scenarionum => {type: string, title: string, steps: steprunner[]}.
*/
private array $scenarios = [];
/**
* Get the general error, if any.
* @return string
*/
public function get_general_error(): string {
if (!$this->isvalid) {
return get_string('testscenario_invalidfile', 'tool_generator');
}
if ($this->stepcount == 0) {
return get_string('testscenario_nosteps', 'tool_generator');
}
return '';
}
/**
* Check if the parsed feature is valid.
* @return bool
*/
public function is_valid(): bool {
return $this->isvalid && $this->stepcount > 0;
}
/**
* Add a line to the current scenario.
* @param steprunner $step the step to add.
*/
public function add_step(steprunner $step) {
if (empty($this->scenarios)) {
$this->add_scenario('scenario', null);
}
$currentscenario = count($this->scenarios) - 1;
$this->scenarios[$currentscenario]->steps[] = $step;
$this->stepcount++;
if (!$step->is_valid()) {
$this->isvalid = false;
}
}
/**
* Insert a new scenario.
* @param string $type the type of the scenario.
* @param string|null $name the name of the scenario.
*/
public function add_scenario(string $type, ?string $name) {
$this->scenarios[] = (object) [
'type' => $type,
'name' => $name ?? '',
'steps' => [],
'error' => '',
];
}
/**
* Add an error to the current scenario.
* @param string $error
*/
public function add_error(string $error) {
$currentscenario = count($this->scenarios) - 1;
$this->scenarios[$currentscenario]->error = $error;
}
/**
* Get the list of scenarios.
* @return stdClass[] array of scenarionum => {type: string, title: string, steps: steprunner[]}
*/
public function get_scenarios(): array {
return $this->scenarios;
}
/**
* Get all the steps form all scenarios.
* @return steprunner[]
*/
public function get_all_steps(): array {
$result = [];
foreach ($this->scenarios as $scenario) {
foreach ($scenario->steps as $step) {
$result[] = $step;
}
}
return $result;
}
}
@@ -0,0 +1,203 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
namespace tool_generator\local\testscenario;
use behat_data_generators;
use Behat\Gherkin\Parser;
use Behat\Gherkin\Lexer;
use Behat\Gherkin\Keywords\ArrayKeywords;
use ReflectionClass;
use ReflectionMethod;
/**
* Class to process a scenario generator file.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class runner {
/** @var behat_data_generators the behat data generator instance. */
private behat_data_generators $generator;
/** @var array of valid steps indexed by given expression tag. */
private array $validsteps;
/**
* Initi all composer, behat libraries and load the valid steps.
*/
public function init() {
$this->include_composer_libraries();
$this->include_behat_libraries();
$this->load_generator();
}
/**
* Include composer autload.
*/
public function include_composer_libraries() {
global $CFG;
if (!file_exists($CFG->dirroot . '/vendor/autoload.php')) {
throw new \moodle_exception('Missing composer.');
}
require_once($CFG->dirroot . '/vendor/autoload.php');
return true;
}
/**
* Include all necessary behat libraries.
*/
public function include_behat_libraries() {
global $CFG;
if (!class_exists('Behat\Gherkin\Lexer')) {
throw new \moodle_exception('Missing behat classes.');
}
// Behat constant.
if (!defined('BEHAT_TEST')) {
define('BEHAT_TEST', 1);
}
// Behat utilities.
require_once($CFG->libdir . '/behat/classes/util.php');
require_once($CFG->libdir . '/behat/classes/behat_command.php');
require_once($CFG->libdir . '/behat/behat_base.php');
require_once("{$CFG->libdir}/tests/behat/behat_data_generators.php");
return true;
}
/**
* Load all generators.
*/
private function load_generator() {
$this->generator = new behat_data_generators();
$this->validsteps = $this->scan_generator($this->generator);
}
/**
* Scan a generator to get all valid steps.
* @param behat_data_generators $generator the generator to scan.
* @return array the valid steps.
*/
private function scan_generator(behat_data_generators $generator): array {
$result = [];
$class = new ReflectionClass($generator);
$methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
$given = $this->get_method_given($method);
if ($given) {
$result[$given] = $method->getName();
}
}
return $result;
}
/**
* Get the given expression tag of a method.
*
* @param ReflectionMethod $method the method to get the given expression tag.
* @return string|null the given expression tag or null if not found.
*/
private function get_method_given(ReflectionMethod $method): ?string {
$doccomment = $method->getDocComment();
$doccomment = str_replace("\r\n", "\n", $doccomment);
$doccomment = str_replace("\r", "\n", $doccomment);
$doccomment = explode("\n", $doccomment);
foreach ($doccomment as $line) {
$matches = [];
if (preg_match('/.*\@(given|when|then)\s+(.+)$/i', $line, $matches)) {
return $matches[2];
}
}
return null;
}
/**
* Parse a feature file.
* @param string $content the feature file content.
* @return parsedfeature
*/
public function parse_feature(string $content): parsedfeature {
$result = new parsedfeature();
$parser = $this->get_parser();
$feature = $parser->parse($content);
// No need for background in testing scenarios because scenarios can only contain generators.
// In the future the background can be used to define clean up steps (when clean up methods
// are implemented).
if ($feature->hasScenarios()) {
$scenarios = $feature->getScenarios();
foreach ($scenarios as $scenario) {
if ($scenario->getNodeType() == 'Outline') {
$result->add_scenario($scenario->getNodeType(), $scenario->getTitle());
$result->add_error(get_string('testscenario_outline', 'tool_generator'));
continue;
}
$result->add_scenario($scenario->getNodeType(), $scenario->getTitle());
$steps = $scenario->getSteps();
foreach ($steps as $step) {
$result->add_step(new steprunner($this->generator, $this->validsteps, $step));
}
}
}
return $result;
}
/**
* Get the parser.
* @return Parser
*/
private function get_parser(): Parser {
$keywords = new ArrayKeywords([
'en' => [
'feature' => 'Feature',
// If in the future we have clean up steps, background will be renamed to "Clean up".
'background' => 'Background',
'scenario' => 'Scenario',
'scenario_outline' => 'Scenario Outline|Scenario Template',
'examples' => 'Examples|Scenarios',
'given' => 'Given',
'when' => 'When',
'then' => 'Then',
'and' => 'And',
'but' => 'But',
],
]);
$lexer = new Lexer($keywords);
$parser = new Parser($lexer);
return $parser;
}
/**
* Execute a parsed feature.
* @param parsedfeature $parsedfeature the parsed feature to execute.
* @return bool true if all steps were executed successfully.
*/
public function execute(parsedfeature $parsedfeature): bool {
if (!$parsedfeature->is_valid()) {
return false;
}
$result = true;
$steps = $parsedfeature->get_all_steps();
foreach ($steps as $step) {
$result = $step->execute() && $result;
}
return $result;
}
}
@@ -0,0 +1,217 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
namespace tool_generator\local\testscenario;
use behat_data_generators;
use Behat\Gherkin\Node\StepNode;
/**
* Class to validate and process a scenario step.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class steprunner {
/** @var behat_data_generators the behat data generator instance. */
private behat_data_generators $generator;
/** @var array the valid steps indexed by given expression tag. */
private array $validsteps;
/** @var StepNode the step node to process. */
private StepNode $stepnode;
/** @var string|null the generator method to call. */
private ?string $method = null;
/** @var array the parameters to pass to the generator method. */
private array $params = [];
/** @var bool if the step is valid. */
private bool $isvalid = false;
/** @var bool if the step has been executed. */
private bool $executed = false;
/** @var string the error message if any. */
private string $error = '';
/**
* Constructor.
* @param behat_data_generators $generator the behat data generator instance.
* @param array $validsteps the valid steps indexed by given expression tag.
* @param StepNode $stepnode the step node to process.
*/
public function __construct(behat_data_generators $generator, array $validsteps, StepNode $stepnode) {
$this->generator = $generator;
$this->validsteps = $validsteps;
$this->stepnode = $stepnode;
$this->init();
}
/**
* Init the step runner.
*
* This method will check if the step is valid and all the needed information
* in case it is executed.
*/
private function init() {
$matches = [];
$linetext = $this->stepnode->getText();
foreach ($this->validsteps as $pattern => $method) {
if (!$this->match_given($pattern, $linetext, $matches)) {
continue;
}
$this->method = $method;
$this->params = $this->build_method_params($method, $matches);
$this->isvalid = true;
return;
}
$this->error = get_string('testscenario_invalidstep', 'tool_generator');
}
/**
* Build the method parameters.
* @param string $methodname the method name.
* @param array $matches the matches.
* @return array the method parameters.
*/
private function build_method_params($methodname, $matches) {
$method = new \ReflectionMethod($this->generator, $methodname);
$params = [];
foreach ($method->getParameters() as $param) {
$paramname = $param->getName();
if (isset($matches[$paramname])) {
$params[] = $matches[$paramname];
unset($matches[$paramname]);
} else if (count($matches) > 0) {
// If the param is not present means the regular expressions does not use
// proper names. So we will try to find the param by position.
$params[] = array_pop($matches);
} else {
// No more params to match.
break;
}
}
return array_merge($params, $this->stepnode->getArguments());
}
/**
* Return if the step is valid.
* @return bool
*/
public function is_valid(): bool {
return $this->isvalid;
}
/**
* Return if the step has been executed.
* @return bool
*/
public function is_executed(): bool {
return $this->executed;
}
/**
* Return the step text.
* @return string
*/
public function get_text(): string {
return $this->stepnode->getText();
}
/**
* Return the step error message.
* @return string
*/
public function get_error(): string {
return $this->error;
}
/**
* Return the step arguments as string.
* @return string
*/
public function get_arguments_string(): string {
$result = '';
foreach ($this->stepnode->getArguments() as $argument) {
$result .= $argument->getTableAsString();
}
return $result;
}
/**
* Match a given expression with a text.
* @param string $pattern the given expression.
* @param string $text the text to match.
* @param array $matches the matches.
* @return bool if the step matched the generator given expression.
*/
private function match_given(string $pattern, $text, array &$matches) {
$internalmatcher = [];
if (substr($pattern, 0, 1) === '/') {
// Pattern is a regular expression.
$result = preg_match($pattern, $text, $matches);
foreach ($matches as $key => $value) {
if (is_int($key)) {
unset($matches[$key]);
}
}
return $result;
}
// Patter is a string with parameters.
$elementmatches = [];
preg_match_all('/:([^ ]+)/', $pattern, $elementmatches, PREG_SET_ORDER, 0);
$pattern = preg_replace('/:([^ ]+)/', '(?P<$1>"[^"]+"|[^" ]+)', $pattern);
$pattern = '/^' . $pattern . '$/';
$result = preg_match($pattern, $text, $internalmatcher);
if (!$result) {
return false;
}
foreach ($elementmatches as $elementmatch) {
// Remove any possible " at the beggining and end of $internalmatcher[$elementmatch[1]].
$paramvalue = preg_replace('/^"(.*)"$/', '$1', $internalmatcher[$elementmatch[1]]);
$matches[$elementmatch[1]] = $paramvalue;
}
return true;
}
/**
* Execute the step.
* @return bool if the step is executed or not.
*/
public function execute(): bool {
if (!$this->isvalid) {
return false;
}
$this->executed = true;
try {
call_user_func_array(
[$this->generator, $this->method],
$this->params
);
} catch (\moodle_exception $exception) {
$this->error = $exception->getMessage();
$this->isvalid = false;
return false;
}
return true;
}
}
@@ -0,0 +1,103 @@
<?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/>.
/**
* Course form.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
/**
* Form with options for creating large course.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_generator_make_course_form extends moodleform {
/**
* Course generation tool form definition.
*
* @return void
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('select', 'size', get_string('size', 'tool_generator'),
tool_generator_course_backend::get_size_choices());
$mform->setDefault('size', tool_generator_course_backend::DEFAULT_SIZE);
$mform->addElement('text', 'shortname', get_string('shortnamecourse'));
$mform->addRule('shortname', get_string('missingshortname'), 'required', null, 'client');
$mform->setType('shortname', PARAM_TEXT);
$mform->addElement('text', 'fullname', get_string('fullnamecourse'));
$mform->setType('fullname', PARAM_TEXT);
$mform->addElement('editor', 'summary', get_string('coursesummary'));
$mform->setType('summary', PARAM_RAW);
$additionalmodules = [];
$pluginsfunction = get_plugins_with_function('course_backend_generator_create_activity');
foreach ($pluginsfunction as $plugintype => $plugins) {
foreach ($plugins as $pluginname => $pluginfunction) {
$additionalmodules[$pluginname] = get_string("pluginname", "{$plugintype}_{$pluginname}");
}
}
$mform->addElement('autocomplete', 'additionalmodules',
get_string('additionalmodules', 'tool_generator'),
$additionalmodules,
[
'multiple' => true,
'noselectionstring' => get_string('noselection', 'form')
]
);
$mform->addHelpButton('additionalmodules', 'additionalmodules', 'tool_generator');
$mform->setType('additionalmodules', PARAM_ALPHAEXT);
$mform->addElement('submit', 'submit', get_string('createcourse', 'tool_generator'));
}
/**
* Form validation.
*
* @param array $data
* @param array $files
* @return void
*/
public function validation($data, $files) {
global $DB;
$errors = array();
// Check course doesn't already exist.
if (!empty($data['shortname'])) {
// Check shortname.
$error = tool_generator_course_backend::check_shortname_available($data['shortname']);
if ($error) {
$errors['shortname'] = $error;
}
}
return $errors;
}
}
@@ -0,0 +1,81 @@
<?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/>.
/**
* Test plan form.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
/**
* Form with options for creating large course.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_generator_make_testplan_form extends moodleform {
/**
* Test plan form definition.
*
* @return void
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('select', 'size', get_string('size', 'tool_generator'),
tool_generator_testplan_backend::get_size_choices());
$mform->setDefault('size', tool_generator_testplan_backend::DEFAULT_SIZE);
$mform->addElement('course', 'courseid', get_string('targetcourse', 'tool_generator'));
$mform->addElement('advcheckbox', 'updateuserspassword', get_string('updateuserspassword', 'tool_generator'));
$mform->addHelpButton('updateuserspassword', 'updateuserspassword', 'tool_generator');
$mform->addElement('submit', 'submit', get_string('createtestplan', 'tool_generator'));
}
/**
* Checks that the submitted data allows us to create a test plan.
*
* @param array $data
* @param array $files
* @return array An array of errors
*/
public function validation($data, $files) {
global $CFG;
$errors = array();
if (empty($CFG->tool_generator_users_password) || is_bool($CFG->tool_generator_users_password)) {
$errors['updateuserspassword'] = get_string('error_nouserspassword', 'tool_generator');
}
// Better to repeat here the query than to do it afterwards and end up with an exception.
if ($courseerrors = tool_generator_testplan_backend::has_selected_course_any_problem($data['courseid'], $data['size'])) {
$errors = array_merge($errors, $courseerrors);
}
return $errors;
}
}
@@ -0,0 +1,88 @@
<?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/>.
namespace tool_generator\output;
use renderable;
use renderer_base;
use templatable;
use tool_generator\local\testscenario\parsedfeature;
/**
* A report to show the feature file parsing process.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class parsingresult implements renderable, templatable {
/** @var parsedfeature the processed feature object. */
protected $parsedfeature;
/**
* Constructor.
*
* @param parsedfeature $parsedfeature
*/
public function __construct(parsedfeature $parsedfeature) {
$this->parsedfeature = $parsedfeature;
}
/**
* Export for template.
*
* @param renderer_base $output The renderer.
* @return array
*/
public function export_for_template(renderer_base $output): array {
$data = [
'scenarios' => [],
'isvalid' => $this->parsedfeature->is_valid(),
'generalerror' => $this->parsedfeature->get_general_error(),
];
$haslines = false;
foreach ($this->parsedfeature->get_scenarios() as $scenario) {
$scenariodata = [
'type' => $scenario->type,
'name' => $scenario->name,
'steps' => [],
];
if (!empty($scenario->error)) {
$scenariodata['scenarioerror'] = $scenario->error;
}
foreach ($scenario->steps as $step) {
$scenariodata['steps'][] = [
'text' => $step->get_text(),
'arguments' => $step->get_arguments_string(),
'hasarguments' => !empty($step->get_arguments_string()),
'isvalid' => $step->is_valid(),
'error' => $step->get_error(),
'isexecuted' => $step->is_executed(),
];
$haslines = true;
}
if (!empty($scenariodata['steps'])) {
$scenariodata['hassteps'] = true;
}
$data['scenarios'][] = $scenariodata;
}
if ($haslines) {
$data['haslines'] = $haslines;
}
return $data;
}
}
@@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for tool_generator.
*
* @package tool_generator
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_generator\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for tool_generator implementing null_provider.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,212 @@
<?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/>.
/**
* tool_generator site backend.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Backend code for the site generator.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_generator_site_backend extends tool_generator_backend {
/**
* @var string The course's shortname prefix.
*/
const SHORTNAMEPREFIX = 'testcourse_';
/**
* @var bool If the debugging level checking was skipped.
*/
protected $bypasscheck;
/**
* @var array Multidimensional array where the first level is the course size and the second the site size.
*/
protected static $sitecourses = array(
array(2, 8, 64, 256, 1024, 4096),
array(1, 4, 8, 16, 32, 64),
array(0, 0, 1, 4, 8, 16),
array(0, 0, 0, 1, 0, 0),
array(0, 0, 0, 0, 1, 0),
array(0, 0, 0, 0, 0, 1)
);
/**
* Constructs object ready to make the site.
*
* @param int $size Size as numeric index
* @param bool $bypasscheck If debugging level checking was skipped.
* @param bool $fixeddataset To use fixed or random data
* @param int|bool $filesizelimit The max number of bytes for a generated file
* @param bool $progress True if progress information should be displayed
* @return int Course id
*/
public function __construct($size, $bypasscheck, $fixeddataset = false, $filesizelimit = false, $progress = true) {
// Set parameters.
$this->bypasscheck = $bypasscheck;
parent::__construct($size, $fixeddataset, $filesizelimit, $progress);
}
/**
* Gets a list of size choices supported by this backend.
*
* @return array List of size (int) => text description for display
*/
public static function get_size_choices() {
$options = array();
for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
$options[$size] = get_string('sitesize_' . $size, 'tool_generator');
}
return $options;
}
/**
* Runs the entire 'make' process.
*
* @return int Course id
*/
public function make() {
global $DB, $CFG;
raise_memory_limit(MEMORY_EXTRA);
if ($this->progress && !CLI_SCRIPT) {
echo html_writer::start_tag('ul');
}
$entirestart = microtime(true);
// Create courses.
$prevchdir = getcwd();
chdir($CFG->dirroot);
$ncourse = self::get_last_testcourse_id();
foreach (self::$sitecourses as $coursesize => $ncourses) {
for ($i = 1; $i <= $ncourses[$this->size]; $i++) {
// Non language-dependant shortname.
$ncourse++;
$this->run_create_course(self::SHORTNAMEPREFIX . $ncourse, $coursesize);
}
}
chdir($prevchdir);
// Store last course id to return it (will be the bigger one).
$lastcourseid = $DB->get_field('course', 'id', array('shortname' => self::SHORTNAMEPREFIX . $ncourse));
// Log total time.
$this->log('sitecompleted', round(microtime(true) - $entirestart, 1));
if ($this->progress && !CLI_SCRIPT) {
echo html_writer::end_tag('ul');
}
return $lastcourseid;
}
/**
* Creates a course with the specified shortname, coursesize and the provided maketestsite options.
*
* @param string $shortname The course shortname
* @param int $coursesize One of the possible course sizes.
* @return void
*/
protected function run_create_course($shortname, $coursesize) {
// We are in $CFG->dirroot.
$command = 'php admin/tool/generator/cli/maketestcourse.php';
$options = array(
'--shortname="' . $shortname . '"',
'--size="' . get_string('shortsize_' . $coursesize, 'tool_generator') . '"'
);
if (!$this->progress) {
$options[] = '--quiet';
}
if ($this->filesizelimit) {
$options[] = '--filesizelimit="' . $this->filesizelimit . '"';
}
// Extend options.
$optionstoextend = array(
'fixeddataset' => 'fixeddataset',
'bypasscheck' => 'bypasscheck',
);
// Getting an options string.
foreach ($optionstoextend as $attribute => $option) {
if (!empty($this->{$attribute})) {
$options[] = '--' . $option;
}
}
$options = implode(' ', $options);
if ($this->progress) {
system($command . ' ' . $options, $exitcode);
} else {
passthru($command . ' ' . $options, $exitcode);
}
if ($exitcode != 0) {
exit($exitcode);
}
}
/**
* Obtains the last unique sufix (numeric) using the test course prefix.
*
* @return int The last generated numeric value.
*/
protected static function get_last_testcourse_id() {
global $DB;
$params = array();
$params['shortnameprefix'] = $DB->sql_like_escape(self::SHORTNAMEPREFIX) . '%';
$like = $DB->sql_like('shortname', ':shortnameprefix');
if (!$testcourses = $DB->get_records_select('course', $like, $params, '', 'shortname')) {
return 0;
}
// SQL order by is not appropiate here as is ordering strings.
$shortnames = array_keys($testcourses);
core_collator::asort($shortnames, core_collator::SORT_NATURAL);
$shortnames = array_reverse($shortnames);
// They come ordered by shortname DESC, so non-numeric values will be the first ones.
$prefixnchars = strlen(self::SHORTNAMEPREFIX);
foreach ($shortnames as $shortname) {
$sufix = substr($shortname, $prefixnchars);
if (preg_match('/^[\d]+$/', $sufix)) {
return $sufix;
}
}
// If all sufixes are not numeric this is the first make test site run.
return 0;
}
}
@@ -0,0 +1,317 @@
<?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/>.
/**
* Test plan generator.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Generates the files required by JMeter.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_generator_testplan_backend extends tool_generator_backend {
/**
* @var The URL to the repository of the external project.
*/
protected static $repourl = 'https://github.com/moodlehq/moodle-performance-comparison';
/**
* @var Number of users depending on the selected size.
*/
protected static $users = array(1, 30, 100, 1000, 5000, 10000);
/**
* @var Number of loops depending on the selected size.
*/
protected static $loops = array(5, 5, 5, 6, 6, 7);
/**
* @var Rampup period depending on the selected size.
*/
protected static $rampups = array(1, 6, 40, 100, 500, 800);
/**
* Gets a list of size choices supported by this backend.
*
* @return array List of size (int) => text description for display
*/
public static function get_size_choices() {
$options = array();
for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
$a = new stdClass();
$a->users = self::$users[$size];
$a->loops = self::$loops[$size];
$a->rampup = self::$rampups[$size];
$options[$size] = get_string('testplansize_' . $size, 'tool_generator', $a);
}
return $options;
}
/**
* Getter for moodle-performance-comparison project URL.
*
* @return string
*/
public static function get_repourl() {
return self::$repourl;
}
/**
* Creates the test plan file.
*
* @param int $courseid The target course id
* @param int $size The test plan size
* @return stored_file
*/
public static function create_testplan_file($courseid, $size) {
$jmxcontents = self::generate_test_plan($courseid, $size);
$fs = get_file_storage();
$filerecord = self::get_file_record('testplan', 'jmx');
return $fs->create_file_from_string($filerecord, $jmxcontents);
}
/**
* Creates the users data file.
*
* @param int $courseid The target course id
* @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
* @param int|null $size of the test plan. Used to limit the number of users exported
* to match the threads in the plan. For BC, defaults to null that means all enrolled users.
* @return stored_file
*/
public static function create_users_file($courseid, $updateuserspassword, ?int $size = null) {
$csvcontents = self::generate_users_file($courseid, $updateuserspassword, $size);
$fs = get_file_storage();
$filerecord = self::get_file_record('users', 'csv');
return $fs->create_file_from_string($filerecord, $csvcontents);
}
/**
* Generates the test plan according to the target course contents.
*
* @param int $targetcourseid The target course id
* @param int $size The test plan size
* @return string The test plan as a string
*/
protected static function generate_test_plan($targetcourseid, $size) {
global $CFG;
// Getting the template.
$template = file_get_contents(__DIR__ . '/../testplan.template.jmx');
// Getting the course modules data.
$coursedata = self::get_course_test_data($targetcourseid);
// Host and path to the site.
$urlcomponents = parse_url($CFG->wwwroot);
if (empty($urlcomponents['path'])) {
$urlcomponents['path'] = '';
}
$replacements = array(
$CFG->version,
self::$users[$size],
self::$loops[$size],
self::$rampups[$size],
$urlcomponents['host'],
$urlcomponents['path'],
get_string('shortsize_' . $size, 'tool_generator'),
$targetcourseid,
$coursedata->pageid,
$coursedata->forumid,
$coursedata->forumdiscussionid,
$coursedata->forumreplyid
);
$placeholders = array(
'{{MOODLEVERSION_PLACEHOLDER}}',
'{{USERS_PLACEHOLDER}}',
'{{LOOPS_PLACEHOLDER}}',
'{{RAMPUP_PLACEHOLDER}}',
'{{HOST_PLACEHOLDER}}',
'{{SITEPATH_PLACEHOLDER}}',
'{{SIZE_PLACEHOLDER}}',
'{{COURSEID_PLACEHOLDER}}',
'{{PAGEACTIVITYID_PLACEHOLDER}}',
'{{FORUMACTIVITYID_PLACEHOLDER}}',
'{{FORUMDISCUSSIONID_PLACEHOLDER}}',
'{{FORUMREPLYID_PLACEHOLDER}}'
);
// Fill the template with the target course values.
return str_replace($placeholders, $replacements, $template);
}
/**
* Generates the user's credentials file with all the course's users
*
* @param int $targetcourseid
* @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
* @param int|null $size of the test plan. Used to limit the number of users exported
* to match the threads in the plan. For BC, defaults to null that means all enrolled users.
* @return string The users csv file contents.
*/
protected static function generate_users_file($targetcourseid, $updateuserspassword, ?int $size = null) {
global $CFG;
$coursecontext = context_course::instance($targetcourseid);
// If requested, get the number of users (threads) to use in the plan. We only need those in the exported file.
$planusers = self::$users[$size] ?? 0;
$users = get_enrolled_users($coursecontext, '', 0, 'u.id, u.username, u.auth', 'u.username ASC', 0, $planusers);
if (!$users) {
throw new \moodle_exception('coursewithoutusers', 'tool_generator');
}
$lines = array();
foreach ($users as $user) {
// Updating password to the one set in config.php.
if ($updateuserspassword) {
$userauth = get_auth_plugin($user->auth);
if (!$userauth->user_update_password($user, $CFG->tool_generator_users_password)) {
throw new \moodle_exception('errorpasswordupdate', 'auth');
}
}
// Here we already checked that $CFG->tool_generator_users_password is not null.
$lines[] = $user->username . ',' . $CFG->tool_generator_users_password;
}
return implode(PHP_EOL, $lines);
}
/**
* Returns a tool_generator file record
*
* @param string $filearea testplan or users
* @param string $filetype The file extension jmx or csv
* @return stdClass The file record to use when creating tool_generator files
*/
protected static function get_file_record($filearea, $filetype) {
$systemcontext = context_system::instance();
$filerecord = new stdClass();
$filerecord->contextid = $systemcontext->id;
$filerecord->component = 'tool_generator';
$filerecord->filearea = $filearea;
$filerecord->itemid = 0;
$filerecord->filepath = '/';
// Random generated number to avoid concurrent execution problems.
$filerecord->filename = $filearea . '_' . date('YmdHi', time()) . '_' . rand(1000, 9999) . '.' . $filetype;
return $filerecord;
}
/**
* Gets the data required to fill the test plan template with the database contents.
*
* @param int $targetcourseid The target course id
* @return stdClass The ids required by the test plan
*/
protected static function get_course_test_data($targetcourseid) {
global $DB, $USER;
$data = new stdClass();
// Getting course contents info as the current user (will be an admin).
$course = new stdClass();
$course->id = $targetcourseid;
$courseinfo = new course_modinfo($course, $USER->id);
// Getting the first page module instance.
if (!$pages = $courseinfo->get_instances_of('page')) {
throw new \moodle_exception('error_nopageinstances', 'tool_generator');
}
$data->pageid = reset($pages)->id;
// Getting the first forum module instance and it's first discussion and reply as well.
if (!$forums = $courseinfo->get_instances_of('forum')) {
throw new \moodle_exception('error_noforuminstances', 'tool_generator');
}
$forum = reset($forums);
// Getting the first discussion (and reply).
if (!$discussions = forum_get_discussions($forum, 'd.timemodified ASC', false, -1, 1)) {
throw new \moodle_exception('error_noforumdiscussions', 'tool_generator');
}
$discussion = reset($discussions);
$data->forumid = $forum->id;
$data->forumdiscussionid = $discussion->discussion;
$data->forumreplyid = $discussion->id;
// According to the current test plan.
return $data;
}
/**
* Checks if the selected target course is ok.
*
* @param int|string $course
* @param int $size
* @return array Errors array or false if everything is ok
*/
public static function has_selected_course_any_problem($course, $size) {
global $DB;
$errors = array();
if (!is_numeric($course)) {
if (!$course = $DB->get_field('course', 'id', array('shortname' => $course))) {
$errors['courseid'] = get_string('error_nonexistingcourse', 'tool_generator');
return $errors;
}
}
$coursecontext = context_course::instance($course, IGNORE_MISSING);
if (!$coursecontext) {
$errors['courseid'] = get_string('error_nonexistingcourse', 'tool_generator');
return $errors;
}
if (!$users = get_enrolled_users($coursecontext, '', 0, 'u.id')) {
$errors['courseid'] = get_string('coursewithoutusers', 'tool_generator');
}
// Checks that the selected course has enough users.
$coursesizes = tool_generator_course_backend::get_users_per_size();
if (count($users) < self::$users[$size]) {
$errors['size'] = get_string('notenoughusers', 'tool_generator');
}
if (empty($errors)) {
return false;
}
return $errors;
}
}
+126
View File
@@ -0,0 +1,126 @@
<?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/>.
/**
* CLI interface for creating a test course.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('CLI_SCRIPT', true);
define('NO_OUTPUT_BUFFERING', true);
require(__DIR__ . '/../../../../config.php');
require_once($CFG->libdir. '/clilib.php');
// CLI options.
list($options, $unrecognized) = cli_get_params(
array(
'help' => false,
'shortname' => false,
'fullname' => false,
'summary' => false,
'size' => false,
'fixeddataset' => false,
'filesizelimit' => false,
'bypasscheck' => false,
'additionalmodules' => "",
'quiet' => false
),
array(
'h' => 'help'
)
);
// Display help.
if (!empty($options['help']) || empty($options['shortname']) || empty($options['size'])) {
echo "
Utility to create standard test course. (Also available in GUI interface.)
Not for use on live sites; only normally works if debugging is set to DEVELOPER
level.
Options:
--shortname Shortname of course to create (required)
--fullname Fullname of course to create (optional)
--summary Course summary, in double quotes (optional)
--size Size of course to create XS, S, M, L, XL, or XXL (required)
--fixeddataset Use a fixed data set instead of randomly generated data
--filesizelimit Limits the size of the generated files to the specified bytes
--bypasscheck Bypasses the developer-mode check (be careful!)
--additionalmodules Additional modules to be created when creating a course: a comma separated modules names without the mod_prefix
(like quiz, forum...)
--quiet Do not show any output
-h, --help Print out this help
Example from Moodle root directory:
\$ php admin/tool/generator/cli/maketestcourse.php --shortname=SIZE_S --size=S
";
// Exit with error unless we're showing this because they asked for it.
exit(empty($options['help']) ? 1 : 0);
}
// Check debugging is set to developer level.
if (empty($options['bypasscheck']) && !debugging('', DEBUG_DEVELOPER)) {
cli_error(get_string('error_notdebugging', 'tool_generator'));
}
// Get options.
$shortname = $options['shortname'];
$fullname = $options['fullname'];
$summary = $options['summary'];
$sizename = $options['size'];
$fixeddataset = $options['fixeddataset'];
$filesizelimit = $options['filesizelimit'];
// Check size.
try {
$size = tool_generator_course_backend::size_for_name($sizename);
} catch (coding_exception $e) {
cli_error("Invalid size ($sizename). Use --help for help.");
}
// Check shortname.
if ($error = tool_generator_course_backend::check_shortname_available($shortname)) {
cli_error($error);
}
// Switch to admin user account.
\core\session\manager::set_user(get_admin());
$additionalmodulesarray = [];
if (!empty($options['additionalmodules'])) {
$additionalmodulesarray = explode(',', trim($options['additionalmodules']));
}
// Do backend code to generate course.
$backend = new tool_generator_course_backend(
$shortname,
$size,
$fixeddataset,
$filesizelimit,
empty($options['quiet']),
$fullname,
$summary,
FORMAT_HTML,
$additionalmodulesarray
);
$id = $backend->make();
if (empty($options['quiet'])) {
echo PHP_EOL.'Generated course: '.course_get_url($id).PHP_EOL;
}
+122
View File
@@ -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/>.
/**
* CLI interface for creating a test plan
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('CLI_SCRIPT', true);
define('NO_OUTPUT_BUFFERING', true);
require(__DIR__ . '/../../../../config.php');
require_once($CFG->libdir. '/clilib.php');
// CLI options.
list($options, $unrecognized) = cli_get_params(
array(
'help' => false,
'shortname' => false,
'size' => false,
'bypasscheck' => false,
'updateuserspassword' => false
),
array(
'h' => 'help'
)
);
$testplansizes = '* ' . implode(PHP_EOL . '* ', tool_generator_testplan_backend::get_size_choices());
// Display help.
if (!empty($options['help']) || empty($options['shortname']) || empty($options['size'])) {
echo get_string('testplanexplanation', 'tool_generator', tool_generator_testplan_backend::get_repourl()) .
"Options:
-h, --help Print out this help
--shortname Shortname of the test plan's target course (required)
--size Size of the test plan to create XS, S, M, L, XL, or XXL (required)
--bypasscheck Bypasses the developer-mode check (be careful!)
--updateuserspassword Updates the target course users password according to \$CFG->tool_generator_users_password
$testplansizes
Consider that, the server resources you will need to run the test plan will be higher as the test plan size is higher.
Example from Moodle root directory:
\$ sudo -u www-data /usr/bin/php admin/tool/generator/cli/maketestplan.php --shortname=\"testcourse_12\" --size=S
";
// Exit with error unless we're showing this because they asked for it.
exit(empty($options['help']) ? 1 : 0);
}
// Check debugging is set to developer level.
if (empty($options['bypasscheck']) && !$CFG->debugdeveloper) {
cli_error(get_string('error_notdebugging', 'tool_generator'));
}
// Get options.
$shortname = $options['shortname'];
$sizename = $options['size'];
// Check size.
try {
$size = tool_generator_testplan_backend::size_for_name($sizename);
} catch (coding_exception $e) {
cli_error("Error: Invalid size ($sizename). Use --help for help.");
}
// Check selected course.
if ($errors = tool_generator_testplan_backend::has_selected_course_any_problem($shortname, $size)) {
// Showing the first reported problem.
cli_error("Error: " . reset($errors));
}
// Checking if test users password is set.
if (empty($CFG->tool_generator_users_password) || is_bool($CFG->tool_generator_users_password)) {
cli_error("Error: " . get_string('error_nouserspassword', 'tool_generator'));
}
// Switch to admin user account.
\core\session\manager::set_user(get_admin());
// Create files.
$courseid = $DB->get_field('course', 'id', array('shortname' => $shortname));
$usersfile = tool_generator_testplan_backend::create_users_file($courseid, !empty($options['updateuserspassword']), $size);
$testplanfile = tool_generator_testplan_backend::create_testplan_file($courseid, $size);
// One file path per line so other CLI scripts can easily parse the output.
echo moodle_url::make_pluginfile_url(
$testplanfile->get_contextid(),
$testplanfile->get_component(),
$testplanfile->get_filearea(),
$testplanfile->get_itemid(),
$testplanfile->get_filepath(),
$testplanfile->get_filename()
) .
PHP_EOL .
moodle_url::make_pluginfile_url(
$usersfile->get_contextid(),
$usersfile->get_component(),
$usersfile->get_filearea(),
$usersfile->get_itemid(),
$usersfile->get_filepath(),
$usersfile->get_filename()
) .
PHP_EOL;
+98
View File
@@ -0,0 +1,98 @@
<?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/>.
/**
* CLI interface for creating a test site.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('CLI_SCRIPT', true);
define('NO_OUTPUT_BUFFERING', true);
require(__DIR__ . '/../../../../config.php');
require_once($CFG->libdir. '/clilib.php');
// CLI options.
list($options, $unrecognized) = cli_get_params(
array(
'help' => false,
'size' => false,
'fixeddataset' => false,
'filesizelimit' => false,
'bypasscheck' => false,
'quiet' => false
),
array(
'h' => 'help'
)
);
$sitesizes = '* ' . implode(PHP_EOL . '* ', tool_generator_site_backend::get_size_choices());
// Display help.
if (!empty($options['help']) || empty($options['size'])) {
echo "
Utility to generate a standard test site data set.
Not for use on live sites; only normally works if debugging is set to DEVELOPER
level.
Consider that, depending on the size you select, this CLI tool can really generate a lot of data, aproximated sizes:
$sitesizes
Options:
--size Size of the generated site, this value affects the number of courses and their size. Accepted values: XS, S, M, L, XL, or XXL (required)
--fixeddataset Use a fixed data set instead of randomly generated data
--filesizelimit Limits the size of the generated files to the specified bytes
--bypasscheck Bypasses the developer-mode check (be careful!)
--quiet Do not show any output
-h, --help Print out this help
Example from Moodle root directory:
\$ php admin/tool/generator/cli/maketestsite.php --size=S
";
// Exit with error unless we're showing this because they asked for it.
exit(empty($options['help']) ? 1 : 0);
}
// Check debugging is set to developer level.
if (empty($options['bypasscheck']) && !$CFG->debugdeveloper) {
cli_error(get_string('error_notdebugging', 'tool_generator'));
}
// Get options.
$sizename = $options['size'];
$fixeddataset = $options['fixeddataset'];
$filesizelimit = $options['filesizelimit'];
// Check size.
try {
$size = tool_generator_site_backend::size_for_name($sizename);
} catch (coding_exception $e) {
cli_error("Invalid size ($sizename). Use --help for help.");
}
// Switch to admin user account.
\core\session\manager::set_user(get_admin());
// Do backend code to generate site.
$backend = new tool_generator_site_backend($size, $options['bypasscheck'], $fixeddataset, $filesizelimit, empty($options['quiet']));
$backend->make();
@@ -0,0 +1,181 @@
<?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/>.
/**
* Run a test scenario generator feature file.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
if (isset($_SERVER['REMOTE_ADDR'])) {
die(); // No access from web!
}
define('CLI_SCRIPT', true);
require_once(__DIR__ . '/../../../../config.php');
require_once(__DIR__ . '/../../../../lib/clilib.php');
require_once(__DIR__ . '/../../../../lib/behat/classes/behat_config_manager.php');
require_once(__DIR__ . '/../../../../lib/testing/lib.php');
ini_set('display_errors', '1');
ini_set('log_errors', '1');
list($options, $unrecognised) = cli_get_params(
[
'help' => false,
'feature' => '',
'disable-composer' => false,
'composer-upgrade' => true,
'composer-self-update' => true,
],
[
'h' => 'help',
'f' => 'feature',
]
);
// Checking run.php CLI script usage.
$help = "
Run a feature file into the current Moodle instance. The feature file can only
contains scenarios with core_data_generator steps. It is not yet compatible
with scenario outlines. All scenarios will be executed at once, event background
steps.
Usage:
php runtestscenario.php [--feature=\"value\"] [--help]
[--no-composer-self-update] [--no-composer-upgrade]
[--disable-composer]
Options:
-f, --feature Execute specified feature file (Absolute path of feature file).
--no-composer-self-update
Prevent upgrade of the composer utility using its self-update command
--no-composer-upgrade
Prevent update development dependencies using composer
--disable-composer
A shortcut to disable composer self-update and dependency update
Note: Installation of composer and/or dependencies will still happen as required
-h, --help Print out this help
Example from Moodle root directory:
\$ php admin/tool/generator/cli/runtestscenario.php --feature=/path/to/some/testing/scenario.feature
";
if (!empty($options['help'])) {
echo $help;
exit(0);
}
// The command will install composer if not present. Usually composer libraries are
// installed when behat or phpunit are installed, but we do not want to force users
// to create all phpunit or behat databases tables just to run a test scenario locally.
if (!file_exists($CFG->dirroot . '/vendor/autoload.php')) {
// Force OPcache reset if used, we do not want any stale caches
// when preparing test environment.
if (function_exists('opcache_reset')) {
opcache_reset();
}
if ($options['disable-composer']) {
// Disable self-update and upgrade easily.
// Note: Installation will still occur regardless of this setting.
$options['composer-self-update'] = false;
$options['composer-upgrade'] = false;
}
// Install and update composer and dependencies as required.
testing_update_composer_dependencies($options['composer-self-update'], $options['composer-upgrade']);
}
if (empty($options['feature'])) {
echo "Missing feature file path.\n";
exit(0);
}
$featurefile = $options['feature'];
if (!file_exists($featurefile)) {
echo "Feature file not found.\n";
exit(0);
}
// Switch to admin user account.
\core\session\manager::set_user(get_admin());
$runner = new tool_generator\local\testscenario\runner();
try {
$runner->init();
} catch (Exception $e) {
echo "Something is wrong with the behat setup.\n";
echo " Please,try running \"php admin/tool/behat/cli/init.php\" from your Moodle root directory.\n";
exit(0);
}
$content = file_get_contents($featurefile);
if (empty($content)) {
echo "The feature file is empty.\n";
exit(0);
}
try {
$parsedfeature = $runner->parse_feature($content);
} catch (\Exception $error) {
echo "Error parsing feature file: {$error->getMessage()}\n";
echo "Use the web version of the tool to see the parsing details:\n";
echo " Site administration -> development -> Create testing scenarios\n";
exit(0);
}
if (!$parsedfeature->is_valid()) {
echo "The file is not valid: {$parsedfeature->get_general_error()}\n";
echo "Use the web version of the tool to see the details:\n";
echo " Site administration -> development -> Create testing scenarios\n";
exit(0);
}
$total = 0;
$success = 0;
foreach ($parsedfeature->get_all_steps() as $step) {
if ($step->execute()) {
echo "\nOK: {$step->get_text()}\n";
echo "{$step->get_arguments_string()}\n";
$success++;
} else {
echo "\nFAIL: {$step->get_text()}\n";
echo "{$step->get_arguments_string()}\n";
echo "{$step->get_error()}\n";
}
$total++;
}
echo "\n{$success}/{$total} steps executed successfully.\n";
if ($success < $total) {
echo "\nSome steps failed.\n";
exit(1);
} else {
echo "\nAll steps executed successfully.\n";
exit(0);
}
+28
View File
@@ -0,0 +1,28 @@
<?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/>.
/**
* Development data generator.
*
* @package tool_generator
* @copyright 2009 Nicolas Connault
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__ . '/../../../config.php');
// This index page was previously in use, for now we redirect to the make test
// course page - but we might reinstate this page in the future.
redirect(new moodle_url('/admin/tool/generator/maketestcourse.php'));
@@ -0,0 +1,140 @@
<?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/>.
/**
* Language strings.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['bigfile'] = 'Big file {$a}';
$string['courseexplanation'] = 'This tool creates standard test courses that include many
sections, activities, and files.
This is intended to provide a standardised measure for checking the reliability
and performance of various system components (such as backup and restore).
This test is important because there have been many cases previously where,
faced with real-life use cases (e.g. a course with 1,000 activities), the system
does not work.
Courses created using this feature can occupy a large amount of database and
filesystem space (tens of gigabytes). You will need to delete the courses
(and wait for various cleanup runs) to release this space again.
**Do not use this feature on a live system**. Use only on a developer server.
(To avoid accidental use, this feature is disabled unless you have also selected
DEVELOPER debugging level.)';
$string['coursesize_0'] = 'XS (~10KB; create in ~1 second)';
$string['coursesize_1'] = 'S (~10MB; create in ~30 seconds)';
$string['coursesize_2'] = 'M (~100MB; create in ~2 minutes)';
$string['coursesize_3'] = 'L (~1GB; create in ~30 minutes)';
$string['coursesize_4'] = 'XL (~10GB; create in ~2 hours)';
$string['coursesize_5'] = 'XXL (~20GB; create in ~4 hours)';
$string['additionalmodules'] = 'Additional activities';
$string['additionalmodules_help'] = 'Select more activities that implement the course_backend_generator_create_activity function to include in the test course.';
$string['coursewithoutusers'] = 'The selected course has no users';
$string['createcourse'] = 'Create course';
$string['createtestplan'] = 'Create test plan';
$string['creating'] = 'Creating course';
$string['done'] = 'done ({$a}s)';
$string['downloadtestplan'] = 'Download test plan';
$string['downloadusersfile'] = 'Download users file';
$string['error_nocourses'] = 'There are no courses to generate the test plan';
$string['error_noforumdiscussions'] = 'The selected course does not contain forum discussions';
$string['error_noforuminstances'] = 'The selected course does not contain forum module instances';
$string['error_noforumreplies'] = 'The selected course does not contain forum replies';
$string['error_nonexistingcourse'] = 'The specified course does not exist';
$string['error_nopageinstances'] = 'The selected course does not contain page module instances';
$string['error_notdebugging'] = 'Not available on this server because debugging is not set to DEVELOPER';
$string['error_nouserspassword'] = 'You need to set $CFG->tool_generator_users_password in config.php to generate the test plan';
$string['fullname'] = 'Test course: {$a->size}';
$string['maketestcourse'] = 'Make test course';
$string['maketestplan'] = 'Make JMeter test plan';
$string['notenoughusers'] = 'The selected course does not have enough users';
$string['pluginname'] = 'Development data generator';
$string['progress_checkaccounts'] = 'Checking user accounts ({$a})';
$string['progress_coursecompleted'] = 'Course completed ({$a}s)';
$string['progress_createaccounts'] = 'Creating user accounts ({$a->from} - {$a->to})';
$string['progress_createassignments'] = 'Creating assignments ({$a})';
$string['progress_createbigfiles'] = 'Creating big files ({$a})';
$string['progress_createcourse'] = 'Creating course {$a}';
$string['progress_createforum'] = 'Creating forum ({$a} posts)';
$string['progress_createpages'] = 'Creating pages ({$a})';
$string['progress_createsmallfiles'] = 'Creating small files ({$a})';
$string['progress_createusers'] = 'Creating user accounts ({$a})';
$string['progress_enrol'] = 'Enrolling users into course ({$a})';
$string['progress_sitecompleted'] = 'Site completed ({$a}s)';
$string['shortsize_0'] = 'XS';
$string['shortsize_1'] = 'S';
$string['shortsize_2'] = 'M';
$string['shortsize_3'] = 'L';
$string['shortsize_4'] = 'XL';
$string['shortsize_5'] = 'XXL';
$string['sitesize_0'] = 'XS (~10MB; 3 courses, created in ~30 seconds)';
$string['sitesize_1'] = 'S (~50MB; 8 courses, created in ~2 minutes)';
$string['sitesize_2'] = 'M (~200MB; 73 courses, created in ~10 minutes)';
$string['sitesize_3'] = 'L (~1\'5GB; 277 courses, created in ~1\'5 hours)';
$string['sitesize_4'] = 'XL (~10GB; 1065 courses, created in ~5 hours)';
$string['sitesize_5'] = 'XXL (~20GB; 4177 courses, created in ~10 hours)';
$string['size'] = 'Size of course';
$string['smallfiles'] = 'Small files';
$string['targetcourse'] = 'Test target course';
$string['testscenario'] = 'Create testing scenarios';
$string['testscenario_description'] = 'Creating testing scenarios uses a limited feature files syntax to create all necessary elements to run a manual test.';
$string['testscenario_filedesc'] = 'The upload feature files can only contain scenarios with core_data_generator steps. It is not yet compatible with scenario outlines. All scenarios will be executed at once but background steps will be ignored.';
$string['testscenario_errorparsing'] = 'Error parsing feature file: {$a}';
$string['testscenario_file'] = 'Feature file';
$string['testscenario_invalidfile'] = 'The file format is not valid or contains invalid steps.';
$string['testscenario_invalidstep'] = 'Unknown step. Create testing scenarios only accepts generator steps.';
$string['testscenario_outline'] = 'Scenario outlines are not supported.';
$string['testscenario_nosteps'] = 'There are no steps to execute in the file.';
$string['testscenario_notready'] = 'Composer and Behat libraries are not yet installed.<br><br>Execute this command to enable this tool: <strong>php admin/tool/generator/cli/runtestscenario.php</strong>';
$string['testscenario_scenarionosteps'] = 'This scenario does not have any steps.';
$string['testscenario_steps'] = 'Testing scenario steps:';
$string['testplanexplanation'] = 'This tool creates a JMeter test plan file along with the user credentials file.
This test plan is designed to work along with {$a}, which makes easier to run the test plan in a specific Moodle environment, gathers information about the runs and compares the results, so you will need to download it and use it\'s test_runner.sh script or follow the installation and usage instructions.
You need to set a password for the course users in config.php (e.g. $CFG->tool_generator_users_password = \'moodle\';). There is no default value for this password to prevent unintended usages of the tool. You need to use the update passwords option in case your course users have other passwords or they were generated by tool_generator but without setting a $CFG->tool_generator_users_password value.
It is part of tool_generator so it works well with the courses generated by the courses and the site generators, it can
also be used with any course that contains, at least:
* Enough enrolled users (depends on the test plan size you select) with the password reset to \'moodle\'
* A page module instance
* A forum module instance with at least one discussion and one reply
You might want to consider your servers capacity when running large test plans as the amount to load generated by JMeter
can be specially big. The ramp up period has been adjusted according to the number of threads (users) to reduce this kind
of issues but the load is still huge.
**Do not run the test plan on a live system**. This feature only creates the files to feed JMeter so is not dangerous by
itself, but you should **NEVER** run this test plan in a production site.
';
$string['testplansize_0'] = 'XS ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_1'] = 'S ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_2'] = 'M ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_3'] = 'L ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_4'] = 'XL ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_5'] = 'XXL ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['updateuserspassword'] = 'Update course users password';
$string['updateuserspassword_help'] = 'JMeter needs to login as the course users, you can set the users password using $CFG->tool_generator_users_password in config.php; this setting updates the course user\'s password according to $CFG->tool_generator_users_password. It can be useful in case you are using a course not generated by tool_generator or $CFG->tool_generator_users_password was not set when you created the test courses.';
$string['privacy:metadata'] = 'The Development data generator plugin does not store any personal data.';
+59
View File
@@ -0,0 +1,59 @@
<?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/>.
/**
* Generator tool functions.
*
* @package tool_generator
* @copyright David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Files support.
*
* Exits if the required permissions are not satisfied.
*
* @param stdClass $course course object
* @param stdClass $cm
* @param stdClass $context context object
* @param string $filearea file area
* @param array $args extra arguments
* @param bool $forcedownload whether or not force download
* @param array $options additional options affecting the file serving
* @return void The file is sent along with it's headers
*/
function tool_generator_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) {
// Only for admins or CLI.
if (!defined('CLI_SCRIPT') && !is_siteadmin()) {
die;
}
if ($context->contextlevel != CONTEXT_SYSTEM) {
send_file_not_found();
}
$fs = get_file_storage();
$file = $fs->get_file($context->id, 'tool_generator', $filearea, $args[0], '/', $args[1]);
// Send the file, always forcing download, we don't want options.
\core\session\manager::write_close();
send_stored_file($file, 0, 0, true);
}
+86
View File
@@ -0,0 +1,86 @@
<?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/>.
/**
* Script creates a standardised large course for testing reliability and performance.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// Disable buffering so that the progress output displays gradually without
// needing to call flush().
define('NO_OUTPUT_BUFFERING', true);
require('../../../config.php');
require_once($CFG->libdir . '/adminlib.php');
// Initialise page and check permissions.
admin_externalpage_setup('toolgeneratorcourse');
// Start page.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('maketestcourse', 'tool_generator'));
// Information message.
$context = context_system::instance();
echo $OUTPUT->box(format_text(get_string('courseexplanation', 'tool_generator'),
FORMAT_MARKDOWN, array('context' => $context)));
// Check debugging is set to DEVELOPER.
if (!debugging('', DEBUG_DEVELOPER)) {
echo $OUTPUT->notification(get_string('error_notdebugging', 'tool_generator'));
echo $OUTPUT->footer();
exit;
}
// Finish page.
echo $OUTPUT->footer();
echo $OUTPUT->select_element_for_append();
// Preemptively reset the navcache before closing, so it remains the same on shutdown.
navigation_cache::destroy_volatile_caches();
\core\session\manager::write_close();
// Set up the form.
$mform = new tool_generator_make_course_form('maketestcourse.php');
if ($data = $mform->get_data()) {
// Do actual work.
echo $OUTPUT->heading(get_string('creating', 'tool_generator'));
$backend = new tool_generator_course_backend(
$data->shortname,
$data->size,
false,
false,
true,
$data->fullname,
$data->summary['text'],
$data->summary['format'],
$data->additionalmodules
);
$id = $backend->make();
echo html_writer::div(
html_writer::link(new moodle_url('/course/view.php', array('id' => $id)),
get_string('continue')));
} else {
// Display form.
$mform->display();
}
+87
View File
@@ -0,0 +1,87 @@
<?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/>.
/**
* Generates a JMeter test plan to performance comparison.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/adminlib.php');
// Initialise page and check permissions.
admin_externalpage_setup('toolgeneratortestplan');
// Start page.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('maketestplan', 'tool_generator'));
// Information message.
$context = context_system::instance();
$markdownlink = '[' . tool_generator_testplan_backend::get_repourl() . '](' . tool_generator_testplan_backend::get_repourl() . ')';
echo $OUTPUT->box(format_text(get_string('testplanexplanation', 'tool_generator', $markdownlink),
FORMAT_MARKDOWN, array('context' => $context)));
// Check debugging is set to DEVELOPER.
if (!$CFG->debugdeveloper) {
echo $OUTPUT->notification(get_string('error_notdebugging', 'tool_generator'));
echo $OUTPUT->footer();
exit;
}
// Set up the form.
$mform = new tool_generator_make_testplan_form('maketestplan.php');
if ($data = $mform->get_data()) {
// Creating both test plan and users files.
$testplanfile = tool_generator_testplan_backend::create_testplan_file($data->courseid, $data->size);
$usersfile = tool_generator_testplan_backend::create_users_file($data->courseid, $data->updateuserspassword, $data->size);
// Test plan link.
$testplanurl = moodle_url::make_pluginfile_url(
$testplanfile->get_contextid(),
$testplanfile->get_component(),
$testplanfile->get_filearea(),
$testplanfile->get_itemid(),
$testplanfile->get_filepath(),
$testplanfile->get_filename()
);
echo html_writer::div(
html_writer::link($testplanurl, get_string('downloadtestplan', 'tool_generator'))
);
// Users file link.
$usersfileurl = moodle_url::make_pluginfile_url(
$usersfile->get_contextid(),
$usersfile->get_component(),
$usersfile->get_filearea(),
$usersfile->get_itemid(),
$usersfile->get_filepath(),
$usersfile->get_filename()
);
echo html_writer::div(
html_writer::link($usersfileurl, get_string('downloadusersfile', 'tool_generator'))
);
} else {
// Display form.
$mform->display();
}
echo $OUTPUT->footer();
+95
View File
@@ -0,0 +1,95 @@
<?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/>.
/**
* Web interface to list and filter steps
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_generator\local\testscenario\runner;
use tool_generator\form\featureimport;
use tool_generator\output\parsingresult;
require(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/adminlib.php');
require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
// Executing behat generator can take some time.
core_php_time_limit::raise(300);
admin_externalpage_setup('toolgenerator_runtestscenario');
$currenturl = new moodle_url('/admin/tool/generator/runtestscenario.php');
$runner = new runner();
/** @var core_renderer $output*/
$output = $PAGE->get_renderer('core');
echo $output->header();
echo $output->heading(get_string('testscenario', 'tool_generator'));
echo $output->paragraph(get_string('testscenario_description', 'tool_generator'));
try {
$runner->init();
} catch (Exception $e) {
echo $output->notification(get_string('testscenario_notready', 'tool_generator'), null, false);
echo $output->footer();
die;
}
echo $output->paragraph(get_string('testscenario_filedesc', 'tool_generator'));
$mform = new featureimport();
$data = null;
if (!$mform->is_cancelled()) {
$data = $mform->get_data();
}
if (empty($data)) {
$mform->display();
echo $OUTPUT->footer();
die;
}
$content = $mform->get_feature_contents();
if (empty($content)) {
echo $output->notification(get_string('testscenario_invalidfile', 'tool_generator'));
echo $output->continue_button($currenturl);
echo $output->footer();
die;
}
try {
$parsedfeature = $runner->parse_feature($content);
} catch (\Throwable $th) {
echo $output->notification(get_string('testscenario_errorparsing', 'tool_generator', $th->getMessage()));
echo $output->continue_button($currenturl);
echo $output->footer();
die;
}
$runner->execute($parsedfeature);
echo $output->render(new parsingresult($parsedfeature));
echo $output->continue_button($currenturl);
echo $output->footer();
+44
View File
@@ -0,0 +1,44 @@
<?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/>.
/**
* Admin settings.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($hassiteconfig) {
$ADMIN->add('development', new admin_externalpage('toolgeneratorcourse',
get_string('maketestcourse', 'tool_generator'),
$CFG->wwwroot . '/' . $CFG->admin . '/tool/generator/maketestcourse.php'));
$ADMIN->add('development', new admin_externalpage('toolgeneratortestplan',
get_string('maketestplan', 'tool_generator'),
$CFG->wwwroot . '/' . $CFG->admin . '/tool/generator/maketestplan.php'));
$ADMIN->add(
'development',
new admin_externalpage(
'toolgenerator_runtestscenario',
get_string('testscenario', 'tool_generator'),
$CFG->wwwroot . '/' . $CFG->admin . '/tool/generator/runtestscenario.php'
)
);
}
@@ -0,0 +1,106 @@
{{!
This file is part of Moodle - https://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/>.
}}
{{!
@template tool_generator/parsingresult
Display the result of parsing or executing a behat testing scenario.
Example context (json):
{
"isvalid": true,
"generalerror": "Error message",
"haslines": true,
"scenarios": [
{
"type": "Scenario",
"title": "Scenario title",
"hassteps": true,
"steps": [
{
"text": "Step text",
"isvalid": true,
"isexecuted": true,
"hasarguments": true,
"arguments": "Argument text",
"error": "Error message"
}
]
}
]
}
}}
{{^isvalid}}
<div class="alert alert-danger mb-3" role="alert">
<span class="fa fa-times"></span>
{{generalerror}}
</div>
{{/isvalid}}
{{#haslines}}
<p>{{#str}} testscenario_steps, tool_generator {{/str}}</p>
{{/haslines}}
{{#scenarios}}
<h3>{{type}}: {{name}}</h3>
<div>
{{#scenarioerror}}
<div class="alert alert-danger mb-3" role="alert">
<span class="fa fa-times"></span>
{{scenarioerror}}
</div>
{{/scenarioerror}}
{{^hassteps}}
<div class="alert alert-info mb-3" role="alert">
<span class="fa fa-info-circle"></span>
{{#str}} testscenario_scenarionosteps, tool_generator {{/str}}
</div>
{{/hassteps}}
{{#hassteps}}
<ul class="list-group mb-3">
{{#steps}}
<li class="list-group-item">
<div>
{{text}}
{{^isvalid}}
<span class="badge badge-pill badge-danger">
<span class="fa fa-times"></span>
{{#str}} error {{/str}}
</span>
{{/isvalid}}
{{#isvalid}}
{{#isexecuted}}
<span class="badge badge-pill badge-success">
<span class="fa fa-check "></span>
{{#str}} success {{/str}}
</span>
{{/isexecuted}}
{{/isvalid}}
</div>
{{#hasarguments}}
<div class="alert alert-info mb-0 mt-2" role="alert" style="overflow: auto;">
<pre class="mb-0">{{arguments}}</pre>
</div>
{{/hasarguments}}
{{^isvalid}}
<div class="alert alert-danger mb-0 mt-2" role="alert" style="overflow: auto;">
{{error}}
</div>
{{/isvalid}}
</li>
{{/steps}}
</ul>
{{/hassteps}}
</div>
{{/scenarios}}
+974
View File
@@ -0,0 +1,974 @@
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="2.4">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">true</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="runtimestamp" elementType="Argument">
<stringProp name="Argument.name">runtimestamp</stringProp>
<stringProp name="Argument.value">${__time()}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="moodleversion" elementType="Argument">
<stringProp name="Argument.name">moodleversion</stringProp>
<stringProp name="Argument.value">{{MOODLEVERSION_PLACEHOLDER}}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="size" elementType="Argument">
<stringProp name="Argument.name">size</stringProp>
<stringProp name="Argument.value">{{SIZE_PLACEHOLDER}}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="host" elementType="Argument">
<stringProp name="Argument.name">host</stringProp>
<stringProp name="Argument.value">{{HOST_PLACEHOLDER}}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="sitepath" elementType="Argument">
<stringProp name="Argument.name">sitepath</stringProp>
<stringProp name="Argument.value">{{SITEPATH_PLACEHOLDER}}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="courseid" elementType="Argument">
<stringProp name="Argument.name">courseid</stringProp>
<stringProp name="Argument.value">{{COURSEID_PLACEHOLDER}}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="pageactivityid" elementType="Argument">
<stringProp name="Argument.name">pageactivityid</stringProp>
<stringProp name="Argument.value">{{PAGEACTIVITYID_PLACEHOLDER}}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="forumactivityid" elementType="Argument">
<stringProp name="Argument.name">forumactivityid</stringProp>
<stringProp name="Argument.value">{{FORUMACTIVITYID_PLACEHOLDER}}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="forumdiscussionid" elementType="Argument">
<stringProp name="Argument.name">forumdiscussionid</stringProp>
<stringProp name="Argument.value">{{FORUMDISCUSSIONID_PLACEHOLDER}}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="forumreplyid" elementType="Argument">
<stringProp name="Argument.name">forumreplyid</stringProp>
<stringProp name="Argument.value">{{FORUMREPLYID_PLACEHOLDER}}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ConstantThroughputTimer guiclass="TestBeanGUI" testclass="ConstantThroughputTimer" testname="Samples per minute" enabled="true">
<stringProp name="calcMode">all active threads (shared)</stringProp>
<stringProp name="throughput">${__property(throughput,throughput,120.0)}</stringProp>
</ConstantThroughputTimer>
<hashTree/>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Warm-up site" enabled="true">
<stringProp name="TestPlan.comments">Used to fill the caches, logs in every user</stringProp>
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">1</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">${__P(users,{{USERS_PLACEHOLDER}})}</stringProp>
<stringProp name="ThreadGroup.ramp_time">${__P(rampup,{{RAMPUP_PLACEHOLDER}})}</stringProp>
<longProp name="ThreadGroup.start_time">1378187955000</longProp>
<longProp name="ThreadGroup.end_time">1378187955000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="Default site request" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">${host}</stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}</stringProp>
<stringProp name="HTTPSampler.concurrentPool">4</stringProp>
</ConfigTestElement>
<hashTree/>
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV users data" enabled="true">
<stringProp name="delimiter">,</stringProp>
<stringProp name="fileEncoding"></stringProp>
<stringProp name="filename">${__P(usersfile,YOU_FORGOT_TO_SPECIFY_USERS_CSV_FILE.csv)}</stringProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<stringProp name="shareMode">All threads</stringProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="variableNames">username,password</stringProp>
</CSVDataSet>
<hashTree/>
<LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Test plan loop" enabled="true">
<boolProp name="LoopController.continue_forever">true</boolProp>
<stringProp name="LoopController.loops">1</stringProp>
</LoopController>
<hashTree>
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true">
<collectionProp name="CookieManager.cookies"/>
<boolProp name="CookieManager.clearEachIteration">true</boolProp>
<stringProp name="CookieManager.policy">rfc2109</stringProp>
</CookieManager>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Frontpage not logged" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View login page" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/login/index.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Get logintoken" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">LOGIN_TOKEN</stringProp>
<stringProp name="RegexExtractor.regex">type=&quot;hidden&quot;\sname=&quot;logintoken&quot;\svalue=&quot;(\w+)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default">0</stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
</RegexExtractor>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Login" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="username" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${username}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">username</stringProp>
</elementProp>
<elementProp name="password" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${password}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">password</stringProp>
</elementProp>
<elementProp name="logintoken" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${LOGIN_TOKEN}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">logintoken</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/login/index.php</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Logged in" enabled="true">
<collectionProp name="Asserion.test_strings">
<stringProp name="615717117">&lt;div class=&quot;logininfo&quot;&gt;You are logged in as</stringProp>
</collectionProp>
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
<boolProp name="Assertion.assume_success">false</boolProp>
<intProp name="Assertion.test_type">2</intProp>
</ResponseAssertion>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Frontpage logged" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View course" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="id" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${courseid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">id</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/course/view.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="TestPlan.comments"> </stringProp>
</HTTPSamplerProxy>
<hashTree>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">SESSION_SESSKEY</stringProp>
<stringProp name="RegexExtractor.regex">sesskey=([^&quot;]+)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default"></stringProp>
<stringProp name="RegexExtractor.match_number">2</stringProp>
<stringProp name="Sample.scope">all</stringProp>
</RegexExtractor>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Logout" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="sesskey" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${SESSION_SESSKEY}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">sesskey</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/login/logout.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
</hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Moodle Test" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">${__property(loops,loops,{{LOOPS_PLACEHOLDER}})}</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">${__property(users,users,{{USERS_PLACEHOLDER}})}</stringProp>
<stringProp name="ThreadGroup.ramp_time">${__property(rampup,rampup,{{RAMPUP_PLACEHOLDER}})}</stringProp>
<longProp name="ThreadGroup.start_time">1376636813000</longProp>
<longProp name="ThreadGroup.end_time">1376636813000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="Default site request" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">${host}</stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}</stringProp>
<stringProp name="HTTPSampler.concurrentPool">4</stringProp>
</ConfigTestElement>
<hashTree/>
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV users data" enabled="true">
<stringProp name="delimiter">,</stringProp>
<stringProp name="fileEncoding"></stringProp>
<stringProp name="filename">${__P(usersfile,YOU_FORGOT_TO_SPECIFY_USERS_CSV_FILE.csv)}</stringProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<stringProp name="shareMode">All threads</stringProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="variableNames">username,password</stringProp>
</CSVDataSet>
<hashTree/>
<LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Test plan loop" enabled="true">
<boolProp name="LoopController.continue_forever">true</boolProp>
<stringProp name="LoopController.loops">1</stringProp>
</LoopController>
<hashTree>
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true">
<collectionProp name="CookieManager.cookies"/>
<boolProp name="CookieManager.clearEachIteration">true</boolProp>
<stringProp name="CookieManager.policy">rfc2109</stringProp>
</CookieManager>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Frontpage not logged" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View login page" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/login/index.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Get logintoken" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">LOGIN_TOKEN</stringProp>
<stringProp name="RegexExtractor.regex">type=&quot;hidden&quot;\sname=&quot;logintoken&quot;\svalue=&quot;(\w+)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default">0</stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
</RegexExtractor>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Login" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="username" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${username}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">username</stringProp>
</elementProp>
<elementProp name="password" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${password}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">password</stringProp>
</elementProp>
<elementProp name="logintoken" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${LOGIN_TOKEN}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">logintoken</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/login/index.php</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Logged in" enabled="true">
<collectionProp name="Asserion.test_strings">
<stringProp name="615717117">&lt;div class=&quot;logininfo&quot;&gt;You are logged in as</stringProp>
</collectionProp>
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
<boolProp name="Assertion.assume_success">false</boolProp>
<intProp name="Assertion.test_type">2</intProp>
</ResponseAssertion>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Frontpage logged" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View course" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="id" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${courseid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">id</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/course/view.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="TestPlan.comments"> </stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View a page activity" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="id" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${pageactivityid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">id</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/mod/page/view.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View course again" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="id" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${courseid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">id</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/course/view.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View a forum activity" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="id" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${forumactivityid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">id</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/mod/forum/view.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View a forum discussion" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="d" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${forumdiscussionid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">d</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/mod/forum/discuss.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Fill a form to reply a forum discussion" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="reply" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${forumreplyid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">reply</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/mod/forum/post.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Get session userid" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">SESSION_USERID</stringProp>
<stringProp name="RegexExtractor.regex">name=&quot;userid&quot;\stype=&quot;hidden&quot;\svalue=&quot;(\d+)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default">0</stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
</RegexExtractor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Get session sesskey" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">SESSION_SESSKEY</stringProp>
<stringProp name="RegexExtractor.regex">name=&quot;sesskey&quot;\stype=&quot;hidden&quot;\svalue=&quot;([^&quot;]+)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default">0</stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
</RegexExtractor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Get forum form attachments" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">SESSION_FORUMFORMATTACHMENTS</stringProp>
<stringProp name="RegexExtractor.regex">value=&quot;(\d+)&quot;\sname=&quot;attachments&quot;\stype=&quot;hidden&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default">0</stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
</RegexExtractor>
<hashTree/>
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Get forum form itemid" enabled="true">
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
<stringProp name="RegexExtractor.refname">SESSION_FORUMFORMITEMID</stringProp>
<stringProp name="RegexExtractor.regex">type=&quot;hidden&quot;\sname=&quot;message\[itemid\]&quot;\svalue=&quot;(\d+)&quot;</stringProp>
<stringProp name="RegexExtractor.template">$1$</stringProp>
<stringProp name="RegexExtractor.default">0</stringProp>
<stringProp name="RegexExtractor.match_number">1</stringProp>
</RegexExtractor>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Send the forum discussion reply" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="course" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${courseid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">course</stringProp>
</elementProp>
<elementProp name="forum" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">0</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">forum</stringProp>
</elementProp>
<elementProp name="discussion" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${forumdiscussionid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">discussion</stringProp>
</elementProp>
<elementProp name="userid" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${SESSION_USERID}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">userid</stringProp>
</elementProp>
<elementProp name="groupid" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">0</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">groupid</stringProp>
</elementProp>
<elementProp name="edit" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">0</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">edit</stringProp>
</elementProp>
<elementProp name="reply" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${forumreplyid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">reply</stringProp>
</elementProp>
<elementProp name="sesskey" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${SESSION_SESSKEY}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">sesskey</stringProp>
</elementProp>
<elementProp name="_qf__mod_forum_post_form" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">1</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">_qf__mod_forum_post_form</stringProp>
</elementProp>
<elementProp name="subject" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">Re: I am the test plan reply subject</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">subject</stringProp>
</elementProp>
<elementProp name="message[itemid]" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${SESSION_FORUMFORMITEMID}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">message[itemid]</stringProp>
</elementProp>
<elementProp name="message[format]" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">1</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">message[format]</stringProp>
</elementProp>
<elementProp name="message[text]" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">I am the test plan reply message</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">message[text]</stringProp>
</elementProp>
<elementProp name="parent" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${forumreplyid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">parent</stringProp>
</elementProp>
<elementProp name="subscribe" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">1</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">subscribe</stringProp>
</elementProp>
<elementProp name="attachments" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${SESSION_FORUMFORMATTACHMENTS}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">attachments</stringProp>
</elementProp>
<elementProp name="timestart" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">0</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">timestart</stringProp>
</elementProp>
<elementProp name="timeend" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">0</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">timeend</stringProp>
</elementProp>
<elementProp name="submitbutton" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">Post to forum</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">submitbutton</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/mod/forum/post.php</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">false</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View course once more" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="id" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${courseid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">id</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/course/view.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="View course participants" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="id" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${courseid}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">id</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/user/index.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Logout" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="sesskey" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">${SESSION_SESSKEY}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">sesskey</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">${sitepath}/login/logout.php</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<boolProp name="HTTPSampler.monitor">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
<BeanShellListener guiclass="TestBeanGUI" testclass="BeanShellListener" testname="Create php array results" enabled="true">
<stringProp name="filename">recorder.bsf</stringProp>
<stringProp name="parameters"></stringProp>
<boolProp name="resetInterpreter">false</boolProp>
<stringProp name="script"></stringProp>
</BeanShellListener>
<hashTree/>
<ResultCollector guiclass="SimpleDataWriter" testclass="ResultCollector" testname="Simple Data Writer" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>true</xml>
<fieldNames>false</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
</value>
</objProp>
<stringProp name="filename">runs_samples/data.${runtimestamp}.jtl</stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
@@ -0,0 +1,33 @@
@core @core_course @tool_generator
Feature: Admins can create test courses
In order to create testing information
As an admin
I need to create testing courses quickly
@javascript
Scenario: 'Auto-enrol admin in new courses' setting when creating a test course as admin
Given I log in as "admin"
And the following config values are set as admin:
| enroladminnewcourse | 0 |
And I navigate to "Development > Make test course" in site administration
And I set the following fields to these values:
| Size of course | XS |
| Course full name | Fake course for testing |
| Course short name | fake |
And I press "Create course"
And I click on "Continue" "link"
And I navigate to course participants
Then I should not see "Teacher"
And I should not see "Nothing to display"
And the following config values are set as admin:
| enroladminnewcourse | 1 |
And I navigate to "Courses > Add a new course" in site administration
And I navigate to "Development > Make test course" in site administration
And I set the following fields to these values:
| Size of course | XS |
| Course full name | New fake course for testing |
| Course short name | newfake |
And I press "Create course"
And I click on "Continue" "link"
And I navigate to course participants
And I should see "Teacher"
@@ -0,0 +1,64 @@
@tool @tool_generator @_file_upload
Feature: Create testing scenarios using generators
In order to execute manual tests
As a developer
I need to use a feature file to execute generators into the current instance
@javascript
Scenario: Create a testing scenario with a course enrolled users and activities
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario.feature" file to "Feature file" filemanager
And I press "Import"
And I should see "Scenario: Create course content"
Then I am on the "C1" "Course" page
And I should see "Activity sample 1" in the "Section 1" "section"
And I should see "Activity sample 2" in the "Section 1" "section"
And I navigate to course participants
And I should see "Teacher Test1"
And I should see "Student Test1"
And I should see "Student Test2"
And I should see "Student Test3"
And I should see "Student Test4"
And I should see "Student Test5"
@javascript
Scenario: Prevent creating a testing scenario with no steps to execute
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongempty.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "There are no steps to execute in the file."
@javascript
Scenario: Prevent creating a testing scenario with only background steps to execute
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongonlybackground.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "There are no steps to execute in the file."
@javascript
Scenario: Prevent creating a testing scenario with a wrong file format
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongformat.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "Error parsing feature file"
@javascript
Scenario: Prevent creating a testing scenario with non generator steps
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongstep.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "The file format is not valid or contains invalid steps"
@javascript
Scenario: Prevent creating a testing scenario from a scenario outline
Given I log in as "admin"
And I navigate to "Development > Create testing scenarios" in site administration
When I upload "admin/tool/generator/tests/fixtures/testscenario/scenario_wrongoutline.feature" file to "Feature file" filemanager
And I press "Import"
Then I should see "Scenario outlines are not supported"
Then I should see "There are no steps to execute in the file"
@@ -0,0 +1,28 @@
Feature: Prepare scenario for testing
Scenario: Create course content
Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |
| numsections | 3 |
| initsections | 1 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section | visible |
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | 1 |
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | 0 |
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | sample@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And "5" "users" exist with the following data:
| username | student[count] |
| firstname | Student |
| lastname | Test[count] |
| email | student[count]@example.com |
And "5" "course enrolments" exist with the following data:
| user | student[count] |
| course | C1 |
| role | student |
@@ -0,0 +1,3 @@
Feature: Prepare scenario for testing
Scenario: only empty scenario 1.
Scenario: only empty scenario 2.
@@ -0,0 +1,5 @@
This is not a valid feature file.
However: it has lines
indented and stuff
like if it was.
@@ -0,0 +1,11 @@
Feature: Invalid texting because background is not compatible
Background:
Given the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| category | 0 |
| numsections | 3 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section | visible |
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | 1 |
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | 0 |
@@ -0,0 +1,14 @@
Feature: Prepare scenario for testing
Scenario Outline: test outline scenarios are not supported yet
Given the following "course" exists:
| fullname | <name> |
| shortname | <shortname> |
| category | 0 |
| numsections | 3 |
Examples:
| name | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
| Course 3 | C3 |
@@ -0,0 +1,13 @@
Feature: Contains wrong steps
Scenario: Scenario with non generator steps
Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |
| numsections | 3 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section | visible |
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | 1 |
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | 0 |
And I click on "Tokens filter" "link"
@@ -0,0 +1,216 @@
<?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/>.
namespace tool_generator\local\testscenario;
/**
* Tests for parsedfeature class.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodel.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \tool_generator\local\testscenario\parsedfeature
*/
class parsedfeature_test extends \advanced_testcase {
/**
* Get a parsed feature from a content.
* @param string $content the feature content.
* @return parsedfeature the parsed feature.
*/
private function get_feature_from_content(string $content): parsedfeature {
$runner = new runner();
$runner->init();
return $runner->parse_feature($content);
}
/**
* Test for parse_feature.
* @covers ::get_general_error
* @covers ::add_scenario
* @covers ::add_step
*/
public function test_general_error(): void {
$nosteps = get_string('testscenario_nosteps', 'tool_generator');
$invalidfile = get_string('testscenario_invalidfile', 'tool_generator');
$parsedfeature = new parsedfeature();
$this->assertEquals($nosteps, $parsedfeature->get_general_error());
$parsedfeature->add_scenario('Scenario', 'Test scenario');
$this->assertEquals($nosteps, $parsedfeature->get_general_error());
// Add some valid step.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | sample@example.com |
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertEquals('', $parsedfeature->get_general_error());
// Only generator methods are allowed.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Not generator
Given I am in a course
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertEquals($invalidfile, $parsedfeature->get_general_error());
}
/**
* Test for parse_feature.
* @covers ::is_valid
* @covers ::add_scenario
* @covers ::add_step
*/
public function test_is_valid(): void {
$parsedfeature = new parsedfeature();
$this->assertFalse($parsedfeature->is_valid());
$parsedfeature->add_scenario('Scenario', 'Test scenario');
$this->assertFalse($parsedfeature->is_valid());
// Add some valid step.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | sample@example.com |
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertTrue($parsedfeature->is_valid());
// Only generator methods are allowed.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Not generator
Given I am in a course
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertFalse($parsedfeature->is_valid());
}
/**
* Test for ading steps into scenarios.
* @covers ::add_step
* @covers ::add_scenario
* @covers ::get_all_steps
* @covers ::get_scenarios
*/
public function test_add_step(): void {
$parsedfeature = new parsedfeature();
$this->assertEquals(0, count($parsedfeature->get_all_steps()));
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | sample@example.com |
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step);
$this->assertEquals(1, count($parsedfeature->get_all_steps()));
// Should create a default scenario.
$scenarios = $parsedfeature->get_scenarios();
$this->assertEquals(1, count($scenarios));
$this->assertEquals('scenario', $scenarios[0]->type);
$this->assertEquals('', $scenarios[0]->name);
$this->assertEquals(1, count($scenarios[0]->steps));
$this->assertEquals($step, $scenarios[0]->steps[0]);
$this->assertEquals('', $scenarios[0]->error);
// Add a second step.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Not generator
Given I am in a course
');
$step2 = $extrafeature->get_all_steps()[0];
$parsedfeature->add_step($step2);
$scenarios = $parsedfeature->get_scenarios();
$this->assertEquals(1, count($scenarios));
$this->assertEquals('scenario', $scenarios[0]->type);
$this->assertEquals('', $scenarios[0]->name);
$this->assertEquals(2, count($scenarios[0]->steps));
$this->assertEquals($step, $scenarios[0]->steps[0]);
$this->assertEquals($step2, $scenarios[0]->steps[1]);
$this->assertEquals('', $scenarios[0]->error);
// Create a new scenario.
$parsedfeature->add_scenario('scenario', 'Test scenario 2');
$parsedfeature->add_step($step2);
$scenarios = $parsedfeature->get_scenarios();
$this->assertEquals(2, count($scenarios));
// Scenario 1.
$this->assertEquals('scenario', $scenarios[0]->type);
$this->assertEquals('', $scenarios[0]->name);
$this->assertEquals(2, count($scenarios[0]->steps));
$this->assertEquals($step, $scenarios[0]->steps[0]);
$this->assertEquals($step2, $scenarios[0]->steps[1]);
$this->assertEquals('', $scenarios[0]->error);
// Scenario 2.
$this->assertEquals('scenario', $scenarios[1]->type);
$this->assertEquals('Test scenario 2', $scenarios[1]->name);
$this->assertEquals(1, count($scenarios[1]->steps));
$this->assertEquals($step2, $scenarios[1]->steps[0]);
$this->assertEquals('', $scenarios[1]->error);
}
/**
* Test for ading errors into scenarios.
* @covers ::add_error
* @covers ::add_scenario
* @covers ::add_step
* @covers ::get_scenarios
*/
public function test_add_error(): void {
$parsedfeature = new parsedfeature();
// Add some valid step.
$extrafeature = $this->get_feature_from_content('Feature: Test feature
Scenario: Create users
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Test1 | sample@example.com |
');
$step = $extrafeature->get_all_steps()[0];
$parsedfeature->add_scenario('scenario', 'Test scenario 1');
$parsedfeature->add_step($step);
$parsedfeature->add_error('Error message');
$parsedfeature->add_scenario('scenario', 'Test scenario 2');
$parsedfeature->add_step($step);
$scenarios = $parsedfeature->get_scenarios();
$this->assertEquals(2, count($scenarios));
// Scenario 1.
$this->assertEquals('scenario', $scenarios[0]->type);
$this->assertEquals('Test scenario 1', $scenarios[0]->name);
$this->assertEquals(1, count($scenarios[0]->steps));
$this->assertEquals($step, $scenarios[0]->steps[0]);
$this->assertEquals('Error message', $scenarios[0]->error);
// Scenario 2.
$this->assertEquals('scenario', $scenarios[1]->type);
$this->assertEquals('Test scenario 2', $scenarios[1]->name);
$this->assertEquals(1, count($scenarios[1]->steps));
$this->assertEquals($step, $scenarios[1]->steps[0]);
$this->assertEquals('', $scenarios[1]->error);
}
}
@@ -0,0 +1,107 @@
<?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/>.
namespace tool_generator\local\testscenario;
/**
* Tests for runner class.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodel.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \tool_generator\local\testscenario\runner
*/
class runner_test extends \advanced_testcase {
/**
* Test for parse_feature.
* @covers ::parse_feature
* @covers ::execute
*/
public function test_parse_and_execute_feature(): void {
global $CFG, $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Call the init method to include all behat libraries and attributes.
$runner = new runner();
$runner->init();
$featurefile = $CFG->dirroot . '/admin/tool/generator/tests/fixtures/testscenario/scenario.feature';
$contents = file_get_contents($featurefile);
$feature = $runner->parse_feature($contents);
$this->assertEquals(2, count($feature->get_scenarios()));
$this->assertEquals(6, count($feature->get_all_steps()));
$this->assertTrue($feature->is_valid());
$result = $runner->execute($feature);
$this->assertTrue($result);
// Validate everything is created.
$this->assertEquals(
1,
$DB->count_records('course', ['shortname' => 'C1'])
);
$course = $DB->get_record('course', ['shortname' => 'C1']);
$this->assertEquals(
2,
$DB->count_records('course_modules', ['course' => $course->id])
);
$this->assertEquals(
1,
$DB->count_records('user', ['firstname' => 'Teacher'])
);
$this->assertEquals(
5,
$DB->count_records('user', ['firstname' => 'Student'])
);
$context = \context_course::instance($course->id);
$this->assertEquals(
6,
$DB->count_records('role_assignments', ['contextid' => $context->id])
);
}
/**
* Test for parse_feature.
* @covers ::parse_feature
* @covers ::execute
*/
public function test_parse_and_execute_wrong_feature(): void {
global $CFG, $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Call the init method to include all behat libraries and attributes.
$runner = new runner();
$runner->init();
$featurefile = $CFG->dirroot . '/admin/tool/generator/tests/fixtures/testscenario/scenario_wrongstep.feature';
$contents = file_get_contents($featurefile);
$feature = $runner->parse_feature($contents);
$this->assertEquals(1, count($feature->get_scenarios()));
$this->assertEquals(3, count($feature->get_all_steps()));
$this->assertFalse($feature->is_valid());
$result = $runner->execute($feature);
$this->assertFalse($result);
$this->assertEquals(0, $DB->count_records('course', ['shortname' => 'C1']));
}
}
@@ -0,0 +1,238 @@
<?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/>.
namespace tool_generator\local\testscenario;
use behat_data_generators;
use Behat\Gherkin\Parser;
use Behat\Gherkin\Lexer;
use Behat\Gherkin\Keywords\ArrayKeywords;
use Behat\Gherkin\Node\StepNode;
/**
* Tests for steprunner class.
*
* @package tool_generator
* @copyright 2023 Ferran Recio <ferran@moodel.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \tool_generator\local\testscenario\steprunner
*/
class steprunner_test extends \advanced_testcase {
public static function setUpBeforeClass(): void {
// Call the init method to include all behat libraries and attributes.
$runner = new runner();
$runner->init();
}
/**
* Get a step node from a string.
* @param string $step the step string.
* @return StepNode the step node.
*/
private function get_step(string $step): StepNode {
$content = 'Feature: Test feature
Scenario: Test scenario
' . $step . '
';
$method = new \ReflectionMethod(runner::class, 'get_parser');
$parser = $method->invoke(new runner());
$feature = $parser->parse($content);
$scenario = $feature->getScenarios()[0];
$steps = $scenario->getSteps();
return $steps[0];
}
/**
* Test for parse_feature.
* @covers ::is_valid
* @param string $step the step to validate.
* @param bool $expected if the step is expected to be valid.
* @dataProvider execute_steps_provider
*/
public function test_is_valid(string $step, bool $expected): void {
$generator = new behat_data_generators();
$validsteps = [
'/^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/' => 'the_following_entities_exist',
':count :entitytype exist with the following data:' => 'the_following_repeated_entities_exist',
'the following :entitytype exists:' => 'the_following_entity_exists',
];
$step = $this->get_step($step);
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertEquals($expected, $steprunner->is_valid());
}
/**
* Test for execute step.
*
* @covers ::is_executed
* @covers ::execute
* @param string $step the step to execute.
* @param bool $expected if the step is expected to be executed.
* @dataProvider execute_steps_provider
*/
public function test_execute(string $step, bool $expected): void {
global $DB;
$this->resetAfterTest();
$generator = new behat_data_generators();
$validsteps = [
'/^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/' => 'the_following_entities_exist',
':count :entitytype exist with the following data:' => 'the_following_repeated_entities_exist',
'the following :entitytype exists:' => 'the_following_entity_exists',
];
$step = $this->get_step($step);
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertFalse($steprunner->is_executed());
$result = $steprunner->execute();
$this->assertEquals($expected, $result);
$this->assertEquals($expected, $steprunner->is_executed());
if ($expected) {
// Validate everything is created.
$this->assertEquals(
1,
$DB->count_records('course', ['shortname' => 'C1'])
);
}
}
/**
* Data provider for test_execute.
* @return array the data.
*/
public static function execute_steps_provider(): array {
return [
'Following exists' => [
'step' => 'Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |',
'expected' => true,
],
'Following exist' => [
'step' => 'Given the following "course" exist:
| fullname | shortname | category |
| Course test | C1 | 0 |',
'expected' => true,
],
'Repeated entities' => [
'step' => 'Given "1" "courses" exist with the following data:
| fullname | Course test |
| shortname | C[count] |
| category | 0 |
| numsections | 3 |',
'expected' => true,
],
'Invalid step' => [
'step' => 'Given I click on "Tokens filter" "link"',
'expected' => false,
],
];
}
/**
* Test for execute step.
* @covers ::is_executed
* @covers ::execute
* @covers ::get_error
*/
public function test_execute_duplicated(): void {
global $DB;
$this->resetAfterTest();
$generator = new behat_data_generators();
$validsteps = [
'/^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/' => 'the_following_entities_exist',
':count :entitytype exist with the following data:' => 'the_following_repeated_entities_exist',
'the following :entitytype exists:' => 'the_following_entity_exists',
];
$step = $this->get_step('Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |');
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertFalse($steprunner->is_executed());
$result = $steprunner->execute();
$this->assertTrue($result);
$this->assertTrue($steprunner->is_executed());
$this->assertEquals('', $steprunner->get_error());
// Validate everything is created.
$this->assertEquals(
1,
$DB->count_records('course', ['shortname' => 'C1'])
);
// Execute the same course creation.
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertFalse($steprunner->is_executed());
$result = $steprunner->execute();
$this->assertFalse($result);
$this->assertTrue($steprunner->is_executed());
$this->assertEquals(get_string('shortnametaken', 'error', 'C1'), $steprunner->get_error());
}
/**
* Test for parse_feature.
* @covers ::get_text
* @covers ::get_arguments_string
*/
public function test_get_step_content(): void {
$generator = new behat_data_generators();
$validsteps = [
'/^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/' => 'the_following_entities_exist',
':count :entitytype exist with the following data:' => 'the_following_repeated_entities_exist',
'the following :entitytype exists:' => 'the_following_entity_exists',
];
$step = $this->get_step('Given the following "course" exists:
| fullname | Course test |
| shortname | C1 |
| category | 0 |
| numsections | 3 |');
$steprunner = new steprunner($generator, $validsteps, $step);
$this->assertEquals(
'the following "course" exists:',
$steprunner->get_text()
);
$data = [
'| fullname | Course test |',
'| shortname | C1 |',
'| category | 0 |',
'| numsections | 3 |',
];
$arguments = explode("\n", $steprunner->get_arguments_string());
$this->assertEquals(
$data,
$arguments
);
}
}
@@ -0,0 +1,230 @@
<?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/>.
namespace tool_generator;
use tool_generator_course_backend;
/**
* Automated unit testing. This tests the 'make large course' backend,
* using the 'XS' option so that it completes quickly.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class maketestcourse_test extends \advanced_testcase {
/**
* Creates a small test course and checks all the components have been put in place.
*/
public function test_make_xs_course(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$expectedshortname = 'TOOL_MAKELARGECOURSE_XS';
$expectedfullname = 'Ridiculous fullname';
$expectedsummary = 'who even knows what this is about';
// Create the XS course.
$backend = new tool_generator_course_backend(
$expectedshortname,
0,
false,
false,
false,
$expectedfullname,
$expectedsummary
);
$courseid = $backend->make();
// Get course details.
$course = get_course($courseid);
$context = \context_course::instance($courseid);
$modinfo = get_fast_modinfo($course);
// Check course names.
$this->assertEquals($expectedshortname, $course->shortname);
$this->assertEquals($expectedfullname, $course->fullname);
// Check course summary.
$this->assertEquals($expectedsummary, $course->summary);
// Check sections (just section 0 plus one other).
$this->assertEquals(2, count($modinfo->get_section_info_all()));
// Check user is enrolled.
// enroladminnewcourse is enabled by default, so admin is also enrolled as teacher.
$users = get_enrolled_users($context);
$this->assertEquals(2, count($users));
$usernames = array_map(function($user) {
return $user->username;
}, $users);
$this->assertTrue(in_array('admin', $usernames));
$this->assertTrue(in_array('tool_generator_000001', $usernames));
// Check there's a page on the course.
$pages = $modinfo->get_instances_of('page');
$this->assertEquals(1, count($pages));
// Check there are small files.
$resources = $modinfo->get_instances_of('resource');
$ok = false;
foreach ($resources as $resource) {
if ($resource->sectionnum == 0) {
// The one in section 0 is the 'small files' resource.
$ok = true;
break;
}
}
$this->assertTrue($ok);
// Check it contains 2 files (the default txt and a dat file).
$fs = get_file_storage();
$resourcecontext = \context_module::instance($resource->id);
$files = $fs->get_area_files($resourcecontext->id, 'mod_resource', 'content', false, 'filename', false);
$files = array_values($files);
$this->assertEquals(2, count($files));
$this->assertEquals('resource1.txt', $files[0]->get_filename());
$this->assertEquals('smallfile0.dat', $files[1]->get_filename());
// Check there's a single 'big' file (it's actually only 8KB).
$ok = false;
foreach ($resources as $resource) {
if ($resource->sectionnum == 1) {
$ok = true;
break;
}
}
$this->assertTrue($ok);
// Check it contains 2 files.
$resourcecontext = \context_module::instance($resource->id);
$files = $fs->get_area_files($resourcecontext->id, 'mod_resource', 'content', false, 'filename', false);
$files = array_values($files);
$this->assertEquals(2, count($files));
$this->assertEquals('bigfile0.dat', $files[0]->get_filename());
$this->assertEquals('resource2.txt', $files[1]->get_filename());
// Get forum and count the number of posts on it.
$forums = $modinfo->get_instances_of('forum');
$forum = reset($forums);
$posts = $DB->count_records_sql("
SELECT
COUNT(1)
FROM
{forum_posts} fp
JOIN {forum_discussions} fd ON fd.id = fp.discussion
WHERE
fd.forum = ?", array($forum->instance));
$this->assertEquals(2, $posts);
}
/**
* Creates an small test course with fixed data set and checks the used sections and users.
*/
public function test_fixed_data_set(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create the S course (more sections and activities than XS).
$backend = new tool_generator_course_backend('TOOL_S_COURSE_1', 1, true, false, false);
$courseid = $backend->make();
// Get course details.
$course = get_course($courseid);
$modinfo = get_fast_modinfo($course);
// Check module instances belongs to section 1.
$instances = $modinfo->get_instances_of('page');
foreach ($instances as $instance) {
$this->assertEquals(1, $instance->sectionnum);
}
// Users that started discussions are the same.
$forums = $modinfo->get_instances_of('forum');
$discussions = forum_get_discussions(reset($forums), 'd.timemodified ASC');
$lastusernumber = 0;
$discussionstarters = array();
foreach ($discussions as $discussion) {
$usernumber = \core_user::get_user($discussion->userid, 'id, idnumber')->idnumber;
// Checks that the users are odd numbers.
$this->assertEquals(1, $usernumber % 2);
// Checks that the users follows an increasing order.
$this->assertGreaterThan($lastusernumber, $usernumber);
$lastusernumber = $usernumber;
$discussionstarters[$discussion->userid] = $discussion->subject;
}
}
/**
* Creates a small test course specifying a maximum size and checks the generated files size is limited.
*/
public function test_filesize_limit(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Limit.
$filesizelimit = 100;
// Create a limited XS course.
$backend = new tool_generator_course_backend('TOOL_XS_LIMITED', 0, false, $filesizelimit, false);
$courseid = $backend->make();
$course = get_course($courseid);
$modinfo = get_fast_modinfo($course);
// Check there are small files.
$fs = get_file_storage();
$resources = $modinfo->get_instances_of('resource');
foreach ($resources as $resource) {
$resourcecontext = \context_module::instance($resource->id);
$files = $fs->get_area_files($resourcecontext->id, 'mod_resource', 'content', false, 'filename', false);
foreach ($files as $file) {
if ($file->get_mimetype() == 'application/octet-stream') {
$this->assertLessThanOrEqual($filesizelimit, $file->get_filesize());
}
}
}
// Create a non-limited XS course.
$backend = new tool_generator_course_backend('TOOL_XS_NOLIMITS', 0, false, false, false);
$courseid = $backend->make();
$course = get_course($courseid);
$modinfo = get_fast_modinfo($course);
// Check there are small files.
$fs = get_file_storage();
$resources = $modinfo->get_instances_of('resource');
foreach ($resources as $resource) {
$resourcecontext = \context_module::instance($resource->id);
$files = $fs->get_area_files($resourcecontext->id, 'mod_resource', 'content', false, 'filename', false);
foreach ($files as $file) {
if ($file->get_mimetype() == 'application/octet-stream') {
$this->assertGreaterThan($filesizelimit, (int)$file->get_filesize());
}
}
}
}
}
@@ -0,0 +1,108 @@
<?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/>.
/**
* Unit test for the site generator
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_generator;
use tool_generator_site_backend;
defined('MOODLE_INTERNAL') || die();
/**
* Unit test for the site generator
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class maketestsite_test extends \advanced_testcase {
/**
* Checks that site courses shortnames are properly generated.
*/
public function test_shortnames_generation(): void {
$this->resetAfterTest();
$this->setAdminUser();
$generator = $this->getDataGenerator();
// Shortname common prefix.
$prefix = tool_generator_site_backend::SHORTNAMEPREFIX;
$record = array();
// Without courses will be 0.
$lastshortname = testable_tool_generator_site_backend::get_last_testcourse_id();
$this->assertEquals(0, $lastshortname);
// Without {$prefix} + {no integer} courses will be 0.
$record['shortname'] = $prefix . 'AA';
$generator->create_course($record);
$record['shortname'] = $prefix . '__';
$generator->create_course($record);
$record['shortname'] = $prefix . '12.2';
$generator->create_course($record);
$lastshortname = testable_tool_generator_site_backend::get_last_testcourse_id();
$this->assertEquals(0, $lastshortname);
// With {$prefix} + {integer} courses will be the higher one.
$record['shortname'] = $prefix . '2';
$generator->create_course($record);
$record['shortname'] = $prefix . '20';
$generator->create_course($record);
$record['shortname'] = $prefix . '8';
$generator->create_course($record);
$lastshortname = testable_tool_generator_site_backend::get_last_testcourse_id();
$this->assertEquals(20, $lastshortname);
// Numeric order.
for ($i = 9; $i < 14; $i++) {
$record['shortname'] = $prefix . $i;
$generator->create_course($record);
}
$lastshortname = testable_tool_generator_site_backend::get_last_testcourse_id();
$this->assertEquals(20, $lastshortname);
}
}
/**
* Silly class to access site_backend internal methods.
*
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class testable_tool_generator_site_backend extends tool_generator_site_backend {
/**
* Public accessor.
*
* @return int
*/
public static function get_last_testcourse_id() {
return parent::get_last_testcourse_id();
}
}
+20
View File
@@ -0,0 +1,20 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 4.2 ===
* Any activity module (mod) that declares the 'xxx_course_backend_generator_create_activity' function in lib.php will now be able to create instance of the
module in the test course. There is now an additional parameter to the admin/tool/generator/cli/maketestcourse.php command so we can whitelist all the modules
that need to be added to the course via the additionalmodules parameter.
See bigbluebuttonbn_course_backend_generator_create_activity as an example.
=== 4.0 ===
* Function tool_generator_testplan_backend::create_users_file() now supports to pass the size of the testing plan,
that needs to be equal or smaller than the size of the generated site. That's used to effectively restrict the
exported number of users to the number of threads the jmeter plan will have (previously all the enrolled users
were being exported, with that leading to "false" loops (users not really looping X times).
=== 3.7 ===
* Function tool_generator_testplan_backend::get_course_options() is removed, the 'course' form element is used instead.
+29
View File
@@ -0,0 +1,29 @@
<?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/>.
/**
* Version details.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200;
$plugin->requires = 2024041600;
$plugin->component = 'tool_generator';