Skip to content

Commit 5376947

Browse files
Ki-SekifridayL
andauthored
ci: Include macOS and Windows in CI pipeline (#22)
* ci: include mac and win * fix: use pathlib for cross-platform path handling in cube creation test * fix: improve file path handling in test_dump using os.path.join * fix: use a small hf model instead * fix: add LLMFactory mock to test MOS initialization with invalid user * fix: remove warning about platform support * fix:db close in windows for sqlitedb * fix: fix ci * fix: delete db after close and get permission * ci: explicitly checks macos-14 and macos-15 * docs: add warning about macOS compatibility --------- Co-authored-by: fridayL <[email protected]>
1 parent 409cab4 commit 5376947

File tree

6 files changed

+66
-21
lines changed

6 files changed

+66
-21
lines changed

.github/workflows/python-tests.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ jobs:
2525
matrix:
2626
os:
2727
- "ubuntu-latest"
28+
- "windows-latest"
29+
- "macos-14"
30+
- "macos-15"
31+
# Ref: https://docs.github.com/en/actions/how-tos/writing-workflows/choosing-where-your-workflow-runs/choosing-the-runner-for-a-job
2832
python-version:
2933
- "3.10"
3034
- "3.11"
@@ -51,4 +55,4 @@ jobs:
5155
poetry run ruff format --check
5256
- name: Test with pytest
5357
run: |
54-
PYTHONPATH=src poetry run pytest tests -vv
58+
poetry run pytest tests -vv

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,11 @@ For more detailed examples, please check out the [`examples`](./examples) direct
151151
## 📦 Installation
152152

153153
> [!WARNING]
154-
> Currently, MemOS primarily supports Linux platforms. You may encounter issues on Windows and macOS temporarily.
154+
> MemOS is compatible with Linux, Windows, and macOS.
155+
>
156+
> However, if you're using macOS, please note that there may be dependency issues that are difficult to resolve.
157+
>
158+
> For example, compatibility with macOS 13 Ventura is currently challenging.
155159
156160
### Install via pip
157161

src/memos/mem_user/user_manager.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,3 +476,13 @@ def delete_cube(self, cube_id: str) -> bool:
476476
return False
477477
finally:
478478
session.close()
479+
480+
def close(self) -> None:
481+
"""Close the database engine and dispose of all connections.
482+
483+
This method should be called when the UserManager is no longer needed
484+
to ensure proper cleanup of database connections.
485+
"""
486+
if hasattr(self, "engine"):
487+
self.engine.dispose()
488+
logger.info("UserManager database connections closed")

tests/mem_os/test_memos_core.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def mock_config():
2222
"chat_model": {
2323
"backend": "huggingface",
2424
"config": {
25-
"model_name_or_path": "Qwen/Qwen3-1.7B",
25+
"model_name_or_path": "hf-internal-testing/tiny-random-gpt2",
2626
"temperature": 0.1,
2727
"remove_think_prefix": True,
2828
"max_tokens": 4096,
@@ -188,8 +188,10 @@ def test_mos_init_success(
188188
mock_user_manager.validate_user.assert_called_once_with("test_user")
189189

190190
@patch("memos.mem_os.core.UserManager")
191-
def test_mos_init_invalid_user(self, mock_user_manager_class, mock_config):
191+
@patch("memos.mem_os.core.LLMFactory")
192+
def test_mos_init_invalid_user(self, mock_llm_factory, mock_user_manager_class, mock_config):
192193
"""Test MOS initialization with invalid user."""
194+
mock_llm_factory.from_config.return_value = MagicMock()
193195
mock_user_manager = MagicMock()
194196
mock_user_manager.validate_user.return_value = False
195197
mock_user_manager_class.return_value = mock_user_manager

tests/mem_user/test_mem_user.py

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,22 @@ def temp_db(self):
2727
temp_dir = tempfile.mkdtemp()
2828
db_path = os.path.join(temp_dir, "test_memos.db")
2929
yield db_path
30-
# Cleanup
31-
if os.path.exists(db_path):
32-
os.remove(db_path)
33-
os.rmdir(temp_dir)
30+
# Cleanup - note: file cleanup is handled by user_manager fixture
31+
try:
32+
if os.path.exists(db_path):
33+
os.remove(db_path)
34+
os.rmdir(temp_dir)
35+
except (OSError, PermissionError):
36+
# On Windows, files might still be locked, ignore cleanup errors
37+
pass
3438

3539
@pytest.fixture
3640
def user_manager(self, temp_db):
3741
"""Create UserManager instance with temporary database."""
38-
return UserManager(db_path=temp_db)
42+
manager = UserManager(db_path=temp_db)
43+
yield manager
44+
# Ensure database connections are closed
45+
manager.close()
3946

4047
def test_initialization(self, temp_db):
4148
"""Test UserManager initialization."""
@@ -63,18 +70,27 @@ class MockSettings:
6370
# Replace the settings import
6471
monkeypatch.setattr("memos.mem_user.user_manager.settings", MockSettings())
6572

73+
manager = None
6674
try:
6775
manager = UserManager()
6876
expected_path = mock_memos_dir / "memos_users.db"
6977
assert manager.db_path == str(expected_path)
7078
assert os.path.exists(expected_path)
7179
finally:
80+
# Close database connections first
81+
if manager:
82+
manager.close()
83+
7284
# Cleanup
73-
expected_path = mock_memos_dir / "memos_users.db"
74-
if os.path.exists(expected_path):
75-
os.remove(expected_path)
76-
if os.path.exists(temp_dir):
77-
os.rmdir(temp_dir)
85+
try:
86+
expected_path = mock_memos_dir / "memos_users.db"
87+
if os.path.exists(expected_path):
88+
os.remove(expected_path)
89+
if os.path.exists(temp_dir):
90+
os.rmdir(temp_dir)
91+
except (OSError, PermissionError):
92+
# On Windows, files might still be locked, ignore cleanup errors
93+
pass
7894

7995

8096
class TestUserOperations:
@@ -93,7 +109,9 @@ def temp_db(self):
93109
@pytest.fixture
94110
def user_manager(self, temp_db):
95111
"""Create UserManager instance with temporary database."""
96-
return UserManager(db_path=temp_db)
112+
manager = UserManager(db_path=temp_db)
113+
yield manager
114+
manager.close()
97115

98116
def test_create_user(self, user_manager):
99117
"""Test user creation."""
@@ -239,7 +257,9 @@ def temp_db(self):
239257
@pytest.fixture
240258
def user_manager(self, temp_db):
241259
"""Create UserManager instance with temporary database."""
242-
return UserManager(db_path=temp_db)
260+
manager = UserManager(db_path=temp_db)
261+
yield manager
262+
manager.close()
243263

244264
def test_create_cube(self, user_manager):
245265
"""Test cube creation."""
@@ -264,7 +284,7 @@ def test_create_cube_with_path_and_custom_id(self, user_manager):
264284
owner_id = user_manager.create_user("cube_owner", UserRole.USER)
265285

266286
custom_cube_id = "custom_cube_123"
267-
cube_path = "/path/to/cube"
287+
cube_path = str(Path("/path/to/cube")) # Use pathlib for cross-platform path handling
268288

269289
cube_id = user_manager.create_cube(
270290
"custom_cube", owner_id, cube_path=cube_path, cube_id=custom_cube_id
@@ -433,7 +453,9 @@ def temp_db(self):
433453
@pytest.fixture
434454
def user_manager(self, temp_db):
435455
"""Create UserManager instance with temporary database."""
436-
return UserManager(db_path=temp_db)
456+
manager = UserManager(db_path=temp_db)
457+
yield manager
458+
manager.close()
437459

438460
def test_user_roles(self, user_manager):
439461
"""Test different user roles."""
@@ -483,7 +505,9 @@ def temp_db(self):
483505
@pytest.fixture
484506
def user_manager(self, temp_db):
485507
"""Create UserManager instance with temporary database."""
486-
return UserManager(db_path=temp_db)
508+
manager = UserManager(db_path=temp_db)
509+
yield manager
510+
manager.close()
487511

488512
def test_cascade_delete_user_cubes(self, user_manager):
489513
"""Test that user's owned cubes are handled when user is deleted."""

tests/memories/textual/test_general.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# TODO: Overcomplex. Use pytest fixtures instead of setUp/tearDown.
22
import json
3+
import os
34
import unittest
45
import uuid
56

@@ -455,9 +456,9 @@ def test_load(self):
455456

456457
def test_dump(self):
457458
"""Test dump functionality for GeneralTextMemory."""
458-
test_dir = "/test/directory"
459+
test_dir = "test/directory"
459460
memory_filename = "textual_memory.json"
460-
memory_file_path = test_dir + "/" + memory_filename
461+
memory_file_path = os.path.join(test_dir, memory_filename)
461462

462463
# Set the config's memory_filename
463464
self.config.memory_filename = memory_filename

0 commit comments

Comments
 (0)