From 68ada59315505c715708740aa64cbc2ad57fc89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Prchl=C3=ADk?= Date: Wed, 29 Mar 2023 14:31:37 +0200 Subject: [PATCH] squash: more verbose messages from schema validation --- tmt/base.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/tmt/base.py b/tmt/base.py index c925d45f0c..5c5d51d627 100644 --- a/tmt/base.py +++ b/tmt/base.py @@ -16,6 +16,7 @@ import fmf import fmf.base import fmf.utils +import jsonschema from click import confirm, echo, style from fmf.utils import listed from ruamel.yaml.error import MarkedYAMLError @@ -704,12 +705,72 @@ def _lint_keys(self, additional_keys: List[str]) -> List[str]: def lint_validate(self) -> LinterReturn: """ C000: fmf node should pass the schema validation """ - errors = tmt.utils.validate_fmf_node(self.node, f'{self.__class__.__name__.lower()}.yaml') + schema_name = self.__class__.__name__.lower() + + errors = tmt.utils.validate_fmf_node(self.node, f'{schema_name}.yaml') if errors: - for exc, message in errors: - # TODO: one day, this should become FAIL - yield LinterOutcome.WARN, f'fmf node failed schema validation, {message}' + # A bit of error formatting. It is possible to use str(error), but the result + # is a bit too JSON-ish. Let's present an error message in a way that helps + # users to point finger on each and every issue. + + def detect_unallowed_properties(error: jsonschema.ValidationError) -> LinterReturn: + match = re.search( + r"(?mi)Additional properties are not allowed \(([a-zA-Z0-9', \-]+) (?:was|were) unexpected\)", # noqa: E501 + str(error)) + + if not match: + return + + for bad_property in match.group(1).replace("'", '').replace(' ', '').split(','): + yield LinterOutcome.WARN, \ + f'key "{bad_property}" not recognized by schema {error.schema["$id"]}' + + # A key value is not recognized. This is often a case with keys whose values are + # limited by an enum, like `how`. Unfortunatelly, validator will record every mismatch + # between a value and enum even if eventually a match is found. So it might be tempting + # to ignore this particular kind of error - it may also indicate a genuine typo in + # key name or completely misplaced key, so it's still necessary to report the error. + def detect_enum_violations(error: jsonschema.ValidationError) -> LinterReturn: + match = re.search( + r"(?mi)'([a-z\-]+)' is not one of \['([a-zA-Z\-]+)'\]", + str(error)) + + if not match: + return + + yield LinterOutcome.WARN, \ + f'value of "{error.json_path.split(".")[-1]}" is not "{match.group(2)}"' + + for error, _ in errors: + # if error.path: + # # Path to a place in tested instance where the error appeared + # error_path = '.'.join(error.path) + # + # yield LinterOutcome.WARN, textwrap.dedent(f""" + # At key "{error_path}": + # + # ----- + # {error_path}: + # {textwrap.indent(tmt.utils.dict_to_yaml(error.instance), ' ')} + # ----- + # """) + # + # else: + # yield LinterOutcome.WARN, textwrap.dedent(f""" + # ----- + # {textwrap.indent(tmt.utils.dict_to_yaml(error.instance), ' ')} + # ----- + # """) + + yield from detect_unallowed_properties(error) + yield from detect_enum_violations(error) + + # Validation errors can have "context", a list of "sub" errors encountered during + # validation. Interesting ones are identified & added to our error message. + for suberror in error.context: + yield from detect_unallowed_properties(suberror) + yield from detect_enum_violations(suberror) return