Skip to content

Commit

Permalink
fix(RHTAPWATCH-688): identify compose failures
Browse files Browse the repository at this point in the history
Previously, we only covered failures that caused exceptions.
With this change, also detect composed that finish and indicate their
failure inside the compose record.

Signed-off-by: Yftach Herzog <[email protected]>
  • Loading branch information
yftacherzog committed Dec 24, 2023
1 parent 5d7a078 commit 015b392
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 6 deletions.
15 changes: 15 additions & 0 deletions generate_compose/odcs_requester.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ def __call__(self, compose_configs: ODCSComposesConfigs) -> ODCSRequestReference
self.odcs.wait_for_compose(compose["id"]) for compose in composes
]

failed_composes = [
compose for compose in finished_composes if compose["state_name"] != "done"
]

if failed_composes:
failures = [
f"{compose['id']}: state={compose['state_name']} "
f"reason={compose['state_reason']}"
for compose in failed_composes
]
sep = "\n"
raise RuntimeError(
f"Failed to generate some composes:\n{sep.join(failures)}"
)

compose_urls = [compose["result_repofile"] for compose in finished_composes]
req_refrences = ODCSRequestReferences(compose_urls=compose_urls)
return req_refrences
95 changes: 89 additions & 6 deletions tests/test_odcs_requester.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""test_odcs_requester.py - test odcs_requester"""
from textwrap import dedent
from typing import Callable
from unittest.mock import MagicMock, call, create_autospec
from unittest.mock import MagicMock, call, create_autospec, sentinel

import pytest
from odcs.client.odcs import ODCS, ComposeSourceGeneric # type: ignore
Expand All @@ -27,19 +28,34 @@ def create_odcs_mock(self, compose_url: str) -> Callable[[int, bool], MagicMock]
"""Create an ODCS mock with specific results for the compose method"""

def _mock_odcs_compose(
num_of_composes: int = 1, exception: bool = False
num_of_composes: int = 1,
exception: bool = False,
num_of_failed: int = 0,
) -> MagicMock:
"""Mock for ODCS.compose"""
mock: MagicMock = create_autospec(ODCS)
if exception:
mock.request_compose.side_effect = HTTPError
else:
mock.request_compose.side_effect = [
{"result_repofile": "arbitrary-temp-value", "id": i}
{
"result_repofile": "arbitrary-temp-value",
"id": i,
"state_name": "not-ready",
}
for i in range(1, num_of_composes + 1)
]
mock.wait_for_compose.side_effect = [
{"result_repofile": f"{compose_url}", "id": i}
{
"result_repofile": f"{compose_url}",
"id": i,
"state_name": "done"
if i > num_of_failed
else sentinel.fail_state,
"state_reason": "ok"
if i > num_of_failed
else sentinel.fail_reason,
}
for i in range(1, num_of_composes + 1)
]
return mock
Expand All @@ -57,7 +73,7 @@ def compose_source(self) -> MagicMock:
[
pytest.param(
ODCSComposesConfigs([ODCSComposeConfig(spec=compose_source)]),
id="single compose, tag source, no additional arguments",
id="single compose",
),
pytest.param(
ODCSComposesConfigs(
Expand All @@ -66,7 +82,7 @@ def compose_source(self) -> MagicMock:
ODCSComposeConfig(spec=compose_source),
]
),
id="multiple composes, , tag source, no additional arguments",
id="multiple composes",
),
],
)
Expand Down Expand Up @@ -126,3 +142,70 @@ def test_odcs_requester_compose_timeout_failure_should_raise(
odcs_requester(compose_configs=composes_config)
assert mock_odcs.request_compose.call_count == 1
assert mock_odcs.wait_for_compose.call_count == 1

@pytest.mark.parametrize(
("composes_configs", "num_of_failed", "expected_output"),
[
pytest.param(
ODCSComposesConfigs([ODCSComposeConfig(spec=compose_source)]),
1,
dedent(
"""
Failed to generate some composes:
1: state=sentinel.fail_state reason=sentinel.fail_reason
"""
).strip(),
id="single compose - failed",
),
pytest.param(
ODCSComposesConfigs(
[
ODCSComposeConfig(spec=compose_source),
ODCSComposeConfig(spec=compose_source),
]
),
1,
dedent(
"""
Failed to generate some composes:
1: state=sentinel.fail_state reason=sentinel.fail_reason
"""
).strip(),
id="multiple composes - some failed",
),
pytest.param(
ODCSComposesConfigs(
[
ODCSComposeConfig(spec=compose_source),
ODCSComposeConfig(spec=compose_source),
]
),
2,
dedent(
"""
Failed to generate some composes:
1: state=sentinel.fail_state reason=sentinel.fail_reason
2: state=sentinel.fail_state reason=sentinel.fail_reason
"""
).strip(),
id="multiple composes - all failed",
),
],
)
def test_odcs_requester_failed_generating(
self,
create_odcs_mock: Callable,
composes_configs: ODCSComposesConfigs,
num_of_failed: int,
expected_output: str,
) -> None:
"""test ODCSRequester.__call__ in a scenario in which no exceptions were
raised during compose creation, but some of the composes did not finish
successfully"""

mock_odcs = create_odcs_mock(num_of_composes=2, num_of_failed=num_of_failed)
odcs_requester = ODCSRequester(odcs=mock_odcs)

with pytest.raises(RuntimeError) as ex:
odcs_requester(compose_configs=composes_configs)
assert str(ex.value) == expected_output

0 comments on commit 015b392

Please sign in to comment.