Skip to content

Commit 5028ac4

Browse files
committed
Tracks compliance with jmespath.org test suite.
1 parent 48b1ada commit 5028ac4

File tree

6 files changed

+146
-9
lines changed

6 files changed

+146
-9
lines changed

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ jobs:
2828
- name: Test with pytest
2929
run: |
3030
./scripts/sync-tests
31-
cd tests/ && py.test --cov jmespath --cov-report term-missing
31+
./scripts/run-tests

scripts/run-tests

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env python
2+
"""Run tests."""
3+
from subprocess import check_call
4+
5+
def main():
6+
check_call('cd tests && py.test --cov jmespath --cov-report term-missing', shell=True)
7+
8+
9+
if __name__ == '__main__':
10+
main()

scripts/sync-tests

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ from subprocess import check_call
66
def main():
77
check_call('git submodule update', shell=True)
88
check_call('cp -r jmespath.test/tests/* tests/compliance/', shell=True)
9+
check_call('git clone https://github.com/jmespath/jmespath.test tests/jmespath.org', shell=True)
910

1011

1112
if __name__ == '__main__':

tests/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jmespath.org/

tests/test_compliance.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ def _walk_files():
4040
if single_file is not None:
4141
yield os.path.abspath(single_file)
4242
else:
43-
for root, dirnames, filenames in os.walk(TEST_DIR):
44-
for filename in filenames:
45-
yield os.path.join(root, filename)
46-
for root, dirnames, filenames in os.walk(LEGACY_DIR):
47-
for filename in filenames:
48-
yield os.path.join(root, filename)
43+
for dir in [COMPLIANCE_DIR, LEGACY_DIR]:
44+
for root, dirnames, filenames in os.walk(dir):
45+
for filename in filenames:
46+
if filename.endswith(".json") and not filename.endswith("schema.json"):
47+
print(filename)
48+
yield os.path.join(root, filename)
4949

5050

5151
def load_cases(full_path):
@@ -92,8 +92,9 @@ def test_expression(given, expression, expected, filename):
9292
_compliance_tests('error')
9393
)
9494
def test_error_expression(given, expression, error, filename):
95-
if error not in ('syntax', 'invalid-type',
96-
'unknown-function', 'invalid-arity', 'invalid-value'):
95+
if error not in ('invalid-arity', 'invalid-type',
96+
'invalid-value', 'not-a-number', 'syntax',
97+
'unknown-function'):
9798
raise RuntimeError("Unknown error type '%s'" % error)
9899
try:
99100
(_, parsed) = _search_expression(given, expression, filename)

tests/test_compliance_org.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
## JMESPATH.ORG COMPLIANCE
2+
## run the following command to extract jmespath.org compliance test suite
3+
## git clone https://github.com/jmespath/jmespath.test tests/jmespath.org
4+
5+
import os
6+
from pprint import pformat
7+
from tests import OrderedDict
8+
from tests import json
9+
10+
import pytest
11+
12+
from jmespath.visitor import Options
13+
14+
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
15+
JMESPATH_ORG_DIR = os.path.join(TEST_DIR, 'jmespath.org')
16+
LEGACY_OPTIONS = Options(dict_cls=OrderedDict, enable_legacy_literals=True)
17+
18+
ExcludedTests = [
19+
"literal.json"
20+
]
21+
22+
def _compliance_tests(requested_test_type):
23+
for full_path in _walk_files():
24+
if full_path.endswith('.json'):
25+
for given, test_type, test_data in load_cases(full_path):
26+
t = test_data
27+
# Benchmark tests aren't run as part of the normal
28+
# test suite, so we only care about 'result' and
29+
# 'error' test_types.
30+
if test_type == 'result' and test_type == requested_test_type:
31+
yield (given, t['expression'],
32+
t['result'], os.path.basename(full_path))
33+
elif test_type == 'error' and test_type == requested_test_type:
34+
yield (given, t['expression'],
35+
t['error'], os.path.basename(full_path))
36+
37+
def _is_valid_test_file(filename):
38+
if filename.endswith(".json") and \
39+
not filename.endswith("schema.json") and \
40+
not os.path.basename(filename) in ExcludedTests:
41+
return True
42+
return False
43+
44+
def _walk_files():
45+
for dir in [JMESPATH_ORG_DIR]:
46+
for root, dirnames, filenames in os.walk(dir):
47+
for filename in filenames:
48+
if _is_valid_test_file(filename):
49+
yield os.path.join(root, filename)
50+
51+
def load_cases(full_path):
52+
all_test_data = json.load(open(full_path), object_pairs_hook=OrderedDict)
53+
for test_data in all_test_data:
54+
given = test_data['given']
55+
for case in test_data['cases']:
56+
if 'result' in case:
57+
test_type = 'result'
58+
elif 'error' in case:
59+
test_type = 'error'
60+
elif 'bench' in case:
61+
test_type = 'bench'
62+
else:
63+
raise RuntimeError("Unknown test type: %s" % json.dumps(case))
64+
yield (given, test_type, case)
65+
66+
67+
@pytest.mark.parametrize(
68+
'given, expression, expected, filename',
69+
_compliance_tests('result')
70+
)
71+
def test_expression(given, expression, expected, filename):
72+
try:
73+
(actual, parsed) = _search_expression(given, expression, filename)
74+
except ValueError as e:
75+
raise AssertionError(
76+
'jmespath expression failed to compile: "%s", error: %s"' %
77+
(expression, e))
78+
79+
expected_repr = json.dumps(expected, indent=4)
80+
actual_repr = json.dumps(actual, indent=4)
81+
error_msg = ("\n\n (%s) The expression '%s' was supposed to give:\n%s\n"
82+
"Instead it matched:\n%s\nparsed as:\n%s\ngiven:\n%s" % (
83+
filename, expression, expected_repr,
84+
actual_repr, pformat(parsed.parsed),
85+
json.dumps(given, indent=4)))
86+
error_msg = error_msg.replace(r'\n', '\n')
87+
assert actual == expected, error_msg
88+
89+
90+
@pytest.mark.parametrize(
91+
'given, expression, error, filename',
92+
_compliance_tests('error')
93+
)
94+
def test_error_expression(given, expression, error, filename):
95+
if error not in ('syntax', 'invalid-type',
96+
'unknown-function', 'invalid-arity', 'invalid-value'):
97+
raise RuntimeError("Unknown error type '%s'" % error)
98+
try:
99+
(_, parsed) = _search_expression(given, expression, filename)
100+
except ValueError:
101+
# Test passes, it raised a parse error as expected.
102+
pass
103+
except Exception as e:
104+
# Failure because an unexpected exception was raised.
105+
error_msg = ("\n\n (%s) The expression '%s' was suppose to be a "
106+
"syntax error, but it raised an unexpected error:\n\n%s" % (
107+
filename, expression, e))
108+
error_msg = error_msg.replace(r'\n', '\n')
109+
raise AssertionError(error_msg)
110+
else:
111+
error_msg = ("\n\n (%s) The expression '%s' was suppose to be a "
112+
"syntax error, but it successfully parsed as:\n\n%s" % (
113+
filename, expression, pformat(parsed.parsed)))
114+
error_msg = error_msg.replace(r'\n', '\n')
115+
raise AssertionError(error_msg)
116+
117+
def _search_expression(given, expression, filename):
118+
import jmespath.parser
119+
120+
options = LEGACY_OPTIONS
121+
122+
parsed = jmespath.compile(expression, options=options)
123+
actual = parsed.search(given, options=options)
124+
return (actual, parsed)

0 commit comments

Comments
 (0)