xref: /aosp_15_r20/development/python-packages/fetchartifact/tests/test_fetchartifact.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker#
2*90c8c64dSAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project
3*90c8c64dSAndroid Build Coastguard Worker#
4*90c8c64dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*90c8c64dSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*90c8c64dSAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*90c8c64dSAndroid Build Coastguard Worker#
8*90c8c64dSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
9*90c8c64dSAndroid Build Coastguard Worker#
10*90c8c64dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*90c8c64dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*90c8c64dSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*90c8c64dSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*90c8c64dSAndroid Build Coastguard Worker# limitations under the License.
15*90c8c64dSAndroid Build Coastguard Worker#
16*90c8c64dSAndroid Build Coastguard Worker"""Tests for fetchartifact."""
17*90c8c64dSAndroid Build Coastguard Workerfrom typing import cast
18*90c8c64dSAndroid Build Coastguard Worker
19*90c8c64dSAndroid Build Coastguard Workerimport pytest
20*90c8c64dSAndroid Build Coastguard Workerfrom aiohttp import ClientResponseError, ClientSession
21*90c8c64dSAndroid Build Coastguard Workerfrom aiohttp.test_utils import TestClient
22*90c8c64dSAndroid Build Coastguard Workerfrom aiohttp.web import Application, Request, Response
23*90c8c64dSAndroid Build Coastguard Worker
24*90c8c64dSAndroid Build Coastguard Workerfrom fetchartifact import ArtifactDownloader, fetch_artifact, fetch_artifact_chunked
25*90c8c64dSAndroid Build Coastguard Worker
26*90c8c64dSAndroid Build Coastguard WorkerTEST_BUILD_ID = "1234"
27*90c8c64dSAndroid Build Coastguard WorkerTEST_TARGET = "linux"
28*90c8c64dSAndroid Build Coastguard WorkerTEST_ARTIFACT_NAME = "output.zip"
29*90c8c64dSAndroid Build Coastguard WorkerTEST_DOWNLOAD_URL = (
30*90c8c64dSAndroid Build Coastguard Worker    f"/android/internal/build/v3/builds/{TEST_BUILD_ID}/{TEST_TARGET}/"
31*90c8c64dSAndroid Build Coastguard Worker    f"attempts/latest/artifacts/{TEST_ARTIFACT_NAME}/url"
32*90c8c64dSAndroid Build Coastguard Worker)
33*90c8c64dSAndroid Build Coastguard WorkerTEST_RESPONSE = b"Hello, world!"
34*90c8c64dSAndroid Build Coastguard Worker
35*90c8c64dSAndroid Build Coastguard Worker
36*90c8c64dSAndroid Build Coastguard Worker@pytest.fixture(name="android_ci_client")
37*90c8c64dSAndroid Build Coastguard Workerasync def fixture_android_ci_client(aiohttp_client: type[TestClient]) -> TestClient:
38*90c8c64dSAndroid Build Coastguard Worker    """Fixture for mocking the Android CI APIs."""
39*90c8c64dSAndroid Build Coastguard Worker
40*90c8c64dSAndroid Build Coastguard Worker    async def download(_request: Request) -> Response:
41*90c8c64dSAndroid Build Coastguard Worker        return Response(text=TEST_RESPONSE.decode("utf-8"))
42*90c8c64dSAndroid Build Coastguard Worker
43*90c8c64dSAndroid Build Coastguard Worker    app = Application()
44*90c8c64dSAndroid Build Coastguard Worker    app.router.add_get(TEST_DOWNLOAD_URL, download)
45*90c8c64dSAndroid Build Coastguard Worker    return await aiohttp_client(app)  # type: ignore
46*90c8c64dSAndroid Build Coastguard Worker
47*90c8c64dSAndroid Build Coastguard Worker
48*90c8c64dSAndroid Build Coastguard Workerasync def test_fetch_artifact(android_ci_client: TestClient) -> None:
49*90c8c64dSAndroid Build Coastguard Worker    """Tests that the download URL is queried."""
50*90c8c64dSAndroid Build Coastguard Worker    assert TEST_RESPONSE == await fetch_artifact(
51*90c8c64dSAndroid Build Coastguard Worker        TEST_TARGET,
52*90c8c64dSAndroid Build Coastguard Worker        TEST_BUILD_ID,
53*90c8c64dSAndroid Build Coastguard Worker        TEST_ARTIFACT_NAME,
54*90c8c64dSAndroid Build Coastguard Worker        cast(ClientSession, android_ci_client),
55*90c8c64dSAndroid Build Coastguard Worker        query_url_base="",
56*90c8c64dSAndroid Build Coastguard Worker    )
57*90c8c64dSAndroid Build Coastguard Worker
58*90c8c64dSAndroid Build Coastguard Worker
59*90c8c64dSAndroid Build Coastguard Workerasync def test_fetch_artifact_chunked(android_ci_client: TestClient) -> None:
60*90c8c64dSAndroid Build Coastguard Worker    """Tests that the full file contents are downloaded."""
61*90c8c64dSAndroid Build Coastguard Worker    assert [c.encode("utf-8") for c in TEST_RESPONSE.decode("utf-8")] == [
62*90c8c64dSAndroid Build Coastguard Worker        chunk
63*90c8c64dSAndroid Build Coastguard Worker        async for chunk in fetch_artifact_chunked(
64*90c8c64dSAndroid Build Coastguard Worker            TEST_TARGET,
65*90c8c64dSAndroid Build Coastguard Worker            TEST_BUILD_ID,
66*90c8c64dSAndroid Build Coastguard Worker            TEST_ARTIFACT_NAME,
67*90c8c64dSAndroid Build Coastguard Worker            cast(ClientSession, android_ci_client),
68*90c8c64dSAndroid Build Coastguard Worker            chunk_size=1,
69*90c8c64dSAndroid Build Coastguard Worker            query_url_base="",
70*90c8c64dSAndroid Build Coastguard Worker        )
71*90c8c64dSAndroid Build Coastguard Worker    ]
72*90c8c64dSAndroid Build Coastguard Worker
73*90c8c64dSAndroid Build Coastguard Worker
74*90c8c64dSAndroid Build Coastguard Workerasync def test_failure_raises(android_ci_client: TestClient) -> None:
75*90c8c64dSAndroid Build Coastguard Worker    """Tests that fetch failure raises an exception."""
76*90c8c64dSAndroid Build Coastguard Worker    with pytest.raises(ClientResponseError):
77*90c8c64dSAndroid Build Coastguard Worker        await fetch_artifact(
78*90c8c64dSAndroid Build Coastguard Worker            TEST_TARGET,
79*90c8c64dSAndroid Build Coastguard Worker            TEST_BUILD_ID,
80*90c8c64dSAndroid Build Coastguard Worker            TEST_ARTIFACT_NAME,
81*90c8c64dSAndroid Build Coastguard Worker            cast(ClientSession, android_ci_client),
82*90c8c64dSAndroid Build Coastguard Worker            query_url_base="/bad",
83*90c8c64dSAndroid Build Coastguard Worker        )
84*90c8c64dSAndroid Build Coastguard Worker
85*90c8c64dSAndroid Build Coastguard Worker    with pytest.raises(ClientResponseError):
86*90c8c64dSAndroid Build Coastguard Worker        async for _chunk in fetch_artifact_chunked(
87*90c8c64dSAndroid Build Coastguard Worker            TEST_TARGET,
88*90c8c64dSAndroid Build Coastguard Worker            TEST_BUILD_ID,
89*90c8c64dSAndroid Build Coastguard Worker            TEST_ARTIFACT_NAME,
90*90c8c64dSAndroid Build Coastguard Worker            cast(ClientSession, android_ci_client),
91*90c8c64dSAndroid Build Coastguard Worker            query_url_base="/bad",
92*90c8c64dSAndroid Build Coastguard Worker        ):
93*90c8c64dSAndroid Build Coastguard Worker            pass
94*90c8c64dSAndroid Build Coastguard Worker
95*90c8c64dSAndroid Build Coastguard Worker
96*90c8c64dSAndroid Build Coastguard Workerclass TestDownloader(ArtifactDownloader):
97*90c8c64dSAndroid Build Coastguard Worker    """Downloader which tracks calls to on_artifact_size and after_chunk."""
98*90c8c64dSAndroid Build Coastguard Worker
99*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, target: str, build_id: str, artifact_name: str) -> None:
100*90c8c64dSAndroid Build Coastguard Worker        super().__init__(target, build_id, artifact_name, query_url_base="")
101*90c8c64dSAndroid Build Coastguard Worker        self.reported_content_length: int | None = None
102*90c8c64dSAndroid Build Coastguard Worker        self.reported_chunk_sizes: list[int] = []
103*90c8c64dSAndroid Build Coastguard Worker
104*90c8c64dSAndroid Build Coastguard Worker    def on_artifact_size(self, size: int) -> None:
105*90c8c64dSAndroid Build Coastguard Worker        super().on_artifact_size(size)
106*90c8c64dSAndroid Build Coastguard Worker        assert self.reported_content_length is None
107*90c8c64dSAndroid Build Coastguard Worker        self.reported_content_length = size
108*90c8c64dSAndroid Build Coastguard Worker
109*90c8c64dSAndroid Build Coastguard Worker    def after_chunk(self, size: int) -> None:
110*90c8c64dSAndroid Build Coastguard Worker        super().after_chunk(size)
111*90c8c64dSAndroid Build Coastguard Worker        self.reported_chunk_sizes.append(size)
112*90c8c64dSAndroid Build Coastguard Worker
113*90c8c64dSAndroid Build Coastguard Worker
114*90c8c64dSAndroid Build Coastguard Workerasync def test_downloader_progress_reports(android_ci_client: TestClient) -> None:
115*90c8c64dSAndroid Build Coastguard Worker    """Tests that progress is reported when using ArtifactDownloader."""
116*90c8c64dSAndroid Build Coastguard Worker    downloader = TestDownloader(TEST_TARGET, TEST_BUILD_ID, TEST_ARTIFACT_NAME)
117*90c8c64dSAndroid Build Coastguard Worker
118*90c8c64dSAndroid Build Coastguard Worker    assert [b"Hell", b"o, w", b"orld", b"!"] == [
119*90c8c64dSAndroid Build Coastguard Worker        chunk
120*90c8c64dSAndroid Build Coastguard Worker        async for chunk in downloader.download(
121*90c8c64dSAndroid Build Coastguard Worker            cast(ClientSession, android_ci_client), chunk_size=4
122*90c8c64dSAndroid Build Coastguard Worker        )
123*90c8c64dSAndroid Build Coastguard Worker    ]
124*90c8c64dSAndroid Build Coastguard Worker    assert downloader.reported_content_length == len(TEST_RESPONSE.decode("utf-8"))
125*90c8c64dSAndroid Build Coastguard Worker    assert downloader.reported_chunk_sizes == [4, 4, 4, 1]
126*90c8c64dSAndroid Build Coastguard Worker
127*90c8c64dSAndroid Build Coastguard Worker
128*90c8c64dSAndroid Build Coastguard Worker@pytest.mark.requires_network
129*90c8c64dSAndroid Build Coastguard Workerasync def test_real_artifact() -> None:
130*90c8c64dSAndroid Build Coastguard Worker    """Tests with a real artifact. Requires an internet connection."""
131*90c8c64dSAndroid Build Coastguard Worker    async with ClientSession() as session:
132*90c8c64dSAndroid Build Coastguard Worker        contents = await fetch_artifact("linux", "9945621", "logs/SUCCEEDED", session)
133*90c8c64dSAndroid Build Coastguard Worker        assert contents == b"1681499053\n"
134