diff --git a/e2e_correctness/conftest.py b/e2e_correctness/conftest.py index 11f3aae60..79533a42f 100644 --- a/e2e_correctness/conftest.py +++ b/e2e_correctness/conftest.py @@ -1,5 +1,3 @@ - - def pytest_addoption(parser): parser.addoption("--memgraph-port", type=int, action="store") parser.addoption("--neo4j-port", type=int, action="store") diff --git a/e2e_correctness/path_test/test_path_expand1/config.yml b/e2e_correctness/path_test/test_path_expand1/config.yml new file mode 100644 index 000000000..173fc413a --- /dev/null +++ b/e2e_correctness/path_test/test_path_expand1/config.yml @@ -0,0 +1,2 @@ +path_option: > + True diff --git a/e2e_correctness/path_test/test_path_expand1/input.cyp b/e2e_correctness/path_test/test_path_expand1/input.cyp new file mode 100644 index 000000000..8d7b1ed1c --- /dev/null +++ b/e2e_correctness/path_test/test_path_expand1/input.cyp @@ -0,0 +1,7 @@ +CREATE (d:Dog {name: "Rex", id: 0})-[h:HUNTS {id: 0}]->(c:Cat {name: "Tom", id: 1})-[catc:CATCHES {id:1}]->(m:Mouse {name: "Squiggles", id: 2}); +MATCH (d:Dog) CREATE (d)-[bff:BEST_FRIENDS {id:2}]->(c:Cat {name: "Rinko", id: 3}); +MATCH (c:Cat {name: "Tom"}) CREATE (h:Human {name: "Matija", id: 4})-[o:OWNS {id:3}]->(c); +MATCH (d:Dog {name: "Rex"}),(h:Human {name:"Matija"}) CREATE (h)-[o:PLAYS_WITH {id:4}]->(d); +MATCH (d:Dog {name: "Rex"}) CREATE (d)-[l:LIVES {id:5}]->(z:Zadar {id: 5}); +MATCH (m:Mouse {name: "Squiggles"}), (z:Zadar) CREATE (m)-[r:RUNS_THROUGH {id:6}]->(z); +MATCH (z:Zadar) CREATE (h:Human {name: "Dena", id: 6})-[g:GOES_TO {id:7}]->(z); diff --git a/e2e_correctness/path_test/test_path_expand1/test.yml b/e2e_correctness/path_test/test_path_expand1/test.yml new file mode 100644 index 000000000..c0260c510 --- /dev/null +++ b/e2e_correctness/path_test/test_path_expand1/test.yml @@ -0,0 +1,8 @@ +memgraph_query: > + MATCH (d:Dog), (h:Human) + CALL path.expand([d,id(h)],[],[],1,10) YIELD result RETURN result; + + +neo4j_query: > + MATCH (d:Dog), (h:Human) + CALL apoc.path.expand([d,h],"","",1,10) YIELD path RETURN path; diff --git a/e2e_correctness/query_neo_mem.py b/e2e_correctness/query_neo_mem.py index 0650600ad..7bab351a9 100644 --- a/e2e_correctness/query_neo_mem.py +++ b/e2e_correctness/query_neo_mem.py @@ -74,7 +74,11 @@ def __eq__(self, other): class Edge: def __init__( - self, from_vertex: int, to_vertex: int, label: str, properties: Dict[str, Any] + self, + from_vertex: int, + to_vertex: int, + label: str, + properties: Dict[str, Any], ): self._from_vertex = from_vertex self._to_vertex = to_vertex @@ -297,3 +301,83 @@ def neo4j_get_graph(neo4j_driver: neo4j.BoltDriver) -> Graph: json_data = get_neo4j_data_json(neo4j_driver) logger.debug("Building the graph from Neo4j JSON data") return create_graph_neo4j_json(json_data) + + +# additions for path testing +def sort_dict(dict): + keys = list(dict.keys()) + keys.sort() + sorted_dict = {i: dict[i] for i in keys} + return sorted_dict + + +def execute_query_neo4j(driver: neo4j.BoltDriver, query: str) -> list: + with driver.session() as session: + query = neo4j.Query(query) + results = session.run(query).value() + return results + + +def path_to_string_neo4j(path): #type should be neo4j.graph.path but it doesnt recognize it in the definition + path_string_list = ["PATH: "] + + n = len(path.nodes) + + for i in range(0, n): + node = path.nodes[i] + node_labels = list(node.labels) + node_labels.sort() + sorted_dict = sort_dict(node._properties) + if "id" in sorted_dict: + sorted_dict.pop("id") + node_props = str(sorted_dict) + path_string_list.append(f"(id:{str(node.get('id'))} labels: {str(node_labels)} {node_props})-") + + if i == n - 1: + path_string = "".join(path_string_list) + return path_string[:-1] + + relationship = path.relationships[i] + sorted_dict_rel = sort_dict(relationship._properties) + if "id" in sorted_dict_rel: + sorted_dict_rel.pop("id") + rel_props = str(sorted_dict_rel) + path_string_list.append(f"[id:{str(relationship.get('id'))} type: {relationship.type} {str(rel_props)}]-") + +def parse_neo4j(results: list) -> List[str]: + paths = [path_to_string_neo4j(res) for res in results] + paths.sort() + return paths + + +def path_to_string_mem(path: gqlalchemy.Path) -> str: + path_string_list = ["PATH: "] + + n = len(path._nodes) + + for i in range(0, n): + node = path._nodes[i] + node_labels = list(node._labels) + node_labels.sort() + sorted_dict = sort_dict(node._properties) + if "id" in sorted_dict: + sorted_dict.pop("id") + node_props = str(sorted_dict) + path_string_list.append(f"(id:{str(node._properties.get('id'))} labels: {str(node_labels)} {str(node_props)})-") + + if i == n - 1: + path_string = "".join(path_string_list) + return path_string[:-1] + + relationship = path._relationships[i] + sorted_dict_rel = sort_dict(relationship._properties) + if "id" in sorted_dict_rel: + sorted_dict_rel.pop("id") + rel_props = str(sorted_dict_rel) + path_string_list.append(f"[id:{str(relationship._properties.get('id'))} type: {relationship._type} {str(rel_props)}]-") + + +def parse_mem(results:list) -> List[str]: + paths = [path_to_string_mem(result["result"]) for result in results] + paths.sort() + return paths diff --git a/e2e_correctness/test_modules.py b/e2e_correctness/test_modules.py index 9357ed24b..9bf223c73 100644 --- a/e2e_correctness/test_modules.py +++ b/e2e_correctness/test_modules.py @@ -6,6 +6,7 @@ import neo4j import pytest import yaml +import os from gqlalchemy import Memgraph @@ -25,6 +26,11 @@ neo4j_get_graph, run_memgraph_query, run_neo4j_query, + execute_query_neo4j, + path_to_string_neo4j, + parse_neo4j, + path_to_string_mem, + parse_mem, ) logging.basicConfig(format="%(asctime)-15s [%(levelname)s]: %(message)s") @@ -49,6 +55,8 @@ class TestConstants: TEST_FILE = "test.yml" MEMGRAPH_QUERY = "memgraph_query" NEO4J_QUERY = "neo4j_query" + CONFIG_FILE = "config.yml" + class ConfigConstants: @@ -79,7 +87,9 @@ def get_all_tests(): if not test_or_group_dir.is_dir(): continue - if test_or_group_dir.name.endswith(TestConstants.TEST_GROUP_DIR_SUFFIX): + if test_or_group_dir.name.endswith( + TestConstants.TEST_GROUP_DIR_SUFFIX + ): for test_dir in test_or_group_dir.iterdir(): if not test_dir.is_dir(): continue @@ -143,11 +153,15 @@ def _graphs_equal(memgraph_graph: Graph, neo4j_graph: Graph) -> bool: return True -def _run_test(test_dir: str, memgraph_db: Memgraph, neo4j_driver: neo4j.BoltDriver): +def _run_test( + test_dir: str, memgraph_db: Memgraph, neo4j_driver: neo4j.BoltDriver +) -> None: """ Run input queries on Memgraph and Neo4j and compare graphs after running test query """ - input_cyphers = test_dir.joinpath(TestConstants.INPUT_FILE).open("r").readlines() + input_cyphers = ( + test_dir.joinpath(TestConstants.INPUT_FILE).open("r").readlines() + ) mg_execute_cyphers(input_cyphers, memgraph_db) logger.info(f"Imported data into Memgraph from {input_cyphers}") neo4j_execute_cyphers(input_cyphers, neo4j_driver) @@ -162,7 +176,9 @@ def _run_test(test_dir: str, memgraph_db: Memgraph, neo4j_driver: neo4j.BoltDriv run_memgraph_query(test_dict[TestConstants.MEMGRAPH_QUERY], memgraph_db) logger.info("Done") - logger.info(f"Running query against Neo4j: {test_dict[TestConstants.NEO4J_QUERY]}") + logger.info( + f"Running query against Neo4j: {test_dict[TestConstants.NEO4J_QUERY]}" + ) run_neo4j_query(test_dict[TestConstants.NEO4J_QUERY], neo4j_driver) logger.info("Done") @@ -174,11 +190,57 @@ def _run_test(test_dir: str, memgraph_db: Memgraph, neo4j_driver: neo4j.BoltDriv ), "The graphs are not equal, check the logs for more details" +def _run_path_test( + test_dir: str, memgraph_db: Memgraph, neo4j_driver: neo4j.BoltDriver +) -> None: + """ + Run input queries on Memgraph and Neo4j and compare path results after running test query + """ + input_cyphers = ( + test_dir.joinpath(TestConstants.INPUT_FILE).open("r").readlines() + ) + logger.info(f"Importing data from {input_cyphers}") + mg_execute_cyphers(input_cyphers, memgraph_db) + logger.info("Imported data into Memgraph") + neo4j_execute_cyphers(input_cyphers, neo4j_driver) + logger.info("Imported data into Neo4j") + + test_dict = _load_yaml(test_dir.joinpath(TestConstants.TEST_FILE)) + logger.info(f"Test dict {test_dict}") + logger.info( + f"Running query against Memgraph: {test_dict[TestConstants.MEMGRAPH_QUERY]}" + ) + memgraph_results = memgraph_db.execute_and_fetch( + test_dict[TestConstants.MEMGRAPH_QUERY] + ) + memgraph_paths = parse_mem(memgraph_results) + logger.info("Done") + + logger.info( + f"Running query against Neo4j: {test_dict[TestConstants.NEO4J_QUERY]}" + ) + neo4j_results = execute_query_neo4j( + neo4j_driver, test_dict[TestConstants.NEO4J_QUERY] + ) + neo4j_paths = parse_neo4j(neo4j_results) + logger.info("Done") + + assert memgraph_paths == neo4j_paths + +def check_path_option(test_dir): + config_path = test_dir.joinpath(TestConstants.CONFIG_FILE) + if(os.path.exists(config_path)): + config_dict = _load_yaml(config_path) + if "path_option" in config_dict: + option = config_dict["path_option"].strip() + return ( option == "True") + return False @pytest.fixture(scope="session") def memgraph_port(pytestconfig): return pytestconfig.getoption("--memgraph-port") + @pytest.fixture(scope="session", autouse=True) def memgraph_db(memgraph_port): memgraph_db = create_memgraph_db(memgraph_port) @@ -191,6 +253,7 @@ def memgraph_db(memgraph_port): def neo4j_port(pytestconfig): return pytestconfig.getoption("--neo4j-port") + @pytest.fixture(scope="session", autouse=True) def neo4j_driver(neo4j_port): neo4j_driver = create_neo4j_driver(neo4j_port) @@ -201,7 +264,9 @@ def neo4j_driver(neo4j_port): @pytest.mark.parametrize("test_dir", tests) def test_end2end( - test_dir: Path, memgraph_db: Memgraph, neo4j_driver: neo4j.BoltDriver + test_dir: Path, + memgraph_db: Memgraph, + neo4j_driver: neo4j.BoltDriver, ): logger.debug("Dropping the Memgraph and Neo4j databases.") @@ -209,7 +274,11 @@ def test_end2end( clean_neo4j_db(neo4j_driver) if test_dir.name.startswith(TestConstants.TEST_SUBDIR_PREFIX): - _run_test(test_dir, memgraph_db, neo4j_driver) + + if check_path_option(test_dir): + _run_path_test(test_dir, memgraph_db, neo4j_driver) + else: + _run_test(test_dir, memgraph_db, neo4j_driver) else: logger.info(f"Skipping directory: {test_dir.name}") diff --git a/test_e2e_correctness.py b/test_e2e_correctness.py index 10ba141a7..ff1c52c81 100644 --- a/test_e2e_correctness.py +++ b/test_e2e_correctness.py @@ -12,18 +12,26 @@ class ConfigConstants: NEO4J_PORT = 7688 MEMGRAPH_PORT = 7687 + def parse_arguments(): - parser = argparse.ArgumentParser( - description="Test MAGE E2E correctness." - ) + parser = argparse.ArgumentParser(description="Test MAGE E2E correctness.") parser.add_argument( - "-k", help="Filter what tests you want to run", type=str, required=False + "-k", + help="Filter what tests you want to run", + type=str, + required=False, ) parser.add_argument( - "--memgraph-port", help="Set the port that Memgraph is listening on", type=int, required=False + "--memgraph-port", + help="Set the port that Memgraph is listening on", + type=int, + required=False, ) parser.add_argument( - "--neo4j-port", help="Set the port that Neo4j is listening on", type=int, required=False + "--neo4j-port", + help="Set the port that Neo4j is listening on", + type=int, + required=False, ) args = parser.parse_args() return args @@ -34,18 +42,21 @@ def parse_arguments(): ################################################# -def main(test_filter: str = None, - memgraph_port:str = str(ConfigConstants.MEMGRAPH_PORT), - neo4j_port: str = str(ConfigConstants.NEO4J_PORT)): +def main( + test_filter: str = None, + memgraph_port: str = str(ConfigConstants.MEMGRAPH_PORT), + neo4j_port: str = str(ConfigConstants.NEO4J_PORT), +): os.environ["PYTHONPATH"] = E2E_CORRECTNESS_DIRECTORY os.chdir(E2E_CORRECTNESS_DIRECTORY) command = ["python3", "-m", "pytest", ".", "-vv"] if test_filter: command.extend(["-k", test_filter]) - command.extend(["--memgraph-port", memgraph_port]) command.extend(["--neo4j-port", neo4j_port]) + + subprocess.run(command) @@ -59,7 +70,9 @@ def main(test_filter: str = None, memgraph_port = str(memgraph_port) if neo4j_port: neo4j_port = str(neo4j_port) - - main(test_filter=test_filter, - memgraph_port=memgraph_port, - neo4j_port=neo4j_port) + + main( + test_filter=test_filter, + memgraph_port=memgraph_port, + neo4j_port=neo4j_port, + )