validation_outcome.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. # Copyright Materialize, Inc. and contributors. All rights reserved.
  2. #
  3. # Use of this software is governed by the Business Source License
  4. # included in the LICENSE file at the root of this repository.
  5. #
  6. # As of the Change Date specified in that file, in accordance with
  7. # the Business Source License, use of this software will be governed
  8. # by the Apache License, Version 2.0.
  9. from collections.abc import Sequence
  10. from enum import Enum
  11. from materialize.mzcompose.test_result import TestFailureDetails
  12. from materialize.output_consistency.ignore_filter.inconsistency_ignore_filter import (
  13. GenericInconsistencyIgnoreFilter,
  14. )
  15. from materialize.output_consistency.ignore_filter.internal_output_inconsistency_ignore_filter import (
  16. YesIgnore,
  17. )
  18. from materialize.output_consistency.output.format_constants import LI_PREFIX
  19. from materialize.output_consistency.output.reproduction_code_printer import (
  20. ReproductionCodePrinter,
  21. )
  22. from materialize.output_consistency.validation.validation_message import (
  23. ValidationError,
  24. ValidationMessage,
  25. ValidationRemark,
  26. ValidationWarning,
  27. )
  28. class ValidationVerdict(Enum):
  29. SUCCESS = 1
  30. SUCCESS_WITH_WARNINGS = 2
  31. IGNORED_FAILURE = 3
  32. FAILURE = 4
  33. def succeeded(self) -> bool:
  34. return self in {
  35. ValidationVerdict.SUCCESS,
  36. ValidationVerdict.SUCCESS_WITH_WARNINGS,
  37. }
  38. def accepted(self) -> bool:
  39. return self in {
  40. ValidationVerdict.SUCCESS,
  41. ValidationVerdict.SUCCESS_WITH_WARNINGS,
  42. ValidationVerdict.IGNORED_FAILURE,
  43. }
  44. class ValidationOutcome:
  45. """Outcome of a result comparison"""
  46. def __init__(self) -> None:
  47. self.success_reason: str | None = None
  48. self.count_ignored_errors = 0
  49. self.errors: list[ValidationError] = []
  50. self.warnings: list[ValidationWarning] = []
  51. self.remarks: list[ValidationRemark] = []
  52. self.query_execution_succeeded_in_all_strategies: bool = False
  53. """Whether the query was executed successfully in all strategies. This provides no info about the validity of the results."""
  54. def add_error(
  55. self, ignore_filter: GenericInconsistencyIgnoreFilter, error: ValidationError
  56. ) -> None:
  57. ignore_verdict = ignore_filter.shall_ignore_error(error)
  58. if isinstance(ignore_verdict, YesIgnore):
  59. self.count_ignored_errors += 1
  60. self.add_warning(
  61. ValidationWarning(
  62. f"Ignoring {error.error_type.name} ({error.message}) because of: {ignore_verdict.reason}"
  63. )
  64. )
  65. else:
  66. self.errors.append(error)
  67. def add_warning(self, warning: ValidationWarning) -> None:
  68. self.warnings.append(warning)
  69. def add_remark(self, remark: ValidationRemark) -> None:
  70. self.remarks.append(remark)
  71. def verdict(self) -> ValidationVerdict:
  72. if self.has_errors():
  73. return ValidationVerdict.FAILURE
  74. if self.has_ignored_errors():
  75. return ValidationVerdict.IGNORED_FAILURE
  76. if self.has_warnings():
  77. return ValidationVerdict.SUCCESS_WITH_WARNINGS
  78. else:
  79. return ValidationVerdict.SUCCESS
  80. def has_ignored_errors(self) -> bool:
  81. return self.count_ignored_errors > 0
  82. def has_errors(self) -> bool:
  83. return len(self.errors) > 0
  84. def has_warnings(self) -> bool:
  85. return len(self.warnings) > 0
  86. def has_remarks(self) -> bool:
  87. return len(self.remarks) > 0
  88. def error_output(self) -> str:
  89. return self._problem_marker_output(self.errors)
  90. def warning_output(self) -> str:
  91. return self._problem_marker_output(self.warnings)
  92. def remark_output(self) -> str:
  93. return self._problem_marker_output(self.remarks)
  94. def _problem_marker_output(self, entries: Sequence[ValidationMessage]) -> str:
  95. if len(entries) == 0:
  96. return ""
  97. return "\n".join(f"{LI_PREFIX}{str(entry)}" for entry in entries)
  98. def to_failure_details(
  99. self, reproduction_code_printer: ReproductionCodePrinter
  100. ) -> list[TestFailureDetails]:
  101. failures = []
  102. for error in self.errors:
  103. test_case_name = f"Query {error.query_execution.query_id}"
  104. if error.concerned_expression_str is not None:
  105. test_case_name = (
  106. f"{test_case_name} (`{error.concerned_expression_str}`)"
  107. )
  108. failures.append(
  109. TestFailureDetails(
  110. test_case_name_override=test_case_name,
  111. message=error.message,
  112. details=str(error),
  113. additional_details_header="Code to reproduce",
  114. additional_details=reproduction_code_printer.get_reproduction_code_of_error(
  115. error
  116. ),
  117. )
  118. )
  119. return failures