diff --git a/question/engine/datalib.php b/question/engine/datalib.php index 61910e8e01b..7e367039fbb 100644 --- a/question/engine/datalib.php +++ b/question/engine/datalib.php @@ -1076,6 +1076,20 @@ ORDER BY * @param number $newmaxmark the new max mark to set. */ public function set_max_mark_in_attempts(qubaid_condition $qubaids, $slot, $newmaxmark) { + if ($this->db->get_dbfamily() == 'mysql') { + // MySQL's query optimiser completely fails to cope with the + // set_field_select call below, so we have to give it a clue. See MDL-32616. + // TODO MDL-29589 encapsulate this MySQL-specific code with a $DB method. + $this->db->execute(" + UPDATE " . $qubaids->from_question_attempts('qa') . " + SET qa.maxmark = :newmaxmark + WHERE " . $qubaids->where() . " + AND slot = :slot + ", $qubaids->from_where_params() + array('newmaxmark' => $newmaxmark, 'slot' => $slot)); + return; + } + + // Normal databases. $this->db->set_field_select('question_attempts', 'maxmark', $newmaxmark, "questionusageid {$qubaids->usage_id_in()} AND slot = :slot", $qubaids->usage_id_in_params() + array('slot' => $slot)); diff --git a/question/engine/tests/question_engine_data_mapper_test.php b/question/engine/tests/datalib_reporting_queries_test.php similarity index 92% rename from question/engine/tests/question_engine_data_mapper_test.php rename to question/engine/tests/datalib_reporting_queries_test.php index 932576c0396..5288fdba5fe 100644 --- a/question/engine/tests/question_engine_data_mapper_test.php +++ b/question/engine/tests/datalib_reporting_queries_test.php @@ -15,12 +15,12 @@ // along with Moodle. If not, see . /** - * This file contains tests for the autosave code in the question_usage class. + * Unit tests for the parts of {@link question_engine_data_mapper} related to reporting. * - * @package moodlecore - * @subpackage questionengine - * @copyright 2013 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package core_question + * @category test + * @copyright 2013 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -32,17 +32,12 @@ require_once(dirname(__FILE__) . '/helpers.php'); /** - * Unit tests for the autosave parts of the {@link question_usage} class. - * - * Note that many of the methods used when attempting questions, like - * load_questions_usage_by_activity, insert_question_*, delete_steps are - * tested elsewhere, e.g. by {@link question_usage_autosave_test}. We do not - * re-test them here. + * Unit tests for the parts of {@link question_engine_data_mapper} related to reporting. * * @copyright 2013 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_base { +class question_engine_data_mapper_reporting_testcase extends qbehaviour_walkthrough_test_base { /** @var question_engine_data_mapper */ protected $dm; @@ -132,6 +127,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b $this->dotest_question_attempt_latest_state_view(); } + /** + * This test is executed by {@link test_reporting_queries()}. + */ protected function dotest_load_questions_usages_latest_steps() { $rawstates = $this->dm->load_questions_usages_latest_steps($this->bothusages, $this->allslots, 'qa.id AS questionattemptid, qa.questionusageid, qa.slot, ' . @@ -178,6 +176,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b ), $state); } + /** + * This test is executed by {@link test_reporting_queries()}. + */ protected function dotest_load_questions_usages_question_state_summary() { $summary = $this->dm->load_questions_usages_question_state_summary( $this->bothusages, $this->allslots); @@ -206,6 +207,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b )); } + /** + * This test is executed by {@link test_reporting_queries()}. + */ protected function dotest_load_questions_usages_where_question_in_state() { $this->assertEquals( array(array($this->usageids[0], $this->usageids[1]), 2), @@ -223,6 +227,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b 'needsgrading', $this->allslots[1], null, 'questionusageid')); } + /** + * This test is executed by {@link test_reporting_queries()}. + */ protected function dotest_load_average_marks() { $averages = $this->dm->load_average_marks($this->bothusages); @@ -240,6 +247,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b ), $averages); } + /** + * This test is executed by {@link test_reporting_queries()}. + */ protected function dotest_sum_usage_marks_subquery() { global $DB; @@ -253,6 +263,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b $this->assertEquals(0, $totals[$this->usageids[1]]); } + /** + * This test is executed by {@link test_reporting_queries()}. + */ protected function dotest_question_attempt_latest_state_view() { global $DB; diff --git a/question/engine/tests/datalib_test.php b/question/engine/tests/datalib_test.php new file mode 100644 index 00000000000..9279626be67 --- /dev/null +++ b/question/engine/tests/datalib_test.php @@ -0,0 +1,129 @@ +. + +/** + * Unit tests for parts of {@link question_engine_data_mapper}. + * + * @package core_question + * @category test + * @copyright 2014 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once(dirname(__FILE__) . '/../lib.php'); +require_once(dirname(__FILE__) . '/helpers.php'); + + +/** + * Unit tests for parts of {@link question_engine_data_mapper}. + * + * Note that many of the methods used when attempting questions, like + * load_questions_usage_by_activity, insert_question_*, delete_steps are + * tested elsewhere, e.g. by {@link question_usage_autosave_test}. We do not + * re-test them here. + * + * @copyright 2014 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_base { + + /** + * We create two usages, each with two questions, a short-answer marked + * out of 5, and and essay marked out of 10. We just start these attempts. + * + * Then we change the max mark for the short-answer question in one of the + * usages to 20, using a qubaid_list, and verify. + * + * Then we change the max mark for the essay question in the other + * usage to 2, using a qubaid_join, and verify. + */ + public function test_set_max_mark_in_attempts() { + + // Set up some things the tests will need. + $this->resetAfterTest(); + $dm = new question_engine_data_mapper(); + + // Create the questions. + $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $cat = $generator->create_question_category(); + $sa = $generator->create_question('shortanswer', null, + array('category' => $cat->id)); + $essay = $generator->create_question('essay', null, + array('category' => $cat->id)); + + // Create the first usage. + $q = question_bank::load_question($sa->id); + $this->start_attempt_at_question($q, 'interactive', 5); + + $q = question_bank::load_question($essay->id); + $this->start_attempt_at_question($q, 'interactive', 10); + + $this->finish(); + $this->save_quba(); + $usage1id = $this->quba->get_id(); + + // Create the second usage. + $this->quba = question_engine::make_questions_usage_by_activity('unit_test', + context_system::instance()); + + $q = question_bank::load_question($sa->id); + $this->start_attempt_at_question($q, 'interactive', 5); + $this->process_submission(array('answer' => 'fish')); + + $q = question_bank::load_question($essay->id); + $this->start_attempt_at_question($q, 'interactive', 10); + + $this->finish(); + $this->save_quba(); + $usage2id = $this->quba->get_id(); + + // Test set_max_mark_in_attempts with a qubaid_list. + $usagestoupdate = new qubaid_list(array($usage1id)); + $dm->set_max_mark_in_attempts($usagestoupdate, 1, 20.0); + $quba1 = question_engine::load_questions_usage_by_activity($usage1id); + $quba2 = question_engine::load_questions_usage_by_activity($usage2id); + $this->assertEquals(20, $quba1->get_question_max_mark(1)); + $this->assertEquals(10, $quba1->get_question_max_mark(2)); + $this->assertEquals( 5, $quba2->get_question_max_mark(1)); + $this->assertEquals(10, $quba2->get_question_max_mark(2)); + + // Test set_max_mark_in_attempts with a qubaid_join. + $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', + 'qu.id = :usageid', array('usageid' => $usage2id)); + $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0); + $quba1 = question_engine::load_questions_usage_by_activity($usage1id); + $quba2 = question_engine::load_questions_usage_by_activity($usage2id); + $this->assertEquals(20, $quba1->get_question_max_mark(1)); + $this->assertEquals(10, $quba1->get_question_max_mark(2)); + $this->assertEquals( 5, $quba2->get_question_max_mark(1)); + $this->assertEquals( 2, $quba2->get_question_max_mark(2)); + + // Test the nothing to do case. + $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id', + 'qu.id = :usageid', array('usageid' => -1)); + $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0); + $quba1 = question_engine::load_questions_usage_by_activity($usage1id); + $quba2 = question_engine::load_questions_usage_by_activity($usage2id); + $this->assertEquals(20, $quba1->get_question_max_mark(1)); + $this->assertEquals(10, $quba1->get_question_max_mark(2)); + $this->assertEquals( 5, $quba2->get_question_max_mark(1)); + $this->assertEquals( 2, $quba2->get_question_max_mark(2)); + } +}