class XsdValidatorTestCase(unittest.TestCase): """ Base class for testing XSD validators. """ TEST_CASES_DIR = None schema_class = XMLSchema10 etree_register_namespace(prefix='xs', uri=XSD_NAMESPACE) etree_register_namespace(prefix='ns', uri="ns") @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', } if os.path.isfile(cls.casepath('testfiles')): 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, relative_path): """ Returns the absolute path from a relative path specified from the referenced TEST_CASES_DIR. """ return os.path.join(cls.TEST_CASES_DIR or '', relative_path) def get_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:xs': "http://www.w3.org/2001/XMLSchema", '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) elif source.startswith('<?xml ') or source.startswith( '<xs:schema '): return source else: return SCHEMA_TEMPLATE.format(self.schema_class.XSD_VERSION, source) def get_schema(self, source, **kwargs): return self.schema_class(self.get_schema_source(source), **kwargs) def get_element(self, name, **attrib): source = '<xs:element name="{}" {}/>'.format( name, ' '.join('%s="%s"' % (k, v) for k, v in attrib.items())) schema = self.schema_class(self.get_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_schema(self, source, expected=None, **kwargs): """ Create a schema for a test case. :param source: A relative path or a root Element or a portion of schema for a template. :param expected: If it's an Exception class test the schema for raise an error. \ Otherwise build the schema and test a condition if expected is a callable, or make \ a substring test if it's not `None` (maybe a string). Then returns the schema instance. """ if isinstance(expected, type) and issubclass(expected, Exception): self.assertRaises(expected, self.schema_class, self.get_schema_source(source), **kwargs) else: schema = self.schema_class(self.get_schema_source(source), **kwargs) if callable(expected): self.assertTrue(expected(schema)) return schema 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 = str(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( [str(e) for e in self.errors[:5]]) raise ValueError( msg.format(path, expected, len(self.errors), error_string))
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_dir = os.path.dirname(__file__) 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.xsd_types = cls.schema_class.builtin_types() cls.content_pattern = re.compile(r'(xs:sequence|xs:choice|xs:all)') cls.default_namespaces = { 'ns': 'ns', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance' } cls.vh_dir = cls.abspath('cases/examples/vehicles') cls.vh_xsd_file = cls.abspath('cases/examples/vehicles/vehicles.xsd') cls.vh_xml_file = cls.abspath('cases/examples/vehicles/vehicles.xml') cls.vh_json_file = cls.abspath('cases/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.abspath('cases/examples/collection') cls.col_xsd_file = cls.abspath( 'cases/examples/collection/collection.xsd') cls.col_xml_file = cls.abspath( 'cases/examples/collection/collection.xml') cls.col_json_file = cls.abspath( 'cases/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.abspath( 'cases/features/decoder/simple-types.xsd') cls.st_schema = cls.schema_class(cls.st_xsd_file) cls.models_xsd_file = cls.abspath('cases/features/models/models.xsd') cls.models_schema = cls.schema_class(cls.models_xsd_file) @classmethod def abspath(cls, path): return os.path.join(cls.test_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 os.path.join(self.test_dir, 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)
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))