diff --git a/tools/chatgui/tests/test_radio_commands.cpp b/tools/chatgui/tests/test_radio_commands.cpp index e1117f2b..7303f79f 100644 --- a/tools/chatgui/tests/test_radio_commands.cpp +++ b/tools/chatgui/tests/test_radio_commands.cpp @@ -53,7 +53,12 @@ extern "C" void __wrap_u_free_impl(void* pointer, const char* source) { extern "C" void __real_radio_call_trampoline(void*); extern "C" void __wrap_radio_call_trampoline(void* arg) { auto* request = static_cast(arg); - bindingValid &= request->inst == executingCore; observed.append(*request); + if (request->inst != executingCore) { + bindingValid = false; + DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "Radio command binding mismatch id=%llu request_inst=%p executing_core=%p", + (unsigned long long)request->id, static_cast(request->inst), static_cast(executingCore)); + } + observed.append(*request); __real_radio_call_trampoline(arg); } // Настоящий mixer остаётся включённым; null backend устраняет зависимость от аппаратных устройств. @@ -199,18 +204,33 @@ private slots: #ifdef UTUN_TEST_RADIO_HOOKS watching = true; #endif - std::thread producer([] { - for (uint64_t id = 1; id <= 96; ++id) gui_bridge_radio_call(GUI_RADIO_CALL_CANCEL, id, 0, 0); + std::atomic turn{0}, submitted{0}; + std::thread producer([&] { + for (unsigned id = 1; id <= 96; ++id) { + while (turn < id) std::this_thread::yield(); + gui_bridge_radio_call(GUI_RADIO_CALL_CANCEL, id, 0, 0); submitted = id; + } }); - for (unsigned i = 0; i < 64; ++i) { - gui_bridge_set_inst(nullptr); auto& core = i % 2 ? first : second; core.attach(); core.poll(); + bool controlsAccepted = true; + for (unsigned i = 1; i <= 96; ++i) { + turn = i; + gui_bridge_set_inst(nullptr); auto& core = i % 2 ? first : second; core.attach(); + while (submitted < i) std::this_thread::yield(); + first.poll(); second.poll(); + // Контроль исполняется у каждого владельца даже при отклонении/отмене конкурирующей команды. + controlsAccepted &= gui_bridge_radio_call(GUI_RADIO_CALL_CANCEL, 1000 + i, 0, 0) == 0; core.poll(); } producer.join(); gui_bridge_set_inst(nullptr); first.attach(); - QCOMPARE(gui_bridge_radio_call(GUI_RADIO_CALL_START, 1000, 7654321, 42), 0); first.poll(); - QVERIFY(bindingValid); QCOMPARE(pending(first.ua), 0u); QCOMPARE(pending(second.ua), 0u); + QCOMPARE(gui_bridge_radio_call(GUI_RADIO_CALL_START, 2000, 7654321, 42), 0); first.poll(); + QVERIFY(controlsAccepted); QVERIFY(bindingValid); QCOMPARE(pending(first.ua), 0u); QCOMPARE(pending(second.ua), 0u); QCOMPARE(first.results, 1u); QCOMPARE(second.results, 0u); QVERIFY(first.resultValid); #ifdef UTUN_TEST_RADIO_HOOKS - QVERIFY(!observed.isEmpty()); QCOMPARE(observed.back().inst, first.inst); QCOMPARE(observed.back().id, uint64_t(1000)); + size_t firstCount = 0, secondCount = 0; + for (const auto& request : observed) { firstCount += request.inst == first.inst; secondCount += request.inst == second.inst; } + QVERIFY(firstCount >= 49); QVERIFY(secondCount >= 48); + QCOMPARE(observed.back().inst, first.inst); QCOMPARE(observed.back().id, uint64_t(2000)); + DEBUG_INFO(DEBUG_CATEGORY_RADIO, "Radio binding verified: first_executed=%zu second_executed=%zu binding_valid=%d", + firstCount, secondCount, bindingValid); #endif } };