Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion newa/cli/schedule_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ def _determine_architectures(
if arch_options:
return Arch.architectures([Arch(a.strip()) for a in arch_options], compose=compose)

if jira_job.erratum and jira_job.erratum.archs:
# When the artifact carries architecture metadata, honor it verbatim - even
# when it is empty. An empty list is not missing metadata; it means the
# artifact targets only unsupported architectures (e.g. riscv64) and so must
# not fall back to the defaults below.
if jira_job.erratum:
return jira_job.erratum.archs
if jira_job.rog:
return jira_job.rog.archs

return Arch.architectures(compose=compose)

Expand Down Expand Up @@ -282,6 +288,15 @@ def _process_jira_job(
compose = jira_job.compose.id if jira_job.compose else None
architectures = _determine_architectures(ctx, arch_options, jira_job, compose)

# Skip artifacts that target only unsupported architectures. In that case
# the artifact has an explicitly empty supported-architecture set and there
# is nothing to schedule it on.
if (jira_job.erratum or jira_job.rog) and not arch_options and not architectures:
ctx.logger.info(
f'Skipping jira job {jira_job.jira.id} - artifact targets only '
f'unsupported architectures.')
return

# Prepare initial and CLI configs
initial_config = _prepare_initial_config(ctx, jira_job, compose)
cli_config = RawRecipeConfigDimension(
Expand Down
1 change: 1 addition & 0 deletions newa/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Arch(Enum):
S390X = 's390x'
PPC64LE = 'ppc64le'
PPC64 = 'ppc64'
RISCV64 = 'riscv64'
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Resolved
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Resolved
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Resolved
NOARCH = 'noarch'
MULTI = 'multi'
SRPMS = 'SRPMS' # just to ease errata processing
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/test_arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ def test_arch_rhel6_multi_returns_default():
assert set(result) == {Arch.X86_64, Arch.S390X, Arch.I386}


def test_arch_excluded_only_returns_empty():
# an artifact targeting only unsupported architectures (e.g. riscv64)
# must resolve to an empty list, not a fallback to the defaults
assert Arch.architectures([Arch.RISCV64]) == []
assert Arch.architectures([Arch.SRPMS]) == []


def test_arch_mixed_supported_and_excluded():
# unsupported architectures are dropped while supported ones are kept
result = Arch.architectures([Arch.X86_64, Arch.RISCV64])
assert result == [Arch.X86_64]


def test_arch_i386_excluded_without_rhel6():
preset = [Arch.I386, Arch.X86_64, Arch.S390X]
result = Arch.architectures(preset)
Expand Down
108 changes: 108 additions & 0 deletions tests/unit/test_determine_architectures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""Tests for architecture resolution and arch-based skipping in scheduling."""

from types import SimpleNamespace
from unittest import mock

import pytest

from newa import Arch
from newa.cli.schedule_helpers import _determine_architectures, _process_jira_job

DEFAULT_ARCHS = [Arch.X86_64, Arch.S390X, Arch.PPC64LE, Arch.AARCH64]


def _erratum_job(archs):
"""A minimal jira_job stand-in carrying an erratum with the given archs."""
return SimpleNamespace(erratum=SimpleNamespace(archs=archs), rog=None)


def _rog_job(archs):
"""A minimal jira_job stand-in carrying a RoG MR with the given archs."""
return SimpleNamespace(erratum=None, rog=SimpleNamespace(archs=archs))


def _plain_job():
"""A jira_job with neither erratum nor RoG metadata."""
return SimpleNamespace(erratum=None, rog=None)


class TestDetermineArchitectures:
"""_determine_architectures across erratum, RoG and compose-default paths."""

def test_arch_options_override_everything(self):
# explicit --arch options win over any artifact metadata
job = _erratum_job([Arch.S390X])
result = _determine_architectures(None, ['x86_64', 'riscv64'], job, None)
# riscv64 is filtered out, x86_64 kept
assert result == [Arch.X86_64]

# --- erratum ---------------------------------------------------------

def test_erratum_defined_arch(self):
job = _erratum_job([Arch.X86_64, Arch.S390X])
assert _determine_architectures(None, [], job, None) == [Arch.X86_64, Arch.S390X]

def test_erratum_noarch_resolves_to_defaults(self):
# noarch builds are expanded to the default set upstream, so the
# erratum carries the full default list - it must be honored as-is
job = _erratum_job(list(DEFAULT_ARCHS))
assert _determine_architectures(None, [], job, None) == DEFAULT_ARCHS

def test_erratum_excluded_only_is_empty(self):
# a riscv64-only erratum resolves to an empty supported-arch set
job = _erratum_job([])
assert _determine_architectures(None, [], job, None) == []

# --- RoG -------------------------------------------------------------

def test_rog_defined_arch(self):
job = _rog_job([Arch.AARCH64])
assert _determine_architectures(None, [], job, None) == [Arch.AARCH64]

def test_rog_noarch_resolves_to_defaults(self):
job = _rog_job(list(DEFAULT_ARCHS))
assert _determine_architectures(None, [], job, None) == DEFAULT_ARCHS

def test_rog_excluded_only_is_empty(self):
job = _rog_job([])
assert _determine_architectures(None, [], job, None) == []

# --- fallback --------------------------------------------------------

def test_no_artifact_falls_back_to_compose_defaults(self):
job = _plain_job()
assert _determine_architectures(None, [], job, None) == DEFAULT_ARCHS


class TestProcessJiraJobArchSkip:
"""_process_jira_job skips artifacts with an empty supported-arch set."""

def _make_jira_job(self, erratum=None, rog=None):
return SimpleNamespace(
recipe=SimpleNamespace(auto_schedule=True),
jira=SimpleNamespace(id='TEST-1'),
compose=None,
erratum=erratum,
rog=rog,
)

@pytest.mark.parametrize('job_factory', [
lambda self: self._make_jira_job(erratum=SimpleNamespace(archs=[])),
lambda self: self._make_jira_job(rog=SimpleNamespace(archs=[])),
])
def test_skips_when_only_excluded_archs(self, tmp_path, job_factory):
ctx = mock.MagicMock()
ctx.state_dirpath = tmp_path
ctx.action_id_filter_pattern = None
ctx.issue_id_filter_pattern = None
ctx.action_tag_filter_pattern = None
jira_job = job_factory(self)

_process_jira_job(ctx, jira_job, arch_options=[], fixtures=[],
no_reportportal=True)

# job was skipped: an informative message was logged and no schedule
# files were produced
assert ctx.logger.info.called
assert 'unsupported architectures' in str(ctx.logger.info.call_args)
assert list(tmp_path.glob('*.yaml')) == []
Loading