LCOV - code coverage report
Current view: top level - src/qt/test - wallettests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 5 164 3.0 %
Date: 2020-09-26 01:30:44 Functions: 1 8 12.5 %

          Line data    Source code
       1             : // Copyright (c) 2015-2020 The Bitcoin Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <qt/test/wallettests.h>
       6             : #include <qt/test/util.h>
       7             : 
       8             : #include <interfaces/chain.h>
       9             : #include <interfaces/node.h>
      10             : #include <qt/bitcoinamountfield.h>
      11             : #include <qt/clientmodel.h>
      12             : #include <qt/optionsmodel.h>
      13             : #include <qt/platformstyle.h>
      14             : #include <qt/qvalidatedlineedit.h>
      15             : #include <qt/sendcoinsdialog.h>
      16             : #include <qt/sendcoinsentry.h>
      17             : #include <qt/transactiontablemodel.h>
      18             : #include <qt/transactionview.h>
      19             : #include <qt/walletmodel.h>
      20             : #include <key_io.h>
      21             : #include <test/util/setup_common.h>
      22             : #include <validation.h>
      23             : #include <wallet/wallet.h>
      24             : #include <qt/overviewpage.h>
      25             : #include <qt/receivecoinsdialog.h>
      26             : #include <qt/recentrequeststablemodel.h>
      27             : #include <qt/receiverequestdialog.h>
      28             : 
      29             : #include <memory>
      30             : 
      31             : #include <QAbstractButton>
      32             : #include <QAction>
      33             : #include <QApplication>
      34             : #include <QCheckBox>
      35             : #include <QPushButton>
      36             : #include <QTimer>
      37             : #include <QVBoxLayout>
      38             : #include <QTextEdit>
      39             : #include <QListView>
      40             : #include <QDialogButtonBox>
      41             : 
      42             : namespace
      43             : {
      44             : //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
      45           0 : void ConfirmSend(QString* text = nullptr, bool cancel = false)
      46             : {
      47           0 :     QTimer::singleShot(0, [text, cancel]() {
      48           0 :         for (QWidget* widget : QApplication::topLevelWidgets()) {
      49           0 :             if (widget->inherits("SendConfirmationDialog")) {
      50           0 :                 SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
      51           0 :                 if (text) *text = dialog->text();
      52           0 :                 QAbstractButton* button = dialog->button(cancel ? QMessageBox::Cancel : QMessageBox::Yes);
      53           0 :                 button->setEnabled(true);
      54           0 :                 button->click();
      55           0 :             }
      56           0 :         }
      57           0 :     });
      58           0 : }
      59             : 
      60             : //! Send coins to address and return txid.
      61           0 : uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDestination& address, CAmount amount, bool rbf)
      62             : {
      63           0 :     QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");
      64           0 :     SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
      65           0 :     entry->findChild<QValidatedLineEdit*>("payTo")->setText(QString::fromStdString(EncodeDestination(address)));
      66           0 :     entry->findChild<BitcoinAmountField*>("payAmount")->setValue(amount);
      67           0 :     sendCoinsDialog.findChild<QFrame*>("frameFee")
      68           0 :         ->findChild<QFrame*>("frameFeeSelection")
      69           0 :         ->findChild<QCheckBox*>("optInRBF")
      70           0 :         ->setCheckState(rbf ? Qt::Checked : Qt::Unchecked);
      71           0 :     uint256 txid;
      72           0 :     boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](CWallet*, const uint256& hash, ChangeType status) {
      73           0 :         if (status == CT_NEW) txid = hash;
      74           0 :     }));
      75           0 :     ConfirmSend();
      76           0 :     bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked");
      77           0 :     assert(invoked);
      78             :     return txid;
      79           0 : }
      80             : 
      81             : //! Find index of txid in transaction list.
      82           0 : QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
      83             : {
      84           0 :     QString hash = QString::fromStdString(txid.ToString());
      85           0 :     int rows = model.rowCount({});
      86           0 :     for (int row = 0; row < rows; ++row) {
      87           0 :         QModelIndex index = model.index(row, 0, {});
      88           0 :         if (model.data(index, TransactionTableModel::TxHashRole) == hash) {
      89           0 :             return index;
      90             :         }
      91             :     }
      92           0 :     return {};
      93           0 : }
      94             : 
      95             : //! Invoke bumpfee on txid and check results.
      96           0 : void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, std::string expectError, bool cancel)
      97             : {
      98           0 :     QTableView* table = view.findChild<QTableView*>("transactionView");
      99           0 :     QModelIndex index = FindTx(*table->selectionModel()->model(), txid);
     100           0 :     QVERIFY2(index.isValid(), "Could not find BumpFee txid");
     101             : 
     102             :     // Select row in table, invoke context menu, and make sure bumpfee action is
     103             :     // enabled or disabled as expected.
     104           0 :     QAction* action = view.findChild<QAction*>("bumpFeeAction");
     105           0 :     table->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
     106           0 :     action->setEnabled(expectDisabled);
     107           0 :     table->customContextMenuRequested({});
     108           0 :     QCOMPARE(action->isEnabled(), !expectDisabled);
     109             : 
     110           0 :     action->setEnabled(true);
     111           0 :     QString text;
     112           0 :     if (expectError.empty()) {
     113           0 :         ConfirmSend(&text, cancel);
     114             :     } else {
     115           0 :         ConfirmMessage(&text);
     116             :     }
     117           0 :     action->trigger();
     118           0 :     QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1);
     119           0 : }
     120             : 
     121             : //! Simple qt wallet tests.
     122             : //
     123             : // Test widgets can be debugged interactively calling show() on them and
     124             : // manually running the event loop, e.g.:
     125             : //
     126             : //     sendCoinsDialog.show();
     127             : //     QEventLoop().exec();
     128             : //
     129             : // This also requires overriding the default minimal Qt platform:
     130             : //
     131             : //     QT_QPA_PLATFORM=xcb     src/qt/test/test_bitcoin-qt  # Linux
     132             : //     QT_QPA_PLATFORM=windows src/qt/test/test_bitcoin-qt  # Windows
     133             : //     QT_QPA_PLATFORM=cocoa   src/qt/test/test_bitcoin-qt  # macOS
     134           0 : void TestGUI(interfaces::Node& node)
     135             : {
     136             :     // Set up wallet and chain with 105 blocks (5 mature blocks for spending).
     137           0 :     TestChain100Setup test;
     138           0 :     for (int i = 0; i < 5; ++i) {
     139           0 :         test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
     140             :     }
     141           0 :     node.setContext(&test.m_node);
     142           0 :     std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase());
     143           0 :     bool firstRun;
     144           0 :     wallet->LoadWallet(firstRun);
     145             :     {
     146           0 :         auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
     147           0 :         LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
     148           0 :         wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive");
     149           0 :         spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
     150           0 :         wallet->SetLastBlockProcessed(105, ::ChainActive().Tip()->GetBlockHash());
     151           0 :     }
     152             :     {
     153           0 :         WalletRescanReserver reserver(*wallet);
     154           0 :         reserver.reserve();
     155           0 :         CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, 0 /* block height */, {} /* max height */, reserver, true /* fUpdate */);
     156           0 :         QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
     157           0 :         QCOMPARE(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash());
     158           0 :         QVERIFY(result.last_failed_block.IsNull());
     159           0 :     }
     160           0 :     wallet->SetBroadcastTransactions(true);
     161             : 
     162             :     // Create widgets for sending coins and listing transactions.
     163           0 :     std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
     164           0 :     SendCoinsDialog sendCoinsDialog(platformStyle.get());
     165           0 :     TransactionView transactionView(platformStyle.get());
     166           0 :     OptionsModel optionsModel;
     167           0 :     ClientModel clientModel(node, &optionsModel);
     168           0 :     AddWallet(wallet);
     169           0 :     WalletModel walletModel(interfaces::MakeWallet(wallet), clientModel, platformStyle.get());
     170           0 :     RemoveWallet(wallet, nullopt);
     171           0 :     sendCoinsDialog.setModel(&walletModel);
     172           0 :     transactionView.setModel(&walletModel);
     173             : 
     174             :     {
     175             :         // Check balance in send dialog
     176           0 :         QLabel* balanceLabel = sendCoinsDialog.findChild<QLabel*>("labelBalance");
     177           0 :         QString balanceText = balanceLabel->text();
     178           0 :         int unit = walletModel.getOptionsModel()->getDisplayUnit();
     179           0 :         CAmount balance = walletModel.wallet().getBalance();
     180           0 :         QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::SeparatorStyle::ALWAYS);
     181           0 :         QCOMPARE(balanceText, balanceComparison);
     182           0 :     }
     183             : 
     184             :     // Send two transactions, and verify they are added to transaction list.
     185           0 :     TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
     186           0 :     QCOMPARE(transactionTableModel->rowCount({}), 105);
     187           0 :     uint256 txid1 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, false /* rbf */);
     188           0 :     uint256 txid2 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 10 * COIN, true /* rbf */);
     189           0 :     QCOMPARE(transactionTableModel->rowCount({}), 107);
     190           0 :     QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
     191           0 :     QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
     192             : 
     193             :     // Call bumpfee. Test disabled, canceled, enabled, then failing cases.
     194           0 :     BumpFee(transactionView, txid1, true /* expect disabled */, "not BIP 125 replaceable" /* expected error */, false /* cancel */);
     195           0 :     BumpFee(transactionView, txid2, false /* expect disabled */, {} /* expected error */, true /* cancel */);
     196           0 :     BumpFee(transactionView, txid2, false /* expect disabled */, {} /* expected error */, false /* cancel */);
     197           0 :     BumpFee(transactionView, txid2, true /* expect disabled */, "already bumped" /* expected error */, false /* cancel */);
     198             : 
     199             :     // Check current balance on OverviewPage
     200           0 :     OverviewPage overviewPage(platformStyle.get());
     201           0 :     overviewPage.setWalletModel(&walletModel);
     202           0 :     QLabel* balanceLabel = overviewPage.findChild<QLabel*>("labelBalance");
     203           0 :     QString balanceText = balanceLabel->text().trimmed();
     204           0 :     int unit = walletModel.getOptionsModel()->getDisplayUnit();
     205           0 :     CAmount balance = walletModel.wallet().getBalance();
     206           0 :     QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::SeparatorStyle::ALWAYS);
     207           0 :     QCOMPARE(balanceText, balanceComparison);
     208             : 
     209             :     // Check Request Payment button
     210           0 :     ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get());
     211           0 :     receiveCoinsDialog.setModel(&walletModel);
     212           0 :     RecentRequestsTableModel* requestTableModel = walletModel.getRecentRequestsTableModel();
     213             : 
     214             :     // Label input
     215           0 :     QLineEdit* labelInput = receiveCoinsDialog.findChild<QLineEdit*>("reqLabel");
     216           0 :     labelInput->setText("TEST_LABEL_1");
     217             : 
     218             :     // Amount input
     219           0 :     BitcoinAmountField* amountInput = receiveCoinsDialog.findChild<BitcoinAmountField*>("reqAmount");
     220           0 :     amountInput->setValue(1);
     221             : 
     222             :     // Message input
     223           0 :     QLineEdit* messageInput = receiveCoinsDialog.findChild<QLineEdit*>("reqMessage");
     224           0 :     messageInput->setText("TEST_MESSAGE_1");
     225           0 :     int initialRowCount = requestTableModel->rowCount({});
     226           0 :     QPushButton* requestPaymentButton = receiveCoinsDialog.findChild<QPushButton*>("receiveButton");
     227           0 :     requestPaymentButton->click();
     228           0 :     for (QWidget* widget : QApplication::topLevelWidgets()) {
     229           0 :         if (widget->inherits("ReceiveRequestDialog")) {
     230           0 :             ReceiveRequestDialog* receiveRequestDialog = qobject_cast<ReceiveRequestDialog*>(widget);
     231           0 :             QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("payment_header")->text(), QString("Payment information"));
     232           0 :             QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("uri_tag")->text(), QString("URI:"));
     233           0 :             QString uri = receiveRequestDialog->QObject::findChild<QLabel*>("uri_content")->text();
     234           0 :             QCOMPARE(uri.count("bitcoin:"), 2);
     235           0 :             QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("address_tag")->text(), QString("Address:"));
     236             : 
     237           0 :             QCOMPARE(uri.count("amount=0.00000001"), 2);
     238           0 :             QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("amount_tag")->text(), QString("Amount:"));
     239           0 :             QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("amount_content")->text(), QString("0.00000001 ") + QString::fromStdString(CURRENCY_UNIT));
     240             : 
     241           0 :             QCOMPARE(uri.count("label=TEST_LABEL_1"), 2);
     242           0 :             QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("label_tag")->text(), QString("Label:"));
     243           0 :             QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("label_content")->text(), QString("TEST_LABEL_1"));
     244             : 
     245           0 :             QCOMPARE(uri.count("message=TEST_MESSAGE_1"), 2);
     246           0 :             QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("message_tag")->text(), QString("Message:"));
     247           0 :             QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("message_content")->text(), QString("TEST_MESSAGE_1"));
     248           0 :         }
     249           0 :     }
     250             : 
     251             :     // Clear button
     252           0 :     QPushButton* clearButton = receiveCoinsDialog.findChild<QPushButton*>("clearButton");
     253           0 :     clearButton->click();
     254           0 :     QCOMPARE(labelInput->text(), QString(""));
     255           0 :     QCOMPARE(amountInput->value(), CAmount(0));
     256           0 :     QCOMPARE(messageInput->text(), QString(""));
     257             : 
     258             :     // Check addition to history
     259           0 :     int currentRowCount = requestTableModel->rowCount({});
     260           0 :     QCOMPARE(currentRowCount, initialRowCount+1);
     261             : 
     262             :     // Check Remove button
     263           0 :     QTableView* table = receiveCoinsDialog.findChild<QTableView*>("recentRequestsView");
     264           0 :     table->selectRow(currentRowCount-1);
     265           0 :     QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>("removeRequestButton");
     266           0 :     removeRequestButton->click();
     267           0 :     QCOMPARE(requestTableModel->rowCount({}), currentRowCount-1);
     268           0 : }
     269             : 
     270             : } // namespace
     271             : 
     272           1 : void WalletTests::walletTests()
     273             : {
     274             : #ifdef Q_OS_MAC
     275           1 :     if (QApplication::platformName() == "minimal") {
     276             :         // Disable for mac on "minimal" platform to avoid crashes inside the Qt
     277             :         // framework when it tries to look up unimplemented cocoa functions,
     278             :         // and fails to handle returned nulls
     279             :         // (https://bugreports.qt.io/browse/QTBUG-49686).
     280           1 :         QWARN("Skipping WalletTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke "
     281             :               "with 'QT_QPA_PLATFORM=cocoa test_bitcoin-qt' on mac, or else use a linux or windows build.");
     282           1 :         return;
     283             :     }
     284             : #endif
     285           0 :     TestGUI(m_node);
     286           1 : }

Generated by: LCOV version 1.15