Skip to content

Commit 14b92fc

Browse files
committed
Merge pull request #25 from neo4j/1.0-tck-tests
Added TCK for python
2 parents 133829a + 89a0324 commit 14b92fc

File tree

7 files changed

+315
-0
lines changed

7 files changed

+315
-0
lines changed

runtests.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +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+
BEHAVE_RUNNER="behave test/tck"
6566
if [ ${RUNNING} -eq 1 ]
6667
then
6768
${TEST_RUNNER}
@@ -73,6 +74,11 @@ else
7374
then
7475
coverage report --show-missing
7576
fi
77+
python -c 'from test.tck.configure_feature_files import *; set_up()'
78+
echo "Feature files downloaded"
79+
neokit/neorun ${NEORUN_OPTIONS} "${BEHAVE_RUNNER}" ${VERSIONS}
80+
python -c 'from test.tck.configure_feature_files import *; clean_up()'
81+
echo "Feature files removed"
7682
fi
7783

7884
# Exit correctly

test/tck/__init__.py

Whitespace-only changes.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import os
2+
import tarfile
3+
4+
def clean_up():
5+
dir_path = (os.path.dirname(os.path.realpath(__file__)))
6+
files = os.listdir(dir_path)
7+
for f in files:
8+
if not os.path.isdir(f) and ".feature" in f:
9+
os.remove(os.path.join(dir_path, f))
10+
11+
12+
def set_up():
13+
dir_path = (os.path.dirname(os.path.realpath(__file__)))
14+
url = "https://s3-eu-west-1.amazonaws.com/remoting.neotechnology.com/driver-compliance/tck.tar.gz"
15+
file_name = url.split('/')[-1]
16+
_download_tar(url,file_name)
17+
18+
tar = tarfile.open(file_name)
19+
tar.extractall(dir_path)
20+
tar.close()
21+
os.remove(file_name)
22+
23+
24+
def _download_tar(url, file_name):
25+
try:
26+
import urllib2
27+
tar = open(file_name, 'w')
28+
response = urllib2.urlopen(url)
29+
block_sz = 1024
30+
while True:
31+
buffer = response.read(block_sz)
32+
if not buffer:
33+
break
34+
tar.write(buffer)
35+
tar.close()
36+
except ImportError:
37+
from urllib import request
38+
request.urlretrieve(url, file_name)

test/tck/environment.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import logging
2+
3+
from test.tck import tck_util
4+
from behave.log_capture import capture
5+
6+
7+
def before_all(context):
8+
# -- SET LOG LEVEL: behave --logging-level=ERROR ...
9+
# on behave command-line or in "behave.ini".
10+
context.config.setup_logging()
11+
12+
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)

test/tck/steps/bolt_type_steps.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from behave import *
2+
3+
from test.tck import tck_util
4+
5+
use_step_matcher("re")
6+
7+
8+
@given("A running database")
9+
def step_impl(context):
10+
return None
11+
# check if running
12+
13+
14+
@given("a value (?P<input>.+) of type (?P<bolt_type>.+)")
15+
def step_impl(context, input, bolt_type):
16+
context.expected = tck_util.get_bolt_value(bolt_type, input)
17+
18+
19+
@given("a value of type (?P<bolt_type>.+)")
20+
def step_impl(context, bolt_type):
21+
context.expected = tck_util.get_bolt_value(bolt_type, u' ')
22+
23+
24+
@given("a list value (?P<input>.+) of type (?P<bolt_type>.+)")
25+
def step_impl(context, input, bolt_type):
26+
context.expected = tck_util.get_list_from_feature_file(input, bolt_type)
27+
28+
29+
@given("an empty list L")
30+
def step_impl(context):
31+
context.L = []
32+
33+
34+
@given("an empty map M")
35+
def step_impl(context):
36+
context.M = {}
37+
38+
39+
@given("a String of size (?P<size>\d+)")
40+
def step_impl(context, size):
41+
context.expected = tck_util.get_random_string(int(size))
42+
43+
44+
@given("a List of size (?P<size>\d+) and type (?P<type>.+)")
45+
def step_impl(context, size, type):
46+
context.expected = tck_util.get_list_of_random_type(int(size), type)
47+
48+
49+
@given("a Map of size (?P<size>\d+) and type (?P<type>.+)")
50+
def step_impl(context, size, type):
51+
context.expected = tck_util.get_dict_of_random_type(int(size), type)
52+
53+
54+
@step("adding a table of lists to the list L")
55+
def step_impl(context):
56+
for row in context.table:
57+
context.L.append(tck_util.get_list_from_feature_file(row[1], row[0]))
58+
59+
60+
@step("adding a table of values to the list L")
61+
def step_impl(context):
62+
for row in context.table:
63+
context.L.append(tck_util.get_bolt_value(row[0], row[1]))
64+
65+
66+
@step("adding a table of values to the map M")
67+
def step_impl(context):
68+
for row in context.table:
69+
context.M['a%d' % len(context.M)] = tck_util.get_bolt_value(row[0], row[1])
70+
71+
72+
@step("adding map M to list L")
73+
def step_impl(context):
74+
context.L.append(context.M)
75+
76+
77+
@when("adding a table of lists to the map M")
78+
def step_impl(context):
79+
for row in context.table:
80+
context.M['a%d' % len(context.M)] = tck_util.get_list_from_feature_file(row[1], row[0])
81+
82+
83+
@step("adding a copy of map M to map M")
84+
def step_impl(context):
85+
context.M['a%d' % len(context.M)] = context.M.copy()
86+
87+
88+
@when("the driver asks the server to echo this value back")
89+
def step_impl(context):
90+
context.results = {}
91+
context.results["as_string"] = tck_util.send_string("RETURN " + tck_util.as_cypher_text(context.expected))
92+
context.results["as_parameters"] = tck_util.send_parameters("RETURN {input}", {'input': context.expected})
93+
94+
95+
@when("the driver asks the server to echo this list back")
96+
def step_impl(context):
97+
context.expected = context.L
98+
context.results = {}
99+
context.results["as_string"] = tck_util.send_string("RETURN " + tck_util.as_cypher_text(context.expected))
100+
context.results["as_parameters"] = tck_util.send_parameters("RETURN {input}", {'input': context.expected})
101+
102+
103+
@when("the driver asks the server to echo this map back")
104+
def step_impl(context):
105+
context.expected = context.M
106+
context.results = {}
107+
context.results["as_string"] = tck_util.send_string("RETURN " + tck_util.as_cypher_text(context.expected))
108+
context.results["as_parameters"] = tck_util.send_parameters("RETURN {input}", {'input': context.expected})
109+
110+
111+
@then("the result returned from the server should be a single record with a single value")
112+
def step_impl(context):
113+
assert context.results
114+
for result in context.results.values():
115+
assert len(result) == 1
116+
assert len(result[0]) == 1
117+
118+
119+
@step("the value given in the result should be the same as what was sent")
120+
def step_impl(context):
121+
assert len(context.results) > 0
122+
for result in context.results.values():
123+
result_value = result[0].values()[0]
124+
assert result_value == context.expected

test/tck/tck_util.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import string
2+
import random
3+
from neo4j.v1 import compat
4+
5+
from neo4j.v1 import GraphDatabase
6+
7+
driver = GraphDatabase.driver("bolt://localhost")
8+
9+
10+
def send_string(text):
11+
session = driver.session()
12+
result = session.run(text)
13+
session.close()
14+
return result
15+
16+
17+
def send_parameters(statement, parameters):
18+
session = driver.session()
19+
result = session.run(statement, parameters)
20+
session.close()
21+
return result
22+
23+
24+
def get_bolt_value(type, value):
25+
if type == 'Integer':
26+
return int(value)
27+
if type == 'Float':
28+
return float(value)
29+
if type == 'String':
30+
return to_unicode(value)
31+
if type == 'Null':
32+
return None
33+
if type == 'Boolean':
34+
return bool(value)
35+
raise ValueError('No such type : %s' % type)
36+
37+
38+
def as_cypher_text(expected):
39+
if expected is None:
40+
return "Null"
41+
if isinstance(expected, (str, compat.string)):
42+
return '"' + expected + '"'
43+
if isinstance(expected, float):
44+
return repr(expected).replace('+', '')
45+
if isinstance(expected, list):
46+
l = u'['
47+
for i, val in enumerate(expected):
48+
l += as_cypher_text(val)
49+
if i < len(expected)-1:
50+
l+= u','
51+
l += u']'
52+
return l
53+
if isinstance(expected, dict):
54+
d = u'{'
55+
for i, (key, val) in enumerate(expected.items()):
56+
d += to_unicode(key) + ':'
57+
d += as_cypher_text(val)
58+
if i < len(expected.items())-1:
59+
d+= u','
60+
d += u'}'
61+
return d
62+
else:
63+
return to_unicode(expected)
64+
65+
66+
def get_list_from_feature_file(string_list, bolt_type):
67+
inputs = string_list.strip('[]')
68+
inputs = inputs.split(',')
69+
list_to_return = []
70+
for value in inputs:
71+
list_to_return.append(get_bolt_value(bolt_type, value))
72+
return list_to_return
73+
74+
75+
def get_random_string(size):
76+
return u''.join(
77+
random.SystemRandom().choice(list(string.ascii_uppercase + string.digits + string.ascii_lowercase)) for _ in
78+
range(size))
79+
80+
81+
def get_random_bool():
82+
return bool(random.randint(0, 1))
83+
84+
85+
def _get_random_func(type):
86+
def get_none():
87+
return None
88+
89+
if type == 'Integer':
90+
fu = random.randint
91+
args = [-9223372036854775808, 9223372036854775808]
92+
elif type == 'Float':
93+
fu = random.random
94+
args = []
95+
elif type == 'String':
96+
fu = get_random_string
97+
args = [3]
98+
elif type == 'Null':
99+
fu = get_none
100+
args = []
101+
elif type == 'Boolean':
102+
fu = get_random_bool
103+
args = []
104+
else:
105+
raise ValueError('No such type : %s' % type)
106+
return (fu, args)
107+
108+
109+
def get_list_of_random_type(size, type):
110+
fu, args = _get_random_func(type)
111+
return [fu(*args) for _ in range(size)]
112+
113+
114+
def get_dict_of_random_type(size, type):
115+
fu, args = _get_random_func(type)
116+
return {'a%d' % i: fu(*args) for i in range(size)}
117+
118+
def to_unicode(val):
119+
try:
120+
return unicode(val)
121+
except NameError:
122+
return str(val)
123+

test_requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
behave
12
coverage
23
teamcity-messages

0 commit comments

Comments
 (0)