Skip to content
Bitcoin Roots

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.