test: new test framework to compare json parts

This makes it easier to write fairly compact, readable tests of json
output, without needing to sanitize away parts that we don't care
about.

Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
This commit is contained in:
Jameson Graef Rollins 2019-05-27 18:35:10 +00:00 committed by David Bremner
parent a6b0772b60
commit 03839a8110
2 changed files with 138 additions and 0 deletions

114
test/json_check_nodes.py Executable file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env python
import re
import sys
import json
EXPR_RE = re.compile('(?P<label>[a-zA-Z0-9_-]+):(?P<address>[^=!]+)(?:(?P<type>[=!])(?P<val>.*))?', re.DOTALL|re.MULTILINE)
if len(sys.argv) < 2:
sys.exit('usage: '+ sys.argv[0] + """ EXPR [EXPR]
Takes json data on stdin and evaluates test expressions specified in
arguments. Each test is evaluated, and output is printed only if the
test fails. If any test fails the return value of execution will be
non-zero.
EXPR can be one of following types:
Value test: test that object in json data found at address is equal to
specified value:
label:address=value
Existence test: test that dict or list in json data found at address
does *not* contain the specified key:
label:address!key
Extract: extract object from json data found at address and print
label:address
Results are printed to stdout prefixed by expression label. In all
cases the test will fail if object does not exist in data.
Example:
0 $ echo '["a", "b", {"c": 1}]' | python3 json_check_nodes.py 'second_d:[1]="d"' 'no_c:[2]!"c"'
second_d: value not equal: data[1] = 'b' != 'd'
no_c: dict contains key: data[2]["c"] = 1
1 $
""")
# parse expressions from arguments
exprs = []
for expr in sys.argv[1:]:
m = re.match(EXPR_RE, expr)
if not m:
sys.exit("Invalid expression: {}".format(expr))
exprs.append(m)
data = json.load(sys.stdin)
fail = False
for expr in exprs:
# print(expr.groups(),fail)
e = 'data{}'.format(expr.group('address'))
try:
val = eval(e)
except SyntaxError:
fail = True
print("{}: syntax error on evaluation of object: {}".format(
expr.group('label'), e))
continue
except:
fail = True
print("{}: object not found: data{}".format(
expr.group('label'), expr.group('address')))
continue
if expr.group('type') == '=':
try:
obj_val = json.loads(expr.group('val'))
except:
fail = True
print("{}: error evaluating value: {}".format(
expr.group('label'), expr.group('address')))
continue
if val != obj_val:
fail = True
print("{}: value not equal: data{} = {} != {}".format(
expr.group('label'), expr.group('address'), repr(val), repr(obj_val)))
elif expr.group('type') == '!':
if not isinstance(val, (dict, list)):
fail = True
print("{}: not a dict or a list: data{}".format(
expr.group('label'), expr.group('address')))
continue
try:
idx = json.loads(expr.group('val'))
if idx in val:
fail = True
print("{}: {} contains key: {}[{}] = {}".format(
expr.group('label'), type(val).__name__, e, expr.group('val'), val[idx]))
except SyntaxError:
fail = True
print("{}: syntax error on evaluation of value: {}".format(
expr.group('label'), expr.group('val')))
continue
elif expr.group('type') is None:
print("{}: {}".format(expr.group('label'), val))
if fail:
sys.exit(1)
sys.exit(0)

View file

@ -507,6 +507,30 @@ test_sort_json () {
"import sys, json; json.dump(sorted(json.load(sys.stdin)),sys.stdout)"
}
# test for json objects:
# read the source of test/json_check_nodes.py (or the output when
# invoking it without arguments) for an explanation of the syntax.
test_json_nodes () {
exec 1>&6 2>&7 # Restore stdout and stderr
if [ -z "$inside_subtest" ]; then
error "bug in the test script: test_json_eval without test_begin_subtest"
fi
inside_subtest=
test "$#" > 0 ||
error "bug in the test script: test_json_nodes needs at least 1 parameter"
if ! test_skip "$test_subtest_name"
then
output=$(PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON "$TEST_DIRECTORY"/json_check_nodes.py "$@")
if [ "$?" = 0 ]
then
test_ok_
else
test_failure_ "$output"
fi
fi
}
test_emacs_expect_t () {
test "$#" = 1 ||
error "bug in the test script: not 1 parameter to test_emacs_expect_t"