Skip to content
Open
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
105 changes: 105 additions & 0 deletions tests/test_check_tiers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""Unit tests for check_tiers.py.

Tests the build orchestration logic with mocked subprocess and file I/O.
"""

import json
import os
import sys
from pathlib import Path
from unittest.mock import patch, MagicMock, mock_open

import pytest

# Add project root to sys.path for importing check_tiers
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

import check_tiers


class TestGetPkgName:
def test_get_pkg_name_from_spec(self, tmp_path):
"""Should return package name from rpmspec output."""
spec_file = tmp_path / "mypkg.spec"
spec_file.write_text("Name: mypkg\nVersion: 1.0\n")

with patch("check_tiers.subprocess.check_output") as mock_check_output:
mock_check_output.return_value = "mypkg\n"
result = check_tiers.get_pkg_name(str(tmp_path))
assert result == "mypkg"
mock_check_output.assert_called_once()

def test_get_pkg_name_fallback_to_dirname(self, tmp_path):
"""Should fall back to directory name when rpmspec fails."""
spec_file = tmp_path / "mypkg.spec"
spec_file.write_text("broken spec")

with patch("check_tiers.subprocess.check_output") as mock_check_output:
mock_check_output.side_effect = Exception("rpmspec failed")
result = check_tiers.get_pkg_name(str(tmp_path))
# Should fall back to directory basename
assert result == tmp_path.name

def test_get_pkg_name_skips_bootstrap_specs(self, tmp_path):
"""Should prefer non-bootstrap spec files."""
bootstrap_spec = tmp_path / "mypkg-bootstrap.spec"
bootstrap_spec.write_text("Name: mypkg-bootstrap\n")
main_spec = tmp_path / "mypkg.spec"
main_spec.write_text("Name: mypkg\n")

with patch("check_tiers.subprocess.check_output") as mock_check_output:
mock_check_output.return_value = "mypkg\n"
result = check_tiers.get_pkg_name(str(tmp_path))
# Should find the non-bootstrap spec
assert result == "mypkg"

def test_get_pkg_name_no_spec_files(self, tmp_path):
"""Should fall back to directory name when no .spec files exist."""
(tmp_path / "somefile.txt").write_text("hello")

with patch("check_tiers.subprocess.check_output") as mock_check_output:
mock_check_output.side_effect = Exception("no spec")
result = check_tiers.get_pkg_name(str(tmp_path))
assert result == tmp_path.name

def test_get_pkg_name_only_bootstrap_spec(self, tmp_path):
"""When only bootstrap spec exists, use it."""
spec_file = tmp_path / "mypkg-bootstrap.spec"
spec_file.write_text("Name: mypkg-bootstrap\n")

with patch("check_tiers.subprocess.check_output") as mock_check_output:
mock_check_output.return_value = "mypkg-bootstrap\n"
result = check_tiers.get_pkg_name(str(tmp_path))
assert result == "mypkg-bootstrap"


class TestGetStatus:
def test_get_status_returns_map(self):
"""Should return a dict keyed by (pkg, chroot) with state values."""
mock_json = json.dumps([
{"name": "pkg1", "chroot": "epel-10-aarch64", "state": "succeeded"},
{"name": "pkg1", "chroot": "alma-kitten+epel-10-x86_64_v2", "state": "succeeded"},
{"name": "pkg2", "chroot": "epel-10-aarch64", "state": "failed"},
])

with patch("check_tiers.subprocess.check_output", return_value=mock_json):
result = check_tiers.get_status()

assert ("pkg1", "epel-10-aarch64") in result
assert ("pkg1", "alma-kitten+epel-10-x86_64_v2") in result
assert ("pkg2", "epel-10-aarch64") in result
assert result[("pkg1", "epel-10-aarch64")] == "succeeded"
assert result[("pkg2", "epel-10-aarch64")] == "failed"

def test_get_status_takes_first_entry(self):
"""When duplicate entries exist, should take the first one."""
mock_json = json.dumps([
{"name": "pkg1", "chroot": "epel-10-aarch64", "state": "succeeded"},
{"name": "pkg1", "chroot": "epel-10-aarch64", "state": "failed"},
])

with patch("check_tiers.subprocess.check_output", return_value=mock_json):
result = check_tiers.get_status()

assert result[("pkg1", "epel-10-aarch64")] == "succeeded"
132 changes: 132 additions & 0 deletions tests/test_check_tiers_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""Integration-style tests for check_tiers.main() with mocked dependencies."""

import json
import os
import sys
from pathlib import Path
from unittest.mock import patch, MagicMock, mock_open

import yaml
import pytest

PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

import check_tiers


# Sample build-order.yml for testing
SAMPLE_BUILD_ORDER = {
"tiers": [
{
"name": "bootstrap",
"packages": [
{"path": "pkg1"},
{"path": "pkg2"},
],
},
{
"name": "core",
"packages": [
{"path": "pkg3"},
],
},
],
}


class TestMain:
def test_main_all_succeeded(self, tmp_path):
"""When all packages have succeeded, main should print completion."""
build_order = tmp_path / "build-order.yml"
build_order.write_text(yaml.dump(SAMPLE_BUILD_ORDER))

mock_status = {
("pkg1", check_tiers.ARM_CHROOT): "succeeded",
("pkg1", check_tiers.V2_CHROOT): "succeeded",
("pkg2", check_tiers.ARM_CHROOT): "succeeded",
("pkg2", check_tiers.V2_CHROOT): "succeeded",
("pkg3", check_tiers.ARM_CHROOT): "succeeded",
("pkg3", check_tiers.V2_CHROOT): "succeeded",
}

with patch.multiple(
check_tiers,
get_status=MagicMock(return_value=mock_status),
get_pkg_name=MagicMock(side_effect=lambda p: Path(p).name),
):
with patch.object(sys, "argv", ["check_tiers.py"]):
with patch("pathlib.Path.read_text", return_value=yaml.dump(SAMPLE_BUILD_ORDER)):
# main() should complete without triggering any builds
result = check_tiers.main()

def test_main_missing_package_triggers_build(self, tmp_path):
"""When a package is missing/failed, main should trigger builds."""
build_order = tmp_path / "build-order.yml"
build_order.write_text(yaml.dump(SAMPLE_BUILD_ORDER))

mock_status = {
("pkg1", check_tiers.ARM_CHROOT): "succeeded",
("pkg1", check_tiers.V2_CHROOT): "succeeded",
("pkg2", check_tiers.ARM_CHROOT): "failed", # This should trigger
("pkg2", check_tiers.V2_CHROOT): "succeeded",
("pkg3", check_tiers.ARM_CHROOT): "succeeded",
("pkg3", check_tiers.V2_CHROOT): "succeeded",
}

with patch.multiple(
check_tiers,
get_status=MagicMock(return_value=mock_status),
get_pkg_name=MagicMock(side_effect=lambda p: Path(p).name),
):
with patch.object(sys, "argv", ["check_tiers.py"]):
with patch("pathlib.Path.read_text", return_value=yaml.dump(SAMPLE_BUILD_ORDER)):
with patch("check_tiers.subprocess.run") as mock_run:
mock_run.return_value = MagicMock()
result = check_tiers.main()
# Should have triggered build for pkg2
mock_run.assert_called_once()

def test_main_multiple_missing_in_one_tier(self, tmp_path):
"""When multiple packages are missing, main should trigger all builds."""
build_order = tmp_path / "build-order.yml"
build_order.write_text(yaml.dump(SAMPLE_BUILD_ORDER))

mock_status = {
("pkg1", check_tiers.ARM_CHROOT): "succeeded",
("pkg1", check_tiers.V2_CHROOT): "failed",
("pkg2", check_tiers.ARM_CHROOT): "failed",
("pkg2", check_tiers.V2_CHROOT): "failed",
("pkg3", check_tiers.ARM_CHROOT): "succeeded",
("pkg3", check_tiers.V2_CHROOT): "succeeded",
}

with patch.multiple(
check_tiers,
get_status=MagicMock(return_value=mock_status),
get_pkg_name=MagicMock(side_effect=lambda p: Path(p).name),
):
with patch.object(sys, "argv", ["check_tiers.py"]):
with patch("pathlib.Path.read_text", return_value=yaml.dump(SAMPLE_BUILD_ORDER)):
with patch("check_tiers.subprocess.run") as mock_run:
mock_run.return_value = MagicMock()
result = check_tiers.main()
# Should have triggered builds for pkg1 and pkg2
assert mock_run.call_count >= 1

def test_main_empty_tiers(self, tmp_path):
"""When build-order.yml has no tiers, main should handle gracefully."""
empty_order = {"tiers": []}
build_order = tmp_path / "build-order.yml"
build_order.write_text(yaml.dump(empty_order))

with patch.multiple(
check_tiers,
get_status=MagicMock(return_value={}),
get_pkg_name=MagicMock(return_value="pkg"),
):
with patch.object(sys, "argv", ["check_tiers.py"]):
with patch("pathlib.Path.read_text", return_value=yaml.dump(empty_order)):
result = check_tiers.main()
# No tiers means no builds triggered
assert result is None # main() returns None on success
Loading