Skip to content

Commit c34982c

Browse files
committed
Merge pull request #30 from neo4j/1.0-tck-tests
1.0 tck tests - Added steps for Cypher compliance kit
2 parents 92df09b + 4dc2970 commit c34982c

File tree

9 files changed

+697
-242
lines changed

9 files changed

+697
-242
lines changed

.gitmodules

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
[submodule "neokit"]
2-
path = neokit
3-
url = https://github.com/nigelsmall/neokit.git

neokit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Subproject commit db7ab3580be3f0e09fcb2352408750f45c14a70e
1+
Subproject commit 23ffc81c7a1a1f16369aa1cea71d77d256e57c8d

runtests.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ echo "Running tests with $(python --version)"
6262
pip install --upgrade -r ${DRIVER_HOME}/test_requirements.txt
6363
echo ""
6464
TEST_RUNNER="coverage run -m ${UNITTEST} discover -vfs ${TEST}"
65-
EXAMPLES_RUNNER="coverage run -m ${UNITTEST} discover -vfs examples"
66-
BEHAVE_RUNNER="behave test/tck"
65+
BEHAVE_RUNNER="behave --tags=-db,-in_dev test/tck"
6766
if [ ${RUNNING} -eq 1 ]
6867
then
6968
${TEST_RUNNER}

test/tck/configure_feature_files.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
4+
# Copyright (c) 2002-2016 "Neo Technology,"
5+
# Network Engine for Objects in Lund AB [http://neotechnology.com]
6+
#
7+
# This file is part of Neo4j.
8+
#
9+
# Licensed under the Apache License, Version 2.0 (the "License");
10+
# you may not use this file except in compliance with the License.
11+
# You may obtain a copy of the License at
12+
#
13+
# http://www.apache.org/licenses/LICENSE-2.0
14+
#
15+
# Unless required by applicable law or agreed to in writing, software
16+
# distributed under the License is distributed on an "AS IS" BASIS,
17+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
# See the License for the specific language governing permissions and
19+
# limitations under the License.
20+
121
import os
222
import tarfile
323

24+
425
def clean_up():
526
dir_path = (os.path.dirname(os.path.realpath(__file__)))
627
files = os.listdir(dir_path)

test/tck/environment.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
1-
import logging
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
4+
# Copyright (c) 2002-2016 "Neo Technology,"
5+
# Network Engine for Objects in Lund AB [http://neotechnology.com]
6+
#
7+
# This file is part of Neo4j.
8+
#
9+
# Licensed under the Apache License, Version 2.0 (the "License");
10+
# you may not use this file except in compliance with the License.
11+
# You may obtain a copy of the License at
12+
#
13+
# http://www.apache.org/licenses/LICENSE-2.0
14+
#
15+
# Unless required by applicable law or agreed to in writing, software
16+
# distributed under the License is distributed on an "AS IS" BASIS,
17+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
# See the License for the specific language governing permissions and
19+
# limitations under the License.
220

321
from test.tck import tck_util
4-
from behave.log_capture import capture
522

623

724
def before_all(context):
8-
# -- SET LOG LEVEL: behave --logging-level=ERROR ...
9-
# on behave command-line or in "behave.ini".
1025
context.config.setup_logging()
1126

1227

13-
@capture
14-
def after_scenario(context, scenario):
15-
for step in scenario.steps:
16-
if step.status == 'failed':
17-
logging.error("Scenario :'%s' at step: '%s' failed! ", scenario.name, step.name)
18-
logging.debug("Expected result: %s", tck_util.as_cypher_text(context.expected))
19-
logging.debug("Actual result: %s", tck_util.as_cypher_text(context.results))
20-
if step.status == 'skipped':
21-
logging.warn("Scenario :'%s' at step: '%s' was skipped! ", scenario.name, step.name)
22-
if step.status == 'passed':
23-
logging.debug("Scenario :'%s' at step: '%s' was passed! ", scenario.name, step.name)
28+
def before_feature(context, feature):
29+
# Workaround. Behave has a different way of tagging than cucumber
30+
if "reset_database" in feature.tags:
31+
for scenario in feature.scenarios:
32+
scenario.tags.append("reset_database")
33+
34+
35+
def before_scenario(context, scenario):
36+
if "reset_database" in scenario.tags:
37+
tck_util.send_string("MATCH (n) DETACH DELETE n")
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
4+
# Copyright (c) 2002-2016 "Neo Technology,"
5+
# Network Engine for Objects in Lund AB [http://neotechnology.com]
6+
#
7+
# This file is part of Neo4j.
8+
#
9+
# Licensed under the Apache License, Version 2.0 (the "License");
10+
# you may not use this file except in compliance with the License.
11+
# You may obtain a copy of the License at
12+
#
13+
# http://www.apache.org/licenses/LICENSE-2.0
14+
#
15+
# Unless required by applicable law or agreed to in writing, software
16+
# distributed under the License is distributed on an "AS IS" BASIS,
17+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
# See the License for the specific language governing permissions and
19+
# limitations under the License.
20+
21+
import random
22+
import string
23+
24+
from behave import *
25+
26+
from neo4j.v1 import GraphDatabase
27+
from test.tck.tck_util import to_unicode, Type, send_string, send_parameters, string_to_type
28+
29+
from neo4j.v1 import compat
30+
use_step_matcher("re")
31+
32+
33+
@given("A running database")
34+
def step_impl(context):
35+
send_string("RETURN 1")
36+
37+
38+
@given("a value (?P<input>.+) of type (?P<bolt_type>.+)")
39+
def step_impl(context, input, bolt_type):
40+
context.expected = get_bolt_value(string_to_type(bolt_type), input)
41+
42+
43+
@given("a value of type (?P<bolt_type>.+)")
44+
def step_impl(context, bolt_type):
45+
context.expected = get_bolt_value(string_to_type(bolt_type), u' ')
46+
47+
48+
@given("a list value (?P<input>.+) of type (?P<bolt_type>.+)")
49+
def step_impl(context, input, bolt_type):
50+
context.expected = get_list_from_feature_file(input, string_to_type(bolt_type))
51+
52+
53+
@given("an empty list L")
54+
def step_impl(context):
55+
context.L = []
56+
57+
58+
@given("an empty map M")
59+
def step_impl(context):
60+
context.M = {}
61+
62+
63+
@given("a String of size (?P<size>\d+)")
64+
def step_impl(context, size):
65+
context.expected = get_random_string(int(size))
66+
67+
68+
@given("a List of size (?P<size>\d+) and type (?P<type>.+)")
69+
def step_impl(context, size, type):
70+
context.expected = get_list_of_random_type(int(size), string_to_type(type))
71+
72+
73+
@given("a Map of size (?P<size>\d+) and type (?P<type>.+)")
74+
def step_impl(context, size, type):
75+
context.expected = get_dict_of_random_type(int(size), string_to_type(type))
76+
77+
78+
@step("adding a table of lists to the list L")
79+
def step_impl(context):
80+
for row in context.table:
81+
context.L.append(get_list_from_feature_file(row[1], row[0]))
82+
83+
84+
@step("adding a table of values to the list L")
85+
def step_impl(context):
86+
for row in context.table:
87+
context.L.append(get_bolt_value(row[0], row[1]))
88+
89+
90+
@step("adding a table of values to the map M")
91+
def step_impl(context):
92+
for row in context.table:
93+
context.M['a%d' % len(context.M)] = get_bolt_value(row[0], row[1])
94+
95+
96+
@step("adding map M to list L")
97+
def step_impl(context):
98+
context.L.append(context.M)
99+
100+
101+
@when("adding a table of lists to the map M")
102+
def step_impl(context):
103+
for row in context.table:
104+
context.M['a%d' % len(context.M)] = get_list_from_feature_file(row[1], row[0])
105+
106+
107+
@step("adding a copy of map M to map M")
108+
def step_impl(context):
109+
context.M['a%d' % len(context.M)] = context.M.copy()
110+
111+
112+
@when("the driver asks the server to echo this value back")
113+
def step_impl(context):
114+
context.results = {"as_string": send_string("RETURN " + as_cypher_text(context.expected)),
115+
"as_parameters": send_parameters("RETURN {input}", {'input': context.expected})}
116+
117+
118+
@when("the driver asks the server to echo this list back")
119+
def step_impl(context):
120+
context.expected = context.L
121+
context.results = {"as_string": send_string("RETURN " + as_cypher_text(context.expected)),
122+
"as_parameters": send_parameters("RETURN {input}", {'input': context.expected})}
123+
124+
125+
@when("the driver asks the server to echo this map back")
126+
def step_impl(context):
127+
context.expected = context.M
128+
context.results = {"as_string": send_string("RETURN " + as_cypher_text(context.expected)),
129+
"as_parameters": send_parameters("RETURN {input}", {'input': context.expected})}
130+
131+
132+
@step("the value given in the result should be the same as what was sent")
133+
def step_impl(context):
134+
assert len(context.results) > 0
135+
for result in context.results.values():
136+
result_value = result[0].values()[0]
137+
assert result_value == context.expected
138+
139+
140+
@given("A driver containing a session pool of size (?P<size>\d+)")
141+
def step_impl(context, size):
142+
context.driver = GraphDatabase.driver("bolt://localhost", max_pool_size=1)
143+
144+
145+
@when("acquiring a session from the driver")
146+
def step_impl(context):
147+
context.session = context.driver.session()
148+
149+
150+
@step('with the session running the Cypher statement "(?P<statement>.+)"')
151+
def step_impl(context, statement):
152+
context.cursor = context.session.run(statement)
153+
154+
155+
@step("pulling the result records")
156+
def step_impl(context):
157+
context.cursor.consume()
158+
159+
160+
@then("acquiring a session from the driver should not be possible")
161+
def step_impl(context):
162+
try:
163+
context.session = context.driver.session()
164+
except:
165+
assert True
166+
else:
167+
assert False
168+
169+
170+
@then("acquiring a session from the driver should be possible")
171+
def step_impl(context):
172+
_ = context.driver.session()
173+
assert True
174+
175+
176+
def get_bolt_value(type, value):
177+
if type == Type.INTEGER:
178+
return int(value)
179+
if type == Type.FLOAT:
180+
return float(value)
181+
if type == Type.STRING:
182+
return to_unicode(value)
183+
if type == Type.NULL:
184+
return None
185+
if type == Type.BOOLEAN:
186+
return bool(value)
187+
raise ValueError('No such type : %s' % type)
188+
189+
190+
def as_cypher_text(expected):
191+
if expected is None:
192+
return "Null"
193+
if isinstance(expected, (str, compat.string)):
194+
return '"' + expected + '"'
195+
if isinstance(expected, float):
196+
return repr(expected).replace('+', '')
197+
if isinstance(expected, list):
198+
l = u'['
199+
for i, val in enumerate(expected):
200+
l += as_cypher_text(val)
201+
if i < len(expected) - 1:
202+
l += u','
203+
l += u']'
204+
return l
205+
if isinstance(expected, dict):
206+
d = u'{'
207+
for i, (key, val) in enumerate(expected.items()):
208+
d += to_unicode(key) + ':'
209+
d += as_cypher_text(val)
210+
if i < len(expected.items()) - 1:
211+
d += u','
212+
d += u'}'
213+
return d
214+
else:
215+
return to_unicode(expected)
216+
217+
218+
def get_list_from_feature_file(string_list, bolt_type):
219+
inputs = string_list.strip('[]')
220+
inputs = inputs.split(',')
221+
list_to_return = []
222+
for value in inputs:
223+
list_to_return.append(get_bolt_value(bolt_type, value))
224+
return list_to_return
225+
226+
227+
def get_random_string(size):
228+
return u''.join(
229+
random.SystemRandom().choice(list(string.ascii_uppercase + string.digits + string.ascii_lowercase)) for _ in
230+
range(size))
231+
232+
233+
def get_random_bool():
234+
return bool(random.randint(0, 1))
235+
236+
237+
def _get_random_func(type):
238+
def get_none():
239+
return None
240+
241+
if type == Type.INTEGER:
242+
fu = random.randint
243+
args = [-9223372036854775808, 9223372036854775808]
244+
elif type == Type.FLOAT:
245+
fu = random.random
246+
args = []
247+
elif type == Type.STRING:
248+
fu = get_random_string
249+
args = [3]
250+
elif type == Type.NULL:
251+
fu = get_none
252+
args = []
253+
elif type == Type.BOOLEAN:
254+
fu = get_random_bool
255+
args = []
256+
else:
257+
raise ValueError('No such type : %s' % type)
258+
return (fu, args)
259+
260+
261+
def get_list_of_random_type(size, type):
262+
fu, args = _get_random_func(type)
263+
return [fu(*args) for _ in range(size)]
264+
265+
266+
def get_dict_of_random_type(size, type):
267+
fu, args = _get_random_func(type)
268+
return {'a%d' % i: fu(*args) for i in range(size)}

0 commit comments

Comments
 (0)