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

Cole-Greer pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/master by this push:
     new 86799f0eea Added coverage for gremlin-console and gremlin-language 
(#3512)
86799f0eea is described below

commit 86799f0eea756094fef5c1d94964f93495cf6b39
Author: Guian Gumpac <[email protected]>
AuthorDate: Wed Oct 21 11:01:22 2026 -0700

    Added coverage for gremlin-console and gremlin-language (#3512)
    
    Two related but independent changes that improve test coverage in 
TinkerPop, each self-contained:
    
    1. **Grammar parser coverage** - raises `GremlinParser` coverage in 
`gremlin-language`.
    2. **gremlin-console coverage** - adds unit + end-to-end (pexpect) tests 
for the console.
    
    Both changes also remove dead code found while raising coverage. Removals 
are listed first, followed by a consolidated coverage summary with the 
before/after numbers and an explanation of why coverage doesn't go higher.
    
    Assisted-by: Kiro: Claude Opus 4.8
---
 gremlin-console/pom.xml                            |   8 +
 .../tinkerpop/gremlin/console/Console.groovy       |  38 ---
 .../tinkerpop/gremlin/console/PluggedIn.groovy     |  31 --
 .../gremlin/console/GremlinGroovyshTest.groovy     |  82 ++++-
 .../tinkerpop/gremlin/console/MediatorTest.groovy  |  62 ++++
 .../gremlin/console/PreferencesTest.groovy         | 132 ++++++++
 .../console/commands/GremlinSetCommandTest.groovy  | 128 ++++++++
 .../console/commands/InstallCommandTest.groovy     |  82 +++++
 .../console/commands/PluginCommandTest.groovy      |  73 +++++
 .../console/commands/UninstallCommandTest.groovy   |  64 ++++
 .../console/InMemoryPreferencesFactory.java        | 109 +++++++
 .../jsr223/MockGroovyGremlinShellEnvironment.java  |  72 -----
 .../src/test/python/tests/test_console.py          | 344 +++++++++++++++++++++
 .../grammar/GeneralLiteralVisitorTest.java         |  65 ++++
 .../grammar/TraversalMethodVisitorTest.java        |  89 +++++-
 .../grammar/TraversalPredicateVisitorTest.java     |   4 +
 .../grammar/TraversalSourceSpawnVisitorTest.java   |  62 ++++
 .../gremlin/language/grammar/BasicGrammarTest.java | 158 ++++++++++
 18 files changed, 1459 insertions(+), 144 deletions(-)

diff --git a/gremlin-console/pom.xml b/gremlin-console/pom.xml
index 0c96d16aa7..960d70c5b2 100644
--- a/gremlin-console/pom.xml
+++ b/gremlin-console/pom.xml
@@ -160,6 +160,14 @@ limitations under the License.
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <!-- Isolate console preference tests in an in-memory 
java.util.prefs store so they never
+                         touch the developer's real per-user preferences. 
Resolved at fork startup, before
+                         Preferences.STORE binds. Merges with the inherited 
argLine configuration. -->
+                    <systemPropertyVariables>
+                        
<java.util.prefs.PreferencesFactory>org.apache.tinkerpop.gremlin.console.InMemoryPreferencesFactory</java.util.prefs.PreferencesFactory>
+                    </systemPropertyVariables>
+                </configuration>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
diff --git 
a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
 
b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
index 81f4dd48b7..d8a607d0c3 100644
--- 
a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
+++ 
b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
@@ -407,44 +407,6 @@ class Console {
         return null
     }
 
-    private def writeTraverserToErrorLines(Traverser t, List errorLines) {
-        // every traverser has an object so toString() that. pad with spaces 
to cover "side-effects" width
-        errorLines << "Traverser> $t"
-
-        def optGenerator = t.asAdmin().generator
-        if (optGenerator.isPresent()) {
-            def width = "Traverser".length()
-            def generator = optGenerator.get()
-            if 
(generator.providedRequirements.contains(TraverserRequirement.BULK)) {
-                errorLines << "  Bulk".padRight(width) + "> " + t.bulk()
-            }
-
-            if 
(generator.providedRequirements.contains(TraverserRequirement.SACK)) {
-                errorLines << "  Sack".padRight(width) + "> " + t.sack()
-            }
-
-            if 
(generator.providedRequirements.contains(TraverserRequirement.PATH)) {
-                errorLines << "  Path".padRight(width) + "> " + t.path()
-            }
-
-            if 
(generator.providedRequirements.contains(TraverserRequirement.SINGLE_LOOP) ||
-                    
generator.providedRequirements.contains(TraverserRequirement.NESTED_LOOP)) {
-                // flatten loops/names if present
-                def loopNames = t.asAdmin().loopNames
-                def loopsLine = loopNames.isEmpty() ? t.loops() : 
loopNames.collect { [(it): t.loops(it)]}
-                errorLines << "  Loops".padRight(width) + "> " + loopsLine
-            }
-
-            if 
(generator.providedRequirements.contains(TraverserRequirement.SIDE_EFFECTS)) {
-                // convert side-effects to a map
-                def sideEffects = t.asAdmin().sideEffects
-                def keys = sideEffects.keys()
-                errorLines << "  S/E".padRight(width) + "> " + 
keys.collectEntries { [(it): sideEffects.get(it)]}
-            }
-
-        }
-    }
-
     private static String buildResultPrompt() {
         final String groovyshellProperty = System.getProperty("gremlin.prompt")
         if (groovyshellProperty != null)
diff --git 
a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/PluggedIn.groovy
 
b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/PluggedIn.groovy
index 268d7a28e3..37e21a9df7 100644
--- 
a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/PluggedIn.groovy
+++ 
b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/PluggedIn.groovy
@@ -77,35 +77,4 @@ class PluggedIn {
     void deactivate() {
         this.activated = false
     }
-
-    public class GroovyGremlinShellEnvironment implements 
GremlinShellEnvironment {
-
-        @Override
-        def <T> T getVariable(final String variableName) {
-            return (T) shell.interp.context.getVariable(variableName)
-        }
-
-        @Override
-        def <T> void setVariable(final String variableName, final T 
variableValue) {
-            shell.interp.context.setVariable(variableName, variableValue)
-        }
-
-        @Override
-        void println(final String line) {
-            io.println(line)
-        }
-
-        @Override
-        void errPrintln(final String line) {
-            if (!Preferences.warnings) {
-                return;
-            }
-            io.err.println("[warn] " + line);
-        }
-
-        @Override
-        def <T> T execute(final String line) {
-            return (T) shell.execute(line)
-        }
-    }
 }
diff --git 
a/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/GremlinGroovyshTest.groovy
 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/GremlinGroovyshTest.groovy
index 730f4e871e..89f1673cf8 100644
--- 
a/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/GremlinGroovyshTest.groovy
+++ 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/GremlinGroovyshTest.groovy
@@ -21,19 +21,28 @@ package org.apache.tinkerpop.gremlin.console
 
 import 
org.apache.tinkerpop.gremlin.console.jsr223.AbstractGremlinServerIntegrationTest
 import org.apache.tinkerpop.gremlin.console.jsr223.RemoteGremlinPlugin
+import org.apache.tinkerpop.gremlin.console.commands.GremlinSetCommand
 import org.apache.tinkerpop.gremlin.structure.io.Storage
 import org.apache.tinkerpop.gremlin.util.TestSupport
+import org.apache.groovy.groovysh.Command
+import org.apache.groovy.groovysh.commands.SetCommand
 import org.codehaus.groovy.tools.shell.IO
 import org.junit.Test
 
 import java.nio.file.Files
 
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertNull
+import static org.junit.Assert.assertSame
 import static org.junit.Assert.assertTrue
 
 class GremlinGroovyshTest extends AbstractGremlinServerIntegrationTest {
     private IO testio
     private ByteArrayOutputStream out
     private ByteArrayOutputStream err
+    private Mediator mediator
     private GremlinGroovysh shell
     private File propertiesFile
 
@@ -43,7 +52,8 @@ class GremlinGroovyshTest extends 
AbstractGremlinServerIntegrationTest {
         out = new ByteArrayOutputStream()
         err = new ByteArrayOutputStream()
         testio = new IO(new ByteArrayInputStream(), out, err)
-        shell = new GremlinGroovysh(new Mediator(null), testio)
+        mediator = new Mediator(null)
+        shell = new GremlinGroovysh(mediator, testio)
 
         prepareConfigFiles()
     }
@@ -73,6 +83,76 @@ class GremlinGroovyshTest extends 
AbstractGremlinServerIntegrationTest {
         assertTrue(out.toString().endsWith("6" + System.lineSeparator()))
     }
 
+    @Test
+    void shouldParseLineIntoWhitespaceDelimitedTokens() {
+        assertEquals(["g.V().count()"], shell.parseLine("g.V().count()"))
+        assertEquals([":set", "a", "b"], shell.parseLine("  :set a b  "))
+        assertTrue(shell.parseLine("   ").isEmpty())
+    }
+
+    @Test
+    void shouldReturnNullCommandForEmptyOrBlankLine() {
+        assertNull(shell.findCommand(""))
+        assertNull(shell.findCommand("   "))
+    }
+
+    @Test
+    void shouldReturnNullCommandForNonCommandLine() {
+        assertNull(shell.findCommand("g.V().count()", []))
+    }
+
+    @Test
+    void shouldFindCommandWithoutPopulatingArgsForSingleToken() {
+        final List<String> parsed = []
+        final Command cmd = shell.findCommand(":help", parsed)
+        assertNotNull(cmd)
+        assertTrue(parsed.isEmpty())
+    }
+
+    @Test
+    void shouldPopulateParsedArgsForNonSetCommand() {
+        // the standard :set command (still registered on a bare shell) drives 
the non-GremlinSetCommand
+        // branch which simply appends the remaining tokens
+        final List<String> parsed = []
+        final Command cmd = shell.findCommand(":set foo bar", parsed)
+        assertNotNull(cmd)
+        assertEquals(["foo", "bar"], parsed)
+    }
+
+    @Test
+    void shouldParseQuotedArgsForGremlinSetCommand() {
+        // replace the standard SetCommand with the Gremlin variant, as the 
real Console does, so that the
+        // GremlinSetCommand branch (which honours quoted arguments) is 
exercised
+        shell.getRegistry().commands().findAll { it instanceof SetCommand 
}.each { shell.getRegistry().remove(it) }
+        shell.register(new GremlinSetCommand(shell))
+
+        final List<String> parsed = []
+        final Command cmd = shell.findCommand(":set result.prompt \"==> \"", 
parsed)
+        assertTrue(cmd instanceof GremlinSetCommand)
+        // the quoted value is preserved as a single argument rather than 
being split on whitespace
+        assertEquals("result.prompt", parsed[0])
+        assertEquals("==> ", parsed[1])
+    }
+
+    @Test
+    void shouldEvaluateLocalExpressionAndResetEvaluatingFlag() {
+        out.reset()
+        final def result = shell.execute("1 + 1")
+        assertEquals(2, result)
+        // the evaluating flag is toggled during execution and reset in the 
finally block
+        assertFalse(mediator.evaluating.get())
+    }
+
+    @Test
+    void shouldResetEvaluatingFlagAfterFailedExecution() {
+        try {
+            shell.execute("this is not valid groovy ^^^")
+        } catch (Throwable ignored) {
+            // a compilation/evaluation failure is expected here
+        }
+        assertFalse(mediator.evaluating.get())
+    }
+
     @Override
     void tearDown() {
         super.tearDown()
diff --git 
a/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/MediatorTest.groovy
 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/MediatorTest.groovy
new file mode 100644
index 0000000000..3b5d37462c
--- /dev/null
+++ 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/MediatorTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * 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.console
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+
+/**
+ * Unit tests for {@link Mediator} that avoid a live Gremlin server or a fully 
constructed {@link Console}.
+ */
+class MediatorTest {
+
+    @Test
+    void shouldConstructWithNullConsole() {
+        final Mediator mediator = new Mediator(null)
+        assertNotNull(mediator)
+        assertTrue(mediator.availablePlugins.isEmpty())
+        assertFalse(mediator.evaluating.get())
+    }
+
+    @Test
+    void shouldReturnEmptyActivePluginsWhenNoneAvailable() {
+        final Mediator mediator = new Mediator(null)
+        assertTrue(mediator.activePlugins().isEmpty())
+    }
+
+    @Test
+    void shouldReadEmptyPluginStateWhenConfigMissing() {
+        // with no plugin-config file present, readPluginState() returns an 
empty list (not null)
+        new File(ConsoleFs.PLUGIN_CONFIG_FILE).delete()
+        assertEquals([], Mediator.readPluginState())
+    }
+
+    @Test
+    void shouldNotWritePluginStateWhenConfigNotWritable() {
+        // File.canWrite() is false when the config file does not exist, so 
writePluginState() reports false
+        // and writes nothing
+        new File(ConsoleFs.PLUGIN_CONFIG_FILE).delete()
+        final Mediator mediator = new Mediator(null)
+        assertFalse(mediator.writePluginState())
+    }
+}
diff --git 
a/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/PreferencesTest.groovy
 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/PreferencesTest.groovy
new file mode 100644
index 0000000000..7910011973
--- /dev/null
+++ 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/PreferencesTest.groovy
@@ -0,0 +1,132 @@
+/*
+ * 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.console
+
+import org.codehaus.groovy.tools.shell.util.Preferences as ShellPreferences
+import org.codehaus.groovy.tools.shell.IO
+import org.fusesource.jansi.Ansi
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+/**
+ * Exercises the {@link Preferences} change listeners installed by {@code 
expandoMagic()}, focusing on the
+ * validation/fallback branches (bad color, bad integer, bad verbosity). 
Preference writes reach the listeners
+ * asynchronously via the backing {@code java.util.prefs} store, so assertions 
poll for the update.
+ */
+class PreferencesTest {
+
+    private static boolean originalAnsiEnabled
+
+    @BeforeClass
+    static void setUpClass() {
+        // Preferences are backed by an in-memory java.util.prefs store 
(InMemoryPreferencesFactory, selected via
+        // this module's surefire config), so these tests never touch the 
developer's real console preferences.
+
+        // Ansi must be on for Colorizer.render to validate colors and throw, 
driving the getValidColor fallback.
+        originalAnsiEnabled = Ansi.isEnabled()
+        Ansi.setEnabled(true)
+        Preferences.colors = true
+
+        // Installs the change listeners AND loads default values.
+        Preferences.expandoMagic()
+    }
+
+    @AfterClass
+    static void tearDownClass() {
+        Ansi.setEnabled(originalAnsiEnabled)
+    }
+
+    @Test
+    void shouldUpdateColorOnValidValueAndFallBackOnInvalid() {
+        // getValidColor only falls back when Colorizer.render throws, which 
requires ansi + colors enabled
+        // (another test may have turned colors off, and test order is not 
guaranteed).
+        Ansi.setEnabled(true)
+        Preferences.colors = true
+
+        // valid color drives the getValidColor success path
+        putAndWait(Preferences.PREF_GREMLIN_COLOR, "red") { 
Preferences.gremlinColor == "red" }
+        assertEquals("red", Preferences.gremlinColor)
+
+        // an unrenderable color makes Colorizer.render throw, driving the 
getValidColor catch/fallback branch
+        putAndWait(Preferences.PREF_GREMLIN_COLOR, "notavalidcolorname") {
+            Preferences.gremlinColor == Preferences.PREF_GREMLIN_COLOR_DEFAULT
+        }
+        assertEquals(Preferences.PREF_GREMLIN_COLOR_DEFAULT, 
Preferences.gremlinColor)
+    }
+
+    @Test
+    void shouldUpdateMaxIterationOnValidValueAndFallBackOnInvalid() {
+        putAndWait(Preferences.PREFERENCE_ITERATION_MAX, "50") { 
Preferences.maxIteration == 50 }
+        assertEquals(50, Preferences.maxIteration)
+
+        // a non-numeric value drives the NumberFormatException fallback to 
the default
+        putAndWait(Preferences.PREFERENCE_ITERATION_MAX, "not-a-number") { 
Preferences.maxIteration == 100 }
+        assertEquals(100, Preferences.maxIteration)
+    }
+
+    @Test
+    void shouldUpdateStringPreferences() {
+        putAndWait(Preferences.PREF_RESULT_IND_NULL, "nothing") { 
Preferences.emptyResult == "nothing" }
+        assertEquals("nothing", Preferences.emptyResult)
+
+        putAndWait(Preferences.PREF_INPUT_PROMPT, "in>") { 
Preferences.inputPrompt == "in>" }
+        assertEquals("in>", Preferences.inputPrompt)
+
+        putAndWait(Preferences.PREF_RESULT_PROMPT, "res>") { 
Preferences.resultPrompt == "res>" }
+        assertEquals("res>", Preferences.resultPrompt)
+    }
+
+    @Test
+    void shouldUpdateBooleanPreferences() {
+        putAndWait(Preferences.PREF_COLORS, "false") { !Preferences.colors }
+        assertEquals(false, Preferences.colors)
+
+        putAndWait(Preferences.PREF_WARNINGS, "false") { !Preferences.warnings 
}
+        assertEquals(false, Preferences.warnings)
+    }
+
+    @Test
+    void shouldUpdateVerbosityOnValidValueAndRestoreOnInvalid() {
+        putAndWait(ShellPreferences.VERBOSITY_KEY, IO.Verbosity.INFO.name) {
+            ShellPreferences.verbosity == IO.Verbosity.INFO
+        }
+        assertEquals(IO.Verbosity.INFO, ShellPreferences.verbosity)
+
+        // an invalid value makes IO.Verbosity.forName throw; the listener's 
catch branch re-writes the current
+        // valid verbosity name back to the store, so wait for that re-write 
and confirm verbosity is unchanged
+        putAndWait(ShellPreferences.VERBOSITY_KEY, "notaverbosity") {
+            ShellPreferences.get(ShellPreferences.VERBOSITY_KEY, null) == 
IO.Verbosity.INFO.name
+        }
+        assertEquals(IO.Verbosity.INFO, ShellPreferences.verbosity)
+    }
+
+    /**
+     * Writes a preference and polls until the listener updates the target 
field, or the timeout expires.
+     */
+    private static void putAndWait(final String key, final String value, final 
Closure<Boolean> condition) {
+        ShellPreferences.put(key, value)
+        final long deadline = System.currentTimeMillis() + 5000
+        while (!condition.call() && System.currentTimeMillis() < deadline) {
+            Thread.sleep(10)
+        }
+    }
+}
diff --git 
a/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/GremlinSetCommandTest.groovy
 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/GremlinSetCommandTest.groovy
new file mode 100644
index 0000000000..56742738ab
--- /dev/null
+++ 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/GremlinSetCommandTest.groovy
@@ -0,0 +1,128 @@
+/*
+ * 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.console.commands
+
+import jline.console.completer.Completer
+import org.apache.tinkerpop.gremlin.console.GremlinGroovysh
+import org.apache.tinkerpop.gremlin.console.Mediator
+import org.apache.tinkerpop.gremlin.console.Preferences
+import org.codehaus.groovy.tools.shell.IO
+import org.codehaus.groovy.tools.shell.util.Preferences as ShellPreferences
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertNull
+import static org.junit.Assert.assertTrue
+
+/**
+ * Unit tests for {@link GremlinSetCommand} that exercise the Gremlin-specific 
completer and the preference
+ * setting/listing behaviour without a live Gremlin server, Grape, or network 
access.
+ */
+class GremlinSetCommandTest {
+
+    private ByteArrayOutputStream out
+    private ByteArrayOutputStream err
+    private GremlinGroovysh shell
+    private GremlinSetCommand command
+
+    @Before
+    void setUp() {
+        out = new ByteArrayOutputStream()
+        err = new ByteArrayOutputStream()
+        final IO testio = new IO(new ByteArrayInputStream(), out, err)
+        shell = new GremlinGroovysh(new Mediator(null), testio)
+        command = new GremlinSetCommand(shell)
+    }
+
+    @After
+    void tearDown() {
+        // These tests write to the real per-user java.util.prefs store, so 
clear it to avoid polluting dev prefs.
+        ShellPreferences.clear()
+    }
+
+    @Test
+    void shouldCreateCompletersWithNullSecondEntry() {
+        final List<Completer> completers = command.createCompleters()
+        assertNotNull(completers)
+        assertEquals(2, completers.size())
+        assertNotNull(completers[0])
+        assertNull(completers[1])
+    }
+
+    @Test
+    void shouldIncludeGremlinSpecificPreferencesInCompleter() {
+        final Completer completer = command.createCompleters()[0]
+        final List<CharSequence> candidates = []
+        completer.complete("", 0, candidates)
+
+        final List<String> candidateStrings = candidates.collect { 
it.toString().trim() }
+
+        // Gremlin Console specific preferences that the base SetCommand does 
not offer.
+        assertTrue("expected max-iteration in completer candidates: 
${candidateStrings}".toString(),
+                
candidateStrings.contains(Preferences.PREFERENCE_ITERATION_MAX))
+        assertTrue("expected result.indicator.null in completer candidates: 
${candidateStrings}".toString(),
+                candidateStrings.contains(Preferences.PREF_RESULT_IND_NULL))
+        assertTrue("expected vertex.color in completer candidates: 
${candidateStrings}".toString(),
+                candidateStrings.contains(Preferences.PREF_VERTEX_COLOR))
+        assertTrue("expected input.prompt in completer candidates: 
${candidateStrings}".toString(),
+                candidateStrings.contains(Preferences.PREF_INPUT_PROMPT))
+    }
+
+    @Test
+    void shouldFilterCompleterCandidatesByPrefix() {
+        final Completer completer = command.createCompleters()[0]
+        final List<CharSequence> candidates = []
+        completer.complete("max", 3, candidates)
+
+        final List<String> candidateStrings = candidates.collect { 
it.toString().trim() }
+        assertTrue("expected only 'max' prefixed candidates: 
${candidateStrings}".toString(),
+                
candidateStrings.contains(Preferences.PREFERENCE_ITERATION_MAX))
+        assertFalse("did not expect unrelated keys: 
${candidateStrings}".toString(),
+                candidateStrings.contains(Preferences.PREF_VERTEX_COLOR))
+    }
+
+    @Test
+    void shouldSetAndRetrieveKnownPreference() {
+        command.execute([Preferences.PREFERENCE_ITERATION_MAX, "42"])
+        assertEquals("42", 
ShellPreferences.get(Preferences.PREFERENCE_ITERATION_MAX, null))
+    }
+
+    @Test
+    void shouldSetBooleanPreferenceToTrueWhenValueOmitted() {
+        // With a single argument the value defaults to "true".
+        command.execute([Preferences.PREF_WARNINGS])
+        assertEquals("true", ShellPreferences.get(Preferences.PREF_WARNINGS, 
null))
+    }
+
+    @Test
+    void shouldListPreferencesWhenNoArguments() {
+        command.execute([Preferences.PREF_RESULT_PROMPT, "==>"])
+        out.reset()
+
+        command.execute([])
+
+        final String output = out.toString()
+        assertTrue("expected a preferences listing but got: 
${output}".toString(),
+                output.contains("Preferences:") && 
output.contains(Preferences.PREF_RESULT_PROMPT))
+    }
+}
diff --git 
a/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/InstallCommandTest.groovy
 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/InstallCommandTest.groovy
new file mode 100644
index 0000000000..ec1f6e8b94
--- /dev/null
+++ 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/InstallCommandTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * 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.console.commands
+
+import org.apache.tinkerpop.gremlin.console.GremlinGroovysh
+import org.apache.tinkerpop.gremlin.console.GremlinGroovysh
+import org.apache.tinkerpop.gremlin.console.Mediator
+import groovy.grape.Grape
+import org.junit.After
+import org.codehaus.groovy.tools.shell.IO
+import org.junit.Before
+import org.junit.Test
+
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+
+class InstallCommandTest {
+    private InstallCommand cmd
+
+    @Before
+    void setUp() {
+        def io = new IO(new ByteArrayInputStream(), new 
ByteArrayOutputStream(), new ByteArrayOutputStream())
+        def shell = new GremlinGroovysh(new Mediator(null), io)
+        cmd = new InstallCommand(shell, new Mediator(null))
+    }
+
+    @Test(expected = IllegalArgumentException)
+    void shouldRejectMissingGroup() { cmd.execute([]) }
+
+    @Test(expected = IllegalArgumentException)
+    void shouldRejectMissingModule() { cmd.execute(["org.example"]) }
+
+    @Test(expected = IllegalArgumentException)
+    void shouldRejectMissingVersion() { cmd.execute(["org.example", 
"some-artifact"]) }
+
+    @Test
+    void shouldReturnErrorMessageWhenArtifactCannotBeResolved() {
+        // valid args but a bogus, unresolvable artifact -> download fails -> 
catch returns the message (String)
+        def result = cmd.execute(["com.tinkerpop.nonexistent.fake", 
"no-such-artifact-xyz", "9.9.9"])
+        assertNotNull(result)
+        assertTrue(result instanceof String)
+        final String message = (String) result
+        // the returned message reports the dependency-resolution failure for 
the requested artifact
+        assertTrue(message, message.contains("unresolved dependency"))
+        assertTrue(message, message.contains("no-such-artifact-xyz"))
+    }
+
+    @After
+    void tearDown() {
+        // remove the metaclass stub so other tests/JVM state are unaffected
+        GroovySystem.metaClassRegistry.removeMetaClass(Grape)
+    }
+
+    @Test
+    void shouldRegisterAvailablePluginsFromServiceLoaderInGrabDeps() {
+        // stub the network-dependent Grape.grab so grabDeps proceeds offline;
+        // ServiceLoader then finds the GremlinPlugin impls already on the 
classpath and runs the closure body
+        Grape.metaClass.static.grab = { Map m -> null }
+        def io = new IO(new ByteArrayInputStream(), new 
ByteArrayOutputStream(), new ByteArrayOutputStream())
+        def mediator = new Mediator(null)
+        def shell = new GremlinGroovysh(mediator, io)
+        def cmd = new InstallCommand(shell, mediator)
+        cmd.grabDeps([group: "x", module: "y", version: "z"])
+        assertTrue(mediator.availablePlugins.size() > 0)
+    }
+}
diff --git 
a/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/PluginCommandTest.groovy
 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/PluginCommandTest.groovy
new file mode 100644
index 0000000000..b9476ec9a2
--- /dev/null
+++ 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/PluginCommandTest.groovy
@@ -0,0 +1,73 @@
+/*
+ * 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.console.commands
+
+import org.apache.tinkerpop.gremlin.console.GremlinGroovysh
+import org.apache.tinkerpop.gremlin.console.Mediator
+import org.apache.tinkerpop.gremlin.console.PluggedIn
+import org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin
+import org.codehaus.groovy.tools.shell.IO
+import org.junit.Before
+import org.junit.Test
+
+import static org.junit.Assert.assertTrue
+
+class PluginCommandTest {
+    private PluginCommand cmd
+
+    @Before
+    void setUp() {
+        def io = new IO(new ByteArrayInputStream(), new 
ByteArrayOutputStream(), new ByteArrayOutputStream())
+        def shell = new GremlinGroovysh(new Mediator(null), io)
+        cmd = new PluginCommand(shell, new Mediator(null))
+    }
+
+    @Test
+    void shouldRequirePluginNameForUse() {
+        assertTrue(((String) cmd.do_use.call([])).contains("Specify the name"))
+    }
+
+    @Test
+    void shouldReportUnknownPluginForUse() {
+        assertTrue(((String) 
cmd.do_use.call(["no.such.plugin"])).contains("could not be found"))
+    }
+
+    @Test
+    void shouldRequirePluginNameForUnuse() {
+        assertTrue(((String) cmd.do_unuse.call([])).contains("Specify the 
name"))
+    }
+
+    @Test
+    void shouldReportUnknownPluginForUnuse() {
+        assertTrue(((String) 
cmd.do_unuse.call(["no.such.plugin"])).contains("could not be found"))
+    }
+
+    @Test
+    void shouldDeactivateAnAvailablePlugin() {
+        def io = new IO(new ByteArrayInputStream(), new 
ByteArrayOutputStream(), new ByteArrayOutputStream())
+        def mediator = new Mediator(null)
+        def shell = new GremlinGroovysh(mediator, io)
+        // populate a real plugin from the classpath so do_unuse's any/find 
predicates + deactivate path run
+        def plugin = ServiceLoader.load(GremlinPlugin, 
shell.getInterp().getClassLoader()).iterator().next()
+        mediator.availablePlugins.put(plugin.class.name, new 
PluggedIn((GremlinPlugin) plugin, shell, io, false))
+        def cmd = new PluginCommand(shell, mediator)
+        def result = (String) cmd.do_unuse.call([plugin.name])
+        assertTrue(result.contains("deactivated"))
+    }
+}
diff --git 
a/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/UninstallCommandTest.groovy
 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/UninstallCommandTest.groovy
new file mode 100644
index 0000000000..4b01e43ad8
--- /dev/null
+++ 
b/gremlin-console/src/test/groovy/org/apache/tinkerpop/gremlin/console/commands/UninstallCommandTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * 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.console.commands
+
+import org.apache.tinkerpop.gremlin.console.ConsoleFs
+import org.apache.tinkerpop.gremlin.console.GremlinGroovysh
+import org.apache.tinkerpop.gremlin.console.Mediator
+import org.codehaus.groovy.tools.shell.IO
+import org.junit.Before
+import org.junit.Test
+
+import static org.junit.Assert.assertTrue
+
+class UninstallCommandTest {
+    private UninstallCommand cmd
+
+    @Before
+    void setUp() {
+        def io = new IO(new ByteArrayInputStream(), new 
ByteArrayOutputStream(), new ByteArrayOutputStream())
+        def shell = new GremlinGroovysh(new Mediator(null), io)
+        cmd = new UninstallCommand(shell, new Mediator(null))
+    }
+
+    @Test
+    void shouldReportMissingModuleArgument() {
+        assertTrue(((String) cmd.execute([])).contains("Specify the name"))
+    }
+
+    @Test
+    void shouldReportUnknownModule() {
+        def result = (String) cmd.execute(["no-such-module-xyz123"])
+        assertTrue(result.contains("no module with the name"))
+    }
+
+    @Test
+    void shouldUninstallExistingModule() {
+        def name = "test-uninstall-" + System.currentTimeMillis()
+        def dir = new File(ConsoleFs.CONSOLE_HOME_DIR, name)
+        dir.mkdirs()
+        try {
+            def result = (String) cmd.execute([name])
+            assertTrue(result.contains("Uninstalled"))
+            assertTrue(!dir.exists())
+        } finally {
+            dir.deleteDir()
+        }
+    }
+}
diff --git 
a/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/InMemoryPreferencesFactory.java
 
b/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/InMemoryPreferencesFactory.java
new file mode 100644
index 0000000000..d8192c280d
--- /dev/null
+++ 
b/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/InMemoryPreferencesFactory.java
@@ -0,0 +1,109 @@
+/*
+ * 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.console;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.prefs.AbstractPreferences;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+import java.util.prefs.PreferencesFactory;
+
+/**
+ * A purely in-memory {@link PreferencesFactory} used only by tests. Selected 
for the test JVM via the
+ * {@code java.util.prefs.PreferencesFactory} system property (configured in 
this module's surefire setup) so that
+ * console preference tests never read from or write to the developer's real 
per-user {@code java.util.prefs} store.
+ * The property is resolved by {@link Preferences} on first access, before 
{@code Preferences.STORE} binds, so the
+ * console's preference node is backed by this in-memory store for the entire 
fork.
+ */
+public class InMemoryPreferencesFactory implements PreferencesFactory {
+
+    private static final Preferences USER_ROOT = new InMemoryPreferences(null, 
"");
+    private static final Preferences SYSTEM_ROOT = new 
InMemoryPreferences(null, "");
+
+    @Override
+    public Preferences userRoot() {
+        return USER_ROOT;
+    }
+
+    @Override
+    public Preferences systemRoot() {
+        return SYSTEM_ROOT;
+    }
+
+    /**
+     * Minimal {@link AbstractPreferences} node keeping keys and children in 
memory. {@code AbstractPreferences}
+     * handles locking and the asynchronous change-event dispatch, so the SPI 
methods only manage the maps.
+     */
+    private static final class InMemoryPreferences extends AbstractPreferences 
{
+
+        private final Map<String, String> values = new ConcurrentHashMap<>();
+        private final Map<String, InMemoryPreferences> children = new 
ConcurrentHashMap<>();
+
+        InMemoryPreferences(final InMemoryPreferences parent, final String 
name) {
+            super(parent, name);
+        }
+
+        @Override
+        protected void putSpi(final String key, final String value) {
+            values.put(key, value);
+        }
+
+        @Override
+        protected String getSpi(final String key) {
+            return values.get(key);
+        }
+
+        @Override
+        protected void removeSpi(final String key) {
+            values.remove(key);
+        }
+
+        @Override
+        protected void removeNodeSpi() throws BackingStoreException {
+            values.clear();
+            children.clear();
+        }
+
+        @Override
+        protected String[] keysSpi() throws BackingStoreException {
+            return values.keySet().toArray(new String[0]);
+        }
+
+        @Override
+        protected String[] childrenNamesSpi() throws BackingStoreException {
+            return children.keySet().toArray(new String[0]);
+        }
+
+        @Override
+        protected AbstractPreferences childSpi(final String name) {
+            return children.computeIfAbsent(name, n -> new 
InMemoryPreferences(this, n));
+        }
+
+        @Override
+        protected void syncSpi() throws BackingStoreException {
+            // nothing to sync for an in-memory store
+        }
+
+        @Override
+        protected void flushSpi() throws BackingStoreException {
+            // nothing to flush for an in-memory store
+        }
+    }
+}
diff --git 
a/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/jsr223/MockGroovyGremlinShellEnvironment.java
 
b/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/jsr223/MockGroovyGremlinShellEnvironment.java
deleted file mode 100644
index 7a05af3fda..0000000000
--- 
a/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/jsr223/MockGroovyGremlinShellEnvironment.java
+++ /dev/null
@@ -1,72 +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.console.jsr223;
-
-import org.apache.groovy.groovysh.Groovysh;
-import org.apache.tinkerpop.gremlin.jsr223.console.GremlinShellEnvironment;
-import org.codehaus.groovy.tools.shell.IO;
-
-/**
- * @author Stephen Mallette (http://stephen.genoprime.com)
- */
-public class MockGroovyGremlinShellEnvironment implements 
GremlinShellEnvironment {
-
-    private final Groovysh groovysh;
-    private final IO io;
-
-    public MockGroovyGremlinShellEnvironment(final Groovysh groovysh) {
-        this(groovysh, null);
-    }
-
-    public MockGroovyGremlinShellEnvironment(final Groovysh groovysh, final IO 
io) {
-        this.groovysh = groovysh;
-        this.io = io;
-    }
-
-    @Override
-    public <T> T getVariable(final String variableName) {
-        return (T) groovysh.getInterp().getContext().getVariable(variableName);
-    }
-
-    @Override
-    public <T> void setVariable(final String variableName, final T 
variableValue) {
-        groovysh.getInterp().getContext().setVariable(variableName, 
variableValue);
-    }
-
-    @Override
-    public void println(final String line) {
-        if (null == io)
-            groovysh.getIo().out.println(line);
-        else
-            io.out.println(line);
-    }
-
-    @Override
-    public void errPrintln(final String line) {
-        if (null == io)
-            groovysh.getIo().err.println("[warn]" + line);
-        else
-            io.err.println("[warn]" + line);
-    }
-
-    @Override
-    public <T> T execute(final String line) {
-        return (T) groovysh.execute(line);
-    }
-}
diff --git a/gremlin-console/src/test/python/tests/test_console.py 
b/gremlin-console/src/test/python/tests/test_console.py
index 137c21b259..f6c03a1d63 100644
--- a/gremlin-console/src/test/python/tests/test_console.py
+++ b/gremlin-console/src/test/python/tests/test_console.py
@@ -18,6 +18,8 @@ under the License.
 '''
 
 import pexpect
+import os
+import tempfile
 
 
 class TestConsole(object):
@@ -171,6 +173,348 @@ class TestConsole(object):
         child.expect("6\r\n")
         TestConsole._close(child)
 
+    # dimensions=(50, 400): the wide terminal stops the pty from wrapping long
+    # input lines, which would otherwise garble pexpect echo/output matching.
+    def test_result_rendering_of_various_types(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+        TestConsole._setup_modern_graph(child)
+
+        # number result
+        child.sendline("g.V().count()")
+        child.expect_exact("==>6")
+        TestConsole._expect_prompt(child)
+
+        # list of strings - iterated one result per line, assert on the first
+        child.sendline("g.V().values('name')")
+        child.expect_exact("==>marko")
+        TestConsole._expect_prompt(child)
+
+        # map result iterated one entry per line
+        child.sendline("g.V().has('name','marko').valueMap().next()")
+        child.expect_exact("==>name=[marko]")
+        TestConsole._expect_prompt(child)
+
+        # vertex result
+        child.sendline("g.V(1).next()")
+        child.expect_exact("==>v[1]")
+        TestConsole._expect_prompt(child)
+
+        # null indicator for a null result
+        child.sendline("null")
+        child.expect_exact("==>null")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_error_handling_runtime_exception(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # a runtime exception should be reported and then the interactive
+        # console prompts to display the stack trace
+        child.sendline("1/0")
+        child.expect_exact("Division by zero")
+        child.expect_exact("Display stack trace? [yN]")
+        child.sendline("n")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_error_handling_missing_property(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        child.sendline("undefinedVariable123")
+        child.expect_exact("No such property")
+        child.expect_exact("Display stack trace? [yN]")
+        child.sendline("n")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_command_help(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        child.sendline(":help")
+        # the help listing enumerates the available commands
+        child.expect_exact(":help")
+        child.expect_exact(":quit")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_command_clear(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # :clear resets the buffer and returns to the prompt without error
+        child.sendline(":clear")
+        TestConsole._expect_prompt(child)
+        # confirm the console is still usable afterwards
+        child.sendline("1+1")
+        child.expect_exact("==>2")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_set_max_iteration_limits_results(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        child.sendline(":set max-iteration 5")
+        TestConsole._expect_prompt(child)
+
+        # a result larger than the limit should be truncated with an ellipsis
+        child.sendline("(1..100).toList()")
+        child.expect_exact("==>5")
+        child.expect_exact("...")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_set_with_too_many_arguments_errors(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # :set accepts at most <name> [<value>]; three args is too many and is 
rejected
+        # with the arg-form error message
+        child.sendline(":set foo bar baz")
+        child.expect_exact("requires arguments: <name> [<value>]")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_plugin_list(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        child.sendline(":plugin list")
+        child.expect_exact("tinkerpop.tinkergraph")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_multiline_input(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # an incomplete expression continues on the next line; completing it
+        # yields the full result
+        child.sendline("[1,2,")
+        child.sendline("3]")
+        child.expect_exact("==>1")
+        child.expect_exact("==>2")
+        child.expect_exact("==>3")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_error_handling_display_stack_trace_when_answering_yes(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # answering 'y' to the stack trace prompt drives the branch that 
prints the full trace
+        child.sendline("1/0")
+        child.expect_exact("Division by zero")
+        child.expect_exact("Display stack trace? [yN]")
+        child.sendline("y")
+        # the printed trace begins with the fully qualified exception class 
name
+        child.expect_exact("java.lang.ArithmeticException")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_error_handling_failure_step(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+        TestConsole._setup_modern_graph(child)
+
+        # the fail() step raises a Failure which is rendered with the 
dedicated Failure formatter
+        # (note: unlike other errors, this path does not prompt for a stack 
trace)
+        child.sendline("g.inject(1).fail('boomfailure123')")
+        child.expect_exact("fail() Step Triggered")
+        child.expect_exact("boomfailure123")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_result_rendering_path(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+        TestConsole._setup_modern_graph(child)
+
+        # a path result is an Iterable that is colorized element by element
+        child.sendline("g.V(1).out().path()")
+        child.expect_exact("[v[1]")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_result_rendering_edge(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+        TestConsole._setup_modern_graph(child)
+
+        # edges are rendered through the dedicated Edge branch of the colorizer
+        child.sendline("g.E().hasLabel('knows')")
+        child.expect_exact("-knows->")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_result_rendering_map_with_tokens(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+        TestConsole._setup_modern_graph(child)
+
+        # folding the element map yields a list containing a Map, so the Map 
is colorized as a whole
+        # (exercising the Map branch and the T-token key rendering of the 
colorizer)
+        child.sendline("g.V(1).elementMap().fold()")
+        child.expect_exact("label:person")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_result_rendering_explain(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+        TestConsole._setup_modern_graph(child)
+
+        # explain() returns a TraversalExplanation that is pretty printed via 
its own rendering branch
+        child.sendline("g.V().out().explain()")
+        child.expect_exact("Traversal Explanation")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_result_rendering_object_array(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # an Object[] is iterated one element per line through the array 
branch of the result handler
+        child.sendline("[1, 2, 3].toArray()")
+        child.expect_exact("==>1")
+        child.expect_exact("==>2")
+        child.expect_exact("==>3")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_result_rendering_nested_lists(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # each element of the outer list is itself an Iterable, exercising the 
recursive colorizer
+        child.sendline("[[1, 2], [3, 4]]")
+        child.expect_exact("==>[1,2]")
+        child.expect_exact("==>[3,4]")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_result_rendering_large_number_and_boolean(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # large numbers are rendered through the Number branch
+        child.sendline("123456789012345")
+        child.expect_exact("==>123456789012345")
+        TestConsole._expect_prompt(child)
+
+        # a boolean falls through to the default toString branch
+        child.sendline("1 < 2")
+        child.expect_exact("==>true")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_plugin_use_activates_available_plugin(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # activating an already available plugin exercises the plugin 
activation closures without any network
+        child.sendline(":plugin use tinkerpop.tinkergraph")
+        child.expect_exact("tinkerpop.tinkergraph activated")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_plugin_use_unknown_plugin(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        child.sendline(":plugin use does.not.exist")
+        child.expect_exact("could not be found")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_uninstall_absent_module(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        # uninstalling a module that is not present reports a not-found 
message (no network access)
+        child.sendline(":uninstall com.example.nonexistent")
+        child.expect_exact("There is no module with the name")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_uninstall_without_argument(self):
+        child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400), 
timeout=120)
+        TestConsole._expect_gremlin_header(child)
+
+        child.sendline(":uninstall")
+        child.expect_exact("Specify the name of the module")
+        TestConsole._expect_prompt(child)
+
+        TestConsole._close(child)
+
+    def test_interactive_script_with_error_keeps_console_open(self):
+        # -i executes the script and, when a line errors, reports it and 
leaves the console open
+        script = TestConsole._write_error_script()
+        try:
+            child = pexpect.spawn(TestConsole.gremlinsh + "-i " + script, 
dimensions=(50, 400), timeout=120)
+            child.expect_exact("Error in " + script)
+            TestConsole._expect_prompt(child)
+            # the console remains usable after the script error
+            child.sendline("1+1")
+            child.expect_exact("==>2")
+            TestConsole._expect_prompt(child)
+            TestConsole._close(child)
+        finally:
+            os.remove(script)
+
+    def test_execute_script_with_error_closes_console(self):
+        # -e executes the script and, on error, reports it and closes the 
console
+        script = TestConsole._write_error_script()
+        try:
+            child = pexpect.spawn(TestConsole.gremlinsh + "-e " + script, 
dimensions=(50, 400), timeout=120)
+            child.expect_exact("Error in " + script)
+            child.expect(pexpect.EOF)
+        finally:
+            os.remove(script)
+
+    @staticmethod
+    def _write_error_script():
+        # generate a throwaway Gremlin script that errors, rather than 
committing a fixture file
+        fd, path = tempfile.mkstemp(suffix=".script")
+        with os.fdopen(fd, "w") as f:
+            f.write("throw new RuntimeException(\'scripterror123\')\n")
+        return path
+
+    @staticmethod
+    def _setup_modern_graph(child):
+        # builds an in-memory TinkerGraph and an embedded traversal source, no 
server required
+        child.sendline("graph = 
org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory.createModern()")
+        child.expect_exact("==>tinkergraph[vertices:6 edges:6]")
+        TestConsole._expect_prompt(child)
+        child.sendline("g = traversal().withEmbedded(graph)")
+        child.expect_exact("==>graphtraversalsource[tinkergraph[vertices:6 
edges:6], standard]")
+        TestConsole._expect_prompt(child)
+
     @staticmethod
     def _expect_gremlin_header(child):
         # skip/read the Gremlin graphics
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
index ad0c87c00b..a9d2e68611 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java
@@ -21,6 +21,9 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
 import org.apache.tinkerpop.gremlin.process.traversal.GType;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
+import org.apache.tinkerpop.gremlin.process.traversal.Pick;
+import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.T;
 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
@@ -54,6 +57,7 @@ import static org.hamcrest.core.IsInstanceOf.instanceOf;
 import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo;
 import static org.hamcrest.number.OrderingComparison.lessThan;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -1124,4 +1128,65 @@ public class GeneralLiteralVisitorTest {
             assertEquals("", pdt.getValue());
         }
     }
+
+    /**
+     * Tests parsing and visiting of generic literal productions: generic 
literal expressions,
+     * terminated traversals, merge/pick tokens, traversal strategies and 
string varargs.
+     */
+    public static class GenericLiteralProductionTest {
+
+        @Test
+        public void shouldParseGenericLiteralExpr() {
+            final GremlinLexer lexer = new 
GremlinLexer(CharStreams.fromString("1,2,3"));
+            final GremlinParser parser = new GremlinParser(new 
CommonTokenStream(lexer));
+            final GremlinParser.GenericLiteralExprContext ctx = 
parser.genericLiteralExpr();
+            final Object result = new GenericLiteralVisitor(new 
GremlinAntlrToJava()).visitGenericLiteralExpr(ctx);
+            assertArrayEquals(new Object[]{1, 2, 3}, (Object[]) result);
+        }
+
+        @Test
+        public void shouldParseTerminatedTraversal() {
+            final GremlinLexer lexer = new 
GremlinLexer(CharStreams.fromString("g.inject(1).toList()"));
+            final GremlinParser parser = new GremlinParser(new 
CommonTokenStream(lexer));
+            final GremlinParser.TerminatedTraversalContext ctx = 
parser.terminatedTraversal();
+            final Object result = new GenericLiteralVisitor(new 
GremlinAntlrToJava()).visitTerminatedTraversal(ctx);
+            assertEquals(Arrays.asList(1), result);
+        }
+
+        @Test
+        public void shouldParseTraversalMerge() {
+            final GremlinLexer lexer = new 
GremlinLexer(CharStreams.fromString("Merge.onCreate"));
+            final GremlinParser parser = new GremlinParser(new 
CommonTokenStream(lexer));
+            final GremlinParser.TraversalMergeContext ctx = 
parser.traversalMerge();
+            final Object result = new GenericLiteralVisitor(new 
GremlinAntlrToJava()).visitTraversalMerge(ctx);
+            assertEquals(Merge.onCreate, result);
+        }
+
+        @Test
+        public void shouldParseTraversalPick() {
+            final GremlinLexer lexer = new 
GremlinLexer(CharStreams.fromString("Pick.any"));
+            final GremlinParser parser = new GremlinParser(new 
CommonTokenStream(lexer));
+            final GremlinParser.TraversalPickContext ctx = 
parser.traversalPick();
+            final Object result = new GenericLiteralVisitor(new 
GremlinAntlrToJava()).visitTraversalPick(ctx);
+            assertEquals(Pick.any, result);
+        }
+
+        @Test
+        public void shouldParseTraversalStrategy() {
+            final GremlinLexer lexer = new 
GremlinLexer(CharStreams.fromString("ReadOnlyStrategy"));
+            final GremlinParser parser = new GremlinParser(new 
CommonTokenStream(lexer));
+            final GremlinParser.TraversalStrategyContext ctx = 
parser.traversalStrategy();
+            final Object result = new GenericLiteralVisitor(new 
GremlinAntlrToJava()).visitTraversalStrategy(ctx);
+            assertEquals(ReadOnlyStrategy.instance(), result);
+        }
+
+        @Test
+        public void shouldParseStringNullableLiteralVarargs() {
+            final GremlinLexer lexer = new 
GremlinLexer(CharStreams.fromString("'a','b'"));
+            final GremlinParser parser = new GremlinParser(new 
CommonTokenStream(lexer));
+            final GremlinParser.StringNullableLiteralVarargsContext ctx = 
parser.stringNullableLiteralVarargs();
+            final String[] result = new GenericLiteralVisitor(new 
GremlinAntlrToJava()).parseStringVarargs(ctx);
+            assertArrayEquals(new String[]{"a", "b"}, result);
+        }
+    }
 }
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
index 11f299e7db..5a62f04c50 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitorTest.java
@@ -23,12 +23,14 @@ import org.antlr.v4.runtime.CommonTokenStream;
 import 
org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.PageRank;
 import 
org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.PeerPressure;
 import 
org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ShortestPath;
+import org.apache.tinkerpop.gremlin.process.traversal.GType;
+import org.apache.tinkerpop.gremlin.process.traversal.Merge;
 import org.apache.tinkerpop.gremlin.process.traversal.Operator;
 import org.apache.tinkerpop.gremlin.process.traversal.Order;
-import org.apache.tinkerpop.gremlin.process.traversal.P;
-import org.apache.tinkerpop.gremlin.process.traversal.Pop;
+import org.apache.tinkerpop.gremlin.process.traversal.P;import 
org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
 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.step.util.WithOptions;
 import org.apache.tinkerpop.gremlin.structure.Column;
@@ -75,6 +77,89 @@ public class TraversalMethodVisitorTest {
                 ((DefaultGraphTraversal) actual).asAdmin().getGremlinLang());
     }
 
+    @Test
+    public void shouldParseTraversalMethod_io_read() throws Exception {
+        compare(g.io("x").read(), eval("g.io('x').read()"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_io_write() throws Exception {
+        compare(g.io("x").write(), eval("g.io('x').write()"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_none_P() throws Exception {
+        compare(g.V().none(gt(2)), eval("g.V().none(gt(2))"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_choose_P_Traversal_Traversal() 
throws Exception {
+        final GraphTraversal expected = g.V();
+        compare(expected.choose(gt(1), out(), in()), eval("g.V().choose(gt(1), 
__.out(), __.in())"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_emit_P() throws Exception {
+        final GraphTraversal expected = g.V().repeat(out());
+        compare(expected.emit(gt(2)), 
eval("g.V().repeat(__.out()).emit(gt(2))"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_filter_P() throws Exception {
+        final GraphTraversal expected = g.V();
+        compare(expected.filter(gt(2)), eval("g.V().filter(gt(2))"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_until_P() throws Exception {
+        final GraphTraversal expected = g.V().repeat(out());
+        compare(expected.until(gt(2)), 
eval("g.V().repeat(__.out()).until(gt(2))"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_option_Merge_Map() throws Exception 
{
+        compare(g.mergeV(new LinkedHashMap<>()).option(Merge.onCreate, new 
LinkedHashMap<>()),
+                eval("g.mergeV([:]).option(Merge.onCreate, [:])"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_option_Merge_Traversal() throws 
Exception {
+        compare(g.mergeV(new LinkedHashMap<>()).option(Merge.onCreate, 
constant(new LinkedHashMap<>())),
+                eval("g.mergeV([:]).option(Merge.onCreate, 
__.constant([:]))"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_option_Merge_Map_Cardinality() 
throws Exception {
+        compare(g.mergeV(new LinkedHashMap<>()).option(Merge.onMatch, new 
LinkedHashMap<>(), Cardinality.single),
+                eval("g.mergeV([:]).option(Merge.onMatch, [:], 
Cardinality.single)"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_option_Predicate_Traversal() throws 
Exception {
+        compare(g.V().branch(values("age")).option(gt(1), out()),
+                eval("g.V().branch(__.values('age')).option(gt(1), 
__.out())"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_connectedComponent() throws 
Exception {
+        compare(g.V().connectedComponent(), 
eval("g.V().connectedComponent()"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_asBool() throws Exception {
+        compare(g.V().values("x").asBool(), 
eval("g.V().values('x').asBool()"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_asNumber_Empty() throws Exception {
+        compare(g.V().values("x").asNumber(), 
eval("g.V().values('x').asNumber()"));
+    }
+
+    @Test
+    public void shouldParseTraversalMethod_asNumber_traversalGType() throws 
Exception {
+        compare(g.V().values("x").asNumber(GType.INT), 
eval("g.V().values('x').asNumber(GType.int)"));
+    }
+
     @Test
     public void shouldParseChainedTraversal() throws Exception {
         // a random chain traversal.
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java
index 9418bbaab2..ee69502520 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
+import org.apache.tinkerpop.gremlin.process.traversal.GType;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.TextP;
 import org.junit.Assert;
@@ -95,6 +96,9 @@ public class TraversalPredicateVisitorTest {
                 {"TextP.regex('(?i)gfg')", TextP.regex("(?i)gfg")},
                 {"TextP.notRegex('^h')", TextP.notRegex("^h")},
                 {"TextP.regex('^h').negate()", TextP.regex("^h").negate()},
+                {"typeOf(GType.int)", P.typeOf(GType.INT)},
+                {"typeOf(int)", P.typeOf(GType.INT)},
+                {"P.typeOf(GType.string)", P.typeOf(GType.STRING)},
         });
     }
 
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnVisitorTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnVisitorTest.java
index e082c38e2f..5dcba78846 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnVisitorTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnVisitorTest.java
@@ -20,7 +20,9 @@ package org.apache.tinkerpop.gremlin.language.grammar;
 
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
 import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
 import org.junit.Before;
 import org.junit.Test;
@@ -89,6 +91,66 @@ public class TraversalSourceSpawnVisitorTest {
                 ((DefaultGraphTraversal) eval("g.match(\"MATCH (p:person {age: 
$age})-[:knows]->(f:person)\",[\"age\":29])")).asAdmin().getGremlinLang());
     }
 
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_addE() {
+        compare(g.addE("knows"), eval("g.addE('knows')"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_io() {
+        compare(g.io("data.xml"), eval("g.io('data.xml')"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_mergeV_Map() {
+        compare(g.mergeV(new HashMap<>()), eval("g.mergeV([:])"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_mergeV_Traversal() {
+        compare(g.mergeV(__.constant(new HashMap<>())), 
eval("g.mergeV(__.constant([:]))"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_mergeE_Map() {
+        compare(g.mergeE(new HashMap<>()), eval("g.mergeE([:])"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_mergeE_Traversal() {
+        compare(g.mergeE(__.constant(new HashMap<>())), 
eval("g.mergeE(__.constant([:]))"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_call_empty() {
+        compare(g.call(), eval("g.call()"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_call_string() {
+        compare(g.call("svc"), eval("g.call('svc')"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_call_string_map() {
+        compare(g.call("svc", new HashMap<>()), eval("g.call('svc', [:])"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_call_string_traversal() {
+        compare(g.call("svc", __.identity()), eval("g.call('svc', 
__.identity())"));
+    }
+
+    @Test
+    public void 
shouldParseTraversalSourceSpawnMethod_call_string_map_traversal() {
+        compare(g.call("svc", new HashMap<>(), __.identity()), 
eval("g.call('svc', [:], __.identity())"));
+    }
+
+    @Test
+    public void shouldParseTraversalSourceSpawnMethod_union() {
+        compare(g.union((Traversal) __.V(), (Traversal) __.E()), 
eval("g.union(__.V(), __.E())"));
+    }
+
     private Object eval(final String query) {
         final GremlinLexer lexer = new 
GremlinLexer(CharStreams.fromString(query));
         final GremlinParser parser = new GremlinParser(new 
CommonTokenStream(lexer));
diff --git 
a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/BasicGrammarTest.java
 
b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/BasicGrammarTest.java
index 14ac12108e..41d71a8a1d 100644
--- 
a/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/BasicGrammarTest.java
+++ 
b/gremlin-language/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/BasicGrammarTest.java
@@ -28,4 +28,162 @@ public class BasicGrammarTest extends AbstractGrammarTest {
     public void shouldParseV() {
         parse("g.V()", ParserRule.QUERY_LIST);
     }
+
+    @Test
+    public void shouldParseHasLabelP() {
+        parse("g.V().hasLabel(eq(\"person\"))", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseUntilPredicate() {
+        parse("g.V().repeat(__.out()).until(gt(2))", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseEmitPredicate() {
+        parse("g.V().repeat(__.out()).emit(gt(2))", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseFilterPredicate() {
+        parse("g.V().values(\"age\").filter(gt(2))", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseOptionTraversal() {
+        parse("g.V().branch(__.values(\"name\")).option(__.identity())", 
ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseBarrierInt() {
+        parse("g.V().barrier(2500)", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseProfileString() {
+        parse("g.V().profile(\"gremlin.metrics\")", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseToV() {
+        parse("g.E().toV(Direction.OUT)", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseMatchStringMap() {
+        parse("g.V().match(\"gremlin\", [:])", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseEmptyQuery() {
+        parse("\"\"", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseTerminatedTraversal() {
+        parse("g.inject(g.V().count().next())", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParsePdtLiteral() {
+        parse("g.inject(PDT(\"x\", \"y\"))", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseWithOptionsAll() {
+        parse("g.V().valueMap().with(WithOptions.tokens, WithOptions.all)", 
ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseWithOptionsKeys() {
+        parse("g.V().valueMap().with(WithOptions.tokens, WithOptions.keys)", 
ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseWithOptionsNone() {
+        parse("g.V().valueMap().with(WithOptions.tokens, WithOptions.none)", 
ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParsePageRankTimes() {
+        parse("g.V().pageRank().with(PageRank.times, 10)", 
ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParsePeerPressureEdges() {
+        parse("g.V().peerPressure().with(PeerPressure.edges, __.outE())", 
ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParsePeerPressureTimes() {
+        parse("g.V().peerPressure().with(PeerPressure.times, 10)", 
ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseShortestPathMaxDistance() {
+        parse("g.V().shortestPath().with(ShortestPath.maxDistance, 5)", 
ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseConnectedComponentComponent() {
+        parse("g.V().connectedComponent().with(ConnectedComponent.component, 
\"cc\")", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseTraversalSourceWithString() {
+        parse("g.with(\"x\")", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseTraversalSourceWithStringObject() {
+        parse("g.with(\"x\", 123)", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseTypeOfBareGTypeForms() {
+        final String[] bareGTypes = new String[]{
+                "bigDecimal", "BIGDECIMAL", "bigInt", "BIGINT", "binary", 
"BINARY", "boolean", "BOOLEAN",
+                "byte", "BYTE", "char", "CHAR", "datetime", "DATETIME", 
"double", "DOUBLE", "duration", "DURATION",
+                "edge", "EDGE", "float", "FLOAT", "graph", "GRAPH", "int", 
"INT", "list", "LIST", "long", "LONG",
+                "map", "MAP", "null", "NULL", "number", "NUMBER", "path", 
"PATH", "property", "PROPERTY",
+                "set", "SET", "short", "SHORT", "string", "STRING", "tree", 
"TREE", "UUID", "uuid",
+                "vertex", "VERTEX", "vproperty", "VPROPERTY"
+        };
+        for (final String t : bareGTypes) {
+            parse(String.format("g.V().is(typeOf(%s))", t), 
ParserRule.QUERY_LIST);
+        }
+    }
+
+    @Test
+    public void shouldParseTransactionBegin() {
+        parse("g.tx().begin()", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseTransactionCommit() {
+        parse("g.tx().commit()", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseTransactionRollback() {
+        parse("g.tx().rollback()", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseQueryToString() {
+        parse("g.V().toString()", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseWithinWithoutForms() {
+        parse("g.V().is(within())", ParserRule.QUERY_LIST);
+        parse("g.V().is(without())", ParserRule.QUERY_LIST);
+        parse("g.V().is(within(1, 2, 3))", ParserRule.QUERY_LIST);
+        parse("g.V().is(without(1, 2, 3))", ParserRule.QUERY_LIST);
+    }
+
+    @Test
+    public void shouldParseTypeOfString() {
+        parse("g.V().is(typeOf(\"int\"))", ParserRule.QUERY_LIST);
+    }
 }

Reply via email to