1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
11 # Various utilities to help testing SAXophoNit (and SAX parsers in general).
12 module saxophonit
::testing
14 import sax
::xml_reader
15 import sax
::input_source
16 import sax
::helpers
::xml_filter_impl
17 import sax
::ext
::decl_handler
18 import sax
::ext
::lexical_handler
22 # A filter that internally log events it recieves.
24 # Usually, when testing, 2 `SAXEventLogger` are used: one on which methods are
25 # manually called to simulate expected results, and another on which we attach
26 # the tested `XMLReader`. Then, we can compare logs using `diff`.
28 # Note: In order to test the `XMLReader` behaviour with ill-formed documents,
29 # fatal errors are not thrown by default.
39 # Each entry begins with the name of the event. Entries are sorted in the
40 # order they fired (the oldest first). Two event loggers have equivalent
41 # logs if and only if they received the same events in the same order and
42 # with equivalent arguments.
43 private var log
: Array[Array[String]] = new Array[Array[String]]
45 # http://xml.org/sax/properties/declaration-handler
46 private var decl_handler
: nullable DeclHandler = null
47 private var decl_handler_uri
= "http://xml.org/sax/properties/declaration-handler"
49 # http://xml.org/sax/properties/lexical-handler
50 private var lexical_handler
: nullable LexicalHandler = null
51 private var lexical_handler_uri
= "http://xml.org/sax/properties/declaration-handler"
54 # Constants for diff formatting.
56 # Treminal’s default formatting.
57 var term_default
: String = (new TermCharFormat).to_s
59 # Formatting for insertions.
60 var term_insertion
: String =
61 (new TermCharFormat).green_fg
.normal_weight
.to_s
63 # Formatting for emphased insertions.
64 var term_insertion_emphasis
: String =
65 (new TermCharFormat).green_fg
.bold
.to_s
67 # Formatting for deletions.
68 var term_deletion
: String =
69 (new TermCharFormat).red_fg
.normal_weight
.to_s
71 # Formatting for emphased deletions
72 var term_deletion_emphasis
: String =
73 (new TermCharFormat).red_fg
.bold
.to_s
76 # Clear the internal log.
81 # Show the differences between the internal logs of `self` and `expected`.
83 # If there is no differences, return an empty string. Else, return a string
84 # designed to be printed in the terminal. In this case, `=` means “in both”,
85 # `<` means “in `self`” and `>` means “in `expected`”.
86 fun diff
(expected
: SAXEventLogger): Text do
87 var buf
= new FlatBuffer
88 var sub_diff
: Array[Int]
94 if log
.length
< expected
.log
.length
then
97 max
= expected
.log
.length
98 else if expected
.log
.length
< log
.length
then
100 min
= expected
.log
.length
109 sub_diff
= diff_entry
(log
[i
], expected
.log
[i
])
110 if sub_diff
.length
> 0 then
112 diff_append_matches
(buf
, log
, [0..i
[)
115 diff_append_deletion
(buf
, log
, i
, sub_diff
)
116 diff_append_insertion
(buf
, expected
.log
, i
, sub_diff
)
117 else if not equal
then
118 diff_append_matches
(buf
, log
, [i
..i
])
122 if log
.length
< expected
.log
.length
then
124 diff_append_insertion
(buf
, expected
.log
, i
,
125 [0..(expected
.log
[i
].length
)[)
130 diff_append_deletion
(buf
, log
, i
, [0..(log
[i
].length
)[)
137 # Return the list of positions where `actual` and `expected` mismatch.
139 # Indexes are in ascending order.
140 private fun diff_entry
(actual
: Array[String], expected
: Array[String]):
142 var result
= new Array[Int]
147 if actual
.length
< expected
.length
then
149 max
= expected
.length
150 else if expected
.length
< actual
.length
then
151 min
= expected
.length
159 if expected
[i
] != actual
[i
] then
164 result
.insert_all
([i
..max
[, result
.length
)
168 # Append matches to the diff.
172 # * `buf`: buffer for the diff.
173 # * `log`: original log.
174 # * `range`: range to append to the diff.
175 private fun diff_append_matches
(buf
: Buffer, log
: Array[Array[String]],
176 range
: Range[Int]) do
178 buf
.append
("= {i}|{log[i].join("; ")}\n")
182 # Append a deletion to the diff.
186 # * `buf`: buffer for the diff.
187 # * `log`: log that contains the deleted entry.
188 # * `entry_index`: index of the deleted entry in `log`.
189 # * `sorted_mismatches`: sorted list of indexes of the items to emphasize
190 # in the specified entry.
191 private fun diff_append_deletion
(buf
: Buffer, log
: Array[Array[String]],
192 entry_index
: Int, sorted_mismatches
: Collection[Int]) do
193 var sub_buf
= new FlatBuffer
195 buf
.append
(term_deletion
)
196 buf
.append
("< {entry_index}|")
197 diff_append_mismatch_entry
(buf
, log
[entry_index
], sorted_mismatches
,
198 term_deletion
, term_deletion_emphasis
)
199 buf
.append
(term_default
)
203 # Append a insertion to the diff.
207 # * `buf`: buffer for the diff.
208 # * `log`: log that contains the inserted entry.
209 # * `entry_index`: index of the inserted entry in `log`.
210 # * `sorted_mismatches`: sorted list of indexes of the items to emphasize
211 # in the specified entry.
212 private fun diff_append_insertion
(buf
: Buffer, log
: Array[Array[String]],
213 entry_index
: Int, sorted_mismatches
: Collection[Int]) do
214 buf
.append
(term_insertion
)
215 buf
.append
("> {entry_index}|")
216 diff_append_mismatch_entry
(buf
, log
[entry_index
], sorted_mismatches
,
217 term_insertion
, term_insertion_emphasis
)
218 buf
.append
(term_default
)
222 # Show an entry of a mismatch (without the margin).
224 # Append the string designed to be printed in the terminal to the
229 # * `buf`: output buffer.
230 # * `entry`: entry to format.
231 # * `sorted_mismatches`: sorted list of indexes of the items to emphasize.
232 # * `term_normal`: terminal control code to re-apply the formatting that was
233 # in force prior calling this method.
234 # * `term_emphasis`: terminal control code to apply to items listed in
235 # `sorted_mismatches`.
236 private fun diff_append_mismatch_entry
(buf
: Buffer, entry
: Array[String],
237 sorted_mismatches
: Collection[Int], term_normal
: String,
238 term_emphasis
: String) do
240 var j
= sorted_mismatches
.iterator
241 var length
= entry
.length
244 while j
.is_ok
and j
.item
< i
do
247 if j
.is_ok
and j
.item
== i
then
248 buf
.append
(term_emphasis
)
250 buf
.append
(term_normal
)
261 ############################################################################
264 redef fun property
(name
: String): nullable Object do
265 assert sax_recognized
: parent
!= null else
266 sys
.stderr
.write
("Property: {name}\n")
268 if decl_handler_uri
== name
then
269 assert property_readable
: property_readable
(name
) else
270 sys
.stderr
.write
("Property: {name}\n")
273 else if lexical_handler_uri
== name
then
274 assert property_readable
: property_readable
(name
) else
275 sys
.stderr
.write
("Property: {name}\n")
277 return lexical_handler
279 return parent
.property
(name
)
283 redef fun property
=(name
: String, value
: nullable Object) do
284 assert sax_recognized
: parent
!= null else
285 sys
.stderr
.write
("Property: {name}\n")
287 if decl_handler_uri
== name
then
288 assert property_readable
: property_writable
(name
) else
289 sys
.stderr
.write
("Property: {name}\n")
291 decl_handler
= value
.as(nullable DeclHandler)
292 else if lexical_handler_uri
== name
then
293 assert property_readable
: property_writable
(name
) else
294 sys
.stderr
.write
("Property: {name}\n")
296 lexical_handler
= value
.as(nullable LexicalHandler)
298 parent
.property
(name
) = value
302 redef fun parse
(input
: InputSource) do
303 assert parent_is_not_null
: parent
!= 0 else
304 sys
.stderr
.write
("No parent for filter.")
306 if parent
.feature_writable
(decl_handler_uri
) then
307 parent
.property
(decl_handler_uri
) = self
309 if parent
.feature_writable
(lexical_handler_uri
) then
310 parent
.property
(lexical_handler_uri
) = self
316 ############################################################################
319 redef fun resolve_entity
(public_id
: nullable String,
320 system_id
: nullable String):
321 nullable InputSource do
322 log
.push
(["resolve_entity",
323 public_id
or else "^NULL",
324 system_id
or else "^NULL"])
329 ############################################################################
332 redef fun notation_decl
(name
: String, public_id
: String,
333 system_id
: String) do
334 log
.push
(["notation_decl", name
, public_id
, system_id
])
338 redef fun unparsed_entity_decl
(name
: String, public_id
: String,
339 system_id
: String) do
340 log
.push
(["unparsed_entity_decl", name
, public_id
, system_id
])
345 ############################################################################
348 redef fun document_locator
=(locator
: SAXLocator) do
349 log
.push
(["document_locator=",
350 locator
.public_id
or else "^NULL",
351 locator
.system_id
or else "^NULL",
352 locator
.line_number
.to_s
,
353 locator
.column_number
.to_s
])
357 redef fun start_document
do
358 log
.push
(["start_document"])
362 redef fun end_document
do
363 log
.push
(["end_document"])
367 redef fun start_prefix_mapping
(prefix
: String, uri
: String) do
368 log
.push
(["start_prefix_mapping", prefix
, uri
])
372 redef fun end_prefix_mapping
(prefix
: String) do
373 log
.push
(["end_prefix_mapping", prefix
])
377 redef fun start_element
(uri
: String, local_name
: String, qname
: String,
379 var entry
= new Array[String]
381 var length
= atts
.length
383 entry
.push
("start_element")
385 entry
.push
(local_name
)
388 entry
.push
(atts
.uri
(i
) or else "^NULL")
389 entry
.push
(atts
.local_name
(i
) or else "^NULL")
390 entry
.push
(atts
.qname
(i
) or else "^NULL")
391 entry
.push
(atts
.type_of
(i
) or else "^NULL")
392 entry
.push
(atts
.value_of
(i
) or else "^NULL")
399 redef fun end_element
(uri
: String, local_name
: String, qname
: String) do
400 log
.push
(["end_element", uri
, local_name
, qname
])
404 redef fun characters
(str
: String) do
405 log
.push
(["characters", str
])
409 redef fun ignorable_whitespace
(str
: String) do
410 log
.push
(["ignorable_witespace", str
])
414 redef fun processing_instruction
(target
: String, data
: nullable String) do
415 log
.push
(["processing_instruction", target
, data
or else "^NULL"])
419 redef fun skipped_entity
(name
: String) do
420 log
.push
(["skipped_entity", name
])
425 ############################################################################
428 redef fun warning
(exception
: SAXParseException) do
429 log
.push
(["warning", exception
.full_message
])
433 redef fun error
(exception
: SAXParseException) do
434 log
.push
(["error", exception
.full_message
])
438 redef fun fatal_error
(exception
: SAXParseException) do
439 log
.push
(["fatal_error", exception
.full_message
])
440 if error_handler
!= null then
441 error_handler
.fatal_error
(exception
)
446 ############################################################################
449 redef fun element_decl
(name
: String, model
: String) do
450 log
.push
(["element_decl", name
, model
])
451 if decl_handler
!= null then
452 decl_handler
.element_decl
(name
, model
)
456 redef fun attribute_decl
(element_name
: String,
457 attribute_name
: String,
458 attribute_type
: String,
459 mode
: nullable String,
460 value
: nullable String) do
461 log
.push
(["attribute_decl",
465 mode
or else "^NULL",
466 value
or else "^NULL"])
467 if decl_handler
!= null then
468 decl_handler
.attribute_decl
(element_name
, attribute_name
,
469 attribute_type
, mode
, value
)
473 redef fun internal_entity_decl
(name
: String, value
: String) do
474 log
.push
(["internal_entity_decl", name
, value
])
475 if decl_handler
!= null then
476 decl_handler
.internal_entity_decl
(name
, value
)
480 redef fun external_entity_decl
(name
: String, value
: String) do
481 log
.push
(["external_entity_decl", name
, value
])
482 if decl_handler
!= null then
483 decl_handler
.external_entity_decl
(name
, value
)
488 ############################################################################
491 redef fun start_dtd
(name
: String, public_id
: nullable String,
492 system_id
: nullable String) do
493 log
.push
(["start_dtd", name
,
494 public_id
or else "^NULL",
495 system_id
or else "^NULL"])
496 if lexical_handler
!= null then
497 lexical_handler
.start_dtd
(name
, public_id
, system_id
)
502 log
.push
(["end_dtd"])
503 if lexical_handler
!= null then
504 lexical_handler
.end_dtd
508 redef fun start_entity
(name
: String) do
509 log
.push
(["start_entity", name
])
510 if lexical_handler
!= null then
511 lexical_handler
.start_entity
(name
)
515 redef fun end_entity
(name
: String) do
516 log
.push
(["end_entity", name
])
517 if lexical_handler
!= null then
518 lexical_handler
.end_entity
(name
)
522 redef fun start_cdata
do
523 log
.push
(["start_cdata"])
524 if lexical_handler
!= null then
525 lexical_handler
.start_cdata
529 redef fun end_cdata
do
530 log
.push
(["end_cdata"])
531 if lexical_handler
!= null then
532 lexical_handler
.end_cdata
536 redef fun comment
(str
: String) do
537 log
.push
(["comment", str
])
538 if lexical_handler
!= null then
539 lexical_handler
.comment
(str
)
545 # Base class for test suites on a SAX reader.
546 abstract class SAXTestSuite
549 # Logger of the expected event sequence.
550 var expected
: SAXEventLogger = new SAXEventLogger
552 # Logger of the actual event sequence.
553 var actual
: SAXEventLogger = new SAXEventLogger
555 # The tested SAX reader.
556 var reader
: XMLReader is noinit
558 private var init_done
: Bool = false
560 redef fun before_test
do
562 if not init_done
then
563 reader
= create_reader
564 actual
.parent
= reader
567 reader
.feature
("http://xml.org/sax/features/namespaces") = true
568 reader
.feature
("http://xml.org/sax/features/namespace-prefixes") = false
573 # Create a new SAX reader.
575 # This method is called at initialization to set `reader`.
576 fun create_reader
: XMLReader is abstract
578 # Assert logs are equal.
580 var diff
= actual
.diff
(expected
)
582 assert equals
: diff
.length
<= 0 else
583 sys
.stderr
.write
("\n")
584 sys
.stderr
.write
("# {actual.term_deletion}< Actual{actual.term_default}\n")
585 sys
.stderr
.write
("# {actual.term_insertion}> Expected{actual.term_default}\n")
586 sys
.stderr
.write
(diff
)
587 sys
.stderr
.write
("\n")
591 # Make the reader parse the specified string
592 fun parse_string
(str
: String) do
593 actual
.parse
(new InputSource.with_stream
(new StringIStream(str
)))