diff --git a/neo4j/.DS_Store b/neo4j/.DS_Store new file mode 100644 index 00000000..d039ac90 Binary files /dev/null and b/neo4j/.DS_Store differ diff --git a/neo4j/v1/session.py b/neo4j/v1/session.py index 14bcf199..a6adb5e2 100644 --- a/neo4j/v1/session.py +++ b/neo4j/v1/session.py @@ -43,7 +43,7 @@ class which can be used to obtain `Driver` instances that are used for STATEMENT_TYPE_READ_ONLY = "r" STATEMENT_TYPE_READ_WRITE = "rw" STATEMENT_TYPE_WRITE_ONLY = "w" -STATEMENT_TYPE_SCHEMA_WRITE = "sw" +STATEMENT_TYPE_SCHEMA_WRITE = "s" def basic_auth(user, password): @@ -333,11 +333,11 @@ def __repr__(self): @property def contains_updates(self): - return self.nodes_created or self.nodes_deleted or \ + return bool(self.nodes_created or self.nodes_deleted or \ self.relationships_created or self.relationships_deleted or \ self.properties_set or self.labels_added or self.labels_removed or \ self.indexes_added or self.indexes_removed or \ - self.constraints_added or self.constraints_removed + self.constraints_added or self.constraints_removed) #: A plan describes how the database will execute your statement. diff --git a/test/tck/environment.py b/test/tck/environment.py index bbe8646f..3bbb7bae 100644 --- a/test/tck/environment.py +++ b/test/tck/environment.py @@ -20,9 +20,7 @@ from test.tck import tck_util - -def before_all(context): - context.config.setup_logging() +failing_features = {} def before_feature(context, feature): @@ -30,12 +28,38 @@ def before_feature(context, feature): for scenario in feature.scenarios: scenario.tags += feature.tags + def before_scenario(context, scenario): + context.runners = [] if "reset_database" in scenario.tags: - tck_util.send_string("MATCH (n) DETACH DELETE n") + session = tck_util.driver.session() + session.run("MATCH (n) DETACH DELETE n") + session.close() + if "equality_test" in scenario.tags: + context.values = {} + + +def after_feature(context, feature): + failed_scenarios = [] + for scenario in feature.scenarios: + if scenario.status == "untested" or scenario.status == "failed" : + failed_scenarios.append(scenario.name) + if len(failed_scenarios) > 0: + failing_features[feature.name] = failed_scenarios + + +def after_all(context): + if len(failing_features) != 0: + print("Following Features failed in TCK:") + for feature, list_of_scenarios in failing_features.items(): + print("Feature: %s" %feature) + for scenario in list_of_scenarios: + print("Failing scenario: %s" % scenario) + raise Exception("\tTCK FAILED!") def after_scenario(context, scenario): - if scenario.status != "passed": - raise Exception("%s did not pass" %scenario) + pass + for runner in tck_util.runners: + runner.close() diff --git a/test/tck/resultparser.py b/test/tck/resultparser.py index cb198bc6..44a30590 100644 --- a/test/tck/resultparser.py +++ b/test/tck/resultparser.py @@ -181,11 +181,13 @@ def get_path(string_path): string_path = string_path[1:-1] n, string_path = get_node(string_path) list_of_nodes_and_rel = [n] - n.id = ++id + id+=1 + n.id = id while string_path != '': r, string_path, point_up = get_relationship(string_path) n, string_path = get_node(string_path) - n.id = ++id + id+=1 + n.id = id if point_up: r.start = list_of_nodes_and_rel[-1].id r.end = n.id diff --git a/test/tck/steps/bolt_compability_steps.py b/test/tck/steps/bolt_compability_steps.py index ea2b47f6..e56af162 100644 --- a/test/tck/steps/bolt_compability_steps.py +++ b/test/tck/steps/bolt_compability_steps.py @@ -24,8 +24,9 @@ from behave import * +from test.tck import tck_util from test.tck.resultparser import parse_values -from test.tck.tck_util import to_unicode, Type, send_string, send_parameters, string_to_type +from test.tck.tck_util import to_unicode, Type, string_to_type from neo4j.v1 import compat use_step_matcher("re") @@ -33,7 +34,9 @@ @given("A running database") def step_impl(context): - send_string("RETURN 1") + session = tck_util.driver.session() + session.run("RETURN 1") + session.close() @given("a value (?P.+)") @@ -80,15 +83,20 @@ def step_impl(context, size, type): @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})} + str_runner = tck_util.Runner("RETURN " + as_cypher_text(context.expected)).run() + param_runner = tck_util.Runner("RETURN {input}", {'input': context.expected}).run() + context.runners += [str_runner, param_runner] + context.results = [str_runner.result, param_runner.result] @step("the value given in the result should be the same as what was sent") def step_impl(context): assert len(context.results) > 0 - for result in context.results.values(): - result_value = result[0].values()[0] + for result in context.results: + records = list(result) + assert len(records) == 1 + assert len(records[0].values()) == 1 + result_value = records[0].values()[0] assert result_value == context.expected diff --git a/test/tck/steps/cypher_compability_steps.py b/test/tck/steps/cypher_compability_steps.py index db21f58e..42055160 100644 --- a/test/tck/steps/cypher_compability_steps.py +++ b/test/tck/steps/cypher_compability_steps.py @@ -20,6 +20,7 @@ from behave import * +from test.tck import tck_util from test.tck.tck_util import TestValue, send_string, send_parameters from test.tck.resultparser import parse_values, parse_values_to_comparable @@ -28,31 +29,38 @@ @given("init: (?P.+)") def step_impl(context, statement): - send_string(statement) + session = tck_util.driver.session() + session.run(statement) + session.close() -@when("running: (?P.+)") +@step("running: (?P.+)") def step_impl(context, statement): - context.results = {"as_string": send_string(statement)} + runner = tck_util.Runner(statement).run() + context.runners.append(runner) + context.results = [runner.result] -@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) - if not unordered_equal(given, expected): - raise Exception("Does not match given: \n%s expected: \n%s" % (given, expected)) - - -@when('running parametrized: (?P.+)') +@step('running parametrized: (?P.+)') def step_impl(context, statement): assert len(context.table.rows) == 1 keys = context.table.headings values = context.table.rows[0] parameters = {keys[i]: parse_values(values[i]) for i in range(len(keys))} + runner = tck_util.Runner(statement, parameters).run() + context.runners.append(runner) + context.results = [runner.result] - context.results = {"as_string": send_parameters(statement, parameters)} + +@then("result") +def step_impl(context): + expected = table_to_comparable_result(context.table) + assert(len(context.results) > 0) + for result in context.results: + records = list(result) + given = driver_result_to_comparable_result(records) + if not unordered_equal(given, expected): + raise Exception("Does not match given: \n%s expected: \n%s" % (given, expected)) def _driver_value_to_comparable(val): diff --git a/test/tck/steps/driver_equality_steps.py b/test/tck/steps/driver_equality_steps.py new file mode 100644 index 00000000..98c0b602 --- /dev/null +++ b/test/tck/steps/driver_equality_steps.py @@ -0,0 +1,52 @@ +#!/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. + +from behave import * + +from test.tck.tck_util import send_string + +use_step_matcher("re") + + +@step("`(?P.+)` is single value result of: (?P.+)") +def step_impl(context, key, statement): + runner = send_string(statement) + records = list(runner.result) + assert len(records) == 1 + assert len(records[0]) == 1 + context.values[key] = records[0][0] + + +@step("saved values should all equal") +def step_impl(context): + values = list(context.values.values()) + assert len(values) > 1 + first_val = values.pop() + for item in values: + assert item == first_val + + +@step("none of the saved values should be equal") +def step_impl(context): + values = list(context.values.values()) + assert len(values) > 1 + first_val = values.pop() + for item in values: + assert item != first_val diff --git a/test/tck/steps/driver_result_api_steps.py b/test/tck/steps/driver_result_api_steps.py new file mode 100644 index 00000000..315701b5 --- /dev/null +++ b/test/tck/steps/driver_result_api_steps.py @@ -0,0 +1,162 @@ +#!/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. + +from behave import * + +from neo4j.v1 import STATEMENT_TYPE_READ_ONLY, STATEMENT_TYPE_READ_WRITE, STATEMENT_TYPE_WRITE_ONLY, \ + STATEMENT_TYPE_SCHEMA_WRITE + +from test.tck.resultparser import parse_values + +use_step_matcher("re") + + +@step("the `Statement Result` is consumed a `Result Summary` is returned") +def step_impl(context): + context.summaries = [x.consume() for x in context.results] + assert context.summaries[0] is not None + + +@then("the `Statement Result` is closed") +def step_impl(context): + for result in context.results: + assert result.connection is None + + +@step("I request a `Statement` from the `Result Summary`") +def step_impl(context): + context.statements = [] + for summary in context.summaries: + context.statements.append(summary.statement) + + +@then("requesting the `Statement` as text should give: (?P.+)") +def step_impl(context, expected): + for statement in context.statements: + assert statement == expected + + +@step("requesting the `Statement` parameter should give: (?P.+)") +def step_impl(context, expected): + for summary in context.summaries: + assert summary.parameters == parse_values(expected) + + +@step("requesting `Counters` from `Result Summary` should give") +def step_impl(context): + for summary in context.summaries: + for row in context.table: + print(row[0].replace(" ","_")) + print(getattr(summary.counters, row[0].replace(" ","_"))) + assert getattr(summary.counters, row[0].replace(" ","_")) == parse_values(row[1]) + + +@step("requesting the `Statement Type` should give (?P.+)") +def step_impl(context, expected): + for summary in context.summaries: + if expected == "read only": + statement_type = STATEMENT_TYPE_READ_ONLY + elif expected == "read write": + statement_type = STATEMENT_TYPE_READ_WRITE + elif expected == "write only": + statement_type = STATEMENT_TYPE_WRITE_ONLY + elif expected == "schema write": + statement_type = STATEMENT_TYPE_SCHEMA_WRITE + else: + raise ValueError("Not recognisable statement type: %s" % expected) + assert summary.statement_type == statement_type + + +@step("the `Result Summary` has a `Plan`") +def step_impl(context): + for summary in context.summaries: + assert summary.plan is not None + + +@step("the `Result Summary` has a `Profile`") +def step_impl(context): + for summary in context.summaries: + assert summary.profile is not None + + +@step("the `Result Summary` does not have a `Plan`") +def step_impl(context): + for summary in context.summaries: + assert summary.plan is None + + +@step("the `Result Summary` does not have a `Profile`") +def step_impl(context): + for summary in context.summaries: + assert summary.profile is None + + +@step("requesting the `(?P.+)` it contains") +def step_impl(context, plan_type): + for summary in context.summaries: + if plan_type == "Plan": + plan = summary.plan + elif plan_type == "Profile": + plan = summary.profile + else: + raise ValueError("Expected 'plan' or 'profile'. Got: %s" % plan_type) + for row in context.table: + attr = row[0].replace(" ", "_") + if attr == 'records': + attr = 'rows' + assert getattr(plan, attr) == parse_values(row[1]) + + +@step("the `(?P.+)` also contains method calls for") +def step_impl(context, plan_type): + for summary in context.summaries: + if plan_type == "Plan": + plan = summary.plan + elif plan_type == "Profile": + plan = summary.profile + else: + raise ValueError("Expected 'plan' or 'profile'. Got: %s" % plan_type) + for row in context.table: + assert getattr(plan, row[0].replace(" ", "_")) is not None + + +@step("the `Result Summary` `Notifications` is empty") +def step_impl(context): + for summary in context.summaries: + assert len(summary.notifications) == 0 + + +@step("the `Result Summary` `Notifications` has one notification with") +def step_impl(context): + + for summary in context.summaries: + assert len(summary.notifications) == 1 + notification = summary.notifications[0] + for row in context.table: + if row[0] == 'position': + position = getattr(notification, row[0].replace(" ","_")) + expected_position = parse_values(row[1]) + for position_key, value in expected_position.items(): + assert value == getattr(position, position_key.replace(" ", "_")) + else: + assert getattr(notification, row[0].replace(" ","_")) == parse_values(row[1]) + + + diff --git a/test/tck/tck_util.py b/test/tck/tck_util.py index 186d6778..cce50c32 100644 --- a/test/tck/tck_util.py +++ b/test/tck/tck_util.py @@ -22,24 +22,20 @@ from neo4j.v1 import GraphDatabase, Relationship, Node, Path, basic_auth from neo4j.v1.compat import string - driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "password"), encrypted=False) +runners = [] -def send_string(text): - session = driver.session() - result = session.run(text) - records = list(result) - session.close() - return records +def send_string(statement): + runner = Runner(statement).run() + runners.append(runner) + return runner def send_parameters(statement, parameters): - session = driver.session() - result = session.run(statement, parameters) - records = list(result) - session.close() - return records + runner = Runner(statement, parameters).run() + runners.append(runner) + return runner try: @@ -81,6 +77,20 @@ class Type: NULL = "Null" +class Runner: + def __init__(self, statement, parameter=None): + self.session = driver.session() + self.statement = statement + self.parameter = parameter + self.result = None + + def run(self): + self.result = self.session.run(self.statement, self.parameter) + return self + + def close(self): + self.session.close() + class TestValue: content = None