diff --git a/bulktest.php b/bulktest.php index 0f8b7ff9..6b78720d 100644 --- a/bulktest.php +++ b/bulktest.php @@ -25,6 +25,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +namespace qtype_coderunner; + define('NO_OUTPUT_BUFFERING', true); require_once(__DIR__ . '/../../../config.php'); @@ -37,15 +39,16 @@ $repeatrandomonly = optional_param('repeatrandomonly', 1, PARAM_INT); $nruns = optional_param('nruns', 1, PARAM_INT); $questionids = optional_param('questionids', '', PARAM_RAW); // A list of specific questions to check, eg, for rechecking failed tests. +$clearcachefirst = optional_param('clearcachefirst', 0, PARAM_INT); // Login and check permissions. require_login(); -$context = context::instance_by_id($contextid); +$context = \context::instance_by_id($contextid); require_capability('moodle/question:editall', $context); $urlparams = ['contextid' => $context->id, 'categoryid' => $categoryid, 'randomseed' => $randomseed, - 'repeatrandomonly' => $repeatrandomonly, 'nruns' => $nruns, 'questionids' => $questionids]; + 'repeatrandomonly' => $repeatrandomonly, 'nruns' => $nruns, 'clearcachefirst' => $clearcachefirst, 'questionids' => $questionids]; $PAGE->set_url('/question/type/coderunner/bulktest.php', $urlparams); $PAGE->set_context($context); $title = get_string('bulktesttitle', 'qtype_coderunner', $context->get_context_name()); @@ -66,12 +69,13 @@ // Create the helper class. -$bulktester = new qtype_coderunner_bulk_tester( +$bulktester = new bulk_tester( $context, $categoryid, $randomseed, $repeatrandomonly, - $nruns + $nruns, + $clearcachefirst ); // Was: Release the session, so the user can do other things while this runs. @@ -83,6 +87,12 @@ echo $OUTPUT->header(); echo $OUTPUT->heading($title, 4); +// Release the session, so the user can do other things while this runs. +\core\session\manager::write_close(); + + +ini_set('memory_limit', '1024M'); // For big question banks - TODO: make this a setting? + // Run the tests. if (count($questionids) == 0) { $bulktester->run_all_tests_for_context(); diff --git a/bulktestall.php b/bulktestall.php index ee030f36..70c96e4b 100644 --- a/bulktestall.php +++ b/bulktestall.php @@ -23,6 +23,13 @@ * @copyright 2016 Richard Lobb, The University of Canterbury * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +namespace qtype_coderunner; + +use context; +use context_system; +use context_course; +use html_writer; +use moodle_url; define('NO_OUTPUT_BUFFERING', true); @@ -59,7 +66,8 @@ echo $OUTPUT->heading($title, 1); // Run the tests. -$contextdata = qtype_coderunner_bulk_tester::get_num_coderunner_questions_by_context(); +ini_set('memory_limit', '2048M'); // For big question banks - TODO: make this a setting? +$contextdata = bulk_tester::get_num_coderunner_questions_by_context(); foreach ($contextdata as $contextid => $numcoderunnerquestions) { if ($skipping && $contextid != $startfromcontextid) { continue; @@ -68,8 +76,9 @@ $testcontext = context::instance_by_id($contextid); if (has_capability('moodle/question:editall', $context)) { $PAGE->set_context($testcontext); // Helps grading cache pickup right course id. - $bulktester = new qtype_coderunner_bulk_tester($testcontext); + $bulktester = new bulk_tester($testcontext); echo $OUTPUT->heading(get_string('bulktesttitle', 'qtype_coderunner', $testcontext->get_context_name())); + echo html_writer::tag('p', 'Note: Grading cache not cleared -- do it from admin-plugins-cache if you really want to clear the cache for all course!'); echo html_writer::tag('p', html_writer::link( new moodle_url( '/question/type/coderunner/bulktestall.php', @@ -86,5 +95,5 @@ } // Display the final summary. -qtype_coderunner_bulk_tester::print_summary_after_bulktestall($numpasses, $allfailingtests, $allmissinganswers); +bulk_tester::print_summary_after_bulktestall($numpasses, $allfailingtests, $allmissinganswers); echo $OUTPUT->footer(); diff --git a/bulktestindex.php b/bulktestindex.php index dc85c5ef..3e5bf2e6 100644 --- a/bulktestindex.php +++ b/bulktestindex.php @@ -13,6 +13,12 @@ // // You should have received a copy of the GNU General Public License // along with Stack. If not, see <http://www.gnu.org/licenses/>. +namespace qtype_coderunner; + +use context_system; +use context; +use html_writer; +use moodle_url; require_once(__DIR__ . '/../../../config.php'); require_once($CFG->libdir . '/questionlib.php'); @@ -32,7 +38,7 @@ } // Find in which contexts the user can edit questions. -$questionsbycontext = qtype_coderunner_bulk_tester::get_num_coderunner_questions_by_context(); +$questionsbycontext = bulk_tester::get_num_coderunner_questions_by_context(); $availablequestionsbycontext = []; foreach ($questionsbycontext as $contextid => $numcoderunnerquestions) { $context = context::instance_by_id($contextid); @@ -56,7 +62,7 @@ echo <<<HTML <div class="bulk-test-config" style="margin-bottom: 20px; padding: 10px; background-color: #f5f5f5; border: 1px solid #ddd;"> <h3>Test Configuration</h3> - <div style="margin-bottom: 10px; display: grid; grid-template-columns: auto 80px; gap: 10px; align-items: center; max-width: 240px;"> + <div style="margin-bottom: 10px; display: grid; grid-template-columns: auto 80px; gap: 10px; align-items: center; max-width:400px;"> <label for="nruns">Number of runs:</label> <input type="number" id="nruns" value="{$nruns}" min="1" style="width: 80px;"> @@ -67,6 +73,10 @@ <div> <input type="checkbox" id="repeatrandomonly" checked> </div> + <label for="clearcachefirst">Clear course grading cache first (be careful):</label> + <div> + <input type="checkbox" id="clearcachefirst" onchange="confirmCheckboxChange(this)"> + </div> </div> </div> HTML; @@ -77,7 +87,8 @@ } else { echo get_string('bulktestinfo', 'qtype_coderunner'); echo $OUTPUT->heading(get_string('coderunnercontexts', 'qtype_coderunner')); - + $jobehost = get_config('qtype_coderunner', 'jobe_host'); + echo html_writer::tag('p', '<b>jobe_host:</b> ' . $jobehost); echo html_writer::start_tag('ul'); $buttonstyle = 'background-color: #FFFFD0; padding: 2px 2px 0px 2px;border: 4px solid white'; foreach ($availablequestionsbycontext as $name => $info) { @@ -87,7 +98,8 @@ $testallstr = get_string('bulktestallincontext', 'qtype_coderunner'); $testalltitledetails = ['title' => get_string('testalltitle', 'qtype_coderunner'), 'style' => $buttonstyle]; $testallspan = html_writer::tag( - 'span', $testallstr, + 'span', + $testallstr, ['class' => 'test-link', 'data-contextid' => $contextid, 'style' => $buttonstyle . ';cursor:pointer;'] @@ -106,14 +118,16 @@ echo html_writer::start_tag('li', ['class' => $class]); echo $litext; - $categories = qtype_coderunner_bulk_tester::get_categories_for_context($contextid); + $categories = bulk_tester::get_categories_for_context($contextid); echo html_writer::start_tag('ul', ['class' => 'expandable']); $titledetails = ['title' => get_string('testallincategory', 'qtype_coderunner')]; foreach ($categories as $cat) { if ($cat->count > 0) { $linktext = $cat->name . ' (' . $cat->count . ')'; - $span = html_writer::tag('span', $linktext, + $span = html_writer::tag( + 'span', + $linktext, ['class' => 'test-link', 'data-contextid' => $contextid, 'data-categoryid' => $cat->id, @@ -127,7 +141,9 @@ } echo html_writer::end_tag('ul'); - + echo html_writer::empty_tag('br'); + echo html_writer::tag('hr', ''); + echo html_writer::empty_tag('br'); if (has_capability('moodle/site:config', context_system::instance())) { echo html_writer::tag('p', html_writer::link( new moodle_url('/question/type/coderunner/bulktestall.php'), @@ -138,6 +154,17 @@ echo <<<SCRIPT_END <script> +function confirmCheckboxChange(checkbox) { + if (checkbox.checked) { + var prompt = "Are you sure you want to clear the cache for the selected course?"; + prompt = prompt + " This will clear the cache for all attempts on all questions!"; + const confirmed = confirm(prompt); + if (!confirmed) { + checkbox.checked = false; + } + } +} + document.addEventListener("DOMContentLoaded", function(event) { // Handle expandable sections var expandables = document.getElementsByClassName('expandable'); @@ -169,18 +196,19 @@ var nruns = document.getElementById('nruns').value; var randomseed = document.getElementById('randomseed').value; var repeatrandomonly = document.getElementById('repeatrandomonly').checked ? 1 : 0; + var clearcachefirst = document.getElementById('clearcachefirst').checked ? 1 : 0; // Build URL parameters var params = new URLSearchParams(); params.append('contextid', link.dataset.contextid); - params.append('randomseed', randomseed); - params.append('repeatrandomonly', repeatrandomonly); - params.append('nruns', nruns); - // Add category ID if present if (link.dataset.categoryid) { params.append('categoryid', link.dataset.categoryid); } + params.append('nruns', nruns); + params.append('randomseed', randomseed); + params.append('repeatrandomonly', repeatrandomonly); + params.append('clearcachefirst', clearcachefirst); // Construct and navigate to URL var url = M.cfg.wwwroot + '/question/type/coderunner/bulktest.php?' + params.toString(); diff --git a/classes/bulk_tester.php b/classes/bulk_tester.php index c28692e5..ff9cc6dc 100644 --- a/classes/bulk_tester.php +++ b/classes/bulk_tester.php @@ -26,7 +26,15 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class qtype_coderunner_bulk_tester { +namespace qtype_coderunner; + +use moodle_url; +use html_writer; +use question_bank; +use core_php_time_limit; +use question_state; + +class bulk_tester { /** @var context Context to run bulktester for. */ public $context; @@ -42,6 +50,9 @@ class qtype_coderunner_bulk_tester { /** @var int How many runs to do for each question. */ public $nruns; + /** @var int Whether or not to clear the grading cache for this context first Default: 0 . */ + public $clearcachefirst; + /** @var int The number of questions that passed tests. */ public $numpasses; @@ -78,23 +89,26 @@ class qtype_coderunner_bulk_tester { * @param int $repeatrandomonly when true(or 1), only repeats tests for questions with random in the name. * Default = true (or really 1). * @param int $nruns the number times to test each question. Default to 1. + * @param int $clearcachefirst If 1 then clears the grading cache (ignoring ttl) for the given context before running the tests. Default is 0. */ public function __construct( $context = null, $categoryid = null, $randomseed = -1, $repeatrandomonly = 1, - $nruns = 1 + $nruns = 1, + $clearcachefirst = 0 ) { if ($context === null) { $site = get_site(); // Get front page course. - $context = context_course::instance($site->id); + $context = \context_course::instance($site->id); } $this->context = $context; $this->categoryid = $categoryid; $this->randomseed = $randomseed; $this->repeatrandomonly = $repeatrandomonly; $this->nruns = $nruns; + $this->clearcachefirst = $clearcachefirst; $this->numpasses = 0; $this->numfails = 0; $this->failedquestionids = []; @@ -316,6 +330,13 @@ public function run_all_tests_for_context($questionidstoinclude = []) { $questiontestsurl = new moodle_url('/question/type/coderunner/questiontestrun.php'); $questiontestsurl->params($qparams); + // Clear grading cache if requested. usettl is set to false here. + if ($this->clearcachefirst) { + $purger = new cache_purger($this->context->id, false); + $purger->purge_cache_for_context(); + } + $jobehost = get_config('qtype_coderunner', 'jobe_host'); + echo html_writer::tag('p', '<b>jobe_host:</b> ' . $jobehost); $this->numpasses = 0; foreach ($categories as $currentcategoryid => $nameandcount) { $categoryname = $nameandcount->name; @@ -356,7 +377,7 @@ public function run_all_tests_for_context($questionidstoinclude = []) { for ($i = 0; $i < $nrunsthistime; $i++) { // Only records last outcome and message. try { - [$outcome, $message] = $this->load_and_test_question($question->id); + [$outcome, $message] = $this->load_and_test_question($question->id); } catch (Exception $e) { $message = $e->getMessage(); $outcome = self::FAIL; @@ -385,7 +406,7 @@ public function run_all_tests_for_context($questionidstoinclude = []) { } echo "</li>"; gc_collect_cycles(); // Because PHP's default memory management is rubbish. - flush(); // Force output tmemory_limito prevent timeouts and show progress. + flush(); // Force output to prevent timeouts and show progress. $qparams['category'] = $currentcategoryid . ',' . $this->context->id; $qparams['lastchanged'] = $question->id; $qparams['qperpage'] = 1000; @@ -431,7 +452,7 @@ private function load_and_test_question($questionid) { $status = self::FAIL; } } - } catch (qtype_coderunner_exception $e) { + } catch (exception $e) { if (isset($question)) { $questionname = ' ' . format_string($question->name); } else { @@ -462,7 +483,7 @@ private function test_question($question) { if (!empty($params['answer_language'])) { $response['language'] = $params['answer_language']; } else if (!empty($question->acelang) && strpos($question->acelang, ',') !== false) { - [$languages, $defaultlang] = qtype_coderunner_util::extract_languages($question->acelang); + [$languages, $defaultlang] = util::extract_languages($question->acelang); if ($defaultlang === '') { $defaultlang = $languages[0]; } @@ -471,7 +492,7 @@ private function test_question($question) { try { [$fraction, $state] = $question->grade_response($response, false); $ok = $state == question_state::$gradedright; - } catch (qtype_coderunner_exception $e) { + } catch (exception $e) { $ok = false; // If user clicks link to see why, they'll get the same exception. } return $ok; diff --git a/classes/jobesandbox.php b/classes/jobesandbox.php index c4ba6a03..f00a3775 100644 --- a/classes/jobesandbox.php +++ b/classes/jobesandbox.php @@ -162,7 +162,7 @@ public function execute($sourcecode, $language, $input, $files = null, $params = try { // Had to use try here as isset($PAGE->context) always seems to fail even if the context has been set. $context = $PAGE->context; - $courseid = $context->get_course_context(true)->instanceid; // raises exception if context is unknown. + $courseid = $context->get_course_context(true)->instanceid; // Raises exception if context is unknown. } catch (Exception $e) { $courseid = 1; // Use context of 1 as no $PAGE context is set, eg, could be a websocket UI run. } @@ -229,7 +229,8 @@ public function execute($sourcecode, $language, $input, $files = null, $params = } } - + // Add jobserver name(s) to runspec so jobs with different jobeservers are treated as different. + $runspec['jobeserver'] = $this->jobeserver; $cache = cache::make('qtype_coderunner', 'coderunner_grading_cache'); $runresult = null; if (get_config('qtype_coderunner', 'enablegradecache') && $usecache) { diff --git a/db/caches.php b/db/caches.php index ce6fbfc8..27112490 100644 --- a/db/caches.php +++ b/db/caches.php @@ -30,7 +30,7 @@ $definitions = [ 'coderunner_grading_cache' => [ 'mode' => cache_store::MODE_APPLICATION, - 'maxsize' => 50000000, // This will be ignored by the standard file cache + 'maxsize' => 50000000, // This will be ignored by the standard file cache. 'simplekeys' => true, 'simpledata' => false, 'canuselocalstore' => true, diff --git a/downloadquizattempts.php b/downloadquizattempts.php index f5b2e9b1..90d529af 100644 --- a/downloadquizattempts.php +++ b/downloadquizattempts.php @@ -27,15 +27,21 @@ * @copyright 2017 Richard Lobb, The University of Canterbury * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +namespace qtype_coderunner; + +use context_system; +use context_course; +use html_writer; +use moodle_url; + +require_once(__DIR__ . '/../../../config.php'); +require_once($CFG->libdir . '/questionlib.php'); define('NO_OUTPUT_BUFFERING', true); if (!defined('ANONYMISE')) { define('ANONYMISE', 0); } -require_once(__DIR__ . '/../../../config.php'); -require_once($CFG->libdir . '/questionlib.php'); - // Login and check permissions. $context = context_system::instance(); require_login(); @@ -47,7 +53,7 @@ $PAGE->requires->jquery_plugin('ui'); $PAGE->requires->jquery_plugin('ui-css'); -$courses = qtype_coderunner_bulk_tester::get_all_courses(); +$courses = bulk_tester::get_all_courses(); // Start display. echo $OUTPUT->header(); diff --git a/findduplicates.php b/findduplicates.php index c5ab2807..11e4394e 100644 --- a/findduplicates.php +++ b/findduplicates.php @@ -25,6 +25,9 @@ * @copyright 2018 and beyond Richard Lobb, The University of Canterbury * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +namespace qtype_coderunner; + +use context; define('NO_OUTPUT_BUFFERING', true); @@ -57,7 +60,7 @@ echo "<table class='table table-bordered table-striped'>\n"; echo "<tr><th>Q1 name</th><th>Q1 Category</th><th>Q2 name</th><th>Q2 category</th></tr>\n"; // Find all the duplicates. -$allquestionsmap = qtype_coderunner_bulk_tester::get_all_coderunner_questions_in_context($contextid); +$allquestionsmap = bulk_tester::get_all_coderunner_questions_in_context($contextid); $allquestions = array_values($allquestionsmap); $numduplicates = 0; for ($i = 0; $i < count($allquestions); $i++) { diff --git a/findduplicatesindex.php b/findduplicatesindex.php index c134a12c..de8c6298 100644 --- a/findduplicatesindex.php +++ b/findduplicatesindex.php @@ -24,6 +24,13 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +namespace qtype_coderunner; + +use context; +use context_system; +use html_writer; +use moodle_url; + require_once(__DIR__ . '/../../../config.php'); require_once($CFG->libdir . '/questionlib.php'); @@ -40,7 +47,7 @@ echo $OUTPUT->heading('Courses containing CodeRunner questions'); // Find in which contexts the user can edit questions. -$questionsbycontext = qtype_coderunner_bulk_tester::get_num_coderunner_questions_by_context(); +$questionsbycontext = bulk_tester::get_num_coderunner_questions_by_context(); $availablequestionsbycontext = []; foreach ($questionsbycontext as $contextid => $numcoderunnerquestions) { $context = context::instance_by_id($contextid); diff --git a/prototypeusage.php b/prototypeusage.php index 5922f4b1..d0b32ddc 100644 --- a/prototypeusage.php +++ b/prototypeusage.php @@ -24,6 +24,14 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +namespace qtype_coderunner; + +use context; +use context_system; +use html_writer; +use moodle_url; +use qtype_coderunner; + define('NO_OUTPUT_BUFFERING', true); require_once(__DIR__ . '/../../../config.php'); @@ -44,7 +52,7 @@ $PAGE->set_title(get_string('prototypeusage', 'qtype_coderunner')); // Create the helper class. -$bulktester = new qtype_coderunner_bulk_tester(); +$bulktester = new bulk_tester(); // Start display. echo $OUTPUT->header(); diff --git a/prototypeusageindex.php b/prototypeusageindex.php index 99192b3c..52378e1d 100644 --- a/prototypeusageindex.php +++ b/prototypeusageindex.php @@ -26,6 +26,14 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +namespace qtype_coderunner; + +use context; +use context_system; +use context_course; +use html_writer; +use moodle_url; + require_once(__DIR__ . '/../../../config.php'); require_once($CFG->libdir . '/questionlib.php'); @@ -37,7 +45,7 @@ $PAGE->set_context($context); $PAGE->set_title(get_string('prototypeusageindex', 'qtype_coderunner')); -$allcourses = qtype_coderunner_bulk_tester::get_all_courses(); +$allcourses = bulk_tester::get_all_courses(); // Start display. echo $OUTPUT->header();