예제 #1
0
    def setUp(self):
        self.ont = Ontology('test_data/ontology.owl')
        self.doc = Documenter(self.ont)

        self.longMessage = True
예제 #2
0
class TestLabelMap(unittest.TestCase):
    """
    Tests the LabelMap class.
    """
    def setUp(self):
        self.ont = Ontology('test_data/ontology.owl')
        self.lm = LabelMap(self.ont)

    def test_makeMap(self):
        """
        Tests that making a label map from an ontology and its imports closure
        works as expected.
        """
        # Define all IRI/label pairings in the main test ontology.
        testpairs = [
            {
                'iri': 'http://purl.obolibrary.org/obo/OBTO_0001',
                'label': 'test object property 1'
            },
            {
                'iri': 'http://purl.obolibrary.org/obo/OBTO_0020',
                'label': 'test data property 1'
            },
            {
                'iri': 'http://purl.obolibrary.org/obo/OBTO_0030',
                'label': 'annotation property 1'
            },
            {
                'iri': 'http://purl.obolibrary.org/obo/OBTO_0010',
                'label': 'test class 1'
            },
            {
                'iri': 'http://purl.obolibrary.org/obo/OBTO_0011',
                'label': 'test class 2'
            },
            {
                'iri': 'http://purl.obolibrary.org/obo/OBTO_0012',
                'label': 'test class 3'
            },
            {
                'iri': 'http://purl.obolibrary.org/obo/OBTO_8000',
                'label': 'test individual 1'
            },
            {
                'iri': 'http://purl.obolibrary.org/obo/OBTO_8001',
                'label': 'test individual 2'
            }
        ]

        # Check all labels in the main, directly loaded ontology.
        for testpair in testpairs:
            self.assertEqual(
                testpair['iri'], str(self.lm.lookupIRI(testpair['label'])),
            )

        # Check all labels in the imported OWL file.
        self.assertEqual(
            str(self.lm.lookupIRI('imported test class 1')),
            'http://purl.obolibrary.org/obo/OBITO_0001'
        )

    def test_add_lookupIRI(self):
        """
        Tests both add() and lookupIRI().
        """
        # Test basic lookup without a root IRI string.
        self.assertEqual(
            'http://purl.obolibrary.org/obo/OBTO_0001',
            str(self.lm.lookupIRI('test object property 1'))
        )

        # Test lookup of an invalid label.
        with self.assertRaisesRegexp(
            InvalidLabelError,
            'The provided label, "invalid label", does not match'
        ):
            self.lm.lookupIRI('invalid label')

        #
        # Test using a root IRI string to check the value of a retrieved IRI.
        #
        # First use a matching root IRI string.
        self.assertEqual(
            'http://purl.obolibrary.org/obo/OBTO_0001',
            str(self.lm.lookupIRI(
                'test object property 1', 'http://purl.obolibrary.org/obo/OBTO_')
            )
        )

        # Then use an incorrect root IRI string.
        with self.assertRaisesRegexp(
            InvalidLabelError, 'The provided IRI root, .*, does not match'
        ):
            self.lm.lookupIRI(
                'test object property 1',
                'http://purl.obolibrary.org/obo/OBLAH_'
            )

        #
        # Test handling of ambiguous labels.
        #
        # Add an ambiguous label.  This should raise a warning.
        with LogCapture() as lc:
            self.lm.add(
                'test class 1',
                IRI.create('http://purl.obolibrary.org/obo/OBITO_0200')
            )

        # Don't use LogCapture's check() method here because it doesn't support
        # substring matching.
        self.assertTrue(
            'The label "test class 1" is used for more than one IRI in the ontology' in str(lc)
        )

        # Attempt to dereference an ambiguous label.  This should raise an
        # exception.
        with self.assertRaisesRegexp(
            AmbiguousLabelError, 'Attempted to use an ambiguous label'
        ):
            self.lm.lookupIRI('test class 1')

        # Attempt to dereference an ambiguous label with an IRI root string.
        # This should work without error.
        self.assertEqual(
            str(self.lm.lookupIRI('test class 1', 'http://purl.obolibrary.org/obo/OBTO_')),
            'http://purl.obolibrary.org/obo/OBTO_0010'
        )

        # Attempt to dereference an ambiguous label with an IRI root string
        # that is non-unique.  This should raise an exception.
        with self.assertRaisesRegexp(
            AmbiguousLabelError, 'Attempted to use an ambiguous label'
        ):
            self.lm.lookupIRI(
                'test class 1', 'http://purl.obolibrary.org/obo/'
            )

        # Attempt to dereference an ambiguous label with an IRI root string
        # that is invalid.  This should raise an exception.
        with self.assertRaisesRegexp(
            InvalidLabelError, 'The IRI root <.*> did not match any entities'
        ):
            self.lm.lookupIRI(
                'test class 1', 'http://invalid.root/iri/'
            )

    def test_update_notification(self):
        """
        Verifies that LabelMap correctly follows changes to its source
        ontology.
        """
        # The new label should not yet exist.
        with self.assertRaisesRegexp(
            InvalidLabelError, 'The provided label, ".*", does not match'
        ):
            self.lm.lookupIRI('new test class')

        # Create a new class and give it a label.
        newclass = self.ont.createNewClass('OBTO:0013')
        newclass.addLabel('new test class')

        # The LabelMap should automatically have the new label.
        self.assertEqual(
            'http://purl.obolibrary.org/obo/OBTO_0013',
            str(self.lm.lookupIRI('new test class'))
        )
예제 #3
0
class Test_Documenter(unittest.TestCase):
    """
    Tests the Documenter class.
    """
    def setUp(self):
        self.ont = Ontology('test_data/ontology.owl')
        self.doc = Documenter(self.ont)

        self.longMessage = True

    def test_parseDocSpec(self):
        """
        This method tests the results of _parseDocSpec() by converting the
        returned Document objects to a string representation and checking the
        string.
        """
        # Define test values and results for valid specifications.
        testvals = [
            {
                'docspec': '',
                'expected': ''
            },
            {
                'docspec': ' \n',
                'expected': ''
            },
            # Only a title.
            {
                'docspec': '# Document title',
                'expected': '# Document title'
            },
            # Several headings and other non-entities text.
            {
                'docspec':
                """
# Document title

---
## Classes

 - not an entities entry
""",
                'expected':
                """
# Document title

---
## Classes

 - not an entities entry
"""
            },
            # Multiple non-empty document sections.
            {
                'docspec':
                """
## Properties
- ID: OBTO:'test data property 1'

## Classes
- ID: OBTO:0010
- ID: OBTO:0011
""",
                'expected':
                """
## Properties
Entities:
    IRI: http://purl.obolibrary.org/obo/OBTO_0020
    OBO ID: OBTO:0020
    Label: test data property 1

## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBTO_0010
    OBO ID: OBTO:0010
    Label: test class 1

    IRI: http://purl.obolibrary.org/obo/OBTO_0011
    OBO ID: OBTO:0011
    Label: test class 2

"""
            },
            # Multi-level nested child specifications.
            {
                'docspec':
                """
## Classes
- ID: OBITO:0001
  children:
      - ID: OBTO:0010
        children:
            - ID: OBTO:0012
      - ID: OBTO:0011
- ID: OBTO:0090
""",
                'expected':
                """
## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1
    Children:
        IRI: http://purl.obolibrary.org/obo/OBTO_0010
        OBO ID: OBTO:0010
        Label: test class 1
        Children:
            IRI: http://purl.obolibrary.org/obo/OBTO_0012
            OBO ID: OBTO:0012
            Label: test class 3

        IRI: http://purl.obolibrary.org/obo/OBTO_0011
        OBO ID: OBTO:0011
        Label: test class 2

    IRI: http://purl.obolibrary.org/obo/OBTO_0090
    OBO ID: OBTO:0090
    Label: 

"""
            },
            # Values of "descendants" that do not result in entity retrieval.
            {
                'docspec':
                """
## Classes
- ID: OBITO:0001
  descendants: none
""",
                'expected':
                """
## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1

"""
            },
            {
                'docspec':
                """
## Classes
- ID: OBITO:0001
  descendants: 0
""",
                'expected':
                """
## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1

"""
            },
            # Single-level automatic descendants retrieval.
            {
                'docspec':
                """
## Classes
- ID: OBITO:0001
  descendants: 1
""",
                'expected':
                """
## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1
    Children:
        IRI: http://purl.obolibrary.org/obo/OBTO_0010
        OBO ID: OBTO:0010
        Label: test class 1

        IRI: http://purl.obolibrary.org/obo/OBTO_0011
        OBO ID: OBTO:0011
        Label: test class 2

        IRI: http://purl.obolibrary.org/obo/OBTO_0012
        OBO ID: OBTO:0012
        Label: test class 3

"""
            },
            # Multi-level automatic descendants retrieval.
            {
                'docspec':
                """
## Classes
- ID: OBITO:0001
  descendants: all
""",
                'expected':
                """
## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1
    Children:
        IRI: http://purl.obolibrary.org/obo/OBTO_0010
        OBO ID: OBTO:0010
        Label: test class 1
        Children:
            IRI: http://purl.obolibrary.org/obo/OBTO_0091
            OBO ID: OBTO:0091
            Label: 

        IRI: http://purl.obolibrary.org/obo/OBTO_0011
        OBO ID: OBTO:0011
        Label: test class 2

        IRI: http://purl.obolibrary.org/obo/OBTO_0012
        OBO ID: OBTO:0012
        Label: test class 3

"""
            },
            # Multi-level automatic descendants retrieval with filtering.
            {
                'docspec':
                """
## Classes
- ID: OBITO:0001
  descendants: all
  filter_by_label: " 1"
""",
                'expected':
                """
## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1
    Children:
        IRI: http://purl.obolibrary.org/obo/OBTO_0010
        OBO ID: OBTO:0010
        Label: test class 1

"""
            },
            # Multi-level automatic descendants retrieval with both label and
            # IRI filtering.
            {
                'docspec':
                """
## Classes
- ID: OBITO:0001
  descendants: all
  filter_by_label: " 1"
  filter_by_IRI: "OBITO_"
""",
                'expected':
                """
## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1

"""
            },
            # Multiple mixed Markdown and entities sections.
            {
                'docspec':
                """
# Document title.

Markdown paragraph.

## Classes
- ID: OBITO:0001

Another Markdown *paragraph*.

* a
* list

## Properties
- ID: OBTO:'test data property 1'
A final Markdown paragraph.
""",
                'expected':
                """
# Document title.

Markdown paragraph.

## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1

Another Markdown *paragraph*.

* a
* list

## Properties
Entities:
    IRI: http://purl.obolibrary.org/obo/OBTO_0020
    OBO ID: OBTO:0020
    Label: test data property 1

A final Markdown paragraph.
"""
            },
            # Including literal '^- .*' in a Markdown section.
            {
                'docspec':
                r"""
 - text
-- text
\- text
\\- text
\\\- text

## Classes
- ID: OBITO:0001
""",
                'expected':
                r"""
 - text
-- text
- text
\- text
\\- text

## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1

"""
            },
            # Test UTF-8 unicode support.
            {
                'docspec':
                """
# Document title

## Greek alpha: \xce\xb1
Other text.
""",
                'expected':
                u"""
# Document title

## Greek alpha: \u03b1
Other text.
"""
            }
        ]

        # Add some extra classes to the test ontology hierarchy.  After the
        # additions, the class structure should now be as follows:
        #
        # OBTO:0090
        # OBITO:0001
        # |--- OBTO:0010
        # |    |--- OBTO:0091
        # |--- OBTO:0011
        # |--- OBTO:0012
        newclass = self.ont.createNewClass('OBTO:0090')
        newclass = self.ont.createNewClass('OBTO:0091')
        newclass.addSuperclass('OBTO:0010')

        for testval in testvals:
            result = unicode(self.doc._parseDocSpec(testval['docspec']))
            #print result
            # When testing the result, remove the leading newline from the
            # expected results string.
            self.assertEqual(testval['expected'],
                             result,
                             msg='Input specification:"""{0}"""'.format(
                                 testval['docspec']))

        # Create a cycle in the descendant relationships by making OBITO:0001 a
        # subclass of OBTO:0091, and make a polyhierarchy by making OBTO:0010 a
        # subclass of OBTO:0011.  The class structure should look like this:
        #
        # OBTO:0090
        # OBITO:0001
        # |--- OBTO:0010
        # |    |--- OBTO:0091
        # |         |--- OBITO:0001
        # |--- OBTO:0011
        # |    |--- OBTO:0010
        # |         |--- OBTO:0091
        # |              |--- OBITO:0001
        # |--- OBTO:0012
        newclass.addSubclass('OBITO:0001')
        ent = self.ont.getExistingClass('OBTO:0011')
        ent.addSubclass('OBTO:0010')

        # Run new multi-level descendants tests to make sure the cycle doesn't
        # "trap" the algorithms and that the polyhierarchy is handled
        # correctly.
        testvals = [
            # Multi-level automatic descendants retrieval.
            {
                'docspec':
                """
## Classes
- ID: OBITO:0001
  descendants: all
""",
                'expected':
                """
## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1
    Children:
        IRI: http://purl.obolibrary.org/obo/OBTO_0010
        OBO ID: OBTO:0010
        Label: test class 1
        Children:
            IRI: http://purl.obolibrary.org/obo/OBTO_0091
            OBO ID: OBTO:0091
            Label: 
            Children:
                IRI: http://purl.obolibrary.org/obo/OBITO_0001
                OBO ID: OBITO:0001
                Label: imported test class 1

        IRI: http://purl.obolibrary.org/obo/OBTO_0011
        OBO ID: OBTO:0011
        Label: test class 2
        Children:
            IRI: http://purl.obolibrary.org/obo/OBTO_0010
            OBO ID: OBTO:0010
            Label: test class 1
            Children:
                IRI: http://purl.obolibrary.org/obo/OBTO_0091
                OBO ID: OBTO:0091
                Label: 
                Children:
                    IRI: http://purl.obolibrary.org/obo/OBITO_0001
                    OBO ID: OBITO:0001
                    Label: imported test class 1

        IRI: http://purl.obolibrary.org/obo/OBTO_0012
        OBO ID: OBTO:0012
        Label: test class 3

"""
            },
            # Multi-level automatic descendants retrieval with filtering.  This
            # also tests duplicate deletion.
            {
                'docspec':
                """
## Classes
- ID: OBITO:0001
  descendants: all
  filter_by_label: " 1"
""",
                'expected':
                """
## Classes
Entities:
    IRI: http://purl.obolibrary.org/obo/OBITO_0001
    OBO ID: OBITO:0001
    Label: imported test class 1
    Children:
        IRI: http://purl.obolibrary.org/obo/OBTO_0010
        OBO ID: OBTO:0010
        Label: test class 1
        Children:
            IRI: http://purl.obolibrary.org/obo/OBITO_0001
            OBO ID: OBITO:0001
            Label: imported test class 1

"""
            }
        ]

        for testval in testvals:
            result = unicode(self.doc._parseDocSpec(testval['docspec']))
            #print result
            self.assertEqual(testval['expected'], result)

        # Test error conditions to make sure they are handled correctly.
        testvals = [
            # Missing entity ID.
            {
                'docspec': """
## Classes
- descendants: 1
""",
                'errorstr': 'No entity ID was provided'
            },
            # Invalid ID.
            {
                'docspec': """
## Classes
- ID: OBTO:INVALID
""",
                'errorstr': 'No entity with the ID ".*" could be found'
            },
            # Invalid "descendants" values.
            {
                'docspec': """
## Classes
- ID: OBTO:0010
  descendants: invalid
""",
                'errorstr': 'Invalid value for "descendants" directive'
            },
            {
                'docspec': """
## Classes
- ID: OBTO:0010
  descendants: -1
""",
                'errorstr': 'Invalid value for "descendants" directive'
            },
        ]

        for testval in testvals:
            with self.assertRaisesRegexp(DocumentationSpecificationError,
                                         testval['errorstr']):
                self.doc._parseDocSpec(testval['docspec'])
예제 #4
0
 def setUp(self):
     ont = Ontology('test_data/ontology.owl')
     self.rman = ReasonerManager(ont)
예제 #5
0
class _TestOntologyEntity:
    """
    Defines tests that apply to all ontology entities.  This class should not
    be instantiated directly; only its subclasses that target specific ontology
    entities should be run.  To help reinforce this, _TestOntologyEntity does
    not inherit from unittest.TestCase.  All subclasses of _TestOntologyEntity
    should inherit from unittest.TestCase and treat _TestOntologyEntity as a
    sort of "mixin" class that provides standard testing routines.
    """
    # Annotation IRIs.
    LABEL_IRI = IRI.create('http://www.w3.org/2000/01/rdf-schema#label')
    COMMENT_IRI = IRI.create('http://www.w3.org/2000/01/rdf-schema#comment')

    def setUp(self):
        self.test_ont = Ontology('test_data/ontology.owl')
        self.owlont = self.test_ont.getOWLOntology()

        # A test entity instance, OWL API object, and IRI should be provided by
        # child classes by calling _setEntityObject().
        self.t_ent = None
        self.t_owlapiobj = None
        self.t_entIRI = None

    def _setEntityObject(self, entityobj):
        self.t_ent = entityobj
        self.t_owlapiobj = self.t_ent.getOWLAPIObj()
        self.t_entIRI = self.t_ent.getIRI()

    def _checkAnnotation(self, annot_propIRI, valuestrs):
        """
        Checks that the test entity is the subject of an annotation axiom with
        the specified property and value(s).
        """
        if isinstance(valuestrs, basestring):
            strlist = [valuestrs]
        else:
            strlist = valuestrs

        # Check that the entity has the required annotation and that the text
        # value is correct.
        annotvals = self.t_ent.getAnnotationValues(annot_propIRI)
        self.assertEqual(sorted(strlist), sorted(annotvals))

    def test_addDefinition(self):
        defstr = 'A new definition.'

        self.t_ent.addDefinition(defstr)

        # Test that the definition annotation exists and has the correct value.
        self._checkAnnotation(self.t_ent.DEFINITION_IRI, defstr)

    def test_getDefinitions(self):
        # Test the case of no definitions.
        self.assertEqual(0, len(self.t_ent.getDefinitions()))

        # Test a single definition.
        self.t_ent.addDefinition('Definition 1.')
        defvals = self.t_ent.getDefinitions()
        self.assertEqual(['Definition 1.'], defvals)

        # Test multiple definitions.
        self.t_ent.addDefinition('Definition 2.')
        defvals = self.t_ent.getDefinitions()
        self.assertEqual(['Definition 1.', 'Definition 2.'], sorted(defvals))

    def test_addLabel(self):
        labelstr = 'term label!'

        self.t_ent.addLabel(labelstr)

        # Test that the label annotation exists and has the correct value.
        self._checkAnnotation(self.LABEL_IRI, labelstr)

        # Check a label string enclosed in single quotes.
        self.t_ent.addLabel("'another label'")
        self._checkAnnotation(self.LABEL_IRI, ['another label', labelstr])

    def test_getLabels(self):
        # Test the case of no labels.
        self.assertEqual(0, len(self.t_ent.getLabels()))

        # Test a single label.
        self.t_ent.addLabel('Label 1')
        labelvals = self.t_ent.getLabels()
        self.assertEqual(['Label 1'], labelvals)

        # Test multiple labels.
        self.t_ent.addLabel('Label 2')
        labelvals = self.t_ent.getLabels()
        self.assertEqual(['Label 1', 'Label 2'], sorted(labelvals))

    def test_addComment(self):
        commentstr = 'A useful comment.'

        self.t_ent.addComment(commentstr)

        # Test that the comment annotation exists and has the correct value.
        self._checkAnnotation(self.COMMENT_IRI, commentstr)

    def test_getComments(self):
        # Test the case of no comments.
        self.assertEqual(0, len(self.t_ent.getComments()))

        # Test a single comment.
        self.t_ent.addComment('Comment 1.')
        vals = self.t_ent.getComments()
        self.assertEqual(['Comment 1.'], vals)

        # Test multiple comments.
        self.t_ent.addComment('Comment 2.')
        vals = self.t_ent.getComments()
        self.assertEqual(['Comment 1.', 'Comment 2.'], sorted(vals))

    def test_addAnnotation(self):
        annotprop_iri = IRI.create('http://purl.obolibrary.org/obo/OBTO_0030')
        annot_txt = 'Test annotation text.'

        self.t_ent.addAnnotation(annotprop_iri, annot_txt)

        self._checkAnnotation(annotprop_iri, annot_txt)

    def test_getAnnotationValues(self):
        # Test the case of no annotations.
        self.assertEqual(0,
                         len(self.t_ent.getAnnotationValues(self.LABEL_IRI)))

        # Test a single annotation value.
        self.t_ent.addLabel('A label!!')
        annotvals = self.t_ent.getAnnotationValues(self.LABEL_IRI)
        self.assertEqual(['A label!!'], annotvals)

        # Test multiple annotation values.
        commentvals = ['Comment 1', 'Comment 2']
        for commentval in commentvals:
            self.t_ent.addComment(commentval)

        annotvals = self.t_ent.getAnnotationValues(self.COMMENT_IRI)
        self.assertEqual(sorted(commentvals), sorted(annotvals))

    def test_hash(self):
        """
        Tests that entities will behave as expected when they are hashed.  Two
        entity instances that point to the same ontology entity should produce
        equal hashes.
        """
        # Get another instance of the same entity.
        entcpy = self.test_ont.getExistingEntity(self.t_entIRI)

        # Verify that the instances are not the same.
        self.assertFalse(self.t_entIRI is entcpy)

        # Check the hash values.
        self.assertEqual(hash(self.t_ent), hash(entcpy))
        self.assertEqual(hash(self.t_owlapiobj), hash(entcpy.getOWLAPIObj()))

        # Check the equality operator.
        self.assertTrue(self.t_ent == entcpy)

        # Check the inequality operator.
        self.assertFalse(self.t_ent != entcpy)
예제 #6
0
 def setUp(self):
     self.ont = Ontology('test_data/ontology.owl')
     self.doc = Documenter(self.ont)
예제 #7
0
class Test_HTMLWriter(unittest.TestCase):
    """
    Tests the HTMLWriter class.
    """
    def setUp(self):
        self.ont = Ontology('test_data/ontology.owl')
        self.doc = Documenter(self.ont)

        self.hw = HTMLWriter()

    def test_getUniqueValue(self):
        coll = ['a', 'b', 'b-1', 'c-']

        testvals = [{
            'text': '',
            'expected': '-1'
        }, {
            'text': 'd',
            'expected': 'd'
        }, {
            'text': 'a',
            'expected': 'a-1'
        }, {
            'text': 'b',
            'expected': 'b-2'
        }, {
            'text': 'c-',
            'expected': 'c--1'
        }]

        for testval in testvals:
            self.assertEqual(testval['expected'],
                             self.hw._getUniqueValue(testval['text'], coll))

    def test_getIDText(self):
        # String conversion tests.
        testvals = [
            {
                'text': '',
                'expected': '-1'
            },
            {
                'text': '- -- \t\n',
                'expected': '-'
            },
            {
                'text': 'will-not_be-changed',
                'expected': 'will-not_be-changed'
            },
            {
                'text': '(w*&il|}[]l be\tch@anged',
                'expected': 'will-be-changed'
            },
            {
                'text': 'Case-Will-Change',
                'expected': 'case-will-change'
            },
            {
                # Includes a unicode lower-case Greek alpha.
                'text': unicode('unicode: \xce\xb1', 'utf-8'),
                'expected': 'unicode-'
            }
        ]

        for testval in testvals:
            self.assertEqual(testval['expected'],
                             self.hw._getIDText(testval['text'], set()))

        # Test unique ID generation.
        usedIDs = {
            'ida', 'idb', 'idb-1', 'idc', 'idc-2', 'idd', 'idd-1', 'idd-2'
        }

        testvals = [{
            'text': 'ide',
            'expected': 'ide'
        }, {
            'text': 'ida',
            'expected': 'ida-1'
        }, {
            'text': 'idb',
            'expected': 'idb-2'
        }, {
            'text': 'idc',
            'expected': 'idc-1'
        }, {
            'text': 'idd',
            'expected': 'idd-3'
        }]

        for testval in testvals:
            self.assertEqual(testval['expected'],
                             self.hw._getIDText(testval['text'], usedIDs))

    def _printResultsComparison(self, expected, result):
        """
        Prints a line-by-line comparison of an expected text string and a
        result text string.
        """
        for e_line, r_line in zip(expected.splitlines(), result.splitlines()):
            if e_line != r_line:
                print 'MISMATCH:'
            print 'exp: "{0}"\nres: "{1}"'.format(e_line, r_line)

    def test_write(self):
        testvals = [
            # A document specification that includes two Markdown sections with
            # h2 headers, separated by an entities section, followed by another
            # entities section.  One of the h2 headers has content that will
            # generate an ID that conflicts with a reserved ID, to verify that
            # reserved IDs are properly preserved.
            {
                'docspec':
                """
# Test documentation

## First h2 header

## main

## Properties
- ID: OBTO:0020

## Classes

- ID: OBITO:0001
  descendants: 1
""",
                'expected':
                """
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
    <meta charset="utf-8" />
    <title>Test documentation</title>
    <link rel="stylesheet" type="text/css" href="documentation_styles.css" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="navtree.js"></script>
</head>
<body>

<nav id="toc">
<h1>Table of Contents</h1>
<div id="toc_buttons">
    <div id="expand_all" class="top_toc_button">expand all</div>
    <div id="collapse_all" class="top_toc_button">collapse all</div>
</div>
<ul>
<li><a href="#first-h2-header">First h2 header</a>
</li>
<li><a href="#main-1">main</a>
</li>
<li><a href="#properties">Properties</a>
    <ul>
    <li><a href="#test-data-property-1">test data property 1</a></li>
    </ul>
</li>
<li><a href="#classes">Classes</a>
    <ul>
    <li><a href="#imported-test-class-1">imported test class 1</a>
        <ul>
        <li><a href="#test-class-1">test class 1</a></li>
        <li><a href="#test-class-2">test class 2</a></li>
        <li><a href="#test-class-3">test class 3</a></li>
        </ul>
    </li>
    </ul>
</li>
</ul>
</nav>

<main>
<h1>Test documentation</h1>

<h2 id="first-h2-header">First h2 header</h2>

<h2 id="main-1">main</h2>

<h2 id="properties">Properties</h2>
<ul class="entity_list">
<li>
    <h3 id="test-data-property-1">test data property 1</h3>
    <p>OBO ID: OBTO:0020</p>
    <p>IRI: http://purl.obolibrary.org/obo/OBTO_0020</p>
</li>
</ul>

<h2 id="classes">Classes</h2>
<ul class="entity_list">
<li>
    <h3 id="imported-test-class-1">imported test class 1</h3>
    <p>OBO ID: OBITO:0001</p>
    <p>IRI: http://purl.obolibrary.org/obo/OBITO_0001</p>
    <ul class="entity_list">
    <li>
        <h3 id="test-class-1">test class 1</h3>
        <p>OBO ID: OBTO:0010</p>
        <p>IRI: http://purl.obolibrary.org/obo/OBTO_0010</p>
    </li>
    <li>
        <h3 id="test-class-2">test class 2</h3>
        <p>OBO ID: OBTO:0011</p>
        <p>IRI: http://purl.obolibrary.org/obo/OBTO_0011</p>
    </li>
    <li>
        <h3 id="test-class-3">test class 3</h3>
        <p>OBO ID: OBTO:0012</p>
        <p>IRI: http://purl.obolibrary.org/obo/OBTO_0012</p>
    </li>
    </ul>
</li>
</ul>

</main>
</body>
</html>"""
            },
            # A document specification that includes UTF-8 non-ASCI text.
            {
                'docspec':
                """
## UTF-8 Greek alpha: \xce\xb1
""",
                'expected':
                """
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
    <meta charset="utf-8" />
    <title></title>
    <link rel="stylesheet" type="text/css" href="documentation_styles.css" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="navtree.js"></script>
</head>
<body>

<nav id="toc">
<h1>Table of Contents</h1>
<div id="toc_buttons">
    <div id="expand_all" class="top_toc_button">expand all</div>
    <div id="collapse_all" class="top_toc_button">collapse all</div>
</div>
<ul>
<li><a href="#utf-8-greek-alpha-">UTF-8 Greek alpha: \xce\xb1</a>
</li>
</ul>
</nav>

<main>
<h2 id="utf-8-greek-alpha-">UTF-8 Greek alpha: \xce\xb1</h2>
</main>
</body>
</html>"""
            }
        ]

        self.doc.setWriter(HTMLWriter())

        for testval in testvals:
            docspec = testval['docspec']
            expected = testval['expected']

            strbuf = StringIO.StringIO()
            self.doc.document(docspec, strbuf)
            result = strbuf.getvalue()
            strbuf.close()

            #self._printResultsComparison(expected[1:], result)

            self.assertEqual(expected[1:], result)

        # Create a cycle in the descendant relationships by making OBITO:0001 a
        # subclass of OBTO:0010, and make a polyhierarchy by making OBTO:0012 a
        # subclass of OBTO:0011.  The class structure should look like this:
        #
        # OBTO:0090
        # OBITO:0001
        # |--- OBTO:0010
        # |    |--- OBITO:0001
        # |--- OBTO:0011
        # |    |--- OBTO:0012
        # |--- OBTO:0012
        ent = self.ont.getExistingClass('OBTO:0010')
        ent.addSubclass('OBITO:0001')
        ent = self.ont.getExistingClass('OBTO:0011')
        ent.addSubclass('OBTO:0012')

        # Make sure the cycle doesn't "trap" the documentation generating
        # algorithms and that the polyhierarchy is handled correctly.
        docspec = """
## Classes
- ID: OBITO:0001
  descendants: all
"""
        expected = """
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
    <meta charset="utf-8" />
    <title></title>
    <link rel="stylesheet" type="text/css" href="documentation_styles.css" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="navtree.js"></script>
</head>
<body>

<nav id="toc">
<h1>Table of Contents</h1>
<div id="toc_buttons">
    <div id="expand_all" class="top_toc_button">expand all</div>
    <div id="collapse_all" class="top_toc_button">collapse all</div>
</div>
<ul>
<li><a href="#classes">Classes</a>
    <ul>
    <li><a href="#imported-test-class-1">imported test class 1</a>
        <ul>
        <li><a href="#test-class-1">test class 1</a>
            <ul>
            <li><a href="#imported-test-class-1">imported test class 1</a></li>
            </ul>
        </li>
        <li><a href="#test-class-2">test class 2</a>
            <ul>
            <li><a href="#test-class-3">test class 3</a></li>
            </ul>
        </li>
        <li><a href="#test-class-3">test class 3</a></li>
        </ul>
    </li>
    </ul>
</li>
</ul>
</nav>

<main>
<h2 id="classes">Classes</h2>
<ul class="entity_list">
<li>
    <h3 id="imported-test-class-1">imported test class 1</h3>
    <p>OBO ID: OBITO:0001</p>
    <p>IRI: http://purl.obolibrary.org/obo/OBITO_0001</p>
    <ul class="entity_list">
    <li>
        <h3 id="test-class-1">test class 1</h3>
        <p>OBO ID: OBTO:0010</p>
        <p>IRI: http://purl.obolibrary.org/obo/OBTO_0010</p>
        <ul class="entity_list">
        <li>
            <h3>imported test class 1</h3>
            <p>OBO ID: OBITO:0001</p>
            <p>IRI: http://purl.obolibrary.org/obo/OBITO_0001</p>
        </li>
        </ul>
    </li>
    <li>
        <h3 id="test-class-2">test class 2</h3>
        <p>OBO ID: OBTO:0011</p>
        <p>IRI: http://purl.obolibrary.org/obo/OBTO_0011</p>
        <ul class="entity_list">
        <li>
            <h3 id="test-class-3">test class 3</h3>
            <p>OBO ID: OBTO:0012</p>
            <p>IRI: http://purl.obolibrary.org/obo/OBTO_0012</p>
        </li>
        </ul>
    </li>
    <li>
        <h3>test class 3</h3>
        <p>OBO ID: OBTO:0012</p>
        <p>IRI: http://purl.obolibrary.org/obo/OBTO_0012</p>
    </li>
    </ul>
</li>
</ul>

</main>
</body>
</html>"""

        strbuf = StringIO.StringIO()
        self.doc.document(docspec, strbuf)
        result = strbuf.getvalue()
        strbuf.close()

        #self._printResultsComparison(expected[1:], result)

        self.assertEqual(expected[1:], result)