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);
+ }
}