litellm/scripts/e2e_llm_loop.sh
Claude e7a9a8138b
feat(tests): scaffold per-provider e2e LLM translation refactor loop
Adds tooling for an agent-driven loop that hardens a provider's e2e
test suite (coverage + mutation) before refactoring its source. Source
edits are blocked via chmod + a sentinel-gated pre-commit hook so tests
can't drift to cover internals the refactor will change.

- scripts/e2e_llm_loop.sh: per-provider pytest+coverage and mutmut runs
- scripts/e2e_llm_lock.sh: chmod-based source lock
- scripts/e2e_llm_install_hook.sh: belt-and-suspenders pre-commit hook
- Makefile: e2e-llm-{coverage,mutation,lock,unlock,hook-*} targets
- tests/llm_translation/E2E_AGENT.md: agent prompt and operating loop
- .gitignore: defensive blocks on file-based VCR cassettes
- pyproject.toml: branch coverage + report config

https://claude.ai/code/session_01MWq8irLUTyWi79JXY8TBHy
2026-05-12 22:39:23 +00:00

123 lines
3.6 KiB
Bash
Executable file

#!/usr/bin/env bash
# e2e_llm_loop.sh — run e2e LLM-translation tests with coverage (and
# optionally mutation) scoped to ONE provider's source dir.
#
# Drives the loop described in tests/llm_translation/E2E_AGENT.md.
#
# Usage:
# scripts/e2e_llm_loop.sh coverage <provider> [--fail-under N] [pytest-args...]
# scripts/e2e_llm_loop.sh mutation <provider> [mutmut-args...]
#
# Example:
# scripts/e2e_llm_loop.sh coverage anthropic --fail-under 90
# scripts/e2e_llm_loop.sh mutation anthropic
#
# Provider name = subdirectory of litellm/llms/ (e.g. anthropic, openai,
# bedrock). Test files are auto-discovered from
# tests/llm_translation/test_<provider>*.py.
#
# Coverage scope is litellm/llms/<provider>/ only — everything else is
# excluded from the report so the threshold reflects the provider's
# translation code, not the whole repo.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "${ROOT}"
mode="${1:-}"
provider="${2:-}"
if [[ -z "${mode}" || -z "${provider}" ]]; then
echo "Usage: $0 {coverage|mutation} <provider> [extra args...]" >&2
exit 2
fi
shift 2
PROVIDER_SRC="litellm/llms/${provider}"
if [[ ! -d "${PROVIDER_SRC}" ]]; then
echo "No source dir: ${PROVIDER_SRC}" >&2
exit 2
fi
# Test files: test_<provider>*.py under tests/llm_translation/. Glob may
# expand to multiple files; that's fine for pytest.
shopt -s nullglob
test_files=( tests/llm_translation/test_${provider}*.py )
shopt -u nullglob
if [[ ${#test_files[@]} -eq 0 ]]; then
echo "No test files matching tests/llm_translation/test_${provider}*.py" >&2
exit 2
fi
case "${mode}" in
coverage)
fail_under=90
pytest_extra=()
while [[ $# -gt 0 ]]; do
case "$1" in
--fail-under)
fail_under="$2"
shift 2
;;
--fail-under=*)
fail_under="${1#*=}"
shift
;;
*)
pytest_extra+=( "$1" )
shift
;;
esac
done
out_dir="e2e_llm_coverage/${provider}"
mkdir -p "${out_dir}"
echo "==> Coverage run: provider=${provider} fail_under=${fail_under}"
echo " Tests: ${test_files[*]}"
echo " Source scope: ${PROVIDER_SRC}"
uv run --no-sync pytest \
"${test_files[@]}" \
--cov="${PROVIDER_SRC}" \
--cov-branch \
--cov-report="term-missing:skip-covered" \
--cov-report="html:${out_dir}/html" \
--cov-report="xml:${out_dir}/coverage.xml" \
--cov-fail-under="${fail_under}" \
"${pytest_extra[@]}"
;;
mutation)
out_dir="e2e_llm_mutation/${provider}"
mkdir -p "${out_dir}"
echo "==> Mutation run: provider=${provider}"
echo " Tests: ${test_files[*]}"
echo " Mutate scope: ${PROVIDER_SRC}"
echo " Output: ${out_dir}"
echo ""
echo "NOTE: mutmut config in pyproject.toml [tool.mutmut] is the"
echo " default scope (proxy/management_endpoints). This script"
echo " overrides it via env vars MUTMUT_PATHS / MUTMUT_TESTS"
echo " consumed by the wrapper below."
# mutmut reads paths_to_mutate / tests_dir from pyproject.toml. We
# override at invocation by exporting and using --paths-to-mutate.
uv run --no-sync --with mutmut==3.3.1 mutmut run \
--paths-to-mutate="${PROVIDER_SRC}" \
--tests-dir="tests/llm_translation" \
"$@"
# Persist a JSON report alongside coverage output.
uv run --no-sync --with mutmut==3.3.1 mutmut results > "${out_dir}/results.txt" || true
echo "Wrote ${out_dir}/results.txt"
;;
*)
echo "Unknown mode: ${mode}" >&2
echo "Usage: $0 {coverage|mutation} <provider> [extra args...]" >&2
exit 2
;;
esac