diff --git a/tests/fm_tone_presentation_contract_test.py b/tests/fm_tone_presentation_contract_test.py index f2f8bf038b..eef144fbe7 100644 --- a/tests/fm_tone_presentation_contract_test.py +++ b/tests/fm_tone_presentation_contract_test.py @@ -67,13 +67,17 @@ def main() -> int: ("m_fmToneValueCmb", "m_fmToneRxValueCmb"), ) rx_source = rx_path.read_text(encoding="utf-8") - radio_model_source = (source_root / "src/models/RadioModel.cpp").read_text( + slice_model_source = (source_root / "src/models/SliceModel.cpp").read_text( encoding="utf-8" ) - if "&SliceModel::fmDtcsCommandIssued" not in radio_model_source: - fail("RadioModel no longer forwards normalized DTCS operator intent") - if "m_backend->setSliceFmDtcs(" not in radio_model_source: - fail("RadioModel no longer sends DTCS intent through IRadioBackend") + if "controlRequested({field, DtcsSetting{" not in slice_model_source: + fail("SliceModel no longer publishes normalized DTCS operator intent") + icom_source = (source_root / "src/core/backends/icom/IcomCivBackend.cpp").read_text( + encoding="utf-8" + ) + dtcs_dispatch = re.compile(r"case Field::FmDtcs: \{(?:(?!case Field::).)*setSliceFmDtcs\(", re.S) + if not dtcs_dispatch.search(icom_source): + fail("the Icom backend no longer applies the typed DTCS request") mode_visibility_edge = re.compile( r"m_fmContainer->setVisible\(isFM\);\s*" r"(?:\s*//[^\n]*\n)*\s*configureFmToneControls\(\);" diff --git a/tests/hl2_tci_signaling_test.cpp b/tests/hl2_tci_signaling_test.cpp index 1dd7e9f28b..4f8c2e417c 100644 --- a/tests/hl2_tci_signaling_test.cpp +++ b/tests/hl2_tci_signaling_test.cpp @@ -649,6 +649,18 @@ static void testSeamBackendCannotWedgeOnVfoB() "a refused VFO-B tune leaves no route transition in flight"); } +namespace hl2 { +// Reuses an existing friend rather than adding a production test hook. +struct Hl2TxGateTestAccess { + // Link state only; no Metis client is started. Typed model controls + // dispatch only while the radio is up. + static void setConnected(Hl2Backend& backend, bool connected) + { + backend.m_connected = connected; + } +}; +} // namespace hl2 + // promoteTxSliceAndContinue() has the same shape of trap: `slice set N tx=1` is // Flex text with no seam counterpart, and every caller opens a route transition // around it. A continuation that never runs strands that transition, so the @@ -666,6 +678,12 @@ static void testSeamBackendPromoteAlwaysAnswers() check(!model.slice(0)->isTxSlice(), "fixture precondition: the FIXTURE seeded tx=0 on this slice"); + auto* hl2 = dynamic_cast(model.backend()); + check(hl2 != nullptr, "fixture precondition: the HL2 backend is live"); + if (!hl2) { return; } + // A TCI client promotes a slice on a connected radio. + hl2::Hl2TxGateTestAccess::setConnected(*hl2, true); + TciServer server(&model); bool answered = false, selected = false; Hl2TciSignalingTest::promote(server, 0, &answered, &selected); @@ -691,6 +709,7 @@ static void testSeamBackendPromoteAlwaysAnswers() Hl2TciSignalingTest::promote(server, 0, &answered, &selected); check(answered && selected, "an already-TX slice is selected with no command at all"); + hl2::Hl2TxGateTestAccess::setConnected(*hl2, false); } #endif diff --git a/tests/icom_family_test.cpp b/tests/icom_family_test.cpp index 3de1ee85b5..cf93a0e214 100644 --- a/tests/icom_family_test.cpp +++ b/tests/icom_family_test.cpp @@ -13,6 +13,7 @@ #include "models/TransmitModel.h" #include "core/RadioDiscovery.h" #include "core/backends/icom/IcomCivBackend.h" +#include "core/backends/icom/IcomSession.h" #include "core/backends/icom/IcomControls.h" #include "core/backends/icom/IcomModels.h" #include "core/backends/icom/CivCodec.h" @@ -25,6 +26,7 @@ #include #include +#include #include #include #include @@ -45,6 +47,19 @@ struct IcomCivBackendTestAccess { return backend.confirmationFor(frame); } + // Typed model controls dispatch only while the radio is up. An unstarted + // IcomSession constructs no streams or sockets, so the frame is scheduled + // and then dropped by the unconnected transport. + static void setConnected(IcomCivBackend& backend, bool connected) + { + backend.m_connected = connected; + if (connected) { + backend.m_session = std::make_unique(); + } else { + backend.m_session.reset(); + } + } + static void injectConnectedFrame(IcomCivBackend& backend, const CivFrame& frame) { backend.m_connected = true; @@ -999,11 +1014,22 @@ int main(int argc, char** argv) // phoneLevelFollowsNetworkInput, this would fail on the // m_networkModLevelPercent < 0 refusal — a reason that has nothing // to do with the gate this section pins (PR #5643 review). + // + // Operator TX intent is dispatched only to a connected radio with an + // open TX session. The connect attempt above never completes, so + // reopen admission the socket-free way and mark the link up. + } + check(model.rebuildBackendForTest(QStringLiteral("icom")), + "a socket-free Icom backend replaces the unconnected one"); + auto* live = dynamic_cast(model.backend()); + if (live) { + icom::IcomCivBackendTestAccess::setConnected(*live, true); model.transmitModel().setMicLevel(70); - check(icom::IcomCivBackendTestAccess::micGainReported(*rebuilt) - && icom::IcomCivBackendTestAccess::micGainPercent(*rebuilt) + check(icom::IcomCivBackendTestAccess::micGainReported(*live) + && icom::IcomCivBackendTestAccess::micGainPercent(*live) == 70, "the operator moving the slider still reaches the Icom mic gain"); + icom::IcomCivBackendTestAccess::setConnected(*live, false); } }