37ffb805d94c934ee8dc3f151d93328648fc1357
[moodle.git] / backup / util / xml / parser / tests / parser_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/xml/parser/progressive_parser.class.php');
29 require_once($CFG->dirroot . '/backup/util/xml/parser/processors/progressive_parser_processor.class.php');
30 require_once($CFG->dirroot . '/backup/util/xml/parser/processors/simplified_parser_processor.class.php');
31 require_once($CFG->dirroot . '/backup/util/xml/parser/processors/grouped_parser_processor.class.php');
33 /*
34  * progressive_parser and progressive_parser_processor tests
35  */
36 class progressive_parser_test extends advanced_testcase {
38     /*
39      * test progressive_parser public methods
40      */
41     function test_parser_public_api() {
42         global $CFG;
43         // Instantiate progressive_parser
44         $pp = new progressive_parser();
45         $this->assertTrue($pp instanceof progressive_parser);
46         $pr = new mock_parser_processor();
47         $this->assertTrue($pr instanceof progressive_parser_processor);
49         // Try to process without processor
50         try {
51             $pp->process();
52             $this->assertTrue(false);
53         } catch (exception $e) {
54             $this->assertTrue($e instanceof progressive_parser_exception);
55             $this->assertEquals($e->errorcode, 'undefined_parser_processor');
56         }
58         // Assign processor to parser
59         $pp->set_processor($pr);
61         // Try to process without file and contents
62         try {
63             $pp->process();
64             $this->assertTrue(false);
65         } catch (exception $e) {
66             $this->assertTrue($e instanceof progressive_parser_exception);
67             $this->assertEquals($e->errorcode, 'undefined_xml_to_parse');
68         }
70         // Assign *invalid* processor to parser
71         try {
72             $pp->set_processor(new stdClass());
73             $this->assertTrue(false);
74         } catch (exception $e) {
75             $this->assertTrue($e instanceof progressive_parser_exception);
76             $this->assertEquals($e->errorcode, 'invalid_parser_processor');
77         }
79         // Set file from fixtures (test1.xml) and process it
80         $pp = new progressive_parser();
81         $pr = new mock_parser_processor();
82         $pp->set_processor($pr);
83         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test1.xml');
84         $pp->process();
85         $serfromfile = serialize($pr->get_chunks()); // Get serialized results (to compare later)
86         // Set *unexisting* file from fixtures
87         try {
88             $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test0.xml');
89             $this->assertTrue(false);
90         } catch (exception $e) {
91             $this->assertTrue($e instanceof progressive_parser_exception);
92             $this->assertEquals($e->errorcode, 'invalid_file_to_parse');
93         }
95         // Set contents from fixtures (test1.xml) and process it
96         $pp = new progressive_parser();
97         $pr = new mock_parser_processor();
98         $pp->set_processor($pr);
99         $pp->set_contents(file_get_contents($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test1.xml'));
100         $pp->process();
101         $serfrommemory = serialize($pr->get_chunks()); // Get serialized results (to compare later)
102         // Set *empty* contents
103         try {
104             $pp->set_contents('');
105             $this->assertTrue(false);
106         } catch (exception $e) {
107             $this->assertTrue($e instanceof progressive_parser_exception);
108             $this->assertEquals($e->errorcode, 'invalid_contents_to_parse');
109         }
111         // Check that both results from file processing and content processing are equal
112         $this->assertEquals($serfromfile, $serfrommemory);
114         // Check case_folding is working ok
115         $pp = new progressive_parser(true);
116         $pr = new mock_parser_processor();
117         $pp->set_processor($pr);
118         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test1.xml');
119         $pp->process();
120         $chunks = $pr->get_chunks();
121         $this->assertTrue($chunks[0]['path'] === '/FIRSTTAG');
122         $this->assertTrue($chunks[0]['tags']['SECONDTAG']['name'] === 'SECONDTAG');
123         $this->assertTrue($chunks[0]['tags']['SECONDTAG']['attrs']['NAME'] === 'secondtag');
125         // Check invalid XML exception is working ok
126         $pp = new progressive_parser(true);
127         $pr = new mock_parser_processor();
128         $pp->set_processor($pr);
129         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test2.xml');
130         try {
131             $pp->process();
132         } catch (exception $e) {
133             $this->assertTrue($e instanceof progressive_parser_exception);
134             $this->assertEquals($e->errorcode, 'xml_parsing_error');
135         }
137         // Check double process throws exception
138         $pp = new progressive_parser(true);
139         $pr = new mock_parser_processor();
140         $pp->set_processor($pr);
141         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test1.xml');
142         $pp->process();
143         try { // Second process, will throw exception
144             $pp->process();
145             $this->assertTrue(false);
146         } catch (exception $e) {
147             $this->assertTrue($e instanceof progressive_parser_exception);
148             $this->assertEquals($e->errorcode, 'progressive_parser_already_used');
149         }
150     }
152     /*
153      * test progressive_parser parsing results using testing_parser_processor and test1.xml
154      * auto-described file from fixtures
155      */
156     function test_parser_results() {
157         global $CFG;
158         // Instantiate progressive_parser
159         $pp = new progressive_parser();
160         // Instantiate processor, passing the unit test as param
161         $pr = new mock_auto_parser_processor($this);
162         $this->assertTrue($pr instanceof progressive_parser_processor);
163         // Assign processor to parser
164         $pp->set_processor($pr);
165         // Set file from fixtures
166         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test3.xml');
167         // Process the file, the autotest processor will perform a bunch of automatic tests
168         $pp->process();
169         // Get processor debug info
170         $debug = $pr->debug_info();
171         $this->assertTrue(is_array($debug));
172         $this->assertTrue(array_key_exists('chunks', $debug));
173         // Check the number of chunks is correct for the file
174         $this->assertEquals($debug['chunks'], 10);
175     }
177     /*
178      * test progressive_parser parsing results using simplified_parser_processor and test4.xml
179      * (one simple glossary backup file example)
180      */
181     function test_simplified_parser_results() {
182         global $CFG;
183         // Instantiate progressive_parser
184         $pp =  new progressive_parser();
185         // Instantiate simplified_parser_processor declaring the interesting paths
186         $pr = new mock_simplified_parser_processor(array(
187             '/activity',
188             '/activity/glossary',
189             '/activity/glossary/entries/entry',
190             '/activity/glossary/entries/entry/aliases/alias',
191             '/activity/glossary/entries/entry/ratings/rating',
192             '/activity/glossary/categories/category',
193             '/activity/glossary/onetest',
194             '/activity/glossary/othertest'));
195         $this->assertTrue($pr instanceof progressive_parser_processor);
196         // Assign processor to parser
197         $pp->set_processor($pr);
198         // Set file from fixtures
199         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test4.xml');
200         // Process the file
201         $pp->process();
202         // Get processor debug info
203         $debug = $pr->debug_info();
204         $this->assertTrue(is_array($debug));
205         $this->assertTrue(array_key_exists('chunks', $debug));
207         // Check the number of chunks is correct for the file
208         $this->assertEquals($debug['chunks'], 12);
209         // Get all the simplified chunks and perform various validations
210         $chunks = $pr->get_chunks();
211         // Check we have received the correct number of chunks
212         $this->assertEquals(count($chunks), 12);
214         // chunk[0] (/activity) tests
215         $this->assertEquals(count($chunks[0]), 3);
216         $this->assertEquals($chunks[0]['path'], '/activity');
217         $this->assertEquals($chunks[0]['level'],'2');
218         $tags = $chunks[0]['tags'];
219         $this->assertEquals(count($tags), 4);
220         $this->assertEquals($tags['id'], 1);
221         $this->assertEquals($tags['moduleid'], 5);
222         $this->assertEquals($tags['modulename'], 'glossary');
223         $this->assertEquals($tags['contextid'], 26);
224         $this->assertEquals($chunks[0]['level'],'2');
226         // chunk[1] (/activity/glossary) tests
227         $this->assertEquals(count($chunks[1]), 3);
228         $this->assertEquals($chunks[1]['path'], '/activity/glossary');
229         $this->assertEquals($chunks[1]['level'],'3');
230         $tags = $chunks[1]['tags'];
231         $this->assertEquals(count($tags), 24);
232         $this->assertEquals($tags['id'], 1);
233         $this->assertEquals($tags['intro'], '<p>One simple glossary to test backup &amp; restore. Here it\'s the standard image:</p>'.
234                                            "\n".
235                                            '<p><img src="@@PLUGINFILE@@/88_31.png" alt="pwd by moodle" width="88" height="31" /></p>');
236         $this->assertEquals($tags['timemodified'], 1275639747);
237         $this->assertTrue(!isset($tags['categories']));
239         // chunk[5] (second /activity/glossary/entries/entry) tests
240         $this->assertEquals(count($chunks[5]), 3);
241         $this->assertEquals($chunks[5]['path'], '/activity/glossary/entries/entry');
242         $this->assertEquals($chunks[5]['level'],'5');
243         $tags = $chunks[5]['tags'];
244         $this->assertEquals(count($tags), 15);
245         $this->assertEquals($tags['id'], 2);
246         $this->assertEquals($tags['concept'], 'cat');
247         $this->assertTrue(!isset($tags['aliases']));
248         $this->assertTrue(!isset($tags['entries']));
250         // chunk[6] (second /activity/glossary/entries/entry/aliases/alias) tests
251         $this->assertEquals(count($chunks[6]), 3);
252         $this->assertEquals($chunks[6]['path'], '/activity/glossary/entries/entry/aliases/alias');
253         $this->assertEquals($chunks[6]['level'],'7');
254         $tags = $chunks[6]['tags'];
255         $this->assertEquals(count($tags), 2);
256         $this->assertEquals($tags['id'], 2);
257         $this->assertEquals($tags['alias_text'], 'cats');
259         // chunk[7] (second /activity/glossary/entries/entry/aliases/alias) tests
260         $this->assertEquals(count($chunks[7]), 3);
261         $this->assertEquals($chunks[7]['path'], '/activity/glossary/entries/entry/aliases/alias');
262         $this->assertEquals($chunks[7]['level'],'7');
263         $tags = $chunks[7]['tags'];
264         $this->assertEquals(count($tags), 2);
265         $this->assertEquals($tags['id'], 3);
266         $this->assertEquals($tags['alias_text'], 'felines');
268         // chunk[8] (second /activity/glossary/entries/entry/ratings/rating) tests
269         $this->assertEquals(count($chunks[8]), 3);
270         $this->assertEquals($chunks[8]['path'], '/activity/glossary/entries/entry/ratings/rating');
271         $this->assertEquals($chunks[8]['level'],'7');
272         $tags = $chunks[8]['tags'];
273         $this->assertEquals(count($tags), 6);
274         $this->assertEquals($tags['id'], 1);
275         $this->assertEquals($tags['timemodified'], '1275639779');
277         // chunk[9] (first /activity/glossary/onetest) tests
278         $this->assertEquals(count($chunks[9]), 3);
279         $this->assertEquals($chunks[9]['path'], '/activity/glossary/onetest');
280         $this->assertEquals($chunks[9]['level'],'4');
281         $tags = $chunks[9]['tags'];
282         $this->assertEquals(count($tags), 2);
283         $this->assertEquals($tags['name'], 1);
284         $this->assertEquals($tags['value'], 1);
286         // chunk[10] (second /activity/glossary/onetest) tests
287         $this->assertEquals(count($chunks[10]), 3);
288         $this->assertEquals($chunks[10]['path'], '/activity/glossary/onetest');
289         $this->assertEquals($chunks[10]['level'],'4');
290         $tags = $chunks[10]['tags'];
291         $this->assertEquals(count($tags), 2);
292         $this->assertEquals($tags['name'], 2);
293         $this->assertEquals($tags['value'], 2);
295         // chunk[11] (first /activity/glossary/othertest) tests
296         // note we don't allow repeated "final" element, so we only return the last one
297         $this->assertEquals(count($chunks[11]), 3);
298         $this->assertEquals($chunks[11]['path'], '/activity/glossary/othertest');
299         $this->assertEquals($chunks[11]['level'],'4');
300         $tags = $chunks[11]['tags'];
301         $this->assertEquals(count($tags), 2);
302         $this->assertEquals($tags['name'], 4);
303         $this->assertEquals($tags['value'], 5);
305         // Now check start notifications
306         $snotifs = $pr->get_start_notifications();
307         // Check we have received the correct number of notifications
308         $this->assertEquals(count($snotifs), 12);
309         // Check first, sixth and last notifications
310         $this->assertEquals($snotifs[0], '/activity');
311         $this->assertEquals($snotifs[5], '/activity/glossary/entries/entry');
312         $this->assertEquals($snotifs[11], '/activity/glossary/othertest');
314         // Now check end notifications
315         $enotifs = $pr->get_end_notifications();
316         // Check we have received the correct number of notifications
317         $this->assertEquals(count($snotifs), 12);
318         // Check first, sixth and last notifications
319         $this->assertEquals($enotifs[0], '/activity/glossary/entries/entry/aliases/alias');
320         $this->assertEquals($enotifs[5], '/activity/glossary/entries/entry/ratings/rating');
321         $this->assertEquals($enotifs[11], '/activity');
323         // Check start and end notifications are balanced
324         sort($snotifs);
325         sort($enotifs);
326         $this->assertEquals($snotifs, $enotifs);
328         // Now verify that the start/process/end order is correct
329         $allnotifs = $pr->get_all_notifications();
330         $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count
331         // Check integrity of the notifications
332         $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
333         $this->assertEquals($errcount, 0); // No errors found, plz
334     }
336     /**
337      * test how the simplified processor and the order of start/process/end events happens
338      * with one real fragment of one backup 1.9 file, where some problems
339      * were found by David, hence we honor him in the name of the test ;-)
340      */
341     function test_simplified_david_backup19_file_fragment() {
342         global $CFG;
343         // Instantiate progressive_parser
344         $pp =  new progressive_parser();
345         // Instantiate grouped_parser_processor
346         $pr = new mock_simplified_parser_processor();
347         // Add interesting paths
348         $pr->add_path('/MOODLE_BACKUP/COURSE');
349         $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
350         $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
351         $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES');
352         $this->assertTrue($pr instanceof progressive_parser_processor);
353         // Assign processor to parser
354         $pp->set_processor($pr);
355         // Set file from fixtures
356         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test5.xml');
357         // Process the file
358         $pp->process();
360         // Get all the simplified chunks and perform various validations
361         $chunks = $pr->get_chunks();
362         $this->assertEquals(count($chunks), 3); // Only 3, because 7 (COURSE, ROLES_OVERRIDES and 5 MOD) are empty, aka no chunk
364         // Now check start notifications
365         $snotifs = $pr->get_start_notifications();
366         // Check we have received the correct number of notifications
367         $this->assertEquals(count($snotifs), 10); // Start tags are dispatched for empties (ROLES_OVERRIDES)
368         // Check first and last notifications
369         $this->assertEquals($snotifs[0], '/MOODLE_BACKUP/COURSE');
370         $this->assertEquals($snotifs[1], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
371         $this->assertEquals($snotifs[2], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
372         $this->assertEquals($snotifs[3], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES');
373         $this->assertEquals($snotifs[7], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
374         $this->assertEquals($snotifs[8], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
375         $this->assertEquals($snotifs[9], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
377         // Now check end notifications
378         $enotifs = $pr->get_end_notifications();
379         // Check we have received the correct number of notifications
380         $this->assertEquals(count($snotifs), 10); // End tags are dispatched for empties (ROLES_OVERRIDES)
381         // Check first, and last notifications
382         $this->assertEquals($enotifs[0], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES');
383         $this->assertEquals($enotifs[1], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
384         $this->assertEquals($enotifs[2], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
385         $this->assertEquals($enotifs[3], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
386         $this->assertEquals($enotifs[7], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
387         $this->assertEquals($enotifs[8], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
388         $this->assertEquals($enotifs[9], '/MOODLE_BACKUP/COURSE');
390         // Check start and end notifications are balanced
391         sort($snotifs);
392         sort($enotifs);
393         $this->assertEquals($snotifs, $enotifs);
395         // Now verify that the start/process/end order is correct
396         $allnotifs = $pr->get_all_notifications();
397         $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count
398         // Check integrity of the notifications
399         $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
400         $this->assertEquals($errcount, 0); // No errors found, plz
401     }
403     /*
404      * test progressive_parser parsing results using grouped_parser_processor and test4.xml
405      * (one simple glossary backup file example)
406      */
407     function test_grouped_parser_results() {
408         global $CFG;
409         // Instantiate progressive_parser
410         $pp =  new progressive_parser();
411         // Instantiate grouped_parser_processor
412         $pr = new mock_grouped_parser_processor();
413         // Add interesting paths
414         $pr->add_path('/activity');
415         $pr->add_path('/activity/glossary', true);
416         $pr->add_path('/activity/glossary/entries/entry');
417         $pr->add_path('/activity/glossary/entries/entry/aliases/alias');
418         $pr->add_path('/activity/glossary/entries/entry/ratings/rating');
419         $pr->add_path('/activity/glossary/categories/category');
420         $pr->add_path('/activity/glossary/onetest');
421         $pr->add_path('/activity/glossary/othertest');
422         $this->assertTrue($pr instanceof progressive_parser_processor);
423         // Assign processor to parser
424         $pp->set_processor($pr);
425         // Set file from fixtures
426         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test4.xml');
427         // Process the file
428         $pp->process();
429         // Get processor debug info
430         $debug = $pr->debug_info();
431         $this->assertTrue(is_array($debug));
432         $this->assertTrue(array_key_exists('chunks', $debug));
434         // Check the number of chunks is correct for the file
435         $this->assertEquals($debug['chunks'], 2);
436         // Get all the simplified chunks and perform various validations
437         $chunks = $pr->get_chunks();
438         // Check we have received the correct number of chunks
439         $this->assertEquals(count($chunks), 2);
441         // chunk[0] (/activity) tests
442         $this->assertEquals(count($chunks[0]), 3);
443         $this->assertEquals($chunks[0]['path'], '/activity');
444         $this->assertEquals($chunks[0]['level'],'2');
445         $tags = $chunks[0]['tags'];
446         $this->assertEquals(count($tags), 4);
447         $this->assertEquals($tags['id'], 1);
448         $this->assertEquals($tags['moduleid'], 5);
449         $this->assertEquals($tags['modulename'], 'glossary');
450         $this->assertEquals($tags['contextid'], 26);
451         $this->assertEquals($chunks[0]['level'],'2');
453         // chunk[1] (grouped /activity/glossary tests)
454         $this->assertEquals(count($chunks[1]), 3);
455         $this->assertEquals($chunks[1]['path'], '/activity/glossary');
456         $this->assertEquals($chunks[1]['level'],'3');
457         $tags = $chunks[1]['tags'];
458         $this->assertEquals(count($tags), 27);
459         $this->assertEquals($tags['id'], 1);
460         $this->assertEquals($tags['intro'], '<p>One simple glossary to test backup &amp; restore. Here it\'s the standard image:</p>'.
461                                            "\n".
462                                            '<p><img src="@@PLUGINFILE@@/88_31.png" alt="pwd by moodle" width="88" height="31" /></p>');
463         $this->assertEquals($tags['timemodified'], 1275639747);
464         $this->assertTrue(!isset($tags['categories']));
465         $this->assertTrue(isset($tags['entries']));
466         $this->assertTrue(isset($tags['onetest']));
467         $this->assertTrue(isset($tags['othertest']));
469         // Various tests under the entries
470         $entries = $chunks[1]['tags']['entries']['entry'];
471         $this->assertEquals(count($entries), 2);
473         // First entry
474         $entry1 = $entries[0];
475         $this->assertEquals(count($entry1), 17);
476         $this->assertEquals($entry1['id'], 1);
477         $this->assertEquals($entry1['userid'], 2);
478         $this->assertEquals($entry1['concept'], 'dog');
479         $this->assertEquals($entry1['definition'], '<p>Traditional enemies of cats</p>');
480         $this->assertTrue(isset($entry1['aliases']));
481         $this->assertTrue(isset($entry1['ratings']));
482         // aliases of first entry
483         $aliases = $entry1['aliases']['alias'];
484         $this->assertEquals(count($aliases), 1);
485         // first alias
486         $alias1 = $aliases[0];
487         $this->assertEquals(count($alias1), 2);
488         $this->assertEquals($alias1['id'], 1);
489         $this->assertEquals($alias1['alias_text'], 'dogs');
490         // ratings of first entry
491         $ratings = $entry1['ratings']['rating'];
492         $this->assertEquals(count($ratings), 1);
493         // first rating
494         $rating1 = $ratings[0];
495         $this->assertEquals(count($rating1), 6);
496         $this->assertEquals($rating1['id'], 2);
497         $this->assertEquals($rating1['value'], 6);
498         $this->assertEquals($rating1['timemodified'], '1275639797');
500         // Second entry
501         $entry2 = $entries[1];
502         $this->assertEquals(count($entry2), 17);
503         $this->assertEquals($entry2['id'], 2);
504         $this->assertEquals($entry2['userid'], 2);
505         $this->assertEquals($entry2['concept'], 'cat');
506         $this->assertEquals($entry2['definition'], '<p>traditional enemies of dogs</p>');
507         $this->assertTrue(isset($entry2['aliases']));
508         $this->assertTrue(isset($entry2['ratings']));
509         // aliases of first entry
510         $aliases = $entry2['aliases']['alias'];
511         $this->assertEquals(count($aliases), 2);
512         // first alias
513         $alias1 = $aliases[0];
514         $this->assertEquals(count($alias1), 2);
515         $this->assertEquals($alias1['id'], 2);
516         $this->assertEquals($alias1['alias_text'], 'cats');
517         // second alias
518         $alias2 = $aliases[1];
519         $this->assertEquals(count($alias2), 2);
520         $this->assertEquals($alias2['id'], 3);
521         $this->assertEquals($alias2['alias_text'], 'felines');
522         // ratings of first entry
523         $ratings = $entry2['ratings']['rating'];
524         $this->assertEquals(count($ratings), 1);
525         // first rating
526         $rating1 = $ratings[0];
527         $this->assertEquals(count($rating1), 6);
528         $this->assertEquals($rating1['id'], 1);
529         $this->assertEquals($rating1['value'], 5);
530         $this->assertEquals($rating1['scaleid'], 10);
532         // Onetest test (only 1 level nested)
533         $onetest = $tags['onetest'];
534         $this->assertEquals(count($onetest), 2);
535         $this->assertEquals(count($onetest[0]), 2);
536         $this->assertEquals($onetest[0]['name'], 1);
537         $this->assertEquals($onetest[0]['value'], 1);
538         $this->assertEquals(count($onetest[1]), 2);
539         $this->assertEquals($onetest[1]['name'], 2);
540         $this->assertEquals($onetest[1]['value'], 2);
542         // Other test (0 level nested, only last one is retrieved)
543         $othertest = $tags['othertest'];
544         $this->assertEquals(count($othertest), 1);
545         $this->assertEquals(count($othertest[0]), 2);
546         $this->assertEquals($othertest[0]['name'], 4);
547         $this->assertEquals($othertest[0]['value'], 5);
549         // Now check start notifications
550         $snotifs = $pr->get_start_notifications();
551         // Check we have received the correct number of notifications
552         $this->assertEquals(count($snotifs), 2);
553         // Check first and last notifications
554         $this->assertEquals($snotifs[0], '/activity');
555         $this->assertEquals($snotifs[1], '/activity/glossary');
557         // Now check end notifications
558         $enotifs = $pr->get_end_notifications();
559         // Check we have received the correct number of notifications
560         $this->assertEquals(count($snotifs), 2);
561         // Check first, and last notifications
562         $this->assertEquals($enotifs[0], '/activity/glossary');
563         $this->assertEquals($enotifs[1], '/activity');
565         // Check start and end notifications are balanced
566         sort($snotifs);
567         sort($enotifs);
568         $this->assertEquals($snotifs, $enotifs);
570         // Now verify that the start/process/end order is correct
571         $allnotifs = $pr->get_all_notifications();
572         $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count
573         // Check integrity of the notifications
574         $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
575         $this->assertEquals($errcount, 0); // No errors found, plz
576     }
578     /**
579      * test how the grouped processor and the order of start/process/end events happens
580      * with one real fragment of one backup 1.9 file, where some problems
581      * were found by David, hence we honor him in the name of the test ;-)
582      */
583     function test_grouped_david_backup19_file_fragment() {
584         global $CFG;
585         // Instantiate progressive_parser
586         $pp =  new progressive_parser();
587         // Instantiate grouped_parser_processor
588         $pr = new mock_grouped_parser_processor();
589         // Add interesting paths
590         $pr->add_path('/MOODLE_BACKUP/COURSE');
591         $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION', true);
592         $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
593         $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES');
594         $this->assertTrue($pr instanceof progressive_parser_processor);
595         // Assign processor to parser
596         $pp->set_processor($pr);
597         // Set file from fixtures
598         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test5.xml');
599         // Process the file
600         $pp->process();
602         // Get all the simplified chunks and perform various validations
603         $chunks = $pr->get_chunks();
604         $this->assertEquals(count($chunks), 1); // Only 1, the SECTION one
606         // Now check start notifications
607         $snotifs = $pr->get_start_notifications();
608         // Check we have received the correct number of notifications
609         $this->assertEquals(count($snotifs), 2);
610         // Check first and last notifications
611         $this->assertEquals($snotifs[0], '/MOODLE_BACKUP/COURSE');
612         $this->assertEquals($snotifs[1], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
614         // Now check end notifications
615         $enotifs = $pr->get_end_notifications();
616         // Check we have received the correct number of notifications
617         $this->assertEquals(count($snotifs), 2); // End tags are dispatched for empties (ROLES_OVERRIDES)
618         // Check first, and last notifications
619         $this->assertEquals($enotifs[0], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
620         $this->assertEquals($enotifs[1], '/MOODLE_BACKUP/COURSE');
622         // Check start and end notifications are balanced
623         sort($snotifs);
624         sort($enotifs);
625         $this->assertEquals($snotifs, $enotifs);
627         // Now verify that the start/process/end order is correct
628         $allnotifs = $pr->get_all_notifications();
629         $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count
630         // Check integrity of the notifications
631         $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
632         $this->assertEquals($errcount, 0); // No errors found, plz
633     }
635     /**
636      */
637     function test_grouped_at_empty_node() {
638         global $CFG;
639         // Instantiate progressive_parser.
640         $pp =  new progressive_parser();
641         // Instantiate grouped_parser_processor.
642         $pr = new mock_grouped_parser_processor();
643         $this->assertTrue($pr instanceof progressive_parser_processor);
644         // Add interesting paths - moodle1 style.
645         $pr->add_path('/test/MOODLE_BACKUP/COURSE/FORMATDATA', true);
646         $pr->add_path('/test/MOODLE_BACKUP/COURSE/FORMATDATA/WEEKS/WEEK');
647         $pr->add_path('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', true);
648         $pr->add_path('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', true);
649         $pr->add_path('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED/SUBS/SUB');
650         // Add interesting paths - moodle2 style.
651         $pr->add_path('/test/moodle2/grouped', true);
652         $pr->add_path('/test/moodle2/grouped/subs/sub');
653         $pr->add_path('/test/moodle2/groupedemptywithattr', true);
654         $pr->add_path('/test/moodle2/groupednonemptywithattr', true);
655         $pr->add_path('/test/moodle2/groupednonemptywithattr/subs/sub');
656         // Assign processor to parser.
657         $pp->set_processor($pr);
658         // Set file from fixtures.
659         $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test6.xml');
660         // Process the file.
661         $pp->process();
663         // Get all the simplified chunks and perform various validations.
664         $chunks = $pr->get_chunks();
665         $this->assertEquals(count($chunks), 6); // All grouped elements.
667         // Check some random data.
668         $this->assertEquals('/test/MOODLE_BACKUP/COURSE/FORMATDATA', $chunks[0]['path']);
669         $this->assertEquals(2, $chunks[0]['tags']['WEEKS']['WEEK'][1]['SECTION']);
671         $this->assertEquals('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', $chunks[1]['path']);
672         $this->assertEquals(array(), $chunks[1]['tags']);
674         $this->assertEquals('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', $chunks[2]['path']);
675         $this->assertEquals('Unit tests rock!', $chunks[2]['tags']['SUBS']['SUB'][0]['PROP']);
677         $this->assertEquals('/test/moodle2/grouped', $chunks[3]['path']);
678         $this->assertFalse(isset($chunks[3]['tags']['id'])); // No final elements, this should be fixed one day.
679         $this->assertEquals(34, $chunks[3]['tags']['subs']['sub'][0]['id']); // We have final element so this is parsed.
680         $this->assertEquals('Oh yeah', $chunks[3]['tags']['subs']['sub'][0]['prop']);
682         $this->assertEquals('/test/moodle2/groupednonemptywithattr', $chunks[4]['path']);
683         $this->assertEquals(78, $chunks[4]['tags']['id']); // We have final element so this is parsed.
684         $this->assertEquals('Go baby go', $chunks[4]['tags']['prop']);
685         $this->assertEquals(89, $chunks[4]['tags']['subs']['sub'][0]['id']);
686         $this->assertEquals('http://moodle.org', $chunks[4]['tags']['subs']['sub'][0]['prop']);
688         $this->assertEquals('/test/moodle2/groupedemptywithattr', $chunks[5]['path']);
689         $this->assertFalse(isset($chunks[5]['tags']['attr'])); // No final elements, this should be fixed one day.
691         // Now check start notifications.
692         $snotifs = $pr->get_start_notifications();
693         // Check we have received the correct number of notifications.
694         $this->assertEquals(count($snotifs), 6);
695         // Check the order of notifications (in order they appear in test6.xml).
696         $this->assertEquals('/test/MOODLE_BACKUP/COURSE/FORMATDATA', $snotifs[0]);
697         $this->assertEquals('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', $snotifs[1]);
698         $this->assertEquals('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', $snotifs[2]);
699         $this->assertEquals('/test/moodle2/grouped', $snotifs[3]);
700         $this->assertEquals('/test/moodle2/groupednonemptywithattr', $snotifs[4]);
701         $this->assertEquals('/test/moodle2/groupedemptywithattr', $snotifs[5]);
703         // Now check end notifications.
704         $enotifs = $pr->get_end_notifications();
705         // Check we have received the correct number of notifications.
706         $this->assertEquals(count($enotifs), 6);
707         // Check the order of notifications (in order they appear in test6.xml).
708         $this->assertEquals('/test/MOODLE_BACKUP/COURSE/FORMATDATA', $enotifs[0]);
709         $this->assertEquals('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', $enotifs[1]);
710         $this->assertEquals('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', $enotifs[2]);
711         $this->assertEquals('/test/moodle2/grouped', $enotifs[3]);
712         $this->assertEquals('/test/moodle2/groupednonemptywithattr', $enotifs[4]);
713         $this->assertEquals('/test/moodle2/groupedemptywithattr', $enotifs[5]);
715         // Now verify that the start/process/end order is correct.
716         $allnotifs = $pr->get_all_notifications();
717         $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks));
718         // Check integrity of the notifications.
719         $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
720         $this->assertEquals(0, $errcount); // This fails at the moment.
721     }
723     /**
724      * Helper function that given one array of ordered start/process/end notifications will
725      * check it of integrity like:
726      *    - process only happens if start is the previous notification
727      *    - end only happens if dispatch is the previous notification
728      *    - start only happen with level > than last one and if there is no already started like that
729      *
730      * @param array $notifications ordered array of notifications with format [start|process|end]:path
731      * @return int number of integrity problems found (errors)
732      */
733     function helper_check_notifications_order_integrity($notifications) {
734         $numerrors = 0;
735         $notifpile = array('pilebase' => 'start');
736         $lastnotif = 'start:pilebase';
737         foreach ($notifications as $notif) {
739             $lastpiletype = end($notifpile);
740             $lastpilepath = key($notifpile);
741             $lastpilelevel = strlen(preg_replace('/[^\/]/', '', $lastpilepath));
743             $lastnotiftype  = preg_replace('/:.*/', '', $lastnotif);
744             $lastnotifpath  = preg_replace('/.*:/', '', $lastnotif);
745             $lastnotiflevel = strlen(preg_replace('/[^\/]/', '', $lastnotifpath));
747             $notiftype  = preg_replace('/:.*/', '', $notif);
748             $notifpath  = preg_replace('/.*:/', '', $notif);
749             $notiflevel = strlen(preg_replace('/[^\/]/', '', $notifpath));
751             switch ($notiftype) {
752                 case 'process':
753                     if ($lastnotifpath != $notifpath or $lastnotiftype != 'start') {
754                         $numerrors++; // Only start for same path from last notification is allowed before process
755                     }
756                     $notifpile[$notifpath] = 'process'; // Update the status in the pile
757                     break;
758                 case 'end':
759                     if ($lastpilepath != $notifpath or ($lastpiletype != 'process' and $lastpiletype != 'start')) {
760                         $numerrors++; // Only process and start for same path from last pile is allowed before end
761                     }
762                     unset($notifpile[$notifpath]); // Delete from the pile
763                     break;
764                 case 'start':
765                     if (array_key_exists($notifpath, $notifpile) or $notiflevel <= $lastpilelevel) {
766                         $numerrors++; // Only non existing in pile and with level > last pile is allowed on start
767                     }
768                     $notifpile[$notifpath] = 'start'; // Add to the pile
769                     break;
770                 default:
771                     $numerrors++; // Incorrect type of notification => error
772             }
773             // Update lastnotif
774             $lastnotif = $notif;
775         }
776         return $numerrors;
777     }
780 /*
781  * helper processor able to perform various auto-cheks based on attributes while processing
782  * the test1.xml file available in the fixtures dir. It performs these checks:
783  *    - name equal to "name" attribute of the tag (if present)
784  *    - level equal to "level" attribute of the tag (if present)
785  *    - path + tagname equal to "path" attribute of the tag (if present)
786  *    - cdata, if not empty is:
787  *        - equal to "value" attribute of the tag (if present)
788  *        - else, equal to tag name
789  *
790  * We pass the whole UnitTestCase object to the processor in order to be
791  * able to perform the tests in the straight in the process
792  */
793 class mock_auto_parser_processor extends progressive_parser_processor {
795     private $utc = null; // To store the unit test case
797     public function __construct($unit_test_case) {
798         parent::__construct();
799         $this->utc = $unit_test_case;
800     }
802     public function process_chunk($data) {
803         // Perform auto-checks based in the rules above
804         if (isset($data['tags'])) {
805             foreach ($data['tags'] as $tag) {
806                 if (isset($tag['attrs']['name'])) { // name tests
807                     $this->utc->assertEquals($tag['name'], $tag['attrs']['name']);
808                 }
809                 if (isset($tag['attrs']['level'])) { // level tests
810                     $this->utc->assertEquals($data['level'], $tag['attrs']['level']);
811                 }
812                 if (isset($tag['attrs']['path'])) { // path tests
813                     $this->utc->assertEquals(rtrim($data['path'], '/') . '/' . $tag['name'], $tag['attrs']['path']);
814                 }
815                 if (!empty($tag['cdata'])) { // cdata tests
816                     if (isset($tag['attrs']['value'])) {
817                         $this->utc->assertEquals($tag['cdata'], $tag['attrs']['value']);
818                     } else {
819                         $this->utc->assertEquals($tag['cdata'], $tag['name']);
820                     }
821                 }
822             }
823         }
824     }
827 /*
828  * helper processor that accumulates all the chunks, resturning them with the get_chunks() method
829  */
830 class mock_parser_processor extends progressive_parser_processor {
832     private $chunksarr = array(); // To accumulate the found chunks
834     public function process_chunk($data) {
835         $this->chunksarr[] = $data;
836     }
838     public function get_chunks() {
839         return $this->chunksarr;
840     }
843 /*
844  * helper processor that accumulates simplified chunks, returning them with the get_chunks() method
845  */
846 class mock_simplified_parser_processor extends simplified_parser_processor {
848     private $chunksarr = array(); // To accumulate the found chunks
849     private $startarr  = array(); // To accumulate all the notified path starts
850     private $endarr    = array(); // To accumulate all the notified path ends
851     private $allnotif  = array(); // To accumulate all the notified and dispatched events in an ordered way
853     public function dispatch_chunk($data) {
854         $this->chunksarr[] = $data;
855         $this->allnotif[] = 'process:' . $data['path'];
856     }
858     public function notify_path_start($path) {
859         $this->startarr[] = $path;
860         $this->allnotif[] = 'start:' . $path;
861     }
863     public function notify_path_end($path) {
864         $this->endarr[] = $path;
865         $this->allnotif[] = 'end:' . $path;
866     }
868     public function get_chunks() {
869         return $this->chunksarr;
870     }
872     public function get_start_notifications() {
873         return $this->startarr;
874     }
876     public function get_end_notifications() {
877         return $this->endarr;
878     }
880     public function get_all_notifications() {
881         return $this->allnotif;
882     }
885 /*
886  * helper processor that accumulates grouped chunks, returning them with the get_chunks() method
887  */
888 class mock_grouped_parser_processor extends grouped_parser_processor {
890     private $chunksarr = array(); // To accumulate the found chunks
891     private $startarr  = array(); // To accumulate all the notified path starts
892     private $endarr    = array(); // To accumulate all the notified path ends
893     private $allnotif  = array(); // To accumulate all the notified and dispatched events in an ordered way
895     public function dispatch_chunk($data) {
896         $this->chunksarr[] = $data;
897         $this->allnotif[] = 'process:' . $data['path'];
898     }
900     public function notify_path_start($path) {
901         $this->startarr[] = $path;
902         $this->allnotif[] = 'start:' . $path;
903     }
905     public function notify_path_end($path) {
906         $this->endarr[] = $path;
907         $this->allnotif[] = 'end:' . $path;
908     }
910     public function get_chunks() {
911         return $this->chunksarr;
912     }
914     public function get_start_notifications() {
915         return $this->startarr;
916     }
918     public function get_end_notifications() {
919         return $this->endarr;
920     }
922     public function get_all_notifications() {
923         return $this->allnotif;
924     }