Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

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