debian-xmlschema/xmlschema/tests/__init__.py

198 lines
7.8 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright (c), 2016-2019, SISSA (International School for Advanced Studies).
# All rights reserved.
# This file is distributed under the terms of the MIT License.
# See the file 'LICENSE' in the root directory of the present
# distribution, or http://opensource.org/licenses/MIT.
#
# @author Davide Brunato <brunato@sissa.it>
#
"""
Tests subpackage module: common definitions for unittest scripts of the 'xmlschema' package.
"""
import unittest
import platform
import re
import os
import xmlschema
from xmlschema import XMLSchema
from xmlschema.compat import urlopen, URLError, unicode_type
from xmlschema.exceptions import XMLSchemaValueError
from xmlschema.etree import (
is_etree_element, etree_element, etree_register_namespace, etree_elements_assert_equal
)
from xmlschema.resources import fetch_namespaces
from xmlschema.qnames import XSD_SCHEMA
from xmlschema.helpers import get_namespace
from xmlschema.namespaces import XSD_NAMESPACE
from .schema_observers import SchemaObserver
from .test_factory import tests_factory
def has_network_access(*locations):
for url in locations:
try:
urlopen(url, timeout=5)
except (URLError, OSError):
pass
else:
return True
return False
SKIP_REMOTE_TESTS = not has_network_access('http://www.sissa.it', 'http://www.w3.org/', 'http://dublincore.org/')
PROTECTED_PREFIX_PATTERN = re.compile(r'ns\d:')
def print_test_header():
header1 = "Test %r" % xmlschema
header2 = "with Python {} on platform {}".format(platform.python_version(), platform.platform())
print('{0}\n{1}\n{2}\n{0}'.format("*" * max(len(header1), len(header2)), header1, header2))
class XMLSchemaTestCase(unittest.TestCase):
"""
XMLSchema TestCase class.
Setup tests common environment. The tests parts have to use empty prefix for
XSD namespace names and 'ns' prefix for XMLSchema test namespace names.
"""
test_cases_dir = os.path.join(os.path.dirname(__file__), 'test_cases/')
etree_register_namespace(prefix='', uri=XSD_NAMESPACE)
etree_register_namespace(prefix='ns', uri="ns")
SCHEMA_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns:ns="ns" xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="ns" elementFormDefault="unqualified" version="{0}">
{1}
</schema>"""
schema_class = XMLSchema
@classmethod
def setUpClass(cls):
cls.errors = []
cls.xsd_types = cls.schema_class.builtin_types()
cls.content_pattern = re.compile(r'(<|<xs:)(sequence|choice|all)')
cls.default_namespaces = {
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'tns': 'http://xmlschema.test/ns',
'ns': 'ns',
}
cls.vh_dir = cls.casepath('examples/vehicles')
cls.vh_xsd_file = cls.casepath('examples/vehicles/vehicles.xsd')
cls.vh_xml_file = cls.casepath('examples/vehicles/vehicles.xml')
cls.vh_json_file = cls.casepath('examples/vehicles/vehicles.json')
cls.vh_schema = cls.schema_class(cls.vh_xsd_file)
cls.vh_namespaces = fetch_namespaces(cls.vh_xml_file)
cls.col_dir = cls.casepath('examples/collection')
cls.col_xsd_file = cls.casepath('examples/collection/collection.xsd')
cls.col_xml_file = cls.casepath('examples/collection/collection.xml')
cls.col_json_file = cls.casepath('examples/collection/collection.json')
cls.col_schema = cls.schema_class(cls.col_xsd_file)
cls.col_namespaces = fetch_namespaces(cls.col_xml_file)
cls.st_xsd_file = cls.casepath('features/decoder/simple-types.xsd')
cls.st_schema = cls.schema_class(cls.st_xsd_file)
cls.models_xsd_file = cls.casepath('features/models/models.xsd')
cls.models_schema = cls.schema_class(cls.models_xsd_file)
@classmethod
def casepath(cls, path):
"""
Returns the absolute path of a test case file.
:param path: the relative path of the case file from base dir ``xmlschema/tests/test_cases/``.
"""
return os.path.join(cls.test_cases_dir, path)
def retrieve_schema_source(self, source):
"""
Returns a schema source that can be used to create an XMLSchema instance.
:param source: A string or an ElementTree's Element.
:return: An schema source string, an ElementTree's Element or a full pathname.
"""
if is_etree_element(source):
if source.tag in (XSD_SCHEMA, 'schema'):
return source
elif get_namespace(source.tag):
raise XMLSchemaValueError("source %r namespace has to be empty." % source)
elif source.tag not in {'element', 'attribute', 'simpleType', 'complexType',
'group', 'attributeGroup', 'notation'}:
raise XMLSchemaValueError("% is not an XSD global definition/declaration." % source)
root = etree_element('schema', attrib={
'xmlns:ns': "ns",
'xmlns': "http://www.w3.org/2001/XMLSchema",
'targetNamespace': "ns",
'elementFormDefault': "qualified",
'version': self.schema_class.XSD_VERSION,
})
root.append(source)
return root
else:
source = source.strip()
if not source.startswith('<'):
return self.casepath(source)
else:
return self.SCHEMA_TEMPLATE.format(self.schema_class.XSD_VERSION, source)
def get_schema(self, source):
return self.schema_class(self.retrieve_schema_source(source))
def get_element(self, name, **attrib):
source = '<element name="{}" {}/>'.format(
name, ' '.join('%s="%s"' % (k, v) for k, v in attrib.items())
)
schema = self.schema_class(self.retrieve_schema_source(source))
return schema.elements[name]
def check_etree_elements(self, elem, other):
"""Checks if two ElementTree elements are equal."""
try:
self.assertIsNone(etree_elements_assert_equal(elem, other, strict=False, skip_comments=True))
except AssertionError as err:
self.assertIsNone(err, None)
def check_namespace_prefixes(self, s):
"""Checks that a string doesn't contain protected prefixes (ns0, ns1 ...)."""
match = PROTECTED_PREFIX_PATTERN.search(s)
if match:
msg = "Protected prefix {!r} found:\n {}".format(match.group(0), s)
self.assertIsNone(match, msg)
def check_errors(self, path, expected):
"""
Checks schema or validation errors, checking information completeness of the
instances and those number against expected.
:param path: the path of the test case.
:param expected: the number of expected errors.
"""
for e in self.errors:
error_string = unicode_type(e)
self.assertTrue(e.path, "Missing path for: %s" % error_string)
self.assertTrue(e.namespaces, "Missing namespaces for: %s" % error_string)
self.check_namespace_prefixes(error_string)
if not self.errors and expected:
raise ValueError("{!r}: found no errors when {} expected.".format(path, expected))
elif len(self.errors) != expected:
num_errors = len(self.errors)
if num_errors == 1:
msg = "{!r}: n.{} errors expected, found {}:\n\n{}"
elif num_errors <= 5:
msg = "{!r}: n.{} errors expected, found {}. Errors follow:\n\n{}"
else:
msg = "{!r}: n.{} errors expected, found {}. First five errors follow:\n\n{}"
error_string = '\n++++++++++\n\n'.join([unicode_type(e) for e in self.errors[:5]])
raise ValueError(msg.format(path, expected, len(self.errors), error_string))