This is an automated email from the ASF dual-hosted git repository.

shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 07c9ceba527 Fail provider DB test jobs when their tests fail (#74172)
07c9ceba527 is described below

commit 07c9ceba527755955b88e9829e6d8a1824e8c309
Author: Yossi Eliaz <[email protected]>
AuthorDate: Sun Oct 4 09:49:42 2026 +0300

    Fail provider DB test jobs when their tests fail (#74172)
    
    * Fail provider DB test jobs when their tests fail
    
    run_unit_tests.sh read exit code 1 from the provider DB test run as "no
    DB tests were collected" and passed the job. A parallel breeze run exits
    1 whenever any test type fails, so failing provider DB tests have been
    reported green; only a timeout (exit 2) still failed the job. Core DB
    jobs never had the exception.
    
    A test type without DB tests is still not a failure: pytest exits 5 once
    --run-db-tests-only has deselected all of its tests. That can only be
    recognised per test type, before the parallel run folds every return
    code into 1, so breeze now decides it there.
    
    * Guard provider DB exit handling against interrupted and killed runs
    
    Only an empty DB test selection may be normalized to success; test failures 
and process failures must retain their return codes.
---
 .../airflow_breeze/commands/testing_commands.py    | 10 +++++++
 dev/breeze/tests/test_run_test_args.py             | 35 ++++++++++++++++++++--
 scripts/ci/testing/run_unit_tests.sh               |  7 +----
 scripts/tests/ci/testing/test_run_unit_tests.py    | 15 ++++++++++
 4 files changed, 59 insertions(+), 8 deletions(-)

diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands.py 
b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
index d9032c6422b..279f05d2a86 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
@@ -202,6 +202,8 @@ TEST_PROGRESS_REGEXP = (
     
r"airflow-core/tests/.*|providers/.*/tests/.*|task-sdk/tests/.*|airflow-ctl/tests/.*|.*=====.*"
 )
 PERCENT_TEST_PROGRESS_REGEXP = r"^tests/.*\[[ \d%]*\].*|^\..*\[[ \d%]*\].*"
+# pytest.ExitCode.NO_TESTS_COLLECTED - spelled out so that breeze does not 
have to import pytest
+PYTEST_NO_TESTS_COLLECTED_EXIT_CODE = 5
 
 
 def _run_test(
@@ -291,6 +293,14 @@ def _run_test(
             output_outside_the_group=output_outside_the_group,
             env=env,
         )
+        if shell_params.run_db_tests_only and result.returncode == 
PYTEST_NO_TESTS_COLLECTED_EXIT_CODE:
+            # --run-db-tests-only deselects non-DB tests at collection time, 
so a test type without DB
+            # tests collects nothing. This must be decided here, per test 
type: the parallel run turns
+            # any non-zero code into exit code 1, which looks the same as a 
failing test.
+            get_console(output=output).print(
+                f"[info]No DB tests collected for {shell_params.test_type}. 
Nothing to run.[/]"
+            )
+            return 0, f"No DB tests collected: {shell_params.test_type}"
         if result.returncode != 0:
             notify_on_unhealthy_backend_container(
                 project_name=compose_project_name, 
backend=shell_params.backend, output=output
diff --git a/dev/breeze/tests/test_run_test_args.py 
b/dev/breeze/tests/test_run_test_args.py
index bac6d67288a..b146e61d305 100644
--- a/dev/breeze/tests/test_run_test_args.py
+++ b/dev/breeze/tests/test_run_test_args.py
@@ -27,8 +27,8 @@ from airflow_breeze.params.shell_params import ShellParams
 
 @pytest.fixture(autouse=True)
 def mock_run_command():
-    """We mock run_command to capture its call args; it returns nothing so 
mock training is unnecessary."""
-    with patch("airflow_breeze.commands.testing_commands.run_command") as mck:
+    """We mock run_command to capture its call args; tests that depend on its 
return code set it."""
+    with patch("airflow_breeze.commands.testing_commands.run_command", 
autospec=True) as mck:
         yield mck
 
 
@@ -154,6 +154,37 @@ def 
test_none_test_type_with_extra_args_does_not_skip(mock_run_command):
     assert mock_run_command.call_count >= 2  # compose down + compose run
 
 
[email protected](
+    ("run_db_tests_only", "pytest_exit_code", "expected_return_code"),
+    [
+        pytest.param(True, 5, 0, id="db-only-no-tests-collected"),
+        pytest.param(True, 0, 0, id="db-only-passed"),
+        pytest.param(True, 1, 1, id="db-only-test-failed"),
+        pytest.param(True, 2, 2, id="db-only-interrupted"),
+        pytest.param(True, 137, 137, id="db-only-killed"),
+        pytest.param(False, 5, 5, id="no-tests-collected-without-db-only"),
+    ],
+)
+def test_no_tests_collected_is_success_only_for_db_only_runs(
+    mock_run_command, run_db_tests_only, pytest_exit_code, expected_return_code
+):
+    """Pytest's "no tests collected" (5) passes only in DB-only runs; a 
failing test keeps its code."""
+    mock_run_command.return_value.returncode = pytest_exit_code
+    return_code, _ = _run_test(
+        shell_params=ShellParams(
+            test_group=GroupOfTests.PROVIDERS,
+            test_type="Providers[http]",
+            run_db_tests_only=run_db_tests_only,
+        ),
+        extra_pytest_args=(),
+        python_version="3.10",
+        output=None,
+        test_timeout=60,
+        skip_docker_compose_down=True,
+    )
+    assert return_code == expected_return_code
+
+
 @pytest.mark.parametrize(
     ("test_timeout", "expected_setup_teardown", "expected_execution"),
     [
diff --git a/scripts/ci/testing/run_unit_tests.sh 
b/scripts/ci/testing/run_unit_tests.sh
index cc0fe6c849e..bd54f8a3ce0 100755
--- a/scripts/ci/testing/run_unit_tests.sh
+++ b/scripts/ci/testing/run_unit_tests.sh
@@ -121,12 +121,7 @@ function providers_tests() {
         exit 1
     fi
     set -e
-    # If pytest returns exit code 1 (no tests collected) for DB-only runs, 
treat it as success
-    # to avoid failing CI when there are simply no DB tests defined for the 
group.
-    if [[ ${RESULT} == "1" && "${TEST_SCOPE}" == "DB" ]]; then
-        echo
-        echo "${COLOR_YELLOW}No DB tests were collected for ${TEST_GROUP}; 
treating as success.${COLOR_RESET}"
-    elif [[ ${RESULT} != "0" ]]; then
+    if [[ ${RESULT} != "0" ]]; then
         echo
         echo "${COLOR_RED}The ${TEST_GROUP} test ${TEST_SCOPE} failed! Giving 
up${COLOR_RESET}"
         echo
diff --git a/scripts/tests/ci/testing/test_run_unit_tests.py 
b/scripts/tests/ci/testing/test_run_unit_tests.py
index ecd6204c634..8270f3a45e4 100644
--- a/scripts/tests/ci/testing/test_run_unit_tests.py
+++ b/scripts/tests/ci/testing/test_run_unit_tests.py
@@ -36,3 +36,18 @@ def 
test_run_unit_tests_aborts_when_the_job_budget_is_missing_in_github_actions(
     )
     assert result.returncode == 1
     assert "JOB_START_EPOCH and JOB_TIMEOUT_MINUTES must both be set" in 
result.stdout
+
+
+def test_providers_db_tests_fail_when_breeze_fails(tmp_path):
+    """A parallel breeze run exits 1 when any test type fails, so that must 
fail the job, as for core."""
+    (tmp_path / "breeze").write_text("#!/usr/bin/env bash\nexit 1\n")
+    (tmp_path / "breeze").chmod(0o755)
+    result = subprocess.run(
+        ["bash", str(SCRIPT_PATH), "providers", "DB"],
+        env={"PATH": f"{tmp_path}:{MINIMAL_ENVIRONMENT['PATH']}"},
+        capture_output=True,
+        text=True,
+        check=False,
+    )
+    assert result.returncode == 1
+    assert "The providers test DB failed! Giving up" in result.stdout

Reply via email to