MDL-54205 tests: verify destroy() and close() behavior
[moodle.git] / backup / util / loggers / tests / logger_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  * @package    core_backup
19  * @category   phpunit
20  * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 defined('MOODLE_INTERNAL') || die();
26 // Include all the needed stuff
27 global $CFG;
28 require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
29 require_once($CFG->dirroot . '/backup/backup.class.php');
30 require_once($CFG->dirroot . '/backup/util/loggers/base_logger.class.php');
31 require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
32 require_once($CFG->dirroot . '/backup/util/loggers/output_text_logger.class.php');
33 require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
34 require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
35 require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
38 /**
39  * logger tests (all)
40  */
41 class backup_logger_testcase extends basic_testcase {
43     /**
44      * test base_logger class
45      */
46     function test_base_logger() {
47         // Test logger with simple action (message * level)
48         $lo = new mock_base_logger1(backup::LOG_ERROR);
49         $msg = 13;
50         $this->assertEquals($lo->process($msg, backup::LOG_ERROR), $msg * backup::LOG_ERROR);
51         // With lowest level must return true
52         $lo = new mock_base_logger1(backup::LOG_ERROR);
53         $msg = 13;
54         $this->assertTrue($lo->process($msg, backup::LOG_DEBUG));
56         // Chain 2 loggers, we must get as result the result of the inner one
57         $lo1 = new mock_base_logger1(backup::LOG_ERROR);
58         $lo2 = new mock_base_logger2(backup::LOG_ERROR);
59         $lo1->set_next($lo2);
60         $msg = 13;
61         $this->assertEquals($lo1->process($msg, backup::LOG_ERROR), $msg + backup::LOG_ERROR);
63         // Try circular reference
64         $lo1 = new mock_base_logger1(backup::LOG_ERROR);
65         try {
66             $lo1->set_next($lo1); //self
67             $this->assertTrue(false, 'base_logger_exception expected');
68         } catch (exception $e) {
69             $this->assertTrue($e instanceof base_logger_exception);
70             $this->assertEquals($e->errorcode, 'logger_circular_reference');
71             $this->assertTrue($e->a instanceof stdclass);
72             $this->assertEquals($e->a->main, get_class($lo1));
73             $this->assertEquals($e->a->alreadyinchain, get_class($lo1));
74         }
76         $lo1 = new mock_base_logger1(backup::LOG_ERROR);
77         $lo2 = new mock_base_logger2(backup::LOG_ERROR);
78         $lo3 = new mock_base_logger3(backup::LOG_ERROR);
79         $lo1->set_next($lo2);
80         $lo2->set_next($lo3);
81         try {
82             $lo3->set_next($lo1);
83             $this->assertTrue(false, 'base_logger_exception expected');
84         } catch (exception $e) {
85             $this->assertTrue($e instanceof base_logger_exception);
86             $this->assertEquals($e->errorcode, 'logger_circular_reference');
87             $this->assertTrue($e->a instanceof stdclass);
88             $this->assertEquals($e->a->main, get_class($lo1));
89             $this->assertEquals($e->a->alreadyinchain, get_class($lo3));
90         }
92         // Test stopper logger
93         $lo1 = new mock_base_logger1(backup::LOG_ERROR);
94         $lo2 = new mock_base_logger2(backup::LOG_ERROR);
95         $lo3 = new mock_base_logger3(backup::LOG_ERROR);
96         $lo1->set_next($lo2);
97         $lo2->set_next($lo3);
98         $this->assertFalse($lo1->process('test', backup::LOG_ERROR));
100         // Test checksum correct
101         $lo1 = new mock_base_logger1(backup::LOG_ERROR);
102         $lo1->is_checksum_correct(get_class($lo1) . '-' . backup::LOG_ERROR);
104         // Test get_levelstr()
105         $lo1 = new mock_base_logger1(backup::LOG_ERROR);
106         $this->assertEquals($lo1->get_levelstr(backup::LOG_NONE), 'undefined');
107         $this->assertEquals($lo1->get_levelstr(backup::LOG_ERROR), 'error');
108         $this->assertEquals($lo1->get_levelstr(backup::LOG_WARNING), 'warn');
109         $this->assertEquals($lo1->get_levelstr(backup::LOG_INFO), 'info');
110         $this->assertEquals($lo1->get_levelstr(backup::LOG_DEBUG), 'debug');
112         // Test destroy.
113         $lo1 = new mock_base_logger1(backup::LOG_ERROR);
114         $lo2 = new mock_base_logger2(backup::LOG_ERROR);
115         $lo1->set_next($lo2);
116         $this->assertInstanceOf('base_logger', $lo1->get_next());
117         $this->assertNull($lo2->get_next());
118         $lo1->destroy();
119         $this->assertNull($lo1->get_next());
120         $this->assertNull($lo2->get_next());
121     }
123     /**
124      * test error_log_logger class
125      */
126     function test_error_log_logger() {
127         // Not much really to test, just instantiate and execute, should return true
128         $lo = new error_log_logger(backup::LOG_ERROR);
129         $this->assertTrue($lo instanceof error_log_logger);
130         $message = 'This log exists because you have run Moodle unit tests: Ignore it';
131         $result = $lo->process($message, backup::LOG_ERROR);
132         $this->assertTrue($result);
133     }
135     /**
136      * test output_text_logger class
137      */
138     function test_output_text_logger() {
139         // Instantiate without date nor level output
140         $lo = new output_text_logger(backup::LOG_ERROR);
141         $this->assertTrue($lo instanceof output_text_logger);
142         $message = 'testing output_text_logger';
143         ob_start(); // Capture output
144         $result = $lo->process($message, backup::LOG_ERROR);
145         $contents = ob_get_contents();
146         ob_end_clean(); // End capture and discard
147         $this->assertTrue($result);
148         $this->assertTrue(strpos($contents, $message) !== false);
150         // Instantiate with date and level output
151         $lo = new output_text_logger(backup::LOG_ERROR, true, true);
152         $this->assertTrue($lo instanceof output_text_logger);
153         $message = 'testing output_text_logger';
154         ob_start(); // Capture output
155         $result = $lo->process($message, backup::LOG_ERROR);
156         $contents = ob_get_contents();
157         ob_end_clean(); // End capture and discard
158         $this->assertTrue($result);
159         $this->assertTrue(strpos($contents,'[') === 0);
160         $this->assertTrue(strpos($contents,'[error]') !== false);
161         $this->assertTrue(strpos($contents, $message) !== false);
162         $this->assertTrue(substr_count($contents , '] ') >= 2);
163     }
165     /**
166      * test output_indented_logger class
167      */
168     function test_output_indented_logger() {
169         // Instantiate without date nor level output
170         $options = array('depth' => 2);
171         $lo = new output_indented_logger(backup::LOG_ERROR);
172         $this->assertTrue($lo instanceof output_indented_logger);
173         $message = 'testing output_indented_logger';
174         ob_start(); // Capture output
175         $result = $lo->process($message, backup::LOG_ERROR, $options);
176         $contents = ob_get_contents();
177         ob_end_clean(); // End capture and discard
178         $this->assertTrue($result);
179         if (defined('STDOUT')) {
180             $check = '  ';
181         } else {
182             $check = '&nbsp;&nbsp;';
183         }
184         $this->assertTrue(strpos($contents, str_repeat($check, $options['depth']) . $message) !== false);
186         // Instantiate with date and level output
187         $options = array('depth' => 3);
188         $lo = new output_indented_logger(backup::LOG_ERROR, true, true);
189         $this->assertTrue($lo instanceof output_indented_logger);
190         $message = 'testing output_indented_logger';
191         ob_start(); // Capture output
192         $result = $lo->process($message, backup::LOG_ERROR, $options);
193         $contents = ob_get_contents();
194         ob_end_clean(); // End capture and discard
195         $this->assertTrue($result);
196         $this->assertTrue(strpos($contents,'[') === 0);
197         $this->assertTrue(strpos($contents,'[error]') !== false);
198         $this->assertTrue(strpos($contents, $message) !== false);
199         $this->assertTrue(substr_count($contents , '] ') >= 2);
200         if (defined('STDOUT')) {
201             $check = '  ';
202         } else {
203             $check = '&nbsp;&nbsp;';
204         }
205         $this->assertTrue(strpos($contents, str_repeat($check, $options['depth']) . $message) !== false);
206     }
208     /**
209      * test database_logger class
210      */
211     function test_database_logger() {
212         // Instantiate with date and level output (and with specs from the global moodle "log" table so checks will pass
213         $now = time();
214         $datecol = 'time';
215         $levelcol = 'action';
216         $messagecol = 'info';
217         $logtable = 'log';
218         $columns = array('url' => 'http://127.0.0.1');
219         $loglevel = backup::LOG_ERROR;
220         $lo = new mock_database_logger(backup::LOG_ERROR, $datecol, $levelcol, $messagecol, $logtable, $columns);
221         $this->assertTrue($lo instanceof database_logger);
222         $message = 'testing database_logger';
223         $result = $lo->process($message, $loglevel);
224         // Check everything is ready to be inserted to DB
225         $this->assertEquals($result['table'], $logtable);
226         $this->assertTrue($result['columns'][$datecol] >= $now);
227         $this->assertEquals($result['columns'][$levelcol], $loglevel);
228         $this->assertEquals($result['columns'][$messagecol], $message);
229         $this->assertEquals($result['columns']['url'], $columns['url']);
230     }
232     /**
233      * test file_logger class
234      */
235     function test_file_logger() {
236         global $CFG;
238         $file = $CFG->tempdir . '/test/test_file_logger.txt';
239         // Remove the test dir and any content
240         @remove_dir(dirname($file));
241         // Recreate test dir
242         if (!check_dir_exists(dirname($file), true, true)) {
243             throw new moodle_exception('error_creating_temp_dir', 'error', dirname($file));
244         }
246         // Instantiate with date and level output, and also use the depth option
247         $options = array('depth' => 3);
248         $lo1 = new file_logger(backup::LOG_ERROR, true, true, $file);
249         $this->assertTrue($lo1 instanceof file_logger);
250         $message1 = 'testing file_logger';
251         $result = $lo1->process($message1, backup::LOG_ERROR, $options);
252         $this->assertTrue($result);
254         // Another file_logger is going towrite there too without closing
255         $options = array();
256         $lo2 = new file_logger(backup::LOG_WARNING, true, true, $file);
257         $this->assertTrue($lo2 instanceof file_logger);
258         $message2 = 'testing file_logger2';
259         $result = $lo2->process($message2, backup::LOG_WARNING, $options);
260         $this->assertTrue($result);
262         // Destroy loggers.
263         $lo1->destroy();
264         $lo2->destroy();
266         // Load file results to analyze them
267         $fcontents = file_get_contents($file);
268         $acontents = explode(PHP_EOL, $fcontents); // Split by line
269         $this->assertTrue(strpos($acontents[0], $message1) !== false);
270         $this->assertTrue(strpos($acontents[0], '[error]') !== false);
271         $this->assertTrue(strpos($acontents[0], '      ') !== false);
272         $this->assertTrue(substr_count($acontents[0] , '] ') >= 2);
273         $this->assertTrue(strpos($acontents[1], $message2) !== false);
274         $this->assertTrue(strpos($acontents[1], '[warn]') !== false);
275         $this->assertTrue(strpos($acontents[1], '      ') === false);
276         $this->assertTrue(substr_count($acontents[1] , '] ') >= 2);
277         unlink($file); // delete file
279         // Try one html file
280         check_dir_exists($CFG->tempdir . '/test');
281         $file = $CFG->tempdir . '/test/test_file_logger.html';
282         $options = array('depth' => 1);
283         $lo = new file_logger(backup::LOG_ERROR, true, true, $file);
284         $this->assertTrue($lo instanceof file_logger);
285         $this->assertTrue(file_exists($file));
286         $message = 'testing file_logger';
287         $result = $lo->process($message, backup::LOG_ERROR, $options);
288         $lo->close(); // Closes logger.
289         // Get file contents and inspect them
290         $fcontents = file_get_contents($file);
291         $this->assertTrue($result);
292         $this->assertTrue(strpos($fcontents, $message) !== false);
293         $this->assertTrue(strpos($fcontents, '[error]') !== false);
294         $this->assertTrue(strpos($fcontents, '&nbsp;&nbsp;') !== false);
295         $this->assertTrue(substr_count($fcontents , '] ') >= 2);
296         unlink($file); // delete file
298         // Instantiate, write something, force deletion, try to write again
299         check_dir_exists($CFG->tempdir . '/test');
300         $file = $CFG->tempdir . '/test/test_file_logger.html';
301         $lo = new mock_file_logger(backup::LOG_ERROR, true, true, $file);
302         $this->assertTrue(file_exists($file));
303         $message = 'testing file_logger';
304         $result = $lo->process($message, backup::LOG_ERROR);
305         $lo->close();
306         $this->assertNull($lo->get_fhandle());
307         try {
308             $result = @$lo->process($message, backup::LOG_ERROR); // Try to write again
309             $this->assertTrue(false, 'base_logger_exception expected');
310         } catch (exception $e) {
311             $this->assertTrue($e instanceof base_logger_exception);
312             $this->assertEquals($e->errorcode, 'error_writing_file');
313         }
315         // Instantiate without file
316         try {
317             $lo = new file_logger(backup::LOG_WARNING, true, true, '');
318             $this->assertTrue(false, 'base_logger_exception expected');
319         } catch (exception $e) {
320             $this->assertTrue($e instanceof base_logger_exception);
321             $this->assertEquals($e->errorcode, 'missing_fullpath_parameter');
322         }
324         // Instantiate in (near) impossible path
325         $file =  $CFG->tempdir . '/test_azby/test_file_logger.txt';
326         try {
327             $lo = new file_logger(backup::LOG_WARNING, true, true, $file);
328             $this->assertTrue(false, 'base_logger_exception expected');
329         } catch (exception $e) {
330             $this->assertTrue($e instanceof base_logger_exception);
331             $this->assertEquals($e->errorcode, 'file_not_writable');
332             $this->assertEquals($e->a, $file);
333         }
335         // Instantiate one file logger with level = backup::LOG_NONE
336         $file =  $CFG->tempdir . '/test/test_file_logger.txt';
337         $lo = new file_logger(backup::LOG_NONE, true, true, $file);
338         $this->assertTrue($lo instanceof file_logger);
339         $this->assertFalse(file_exists($file));
340         $lo->close();
342         // Remove the test dir and any content
343         @remove_dir(dirname($file));
344     }
348 /**
349  * helper extended base_logger class that implements some methods for testing
350  * Simply return the product of message and level
351  */
352 class mock_base_logger1 extends base_logger {
354     protected function action($message, $level, $options = null) {
355         return $message * $level; // Simply return that, for testing
356     }
357     public function get_levelstr($level) {
358         return parent::get_levelstr($level);
359     }
362 /**
363  * helper extended base_logger class that implements some methods for testing
364  * Simply return the sum of message and level
365  */
366 class mock_base_logger2 extends base_logger {
368     protected function action($message, $level, $options = null) {
369         return $message + $level; // Simply return that, for testing
370     }
373 /**
374  * helper extended base_logger class that implements some methods for testing
375  * Simply return 8
376  */
377 class mock_base_logger3 extends base_logger {
379     protected function action($message, $level, $options = null) {
380         return false; // Simply return false, for testing stopper
381     }
384 /**
385  * helper extended database_logger class that implements some methods for testing
386  * Returns the complete info that normally will be used by insert record calls
387  */
388 class mock_database_logger extends database_logger {
390     protected function insert_log_record($table, $columns) {
391         return array('table' => $table, 'columns' => $columns);
392     }
395 /**
396  * helper extended file_logger class that implements some methods for testing
397  * Returns the, usually protected, handle
398  */
399 class mock_file_logger extends file_logger {
401     function get_fhandle() {
402         return $this->fhandle;
403     }