mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-11-03 22:32:27 +08:00
feat(plugins): consolidate official plugins with independent releases (LANG-180) (#2797)
Co-authored-by: multica-agent <[email protected]>
This commit is contained in:
co-authored by
multica-agent
parent
d099033d79
commit
3a77306c3c
@@ -0,0 +1,107 @@
|
||||
"""Regression fixtures are local; no external model/parser/provider requests."""
|
||||
import asyncio
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
|
||||
from test_shared_state import load_plugin, bind, StorageFixture, CONNECTORS
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_offload_slot_is_held_through_repeated_cancellation():
|
||||
pc, _ = load_plugin('LangRAG')
|
||||
plugin = bind(pc, StorageFixture())
|
||||
assert hasattr(plugin, 'offload'), 'CPU parsing/chunking needs installation bounded offload'
|
||||
entered = threading.Event()
|
||||
release = threading.Event()
|
||||
count = 0
|
||||
lock = threading.Lock()
|
||||
def job():
|
||||
nonlocal count
|
||||
with lock:
|
||||
count += 1
|
||||
if count == 2:
|
||||
entered.set()
|
||||
release.wait(2)
|
||||
return 'done'
|
||||
tasks = [asyncio.create_task(plugin.offload.run(job)) for _ in range(5)]
|
||||
try:
|
||||
for _ in range(100):
|
||||
if entered.is_set():
|
||||
break
|
||||
await asyncio.sleep(.005)
|
||||
assert entered.is_set()
|
||||
tasks[0].cancel()
|
||||
await asyncio.sleep(.01)
|
||||
tasks[0].cancel()
|
||||
await asyncio.sleep(.01)
|
||||
assert count == 2, 'Cancelled waiters must not release running thread slots'
|
||||
release.set()
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
assert isinstance(results[0], asyncio.CancelledError)
|
||||
assert results[1:] == ['done'] * 4
|
||||
finally:
|
||||
release.set()
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_parser_rejects_oversized_input_before_decode():
|
||||
load_plugin('LangRAG')
|
||||
from components.knowledge_engine.parser import FileParser
|
||||
with pytest.raises(ValueError, match='limit'):
|
||||
await FileParser().parse(b'x' * (16 * 1024 * 1024 + 1), 'doc.txt')
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize('name', CONNECTORS)
|
||||
async def test_connector_storage_failure_is_not_missing_and_refuses_mutations(name):
|
||||
pc, ec = load_plugin(name)
|
||||
store = StorageFixture()
|
||||
engine = ec()
|
||||
engine.plugin = bind(pc, store)
|
||||
store.fail = True
|
||||
# Unreadable fence state is "unknown", never "unfenced": the mutation is
|
||||
# refused with the Host failure instead of being dispatched.
|
||||
with pytest.raises(RuntimeError, match='offline'):
|
||||
await engine.delete_document('kb', 'doc')
|
||||
with pytest.raises(RuntimeError, match='offline'):
|
||||
await engine.on_knowledge_base_create('kb', {})
|
||||
assert not store.data
|
||||
# The unreadable state is not cached either: once Host storage answers, the
|
||||
# same mutation is retried and proceeds rather than staying refused.
|
||||
store.fail = False
|
||||
await engine.on_knowledge_base_create('kb', {})
|
||||
assert store.data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_telemetry_cancellation_clear_and_failure_fencing():
|
||||
pc, _ = load_plugin('LangRAG')
|
||||
store = StorageFixture()
|
||||
plugin = bind(pc, store)
|
||||
await plugin.initialize()
|
||||
store.release.clear()
|
||||
task = asyncio.create_task(plugin.telemetry.record_delete(collection_id='kb', document_id='old', status='completed', duration_ms=1))
|
||||
await store.started.wait()
|
||||
task.cancel()
|
||||
clear = asyncio.create_task(plugin.telemetry.clear())
|
||||
await asyncio.sleep(.01)
|
||||
with pytest.raises(RuntimeError, match='fenced'):
|
||||
await clear
|
||||
store.release.set()
|
||||
await asyncio.gather(task, return_exceptions=True)
|
||||
other = bind(pc, store)
|
||||
await other.initialize()
|
||||
# A fresh reader may see the detached commit, but a failed clear cannot
|
||||
# acknowledge deletion or introduce a later event.
|
||||
assert all(event['document_id'] != 'new' for event in (await other.telemetry.snapshot())['recent']['delete'])
|
||||
store.fail = True
|
||||
with pytest.raises(RuntimeError, match='fenced'):
|
||||
await plugin.telemetry.record_delete(collection_id='kb', document_id='new', status='completed', duration_ms=1)
|
||||
with pytest.raises(RuntimeError, match='fenced'):
|
||||
await plugin.telemetry.snapshot()
|
||||
store.fail = False
|
||||
with pytest.raises(RuntimeError, match='fenced'):
|
||||
await plugin.telemetry.clear()
|
||||
assert all(event['document_id'] != 'new' for event in (await other.telemetry.snapshot())['recent']['delete'])
|
||||
Reference in New Issue
Block a user