Skip to content

Commit c04b3f4

Browse files
committed
scripts: add configuration validation script
Add a script to validate configurations against the schema. Use https://github.com/Julian/jsonschema to perform the json validation. The script is intended to be run from a continuous integration environment or by configuration/schema developers. A key assumption/feature of the script is that its users will always prefer to resolve relative references to the local filesystem. As such, the script computes a base URI that points to the filesystem and instructs the validator to use that in place of whatever base_uri it derives from the global $id attribute. For additional reading see: https://json-schema.org/understanding-json-schema/structuring.html#the-id-property python-jsonschema/jsonschema#98 Without any options the script assumes it is being run from an entity-manager source distribution and attempts to find the schema and configuration files relative to the location of the script. Alternatively, the script can validate arbitrary json files against arbitrary schema: ./validate-configs.py -s foo.schema.json -c test1.json -c test2.json By default the validation stops as soon as a configuration does not validate. Use -k to override this behavior and validate as many configurations as possible. Provide an option to instruct the script to ignore a list of configurations that are expected to fail validation to be used in continuous integration setups - similar in concept to xfail mechanisms provided by most build systems with unit test support. Change-Id: I7d67a54993a6d5e00daf552d9d350c80411b997b Signed-off-by: Brad Bishop <[email protected]>
1 parent b2fd7ca commit c04b3f4

File tree

1 file changed

+145
-0
lines changed

1 file changed

+145
-0
lines changed

scripts/validate-configs.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
A tool for validating entity manager configurations.
5+
"""
6+
import argparse
7+
import json
8+
import jsonschema.validators
9+
import os
10+
import sys
11+
12+
DEFAULT_SCHEMA_FILENAME = "global.json"
13+
14+
15+
def main():
16+
parser = argparse.ArgumentParser(
17+
description="Entity manager configuration validator",
18+
)
19+
parser.add_argument(
20+
"-s", "--schema", help=(
21+
"Use the specified schema file instead of the default "
22+
"(__file__/../../schemas/global.json)"))
23+
parser.add_argument(
24+
"-c", "--config", action='append', help=(
25+
"Validate the specified configuration files (can be "
26+
"specified more than once) instead of the default "
27+
"(__file__/../../configurations/**.json)"))
28+
parser.add_argument(
29+
"-e", "--expected-fails", help=(
30+
"A file with a list of configurations to ignore should "
31+
"they fail to validate"))
32+
parser.add_argument(
33+
"-k", "--continue", action='store_true', help=(
34+
"keep validating after a failure"))
35+
parser.add_argument(
36+
"-v", "--verbose", action='store_true', help=(
37+
"be noisy"))
38+
args = parser.parse_args()
39+
40+
schema_file = args.schema
41+
if schema_file is None:
42+
try:
43+
source_dir = os.path.realpath(__file__).split(os.sep)[:-2]
44+
schema_file = os.sep + os.path.join(
45+
*source_dir, 'schemas', DEFAULT_SCHEMA_FILENAME)
46+
except Exception as e:
47+
sys.stderr.write(
48+
"Could not guess location of {}\n".format(
49+
DEFAULT_SCHEMA_FILENAME))
50+
sys.exit(2)
51+
52+
schema = {}
53+
try:
54+
with open(schema_file) as fd:
55+
schema = json.load(fd)
56+
except FileNotFoundError as e:
57+
sys.stderr.write(
58+
"Could not read schema file '{}'\n".format(schema_file))
59+
sys.exit(2)
60+
61+
config_files = args.config or []
62+
if len(config_files) == 0:
63+
try:
64+
source_dir = os.path.realpath(__file__).split(os.sep)[:-2]
65+
configs_dir = os.sep + os.path.join(*source_dir, 'configurations')
66+
data = os.walk(configs_dir)
67+
for root, _, files in data:
68+
for f in files:
69+
if f.endswith('.json'):
70+
config_files.append(os.path.join(root, f))
71+
except Exception as e:
72+
sys.stderr.write(
73+
"Could not guess location of configurations\n")
74+
sys.exit(2)
75+
76+
configs = []
77+
for config_file in config_files:
78+
try:
79+
with open(config_file) as fd:
80+
configs.append(json.load(fd))
81+
except FileNotFoundError as e:
82+
sys.stderr.write(
83+
"Could not parse config file '{}'\n".format(config_file))
84+
sys.exit(2)
85+
86+
expected_fails = []
87+
if args.expected_fails:
88+
try:
89+
with open(args.expected_fails) as fd:
90+
for line in fd:
91+
expected_fails.append(line.strip())
92+
except Exception as e:
93+
sys.stderr.write(
94+
"Could not read expected fails file '{}'\n".format(
95+
args.expected_fails))
96+
sys.exit(2)
97+
98+
base_uri = "file://{}/".format(
99+
os.path.split(os.path.realpath(schema_file))[0])
100+
resolver = jsonschema.RefResolver(base_uri, schema)
101+
validator = jsonschema.Draft7Validator(schema, resolver=resolver)
102+
103+
results = {
104+
"invalid": [],
105+
"unexpected_pass": [],
106+
}
107+
for config_file, config in zip(config_files, configs):
108+
name = os.path.split(config_file)[1]
109+
expect_fail = name in expected_fails
110+
try:
111+
validator.validate(config)
112+
if expect_fail:
113+
results["unexpected_pass"].append(name)
114+
if not getattr(args, "continue"):
115+
break
116+
except jsonschema.exceptions.ValidationError as e:
117+
if not expect_fail:
118+
results["invalid"].append(name)
119+
if args.verbose:
120+
print(e)
121+
if expect_fail or getattr(args, "continue"):
122+
continue
123+
break
124+
125+
exit_status = 0
126+
if len(results["invalid"]) + len(results["unexpected_pass"]):
127+
exit_status = 1
128+
unexpected_pass_suffix = " **"
129+
show_suffix_explanation = False
130+
print("results:")
131+
for f in config_files:
132+
if any([x in f for x in results["unexpected_pass"]]):
133+
show_suffix_explanation = True
134+
print(" '{}' passed!{}".format(f, unexpected_pass_suffix))
135+
if any([x in f for x in results["invalid"]]):
136+
print(" '{}' failed!".format(f))
137+
138+
if show_suffix_explanation:
139+
print("\n** configuration expected to fail")
140+
141+
sys.exit(exit_status)
142+
143+
144+
if __name__ == "__main__":
145+
main()

0 commit comments

Comments
 (0)