diff --git a/neokit b/neokit
index 225f07d2d..8fc30fd9f 160000
--- a/neokit
+++ b/neokit
@@ -1 +1 @@
-Subproject commit 225f07d2d8cd8082ac1bfb8229c8a413be82c546
+Subproject commit 8fc30fd9fa5145fdd8b6627f3525a66b0213becc
diff --git a/test/tck/configure_feature_files.py b/test/tck/configure_feature_files.py
index eec48766f..e0c2768ac 100644
--- a/test/tck/configure_feature_files.py
+++ b/test/tck/configure_feature_files.py
@@ -56,4 +56,4 @@ def _download_tar(url, file_name):
tar.close()
except ImportError:
from urllib import request
- request.urlretrieve(url, file_name)
\ No newline at end of file
+ request.urlretrieve(url, file_name)
diff --git a/test/tck/environment.py b/test/tck/environment.py
index 3e08a3a56..222b71ebd 100644
--- a/test/tck/environment.py
+++ b/test/tck/environment.py
@@ -34,4 +34,4 @@ def before_feature(context, feature):
def before_scenario(context, scenario):
if "reset_database" in scenario.tags:
- tck_util.send_string("MATCH (n) DETACH DELETE n")
\ No newline at end of file
+ tck_util.send_string("MATCH (n) DETACH DELETE n")
diff --git a/test/tck/resultparser.py b/test/tck/resultparser.py
new file mode 100644
index 000000000..75bd3789a
--- /dev/null
+++ b/test/tck/resultparser.py
@@ -0,0 +1,200 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+# Copyright (c) 2002-2016 "Neo Technology,"
+# Network Engine for Objects in Lund AB [http://neotechnology.com]
+#
+# This file is part of Neo4j.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import re
+from neo4j.v1 import Node, Relationship, Path
+from tck_util import TestValue
+
+
+def parse_values_to_comparable(row):
+ return _parse(row, True)
+
+
+def parse_values(row):
+ return _parse(row, False)
+
+
+def _parse(row, comparable=False):
+ if is_array(row):
+ values = get_array(row)
+ result = []
+ for val in values:
+ result.append(_parse(val, comparable))
+ return result
+ elif is_map(row):
+ if comparable:
+ raise ValueError("No support for pasing maps of Node/Rels/paths atm")
+ return get_map(row)
+ else:
+ if comparable:
+ return value_to_comparable_object(row)
+ else:
+ return value_to_object(row)
+
+
+def is_array(val):
+ return val[0] == '[' and val[-1] == ']' and val[1] != ':'
+
+
+def get_array(val):
+ return val[1:-1].split(',')
+
+
+def is_map(val):
+ return val[0] == '{' and val[-1] == '}'
+
+
+def get_map(val):
+ return json.loads(val)
+
+
+def value_to_comparable_object(val):
+ return TestValue(value_to_object(val))
+
+
+def value_to_object(val):
+ val = val.strip()
+ PATH = '^(<\().*(\)>)$'
+ NODE = '^(\().*(\))$'
+ RELATIONSHIP = '^(\[:).*(\])$'
+ INTEGER = '^(-?[0-9]+)$'
+ STRING = '^(").*(")$'
+ NULL = '^null$'
+ BOOLEAN = '^(false|true)$'
+ # TEST FLOAT AFTER INT
+ FLOAT = '^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$'
+ if re.match(PATH, val):
+ return get_path(val)[0]
+ if re.match(RELATIONSHIP, val):
+ return get_relationship(val)[0]
+ if re.match(NODE, val):
+ return get_node(val)[0]
+ if re.match(NULL, val):
+ return None
+ if re.match(BOOLEAN, val):
+ return bool(val)
+ if re.match(STRING, val):
+ return val[1:-1]
+ if re.match(INTEGER, val):
+ return int(val)
+ if re.match(FLOAT, val):
+ return float(val)
+ raise TypeError("String value does not match any type. Got: %s" % val)
+
+
+def get_properties(prop):
+ split_at = prop.find('{')
+ if split_at != -1:
+ n_start, prop = split_two(prop, split_at)
+ split_at = prop.find('}')
+ if split_at == -1:
+ raise ValueError(
+ "Node properties not correctly representetd. Found starrting '{' but no ending '}' in : %s" % prop)
+ prop, n_end = split_two(prop, split_at + 1)
+ n = n_start + n_end
+ properties = json.loads(prop)
+ return properties, n
+ else:
+ return {}, prop
+
+
+def get_labels(labels):
+ labels = labels.split(':')
+ if len(labels) == 1:
+ return [], labels[0]
+ n = labels[0]
+ labels = labels[1:]
+ if labels[-1].find(' ') != -1:
+ labels[-1], n_end = split_two(labels[-1], labels[-1].find(' '))
+ n += n_end
+ return labels, n
+
+
+def get_labels_and_properties(n):
+ prop, n = get_properties(n)
+ labels, n = get_labels(n)
+ return labels, prop, n
+
+
+def split_two(string_entity, split_at):
+ return string_entity[:split_at], string_entity[split_at:]
+
+
+def get_node(string_entity):
+ if string_entity[0] != "(":
+ raise ValueError("Excpected node which shuld start with '('. Got: %s" % string_entity[0])
+ split_at = string_entity.find(')')
+ if split_at == -1:
+ raise ValueError("Excpected node which shuld end with ')'. Found no such character in: %s" % string_entity)
+ n, string_entity = split_two(string_entity, split_at + 1)
+ n = n[1:-1]
+ labels, properties, n = get_labels_and_properties(n)
+ node = Node(labels=labels, properties=properties)
+ return node, string_entity
+
+
+def get_relationship(string_entity):
+ point_up = None
+ if string_entity[:3] == "<-[":
+ point_up = False
+ string_entity = string_entity[3:]
+ rel = string_entity[:string_entity.find(']-')]
+ string_entity = string_entity[string_entity.find(']-') + 2:]
+ elif string_entity[:2] == "-[":
+ point_up = True
+ string_entity = string_entity[2:]
+ rel = string_entity[:string_entity.find(']-')]
+ string_entity = string_entity[string_entity.find(']->') + 3:]
+ elif string_entity[0] == "[":
+ string_entity = string_entity[1:]
+ rel = string_entity[:string_entity.find(']')]
+ string_entity = string_entity[string_entity.find(']') + 1:]
+ else:
+ raise ValueError("Cant identify relationship from: %s" % string_entity)
+ type, properties, str = get_labels_and_properties(rel)
+ if len(type) > 1:
+ raise ValueError("Relationship can only have one type. Got: %s" % type)
+ relationship = Relationship(1, 2, type[0], properties=properties)
+ return relationship, string_entity, point_up
+
+
+def get_path(string_path):
+ id = 0
+ string_path = string_path[1:-1]
+ n, string_path = get_node(string_path)
+ list_of_nodes_and_rel = [n]
+ n.identity = ++id
+ while string_path != '':
+ r, string_path, point_up = get_relationship(string_path)
+ n, string_path = get_node(string_path)
+ n.identity = ++id
+ if point_up:
+ r.start = list_of_nodes_and_rel[-1].identity
+ r.end = n.identity
+ r.identity = 0
+ else:
+ r.start = n.identity
+ r.end = list_of_nodes_and_rel[-1].identity
+ r.identity = 0
+ list_of_nodes_and_rel.append(r)
+ list_of_nodes_and_rel.append(n)
+ path = Path(list_of_nodes_and_rel[0], *list_of_nodes_and_rel[1:])
+ return path, string_path
diff --git a/test/tck/steps/bolt_compability_steps.py b/test/tck/steps/bolt_compability_steps.py
index a9cf16174..ea2b47f6e 100644
--- a/test/tck/steps/bolt_compability_steps.py
+++ b/test/tck/steps/bolt_compability_steps.py
@@ -20,10 +20,11 @@
import random
import string
+import copy
from behave import *
-from neo4j.v1 import GraphDatabase
+from test.tck.resultparser import parse_values
from test.tck.tck_util import to_unicode, Type, send_string, send_parameters, string_to_type
from neo4j.v1 import compat
@@ -35,29 +36,31 @@ def step_impl(context):
send_string("RETURN 1")
-@given("a value (?P.+) of type (?P.+)")
-def step_impl(context, input, bolt_type):
- context.expected = get_bolt_value(string_to_type(bolt_type), input)
+@given("a value (?P.+)")
+def step_impl(context, input):
+ context.expected = parse_values(input)
-@given("a value of type (?P.+)")
-def step_impl(context, bolt_type):
- context.expected = get_bolt_value(string_to_type(bolt_type), u' ')
+@given("a list containing")
+def step_impl(context):
+ context.expected = [parse_values(row[0]) for row in context.table.rows]
-@given("a list value (?P.+) of type (?P.+)")
-def step_impl(context, input, bolt_type):
- context.expected = get_list_from_feature_file(input, string_to_type(bolt_type))
+@step("adding this list to itself")
+def step_impl(context):
+ clone = context.expected[:]
+ context.expected.append(clone)
-@given("an empty list L")
+@given("a map containing")
def step_impl(context):
- context.L = []
+ context.expected = {parse_values(row[0]): parse_values(row[1]) for row in context.table.rows}
-@given("an empty map M")
-def step_impl(context):
- context.M = {}
+@step('adding this map to itself with key "(?P.+)"')
+def step_impl(context, key):
+ clone = copy.deepcopy(context.expected)
+ context.expected[key] = clone
@given("a String of size (?P\d+)")
@@ -75,56 +78,8 @@ def step_impl(context, size, type):
context.expected = get_dict_of_random_type(int(size), string_to_type(type))
-@step("adding a table of lists to the list L")
-def step_impl(context):
- for row in context.table:
- context.L.append(get_list_from_feature_file(row[1], row[0]))
-
-
-@step("adding a table of values to the list L")
-def step_impl(context):
- for row in context.table:
- context.L.append(get_bolt_value(row[0], row[1]))
-
-
-@step("adding a table of values to the map M")
-def step_impl(context):
- for row in context.table:
- context.M['a%d' % len(context.M)] = get_bolt_value(row[0], row[1])
-
-
-@step("adding map M to list L")
-def step_impl(context):
- context.L.append(context.M)
-
-
-@when("adding a table of lists to the map M")
-def step_impl(context):
- for row in context.table:
- context.M['a%d' % len(context.M)] = get_list_from_feature_file(row[1], row[0])
-
-
-@step("adding a copy of map M to map M")
-def step_impl(context):
- context.M['a%d' % len(context.M)] = context.M.copy()
-
-
-@when("the driver asks the server to echo this value back")
-def step_impl(context):
- context.results = {"as_string": send_string("RETURN " + as_cypher_text(context.expected)),
- "as_parameters": send_parameters("RETURN {input}", {'input': context.expected})}
-
-
-@when("the driver asks the server to echo this list back")
-def step_impl(context):
- context.expected = context.L
- context.results = {"as_string": send_string("RETURN " + as_cypher_text(context.expected)),
- "as_parameters": send_parameters("RETURN {input}", {'input': context.expected})}
-
-
-@when("the driver asks the server to echo this map back")
-def step_impl(context):
- context.expected = context.M
+@when("the driver asks the server to echo this (?P.+) back")
+def step_impl(context, unused):
context.results = {"as_string": send_string("RETURN " + as_cypher_text(context.expected)),
"as_parameters": send_parameters("RETURN {input}", {'input': context.expected})}
@@ -137,56 +92,6 @@ def step_impl(context):
assert result_value == context.expected
-@given("A driver containing a session pool of size (?P\d+)")
-def step_impl(context, size):
- context.driver = GraphDatabase.driver("bolt://localhost", max_pool_size=1)
-
-
-@when("acquiring a session from the driver")
-def step_impl(context):
- context.session = context.driver.session()
-
-
-@step('with the session running the Cypher statement "(?P.+)"')
-def step_impl(context, statement):
- context.cursor = context.session.run(statement)
-
-
-@step("pulling the result records")
-def step_impl(context):
- context.cursor.consume()
-
-
-@then("acquiring a session from the driver should not be possible")
-def step_impl(context):
- try:
- context.session = context.driver.session()
- except:
- assert True
- else:
- assert False
-
-
-@then("acquiring a session from the driver should be possible")
-def step_impl(context):
- _ = context.driver.session()
- assert True
-
-
-def get_bolt_value(type, value):
- if type == Type.INTEGER:
- return int(value)
- if type == Type.FLOAT:
- return float(value)
- if type == Type.STRING:
- return to_unicode(value)
- if type == Type.NULL:
- return None
- if type == Type.BOOLEAN:
- return bool(value)
- raise ValueError('No such type : %s' % type)
-
-
def as_cypher_text(expected):
if expected is None:
return "Null"
@@ -215,15 +120,6 @@ def as_cypher_text(expected):
return to_unicode(expected)
-def get_list_from_feature_file(string_list, bolt_type):
- inputs = string_list.strip('[]')
- inputs = inputs.split(',')
- list_to_return = []
- for value in inputs:
- list_to_return.append(get_bolt_value(bolt_type, value))
- return list_to_return
-
-
def get_random_string(size):
return u''.join(
random.SystemRandom().choice(list(string.ascii_uppercase + string.digits + string.ascii_lowercase)) for _ in
diff --git a/test/tck/steps/cypher_compability_steps.py b/test/tck/steps/cypher_compability_steps.py
index a2baeb91b..db21f58e8 100644
--- a/test/tck/steps/cypher_compability_steps.py
+++ b/test/tck/steps/cypher_compability_steps.py
@@ -18,219 +18,57 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import json
-
from behave import *
-from neo4j.v1 import compat, Node, Relationship, Path
-from test.tck.tck_util import Value, Type, string_to_type, send_string, send_parameters
+from test.tck.tck_util import TestValue, send_string, send_parameters
+from test.tck.resultparser import parse_values, parse_values_to_comparable
use_step_matcher("re")
-@given("init: (?P.+);")
+@given("init: (?P.+)")
def step_impl(context, statement):
send_string(statement)
-@when("running: (?P.+);")
+@when("running: (?P.+)")
def step_impl(context, statement):
context.results = {"as_string": send_string(statement)}
-@then("result should be (?P.+)\(s\)")
-def step_impl(context, type):
- result = context.results["as_string"]
- given = driver_result_to_comparable_result(result)
- expected = table_to_comparable_result(context.table, type)
- if not unordered_equal(given, expected):
- raise Exception("%s does not match given: \n%s expected: \n%s" % (type, given, expected))
-
-
-@then("result should be empty")
-def step_impl(context):
- assert context.results
- for result in context.results.values():
- assert len(result) == 0
-
-
-@then("result should map to types")
-def step_impl(context):
- keys = context.table.headings
- values = context.table.rows[0]
- context.types = {keys[i]: values[i] for i in range(len(keys))}
-
-
-@then("result should be mixed")
+@then("result")
def step_impl(context):
result = context.results["as_string"]
given = driver_result_to_comparable_result(result)
- expected = table_to_comparable_result(context.table, context.types)
- if given != expected:
- raise Exception("Mixed response does not match given: %s expected: %s" % (given, expected))
+ expected = table_to_comparable_result(context.table)
+ if not unordered_equal(given, expected):
+ raise Exception("Does not match given: \n%s expected: \n%s" % (given, expected))
-@when('running parametrized: (?P.+);')
+@when('running parametrized: (?P.+)')
def step_impl(context, statement):
- parameters = {}
- keys = context.table.headings
assert len(context.table.rows) == 1
- values = context.table[0]
- for i, v in enumerate(values):
- if v[0] == '"':
- val = v[1:-1]
- else:
- val = int(v)
- parameters[keys[i]] = val
+ keys = context.table.headings
+ values = context.table.rows[0]
+ parameters = {keys[i]: parse_values(values[i]) for i in range(len(keys))}
context.results = {"as_string": send_parameters(statement, parameters)}
-def get_properties(prop):
- split_at = prop.find('{')
- if split_at != -1:
- n_start, prop = split_two(prop, split_at)
- split_at = prop.find('}')
- if split_at == -1:
- raise ValueError(
- "Node properties not correctly representetd. Found starrting '{' but no ending '}' in : %s" % prop)
- prop, n_end = split_two(prop, split_at + 1)
- n = n_start + n_end
- properties = json.loads(prop)
- return properties, n
- else:
- return {}, prop
-
-
-def get_labels(labels):
- labels = labels.split(':')
- if len(labels) == 1:
- return [], labels[0]
- n = labels[0]
- labels = labels[1:]
- if labels[-1].find(' ') != -1:
- labels[-1], n_end = split_two(labels[-1], labels[-1].find(' '))
- n += n_end
- return labels, n
-
-
-def get_labels_and_properties(n):
- prop, n = get_properties(n)
- labels, n = get_labels(n)
- return labels, prop, n
-
-
-def split_two(string_entity, split_at):
- return string_entity[:split_at], string_entity[split_at:]
-
-
-def get_node(string_entity):
- if string_entity is None:
- return None, ''
- if string_entity[0] != "(":
- raise ValueError("Excpected node which shuld start with '('. Got: %s" % string_entity[0])
- split_at = string_entity.find(')')
- if split_at == -1:
- raise ValueError("Excpected node which shuld end with ')'. Found no such character in: %s" % string_entity)
- n, string_entity = split_two(string_entity, split_at + 1)
- n = n[1:-1]
- labels, properties, n = get_labels_and_properties(n)
- node = Node(labels=labels, properties=properties)
- return node, string_entity
-
-
-def get_relationship(string_entity):
- point_up = None
- if string_entity[:3] == "<-[":
- point_up = False
- string_entity = string_entity[3:]
- rel = string_entity[:string_entity.find(']-')]
- string_entity = string_entity[string_entity.find(']-') + 2:]
- elif string_entity[:2] == "-[":
- point_up = True
- string_entity = string_entity[2:]
- rel = string_entity[:string_entity.find(']-')]
- string_entity = string_entity[string_entity.find(']->') + 3:]
- elif string_entity[0] == "[":
- string_entity = string_entity[1:]
- rel = string_entity[:string_entity.find(']')]
- string_entity = string_entity[string_entity.find(']') + 1:]
- else:
- raise ValueError("Cant identify relationship from: %s" % string_entity)
- type, properties, str = get_labels_and_properties(rel)
- if len(type) > 1:
- raise ValueError("Relationship can only have one type. Got: %s" % type)
- relationship = Relationship(1, 2, type[0], properties=properties)
- return relationship, string_entity, point_up
-
-
-def get_path(string_path):
- n, string_path = get_node(string_path)
- list_of_nodes_and_rel = [n]
- n.identity = 0
- while string_path != '':
- r, string_path, point_up = get_relationship(string_path)
- n, string_path = get_node(string_path)
- n.identity = len(list_of_nodes_and_rel)
- if point_up:
- r.start = list_of_nodes_and_rel[-1].identity
- r.end = n.identity
- else:
- r.start = n.identity
- r.end = list_of_nodes_and_rel[-1].identity
- list_of_nodes_and_rel.append(r)
- list_of_nodes_and_rel.append(n)
- path = Path(list_of_nodes_and_rel[0], *list_of_nodes_and_rel[1:])
- return path, string_path
-
-
-def _string_value_to_comparable(val, type):
- def get_val(v):
- if type == Type.INTEGER:
- return Value(int(v))
- elif type == Type.STRING:
- return Value(v)
- elif type == Type.NODE:
- return Value(get_node(v)[0])
- elif type == Type.RELATIONSHIP:
- return Value(get_relationship(v)[0])
- elif type == Type.PATH:
- return Value(get_path(v)[0])
- else:
- raise ValueError("Not recognized type: %s" % type)
-
- assert isinstance(type, compat.string)
- if val == 'null':
- return Value(None)
- if val[0] == '[':
- if type != Type.RELATIONSHIP or (type == Type.RELATIONSHIP and val[1] == '['):
- val = val[1:-1].split(", ")
- if isinstance(val, list):
- return tuple([get_val(v) for v in val])
- else:
- return get_val(val)
-
-
def _driver_value_to_comparable(val):
if isinstance(val, list):
- return tuple([Value(v) for v in val])
+ l = [_driver_value_to_comparable(v) for v in val]
+ return l
else:
- return Value(val)
+ return TestValue(val)
-def table_to_comparable_result(table, types):
+def table_to_comparable_result(table):
result = []
keys = table.headings
- if isinstance(types, compat.string):
- types = {key: string_to_type(types) for key in keys}
- elif isinstance(types, dict):
- assert set(types.keys()) == set(keys)
- else:
- raise ValueError("types must be either string of single type or map of types corresponding to result keys Got:"
- " %s" % types)
for row in table:
result.append(
- {keys[i]: _string_value_to_comparable(row[i], string_to_type(types[keys[i]])) for i in range(len(row))})
+ {keys[i]: parse_values_to_comparable(row[i]) for i in range(len(row))})
return result
@@ -241,9 +79,9 @@ def driver_result_to_comparable_result(result):
return records
-def unordered_equal(one, two):
- l1 = one[:]
- l2 = two[:]
+def unordered_equal(given, expected):
+ l1 = given[:]
+ l2 = expected[:]
assert isinstance(l1, list)
assert isinstance(l2, list)
assert len(l1) == len(l2)
diff --git a/test/tck/tck_util.py b/test/tck/tck_util.py
index 4a1821ad0..d05913f75 100644
--- a/test/tck/tck_util.py
+++ b/test/tck/tck_util.py
@@ -79,7 +79,7 @@ class Type:
NULL = "Null"
-class Value:
+class TestValue:
content = None
def __init__(self, entity):
@@ -100,7 +100,6 @@ def __hash__(self):
return hash(repr(self))
def __eq__(self, other):
- assert isinstance(other, Value)
return self.content == other.content
def __repr__(self):