This is an automated email from the ASF dual-hosted git repository. potiuk pushed a commit to branch backport-ci-sync-v3-4-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 53fded7dcde991b8b019570b2578636ae96f6c4d Author: Jarek Potiuk <[email protected]> AuthorDate: Sun Oct 25 19:38:24 2026 +0100 Fail the local yml mounts prek hook when synchronization fails (#73785) * Fail the local yml mounts prek hook when synchronization fails The hook printed a red error and exited zero, so a failed synchronization passed as green and left the generated mounts in local.yml stale. The failure it is most likely to hide is a broken docker_command_utils.py, which is one of the two files that trigger the hook in the first place. * Run the real prek helper in the local yml mounts test Stubbing common_prek_utils made the test carry a second copy of the script's imports and a copy of the script itself, and left the helper the hook actually runs untested. A fake breeze on PATH is enough. (cherry picked from commit eddc34c4b445f945cef9340ac2117a67e5ac39b9) Generated-by: Claude Opus 5 --- scripts/ci/prek/local_yml_mounts.py | 2 + scripts/tests/ci/prek/test_local_yml_mounts.py | 79 ++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/scripts/ci/prek/local_yml_mounts.py b/scripts/ci/prek/local_yml_mounts.py index 5f699e56599..bb6694b4498 100755 --- a/scripts/ci/prek/local_yml_mounts.py +++ b/scripts/ci/prek/local_yml_mounts.py @@ -18,6 +18,7 @@ from __future__ import annotations import subprocess +import sys from common_prek_utils import console, initialize_breeze_prek @@ -29,3 +30,4 @@ res = subprocess.run( ) if res.returncode != 0: console.print("\n[red]Error when running local mounts synchronization.\n") + sys.exit(res.returncode) diff --git a/scripts/tests/ci/prek/test_local_yml_mounts.py b/scripts/tests/ci/prek/test_local_yml_mounts.py new file mode 100644 index 00000000000..2918c5d7120 --- /dev/null +++ b/scripts/tests/ci/prek/test_local_yml_mounts.py @@ -0,0 +1,79 @@ +# 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. +"""Tests for the exit code of local_yml_mounts.py.""" + +from __future__ import annotations + +import os +import subprocess +import sys +import textwrap +from pathlib import Path + +import pytest + +SCRIPT_PATH = Path(__file__).resolve().parents[3] / "ci" / "prek" / "local_yml_mounts.py" + +CALL_LOG_NAME = "breeze_calls.txt" + +BREEZE_STUB = textwrap.dedent( + """\ + #!/usr/bin/env python3 + import os + import sys + + with open(os.environ["BREEZE_STUB_CALL_LOG"], "a") as call_log: + call_log.write(" ".join(sys.argv[1:]) + "\\n") + + sys.exit(int(os.environ["BREEZE_STUB_RETURNCODE"])) + """ +) + + +def run_script(tmp_path: Path, breeze_returncode: int) -> subprocess.CompletedProcess: + bin_dir = tmp_path / "bin" + bin_dir.mkdir() + breeze_stub = bin_dir / "breeze" + breeze_stub.write_text(BREEZE_STUB) + breeze_stub.chmod(0o755) + + env = { + **os.environ, + "PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}", + "BREEZE_STUB_CALL_LOG": os.fspath(tmp_path / CALL_LOG_NAME), + "BREEZE_STUB_RETURNCODE": str(breeze_returncode), + } + # initialize_breeze_prek honours this by exiting 0 before it ever reaches breeze. + env.pop("SKIP_BREEZE_PREK_HOOKS", None) + + # The script exits when imported as a module, so it has to run as a subprocess. + return subprocess.run( + [sys.executable, os.fspath(SCRIPT_PATH)], + env=env, + capture_output=True, + text=True, + check=False, + ) + + [email protected]("breeze_returncode", [0, 1, 2]) +def test_hook_exits_with_the_breeze_return_code(tmp_path, breeze_returncode): + result = run_script(tmp_path, breeze_returncode) + + # Without this, an exit from initialize_breeze_prek's own preflight would satisfy the next line. + assert (tmp_path / CALL_LOG_NAME).exists(), f"breeze was never invoked: {result.stdout}{result.stderr}" + assert result.returncode == breeze_returncode
