forked from OvertureMaps/schema
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test.sh
executable file
·243 lines (216 loc) · 5.54 KB
/
test.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
#!/usr/bin/env bash
set -eo pipefail
shopt -s lastpipe extglob
self="$(basename "$0")"
title="$self: Verify Overture schema"
declare -A modes
declare -a patterns
function usage() {
>&2 <<EOF cat
usage: $self [OPTIONS] [PATTERNS]
(or: $self --help)
EOF
}
function help() {
>&2 <<EOF cat
$title
OPTIONS:
-m, --mode=MODE validation mode - schema|examples|counterexamples
this argument maybe specified more than once, e.g.
\`-m schema -m examples\`. if this argument is omitted,
all validation modes are run.
EXAMPLES:
$self --help
$self
$self -m schema
$self -m examples -m counterexamples "transportation/.*\.json$"
EOF
}
function emit() {
>&2 printf '%s: %s\n' "$self" "$*"
}
function bad_usage() {
emit "$*"
usage
exit 64
}
function parse_args() {
while (($#)); do
local arg="$1"
shift
case "$arg" in
-m|--mode)
add_mode "$1"
shift
;;
--mode=*)
add_mode "${arg/#*=/}"
;;
-h|--help)
usage
echo
help
exit 0
;;
*)
patterns=("$arg" "$@")
break
;;
esac
done
if [ "${#modes[@]}" -eq 0 ]; then
modes=([schema]=yes [examples]=yes [counterexamples]=yes)
fi
}
function add_mode() {
mode="$1"
case "$mode" in
s|sc|sch|sche|schem|schema)
mode=schema
;;
e|ex|exa|exam|examp|exampl|example|examples)
mode=examples
;;
c|co|cou|coun|count|counte|counter|countere|counterex|counterexa|counterexam|counterexamp|counterexampl|counterexample|counterexamples)
mode=counterexamples
;;
*)
bad_usage "invalid mode: '$mode'. valid modes are: schema|examples|counterexamples"
;;
esac
modes["$mode"]=yes
}
schema_file=schema/schema.yaml
function match() {
if [ "${#patterns}" == 0 ]; then
return 0
else
candidate="$1"
for pattern in "${patterns[@]}"; do
if [[ "$candidate" =~ $pattern ]]; then
return 0
fi
done
return 1
fi
}
function verify() {
local mode="$1"
local instance_file="${2:-}"
local -a jv_args=(-assertformat -assertcontent "$schema_file")
if [ -n "$instance_file" ]; then
jv_args+=("$instance_file")
fi
case "$mode" in
quiet)
jv "${jv_args[@]}" 2>/dev/null
;;
simple)
jv "${jv_args[@]}"
;;
*)
jv -output "$mode" "${jv_args[@]}"
;;
esac
}
function expected_errors() {
local instance_file="$1"
local type
type=$(yq -r '.properties | type' "$instance_file")
if [[ "$type" != "object" && "$type" != "!!map" ]]; then
return 1
fi
yq -r '(.properties.ext_expected_errors // []) | .[]' "$instance_file"
}
function schema() {
echo "---- VERIFYING schema ----"
printf "%s..." "$schema_file"
if verify quiet; then
echo OK
else
echo FAILED
printf "\nthe schema itself is invalid.\n"
verify detailed
fi
}
function examples() {
echo "---- VERIFYING examples ----"
find examples -type f | sort | while read -r instance_file; do
if ! [[ "$instance_file" == *.yaml ]]; then
printf "%s...FAILED\nexample instance '%s' is EXPECTED to be a .yaml file but ACTUALLY it is not.\n" "$instance_file" "$instance_file"
elif ! match "$instance_file"; then
continue
fi
printf "%s..." "$instance_file"
if verify quiet "$instance_file"; then
echo OK
else
echo FAILED
printf "\nexample instance '%s' is EXPECTED to pass validation but ACTUALLY it failed.\n" "$instance_file"
verify detailed "$instance_file"
fi
done
}
function counterexamples() {
echo "---- VERIFYING counterexamples ----"
yq_installed=
if command -v yq >/dev/null; then
yq_installed=true
else
>&2 printf "WARNING: yq is not installed. Install yq for higher-fidelity counterexample testing.\n"
fi
find counterexamples -type f | sort | while read -r instance_file; do
if ! match "$instance_file"; then
continue
fi
printf "%s..." "$instance_file"
declare -a expected_errors
if verify quiet "$instance_file"; then
echo FAILED
printf "\ncounterexample instance '%s' is EXPECTED to fail validation but ACTUALLY it passed.\n" "$instance_file"
exit 1
elif [ -z "$yq_installed" ] || ! expected_errors "$instance_file" | mapfile -t expected_errors || [ ${#expected_errors} == 0 ]; then
echo OK
else
declare -a actual_errors
(verify simple "$instance_file" || true) 2>&1 | mapfile -t actual_errors
for expected_error in "${expected_errors[@]}"; do
for actual_error in "${actual_errors[@]}"; do
if [[ "$actual_error" == *"$expected_error"* ]]; then
continue 2
fi
done
echo FAILED
printf "\ncounterexample instance '%s' is EXPECTED to trigger the following validation error but ACTUALLY it did not.\n\n" "$instance_file"
printf "%s\n" "------------------------ MISSED EXPECTED ERROR -----------------------"
printf "%s\n\n" "$expected_error"
printf "%s\n" "---------------------- ACTUAL VALIDATION OUTPUT ----------------------"
printf "%s\n" "${actual_errors[@]}"
exit 1
done
echo OK
fi
done
}
parse_args "$@"
need_newline=
function optional_newline() {
if [ "$need_newline" == yes ]; then
echo
need_newline=no
fi
}
if [ "${modes[schema]}" == yes ]; then
schema
need_newline=yes
fi
if [ "${modes[examples]}" == yes ]; then
optional_newline
examples
need_newline=yes
fi
if [ "${modes[counterexamples]}" == yes ]; then
optional_newline
counterexamples
need_newline=yes
fi