mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-02-19 11:59:23 +00:00
There are some mutable, global state variables that are currently reset by UnloadBlockIndex such as pindexBestHeader which should be cleaned up whenever the ChainstateManager is unloaded/reset/destructed/etc. Not cleaning them up leads to bugs like a use-after-free that happens like so: 1. At the end of a test, ChainstateManager is destructed, which also destructs BlockManager, which calls BlockManager::Unload to free all CBlockIndexes in its BlockMap 2. Since pindexBestHeader is not cleaned up, it now points to an invalid location 3. Another test starts to init, and calls LoadGenesisBlock, which calls AddToBlockIndex, which compares the genesis block with an invalid location 4. Cute puppies perish by the hundreds Previously, for normal codepaths (e.g. bitcoind), we relied on the fact that our program will be unloaded by the operating system which effectively resets these variables. The one exception is in QT tests, where these variables had to be manually reset. Since now ChainstateManager is no longer a global, we can just put this logic in its destructor to make sure that callers are always correct. Over time, we should probably move these mutable global state variables into ChainstateManager or CChainState so it's easier to reason about their lifecycles.
118 lines
3.9 KiB
C++
118 lines
3.9 KiB
C++
// Copyright (c) 2018-2020 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 <key.h>
|
|
#include <qt/bitcoin.h>
|
|
#include <qt/bitcoingui.h>
|
|
#include <qt/networkstyle.h>
|
|
#include <qt/rpcconsole.h>
|
|
#include <shutdown.h>
|
|
#include <test/util/setup_common.h>
|
|
#include <univalue.h>
|
|
#include <validation.h>
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include <config/bitcoin-config.h>
|
|
#endif
|
|
|
|
#include <QAction>
|
|
#include <QLineEdit>
|
|
#include <QScopedPointer>
|
|
#include <QSignalSpy>
|
|
#include <QTest>
|
|
#include <QTextEdit>
|
|
#include <QtGlobal>
|
|
#include <QtTest/QtTestWidgets>
|
|
#include <QtTest/QtTestGui>
|
|
|
|
namespace {
|
|
//! Call getblockchaininfo RPC and check first field of JSON output.
|
|
void TestRpcCommand(RPCConsole* console)
|
|
{
|
|
QTextEdit* messagesWidget = console->findChild<QTextEdit*>("messagesWidget");
|
|
QLineEdit* lineEdit = console->findChild<QLineEdit*>("lineEdit");
|
|
QSignalSpy mw_spy(messagesWidget, &QTextEdit::textChanged);
|
|
QVERIFY(mw_spy.isValid());
|
|
QTest::keyClicks(lineEdit, "getblockchaininfo");
|
|
QTest::keyClick(lineEdit, Qt::Key_Return);
|
|
QVERIFY(mw_spy.wait(1000));
|
|
QCOMPARE(mw_spy.count(), 4);
|
|
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 'QT_QPA_PLATFORM=cocoa test_bitcoin-qt' on mac, or else use a linux or windows build.");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
fs::create_directories([] {
|
|
BasicTestingSetup test{CBaseChainParams::REGTEST}; // Create a temp data directory to backup the gui settings to
|
|
return gArgs.GetDataDirNet() / "blocks";
|
|
}());
|
|
|
|
qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
|
|
m_app.parameterSetup();
|
|
m_app.createOptionsModel(true /* reset settings */);
|
|
QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(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.
|
|
LogInstance().DisconnectTestLogger();
|
|
AbortShutdown();
|
|
}
|
|
|
|
//! 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();
|
|
}
|
|
}
|