feat: add OOT module directory as MCP resources
Curated catalog of 20 GNU Radio OOT modules served via two MCP
resources (oot://directory, oot://directory/{name}). Each entry
includes git URL, branch, build deps, and a ready-to-use
install_oot_module() example.
Modules are tagged preinstalled when they ship with the
gnuradio-runtime base Docker image (12 of 20), so agents can
distinguish what's already available from what needs building.
This commit is contained in:
parent
61471b7280
commit
521c306173
300
src/gnuradio_mcp/oot_catalog.py
Normal file
300
src/gnuradio_mcp/oot_catalog.py
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
"""Curated catalog of GNU Radio OOT modules.
|
||||||
|
|
||||||
|
Provides browsable metadata so MCP clients can discover available
|
||||||
|
modules and get the exact parameters needed for install_oot_module()
|
||||||
|
without guessing URLs or build dependencies.
|
||||||
|
|
||||||
|
Modules marked ``preinstalled=True`` ship with the gnuradio-runtime
|
||||||
|
base Docker image via Debian packages. They can still be rebuilt
|
||||||
|
from source (e.g., to get a newer version) via install_oot_module().
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
# Data Models
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
class OOTModuleEntry(BaseModel):
|
||||||
|
"""A curated OOT module in the directory."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
category: str
|
||||||
|
git_url: str
|
||||||
|
branch: str = "main"
|
||||||
|
build_deps: list[str] = []
|
||||||
|
cmake_args: list[str] = []
|
||||||
|
homepage: str = ""
|
||||||
|
gr_versions: str = "3.10+"
|
||||||
|
preinstalled: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class OOTModuleSummary(BaseModel):
|
||||||
|
"""Compact entry for the directory index."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
category: str
|
||||||
|
preinstalled: bool = False
|
||||||
|
installed: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class OOTDirectoryIndex(BaseModel):
|
||||||
|
"""Response shape for oot://directory."""
|
||||||
|
|
||||||
|
modules: list[OOTModuleSummary]
|
||||||
|
count: int
|
||||||
|
|
||||||
|
|
||||||
|
class OOTModuleDetail(BaseModel):
|
||||||
|
"""Response shape for oot://directory/{name}."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
category: str
|
||||||
|
git_url: str
|
||||||
|
branch: str
|
||||||
|
build_deps: list[str]
|
||||||
|
cmake_args: list[str]
|
||||||
|
homepage: str
|
||||||
|
gr_versions: str
|
||||||
|
preinstalled: bool = False
|
||||||
|
installed: bool | None = None
|
||||||
|
installed_image_tag: str | None = None
|
||||||
|
install_example: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
# Catalog Entries
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def _entry(
|
||||||
|
name: str,
|
||||||
|
description: str,
|
||||||
|
category: str,
|
||||||
|
git_url: str,
|
||||||
|
branch: str = "main",
|
||||||
|
build_deps: list[str] | None = None,
|
||||||
|
cmake_args: list[str] | None = None,
|
||||||
|
homepage: str = "",
|
||||||
|
gr_versions: str = "3.10+",
|
||||||
|
preinstalled: bool = False,
|
||||||
|
) -> OOTModuleEntry:
|
||||||
|
return OOTModuleEntry(
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
category=category,
|
||||||
|
git_url=git_url,
|
||||||
|
branch=branch,
|
||||||
|
build_deps=build_deps or [],
|
||||||
|
cmake_args=cmake_args or [],
|
||||||
|
homepage=homepage,
|
||||||
|
gr_versions=gr_versions,
|
||||||
|
preinstalled=preinstalled,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CATALOG: dict[str, OOTModuleEntry] = {
|
||||||
|
e.name: e
|
||||||
|
for e in [
|
||||||
|
# ── Pre-installed in gnuradio-runtime base image ──
|
||||||
|
_entry(
|
||||||
|
name="osmosdr",
|
||||||
|
description="Hardware source/sink for RTL-SDR, Airspy, HackRF, and more",
|
||||||
|
category="Hardware",
|
||||||
|
git_url="https://github.com/osmocom/gr-osmosdr",
|
||||||
|
branch="master",
|
||||||
|
build_deps=["librtlsdr-dev", "libairspy-dev", "libhackrf-dev"],
|
||||||
|
homepage="https://osmocom.org/projects/gr-osmosdr/wiki",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="satellites",
|
||||||
|
description="Satellite telemetry decoders (AX.25, CCSDS, AO-73, etc.)",
|
||||||
|
category="Satellite",
|
||||||
|
git_url="https://github.com/daniestevez/gr-satellites",
|
||||||
|
branch="main",
|
||||||
|
build_deps=["python3-construct", "python3-requests"],
|
||||||
|
homepage="https://gr-satellites.readthedocs.io/",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="gsm",
|
||||||
|
description="GSM/GPRS burst receiver and channel decoder",
|
||||||
|
category="Cellular",
|
||||||
|
git_url="https://github.com/ptrkrysik/gr-gsm",
|
||||||
|
branch="master",
|
||||||
|
build_deps=["libosmocore-dev"],
|
||||||
|
homepage="https://github.com/ptrkrysik/gr-gsm",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="rds",
|
||||||
|
description="FM RDS/RBDS (Radio Data System) decoder",
|
||||||
|
category="Broadcast",
|
||||||
|
git_url="https://github.com/bastibl/gr-rds",
|
||||||
|
branch="main",
|
||||||
|
homepage="https://github.com/bastibl/gr-rds",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="fosphor",
|
||||||
|
description="GPU-accelerated real-time spectrum display (OpenCL)",
|
||||||
|
category="Visualization",
|
||||||
|
git_url="https://github.com/osmocom/gr-fosphor",
|
||||||
|
branch="master",
|
||||||
|
build_deps=["libfreetype6-dev", "ocl-icd-opencl-dev"],
|
||||||
|
homepage="https://osmocom.org/projects/sdr/wiki/Fosphor",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="air_modes",
|
||||||
|
description="Mode-S/ADS-B aircraft transponder decoder (1090 MHz)",
|
||||||
|
category="Aviation",
|
||||||
|
git_url="https://github.com/bistromath/gr-air-modes",
|
||||||
|
branch="master",
|
||||||
|
homepage="https://github.com/bistromath/gr-air-modes",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="funcube",
|
||||||
|
description="Funcube Dongle Pro/Pro+ controller and source block",
|
||||||
|
category="Hardware",
|
||||||
|
git_url="https://github.com/dl1ksv/gr-funcube",
|
||||||
|
branch="master",
|
||||||
|
homepage="https://github.com/dl1ksv/gr-funcube",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="hpsdr",
|
||||||
|
description="OpenHPSDR Protocol 1 interface for HPSDR hardware",
|
||||||
|
category="Hardware",
|
||||||
|
git_url="https://github.com/Tom-McDermott/gr-hpsdr",
|
||||||
|
branch="master",
|
||||||
|
homepage="https://github.com/Tom-McDermott/gr-hpsdr",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="iqbal",
|
||||||
|
description="Blind IQ imbalance estimator and correction",
|
||||||
|
category="Analysis",
|
||||||
|
git_url="https://github.com/osmocom/gr-iqbal",
|
||||||
|
branch="master",
|
||||||
|
homepage="https://git.osmocom.org/gr-iqbal",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="limesdr",
|
||||||
|
description="LimeSDR source/sink blocks (LMS7002M)",
|
||||||
|
category="Hardware",
|
||||||
|
git_url="https://github.com/myriadrf/gr-limesdr",
|
||||||
|
branch="master",
|
||||||
|
homepage="https://wiki.myriadrf.org/Gr-limesdr_Plugin_for_GNURadio",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="radar",
|
||||||
|
description="Radar signal processing toolbox (FMCW, OFDM radar)",
|
||||||
|
category="Analysis",
|
||||||
|
git_url="https://github.com/kit-cel/gr-radar",
|
||||||
|
branch="master",
|
||||||
|
homepage="https://github.com/kit-cel/gr-radar",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="satnogs",
|
||||||
|
description="SatNOGS satellite ground station decoders and deframers",
|
||||||
|
category="Satellite",
|
||||||
|
git_url="https://gitlab.com/librespacefoundation/satnogs/gr-satnogs",
|
||||||
|
branch="master",
|
||||||
|
homepage="https://gitlab.com/librespacefoundation/satnogs/gr-satnogs",
|
||||||
|
preinstalled=True,
|
||||||
|
),
|
||||||
|
# ── Installable via install_oot_module ──
|
||||||
|
_entry(
|
||||||
|
name="lora_sdr",
|
||||||
|
description="LoRa PHY transceiver (CSS modulation/demodulation)",
|
||||||
|
category="IoT",
|
||||||
|
git_url="https://github.com/tapparelj/gr-lora_sdr",
|
||||||
|
branch="master",
|
||||||
|
homepage="https://github.com/tapparelj/gr-lora_sdr",
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="ieee802_11",
|
||||||
|
description="IEEE 802.11a/g/p OFDM transceiver",
|
||||||
|
category="WiFi",
|
||||||
|
git_url="https://github.com/bastibl/gr-ieee802-11",
|
||||||
|
branch="main",
|
||||||
|
homepage="https://github.com/bastibl/gr-ieee802-11",
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="ieee802_15_4",
|
||||||
|
description="IEEE 802.15.4 (Zigbee) O-QPSK transceiver",
|
||||||
|
category="IoT",
|
||||||
|
git_url="https://github.com/bastibl/gr-ieee802-15-4",
|
||||||
|
branch="main",
|
||||||
|
homepage="https://github.com/bastibl/gr-ieee802-15-4",
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="adsb",
|
||||||
|
description="ADS-B (1090 MHz) aircraft transponder decoder",
|
||||||
|
category="Aviation",
|
||||||
|
git_url="https://github.com/mhostetter/gr-adsb",
|
||||||
|
branch="main",
|
||||||
|
homepage="https://github.com/mhostetter/gr-adsb",
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="iridium",
|
||||||
|
description="Iridium satellite burst detector and demodulator",
|
||||||
|
category="Satellite",
|
||||||
|
git_url="https://github.com/muccc/gr-iridium",
|
||||||
|
branch="main",
|
||||||
|
homepage="https://github.com/muccc/gr-iridium",
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="inspector",
|
||||||
|
description="Signal analysis toolbox (energy detection, OFDM estimation)",
|
||||||
|
category="Analysis",
|
||||||
|
git_url="https://github.com/gnuradio/gr-inspector",
|
||||||
|
branch="master",
|
||||||
|
homepage="https://github.com/gnuradio/gr-inspector",
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="nrsc5",
|
||||||
|
description="HD Radio (NRSC-5) digital broadcast decoder",
|
||||||
|
category="Broadcast",
|
||||||
|
git_url="https://github.com/argilo/gr-nrsc5",
|
||||||
|
branch="main",
|
||||||
|
homepage="https://github.com/argilo/gr-nrsc5",
|
||||||
|
),
|
||||||
|
_entry(
|
||||||
|
name="packet_radio",
|
||||||
|
description="Amateur packet radio (AFSK, AX.25) modem",
|
||||||
|
category="Amateur",
|
||||||
|
git_url="https://github.com/duggabe/gr-packet-radio",
|
||||||
|
branch="main",
|
||||||
|
homepage="https://github.com/duggabe/gr-packet-radio",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_install_example(entry: OOTModuleEntry) -> str:
|
||||||
|
"""Format a copy-paste install_oot_module() call for this module."""
|
||||||
|
parts = [f'install_oot_module(git_url="{entry.git_url}"']
|
||||||
|
if entry.branch != "main":
|
||||||
|
parts.append(f', branch="{entry.branch}"')
|
||||||
|
if entry.build_deps:
|
||||||
|
deps = ", ".join(f'"{d}"' for d in entry.build_deps)
|
||||||
|
parts.append(f", build_deps=[{deps}]")
|
||||||
|
if entry.cmake_args:
|
||||||
|
args = ", ".join(f'"{a}"' for a in entry.cmake_args)
|
||||||
|
parts.append(f", cmake_args=[{args}]")
|
||||||
|
parts.append(")")
|
||||||
|
return "".join(parts)
|
||||||
@ -23,6 +23,7 @@ class McpRuntimeProvider:
|
|||||||
self._mcp = mcp_instance
|
self._mcp = mcp_instance
|
||||||
self._provider = runtime_provider
|
self._provider = runtime_provider
|
||||||
self.__init_tools()
|
self.__init_tools()
|
||||||
|
self.__init_resources()
|
||||||
|
|
||||||
def __init_tools(self):
|
def __init_tools(self):
|
||||||
p = self._provider
|
p = self._provider
|
||||||
@ -86,6 +87,84 @@ class McpRuntimeProvider:
|
|||||||
"container tools skipped)"
|
"container tools skipped)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init_resources(self):
|
||||||
|
from gnuradio_mcp.oot_catalog import (
|
||||||
|
CATALOG,
|
||||||
|
OOTDirectoryIndex,
|
||||||
|
OOTModuleDetail,
|
||||||
|
OOTModuleSummary,
|
||||||
|
build_install_example,
|
||||||
|
)
|
||||||
|
|
||||||
|
oot_mw = self._provider._oot # None when Docker unavailable
|
||||||
|
|
||||||
|
@self._mcp.resource(
|
||||||
|
"oot://directory",
|
||||||
|
name="oot_directory",
|
||||||
|
description="Index of curated GNU Radio OOT modules available for installation",
|
||||||
|
mime_type="application/json",
|
||||||
|
)
|
||||||
|
def list_oot_directory() -> str:
|
||||||
|
summaries = []
|
||||||
|
for entry in CATALOG.values():
|
||||||
|
installed = None
|
||||||
|
if oot_mw is not None:
|
||||||
|
installed = entry.name in oot_mw._registry
|
||||||
|
summaries.append(
|
||||||
|
OOTModuleSummary(
|
||||||
|
name=entry.name,
|
||||||
|
description=entry.description,
|
||||||
|
category=entry.category,
|
||||||
|
preinstalled=entry.preinstalled,
|
||||||
|
installed=installed,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
index = OOTDirectoryIndex(modules=summaries, count=len(summaries))
|
||||||
|
return index.model_dump_json()
|
||||||
|
|
||||||
|
@self._mcp.resource(
|
||||||
|
"oot://directory/{module_name}",
|
||||||
|
name="oot_module_detail",
|
||||||
|
description="Full installation details for a specific OOT module",
|
||||||
|
mime_type="application/json",
|
||||||
|
)
|
||||||
|
def get_oot_module(module_name: str) -> str:
|
||||||
|
entry = CATALOG.get(module_name)
|
||||||
|
if entry is None:
|
||||||
|
known = ", ".join(sorted(CATALOG.keys()))
|
||||||
|
raise ValueError(
|
||||||
|
f"Unknown module '{module_name}'. Available: {known}"
|
||||||
|
)
|
||||||
|
|
||||||
|
installed = None
|
||||||
|
installed_image_tag = None
|
||||||
|
if oot_mw is not None:
|
||||||
|
info = oot_mw._registry.get(entry.name)
|
||||||
|
installed = info is not None
|
||||||
|
if info is not None:
|
||||||
|
installed_image_tag = info.image_tag
|
||||||
|
|
||||||
|
detail = OOTModuleDetail(
|
||||||
|
name=entry.name,
|
||||||
|
description=entry.description,
|
||||||
|
category=entry.category,
|
||||||
|
git_url=entry.git_url,
|
||||||
|
branch=entry.branch,
|
||||||
|
build_deps=entry.build_deps,
|
||||||
|
cmake_args=entry.cmake_args,
|
||||||
|
homepage=entry.homepage,
|
||||||
|
gr_versions=entry.gr_versions,
|
||||||
|
preinstalled=entry.preinstalled,
|
||||||
|
installed=installed,
|
||||||
|
installed_image_tag=installed_image_tag,
|
||||||
|
install_example=build_install_example(entry),
|
||||||
|
)
|
||||||
|
return detail.model_dump_json()
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Registered OOT directory resources (%d modules)", len(CATALOG)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, mcp_instance: FastMCP) -> McpRuntimeProvider:
|
def create(cls, mcp_instance: FastMCP) -> McpRuntimeProvider:
|
||||||
"""Factory: create RuntimeProvider with optional Docker support."""
|
"""Factory: create RuntimeProvider with optional Docker support."""
|
||||||
|
|||||||
185
tests/unit/test_oot_catalog.py
Normal file
185
tests/unit/test_oot_catalog.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
"""Tests for the OOT module catalog and its data models."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from gnuradio_mcp.oot_catalog import (
|
||||||
|
CATALOG,
|
||||||
|
OOTDirectoryIndex,
|
||||||
|
OOTModuleDetail,
|
||||||
|
OOTModuleEntry,
|
||||||
|
OOTModuleSummary,
|
||||||
|
build_install_example,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCatalogIntegrity:
|
||||||
|
def test_catalog_has_entries(self):
|
||||||
|
assert len(CATALOG) >= 15
|
||||||
|
|
||||||
|
def test_all_entries_have_git_url(self):
|
||||||
|
for name, entry in CATALOG.items():
|
||||||
|
assert entry.git_url.startswith("https://"), (
|
||||||
|
f"{name}: git_url must start with https://"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_module_names_unique(self):
|
||||||
|
names = [e.name for e in CATALOG.values()]
|
||||||
|
assert len(names) == len(set(names))
|
||||||
|
|
||||||
|
def test_all_categories_nonempty(self):
|
||||||
|
for name, entry in CATALOG.items():
|
||||||
|
assert entry.category, f"{name}: category must not be empty"
|
||||||
|
|
||||||
|
def test_all_entries_have_description(self):
|
||||||
|
for name, entry in CATALOG.items():
|
||||||
|
assert entry.description, f"{name}: description must not be empty"
|
||||||
|
|
||||||
|
def test_catalog_keys_match_entry_names(self):
|
||||||
|
for key, entry in CATALOG.items():
|
||||||
|
assert key == entry.name, (
|
||||||
|
f"Key '{key}' does not match entry name '{entry.name}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_unknown_module_not_in_catalog(self):
|
||||||
|
assert CATALOG.get("nonexistent") is None
|
||||||
|
|
||||||
|
def test_has_preinstalled_modules(self):
|
||||||
|
preinstalled = [e for e in CATALOG.values() if e.preinstalled]
|
||||||
|
assert len(preinstalled) >= 5
|
||||||
|
|
||||||
|
def test_has_installable_modules(self):
|
||||||
|
installable = [e for e in CATALOG.values() if not e.preinstalled]
|
||||||
|
assert len(installable) >= 5
|
||||||
|
|
||||||
|
def test_known_preinstalled_modules(self):
|
||||||
|
expected = {"osmosdr", "satellites", "gsm", "rds", "fosphor"}
|
||||||
|
preinstalled_names = {
|
||||||
|
e.name for e in CATALOG.values() if e.preinstalled
|
||||||
|
}
|
||||||
|
assert expected.issubset(preinstalled_names)
|
||||||
|
|
||||||
|
def test_known_installable_modules(self):
|
||||||
|
expected = {"lora_sdr", "ieee802_11", "adsb", "iridium"}
|
||||||
|
installable_names = {
|
||||||
|
e.name for e in CATALOG.values() if not e.preinstalled
|
||||||
|
}
|
||||||
|
assert expected.issubset(installable_names)
|
||||||
|
|
||||||
|
|
||||||
|
class TestModels:
|
||||||
|
def test_summary_round_trip(self):
|
||||||
|
summary = OOTModuleSummary(
|
||||||
|
name="test_mod",
|
||||||
|
description="A test module",
|
||||||
|
category="Testing",
|
||||||
|
preinstalled=True,
|
||||||
|
installed=True,
|
||||||
|
)
|
||||||
|
data = json.loads(summary.model_dump_json())
|
||||||
|
restored = OOTModuleSummary(**data)
|
||||||
|
assert restored.name == "test_mod"
|
||||||
|
assert restored.preinstalled is True
|
||||||
|
assert restored.installed is True
|
||||||
|
|
||||||
|
def test_summary_installed_default_none(self):
|
||||||
|
summary = OOTModuleSummary(
|
||||||
|
name="x", description="y", category="z"
|
||||||
|
)
|
||||||
|
assert summary.installed is None
|
||||||
|
assert summary.preinstalled is False
|
||||||
|
|
||||||
|
def test_detail_includes_install_fields(self):
|
||||||
|
detail = OOTModuleDetail(
|
||||||
|
name="lora_sdr",
|
||||||
|
description="LoRa",
|
||||||
|
category="IoT",
|
||||||
|
git_url="https://github.com/tapparelj/gr-lora_sdr",
|
||||||
|
branch="master",
|
||||||
|
build_deps=[],
|
||||||
|
cmake_args=[],
|
||||||
|
homepage="",
|
||||||
|
gr_versions="3.10+",
|
||||||
|
)
|
||||||
|
data = detail.model_dump()
|
||||||
|
assert "git_url" in data
|
||||||
|
assert "branch" in data
|
||||||
|
assert "build_deps" in data
|
||||||
|
assert "install_example" in data
|
||||||
|
assert "preinstalled" in data
|
||||||
|
|
||||||
|
def test_detail_preinstalled_flag(self):
|
||||||
|
detail = OOTModuleDetail(
|
||||||
|
name="osmosdr",
|
||||||
|
description="HW",
|
||||||
|
category="Hardware",
|
||||||
|
git_url="https://example.com",
|
||||||
|
branch="master",
|
||||||
|
build_deps=[],
|
||||||
|
cmake_args=[],
|
||||||
|
homepage="",
|
||||||
|
gr_versions="3.10+",
|
||||||
|
preinstalled=True,
|
||||||
|
)
|
||||||
|
assert detail.preinstalled is True
|
||||||
|
|
||||||
|
def test_directory_index_count(self):
|
||||||
|
summaries = [
|
||||||
|
OOTModuleSummary(name="a", description="A", category="X"),
|
||||||
|
OOTModuleSummary(
|
||||||
|
name="b", description="B", category="Y", preinstalled=True
|
||||||
|
),
|
||||||
|
]
|
||||||
|
index = OOTDirectoryIndex(modules=summaries, count=2)
|
||||||
|
assert index.count == 2
|
||||||
|
assert len(index.modules) == 2
|
||||||
|
assert index.modules[1].preinstalled is True
|
||||||
|
|
||||||
|
|
||||||
|
class TestBuildInstallExample:
|
||||||
|
def test_simple_module(self):
|
||||||
|
entry = OOTModuleEntry(
|
||||||
|
name="adsb",
|
||||||
|
description="ADS-B decoder",
|
||||||
|
category="Aviation",
|
||||||
|
git_url="https://github.com/mhostetter/gr-adsb",
|
||||||
|
branch="main",
|
||||||
|
)
|
||||||
|
example = build_install_example(entry)
|
||||||
|
assert "git_url=" in example
|
||||||
|
assert "gr-adsb" in example
|
||||||
|
# branch=main is the default, should not appear
|
||||||
|
assert "branch=" not in example
|
||||||
|
|
||||||
|
def test_non_default_branch(self):
|
||||||
|
entry = OOTModuleEntry(
|
||||||
|
name="lora_sdr",
|
||||||
|
description="LoRa",
|
||||||
|
category="IoT",
|
||||||
|
git_url="https://github.com/tapparelj/gr-lora_sdr",
|
||||||
|
branch="master",
|
||||||
|
)
|
||||||
|
example = build_install_example(entry)
|
||||||
|
assert 'branch="master"' in example
|
||||||
|
|
||||||
|
def test_with_build_deps(self):
|
||||||
|
entry = OOTModuleEntry(
|
||||||
|
name="osmosdr",
|
||||||
|
description="HW source/sink",
|
||||||
|
category="Hardware",
|
||||||
|
git_url="https://github.com/osmocom/gr-osmosdr",
|
||||||
|
branch="master",
|
||||||
|
build_deps=["librtlsdr-dev", "libairspy-dev"],
|
||||||
|
)
|
||||||
|
example = build_install_example(entry)
|
||||||
|
assert "build_deps=" in example
|
||||||
|
assert "librtlsdr-dev" in example
|
||||||
|
|
||||||
|
def test_all_catalog_entries_produce_example(self):
|
||||||
|
for name, entry in CATALOG.items():
|
||||||
|
example = build_install_example(entry)
|
||||||
|
assert example.startswith("install_oot_module("), (
|
||||||
|
f"{name}: bad install example"
|
||||||
|
)
|
||||||
|
assert example.endswith(")")
|
||||||
Loading…
x
Reference in New Issue
Block a user