dncurrency/nano/test_common/testutil.hpp
Piotr Wójcik baabcca426
Telemetry refactor (#4026)
* Simplify telemetry

* Fix tests

* Cleanup config

* Cleanup local telemetry

* Remove unused flag

* Fix slow tests

* Fix rpc tests

* Cleanup `nano::test::compare_telemetry`

* Add more testcases

* Add ongoing telemetry broadcasts

* Cleanup

* Fixes

* Do not immediately remove telemetry from disconnected peers

* Increase telemetry broadcast & request intervals

* Update docs

* Refactor `peer_exclusion` a bit

* Filter and disconnect from peers with mismatched genesis

---------

Co-authored-by: clemahieu <clemahieu@gmail.com>
2023-02-02 16:14:26 +01:00

410 lines
13 KiB
C++

#pragma once
#include <nano/lib/errors.hpp>
#include <nano/lib/locks.hpp>
#include <nano/lib/timer.hpp>
#include <nano/node/transport/transport.hpp>
#include <gtest/gtest.h>
#include <boost/iostreams/concepts.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <string>
#define GTEST_TEST_ERROR_CODE(expression, text, actual, expected, fail) \
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
if (const ::testing::AssertionResult gtest_ar_ = ::testing::AssertionResult (expression)) \
; \
else \
fail (::testing::internal::GetBoolAssertionFailureMessage ( \
gtest_ar_, text, actual, expected) \
.c_str ())
/** Extends gtest with a std::error_code assert that prints the error code message when non-zero */
#define ASSERT_NO_ERROR(condition) \
GTEST_TEST_ERROR_CODE (!(condition), #condition, condition.message ().c_str (), "", \
GTEST_FATAL_FAILURE_)
/** Extends gtest with a std::error_code assert that prints the error code message when non-zero */
#define EXPECT_NO_ERROR(condition) \
GTEST_TEST_ERROR_CODE (!(condition), #condition, condition.message ().c_str (), "", \
GTEST_NONFATAL_FAILURE_)
/** Extends gtest with a std::error_code assert that expects an error */
#define ASSERT_IS_ERROR(condition) \
GTEST_TEST_ERROR_CODE ((condition.value () != 0), #condition, "An error was expected", "", \
GTEST_FATAL_FAILURE_)
/** Extends gtest with a std::error_code assert that expects an error */
#define EXPECT_IS_ERROR(condition) \
GTEST_TEST_ERROR_CODE ((condition.value () != 0), #condition, "An error was expected", "", \
GTEST_NONFATAL_FAILURE_)
/** Asserts that the condition becomes true within the deadline */
#define ASSERT_TIMELY(time, condition) \
system.deadline_set (time); \
while (!(condition)) \
{ \
ASSERT_NO_ERROR (system.poll ()); \
}
/** Expects that the condition becomes true within the deadline */
#define EXPECT_TIMELY(time, condition) \
system.deadline_set (time); \
{ \
std::error_code _ec; \
while (!(condition) && !(_ec = system.poll ())) \
{ \
} \
EXPECT_NO_ERROR (_ec); \
}
/*
* Asserts that the `val1 == val2` condition becomes true within the deadline
* Condition must hold for at least 2 consecutive reads
*/
#define ASSERT_TIMELY_EQ(time, val1, val2) \
system.deadline_set (time); \
while (!((val1) == (val2)) && !system.poll ()) \
{ \
} \
ASSERT_EQ (val1, val2);
/*
* Waits specified number of time while keeping system running.
* Useful for asserting conditions that should still hold after some delay of time
*/
#define WAIT(time) \
system.deadline_set (time); \
while (!system.poll ()) \
{ \
}
/*
* Asserts that condition is always true during the specified amount of time
*/
#define ASSERT_ALWAYS(time, condition) \
system.deadline_set (time); \
while (!system.poll ()) \
{ \
ASSERT_TRUE (condition); \
}
/*
* Asserts that condition is always true during the specified amount of time
*/
#define ASSERT_ALWAYS_EQ(time, val1, val2) \
system.deadline_set (time); \
while (!system.poll ()) \
{ \
ASSERT_EQ (val1, val2); \
}
/*
* Asserts that condition is never true during the specified amount of time
*/
#define ASSERT_NEVER(time, condition) \
system.deadline_set (time); \
while (!system.poll ()) \
{ \
ASSERT_FALSE (condition); \
}
/* Convenience globals for gtest projects */
namespace nano
{
class node;
using uint128_t = boost::multiprecision::uint128_t;
class keypair;
class public_key;
class block_hash;
class telemetry_data;
class network_params;
class vote;
class block;
extern nano::uint128_t const & genesis_amount;
namespace test
{
class system;
class stringstream_mt_sink : public boost::iostreams::sink
{
public:
stringstream_mt_sink () = default;
stringstream_mt_sink (stringstream_mt_sink const & sink)
{
nano::lock_guard<nano::mutex> guard{ mutex };
ss << sink.ss.str ();
}
std::streamsize write (char const * string_to_write, std::streamsize size)
{
nano::lock_guard<nano::mutex> guard{ mutex };
ss << std::string (string_to_write, size);
return size;
}
std::string str ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
return ss.str ();
}
private:
mutable nano::mutex mutex;
std::stringstream ss;
};
class boost_log_cerr_redirect
{
public:
boost_log_cerr_redirect (std::streambuf * new_buffer) :
old (std::cerr.rdbuf (new_buffer))
{
console_sink = (boost::log::add_console_log (std::cerr, boost::log::keywords::format = "%Message%"));
}
~boost_log_cerr_redirect ()
{
std::cerr.rdbuf (old);
boost::log::core::get ()->remove_sink (console_sink);
}
private:
std::streambuf * old;
boost::shared_ptr<boost::log::sinks::synchronous_sink<boost::log::sinks::text_ostream_backend>> console_sink;
};
class cout_redirect
{
public:
cout_redirect (std::streambuf * new_buffer)
{
std::cout.rdbuf (new_buffer);
}
~cout_redirect ()
{
std::cout.rdbuf (old);
}
private:
std::streambuf * old{ std::cout.rdbuf () };
};
/**
* Helper to signal completion of async handlers in tests.
* Subclasses implement specific conditions for completion.
*/
class completion_signal
{
public:
virtual ~completion_signal ()
{
notify ();
}
/** Explicitly notify the completion */
void notify ()
{
cv.notify_all ();
}
protected:
nano::condition_variable cv;
nano::mutex mutex;
};
/**
* Signals completion when a count is reached.
*/
class counted_completion : public completion_signal
{
public:
/**
* Constructor
* @param required_count_a When increment() reaches this count within the deadline, await_count_for() will return false.
*/
counted_completion (unsigned required_count_a) :
required_count (required_count_a)
{
}
/**
* Wait for increment() to signal completion, or reaching the deadline.
* @param deadline_duration_a Deadline as a std::chrono duration
* @return true if the count is reached within the deadline
*/
template <typename UNIT>
bool await_count_for (UNIT deadline_duration_a)
{
nano::timer<UNIT> timer (nano::timer_state::started);
bool error = true;
while (error && timer.before_deadline (deadline_duration_a))
{
error = count < required_count;
if (error)
{
nano::unique_lock<nano::mutex> lock{ mutex };
cv.wait_for (lock, std::chrono::milliseconds (1));
}
}
return error;
}
/** Increments the current count. If the required count is reached, await_count_for() waiters are notified. */
unsigned increment ()
{
auto val (count.fetch_add (1));
if (val >= required_count)
{
notify ();
}
return val;
}
void increment_required_count ()
{
++required_count;
}
private:
std::atomic<unsigned> count{ 0 };
std::atomic<unsigned> required_count;
};
/**
* A helper that calls `start` from constructor and `stop` from destructor
*/
template <class T>
class start_stop_guard
{
public:
explicit start_stop_guard (T & ref_a) :
ref{ ref_a }
{
ref.start ();
}
~start_stop_guard ()
{
ref.stop ();
}
private:
T & ref;
};
void wait_peer_connections (nano::test::system &);
/**
* Generate a random block hash
*/
nano::hash_or_account random_hash_or_account ();
/**
* Generate a random block hash
*/
nano::block_hash random_hash ();
/**
* Generate a random block hash
*/
nano::account random_account ();
/**
Convenience function to call `node::process` function for multiple blocks at once.
@return true if all blocks were successfully processed and inserted into ledger
*/
bool process (nano::node & node, std::vector<std::shared_ptr<nano::block>> blocks);
/*
* Convenience function to process multiple blocks as if they were live blocks arriving from the network
* It is not guaranted that those blocks will be inserted into ledger (there might be forks, missing links etc)
* @return true if all blocks were successfully processed
*/
bool process_live (nano::node & node, std::vector<std::shared_ptr<nano::block>> blocks);
/*
* Convenience function to confirm a list of blocks
* The actual confirmation will happen asynchronously, check for that with `nano::test::confirmed (..)` function
* @return true if successfully scheduled blocks to be confirmed
*/
bool confirm (nano::node & node, std::vector<std::shared_ptr<nano::block>> blocks);
/*
* Convenience function to confirm a list of hashes
* The actual confirmation will happen asynchronously, check for that with `nano::test::confirmed (..)` function
* @return true if successfully scheduled blocks to be confirmed
*/
bool confirm (nano::node & node, std::vector<nano::block_hash> hashes);
/*
* Convenience function to check whether a list of blocks is confirmed.
* @return true if all blocks are confirmed, false otherwise
*/
bool confirmed (nano::node & node, std::vector<std::shared_ptr<nano::block>> blocks);
/*
* Convenience function to check whether a list of hashes is confirmed.
* @return true if all blocks are confirmed, false otherwise
*/
bool confirmed (nano::node & node, std::vector<nano::block_hash> hashes);
/*
* Convenience function to check whether a list of hashes exists in node ledger.
* @return true if all blocks are fully processed and inserted in the ledger, false otherwise
*/
bool exists (nano::node & node, std::vector<nano::block_hash> hashes);
/*
* Convenience function to check whether a list of blocks exists in node ledger.
* @return true if all blocks are fully processed and inserted in the ledger, false otherwise
*/
bool exists (nano::node & node, std::vector<std::shared_ptr<nano::block>> blocks);
/*
* Convenience function to start elections for a list of hashes. Blocks are loaded from ledger.
* @return true if all blocks exist and were queued to election scheduler
*/
bool activate (nano::node & node, std::vector<nano::block_hash> hashes);
/*
* Convenience function to start elections for a list of hashes. Blocks are loaded from ledger.
* @return true if all blocks exist and were queued to election scheduler
*/
bool activate (nano::node & node, std::vector<std::shared_ptr<nano::block>> blocks);
/*
* Convenience function that checks whether all hashes from list have currently active elections
* @return true if all blocks have currently active elections, false othersie
*/
bool active (nano::node & node, std::vector<nano::block_hash> hashes);
/*
* Convenience function that checks whether all hashes from list have currently active elections
* @return true if all blocks have currently active elections, false othersie
*/
bool active (nano::node & node, std::vector<std::shared_ptr<nano::block>> blocks);
/*
* Convenience function to create a new vote from list of blocks
*/
std::shared_ptr<nano::vote> make_vote (nano::keypair key, std::vector<std::shared_ptr<nano::block>> blocks, uint64_t timestamp = 0, uint8_t duration = 0);
/*
* Convenience function to create a new vote from list of block hashes
*/
std::shared_ptr<nano::vote> make_vote (nano::keypair key, std::vector<nano::block_hash> hashes, uint64_t timestamp = 0, uint8_t duration = 0);
/*
* Convenience function to create a new final vote from list of blocks
*/
std::shared_ptr<nano::vote> make_final_vote (nano::keypair key, std::vector<std::shared_ptr<nano::block>> blocks);
/*
* Convenience function to create a new final vote from list of block hashes
*/
std::shared_ptr<nano::vote> make_final_vote (nano::keypair key, std::vector<nano::block_hash> hashes);
/*
* Converts list of blocks to list of hashes
*/
std::vector<nano::block_hash> blocks_to_hashes (std::vector<std::shared_ptr<nano::block>> blocks);
/*
* Creates a new fake channel associated with `node`
*/
std::shared_ptr<nano::transport::channel> fake_channel (nano::node & node, nano::account node_id = { 0 });
}
}