Files
memo/TESTING.md
T

8.3 KiB
Raw Blame History

TESTING — приёмочный тест-план

Тестирует заказчик (Hermes) на реальном железе, а не исполнитель. Исполнитель пишет тесты; приёмка идёт независимыми проверками ниже. Правило приёмки: «зелёная сборка» ничего не доказывает, пока мутация не валит нужный тест.

0. Подготовка стенда

export MEMO_MODEL_DIR=/root/WORK/memo/models/siglip2
mkdir -p "$MEMO_MODEL_DIR"
curl -fSL -o "$MEMO_MODEL_DIR/text_model_int8.onnx" http://static.binom.pw/models/siglip2/text_model_int8.onnx  # 283M
curl -fSL -o "$MEMO_MODEL_DIR/tokenizer.model" http://static.binom.pw/models/siglip2/tokenizer.model           # 4.2M

# эталонный корпус: 12-15 заметок в 3 папках-коллекциях
export MEMO_ROOT=/root/WORK/memo-e2e
# infra/*.md (серверы, домены), work/jira/*.md, life/books/*.md — обычный markdown с заголовками

Пороговое требование: файлов не меньше 12 в трёх папках, в текстах есть и кириллица, и латиница, и хотя бы по одному точному значению на папку (IP, номер тикета, год издания).

T1. Индексация и её повтор

cd /root/WORK/memo && ./gradlew :memo-cli:installDist -q
CLI=./memo-cli/build/install/memo-cli/bin/memo
$CLI index "$MEMO_ROOT"; $CLI status "$MEMO_ROOT"
ls -l "$MEMO_ROOT"/*/.memo/index.db
$CLI index "$MEMO_ROOT"      # второй прогон

Ожидается: в каждой папке появился .memo/index.db; второй прогон не переэмбеддивает ни одного файла (в выводе 0 обновлённых, status показывает те же indexed_at). Провал: нет .memo/index.db; второй прогон молча переиндексирует всё.

T2. Смысловой поиск (вектор работает)

20 вопросов, сформулированных другими словами, чем в заметках. Ожидание: нужный раздел в top-5 не реже 16 раз из 20 (recall@5 ≥ 0.8). Формулировки и ожидаемый файл — в e2e/questions.tsv (создаёт заказчик).

$CLI search "$MEMO_ROOT/infra" "как пережить перезагрузку сервера, чтобы операция не умерла" --json

Провал: в результатах нет нужного файла; возвращаются чанки без текста (агент не сможет ими воспользоваться); поиск идёт по всему корню вместо указанной папки.

T3. Лексический поиск (BM25 работает)

Вопросы с точными значениями: IP-адрес, номер тикета, версия, год. Ожидание: точное совпадение найдено, и именно через лексику.

$CLI search "$MEMO_ROOT/infra" "76.132" --json          # должен найти заметку с этим IP
$CLI search "$MEMO_ROOT/infra" "76.132" --mode lex --json

Провал: находит «похожее по смыслу», но не строку с точным значением; --mode lex даёт тот же результат, что --mode vec (значит, режимы не разделены).

T4. Живая правка файла

echo "\n## Свежий раздел\nуникальное_слово_дзынь_47" >> "$MEMO_ROOT/infra/hosts.md"
sleep 1
$CLI search "$MEMO_ROOT/infra" "уникальное слово дзынь" --json      # ≤1 с после правки
echo "## Ещё раздел\nвторое_слово_бамс_93" >> "$MEMO_ROOT/infra/hosts.md"
$CLI search "$MEMO_ROOT/infra" "второе слово бамс" --json            # БЕЗ sleep: тот же вызов

Ожидается: первая правка подхвачена watcher'ом ≤1 с; вторая видна в том же вызове поиска (stat-догон до поиска). Провал: поиск не видит свежую правку; database is locked при одновременной записи watcher'ом и чтении (признак: WAL не включён).

T5. MCP-сервер по протоколу (не по самоотчёту)

Инструменты подключаются к Hermes только в новой сессии, поэтому проверяем протоколом:

python3 scripts/mcp_probe.py --cmd "./memo-mcp/build/install/memo-mcp/bin/memo-mcp" \
  --call memo_search --args '{"path":"'"$MEMO_ROOT"'/infra","query":"домены","k":3}'

Скрипт-зонд: пишет initialize, notifications/initialized, tools/list, tools/call в stdin процесса, читает JSON-RPC-ответы. Ожидается: tools/list вернул 3 инструмента; tools/call — непустой content[0].text с результатами; isError отсутствует.

Тестовые ручки (то же без транспорта):

$CLI mcp-probe --tool memo_search --args '{"path":"'"$MEMO_ROOT"'/infra","query":"домены"}'
# плюс режим "одного вызова": разобрать строку запроса и вернуть ответ, не поднимая stdio

Провал: сервер печатает что-то в stdout помимо JSON-RPC (ломает клиента); на tools/call отвечает -32601; результат — пустая строка.

T6. Офлайн

# отключаем внешний интерфейс у процесса: без сети индексация и поиск обязаны работать
$CLI index "$MEMO_ROOT" && $CLI search "$MEMO_ROOT" "любой вопрос" --json

Провал: любой сетевой вызов в рантайме (модель качается при каждом старте, эмбеддинг уходит в API).

M. Мутационная приёмка (обязательна)

Для каждого пункта: сломать ровно одно место в боевом коде (python-replace с assert old in s), прогнать ./gradlew cleanTest test, сверить XML-отчёты и их mtime, затем git checkout -- <файл>.

Мутация Какой тест обязан упасть
Чанкер: резать по 4000 символов, игнорируя заголовки T2 (recall), тест чанкера
Поиск: mode=lex уходит в вектор T3, тест разделения режимов
RRF: убрать слияние (только вектор) T3
Индексация: игнорировать mtime/хэш, всегда переиндексировать T1 (второй прогон не no-op)
Watcher: убрать вызов pollEvents/регистрацию T4 (первая часть)
Поиск: убрать stat-догон перед поиском T4 (вторая часть)
MCP: tools/list возвращает пустой список T5
Схема: chunks_vec дименсия 384 вместо 768 T3-KNN, тест round-trip

Мутация, которая не валит ни одного теста, означает дыру в покрытии: тест дописать, находку — в отчёт.

Признаки провала исполнителя

  • Вывод — «план реализации» текстом, git status пуст, exit 0.
  • Тесты зелёные, но мутации не валят (тесты написаны под код, а не под поведение).
  • Изменён боевой код вне заказанного списка файлов (git diff --stat не совпал с заказом).
  • Появились зависимости, которых нет в TASK.md §2.
  • Файлы модели или .db попали в коммит.