This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2601
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit bab2606df0c6e1337e884fc0b3b459c1013cb6ed
Author: Stephen Mallette <[email protected]>
AuthorDate: Mon Feb 8 10:05:24 2021 -0500

    TINKERPOP-2601 Basics in place for Gherkin test to run on JVM
    
    Fully working pattern for gherkin tests running on TinkerGraph and using 
gremlin-language to parse to a Traversal object.
---
 gremlin-core/pom.xml                               |   9 -
 .../language/grammar/TraversalMethodVisitor.java   |  12 +-
 gremlin-javascript/build/generate.groovy           |   2 +-
 gremlin-language/pom.xml                           |  14 +-
 .../language/corpus}/DocumentationReader.java      |   4 +-
 .../gremlin/language/corpus/FeatureReader.java     | 135 +++++++
 .../language/corpus/DocumentationReaderTest.java   |  13 +-
 .../language/corpus}/FeatureReaderTest.java        |  23 +-
 .../gremlin/language/grammar/FeatureReader.java    |  95 -----
 .../language/grammar/ReferenceGrammarTest.java     |  37 +-
 gremlin-python/build/generate.groovy               |   2 +-
 gremlin-test/features/map/Map.feature              |   1 +
 gremlin-test/features/map/Vertex.feature           |  18 +-
 gremlin-test/pom.xml                               |  21 +
 .../tinkerpop/gremlin/features/FeatureReader.java  |  79 ----
 .../tinkerpop/gremlin/features/StepDefinition.java | 433 +++++++++++++++++++++
 .../apache/tinkerpop/gremlin/features/World.java   |  57 +++
 pom.xml                                            |   1 +
 tinkergraph-gremlin/pom.xml                        |   6 +
 .../tinkergraph/TinkerGraphFeatureTest.java        |  89 +++++
 .../src/test/resources/cucumber.properties         |   1 +
 21 files changed, 830 insertions(+), 222 deletions(-)

diff --git a/gremlin-core/pom.xml b/gremlin-core/pom.xml
index 6c10b85..cef750b 100644
--- a/gremlin-core/pom.xml
+++ b/gremlin-core/pom.xml
@@ -52,20 +52,11 @@ limitations under the License.
             <artifactId>commons-lang3</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-text</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.yaml</groupId>
             <artifactId>snakeyaml</artifactId>
             <version>${snakeyaml.version}</version>
         </dependency>
         <dependency>
-            <groupId>org.javatuples</groupId>
-            <artifactId>javatuples</artifactId>
-            <version>${java.tuples.version}</version>
-        </dependency>
-        <dependency>
             <groupId>com.carrotsearch</groupId>
             <artifactId>hppc</artifactId>
             <version>0.7.1</version>
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
index 9b67447..598dd45 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
@@ -940,7 +940,7 @@ public class TraversalMethodVisitor extends 
TraversalRootVisitor<GraphTraversal>
      */
     @Override
     public GraphTraversal visitTraversalMethod_option_Object_Traversal(final 
GremlinParser.TraversalMethod_option_Object_TraversalContext ctx) {
-        return 
graphTraversal.option(GenericLiteralVisitor.getInstance().visitGenericLiteral(ctx.genericLiteral()),
+        return graphTraversal.option(new 
GenericLiteralVisitor(antlr).visitGenericLiteral(ctx.genericLiteral()),
                 antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
     }
 
@@ -1118,8 +1118,8 @@ public class TraversalMethodVisitor extends 
TraversalRootVisitor<GraphTraversal>
      */
     @Override
     public GraphTraversal 
visitTraversalMethod_property_Object_Object_Object(final 
GremlinParser.TraversalMethod_property_Object_Object_ObjectContext ctx) {
-        return 
graphTraversal.property(GenericLiteralVisitor.getInstance().visitGenericLiteral(ctx.genericLiteral(0)),
-                
GenericLiteralVisitor.getInstance().visitGenericLiteral(ctx.genericLiteral(1)),
+        return graphTraversal.property(new 
GenericLiteralVisitor(antlr).visitGenericLiteral(ctx.genericLiteral(0)),
+                new 
GenericLiteralVisitor(antlr).visitGenericLiteral(ctx.genericLiteral(1)),
                 
GenericLiteralVisitor.getGenericLiteralList(ctx.genericLiteralList()));
     }
 
@@ -1504,6 +1504,12 @@ public class TraversalMethodVisitor extends 
TraversalRootVisitor<GraphTraversal>
         return 
graphTraversal.math(GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()));
     }
 
+    @Override
+    public Traversal visitTraversalMethod_option_Predicate_Traversal(final 
GremlinParser.TraversalMethod_option_Predicate_TraversalContext ctx) {
+        return 
graphTraversal.option(TraversalPredicateVisitor.getInstance().visitTraversalPredicate(ctx.traversalPredicate()),
+                antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal()));
+    }
+
     public GraphTraversal[] getNestedTraversalList(final 
GremlinParser.NestedTraversalListContext ctx) {
         return ctx.nestedTraversalExpr().nestedTraversal()
                 .stream()
diff --git a/gremlin-javascript/build/generate.groovy 
b/gremlin-javascript/build/generate.groovy
index 7a374c0..404f14a 100644
--- a/gremlin-javascript/build/generate.groovy
+++ b/gremlin-javascript/build/generate.groovy
@@ -42,7 +42,7 @@ gremlinGroovyScriptEngine = new GremlinGroovyScriptEngine(new 
GroovyCustomizer()
     }
 })
 translator = JavascriptTranslator.of('g')
-g = traversal().withGraph(EmptyGraph.instance())
+g = traversal().withEmbedded(EmptyGraph.instance())
 bindings = new SimpleBindings()
 bindings.put('g', g)
 
diff --git a/gremlin-language/pom.xml b/gremlin-language/pom.xml
index aff53a3..a48e6ed 100644
--- a/gremlin-language/pom.xml
+++ b/gremlin-language/pom.xml
@@ -32,6 +32,15 @@ limitations under the License.
             <artifactId>antlr4</artifactId>
             <version>${antlr4.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.javatuples</groupId>
+            <artifactId>javatuples</artifactId>
+            <version>${java.tuples.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-text</artifactId>
+        </dependency>
         <!-- TESTING -->
         <dependency>
             <groupId>junit</groupId>
@@ -43,11 +52,6 @@ limitations under the License.
             <artifactId>hamcrest</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-text</artifactId>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
 
     <properties>
diff --git 
a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/DocumentationReader.java
 
b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java
similarity index 98%
rename from 
gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/DocumentationReader.java
rename to 
gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java
index 93bbccd..b657cae 100644
--- 
a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/DocumentationReader.java
+++ 
b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java
@@ -16,9 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.language.grammar;
-
-import org.apache.commons.text.StringEscapeUtils;
+package org.apache.tinkerpop.gremlin.language.corpus;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
diff --git 
a/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/FeatureReader.java
 
b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/FeatureReader.java
new file mode 100644
index 0000000..07457bd
--- /dev/null
+++ 
b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/FeatureReader.java
@@ -0,0 +1,135 @@
+/*
+ * 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.language.corpus;
+
+import org.apache.commons.text.StringEscapeUtils;
+import org.javatuples.Pair;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Reads the Gherkin feature files of {@code gremlin-test} and extracts 
Gremlin examples.
+ */
+public class FeatureReader {
+
+    private static final Pattern generalParameterPattern = 
Pattern.compile("And using the parameter (.+) (defined as|of) (.*)");
+
+    /**
+     * Parses Gremlin to a {@code Map} structure of the test name as the key 
with a {@code List} of Gremlin strings
+     * for the value.
+     * @param projectRoot the root directory of the TinkerPop project source 
code
+     */
+    public static Map<String, List<String>> parse(final String projectRoot) 
throws IOException {
+        return parse(projectRoot, Collections.emptyList());
+    }
+
+    /**
+     * Parses Gremlin to a {@code Map} structure of the test name as the key 
with a {@code List} of Gremlin strings
+     * for the value.
+     * @param projectRoot the root directory of the TinkerPop project source 
code
+     * @param parameterMatchers list of pattern/functions that will transform 
a parameter from its Gherkin form to
+     *                          another format triggering that new formatted 
string to be inserted into the Gremlin
+     *                          itself
+     */
+    public static Map<String, List<String>> parse(final String projectRoot,
+                                                  final List<Pair<Pattern, 
BiFunction<String, String, String>>> parameterMatchers) throws IOException {
+        final Map<String, List<String>> gremlins = new LinkedHashMap<>();
+        Files.find(Paths.get(projectRoot, "gremlin-test", "features"),
+                   Integer.MAX_VALUE,
+                (filePath, fileAttr) -> fileAttr.isRegularFile() && 
filePath.toString().endsWith(".feature")).
+                sorted().
+                forEach(f -> {
+                    String currentGremlin = "";
+                    boolean openTriples = false;
+                    boolean skipIgnored = false;
+                    String scenarioName = "";
+                    Map<String,String> parameters = new HashMap<>();
+
+                    try {
+                        final List<String> lines = Files.readAllLines(f, 
StandardCharsets.UTF_8);
+                        for (String line : lines) {
+                            String cleanLine = line.trim();
+                            if (cleanLine.startsWith("Scenario:")) {
+                                scenarioName = cleanLine.split(":")[1].trim();
+                                skipIgnored = false;
+                                parameters.clear();
+                            } else if (!parameterMatchers.isEmpty() && 
cleanLine.startsWith("And using the parameter")) {
+                                final Matcher m = 
generalParameterPattern.matcher(cleanLine);
+                                if (m.matches()) {
+                                    parameters.put(m.group(1), 
matchAndTransform(m.group(1), StringEscapeUtils.unescapeJava(m.group(3)), 
parameterMatchers));
+                                } else {
+                                    throw new 
IllegalStateException(String.format("Could not read parameters at: %s", 
cleanLine));
+                                }
+                            } else if (cleanLine.startsWith("Then nothing 
should happen because")) {
+                                skipIgnored = true;
+                            } else if (cleanLine.startsWith("And the graph 
should return")) {
+                                gremlins.computeIfAbsent(scenarioName, k -> 
new 
ArrayList<>()).add(applyParametersToGremlin(StringEscapeUtils.unescapeJava(cleanLine.substring(cleanLine.indexOf("\"")
 + 1, cleanLine.lastIndexOf("\""))), parameters));
+                            } else if (cleanLine.startsWith("\"\"\"")) {
+                                openTriples = !openTriples;
+                                if (!skipIgnored && !openTriples) {
+                                    currentGremlin = 
applyParametersToGremlin(currentGremlin, parameters);
+                                    gremlins.computeIfAbsent(scenarioName, k 
-> new ArrayList<>()).add(currentGremlin);
+                                    currentGremlin = "";
+                                }
+                            } else if (openTriples && !skipIgnored) {
+                                currentGremlin += cleanLine;
+                            }
+                        }
+                    } catch (IOException ioe) {
+                        throw new RuntimeException(ioe);
+                    }
+                });
+
+        return gremlins;
+    }
+
+    private static String applyParametersToGremlin(String currentGremlin, 
Map<String, String> parameters) {
+        for (Map.Entry<String,String> kv : parameters.entrySet()) {
+            currentGremlin = currentGremlin.replace(kv.getKey(), 
kv.getValue());
+        }
+        return currentGremlin;
+    }
+
+    private static String matchAndTransform(final String k, final String v,
+                                            final List<Pair<Pattern, 
BiFunction<String, String, String>>> parameterMatchers) {
+        for (Pair<Pattern,BiFunction<String,String,String>> matcherConverter : 
parameterMatchers) {
+            final Pattern pattern = matcherConverter.getValue0();
+            final Matcher matcher = pattern.matcher(v);
+            if (matcher.find()) {
+                final BiFunction<String,String,String> converter = 
matcherConverter.getValue1();
+                // when there are no groups there is a direct match
+                return converter.apply(k, matcher.groupCount() == 0 ? "" : 
matcher.group(1));
+            }
+        }
+
+        throw new IllegalStateException(String.format("Could not match the 
parameter [%s] pattern of %s", k, v));
+    }
+}
diff --git 
a/gremlin-test/src/test/java/org/apache/tinkerpop/gremlin/features/FeatureReaderTest.java
 
b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReaderTest.java
similarity index 80%
copy from 
gremlin-test/src/test/java/org/apache/tinkerpop/gremlin/features/FeatureReaderTest.java
copy to 
gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReaderTest.java
index 8d67347..ab131c4 100644
--- 
a/gremlin-test/src/test/java/org/apache/tinkerpop/gremlin/features/FeatureReaderTest.java
+++ 
b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReaderTest.java
@@ -16,27 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.features;
+package org.apache.tinkerpop.gremlin.language.corpus;
 
 import org.junit.Test;
 
 import java.io.IOException;
-import java.util.List;
-import java.util.Map;
+import java.util.Set;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.number.OrderingComparison.greaterThan;
 import static org.junit.Assert.assertEquals;
 
-public class FeatureReaderTest {
+public class DocumentationReaderTest {
 
     @Test
     public void shouldParseInSameOrder() throws IOException {
         final String projectRoot = "../";
-        final Map<String,List<String>> gremlins = 
FeatureReader.parse(projectRoot);
+        final Set<String> gremlins = DocumentationReader.parse(projectRoot);
         assertThat(gremlins.size(), greaterThan(0));
-        assertEquals(gremlins,
-                     FeatureReader.parse(projectRoot));
-
+        assertEquals(gremlins, DocumentationReader.parse(projectRoot));
     }
 }
diff --git 
a/gremlin-test/src/test/java/org/apache/tinkerpop/gremlin/features/FeatureReaderTest.java
 
b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/corpus/FeatureReaderTest.java
similarity index 57%
rename from 
gremlin-test/src/test/java/org/apache/tinkerpop/gremlin/features/FeatureReaderTest.java
rename to 
gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/corpus/FeatureReaderTest.java
index 8d67347..6639ea8 100644
--- 
a/gremlin-test/src/test/java/org/apache/tinkerpop/gremlin/features/FeatureReaderTest.java
+++ 
b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/corpus/FeatureReaderTest.java
@@ -16,15 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.features;
+package org.apache.tinkerpop.gremlin.language.corpus;
 
+import org.javatuples.Pair;
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.regex.Pattern;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.number.OrderingComparison.greaterThan;
 import static org.junit.Assert.assertEquals;
 
@@ -35,8 +41,19 @@ public class FeatureReaderTest {
         final String projectRoot = "../";
         final Map<String,List<String>> gremlins = 
FeatureReader.parse(projectRoot);
         assertThat(gremlins.size(), greaterThan(0));
-        assertEquals(gremlins,
-                     FeatureReader.parse(projectRoot));
+        assertEquals(gremlins, FeatureReader.parse(projectRoot));
+    }
+
+    @Test
+    public void shouldParseAndEmbed() throws IOException {
+        final String replaceToken = "****replaced****";
+        final List<Pair<Pattern, BiFunction<String, String, String>>> 
parameterMatchers = new ArrayList<>();
+        parameterMatchers.add(Pair.with(Pattern.compile("(.*)"), (k, v) -> 
replaceToken));
+        final String projectRoot = "../";
+        final Map<String,List<String>> gremlins = 
FeatureReader.parse(projectRoot, parameterMatchers);
 
+        // at least one of these things must have the "replaced" token
+        assertThat(gremlins.values().stream().
+                flatMap(Collection::stream).anyMatch(gremlin -> 
gremlin.contains(replaceToken)), is(true));
     }
 }
diff --git 
a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/FeatureReader.java
 
b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/FeatureReader.java
deleted file mode 100644
index 6006c7b..0000000
--- 
a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/FeatureReader.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.language.grammar;
-
-import org.apache.commons.text.StringEscapeUtils;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Reads the feature files and extracts Gremlin to help build the parsing test 
corpus.
- */
-public class FeatureReader {
-
-    public static Set<String> parse(final String projectRoot) throws 
IOException {
-        final Set<String> gremlins = new LinkedHashSet<>();
-        Files.find(Paths.get(projectRoot, "gremlin-test", "features"),
-                   Integer.MAX_VALUE,
-                (filePath, fileAttr) -> fileAttr.isRegularFile() && 
filePath.toString().endsWith(".feature")).
-                sorted().
-                forEach(f -> {
-                    String currentGremlin = "";
-                    boolean openTriples = false;
-                    boolean skipIgnored = false;
-
-                    try {
-                        final List<String> lines = Files.readAllLines(f, 
StandardCharsets.UTF_8);
-                        for (String line : lines) {
-                            String cleanLine = line.trim();
-                            if (cleanLine.startsWith("Then nothing should 
happen because")) {
-                                skipIgnored = true;
-                            } else if (cleanLine.startsWith("And the graph 
should return")) {
-                                gremlins.add(replaceVariables(
-                                        StringEscapeUtils.unescapeJava(
-                                                
cleanLine.substring(cleanLine.indexOf("\"") + 1, 
cleanLine.lastIndexOf("\"")))));
-                            } else if (cleanLine.startsWith("\"\"\"")) {
-                                openTriples = !openTriples;
-                                if (!skipIgnored && !openTriples) {
-                                    
gremlins.add(replaceVariables(currentGremlin));
-                                    currentGremlin = "";
-                                }
-                            } else if (openTriples && !skipIgnored) {
-                                currentGremlin += cleanLine;
-                            }
-                        }
-                    } catch (IOException ioe) {
-                        throw new RuntimeException(ioe);
-                    }
-                });
-
-        return gremlins;
-    }
-
-    /**
-     * Variables can't be parsed by the grammar so they must be replaced with 
something concrete.
-     */
-    private static String replaceVariables(final String gremlin) {
-        return gremlin.replace("xx1", "\"1\"").
-                replace("xx2", "\"2\"").
-                replace("xx3", "\"3\"").
-                replace("vid1", "1").
-                replace("vid2", "2").
-                replace("vid3", "3").
-                replace("vid4", "4").
-                replace("vid5", "5").
-                replace("vid6", "6").
-                replace("eid7", "7").
-                replace("eid8", "8").
-                replace("eid9", "9").
-                replace("eid10", "10").
-                replace("eid11", "11").
-                replace("eid12", "12");
-    }
-}
diff --git 
a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
 
b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
index 2246fd3..f1f2e4f 100644
--- 
a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
+++ 
b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ReferenceGrammarTest.java
@@ -18,14 +18,23 @@
  */
 package org.apache.tinkerpop.gremlin.language.grammar;
 
+import org.apache.tinkerpop.gremlin.language.corpus.DocumentationReader;
+import org.apache.tinkerpop.gremlin.language.corpus.FeatureReader;
+import org.javatuples.Pair;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
+import java.util.function.BiFunction;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assume.assumeThat;
@@ -40,10 +49,31 @@ public class ReferenceGrammarTest extends 
AbstractGrammarTest {
     private static final Pattern vertexPattern = Pattern.compile(".*v\\d.*");
     private static final Pattern edgePattern = Pattern.compile(".*e\\d.*");
 
+    private static final List<Pair<Pattern, BiFunction<String,String,String>>> 
stringMatcherConverters = new ArrayList<Pair<Pattern, 
BiFunction<String,String,String>>>() {{
+        add(Pair.with(Pattern.compile("l\\[\\]"), (k,v) -> "[]"));
+        add(Pair.with(Pattern.compile("l\\[(.*)\\]"), (k,v) -> {
+            final String[] items = v.split(",");
+            final String listItems = Stream.of(items).map(String::trim).map(x 
-> String.format("\"%s\"", x)).collect(Collectors.joining(","));
+            return String.format("[%s]", listItems);
+        }));
+        add(Pair.with(Pattern.compile("v\\[(.+)\\]"), (k,v) -> "\"1\""));
+        add(Pair.with(Pattern.compile("e\\[(.+)\\]"), (k,v) -> "\"1\""));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.?.*"), (k,v) -> v));
+        add(Pair.with(Pattern.compile("m\\[(.*)\\]"), (k,v) -> 
v.replace('{','[').replace('}', ']')));
+        add(Pair.with(Pattern.compile("t\\[(.*)\\]"), (k,v) -> 
String.format("T.%s", v)));
+        add(Pair.with(Pattern.compile("D\\[(.*)\\]"), (k,v) -> 
String.format("Direction.%s", v)));
+
+        // the grammar doesn't support all the Gremlin we have in the gherkin 
set, so try to coerce it into
+        // something that can be parsed so that we get maximum exercise over 
the parser itself.
+        add(Pair.with(Pattern.compile("c\\[(.*)\\]"), (k,v) -> k.equals("c1") 
|| k.equals("c2") ? "Order.desc" : "__.identity()"));  // closure -> Comparator 
|| Traversal
+        add(Pair.with(Pattern.compile("s\\[\\]"), (k,v) -> "[]"));  // set -> 
list
+        add(Pair.with(Pattern.compile("s\\[(.*)\\]"), (k,v) -> "[]"));  // set 
-> list
+    }};
+
     @Parameterized.Parameters(name = "{0}")
     public static Iterable<String> queries() throws IOException {
         final Set<String> gremlins = new 
LinkedHashSet<>(DocumentationReader.parse("../"));
-        gremlins.addAll(FeatureReader.parse("../"));
+        gremlins.addAll(FeatureReader.parse("../", 
stringMatcherConverters).values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
         return gremlins;
     }
 
@@ -52,11 +82,6 @@ public class ReferenceGrammarTest extends 
AbstractGrammarTest {
 
     @Test
     public void test_parse() {
-        assumeThat("Lambdas are not supported", query.contains("l1"), 
is(false));
-        assumeThat("Lambdas are not supported", query.contains("l2"), 
is(false));
-        assumeThat("Lambdas are not supported", query.contains("pred1"), 
is(false));
-        assumeThat("Lambdas are not supported", query.contains("c1"), 
is(false));
-        assumeThat("Lambdas are not supported", query.contains("c2"), 
is(false));
         assumeThat("Lambdas are not supported", 
query.contains("Lambda.function("), is(false));
         // start of a closure
         assumeThat("Lambdas are not supported", query.contains("{"), 
is(false));
diff --git a/gremlin-python/build/generate.groovy 
b/gremlin-python/build/generate.groovy
index 05507d5..4ad84dc 100644
--- a/gremlin-python/build/generate.groovy
+++ b/gremlin-python/build/generate.groovy
@@ -42,7 +42,7 @@ gremlinGroovyScriptEngine = new GremlinGroovyScriptEngine(new 
GroovyCustomizer()
     }
 })
 translator = PythonTranslator.of('g')
-g = traversal().withGraph(EmptyGraph.instance())
+g = traversal().withEmbedded(EmptyGraph.instance())
 bindings = new SimpleBindings()
 bindings.put('g', g)
 
diff --git a/gremlin-test/features/map/Map.feature 
b/gremlin-test/features/map/Map.feature
index d9acfed..58f5d37 100644
--- a/gremlin-test/features/map/Map.feature
+++ b/gremlin-test/features/map/Map.feature
@@ -61,6 +61,7 @@ Feature: Step - map()
       | d[5].i |
       | d[4].i |
 
+  @RemoteOnly
   Scenario: g_VX1X_out_mapXlambdaXnameXX_mapXlambdaXlengthXX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
diff --git a/gremlin-test/features/map/Vertex.feature 
b/gremlin-test/features/map/Vertex.feature
index 62ac798..d1715d1 100644
--- a/gremlin-test/features/map/Vertex.feature
+++ b/gremlin-test/features/map/Vertex.feature
@@ -135,7 +135,7 @@ Feature: Step - V(), E(), out(), in(), both(), inE(), 
outE(), bothE()
     Given the modern graph
     And using the parameter eid11 defined as "e[josh-created->lop].id"
     And the traversal of
-    """
+      """
       g.E(eid11)
       """
     When iterated to list
@@ -147,7 +147,7 @@ Feature: Step - V(), E(), out(), in(), both(), inE(), 
outE(), bothE()
     Given the modern graph
     And using the parameter eid11 defined as "e[josh-created->lop].sid"
     And the traversal of
-    """
+      """
       g.E(eid11)
       """
     When iterated to list
@@ -159,7 +159,7 @@ Feature: Step - V(), E(), out(), in(), both(), inE(), 
outE(), bothE()
     Given the modern graph
     And using the parameter e11 defined as "e[josh-created->lop]"
     And the traversal of
-    """
+      """
       g.E(e11)
       """
     When iterated to list
@@ -172,7 +172,7 @@ Feature: Step - V(), E(), out(), in(), both(), inE(), 
outE(), bothE()
     And using the parameter e7 defined as "e[marko-knows->vadas]"
     And using the parameter e11 defined as "e[josh-created->lop]"
     And the traversal of
-    """
+      """
       g.E(e7,e11)
       """
     When iterated to list
@@ -185,7 +185,7 @@ Feature: Step - V(), E(), out(), in(), both(), inE(), 
outE(), bothE()
     Given the modern graph
     And using the parameter xx1 defined as 
"l[e[marko-knows->vadas],e[josh-created->lop]]"
     And the traversal of
-    """
+      """
       g.E(xx1)
       """
     When iterated to list
@@ -198,7 +198,7 @@ Feature: Step - V(), E(), out(), in(), both(), inE(), 
outE(), bothE()
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
     And the traversal of
-    """
+      """
       g.V(vid1).outE()
       """
     When iterated to list
@@ -212,7 +212,7 @@ Feature: Step - V(), E(), out(), in(), both(), inE(), 
outE(), bothE()
     Given the modern graph
     And using the parameter vid2 defined as "v[vadas].id"
     And the traversal of
-    """
+      """
       g.V(vid2).inE()
       """
     When iterated to list
@@ -224,7 +224,7 @@ Feature: Step - V(), E(), out(), in(), both(), inE(), 
outE(), bothE()
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
     And the traversal of
-    """
+      """
       g.V(vid4).bothE("created")
       """
     When iterated to list
@@ -237,7 +237,7 @@ Feature: Step - V(), E(), out(), in(), both(), inE(), 
outE(), bothE()
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
     And the traversal of
-    """
+      """
       g.V(vid4).bothE()
       """
     When iterated to list
diff --git a/gremlin-test/pom.xml b/gremlin-test/pom.xml
index 343159c..0ae28f7 100644
--- a/gremlin-test/pom.xml
+++ b/gremlin-test/pom.xml
@@ -48,6 +48,27 @@ limitations under the License.
             <artifactId>hamcrest</artifactId>
         </dependency>
         <dependency>
+            <groupId>io.cucumber</groupId>
+            <artifactId>cucumber-java</artifactId>
+            <version>6.11.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.cucumber</groupId>
+            <artifactId>cucumber-junit</artifactId>
+            <version>6.11.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.cucumber</groupId>
+            <artifactId>cucumber-guice</artifactId>
+            <version>6.11.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <version>4.2.3</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
             <optional>true</optional>
diff --git 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/FeatureReader.java
 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/FeatureReader.java
deleted file mode 100644
index 763cb0e..0000000
--- 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/FeatureReader.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.features;
-
-import org.apache.commons.text.StringEscapeUtils;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Reads the feature files and extracts Gremlin to a {@code Map} structure of 
the test name as the key with a
- * {@code List} of Gremlin strings for the value.
- */
-public class FeatureReader {
-
-    public static Map<String, List<String>> parse(final String projectRoot) 
throws IOException {
-        final Map<String, List<String>> gremlins = new LinkedHashMap<>();
-        Files.find(Paths.get(projectRoot, "gremlin-test", "features"),
-                   Integer.MAX_VALUE,
-                (filePath, fileAttr) -> fileAttr.isRegularFile() && 
filePath.toString().endsWith(".feature")).
-                sorted().
-                forEach(f -> {
-                    String currentGremlin = "";
-                    boolean openTriples = false;
-                    boolean skipIgnored = false;
-                    String scenarioName = "";
-
-                    try {
-                        final List<String> lines = Files.readAllLines(f, 
StandardCharsets.UTF_8);
-                        for (String line : lines) {
-                            String cleanLine = line.trim();
-                            if (cleanLine.startsWith("Scenario:")) {
-                                scenarioName = cleanLine.split(":")[1].trim();
-                                skipIgnored = false;
-                            } else if (cleanLine.startsWith("Then nothing 
should happen because")) {
-                                skipIgnored = true;
-                            } else if (cleanLine.startsWith("And the graph 
should return")) {
-                                gremlins.computeIfAbsent(scenarioName, k -> 
new 
ArrayList<>()).add(StringEscapeUtils.unescapeJava(cleanLine.substring(cleanLine.indexOf("\"")
 + 1, cleanLine.lastIndexOf("\""))));
-                            } else if (cleanLine.startsWith("\"\"\"")) {
-                                openTriples = !openTriples;
-                                if (!skipIgnored && !openTriples) {
-                                    gremlins.computeIfAbsent(scenarioName, k 
-> new ArrayList<>()).add(currentGremlin);
-                                    currentGremlin = "";
-                                }
-                            } else if (openTriples && !skipIgnored) {
-                                currentGremlin += cleanLine;
-                            }
-                        }
-                    } catch (IOException ioe) {
-                        throw new RuntimeException(ioe);
-                    }
-                });
-
-        return gremlins;
-    }
-}
diff --git 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
new file mode 100644
index 0000000..e1023ab
--- /dev/null
+++ 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
@@ -0,0 +1,433 @@
+/*
+ * 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.features;
+
+import com.google.inject.Inject;
+import io.cucumber.datatable.DataTable;
+import io.cucumber.guice.ScenarioScoped;
+import io.cucumber.java.After;
+import io.cucumber.java.Before;
+import io.cucumber.java.Scenario;
+import io.cucumber.java.en.Given;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.apache.tinkerpop.gremlin.language.grammar.GremlinAntlrToJava;
+import org.apache.tinkerpop.gremlin.language.grammar.GremlinLexer;
+import org.apache.tinkerpop.gremlin.language.grammar.GremlinParser;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+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.structure.Direction;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
+import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
+import org.javatuples.Pair;
+import org.javatuples.Triplet;
+import org.junit.AssumptionViolatedException;
+
+import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.inV;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsIn.in;
+import static 
org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.hamcrest.core.Every.everyItem;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@ScenarioScoped
+public final class StepDefinition {
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    private final World world;
+    private GraphTraversalSource g;
+    private final Map<String, String> stringParameters = new HashMap<>();
+    private Traversal traversal;
+    private Object result;
+    private static Pattern edgeTriplet = Pattern.compile("(.+)-(.+)->(.+)");
+    private List<Pair<Pattern, Function<String,String>>> 
stringMatcherConverters = new ArrayList<Pair<Pattern, 
Function<String,String>>>() {{
+        // expects json so that should port to the Gremlin script form - 
replace curly json braces with square ones
+        // for Gremlin sake.
+        add(Pair.with(Pattern.compile("m\\[(.*)\\]"), s -> 
s.replace('{','[').replace('}', ']')));
+
+        add(Pair.with(Pattern.compile("l\\[\\]"), s -> "[]"));
+        add(Pair.with(Pattern.compile("l\\[(.*)\\]"), s -> {
+            final String[] items = s.split(",");
+            final String listItems = Stream.of(items).map(String::trim).map(x 
-> convertToString(x)).collect(Collectors.joining(","));
+            return String.format("[%s]", listItems);
+        }));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.i"), s -> s));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.l"), s -> s + "l"));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.f"), s -> s + "f"));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.d"), s -> s + "d"));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.m"), s -> 
String.format("new BigDecimal(%s)", s)));
+
+        add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.id"), s -> 
g.V().has("name", s).id().next().toString()));
+        add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.sid"), s -> 
g.V().has("name", s).id().next().toString()));
+        add(Pair.with(Pattern.compile("e\\[(.+)\\]\\.id"), s -> 
getEdgeIdString(g, s)));
+        add(Pair.with(Pattern.compile("e\\[(.+)\\]\\.sid"), s -> 
getEdgeIdString(g, s)));
+
+        add(Pair.with(Pattern.compile("t\\[(.*)\\]"), s -> 
String.format("T.%s", s)));
+        add(Pair.with(Pattern.compile("D\\[(.*)\\]"), s -> 
String.format("Direction.%s", s)));
+
+        // the following force ignore conditions as they cannot be parsed by 
the grammar at this time. the grammar will
+        // need to be modified to handle them or perhaps these tests stay 
relegated to the JVM in some way for certain
+        // cases like the lambda item which likely won't make it to the 
grammar as it's raw groovy.
+        add(Pair.with(Pattern.compile("c\\[(.*)\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a lambda as 
a parameter which is not supported by gremlin-language");
+        }));
+        add(Pair.with(Pattern.compile("v\\[(.+)\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a Vertex as 
a parameter which is not supported by gremlin-language");
+        }));
+        add(Pair.with(Pattern.compile("e\\[(.+)\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a Edge as a 
parameter which is not supported by gremlin-language");
+        }));
+        add(Pair.with(Pattern.compile("p\\[(.*)\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a Path as a 
parameter which is not supported by gremlin-language");
+        }));
+        add(Pair.with(Pattern.compile("s\\[\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a empty Set 
as a parameter which is not supported by gremlin-language");
+        }));
+        add(Pair.with(Pattern.compile("s\\[(.*)\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a Set as a 
parameter which is not supported by gremlin-language");
+        }));
+    }};
+
+    private List<Pair<Pattern, Function<String,Object>>> 
objectMatcherConverters = new ArrayList<Pair<Pattern, 
Function<String,Object>>>() {{
+        // expects json so that should port to the Gremlin script form - 
replace curly json braces with square ones
+        // for Gremlin sake.
+        add(Pair.with(Pattern.compile("m\\[(.*)\\]"), s -> {
+            try {
+                // read tree from JSON - can't parse right to Map as each m[] 
level needs to be managed individually
+                return convertToObject(mapper.readTree(s));
+            } catch (Exception ex) {
+                throw new IllegalStateException(String.format("Can't parse 
JSON to map for %s", s), ex);
+            }
+        }));
+
+        add(Pair.with(Pattern.compile("l\\[\\]"), s -> 
Collections.emptyList()));
+        add(Pair.with(Pattern.compile("l\\[(.*)\\]"), s -> {
+            final String[] items = s.split(",");
+            return Stream.of(items).map(String::trim).map(x -> 
convertToObject(x)).collect(Collectors.toList());
+        }));
+
+        add(Pair.with(Pattern.compile("p\\[(.*)\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a Path as a 
parameter which is not supported by gremlin-language");
+        }));
+
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.i"), Integer::parseInt));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.l"), Long::parseLong));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.f"), Float::parseFloat));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.d"), 
Double::parseDouble));
+        add(Pair.with(Pattern.compile("d\\[(.*)\\]\\.m"), BigDecimal::new));
+
+        add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.id"), s -> 
g.V().has("name", s).id().next()));
+        add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.sid"), s -> 
g.V().has("name", s).id().next().toString()));
+        add(Pair.with(Pattern.compile("v\\[(.+)\\]"), s -> g.V().has("name", 
s).next()));
+        add(Pair.with(Pattern.compile("e\\[(.+)\\]\\.id"), s -> getEdgeId(g, 
s)));
+        add(Pair.with(Pattern.compile("e\\[(.+)\\]\\.sid"), s -> 
getEdgeIdString(g, s)));
+        add(Pair.with(Pattern.compile("e\\[(.+)\\]"), s -> getEdge(g, s)));
+
+        add(Pair.with(Pattern.compile("t\\[(.*)\\]"), T::valueOf));
+        add(Pair.with(Pattern.compile("D\\[(.*)\\]"), Direction::valueOf));
+
+        add(Pair.with(Pattern.compile("c\\[(.*)\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a lambda as 
a parameter which is not supported by gremlin-language");
+        }));
+        add(Pair.with(Pattern.compile("s\\[\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a empty Set 
as a parameter which is not supported by gremlin-language");
+        }));
+        add(Pair.with(Pattern.compile("s\\[(.*)\\]"), s -> {
+            throw new AssumptionViolatedException("This test uses a Set as a 
parameter which is not supported by gremlin-language");
+        }));
+
+        add(Pair.with(Pattern.compile("(null)"), s -> null));
+    }};
+
+    @Inject
+    public StepDefinition(final World world) {
+        this.world = Objects.requireNonNull(world, "world must not be null");
+    }
+
+    @Before
+    public void beforeEachScenario(final Scenario scenario) throws Exception {
+        world.beforeEachScenario(scenario);
+        stringParameters.clear();
+        if (traversal != null) {
+            traversal.close();
+            traversal = null;
+        }
+
+        if (result != null) result = null;
+    }
+
+    @After
+    public void afterEachScenario() throws Exception {
+        world.afterEachScenario();
+    }
+
+    @Given("the {word} graph")
+    public void givenTheXGraph(final String graphName) {
+        if (graphName.equals("empty"))
+            this.g = world.getGraphTraversalSource(null);
+        else
+            this.g = 
world.getGraphTraversalSource(GraphData.valueOf(graphName.toUpperCase()));
+    }
+
+    @Given("the graph initializer of")
+    public void theGraphInitializerOf(final String gremlin) {
+        parseGremlin(gremlin).iterate();
+    }
+
+    @Given("using the parameter {word} defined as {string}")
+    public void usingTheParameterXDefinedAsX(final String key, final String 
value) {
+        stringParameters.put(key, convertToString(value));
+    }
+
+    @Given("using the parameter {word} of P.{word}\\({string})")
+    public void usingTheParameterXOfPX(final String key, final String pval, 
final String string) {
+        stringParameters.put(key, String.format("P.%s(%s)", pval, 
convertToString(string)));
+    }
+
+    @Given("the traversal of")
+    public void theTraversalOf(final String docString) {
+        traversal = parseGremlin(applyParameters(docString));
+    }
+
+    @When("iterated to list")
+    public void iteratedToList() {
+        result = traversal.toList();
+    }
+
+    @When("iterated next")
+    public void iteratedNext() {
+        result = traversal.next();
+    }
+
+    @Then("the result should be unordered")
+    public void theResultShouldBeUnordered(final DataTable dataTable) {
+        final List<Object> actual = translateResultsToActual();
+
+        // account for header in dataTable size
+        assertEquals(dataTable.height() - 1, actual.size());
+
+        // skip the header in the dataTable
+        final Object[] expected = 
dataTable.asList().stream().skip(1).map(this::convertToObject).toArray();
+        assertThat(actual, containsInAnyOrder(expected));
+    }
+
+    @Then("the result should be ordered")
+    public void theResultShouldBeOrdered(final DataTable dataTable) {
+        final List<Object> actual = translateResultsToActual();
+
+        // account for header in dataTable size
+        assertEquals(dataTable.height() - 1, actual.size());
+
+        // skip the header in the dataTable
+        final Object[] expected = 
dataTable.asList().stream().skip(1).map(this::convertToObject).toArray();
+        assertThat(actual, contains(expected));
+    }
+
+    @Then("the result should be of")
+    public void theResultShouldBeOf(final DataTable dataTable) {
+        final List<Object> actual = translateResultsToActual();
+
+        // skip the header in the dataTable
+        final Object[] expected = 
dataTable.asList().stream().skip(1).map(this::convertToObject).toArray();
+        assertThat(actual, everyItem(in(expected)));
+    }
+
+    @Then("the result should have a count of {int}")
+    public void theResultShouldHaveACountOf(final Integer val) {
+        if (result instanceof Iterable)
+            assertEquals(val.intValue(), IteratorUtils.count((Iterable) 
result));
+        else if (result instanceof Map)
+            assertEquals(val.intValue(), ((Map) result).size());
+        else
+            fail(String.format("Missing an assert for this type", 
result.getClass()));
+    }
+
+    @Then("the graph should return {int} for count of {string}")
+    public void theGraphShouldReturnForCountOf(final Integer count, final 
String gremlin) {
+        assertEquals(count.longValue(), ((GraphTraversal) 
parseGremlin(applyParameters(gremlin))).count().next());
+    }
+
+    @Then("the result should be empty")
+    public void theResultShouldBeEmpty() {
+        assertThat(result, instanceOf(Collection.class));
+        assertEquals(0, IteratorUtils.count((Collection) result));
+    }
+
+    //////////////////////////////////////////////
+
+    @Given("an unsupported test")
+    public void anUnsupportedTest() {
+        // placeholder text - no operation needed because it should be 
followed by nothingShouldHappenBecause()
+    }
+
+    @Then("nothing should happen because")
+    public void nothingShouldHappenBecause(final String message) {
+        throw new AssumptionViolatedException(String.format("This test is not 
supported by Gherkin because: %s", message));
+    }
+
+    //////////////////////////////////////////////
+
+    private Traversal parseGremlin(final String script) {
+        if (script.contains(".withComputer("))
+            throw new AssumptionViolatedException("withComputer() syntax is 
not supported by gremlin-language at this time");
+
+        // TODO: fix io() data pathing stuff to bind it better to the graph
+        if (script.startsWith("g.io(\"")) {
+            throw new AssumptionViolatedException("io() syntax");
+        }
+
+        final GremlinLexer lexer = new 
GremlinLexer(CharStreams.fromString(script));
+        final GremlinParser parser = new GremlinParser(new 
CommonTokenStream(lexer));
+        final GremlinParser.QueryContext ctx = parser.query();
+        return (Traversal) new 
GremlinAntlrToJava(g.getGraph()).visitQuery(ctx);
+    }
+
+    private List<Object> translateResultsToActual() {
+        final List<Object> r = result instanceof List ? (List<Object>) result 
: IteratorUtils.asList(result);
+
+        // gotta convert Map.Entry to individual Map coz that how we assert 
those for GLVs - dah
+        final List<Object> actual = r.stream().map(o -> {
+            if (o instanceof Map.Entry) {
+                return new HashMap() {{
+                    put(((Entry<?, ?>) o).getKey(), ((Entry<?, ?>) 
o).getValue());
+                }};
+            } else {
+                return o;
+            }
+        }).collect(Collectors.toList());
+        return actual;
+    }
+
+    private String convertToString(final String pvalue) {
+        return convertToString(null, pvalue);
+    }
+
+    private String convertToString(final String pkey, final String pvalue) {
+        for (Pair<Pattern,Function<String,String>> matcherConverter : 
stringMatcherConverters) {
+            final Pattern pattern = matcherConverter.getValue0();
+            final Matcher matcher = pattern.matcher(pvalue);
+            if (matcher.find()) {
+                final Function<String,String> converter = 
matcherConverter.getValue1();
+
+                // when there are no groups there is a direct match
+                return converter.apply(matcher.groupCount() == 0 ? "" : 
matcher.group(1));
+            }
+        }
+
+        // this should be a raw string if it didn't match anything - suppose 
it could be a syntax error in the
+        // test too, but i guess the test would fail so perhaps ok to just 
assume it's raw string value that
+        // didn't need a transform by default
+        return String.format("\"%s\"", pvalue);
+    }
+
+    private Object convertToObject(final Object pvalue) {
+        final Object v;
+        // we may get some json stuff if it's a m[]
+        if (pvalue instanceof JsonNode) {
+            final JsonNode n = (JsonNode) pvalue;
+            if (n.isArray()) {
+                v = 
IteratorUtils.stream(n.elements()).map(this::convertToObject).collect(Collectors.toList());
+            } else if (n.isObject()) {
+                final Map<Object,Object> m = new HashMap<>(n.size());
+                n.fields().forEachRemaining(e -> 
m.put(convertToObject(e.getKey()), convertToObject(e.getValue())));
+                v = m;
+            } else if (n.isNumber()) {
+                v = n.numberValue();
+            } else if (n.isBoolean()) {
+                v = n.booleanValue();
+            } else {
+                v = n.textValue();
+            }
+        } else {
+            v = pvalue;
+        }
+
+        // if the object is already of a type then no need to push it through 
the matchers.
+        if (!(v instanceof String)) return v;
+
+        for (Pair<Pattern,Function<String,Object>> matcherConverter : 
objectMatcherConverters) {
+            final Pattern pattern = matcherConverter.getValue0();
+            final Matcher matcher = pattern.matcher((String) v);
+            if (matcher.find()) {
+                final Function<String,Object> converter = 
matcherConverter.getValue1();
+                return converter.apply(matcher.group(1));
+            }
+        }
+
+        // this should be a raw string if it didn't match anything - suppose 
it could be a syntax error in the
+        // test too, but i guess the test would fail so perhaps ok to just 
assume it's raw string value that
+        // didn't need a transform by default
+        return String.format("%s", v);
+    }
+
+    private static Triplet<String,String,String> getEdgeTriplet(final String 
e) {
+        final Matcher m = edgeTriplet.matcher(e);
+        if (m.matches()) {
+            return Triplet.with(m.group(1), m.group(2), m.group(3));
+        }
+
+        throw new IllegalStateException(String.format("Invalid edge 
identifier: %s", e));
+    }
+
+    private static Edge getEdge(final GraphTraversalSource g, final String e) {
+        final Triplet<String,String,String> t = getEdgeTriplet(e);
+        return g.V().has("name", 
t.getValue0()).outE(t.getValue1()).where(inV().has("name", 
t.getValue2())).next();
+    }
+
+    private static Object getEdgeId(final GraphTraversalSource g, final String 
e) {
+        return getEdge(g, e).id();
+    }
+
+    private static String getEdgeIdString(final GraphTraversalSource g, final 
String e) {
+        return getEdgeId(g, e).toString();
+    }
+
+    private String applyParameters(final String docString) {
+        String replaced = docString;
+        for (Map.Entry<String, String> kv : stringParameters.entrySet()) {
+            replaced = replaced.replace(kv.getKey(), kv.getValue());
+        }
+        return replaced;
+    }
+}
diff --git 
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/World.java 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/World.java
new file mode 100644
index 0000000..99fac71
--- /dev/null
+++ 
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/World.java
@@ -0,0 +1,57 @@
+/*
+ * 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.features;
+
+import io.cucumber.java.Scenario;
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+
+import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData;
+
+/**
+ * This interface provides the context the test suite needs in order to 
execute the Gherkin tests. It is implemented
+ * by graph providers who wish to test their graph systems against the 
TinkerPop test suite. It is paired with a
+ * test that uses the Cucumber test runner (i.e. {@code 
@RunWith(Cucumber.class)}) and requires a dependency injection
+ * package (e.g. {@code guice}) to push an instance into the Cucumber 
execution.
+ */
+public interface World {
+
+    /**
+     * Gets a {@link GraphTraversalSource} that is backed by the specified 
{@link GraphData}. For {@code null}, the
+     * returned source should be an empty graph with no data in it. Tests do 
not mutate the standard graphs. Only tests
+     * that use an empty graph will change its state.
+     */
+    public GraphTraversalSource getGraphTraversalSource(final GraphData 
graphData);
+
+    /**
+     * Called before each individual test is executed which provides an 
opportunity to do some setup. For example,
+     * if there is a specific test that can't be supported it can be ignored 
by checking for the name with
+     * {@code scenario.getName()} and then throwing an {@code 
AssumptionViolationException}.
+     * @param scenario
+     */
+    public default void beforeEachScenario(final Scenario scenario) {
+        // do nothing
+    }
+
+    /**
+     * Called after each individual test is executed allowing for cleanup of 
any open resources.
+     */
+    public default void afterEachScenario() {
+        // do nothing
+    }
+}
diff --git a/pom.xml b/pom.xml
index efe897d..8460bf9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -405,6 +405,7 @@ limitations under the License.
                         <exclude>**/goal.txt</exclude>
                         
<exclude>**/src/main/resources/META-INF/services/**</exclude>
                         
<exclude>**/src/test/resources/META-INF/services/**</exclude>
+                        
<exclude>**/src/test/resources/cucumber.properties</exclude>
                         
<exclude>**/src/test/resources/incorrect-traversals.txt</exclude>
                         
<exclude>**/src/test/resources/org/apache/tinkerpop/gremlin/console/groovy/plugin/script-customizer-*.groovy</exclude>
                         
<exclude>**/src/test/resources/org/apache/tinkerpop/gremlin/jsr223/script-customizer-*.groovy</exclude>
diff --git a/tinkergraph-gremlin/pom.xml b/tinkergraph-gremlin/pom.xml
index 3f31389..ea943b3 100644
--- a/tinkergraph-gremlin/pom.xml
+++ b/tinkergraph-gremlin/pom.xml
@@ -36,6 +36,12 @@ limitations under the License.
             <artifactId>commons-lang3</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <version>4.2.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
             <scope>test</scope>
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java
new file mode 100644
index 0000000..51896ea
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerGraphFeatureTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.tinkergraph;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import io.cucumber.guice.CucumberModules;
+import io.cucumber.guice.GuiceFactory;
+import io.cucumber.guice.InjectorSource;
+import io.cucumber.java.Scenario;
+import io.cucumber.junit.Cucumber;
+import io.cucumber.junit.CucumberOptions;
+import org.apache.tinkerpop.gremlin.features.World;
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.junit.AssumptionViolatedException;
+import org.junit.runner.RunWith;
+
+import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData;
+
+@RunWith(Cucumber.class)
+@CucumberOptions(
+        tags = "not @RemoteOnly",
+        glue = { "org.apache.tinkerpop.gremlin.features" },
+        objectFactory = GuiceFactory.class,
+        features = { "../gremlin-test/features" },
+        plugin = {"pretty", "junit:target/cucumber.xml"})
+public class TinkerGraphFeatureTest {
+
+    public static final class ServiceModule extends AbstractModule {
+        @Override
+        protected void configure() {
+            bind(World.class).to(TinkerGraphWorld.class);
+        }
+    }
+
+    public static class TinkerGraphWorld implements World {
+        private static final TinkerGraph modern = TinkerFactory.createModern();
+        private static final TinkerGraph classic = 
TinkerFactory.createClassic();
+        private static final TinkerGraph crew = TinkerFactory.createTheCrew();
+        private static final TinkerGraph sink = 
TinkerFactory.createKitchenSink();
+        private static final TinkerGraph grateful = 
TinkerFactory.createGratefulDead();
+
+        @Override
+        public GraphTraversalSource getGraphTraversalSource(final GraphData 
graphData) {
+            if (null == graphData)
+                return TinkerGraph.open().traversal();
+            else if (graphData == GraphData.CLASSIC)
+                return classic.traversal();
+            else if (graphData == GraphData.CREW)
+                return crew.traversal();
+            else if (graphData == GraphData.MODERN)
+                return modern.traversal();
+            else if (graphData == GraphData.SINK)
+                return sink.traversal();
+            else if (graphData == GraphData.GRATEFUL)
+                return grateful.traversal();
+            else
+                throw new UnsupportedOperationException("GraphData not 
supported: " + graphData.name());
+        }
+    }
+
+    public static final class WorldInjectorSource implements InjectorSource {
+        @Override
+        public Injector getInjector() {
+            return Guice.createInjector(Stage.PRODUCTION, 
CucumberModules.createScenarioModule(), new ServiceModule());
+        }
+    }
+
+}
diff --git a/tinkergraph-gremlin/src/test/resources/cucumber.properties 
b/tinkergraph-gremlin/src/test/resources/cucumber.properties
new file mode 100644
index 0000000..159b8c8
--- /dev/null
+++ b/tinkergraph-gremlin/src/test/resources/cucumber.properties
@@ -0,0 +1 @@
+guice.injector-source=org.apache.tinkerpop.gremlin.tinkergraph.TinkerGraphFeatureTest$WorldInjectorSource
\ No newline at end of file

Reply via email to