| /* |
| * 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.hugegraph.tinkerpop; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.lang.reflect.Method; |
| import java.net.URL; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.commons.configuration2.Configuration; |
| import org.apache.commons.configuration2.PropertiesConfiguration; |
| import org.apache.tinkerpop.gremlin.AbstractGraphProvider; |
| import org.apache.tinkerpop.gremlin.FeatureRequirement; |
| import org.apache.tinkerpop.gremlin.FeatureRequirements; |
| import org.apache.tinkerpop.gremlin.LoadGraphWith; |
| import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; |
| import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.LazyBarrierStrategy; |
| import org.apache.tinkerpop.gremlin.structure.Element; |
| import org.apache.tinkerpop.gremlin.structure.Graph; |
| import org.apache.tinkerpop.gremlin.structure.Graph.Features.VertexPropertyFeatures; |
| import org.apache.tinkerpop.gremlin.structure.Transaction; |
| import org.junit.Assert; |
| import org.junit.Assume; |
| import org.slf4j.Logger; |
| |
| import org.apache.hugegraph.HugeGraph; |
| import org.apache.hugegraph.config.CoreOptions; |
| import org.apache.hugegraph.perf.PerfUtil.Watched; |
| import org.apache.hugegraph.structure.HugeEdge; |
| import org.apache.hugegraph.structure.HugeElement; |
| import org.apache.hugegraph.structure.HugeProperty; |
| import org.apache.hugegraph.structure.HugeVertex; |
| import org.apache.hugegraph.structure.HugeVertexProperty; |
| import org.apache.hugegraph.testutil.Utils; |
| import org.apache.hugegraph.type.define.IdStrategy; |
| import org.apache.hugegraph.util.E; |
| import org.apache.hugegraph.util.Log; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.util.concurrent.RateLimiter; |
| |
| public class TestGraphProvider extends AbstractGraphProvider { |
| |
| private static final Logger LOG = Log.logger(TestGraphProvider.class); |
| |
| @SuppressWarnings("rawtypes") |
| private static final Set<Class> IMPLEMENTATIONS = ImmutableSet.of( |
| HugeEdge.class, |
| HugeElement.class, |
| HugeGraph.class, |
| HugeProperty.class, |
| HugeVertex.class, |
| HugeVertexProperty.class); |
| |
| private static final String FILTER = "test.tinkerpop.filter"; |
| private static final String DEFAULT_FILTER = "methods.filter"; |
| |
| private static final String TEST_CLASS = "testClass"; |
| private static final String TEST_METHOD = "testMethod"; |
| |
| private static final String LOAD_GRAPH = "loadGraph"; |
| private static final String STANDARD = "standard"; |
| private static final String REGULAR_LOAD = "regularLoad"; |
| |
| private static final String GREMLIN_GRAPH_KEY = "gremlin.graph"; |
| private static final String GREMLIN_GRAPH_VALUE = |
| "org.apache.hugegraph.tinkerpop.TestGraphFactory"; |
| |
| private static final String AKEY_CLASS_PREFIX = |
| "org.apache.tinkerpop.gremlin.structure." + |
| "PropertyTest.PropertyFeatureSupportTest"; |
| private static final String IO_CLASS_PREFIX = |
| "org.apache.tinkerpop.gremlin.structure.io.IoGraphTest"; |
| private static final String IO_TEST_PREFIX = |
| "org.apache.tinkerpop.gremlin.structure.io.IoTest"; |
| |
| private static final String EXPECT_CUSTOMIZED_ID = "expectCustomizedId"; |
| private static final Set<String> CUSTOMIZED_ID_METHODS = ImmutableSet.of( |
| "shouldReadWriteSelfLoopingEdges", |
| "shouldEvaluateConnectivityPatterns"); |
| |
| private static final Set<String> ID_TYPES = ImmutableSet.of( |
| VertexPropertyFeatures.FEATURE_USER_SUPPLIED_IDS, |
| VertexPropertyFeatures.FEATURE_NUMERIC_IDS, |
| VertexPropertyFeatures.FEATURE_STRING_IDS); |
| |
| private static final RateLimiter LOG_RATE_LIMITER = |
| RateLimiter.create(1.0 / 300); |
| |
| private Map<String, String> blackMethods = new HashMap<>(); |
| private Map<String, TestGraph> graphs = new HashMap<>(); |
| private final String suite; |
| |
| public TestGraphProvider(String suite) throws IOException { |
| super(); |
| this.initBlackList(); |
| this.suite = suite; |
| } |
| |
| private void initBlackList() throws IOException { |
| String filter = Utils.getConf().getString(FILTER); |
| if (filter == null || filter.isEmpty()) { |
| filter = DEFAULT_FILTER; |
| } |
| |
| URL blackList = TestGraphProvider.class.getClassLoader() |
| .getResource(filter); |
| E.checkArgument(blackList != null, |
| "Can't find tests filter '%s' in resource directory", |
| filter); |
| File file = new File(blackList.getPath()); |
| E.checkArgument( |
| file.exists() && file.isFile() && file.canRead(), |
| "Need to specify a readable filter file rather than: %s", |
| file.toString()); |
| try (FileReader fr = new FileReader(file); |
| BufferedReader reader = new BufferedReader(fr)) { |
| String line; |
| while ((line = reader.readLine()) != null) { |
| if (line.isEmpty() || line.startsWith("#")) { |
| // Empty line or comment line |
| continue; |
| } |
| String[] parts = line.split(":"); |
| Assert.assertEquals("methods.filter proper format is: " + |
| "'testMethodName: ignore reason'", |
| 2, parts.length); |
| Assert.assertTrue( |
| "Test method name in methods.filter can't be empty", |
| parts[0] != null && !parts[0].trim().isEmpty()); |
| Assert.assertTrue( |
| "Reason why ignore in methods.filter can't be empty", |
| parts[1] != null && !parts[1].trim().isEmpty()); |
| this.blackMethods.putIfAbsent(parts[0], parts[1]); |
| } |
| } |
| } |
| |
| @Override |
| public Map<String, Object> getBaseConfiguration( |
| String graphName, |
| Class<?> testClass, String testMethod, |
| LoadGraphWith.GraphData graphData) { |
| // Check if test in blackList |
| String testFullName = testClass.getCanonicalName() + "." + testMethod; |
| int index = testFullName.indexOf('@') == -1 ? |
| testFullName.length() : testFullName.indexOf('@'); |
| |
| testFullName = testFullName.substring(0, index); |
| Assume.assumeFalse( |
| String.format("Test %s will be ignored with reason: %s", |
| testFullName, this.blackMethods.get(testFullName)), |
| this.blackMethods.containsKey(testFullName)); |
| |
| LOG.debug("Full name of test is: {}", testFullName); |
| LOG.debug("Prefix of test is: {}", testFullName.substring(0, index)); |
| HashMap<String, Object> confMap = new HashMap<>(); |
| PropertiesConfiguration config = Utils.getConf(); |
| Iterator<String> keys = config.getKeys(); |
| while (keys.hasNext()) { |
| String key = keys.next(); |
| confMap.put(key, config.getProperty(key)); |
| } |
| String storePrefix = config.getString(CoreOptions.STORE.name()); |
| confMap.put(CoreOptions.STORE.name(), |
| storePrefix + "_" + this.suite + "_" + graphName); |
| confMap.put(GREMLIN_GRAPH_KEY, GREMLIN_GRAPH_VALUE); |
| confMap.put(TEST_CLASS, testClass); |
| confMap.put(TEST_METHOD, testMethod); |
| confMap.put(LOAD_GRAPH, graphData); |
| confMap.put(EXPECT_CUSTOMIZED_ID, customizedId(testClass, testMethod)); |
| |
| return confMap; |
| } |
| |
| private static boolean customizedId(Class<?> test, String testMethod) { |
| Method method; |
| try { |
| method = test.getDeclaredMethod(testMethod); |
| } catch (NoSuchMethodException ignored) { |
| return false; |
| } |
| FeatureRequirements features = |
| method.getAnnotation(FeatureRequirements.class); |
| if (features == null) { |
| return false; |
| } |
| for (FeatureRequirement feature : features.value()) { |
| if (feature.featureClass() == Graph.Features.VertexFeatures.class && |
| ID_TYPES.contains(feature.feature())) { |
| // Expect CUSTOMIZED_ID if want to pass id to create vertex |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static String getAKeyType(Class<?> clazz, String method) { |
| if (clazz.getCanonicalName().startsWith(AKEY_CLASS_PREFIX)) { |
| return method.substring(method.indexOf('[') + 9, |
| method.indexOf('(') - 6); |
| } |
| return null; |
| } |
| |
| private static String getIoType(Class<?> clazz, String method) { |
| if (clazz.getCanonicalName().startsWith(IO_CLASS_PREFIX)) { |
| return method.substring(method.indexOf('[') + 1, |
| method.indexOf(']')); |
| } |
| return null; |
| } |
| |
| private static boolean isIoTest(Class<?> clazz) { |
| return clazz.getCanonicalName().startsWith(IO_TEST_PREFIX); |
| } |
| |
| private static IdStrategy idStrategy(Configuration config) { |
| Class<?> testClass = (Class<?>) config.getProperty(TEST_CLASS); |
| String testMethod = config.getString(TEST_METHOD); |
| |
| // Set id strategy |
| IdStrategy idStrategy = IdStrategy.AUTOMATIC; |
| if (config.getBoolean(EXPECT_CUSTOMIZED_ID) || isIoTest(testClass) || |
| CUSTOMIZED_ID_METHODS.contains(testMethod)) { |
| /* |
| * Use CUSTOMIZED_ID if the following case are met: |
| * 1.Expect CUSTOMIZED_ID for some specific features |
| * 2.IoTest, it will copy vertex from IO or other graph |
| * 3.Some tests that need to pass vertex id manually |
| */ |
| idStrategy = IdStrategy.CUSTOMIZE_STRING; |
| } |
| return idStrategy; |
| } |
| |
| @Watched |
| private TestGraph newTestGraph(final Configuration config) { |
| TestGraph testGraph = ((TestGraph) super.openTestGraph(config)); |
| testGraph.initBackend(); |
| return testGraph; |
| } |
| |
| @Watched |
| @Override |
| public Graph openTestGraph(final Configuration config) { |
| String graphName = config.getString(CoreOptions.STORE.name()); |
| Class<?> testClass = (Class<?>) config.getProperty(TEST_CLASS); |
| String testMethod = config.getString(TEST_METHOD); |
| |
| TestGraph testGraph = this.graphs.get(graphName); |
| if (testGraph == null) { |
| this.graphs.putIfAbsent(graphName, this.newTestGraph(config)); |
| } else if (testGraph.closed()) { |
| this.graphs.put(graphName, this.newTestGraph(config)); |
| } |
| testGraph = this.graphs.get(graphName); |
| |
| // Ensure tx clean |
| testGraph.tx().rollback(); |
| |
| // Define property key 'aKey' based on specified type in test name |
| String aKeyType = getAKeyType(testClass, testMethod); |
| if (aKeyType != null) { |
| testGraph.initPropertyKey("aKey", aKeyType); |
| } |
| |
| if (testMethod.equals( |
| "shouldHaveTruncatedStringRepresentationForEdgeProperty")) { |
| testGraph.initPropertyKey("long", "String"); |
| } else { |
| testGraph.initPropertyKey("long", "Long"); |
| } |
| |
| // Basic schema is initiated by default once a graph is open |
| testGraph.initBasicSchema(idStrategy(config), TestGraph.DEFAULT_VL); |
| if (testClass.getName().equals( |
| "org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest$Traversals")) { |
| testGraph.initEdgeLabelPersonKnowsPerson(); |
| testGraph.initEdgeLabelPersonCreatedSoftware(); |
| } else { |
| testGraph.initEdgeLabelDefaultKnowsDefault(TestGraph.DEFAULT_VL); |
| testGraph.initEdgeLabelDefaultCreatedDefault(TestGraph.DEFAULT_VL); |
| } |
| testGraph.tx().commit(); |
| |
| testGraph.loadedGraph(getIoType(testClass, testMethod)); |
| testGraph.autoPerson(false); |
| testGraph.ioTest(isIoTest(testClass)); |
| |
| Object loadGraph = config.getProperty(LOAD_GRAPH); |
| if (loadGraph != null && !graphName.endsWith(STANDARD)) { |
| this.loadGraphData(testGraph, (LoadGraphWith.GraphData) loadGraph); |
| } |
| |
| // Used for travis ci output log |
| if (LOG_RATE_LIMITER.tryAcquire(1)) { |
| LOG.info("Open graph '{}' for test '{}'", graphName, testMethod); |
| } |
| return testGraph; |
| } |
| |
| @Watched |
| @Override |
| public void clear(Graph graph, Configuration config) throws Exception { |
| TestGraph testGraph = (TestGraph) graph; |
| if (testGraph == null) { |
| return; |
| } |
| String graphName = config.getString(CoreOptions.STORE.name()); |
| if (!testGraph.initedBackend()) { |
| testGraph.close(); |
| } |
| if (testGraph.closed()) { |
| if (this.graphs.get(graphName) == testGraph) { |
| this.graphs.remove(graphName); |
| } |
| return; |
| } |
| |
| // Reset consumers |
| graph.tx().onReadWrite(Transaction.READ_WRITE_BEHAVIOR.AUTO); |
| graph.tx().onClose(Transaction.CLOSE_BEHAVIOR.ROLLBACK); |
| |
| // Ensure tx clean |
| graph.tx().rollback(); |
| |
| // Clear all data |
| Class<?> testClass = (Class<?>) config.getProperty(TEST_CLASS); |
| testGraph.clearAll(testClass.getCanonicalName()); |
| |
| LOG.debug("Clear graph '{}'", graphName); |
| } |
| |
| public void clear() { |
| for (TestGraph graph : this.graphs.values()) { |
| graph.clearBackend(); |
| } |
| for (TestGraph graph : this.graphs.values()) { |
| try { |
| graph.close(); |
| } catch (Exception e) { |
| LOG.error("Error while closing graph '{}'", graph, e); |
| } |
| } |
| this.graphs.clear(); |
| } |
| |
| @Watched |
| @SuppressWarnings("rawtypes") |
| @Override |
| public void loadGraphData(final Graph graph, |
| final LoadGraphWith loadGraphWith, |
| final Class testClass, |
| final String testName) { |
| if (loadGraphWith == null) { |
| super.loadGraphData(graph, loadGraphWith, testClass, testName); |
| return; |
| } |
| |
| this.loadGraphData(graph, loadGraphWith.value()); |
| |
| super.loadGraphData(graph, loadGraphWith, testClass, testName); |
| } |
| |
| @Watched |
| public void loadGraphData(final Graph graph, |
| final LoadGraphWith.GraphData loadGraphWith) { |
| TestGraph testGraph = (TestGraph) graph; |
| |
| // Clear basic schema initiated in openTestGraph |
| testGraph.clearAll(""); |
| |
| if (testGraph.loadedGraph() == null) { |
| testGraph.loadedGraph(REGULAR_LOAD); |
| } |
| |
| boolean standard = testGraph.hugegraph().name().endsWith(STANDARD); |
| IdStrategy idStrategy = standard && !testGraph.ioTest() ? |
| IdStrategy.AUTOMATIC : |
| IdStrategy.CUSTOMIZE_STRING; |
| |
| switch (loadGraphWith) { |
| case GRATEFUL: |
| testGraph.initGratefulSchema(idStrategy); |
| break; |
| case MODERN: |
| testGraph.initModernSchema(idStrategy); |
| break; |
| case CLASSIC: |
| testGraph.initClassicSchema(idStrategy); |
| break; |
| case CREW: |
| break; |
| case SINK: |
| testGraph.initSinkSchema(); |
| break; |
| default: |
| throw new AssertionError(String.format( |
| "Only support GRATEFUL, MODERN and CLASSIC " + |
| "for @LoadGraphWith(), but '%s' is used ", |
| loadGraphWith)); |
| } |
| LOG.debug("Load graph with {} schema", loadGraphWith); |
| testGraph.tx().commit(); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| @Override |
| public Set<Class> getImplementations() { |
| return IMPLEMENTATIONS; |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public GraphTraversalSource traversal(Graph graph) { |
| HugeGraph hugegraph = ((TestGraph) graph).hugegraph(); |
| return hugegraph.traversal() |
| .withoutStrategies(LazyBarrierStrategy.class); |
| } |
| |
| @Override |
| public String convertId(Object id, Class<? extends Element> c) { |
| return id.toString(); |
| } |
| } |