litellm/tests/integration/run.py
yuneng-jiang 635a718ba1
ci: cut CircleCI wall time without loosening test isolation (#43347)
* ci: cut CircleCI wall time without loosening test isolation

* fix(ci): parse integration split files that follow --results

The CircleCI machine image ships Python 3.12.2, whose argparse leaves the
files positional empty when it follows an option and another positional, so
every extensions node exited with 'unrecognized arguments'. Reproduced on
3.12.2; parse_intermixed_args selects the files on 3.12.2, 3.12.13 and 3.13

* test(ci): resolve command references in the Rust toolchain guard

The Windows rustup install moved into the install_windows_toolchain command,
which the guard only recognized for install_rust. It now accepts any command
that installs a pinned rustup and reads the Windows toolchain pin from it

* ci: cache the Windows release cargo build from main

windows_release_wheel rebuilt every dependency with fat LTO on each run. It now
restores the release target and cargo registry saved by main's scheduled run,
drops the workspace crates' fingerprints so they always rebuild from the
checked-out source, and still runs the full LTO link

* ci: run the Windows release wheel build on windows.xlarge

The fat-LTO release build is the slowest job in the pipeline; more cores
speed up the dependency compile ahead of the final link

* ci: skip the Windows fingerprint cleanup when the cargo cache missed

On a cold cache the release fingerprint directory does not exist, and the
CircleCI PowerShell wrapper failed the step on the suppressed not-found error
2026-09-26 15:34:53 -07:00

99 lines
3.6 KiB
Python

from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from types import MappingProxyType
from typing import Final
GROUPS: Final = MappingProxyType(
{
"management": ("management", "authorization", "configuration"),
"accounting": ("pricing", "spend"),
"database": ("database",),
"providers": ("providers", "routing", "streaming"),
"extensions": ("observability", "compatibility"),
"mcp": ("mcp",),
"sdk": ("sdk",),
"cost": ("cost_calculation",),
}
)
def main() -> int:
parser: Final = argparse.ArgumentParser()
parser.add_argument("group", choices=tuple(GROUPS))
parser.add_argument("--results", type=Path, default=Path("test-results/integration"))
parser.add_argument("--seed", type=int, default=int(os.environ.get("INTEGRATION_SEED", "4106601")))
parser.add_argument("--order-seed", type=int, default=int(os.environ.get("INTEGRATION_ORDER_SEED", "0")))
parser.add_argument("--workers", type=int, default=int(os.environ.get("INTEGRATION_WORKERS", "1")))
parser.add_argument("--list", action="store_true", help="print the group's test files and exit")
parser.add_argument("files", nargs="*", help="run only these files of the group")
options: Final = parser.parse_intermixed_args()
root: Final = Path(__file__).resolve().parents[2]
group_files: Final = tuple(
str(path.relative_to(root))
for folder in GROUPS[options.group]
for path in sorted((root / "tests/integration" / folder).glob("test_*.py"))
)
if options.list:
print("\n".join(group_files))
return 0
foreign: Final = sorted(set(options.files) - set(group_files))
if foreign:
parser.error(f"Not in the {options.group} group: {', '.join(foreign)}")
selected: Final = tuple(options.files) or group_files
if not selected:
parser.error(f"No integration test files selected for {options.group}")
output: Final = options.results.resolve()
output.mkdir(parents=True, exist_ok=True)
environment: Final = {
**os.environ,
"PYTHONPATH": os.pathsep.join((str(root), str(root / "tests"), str(root / "tests/e2e"))),
"INTEGRATION_RESULTS_DIR": str(output),
"LITELLM_LOCAL_MODEL_COST_MAP": "True",
}
result: Final = subprocess.call(
[
sys.executable,
"-m",
"pytest",
*selected,
"-vv",
"-rs",
"--strict-markers",
"-p",
"no:pytest-retry",
"-p",
"no:rerunfailures",
"--timeout=90",
"--durations=15",
f"--hypothesis-seed={options.seed}",
f"--integration-order-seed={options.order_seed}",
f"--junitxml={output / 'junit.xml'}",
"-o",
"junit_family=xunit1",
*(("-n", str(options.workers)) if options.workers > 1 else ()),
],
cwd=root,
env=environment,
)
if result != 0:
return result
evidence: Final = json.loads((output / "execution.json").read_text())
collected_files: Final = {node.split("::", 1)[0] for node in evidence["collected"]}
empty: Final = tuple(path for path in selected if path not in collected_files)
if empty:
sys.stderr.write(f"Selected integration files collected zero tests: {', '.join(empty)}\n")
return 1
if not evidence["complete"]:
sys.stderr.write("Integration run did not complete: a collected node neither passed nor skipped\n")
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())