Fix desktop node startup rollback and state ownership
This commit is contained in:
1 parent
b163642b52
commit
d22bae19b4
5 files changed
+297
-110
No files matched your search
@@ -3,6 +3,7 @@
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include <QtTest>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
extern "C" {
|
||||
@@ -12,8 +13,30 @@ extern "C" {
|
||||
#include "transport_layer/node_conn_direct.h"
|
||||
#include "u_async.h"
|
||||
#include "debug_config.h"
|
||||
#include "call/call_video_port.h"
|
||||
#include "stream/stream_publisher.h"
|
||||
#include "stream/stream_viewer.h"
|
||||
#include "mem.h"
|
||||
}
|
||||
|
||||
#ifdef UTUN_TEST_NODE_STREAM_FAILURES
|
||||
static std::atomic<int> streamFailure{0};
|
||||
extern "C" int __real_stream_publisher_create(struct stream_publisher**, UTUN_INSTANCE*);
|
||||
extern "C" int __real_stream_viewers_create(struct stream_viewers**, UTUN_INSTANCE*);
|
||||
extern "C" int __wrap_stream_publisher_create(struct stream_publisher** out, UTUN_INSTANCE* inst) {
|
||||
if (streamFailure == 1) {
|
||||
streamFailure = 0; DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "[INJECT] desktop publisher creation failure"); return -1;
|
||||
}
|
||||
return __real_stream_publisher_create(out, inst);
|
||||
}
|
||||
extern "C" int __wrap_stream_viewers_create(struct stream_viewers** out, UTUN_INSTANCE* inst) {
|
||||
if (streamFailure == 2) {
|
||||
streamFailure = 0; DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "[INJECT] desktop viewers creation failure"); return -1;
|
||||
}
|
||||
return __real_stream_viewers_create(out, inst);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const QByteArray identity =
|
||||
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
|
||||
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n";
|
||||
@@ -107,6 +130,126 @@ private slots:
|
||||
QVERIFY(!node.isRunning() && !node.instance() && !gui_bridge_get_inst() && !gui_bridge_is_uasync_ready());
|
||||
}
|
||||
|
||||
void restartKeepsLogPathAndDoesNotLeakFiles() {
|
||||
QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("node.cfg"), logPath = directory.filePath("node.log");
|
||||
QFile config(path); QVERIFY(config.open(QIODevice::WriteOnly));
|
||||
const QByteArray text = "[global]\n" + identity +
|
||||
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n[ntp]\nenabled=no\n";
|
||||
QCOMPARE(config.write(text), text.size()); config.close();
|
||||
#ifdef Q_OS_LINUX
|
||||
const int descriptors = QDir("/proc/self/fd").entryList(QDir::AllEntries | QDir::NoDotAndDotDot).size();
|
||||
#endif
|
||||
{
|
||||
UtunNode node;
|
||||
node.setDbPath(directory.filePath("db")); node.setDebugFile(logPath);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
QVERIFY(node.start(path));
|
||||
node.stop();
|
||||
// Путь хранится logger-ом до закрытия файла, включая время после stop.
|
||||
QCOMPARE(QByteArray(g_debug_config.output_file), logPath.toUtf8());
|
||||
QCOMPARE(debug_reopen_log(), 0);
|
||||
}
|
||||
}
|
||||
debug_disable_file_output();
|
||||
#ifdef Q_OS_LINUX
|
||||
QCOMPARE(QDir("/proc/self/fd").entryList(QDir::AllEntries | QDir::NoDotAndDotDot).size(), descriptors);
|
||||
#endif
|
||||
}
|
||||
|
||||
void mediaPortFailureRejectsStartupAndAllowsRetry() {
|
||||
QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("node.cfg");
|
||||
QFile config(path); QVERIFY(config.open(QIODevice::WriteOnly));
|
||||
const QByteArray text = "[global]\n" + identity +
|
||||
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n[ntp]\nenabled=no\n";
|
||||
QCOMPARE(config.write(text), text.size()); config.close();
|
||||
struct PortOwner {
|
||||
UTUN_INSTANCE instance{};
|
||||
~PortOwner() { call_video_port_detach(&instance); }
|
||||
} owner;
|
||||
QCOMPARE(call_video_port_attach(&owner.instance), 0);
|
||||
UtunNode node;
|
||||
node.setDbPath(directory.filePath("db")); node.setDebugFile(directory.filePath("node.log"));
|
||||
QSignalSpy started(&node, &UtunNode::started), errors(&node, &UtunNode::error);
|
||||
const bool running = node.start(path);
|
||||
if (running) node.stop(); // Старая реализация не должна оставлять поток после FAIL теста.
|
||||
QVERIFY(!running);
|
||||
QVERIFY(!node.isRunning() && !node.instance() && !gui_bridge_get_inst() && !gui_bridge_is_uasync_ready());
|
||||
QCOMPARE(started.count(), 0); QCOMPARE(errors.count(), 1);
|
||||
QVERIFY(node.startupError().contains("call_video_port_attach"));
|
||||
// Откат не снимает чужую привязку.
|
||||
QVERIFY(call_video_port_attach(&owner.instance) < 0);
|
||||
call_video_port_detach(&owner.instance);
|
||||
QVERIFY(node.start(path)); node.stop();
|
||||
}
|
||||
|
||||
void restartDiscardsPreviousStoppedAndCopiesCoreState() {
|
||||
QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("node.cfg");
|
||||
QFile config(path); QVERIFY(config.open(QIODevice::WriteOnly));
|
||||
const QByteArray text = "[global]\n" + identity +
|
||||
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n[ntp]\nenabled=no\n";
|
||||
QCOMPARE(config.write(text), text.size()); config.close();
|
||||
UtunNode node;
|
||||
node.setDbPath(directory.filePath("db")); node.setDebugFile(directory.filePath("node.log"));
|
||||
node.setDebugCategories("sys=none,bgp=none");
|
||||
QSignalSpy stopped(&node, &UtunNode::stopped), errors(&node, &UtunNode::error);
|
||||
QVERIFY(node.start(path));
|
||||
QCOMPARE(g_debug_config.category_levels[DEBUG_CATEGORY_SYS], DEBUG_LEVEL_NONE);
|
||||
QCOMPARE(g_debug_config.category_levels[DEBUG_CATEGORY_BGP], DEBUG_LEVEL_NONE);
|
||||
const QString id = node.nodeIdHex(); QCOMPARE(id.size(), 16);
|
||||
QCOMPARE(node.pubKeyHex(), QStringLiteral("b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01"));
|
||||
QVERIFY(!node.start(path)); QCOMPARE(errors.count(), 1); QVERIFY(node.isRunning());
|
||||
node.stop();
|
||||
QVERIFY(node.nodeIdHex().isEmpty() && node.pubKeyHex().isEmpty());
|
||||
QVERIFY(!node.ntpEnabled() && !node.ntpSynced() && node.ntpOffsetUs() == 0);
|
||||
QVERIFY(node.start(path)); QCOMPARE(node.nodeIdHex(), id);
|
||||
QCoreApplication::processEvents(); QCOMPARE(stopped.count(), 0); QVERIFY(node.isRunning());
|
||||
gui_bridge_post_uasync_fn([](void*) {
|
||||
auto* inst = gui_bridge_get_inst();
|
||||
inst->ntp.offset_us = 1234567; inst->ntp.synced = 1; inst->ntp.enabled = 1;
|
||||
}, nullptr);
|
||||
QTRY_COMPARE_WITH_TIMEOUT(node.ntpOffsetUs(), int64_t(1234567), 3000);
|
||||
QVERIFY(node.ntpSynced() && node.ntpEnabled());
|
||||
gui_bridge_post_uasync_fn([](void*) { utun_instance_stop(gui_bridge_get_inst()); }, nullptr);
|
||||
QTRY_COMPARE_WITH_TIMEOUT(stopped.count(), 1, 3000);
|
||||
QVERIFY(!node.instance()); QCOMPARE(node.nodeIdHex(), id); // Копия переживает ядро до stop.
|
||||
node.stop();
|
||||
QVERIFY(node.nodeIdHex().isEmpty() && !node.ntpSynced());
|
||||
}
|
||||
|
||||
#ifdef UTUN_TEST_NODE_STREAM_FAILURES
|
||||
void streamFailureRollsBackAndAllowsRetry_data() {
|
||||
QTest::addColumn<int>("stage");
|
||||
QTest::newRow("publisher") << 1;
|
||||
QTest::newRow("viewers-after-publisher") << 2;
|
||||
}
|
||||
|
||||
void streamFailureRollsBackAndAllowsRetry() {
|
||||
QFETCH(int, stage);
|
||||
QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("node.cfg");
|
||||
QFile config(path); QVERIFY(config.open(QIODevice::WriteOnly));
|
||||
const QByteArray text = "[global]\n" + identity +
|
||||
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n[ntp]\nenabled=no\n";
|
||||
QCOMPARE(config.write(text), text.size()); config.close();
|
||||
const size_t allocated = u_get_allocated_count();
|
||||
UtunNode node;
|
||||
node.setDbPath(directory.filePath("db")); node.setDebugFile(directory.filePath("node.log"));
|
||||
QSignalSpy started(&node, &UtunNode::started), stopped(&node, &UtunNode::stopped), errors(&node, &UtunNode::error);
|
||||
streamFailure = stage;
|
||||
QVERIFY(!node.start(path)); QCOMPARE(streamFailure.load(), 0);
|
||||
QCOMPARE(started.count(), 0); QCOMPARE(errors.count(), 1);
|
||||
QVERIFY(node.startupError().contains("stream bridge"));
|
||||
QVERIFY(!node.isRunning() && !node.instance() && !gui_bridge_get_inst() && !gui_bridge_is_uasync_ready());
|
||||
QCoreApplication::processEvents(); QCOMPARE(stopped.count(), 0);
|
||||
QCOMPARE(u_get_allocated_count(), allocated);
|
||||
QVERIFY(node.start(path)); node.stop(); QCOMPARE(started.count(), 1);
|
||||
QCOMPARE(u_get_allocated_count(), allocated);
|
||||
}
|
||||
#endif
|
||||
|
||||
void tunFailureRejectsStartup() {
|
||||
QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("node.cfg");
|
||||
|
||||
Reference in new issue
Block a user