Proyectos de Subversion Moodle

Rev

| 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
 */
36
class quiz_statistics_report_test extends \advanced_testcase {
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;
62
        self::$lockdb = \moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary);
63
        self::$lockdb->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, $CFG->dboptions);
64
 
65
        $lockfactoryclass = \core\lock\lock_config::get_lock_factory_class();
66
        $lockfactory = new $lockfactoryclass('quiz_statistics_get_stats');
67
 
68
        // Iterate lock factory hierarchy to see if it contains a 'db' property we can use.
69
        $reflectionclass = new \ReflectionClass($lockfactory);
70
        while ($reflectionclass) {
71
            if ($reflectionhasdb = $reflectionclass->hasProperty('db')) {
72
                break;
73
            }
74
            $reflectionclass = $reflectionclass->getParentClass();
75
        }
76
 
77
        if (!$reflectionhasdb) {
78
            $this->markTestSkipped('Test lock factory should be a db type');
79
        }
80
 
81
        $reflectiondb = new \ReflectionProperty($lockfactory, 'db');
82
        $reflectiondb->setValue($lockfactory, self::$lockdb);
83
        self::$lockfactory = $lockfactory;
84
    }
85
 
86
    /**
87
     * Dispose of the extra DB connection and lock factory.
88
     */
89
    public function tearDown(): void {
90
        self::$lockdb->dispose();
91
        self::$lockdb = null;
92
        self::$lockfactory = null;
93
    }
94
 
95
    /**
96
     * Return a generated quiz
97
     *
98
     * @return \stdClass
99
     */
100
    protected function create_and_attempt_quiz(): \stdClass {
101
        $course = $this->getDataGenerator()->create_course();
102
        $user = $this->getDataGenerator()->create_user();
103
        $quiz = $this->create_test_quiz($course);
104
        $quizcontext = \context_module::instance($quiz->cmid);
105
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
106
        $this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
107
        $this->attempt_quiz($quiz, $user);
108
 
109
        return $quiz;
110
    }
111
 
112
    /**
113
     * Test locking the calculation process.
114
     *
115
     * When there is a lock on the hash code, test_get_all_stats_and_analysis() should wait until the lock timeout, then throw an
116
     * exception.
117
     *
118
     * When there is no lock (or the lock has been released), it should return a result.
119
     *
120
     * @return void
121
     */
122
    public function test_get_all_stats_and_analysis_locking(): void {
123
        $this->resetAfterTest(true);
124
        $quiz = $this->create_and_attempt_quiz();
125
        $whichattempts = QUIZ_GRADEAVERAGE; // All attempts.
126
        $whichtries = \question_attempt::ALL_TRIES;
127
        $groupstudentsjoins = new \core\dml\sql_join();
128
        $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudentsjoins, $whichattempts);
129
 
130
        $report = new \quiz_statistics_report();
131
        $questions = $report->load_and_initialise_questions_for_calculations($quiz);
132
 
133
        $timeoutseconds = 20;
134
        set_config('getstatslocktimeout', $timeoutseconds, 'quiz_statistics');
135
        $lock = self::$lockfactory->get_lock($qubaids->get_hash_code(), 0);
136
 
137
        $progress = new \core\progress\none();
138
 
139
        $this->resetDebugging();
140
        $timebefore = microtime(true);
141
        try {
142
            $result = $report->get_all_stats_and_analysis(
143
                $quiz,
144
                $whichattempts,
145
                $whichtries,
146
                $groupstudentsjoins,
147
                $questions,
148
                $progress
149
            );
150
            $timeafter = microtime(true);
151
 
152
            // Verify that we waited as long as the timeout.
153
            $this->assertEqualsWithDelta($timeoutseconds, $timeafter - $timebefore, 1);
154
            $this->assertDebuggingCalled('Could not get lock on ' .
155
                    $qubaids->get_hash_code() . ' (Quiz ID ' . $quiz->id . ') after ' .
156
                    $timeoutseconds . ' seconds');
157
            $this->assertEquals([null, null], $result);
158
        } finally {
159
            $lock->release();
160
        }
161
 
162
        $this->resetDebugging();
163
        $result = $report->get_all_stats_and_analysis(
164
            $quiz,
165
            $whichattempts,
166
            $whichtries,
167
            $groupstudentsjoins,
168
            $questions
169
        );
170
        $this->assertDebuggingNotCalled();
171
        $this->assertNotEquals([null, null], $result);
172
    }
173
 
174
    /**
175
     * Test locking when the current page does not require calculations.
176
     *
177
     * When there is a lock on the hash code, test_get_all_stats_and_analysis() should return a null result immediately,
178
     * with no exception thrown.
179
     *
180
     * @return void
181
     */
182
    public function test_get_all_stats_and_analysis_locking_no_calculation(): void {
183
        $this->resetAfterTest(true);
184
        $quiz = $this->create_and_attempt_quiz();
185
 
186
        $whichattempts = QUIZ_GRADEAVERAGE; // All attempts.
187
        $whichtries = \question_attempt::ALL_TRIES;
188
        $groupstudentsjoins = new \core\dml\sql_join();
189
        $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudentsjoins, $whichattempts);
190
 
191
        $report = new \quiz_statistics_report();
192
        $questions = $report->load_and_initialise_questions_for_calculations($quiz);
193
 
194
        $timeoutseconds = 20;
195
        set_config('getstatslocktimeout', $timeoutseconds, 'quiz_statistics');
196
 
197
        $lock = self::$lockfactory->get_lock($qubaids->get_hash_code(), 0);
198
 
199
        $this->resetDebugging();
200
        try {
201
            $progress = new \core\progress\none();
202
 
203
            $timebefore = microtime(true);
204
            $result = $report->get_all_stats_and_analysis(
205
                $quiz,
206
                $whichattempts,
207
                $whichtries,
208
                $groupstudentsjoins,
209
                $questions,
210
                $progress,
211
                false
212
            );
213
            $timeafter = microtime(true);
214
 
215
            // Verify that we did not wait for the timeout before returning.
216
            $this->assertLessThan($timeoutseconds, $timeafter - $timebefore);
217
            $this->assertEquals([null, null], $result);
218
            $this->assertDebuggingNotCalled();
219
        } finally {
220
            $lock->release();
221
        }
222
    }
223
}