com.graphaware.test.unit.GraphUnit Maven / Gradle / Ivy
/*
* Copyright (c) 2014 GraphAware
*
* This file is part of GraphAware.
*
* GraphAware 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.InclusionPolicies;
import com.graphaware.common.util.PropertyContainerUtils;
import org.neo4j.graphdb.*;
import org.neo4j.test.TestGraphDatabaseFactory;
import org.neo4j.tooling.GlobalGraphOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import static com.graphaware.common.util.DatabaseUtils.registerShutdownHook;
import static com.graphaware.common.util.PropertyContainerUtils.*;
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.tooling.GlobalGraphOperations.at;
/**
* 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 Logger LOG = LoggerFactory.getLogger(GraphUnit.class);
/**
* 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 com.graphaware.common.policy.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 = new TestGraphDatabaseFactory().newImpermanentDatabase();
registerShutdownHook(otherDatabase);
otherDatabase.execute(sameGraphCypher);
try {
assertSameGraph(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 com.graphaware.common.policy.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 = new TestGraphDatabaseFactory().newImpermanentDatabase();
registerShutdownHook(otherDatabase);
otherDatabase.execute(subgraphCypher);
try {
assertSubgraph(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 com.graphaware.common.policy.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 : GlobalGraphOperations.at(database).getAllNodes()) {
if (inclusionPolicies.getNodeInclusionPolicy().include(node)) {
fail("The database is not empty, there are nodes");
}
}
for (Relationship relationship : GlobalGraphOperations.at(database).getAllRelationships()) {
if (inclusionPolicies.getRelationshipInclusionPolicy().include(relationship)) {
fail("The database is not empty, there are relationships");
}
}
tx.success();
}
}
/**
* 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 com.graphaware.common.policy.InclusionPolicies} deciding whether to include nodes/relationships or not.
* Note that {@link com.graphaware.common.policy.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 : GlobalGraphOperations.at(database).getAllRelationships()) {
if (isRelationshipIncluded(rel, inclusionPolicies)) {
rel.delete();
}
}
for (Node node : GlobalGraphOperations.at(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 com.graphaware.common.policy.InclusionPolicies} deciding whether to include nodes/relationships or not.
* Note that {@link com.graphaware.common.policy.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 : GlobalGraphOperations.at(database).getAllNodes()) {
if (isNodeIncluded(node, inclusionPolicies)) {
System.out.println(PropertyContainerUtils.nodeToString(node));
}
}
System.out.println("Relationships:");
for (Relationship rel : GlobalGraphOperations.at(database).getAllRelationships()) {
if (isRelationshipIncluded(rel, inclusionPolicies)) {
System.out.println(PropertyContainerUtils.relationshipToString(rel));
}
}
tx.success();
}
}
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 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