提交 7215da1f 编写于 作者: N Nathan Hourt

More work on testing

- Cleaned out some cruft
- Added some new macros to remove boilerplate
- Added some more test cases
上级 4faabfe6
......@@ -38,151 +38,10 @@
#include "database_fixture.hpp"
using namespace eos::chain::test;
uint32_t EOS_TESTING_GENESIS_TIMESTAMP = 1431700000;
namespace eos { namespace chain {
using std::cout;
using std::cerr;
database_fixture::database_fixture()
: app(), db( *app.chain_database() )
{
try {
int argc = boost::unit_test::framework::master_test_suite().argc;
char** argv = boost::unit_test::framework::master_test_suite().argv;
for( int i=1; i<argc; i++ )
{
const std::string arg = argv[i];
if( arg == "--record-assert-trip" )
fc::enable_record_assert_trip = true;
if( arg == "--show-test-names" )
std::cout << "running test " << boost::unit_test::framework::current_test_case().p_name << std::endl;
}
init_account_pub_key = init_account_priv_key.get_public_key();
boost::program_options::variables_map options;
genesis_state.initial_timestamp = fc::time_point_sec( EOS_TESTING_GENESIS_TIMESTAMP );
genesis_state.initial_producer_count = 10;
for( int i = 0; i < genesis_state.initial_producer_count; ++i )
{
auto name = "init"+fc::to_string(i);
genesis_state.initial_accounts.emplace_back(name,
init_account_priv_key.get_public_key(),
init_account_priv_key.get_public_key());
genesis_state.initial_producers.push_back({name, init_account_priv_key.get_public_key()});
}
open_database();
generate_block();
set_expiration( db, trx );
} catch ( const fc::exception& e )
{
edump( (e.to_detail_string()) );
throw;
}
return;
}
database_fixture::~database_fixture()
{ try {
// If we're unwinding due to an exception, don't do any more checks.
// This way, boost test's last checkpoint tells us approximately where the error was.
if( !std::uncaught_exception() )
{
BOOST_CHECK( db.get_node_properties().skip_flags == database::skip_nothing );
}
if( data_dir )
db.close();
return;
} FC_CAPTURE_AND_RETHROW() }
fc::ecc::private_key database_fixture::generate_private_key(string seed)
{
static const fc::ecc::private_key committee = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key")));
if( seed == "null_key" )
return committee;
return fc::ecc::private_key::regenerate(fc::sha256::hash(seed));
}
string database_fixture::generate_anon_acct_name()
{
// names of the form "anon-acct-x123" ; the "x" is necessary
// to workaround issue #46
return "anon-acct-x" + std::to_string( anon_acct_count++ );
}
void database_fixture::open_database()
{
if( !data_dir ) {
data_dir = fc::temp_directory( eos::utilities::temp_directory_path() );
db.open(data_dir->path(), TEST_DB_SIZE, [this]{return genesis_state;});
}
}
signed_block database_fixture::generate_block(uint32_t skip, const fc::ecc::private_key& key, int miss_blocks)
{
skip |= database::skip_undo_history_check;
// skip == ~0 will skip checks specified in database::validation_steps
auto block = db.generate_block(db.get_slot_time(miss_blocks + 1),
db.get_scheduled_producer(miss_blocks + 1),
key, skip);
db.clear_pending();
return block;
}
void database_fixture::generate_blocks( uint32_t block_count )
{
for( uint32_t i = 0; i < block_count; ++i )
generate_block();
}
void database_fixture::generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks, uint32_t skip)
{
if( miss_intermediate_blocks )
{
generate_block(skip);
auto slots_to_miss = db.get_slot_at_time(timestamp);
if( slots_to_miss <= 1 )
return;
--slots_to_miss;
generate_block(skip, init_account_priv_key, slots_to_miss);
return;
}
while( db.head_block_time() < timestamp )
generate_block(skip);
}
namespace test {
void set_expiration( const database& db, transaction& tx )
{
const chain_parameters& params = db.get_global_properties().parameters;
tx.set_reference_block(db.head_block_id());
tx.set_expiration( db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3 ) );
return;
}
bool _push_block( database& db, const signed_block& b, uint32_t skip_flags /* = 0 */ )
{
return db.push_block( b, skip_flags);
}
signed_transaction _push_transaction( database& db, const signed_transaction& tx, uint32_t skip_flags /* = 0 */ )
{ try {
auto pt = db.push_transaction( tx, skip_flags );
return pt;
} FC_CAPTURE_AND_RETHROW((tx)) }
}
testing_fixture::testing_fixture() {
default_genesis_state.initial_timestamp = fc::time_point_sec(EOS_TESTING_GENESIS_TIMESTAMP);
default_genesis_state.initial_producer_count = EOS_DEFAULT_MIN_PRODUCER_COUNT;
......@@ -236,10 +95,20 @@ void testing_database::open() {
database::open(data_dir, TEST_DB_SIZE, [this]{return genesis_state;});
}
void testing_database::reindex() {
database::reindex(data_dir, TEST_DB_SIZE, genesis_state);
}
void testing_database::wipe(bool include_blocks) {
database::wipe(data_dir, include_blocks);
}
void testing_database::produce_blocks(uint32_t count, uint32_t blocks_to_miss) {
if (count == 0)
return;
BOOST_REQUIRE_MESSAGE(is_open(), "Producing blocks on closed db... Did you forget to open it?");
for (int i = 0; i < count; ++i) {
auto slot = blocks_to_miss + 1;
auto producer_id = get_scheduled_producer(slot);
......@@ -303,6 +172,4 @@ void testing_network::propagate_block(const signed_block& block) {
currently_propagating_block = false;
}
// eos::chain::test
} } // eos::chain
......@@ -31,6 +31,9 @@
#include <fc/smart_ref_impl.hpp>
#include <fc/signals.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/facilities/overload.hpp>
#include <iostream>
using namespace eos::chain;
......@@ -43,29 +46,6 @@ FC_DECLARE_EXCEPTION(testing_exception, 6000000, "test framework exception")
#define TEST_DB_SIZE (1024*1024*10)
#define PUSH_TX \
eos::chain::test::_push_transaction
#define PUSH_BLOCK \
eos::chain::test::_push_block
// See below
#define REQUIRE_OP_VALIDATION_SUCCESS( op, field, value ) \
{ \
const auto temp = op.field; \
op.field = value; \
op.validate(); \
op.field = temp; \
}
#define REQUIRE_OP_EVALUATION_SUCCESS( op, field, value ) \
{ \
const auto temp = op.field; \
op.field = value; \
trx.operations.back() = op; \
op.field = temp; \
db.push_transaction( trx, ~0 ); \
}
#define EOS_REQUIRE_THROW( expr, exc_type ) \
{ \
std::string req_throw_info = fc::json::to_string( \
......@@ -102,102 +82,8 @@ FC_DECLARE_EXCEPTION(testing_exception, 6000000, "test framework exception")
<< req_throw_info << std::endl; \
}
#define REQUIRE_OP_VALIDATION_FAILURE_2( op, field, value, exc_type ) \
{ \
const auto temp = op.field; \
op.field = value; \
EOS_REQUIRE_THROW( op.validate(), exc_type ); \
op.field = temp; \
}
#define REQUIRE_OP_VALIDATION_FAILURE( op, field, value ) \
REQUIRE_OP_VALIDATION_FAILURE_2( op, field, value, fc::exception )
#define REQUIRE_THROW_WITH_VALUE_2(op, field, value, exc_type) \
{ \
auto bak = op.field; \
op.field = value; \
trx.operations.back() = op; \
op.field = bak; \
EOS_REQUIRE_THROW(db.push_transaction(trx, ~0), exc_type); \
}
#define REQUIRE_THROW_WITH_VALUE( op, field, value ) \
REQUIRE_THROW_WITH_VALUE_2( op, field, value, fc::exception )
///This simply resets v back to its default-constructed value. Requires v to have a working assingment operator and
/// default constructor.
#define RESET(v) v = decltype(v)()
///This allows me to build consecutive test cases. It's pretty ugly, but it works well enough for unit tests.
/// i.e. This allows a test on update_account to begin with the database at the end state of create_account.
#define INVOKE(test) ((struct test*)this)->test_method(); trx.clear()
#define PREP_ACTOR(name) \
fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name)); \
public_key_type name ## _public_key = name ## _private_key.get_public_key();
#define ACTOR(name) \
PREP_ACTOR(name) \
const auto& name = create_account(BOOST_PP_STRINGIZE(name), name ## _public_key); \
account_id_type name ## _id = name.id; (void)name ## _id;
#define GET_ACTOR(name) \
fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name)); \
const account_object& name = get_account(BOOST_PP_STRINGIZE(name)); \
account_id_type name ## _id = name.id; \
(void)name ##_id
#define ACTORS_IMPL(r, data, elem) ACTOR(elem)
#define ACTORS(names) BOOST_PP_SEQ_FOR_EACH(ACTORS_IMPL, ~, names)
namespace eos { namespace chain {
struct database_fixture {
// the reason we use an app is to exercise the indexes of built-in plugins
eos::app::application app;
genesis_state_type genesis_state;
eos::chain::database &db;
signed_transaction trx;
fc::ecc::private_key private_key = fc::ecc::private_key::generate();
fc::ecc::private_key init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key")) );
public_key_type init_account_pub_key;
optional<fc::temp_directory> data_dir;
bool skip_key_index_test = false;
uint32_t anon_acct_count;
database_fixture();
~database_fixture();
static fc::ecc::private_key generate_private_key(string seed);
string generate_anon_acct_name();
static void verify_asset_supplies( const database& db );
void verify_account_history_plugin_index( )const;
void open_database();
signed_block generate_block(uint32_t skip = ~0,
const fc::ecc::private_key& key = generate_private_key("null_key"),
int miss_blocks = 0);
/**
* @brief Generates block_count blocks
* @param block_count number of blocks to generate
*/
void generate_blocks(uint32_t block_count);
/**
* @brief Generates blocks until the head block time matches or exceeds timestamp
* @param timestamp target time to generate blocks until
*/
void generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks = true, uint32_t skip = ~0);
};
namespace test {
/// set a reasonable expiration time for the transaction
void set_expiration( const database& db, transaction& tx );
bool _push_block( database& db, const signed_block& b, uint32_t skip_flags = 0 );
signed_transaction _push_transaction( database& db, const signed_transaction& tx, uint32_t skip_flags = 0 );
}
/**
* @brief The testing_fixture class provides various services relevant to testing the database.
*/
......@@ -255,6 +141,16 @@ public:
* @brief Open the database using the boilerplate testing database settings
*/
void open();
/**
* @brief Reindex the database using the boilerplate testing database settings
*/
void reindex();
/**
* @brief Wipe the database using the boilerplate testing database settings
* @param include_blocks If true, the blocks will be removed as well; otherwise, only the database will be wiped and
* can then be rebuilt from the local blocks
*/
void wipe(bool include_blocks = true);
/**
* @brief Produce new blocks, adding them to the database, optionally following a gap of missed blocks
......@@ -284,6 +180,38 @@ protected:
testing_fixture& fixture;
};
/// Some helpful macros to reduce boilerplate when making testing_databases @{
#define MKDB1(name) testing_database name(*this); name.open();
#define MKDB2(name, id) testing_database name(*this, #id); name.open();
/**
* @brief Create/Open a testing_database, optionally with an ID
*
* Creates and opens a testing_database with the first argument as its name, and, if present, the second argument as
* its ID. The ID should be provided without quotes.
*
* Example:
* @code{.cpp}
* // Create testing_databases db1 and db2, with db2 having ID "id2"
* MKDB(db1)
* MKDB(db2, id2)
* @endcode
*/
#define MKDB(...) BOOST_PP_OVERLOAD(MKDB, __VA_ARGS__)(__VA_ARGS__)
#define MKDBS_MACRO(x, y, name) MKDB(name)
/**
* @brief Similar to @ref MKDB, but works with several databases at once
*
* Creates and opens several testing_databases
*
* Example:
* @code{.cpp}
* // Create testing_databases db1 and db2, with db2 having ID "id2"
* MKDBS((db1)(db2, id2))
* @endcode
*/
#define MKDBS(...) BOOST_PP_SEQ_FOR_EACH(MKDBS_MACRO, _, __VA_ARGS__)
/// @}
/**
* @brief The testing_network class provides a simplistic virtual P2P network connecting testing_databases together.
*
......@@ -321,4 +249,29 @@ protected:
bool currently_propagating_block = false;
};
/// Some helpful macros to reduce boilerplate when making a testing_network and connecting some testing_databases @{
#define MKNET1(name) testing_network name;
#define MKNET2_MACRO(x, name, db) name.connect_database(db);
#define MKNET2(name, ...) MKNET1(name) BOOST_PP_SEQ_FOR_EACH(MKNET2_MACRO, name, __VA_ARGS__)
/**
* @brief MKNET is a shorthand way to create a testing_network and connect some testing_databases to it.
*
* Example usage:
* @code{.cpp}
* // Create and open testing_databases named alice, bob, and charlie
* MKDBS((alice)(bob)(charlie))
* // Create a testing_network named net and connect alice and bob to it
* MKNET(net, (alice)(bob))
*
* // Connect charlie to net, then disconnect alice
* net.connect_database(charlie);
* net.disconnect_database(alice);
*
* // Create a testing_network named net2 with no databases connected
* MKNET(net2)
* @endcode
*/
#define MKNET(...) BOOST_PP_OVERLOAD(MKNET, __VA_ARGS__)(__VA_ARGS__)
/// @}
} }
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册