weekly release 4.1dev
[moodle.git] / backup / util / xml / tests / writer_test.php
CommitLineData
4ac88d6d
PS
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/**
9f53b0e9
EL
18 * Test xml_writer tests.
19 *
4ac88d6d 20 * @package core_backup
9f53b0e9 21 * @category test
4ac88d6d
PS
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
9f53b0e9
EL
26namespace core_backup;
27
28use memory_xml_output;
29use xml_contenttransformer;
30use xml_output;
31use xml_writer;
32use xml_writer_exception;
33
4ac88d6d
PS
34defined('MOODLE_INTERNAL') || die();
35
36// Include all the needed stuff
37global $CFG;
38require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php');
39require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php');
40require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php');
41require_once($CFG->dirroot . '/backup/util/xml/contenttransformer/xml_contenttransformer.class.php');
42
43/**
9f53b0e9
EL
44 * Test xml_writer 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
4ac88d6d 50 */
9f53b0e9 51class writer_test extends \basic_testcase {
4ac88d6d
PS
52
53 /**
54 * test xml_writer public methods
55 */
56 function test_xml_writer_public_api() {
57 global $CFG;
58 // Instantiate xml_output
59 $xo = new memory_xml_output();
60 $this->assertTrue($xo instanceof xml_output);
61
62 // Instantiate xml_writer with null xml_output
63 try {
64 $xw = new mock_xml_writer(null);
65 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 66 } catch (\Exception $e) {
4ac88d6d
PS
67 $this->assertTrue($e instanceof xml_writer_exception);
68 $this->assertEquals($e->errorcode, 'invalid_xml_output');
69 }
70
71 // Instantiate xml_writer with wrong xml_output object
72 try {
9f53b0e9 73 $xw = new mock_xml_writer(new \stdClass());
4ac88d6d 74 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 75 } catch (\Exception $e) {
4ac88d6d
PS
76 $this->assertTrue($e instanceof xml_writer_exception);
77 $this->assertEquals($e->errorcode, 'invalid_xml_output');
78 }
79
80 // Instantiate xml_writer with wrong xml_contenttransformer object
81 try {
9f53b0e9 82 $xw = new mock_xml_writer($xo, new \stdClass());
4ac88d6d 83 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 84 } catch (\Exception $e) {
4ac88d6d
PS
85 $this->assertTrue($e instanceof xml_writer_exception);
86 $this->assertEquals($e->errorcode, 'invalid_xml_contenttransformer');
87 }
88
89 // Instantiate xml_writer and start it twice
90 $xw = new mock_xml_writer($xo);
91 $xw->start();
92 try {
93 $xw->start();
94 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 95 } catch (\Exception $e) {
4ac88d6d
PS
96 $this->assertTrue($e instanceof xml_writer_exception);
97 $this->assertEquals($e->errorcode, 'xml_writer_already_started');
98 }
99
100 // Instantiate xml_writer and stop it twice
101 $xo = new memory_xml_output();
102 $xw = new mock_xml_writer($xo);
103 $xw->start();
104 $xw->stop();
105 try {
106 $xw->stop();
107 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 108 } catch (\Exception $e) {
4ac88d6d
PS
109 $this->assertTrue($e instanceof xml_writer_exception);
110 $this->assertEquals($e->errorcode, 'xml_writer_already_stopped');
111 }
112
113 // Stop writer without starting it
114 $xo = new memory_xml_output();
115 $xw = new mock_xml_writer($xo);
116 try {
117 $xw->stop();
118 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 119 } catch (\Exception $e) {
4ac88d6d
PS
120 $this->assertTrue($e instanceof xml_writer_exception);
121 $this->assertEquals($e->errorcode, 'xml_writer_not_started');
122 }
123
124 // Start writer after stopping it
125 $xo = new memory_xml_output();
126 $xw = new mock_xml_writer($xo);
127 $xw->start();
128 $xw->stop();
129 try {
130 $xw->start();
131 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 132 } catch (\Exception $e) {
4ac88d6d
PS
133 $this->assertTrue($e instanceof xml_writer_exception);
134 $this->assertEquals($e->errorcode, 'xml_writer_already_stopped');
135 }
136
137 // Try to set prologue/schema after start
138 $xo = new memory_xml_output();
139 $xw = new mock_xml_writer($xo);
140 $xw->start();
141 try {
142 $xw->set_nonamespace_schema('http://moodle.org');
143 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 144 } catch (\Exception $e) {
4ac88d6d
PS
145 $this->assertTrue($e instanceof xml_writer_exception);
146 $this->assertEquals($e->errorcode, 'xml_writer_already_started');
147 }
148 try {
149 $xw->set_prologue('sweet prologue');
150 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 151 } catch (\Exception $e) {
4ac88d6d
PS
152 $this->assertTrue($e instanceof xml_writer_exception);
153 $this->assertEquals($e->errorcode, 'xml_writer_already_started');
154 }
155
156 // Instantiate properly with memory_xml_output, start and stop.
157 // Must get default UTF-8 prologue
158 $xo = new memory_xml_output();
159 $xw = new mock_xml_writer($xo);
160 $xw->start();
161 $xw->stop();
162 $this->assertEquals($xo->get_allcontents(), $xw->get_default_prologue());
163
164 // Instantiate, set prologue and schema, put 1 full tag and get results
165 $xo = new memory_xml_output();
166 $xw = new mock_xml_writer($xo);
167 $xw->set_prologue('CLEARLY WRONG PROLOGUE');
168 $xw->set_nonamespace_schema('http://moodle.org/littleschema');
169 $xw->start();
170 $xw->full_tag('TEST', 'Hello World!', array('id' => 1));
171 $xw->stop();
172 $result = $xo->get_allcontents();
173 // Perform various checks
174 $this->assertEquals(strpos($result, 'WRONG'), 8);
175 $this->assertEquals(strpos($result, '<TEST id="1"'), 22);
176 $this->assertEquals(strpos($result, 'xmlns:xsi='), 39);
177 $this->assertEquals(strpos($result, 'http://moodle.org/littleschema'), 128);
178 $this->assertEquals(strpos($result, 'Hello World'), 160);
179 $this->assertFalse(strpos($result, $xw->get_default_prologue()));
180
181 // Try to close one tag in wrong order
182 $xo = new memory_xml_output();
183 $xw = new mock_xml_writer($xo);
184 $xw->start();
185 $xw->begin_tag('first');
186 $xw->begin_tag('second');
187 try {
188 $xw->end_tag('first');
189 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 190 } catch (\Exception $e) {
4ac88d6d
PS
191 $this->assertTrue($e instanceof xml_writer_exception);
192 $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match');
193 }
194
195 // Try to close one tag before starting any tag
196 $xo = new memory_xml_output();
197 $xw = new mock_xml_writer($xo);
198 $xw->start();
199 try {
200 $xw->end_tag('first');
201 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 202 } catch (\Exception $e) {
4ac88d6d
PS
203 $this->assertTrue($e instanceof xml_writer_exception);
204 $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match');
205 }
206
207 // Full tag without contents (null and empty string)
208 $xo = new memory_xml_output();
209 $xw = new mock_xml_writer($xo);
210 $xw->set_prologue(''); // empty prologue for easier matching
211 $xw->start();
212 $xw->full_tag('tagname', null, array('attrname' => 'attrvalue'));
213 $xw->full_tag('tagname2', '', array('attrname' => 'attrvalue'));
214 $xw->stop();
215 $result = $xo->get_allcontents();
216 $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue"></tagname2>');
217
218
219 // Test case-folding is working
220 $xo = new memory_xml_output();
221 $xw = new mock_xml_writer($xo, null, true);
222 $xw->set_prologue(''); // empty prologue for easier matching
223 $xw->start();
224 $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attrvalue'));
225 $xw->stop();
226 $result = $xo->get_allcontents();
227 $this->assertEquals($result, '<TAGNAME ATTRNAME="attrvalue">textcontent</TAGNAME>');
228
229 // Test UTF-8 chars in tag and attribute names, attr values and contents
230 $xo = new memory_xml_output();
231 $xw = new mock_xml_writer($xo);
232 $xw->set_prologue(''); // empty prologue for easier matching
233 $xw->start();
234 $xw->full_tag('áéíóú', 'ÁÉÍÓÚ', array('àèìòù' => 'ÀÈÌÒÙ'));
235 $xw->stop();
236 $result = $xo->get_allcontents();
237 $this->assertEquals($result, '<áéíóú àèìòù="ÀÈÌÒÙ">ÁÉÍÓÚ</áéíóú>');
238
239 // Try non-safe content in attributes
240 $xo = new memory_xml_output();
241 $xw = new mock_xml_writer($xo);
242 $xw->set_prologue(''); // empty prologue for easier matching
243 $xw->start();
244 $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attr' . chr(27) . '\'"value'));
245 $xw->stop();
246 $result = $xo->get_allcontents();
247 $this->assertEquals($result, '<tagname attrname="attr\'&quot;value">textcontent</tagname>');
248
249 // Try non-safe content in text
250 $xo = new memory_xml_output();
251 $xw = new mock_xml_writer($xo);
252 $xw->set_prologue(''); // empty prologue for easier matching
253 $xw->start();
254 $xw->full_tag('tagname', "text\r\ncontent\rwith" . chr(27), array('attrname' => 'attrvalue'));
255 $xw->stop();
256 $result = $xo->get_allcontents();
257 $this->assertEquals($result, '<tagname attrname="attrvalue">text' . "\ncontent\n" . 'with</tagname>');
258
259 // Try to stop the writer without clossing all the open tags
260 $xo = new memory_xml_output();
261 $xw = new mock_xml_writer($xo);
262 $xw->start();
263 $xw->begin_tag('first');
264 try {
265 $xw->stop();
266 $this->assertTrue(false, 'xml_writer_exception expected');
9f53b0e9 267 } catch (\Exception $e) {
4ac88d6d
PS
268 $this->assertTrue($e instanceof xml_writer_exception);
269 $this->assertEquals($e->errorcode, 'xml_writer_open_tags_remaining');
270 }
271
272 // Test simple transformer
273 $xo = new memory_xml_output();
274 $xt = new mock_xml_contenttransformer();
275 $xw = new mock_xml_writer($xo, $xt);
276 $xw->set_prologue(''); // empty prologue for easier matching
277 $xw->start();
278 $xw->full_tag('tagname', null, array('attrname' => 'attrvalue'));
279 $xw->full_tag('tagname2', 'somecontent', array('attrname' => 'attrvalue'));
280 $xw->stop();
281 $result = $xo->get_allcontents();
282 $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue">testsomecontent</tagname2>');
283
284 // Build a complex XML file and test results against stored file in fixtures
285 $xo = new memory_xml_output();
286 $xw = new mock_xml_writer($xo);
287 $xw->start();
288 $xw->begin_tag('toptag', array('name' => 'toptag', 'level' => 1, 'path' => '/toptag'));
289 $xw->full_tag('secondtag', 'secondvalue', array('name' => 'secondtag', 'level' => 2, 'path' => '/toptag/secondtag', 'value' => 'secondvalue'));
290 $xw->begin_tag('thirdtag', array('name' => 'thirdtag', 'level' => 2, 'path' => '/toptag/thirdtag'));
291 $xw->full_tag('onevalue', 'onevalue', array('name' => 'onevalue', 'level' => 3, 'path' => '/toptag/thirdtag/onevalue'));
292 $xw->full_tag('onevalue', 'anothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'anothervalue'));
293 $xw->full_tag('onevalue', 'yetanothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'yetanothervalue'));
294 $xw->full_tag('twovalue', 'twovalue', array('name' => 'twovalue', 'level' => 3, 'path' => '/toptag/thirdtag/twovalue'));
295 $xw->begin_tag('forthtag', array('name' => 'forthtag', 'level' => 3, 'path' => '/toptag/thirdtag/forthtag'));
296 $xw->full_tag('innervalue', 'innervalue');
297 $xw->begin_tag('innertag');
298 $xw->begin_tag('superinnertag', array('name' => 'superinnertag', 'level' => 5));
299 $xw->full_tag('superinnervalue', 'superinnervalue', array('name' => 'superinnervalue', 'level' => 6));
300 $xw->end_tag('superinnertag');
301 $xw->end_tag('innertag');
302 $xw->end_tag('forthtag');
303 $xw->begin_tag('fifthtag', array('level' => 3));
304 $xw->begin_tag('sixthtag', array('level' => 4));
305 $xw->full_tag('seventh', 'seventh', array('level' => 5));
306 $xw->end_tag('sixthtag');
307 $xw->end_tag('fifthtag');
308 $xw->full_tag('finalvalue', 'finalvalue', array('name' => 'finalvalue', 'level' => 3, 'path' => '/toptag/thirdtag/finalvalue'));
309 $xw->full_tag('finalvalue');
310 $xw->end_tag('thirdtag');
311 $xw->end_tag('toptag');
312 $xw->stop();
313 $result = $xo->get_allcontents();
bb7898c6 314 $fcontents = file_get_contents($CFG->dirroot . '/backup/util/xml/tests/fixtures/test1.xml');
4ac88d6d
PS
315
316 // Normalise carriage return characters.
317 $fcontents = str_replace("\r\n", "\n", $fcontents);
318 $this->assertEquals(trim($result), trim($fcontents));
319 }
320}
321
322/*
323 * helper extended xml_writer class that makes some methods public for testing
324 */
325class mock_xml_writer extends xml_writer {
326 public function get_default_prologue() {
327 return parent::get_default_prologue();
328 }
329}
330
331/*
332 * helper extended xml_contenttransformer prepending "test" to all the notnull contents
333 */
334class mock_xml_contenttransformer extends xml_contenttransformer {
335 public function process($content) {
336 return is_null($content) ? null : 'test' . $content;
337 }
338}