Cover channel settings queue failures and nested dialog lifetime
This commit is contained in:
1 parent
f12c7d072a
commit
271cfcd2f8
2 files changed
+182
No files matched your search
@@ -480,6 +480,16 @@ target_link_libraries(test_channel_list PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun u
|
||||
add_test(NAME test_channel_list COMMAND test_channel_list)
|
||||
set_tests_properties(test_channel_list PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 30)
|
||||
|
||||
add_executable(test_channel_settings tests/test_channel_settings.cpp src/channelsettingsdialog.cpp
|
||||
src/avatars/groupavatarmanager.cpp db/db_manager.cpp transport/gui_bridge_impl.cpp)
|
||||
target_link_libraries(test_channel_settings PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun utun_voice pthread ${CMAKE_DL_LIBS})
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_compile_definitions(test_channel_settings PRIVATE UTUN_TEST_SETTINGS_ALLOC_FAILURES)
|
||||
target_link_options(test_channel_settings PRIVATE "-Wl,--wrap=u_malloc_impl" "-Wl,--wrap=u_calloc_impl")
|
||||
endif()
|
||||
add_test(NAME test_channel_settings COMMAND test_channel_settings)
|
||||
set_tests_properties(test_channel_settings PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 30)
|
||||
|
||||
add_executable(test_single_instance tests/test_single_instance.cpp src/single_instance.cpp)
|
||||
target_link_libraries(test_single_instance PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun pthread ${CMAKE_DL_LIBS})
|
||||
if(WIN32)
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/* Настоящие Qt-диалоги/SQLite/bridge: отказы очереди и владение вложенными окнами. */
|
||||
#include "../src/channelsettingsdialog.h"
|
||||
#include "../src/avatars/groupavatarmanager.h"
|
||||
#include "../db/db_manager.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include <QtTest>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QPointer>
|
||||
#include <QScopeGuard>
|
||||
extern "C" {
|
||||
#include "utun_instance.h"
|
||||
#include "chat/chat_core.h"
|
||||
#include "mem.h"
|
||||
#include "debug_config.h"
|
||||
}
|
||||
|
||||
#ifdef UTUN_TEST_SETTINGS_ALLOC_FAILURES
|
||||
static int failAllocation;
|
||||
static const char* allocationFile;
|
||||
static bool rejectAllocation(const char* where) {
|
||||
if (!failAllocation || !strstr(where, allocationFile) || --failAllocation) return false;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "[INJECT] channel settings allocation failure at %s", where); return true;
|
||||
}
|
||||
extern "C" void* __real_u_malloc_impl(uint32_t, const char*);
|
||||
extern "C" void* __wrap_u_malloc_impl(uint32_t bytes, const char* where) {
|
||||
return rejectAllocation(where) ? nullptr : __real_u_malloc_impl(bytes, where);
|
||||
}
|
||||
extern "C" void* __real_u_calloc_impl(uint32_t, uint32_t, const char*);
|
||||
extern "C" void* __wrap_u_calloc_impl(uint32_t count, uint32_t bytes, const char* where) {
|
||||
return rejectAllocation(where) ? nullptr : __real_u_calloc_impl(count, bytes, where);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct SettingsCore {
|
||||
UASYNC* ua = uasync_create();
|
||||
UTUN_INSTANCE* inst = nullptr;
|
||||
DbManager db;
|
||||
QString channel;
|
||||
~SettingsCore() {
|
||||
gui_bridge_set_inst(nullptr); gui_bridge_set_uasync(nullptr);
|
||||
if (ua) uasync_mark_stopped(ua);
|
||||
db.setDb(nullptr);
|
||||
if (inst) utun_instance_destroy(inst);
|
||||
if (ua) uasync_destroy(ua, 0);
|
||||
}
|
||||
bool start(bool attach = true) {
|
||||
if (!ua) return false;
|
||||
const char* config = "[global]\n"
|
||||
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
|
||||
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
|
||||
"[chatserver]\ngroup_autoconnect=0\nmember_files_enabled=0\nstorage_autoload=0\n";
|
||||
inst = utun_instance_create_from_str(ua, config);
|
||||
if (!inst || utun_core_start(inst) || chat_service_start(inst)) return false;
|
||||
chat_core_create_channel_auto(inst, "Settings");
|
||||
db.setDb(inst->topo_sqlite_db); db.setMyNodeId(inst->node_id);
|
||||
if (db.getChannels().size() != 1) return false;
|
||||
channel = db.getChannels()[0].channelId;
|
||||
if (attach) { gui_bridge_set_uasync(ua); gui_bridge_set_inst(inst); }
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ChannelSettingsTest : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); gui_bridge_init();
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs);
|
||||
}
|
||||
|
||||
void pendingSaveIsOwned() {
|
||||
SettingsCore core; QVERIFY(core.start());
|
||||
ChannelSettingsDialog dialog(core.channel, &core.db);
|
||||
const auto baseline = u_get_allocated_count();
|
||||
QSignalSpy accepted(&dialog, &QDialog::accepted);
|
||||
QVERIFY(QMetaObject::invokeMethod(&dialog, "onSave", Qt::DirectConnection));
|
||||
QCOMPARE(accepted.count(), 1);
|
||||
gui_bridge_set_inst(nullptr);
|
||||
QCOMPARE(u_get_allocated_count(), baseline);
|
||||
}
|
||||
|
||||
void savesBothValues() {
|
||||
SettingsCore core; QVERIFY(core.start());
|
||||
ChannelSettingsDialog dialog(core.channel, &core.db);
|
||||
const auto checks = dialog.findChildren<QCheckBox*>(); QCOMPARE(checks.size(), 2);
|
||||
QVERIFY(!checks[0]->isChecked()); QVERIFY(checks[1]->isChecked());
|
||||
checks[0]->setChecked(true); checks[1]->setChecked(false);
|
||||
QVERIFY(QMetaObject::invokeMethod(&dialog, "onSave", Qt::DirectConnection));
|
||||
uasync_poll(core.ua, 0);
|
||||
QCOMPARE(core.db.getUiStateInt("ch_" + core.channel + "_auto_play_voice", -1), 1);
|
||||
QCOMPARE(core.db.getUiStateInt("ch_" + core.channel + "_sound_on_message", -1), 0);
|
||||
}
|
||||
|
||||
void stoppedCoreKeepsDialogOpen() {
|
||||
SettingsCore core; QVERIFY(core.start(false));
|
||||
ChannelSettingsDialog dialog(core.channel, &core.db);
|
||||
QSignalSpy accepted(&dialog, &QDialog::accepted);
|
||||
QVERIFY(QMetaObject::invokeMethod(&dialog, "onSave", Qt::DirectConnection));
|
||||
QVERIFY(accepted.isEmpty());
|
||||
auto* status = dialog.findChild<QLabel*>("channelSettingsStatus"); QVERIFY(status && !status->text().isEmpty());
|
||||
}
|
||||
|
||||
#ifdef UTUN_TEST_SETTINGS_ALLOC_FAILURES
|
||||
void allocationFailure_data() {
|
||||
QTest::addColumn<QByteArray>("source"); QTest::addColumn<int>("position");
|
||||
for (const auto* source : {"channelsettingsdialog.cpp", "gui_bridge_impl.cpp"})
|
||||
for (int position : {1, 2}) QTest::newRow(qPrintable(QString("%1-%2").arg(source).arg(position))) << QByteArray(source) << position;
|
||||
}
|
||||
void allocationFailure() {
|
||||
QFETCH(QByteArray, source); QFETCH(int, position);
|
||||
SettingsCore core; QVERIFY(core.start());
|
||||
ChannelSettingsDialog dialog(core.channel, &core.db);
|
||||
QSignalSpy accepted(&dialog, &QDialog::accepted);
|
||||
const auto baseline = u_get_allocated_count();
|
||||
allocationFile = source.constData(); failAllocation = position;
|
||||
auto reset = qScopeGuard([] { failAllocation = 0; allocationFile = nullptr; });
|
||||
QVERIFY(QMetaObject::invokeMethod(&dialog, "onSave", Qt::DirectConnection));
|
||||
QCOMPARE(failAllocation, 0); QVERIFY(accepted.isEmpty());
|
||||
gui_bridge_set_inst(nullptr); QCOMPARE(u_get_allocated_count(), baseline);
|
||||
auto* status = dialog.findChild<QLabel*>("channelSettingsStatus"); QVERIFY(status && !status->text().isEmpty());
|
||||
}
|
||||
#endif
|
||||
|
||||
void invalidChannel_data() {
|
||||
QTest::addColumn<QString>("channel");
|
||||
for (const auto* value : {"", "0", "01", "+1", "1 ", "dm:1", "18446744073709551616"})
|
||||
QTest::newRow(value) << QString::fromLatin1(value);
|
||||
QTest::newRow("embedded-nul") << QString::fromLatin1("1\0tail", 6);
|
||||
}
|
||||
void invalidChannel() {
|
||||
QFETCH(QString, channel);
|
||||
SettingsCore core; QVERIFY(core.start());
|
||||
ChannelSettingsDialog dialog(channel, &core.db);
|
||||
QSignalSpy accepted(&dialog, &QDialog::accepted);
|
||||
QVERIFY(!dialog.findChild<QPushButton*>("deleteChannel")->isEnabled());
|
||||
QVERIFY(QMetaObject::invokeMethod(&dialog, "onSave", Qt::DirectConnection));
|
||||
QVERIFY(accepted.isEmpty());
|
||||
}
|
||||
|
||||
void borrowedDbDestruction() {
|
||||
SettingsCore core; QVERIFY(core.start());
|
||||
auto* db = new DbManager; db->setDb(core.inst->topo_sqlite_db);
|
||||
ChannelSettingsDialog dialog(core.channel, db);
|
||||
QSignalSpy rejected(&dialog, &QDialog::rejected); delete db;
|
||||
QCOMPARE(rejected.count(), 1);
|
||||
QSignalSpy accepted(&dialog, &QDialog::accepted);
|
||||
QVERIFY(QMetaObject::invokeMethod(&dialog, "onSave", Qt::DirectConnection)); QVERIFY(accepted.isEmpty());
|
||||
}
|
||||
|
||||
void confirmationSurvivesOwnerDeletion() {
|
||||
SettingsCore core; QVERIFY(core.start());
|
||||
QPointer<ChannelSettingsDialog> dialog = new ChannelSettingsDialog(core.channel, &core.db);
|
||||
dialog->show();
|
||||
QTimer::singleShot(0, [&] { QVERIFY(qobject_cast<QMessageBox*>(QApplication::activeModalWidget())); delete dialog.data(); });
|
||||
QVERIFY(QMetaObject::invokeMethod(dialog.data(), "onDelete", Qt::DirectConnection));
|
||||
QVERIFY(!dialog); QCOMPARE(core.db.getChannels().size(), 1);
|
||||
}
|
||||
|
||||
void avatarChooserSurvivesOwnerDeletion() {
|
||||
SettingsCore core; QVERIFY(core.start()); GroupAvatarManager avatars(&core.db);
|
||||
QPointer<ChannelSettingsDialog> dialog = new ChannelSettingsDialog(core.channel, &core.db);
|
||||
dialog->show(); auto* upload = dialog->findChild<QPushButton*>("uploadGroupAvatar"); QVERIFY(upload && upload->isEnabled());
|
||||
QTimer::singleShot(0, [&] { QVERIFY(qobject_cast<QFileDialog*>(QApplication::activeModalWidget())); delete dialog.data(); });
|
||||
upload->click(); QVERIFY(!dialog);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(ChannelSettingsTest)
|
||||
#include "test_channel_settings.moc"
|
||||
Reference in new issue
Block a user