diff --git a/.travis.yml b/.travis.yml --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ # bitcoind - HOST=x86_64-unknown-linux-gnu PACKAGES="bc python3-zmq" DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports CPPFLAGS=-DDEBUG_LOCKORDER" # No wallet - - HOST=x86_64-unknown-linux-gnu PACKAGES="python3" DEP_OPTS="NO_WALLET=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports" + - HOST=x86_64-unknown-linux-gnu PACKAGES="python3 xvfb" DEP_OPTS="NO_WALLET=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports" # Cross-Mac - HOST=x86_64-apple-darwin11 PACKAGES="cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python-dev" BITCOIN_CONFIG="--enable-gui --enable-reduce-exports" OSX_SDK=10.11 GOAL="deploy" @@ -61,6 +61,8 @@ - if [ -n "$OSX_SDK" -a ! -f depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then curl --location --fail $SDK_URL/MacOSX${OSX_SDK}.sdk.tar.gz -o depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz; fi - if [ -n "$OSX_SDK" -a -f depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then tar -C depends/SDKs -xf depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz; fi - make $MAKEJOBS -C depends HOST=$HOST $DEP_OPTS + # Start xvfb if needed, as documented at https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-xvfb-to-Run-Tests-That-Require-a-GUI + - if [ "$RUN_TESTS" = "true" -a "${DEP_OPTS#*NO_QT=1}" = "$DEP_OPTS" ]; then export DISPLAY=:99.0; /sbin/start-stop-daemon --start --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac; fi script: - if [ "$RUN_TESTS" = "true" -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then while read LINE; do travis_retry gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys $LINE; done < contrib/verify-commits/trusted-keys; fi - if [ "$RUN_TESTS" = "true" -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then git fetch --unshallow; fi diff --git a/build-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4 --- a/build-aux/m4/bitcoin_qt.m4 +++ b/build-aux/m4/bitcoin_qt.m4 @@ -130,6 +130,8 @@ if test "x$bitcoin_cv_need_acc_widget" = "xyes"; then _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(AccessibleFactory)], [-lqtaccessiblewidgets]) fi + _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin)],[-lqminimal]) + AC_DEFINE(QT_QPA_PLATFORM_MINIMAL, 1, [Define this symbol if the minimal qt platform exists]) if test x$TARGET_OS = xwindows; then _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)],[-lqwindows]) AC_DEFINE(QT_QPA_PLATFORM_WINDOWS, 1, [Define this symbol if the qt platform is windows]) diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -13,7 +13,9 @@ qt/test/moc_uritests.cpp if ENABLE_WALLET -TEST_QT_MOC_CPP += qt/test/moc_paymentservertests.cpp +TEST_QT_MOC_CPP += \ + qt/test/moc_paymentservertests.cpp \ + qt/test/moc_wallettests.cpp endif TEST_QT_H = \ @@ -23,7 +25,16 @@ qt/test/rpcnestedtests.h \ qt/test/uritests.h \ qt/test/paymentrequestdata.h \ - qt/test/paymentservertests.h + qt/test/paymentservertests.h \ + qt/test/wallettests.h + +TEST_BITCOIN_CPP = \ + test/test_bitcoin.cpp \ + test/testutil.cpp + +TEST_BITCOIN_H = \ + test/test_bitcoin.h \ + test/testutil.h qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(PROTOBUF_CFLAGS) @@ -35,10 +46,13 @@ qt/test/rpcnestedtests.cpp \ qt/test/test_main.cpp \ qt/test/uritests.cpp \ - $(TEST_QT_H) + $(TEST_QT_H) \ + $(TEST_BITCOIN_CPP) \ + $(TEST_BITCOIN_H) if ENABLE_WALLET qt_test_test_bitcoin_qt_SOURCES += \ - qt/test/paymentservertests.cpp + qt/test/paymentservertests.cpp \ + qt/test/wallettests.cpp endif nodist_qt_test_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP) diff --git a/src/Makefile.test.include b/src/Makefile.test.include --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -129,6 +129,7 @@ test/streams_tests.cpp \ test/test_bitcoin.cpp \ test/test_bitcoin.h \ + test/test_bitcoin_main.cpp \ test/testutil.cpp \ test/testutil.h \ test/timedata_tests.cpp \ diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -209,9 +209,13 @@ std::runtime_error); #endif + UnloadBlockIndex(); delete pcoinsTip; + pcoinsTip = nullptr; delete pcoinsdbview; + pcoinsdbview = nullptr; delete pblocktree; + pblocktree = nullptr; fs::remove_all(fs::path(path)); } 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 @@ -17,27 +17,40 @@ #ifdef ENABLE_WALLET #include "paymentservertests.h" +#include "wallettests.h" #endif -#include +#include #include #include #include -#if defined(QT_STATICPLUGIN) && QT_VERSION < 0x050000 +#if defined(QT_STATICPLUGIN) #include +#if QT_VERSION < 0x050000 Q_IMPORT_PLUGIN(qcncodecs) Q_IMPORT_PLUGIN(qjpcodecs) Q_IMPORT_PLUGIN(qtwcodecs) Q_IMPORT_PLUGIN(qkrcodecs) +#else +#if defined(QT_QPA_PLATFORM_MINIMAL) +Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin); +#endif +#if defined(QT_QPA_PLATFORM_XCB) +Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); +#elif defined(QT_QPA_PLATFORM_WINDOWS) +Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); +#elif defined(QT_QPA_PLATFORM_COCOA) +Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); +#endif +#endif #endif extern void noui_connect(); // This is all you need to run all the tests int main(int argc, char *argv[]) { - ECC_Start(); SetupEnvironment(); SetupNetworking(); SelectParams(CBaseChainParams::MAIN); @@ -45,9 +58,14 @@ bool fInvalid = false; + // Prefer the "minimal" platform for the test instead of the normal default + // platform ("xcb", "windows", or "cocoa") so tests can't unintentially + // interfere with any background GUIs and don't require extra resources. + setenv("QT_QPA_PLATFORM", "minimal", 0); + // Don't remove this, it's needed to access - // QCoreApplication:: in the tests - QCoreApplication app(argc, argv); + // QApplication:: and QCoreApplication:: in the tests + QApplication app(argc, argv); app.setApplicationName("BitcoinABC-Qt-test"); // This is necessary to initialize openssl on the test framework @@ -80,7 +98,12 @@ if (QTest::qExec(&test6) != 0) { fInvalid = true; } +#ifdef ENABLE_WALLET + WalletTests test7; + if (QTest::qExec(&test7) != 0) { + fInvalid = true; + } +#endif - ECC_Stop(); return fInvalid; } diff --git a/src/qt/test/wallettests.h b/src/qt/test/wallettests.h new file mode 100644 --- /dev/null +++ b/src/qt/test/wallettests.h @@ -0,0 +1,14 @@ +#ifndef BITCOIN_QT_TEST_WALLETTESTS_H +#define BITCOIN_QT_TEST_WALLETTESTS_H + +#include +#include + +class WalletTests : public QObject { + Q_OBJECT + +private Q_SLOTS: + void walletTests(); +}; + +#endif // BITCOIN_QT_TEST_WALLETTESTS_H diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp new file mode 100644 --- /dev/null +++ b/src/qt/test/wallettests.cpp @@ -0,0 +1,127 @@ +#include "wallettests.h" + +#include "dstencode.h" +#include "qt/bitcoinamountfield.h" +#include "qt/optionsmodel.h" +#include "qt/platformstyle.h" +#include "qt/qvalidatedlineedit.h" +#include "qt/sendcoinsdialog.h" +#include "qt/sendcoinsentry.h" +#include "qt/transactiontablemodel.h" +#include "qt/walletmodel.h" +#include "test/test_bitcoin.h" +#include "validation.h" +#include "wallet/wallet.h" + +#include +#include +#include +#include + +namespace { +//! Press "Yes" button in modal send confirmation dialog. +void ConfirmSend() { + QTimer::singleShot(0, Qt::PreciseTimer, []() { + for (QWidget *widget : QApplication::topLevelWidgets()) { + if (widget->inherits("SendConfirmationDialog")) { + SendConfirmationDialog *dialog = + qobject_cast(widget); + QAbstractButton *button = dialog->button(QMessageBox::Yes); + button->setEnabled(true); + button->click(); + } + } + }); +} + +//! Send coins to address and return txid. +uint256 SendCoins(CWallet &wallet, SendCoinsDialog &sendCoinsDialog, + const CTxDestination &address, Amount amount) { + QVBoxLayout *entries = sendCoinsDialog.findChild("entries"); + SendCoinsEntry *entry = + qobject_cast(entries->itemAt(0)->widget()); + entry->findChild("payTo")->setText( + QString::fromStdString(EncodeDestination(address))); + entry->findChild("payAmount")->setValue(amount); + uint256 txid; + boost::signals2::scoped_connection c = + wallet.NotifyTransactionChanged.connect( + [&txid](CWallet *, const uint256 &hash, ChangeType status) { + if (status == CT_NEW) txid = hash; + }); + ConfirmSend(); + QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked"); + return txid; +} + +//! Find index of txid in transaction list. +QModelIndex FindTx(const QAbstractItemModel &model, const uint256 &txid) { + QString hash = QString::fromStdString(txid.ToString()); + int rows = model.rowCount({}); + for (int row = 0; row < rows; ++row) { + QModelIndex index = model.index(row, 0, {}); + if (model.data(index, TransactionTableModel::TxHashRole) == hash) { + return index; + } + } + return {}; +} +} + +//! Simple qt wallet tests. +// +// Test widgets can be debugged interactively calling show() on them and +// manually running the event loop, e.g.: +// +// sendCoinsDialog.show(); +// QEventLoop().exec(); +// +// This also requires overriding the default minimal Qt platform: +// +// src/qt/test/test_bitcoin-qt -platform xcb # Linux +// src/qt/test/test_bitcoin-qt -platform windows # Windows +// src/qt/test/test_bitcoin-qt -platform cocoa # macOS +void WalletTests::walletTests() { + // Set up wallet and chain with 101 blocks (1 mature block for spending). + TestChain100Setup test; + test.CreateAndProcessBlock( + {}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); + bitdb.MakeMock(); + CWallet wallet("wallet_test.dat"); + bool firstRun; + wallet.LoadWallet(firstRun); + { + LOCK(wallet.cs_wallet); + wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", + "receive"); + wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); + } + { + LOCK(cs_main); + wallet.ScanForWalletTransactions(chainActive.Genesis(), true); + } + wallet.SetBroadcastTransactions(true); + + // Create widgets for sending coins and listing transactions. + std::unique_ptr platformStyle( + PlatformStyle::instantiate("other")); + SendCoinsDialog sendCoinsDialog(platformStyle.get()); + OptionsModel optionsModel; + WalletModel walletModel(platformStyle.get(), &wallet, &optionsModel); + sendCoinsDialog.setModel(&walletModel); + + // Send two transactions, and verify they are added to transaction list. + TransactionTableModel *transactionTableModel = + walletModel.getTransactionTableModel(); + QCOMPARE(transactionTableModel->rowCount({}), 101); + uint256 txid1 = + SendCoins(wallet, sendCoinsDialog, CTxDestination(CKeyID()), 5 * COIN); + uint256 txid2 = + SendCoins(wallet, sendCoinsDialog, CTxDestination(CKeyID()), 10 * COIN); + QCOMPARE(transactionTableModel->rowCount({}), 103); + QVERIFY(FindTx(*transactionTableModel, txid1).isValid()); + QVERIFY(FindTx(*transactionTableModel, txid2).isValid()); + + bitdb.Flush(true); + bitdb.Reset(); +} diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -2,8 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#define BOOST_TEST_MODULE Bitcoin Test Suite - #include "test_bitcoin.h" #include "chainparams.h" @@ -28,9 +26,6 @@ #include "test/testutil.h" -#include -#include - #include #include #include @@ -40,7 +35,6 @@ #include #include -std::unique_ptr g_connman; uint256 insecure_rand_seed = GetRandHash(); FastRandomContext insecure_rand_ctx(insecure_rand_seed); @@ -88,11 +82,14 @@ pblocktree = new CBlockTreeDB(1 << 20, true); pcoinsdbview = new CCoinsViewDB(1 << 23, true); pcoinsTip = new CCoinsViewCache(pcoinsdbview); - BOOST_REQUIRE(InitBlockIndex(config)); + if (!InitBlockIndex(config)) { + throw std::runtime_error("InitBlockIndex failed."); + } { CValidationState state; - bool ok = ActivateBestChain(config, state); - BOOST_REQUIRE(ok); + if (!ActivateBestChain(config, state)) { + throw std::runtime_error("ActivateBestChain failed."); + } } nScriptCheckThreads = 3; for (int i = 0; i < nScriptCheckThreads - 1; i++) { @@ -182,18 +179,6 @@ lp); } -void Shutdown(void *parg) { - exit(0); -} - -void StartShutdown() { - exit(0); -} - -bool ShutdownRequested() { - return false; -} - namespace { // A place to put misc. setup code eg "the travis workaround" that needs to run // at program startup and exit @@ -207,7 +192,6 @@ Init init; Init::Init() { - if (getenv("TRAVIS_NOHANG_WORKAROUND")) { // This is a workaround for MinGW/Win32 builds on Travis sometimes // hanging due to no output received by Travis after a 10-minute diff --git a/src/test/test_bitcoin_main.cpp b/src/test/test_bitcoin_main.cpp new file mode 100644 --- /dev/null +++ b/src/test/test_bitcoin_main.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2011-2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#define BOOST_TEST_MODULE Bitcoin Test Suite + +#include "net.h" + +#include + +std::unique_ptr g_connman; + +void Shutdown(void *parg) { + exit(EXIT_SUCCESS); +} + +void StartShutdown() { + exit(EXIT_SUCCESS); +} + +bool ShutdownRequested() { + return false; +}