Native Tester And Compilation
As of v1.5.0 native compilation can be performed and a new set of libraries to facilitate native testing and native "scratch pad" compilation. cdt-cc
, cdt-cpp
and cdt-ld
now support building "smart contracts" and unit tests natively for quick tests to help facilitate faster development (note the default implementations of a lot of the Antelope intrinsics (aka host functions) are currently asserts that state they are unavailable, these are user definable.)
Getting Started
Once you have your smart contract written then a test source file can be written.
hello.hpp
#include <eosio/eosio.hpp>
using namespace eosio;
class [[eosio::contract]] hello : public eosio::contract {
public:
using contract::contract;
[[eosio::action]] void hi( name user );
// accessor for external contracts to easily send inline actions to your contract
using hi_action = action_wrapper<"hi"_n, &hello::hi>;
};
and then a quick test
hello_test.cpp
#include <eosio/eosio.hpp>
#include <eosio/tester.hpp>
#include <hello.hpp>
using namespace eosio;
using namespace eosio::native;
EOSIO_TEST_BEGIN(hello_test)
// These can be redefined by the user to suit there needs per unit test
// the idea is that in a future release there will be a base library that
// initializes these to "useable" default implementations and probably
// helpers to more easily define read_action_data and action_data_size intrinsics
// like these"
intrinsics::set_intrinsic<intrinsics::read_action_data>(
[](void* m, uint32_t len) {
check(len <= sizeof(eosio::name), "failed from read_action_data");
*((eosio::name*)m) = "hello"_n;
return len;
});
intrinsics::set_intrinsic<intrinsics::action_data_size>(
[]() {
return (uint32_t)sizeof(eosio::name);
});
intrinsics::set_intrinsic<intrinsics::require_auth>(
[](capi_name nm) {
});
// "Name : hello" should be in the print buffer
CHECK_PRINT("Name : hello",
[]() {
apply("test"_n.value, "test"_n.value, "hi"_n.value);
});
// should not assert
apply("test"_n.value, "test"_n.value, "check"_n.value);
name nm = "null"_n;
intrinsics::set_intrinsic<intrinsics::read_action_data>(
[&](void* m, uint32_t len) {
check(len <= sizeof(eosio::name), "failed from read_action_data");
*((eosio::name*)m) = nm;
return len;
});
REQUIRE_ASSERT( "check name not equal to `hello`",
[]() {
// should assert
apply("test"_n.value, "test"_n.value, "check"_n.value);
});
EOSIO_TEST_END
// boilerplate main, this will be generated in a future release
int main(int argc, char** argv) {
silence_output(true);
EOSIO_TEST(hello_test);
return has_failed();
}
Every intrinsic
that is defined for eosio (prints, require_auth, etc.) is re-definable given the intrinsics::set_intrinsics<intrinsics::the_intrinsic_name>()
functions. These take a lambda whose arguments and return type should match that of the intrinsic you are trying to define. This gives the contract writer the flexibility to modify behavior to suit the unit test being written. A sister function intrinsics::get_intrinsics<intrinsics::the_intrinsic_name>()
will return the function object that currently defines the behavior for said intrinsic. This pattern can be used to mock functionality and allow for easier testing of smart contracts. For more information see, either the tests directory or hello_test.cpp for working examples.
Compiling Native Code
- Raw
cdt-cpp
to compile the test or program the only addition needed to the command line is to add the flag-fnative
this will then generate native code instead ofwasm
code. - Via CMake
add_native_library
andadd_native_executable
CMake macros have been added (these are a drop in replacement for add_library and add_executable).
CDT Native Tester API
- CHECK_ASSERT(...) : This macro will check whether a particular assert has occured and flag the tests as failed but allow the rest of the tests to run.
- This is called either by
CHECK_ASSERT("<assert message>", [](<args>){ whatever_function(<args>); })
CHECK_ASSERT([](std::string msg){ user defined comparison function }, [](<args>){ whatever_function(<args>); })
- This is called either by
- CHECK_PRINT(...) : This macro will check whether the print buffer holds the string that is expected and flag the tests as failed but allow the rest of the test to run.
- This is called either by
CHECK_PRINT("<print message>", [](<args>){ whatever_function(<args>); })
CHECK_PRINT([](std::string print_buffer){ user defined comparison function }, [](<args>){ whatever_function(<args>); })
- This is called either by
- CHECK_EQUAL(X, Y) : This macro will check whether two inputs equal eachother and fail the test but allow the rest of the test to continue.
- REQUIRE_ASSERT(...) : This macro will check whether a particular assert has occured and flag the tests as failed and halt the test on failure.
- This is called either by
REQUIRE_ASSERT("<assert message>", [](<args>){ whatever_function(<args>); })
REQUIRE_ASSERT([](std::string msg){ user defined comparison function }, [](<args>){ whatever_function(<args>); })
- This is called either by
- REQUIRE_PRINT(...) : This macro will check whether the print buffer holds the string that is expected and flag the tests as failed and halt the test on failure.
- This is called either by
REQUIRE_PRINT("<print message>", [](<args>){ whatever_function(<args>); })
REQUIRE_PRINT([](std::string print_buffer){ user defined comparison function }, [](<args>){ whatever_function(<args>); })
- This is called either by
- REQUIRE_EQUAL(X, Y) : This macro will check whether two inputs
X
andY
equal eachother and fail the test and halt the test on failure. - EOSIO_TEST_BEGIN(X) : This macro defines the beginning of a unit test and assigns
X
as the symbolic name of that test. - EOSIO_TEST_END : This macro defines the end of a unit test.
- EOSIO_TEST(X) : This is used to run a particular named unit test
X
in the main function.