172 lines
8.3 KiB
C++
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"
|