test: make it possible for DebugLogHelper to wait for messages

Extend `DebugLogHelper::~DebugLogHelper()` to be able to
optionally wait for messages, possibly logged from another thread, to
arrive before performing the final check.
This commit is contained in:
Vasil Dimov 2022-12-23 10:58:39 +01:00
parent 81e469a697
commit 18ead6a82a
No known key found for this signature in database
GPG key ID: 54DF06F64B55CBBF
3 changed files with 47 additions and 6 deletions

View file

@ -12,13 +12,18 @@
#include <iostream>
#include <stdexcept>
DebugLogHelper::DebugLogHelper(std::string message, MatchFn match)
: m_message{std::move(message)}, m_match(std::move(match))
DebugLogHelper::DebugLogHelper(std::string message, MatchFn match, std::optional<std::chrono::milliseconds> timeout)
: m_message{std::move(message)}, m_timeout{timeout}, m_match(std::move(match))
{
m_print_connection = LogInstance().PushBackCallback(
[this](const std::string& s) {
if (m_found) return;
m_found = s.find(m_message) != std::string::npos && m_match(&s);
StdLockGuard lock{m_mutex};
if (!m_found) {
if (s.find(m_message) != std::string::npos && m_match(&s)) {
m_found = true;
m_cv.notify_all();
}
}
});
noui_test_redirect();
m_receiving_log = true;
@ -26,6 +31,12 @@ DebugLogHelper::DebugLogHelper(std::string message, MatchFn match)
DebugLogHelper::~DebugLogHelper()
{
{
StdUniqueLock lock{m_mutex};
if (m_timeout.has_value()) {
m_cv.wait_for(lock, m_timeout.value(), [this]() EXCLUSIVE_LOCKS_REQUIRED(m_mutex) { return m_found; });
}
}
StopReceivingLog();
if (!m_found && m_match(nullptr)) {
std::cerr << "Fatal error: expected message not found in the debug log: " << m_message << "\n";

View file

@ -5,10 +5,14 @@
#ifndef BITCOIN_TEST_UTIL_LOGGING_H
#define BITCOIN_TEST_UTIL_LOGGING_H
#include <sync.h>
#include <util/macros.h>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <list>
#include <optional>
#include <string>
class DebugLogHelper
@ -16,13 +20,27 @@ class DebugLogHelper
public:
using MatchFn = std::function<bool(const std::string* line)>;
explicit DebugLogHelper(std::string message, MatchFn match = [](const std::string*){ return true; });
static bool MatchFnDefault(const std::string*)
{
return true;
}
explicit DebugLogHelper(
std::string message,
MatchFn match = MatchFnDefault,
std::optional<std::chrono::milliseconds> timeout = std::nullopt);
~DebugLogHelper();
private:
const std::string m_message;
bool m_found{false};
const std::optional<std::chrono::milliseconds> m_timeout;
// Mutex + LOCK() is not usable here because LOCK() may print to the log
// itself (see DEBUG_LOCKCONTENTION) causing a deadlock between this mutex
// and BCLog::Logger::m_cs which is acquired when logging a message.
StdMutex m_mutex;
std::condition_variable_any m_cv;
bool m_found GUARDED_BY(m_mutex){false};
std::list<std::function<void(const std::string&)>>::iterator m_print_connection;
//! Custom match checking function.
@ -42,4 +60,7 @@ private:
#define ASSERT_DEBUG_LOG(message) DebugLogHelper UNIQUE_NAME(debugloghelper)(message)
#define ASSERT_DEBUG_LOG_WAIT(message, timeout) \
DebugLogHelper UNIQUE_NAME(debugloghelper)(message, DebugLogHelper::MatchFnDefault, timeout)
#endif // BITCOIN_TEST_UTIL_LOGGING_H

View file

@ -74,4 +74,13 @@ public:
~StdLockGuard() UNLOCK_FUNCTION() = default;
};
// StdUniqueLock provides an annotated version of std::unique_lock for us,
// and should only be used when sync.h Mutex/LOCK/etc are not usable.
class SCOPED_LOCKABLE StdUniqueLock : public std::unique_lock<StdMutex>
{
public:
explicit StdUniqueLock(StdMutex& cs) EXCLUSIVE_LOCK_FUNCTION(cs) : std::unique_lock<StdMutex>(cs) {}
~StdUniqueLock() UNLOCK_FUNCTION() = default;
};
#endif // BITCOIN_THREADSAFETY_H