From e663964672861e5bffaa3bd0eb8454520a7b4dae Mon Sep 17 00:00:00 2001 From: bugfix-mission Date: Thu, 23 Apr 2026 01:14:42 +0800 Subject: [PATCH] fix(exceptions): count duplicate-validator errors in ErrorTree ErrorTree.total_errors used len(self.errors), but self.errors is a dict keyed by validator keyword, so multiple errors at the same path sharing a validator (e.g. several 'type' failures) were collapsed and undercounted. Track every error in an internal list and use its length for total_errors. --- jsonschema/exceptions.py | 4 +++- jsonschema/tests/test_exceptions.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py index 2e5d4ca0..be851fa2 100644 --- a/jsonschema/exceptions.py +++ b/jsonschema/exceptions.py @@ -321,6 +321,7 @@ class ErrorTree: def __init__(self, errors: Iterable[ValidationError] = ()): self.errors: MutableMapping[str, ValidationError] = {} + self._all_errors: list[ValidationError] = [] self._contents: Mapping[str, ErrorTree] = defaultdict(self.__class__) for error in errors: @@ -328,6 +329,7 @@ def __init__(self, errors: Iterable[ValidationError] = ()): for element in error.path: container = container[element] container.errors[error.validator] = error + container._all_errors.append(error) container._instance = error.instance @@ -390,7 +392,7 @@ def total_errors(self): The total number of errors in the entire tree, including children. """ child_errors = sum(len(tree) for _, tree in self._contents.items()) - return len(self.errors) + child_errors + return len(self._all_errors) + child_errors def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES): diff --git a/jsonschema/tests/test_exceptions.py b/jsonschema/tests/test_exceptions.py index 358b9242..d40784f0 100644 --- a/jsonschema/tests/test_exceptions.py +++ b/jsonschema/tests/test_exceptions.py @@ -394,6 +394,17 @@ def test_it_knows_how_many_total_errors_it_contains(self): tree = exceptions.ErrorTree(errors) self.assertEqual(tree.total_errors, 8) + def test_total_errors_counts_duplicate_validators(self): + # Regression test for #442: multiple errors at the same path + # sharing the same validator keyword should all be counted. + errors = [ + exceptions.ValidationError("first", validator="type"), + exceptions.ValidationError("second", validator="type"), + exceptions.ValidationError("third", validator="type"), + ] + tree = exceptions.ErrorTree(errors) + self.assertEqual(tree.total_errors, 3) + def test_it_contains_an_item_if_the_item_had_an_error(self): errors = [exceptions.ValidationError("a message", path=["bar"])] tree = exceptions.ErrorTree(errors)