Ctrl K
ComunidadGuías…

test/knowledge/test_redis_append.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

import asyncio
import shutil
import subprocess
import tempfile
import time
from collections.abc import Iterator
from pathlib import Path

import pytest

redis = pytest.importorskip("redis")

from redis.asyncio import Redis

from ag2.knowledge.redis import RedisKnowledgeStore

pytestmark = [
    pytest.mark.redis,
    pytest.mark.asyncio,
]

_CONNECTIONS = 20


@pytest.fixture(scope="module")
def redis_socket() -> Iterator[str]:
    executable = shutil.which("redis-server")
    if executable is None:
        pytest.skip("redis-server is required for the knowledge-store integration tests")
    # A short directory: unix socket paths are limited to ~100 characters on macOS.
    with tempfile.TemporaryDirectory(dir="/tmp", prefix="ag2-redis-") as directory:
        socket = str(Path(directory) / "server.sock")
        process = subprocess.Popen(
            [executable, "--port", "0", "--unixsocket", socket, "--save", "", "--appendonly", "no"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        try:
            with redis.Redis(unix_socket_path=socket) as client:
                deadline = time.monotonic() + 5
                while True:
                    try:
                        client.ping()
                        break
                    except redis.ConnectionError:
                        if process.poll() is not None or time.monotonic() >= deadline:
                            pytest.fail("the owned Redis server did not start")
                        time.sleep(0.01)
            yield socket
        finally:
            process.terminate()
            try:
                process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                process.kill()
                process.wait()


@pytest.mark.parametrize("initial", ["", "snow☃\n"])
async def test_independent_connections_append_without_losing_entries(redis_socket: str, initial: str) -> None:
    clients = [Redis(unix_socket_path=redis_socket) for _ in range(_CONNECTIONS)]
    try:
        await clients[0].flushdb()
        stores = [RedisKnowledgeStore(client) for client in clients]
        if initial:
            await stores[0].write("/log.jsonl", initial)
        payloads = [f"entry-{i}-雪\n" for i in range(_CONNECTIONS)]

        offsets = await asyncio.gather(*(store.append("log.jsonl", text) for store, text in zip(stores, payloads)))

        for offset, text in zip(offsets, payloads):
            assert await stores[0].read_range("/log.jsonl", offset, offset + len(text.encode("utf-8"))) == text
        content = await stores[0].read("/log.jsonl")
        assert content is not None
        assert content.startswith(initial)
        assert sorted(content[len(initial) :].splitlines(keepends=True)) == sorted(payloads)
        assert (await stores[0].list_versions_under("/"))["/log.jsonl"] == _CONNECTIONS + bool(initial)
    finally:
        for client in clients:
            await client.aclose()


async def test_append_keeps_empty_and_utf8_byte_offset_semantics(redis_socket: str) -> None:
    async with Redis(unix_socket_path=redis_socket) as client:
        await client.flushdb()
        store = RedisKnowledgeStore(client)
        assert await store.append("/log.jsonl", "") == 0
        assert await store.exists("/log.jsonl")
        assert await store.append("/log.jsonl", "雪") == 0
        assert await store.append("/log.jsonl", "snow☃") == len("雪".encode())
        assert await store.read("/log.jsonl") == "雪snow☃"
        assert (await store.list_versions_under("/"))["/log.jsonl"] == 3
        await store.delete("/log.jsonl")
        assert not await store.exists("/log.jsonl")