Extend desktop command regression suite and verify SQLite stopping handoff
This commit is contained in:
1 parent
72f4258410
commit
9ee64d9a9f
3 files changed
+51
-32
No files matched your search
@@ -580,19 +580,19 @@ endif()
|
||||
add_test(NAME test_settings_save COMMAND test_settings_save)
|
||||
set_tests_properties(test_settings_save PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;XDG_SESSION_TYPE=x11" TIMEOUT 60)
|
||||
|
||||
add_executable(test_radio_commands tests/test_radio_commands.cpp ${DM_DELETION_SOURCES})
|
||||
add_dependencies(test_radio_commands chatgui_translations)
|
||||
target_include_directories(test_radio_commands PRIVATE $<TARGET_PROPERTY:vibechat,INCLUDE_DIRECTORIES>)
|
||||
target_compile_definitions(test_radio_commands PRIVATE $<TARGET_PROPERTY:vibechat,COMPILE_DEFINITIONS>)
|
||||
target_link_libraries(test_radio_commands PRIVATE ${DM_DELETION_LIBRARIES} ${QT_TEST_LIB})
|
||||
add_executable(test_desktop_commands tests/test_desktop_commands.cpp ${DM_DELETION_SOURCES})
|
||||
add_dependencies(test_desktop_commands chatgui_translations)
|
||||
target_include_directories(test_desktop_commands PRIVATE $<TARGET_PROPERTY:vibechat,INCLUDE_DIRECTORIES>)
|
||||
target_compile_definitions(test_desktop_commands PRIVATE $<TARGET_PROPERTY:vibechat,COMPILE_DEFINITIONS>)
|
||||
target_link_libraries(test_desktop_commands PRIVATE ${DM_DELETION_LIBRARIES} ${QT_TEST_LIB})
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_compile_definitions(test_radio_commands PRIVATE UTUN_TEST_RADIO_HOOKS)
|
||||
foreach(symbol u_malloc_impl u_calloc_impl u_free_impl radio_call_trampoline ma_context_init)
|
||||
target_link_options(test_radio_commands PRIVATE "-Wl,--wrap=${symbol}")
|
||||
target_compile_definitions(test_desktop_commands PRIVATE UTUN_TEST_DESKTOP_HOOKS)
|
||||
foreach(symbol u_malloc_impl u_calloc_impl u_free_impl radio_call_trampoline ma_context_init sqlite3_step sqlite3_bind_text sqlite3_finalize)
|
||||
target_link_options(test_desktop_commands PRIVATE "-Wl,--wrap=${symbol}")
|
||||
endforeach()
|
||||
endif()
|
||||
add_test(NAME test_radio_commands COMMAND test_radio_commands)
|
||||
set_tests_properties(test_radio_commands PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;XDG_SESSION_TYPE=x11" TIMEOUT 30)
|
||||
add_test(NAME test_desktop_commands COMMAND test_desktop_commands)
|
||||
set_tests_properties(test_desktop_commands PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen;XDG_SESSION_TYPE=x11" TIMEOUT 30)
|
||||
|
||||
add_executable(test_group_edits tests/test_group_edits.cpp ${DM_DELETION_SOURCES})
|
||||
add_dependencies(test_group_edits chatgui_translations)
|
||||
@@ -982,7 +982,7 @@ add_test(NAME test_voice_file COMMAND test_voice_file)
|
||||
if(WIN32)
|
||||
# AudioDevice закрывает неисправный WASAPI в worker с собственным COM apartment.
|
||||
foreach(audio_target vibechat test_file_drop test_invite_links test_video_engine test_stream_view test_stream_publish test_call_commands
|
||||
test_voice_file test_sound_settings test_settings_save test_radio_commands)
|
||||
test_voice_file test_sound_settings test_settings_save test_desktop_commands)
|
||||
target_link_libraries(${audio_target} PRIVATE ole32)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
+19
-19
@@ -1,4 +1,4 @@
|
||||
/* Настоящие MainWindow/bridge/radio/uasync: отказ, отмена, OOM и привязка команды к владельцу очереди.
|
||||
/* Desktop MainWindow/MessageList/delegate/SQLite/bridge: отказы, отмена, OOM и привязка к владельцу.
|
||||
* Native trampoline исполняется без подмены; пустой каталог CHAT даёт штатный FINISHED/UNAVAILABLE для START. */
|
||||
#include "../src/mainwindow.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
@@ -24,7 +24,7 @@ static UTUN_INSTANCE* executingCore = nullptr;
|
||||
static bool bindingValid = true;
|
||||
static QList<radio_call_request> observed;
|
||||
|
||||
#ifdef UTUN_TEST_RADIO_HOOKS
|
||||
#ifdef UTUN_TEST_DESKTOP_HOOKS
|
||||
#include "miniaudio.h"
|
||||
static std::atomic<bool> watching{false};
|
||||
static std::atomic<int> failAllocation{0};
|
||||
@@ -69,13 +69,13 @@ extern "C" ma_result __wrap_ma_context_init(const ma_backend*, ma_uint32, const
|
||||
}
|
||||
#endif
|
||||
|
||||
struct RadioCore {
|
||||
struct DesktopCore {
|
||||
QTemporaryDir directory;
|
||||
UASYNC* ua = uasync_create();
|
||||
UTUN_INSTANCE* inst = nullptr;
|
||||
unsigned results = 0;
|
||||
bool resultValid = true;
|
||||
~RadioCore() {
|
||||
~DesktopCore() {
|
||||
gui_bridge_set_inst(nullptr); gui_bridge_set_uasync(nullptr);
|
||||
if (ua) uasync_mark_stopped(ua);
|
||||
if (inst) utun_instance_destroy(inst);
|
||||
@@ -98,7 +98,7 @@ struct RadioCore {
|
||||
inst->test_user_ptr = this;
|
||||
chat_event_set_handler(inst, [](UTUN_INSTANCE* instance, int type, const uint8_t* data, int len) {
|
||||
if (type != CHAT_EVT_RADIO_CALL) return;
|
||||
auto* core = static_cast<RadioCore*>(instance->test_user_ptr); ++core->results;
|
||||
auto* core = static_cast<DesktopCore*>(instance->test_user_ptr); ++core->results;
|
||||
core->resultValid &= len == 27 && data[24] == RADIO_CALL_FINISHED && data[25] == RADIO_CALL_UNAVAILABLE && !data[26];
|
||||
});
|
||||
attach(); poll(); return true;
|
||||
@@ -112,7 +112,7 @@ static unsigned pending(UASYNC* ua) {
|
||||
return count;
|
||||
}
|
||||
|
||||
class RadioCommandsTest : public QObject {
|
||||
class DesktopCommandsTest : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
@@ -122,17 +122,17 @@ private slots:
|
||||
}
|
||||
void init() {
|
||||
observed.clear(); bindingValid = true;
|
||||
#ifdef UTUN_TEST_RADIO_HOOKS
|
||||
#ifdef UTUN_TEST_DESKTOP_HOOKS
|
||||
watching = false; failAllocation = 0;
|
||||
#endif
|
||||
}
|
||||
void cleanup() {
|
||||
#ifdef UTUN_TEST_RADIO_HOOKS
|
||||
#ifdef UTUN_TEST_DESKTOP_HOOKS
|
||||
failAllocation = 0;
|
||||
#endif
|
||||
gui_bridge_set_inst(nullptr); gui_bridge_set_uasync(nullptr);
|
||||
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
||||
#ifdef UTUN_TEST_RADIO_HOOKS
|
||||
#ifdef UTUN_TEST_DESKTOP_HOOKS
|
||||
watching = false;
|
||||
std::lock_guard<std::mutex> lock(allocationMutex);
|
||||
for (const auto& item : outstanding)
|
||||
@@ -143,7 +143,7 @@ private slots:
|
||||
void lifecycle_data() {
|
||||
QTest::addColumn<int>("action"); QTest::addColumn<QString>("scenario"); QTest::addColumn<bool>("window");
|
||||
QStringList scenarios{"unavailable", "stopped", "cancelled", "executed"};
|
||||
#ifdef UTUN_TEST_RADIO_HOOKS
|
||||
#ifdef UTUN_TEST_DESKTOP_HOOKS
|
||||
scenarios << "request-oom" << "queue-oom";
|
||||
#endif
|
||||
for (int action = GUI_RADIO_CALL_START; action <= GUI_RADIO_CALL_CONTINUE; ++action)
|
||||
@@ -155,12 +155,12 @@ private slots:
|
||||
}
|
||||
void lifecycle() {
|
||||
QFETCH(int, action); QFETCH(QString, scenario); QFETCH(bool, window);
|
||||
RadioCore core; QVERIFY(core.start());
|
||||
DesktopCore core; QVERIFY(core.start());
|
||||
std::unique_ptr<MainWindow> form;
|
||||
if (window) form = std::make_unique<MainWindow>(nullptr, nullptr, QString()); // UtunNode остаётся владельцу fixture.
|
||||
if (scenario == "unavailable") gui_bridge_set_inst(nullptr);
|
||||
if (scenario == "stopped") uasync_mark_stopped(core.ua);
|
||||
#ifdef UTUN_TEST_RADIO_HOOKS
|
||||
#ifdef UTUN_TEST_DESKTOP_HOOKS
|
||||
watching = true; failAllocation = scenario == "request-oom" ? 1 : scenario == "queue-oom" ? 2 : 0;
|
||||
#endif
|
||||
const bool accepted = scenario == "executed" || scenario == "cancelled" || scenario == "destroyed";
|
||||
@@ -174,7 +174,7 @@ private slots:
|
||||
else gui_bridge_set_inst(nullptr);
|
||||
QCOMPARE(pending(core.ua), 0u); QVERIFY(bindingValid);
|
||||
QCOMPARE(core.results, executes && action == GUI_RADIO_CALL_START ? 1u : 0u); QVERIFY(core.resultValid);
|
||||
#ifdef UTUN_TEST_RADIO_HOOKS
|
||||
#ifdef UTUN_TEST_DESKTOP_HOOKS
|
||||
QCOMPARE(failAllocation.load(), 0); QCOMPARE(observed.size(), executes ? 1 : 0);
|
||||
if (executes) {
|
||||
static const int nativeActions[]{RADIO_CALL_START, RADIO_CALL_READY, RADIO_CALL_ACCEPT, RADIO_CALL_DECLINE,
|
||||
@@ -189,7 +189,7 @@ private slots:
|
||||
window, action, qPrintable(scenario), core.results);
|
||||
}
|
||||
void invalidArguments() {
|
||||
RadioCore core; QVERIFY(core.start()); const auto baseline = u_get_allocated_count();
|
||||
DesktopCore core; QVERIFY(core.start()); const auto baseline = u_get_allocated_count();
|
||||
QCOMPARE(gui_bridge_radio_call(static_cast<gui_radio_call_action>(99), 1, 0, 0), -1);
|
||||
QCOMPARE(gui_bridge_radio_call(GUI_RADIO_CALL_START, 0, 1, 1), -1);
|
||||
QCOMPARE(gui_bridge_radio_call(GUI_RADIO_CALL_START, 1, 0, 1), -1);
|
||||
@@ -200,8 +200,8 @@ private slots:
|
||||
QCOMPARE(pending(core.ua), 0u); QCOMPARE(u_get_allocated_count(), baseline);
|
||||
}
|
||||
void concurrentRebind() {
|
||||
RadioCore first, second; QVERIFY(first.start()); gui_bridge_set_inst(nullptr); QVERIFY(second.start());
|
||||
#ifdef UTUN_TEST_RADIO_HOOKS
|
||||
DesktopCore first, second; QVERIFY(first.start()); gui_bridge_set_inst(nullptr); QVERIFY(second.start());
|
||||
#ifdef UTUN_TEST_DESKTOP_HOOKS
|
||||
watching = true;
|
||||
#endif
|
||||
std::atomic<unsigned> turn{0}, submitted{0};
|
||||
@@ -224,7 +224,7 @@ private slots:
|
||||
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
|
||||
#ifdef UTUN_TEST_DESKTOP_HOOKS
|
||||
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);
|
||||
@@ -235,5 +235,5 @@ private slots:
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(RadioCommandsTest)
|
||||
#include "test_radio_commands.moc"
|
||||
QTEST_MAIN(DesktopCommandsTest)
|
||||
#include "test_desktop_commands.moc"
|
||||
@@ -115,7 +115,12 @@ private slots:
|
||||
}
|
||||
}
|
||||
|
||||
void coreStopFinishesOwnerThread_data() {
|
||||
QTest::addColumn<bool>("direct");
|
||||
QTest::newRow("core-stop") << false; QTest::newRow("GUI-stop") << true;
|
||||
}
|
||||
void coreStopFinishesOwnerThread() {
|
||||
QFETCH(bool, direct);
|
||||
QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("node.cfg");
|
||||
QFile config(path); QVERIFY(config.open(QIODevice::WriteOnly));
|
||||
@@ -123,10 +128,24 @@ private slots:
|
||||
QCOMPARE(config.write(text), text.size()); config.close();
|
||||
UtunNode node;
|
||||
node.setDbPath(directory.filePath("db")); node.setDebugFile(directory.filePath("node.log"));
|
||||
QSignalSpy stopped(&node, &UtunNode::stopped);
|
||||
QSignalSpy stopped(&node, &UtunNode::stopped), stopping(&node, &UtunNode::stopping);
|
||||
sqlite3* borrowed = nullptr; bool liveDuringDetach = false;
|
||||
connect(&node, &UtunNode::stopping, &node, [&] {
|
||||
sqlite3_stmt* statement = nullptr;
|
||||
if (borrowed && QThread::currentThread() == qApp->thread()) {
|
||||
int rc = sqlite3_prepare_v2(borrowed, "SELECT node_id FROM local_identity WHERE id=1", -1, &statement, nullptr);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(statement);
|
||||
const int final = sqlite3_finalize(statement); liveDuringDetach = rc == SQLITE_ROW && final == SQLITE_OK;
|
||||
}
|
||||
borrowed = nullptr;
|
||||
});
|
||||
QVERIFY(node.start(path));
|
||||
gui_bridge_post_uasync_fn([](void*) { utun_instance_stop(gui_bridge_get_inst()); }, nullptr);
|
||||
borrowed = node.databaseHandle(); QVERIFY(borrowed);
|
||||
QCOMPARE(node.databasePath(), directory.filePath("db"));
|
||||
if (direct) node.stop();
|
||||
else gui_bridge_post_uasync_fn([](void*) { utun_instance_stop(gui_bridge_get_inst()); }, nullptr);
|
||||
QTRY_COMPARE_WITH_TIMEOUT(stopped.count(), 1, 3000);
|
||||
QCOMPARE(stopping.count(), 1); QVERIFY(liveDuringDetach); QVERIFY(!borrowed); QVERIFY(!node.databaseHandle());
|
||||
node.stop();
|
||||
QVERIFY(!node.isRunning() && !node.instance() && !gui_bridge_get_inst() && !gui_bridge_is_uasync_ready());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user