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)

Reply via email to