# Perforce Defect Tracking Integration Project
# <http://www.ravenbrook.com/project/p4dti/>
#
# TEST_CHECK_XHTML.PY -- UNIT TEST FOR XHTML CHECKER
#
# Gareth Rees, Ravenbrook Limited, 2001-05-06
#
#
# 1. INTRODUCTION
#
# This Python module is a unit test case for the check_xhtml Python
# module that checks XHTML documents against the XHTML 1.0 Transitional
# specification.
#
# The tests are purely regression tests. The test cases feed documents
# to the XHTML checker, collect the output of the checker, and compare
# the actual output to the expected output.
import StringIO
import check_xhtml
import os
import string
import tempfile
import unittest
import whrandom
class case(unittest.TestCase):
def __init__(self, name, text, errors, methodName='runTest'):
self.name = name
self.text = text
self.errors = errors
unittest.TestCase.__init__(self, methodName)
def shortDescription(self):
return self.name + " (test_check_xhtml)"
def runTest(self):
error_stream = StringIO.StringIO()
h = check_xhtml.checker(error_stream = error_stream)
if os.name != 'nt' and whrandom.randint(0,1):
name = tempfile.mktemp() + '.html'
stream = open(name, 'w')
stream.write(self.text)
stream.close()
h.check(name)
os.remove(name)
else:
text_stream = StringIO.StringIO(self.text)
name = "Test"
h.check_stream(name, text_stream)
expected = string.join(map(lambda i, name=name: name + i + '\n',
self.errors), '')
found = error_stream.getvalue()
if expected != found:
self.fail("XHTML document:\n%s\n%s\n%s\nExpected errors:\n"
"%s%s\nFound errors:\n%s"
% ("-" * 70, self.text, "-" * 70, expected,
"-" * 70, found))
start = ('<?xml version="1.0" encoding="UTF-8"?>\n'
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 '
'Transitional//EN" "DTD/xhtml1-transitional.dtd">\n'
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" '
'lang="en">\n'
'<head><title>Test document</title></head>\n'
'<body>\n')
end = "\n</body></html>"
test_cases = [
('Unknown element',
start + "<foo/>" + end,
["(6) [1] <foo> element is not legal in XHTML 1.0 "
"Transitional."]),
('Element not allowed in element',
start + "<ul><p>Foo</p></ul>" + end,
["(6) [2] Element <p> appears in <ul> (not allowed)."]),
('Unknown attribute',
start + '<p foo="bar">Foo</p>' + end,
["(6) [4] Attribute 'foo' is not allowed in <p>."]),
('Id attribute but no name',
start + '<a id="foo">Foo</a>' + end,
["(6) [5] <a> element has 'id' attribute but no 'name' "
"attribute."]),
('Name attribute but no id',
start + '<a name="foo">Foo</a>' + end,
["(6) [6] <a> element has 'name' attribute but no 'id' "
"attribute."]),
('Id and name attributes differ',
start + '<a name="foo" id="bar">Foo</a>' + end,
["(6) [7] <a> element has id 'bar' but name 'foo'."]),
('Empty element contains character data',
start + '<br>foo</br>' + end,
["(6) [8] <br> element contains character data 'foo'."]),
('Element may not be empty',
start + '<ol></ol>' + end,
["(6) [9] <ol> element is empty."]),
('Unknown value for attribute',
start + '<br clear="foo"/>' + end,
["(6) [10] Attribute 'clear' for element <br> has value 'foo' "
"(must be one of [left, all, right, none])."]),
('Attribute value not a number',
start + '<ol> <li value="foo"></li> </ol>' + end,
["(6) [11] Attribute 'value' for element <li> has illegal "
"value 'foo' (not a Number)."]),
('Duplicate id',
start + '<a id="Foo" name="Foo">Foo</a>\n'
'<a id="Foo" name="Foo">Bar</a>' + end,
["(7) [12] Duplicate id 'Foo' (original on line 6)."]),
('Missing required attribute',
start + '<img height="10" width="10" src="foo"/>' + end,
["(6) [13] Attribute 'alt' is required for <img> but not "
"present."]),
('Table element has two captions',
start + '<table><caption>Foo</caption><caption>Bar</caption>'
'<tr><td>Foo</td></tr></table>' + end,
["(6) [15] Contents of <table> element [caption, caption, tr] "
"doesn't match XHTML specification (caption?, (col*|colgroup*), "
"thead?, tfoot?, (tbody+|tr+))."]),
('Table element has no rows',
start + '<table><caption>Foo</caption></table>' + end,
["(6) [15] Contents of <table> element [caption] doesn't "
"match XHTML specification (caption?, (col*|colgroup*), thead?, "
"tfoot?, (tbody+|tr+))."]),
('Table element mixes col and colgroup',
start + '<table> <col /> <colgroup> <col /> </colgroup> '
'<tbody><tr><td></td></tr></tbody> </table>' + end,
["(6) [15] Contents of <table> element [col, colgroup, tbody] "
"doesn't match XHTML specification (caption?, (col*|colgroup*), "
"thead?, tfoot?, (tbody+|tr+))."]),
('Anchor in anchor',
start + '<a href="foo"><b><a href="bar">baz</a></b></a>' + end,
["(6) [16] Element <a> appears below <a> on line 6 (not "
"allowed)."]),
('Section anchor has no id',
start + '<h1>Title</h1>\n<h2><a>Heading</a></h2>' + end,
["(7) [17] Section anchor has no 'id' attribute."]),
('Section anchor inconsistent with heading level',
start + '<h1>Title</h1>\n'
'<h2><a id="section-1" name="section-1">1. Heading</a></h2>\n'
'<h3><a id="section-2" name="section-2">Heading</a></h3>' + end,
["(8) [18] Anchor for <h3> has id 'section-2': should look "
"like 'section-1.1'."]),
('Missing section anchor',
start + '<h1>Title</h1>\n'
'<h2><a id="section-1" name="section-1">1. Heading</a></h2>\n'
'<h3><a id="section-1.1" name="section-1.1">1.1. Head</a></h3>\n'
'<h4>Example</h4>' + end,
["(9) [19] <h4> has no section anchor."]),
('Table cell with valign attribute',
start + '<table><tr><td valign="top">Foo</td></tr></table>' + end,
["(6) [20] <td> has valign attribute: better in the <tr>."]),
('Wrong reference anchor',
start + '<h1>Title</h1>\n'
'<h2><a id="section-A" name="section-A">A. References</a></h2>\n'
'<table>\n'
'<tr><td><a id="GDR-1971-01-02" name="GDR-1971-01-02">'
'GDR 1971-01-02</a></td></tr>\n</table>' + end,
["(9) [21] Reference anchor has id 'GDR-1971-01-02': should "
"start with 'ref-'."]),
('Cross reference has no target',
start + '<a href="#no-target">Foo</a>' + end,
["(6) [22] Cross-reference '#no-target' has no target."]),
('Cross reference should be link',
start + '<h1>Title</h1>\n'
'<p>[<a href="#ref-GDR-1971-01-03">GDR 1971-01-03</a>]</p>\n'
'<h2><a id="section-A" name="section-A">A. References</a></h2>\n'
'<table>\n'
'<tr valign="top"><td>[<a id="ref-GDR-1971-01-03" '
'name="ref-GDR-1971-01-03" href="http://foo.invalid/">'
'GDR 1971-01-03</a>]</td><td>Foo</td></tr>\n</table>' + end,
["(7) [23] Cross-reference '#ref-GDR-1971-01-03' to "
"references section should link to target 'http://foo.invalid/' "
"instead."]),
('Inconsistent heading sequence',
start + '<h1>Title</h1>\n'
'<h2><a id="section-2" name="section-2">2. Foo</a></h2>\n'
'<h4><a id="section-2.1.1" name="section-2.1.1">2.1.1. Bar</a>'
'</h4>' + end,
["(8) [25] <h4> follows <h2>."]),
('Missing attributes for img element',
start + '<img src="foo.gif" alt="foo" />' + end,
["(6) [26] Attribute 'height' is recommended for <img> but not "
"present.",
"(6) [26] Attribute 'width' is recommended for <img> but not "
"present."]),
('Missing title attribute for abbr element',
start + '<abbr>P4DTI</abbr>' + end,
["(6) [26] Attribute 'title' is recommended for <abbr> but not "
"present."]),
('Id reference has no target',
start + '<table><tr><td headers="no-such-header no-sirree"></td>'
'</tr></table>' + end,
["(6) [27] IDREF 'no-such-header' has no target.",
"(6) [27] IDREF 'no-sirree' has no target."]),
('Head element has two titles',
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
'"DTD/xhtml1-transitional.dtd">\n'
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" '
'lang="en">\n'
'<head><title>Title one</title><title>Title two</title></head>'
'<body /></html>',
["(4) [15] Contents of <head> element [title, title] doesn't "
"match XHTML specification (%head.misc;, ((title, %head.misc;, "
"(base, %head.misc;)?) | (base, %head.misc;, (title, "
"%head.misc;))))."]),
('HTML element has two bodies',
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
'"DTD/xhtml1-transitional.dtd">\n'
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" '
'lang="en">\n'
'<head><title>Title one</title></head> <body /> <body /> </html>',
["(4) [15] Contents of <html> element [head, body, body] "
"doesn't match XHTML specification (head, body)."]),
('Top-level element not HTML',
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
'"DTD/xhtml1-transitional.dtd">\n'
'<body></body>\n',
["(3) [3] Top-level element is <body> (should be <html>)."]),
('Mismatched closing tag',
start + '<p><b>Bold <i>Bold-italic</b> italic</i></p>\n' + end,
["(6) Mismatched closing tag (opening tag was <i> at "
"line 6)."]),
]
def tests():
suite = unittest.TestSuite()
for (name, text, errors) in test_cases:
suite.addTest(case(name, text, errors))
return suite
if __name__ == "__main__":
unittest.main(defaultTest="tests")
# A. REFERENCES
#
# [PyUnit] "PyUnit - a unit testing framework for Python"; Steve
# Purcell; <http://pyunit.sourceforge.net/>.
#
#
# B. DOCUMENT HISTORY
#
# 2001-05-06 GDR Created.
#
# 2001-12-14 GDR Added tests for wrong top-level element and mismatched
# closing tag. Check the file interface as well as the stream
# interface.
#
# 2001-12-25 GDR Added BSD license.
#
#
# C. COPYRIGHT AND LICENSE
#
# Copyright 2001 Gareth Rees. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#
# $Id: //info.ravenbrook.com/project/p4dti/version/2.1/test/test_check_xhtml.py#1 $