Skip to content

Commit

Permalink
feat: Improve codegen errors (#970)
Browse files Browse the repository at this point in the history
  • Loading branch information
tefra committed Mar 9, 2024
1 parent b8a2abc commit 77c05ef
Show file tree
Hide file tree
Showing 33 changed files with 154 additions and 183 deletions.
10 changes: 2 additions & 8 deletions tests/codegen/handlers/test_designate_class_packages.py
@@ -1,6 +1,6 @@
from xsdata.codegen.container import ClassContainer
from xsdata.codegen.exceptions import CodegenError
from xsdata.codegen.handlers import DesignateClassPackages
from xsdata.exceptions import CodeGenerationError
from xsdata.models.config import (
GeneratorConfig,
GeneratorSubstitution,
Expand Down Expand Up @@ -177,15 +177,9 @@ def test_group_by_namespace_clusters_raises_exception(self):
self.config.output.package = "models"
self.container.extend(classes)

with self.assertRaises(CodeGenerationError) as cm:
with self.assertRaises(CodegenError):
self.handler.run()

self.assertEqual(
"Found strongly connected classes from different namespaces, "
"grouping them is impossible!",
str(cm.exception),
)

def test_combine_ns_package(self):
namespace = "urn:foo-bar:add"
result = self.handler.combine_ns_package(namespace)
Expand Down
6 changes: 2 additions & 4 deletions tests/codegen/handlers/test_flatten_attribute_groups.py
@@ -1,10 +1,10 @@
from unittest import mock

from xsdata.codegen.container import ClassContainer
from xsdata.codegen.exceptions import CodegenError
from xsdata.codegen.handlers import FlattenAttributeGroups
from xsdata.codegen.models import Attr, Status
from xsdata.codegen.utils import ClassUtils
from xsdata.exceptions import AnalyzerValueError
from xsdata.models.config import GeneratorConfig
from xsdata.models.enums import Tag
from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase
Expand Down Expand Up @@ -91,7 +91,5 @@ def test_process_attribute_with_unknown_source(self):
target = ClassFactory.create()
target.attrs.append(group_attr)

with self.assertRaises(AnalyzerValueError) as cm:
with self.assertRaises(CodegenError):
self.processor.process_attribute(target, group_attr)

self.assertEqual("Group attribute not found: `bar`", str(cm.exception))
18 changes: 5 additions & 13 deletions tests/codegen/handlers/test_validate_references.py
@@ -1,6 +1,6 @@
from xsdata.codegen.container import ClassContainer
from xsdata.codegen.exceptions import CodegenError
from xsdata.codegen.handlers import ValidateReferences
from xsdata.exceptions import AnalyzerValueError
from xsdata.models.config import (
GeneratorConfig,
)
Expand All @@ -25,34 +25,28 @@ def test_validate_unique_qualified_names(self):
second = first.clone()
self.container.extend([first, second])

with self.assertRaises(AnalyzerValueError) as cm:
with self.assertRaises(CodegenError):
self.handler.run()

self.assertEqual("Duplicate types found", str(cm.exception))

def test_validate_unique_instances(self):
first = ClassFactory.create()
first.extensions.append(ExtensionFactory.create())
second = ClassFactory.create()
second.extensions = first.extensions
self.container.extend([first, second])

with self.assertRaises(AnalyzerValueError) as cm:
with self.assertRaises(CodegenError):
self.handler.run()

self.assertEqual("Cross reference detected", str(cm.exception))

def test_validate_unresolved_references(self):
first = ClassFactory.create()
first.attrs.append(AttrFactory.create())
first.attrs.append(AttrFactory.reference("foo"))
self.container.add(first)

with self.assertRaises(AnalyzerValueError) as cm:
with self.assertRaises(CodegenError):
self.handler.run()

self.assertEqual("Unresolved reference detected", str(cm.exception))

def test_validate_misrepresented_references(self):
first = ClassFactory.create()
inner = ClassFactory.create()
Expand All @@ -64,7 +58,5 @@ def test_validate_misrepresented_references(self):
)
self.container.add(first)

with self.assertRaises(AnalyzerValueError) as cm:
with self.assertRaises(CodegenError):
self.handler.run()

self.assertEqual("Misrepresented reference", str(cm.exception))
9 changes: 2 additions & 7 deletions tests/codegen/models/test_class.py
@@ -1,6 +1,6 @@
import sys

from xsdata.exceptions import CodeGenerationError
from xsdata.codegen.exceptions import CodegenError
from xsdata.models.enums import DataType, Namespace, Tag
from xsdata.utils.namespaces import build_qname
from xsdata.utils.testing import (
Expand Down Expand Up @@ -157,14 +157,9 @@ def test_property_references(self):

def test_property_target_module(self):
obj = ClassFactory.create(module=None, package=None)
with self.assertRaises(CodeGenerationError) as cm:
with self.assertRaises(CodegenError):
obj.target_module

self.assertEqual(
f"Class `{obj.name}` has not been assigned to a module yet!",
str(cm.exception),
)

obj.module = "bar"
self.assertEqual("bar", obj.target_module)

Expand Down
8 changes: 3 additions & 5 deletions tests/codegen/test_resolver.py
@@ -1,8 +1,8 @@
from unittest import mock

from xsdata.codegen.exceptions import CodegenError
from xsdata.codegen.models import Class
from xsdata.codegen.resolver import DependenciesResolver
from xsdata.exceptions import ResolverValueError
from xsdata.models.enums import DataType
from xsdata.utils.namespaces import build_qname
from xsdata.utils.testing import (
Expand Down Expand Up @@ -194,7 +194,7 @@ def test_get_class_module(self):
self.resolver.registry[class_a.qname] = "foo.bar"

self.assertEqual("foo.bar", self.resolver.get_class_module(class_a.qname))
with self.assertRaises(ResolverValueError):
with self.assertRaises(CodegenError):
self.resolver.get_class_module("nope")

def test_import_classes(self):
Expand All @@ -209,11 +209,9 @@ def test_create_class_map(self):

def test_create_class_map_for_duplicate_classes(self):
classes = ClassFactory.list(2, qname="a")
with self.assertRaises(ResolverValueError) as cm:
with self.assertRaises(CodegenError):
self.resolver.create_class_map(classes)

self.assertEqual("Duplicate class: `a`", str(cm.exception))

@mock.patch.object(Class, "dependencies")
def test_create_class_list(self, mock_dependencies):
classes = ClassFactory.list(3)
Expand Down
20 changes: 11 additions & 9 deletions tests/codegen/test_transformer.py
Expand Up @@ -3,7 +3,10 @@
from pathlib import Path
from unittest import mock

from toposort import CircularDependencyError

from xsdata.codegen.container import ClassContainer
from xsdata.codegen.exceptions import CodegenError
from xsdata.codegen.mappers import (
DefinitionsMapper,
DictMapper,
Expand All @@ -15,7 +18,6 @@
from xsdata.codegen.transformer import ResourceTransformer
from xsdata.codegen.utils import ClassUtils
from xsdata.codegen.writer import CodeWriter
from xsdata.exceptions import CodeGenerationError
from xsdata.formats.dataclass.models.generics import AnyElement
from xsdata.formats.dataclass.parsers import TreeParser
from xsdata.models.config import GeneratorConfig
Expand Down Expand Up @@ -104,6 +106,12 @@ def test_process_with_cache(
self.assertEqual(classes, pickle.loads(cache.read_bytes()))
mock_process_classes.assert_called_once_with()

@mock.patch.object(ResourceTransformer, "process_classes")
def test_process_with_circular_dependencies_error(self, mock_process_classes):
mock_process_classes.side_effect = CircularDependencyError({})
with self.assertRaises(CodegenError):
self.transformer.process([])

@mock.patch.object(ResourceTransformer, "convert_schema")
@mock.patch.object(ResourceTransformer, "convert_definitions")
@mock.patch.object(ResourceTransformer, "parse_definitions")
Expand Down Expand Up @@ -282,12 +290,6 @@ def test_process_classes_with_print_false(
]
)

def test_process_classes_with_zero_classes_after_analyze(self):
with self.assertRaises(CodeGenerationError) as cm:
self.transformer.process_classes()

self.assertEqual("Nothing to generate.", str(cm.exception))

@mock.patch.object(ResourceTransformer, "convert_schema")
@mock.patch.object(ResourceTransformer, "parse_schema")
def test_process_schema(
Expand Down Expand Up @@ -456,8 +458,8 @@ def test_classify_resource(self):

@mock.patch("xsdata.codegen.transformer.logger.warning")
def test_load_resource_missing(self, mock_warning):
uri = "file://foo/bar.xsd"
result = self.transformer.load_resource(uri)
uri = Path.cwd().joinpath("foo/bar.xsd").as_uri()
result = self.transformer.process([uri])
self.assertIsNone(result)

mock_warning.assert_called_once_with("Resource not found %s", uri)
Expand Down
13 changes: 5 additions & 8 deletions tests/codegen/test_utils.py
Expand Up @@ -2,9 +2,9 @@
from typing import Generator
from unittest import mock

from xsdata.codegen.exceptions import CodegenError
from xsdata.codegen.models import Restrictions, Status
from xsdata.codegen.utils import ClassUtils
from xsdata.exceptions import CodeGenerationError
from xsdata.models.enums import DataType, Tag
from xsdata.utils.testing import (
AttrFactory,
Expand All @@ -18,13 +18,11 @@
class ClassUtilsTests(FactoryTestCase):
def test_find_value_attr(self):
target = ClassFactory.create()
with self.assertRaises(CodeGenerationError) as cm:
with self.assertRaises(CodegenError):
ClassUtils.find_value_attr(target)

self.assertEqual("Class has no value attr {xsdata}class_B", str(cm.exception))

target.attrs.append(AttrFactory.element())
with self.assertRaises(CodeGenerationError) as cm:
with self.assertRaises(CodegenError):
ClassUtils.find_value_attr(target)

target.attrs.append(AttrFactory.extension())
Expand Down Expand Up @@ -213,7 +211,7 @@ def test_copy_inner_class_with_missing_inner(self):
target = ClassFactory.create()
attr_type = AttrTypeFactory.create(forward=True, qname=target.qname)

with self.assertRaises(CodeGenerationError):
with self.assertRaises(CodegenError):
ClassUtils.copy_inner_class(source, target, attr_type)

def test_find_inner(self):
Expand All @@ -223,10 +221,9 @@ def test_find_inner(self):
third = ClassFactory.enumeration(2, qname="{d}d")
obj.inner.extend((first, second, third))

with self.assertRaises(CodeGenerationError) as cm:
with self.assertRaises(CodegenError):
self.assertIsNone(ClassUtils.find_inner(obj, "nope"))

self.assertEqual("Missing inner class {a}parent.nope", str(cm.exception))
self.assertEqual(first, ClassUtils.find_inner(obj, "{a}a"))
self.assertEqual(second, ClassUtils.find_inner(obj, "{c}c"))
self.assertEqual(third, ClassUtils.find_inner(obj, "{d}d"))
Expand Down
6 changes: 2 additions & 4 deletions tests/codegen/test_writer.py
Expand Up @@ -3,9 +3,9 @@
from typing import Iterator, List
from unittest import mock

from xsdata.codegen.exceptions import CodegenError
from xsdata.codegen.models import Class
from xsdata.codegen.writer import CodeWriter
from xsdata.exceptions import CodeGenerationError
from xsdata.formats.dataclass.generator import DataclassGenerator
from xsdata.formats.mixins import AbstractGenerator, GeneratorResult
from xsdata.models.config import GeneratorConfig
Expand Down Expand Up @@ -72,11 +72,9 @@ def test_from_config(self):
CodeWriter.unregister_generator("dataclasses")
config = GeneratorConfig()

with self.assertRaises(CodeGenerationError) as cm:
with self.assertRaises(CodegenError):
CodeWriter.from_config(config)

self.assertEqual("Unknown output format: 'dataclasses'", str(cm.exception))

CodeWriter.register_generator("dataclasses", DataclassGenerator)
writer = CodeWriter.from_config(config)
self.assertIsInstance(writer.generator, DataclassGenerator)
6 changes: 2 additions & 4 deletions tests/formats/dataclass/test_generator.py
Expand Up @@ -2,8 +2,8 @@
from pathlib import Path
from unittest import mock

from xsdata.codegen.exceptions import CodegenError
from xsdata.codegen.resolver import DependenciesResolver
from xsdata.exceptions import CodeGenerationError
from xsdata.formats.dataclass.generator import DataclassGenerator
from xsdata.models.config import GeneratorConfig
from xsdata.utils.testing import ClassFactory, FactoryTestCase
Expand Down Expand Up @@ -218,7 +218,5 @@ def test_format_with_invalid_code(self):
file_path = Path(__file__)

self.generator.config.output.max_line_length = 55
with self.assertRaises(CodeGenerationError) as cm:
with self.assertRaises(CodegenError):
self.generator.ruff_code(src_code, file_path)

self.assertIn("Ruff failed", str(cm.exception))
8 changes: 2 additions & 6 deletions tests/formats/test_mixins.py
Expand Up @@ -3,8 +3,8 @@
from unittest import mock

from xsdata import __version__
from xsdata.codegen.exceptions import CodegenError
from xsdata.codegen.models import Class
from xsdata.exceptions import CodeGenerationError
from xsdata.formats.mixins import AbstractGenerator, GeneratorResult
from xsdata.models.config import GeneratorConfig
from xsdata.utils.testing import ClassFactory, FactoryTestCase
Expand Down Expand Up @@ -45,13 +45,9 @@ def test_normalize_packages(self, *args):
self.assertEqual("mod", classes[1].module)
self.assertEqual("mod", classes[2].module)

with self.assertRaises(CodeGenerationError) as cm:
with self.assertRaises(CodegenError):
self.generator.normalize_packages(ClassFactory.list(1))

self.assertEqual(
"Class `class_B` has not been assigned to a package", str(cm.exception)
)

@mock.patch("datetime.datetime", wraps=datetime.datetime)
def test_render_header(self, mock_datetime):
actual = self.generator.render_header()
Expand Down
17 changes: 7 additions & 10 deletions tests/models/test_config.py
Expand Up @@ -5,7 +5,8 @@
from unittest import TestCase

from xsdata import __version__
from xsdata.exceptions import GeneratorConfigError, ParserError
from xsdata.codegen.exceptions import CodegenError
from xsdata.exceptions import ParserError
from xsdata.models.config import (
ExtensionType,
GeneratorConfig,
Expand Down Expand Up @@ -127,11 +128,11 @@ def test_read_with_wrong_value(self):
with self.assertRaises(ParserError):
GeneratorConfig.read(file_path)

def test_format_with_invalid_state(self):
with self.assertRaises(GeneratorConfigError) as cm:
def test_format_with_invalid_eq_config(self):
with warnings.catch_warnings(record=True) as w:
OutputFormat(eq=False, order=True)

self.assertEqual("eq must be true if order is true", str(cm.exception))
self.assertEqual("Enabling eq because order is true", str(w[-1].message))

def test_subscriptable_types_requires_390(self):
if sys.version_info < (3, 9):
Expand Down Expand Up @@ -200,15 +201,11 @@ def test_format_kw_only_requires_310(self):
def test_extension_with_invalid_import_string(self):
cases = [None, "", "bar"]
for case in cases:
with self.assertRaises(GeneratorConfigError) as cm:
with self.assertRaises(CodegenError):
GeneratorExtension(type=ExtensionType.DECORATOR, import_string=case)

self.assertEqual(f"Invalid extension import '{case}'", str(cm.exception))

def test_extension_with_invalid_class_name_pattern(self):
with self.assertRaises(GeneratorConfigError) as cm:
with self.assertRaises(CodegenError):
GeneratorExtension(
type=ExtensionType.DECORATOR, import_string="a.b", class_name="*Foo"
)

self.assertEqual("Failed to compile pattern '*Foo'", str(cm.exception))
4 changes: 2 additions & 2 deletions tests/models/test_mixins.py
@@ -1,7 +1,7 @@
from typing import Generator, Iterator
from unittest import TestCase

from xsdata.exceptions import SchemaValueError
from xsdata.codegen.exceptions import CodegenError
from xsdata.models.enums import FormType, Namespace
from xsdata.models.mixins import ElementBase
from xsdata.models.xsd import Alternative, ComplexType, Element, SimpleType
Expand Down Expand Up @@ -145,7 +145,7 @@ def test_property_raw_namespace(self):
def test_property_real_name(self):
element = ElementBase()

with self.assertRaises(SchemaValueError):
with self.assertRaises(CodegenError):
element.real_name

element.ref = "bar:foo"
Expand Down

0 comments on commit 77c05ef

Please sign in to comment.