"""Test that CLI tool commands exit cleanly without hanging. This test ensures that CLI commands properly clean up database connections on exit, preventing process hangs. See GitHub issue for details. The issue occurs when: 1. ensure_initialization() calls asyncio.run(initialize_app()) 2. initialize_app() creates global database connections via db.get_or_create_db() 3. When asyncio.run() completes, the event loop closes 4. But the global database engine holds async connections that prevent clean exit 5. Process hangs indefinitely The fix ensures db.shutdown_db() is called before asyncio.run() returns. """ import os import platform import subprocess import sys import pytest # Windows has different process cleanup behavior that makes these tests unreliable IS_WINDOWS = platform.system() == "Windows" SUBPROCESS_TIMEOUT = 10.0 skip_on_windows = pytest.mark.skipif( IS_WINDOWS, reason="Subprocess cleanup tests unreliable on Windows CI" ) @skip_on_windows class TestCLIToolExit: """Test that CLI tool commands exit cleanly.""" @pytest.mark.parametrize( "command", [ ["tool", "--help"], ["tool", "write-note", "--help"], ["tool", "read-note", "--help"], ["tool", "search-notes", "--help"], ["tool", "build-context", "--help"], ], ) def test_cli_command_exits_cleanly(self, command: list[str]): """Test that CLI commands exit without hanging. Each command should complete within the timeout without requiring manual termination (Ctrl+C). """ full_command = [sys.executable, "-m", "basic_memory.cli.main"] + command try: result = subprocess.run( full_command, capture_output=True, text=True, timeout=SUBPROCESS_TIMEOUT, ) # Command should exit with code 0 for --help assert result.returncode == 0, f"Command failed: {result.stderr}" except subprocess.TimeoutExpired: pytest.fail( f"Command '{' '.join(command)}' hung and did not exit within timeout. " "This indicates database connections are not being cleaned up properly." ) def test_ensure_initialization_exits_cleanly(self, tmp_path): """Test that ensure_initialization doesn't cause process hang. This test directly tests the initialization function that's called by CLI commands, ensuring it cleans up database connections properly. """ code = """ import asyncio from basic_memory.config import ConfigManager from basic_memory.services.initialization import ensure_initialization app_config = ConfigManager().config ensure_initialization(app_config) print("OK") """ try: # Ensure the subprocess uses an isolated home directory so ConfigManager doesn't # touch the real user profile/AppData (which can be slow/flaky on CI Windows). env = dict(os.environ) bm_home = tmp_path / "basic-memory-home" env["BASIC_MEMORY_HOME"] = str(bm_home) env["HOME"] = str(tmp_path) env["USERPROFILE"] = str(tmp_path) result = subprocess.run( [sys.executable, "-c", code], capture_output=True, text=True, timeout=SUBPROCESS_TIMEOUT, env=env, ) assert "OK" in result.stdout, f"Unexpected output: {result.stdout}" except subprocess.TimeoutExpired: pytest.fail( "ensure_initialization() caused process hang. " "Database connections are not being cleaned up before event loop closes." )