Skip to content

Commit 1b66ec6

Browse files
committed
try to fix windows tests
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 7a02ead commit 1b66ec6

File tree

4 files changed

+56
-6
lines changed

4 files changed

+56
-6
lines changed

src/basic_memory/api/app.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""FastAPI application for basic-memory knowledge graph API."""
22

33
import asyncio
4+
import os
45
from contextlib import asynccontextmanager
56

67
from fastapi import FastAPI, HTTPException
@@ -54,12 +55,23 @@ async def lifespan(app: FastAPI): # pragma: no cover
5455
logger.info("Database connections cached in app state")
5556

5657
# Start file sync if enabled
57-
if app_config.sync_changes:
58+
is_test_env = (
59+
app_config.env == "test"
60+
or os.getenv("BASIC_MEMORY_ENV", "").lower() == "test"
61+
or os.getenv("PYTEST_CURRENT_TEST") is not None
62+
)
63+
if app_config.sync_changes and not is_test_env:
5864
logger.info(f"Sync changes enabled: {app_config.sync_changes}")
5965
# start file sync task in background
60-
app.state.sync_task = asyncio.create_task(initialize_file_sync(app_config))
66+
async def _file_sync_runner() -> None:
67+
await initialize_file_sync(app_config)
68+
69+
app.state.sync_task = asyncio.create_task(_file_sync_runner())
6170
else:
62-
logger.info("Sync changes disabled. Skipping file sync service.")
71+
if is_test_env:
72+
logger.info("Test environment detected. Skipping file sync service.")
73+
else:
74+
logger.info("Sync changes disabled. Skipping file sync service.")
6375
app.state.sync_task = None
6476

6577
# proceed with startup

src/basic_memory/db.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import os
3+
import sys
34
from contextlib import asynccontextmanager
45
from enum import Enum, auto
56
from pathlib import Path
@@ -23,6 +24,20 @@
2324
from basic_memory.repository.postgres_search_repository import PostgresSearchRepository
2425
from basic_memory.repository.sqlite_search_repository import SQLiteSearchRepository
2526

27+
# -----------------------------------------------------------------------------
28+
# Windows event loop policy
29+
# -----------------------------------------------------------------------------
30+
# On Windows, the default ProactorEventLoop has known rough edges with aiosqlite
31+
# during shutdown/teardown (threads posting results to a loop that's closing),
32+
# which can manifest as:
33+
# - "RuntimeError: Event loop is closed"
34+
# - "IndexError: pop from an empty deque"
35+
#
36+
# We don't rely on Proactor-only features (like subprocess pipes), so use the
37+
# more stable SelectorEventLoopPolicy everywhere on Windows.
38+
if sys.platform == "win32": # pragma: no cover
39+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
40+
2641
# Module level state
2742
_engine: Optional[AsyncEngine] = None
2843
_session_maker: Optional[async_sessionmaker[AsyncSession]] = None

src/basic_memory/mcp/server.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import asyncio
6+
import os
67
from contextlib import asynccontextmanager
78

89
from fastmcp import FastMCP
@@ -30,9 +31,19 @@ async def lifespan(app: FastMCP):
3031

3132
# Start file sync as background task (if enabled and not in cloud mode)
3233
sync_task = None
33-
if app_config.sync_changes and not app_config.cloud_mode_enabled:
34+
is_test_env = (
35+
app_config.env == "test"
36+
or os.getenv("BASIC_MEMORY_ENV", "").lower() == "test"
37+
or os.getenv("PYTEST_CURRENT_TEST") is not None
38+
)
39+
if is_test_env:
40+
logger.info("Test environment detected - skipping local file sync")
41+
elif app_config.sync_changes and not app_config.cloud_mode_enabled:
3442
logger.info("Starting file sync in background")
35-
sync_task = asyncio.create_task(initialize_file_sync(app_config))
43+
async def _file_sync_runner() -> None:
44+
await initialize_file_sync(app_config)
45+
46+
sync_task = asyncio.create_task(_file_sync_runner())
3647
elif app_config.cloud_mode_enabled:
3748
logger.info("Cloud mode enabled - skipping local file sync")
3849
else:

src/basic_memory/services/initialization.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ async def reconcile_projects_with_config(app_config: BasicMemoryConfig):
7171

7272
async def initialize_file_sync(
7373
app_config: BasicMemoryConfig,
74-
):
74+
) -> None:
7575
"""Initialize file synchronization services. This function starts the watch service and does not return
7676
7777
Args:
@@ -80,6 +80,18 @@ async def initialize_file_sync(
8080
Returns:
8181
The watch service task that's monitoring file changes
8282
"""
83+
# Never start file watching during tests. Even "background" watchers add tasks/threads
84+
# and can interact badly with strict asyncio teardown (especially on Windows/aiosqlite).
85+
#
86+
# Note: Some tests patch ConfigManager.config with a minimal BasicMemoryConfig that
87+
# may not set env="test". So also detect pytest via env vars.
88+
if (
89+
app_config.env == "test"
90+
or os.getenv("BASIC_MEMORY_ENV", "").lower() == "test"
91+
or os.getenv("PYTEST_CURRENT_TEST") is not None
92+
):
93+
logger.info("Test environment detected - skipping file sync initialization")
94+
return None
8395

8496
# delay import
8597
from basic_memory.sync import WatchService

0 commit comments

Comments
 (0)