ci: use shallow checkout for schema compatibility checks

This commit is contained in:
mateo-berri 2026-10-27 12:47:38 -07:00
parent 788868a52f
commit c5ddee6ff8
4 changed files with 111 additions and 14 deletions

View file

@ -543,7 +543,7 @@ jobs:
run: uv run --no-sync pytest -q --noconftest -p no:cacheprovider -c /dev/null tests/code_coverage_tests/test_workflow_job_name_collisions.py
- name: test_unit_passed_gate
run: uv run --no-sync pytest -q --noconftest -p no:cacheprovider -c /dev/null tests/code_coverage_tests/test_unit_passed_gate.py
run: uv run --no-sync pytest -q --noconftest -p no:cacheprovider -c /dev/null tests/code_coverage_tests/test_unit_passed_gate.py tests/code_coverage_tests/test_schema_compatibility_cli.py
- name: test_e2e_idp_stack
run: uv run --no-sync pytest -q --noconftest -p no:cacheprovider -c /dev/null tests/code_coverage_tests/test_e2e_idp_stack.py

View file

@ -955,13 +955,37 @@ jobs:
timeout-minutes: ${{ matrix.shard == 'db-and-spend' && 10 || 3 }}
with:
persist-credentials: false
fetch-depth: ${{ matrix.shard == 'db-and-spend' && '0' || '1' }}
fetch-depth: 1
- name: Detect relevant changes
id: changes
timeout-minutes: 2
uses: ./.github/actions/detect-changes
- name: Fetch schema comparison baseline
if: matrix.shard == 'db-and-spend' && steps.changes.outputs.decision != 'skip'
timeout-minutes: 3
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
CHECKED_OUT_SHA: ${{ github.sha }}
run: |
for attempt in 1 2 3; do
if baseline="$(gh api "repos/${REPO}/compare/${DEFAULT_BRANCH}...${CHECKED_OUT_SHA}?per_page=1" --jq '.merge_base_commit.sha')" \
&& [[ "$baseline" =~ ^[0-9a-f]{40}$ ]] \
&& git fetch --no-tags --depth=1 origin "$baseline"; then
printf 'SCHEMA_BASE_COMMIT=%s\n' "$baseline" >> "$GITHUB_ENV"
exit 0
fi
if [ "$attempt" -lt 3 ]; then
echo "Schema baseline fetch failed, retrying attempt $((attempt + 1))/3"
sleep "$((attempt * 5))"
fi
done
echo "::error::Could not resolve and fetch the schema baseline after 3 attempts"
exit 1
- name: Set up Python
if: steps.changes.outputs.decision != 'skip'
timeout-minutes: 3
@ -994,7 +1018,6 @@ jobs:
env:
RUST_BRIDGE_ARTIFACT: ${{ needs.rust-bridge.outputs.artifact }}
UNIT_SHARD: ${{ matrix.shard }}
SCHEMA_BASE_REF: origin/${{ github.event.repository.default_branch }}
run: |
diff -u model_prices_and_context_window.json litellm/model_prices_and_context_window_backup.json
if [ -z "$RUST_BRIDGE_ARTIFACT" ]; then
@ -1007,7 +1030,7 @@ jobs:
fi
uv run --no-sync python -c 'import os, sys; print(sys.version); assert f"{sys.version_info.major}.{sys.version_info.minor}" == os.environ["UV_PYTHON"]'
if [ "$UNIT_SHARD" = "db-and-spend" ]; then
uv run --no-sync python scripts/check_schema_compatibility.py --base-ref "$SCHEMA_BASE_REF"
uv run --no-sync python scripts/check_schema_compatibility.py --base-commit "$SCHEMA_BASE_COMMIT"
fi
- name: Cache Prisma binaries

View file

@ -7,14 +7,20 @@ import sys
from collections.abc import Iterator, Mapping
from pathlib import Path
from types import MappingProxyType
from typing import Final
from typing import Final, TypeAlias
from pydantic import TypeAdapter
from pydantic import BaseModel, ConfigDict, TypeAdapter
Schema = Mapping[str, Mapping[str, str]]
Schema: TypeAlias = Mapping[str, Mapping[str, str]]
MODELS: Final = re.compile(r"^[ \t]*model\s+(\w+)\s*\{(.*?)^[ \t]*\}", re.MULTILINE | re.DOTALL)
class _Options(BaseModel):
model_config = ConfigDict(frozen=True)
base_ref: str | None
base_commit: str | None
def parse_model_fields(schema: str) -> Schema:
def fields(body: str) -> Mapping[str, str]:
parts: Final = tuple(line.split() for line in body.splitlines())
@ -54,12 +60,20 @@ def check_breaking_changes(old_schema: Schema, new_schema: Schema) -> tuple[str,
def main() -> int:
parser: Final = argparse.ArgumentParser()
parser.add_argument("--base-ref", required=True)
options: Final = parser.parse_args()
base_ref: Final = TypeAdapter(str).validate_python(options.base_ref)
base: Final = subprocess.run(
("git", "merge-base", "HEAD", base_ref), capture_output=True, text=True, check=True
).stdout.strip()
baseline: Final = parser.add_mutually_exclusive_group(required=True)
baseline.add_argument("--base-ref")
baseline.add_argument("--base-commit")
options: Final = _Options.model_validate(vars(parser.parse_args()))
base: Final = (
options.base_commit
if options.base_commit is not None
else subprocess.run(
("git", "merge-base", "HEAD", TypeAdapter(str).validate_python(options.base_ref)),
capture_output=True,
text=True,
check=True,
).stdout.strip()
)
previous: Final = subprocess.run(
("git", "show", f"{base}:schema.prisma"), capture_output=True, text=True, check=True
).stdout
@ -69,7 +83,7 @@ def main() -> int:
if changes:
sys.stderr.write("\n".join(changes) + "\n")
return 1
sys.stdout.write(f"Schema compatibility passed against merge base {base}\n")
sys.stdout.write(f"Schema compatibility passed against baseline {base}\n")
return 0

View file

@ -0,0 +1,60 @@
import subprocess
import sys
from pathlib import Path
from typing import Final
import pytest
CHECKER: Final = Path(__file__).resolve().parents[2] / "scripts/check_schema_compatibility.py"
SCHEMA: Final = "model Item {\n id String @id\n label String?\n}\n"
def _git(root: Path, *args: str) -> str:
return subprocess.run(
("git", "-C", str(root), *args), capture_output=True, text=True, check=True, timeout=30
).stdout.strip()
@pytest.mark.parametrize("baseline_option", ("--base-ref", "--base-commit"))
@pytest.mark.parametrize(("schema", "expected"), ((SCHEMA, 0), ("model Item {\n id String @id\n}\n", 1)))
def test_schema_check_preserves_break_detection_with_or_without_history(
tmp_path: Path, schema: str, expected: int, baseline_option: str
) -> None:
source: Final = tmp_path / "source"
source.mkdir()
_git(source, "init", "--initial-branch=main")
_git(source, "config", "user.name", "Schema test")
_git(source, "config", "user.email", "schema-test@example.invalid")
_git(source, "config", "commit.gpgsign", "false")
(source / "schema.prisma").write_text(SCHEMA)
_git(source, "add", "schema.prisma")
_git(source, "commit", "-m", "baseline")
baseline: Final = _git(source, "rev-parse", "HEAD")
(source / "schema.prisma").write_text(schema)
_git(source, "add", "schema.prisma")
_git(source, "commit", "--allow-empty", "-m", "candidate")
checkout: Final = tmp_path / "checkout"
_git(tmp_path, "clone", "--depth=1", source.as_uri(), str(checkout))
_git(checkout, "fetch", "--no-tags", "--depth=1", "origin", baseline)
if baseline_option == "--base-ref":
_git(checkout, "fetch", "--unshallow", "origin", "main")
result: Final = subprocess.run(
(sys.executable, "-I", str(CHECKER), baseline_option, baseline),
cwd=checkout,
capture_output=True,
text=True,
check=False,
timeout=30,
)
assert result.returncode == expected, result.stdout + result.stderr
assert _git(checkout, "rev-parse", "--is-shallow-repository") == (
"true" if baseline_option == "--base-commit" else "false"
)
expected_output: Final = (
f"Schema compatibility passed against baseline {baseline}"
if expected == 0
else "Breaking: Field Item.label was removed"
)
assert expected_output in result.stdout + result.stderr