com.graphaware.test.unit.GraphUnit Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tests Show documentation
Show all versions of tests Show documentation
Tools for testing Neo4j-related and GraphAware-related code
/*
* Copyright (c) 2013-2019 GraphAware
*
* This file is part of the GraphAware Framework.
*
* GraphAware Framework is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of
* the GNU General Public License along with this program. If not, see
* .
*/
package com.graphaware.test.unit;
import com.graphaware.common.policy.inclusion.InclusionPolicies;
import com.graphaware.common.policy.inclusion.PropertyInclusionPolicy;
import com.graphaware.common.util.EntityUtils;
import org.neo4j.graphdb.*;
import org.neo4j.helpers.collection.Iterators;
import org.neo4j.kernel.configuration.Settings;
import org.neo4j.logging.Log;
import org.neo4j.test.TestGraphDatabaseFactory;
import com.graphaware.common.log.LoggerFactory;
import java.io.File;
import java.util.*;
import static com.graphaware.common.util.DatabaseUtils.registerShutdownHook;
import static com.graphaware.common.util.EntityUtils.*;
import static org.junit.Assert.fail;
import static org.neo4j.graphdb.Direction.OUTGOING;
import static org.neo4j.helpers.collection.Iterables.count;
import static org.neo4j.kernel.configuration.Settings.*;
/**
* A set of assertion methods useful for writing tests for Neo4j. Uses the {@link org.junit.Assert} class from JUnit
* to throw {@link AssertionError}s.
*
* Note: This class is well-tested functionally, but it is not designed for production use, mainly because it hasn't been
* optimised for performance. The performance will be poor on large graphs - the problem it is solving is computationally
* quite hard!
*/
public final class GraphUnit {
private static final Log LOG = LoggerFactory.getLogger(GraphUnit.class);
private static final File PATH = new File("target/test-data/graphunit-db");
/**
* Private constructor - this class is a utility and should not be instantiated.
*/
private GraphUnit() {
}
/**
* Assert that the graph in the given database is exactly the same as the graph that would be created by the given
* Cypher query. The only thing that can be different in those two graphs are IDs of nodes and relationships.
* Properties values are converted to {@link String} before comparison, which means 123L (long) is equal to 123 (int),
* but also "123" (String) is considered equal to 123 (int).
*
* @param database first graph, typically the one that has been created by some code that is being tested by this
* method.
* @param sameGraphCypher second graph expressed as a Cypher create statement, which communicates the desired state
* of the database (first parameter) iff the code that created it is correct.
* @throws AssertionError in case the graphs are not the same.
*/
public static void assertSameGraph(GraphDatabaseService database, String sameGraphCypher) {
assertSameGraph(database, sameGraphCypher, InclusionPolicies.all());
}
/**
* Assert that the graph in the given database is exactly the same as the graph that would be created by the given
* Cypher query. The only thing that can be different in those two graphs are IDs of nodes and relationships
* and nodes/relationships/properties explicitly excluded from comparisons by provided inclusionPolicies.
* Properties values are converted to {@link String} before comparison, which means 123L (long) is equal to 123 (int),
* but also "123" (String) is considered equal to 123 (int).
*
* @param database first graph, typically the one that has been created by some code that is being tested by this
* method.
* @param sameGraphCypher second graph expressed as a Cypher create statement, which communicates the desired state
* of the database (first parameter) iff the code that created it is correct.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships/properties in the comparisons.
* @throws AssertionError in case the graphs are not the same.
*/
public static void assertSameGraph(GraphDatabaseService database, String sameGraphCypher, InclusionPolicies inclusionPolicies) {
if (database == null) {
throw new IllegalArgumentException("Database must not be null");
}
if (sameGraphCypher == null || sameGraphCypher.trim().isEmpty()) {
assertEmpty(database, inclusionPolicies);
return;
}
GraphDatabaseService otherDatabase = createTemporaryDb();
otherDatabase.execute(sameGraphCypher);
try {
assertSameGraph(database, otherDatabase, inclusionPolicies);
} finally {
otherDatabase.shutdown();
}
}
/**
* Compare that the graph in the given database is exactly the same as the graph that would be created by the given
* Cypher query. It is transaction-safe method, it can be used in stored procedures, without making the TopLevelTransaction failed.
* The only thing that can be different in those two graphs are IDs of nodes and relationships.
* Properties values are converted to {@link String} before comparison, which means 123L (long) is equal to 123 (int),
* but also "123" (String) is considered equal to 123 (int).
*
* @param database first graph, typically the one that has been created by some code that is being tested by this
* method.
* @param sameGraphCypher second graph expressed as a Cypher create statement, which communicates the desired state
* of the database (first parameter) iff the code that created it is correct.
* @return boolean value true in case the graphs are the same, false otherwise
*/
public boolean areSameGraph(GraphDatabaseService database, String sameGraphCypher) {
return areSameGraph(database, sameGraphCypher, InclusionPolicies.all());
}
/**
* Compare that the graph in the given database is exactly the same as the graph that would be created by the given
* Cypher query. It is transaction-safe method, it can be used in stored procedures, without making the TopLevelTransaction failed.
* The only thing that can be different in those two graphs are IDs of nodes and relationships
* and nodes/relationships/properties explicitly excluded from comparisons by provided inclusionPolicies.
* Properties values are converted to {@link String} before comparison, which means 123L (long) is equal to 123 (int),
* but also "123" (String) is considered equal to 123 (int).
*
* @param database first graph, typically the one that has been created by some code that is being tested by this
* method.
* @param sameGraphCypher second graph expressed as a Cypher create statement, which communicates the desired state
* of the database (first parameter) iff the code that created it is correct.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships/properties in the comparisons.
* @return boolean value true in case the graphs are the same, false otherwise
*/
public static boolean areSameGraph(GraphDatabaseService database, String sameGraphCypher, InclusionPolicies inclusionPolicies) {
if (database == null) {
throw new IllegalArgumentException("Database must not be null");
}
if (sameGraphCypher == null || sameGraphCypher.trim().isEmpty()) {
return isEmpty(database, inclusionPolicies);
}
GraphDatabaseService otherDatabase = createTemporaryDb();
otherDatabase.execute(sameGraphCypher);
try {
return areSameGraph(database, otherDatabase, inclusionPolicies);
} finally {
otherDatabase.shutdown();
}
}
/**
* Assert that the graph that would be created by the given Cypher query is a subgraph of the graph in the given
* database. This means that every node and every relationship in the (Cypher) subgraph must be present in the
* (database) graph.
*
* Nodes are considered equal if they have the exact same labels and properties. Relationships
* are considered equal if they have the same type and properties. IDs of nodes and relationships are not taken
* into account. Properties are included for comparison based on the InclusionPolicies.
* Properties values are converted to {@link String} before comparison, which means 123L (long) is
* equal to 123 (int), but also "123" (String) is considered equal to 123 (int).
*
* This method is useful for testing that some portion of a graph has been created correctly without the need to
* express the entire graph structure in Cypher.
*
* @param database first graph, typically the one that has been created by some code that is being tested by
* this method.
* @param subgraphCypher second graph expressed as a Cypher create statement, which communicates the desired state
* of the database (first parameter) iff the code that created it is correct.
* @throws AssertionError in case the "cypher" graph is not a subgraph of the "database" graph.
*/
public static void assertSubgraph(GraphDatabaseService database, String subgraphCypher) {
assertSubgraph(database, subgraphCypher, InclusionPolicies.all());
}
/**
* Assert that the graph that would be created by the given Cypher query is a subgraph of the graph in the given
* database. This means that every node and every relationship in the (Cypher) subgraph included as specified by
* the inclusionPolicies must be present in the (database) graph.
*
* Nodes are considered equal if they have the exact same labels and properties. Relationships
* are considered equal if they have the same type and properties. IDs of nodes and relationships are not taken
* into account. Properties are included for comparison based on the inclusionPolicies.
* Properties values are converted to {@link String} before comparison, which means 123L (long) is
* equal to 123 (int), but also "123" (String) is considered equal to 123 (int).
*
* This method is useful for testing that some portion of a graph has been created correctly without the need to
* express the entire graph structure in Cypher.
*
* @param database first graph, typically the one that has been created by some code that is being tested by
* this method.
* @param subgraphCypher second graph expressed as a Cypher create statement, which communicates the desired state
* of the database (first parameter) iff the code that created it is correct.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships/properties or not.
* @throws AssertionError in case the "cypher" graph is not a subgraph of the "database" graph.
*/
public static void assertSubgraph(GraphDatabaseService database, String subgraphCypher, InclusionPolicies inclusionPolicies) {
if (database == null) {
throw new IllegalArgumentException("Database must not be null");
}
if (subgraphCypher == null || subgraphCypher.trim().isEmpty()) {
throw new IllegalArgumentException("Cypher statement must not be null or empty");
}
GraphDatabaseService otherDatabase = createTemporaryDb();
otherDatabase.execute(subgraphCypher);
try {
assertSubgraph(database, otherDatabase, inclusionPolicies);
} finally {
otherDatabase.shutdown();
}
}
/**
* Check that the graph that would be created by the given Cypher query is a subgraph of the graph in the given
* database. This means that every node and every relationship in the (Cypher) subgraph must be present in the
* (database) graph. It is transaction-safe method, it can be used in stored procedures, without making the
* TopLevelTransaction failed.
*
* Nodes are considered equal if they have the exact same labels and properties. Relationships
* are considered equal if they have the same type and properties. IDs of nodes and relationships are not taken
* into account. Properties are included for comparison based on the InclusionPolicies.
* Properties values are converted to {@link String} before comparison, which means 123L (long) is
* equal to 123 (int), but also "123" (String) is considered equal to 123 (int).
*
* This method is useful for testing that some portion of a graph has been created correctly without the need to
* express the entire graph structure in Cypher.
*
* @param database first graph, typically the one that has been created by some code that is being tested by
* this method.
* @param subgraphCypher second graph expressed as a Cypher create statement, which communicates the desired state
* of the database (first parameter) iff the code that created it is correct.
* @return boolean value true in case the "cypher" graph is a subgraph of the "database" graph, false otherwise.
*/
public static boolean isSubgraph(GraphDatabaseService database, String subgraphCypher) {
return isSubgraph(database, subgraphCypher, InclusionPolicies.all());
}
/**
* Check that the graph that would be created by the given Cypher query is a subgraph of the graph in the given
* database. This means that every node and every relationship in the (Cypher) subgraph included as specified by
* the inclusionPolicies must be present in the (database) graph. It is transaction-safe method, it can be used
* in stored procedures, without making the TopLevelTransaction failed.
*
* Nodes are considered equal if they have the exact same labels and properties. Relationships
* are considered equal if they have the same type and properties. IDs of nodes and relationships are not taken
* into account. Properties are included for comparison based on the InclusionPolicies.
* Properties values are converted to {@link String} before comparison, which means 123L (long) is
* equal to 123 (int), but also "123" (String) is considered equal to 123 (int).
*
* This method is useful for testing that some portion of a graph has been created correctly without the need to
* express the entire graph structure in Cypher.
*
* @param database first graph, typically the one that has been created by some code that is being tested by
* this method.
* @param subgraphCypher second graph expressed as a Cypher create statement, which communicates the desired state
* of the database (first parameter) iff the code that created it is correct.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships/properties or not.
* @return boolean value true in case the "cypher" graph is a subgraph of the "database" graph, false otherwise.
*/
public static boolean isSubgraph(GraphDatabaseService database, String subgraphCypher, InclusionPolicies inclusionPolicies) {
if (database == null) {
throw new IllegalArgumentException("Database must not be null");
}
if (subgraphCypher == null || subgraphCypher.trim().isEmpty()) {
throw new IllegalArgumentException("Cypher statement must not be null or empty");
}
GraphDatabaseService otherDatabase = createTemporaryDb();
otherDatabase.execute(subgraphCypher);
try {
return isSubgraph(database, otherDatabase, inclusionPolicies);
} finally {
otherDatabase.shutdown();
}
}
/**
* Assert that the database is empty.
*
* @param database to run the assertion against.
*/
public static void assertEmpty(GraphDatabaseService database) {
assertEmpty(database, InclusionPolicies.all());
}
/**
* Assert that the database is empty.
*
* @param database to run the assertion against.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships/properties or not in the assertion.
*/
public static void assertEmpty(GraphDatabaseService database, InclusionPolicies inclusionPolicies) {
if (database == null) {
throw new IllegalArgumentException("Database must not be null");
}
try (Transaction tx = database.beginTx()) {
for (Node node : database.getAllNodes()) {
if (inclusionPolicies.getNodeInclusionPolicy().include(node)) {
fail("The database is not empty, there are nodes");
}
}
for (Relationship relationship : database.getAllRelationships()) {
if (inclusionPolicies.getRelationshipInclusionPolicy().include(relationship)) {
fail("The database is not empty, there are relationships");
}
}
tx.success();
}
}
/**
* Check that the database is empty.
*
* @param database to run the assertion against.
* @return boolean true if the database is empty, false otherwise.
*/
public static boolean isEmpty(GraphDatabaseService database) {
return isEmpty(database, InclusionPolicies.all());
}
/**
* Check that the database is empty.
*
* @param database to run the assertion against.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships/properties or not in the assertion.
* @return boolean true if the database is empty, false otherwise.
*/
public static boolean isEmpty(GraphDatabaseService database, InclusionPolicies inclusionPolicies) {
if (database == null) {
throw new IllegalArgumentException("Database must not be null");
}
for (Node node : database.getAllNodes()) {
if (inclusionPolicies.getNodeInclusionPolicy().include(node)) {
return false;
}
}
for (Relationship relationship : database.getAllRelationships()) {
if (inclusionPolicies.getRelationshipInclusionPolicy().include(relationship)) {
return false;
}
}
return true;
}
/**
* Assert that the database is not empty.
*
* @param database to run the assertion against.
*/
public static void assertNotEmpty(GraphDatabaseService database) {
assertNotEmpty(database, InclusionPolicies.all());
}
/**
* Assert that the database is not empty.
*
* @param database to run the assertion against.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships/properties or not in the assertion.
*/
public static void assertNotEmpty(GraphDatabaseService database, InclusionPolicies inclusionPolicies) {
if (database == null) {
throw new IllegalArgumentException("Database must not be null");
}
try (Transaction tx = database.beginTx()) {
for (Node node : database.getAllNodes()) {
if (inclusionPolicies.getNodeInclusionPolicy().include(node)) {
return;
}
}
for (Relationship relationship : database.getAllRelationships()) {
if (inclusionPolicies.getRelationshipInclusionPolicy().include(relationship)) {
return;
}
}
tx.success();
}
fail(String.format("The database is empty with respect to inclusion policies: %s",
inclusionPolicies.toString()));
}
/**
* Clear the graph by deleting all nodes and relationships.
*
* @param database graph, typically the one that has been created by some code that is being tested.
*/
public static void clearGraph(GraphDatabaseService database) {
clearGraph(database, InclusionPolicies.all());
}
/**
* Clear the graph by deleting all nodes and relationships specified by inclusionPolicies
*
* @param database graph, typically the one that has been created by some code that is being tested.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships or not.
* Note that {@link PropertyInclusionPolicy}s are ignored when clearing the graph.
*/
public static void clearGraph(GraphDatabaseService database, InclusionPolicies inclusionPolicies) {
if (database == null) {
throw new IllegalArgumentException("Database must not be null");
}
for (Relationship rel : database.getAllRelationships()) {
if (isRelationshipIncluded(rel, inclusionPolicies)) {
rel.delete();
}
}
for (Node node : database.getAllNodes()) {
if (isNodeIncluded(node, inclusionPolicies)) {
node.delete();
}
}
}
/**
* Prints the contents of the graph.
*
* @param database to print.
*/
public static void printGraph(GraphDatabaseService database) {
printGraph(database, InclusionPolicies.all());
}
/**
* Prints the contents of the graph.
*
* @param database to print.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships or not.
* Note that {@link PropertyInclusionPolicy}s are ignored when printing the graph.
*/
public static void printGraph(GraphDatabaseService database, InclusionPolicies inclusionPolicies) {
if (database == null) {
throw new IllegalArgumentException("Database must not be null");
}
try (Transaction tx = database.beginTx()) {
System.out.println("Nodes:");
for (Node node : database.getAllNodes()) {
if (isNodeIncluded(node, inclusionPolicies)) {
System.out.println(EntityUtils.nodeToString(node));
}
}
System.out.println("Relationships:");
for (Relationship rel : database.getAllRelationships()) {
if (isRelationshipIncluded(rel, inclusionPolicies)) {
System.out.println(EntityUtils.relationshipToString(rel));
}
}
tx.success();
}
}
/**
* Compare two {@link org.neo4j.graphdb.Node} to verify they contain the same labels and properties.
*
* @param node1 first node to compare.
* @param node2 second node to compare.
* @return boolean are the nodes the same.
*/
public static boolean areSame(Node node1, Node node2) {
return areSame(node1, node2, InclusionPolicies.all());
}
/**
* Compare two {@link org.neo4j.graphdb.Relationship} to verify they contain the same labels and properties.
*
* @param relationship1 first relationship to compare.
* @param relationship2 second relationship to compare.
* @return boolean are the relationships the same.
*/
public static boolean areSame(Relationship relationship1, Relationship relationship2) {
return areSame(relationship1, relationship2, InclusionPolicies.all());
}
/**
* Compare two {@link org.neo4j.graphdb.Node} to verify they contain the same labels and properties.
*
* @param node1 first node to compare.
* @param node2 second node to compare.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships or not.
* Note that {@link PropertyInclusionPolicy}s are ignored when printing the graph.
* @return boolean are the nodes the same.
*/
public static boolean areSame(Node node1, Node node2, InclusionPolicies inclusionPolicies) {
return haveSameLabels(node1, node2) && haveSameProperties(node1, node2, inclusionPolicies);
}
/**
* Compare two {@link org.neo4j.graphdb.Relationship} to verify they contain the same labels and properties.
*
* @param relationship1 first relationship to compare.
* @param relationship2 second relationship to compare.
* @param inclusionPolicies {@link InclusionPolicies} deciding whether to include nodes/relationships or not.
* Note that {@link PropertyInclusionPolicy}s are ignored when printing the graph.
* @return boolean are the relationships the same.
*/
public static boolean areSame(Relationship relationship1, Relationship relationship2, InclusionPolicies inclusionPolicies) {
return haveSameType(relationship1, relationship2) && haveSameProperties(relationship1, relationship2, inclusionPolicies);
}
private static void assertSameGraph(GraphDatabaseService database, GraphDatabaseService otherDatabase, InclusionPolicies InclusionPolicies) {
try (Transaction tx = database.beginTx()) {
try (Transaction tx2 = otherDatabase.beginTx()) {
doAssertSubgraph(database, otherDatabase, InclusionPolicies, "existing database");
doAssertSubgraph(otherDatabase, database, InclusionPolicies, "Cypher-created database");
tx2.failure();
}
tx.failure();
}
}
private static boolean areSameGraph(GraphDatabaseService database, GraphDatabaseService otherDatabase, InclusionPolicies InclusionPolicies) {
try (Transaction tx = database.beginTx()) {
try (Transaction tx2 = otherDatabase.beginTx()) {
try {
doAssertSubgraph(database, otherDatabase, InclusionPolicies, "existing database");
doAssertSubgraph(otherDatabase, database, InclusionPolicies, "Cypher-created database");
} catch (AssertionError error) {
return false;
}finally {
tx2.success();
tx.success();
}
}
}
return true;
}
private static boolean isSubgraph(GraphDatabaseService database, GraphDatabaseService otherDatabase, InclusionPolicies InclusionPolicies) {
try (Transaction tx = database.beginTx()) {
try (Transaction tx2 = otherDatabase.beginTx()) {
try {
doAssertSubgraph(database, otherDatabase, InclusionPolicies, "existing database");
} catch (AssertionError error) {
return false;
} finally {
tx2.success();
tx.success();
}
}
}
return true;
}
private static void assertSubgraph(GraphDatabaseService database, GraphDatabaseService otherDatabase, InclusionPolicies InclusionPolicies) {
try (Transaction tx = database.beginTx()) {
try (Transaction tx2 = otherDatabase.beginTx()) {
doAssertSubgraph(database, otherDatabase, InclusionPolicies, "existing database");
tx2.failure();
}
tx.failure();
}
}
private static void doAssertSubgraph(GraphDatabaseService database, GraphDatabaseService otherDatabase, InclusionPolicies inclusionPolicies, String firstDatabaseName) {
Map sameNodesMap = buildSameNodesMap(database, otherDatabase, inclusionPolicies, firstDatabaseName);
Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy