Files
airflow/scripts/tests/ci/test_prek_cache_optimizations.py
T
Jarek Potiuk 6ca7a3be6b Build the PROD packages beside the CI image and constraints (#76261)
The PROD image build first built the Airflow and provider wheels, but only after the CI image and
generate-constraints had finished, although the wheels need neither. In a RunsOn pull request run
that put them on the chain that ends the run: CI image to minute 9.1, constraints to 15.2, wheels
15.5 to 17.9, PROD image to 29.6, Kubernetes tests to 41.3. The CI workflows now build the wheels
in their own job right after build-info and tell the PROD image build they are already uploaded;
the other callers of prod-image-build.yml still build them as before.

Generated-by: Claude Opus 5
2026-11-04 01:04:28 +01:00

558 lines
21 KiB
Python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
import os
import re
import shlex
import shutil
import subprocess
import tarfile
from pathlib import Path
from unittest.mock import patch
import pytest
import yaml
from ci import prek_cache_markers
ROOT = Path(__file__).resolve().parents[3]
PREK_ACTION = ".github/actions/install-prek/action.yml"
def load_yaml(path):
return yaml.load((ROOT / path).read_text(), Loader=yaml.BaseLoader)
def find_step(path, *, step_id=None, name=None):
steps = load_yaml(path)["runs"]["steps"]
return next(
step
for step in steps
if (step_id is not None and step.get("id") == step_id)
or (name is not None and step.get("name") == name)
)
def run_shell(script, env):
return subprocess.run(
["bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", script],
cwd=ROOT,
env={**os.environ, **env},
capture_output=True,
text=True,
timeout=15,
check=False,
)
@pytest.fixture
def sandbox(tmp_path):
home = tmp_path / "home"
home.mkdir()
output = tmp_path / "output"
output.touch()
return {"HOME": str(home), "GITHUB_OUTPUT": str(output), "HOOKS": ""}
def read_outputs(env):
return dict(line.split("=", 1) for line in Path(env["GITHUB_OUTPUT"]).read_text().splitlines())
def run_cache_step(step, env, tmp_path):
script = step["run"].replace("/tmp/cache-prek.tar.gz", str(tmp_path / "cache-prek.tar.gz"))
return run_shell(script, env)
def _extract_tar_archive(handle: tarfile.TarFile, path: Path) -> None:
if hasattr(tarfile, "data_filter"):
handle.extractall(path, filter="fully_trusted")
else:
handle.extractall(path)
@pytest.fixture
def fake_tools(tmp_path):
tools = tmp_path / "bin"
tools.mkdir()
log = tmp_path / "commands.log"
command = tools / "command"
command.write_text(
r"""#!/usr/bin/env bash
name="${0##*/}"
printf '%s\n' "${name} $*" >> "${COMMAND_LOG}"
if [[ -n "${FAIL_MATCH:-}" && "${name} $*" == *"${FAIL_MATCH}"* ]]; then
exit 42
fi
if [[ "${name}" == "prek" ]]; then
count_file="${COMMAND_COUNT}"
count=0
[[ ! -f "${count_file}" ]] || count=$(<"${count_file}")
count=$((count + 1))
printf '%s' "${count}" > "${count_file}"
if [[ "${WRITE_MARKER_ATTEMPT:-}" == "${count}" ]]; then
marker="${HOME}/.cache/prek/hooks/python-new/.prek-hook.json"
mkdir -p "${marker%/*}"
printf '{"schema_version":1}' > "${marker}"
fi
if [[ "${WRITE_LOG_ONLY:-false}" == "true" ]]; then
mkdir -p "${HOME}/.cache/prek"
printf 'diagnostic\n' >> "${HOME}/.cache/prek/prek.log"
fi
if (( count <= ${FAIL_ATTEMPTS:-0} )); then
exit 42
fi
fi
"""
)
command.chmod(0o755)
for name in ("prek", "sleep"):
(tools / name).symlink_to(command)
return {
"PATH": f"{tools}:{os.environ['PATH']}",
"COMMAND_LOG": str(log),
"COMMAND_COUNT": str(tmp_path / "command-count"),
}
def read_commands(env):
return [shlex.split(line) for line in Path(env["COMMAND_LOG"]).read_text().splitlines()]
def test_cache_key_keeps_only_environment_inputs():
step = find_step(PREK_ACTION, step_id="cache-key")
assert "cache-prek-v13-${PLATFORM}" in step["run"]
assert "python${PYTHON_VERSION}" in step["run"]
assert "uv${UV_VERSION}" in step["run"]
assert "prek${PREK_VERSION}" in step["run"]
assert step["env"]["PREK_CONFIG_HASH"] == "${{ hashFiles('**/.pre-commit-config.yaml') }}"
@pytest.mark.parametrize(
("inputs", "expected_save", "expected_reason"),
[
({}, False, "reader"),
({"SAVE_CACHE": "true", "STASH_HIT": "false"}, True, "cache-miss"),
({"SAVE_CACHE": "true", "TAR_RESTORED": "false"}, True, "extraction-failed"),
({"SAVE_CACHE": "true", "CACHE_CHANGED": "true"}, True, "cache-repaired"),
({"SAVE_CACHE": "true", "EVENT_NAME": "schedule"}, True, "non-pr-republication"),
({"SAVE_CACHE": "true", "EVENT_NAME": "push"}, True, "non-pr-republication"),
({"SAVE_CACHE": "true", "EVENT_NAME": "workflow_dispatch"}, True, "non-pr-republication"),
(
{"SAVE_CACHE": "true", "CHANGE_DETECTION_UNCERTAIN": "true"},
True,
"change-detection-uncertain",
),
({"SAVE_CACHE": "true"}, False, "unchanged"),
({"HOOKS": "mypy-providers"}, False, "selected-hooks"),
({"HOOKS": "mypy-providers", "SAVE_CACHE": "true", "STASH_HIT": "false"}, False, "selected-hooks"),
],
)
def test_cache_refresh_policy(sandbox, inputs, expected_save, expected_reason):
env = {
**sandbox,
"SAVE_CACHE": "false",
"STASH_HIT": "true",
"TAR_RESTORED": "true",
"CACHE_CHANGED": "false",
"CHANGE_DETECTION_UNCERTAIN": "false",
"EVENT_NAME": "pull_request",
**inputs,
}
result = run_shell(find_step(PREK_ACTION, step_id="cache-policy")["run"], env)
assert result.returncode == 0, result.stderr
assert read_outputs(env)["save"] == str(expected_save).lower()
assert read_outputs(env)["reason"] == expected_reason
def test_restored_hooks_are_always_validated(sandbox, fake_tools):
marker = Path(sandbox["HOME"]) / ".cache/prek/hooks/python-existing/.prek-hook.json"
marker.parent.mkdir(parents=True)
marker.write_text('{"schema_version":1}')
step = find_step(PREK_ACTION, step_id="install-hooks")
assert "if" not in step
result = run_shell(step["run"], {**sandbox, **fake_tools, "WRITE_LOG_ONLY": "true"})
assert result.returncode == 0, result.stderr
assert [
"prek",
"install-hooks",
"--skip",
"run-skill-eval",
"--skip",
"run-skill-eval-codex",
"--skip",
"view-skill-eval",
] in read_commands(fake_tools)
assert read_outputs(sandbox)["cache-changed"] == "false"
assert read_outputs(sandbox)["change-detection-uncertain"] == "false"
@pytest.mark.parametrize(
("hooks", "expected_arguments", "expected_reason"),
[
(
"compile-fab-assets compile-edge-assets",
["compile-fab-assets", "compile-edge-assets"],
"selected-hooks",
),
(
"compile-fab-assets\ncompile-edge-assets\n",
["compile-fab-assets", "compile-edge-assets"],
"selected-hooks",
),
(
" \n",
["--skip", "run-skill-eval", "--skip", "run-skill-eval-codex", "--skip", "view-skill-eval"],
"cache-miss",
),
],
ids=("space-separated", "newline-separated", "whitespace-only"),
)
def test_hooks_input_decides_what_is_prepared_and_saved(
sandbox, fake_tools, hooks, expected_arguments, expected_reason
):
install_step = find_step(PREK_ACTION, step_id="install-hooks")
policy_step = find_step(PREK_ACTION, step_id="cache-policy")
assert install_step["env"]["HOOKS"] == policy_step["env"]["HOOKS"] == "${{ inputs.hooks }}"
result = run_shell(install_step["run"], {**sandbox, **fake_tools, "HOOKS": hooks})
assert result.returncode == 0, result.stderr
assert read_commands(fake_tools) == [["prek", "install-hooks", *expected_arguments]]
result = run_shell(
policy_step["run"], {**sandbox, "HOOKS": hooks, "SAVE_CACHE": "true", "STASH_HIT": "false"}
)
assert result.returncode == 0, result.stderr
assert read_outputs(sandbox)["reason"] == expected_reason
def find_hatch_build_hooks():
return {
hook
for path in (*ROOT.glob("providers/*/hatch_build.py"), *ROOT.glob("providers/*/*/hatch_build.py"))
for hook in re.findall(r'"prek", "run", "([\w-]+)"', path.read_text())
}
def find_hooks_run_by_job(job):
hooks = set()
for step in job["steps"]:
command = step.get("run", "")
if "prepare-provider-distributions" in command:
hooks |= find_hatch_build_hooks()
hooks.update(re.findall(r"^prek .* ([\w-]+) --all-files$", command, re.MULTILINE))
return hooks
def test_selected_hooks_match_the_hooks_each_job_runs():
selections = {
f"{workflow.name}:{job_id}": (set(step["with"]["hooks"].split()), find_hooks_run_by_job(job))
for workflow in (ROOT / ".github/workflows").glob("*.yml")
for job_id, job in load_yaml(workflow)["jobs"].items()
for step in job.get("steps", [])
if step.get("uses") == "./.github/actions/install-prek" and "hooks" in step.get("with", {})
}
assert sorted(selections) == [
"ci-amd.yml:migration-round-trip",
"ci-amd.yml:mypy-providers",
"ci-arm.yml:migration-round-trip",
"ci-arm.yml:mypy-providers",
"generate-constraints.yml:generate-constraints-matrix",
"prod-packages-build.yml:build-prod-packages",
"test-providers.yml:prepare-install-verify-provider-distributions",
"test-providers.yml:providers-compatibility-tests-matrix",
]
for site, (selected, run) in selections.items():
assert selected == run, (
f"Set the hooks input of the install-prek step in {site} to: {' '.join(sorted(run))}"
)
@pytest.mark.parametrize("cache_state", ("missing", "empty", "renamed-markers", "log-only"))
@pytest.mark.parametrize("save_cache", ("true", "false"))
def test_markerless_cache_refresh_policy(sandbox, fake_tools, cache_state, save_cache):
cache = Path(sandbox["HOME"]) / ".cache/prek"
if cache_state == "empty":
cache.mkdir(parents=True)
elif cache_state == "renamed-markers":
marker = cache / "environments/python-one/.new-marker.json"
marker.parent.mkdir(parents=True)
marker.write_text("{}")
result = run_shell(
find_step(PREK_ACTION, step_id="install-hooks")["run"],
{**sandbox, **fake_tools, "WRITE_LOG_ONLY": str(cache_state == "log-only").lower()},
)
assert result.returncode == 0, result.stderr
outputs = read_outputs(sandbox)
uncertain = cache_state in ("renamed-markers", "log-only")
assert outputs["cache-changed"] == "false"
assert outputs["change-detection-uncertain"] == str(uncertain).lower()
result = run_shell(
find_step(PREK_ACTION, step_id="cache-policy")["run"],
{
**sandbox,
"SAVE_CACHE": save_cache,
"STASH_HIT": "true",
"TAR_RESTORED": "true",
"CACHE_CHANGED": outputs["cache-changed"],
"CHANGE_DETECTION_UNCERTAIN": outputs["change-detection-uncertain"],
"EVENT_NAME": "pull_request",
},
)
assert result.returncode == 0, result.stderr
outputs = read_outputs(sandbox)
assert outputs["save"] == str(uncertain and save_cache == "true").lower()
assert outputs["reason"] == (
"reader" if save_cache == "false" else "change-detection-uncertain" if uncertain else "unchanged"
)
def test_change_detection_spans_failed_then_successful_attempts(sandbox, fake_tools):
result = run_shell(
find_step(PREK_ACTION, step_id="install-hooks")["run"],
{**sandbox, **fake_tools, "FAIL_ATTEMPTS": "1", "WRITE_MARKER_ATTEMPT": "1"},
)
assert result.returncode == 0, result.stderr
assert len([cmd for cmd in read_commands(fake_tools) if cmd[:2] == ["prek", "install-hooks"]]) == 2
assert read_outputs(sandbox)["cache-changed"] == "true"
def test_unreadable_installation_metadata_is_uncertain(sandbox, fake_tools):
marker = Path(sandbox["HOME"]) / ".cache/prek/hooks/python-broken/.prek-hook.json"
marker.mkdir(parents=True)
result = run_shell(find_step(PREK_ACTION, step_id="install-hooks")["run"], {**sandbox, **fake_tools})
assert result.returncode == 0, result.stderr
assert read_outputs(sandbox)["cache-changed"] == "false"
assert read_outputs(sandbox)["change-detection-uncertain"] == "true"
def test_hook_install_failure_remains_fatal(sandbox, fake_tools):
assert "if" not in find_step(PREK_ACTION, step_id="cache-policy")
assert "continue-on-error" not in find_step(PREK_ACTION, step_id="install-hooks")
result = run_shell(
find_step(PREK_ACTION, step_id="install-hooks")["run"],
{**sandbox, **fake_tools, "FAIL_MATCH": "prek install-hooks"},
)
assert result.returncode != 0
assert len([cmd for cmd in read_commands(fake_tools) if cmd[:2] == ["prek", "install-hooks"]]) == 4
def test_marker_snapshot_uses_relative_paths_and_exact_contents(tmp_path):
cache = tmp_path / "prek"
hook_marker = cache / "hooks/python-one/.prek-hook.json"
repo_marker = cache / "repos/repo-one/.prek-repo.json"
hook_marker.parent.mkdir(parents=True)
repo_marker.parent.mkdir(parents=True)
hook_marker.write_bytes(b'{"hook": 1}\n')
repo_marker.write_bytes(b'{"repo": 1}\n')
snapshot = prek_cache_markers.snapshot_markers(cache)
assert set(snapshot) == {"hooks/python-one/.prek-hook.json", "repos/repo-one/.prek-repo.json"}
assert snapshot == prek_cache_markers.snapshot_markers(cache)
def test_repaired_archive_is_reused_without_another_save(sandbox, fake_tools, tmp_path):
cache = Path(sandbox["HOME"]) / ".cache/prek"
(cache / "hooks/python-incomplete").mkdir(parents=True)
marker = cache / "hooks/python-existing/.prek-hook.json"
marker.parent.mkdir(parents=True)
marker.write_text('{"schema_version":1}')
install_step = find_step(PREK_ACTION, step_id="install-hooks")
policy_step = find_step(PREK_ACTION, step_id="cache-policy")
first_install = run_shell(install_step["run"], {**sandbox, **fake_tools, "WRITE_MARKER_ATTEMPT": "1"})
assert first_install.returncode == 0, first_install.stderr
first_outputs = read_outputs(sandbox)
assert first_outputs["cache-changed"] == "true"
Path(sandbox["GITHUB_OUTPUT"]).write_text("")
policy_env = {
**sandbox,
"SAVE_CACHE": "true",
"STASH_HIT": "true",
"TAR_RESTORED": "true",
"CACHE_CHANGED": "true",
"CHANGE_DETECTION_UNCERTAIN": "false",
"EVENT_NAME": "pull_request",
}
assert run_shell(policy_step["run"], policy_env).returncode == 0
assert read_outputs(sandbox) == {"save": "true", "reason": "cache-repaired"}
Path(sandbox["GITHUB_OUTPUT"]).write_text("")
archive_step = find_step(PREK_ACTION, step_id="archive-prek")
assert run_cache_step(archive_step, sandbox, tmp_path).returncode == 0
shutil.rmtree(cache)
Path(sandbox["GITHUB_OUTPUT"]).write_text("")
restore_step = find_step(PREK_ACTION, step_id="restore-prek-tar")
assert run_cache_step(restore_step, sandbox, tmp_path).returncode == 0
Path(sandbox["GITHUB_OUTPUT"]).write_text("")
second_install = run_shell(install_step["run"], {**sandbox, **fake_tools})
assert second_install.returncode == 0, second_install.stderr
second_outputs = read_outputs(sandbox)
assert second_outputs["cache-changed"] == "false"
Path(sandbox["GITHUB_OUTPUT"]).write_text("")
second_policy_env = {
**policy_env,
"CACHE_CHANGED": "false",
}
assert run_shell(policy_step["run"], second_policy_env).returncode == 0
assert read_outputs(sandbox) == {"save": "false", "reason": "unchanged"}
@pytest.mark.parametrize(
("supports_data_filter", "expected_kwargs"),
[
(True, {"filter": "fully_trusted"}),
(False, {}),
],
)
def test_extract_tar_archive_uses_supported_filter(
tmp_path, monkeypatch, supports_data_filter, expected_kwargs
):
content = tmp_path / "content"
content.mkdir()
(content / "payload").write_text("cache data")
archive = tmp_path / "cache.tar.gz"
with tarfile.open(archive, "w:gz") as handle:
handle.add(content, arcname="cache")
if supports_data_filter:
monkeypatch.setattr(tarfile, "data_filter", lambda member, path: member, raising=False)
else:
monkeypatch.delattr(tarfile, "data_filter", raising=False)
with patch.object(tarfile.TarFile, "extractall", autospec=True) as extractall:
with tarfile.open(archive, "r:gz") as handle:
_extract_tar_archive(handle, tmp_path)
extractall.assert_called_once_with(handle, tmp_path, **expected_kwargs)
@pytest.mark.skipif(
os.environ.get("RUN_PREK_INTEGRATION") != "1",
reason="Set RUN_PREK_INTEGRATION=1 to exercise real prek 0.5.2",
)
def test_real_prek_repair_and_reuse(tmp_path):
repository = tmp_path / "repository"
repository.mkdir()
subprocess.run(["git", "init", "-q"], cwd=repository, check=True)
(repository / ".pre-commit-config.yaml").write_text(
"""\
repos:
- repo: local
hooks:
- id: local-python
name: local python
entry: python -c 'print("ok")'
language: python
pass_filenames: false
"""
)
cache = tmp_path / "prek-cache"
env = {**os.environ, "PREK_HOME": str(cache)}
command = ["uvx", "--from", "prek==0.5.2", "prek", "install-hooks"]
def install():
return subprocess.run(
command,
cwd=repository,
env=env,
capture_output=True,
text=True,
timeout=180,
check=False,
)
first_install = install()
assert first_install.returncode == 0, first_install.stderr
marker = next((cache / "hooks").glob("*/.prek-hook.json"))
marker.unlink()
before_repair = prek_cache_markers.snapshot_markers(cache)
repair = install()
assert repair.returncode == 0, repair.stderr
repaired = prek_cache_markers.snapshot_markers(cache)
assert repaired != before_repair
archive = tmp_path / "prek-cache.tar.gz"
with tarfile.open(archive, "w:gz") as handle:
handle.add(cache, arcname="prek-cache")
shutil.rmtree(cache)
with tarfile.open(archive, "r:gz") as handle:
_extract_tar_archive(handle, tmp_path)
before_reuse = prek_cache_markers.snapshot_markers(cache)
reuse = install()
assert reuse.returncode == 0, reuse.stderr
assert prek_cache_markers.snapshot_markers(cache) == before_reuse
@pytest.mark.parametrize("payload", ("missing", "corrupt", "wrong-directory"))
def test_unusable_archive_is_a_miss(sandbox, tmp_path, payload):
archive = tmp_path / "cache-prek.tar.gz"
if payload == "corrupt":
archive.write_bytes(b"not a gzip archive")
elif payload == "wrong-directory":
other = tmp_path / "other"
other.mkdir()
with tarfile.open(archive, "w:gz") as handle:
handle.add(other, arcname=".cache/other")
result = run_cache_step(find_step(PREK_ACTION, step_id="restore-prek-tar"), sandbox, tmp_path)
assert result.returncode == 0, result.stderr
assert read_outputs(sandbox)["tar-restored"] == "false"
def test_archive_roundtrip_preserves_permissions_links_and_contents(sandbox, tmp_path):
cache = Path(sandbox["HOME"]) / ".cache/prek"
cache.mkdir(parents=True)
executable = cache / "python"
executable.write_text("payload\n")
executable.chmod(0o751)
(cache / "link").symlink_to("python")
result = run_cache_step(find_step(PREK_ACTION, step_id="archive-prek"), sandbox, tmp_path)
assert result.returncode == 0, result.stderr
shutil.rmtree(cache)
result = run_cache_step(find_step(PREK_ACTION, step_id="restore-prek-tar"), sandbox, tmp_path)
assert result.returncode == 0, result.stderr
assert read_outputs(sandbox)["tar-restored"] == "true"
assert executable.read_text() == "payload\n"
assert executable.stat().st_mode & 0o777 == 0o751
assert (cache / "link").is_symlink()
assert os.readlink(cache / "link") == "python"
def test_missing_cache_cannot_be_saved(sandbox, tmp_path):
result = run_cache_step(find_step(PREK_ACTION, step_id="archive-prek"), sandbox, tmp_path)
assert result.returncode != 0
assert "duration-seconds" not in read_outputs(sandbox)
def test_cache_cleanup_preserves_unrelated_cache(sandbox, tmp_path):
home = Path(sandbox["HOME"])
for name in ("prek", "uv"):
(home / ".cache" / name).mkdir(parents=True)
archive = tmp_path / "cache-prek.tar.gz"
archive.write_bytes(b"stale")
result = run_cache_step(
find_step(PREK_ACTION, name="Clear local prek cache before restore"), sandbox, tmp_path
)
assert result.returncode == 0, result.stderr
assert not (home / ".cache/prek").exists()
assert (home / ".cache/uv").is_dir()
assert not archive.exists()