Add BitcoinApplication & RPCConsole tests

Add test coverage for Qt initialization code & basic RPC console functionality.
This commit is contained in:
Russell Yanofsky 2017-11-06 20:11:43 -05:00
parent ca20b65cc0
commit 7e4bd19785
11 changed files with 235 additions and 24 deletions

View file

@ -303,6 +303,7 @@ RES_ICONS = \
BITCOIN_QT_BASE_CPP = \
qt/bantablemodel.cpp \
qt/bitcoin.cpp \
qt/bitcoinaddressvalidator.cpp \
qt/bitcoinamountfield.cpp \
qt/bitcoingui.cpp \
@ -383,6 +384,9 @@ qt_libbitcoinqt_a_OBJCXXFLAGS = $(AM_OBJCXXFLAGS) $(QT_PIE_FLAGS)
qt_libbitcoinqt_a_SOURCES = $(BITCOIN_QT_CPP) $(BITCOIN_QT_H) $(QT_FORMS_UI) \
$(QT_QRC) $(QT_QRC_LOCALE) $(QT_TS) $(PROTOBUF_PROTO) $(RES_ICONS) $(RES_IMAGES) $(RES_MOVIES)
if TARGET_DARWIN
qt_libbitcoinqt_a_SOURCES += $(BITCOIN_MM)
endif
nodist_qt_libbitcoinqt_a_SOURCES = $(QT_MOC_CPP) $(QT_MOC) $(PROTOBUF_CC) \
$(PROTOBUF_H) $(QT_QRC_CPP) $(QT_QRC_LOCALE_CPP)
@ -405,10 +409,7 @@ qt_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDE
$(QT_INCLUDES) $(PROTOBUF_CFLAGS) $(QR_CFLAGS)
qt_bitcoin_qt_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS)
qt_bitcoin_qt_SOURCES = qt/bitcoin.cpp
if TARGET_DARWIN
qt_bitcoin_qt_SOURCES += $(BITCOIN_MM)
endif
qt_bitcoin_qt_SOURCES = qt/main.cpp
if TARGET_WINDOWS
qt_bitcoin_qt_SOURCES += $(BITCOIN_RC)
endif

View file

@ -6,6 +6,7 @@ bin_PROGRAMS += qt/test/test_bitcoin-qt
TESTS += qt/test/test_bitcoin-qt
TEST_QT_MOC_CPP = \
qt/test/moc_apptests.cpp \
qt/test/moc_compattests.cpp \
qt/test/moc_rpcnestedtests.cpp \
qt/test/moc_uritests.cpp
@ -22,6 +23,7 @@ endif # ENABLE_WALLET
TEST_QT_H = \
qt/test/addressbooktests.h \
qt/test/apptests.h \
qt/test/compattests.h \
qt/test/rpcnestedtests.h \
qt/test/uritests.h \
@ -40,6 +42,7 @@ qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_
$(QT_INCLUDES) $(QT_TEST_INCLUDES) $(PROTOBUF_CFLAGS)
qt_test_test_bitcoin_qt_SOURCES = \
qt/test/apptests.cpp \
qt/test/compattests.cpp \
qt/test/rpcnestedtests.cpp \
qt/test/test_main.cpp \

View file

@ -72,11 +72,6 @@ Q_DECLARE_METATYPE(bool*)
Q_DECLARE_METATYPE(CAmount)
Q_DECLARE_METATYPE(uint256)
/** Translate string to current locale using Qt. */
const std::function<std::string(const char*)> G_TRANSLATION_FUN = [](const char* psz) {
return QCoreApplication::translate("bitcoin-core", psz).toStdString();
};
static QString GetLangTerritory()
{
QSettings settings;
@ -264,6 +259,11 @@ void BitcoinApplication::createSplashScreen(const NetworkStyle *networkStyle)
connect(this, &BitcoinApplication::requestedShutdown, splash, &QWidget::close);
}
bool BitcoinApplication::baseInitialize()
{
return m_node.baseInitialize();
}
void BitcoinApplication::startThread()
{
if(coreThread)
@ -373,7 +373,7 @@ void BitcoinApplication::initializeResult(bool success)
#ifdef ENABLE_BIP70
PaymentServer::LoadRootCAs();
#endif
paymentServer->setOptionsModel(optionsModel);
if (paymentServer) paymentServer->setOptionsModel(optionsModel);
#endif
clientModel = new ClientModel(m_node, optionsModel);
@ -402,16 +402,19 @@ void BitcoinApplication::initializeResult(bool success)
window->show();
}
Q_EMIT splashFinished();
Q_EMIT windowShown(window);
#ifdef ENABLE_WALLET
// Now that initialization/startup is done, process any command-line
// bitcoin: URIs or payment requests:
connect(paymentServer, &PaymentServer::receivedPaymentRequest, window, &BitcoinGUI::handlePaymentRequest);
connect(window, &BitcoinGUI::receivedURI, paymentServer, &PaymentServer::handleURIOrFile);
connect(paymentServer, &PaymentServer::message, [this](const QString& title, const QString& message, unsigned int style) {
window->message(title, message, style);
});
QTimer::singleShot(100, paymentServer, &PaymentServer::uiReady);
if (paymentServer) {
connect(paymentServer, &PaymentServer::receivedPaymentRequest, window, &BitcoinGUI::handlePaymentRequest);
connect(window, &BitcoinGUI::receivedURI, paymentServer, &PaymentServer::handleURIOrFile);
connect(paymentServer, &PaymentServer::message, [this](const QString& title, const QString& message, unsigned int style) {
window->message(title, message, style);
});
QTimer::singleShot(100, paymentServer, &PaymentServer::uiReady);
}
#endif
pollShutdownTimer->start(200);
} else {
@ -454,7 +457,7 @@ static void SetupUIArgs()
}
#ifndef BITCOIN_QT_TEST
int main(int argc, char *argv[])
int GuiMain(int argc, char* argv[])
{
#ifdef WIN32
util::WinCmdLineArgs winArgs;
@ -612,7 +615,7 @@ int main(int argc, char *argv[])
// Perform base initialization before spinning up initialization/shutdown thread
// This is acceptable because this function only contains steps that are quick to execute,
// so the GUI thread won't be held up.
if (node->baseInitialize()) {
if (app.baseInitialize()) {
app.requestInitialize();
#if defined(Q_OS_WIN)
WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId());

View file

@ -71,6 +71,8 @@ public:
void createWindow(const NetworkStyle *networkStyle);
/// Create splash screen
void createSplashScreen(const NetworkStyle *networkStyle);
/// Basic initialization, before starting initialization/shutdown thread. Return true on success.
bool baseInitialize();
/// Request core initialization
void requestInitialize();
@ -99,6 +101,7 @@ Q_SIGNALS:
void requestedShutdown();
void stopThread();
void splashFinished();
void windowShown(BitcoinGUI* window);
private:
QThread *coreThread;
@ -119,4 +122,6 @@ private:
void startThread();
};
int GuiMain(int argc, char* argv[]);
#endif // BITCOIN_QT_BITCOIN_H

View file

@ -110,6 +110,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty
* the central widget is the rpc console.
*/
setCentralWidget(rpcConsole);
Q_EMIT consoleShown(rpcConsole);
}
// Accept D&D of URIs
@ -324,6 +325,7 @@ void BitcoinGUI::createActions()
openRPCConsoleAction->setStatusTip(tr("Open debugging and diagnostic console"));
// initially disable the debug window menu item
openRPCConsoleAction->setEnabled(false);
openRPCConsoleAction->setObjectName("openRPCConsoleAction");
usedSendingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Sending addresses"), this);
usedSendingAddressesAction->setStatusTip(tr("Show the list of used sending addresses and labels"));
@ -642,9 +644,11 @@ void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle)
assert(QSystemTrayIcon::isSystemTrayAvailable());
#ifndef Q_OS_MAC
trayIcon = new QSystemTrayIcon(networkStyle->getTrayAndWindowIcon(), this);
QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + networkStyle->getTitleAddText();
trayIcon->setToolTip(toolTip);
if (QSystemTrayIcon::isSystemTrayAvailable()) {
trayIcon = new QSystemTrayIcon(networkStyle->getTrayAndWindowIcon(), this);
QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + networkStyle->getTitleAddText();
trayIcon->setToolTip(toolTip);
}
#endif
}
@ -724,6 +728,7 @@ void BitcoinGUI::aboutClicked()
void BitcoinGUI::showDebugWindow()
{
GUIUtil::bringToFront(rpcConsole);
Q_EMIT consoleShown(rpcConsole);
}
void BitcoinGUI::showDebugWindowActivateConsole()

View file

@ -187,6 +187,8 @@ private:
Q_SIGNALS:
/** Signal raised when a URI was entered or dragged to the GUI */
void receivedURI(const QString &uri);
/** Signal raised when RPC console shown */
void consoleShown(RPCConsole* console);
public Q_SLOTS:
/** Set number of connections shown in the UI */

17
src/qt/main.cpp Normal file
View file

@ -0,0 +1,17 @@
// Copyright (c) 2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <qt/bitcoin.h>
#include <QCoreApplication>
#include <functional>
#include <string>
/** Translate string to current locale using Qt. */
extern const std::function<std::string(const char*)> G_TRANSLATION_FUN = [](const char* psz) {
return QCoreApplication::translate("bitcoin-core", psz).toStdString();
};
int main(int argc, char* argv[]) { return GuiMain(argc, argv); }

117
src/qt/test/apptests.cpp Normal file
View file

@ -0,0 +1,117 @@
// Copyright (c) 2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <qt/test/apptests.h>
#include <chainparams.h>
#include <init.h>
#include <qt/bitcoin.h>
#include <qt/bitcoingui.h>
#include <qt/networkstyle.h>
#include <qt/rpcconsole.h>
#include <shutdown.h>
#include <validation.h>
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#ifdef ENABLE_WALLET
#include <wallet/db.h>
#endif
#include <QAction>
#include <QEventLoop>
#include <QLineEdit>
#include <QScopedPointer>
#include <QTest>
#include <QTextEdit>
#include <QtGlobal>
#if QT_VERSION >= 0x050000
#include <QtTest/QtTestWidgets>
#endif
#include <QtTest/QtTestGui>
#include <new>
#include <string>
#include <univalue.h>
namespace {
//! Call getblockchaininfo RPC and check first field of JSON output.
void TestRpcCommand(RPCConsole* console)
{
QEventLoop loop;
QTextEdit* messagesWidget = console->findChild<QTextEdit*>("messagesWidget");
QObject::connect(messagesWidget, &QTextEdit::textChanged, &loop, &QEventLoop::quit);
QLineEdit* lineEdit = console->findChild<QLineEdit*>("lineEdit");
QTest::keyClicks(lineEdit, "getblockchaininfo");
QTest::keyClick(lineEdit, Qt::Key_Return);
loop.exec();
QString output = messagesWidget->toPlainText();
UniValue value;
value.read(output.right(output.size() - output.lastIndexOf(QChar::ObjectReplacementCharacter) - 1).toStdString());
QCOMPARE(value["chain"].get_str(), std::string("regtest"));
}
} // namespace
//! Entry point for BitcoinApplication tests.
void AppTests::appTests()
{
#ifdef Q_OS_MAC
if (QApplication::platformName() == "minimal") {
// Disable for mac on "minimal" platform to avoid crashes inside the Qt
// framework when it tries to look up unimplemented cocoa functions,
// and fails to handle returned nulls
// (https://bugreports.qt.io/browse/QTBUG-49686).
QWARN("Skipping AppTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke "
"with 'test_bitcoin-qt -platform cocoa' on mac, or else use a linux or windows build.");
return;
}
#endif
m_app.parameterSetup();
m_app.createOptionsModel(true /* reset settings */);
QScopedPointer<const NetworkStyle> style(
NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString())));
m_app.setupPlatformStyle();
m_app.createWindow(style.data());
connect(&m_app, &BitcoinApplication::windowShown, this, &AppTests::guiTests);
expectCallback("guiTests");
m_app.baseInitialize();
m_app.requestInitialize();
m_app.exec();
m_app.requestShutdown();
m_app.exec();
// Reset global state to avoid interfering with later tests.
AbortShutdown();
UnloadBlockIndex();
}
//! Entry point for BitcoinGUI tests.
void AppTests::guiTests(BitcoinGUI* window)
{
HandleCallback callback{"guiTests", *this};
connect(window, &BitcoinGUI::consoleShown, this, &AppTests::consoleTests);
expectCallback("consoleTests");
QAction* action = window->findChild<QAction*>("openRPCConsoleAction");
action->activate(QAction::Trigger);
}
//! Entry point for RPCConsole tests.
void AppTests::consoleTests(RPCConsole* console)
{
HandleCallback callback{"consoleTests", *this};
TestRpcCommand(console);
}
//! Destructor to shut down after the last expected callback completes.
AppTests::HandleCallback::~HandleCallback()
{
auto& callbacks = m_app_tests.m_callbacks;
auto it = callbacks.find(m_callback);
assert(it != callbacks.end());
callbacks.erase(it);
if (callbacks.empty()) {
m_app_tests.m_app.quit();
}
}

50
src/qt/test/apptests.h Normal file
View file

@ -0,0 +1,50 @@
// Copyright (c) 2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_QT_TEST_APPTESTS_H
#define BITCOIN_QT_TEST_APPTESTS_H
#include <QObject>
#include <set>
#include <string>
#include <utility>
class BitcoinApplication;
class BitcoinGUI;
class RPCConsole;
class AppTests : public QObject
{
Q_OBJECT
public:
explicit AppTests(BitcoinApplication& app) : m_app(app) {}
private Q_SLOTS:
void appTests();
void guiTests(BitcoinGUI* window);
void consoleTests(RPCConsole* console);
private:
//! Add expected callback name to list of pending callbacks.
void expectCallback(std::string callback) { m_callbacks.emplace(std::move(callback)); }
//! RAII helper to remove no-longer-pending callback.
struct HandleCallback
{
std::string m_callback;
AppTests& m_app_tests;
~HandleCallback();
};
//! Bitcoin application.
BitcoinApplication& m_app;
//! Set of pending callback names. Used to track expected callbacks and shut
//! down the app after the last callback has been handled and all tests have
//! either run or thrown exceptions. This could be a simple int counter
//! instead of a set of names, but the names might be useful for debugging.
std::multiset<std::string> m_callbacks;
};
#endif // BITCOIN_QT_TEST_APPTESTS_H

View file

@ -41,7 +41,7 @@ void RPCNestedTests::rpcNestedTests()
TestingSetup test;
SetRPCWarmupFinished();
if (RPCIsInWarmup(nullptr)) SetRPCWarmupFinished();
std::string result;
std::string result2;

View file

@ -7,6 +7,9 @@
#endif
#include <chainparams.h>
#include <interfaces/node.h>
#include <qt/bitcoin.h>
#include <qt/test/apptests.h>
#include <qt/test/rpcnestedtests.h>
#include <util/system.h>
#include <qt/test/uritests.h>
@ -47,12 +50,13 @@ int main(int argc, char *argv[])
{
SetupEnvironment();
SetupNetworking();
SelectParams(CBaseChainParams::MAIN);
SelectParams(CBaseChainParams::REGTEST);
noui_connect();
ClearDatadirCache();
fs::path pathTemp = fs::temp_directory_path() / strprintf("test_bitcoin-qt_%lu_%i", (unsigned long)GetTime(), (int)GetRand(100000));
fs::create_directories(pathTemp);
gArgs.ForceSetArg("-datadir", pathTemp.string());
auto node = interfaces::MakeNode();
bool fInvalid = false;
@ -67,11 +71,15 @@ int main(int argc, char *argv[])
// Don't remove this, it's needed to access
// QApplication:: and QCoreApplication:: in the tests
QApplication app(argc, argv);
BitcoinApplication app(*node, argc, argv);
app.setApplicationName("Bitcoin-Qt-test");
SSL_library_init();
AppTests app_tests(app);
if (QTest::qExec(&app_tests) != 0) {
fInvalid = true;
}
URITests test1;
if (QTest::qExec(&test1) != 0) {
fInvalid = true;