v31.1-roots.1 · Commit 5: [PATCH 05/17] feat(net): preserve Roots peer controls and health view on v31
src/qt/test/apptests.cpp
Before: src/qt/test/apptests.cpp · After: src/qt/test/apptests.cpp
File metadata
index 752c877381031e47a453ae93f38126fe929da2a0..9ec8aa0a19d79762ef432f8476795808a0aed605 100644
Download original patch · Release notes
Diff rows 1–192 of 192. Line numbers refer to the original before/after files.
+ Added− RemovedUnmarked lines are context.
Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.
src/qt/test/apptests.cpp
CHANGED
|
@@ -11,8 +11,23 @@
|
|
| 11 |
#include <qt/bitcoingui.h>
|
| 12 |
#include <qt/networkstyle.h>
|
| 13 |
#include <qt/rpcconsole.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
#include <test/util/setup_common.h>
|
| 15 |
#include <validation.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
#include <QAction>
|
| 18 |
#include <QLineEdit>
|
|
@@ -49,6 +64,149 @@ void TestRpcCommand(RPCConsole* console)
|
|
| 49 |
const QString pattern = QStringLiteral("\"chain\": \"(\\w+)\"");
|
| 50 |
QCOMPARE(FindInConsole(output, pattern), QString("regtest"));
|
| 51 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
} // namespace
|
| 53 |
|
| 54 |
//! Entry point for BitcoinApplication tests.
|
|
@@ -67,6 +225,12 @@ void AppTests::appTests()
|
|
| 67 |
#endif
|
| 68 |
|
| 69 |
qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
m_app.parameterSetup();
|
| 71 |
QVERIFY(m_app.createOptionsModel(/*resetSettings=*/true));
|
| 72 |
QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().GetChainType()));
|
|
@@ -99,6 +263,8 @@ void AppTests::consoleTests(RPCConsole* console)
|
|
| 99 |
{
|
| 100 |
HandleCallback callback{"consoleTests", *this};
|
| 101 |
TestRpcCommand(console);
|
|
|
|
|
|
|
| 102 |
}
|
| 103 |
|
| 104 |
//! Destructor to shut down after the last expected callback completes.
|
|
|
|
| 11 |
#include <qt/bitcoingui.h>
|
| 12 |
#include <qt/networkstyle.h>
|
| 13 |
#include <qt/rpcconsole.h>
|
| 14 |
+
#include <qt/peertablemodel.h>
|
| 15 |
+
#include <QLabel>
|
| 16 |
+
#include <QTableView>
|
| 17 |
+
#include <QSortFilterProxyModel>
|
| 18 |
#include <test/util/setup_common.h>
|
| 19 |
#include <validation.h>
|
| 20 |
+
#include <interfaces/node.h>
|
| 21 |
+
#include <univalue.h>
|
| 22 |
+
#include <QDebug>
|
| 23 |
+
#include <QDir>
|
| 24 |
+
#include <QTabWidget>
|
| 25 |
+
#include <QSplitter>
|
| 26 |
+
#include <QScrollArea>
|
| 27 |
+
#include <QScrollBar>
|
| 28 |
+
#include <QItemSelectionModel>
|
| 29 |
+
#include <future>
|
| 30 |
+
#include <thread>
|
| 31 |
|
| 32 |
#include <QAction>
|
| 33 |
#include <QLineEdit>
|
|
|
|
| 64 |
const QString pattern = QStringLiteral("\"chain\": \"(\\w+)\"");
|
| 65 |
QCOMPARE(FindInConsole(output, pattern), QString("regtest"));
|
| 66 |
}
|
| 67 |
+
void TestPeerRefreshState(RPCConsole* console)
|
| 68 |
+
{
|
| 69 |
+
auto* peer_widget = console->findChild<QTableView*>("peerWidget");
|
| 70 |
+
QVERIFY(peer_widget);
|
| 71 |
+
auto* peer_proxy = qobject_cast<QSortFilterProxyModel*>(peer_widget->model());
|
| 72 |
+
QVERIFY(peer_proxy);
|
| 73 |
+
auto* peer_model = qobject_cast<PeerTableModel*>(peer_proxy->sourceModel());
|
| 74 |
+
QVERIFY(peer_model);
|
| 75 |
+
QCOMPARE(peer_model->rowCount(), 0);
|
| 76 |
+
|
| 77 |
+
QSignalSpy data_changed_spy(peer_model, &QAbstractItemModel::dataChanged);
|
| 78 |
+
QVERIFY(data_changed_spy.isValid());
|
| 79 |
+
peer_model->refresh();
|
| 80 |
+
QCOMPARE(data_changed_spy.count(), 0);
|
| 81 |
+
|
| 82 |
+
const auto set_label = [console](const char* name) {
|
| 83 |
+
QLabel* label = console->findChild<QLabel*>(name);
|
| 84 |
+
if (label) label->setText(QStringLiteral("stale"));
|
| 85 |
+
return label;
|
| 86 |
+
};
|
| 87 |
+
QLabel* time_offset = set_label("timeoffset");
|
| 88 |
+
QLabel* services = set_label("peerServices");
|
| 89 |
+
QLabel* sync_height = set_label("peerSyncHeight");
|
| 90 |
+
QLabel* common_height = set_label("peerCommonHeight");
|
| 91 |
+
QLabel* starting_height = set_label("peerHeight");
|
| 92 |
+
QLabel* ping_wait = set_label("peerPingWait");
|
| 93 |
+
QLabel* addr_relay = set_label("peerAddrRelayEnabled");
|
| 94 |
+
QLabel* addr_processed = set_label("peerAddrProcessed");
|
| 95 |
+
QLabel* addr_rate_limited = set_label("peerAddrRateLimited");
|
| 96 |
+
QLabel* relay_txs = set_label("peerRelayTxes");
|
| 97 |
+
|
| 98 |
+
QVERIFY(time_offset);
|
| 99 |
+
QVERIFY(services);
|
| 100 |
+
QVERIFY(sync_height);
|
| 101 |
+
QVERIFY(common_height);
|
| 102 |
+
QVERIFY(starting_height);
|
| 103 |
+
QVERIFY(ping_wait);
|
| 104 |
+
QVERIFY(addr_relay);
|
| 105 |
+
QVERIFY(addr_processed);
|
| 106 |
+
QVERIFY(addr_rate_limited);
|
| 107 |
+
QVERIFY(relay_txs);
|
| 108 |
+
QVERIFY(QMetaObject::invokeMethod(console, "resetPeerStateStats"));
|
| 109 |
+
QCOMPARE(time_offset->text(), QStringLiteral("N/A"));
|
| 110 |
+
QCOMPARE(services->text(), QStringLiteral("N/A"));
|
| 111 |
+
QCOMPARE(sync_height->text(), QStringLiteral("Unknown"));
|
| 112 |
+
QCOMPARE(common_height->text(), QStringLiteral("Unknown"));
|
| 113 |
+
QCOMPARE(starting_height->text(), QStringLiteral("Unknown"));
|
| 114 |
+
QCOMPARE(ping_wait->text(), QStringLiteral("N/A"));
|
| 115 |
+
QCOMPARE(addr_relay->text(), QStringLiteral("N/A"));
|
| 116 |
+
QCOMPARE(addr_processed->text(), QStringLiteral("N/A"));
|
| 117 |
+
QCOMPARE(addr_rate_limited->text(), QStringLiteral("N/A"));
|
| 118 |
+
QCOMPARE(relay_txs->text(), QStringLiteral("N/A"));
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
// Optional runtime evidence profile. Peers are real regtest nodes supplied by
|
| 122 |
+
// the caller; unavailable state is obtained through the production try-lock.
|
| 123 |
+
void TestLivePeerView(RPCConsole* console, interfaces::Node& node)
|
| 124 |
+
{
|
| 125 |
+
const auto endpoints{qEnvironmentVariable("ROOTS_PEER_ENDPOINTS").split(',', Qt::SkipEmptyParts)};
|
| 126 |
+
if (endpoints.isEmpty()) return;
|
| 127 |
+
QVERIFY(endpoints.size() >= 2);
|
| 128 |
+
auto* table = console->findChild<QTableView*>("peerWidget");
|
| 129 |
+
auto* tabs = console->findChild<QTabWidget*>("tabWidget");
|
| 130 |
+
QVERIFY(table && tabs);
|
| 131 |
+
auto* proxy = qobject_cast<QSortFilterProxyModel*>(table->model());
|
| 132 |
+
QVERIFY(proxy);
|
| 133 |
+
auto* model = qobject_cast<PeerTableModel*>(proxy->sourceModel());
|
| 134 |
+
QVERIFY(model);
|
| 135 |
+
model->stopAutoRefresh();
|
| 136 |
+
for (const auto& endpoint : endpoints) {
|
| 137 |
+
UniValue params{UniValue::VARR};
|
| 138 |
+
params.push_back(endpoint.toStdString());
|
| 139 |
+
params.push_back("onetry");
|
| 140 |
+
node.executeRpc("addnode", params, "");
|
| 141 |
+
}
|
| 142 |
+
QTRY_VERIFY_WITH_TIMEOUT((model->refresh(), model->rowCount() == endpoints.size()), 10000);
|
| 143 |
+
QTRY_VERIFY_WITH_TIMEOUT((model->refresh(), model->index(0, 0).data(PeerTableModel::StatsRole).value<CNodeCombinedStats*>()->nodeStats.nVersion > 0), 10000);
|
| 144 |
+
const auto size{qEnvironmentVariable("ROOTS_PEER_CAPTURE_SIZE", "1280x800").split('x')};
|
| 145 |
+
QCOMPARE(size.size(), 2);
|
| 146 |
+
console->resize(size[0].toInt(), size[1].toInt());
|
| 147 |
+
tabs->setCurrentIndex(3); // Native Peers tab.
|
| 148 |
+
console->show();
|
| 149 |
+
QTest::qWait(100);
|
| 150 |
+
QCOMPARE(console->size(), QSize(size[0].toInt(), size[1].toInt()));
|
| 151 |
+
qInfo() << "Live peer capture:" << console->size() << "font points" << table->font().pointSizeF() << "device pixel ratio" << console->devicePixelRatioF();
|
| 152 |
+
const auto capture_dir{qEnvironmentVariable("ROOTS_PEER_CAPTURE_DIR")};
|
| 153 |
+
const auto capture = [&](const QString& name) {
|
| 154 |
+
if (capture_dir.isEmpty()) return true;
|
| 155 |
+
return QDir().mkpath(capture_dir) && console->grab().save(capture_dir + '/' + name + ".png");
|
| 156 |
+
};
|
| 157 |
+
table->clearSelection();
|
| 158 |
+
QVERIFY(capture("table"));
|
| 159 |
+
table->selectRow(0);
|
| 160 |
+
const auto selected_id{table->selectionModel()->selectedRows().first().data().toLongLong()};
|
| 161 |
+
table->sortByColumn(PeerTableModel::Received, Qt::DescendingOrder);
|
| 162 |
+
model->refresh();
|
| 163 |
+
QCOMPARE(table->selectionModel()->selectedRows().size(), 1);
|
| 164 |
+
QCOMPARE(table->selectionModel()->selectedRows().first().data().toLongLong(), selected_id);
|
| 165 |
+
QVERIFY(console->findChild<QLabel*>("peerSubversion")->text() != QStringLiteral("N/A"));
|
| 166 |
+
// Exercise the native resizable splitter so unavailable values are legible
|
| 167 |
+
// at 150% font scale as well as in the full-width table capture.
|
| 168 |
+
console->findChild<QSplitter*>("splitter")->setSizes({console->width() / 2, console->width() / 2});
|
| 169 |
+
QTest::qWait(100); // Let the shown detail pane receive its layout geometry.
|
| 170 |
+
QVERIFY(console->findChild<QWidget*>("peersTabRightPanel")->isVisible());
|
| 171 |
+
QVERIFY(capture("detail"));
|
| 172 |
+
|
| 173 |
+
std::promise<void> locked, release;
|
| 174 |
+
auto released = release.get_future();
|
| 175 |
+
std::thread blocker{[&] {
|
| 176 |
+
LOCK(cs_main);
|
| 177 |
+
locked.set_value();
|
| 178 |
+
released.wait();
|
| 179 |
+
}};
|
| 180 |
+
locked.get_future().wait();
|
| 181 |
+
model->refresh();
|
| 182 |
+
const auto previous_row{table->selectionModel()->selectedRows().first().row()};
|
| 183 |
+
table->selectRow(previous_row == 0 ? 1 : 0);
|
| 184 |
+
const bool unavailable{!table->selectionModel()->selectedRows().first().data(PeerTableModel::StatsRole).value<CNodeCombinedStats*>()->fNodeStateStatsAvailable};
|
| 185 |
+
const bool cleared{console->findChild<QLabel*>("peerServices")->text() == QStringLiteral("N/A") &&
|
| 186 |
+
console->findChild<QLabel*>("peerSyncHeight")->text() == QStringLiteral("Unknown") &&
|
| 187 |
+
console->findChild<QLabel*>("peerHeight")->text() == QStringLiteral("Unknown") &&
|
| 188 |
+
console->findChild<QLabel*>("timeoffset")->text() == QStringLiteral("N/A")};
|
| 189 |
+
const bool captured{capture("unavailable")};
|
| 190 |
+
auto* scroll = console->findChild<QScrollArea*>("scrollArea");
|
| 191 |
+
scroll->verticalScrollBar()->setValue(scroll->verticalScrollBar()->maximum());
|
| 192 |
+
const bool captured_bottom{capture("unavailable-bottom")};
|
| 193 |
+
scroll->verticalScrollBar()->setValue(0);
|
| 194 |
+
release.set_value();
|
| 195 |
+
blocker.join();
|
| 196 |
+
QVERIFY(unavailable);
|
| 197 |
+
QVERIFY(cleared);
|
| 198 |
+
QVERIFY(captured);
|
| 199 |
+
QVERIFY(captured_bottom);
|
| 200 |
+
model->refresh();
|
| 201 |
+
QVERIFY(console->findChild<QLabel*>("peerServices")->text() != QStringLiteral("N/A"));
|
| 202 |
+
// Removing the selected peer must not leave its detail pane visible.
|
| 203 |
+
UniValue params{UniValue::VARR};
|
| 204 |
+
params.push_back("");
|
| 205 |
+
params.push_back(table->selectionModel()->selectedRows().first().data().toLongLong());
|
| 206 |
+
node.executeRpc("disconnectnode", params, "");
|
| 207 |
+
QTRY_VERIFY_WITH_TIMEOUT((model->refresh(), model->rowCount() == endpoints.size() - 1), 10000);
|
| 208 |
+
QVERIFY(table->selectionModel()->selectedRows().isEmpty());
|
| 209 |
+
}
|
| 210 |
} // namespace
|
| 211 |
|
| 212 |
//! Entry point for BitcoinApplication tests.
|
|
|
|
| 225 |
#endif
|
| 226 |
|
| 227 |
qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
|
| 228 |
+
const auto font_scale{qEnvironmentVariable("ROOTS_PEER_FONT_SCALE", "1.0").toDouble()};
|
| 229 |
+
if (font_scale != 1.0) {
|
| 230 |
+
auto font = m_app.font();
|
| 231 |
+
font.setPointSizeF(font.pointSizeF() * font_scale);
|
| 232 |
+
m_app.setFont(font);
|
| 233 |
+
}
|
| 234 |
m_app.parameterSetup();
|
| 235 |
QVERIFY(m_app.createOptionsModel(/*resetSettings=*/true));
|
| 236 |
QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().GetChainType()));
|
|
|
|
| 263 |
{
|
| 264 |
HandleCallback callback{"consoleTests", *this};
|
| 265 |
TestRpcCommand(console);
|
| 266 |
+
TestPeerRefreshState(console);
|
| 267 |
+
TestLivePeerView(console, m_app.node());
|
| 268 |
}
|
| 269 |
|
| 270 |
//! Destructor to shut down after the last expected callback completes.
|