Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/test-memory-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Test Memory Integration

on:
pull_request:
branches: [ "main" ]
push:
branches: [ "main" ]

permissions:
contents: read
actions: write

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
test-mem0:
runs-on: ubuntu-latest

env:
OLLAMA_MAX_WAIT: 60

steps:
- uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: |
python -m pip install uv
uv venv
source .venv/bin/activate
uv pip install -r requirements.txt

- name: Create and patch config for Mem0 tests
run: |
source .venv/bin/activate
cp aios/config/config.yaml.example aios/config/config.yaml
python scripts/patch_config_for_memory_tests.py

- name: Install Ollama
run: |
curl -fsSL https://ollama.com/install.sh | sh

ollama serve > ollama.log 2>&1 &

for ((i=1;i<=${OLLAMA_MAX_WAIT};i++)); do
curl -s http://localhost:11434/api/version && echo "Ollama ready" && break
echo "Waiting for Ollama… ($i/${OLLAMA_MAX_WAIT})"; sleep 1
done

ollama pull nomic-embed-text
ollama pull qwen3:1.7b

- name: Run Mem0 provider integration tests
run: |
source .venv/bin/activate
pytest tests/modules/memory/test_mem0_provider.py -v -m integration

- name: Upload logs
if: always()
uses: actions/upload-artifact@v4
with:
name: memory-integration-logs
path: |
ollama.log
4 changes: 2 additions & 2 deletions .github/workflows/test-ollama.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.10
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.10"
python-version: "3.11"

- name: Install dependencies
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-qdrant.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.10
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.10"
python-version: "3.11"

- name: Install dependencies
run: |
Expand Down
55 changes: 55 additions & 0 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Test Unit

on:
pull_request:
branches: [ "main" ]
push:
branches: [ "main" ]

permissions:
contents: read
actions: write

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
test-unit:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: |
python -m pip install uv
uv venv
source .venv/bin/activate
uv pip install -r requirements.txt

- name: Create config from example
run: cp aios/config/config.yaml.example aios/config/config.yaml

- name: Run unit tests
run: |
source .venv/bin/activate
# NOTE: Known-broken files excluded from the unit run:
# - tests/modules/llm/openai/test_concurrent.py (broken import)
# - tests/modules/llm/openai/test_single.py (broken import)
# - tests/modules/memory/test_memory.py (broken import)
# - tests/modules/memory/test_memory_user_identity_isolation.py
# (order-dependent ConfigManager singleton pollution — passes
# in isolation, fails when run after other memory tests;
# tracked separately)
# These are pre-existing issues unrelated to the CI workflow.
pytest tests/ -v -m "not integration" \
--ignore=tests/modules/llm/openai/test_concurrent.py \
--ignore=tests/modules/llm/openai/test_single.py \
--ignore=tests/modules/memory/test_memory.py \
--ignore=tests/modules/memory/test_memory_user_identity_isolation.py
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,69 @@ Make sure you have installed a virtualized environment with GUI, then you can re
| vLLM | [All Models](https://docs.vllm.ai/en/latest/) | ✅ | model-name | vllm | - |
| Novita | [All Models](https://novita.ai/models/llm) | ✅ | model-name | novita | NOVITA_API_KEY |

## 🧪 Running Tests

Tests use [pytest](https://docs.pytest.org/) with a custom `integration` marker defined in `pytest.ini`. Tests are organized under `tests/` by module.

### Unit tests (fast, no external services)

The CI command (see [`.github/workflows/test-unit.yml`](.github/workflows/test-unit.yml) for the source of truth):

```bash
pytest tests/ -v -m "not integration" \
--ignore=tests/modules/llm/openai/test_concurrent.py \
--ignore=tests/modules/llm/openai/test_single.py \
--ignore=tests/modules/memory/test_memory.py \
--ignore=tests/modules/memory/test_memory_user_identity_isolation.py
```

### Integration tests (requires external services)

Integration tests require different services depending on the file:

- **`test_shared_memory_e2e.py`** — requires Ollama + ChromaDB. Skips gracefully if Ollama is unreachable.
- **`test_mem0_provider.py`** — requires Ollama (embedder + LLM) + ChromaDB. Does **not** skip gracefully; will fail/error if services aren't running.
- **`test_zep_provider.py`** — requires a configured Zep Cloud API key. Does **not** skip gracefully; will fail/error without valid credentials.
- **`test_qdrant_integration.py`** — requires a running Qdrant server. Skips via `unittest.SkipTest` if Qdrant is unreachable.

Running `-m integration` without every service up will show failures for the files whose services are missing. This is expected in a partial local setup, not a broken test suite.

```bash
# For Mem0/ChromaDB tests only (most common local setup):
# ollama serve
# ollama pull nomic-embed-text
python scripts/patch_config_for_memory_tests.py
pytest tests/modules/memory/test_mem0_provider.py tests/modules/memory/test_shared_memory_e2e.py -v -m integration

# Full integration suite (requires ALL services):
pytest tests/ -v -m integration --continue-on-collection-errors
```

To force-test the graceful-skip path in `test_shared_memory_e2e.py`: `OLLAMA_SKIP=1 pytest tests/modules/memory/test_shared_memory_e2e.py -v -m integration`.

### Running a specific module

```bash
pytest tests/modules/terminal/ -v
pytest tests/modules/scheduler/ -v
pytest tests/modules/syscall/ -v
```

### Marker system

- `integration` — requires external services (Ollama, ChromaDB, Qdrant, Zep). Registered in `pytest.ini` and `tests/conftest.py`.
- Unmarked tests are pure/mocked and run without any services.

### Known pre-existing issues

| File | Problem | Status |
|------|---------|--------|
| `tests/modules/llm/openai/test_concurrent.py` | Broken import (missing module) | Excluded via `--ignore` |
| `tests/modules/llm/openai/test_single.py` | Broken import (missing module) | Excluded via `--ignore` |
| `tests/modules/memory/test_memory.py` | Broken import (missing module) | Excluded via `--ignore` |
| `tests/modules/memory/test_memory_user_identity_isolation.py` | Order-dependent failure — passes in isolation, fails in full suite due to `ConfigManager` singleton state pollution from earlier tests | Excluded via `--ignore` |
| `tests/modules/memory/test_mem0_provider.py`, `test_zep_provider.py` | No graceful skip when backing services are unavailable — will produce failures/errors in a partial local setup | Expected; run only when services are configured |

## 🔧 Experimental Rust Rewrite (aios-rs)
An early experimental Rust scaffold lives in `aios-rs/` providing trait definitions and minimal placeholder implementations (context, memory, storage, tool, scheduler, llm). This is NOT feature-parity yet; it's a foundation for incremental porting and performance-focused components.

Expand Down
7 changes: 7 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
integration: requires external services (Ollama, ChromaDB, etc.)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ redis>=4.5.1
sentence-transformers
nltk
hypothesis
pytest
scikit-learn
pulp
gdown
Expand Down
79 changes: 79 additions & 0 deletions scripts/patch_config_for_memory_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""
Patch aios/config/config.yaml for memory integration tests.

Sets:
- memory.provider = "mem0"
- memory.auto_extract = true
- memory.auto_inject = true
- memory.mem0.embedder uses ollama/nomic-embed-text
- memory.mem0.vector_store uses chroma with persistence path

Reads from and writes to aios/config/config.yaml by default.
Pass a path argument to override.

Usage:
python scripts/patch_config_for_memory_tests.py
python scripts/patch_config_for_memory_tests.py path/to/config.yaml
"""
import sys

import yaml


def patch_config(config_path: str = "aios/config/config.yaml") -> None:
with open(config_path) as f:
cfg = yaml.safe_load(f)

memory = cfg.setdefault("memory", {})

# Switch provider to mem0
memory["provider"] = "mem0"

# Enable personalization pipeline
memory["auto_extract"] = True
memory["auto_inject"] = True

# Ensure mem0 section is present and correct
mem0 = memory.setdefault("mem0", {})
mem0["user_id"] = "default"

# LLM: ollama (avoids needing an OpenAI API key in CI)
mem0["llm"] = {
"provider": "ollama",
"config": {
"model": "qwen3:1.7b",
"ollama_base_url": "http://localhost:11434",
},
}

# Embedder: ollama + nomic-embed-text
mem0["embedder"] = {
"provider": "ollama",
"config": {
"model": "nomic-embed-text",
"ollama_base_url": "http://localhost:11434",
},
}

# Vector store: chroma with persistence
mem0["vector_store"] = {
"provider": "chroma",
"config": {
"collection_name": "mem0_memories",
"path": ".mem0/chroma",
},
}

with open(config_path, "w") as f:
yaml.dump(cfg, f, default_flow_style=False, sort_keys=False)

print(f"Patched {config_path}: memory.provider=mem0, "
f"llm=ollama/qwen3:1.7b, "
f"embedder=ollama/nomic-embed-text, "
f"vector_store=chroma (.mem0/chroma)")


if __name__ == "__main__":
path = sys.argv[1] if len(sys.argv) > 1 else "aios/config/config.yaml"
patch_config(path)
32 changes: 32 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Root conftest for the AIOS test suite.

Responsibilities:
1. Register the ``integration`` marker programmatically so that
pytest never emits an "unknown marker" warning — even if
pytest.ini is not picked up for any reason.
2. Ensure the project root is importable (sys.path fix).
"""

import os
import sys

import pytest

# ── sys.path fix ──────────────────────────────────────────────────────
# Many existing test files already do their own
# sys.path.insert(0, <project_root>)
# This ensures the project root is always available regardless of the
# working directory pytest is invoked from, without duplicating entries.
_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _PROJECT_ROOT not in sys.path:
sys.path.insert(0, _PROJECT_ROOT)


# ── Marker registration ──────────────────────────────────────────────
def pytest_configure(config: pytest.Config) -> None:
"""Register custom markers as a safety-net backup to pytest.ini."""
config.addinivalue_line(
"markers",
"integration: requires external services (Ollama, ChromaDB, etc.)",
)
Empty file added tests/modules/api/__init__.py
Empty file.
Loading
Loading