Display complete pinpoint server error message
This CL adds a new module `requests` for wrapping authed session
requests. In case of a server response error, this module tries to get
an error message from `response.json()` and add it to the
`raise_for_status()` error message. Other modules and unit tests were
simplified as a result.
Bug: b/463582730
Change-Id: I94ec0ffb31917b1e7906a0905f7d9fc23d2e729a
Reviewed-on: https://chromium-review.googlesource.com/c/crossbench/+/7209260
Reviewed-by: Patrick Thier <pthier@chromium.org>
Commit-Queue: Maxim Sheshukov <maximsheshukov@google.com>
diff --git a/crossbench/pinpoint/cancel_job.py b/crossbench/pinpoint/cancel_job.py
index 90cbccc..037a605 100644
--- a/crossbench/pinpoint/cancel_job.py
+++ b/crossbench/pinpoint/cancel_job.py
@@ -6,19 +6,18 @@
import json
+from crossbench.pinpoint import requests
from crossbench.pinpoint.api import PINPOINT_CANCEL_JOB_API_URL
-from crossbench.pinpoint.auth import get_auth_session
from crossbench.pinpoint.helper import annotate
def cancel_job(job_id: str, reason: str) -> None:
"""Cancels a Pinpoint job."""
- authed_session = get_auth_session()
payload = {
"job_id": job_id,
"reason": reason,
}
with annotate("Cancelling Pinpoint job"):
- response = authed_session.post(PINPOINT_CANCEL_JOB_API_URL, data=payload)
+ response = requests.post(PINPOINT_CANCEL_JOB_API_URL, data=payload)
response.raise_for_status()
- print(json.dumps(response.json(), indent=2))
+ print(json.dumps(response.json(), indent=2))
diff --git a/crossbench/pinpoint/job_config.py b/crossbench/pinpoint/job_config.py
index 6e552fa..0f8673d 100644
--- a/crossbench/pinpoint/job_config.py
+++ b/crossbench/pinpoint/job_config.py
@@ -7,21 +7,20 @@
import json
from typing import Any
+from crossbench.pinpoint import requests
from crossbench.pinpoint.api import PINPOINT_JOB_API_URL_TEMPLATE
-from crossbench.pinpoint.auth import get_auth_session
from crossbench.pinpoint.config import PinpointTryJobConfig
from crossbench.pinpoint.helper import annotate
def fetch_job_config(job_id: str, full: bool = False) -> dict[str, Any]:
"""Fetches the configuration for a specific Pinpoint job."""
- authed_session = get_auth_session()
url = PINPOINT_JOB_API_URL_TEMPLATE.format(job_id=job_id)
params = {}
if full:
params["o"] = ["STATE", "ESTIMATE"]
with annotate("Fetching Job Config"):
- response = authed_session.get(url, params=params)
+ response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
diff --git a/crossbench/pinpoint/list_benchmarks.py b/crossbench/pinpoint/list_benchmarks.py
index fdcc8c0..8781357 100644
--- a/crossbench/pinpoint/list_benchmarks.py
+++ b/crossbench/pinpoint/list_benchmarks.py
@@ -4,15 +4,14 @@
from __future__ import annotations
+from crossbench.pinpoint import requests
from crossbench.pinpoint.api import CHROMEPERF_TEST_SUITES_API_URL
-from crossbench.pinpoint.auth import get_auth_session
from crossbench.pinpoint.helper import annotate
def fetch_benchmarks() -> list[str]:
"""Fetches the list of available benchmarks from the Chromeperf API."""
- authed_session = get_auth_session()
with annotate("Fetching benchmarks"):
- response = authed_session.post(CHROMEPERF_TEST_SUITES_API_URL)
+ response = requests.post(CHROMEPERF_TEST_SUITES_API_URL)
response.raise_for_status()
return response.json()
diff --git a/crossbench/pinpoint/list_bots.py b/crossbench/pinpoint/list_bots.py
index a03818b..78f09b5 100644
--- a/crossbench/pinpoint/list_bots.py
+++ b/crossbench/pinpoint/list_bots.py
@@ -4,15 +4,14 @@
from __future__ import annotations
+from crossbench.pinpoint import requests
from crossbench.pinpoint.api import PINPOINT_CONFIG_API_URL
-from crossbench.pinpoint.auth import get_auth_session
from crossbench.pinpoint.helper import annotate
def fetch_bots() -> list[str]:
"""Fetches the list of available Pinpoint bots from the Pinpoint API."""
- authed_session = get_auth_session()
with annotate("Fetching available bots"):
- response = authed_session.post(PINPOINT_CONFIG_API_URL)
+ response = requests.post(PINPOINT_CONFIG_API_URL)
response.raise_for_status()
return response.json()["configurations"]
diff --git a/crossbench/pinpoint/list_builds.py b/crossbench/pinpoint/list_builds.py
index 26a1e15..db532fb 100644
--- a/crossbench/pinpoint/list_builds.py
+++ b/crossbench/pinpoint/list_builds.py
@@ -11,8 +11,8 @@
from tabulate import tabulate
+from crossbench.pinpoint import requests
from crossbench.pinpoint.api import PINPOINT_BUILDS_API_URL_TEMPLATE
-from crossbench.pinpoint.auth import get_auth_session
from crossbench.pinpoint.format_time import DATETIME_FORMAT, format_time
from crossbench.pinpoint.helper import annotate
@@ -37,11 +37,10 @@
def fetch_builds(bot: str) -> list[Build]:
- authed_session = get_auth_session()
url = PINPOINT_BUILDS_API_URL_TEMPLATE.format(bot=bot)
with annotate(f"Fetching recent builds for '{bot}'"):
- response = authed_session.get(url)
+ response = requests.get(url)
response.raise_for_status()
return _convert_json_to_builds(response.json())
diff --git a/crossbench/pinpoint/list_jobs.py b/crossbench/pinpoint/list_jobs.py
index 48598d8..3b0bad6 100644
--- a/crossbench/pinpoint/list_jobs.py
+++ b/crossbench/pinpoint/list_jobs.py
@@ -10,54 +10,42 @@
import json
import sys
from concurrent.futures import ThreadPoolExecutor
-from typing import TYPE_CHECKING, Any, Final, Mapping
+from typing import Any, Final, Mapping
-import requests
import yaml
from tabulate import tabulate
+from crossbench.pinpoint import requests
from crossbench.pinpoint.api import JOB_SHORTEN_URL_TEMPLATE, \
PINPOINT_JOBS_API_URL, USERINFO_API_URL
-from crossbench.pinpoint.auth import get_auth_session
from crossbench.pinpoint.format_time import DATETIME_FORMAT, format_time
from crossbench.pinpoint.helper import annotate
from crossbench.pinpoint.list_format import ListFormatEnum
from crossbench.pinpoint.user import UserEnum
-if TYPE_CHECKING:
- from google.auth.transport import requests as auth_requests
-
def list_jobs(user: UserEnum | str, number: int, truncate: int | None,
output_format: ListFormatEnum) -> None:
- authed_session = get_auth_session()
+ emails_to_query = _fetch_user_emails(user)
- try:
- emails_to_query = _fetch_user_emails(authed_session, user)
+ jobs = []
+ with annotate("Fetching jobs"), ThreadPoolExecutor() as executor:
+ results = executor.map(lambda email: _fetch_jobs(number, email),
+ emails_to_query)
+ jobs = list(itertools.chain.from_iterable(results))
- jobs = []
- with annotate("fetching jobs"), ThreadPoolExecutor() as executor:
- results = executor.map(
- lambda email: _fetch_jobs(authed_session, number, email),
- emails_to_query)
- jobs = list(itertools.chain.from_iterable(results))
+ jobs.sort(key=lambda job: job.get("created", ""), reverse=True)
- jobs.sort(key=lambda job: job.get("created", ""), reverse=True)
+ if not jobs:
+ print("No jobs found.")
+ return
- if not jobs:
- print("No jobs found.")
- return
-
- _display_jobs(jobs[:number], output_format, user == UserEnum.ALL, truncate)
-
- except requests.exceptions.RequestException as e:
- print(f"An error occurred while fetching jobs: {e}")
+ _display_jobs(jobs[:number], output_format, user == UserEnum.ALL, truncate)
-def _fetch_user_emails(authed_session: auth_requests.AuthorizedSession,
- user: UserEnum | str) -> set[str | None]:
+def _fetch_user_emails(user: UserEnum | str) -> set[str | None]:
if user == UserEnum.ME:
- email = _get_user_email(authed_session)
+ email = _get_user_email()
username = email.split("@")[0]
return {email, f"{username}@google.com", f"{username}@chromium.org"}
if user == UserEnum.ALL:
@@ -65,16 +53,14 @@
return {user}
-def _get_user_email(authed_session: auth_requests.AuthorizedSession) -> str:
- with annotate("fetching user-email"):
- response = authed_session.get(USERINFO_API_URL)
+def _get_user_email() -> str:
+ with annotate("Fetching user-email"):
+ response = requests.get(USERINFO_API_URL)
response.raise_for_status()
return response.json()["email"]
-def _fetch_jobs(authed_session: auth_requests.AuthorizedSession,
- number: int,
- email: str | None = None) -> list[dict[str, Any]]:
+def _fetch_jobs(number: int, email: str | None = None) -> list[dict[str, Any]]:
jobs = []
next_cursor = None
params = {}
@@ -85,7 +71,7 @@
if next_cursor:
params["next_cursor"] = next_cursor
- response = authed_session.get(PINPOINT_JOBS_API_URL, params=params)
+ response = requests.get(PINPOINT_JOBS_API_URL, params=params)
response.raise_for_status()
data = response.json()
jobs.extend(data.get("jobs", []))
diff --git a/crossbench/pinpoint/list_stories.py b/crossbench/pinpoint/list_stories.py
index 0702833..84ff115 100644
--- a/crossbench/pinpoint/list_stories.py
+++ b/crossbench/pinpoint/list_stories.py
@@ -4,17 +4,16 @@
from __future__ import annotations
+from crossbench.pinpoint import requests
from crossbench.pinpoint.api import CHROMEPERF_DESCRIBE_API_URL
-from crossbench.pinpoint.auth import get_auth_session
from crossbench.pinpoint.helper import annotate
def fetch_stories(benchmark_name: str) -> list[str]:
"""Fetches the list of available stories for a given benchmark."""
- authed_session = get_auth_session()
params = {"test_suite": benchmark_name, "master": "ChromiumPerf"}
with annotate(f"Fetching stories for benchmark '{benchmark_name}'"):
- response = authed_session.post(CHROMEPERF_DESCRIBE_API_URL, params=params)
+ response = requests.post(CHROMEPERF_DESCRIBE_API_URL, params=params)
response.raise_for_status()
data = response.json()
return data.get("cases", [])
diff --git a/crossbench/pinpoint/patch_resolver.py b/crossbench/pinpoint/patch_resolver.py
index b4c712d..091f12f 100644
--- a/crossbench/pinpoint/patch_resolver.py
+++ b/crossbench/pinpoint/patch_resolver.py
@@ -8,7 +8,7 @@
import re
from urllib.parse import urlparse
-from crossbench.pinpoint.auth import get_auth_session
+from crossbench.pinpoint import requests
def resolve_patch(patch: str) -> str:
@@ -43,9 +43,7 @@
def _revision_to_url(change_id: str, patchset: str | None = None) -> str:
chromium_review_url = "https://chromium-review.googlesource.com"
- authed_session = get_auth_session()
- api_response = authed_session.get(chromium_review_url +
- f"/changes/{change_id}")
+ api_response = requests.get(chromium_review_url + f"/changes/{change_id}")
api_response.raise_for_status()
# Skip the protective prefix ")]}'"
diff --git a/crossbench/pinpoint/requests.py b/crossbench/pinpoint/requests.py
new file mode 100644
index 0000000..6eb344e
--- /dev/null
+++ b/crossbench/pinpoint/requests.py
@@ -0,0 +1,47 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from __future__ import annotations
+
+import json
+
+import requests
+
+from crossbench.pinpoint import auth
+
+
+def get(url: str, **kwargs) -> requests.Response:
+ return _method("GET", url, **kwargs)
+
+
+def post(url: str, **kwargs) -> requests.Response:
+ return _method("POST", url, **kwargs)
+
+
+class ServerError(requests.exceptions.HTTPError):
+
+ def __init__(self, error: requests.exceptions.HTTPError) -> None:
+ error_message = ""
+ try:
+ data = error.response.json()
+ if error_text := data.get("error"):
+ error_message = f"\n{error_text}"
+ else:
+ error_message = f"\n{data}"
+ except json.JSONDecodeError:
+ pass
+ super().__init__(str(error) + error_message, response=error.response)
+
+
+def _method(method: str, url: str, **kwargs) -> requests.Response:
+ response = auth.get_auth_session().request(method, url, **kwargs)
+ _raise_for_status(response)
+ return response
+
+
+def _raise_for_status(response: requests.Response) -> None:
+ try:
+ response.raise_for_status()
+ except requests.exceptions.HTTPError as e:
+ raise ServerError(e) from e
diff --git a/crossbench/pinpoint/start_job.py b/crossbench/pinpoint/start_job.py
index 87e9cf3..e108178 100644
--- a/crossbench/pinpoint/start_job.py
+++ b/crossbench/pinpoint/start_job.py
@@ -7,8 +7,8 @@
import json
from typing import TYPE_CHECKING
+from crossbench.pinpoint import requests
from crossbench.pinpoint.api import PINPOINT_START_JOB_API_URL
-from crossbench.pinpoint.auth import get_auth_session
from crossbench.pinpoint.helper import annotate
if TYPE_CHECKING:
@@ -17,9 +17,8 @@
def start_job(config: PinpointTryJobConfig) -> None:
"""Starts a new Pinpoint job."""
- authed_session = get_auth_session()
with annotate("Starting Pinpoint job"):
- response = authed_session.post(
+ response = requests.post(
PINPOINT_START_JOB_API_URL, data=config.to_request_dict())
response.raise_for_status()
print(json.dumps(response.json(), indent=2))
diff --git a/tests/crossbench/pinpoint/auth_session_mixin.py b/tests/crossbench/pinpoint/auth_session_mixin.py
deleted file mode 100644
index f647625..0000000
--- a/tests/crossbench/pinpoint/auth_session_mixin.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2025 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-from __future__ import annotations
-
-import unittest
-from typing import Final
-from unittest import mock
-
-import requests
-
-
-class MockAuthSessionMixin(unittest.TestCase):
- """Mixin to mock the get_auth_session function."""
-
- _get_auth_session_patch_target: Final[
- str] = "crossbench.pinpoint.auth.get_auth_session"
-
- def setUp(self):
- super().setUp()
- self.mock_get_auth_session = self.enterContext(
- mock.patch(self._get_auth_session_patch_target))
- self.mock_session = mock.Mock(spec=requests.Session)
- self.mock_get_auth_session.return_value = self.mock_session
diff --git a/tests/crossbench/pinpoint/requests_mixin.py b/tests/crossbench/pinpoint/requests_mixin.py
new file mode 100644
index 0000000..9b95d74
--- /dev/null
+++ b/tests/crossbench/pinpoint/requests_mixin.py
@@ -0,0 +1,19 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from __future__ import annotations
+
+import unittest
+from unittest import mock
+
+
+class MockRequestsMixin(unittest.TestCase):
+ """Mixin to mock the requests.get and requests.post functions."""
+
+ def setUp(self):
+ super().setUp()
+ self.mock_get = self.enterContext(
+ mock.patch("crossbench.pinpoint.requests.get"))
+ self.mock_post = self.enterContext(
+ mock.patch("crossbench.pinpoint.requests.post"))
diff --git a/tests/crossbench/pinpoint/test_config.py b/tests/crossbench/pinpoint/test_config.py
index 2df38e3..d9e8fc1 100644
--- a/tests/crossbench/pinpoint/test_config.py
+++ b/tests/crossbench/pinpoint/test_config.py
@@ -12,7 +12,7 @@
from crossbench.pinpoint.config import PinpointTryJobConfig, VariantConfig
from crossbench.pinpoint.list_builds import Build
from tests import test_helper
-from tests.crossbench.pinpoint.auth_session_mixin import MockAuthSessionMixin
+from tests.crossbench.pinpoint.requests_mixin import MockRequestsMixin
_TEST_PATCH: Final[
str] = "https://chromium-review.googlesource.com/c/crossbench/+/12345"
@@ -20,7 +20,7 @@
str] = "https://chromium-review.googlesource.com/c/crossbench/+/111/2"
-class VariantConfigTest(MockAuthSessionMixin):
+class VariantConfigTest(MockRequestsMixin):
_get_auth_session_patch_target = "crossbench.pinpoint.auth.get_auth_session"
def setUp(self):
@@ -135,7 +135,7 @@
})
-class PinpointTryJobConfigTest(MockAuthSessionMixin):
+class PinpointTryJobConfigTest(MockRequestsMixin):
_get_auth_session_patch_target = "crossbench.pinpoint.auth.get_auth_session"
def setUp(self):
diff --git a/tests/crossbench/pinpoint/test_job_config.py b/tests/crossbench/pinpoint/test_job_config.py
index 2bd9975..d8d4de3 100644
--- a/tests/crossbench/pinpoint/test_job_config.py
+++ b/tests/crossbench/pinpoint/test_job_config.py
@@ -4,7 +4,6 @@
from __future__ import annotations
-from typing import Final
from unittest import mock
import requests
@@ -15,7 +14,7 @@
from crossbench.pinpoint.job_config import convert_job_config, \
fetch_job_config, print_job_config
from tests import test_helper
-from tests.crossbench.pinpoint.auth_session_mixin import MockAuthSessionMixin
+from tests.crossbench.pinpoint.requests_mixin import MockRequestsMixin
_TEST_JOB_ID = "1234567890"
_TEST_JOB_RESPONSE = {
@@ -40,9 +39,7 @@
}
-class JobConfigTest(MockAuthSessionMixin):
- _get_auth_session_patch_target: Final[
- str] = "crossbench.pinpoint.job_config.get_auth_session"
+class JobConfigTest(MockRequestsMixin):
def setUp(self):
super().setUp()
@@ -56,7 +53,7 @@
mock_response.raise_for_status.return_value = None
return mock_response
- self.mock_session.get.side_effect = mock_post_side_effect
+ self.mock_get.side_effect = mock_post_side_effect
def test_fetch_job_config(self):
config_dict = fetch_job_config(_TEST_JOB_ID)
@@ -64,18 +61,15 @@
def test_fetch_job_config_full(self):
fetch_job_config(_TEST_JOB_ID, full=True)
- _, kwargs = self.mock_session.get.call_args
+ _, kwargs = self.mock_get.call_args
self.assertEqual(kwargs, {"params": {"o": ["STATE", "ESTIMATE"]}})
def test_fetch_job_config_api_error(self):
- self.mock_session.get.side_effect = requests.exceptions.HTTPError(
- "API Error")
+ self.mock_get.side_effect = requests.exceptions.HTTPError("API Error")
with self.assertRaises(MultiException):
fetch_job_config(_TEST_JOB_ID)
- self.mock_get_auth_session.assert_called_once()
-
def test_convert_job_config(self):
config_dict = fetch_job_config(job_id=_TEST_JOB_ID)
converted_config_dict = convert_job_config(config_dict)
diff --git a/tests/crossbench/pinpoint/test_list_benchmarks.py b/tests/crossbench/pinpoint/test_list_benchmarks.py
index 9920a56..954a603 100644
--- a/tests/crossbench/pinpoint/test_list_benchmarks.py
+++ b/tests/crossbench/pinpoint/test_list_benchmarks.py
@@ -4,7 +4,6 @@
from __future__ import annotations
-from typing import Final
from unittest import mock
import requests
@@ -13,12 +12,10 @@
from crossbench.pinpoint.api import CHROMEPERF_TEST_SUITES_API_URL
from crossbench.pinpoint.list_benchmarks import fetch_benchmarks
from tests import test_helper
-from tests.crossbench.pinpoint.auth_session_mixin import MockAuthSessionMixin
+from tests.crossbench.pinpoint.requests_mixin import MockRequestsMixin
-class ListBenchmarksTest(MockAuthSessionMixin):
- _get_auth_session_patch_target: Final[
- str] = "crossbench.pinpoint.list_benchmarks.get_auth_session"
+class ListBenchmarksTest(MockRequestsMixin):
def setUp(self):
super().setUp()
@@ -34,26 +31,21 @@
mock_response.raise_for_status.return_value = None
return mock_response
- self.mock_session.post.side_effect = mock_post_side_effect
+ self.mock_post.side_effect = mock_post_side_effect
def test_fetch_benchmarks(self):
benchmarks = fetch_benchmarks()
self.assertEqual(benchmarks, ["benchmark1", "benchmark2", "benchmark3"])
- self.mock_get_auth_session.assert_called_once()
- self.mock_session.post.assert_called_once_with(
- CHROMEPERF_TEST_SUITES_API_URL)
+ self.mock_post.assert_called_once_with(CHROMEPERF_TEST_SUITES_API_URL)
def test_fetch_benchmarks_api_error(self):
- self.mock_session.post.side_effect = requests.exceptions.HTTPError(
- "API Error")
+ self.mock_post.side_effect = requests.exceptions.HTTPError("API Error")
with self.assertRaises(MultiException):
fetch_benchmarks()
- self.mock_get_auth_session.assert_called_once()
- self.mock_session.post.assert_called_once_with(
- CHROMEPERF_TEST_SUITES_API_URL)
+ self.mock_post.assert_called_once_with(CHROMEPERF_TEST_SUITES_API_URL)
if __name__ == "__main__":
diff --git a/tests/crossbench/pinpoint/test_list_bots.py b/tests/crossbench/pinpoint/test_list_bots.py
index a4275af..733dfad 100644
--- a/tests/crossbench/pinpoint/test_list_bots.py
+++ b/tests/crossbench/pinpoint/test_list_bots.py
@@ -4,7 +4,6 @@
from __future__ import annotations
-from typing import Final
from unittest import mock
import requests
@@ -13,12 +12,10 @@
from crossbench.pinpoint.api import PINPOINT_CONFIG_API_URL
from crossbench.pinpoint.list_bots import fetch_bots
from tests import test_helper
-from tests.crossbench.pinpoint.auth_session_mixin import MockAuthSessionMixin
+from tests.crossbench.pinpoint.requests_mixin import MockRequestsMixin
-class ListBotsTest(MockAuthSessionMixin):
- _get_auth_session_patch_target: Final[
- str] = "crossbench.pinpoint.list_bots.get_auth_session"
+class ListBotsTest(MockRequestsMixin):
def setUp(self):
super().setUp()
@@ -34,24 +31,21 @@
mock_response.raise_for_status.return_value = None
return mock_response
- self.mock_session.post.side_effect = mock_post_side_effect
+ self.mock_post.side_effect = mock_post_side_effect
def test_fetch_bots(self):
bots = fetch_bots()
self.assertEqual(bots, ["bot1", "bot2", "bot3"])
- self.mock_get_auth_session.assert_called_once()
- self.mock_session.post.assert_called_once_with(PINPOINT_CONFIG_API_URL)
+ self.mock_post.assert_called_once_with(PINPOINT_CONFIG_API_URL)
def test_fetch_bots_api_error(self):
- self.mock_session.post.side_effect = requests.exceptions.HTTPError(
- "API Error")
+ self.mock_post.side_effect = requests.exceptions.HTTPError("API Error")
with self.assertRaises(MultiException):
fetch_bots()
- self.mock_get_auth_session.assert_called_once()
- self.mock_session.post.assert_called_once_with(PINPOINT_CONFIG_API_URL)
+ self.mock_post.assert_called_once_with(PINPOINT_CONFIG_API_URL)
if __name__ == "__main__":
diff --git a/tests/crossbench/pinpoint/test_list_builds.py b/tests/crossbench/pinpoint/test_list_builds.py
index fdc8e43..5cae17f 100644
--- a/tests/crossbench/pinpoint/test_list_builds.py
+++ b/tests/crossbench/pinpoint/test_list_builds.py
@@ -4,7 +4,6 @@
from __future__ import annotations
-from typing import Final
from unittest import mock
import requests
@@ -13,12 +12,10 @@
from crossbench.pinpoint.api import PINPOINT_BUILDS_API_URL_TEMPLATE
from crossbench.pinpoint.list_builds import Build, fetch_builds, list_builds
from tests import test_helper
-from tests.crossbench.pinpoint.auth_session_mixin import MockAuthSessionMixin
+from tests.crossbench.pinpoint.requests_mixin import MockRequestsMixin
-class ListBuildsTest(MockAuthSessionMixin):
- _get_auth_session_patch_target: Final[
- str] = "crossbench.pinpoint.list_builds.get_auth_session"
+class ListBuildsTest(MockRequestsMixin):
def setUp(self):
super().setUp()
@@ -47,7 +44,7 @@
mock_response.raise_for_status.return_value = None
return mock_response
- self.mock_session.get.side_effect = mock_get_side_effect
+ self.mock_get.side_effect = mock_get_side_effect
def test_fetch_builds_contain_correct_builds(self):
builds = fetch_builds("test-bot")
@@ -66,8 +63,7 @@
self.assertIn("commit0", output)
def test_list_builds_api_error(self):
- self.mock_session.get.side_effect = requests.exceptions.HTTPError(
- "API Error")
+ self.mock_get.side_effect = requests.exceptions.HTTPError("API Error")
with self.assertRaises(MultiException):
list_builds("test-bot", 1)
diff --git a/tests/crossbench/pinpoint/test_list_stories.py b/tests/crossbench/pinpoint/test_list_stories.py
index 0893d14..5cda422 100644
--- a/tests/crossbench/pinpoint/test_list_stories.py
+++ b/tests/crossbench/pinpoint/test_list_stories.py
@@ -4,7 +4,6 @@
from __future__ import annotations
-from typing import Final
from unittest import mock
import requests
@@ -13,12 +12,10 @@
from crossbench.pinpoint.api import CHROMEPERF_DESCRIBE_API_URL
from crossbench.pinpoint.list_stories import fetch_stories
from tests import test_helper
-from tests.crossbench.pinpoint.auth_session_mixin import MockAuthSessionMixin
+from tests.crossbench.pinpoint.requests_mixin import MockRequestsMixin
-class ListStoriesTest(MockAuthSessionMixin):
- _get_auth_session_patch_target: Final[
- str] = "crossbench.pinpoint.list_stories.get_auth_session"
+class ListStoriesTest(MockRequestsMixin):
def setUp(self):
super().setUp()
@@ -40,13 +37,12 @@
mock_response.raise_for_status.return_value = None
return mock_response
- self.mock_session.post.side_effect = mock_post_side_effect
+ self.mock_post.side_effect = mock_post_side_effect
def test_fetch_stories(self):
stories = fetch_stories("benchmark1")
self.assertEqual(stories, ["story1", "story2"])
- self.mock_get_auth_session.assert_called_once()
- self.mock_session.post.assert_called_once_with(
+ self.mock_post.assert_called_once_with(
CHROMEPERF_DESCRIBE_API_URL,
params={
"test_suite": "benchmark1",
@@ -58,8 +54,7 @@
self.assertEqual(stories, [])
def test_fetch_stories_api_error(self):
- self.mock_session.post.side_effect = requests.exceptions.HTTPError(
- "API Error")
+ self.mock_post.side_effect = requests.exceptions.HTTPError("API Error")
with self.assertRaises(MultiException):
fetch_stories("benchmark1")
diff --git a/tests/crossbench/pinpoint/test_patch_resolver.py b/tests/crossbench/pinpoint/test_patch_resolver.py
index 706be97..36796c4 100644
--- a/tests/crossbench/pinpoint/test_patch_resolver.py
+++ b/tests/crossbench/pinpoint/test_patch_resolver.py
@@ -9,7 +9,7 @@
from crossbench.pinpoint import patch_resolver
from tests import test_helper
-from tests.crossbench.pinpoint.auth_session_mixin import MockAuthSessionMixin
+from tests.crossbench.pinpoint.requests_mixin import MockRequestsMixin
_TEST_PATCH: Final[
str] = "https://chromium-review.googlesource.com/c/chromium/src/+/12345"
@@ -17,10 +17,7 @@
_TEST_PATCH_WITH_PATCHSET: Final[str] = _TEST_PATCH + "/6"
-class PatchResolverTest(MockAuthSessionMixin):
-
- _get_auth_session_patch_target = (
- "crossbench.pinpoint.patch_resolver.get_auth_session")
+class PatchResolverTest(MockRequestsMixin):
def setUp(self):
super().setUp()
@@ -35,7 +32,7 @@
mock_response.raise_for_status.return_value = None
return mock_response
- self.mock_session.get.side_effect = mock_get_side_effect
+ self.mock_get.side_effect = mock_get_side_effect
def test_resolve_patch(self):
for protocol in ["", "https://"]:
diff --git a/tests/crossbench/pinpoint/test_requests.py b/tests/crossbench/pinpoint/test_requests.py
new file mode 100644
index 0000000..f1cbdf6
--- /dev/null
+++ b/tests/crossbench/pinpoint/test_requests.py
@@ -0,0 +1,78 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from __future__ import annotations
+
+import json
+import unittest
+from unittest import mock
+
+import requests
+
+from crossbench.pinpoint import requests as pinpoint_requests
+from tests import test_helper
+
+
+class RequestsTest(unittest.TestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.mock_response = mock.Mock()
+ self.mock_response.raise_for_status.return_value = None
+ error = requests.exceptions.HTTPError(
+ "Base Error", response=self.mock_response)
+ self.mock_response.raise_for_status.side_effect = error
+
+ self.mock_session = mock.Mock(spec=requests.Session)
+ self.mock_session.request.return_value = self.mock_response
+
+ self.mock_get_auth_session = self.enterContext(
+ mock.patch("crossbench.pinpoint.requests.auth.get_auth_session"))
+ self.mock_get_auth_session.return_value = self.mock_session
+
+ def test_get_success(self):
+ self.mock_response.raise_for_status.side_effect = None
+ response = pinpoint_requests.get("http://example.com", params={"a": 1})
+
+ self.mock_session.request.assert_called_once_with(
+ "GET", "http://example.com", params={"a": 1})
+ self.assertEqual(response, self.mock_response)
+
+ def test_post_success(self):
+ self.mock_response.raise_for_status.side_effect = None
+ response = pinpoint_requests.post("http://example.com", json={"a": 1})
+
+ self.mock_session.request.assert_called_once_with(
+ "POST", "http://example.com", json={"a": 1})
+ self.assertEqual(response, self.mock_response)
+
+ def test_error_with_message(self):
+ self.mock_response.json.return_value = {"error": "Detailed error message"}
+
+ with self.assertRaises(pinpoint_requests.ServerError) as cm:
+ pinpoint_requests.get("http://example.com")
+
+ self.assertIn("Base Error", str(cm.exception))
+ self.assertIn("Detailed error message", str(cm.exception))
+
+ def test_error_with_data(self):
+ self.mock_response.json.return_value = {"other": "data"}
+
+ with self.assertRaises(pinpoint_requests.ServerError) as cm:
+ pinpoint_requests.get("http://example.com")
+
+ self.assertIn("Base Error", str(cm.exception))
+ self.assertIn("{'other': 'data'}", str(cm.exception))
+
+ def test_error_non_json(self):
+ self.mock_response.json.side_effect = json.JSONDecodeError("msg", "doc", 0)
+
+ with self.assertRaises(pinpoint_requests.ServerError) as cm:
+ pinpoint_requests.get("http://example.com")
+
+ self.assertEqual(str(cm.exception), "Base Error")
+
+
+if __name__ == "__main__":
+ test_helper.run_pytest(__file__)
diff --git a/tests/crossbench/pinpoint/test_start_job.py b/tests/crossbench/pinpoint/test_start_job.py
index b3b1c31..a2f16b6 100644
--- a/tests/crossbench/pinpoint/test_start_job.py
+++ b/tests/crossbench/pinpoint/test_start_job.py
@@ -5,7 +5,6 @@
from __future__ import annotations
import json
-from typing import Final
from unittest import mock
import requests
@@ -15,12 +14,10 @@
from crossbench.pinpoint.config import PinpointTryJobConfig
from crossbench.pinpoint.start_job import start_job
from tests import test_helper
-from tests.crossbench.pinpoint.auth_session_mixin import MockAuthSessionMixin
+from tests.crossbench.pinpoint.requests_mixin import MockRequestsMixin
-class StartJobTest(MockAuthSessionMixin):
- _get_auth_session_patch_target: Final[
- str] = "crossbench.pinpoint.start_job.get_auth_session"
+class StartJobTest(MockRequestsMixin):
def setUp(self):
super().setUp()
@@ -36,7 +33,7 @@
mock_response.raise_for_status.return_value = None
return mock_response
- self.mock_session.post.side_effect = mock_post_side_effect
+ self.mock_post.side_effect = mock_post_side_effect
def test_start_job_correct_parameters(self):
start_job(
@@ -87,12 +84,11 @@
"experiment_extra_args":
'--extra-browser-args="--js-flags=--exp-js-flag"',
}
- self.mock_session.post.assert_called_with(
+ self.mock_post.assert_called_with(
PINPOINT_START_JOB_API_URL, data=expected_payload)
def test_start_job_api_error(self):
- self.mock_session.post.side_effect = requests.exceptions.HTTPError(
- "API Error")
+ self.mock_post.side_effect = requests.exceptions.HTTPError("API Error")
with self.assertRaises(MultiException):
start_job(
PinpointTryJobConfig(