eos-unittest
42568 ワード
eos-unittestはホワイトボックステストです.
eosプロジェクトはbuildのたびにリンクeos/unittestの下のソースファイルを自動的にコンパイルし、eos/build/unittests/unit_を生成します.test.後者はバイナリ・ソース・プログラムで、実行するとすべてのテスト・インスタンスが自動的に実行されます.unit_test --list_contentは、すべてのテストキットおよびテスト例を列挙します../unit_test -t eosio_system_tests/stake_unstake単独テストeosio_system_testsテストキットのstake_unstakeテスト例.
eos-unittestはboostライブラリのユニットテストキットを採用しています.
BOOST_AUTO_TEST_SUITE(テスター名)テスターを宣言し、定義の一番前に置くBOOST_AUTO_TEST_SUITE_END()テストキット終了BOOST_FIXTURE_TEST_CASE(試験用例名、試験類名)FIXTURE試験用例
ベース_についてtester
contract_name.abi.hpp contract_name.wast.hppにはabiとwastのデータをヘッダファイルに変換し、テスト時に適用しやすい.
ユニットテストでは、データ構造および関連メンバーの読み取りと検証を容易にするために、永続化されたデータテーブルのデータを逆シーケンス化することがよくあります.
variantについて
variant variant_object mutable_variant_object
variantは、一般的なデータ型を表すデータのシーケンス化と逆シーケンス化によく使用されます.variant_objectはオブジェクトのコンテナに似ています.mutable_variant_objectは、複雑でネスト可能なデータフォーマットを定義するためによく使用され、asテンプレートメソッドでタイプを変換し、[]メンバーにアクセスできます.
TRX&ACTIONの送信
eosプロジェクトはbuildのたびにリンクeos/unittestの下のソースファイルを自動的にコンパイルし、eos/build/unittests/unit_を生成します.test.後者はバイナリ・ソース・プログラムで、実行するとすべてのテスト・インスタンスが自動的に実行されます.unit_test --list_contentは、すべてのテストキットおよびテスト例を列挙します../unit_test -t eosio_system_tests/stake_unstake単独テストeosio_system_testsテストキットのstake_unstakeテスト例.
eos-unittestはboostライブラリのユニットテストキットを採用しています.
BOOST_AUTO_TEST_SUITE(テスター名)テスターを宣言し、定義の一番前に置くBOOST_AUTO_TEST_SUITE_END()テストキット終了BOOST_FIXTURE_TEST_CASE(試験用例名、試験類名)FIXTURE試験用例
class template_test_class : public TESTER
{
// ...
};
BOOST_AUTO_TEST_SUITE(template_test_suite)
BOOST_FIXTURE_TEST_CASE(template_test_case1, template_test_class) try {
// ...
} FC_LOG_AND_RETHROW()
BOOST_AUTO_TEST_SUITE_END()
ベース_についてtester
base_testerクラス、テストクラスのベースクラス.このクラスは、テストフレームワークの主要なインタフェースを提供します.testerクラスはbase_に継承されますtesterクラス、簡単にbase_を拡張tester(ブロックに関連する3つのメソッドが追加されました).カスタムテストクラスはtesterを継承する必要があります.
ブロック取得時間control->head_block_time().sec_since_epoch()
検証用マクロ
BOOST_REQUIRE_EQUALは、assetなどの2つの単純なオブジェクトと比較することがよくあります.REQUIRE_MATCHING_OBJECTは、2つのvariant(特にmutable_variant_ojbect)と比較することが多い.
ブロックの生成
void base_tester::produce_blocks( uint32_t n = 1, bool empty = false );
// exp
produce_blocks();
produce_blocks(3);
ユーザーの作成
vector<transaction_trace_ptr> base_tester::create_accounts(
vector<account_name> names,
bool multisig = false,
bool include_code = true
)
// exp
create_accounts({N(hello), N(world)})
配備契約
// WAVM
void set_code(
account_name name,
const char* wast,
const private_key_type* signer = nullptr
);
// WABT
void set_code(
account_name name,
const vector<uint8_t> wasm,
const private_key_type* signer = nullptr
);
void set_abi(
account_name name,
const char* abi_json,
const private_key_type* signer = nullptr
);
// exp
#include
#include
// ...
set_code( N(eosio.token), eosio_token_wast );
set_abi( N(eosio.token), eosio_token_abi );
eosプロジェクトが持参したeosio_build.shは、2つの特殊なhppファイルを追加的にコンパイルして生成します.
control->head_block_time().sec_since_epoch()
void base_tester::produce_blocks( uint32_t n = 1, bool empty = false );
// exp
produce_blocks();
produce_blocks(3);
vector<transaction_trace_ptr> base_tester::create_accounts(
vector<account_name> names,
bool multisig = false,
bool include_code = true
)
// exp
create_accounts({N(hello), N(world)})
// WAVM
void set_code(
account_name name,
const char* wast,
const private_key_type* signer = nullptr
);
// WABT
void set_code(
account_name name,
const vector<uint8_t> wasm,
const private_key_type* signer = nullptr
);
void set_abi(
account_name name,
const char* abi_json,
const private_key_type* signer = nullptr
);
// exp
#include
#include
// ...
set_code( N(eosio.token), eosio_token_wast );
set_abi( N(eosio.token), eosio_token_abi );
シーケンス化データの設定
ユニットテストでは、データ構造および関連メンバーの読み取りと検証を容易にするために、永続化されたデータテーブルのデータを逆シーケンス化することがよくあります.
//
// token_abi_ser.set_abi
const auto& accnt = control->db().get<account_object,by_name>( N(eosio.token) );
abi_def abi;
BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true);
token_abi_ser.set_abi(abi, abi_serializer_max_time);
// ,
// token_abi_ser.binary_to_variant
{
// ...
vector<char> data = get_row_by_account( N(eosio.token), symbol_code, N(stat), symbol_code );
return data.empty() ? fc::variant() : token_abi_ser.binary_to_variant( "currency_stats", data, abi_serializer_max_time );
}
variantについて
すべてのインテリジェント契約で定義されたデータ構造を共通のフォーマットで識別する3つの一般的なタイプがあります.
variantは、一般的なデータ型を表すデータのシーケンス化と逆シーケンス化によく使用されます.variant_objectはオブジェクトのコンテナに似ています.mutable_variant_objectは、複雑でネスト可能なデータフォーマットを定義するためによく使用され、asテンプレートメソッドでタイプを変換し、[]メンバーにアクセスできます.
TRX&ACTIONの送信
eos-unittestでは、ACTIONを作成する方法がN種類あります.class base_tester {
public:
/*
push_transaction
packed_transaction,zlib ,
signed_transaction,
*/
transaction_trace_ptr push_transaction(
packed_transaction& trx,
fc::time_point deadline = fc::time_point::maximum(),
uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US
);
transaction_trace_ptr push_transaction(
signed_transaction& trx,
fc::time_point deadline = fc::time_point::maximum(),
uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US
);
/*
push_action , transaction:
1. action
2. action 、
3. action 、 active
4. action 、
*/
action_result push_action(action&& cert_act, uint64_t authorizer);
transaction_trace_ptr push_action(
const account_name& code,
const action_name& acttype,
const account_name& actor,
const variant_object& data,
uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
uint32_t delay_sec = 0
);
transaction_trace_ptr push_action(
const account_name& code,
const action_name& acttype,
const vector<account_name>& actors,
const variant_object& data,
uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
uint32_t delay_sec = 0
);
transaction_trace_ptr push_action(
const account_name& code,
const action_name& acttype,
const vector<permission_level>& auths,
const variant_object& data,
uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
uint32_t delay_sec = 0
);
};
ここで、push_を1つ除いてActionはactionを返しますresult、その他はtransactionを返します.trace_ptrは、以下のように具体的に定義される.見覚えがあるのではないでしょうか.transaction_trace_ptrは我々がHTTPインタフェースで得たJSONデータ結果フィールドと一致する.action_resultはbase_を切り取るaction_trace::consoleの文字列結果.class base_tester
{
typedef string action_result;
static action_result success() { return string(); }
static action_result error( const string& msg ) { return msg; }
static action_result wasm_assert_msg( const string& msg ) { return "assertion failure with message: " + msg; }
static action_result wasm_assert_code( uint64_t error_code ) { return "assertion failure with error code: " + std::to_string(error_code); }
};
struct base_action_trace
{
base_action_trace( const action_receipt& r ):receipt(r){}
base_action_trace(){}
action_receipt receipt;
action act;
bool context_free = false;
fc::microseconds elapsed;
uint64_t cpu_usage = 0;
string console;
uint64_t total_cpu_usage = 0; /// total of inline_traces[x].cpu_usage + cpu_usage
transaction_id_type trx_id; ///< the transaction that generated this action
uint32_t block_num = 0;
block_timestamp_type block_time;
fc::optional<block_id_type> producer_block_id;
flat_set<account_delta> account_ram_deltas;
};
struct action_trace : public base_action_trace {
using base_action_trace::base_action_trace;
vector<action_trace> inline_traces;
};
struct transaction_trace;
using transaction_trace_ptr = std::shared_ptr<transaction_trace>;
以下によく使われる例をいくつか示します.action_result hello(
uint64_t param1,
uint64_t param2,
uint64_t param3
)
{
auto data = fc::variant_object(
fc::mutable_variant_object()
("param1", param1)
("param2", param2)
("param3", param3)
);
action act;
act.account = N(code);
act.name = N(hello);
act.data = abi_serializer.variant_to_binary(
abi_serializer.get_action_type(N(hello)),
data,
abi_serializer_max_time
);
return base_tester::push_action(std::move(act), uint64_t(signer));
}
BOOST_REQUIRE_EQUAL(success(), hello(param1, param2, param3));
BOOST_REQUIRE_EQUAL(error("missing authority of alice"), hello(param1, param2, param3));
transaction_trace_ptr hello(
uint64_t param1,
uint64_t param2,
uint64_t param3
)
{
// push_action
return base_tester::push_action(
N(code), N(hello),
vector<account_name>{ {N(actor_account1)}, {N(actor_account2)} },
fc::mutable_variant_object()
("param1", param1)
("param2", param2)
("param3", param3)
);
// PS:mutable_variant_object C++ :
// 1. mutable_variant_object , “,”
// 2. , , ,
// 3. mutable_variant_object mutable_variant_object
}
BOOST_REQUIRE_EQUAL(success(), hello(param1, param2, param3)->action_traces[0].console);
永続化データの取得
書き込みテストの過程で、前置条件と後置条件を判定するには、常に持続化データを判断する必要があり、eos-unittestフレームワークのbase_testerテストクラスには、関連するインタフェースも用意されています.vector<char> base_tester::get_row_by_account(
uint64_t code,
uint64_t scope,
uint64_t table,
const account_name& act
) const;
次に例を示しますfc::variant get_tab1(uint64_t primarykey)
{
vector<char> data = get_row_by_account(N(code), N(scope), N(tab1), primarykey);
// abi_serializer , 。
return data.empty() ? fc::variant() : abi_serializer::binary_to_variant("tab1_struct", data, abi_serializer_max_time);
}
学校で永続化データを検証する過程で、最もよく使われる尤数は口座残高を取得し、以下にいくつかの方法を示します.// eosio.system_tester.cpp , get_row_by_account
asset get_balance(const account_name& act) {
//return get_currency_balance( config::system_account_name, symbol(CORE_SYMBOL), act );
//temporary code. current get_currency_balancy uses table name N(accounts) from currency.h
//generic_currency table name is N(account).
const auto& db = control->db();
const auto* tbl = db.find<table_id_object, by_code_scope_table>(boost::make_tuple(N(eosio.token), act, N(accounts)));
share_type result = 0;
// the balance is implied to be 0 if either the table or row does not exist
if (tbl) {
const auto *obj = db.find<key_value_object, by_scope_primary>(boost::make_tuple(tbl->id, symbol(CORE_SYMBOL).to_symbol_code()));
if (obj) {
// balance is the first field in the serialization
fc::datastream<const char *> ds(obj->value.data(), obj->value.size());
fc::raw::unpack(ds, result);
}
}
return asset( result, symbol(CORE_SYMBOL) );
}
// base_tester
asset base_tester::get_currency_balance(
const account_name& contract,
const symbol& asset_symbol,
const account_name& account
) const;
class base_tester {
public:
/*
push_transaction
packed_transaction,zlib ,
signed_transaction,
*/
transaction_trace_ptr push_transaction(
packed_transaction& trx,
fc::time_point deadline = fc::time_point::maximum(),
uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US
);
transaction_trace_ptr push_transaction(
signed_transaction& trx,
fc::time_point deadline = fc::time_point::maximum(),
uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US
);
/*
push_action , transaction:
1. action
2. action 、
3. action 、 active
4. action 、
*/
action_result push_action(action&& cert_act, uint64_t authorizer);
transaction_trace_ptr push_action(
const account_name& code,
const action_name& acttype,
const account_name& actor,
const variant_object& data,
uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
uint32_t delay_sec = 0
);
transaction_trace_ptr push_action(
const account_name& code,
const action_name& acttype,
const vector<account_name>& actors,
const variant_object& data,
uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
uint32_t delay_sec = 0
);
transaction_trace_ptr push_action(
const account_name& code,
const action_name& acttype,
const vector<permission_level>& auths,
const variant_object& data,
uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
uint32_t delay_sec = 0
);
};
class base_tester
{
typedef string action_result;
static action_result success() { return string(); }
static action_result error( const string& msg ) { return msg; }
static action_result wasm_assert_msg( const string& msg ) { return "assertion failure with message: " + msg; }
static action_result wasm_assert_code( uint64_t error_code ) { return "assertion failure with error code: " + std::to_string(error_code); }
};
struct base_action_trace
{
base_action_trace( const action_receipt& r ):receipt(r){}
base_action_trace(){}
action_receipt receipt;
action act;
bool context_free = false;
fc::microseconds elapsed;
uint64_t cpu_usage = 0;
string console;
uint64_t total_cpu_usage = 0; /// total of inline_traces[x].cpu_usage + cpu_usage
transaction_id_type trx_id; ///< the transaction that generated this action
uint32_t block_num = 0;
block_timestamp_type block_time;
fc::optional<block_id_type> producer_block_id;
flat_set<account_delta> account_ram_deltas;
};
struct action_trace : public base_action_trace {
using base_action_trace::base_action_trace;
vector<action_trace> inline_traces;
};
struct transaction_trace;
using transaction_trace_ptr = std::shared_ptr<transaction_trace>;
action_result hello(
uint64_t param1,
uint64_t param2,
uint64_t param3
)
{
auto data = fc::variant_object(
fc::mutable_variant_object()
("param1", param1)
("param2", param2)
("param3", param3)
);
action act;
act.account = N(code);
act.name = N(hello);
act.data = abi_serializer.variant_to_binary(
abi_serializer.get_action_type(N(hello)),
data,
abi_serializer_max_time
);
return base_tester::push_action(std::move(act), uint64_t(signer));
}
BOOST_REQUIRE_EQUAL(success(), hello(param1, param2, param3));
BOOST_REQUIRE_EQUAL(error("missing authority of alice"), hello(param1, param2, param3));
transaction_trace_ptr hello(
uint64_t param1,
uint64_t param2,
uint64_t param3
)
{
// push_action
return base_tester::push_action(
N(code), N(hello),
vector<account_name>{ {N(actor_account1)}, {N(actor_account2)} },
fc::mutable_variant_object()
("param1", param1)
("param2", param2)
("param3", param3)
);
// PS:mutable_variant_object C++ :
// 1. mutable_variant_object , “,”
// 2. , , ,
// 3. mutable_variant_object mutable_variant_object
}
BOOST_REQUIRE_EQUAL(success(), hello(param1, param2, param3)->action_traces[0].console);
書き込みテストの過程で、前置条件と後置条件を判定するには、常に持続化データを判断する必要があり、eos-unittestフレームワークのbase_testerテストクラスには、関連するインタフェースも用意されています.
vector<char> base_tester::get_row_by_account(
uint64_t code,
uint64_t scope,
uint64_t table,
const account_name& act
) const;
次に例を示します
fc::variant get_tab1(uint64_t primarykey)
{
vector<char> data = get_row_by_account(N(code), N(scope), N(tab1), primarykey);
// abi_serializer , 。
return data.empty() ? fc::variant() : abi_serializer::binary_to_variant("tab1_struct", data, abi_serializer_max_time);
}
学校で永続化データを検証する過程で、最もよく使われる尤数は口座残高を取得し、以下にいくつかの方法を示します.
// eosio.system_tester.cpp , get_row_by_account
asset get_balance(const account_name& act) {
//return get_currency_balance( config::system_account_name, symbol(CORE_SYMBOL), act );
//temporary code. current get_currency_balancy uses table name N(accounts) from currency.h
//generic_currency table name is N(account).
const auto& db = control->db();
const auto* tbl = db.find<table_id_object, by_code_scope_table>(boost::make_tuple(N(eosio.token), act, N(accounts)));
share_type result = 0;
// the balance is implied to be 0 if either the table or row does not exist
if (tbl) {
const auto *obj = db.find<key_value_object, by_scope_primary>(boost::make_tuple(tbl->id, symbol(CORE_SYMBOL).to_symbol_code()));
if (obj) {
// balance is the first field in the serialization
fc::datastream<const char *> ds(obj->value.data(), obj->value.size());
fc::raw::unpack(ds, result);
}
}
return asset( result, symbol(CORE_SYMBOL) );
}
// base_tester
asset base_tester::get_currency_balance(
const account_name& contract,
const symbol& asset_symbol,
const account_name& account
) const;