Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Updates to bulktester and grade caching #239

Closed
wants to merge 6 commits into from
18 changes: 14 additions & 4 deletions bulktest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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());
Expand All @@ -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.
Expand All @@ -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();
Expand Down
15 changes: 12 additions & 3 deletions bulktestall.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand All @@ -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',
Expand All @@ -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();
50 changes: 39 additions & 11 deletions bulktestindex.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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);
Expand All @@ -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;">

Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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;']
Expand All @@ -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,
Expand All @@ -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'),
Expand All @@ -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');
Expand Down Expand Up @@ -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();
Expand Down
37 changes: 29 additions & 8 deletions classes/bulk_tester.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand Down Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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];
}
Expand All @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions classes/jobesandbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion db/caches.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading