|
| 1 | +"""Test that CLI tool commands exit cleanly without hanging. |
| 2 | +
|
| 3 | +This test ensures that CLI commands properly clean up database connections |
| 4 | +on exit, preventing process hangs. See GitHub issue for details. |
| 5 | +
|
| 6 | +The issue occurs when: |
| 7 | +1. ensure_initialization() calls asyncio.run(initialize_app()) |
| 8 | +2. initialize_app() creates global database connections via db.get_or_create_db() |
| 9 | +3. When asyncio.run() completes, the event loop closes |
| 10 | +4. But the global database engine holds async connections that prevent clean exit |
| 11 | +5. Process hangs indefinitely |
| 12 | +
|
| 13 | +The fix ensures db.shutdown_db() is called before asyncio.run() returns. |
| 14 | +""" |
| 15 | + |
| 16 | +import subprocess |
| 17 | +import sys |
| 18 | + |
| 19 | +import pytest |
| 20 | + |
| 21 | + |
| 22 | +class TestCLIToolExit: |
| 23 | + """Test that CLI tool commands exit cleanly.""" |
| 24 | + |
| 25 | + @pytest.mark.parametrize( |
| 26 | + "command", |
| 27 | + [ |
| 28 | + ["tool", "--help"], |
| 29 | + ["tool", "write-note", "--help"], |
| 30 | + ["tool", "read-note", "--help"], |
| 31 | + ["tool", "search-notes", "--help"], |
| 32 | + ["tool", "build-context", "--help"], |
| 33 | + ["status"], # Also affected by same issue |
| 34 | + ], |
| 35 | + ) |
| 36 | + def test_cli_command_exits_cleanly(self, command: list[str]): |
| 37 | + """Test that CLI commands exit without hanging. |
| 38 | +
|
| 39 | + Each command should complete within the timeout without requiring |
| 40 | + manual termination (Ctrl+C). |
| 41 | + """ |
| 42 | + full_command = [sys.executable, "-m", "basic_memory.cli.main"] + command |
| 43 | + |
| 44 | + try: |
| 45 | + result = subprocess.run( |
| 46 | + full_command, |
| 47 | + capture_output=True, |
| 48 | + text=True, |
| 49 | + timeout=10.0, # 10 second timeout - commands should complete in ~2s |
| 50 | + ) |
| 51 | + # Command should exit with code 0 for --help |
| 52 | + assert result.returncode == 0, f"Command failed: {result.stderr}" |
| 53 | + except subprocess.TimeoutExpired: |
| 54 | + pytest.fail( |
| 55 | + f"Command '{' '.join(command)}' hung and did not exit within timeout. " |
| 56 | + "This indicates database connections are not being cleaned up properly." |
| 57 | + ) |
| 58 | + |
| 59 | + def test_ensure_initialization_exits_cleanly(self): |
| 60 | + """Test that ensure_initialization doesn't cause process hang. |
| 61 | +
|
| 62 | + This test directly tests the initialization function that's called |
| 63 | + by CLI commands, ensuring it cleans up database connections properly. |
| 64 | + """ |
| 65 | + code = """ |
| 66 | +import asyncio |
| 67 | +from basic_memory.config import ConfigManager |
| 68 | +from basic_memory.services.initialization import ensure_initialization |
| 69 | +
|
| 70 | +app_config = ConfigManager().config |
| 71 | +ensure_initialization(app_config) |
| 72 | +print("OK") |
| 73 | +""" |
| 74 | + try: |
| 75 | + result = subprocess.run( |
| 76 | + [sys.executable, "-c", code], |
| 77 | + capture_output=True, |
| 78 | + text=True, |
| 79 | + timeout=10.0, |
| 80 | + ) |
| 81 | + assert "OK" in result.stdout, f"Unexpected output: {result.stdout}" |
| 82 | + except subprocess.TimeoutExpired: |
| 83 | + pytest.fail( |
| 84 | + "ensure_initialization() caused process hang. " |
| 85 | + "Database connections are not being cleaned up before event loop closes." |
| 86 | + ) |
0 commit comments