Files
utun2/tools/chatgui/tests/test_member_files_lifecycle.cpp
T

172 lines
8.3 KiB
C++

/* Реальные batch/browser и bridge: отказ очереди, detach до trampoline и итог без утечек.
* Минимальное ядро без файлового сервиса даёт штатный отказ при исполнении команды. */
#include "../src/member_files/browser.h"
#include "../transport/gui_bridge.h"
#include "../db/db_manager.h"
#include <QtTest>
#include <QLabel>
#include <QTemporaryDir>
#include <QScopeGuard>
extern "C" {
#include "utun_instance.h"
#include "member_files/member_files.h"
#include "debug_config.h"
#include "mem.h"
}
#ifdef UTUN_TEST_FILE_ALLOC_FAILURES
static int failAllocation;
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 size, const char* where) {
if (failAllocation && strstr(where, "gui_bridge_impl.cpp") && !--failAllocation) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "[INJECT] file bridge allocation failure at %s", where); return nullptr;
}
return __real_u_calloc_impl(count, size, where);
}
#endif
struct FileCore {
UASYNC* ua = uasync_create();
UTUN_INSTANCE instance{};
void attach() {
instance.ua = ua;
chat_event_set_handler(&instance, [](UTUN_INSTANCE*, int event, const uint8_t* data, int size) {
gui_bridge_post(event, data, size);
});
uasync_mark_running(ua); gui_bridge_set_uasync(ua); gui_bridge_set_inst(&instance);
}
~FileCore() {
gui_bridge_set_inst(nullptr); gui_bridge_set_uasync(nullptr);
if (ua) { uasync_mark_stopped(ua); uasync_destroy(ua, 0); }
}
};
static QPointer<MemberFileBatch> activeBatch;
static QPointer<MemberFileBrowser> activeBrowser;
static QList<QByteArray> responses, transfers;
static int pending(UASYNC* ua) {
int count = 0;
for (auto* task = ua->posted_tasks_head; task; task = task->next) count++;
return count;
}
class FileLifecycleTest : public QObject {
Q_OBJECT
private slots:
void initTestCase() {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_TRACE);
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_DEBUG);
gui_bridge_init();
gui_bridge_set_member_files_response_cb([](const uint8_t* data, int length) {
QVERIFY(QThread::currentThread() == qApp->thread()); responses.append(QByteArray((const char*)data, length));
if (activeBrowser) activeBrowser->onResponse(data, length);
mf_message message{};
if (activeBatch && length >= 16 && !mf_decode(data + 16, length - 16, &message)) activeBatch->response(message);
});
gui_bridge_set_member_files_transfer_cb([](const uint8_t* data, int length) {
QVERIFY(QThread::currentThread() == qApp->thread()); transfers.append(QByteArray((const char*)data, length));
if (activeBrowser) activeBrowser->onTransfer(data, length);
if (activeBatch) activeBatch->transfer(data, length);
});
}
void cleanupTestCase() {
gui_bridge_set_member_files_response_cb(nullptr); gui_bridge_set_member_files_transfer_cb(nullptr);
}
void cleanup() {
activeBatch = nullptr; activeBrowser = nullptr; responses.clear(); transfers.clear();
#ifdef UTUN_TEST_FILE_ALLOC_FAILURES
failAllocation = 0;
#endif
}
void batchRequest_data() {
QTest::addColumn<QString>("scenario");
for (const char* scenario : {"detach", "unavailable", "stopped", "executed-error"}) QTest::newRow(scenario) << QString(scenario);
#ifdef UTUN_TEST_FILE_ALLOC_FAILURES
QTest::newRow("request-allocation") << QString("request-allocation");
QTest::newRow("post-allocation") << QString("post-allocation");
#endif
}
void batchRequest() {
QFETCH(QString, scenario);
const size_t before = u_get_allocated_count();
{
FileCore core; QVERIFY(core.ua); core.attach();
if (scenario == "unavailable") gui_bridge_set_inst(nullptr);
if (scenario == "stopped") uasync_mark_stopped(core.ua);
MemberFileBatch batch(42, 33, 22, MemberFileBatch::Remove, nullptr); activeBatch = &batch;
int completed = 0; bool success = true; QString summary;
batch.progress = [](const QString&, int) {};
batch.finished = [&](bool ok, const QString& message, const QStringList& roots) {
completed++; success = ok; summary = message; QVERIFY(roots.isEmpty());
};
#ifdef UTUN_TEST_FILE_ALLOC_FAILURES
if (scenario == "request-allocation") failAllocation = 1;
if (scenario == "post-allocation") failAllocation = 2;
#endif
batch.start({{"selected.txt", {}, QByteArray(16, '\7'), false}});
if (scenario == "detach") gui_bridge_set_inst(nullptr);
if (scenario == "executed-error") uasync_poll(core.ua, 0);
QTRY_COMPARE_WITH_TIMEOUT(completed, 1, 1000);
QVERIFY(!success); QVERIFY(!summary.isEmpty()); QCOMPARE(pending(core.ua), 0);
#ifdef UTUN_TEST_FILE_ALLOC_FAILURES
QCOMPARE(failAllocation, 0);
#endif
if (scenario == "detach" || scenario == "executed-error") {
QCOMPARE(responses.size(), 1); mf_message message{};
QCOMPARE(mf_decode((const uint8_t*)responses[0].constData() + 16, responses[0].size() - 16, &message), 0);
QCOMPARE(message.status, uint16_t(scenario == "detach" ? MF_CANCELLED : MF_ACCESS_DENIED));
QCOMPARE(message.command, uint8_t(MF_STAT)); QCOMPARE(QString::fromUtf8(message.path), QString("selected.txt"));
} else QVERIFY(responses.isEmpty());
batch.cancel(); QCoreApplication::processEvents(); QCOMPARE(completed, 1);
}
QCOMPARE(u_get_allocated_count(), before);
}
void batchDownload_data() { batchRequest_data(); }
void batchDownload() {
QFETCH(QString, scenario);
const size_t before = u_get_allocated_count();
{
FileCore core; QVERIFY(core.ua); core.attach(); QTemporaryDir root; QVERIFY(root.isValid());
if (scenario == "unavailable") gui_bridge_set_inst(nullptr);
if (scenario == "stopped") uasync_mark_stopped(core.ua);
MemberFileBatch batch(42, 33, 22, MemberFileBatch::Download, nullptr); activeBatch = &batch;
int completed = 0; bool success = true;
batch.progress = [](const QString&, int) {};
batch.finished = [&](bool ok, const QString&, const QStringList& roots) {
completed++; success = ok; QVERIFY(roots.isEmpty());
};
#ifdef UTUN_TEST_FILE_ALLOC_FAILURES
if (scenario == "request-allocation") failAllocation = 1;
if (scenario == "post-allocation") failAllocation = 2;
#endif
batch.start({{"selected.txt", {}, QByteArray(16, '\7'), false}}, root.path(), true);
if (scenario == "detach" || scenario == "executed-error") {
QTRY_COMPARE_WITH_TIMEOUT(pending(core.ua), 1, 1000);
if (scenario == "detach") gui_bridge_set_inst(nullptr);
else uasync_poll(core.ua, 0);
}
QTRY_COMPARE_WITH_TIMEOUT(completed, 1, 1000); QVERIFY(!success); QCOMPARE(pending(core.ua), 0);
QCOMPARE(QDir(root.path()).entryList(QDir::AllEntries | QDir::NoDotAndDotDot).size(), 0);
#ifdef UTUN_TEST_FILE_ALLOC_FAILURES
QCOMPARE(failAllocation, 0);
#endif
if (scenario == "detach" || scenario == "executed-error") {
QCOMPARE(transfers.size(), 1); const auto* data = (const uint8_t*)transfers[0].constData();
QCOMPARE(transfers[0].size(), 51); QCOMPARE(mf_get64(data + 8), uint64_t(42));
QCOMPARE(mf_get64(data + 16), uint64_t(33)); QCOMPARE(mf_get64(data + 24), uint64_t(22));
QCOMPARE(unsigned(data[48]) | unsigned(data[49]) << 8, unsigned(scenario == "detach" ? MF_CANCELLED : MF_BUSY));
QCOMPARE(data[50], uint8_t(2));
} else QVERIFY(transfers.isEmpty());
batch.cancel(); QCoreApplication::processEvents(); QCOMPARE(completed, 1);
}
QCOMPARE(u_get_allocated_count(), before);
}
};
QTEST_MAIN(FileLifecycleTest)
#include "test_member_files_lifecycle.moc"