Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
17
namespace quiz_statistics;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
global $CFG;
22
require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php');
23
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php');
24
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
25
require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php');
26
 
27
/**
28
 * Tests for statistics report
29
 *
30
 * @package   quiz_statistics
31
 * @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net}
32
 * @author    Mark Johnson <mark.johnson@catalyst-eu.net>
33
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34
 * @covers    \quiz_statistics_report
35
 */
1441 ariadna 36
final class quiz_statistics_report_test extends \advanced_testcase {
1 efrain 37
 
38
    use \quiz_question_helper_test_trait;
39
 
40
    /**
41
     * Secondary database connection for creating locks.
42
     *
43
     * @var \moodle_database|null
44
     */
45
    protected static ?\moodle_database $lockdb;
46
 
47
    /**
48
     * Lock factory using the secondary database connection.
49
     *
50
     * @var \moodle_database|null
51
     */
52
    protected static ?\core\lock\lock_factory $lockfactory;
53
 
54
    /**
55
     * Create a lock factory with a second database session.
56
     *
57
     * This allows us to create a lock in our test code that will block a lock request
58
     * on the same key in code under test.
59
     */
60
    public function setUp(): void {
61
        global $CFG;
1441 ariadna 62
        parent::setUp();
1 efrain 63
        self::$lockdb = \moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary);
64
        self::$lockdb->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, $CFG->dboptions);
65
 
66
        $lockfactoryclass = \core\lock\lock_config::get_lock_factory_class();
67
        $lockfactory = new $lockfactoryclass('quiz_statistics_get_stats');
68
 
69
        // Iterate lock factory hierarchy to see if it contains a 'db' property we can use.
70
        $reflectionclass = new \ReflectionClass($lockfactory);
71
        while ($reflectionclass) {
72
            if ($reflectionhasdb = $reflectionclass->hasProperty('db')) {
73
                break;
74
            }
75
            $reflectionclass = $reflectionclass->getParentClass();
76
        }
77
 
78
        if (!$reflectionhasdb) {
79
            $this->markTestSkipped('Test lock factory should be a db type');
80
        }
81
 
82
        $reflectiondb = new \ReflectionProperty($lockfactory, 'db');
83
        $reflectiondb->setValue($lockfactory, self::$lockdb);
84
        self::$lockfactory = $lockfactory;
85
    }
86
 
87
    /**
88
     * Dispose of the extra DB connection and lock factory.
89
     */
90
    public function tearDown(): void {
91
        self::$lockdb->dispose();
92
        self::$lockdb = null;
93
        self::$lockfactory = null;
1441 ariadna 94
        parent::tearDown();
1 efrain 95
    }
96
 
97
    /**
98
     * Return a generated quiz
99
     *
100
     * @return \stdClass
101
     */
102
    protected function create_and_attempt_quiz(): \stdClass {
103
        $course = $this->getDataGenerator()->create_course();
104
        $user = $this->getDataGenerator()->create_user();
105
        $quiz = $this->create_test_quiz($course);
106
        $quizcontext = \context_module::instance($quiz->cmid);
107
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
108
        $this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
109
        $this->attempt_quiz($quiz, $user);
110
 
111
        return $quiz;
112
    }
113
 
114
    /**
115
     * Test locking the calculation process.
116
     *
117
     * When there is a lock on the hash code, test_get_all_stats_and_analysis() should wait until the lock timeout, then throw an
118
     * exception.
119
     *
120
     * When there is no lock (or the lock has been released), it should return a result.
121
     *
122
     * @return void
123
     */
124
    public function test_get_all_stats_and_analysis_locking(): void {
125
        $this->resetAfterTest(true);
126
        $quiz = $this->create_and_attempt_quiz();
127
        $whichattempts = QUIZ_GRADEAVERAGE; // All attempts.
128
        $whichtries = \question_attempt::ALL_TRIES;
129
        $groupstudentsjoins = new \core\dml\sql_join();
130
        $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudentsjoins, $whichattempts);
131
 
132
        $report = new \quiz_statistics_report();
133
        $questions = $report->load_and_initialise_questions_for_calculations($quiz);
134
 
135
        $timeoutseconds = 20;
136
        set_config('getstatslocktimeout', $timeoutseconds, 'quiz_statistics');
137
        $lock = self::$lockfactory->get_lock($qubaids->get_hash_code(), 0);
138
 
139
        $progress = new \core\progress\none();
140
 
141
        $this->resetDebugging();
142
        $timebefore = microtime(true);
143
        try {
144
            $result = $report->get_all_stats_and_analysis(
145
                $quiz,
146
                $whichattempts,
147
                $whichtries,
148
                $groupstudentsjoins,
149
                $questions,
150
                $progress
151
            );
152
            $timeafter = microtime(true);
153
 
154
            // Verify that we waited as long as the timeout.
155
            $this->assertEqualsWithDelta($timeoutseconds, $timeafter - $timebefore, 1);
156
            $this->assertDebuggingCalled('Could not get lock on ' .
157
                    $qubaids->get_hash_code() . ' (Quiz ID ' . $quiz->id . ') after ' .
158
                    $timeoutseconds . ' seconds');
159
            $this->assertEquals([null, null], $result);
160
        } finally {
161
            $lock->release();
162
        }
163
 
164
        $this->resetDebugging();
165
        $result = $report->get_all_stats_and_analysis(
166
            $quiz,
167
            $whichattempts,
168
            $whichtries,
169
            $groupstudentsjoins,
170
            $questions
171
        );
172
        $this->assertDebuggingNotCalled();
173
        $this->assertNotEquals([null, null], $result);
174
    }
175
 
176
    /**
177
     * Test locking when the current page does not require calculations.
178
     *
179
     * When there is a lock on the hash code, test_get_all_stats_and_analysis() should return a null result immediately,
180
     * with no exception thrown.
181
     *
182
     * @return void
183
     */
184
    public function test_get_all_stats_and_analysis_locking_no_calculation(): void {
185
        $this->resetAfterTest(true);
186
        $quiz = $this->create_and_attempt_quiz();
187
 
188
        $whichattempts = QUIZ_GRADEAVERAGE; // All attempts.
189
        $whichtries = \question_attempt::ALL_TRIES;
190
        $groupstudentsjoins = new \core\dml\sql_join();
191
        $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudentsjoins, $whichattempts);
192
 
193
        $report = new \quiz_statistics_report();
194
        $questions = $report->load_and_initialise_questions_for_calculations($quiz);
195
 
196
        $timeoutseconds = 20;
197
        set_config('getstatslocktimeout', $timeoutseconds, 'quiz_statistics');
198
 
199
        $lock = self::$lockfactory->get_lock($qubaids->get_hash_code(), 0);
200
 
201
        $this->resetDebugging();
202
        try {
203
            $progress = new \core\progress\none();
204
 
205
            $timebefore = microtime(true);
206
            $result = $report->get_all_stats_and_analysis(
207
                $quiz,
208
                $whichattempts,
209
                $whichtries,
210
                $groupstudentsjoins,
211
                $questions,
212
                $progress,
213
                false
214
            );
215
            $timeafter = microtime(true);
216
 
217
            // Verify that we did not wait for the timeout before returning.
218
            $this->assertLessThan($timeoutseconds, $timeafter - $timebefore);
219
            $this->assertEquals([null, null], $result);
220
            $this->assertDebuggingNotCalled();
221
        } finally {
222
            $lock->release();
223
        }
224
    }
225
}