This is an automated email from the ASF dual-hosted git repository.
jscheffl pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-1-test by this push:
new 4256cc34029 [v3-1-test] Fix flaky tests related to github api rate
limits (#59879) (#59884)
4256cc34029 is described below
commit 4256cc34029526a2b4b03ba150dbed973f0ac900
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Dec 29 14:07:15 2025 +0100
[v3-1-test] Fix flaky tests related to github api rate limits (#59879)
(#59884)
- Don't try to access the json of a non-200 response.
- Replace API calls with mocks.
(cherry picked from commit dae3441b95e427ab8e92c1a89bf5c19c71945e20)
Co-authored-by: Dev-iL <[email protected]>
---
.../src/airflow_breeze/utils/selective_checks.py | 13 ++-
dev/breeze/tests/conftest.py | 8 ++
dev/breeze/tests/test_selective_checks.py | 98 +++++++++++++++++++++-
3 files changed, 116 insertions(+), 3 deletions(-)
diff --git a/dev/breeze/src/airflow_breeze/utils/selective_checks.py
b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
index 9e71370e247..3f6bb5a26af 100644
--- a/dev/breeze/src/airflow_breeze/utils/selective_checks.py
+++ b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
@@ -1318,7 +1318,11 @@ class SelectiveChecks:
response = requests.get(url, headers=headers, params=payload)
if response.status_code != 200:
- get_console().print(f"[red]Error while listing workflow runs
error: {response.json()}.\n")
+ try:
+ error_msg = response.json()
+ except ValueError:
+ error_msg = response.text[:200] # Truncate long HTML responses
+ get_console().print(f"[red]Error while listing workflow runs
error: {error_msg}.\n")
return None
runs = response.json().get("workflow_runs", [])
if not runs:
@@ -1328,6 +1332,13 @@ class SelectiveChecks:
return None
jobs_url = runs[0].get("jobs_url")
jobs_response = requests.get(jobs_url, headers=headers)
+ if jobs_response.status_code != 200:
+ try:
+ error_msg = jobs_response.json()
+ except ValueError:
+ error_msg = jobs_response.text[:200]
+ get_console().print(f"[red]Error while listing jobs error:
{error_msg}.\n")
+ return None
jobs = jobs_response.json().get("jobs", [])
if not jobs:
get_console().print("[yellow]No jobs information found for jobs
%s.\n", jobs_url)
diff --git a/dev/breeze/tests/conftest.py b/dev/breeze/tests/conftest.py
index f968e10c812..68a1d4cf8d1 100644
--- a/dev/breeze/tests/conftest.py
+++ b/dev/breeze/tests/conftest.py
@@ -36,3 +36,11 @@ def pytest_unconfigure(config):
@pytest.fixture(autouse=True)
def clear_clearable_cache():
clear_all_cached_functions()
+
+
[email protected]
+def json_decode_error():
+ """Provide a requests.exceptions.JSONDecodeError for mocking API
failures."""
+ import requests
+
+ return requests.exceptions.JSONDecodeError("", "", 0)
diff --git a/dev/breeze/tests/test_selective_checks.py
b/dev/breeze/tests/test_selective_checks.py
index 714a157519d..3c1808691c0 100644
--- a/dev/breeze/tests/test_selective_checks.py
+++ b/dev/breeze/tests/test_selective_checks.py
@@ -1760,12 +1760,26 @@ def test_expected_output_pull_request_v2_7(
),
],
)
[email protected]("os.environ", {"GITHUB_TOKEN": "test_token"})
+@patch("requests.get")
def test_expected_output_push(
+ mock_get,
files: tuple[str, ...],
pr_labels: tuple[str, ...],
default_branch: str,
expected_outputs: dict[str, str],
):
+ # Mock GitHub API calls for runner_type property (used in PUSH events)
+ workflow_response = Mock()
+ workflow_response.status_code = 200
+ workflow_response.json.return_value = {"workflow_runs": [{"jobs_url":
"https://api.github.com/jobs/123"}]}
+ jobs_response = Mock()
+ jobs_response.status_code = 200
+ jobs_response.json.return_value = {
+ "jobs": [{"name": "Basic tests (ubuntu-22.04)", "labels":
["ubuntu-22.04"]}]
+ }
+ mock_get.side_effect = [workflow_response, jobs_response]
+
stderr = SelectiveChecks(
files=files,
commit_ref=NEUTRAL_COMMIT,
@@ -1977,7 +1991,20 @@ def test_expected_output_pull_request_target(
GithubEvents.SCHEDULE,
],
)
-def
test_no_commit_provided_trigger_full_build_for_any_event_type(github_event):
[email protected]("os.environ", {"GITHUB_TOKEN": "test_token"})
+@patch("requests.get")
+def test_no_commit_provided_trigger_full_build_for_any_event_type(mock_get,
github_event):
+ # Mock GitHub API calls for runner_type property (used in PUSH/SCHEDULE
events)
+ workflow_response = Mock()
+ workflow_response.status_code = 200
+ workflow_response.json.return_value = {"workflow_runs": [{"jobs_url":
"https://api.github.com/jobs/123"}]}
+ jobs_response = Mock()
+ jobs_response.status_code = 200
+ jobs_response.json.return_value = {
+ "jobs": [{"name": "Basic tests (ubuntu-22.04)", "labels":
["ubuntu-22.04"]}]
+ }
+ mock_get.side_effect = [workflow_response, jobs_response]
+
stderr = SelectiveChecks(
files=(),
commit_ref="",
@@ -2013,7 +2040,20 @@ def
test_no_commit_provided_trigger_full_build_for_any_event_type(github_event):
GithubEvents.SCHEDULE,
],
)
-def test_files_provided_trigger_full_build_for_any_event_type(github_event):
[email protected]("os.environ", {"GITHUB_TOKEN": "test_token"})
+@patch("requests.get")
+def test_files_provided_trigger_full_build_for_any_event_type(mock_get,
github_event):
+ # Mock GitHub API calls for runner_type property (used in PUSH/SCHEDULE
events)
+ workflow_response = Mock()
+ workflow_response.status_code = 200
+ workflow_response.json.return_value = {"workflow_runs": [{"jobs_url":
"https://api.github.com/jobs/123"}]}
+ jobs_response = Mock()
+ jobs_response.status_code = 200
+ jobs_response.json.return_value = {
+ "jobs": [{"name": "Basic tests (ubuntu-22.04)", "labels":
["ubuntu-22.04"]}]
+ }
+ mock_get.side_effect = [workflow_response, jobs_response]
+
stderr = SelectiveChecks(
files=(
"airflow-core/src/airflow/ui/src/pages/Run/Details.tsx",
@@ -2476,6 +2516,7 @@ def test_get_job_label(mock_get):
workflow_response.json.return_value = {"workflow_runs": [{"jobs_url":
"https://api.github.com/jobs/123"}]}
jobs_response = Mock()
+ jobs_response.status_code = 200
jobs_response.json.return_value = {
"jobs": [
{"name": "Basic tests (ubuntu-22.04)", "labels": ["ubuntu-22.04"]},
@@ -2505,6 +2546,7 @@ def test_get_job_label_not_found(mock_get):
workflow_response.json.return_value = {"workflow_runs": [{"jobs_url":
"https://api.github.com/jobs/123"}]}
jobs_response = Mock()
+ jobs_response.status_code = 200
jobs_response.json.return_value = {
"jobs": [
{"name": "Basic tests (ubuntu-22.04)", "labels": []},
@@ -2519,6 +2561,57 @@ def test_get_job_label_not_found(mock_get):
assert result is None
[email protected](
+ ("workflow_status", "jobs_status", "expected_result"),
+ [
+ pytest.param(504, 200, None, id="workflow_api_504_error"),
+ pytest.param(200, 503, None, id="jobs_api_503_error"),
+ pytest.param(200, 200, "ubuntu-22.04", id="both_apis_200_success"),
+ ],
+)
+@patch("requests.get")
[email protected]("os.environ", {"GITHUB_TOKEN": "test_token"})
+def test_get_job_label_api_status_codes(
+ mock_get, workflow_status, jobs_status, expected_result, json_decode_error
+):
+ """Test that get_job_label handles various HTTP status codes correctly."""
+ selective_checks = SelectiveChecks(
+ files=(),
+ github_event=GithubEvents.PULL_REQUEST,
+ github_repository="apache/airflow",
+ github_context_dict={},
+ )
+
+ workflow_response = Mock()
+ workflow_response.status_code = workflow_status
+ if workflow_status == 200:
+ workflow_response.json.return_value = {
+ "workflow_runs": [{"jobs_url": "https://api.github.com/jobs/123"}]
+ }
+ else:
+ workflow_response.json.side_effect = json_decode_error
+ workflow_response.text = "<html>Gateway Timeout</html>"
+
+ jobs_response = Mock()
+ jobs_response.status_code = jobs_status
+ if jobs_status == 200:
+ jobs_response.json.return_value = {
+ "jobs": [
+ {"name": "Basic tests (ubuntu-22.04)", "labels":
["ubuntu-22.04"]},
+ {"name": "Other job", "labels": ["ubuntu-22.04"]},
+ ]
+ }
+ else:
+ jobs_response.json.side_effect = json_decode_error
+ jobs_response.text = "<html>Service Unavailable</html>"
+
+ mock_get.side_effect = [workflow_response, jobs_response]
+
+ result = selective_checks.get_job_label("push", "main")
+
+ assert result == expected_result
+
+
def test_runner_type_pr():
selective_checks = SelectiveChecks(github_event=GithubEvents.PULL_REQUEST)
@@ -2542,6 +2635,7 @@ def test_runner_type_schedule(mock_get):
workflow_response.json.return_value = {"workflow_runs": [{"jobs_url":
"https://api.github.com/jobs/123"}]}
jobs_response = Mock()
+ jobs_response.status_code = 200
jobs_response.json.return_value = {
"jobs": [
{"name": "Basic tests / Test git clone on Windows", "labels":
["windows-2025"]},