- Fix branch names for 5 modules discovered during install attempts: ieee802_11, ieee802_15_4, adsb (maint-3.10), iridium, nrsc5 (master) - Remove packet_radio (repo gone/private) - Add build_deps for modules that need them (castxml, autotools, Qt5) - Add fix_binding_hashes.py helper script to Dockerfile builds to prevent castxml regen failures from stale pybind11 header hashes - Use tar build context in Docker builds to support COPY instruction - Note inspector as incompatible with GR 3.10 (API changes) Successfully built: ieee802_11, ieee802_15_4, adsb, iridium, nrsc5
256 lines
9.3 KiB
Python
256 lines
9.3 KiB
Python
"""Unit tests for OOT module installer middleware.
|
|
|
|
Tests Dockerfile generation, module name extraction, registry persistence,
|
|
and image naming — all without requiring Docker.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from gnuradio_mcp.middlewares.oot import OOTInstallerMiddleware
|
|
from gnuradio_mcp.models import OOTImageInfo
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_docker_client():
|
|
return MagicMock()
|
|
|
|
|
|
@pytest.fixture
|
|
def oot(mock_docker_client, tmp_path):
|
|
mw = OOTInstallerMiddleware(mock_docker_client)
|
|
# Override registry path to use tmp_path
|
|
mw._registry_path = tmp_path / "oot-registry.json"
|
|
mw._registry = {}
|
|
return mw
|
|
|
|
|
|
# ──────────────────────────────────────────
|
|
# Module Name Extraction
|
|
# ──────────────────────────────────────────
|
|
|
|
|
|
class TestModuleNameFromUrl:
|
|
def test_gr_prefix_stripped(self):
|
|
name = OOTInstallerMiddleware._module_name_from_url(
|
|
"https://github.com/tapparelj/gr-lora_sdr.git"
|
|
)
|
|
assert name == "lora_sdr"
|
|
|
|
def test_gr_prefix_no_git_suffix(self):
|
|
name = OOTInstallerMiddleware._module_name_from_url(
|
|
"https://github.com/osmocom/gr-osmosdr"
|
|
)
|
|
assert name == "osmosdr"
|
|
|
|
def test_no_gr_prefix(self):
|
|
name = OOTInstallerMiddleware._module_name_from_url(
|
|
"https://github.com/gnuradio/volk.git"
|
|
)
|
|
assert name == "volk"
|
|
|
|
def test_trailing_slash(self):
|
|
name = OOTInstallerMiddleware._module_name_from_url(
|
|
"https://github.com/tapparelj/gr-lora_sdr/"
|
|
)
|
|
assert name == "lora_sdr"
|
|
|
|
def test_gr_satellites(self):
|
|
name = OOTInstallerMiddleware._module_name_from_url(
|
|
"https://github.com/daniestevez/gr-satellites.git"
|
|
)
|
|
assert name == "satellites"
|
|
|
|
|
|
class TestRepoDirFromUrl:
|
|
def test_preserves_gr_prefix(self):
|
|
d = OOTInstallerMiddleware._repo_dir_from_url(
|
|
"https://github.com/tapparelj/gr-lora_sdr.git"
|
|
)
|
|
assert d == "gr-lora_sdr"
|
|
|
|
def test_no_git_suffix(self):
|
|
d = OOTInstallerMiddleware._repo_dir_from_url(
|
|
"https://github.com/osmocom/gr-osmosdr"
|
|
)
|
|
assert d == "gr-osmosdr"
|
|
|
|
|
|
# ──────────────────────────────────────────
|
|
# Dockerfile Generation
|
|
# ──────────────────────────────────────────
|
|
|
|
|
|
class TestDockerfileGeneration:
|
|
def test_basic_dockerfile(self, oot):
|
|
dockerfile = oot.generate_dockerfile(
|
|
git_url="https://github.com/tapparelj/gr-lora_sdr.git",
|
|
branch="master",
|
|
base_image="gnuradio-runtime:latest",
|
|
)
|
|
assert "FROM gnuradio-runtime:latest" in dockerfile
|
|
assert "git clone --depth 1 --branch master" in dockerfile
|
|
assert "https://github.com/tapparelj/gr-lora_sdr.git" in dockerfile
|
|
assert "cd gr-lora_sdr" in dockerfile
|
|
assert "fix_binding_hashes.py" in dockerfile
|
|
assert "mkdir build" in dockerfile
|
|
assert "cmake -DCMAKE_INSTALL_PREFIX=/usr" in dockerfile
|
|
assert "make -j$(nproc)" in dockerfile
|
|
assert "ldconfig" in dockerfile
|
|
assert "PYTHONPATH" in dockerfile
|
|
|
|
def test_with_extra_build_deps(self, oot):
|
|
dockerfile = oot.generate_dockerfile(
|
|
git_url="https://github.com/tapparelj/gr-lora_sdr.git",
|
|
branch="master",
|
|
base_image="gnuradio-runtime:latest",
|
|
build_deps=["libvolk2-dev", "libboost-all-dev"],
|
|
)
|
|
assert "libvolk2-dev libboost-all-dev" in dockerfile
|
|
|
|
def test_with_cmake_args(self, oot):
|
|
dockerfile = oot.generate_dockerfile(
|
|
git_url="https://github.com/tapparelj/gr-lora_sdr.git",
|
|
branch="master",
|
|
base_image="gnuradio-runtime:latest",
|
|
cmake_args=["-DENABLE_TESTING=OFF", "-DBUILD_DOCS=OFF"],
|
|
)
|
|
assert "-DENABLE_TESTING=OFF -DBUILD_DOCS=OFF" in dockerfile
|
|
|
|
def test_custom_base_image(self, oot):
|
|
dockerfile = oot.generate_dockerfile(
|
|
git_url="https://github.com/tapparelj/gr-lora_sdr.git",
|
|
branch="main",
|
|
base_image="gnuradio-coverage:latest",
|
|
)
|
|
assert "FROM gnuradio-coverage:latest" in dockerfile
|
|
|
|
def test_no_extra_deps_no_trailing_space(self, oot):
|
|
dockerfile = oot.generate_dockerfile(
|
|
git_url="https://github.com/tapparelj/gr-lora_sdr.git",
|
|
branch="master",
|
|
base_image="gnuradio-runtime:latest",
|
|
)
|
|
# With no extra deps, the apt-get line should still work
|
|
assert "build-essential cmake git" in dockerfile
|
|
|
|
|
|
# ──────────────────────────────────────────
|
|
# Registry Persistence
|
|
# ──────────────────────────────────────────
|
|
|
|
|
|
class TestRegistry:
|
|
def test_empty_on_fresh_start(self, oot):
|
|
assert oot._registry == {}
|
|
assert oot.list_images() == []
|
|
|
|
def test_save_and_load_roundtrip(self, oot):
|
|
info = OOTImageInfo(
|
|
module_name="lora_sdr",
|
|
image_tag="gr-oot-lora_sdr:master-862746d",
|
|
git_url="https://github.com/tapparelj/gr-lora_sdr.git",
|
|
branch="master",
|
|
git_commit="862746d",
|
|
base_image="gnuradio-runtime:latest",
|
|
built_at="2025-01-01T00:00:00+00:00",
|
|
)
|
|
oot._registry["lora_sdr"] = info
|
|
oot._save_registry()
|
|
|
|
# Reload from disk
|
|
loaded = oot._load_registry()
|
|
assert "lora_sdr" in loaded
|
|
assert loaded["lora_sdr"].image_tag == "gr-oot-lora_sdr:master-862746d"
|
|
assert loaded["lora_sdr"].git_commit == "862746d"
|
|
|
|
def test_load_missing_file_returns_empty(self, tmp_path):
|
|
mw = OOTInstallerMiddleware(MagicMock())
|
|
mw._registry_path = tmp_path / "nonexistent" / "registry.json"
|
|
result = mw._load_registry()
|
|
assert result == {}
|
|
|
|
def test_load_corrupt_file_returns_empty(self, oot):
|
|
oot._registry_path.write_text("not valid json{{{")
|
|
result = oot._load_registry()
|
|
assert result == {}
|
|
|
|
def test_save_creates_parent_dirs(self, tmp_path):
|
|
mw = OOTInstallerMiddleware(MagicMock())
|
|
mw._registry_path = tmp_path / "nested" / "deep" / "registry.json"
|
|
mw._registry = {}
|
|
mw._save_registry()
|
|
assert mw._registry_path.exists()
|
|
|
|
|
|
# ──────────────────────────────────────────
|
|
# Image Naming
|
|
# ──────────────────────────────────────────
|
|
|
|
|
|
class TestImageTagFormat:
|
|
def test_standard_format(self):
|
|
"""Image tags follow gr-oot-{name}:{branch}-{commit7}."""
|
|
# This verifies the format used in build_module()
|
|
module_name = "lora_sdr"
|
|
branch = "master"
|
|
commit = "862746d"
|
|
tag = f"gr-oot-{module_name}:{branch}-{commit}"
|
|
assert tag == "gr-oot-lora_sdr:master-862746d"
|
|
|
|
def test_different_branch(self):
|
|
tag = f"gr-oot-osmosdr:develop-abc1234"
|
|
assert "develop" in tag
|
|
assert "abc1234" in tag
|
|
|
|
|
|
# ──────────────────────────────────────────
|
|
# Remove Image
|
|
# ──────────────────────────────────────────
|
|
|
|
|
|
class TestRemoveImage:
|
|
def test_remove_existing(self, oot, mock_docker_client):
|
|
info = OOTImageInfo(
|
|
module_name="lora_sdr",
|
|
image_tag="gr-oot-lora_sdr:master-862746d",
|
|
git_url="https://github.com/tapparelj/gr-lora_sdr.git",
|
|
branch="master",
|
|
git_commit="862746d",
|
|
base_image="gnuradio-runtime:latest",
|
|
built_at="2025-01-01T00:00:00+00:00",
|
|
)
|
|
oot._registry["lora_sdr"] = info
|
|
|
|
result = oot.remove_image("lora_sdr")
|
|
assert result is True
|
|
assert "lora_sdr" not in oot._registry
|
|
mock_docker_client.images.remove.assert_called_once_with(
|
|
"gr-oot-lora_sdr:master-862746d", force=True
|
|
)
|
|
|
|
def test_remove_nonexistent(self, oot):
|
|
result = oot.remove_image("does_not_exist")
|
|
assert result is False
|
|
|
|
def test_remove_survives_docker_error(self, oot, mock_docker_client):
|
|
"""Registry entry is removed even if Docker image removal fails."""
|
|
info = OOTImageInfo(
|
|
module_name="lora_sdr",
|
|
image_tag="gr-oot-lora_sdr:master-862746d",
|
|
git_url="https://github.com/tapparelj/gr-lora_sdr.git",
|
|
branch="master",
|
|
git_commit="862746d",
|
|
base_image="gnuradio-runtime:latest",
|
|
built_at="2025-01-01T00:00:00+00:00",
|
|
)
|
|
oot._registry["lora_sdr"] = info
|
|
mock_docker_client.images.remove.side_effect = Exception("image not found")
|
|
|
|
result = oot.remove_image("lora_sdr")
|
|
assert result is True
|
|
assert "lora_sdr" not in oot._registry
|