blob: ae5fb19cc8a5f22651f8f911903502091506b886 [file] [log] [blame]
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""
A tool for validating entity manager configurations.
"""
import argparse
import json
import os
import re
import sys
import jsonschema.validators
DEFAULT_SCHEMA_FILENAME = "global.json"
def remove_c_comments(string):
# first group captures quoted strings (double or single)
# second group captures comments (//single-line or /* multi-line */)
pattern = r"(\".*?(?<!\\)\"|\'.*?(?<!\\)\')|(/\*.*?\*/|//[^\r\n]*$)"
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
def _replacer(match):
if match.group(2) is not None:
return ""
else:
return match.group(1)
return regex.sub(_replacer, string)
def main():
parser = argparse.ArgumentParser(
description="Entity manager configuration validator",
)
parser.add_argument(
"-s",
"--schema",
help=(
"Use the specified schema file instead of the default "
"(__file__/../../schemas/global.json)"
),
)
parser.add_argument(
"-c",
"--config",
action="append",
help=(
"Validate the specified configuration files (can be "
"specified more than once) instead of the default "
"(__file__/../../configurations/**.json)"
),
)
parser.add_argument(
"-e",
"--expected-fails",
help=(
"A file with a list of configurations to ignore should "
"they fail to validate"
),
)
parser.add_argument(
"-k",
"--continue",
action="store_true",
help="keep validating after a failure",
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="be noisy"
)
args = parser.parse_args()
schema_file = args.schema
if schema_file is None:
try:
source_dir = os.path.realpath(__file__).split(os.sep)[:-2]
schema_file = os.sep + os.path.join(
*source_dir, "schemas", DEFAULT_SCHEMA_FILENAME
)
except Exception:
sys.stderr.write(
"Could not guess location of {}\n".format(
DEFAULT_SCHEMA_FILENAME
)
)
sys.exit(2)
schema = {}
try:
with open(schema_file) as fd:
schema = json.load(fd)
except FileNotFoundError:
sys.stderr.write(
"Could not read schema file '{}'\n".format(schema_file)
)
sys.exit(2)
config_files = args.config or []
if len(config_files) == 0:
try:
source_dir = os.path.realpath(__file__).split(os.sep)[:-2]
configs_dir = os.sep + os.path.join(*source_dir, "configurations")
data = os.walk(configs_dir)
for root, _, files in data:
for f in files:
if f.endswith(".json"):
config_files.append(os.path.join(root, f))
except Exception:
sys.stderr.write("Could not guess location of configurations\n")
sys.exit(2)
configs = []
for config_file in config_files:
try:
with open(config_file) as fd:
configs.append(json.loads(remove_c_comments(fd.read())))
except FileNotFoundError:
sys.stderr.write(
"Could not parse config file '{}'\n".format(config_file)
)
sys.exit(2)
expected_fails = []
if args.expected_fails:
try:
with open(args.expected_fails) as fd:
for line in fd:
expected_fails.append(line.strip())
except Exception:
sys.stderr.write(
"Could not read expected fails file '{}'\n".format(
args.expected_fails
)
)
sys.exit(2)
spec = jsonschema.Draft202012Validator
spec.check_schema(schema)
base_uri = "file://{}/".format(
os.path.split(os.path.realpath(schema_file))[0]
)
resolver = jsonschema.RefResolver(base_uri, schema)
validator = spec(schema, resolver=resolver)
results = {
"invalid": [],
"unexpected_pass": [],
}
for config_file, config in zip(config_files, configs):
name = os.path.split(config_file)[1]
expect_fail = name in expected_fails
try:
validator.validate(config)
if expect_fail:
results["unexpected_pass"].append(name)
if not getattr(args, "continue"):
break
except jsonschema.exceptions.ValidationError as e:
if not expect_fail:
results["invalid"].append(name)
if args.verbose:
print(e)
if expect_fail or getattr(args, "continue"):
continue
break
exit_status = 0
if len(results["invalid"]) + len(results["unexpected_pass"]):
exit_status = 1
unexpected_pass_suffix = " **"
show_suffix_explanation = False
print("results:")
for f in config_files:
if any([x in f for x in results["unexpected_pass"]]):
show_suffix_explanation = True
print(" '{}' passed!{}".format(f, unexpected_pass_suffix))
if any([x in f for x in results["invalid"]]):
print(" '{}' failed!".format(f))
if show_suffix_explanation:
print("\n** configuration expected to fail")
sys.exit(exit_status)
if __name__ == "__main__":
main()