blob: fe90619dc987010f8d1146fc85c8ac3f38a0212b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.tinkerpop.gremlin;
import org.apache.commons.configuration.Configuration;
import org.apache.tinkerpop.gremlin.process.computer.GraphComputer;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.B_LP_O_P_S_SE_SL_Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.B_LP_O_S_SE_SL_Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.B_O_S_SE_SL_Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.B_O_Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.O_Traverser;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.Property;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.apache.tinkerpop.gremlin.structure.util.GraphFactory;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* Those developing Gremlin implementations must provide a GraphProvider implementation so that the
* different test suites know how to instantiate their implementations. Implementers may choose to have multiple
* {@code GraphProvider} implementations to mix and match with multiple test suite implementations. For example,
* create one {@code GraphProvider} that has no indices defined and a separate {@code GraphProvider} that has
* indices. Then create separate test suite implementations for each {@code GraphProvider}. This approach will
* have the test suites executed once for each {@code GraphProvider} ensuring that the {@link Graph} implementation
* works under multiple configurations. Consider making these "extra" tests "integration tests" so that they
* don't have to be executed on every run of the build so as to save time. Run the "integration tests" periodically
* to ensure overall compliance.
*
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
public interface GraphProvider {
/**
* Implementations from {@code gremlin-core} that need to be part of the clear process. This does not exempt
* providers from having to register their extensions to any of these classes, but does prevent them from
* having to register them in addition to their own.
*/
public static final Set<Class> CORE_IMPLEMENTATIONS = new HashSet<Class>() {{
add(__.class);
add(DefaultGraphTraversal.class);
add(GraphTraversalSource.class);
add(B_O_S_SE_SL_Traverser.class);
add(B_LP_O_P_S_SE_SL_Traverser.class);
add(B_LP_O_S_SE_SL_Traverser.class);
add(B_O_Traverser.class);
add(O_Traverser.class);
}};
/**
* Create a {@link GraphTraversalSource} from a {@link Graph} instance. The default implementation does not
* use {@link GraphComputer} so providers should override as necessary if their implementation is testing
* something that requires a different engine type, like {@link GraphComputer}.
*/
public default GraphTraversalSource traversal(final Graph graph) {
return graph.traversal();
}
/**
* Create a {@link GraphTraversalSource} from a {@link Graph} instance. The default implementation does not use
* {@link GraphComputer} so providers should override as necessary if their implementation is testing
* something that requires a different engine type, like {@link GraphComputer}.
* <p/>
* Implementations should apply strategies as necessary to the
* {@link GraphTraversalSource} before calling it's {@code create} method.
*/
public default GraphTraversalSource traversal(final Graph graph, final TraversalStrategy... strategies) {
return this.traversal(graph).withStrategies(strategies);
}
/**
* Create a {@link GraphComputer} from the {@link Graph} instance. The default implementation simply calls {@code graph.compute()}.
*
* @param graph the graph to get the graph computer from
* @return a new graph computer
*/
public default GraphComputer getGraphComputer(final Graph graph) {
return graph.compute();
}
/**
* Creates a new {@link Graph} instance using the default
* {@code org.apache.commons.configuration.Configuration} from
* {@link #standardGraphConfiguration(Class, String, LoadGraphWith.GraphData)}.
*/
default public Graph standardTestGraph(final Class<?> test, final String testMethodName, final LoadGraphWith.GraphData loadGraphWith) {
return GraphFactory.open(standardGraphConfiguration(test, testMethodName, loadGraphWith));
}
/**
* Creates a new {@link Graph} instance from the {@code Configuration} object using {@link GraphFactory}.
* The assumption here is that the {@code Configuration} has been created by one of the
* {@link #newGraphConfiguration(String, Class, String, LoadGraphWith.GraphData)} methods and has therefore
* already been modified by the implementation as necessary for {@link Graph} creation.
*/
default public Graph openTestGraph(final Configuration config) {
return GraphFactory.open(config);
}
/**
* Gets the {@code Configuration} object that can construct a {@link Graph} instance from {@link GraphFactory}.
* Note that this method should create a {@link Graph} using the {@code graphName} of "standard", meaning it
* should always return a configuration instance that generates the same {@link Graph} from the
* {@link GraphFactory}.
*/
default public Configuration standardGraphConfiguration(final Class<?> test, final String testMethodName, final LoadGraphWith.GraphData loadGraphWith) {
return newGraphConfiguration("standard", test, testMethodName, Collections.<String, Object>emptyMap(), loadGraphWith);
}
/**
* If possible (usually with persisted graph) clear the space on disk given the configuration that would be used
* to construct the graph. The default implementation simply calls
* {@link #clear(Graph, org.apache.commons.configuration.Configuration)} with
* a null graph argument.
* <p/>
* Implementations should be able to accept an argument of null for the {@code org.apache.commons.configuration.Configuration}
* as well, and a proper handling is needed. Otherwise, a NullPointerException may be thrown.
*/
public default void clear(final Configuration configuration) throws Exception {
clear(null, configuration);
}
/**
* Clears a {@link Graph} of all data and settings. Implementations will have different ways of handling this.
* It is typically expected that {@link Graph#close()} will be called and open transactions will be closed.
* For a brute force approach, implementers can simply delete data directories provided in the configuration.
* Implementers may choose a more elegant approach if it exists.
* <p/>
* Implementations should be able to accept an argument of null for the {@code Graph}, in which case the only action
* that can be performed is a clear given the configuration. The method will typically be called this way
* as clean up task on setup to ensure that a persisted graph has a clear space to create a test graph.
* <p/>
* Implementations should be able to accept an argument of null for the {@code org.apache.commons.configuration.Configuration}
* as well, and a proper handling is needed. Otherwise, a NullPointerException may be thrown.
* <p/>
* Calls to this method may occur multiple times for a specific test. Develop this method to be idempotent.
*/
public void clear(final Graph graph, final Configuration configuration) throws Exception;
/**
* Converts an identifier from a test to an identifier accepted by the {@link Graph} instance. Test that try to
* utilize an Element identifier will pass it to this method before usage. This method should be sure to
* be consistent in the return value such that calling it with "x" should always return the same transformed
* value.
*/
default public Object convertId(final Object id, final Class<? extends Element> c) {
return id;
}
/**
* Converts an label from a test to an label accepted by the Graph instance. Test that try to
* utilize a label will pass it to this method before usage.
*/
default public String convertLabel(final String label) {
return label;
}
/**
* When implementing this method ensure that a test suite can override any settings EXCEPT the
* "gremlin.graph" setting which should be defined by the implementer. It should provide a
* {@code Configuration} that will generate a graph unique to that {@code graphName}.
*
* @param graphName a unique test graph name
* @param test the test class
* @param testMethodName the name of the test
* @param configurationOverrides settings to override defaults with.
* @param loadGraphWith the data set to load and will be null if no data is to be loaded
*/
public Configuration newGraphConfiguration(final String graphName,
final Class<?> test,
final String testMethodName,
final Map<String, Object> configurationOverrides,
final LoadGraphWith.GraphData loadGraphWith);
/**
* When implementing this method ensure that a test suite can override any settings EXCEPT the
* "gremlin.graph" setting which should be defined by the implementer. It should provide a
* {@code Configuration} that will generate a graph unique to that {@code graphName}.
*
* @param graphName a unique test graph name
* @param test the test class
* @param testMethodName the name of the test
* @param loadGraphWith the data set to load and will be null if no data is to be loaded
*/
default public Configuration newGraphConfiguration(final String graphName,
final Class<?> test,
final String testMethodName,
final LoadGraphWith.GraphData loadGraphWith) {
return newGraphConfiguration(graphName, test, testMethodName, new HashMap<>(), loadGraphWith);
}
/**
* Tests are annotated with a {@link LoadGraphWith} annotation. These annotations tell the test what kind of data
* to preload into the graph instance. It is up to the implementation to load the graph with the data specified
* by that annotation. This method also represents the place where indices should be configured according the
* the {@link Graph} implementation's API. Implementers can use the {@code testClass} and {@code testName}
* arguments to implement test specific configurations to their graphs.
*
* @param graph the {@link Graph} instance to load data into constructed by this {@code GraphProvider}
* @param loadGraphWith the annotation for the currently running test - this value may be null if no graph
* data is to be loaded in front of the test.
* @param testClass the test class being executed
* @param testName the name of the test method being executed
*/
public void loadGraphData(final Graph graph, final LoadGraphWith loadGraphWith, final Class testClass, final String testName);
/**
* Get the set of concrete implementations of certain classes and interfaces utilized by the test suite. This
* method should return any implementations or extensions of the following interfaces or classes:
* <ul>
* <li>{@link Edge}</li>
* <li>{@link Element}</li>
* <li>{@link DefaultGraphTraversal}</li>
* <li>{@link Graph}</li>
* <li>{@link Graph.Variables}</li>
* <li>{@link GraphTraversal}</li>
* <li>{@link B_LP_O_P_S_SE_SL_Traverser}</li>
* <li>{@link Property}</li>
* <li>{@link B_O_S_SE_SL_Traverser}</li>
* <li>{@link Traversal}</li>
* <li>{@link Traverser}</li>
* <li>{@link Vertex}</li>
* <li>{@link VertexProperty}</li>
* </ul>
* <p/>
* The test suite only enforces registration of the following core structure interfaces (i.e. these classes must
* be registered or the tests will fail to execute):
* <ul>
* <li>{@link Edge}</li>
* <li>{@link Element}</li>
* <li>{@link Graph}</li>
* <li>{@link Graph.Variables}</li>
* <li>{@link Property}</li>
* <li>{@link Vertex}</li>
* <li>{@link VertexProperty}</li>
* </ul>
* <p/>
* The remaining interfaces and classes should be registered however as failure to do so, might cause failures
* in the Groovy environment testing suite.
* <p/>
* Internally speaking, tests that make use of this method should bind in {@link #CORE_IMPLEMENTATIONS} to the
* {@link Set} because these represent {@code gremlin-core} implementations that are likely not registered
* by the vendor implementations.
*/
public Set<Class> getImplementations();
/**
* Helper method for those build {@link GraphProvider} implementations that need a standard working directory
* for tests (e.g. graphs that persist data to disk). Typically, there is no need to override the default
* behavior of this method and if it is overridden, it is usually best to continue to use the {@link TestHelper}
* to produce the working directory as it will create the path in the appropriate build directories.
* @return UNIX-formatted absolute directory path
*/
public default String getWorkingDirectory() {
return TestHelper.makeTestDataDirectory(this.getClass(), "graph-provider-data");
}
/**
* Returns a {@link TestListener} implementation that provides feedback to the {@link GraphProvider} implementation.
* By default, this returns an empty listener.
*/
public default Optional<TestListener> getTestListener() {
return Optional.empty();
}
/**
* An annotation to be applied to a {@code GraphProvider} implementation that provides additional information
* about its intentions. The {@code Descriptor} is required by those {@code GraphProvider} implementations
* that will be assigned to test suites that use
* {@link org.apache.tinkerpop.gremlin.process.traversal.TraversalEngine.Type#COMPUTER}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Descriptor {
/**
* The {@link GraphComputer} implementation that the {@code GraphProvider} will use when it constructs
* a {@link Traversal} with {@link #traversal(Graph)} or {@link #traversal(Graph, TraversalStrategy[])}.
* This value should be null if a {@link GraphComputer} is not being used.
*/
public Class<? extends GraphComputer> computer();
}
public interface TestListener {
public default void onTestStart(final Class<?> test, final String testName) {
// do nothing
}
public default void onTestEnd(final Class<?> test, final String testName) {
// do nothing
}
}
}