Merge branch 'install_310_STABLE' of https://git.in.moodle.com/amosbot/moodle-install...
[moodle.git] / question / engine / tests / questionusage_autosave_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * This file contains tests for the autosave code in the question_usage class.
19  *
20  * @package    moodlecore
21  * @subpackage questionengine
22  * @copyright  2013 The Open University
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
30 require_once(__DIR__ . '/../lib.php');
31 require_once(__DIR__ . '/helpers.php');
34 /**
35  * Unit tests for the autosave parts of the {@link question_usage} class.
36  *
37  * @copyright 2013 The Open University
38  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class question_usage_autosave_test extends qbehaviour_walkthrough_test_base {
42     public function test_autosave_then_display() {
43         $this->resetAfterTest();
44         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
45         $cat = $generator->create_question_category();
46         $question = $generator->create_question('shortanswer', null,
47                 array('category' => $cat->id));
49         // Start attempt at a shortanswer question.
50         $q = question_bank::load_question($question->id);
51         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
53         $this->check_current_state(question_state::$todo);
54         $this->check_current_mark(null);
55         $this->check_step_count(1);
57         // Process a response and check the expected result.
58         $this->process_submission(array('answer' => 'first response'));
60         $this->check_current_state(question_state::$complete);
61         $this->check_current_mark(null);
62         $this->check_step_count(2);
63         $this->save_quba();
65         // Now check how that is re-displayed.
66         $this->render();
67         $this->check_output_contains_text_input('answer', 'first response');
68         $this->check_output_contains_hidden_input(':sequencecheck', 2);
70         // Process an autosave.
71         $this->load_quba();
72         $this->process_autosave(array('answer' => 'second response'));
73         $this->check_current_state(question_state::$complete);
74         $this->check_current_mark(null);
75         $this->check_step_count(3);
76         $this->save_quba();
78         // Now check how that is re-displayed.
79         $this->load_quba();
80         $this->render();
81         $this->check_output_contains_text_input('answer', 'second response');
82         $this->check_output_contains_hidden_input(':sequencecheck', 2);
84         $this->delete_quba();
85     }
87     public function test_autosave_then_autosave_different_data() {
88         $this->resetAfterTest();
89         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
90         $cat = $generator->create_question_category();
91         $question = $generator->create_question('shortanswer', null,
92                 array('category' => $cat->id));
94         // Start attempt at a shortanswer question.
95         $q = question_bank::load_question($question->id);
96         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
98         $this->check_current_state(question_state::$todo);
99         $this->check_current_mark(null);
100         $this->check_step_count(1);
102         // Process a response and check the expected result.
103         $this->process_submission(array('answer' => 'first response'));
105         $this->check_current_state(question_state::$complete);
106         $this->check_current_mark(null);
107         $this->check_step_count(2);
108         $this->save_quba();
110         // Now check how that is re-displayed.
111         $this->render();
112         $this->check_output_contains_text_input('answer', 'first response');
113         $this->check_output_contains_hidden_input(':sequencecheck', 2);
115         // Process an autosave.
116         $this->load_quba();
117         $this->process_autosave(array('answer' => 'second response'));
118         $this->check_current_state(question_state::$complete);
119         $this->check_current_mark(null);
120         $this->check_step_count(3);
121         $this->save_quba();
123         // Now check how that is re-displayed.
124         $this->load_quba();
125         $this->render();
126         $this->check_output_contains_text_input('answer', 'second response');
127         $this->check_output_contains_hidden_input(':sequencecheck', 2);
129         // Process a second autosave.
130         $this->load_quba();
131         $this->process_autosave(array('answer' => 'third response'));
132         $this->check_current_state(question_state::$complete);
133         $this->check_current_mark(null);
134         $this->check_step_count(3);
135         $this->save_quba();
137         // Now check how that is re-displayed.
138         $this->load_quba();
139         $this->render();
140         $this->check_output_contains_text_input('answer', 'third response');
141         $this->check_output_contains_hidden_input(':sequencecheck', 2);
143         $this->delete_quba();
144     }
146     public function test_autosave_then_autosave_same_data() {
147         $this->resetAfterTest();
148         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
149         $cat = $generator->create_question_category();
150         $question = $generator->create_question('shortanswer', null,
151                 array('category' => $cat->id));
153         // Start attempt at a shortanswer question.
154         $q = question_bank::load_question($question->id);
155         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
157         $this->check_current_state(question_state::$todo);
158         $this->check_current_mark(null);
159         $this->check_step_count(1);
161         // Process a response and check the expected result.
162         $this->process_submission(array('answer' => 'first response'));
164         $this->check_current_state(question_state::$complete);
165         $this->check_current_mark(null);
166         $this->check_step_count(2);
167         $this->save_quba();
169         // Now check how that is re-displayed.
170         $this->render();
171         $this->check_output_contains_text_input('answer', 'first response');
172         $this->check_output_contains_hidden_input(':sequencecheck', 2);
174         // Process an autosave.
175         $this->load_quba();
176         $this->process_autosave(array('answer' => 'second response'));
177         $this->check_current_state(question_state::$complete);
178         $this->check_current_mark(null);
179         $this->check_step_count(3);
180         $this->save_quba();
182         // Now check how that is re-displayed.
183         $this->load_quba();
184         $this->render();
185         $this->check_output_contains_text_input('answer', 'second response');
186         $this->check_output_contains_hidden_input(':sequencecheck', 2);
188         $stepid = $this->quba->get_question_attempt($this->slot)->get_last_step()->get_id();
190         // Process a second autosave.
191         $this->load_quba();
192         $this->process_autosave(array('answer' => 'second response'));
193         $this->check_current_state(question_state::$complete);
194         $this->check_current_mark(null);
195         $this->check_step_count(3);
196         $this->save_quba();
198         // Try to check it is really the same step
199         $newstepid = $this->quba->get_question_attempt($this->slot)->get_last_step()->get_id();
200         $this->assertEquals($stepid, $newstepid);
202         // Now check how that is re-displayed.
203         $this->load_quba();
204         $this->render();
205         $this->check_output_contains_text_input('answer', 'second response');
206         $this->check_output_contains_hidden_input(':sequencecheck', 2);
208         $this->delete_quba();
209     }
211     public function test_autosave_then_autosave_original_data() {
212         $this->resetAfterTest();
213         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
214         $cat = $generator->create_question_category();
215         $question = $generator->create_question('shortanswer', null,
216                 array('category' => $cat->id));
218         // Start attempt at a shortanswer question.
219         $q = question_bank::load_question($question->id);
220         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
222         $this->check_current_state(question_state::$todo);
223         $this->check_current_mark(null);
224         $this->check_step_count(1);
226         // Process a response and check the expected result.
227         $this->process_submission(array('answer' => 'first response'));
229         $this->check_current_state(question_state::$complete);
230         $this->check_current_mark(null);
231         $this->check_step_count(2);
232         $this->save_quba();
234         // Now check how that is re-displayed.
235         $this->render();
236         $this->check_output_contains_text_input('answer', 'first response');
237         $this->check_output_contains_hidden_input(':sequencecheck', 2);
239         // Process an autosave.
240         $this->load_quba();
241         $this->process_autosave(array('answer' => 'second response'));
242         $this->check_current_state(question_state::$complete);
243         $this->check_current_mark(null);
244         $this->check_step_count(3);
245         $this->save_quba();
247         // Now check how that is re-displayed.
248         $this->load_quba();
249         $this->render();
250         $this->check_output_contains_text_input('answer', 'second response');
251         $this->check_output_contains_hidden_input(':sequencecheck', 2);
253         // Process a second autosave saving the original response.
254         // This should remove the autosave step.
255         $this->load_quba();
256         $this->process_autosave(array('answer' => 'first response'));
257         $this->check_current_state(question_state::$complete);
258         $this->check_current_mark(null);
259         $this->check_step_count(2);
260         $this->save_quba();
262         // Now check how that is re-displayed.
263         $this->load_quba();
264         $this->render();
265         $this->check_output_contains_text_input('answer', 'first response');
266         $this->check_output_contains_hidden_input(':sequencecheck', 2);
268         $this->delete_quba();
269     }
271     public function test_autosave_then_real_save() {
272         $this->resetAfterTest();
273         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
274         $cat = $generator->create_question_category();
275         $question = $generator->create_question('shortanswer', null,
276                 array('category' => $cat->id));
278         // Start attempt at a shortanswer question.
279         $q = question_bank::load_question($question->id);
280         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
282         $this->check_current_state(question_state::$todo);
283         $this->check_current_mark(null);
284         $this->check_step_count(1);
286         // Process a response and check the expected result.
287         $this->process_submission(array('answer' => 'first response'));
289         $this->check_current_state(question_state::$complete);
290         $this->check_current_mark(null);
291         $this->check_step_count(2);
292         $this->save_quba();
294         // Now check how that is re-displayed.
295         $this->render();
296         $this->check_output_contains_text_input('answer', 'first response');
297         $this->check_output_contains_hidden_input(':sequencecheck', 2);
299         // Process an autosave.
300         $this->load_quba();
301         $this->process_autosave(array('answer' => 'second response'));
302         $this->check_current_state(question_state::$complete);
303         $this->check_current_mark(null);
304         $this->check_step_count(3);
305         $this->save_quba();
307         // Now check how that is re-displayed.
308         $this->load_quba();
309         $this->render();
310         $this->check_output_contains_text_input('answer', 'second response');
311         $this->check_output_contains_hidden_input(':sequencecheck', 2);
313         // Now save for real a third response.
314         $this->process_submission(array('answer' => 'third response'));
316         $this->check_current_state(question_state::$complete);
317         $this->check_current_mark(null);
318         $this->check_step_count(3);
319         $this->save_quba();
321         // Now check how that is re-displayed.
322         $this->render();
323         $this->check_output_contains_text_input('answer', 'third response');
324         $this->check_output_contains_hidden_input(':sequencecheck', 3);
325     }
327     public function test_autosave_then_real_save_same() {
328         $this->resetAfterTest();
329         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
330         $cat = $generator->create_question_category();
331         $question = $generator->create_question('shortanswer', null,
332                 array('category' => $cat->id));
334         // Start attempt at a shortanswer question.
335         $q = question_bank::load_question($question->id);
336         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
338         $this->check_current_state(question_state::$todo);
339         $this->check_current_mark(null);
340         $this->check_step_count(1);
342         // Process a response and check the expected result.
343         $this->process_submission(array('answer' => 'first response'));
345         $this->check_current_state(question_state::$complete);
346         $this->check_current_mark(null);
347         $this->check_step_count(2);
348         $this->save_quba();
350         // Now check how that is re-displayed.
351         $this->render();
352         $this->check_output_contains_text_input('answer', 'first response');
353         $this->check_output_contains_hidden_input(':sequencecheck', 2);
355         // Process an autosave.
356         $this->load_quba();
357         $this->process_autosave(array('answer' => 'second response'));
358         $this->check_current_state(question_state::$complete);
359         $this->check_current_mark(null);
360         $this->check_step_count(3);
361         $this->save_quba();
363         // Now check how that is re-displayed.
364         $this->load_quba();
365         $this->render();
366         $this->check_output_contains_text_input('answer', 'second response');
367         $this->check_output_contains_hidden_input(':sequencecheck', 2);
369         // Now save for real of the same response.
370         $this->process_submission(array('answer' => 'second response'));
372         $this->check_current_state(question_state::$complete);
373         $this->check_current_mark(null);
374         $this->check_step_count(3);
375         $this->save_quba();
377         // Now check how that is re-displayed.
378         $this->render();
379         $this->check_output_contains_text_input('answer', 'second response');
380         $this->check_output_contains_hidden_input(':sequencecheck', 3);
381     }
383     public function test_autosave_then_submit() {
384         $this->resetAfterTest();
385         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
386         $cat = $generator->create_question_category();
387         $question = $generator->create_question('shortanswer', null,
388                 array('category' => $cat->id));
390         // Start attempt at a shortanswer question.
391         $q = question_bank::load_question($question->id);
392         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
394         $this->check_current_state(question_state::$todo);
395         $this->check_current_mark(null);
396         $this->check_step_count(1);
398         // Process a response and check the expected result.
399         $this->process_submission(array('answer' => 'first response'));
401         $this->check_current_state(question_state::$complete);
402         $this->check_current_mark(null);
403         $this->check_step_count(2);
404         $this->save_quba();
406         // Now check how that is re-displayed.
407         $this->render();
408         $this->check_output_contains_text_input('answer', 'first response');
409         $this->check_output_contains_hidden_input(':sequencecheck', 2);
411         // Process an autosave.
412         $this->load_quba();
413         $this->process_autosave(array('answer' => 'second response'));
414         $this->check_current_state(question_state::$complete);
415         $this->check_current_mark(null);
416         $this->check_step_count(3);
417         $this->save_quba();
419         // Now check how that is re-displayed.
420         $this->load_quba();
421         $this->render();
422         $this->check_output_contains_text_input('answer', 'second response');
423         $this->check_output_contains_hidden_input(':sequencecheck', 2);
425         // Now submit a third response.
426         $this->process_submission(array('answer' => 'third response'));
427         $this->quba->finish_all_questions();
429         $this->check_current_state(question_state::$gradedwrong);
430         $this->check_current_mark(0);
431         $this->check_step_count(4);
432         $this->save_quba();
434         // Now check how that is re-displayed.
435         $this->render();
436         $this->check_output_contains_text_input('answer', 'third response', false);
437         $this->check_output_contains_hidden_input(':sequencecheck', 4);
438     }
440     public function test_autosave_and_save_concurrently() {
441         // This test simulates the following scenario:
442         // 1. Student looking at a page of the quiz, and edits a field then waits.
443         // 2. Autosave starts.
444         // 3. Student immediately clicks Next, which submits the current page.
445         // In this situation, the real submit should beat the autosave, even
446         // thought they happen concurrently. We simulate this by opening a
447         // second db connections.
448         global $DB;
450         // Open second connection
451         $cfg = $DB->export_dbconfig();
452         if (!isset($cfg->dboptions)) {
453             $cfg->dboptions = array();
454         }
455         $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
456         $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
458         // Since we need to commit our transactions in a given order, close the
459         // standard unit test transaction.
460         $this->preventResetByRollback();
462         $this->resetAfterTest();
463         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
464         $cat = $generator->create_question_category();
465         $question = $generator->create_question('shortanswer', null,
466                 array('category' => $cat->id));
468         // Start attempt at a shortanswer question.
469         $q = question_bank::load_question($question->id);
470         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
471         $this->save_quba();
473         $this->check_current_state(question_state::$todo);
474         $this->check_current_mark(null);
475         $this->check_step_count(1);
477         // Start to process an autosave on $DB.
478         $transaction = $DB->start_delegated_transaction();
479         $this->load_quba($DB);
480         $this->process_autosave(array('answer' => 'autosaved response'));
481         $this->check_current_state(question_state::$complete);
482         $this->check_current_mark(null);
483         $this->check_step_count(2);
484         $this->save_quba($DB); // Don't commit the transaction yet.
486         // Now process a real submit on $DB2 (using a different response).
487         $transaction2 = $DB2->start_delegated_transaction();
488         $this->load_quba($DB2);
489         $this->process_submission(array('answer' => 'real response'));
490         $this->check_current_state(question_state::$complete);
491         $this->check_current_mark(null);
492         $this->check_step_count(2);
494         // Now commit the first transaction.
495         $transaction->allow_commit();
497         // Now commit the other transaction.
498         $this->save_quba($DB2);
499         $transaction2->allow_commit();
501         // Now re-load and check how that is re-displayed.
502         $this->load_quba();
503         $this->check_current_state(question_state::$complete);
504         $this->check_current_mark(null);
505         $this->check_step_count(2);
506         $this->render();
507         $this->check_output_contains_text_input('answer', 'real response');
508         $this->check_output_contains_hidden_input(':sequencecheck', 2);
510         $DB2->dispose();
511     }
513     public function test_concurrent_autosaves() {
514         // This test simulates the following scenario:
515         // 1. Student opens  a page of the quiz in two separate browser.
516         // 2. Autosave starts in both at the same time.
517         // In this situation, one autosave will work, and the other one will
518         // get a unique key violation error. This is OK.
519         global $DB;
521         // Open second connection
522         $cfg = $DB->export_dbconfig();
523         if (!isset($cfg->dboptions)) {
524             $cfg->dboptions = array();
525         }
526         $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
527         $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
529         // Since we need to commit our transactions in a given order, close the
530         // standard unit test transaction.
531         $this->preventResetByRollback();
533         $this->resetAfterTest();
534         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
535         $cat = $generator->create_question_category();
536         $question = $generator->create_question('shortanswer', null,
537                 array('category' => $cat->id));
539         // Start attempt at a shortanswer question.
540         $q = question_bank::load_question($question->id);
541         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
542         $this->save_quba();
544         $this->check_current_state(question_state::$todo);
545         $this->check_current_mark(null);
546         $this->check_step_count(1);
548         // Start to process an autosave on $DB.
549         $transaction = $DB->start_delegated_transaction();
550         $this->load_quba($DB);
551         $this->process_autosave(array('answer' => 'autosaved response 1'));
552         $this->check_current_state(question_state::$complete);
553         $this->check_current_mark(null);
554         $this->check_step_count(2);
555         $this->save_quba($DB); // Don't commit the transaction yet.
557         // Now process a real submit on $DB2 (using a different response).
558         $transaction2 = $DB2->start_delegated_transaction();
559         $this->load_quba($DB2);
560         $this->process_autosave(array('answer' => 'autosaved response 2'));
561         $this->check_current_state(question_state::$complete);
562         $this->check_current_mark(null);
563         $this->check_step_count(2);
565         // Now commit the first transaction.
566         $transaction->allow_commit();
568         // Now commit the other transaction.
569         $this->expectException('dml_write_exception');
570         $this->save_quba($DB2);
571         $transaction2->allow_commit();
573         // Now re-load and check how that is re-displayed.
574         $this->load_quba();
575         $this->check_current_state(question_state::$complete);
576         $this->check_current_mark(null);
577         $this->check_step_count(2);
578         $this->render();
579         $this->check_output_contains_text_input('answer', 'autosaved response 1');
580         $this->check_output_contains_hidden_input(':sequencecheck', 1);
582         $DB2->dispose();
583     }
585     public function test_autosave_with_wrong_seq_number_ignored() {
586         $this->resetAfterTest();
587         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
588         $cat = $generator->create_question_category();
589         $question = $generator->create_question('shortanswer', null,
590                 array('category' => $cat->id));
592         // Start attempt at a shortanswer question.
593         $q = question_bank::load_question($question->id);
594         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
596         $this->check_current_state(question_state::$todo);
597         $this->check_current_mark(null);
598         $this->check_step_count(1);
600         // Process a response and check the expected result.
601         $this->process_submission(array('answer' => 'first response'));
603         $this->check_current_state(question_state::$complete);
604         $this->check_current_mark(null);
605         $this->check_step_count(2);
606         $this->save_quba();
608         // Now check how that is re-displayed.
609         $this->render();
610         $this->check_output_contains_text_input('answer', 'first response');
611         $this->check_output_contains_hidden_input(':sequencecheck', 2);
613         // Process an autosave with a sequence number 1 too small (so from the past).
614         $this->load_quba();
615         $postdata = $this->response_data_to_post(array('answer' => 'obsolete response'));
616         $postdata[$this->quba->get_field_prefix($this->slot) . ':sequencecheck'] = $this->get_question_attempt()->get_sequence_check_count() - 1;
617         $this->quba->process_all_autosaves(null, $postdata);
618         $this->check_current_state(question_state::$complete);
619         $this->check_current_mark(null);
620         $this->check_step_count(2);
621         $this->save_quba();
623         // Now check how that is re-displayed.
624         $this->load_quba();
625         $this->render();
626         $this->check_output_contains_text_input('answer', 'first response');
627         $this->check_output_contains_hidden_input(':sequencecheck', 2);
629         $this->delete_quba();
630     }
632     public function test_finish_with_unhandled_autosave_data() {
633         $this->resetAfterTest();
634         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
635         $cat = $generator->create_question_category();
636         $question = $generator->create_question('shortanswer', null,
637                 array('category' => $cat->id));
639         // Start attempt at a shortanswer question.
640         $q = question_bank::load_question($question->id);
641         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
643         $this->check_current_state(question_state::$todo);
644         $this->check_current_mark(null);
645         $this->check_step_count(1);
647         // Process a response and check the expected result.
648         $this->process_submission(array('answer' => 'cat'));
650         $this->check_current_state(question_state::$complete);
651         $this->check_current_mark(null);
652         $this->check_step_count(2);
653         $this->save_quba();
655         // Now check how that is re-displayed.
656         $this->render();
657         $this->check_output_contains_text_input('answer', 'cat');
658         $this->check_output_contains_hidden_input(':sequencecheck', 2);
660         // Process an autosave.
661         $this->load_quba();
662         $this->process_autosave(array('answer' => 'frog'));
663         $this->check_current_state(question_state::$complete);
664         $this->check_current_mark(null);
665         $this->check_step_count(3);
666         $this->save_quba();
668         // Now check how that is re-displayed.
669         $this->load_quba();
670         $this->render();
671         $this->check_output_contains_text_input('answer', 'frog');
672         $this->check_output_contains_hidden_input(':sequencecheck', 2);
674         // Now finishe the attempt, without having done anything since the autosave.
675         $this->finish();
676         $this->save_quba();
678         // Now check how that has been graded and is re-displayed.
679         $this->load_quba();
680         $this->check_current_state(question_state::$gradedright);
681         $this->check_current_mark(1);
682         $this->render();
683         $this->check_output_contains_text_input('answer', 'frog', false);
684         $this->check_output_contains_hidden_input(':sequencecheck', 4);
686         $this->delete_quba();
687     }
689     /**
690      * Test that regrading doesn't convert autosave steps to finished steps.
691      * This can result in students loosing data (due to question_out_of_sequence_exception) if a teacher
692      * regrades an attempt while it is in progress.
693      */
694     public function test_autosave_and_regrade_then_display() {
695         $this->resetAfterTest();
696         $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
697         $cat = $generator->create_question_category();
698         $question = $generator->create_question('shortanswer', null,
699                 array('category' => $cat->id));
701         // Start attempt at a shortanswer question.
702         $q = question_bank::load_question($question->id);
703         $this->start_attempt_at_question($q, 'deferredfeedback', 1);
705         $this->check_current_state(question_state::$todo);
706         $this->check_current_mark(null);
707         $this->check_step_count(1);
709         // First see if the starting sequence is right.
710         $this->render();
711         $this->check_output_contains_hidden_input(':sequencecheck', 1);
713         // Add a submission.
714         $this->process_submission(array('answer' => 'first response'));
715         $this->save_quba();
717         // Check the submission and that the sequence went up.
718         $this->render();
719         $this->check_output_contains_text_input('answer', 'first response');
720         $this->check_output_contains_hidden_input(':sequencecheck', 2);
721         $this->assertFalse($this->get_question_attempt()->has_autosaved_step());
723         // Add a autosave response.
724         $this->load_quba();
725         $this->process_autosave(array('answer' => 'second response'));
726         $this->save_quba();
728         // Confirm that the autosave value shows up, but that the sequence hasn't increased.
729         $this->render();
730         $this->check_output_contains_text_input('answer', 'second response');
731         $this->check_output_contains_hidden_input(':sequencecheck', 2);
732         $this->assertTrue($this->get_question_attempt()->has_autosaved_step());
734         // Call regrade.
735         $this->load_quba();
736         $this->quba->regrade_all_questions();
737         $this->save_quba();
739         // Check and see if the autosave response is still there, that the sequence didn't increase,
740         // and that there is an autosave step.
741         $this->load_quba();
742         $this->render();
743         $this->check_output_contains_text_input('answer', 'second response');
744         $this->check_output_contains_hidden_input(':sequencecheck', 2);
745         $this->assertTrue($this->get_question_attempt()->has_autosaved_step());
747         $this->delete_quba();
748     }
750     protected function tearDown(): void {
751         // This test relies on the destructor for the second DB connection being called before running the next test.
752         // Without this change - there will be unit test failures on "some" DBs (MySQL).
753         gc_collect_cycles();
754     }