Skip to content

Commit f7e3e69

Browse files
committed
Merge branch 'hypothesis' into develop
* hypothesis: Move hypothesis tests other to new extra/ dir Integrate hypothesis testing into jmespath
2 parents 4eaa65b + 50b953c commit f7e3e69

File tree

7 files changed

+108
-2
lines changed

7 files changed

+108
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ nosetests.xml
3535

3636
*.out
3737
_build
38+
39+
.hypothesis

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ install:
1414
- pip install -r requirements.txt
1515
- python setup.py bdist_wheel
1616
- pip install dist/*.whl
17-
script: cd tests/ && nosetests --with-coverage --cover-package jmespath .
17+
script:
18+
- cd tests/ && nosetests --with-coverage --cover-package jmespath .
19+
- if [[ $TRAVIS_PYTHON_VERSION != '2.6' ]]; then JP_MAX_EXAMPLES=10000 nosetests ../extra/test_hypothesis.py; fi
1820
after_success:
1921
- codecov

extra/test_hypothesis.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Test suite using hypothesis to generate test cases.
2+
# This is in a standalone module so that these tests
3+
# can a) be run separately and b) allow for customization
4+
# via env var for longer runs in travis.
5+
import os
6+
import sys
7+
8+
from nose.plugins.skip import SkipTest
9+
from hypothesis import given, settings, assume, HealthCheck
10+
import hypothesis.strategies as st
11+
12+
from jmespath import lexer
13+
from jmespath import parser
14+
from jmespath import exceptions
15+
16+
17+
if sys.version_info[:2] == (2, 6):
18+
raise RuntimeError("Hypothesis tests are not supported on python2.6. "
19+
"Use python2.7, or python3.3 and greater.")
20+
21+
22+
RANDOM_JSON = st.recursive(
23+
st.floats() | st.booleans() | st.text() | st.none(),
24+
lambda children: st.lists(children) | st.dictionaries(st.text(), children)
25+
)
26+
27+
28+
MAX_EXAMPLES = int(os.environ.get('JP_MAX_EXAMPLES', 1000))
29+
BASE_SETTINGS = {
30+
'max_examples': MAX_EXAMPLES,
31+
'suppress_health_check': [HealthCheck.too_slow],
32+
}
33+
34+
35+
# For all of these tests they verify these proprties:
36+
# either the operation succeeds or it raises a JMESPathError.
37+
# If any other exception is raised then we error out.
38+
@settings(**BASE_SETTINGS)
39+
@given(st.text())
40+
def test_lexer_api(expr):
41+
try:
42+
tokens = list(lexer.Lexer().tokenize(expr))
43+
except exceptions.JMESPathError as e:
44+
return
45+
except Exception as e:
46+
raise AssertionError("Non JMESPathError raised: %s" % e)
47+
assert isinstance(tokens, list)
48+
49+
50+
@settings(**BASE_SETTINGS)
51+
@given(st.text())
52+
def test_parser_api_from_str(expr):
53+
# Same a lexer above with the assumption that we're parsing
54+
# a valid sequence of tokens.
55+
try:
56+
list(lexer.Lexer().tokenize(expr))
57+
except exceptions.JMESPathError as e:
58+
# We want to try to parse things that tokenize
59+
# properly.
60+
assume(False)
61+
try:
62+
ast = parser.Parser().parse(expr)
63+
except exceptions.JMESPathError as e:
64+
return
65+
except Exception as e:
66+
raise AssertionError("Non JMESPathError raised: %s" % e)
67+
assert isinstance(ast.parsed, dict)
68+
69+
70+
@settings(**BASE_SETTINGS)
71+
@given(expr=st.text(), data=RANDOM_JSON)
72+
def test_search_api(expr, data):
73+
try:
74+
ast = parser.Parser().parse(expr)
75+
except exceptions.JMESPathError as e:
76+
# We want to try to parse things that tokenize
77+
# properly.
78+
assume(False)
79+
try:
80+
ast.search(data)
81+
except exceptions.JMESPathError as e:
82+
return
83+
except Exception as e:
84+
raise AssertionError("Non JMESPathError raised: %s" % e)

jmespath/lexer.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,15 @@ def tokenize(self, expression):
8787
elif self._current == '!':
8888
yield self._match_or_else('=', 'ne', 'not')
8989
elif self._current == '=':
90-
yield self._match_or_else('=', 'eq', 'unknown')
90+
if self._next() == '=':
91+
yield {'type': 'eq', 'value': '==',
92+
'start': self._position - 1, 'end': self._position}
93+
self._next()
94+
else:
95+
raise LexerError(
96+
lexer_position=self._position - 1,
97+
lexer_value='=',
98+
message="Unknown token =")
9199
else:
92100
raise LexerError(lexer_position=self._position,
93101
lexer_value=self._current,

jmespath/parser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Parser(object):
3939
'eof': 0,
4040
'unquoted_identifier': 0,
4141
'quoted_identifier': 0,
42+
'literal': 0,
4243
'rbracket': 0,
4344
'rparen': 0,
4445
'comma': 0,

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ py==1.4.12
33
tox==1.4.2
44
wheel==0.24.0
55
coverage==3.7.1
6+
hypothesis==3.1.0

tests/compliance/syntax.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@
9595
{
9696
"expression": "!",
9797
"error": "syntax"
98+
},
99+
{
100+
"expression": "@=",
101+
"error": "syntax"
102+
},
103+
{
104+
"expression": "@``",
105+
"error": "syntax"
98106
}
99107
]
100108
},

0 commit comments

Comments
 (0)