This is an automated email from the ASF dual-hosted git repository.
xiazcy 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 39fddba47b CTR fix hanging gremlin-console integration tests
39fddba47b is described below
commit 39fddba47be9f05205d60731c6d49c5d3309b8f2
Author: Yang Xia <[email protected]>
AuthorDate: Thu Oct 22 20:00:58 2026 -0700
CTR fix hanging gremlin-console integration tests
createModern() now renders as tinkermemorygraph[...], so the old assertions
never matched and each affected test waited out its 120s timeout. Assert the
new output, cap expect() at 60s, isolate each console's user.home so :set no
longer leaks between tests, and cover both mixed -i/--interactive orderings.
Assisted-by: Kiro:claude-opus-5.5
---
.../src/test/python/tests/test_console.py | 135 +++++++++++++--------
1 file changed, 87 insertions(+), 48 deletions(-)
diff --git a/gremlin-console/src/test/python/tests/test_console.py
b/gremlin-console/src/test/python/tests/test_console.py
index f6c03a1d63..dafc674ad1 100644
--- a/gremlin-console/src/test/python/tests/test_console.py
+++ b/gremlin-console/src/test/python/tests/test_console.py
@@ -27,8 +27,16 @@ class TestConsole(object):
# the base command must pass -C because if colors are enabled pexpect gets
garbled input and tests won't pass
gremlinsh = "bash gremlin-console/bin/gremlin.sh -C "
+ # How long any expect() waits for output before failing. An output
mismatch costs exactly this
+ # much, so it stays bounded: at 120s a handful of stale assertions was
enough to push the
+ # gremlin-console CI job past its timeout. 60s still leaves ample headroom
for a cold JVM start.
+ EXPECT_TIMEOUT = 60
+
+ # Parent of the per-console user.home directories (see _spawn); removed
when the test process exits.
+ _homes = tempfile.TemporaryDirectory(prefix="gremlin-console-test-")
+
def test_basic_console_operations(self):
- child = pexpect.spawn(TestConsole.gremlinsh)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
TestConsole._send(child, "1-1")
child.expect("==>0\r\n")
@@ -36,7 +44,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_just_dash_i(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "-i x.script")
+ child = TestConsole._spawn("-i x.script")
TestConsole._expect_gremlin_header(child)
TestConsole._send(child, "x")
child.expect("==>2\r\n")
@@ -44,7 +52,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_just_dash_dash_interactive(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "--interactive x.script")
+ child = TestConsole._spawn("--interactive x.script")
TestConsole._expect_gremlin_header(child)
TestConsole._send(child, "x")
child.expect("==>2\r\n")
@@ -52,7 +60,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_dash_i_with_args(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "-i y.script 1 2 3")
+ child = TestConsole._spawn("-i y.script 1 2 3")
TestConsole._expect_gremlin_header(child)
TestConsole._send(child, "y")
child.expect("==>6\r\n")
@@ -60,7 +68,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_dash_dash_interactive_with_args(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "--interactive y.script
1 2 3")
+ child = TestConsole._spawn("--interactive y.script 1 2 3")
TestConsole._expect_gremlin_header(child)
TestConsole._send(child, "y")
child.expect("==>6\r\n")
@@ -68,7 +76,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_dash_dash_interactive_with_args_and_equals(self):
- child = pexpect.spawn(TestConsole.gremlinsh +
"--interactive=\"y.script 1 2 3\"")
+ child = TestConsole._spawn("--interactive=\"y.script 1 2 3\"")
TestConsole._expect_gremlin_header(child)
TestConsole._send(child, "y")
child.expect("==>6\r\n")
@@ -76,7 +84,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_dash_i_multiple_scripts(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "-i y.script 1 2 3 -i
x.script -i \"z.script x -i = --color -D\"")
+ child = TestConsole._spawn("-i y.script 1 2 3 -i x.script -i
\"z.script x -i = --color -D\"")
TestConsole._expect_gremlin_header(child)
TestConsole._send(child, "y")
child.expect("==>6\r\n")
@@ -90,7 +98,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_dash_dash_interactive_multiple_scripts(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "--interactive y.script
1 2 3 --interactive x.script -i \"z.script x -i = --color -D\"")
+ child = TestConsole._spawn("--interactive y.script 1 2 3 --interactive
x.script --interactive \"z.script x -i = --color -D\"")
TestConsole._expect_gremlin_header(child)
TestConsole._send(child, "y")
child.expect("==>6\r\n")
@@ -104,7 +112,21 @@ class TestConsole(object):
TestConsole._close(child)
def test_mixed_interactive_long_short_opts_with_multiple_scripts(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "--interactive y.script
1 2 3 --interactive x.script -i \"z.script x -i = --color -D\"")
+ child = TestConsole._spawn("--interactive y.script 1 2 3 -i x.script
--interactive \"z.script x -i = --color -D\"")
+ TestConsole._expect_gremlin_header(child)
+ TestConsole._send(child, "y")
+ child.expect("==>6\r\n")
+ TestConsole._expect_prompt(child)
+ TestConsole._send(child, "x")
+ child.expect("==>2\r\n")
+ TestConsole._expect_prompt(child)
+ TestConsole._send(child, "z")
+ child.expect("==>argument=\\[x, -i, =, --color, -D\\]\r\n")
+ TestConsole._expect_prompt(child)
+ TestConsole._close(child)
+
+ def
test_mixed_interactive_long_opts_then_short_opt_with_multiple_scripts(self):
+ child = TestConsole._spawn("--interactive y.script 1 2 3 --interactive
x.script -i \"z.script x -i = --color -D\"")
TestConsole._expect_gremlin_header(child)
TestConsole._send(child, "y")
child.expect("==>6\r\n")
@@ -118,65 +140,63 @@ class TestConsole(object):
TestConsole._close(child)
def test_just_dash_e(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "-e x-printed.script")
+ child = TestConsole._spawn("-e x-printed.script")
child.expect("2\r\n")
TestConsole._close(child)
def test_just_dash_e_file_not_found(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "-e=x-printed.script")
+ child = TestConsole._spawn("-e=x-printed.script")
child.expect("Gremlin file not found at
\\[=x-printed.script\\]\\.\r\n")
child.expect(pexpect.EOF)
def test_just_dash_dash_execute(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "--execute
x-printed.script")
+ child = TestConsole._spawn("--execute x-printed.script")
child.expect("2\r\n")
TestConsole._close(child)
def test_dash_e_with_args(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "-e y-printed.script 1 2
3")
+ child = TestConsole._spawn("-e y-printed.script 1 2 3")
child.expect("6\r\n")
TestConsole._close(child)
def test_dash_dash_execute_with_args(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "--execute
y-printed.script 1 2 3")
+ child = TestConsole._spawn("--execute y-printed.script 1 2 3")
child.expect("6\r\n")
TestConsole._close(child)
def test_dash_e_multiple_scripts(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "-e y-printed.script 1 2
3 -e x-printed.script -e \"z-printed.script x -e = --color -D\"")
+ child = TestConsole._spawn("-e y-printed.script 1 2 3 -e
x-printed.script -e \"z-printed.script x -e = --color -D\"")
child.expect("6\r\n")
child.expect("2\r\n")
child.expect("argument=\\[x, -e, =, --color, -D\\]\r\n")
TestConsole._close(child)
def test_dash_dash_execute_multiple_scripts(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "--execute
y-printed.script 1 2 3 --execute x-printed.script --execute \"z-printed.script
x -e = --color -D\"")
+ child = TestConsole._spawn("--execute y-printed.script 1 2 3 --execute
x-printed.script --execute \"z-printed.script x -e = --color -D\"")
child.expect("6\r\n")
child.expect("2\r\n")
child.expect("argument=\\[x, -e, =, --color, -D\\]\r\n")
TestConsole._close(child)
def test_mixed_execute_long_short_opts_with_multiple_scripts(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "--execute
y-printed.script 1 2 3 -e x-printed.script --execute \"z-printed.script x -e =
--color -D\"")
+ child = TestConsole._spawn("--execute y-printed.script 1 2 3 -e
x-printed.script --execute \"z-printed.script x -e = --color -D\"")
child.expect("6\r\n")
child.expect("2\r\n")
child.expect("argument=\\[x, -e, =, --color, -D\\]\r\n")
TestConsole._close(child)
def test_no_mix_dash_i_and_dash_e(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "-i y.script 1 2 3 -i
x.script -e \"z.script x -i --color -D\"")
+ child = TestConsole._spawn("-i y.script 1 2 3 -i x.script -e
\"z.script x -i --color -D\"")
child.expect("-i and -e options are mutually exclusive - provide one
or the other")
child.expect(pexpect.EOF)
def test_debug_logging(self):
- child = pexpect.spawn(TestConsole.gremlinsh + "-l DEBUG --execute
y-printed.script 1 2 3")
+ child = TestConsole._spawn("-l DEBUG --execute y-printed.script 1 2 3")
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)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
TestConsole._setup_modern_graph(child)
@@ -208,7 +228,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_error_handling_runtime_exception(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# a runtime exception should be reported and then the interactive
@@ -222,7 +242,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_error_handling_missing_property(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
child.sendline("undefinedVariable123")
@@ -234,7 +254,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_command_help(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
child.sendline(":help")
@@ -246,7 +266,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_command_clear(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# :clear resets the buffer and returns to the prompt without error
@@ -260,7 +280,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_set_max_iteration_limits_results(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
child.sendline(":set max-iteration 5")
@@ -275,7 +295,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_set_with_too_many_arguments_errors(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# :set accepts at most <name> [<value>]; three args is too many and is
rejected
@@ -287,7 +307,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_plugin_list(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
child.sendline(":plugin list")
@@ -297,7 +317,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_multiline_input(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# an incomplete expression continues on the next line; completing it
@@ -312,7 +332,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_error_handling_display_stack_trace_when_answering_yes(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# answering 'y' to the stack trace prompt drives the branch that
prints the full trace
@@ -327,7 +347,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_error_handling_failure_step(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
TestConsole._setup_modern_graph(child)
@@ -341,7 +361,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_result_rendering_path(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
TestConsole._setup_modern_graph(child)
@@ -353,7 +373,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_result_rendering_edge(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
TestConsole._setup_modern_graph(child)
@@ -365,7 +385,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_result_rendering_map_with_tokens(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
TestConsole._setup_modern_graph(child)
@@ -378,7 +398,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_result_rendering_explain(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
TestConsole._setup_modern_graph(child)
@@ -390,7 +410,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_result_rendering_object_array(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# an Object[] is iterated one element per line through the array
branch of the result handler
@@ -403,7 +423,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_result_rendering_nested_lists(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# each element of the outer list is itself an Iterable, exercising the
recursive colorizer
@@ -415,7 +435,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_result_rendering_large_number_and_boolean(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# large numbers are rendered through the Number branch
@@ -431,7 +451,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_plugin_use_activates_available_plugin(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# activating an already available plugin exercises the plugin
activation closures without any network
@@ -442,7 +462,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_plugin_use_unknown_plugin(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
child.sendline(":plugin use does.not.exist")
@@ -452,7 +472,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_uninstall_absent_module(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
# uninstalling a module that is not present reports a not-found
message (no network access)
@@ -463,7 +483,7 @@ class TestConsole(object):
TestConsole._close(child)
def test_uninstall_without_argument(self):
- child = pexpect.spawn(TestConsole.gremlinsh, dimensions=(50, 400),
timeout=120)
+ child = TestConsole._spawn()
TestConsole._expect_gremlin_header(child)
child.sendline(":uninstall")
@@ -476,7 +496,7 @@ class TestConsole(object):
# -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 = TestConsole._spawn("-i " + script)
child.expect_exact("Error in " + script)
TestConsole._expect_prompt(child)
# the console remains usable after the script error
@@ -491,7 +511,7 @@ class TestConsole(object):
# -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 = TestConsole._spawn("-e " + script)
child.expect_exact("Error in " + script)
child.expect(pexpect.EOF)
finally:
@@ -505,14 +525,27 @@ class TestConsole(object):
f.write("throw new RuntimeException(\'scripterror123\')\n")
return path
+ @staticmethod
+ def _spawn(args=""):
+ # Every console gets its own user.home so the state it persists there
(java.util.prefs, which
+ # backs :set, and the history file) can't leak into later consoles -
e.g. a :set max-iteration
+ # would otherwise truncate the results of every test that runs after
it. The JVM ignores the
+ # HOME environment variable for user.home, so it is passed as a system
property instead.
+ # dimensions=(50, 400): the wide terminal stops the pty from wrapping
long input lines, which
+ # would otherwise garble pexpect echo/output matching.
+ home = tempfile.mkdtemp(dir=TestConsole._homes.name)
+ java_options = (os.environ.get("JAVA_OPTIONS", "") + " -Duser.home=" +
home).strip()
+ return pexpect.spawn(TestConsole.gremlinsh + args, dimensions=(50,
400), timeout=TestConsole.EXPECT_TIMEOUT,
+ env=dict(os.environ, JAVA_OPTIONS=java_options))
+
@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]")
+ child.expect_exact("==>tinkermemorygraph[vertices:6 edges:6]")
TestConsole._expect_prompt(child)
child.sendline("g = traversal().withEmbedded(graph)")
- child.expect_exact("==>graphtraversalsource[tinkergraph[vertices:6
edges:6], standard]")
+
child.expect_exact("==>graphtraversalsource[tinkermemorygraph[vertices:6
edges:6], standard]")
TestConsole._expect_prompt(child)
@staticmethod
@@ -535,4 +568,10 @@ class TestConsole(object):
@staticmethod
def _close(child):
- child.sendline(":x")
+ # Wait for the console to exit so a console that ignores :x fails the
test, and force the pty
+ # closed either way so no console process outlives its test.
+ try:
+ child.sendline(":x")
+ child.expect(pexpect.EOF, timeout=10)
+ finally:
+ child.close(force=True)