diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -295,6 +295,7 @@ BITCOIN_QT_BASE_CPP = \ qt/bantablemodel.cpp \ + qt/bitcoin.cpp \ qt/bitcoinaddressvalidator.cpp \ qt/bitcoinamountfield.cpp \ qt/bitcoingui.cpp \ @@ -369,6 +370,9 @@ 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) @@ -391,10 +395,7 @@ $(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 diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -6,6 +6,7 @@ TESTS += qt/test/test_bitcoin-qt TEST_QT_MOC_CPP = \ + qt/test/moc_apptests.cpp \ qt/test/moc_bitcoinaddressvalidatortests.cpp \ qt/test/moc_compattests.cpp \ qt/test/moc_guiutiltests.cpp \ @@ -21,6 +22,7 @@ TEST_QT_H = \ qt/test/addressbooktests.h \ + qt/test/apptests.h \ qt/test/bitcoinaddressvalidatortests.h \ qt/test/compattests.h \ qt/test/guiutiltests.h \ @@ -41,6 +43,7 @@ $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(PROTOBUF_CFLAGS) qt_test_test_bitcoin_qt_SOURCES = \ + qt/test/apptests.cpp \ qt/test/bitcoinaddressvalidatortests.cpp \ qt/test/compattests.cpp \ qt/test/guiutiltests.cpp \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -80,6 +80,7 @@ add_library(bitcoin-qt-base bantablemodel.cpp + bitcoin.cpp bitcoinaddressvalidator.cpp bitcoinamountfield.cpp bitcoingui.cpp @@ -283,7 +284,7 @@ endif() # The executable -add_executable(bitcoin-qt WIN32 bitcoin.cpp) +add_executable(bitcoin-qt WIN32 main.cpp) if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") target_sources(bitcoin-qt PRIVATE res/bitcoin-qt-res.rc) endif() diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -75,6 +75,9 @@ void createWindow(const Config *, const NetworkStyle *networkStyle); /// Create splash screen void createSplashScreen(const NetworkStyle *networkStyle); + /// Basic initialization, before starting initialization/shutdown thread. + /// Return true on success. + bool baseInitialize(Config &config); /// Request core initialization void requestInitialize(Config &config, RPCServer &rpcServer, @@ -106,6 +109,7 @@ void requestedShutdown(); void stopThread(); void splashFinished(QWidget *window); + void windowShown(BitcoinGUI *window); private: QThread *coreThread; @@ -126,4 +130,6 @@ void startThread(); }; +int GuiMain(int argc, char *argv[]); + #endif // BITCOIN_QT_BITCOIN_H diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -76,14 +76,6 @@ LogPrintf("init message: %s\n", message); } -/** - * Translate string to current locale using Qt. - */ -const std::function G_TRANSLATION_FUN = - [](const char *psz) { - return QCoreApplication::translate("bitcoin-abc", psz).toStdString(); - }; - static QString GetLangTerritory() { QSettings settings; // Get desired locale (e.g. "de_DE") @@ -273,6 +265,10 @@ &QWidget::close); } +bool BitcoinApplication::baseInitialize(Config &config) { + return m_node.baseInitialize(config); +} + void BitcoinApplication::startThread() { if (coreThread) { return; @@ -401,7 +397,9 @@ qWarning() << "Platform customization:" << platformStyle->getName(); #ifdef ENABLE_WALLET PaymentServer::LoadRootCAs(); - paymentServer->setOptionsModel(optionsModel); + if (paymentServer) { + paymentServer->setOptionsModel(optionsModel); + } #endif clientModel = new ClientModel(m_node, optionsModel); @@ -432,19 +430,23 @@ window->show(); } Q_EMIT splashFinished(window); + Q_EMIT windowShown(window); #ifdef ENABLE_WALLET // Now that initialization/startup is done, process any command-line // bitcoincash: 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); @@ -555,7 +557,7 @@ } } -int main(int argc, char *argv[]) { +int GuiMain(int argc, char *argv[]) { #ifdef WIN32 util::WinCmdLineArgs winArgs; std::tie(argc, argv) = winArgs.get(); @@ -764,7 +766,7 @@ // 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(config)) { + if (!app.baseInitialize(config)) { // A dialog with detailed error will have been shown by InitError() return EXIT_FAILURE; } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -187,6 +187,8 @@ 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 */ diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -109,6 +109,7 @@ * central widget is the rpc console. */ setCentralWidget(rpcConsole); + Q_EMIT consoleShown(rpcConsole); } // Accept D&D of URIs @@ -379,6 +380,7 @@ 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"), @@ -679,10 +681,13 @@ 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 } @@ -756,6 +761,7 @@ void BitcoinGUI::showDebugWindow() { GUIUtil::bringToFront(rpcConsole); + Q_EMIT consoleShown(rpcConsole); } void BitcoinGUI::showDebugWindowActivateConsole() { diff --git a/src/qt/main.cpp b/src/qt/main.cpp new file mode 100644 --- /dev/null +++ b/src/qt/main.cpp @@ -0,0 +1,20 @@ +// 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 + +#include + +#include +#include + +/** Translate string to current locale using Qt. */ +extern const std::function G_TRANSLATION_FUN = + [](const char *psz) { + return QCoreApplication::translate("bitcoin-abc", psz).toStdString(); + }; + +int main(int argc, char *argv[]) { + return GuiMain(argc, argv); +} diff --git a/src/qt/test/CMakeLists.txt b/src/qt/test/CMakeLists.txt --- a/src/qt/test/CMakeLists.txt +++ b/src/qt/test/CMakeLists.txt @@ -8,6 +8,7 @@ add_test_to_suite(bitcoin-qt test_bitcoin-qt addressbooktests.cpp + apptests.cpp bitcoinaddressvalidatortests.cpp compattests.cpp guiutiltests.cpp diff --git a/src/qt/test/apptests.h b/src/qt/test/apptests.h new file mode 100644 --- /dev/null +++ b/src/qt/test/apptests.h @@ -0,0 +1,51 @@ +// 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 + +#include +#include +#include + +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 m_callbacks; +}; + +#endif // BITCOIN_QT_TEST_APPTESTS_H diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp new file mode 100644 --- /dev/null +++ b/src/qt/test/apptests.cpp @@ -0,0 +1,130 @@ +// 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_CONFIG_H) +#include +#endif +#ifdef ENABLE_WALLET +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x050000 +#include +#endif +#include + +#include + +#include +#include + +namespace { +//! Call getblockchaininfo RPC and check first field of JSON output. +void TestRpcCommand(RPCConsole *console) { + QEventLoop loop; + QTextEdit *messagesWidget = + console->findChild("messagesWidget"); + QObject::connect(messagesWidget, &QTextEdit::textChanged, &loop, + &QEventLoop::quit); + QLineEdit *lineEdit = console->findChild("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 + + Config &config = const_cast(GetConfig()); + + m_app.parameterSetup(); + m_app.createOptionsModel(true /* reset settings */); + QScopedPointer style(NetworkStyle::instantiate( + QString::fromStdString(Params().NetworkIDString()))); + m_app.setupPlatformStyle(); + m_app.createWindow(&config, style.data()); + connect(&m_app, &BitcoinApplication::windowShown, this, + &AppTests::guiTests); + expectCallback("guiTests"); + m_app.baseInitialize(config); + + RPCServer rpcServer; + HTTPRPCRequestProcessor httpRPCRequestProcessor(config, rpcServer); + m_app.requestInitialize(config, rpcServer, httpRPCRequestProcessor); + m_app.exec(); + m_app.requestShutdown(config); + 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("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(); + } +} diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -8,9 +8,12 @@ #include #include +#include #include #include +#include +#include #include #include #include @@ -48,7 +51,7 @@ int main(int argc, char *argv[]) { SetupEnvironment(); SetupNetworking(); - SelectParams(CBaseChainParams::MAIN); + SelectParams(CBaseChainParams::REGTEST); noui_connect(); ClearDatadirCache(); fs::path pathTemp = @@ -57,6 +60,7 @@ (int)GetRand(100000)); fs::create_directories(pathTemp); gArgs.ForceSetArg("-datadir", pathTemp.string()); + auto node = interfaces::MakeNode(); bool fInvalid = false; @@ -67,13 +71,17 @@ // 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("BitcoinABC-Qt-test"); // This is necessary to initialize openssl on the test framework // (at least on Darwin). SSL_library_init(); + AppTests app_tests(app); + if (QTest::qExec(&app_tests) != 0) { + fInvalid = true; + } URITests test1; if (QTest::qExec(&test1) != 0) { fInvalid = true;