Unverified Commit 552d38bb authored by Michael Müller's avatar Michael Müller Committed by GitHub
Browse files

Implement [ink::test] proc macro (#490)

* [lang] Introduce [ink::test] macro

* [core] Migrate some tests to use [ink::test] macro

* Address comments

* Apply cargo fmt

* [lang] Ensure generated code behaves as if test fn has no return value

* [examples] Migrate tests to use [ink::test] macro

* [core] Revert core using [ink::test]

* [lang] Use all available parameters

* [lang] Remove unused imports

* [lang] Remove implicit Ok(())

* [core] Apply cargo fmt

* [lang] Make it clearer where ItemFn comes from

* Apply cargo fmt
parent 626bfc27
Pipeline #108390 passed with stages
in 9 minutes and 36 seconds
......@@ -398,10 +398,14 @@ fn spread_layout_clear_works() {
#[test]
fn set_works() {
let mut vec = vec_from_slice(&[b'a', b'b', b'c', b'd']);
let _ = vec.set(0, b'x').unwrap();
let expected = vec_from_slice(&[b'x', b'b', b'c', b'd']);
assert_eq!(vec, expected);
env::test::run_test::<env::DefaultEnvTypes, _>(|_| {
let mut vec = vec_from_slice(&[b'a', b'b', b'c', b'd']);
let _ = vec.set(0, b'x').unwrap();
let expected = vec_from_slice(&[b'x', b'b', b'c', b'd']);
assert_eq!(vec, expected);
Ok(())
})
.unwrap()
}
#[test]
......
......@@ -182,18 +182,7 @@ mod dns {
mod tests {
use super::*;
use ink_core::env;
/// Executes the given test through the off-chain environment.
fn run_test<F>(test_fn: F)
where
F: FnOnce(),
{
env::test::run_test::<env::DefaultEnvTypes, _>(|_| {
test_fn();
Ok(())
})
.unwrap()
}
use ink_lang as ink;
const DEFAULT_CALLEE_HASH: [u8; 32] = [0x07; 32];
const DEFAULT_ENDOWMENT: Balance = 1_000_000;
......@@ -214,70 +203,64 @@ mod dns {
)
}
#[test]
#[ink::test]
fn register_works() {
run_test(|| {
let default_accounts = default_accounts();
let name = Hash::from([0x99; 32]);
let default_accounts = default_accounts();
let name = Hash::from([0x99; 32]);
set_next_caller(default_accounts.alice);
let mut contract = DomainNameService::new();
set_next_caller(default_accounts.alice);
let mut contract = DomainNameService::new();
assert_eq!(contract.register(name), Ok(()));
assert_eq!(contract.register(name), Err(Error::NameAlreadyExists));
})
assert_eq!(contract.register(name), Ok(()));
assert_eq!(contract.register(name), Err(Error::NameAlreadyExists));
}
#[test]
#[ink::test]
fn set_address_works() {
run_test(|| {
let accounts = default_accounts();
let name = Hash::from([0x99; 32]);
set_next_caller(accounts.alice);
let mut contract = DomainNameService::new();
assert_eq!(contract.register(name), Ok(()));
// Caller is not owner, `set_address` should fail.
set_next_caller(accounts.bob);
assert_eq!(
contract.set_address(name, accounts.bob),
Err(Error::CallerIsNotOwner)
);
// caller is owner, set_address will be successful
set_next_caller(accounts.alice);
assert_eq!(contract.set_address(name, accounts.bob), Ok(()));
assert_eq!(contract.get_address(name), accounts.bob);
})
let accounts = default_accounts();
let name = Hash::from([0x99; 32]);
set_next_caller(accounts.alice);
let mut contract = DomainNameService::new();
assert_eq!(contract.register(name), Ok(()));
// Caller is not owner, `set_address` should fail.
set_next_caller(accounts.bob);
assert_eq!(
contract.set_address(name, accounts.bob),
Err(Error::CallerIsNotOwner)
);
// caller is owner, set_address will be successful
set_next_caller(accounts.alice);
assert_eq!(contract.set_address(name, accounts.bob), Ok(()));
assert_eq!(contract.get_address(name), accounts.bob);
}
#[test]
#[ink::test]
fn transfer_works() {
run_test(|| {
let accounts = default_accounts();
let name = Hash::from([0x99; 32]);
let accounts = default_accounts();
let name = Hash::from([0x99; 32]);
set_next_caller(accounts.alice);
set_next_caller(accounts.alice);
let mut contract = DomainNameService::new();
assert_eq!(contract.register(name), Ok(()));
let mut contract = DomainNameService::new();
assert_eq!(contract.register(name), Ok(()));
// Test transfer of owner.
assert_eq!(contract.transfer(name, accounts.bob), Ok(()));
// Test transfer of owner.
assert_eq!(contract.transfer(name, accounts.bob), Ok(()));
// Owner is bob, alice `set_address` should fail.
assert_eq!(
contract.set_address(name, accounts.bob),
Err(Error::CallerIsNotOwner)
);
// Owner is bob, alice `set_address` should fail.
assert_eq!(
contract.set_address(name, accounts.bob),
Err(Error::CallerIsNotOwner)
);
set_next_caller(accounts.bob);
// Now owner is bob, `set_address` should be successful.
assert_eq!(contract.set_address(name, accounts.bob), Ok(()));
assert_eq!(contract.get_address(name), accounts.bob);
})
set_next_caller(accounts.bob);
// Now owner is bob, `set_address` should be successful.
assert_eq!(contract.set_address(name, accounts.bob), Ok(()));
assert_eq!(contract.get_address(name), accounts.bob);
}
}
}
......@@ -159,18 +159,7 @@ mod erc20 {
/// Imports all the definitions from the outer scope so we can use them here.
use super::*;
use ink_core::env;
/// Executes the given test through the off-chain environment.
fn run_test<F>(test_fn: F)
where
F: FnOnce(),
{
env::test::run_test::<env::DefaultEnvTypes, _>(|_| {
test_fn();
Ok(())
})
.unwrap()
}
use ink_lang as ink;
fn assert_transfer_event<I>(
raw_events: I,
......@@ -193,153 +182,139 @@ mod erc20 {
}
/// The default constructor does its job.
#[test]
#[ink::test]
fn new_works() {
run_test(|| {
// Constructor works.
let _erc20 = Erc20::new(100);
// Constructor works.
let _erc20 = Erc20::new(100);
// Transfer event triggered during initial construction.
let emitted_events = env::test::recorded_events().collect::<Vec<_>>();
assert_eq!(1, emitted_events.len());
// Transfer event triggered during initial construction.
let emitted_events = env::test::recorded_events().collect::<Vec<_>>();
assert_eq!(1, emitted_events.len());
assert_transfer_event(emitted_events, 0, 100)
})
assert_transfer_event(emitted_events, 0, 100)
}
/// The total supply was applied.
#[test]
#[ink::test]
fn total_supply_works() {
run_test(|| {
// Constructor works.
let erc20 = Erc20::new(100);
// Transfer event triggered during initial construction.
assert_transfer_event(env::test::recorded_events(), 0, 100);
// Get the token total supply.
assert_eq!(erc20.total_supply(), 100);
})
// Constructor works.
let erc20 = Erc20::new(100);
// Transfer event triggered during initial construction.
assert_transfer_event(env::test::recorded_events(), 0, 100);
// Get the token total supply.
assert_eq!(erc20.total_supply(), 100);
}
/// Get the actual balance of an account.
#[test]
#[ink::test]
fn balance_of_works() {
run_test(|| {
// Constructor works
let erc20 = Erc20::new(100);
// Transfer event triggered during initial construction
assert_transfer_event(env::test::recorded_events(), 0, 100);
let accounts = env::test::default_accounts::<env::DefaultEnvTypes>()
.expect("Cannot get accounts");
// Alice owns all the tokens on deployment
assert_eq!(erc20.balance_of(accounts.alice), 100);
// Bob does not owns tokens
assert_eq!(erc20.balance_of(accounts.bob), 0);
})
// Constructor works
let erc20 = Erc20::new(100);
// Transfer event triggered during initial construction
assert_transfer_event(env::test::recorded_events(), 0, 100);
let accounts = env::test::default_accounts::<env::DefaultEnvTypes>()
.expect("Cannot get accounts");
// Alice owns all the tokens on deployment
assert_eq!(erc20.balance_of(accounts.alice), 100);
// Bob does not owns tokens
assert_eq!(erc20.balance_of(accounts.bob), 0);
}
#[test]
#[ink::test]
fn transfer_works() {
run_test(|| {
// Constructor works.
let mut erc20 = Erc20::new(100);
// Transfer event triggered during initial construction.
assert_transfer_event(env::test::recorded_events(), 0, 100);
let accounts = env::test::default_accounts::<env::DefaultEnvTypes>()
.expect("Cannot get accounts");
assert_eq!(erc20.balance_of(accounts.bob), 0);
// Alice transfers 10 tokens to Bob.
assert_eq!(erc20.transfer(accounts.bob, 10), true);
// The second Transfer event takes place.
assert_transfer_event(env::test::recorded_events(), 1, 10);
// Bob owns 10 tokens.
assert_eq!(erc20.balance_of(accounts.bob), 10);
})
// Constructor works.
let mut erc20 = Erc20::new(100);
// Transfer event triggered during initial construction.
assert_transfer_event(env::test::recorded_events(), 0, 100);
let accounts = env::test::default_accounts::<env::DefaultEnvTypes>()
.expect("Cannot get accounts");
assert_eq!(erc20.balance_of(accounts.bob), 0);
// Alice transfers 10 tokens to Bob.
assert_eq!(erc20.transfer(accounts.bob, 10), true);
// The second Transfer event takes place.
assert_transfer_event(env::test::recorded_events(), 1, 10);
// Bob owns 10 tokens.
assert_eq!(erc20.balance_of(accounts.bob), 10);
}
#[test]
#[ink::test]
fn invalid_transfer_should_fail() {
run_test(|| {
// Constructor works.
let mut erc20 = Erc20::new(100);
// Transfer event triggered during initial construction.
assert_transfer_event(env::test::recorded_events(), 0, 100);
let accounts = env::test::default_accounts::<env::DefaultEnvTypes>()
.expect("Cannot get accounts");
assert_eq!(erc20.balance_of(accounts.bob), 0);
// Get contract address.
let callee =
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call
let mut data =
env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Bob as caller
assert_eq!(
env::test::push_execution_context::<env::DefaultEnvTypes>(
accounts.bob,
callee,
1000000,
1000000,
data
),
()
);
// Bob fails to transfers 10 tokens to Eve.
assert_eq!(erc20.transfer(accounts.eve, 10), false);
// Alice owns all the tokens.
assert_eq!(erc20.balance_of(accounts.alice), 100);
assert_eq!(erc20.balance_of(accounts.bob), 0);
assert_eq!(erc20.balance_of(accounts.eve), 0);
})
// Constructor works.
let mut erc20 = Erc20::new(100);
// Transfer event triggered during initial construction.
assert_transfer_event(env::test::recorded_events(), 0, 100);
let accounts = env::test::default_accounts::<env::DefaultEnvTypes>()
.expect("Cannot get accounts");
assert_eq!(erc20.balance_of(accounts.bob), 0);
// Get contract address.
let callee =
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call
let mut data = env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Bob as caller
assert_eq!(
env::test::push_execution_context::<env::DefaultEnvTypes>(
accounts.bob,
callee,
1000000,
1000000,
data
),
()
);
// Bob fails to transfers 10 tokens to Eve.
assert_eq!(erc20.transfer(accounts.eve, 10), false);
// Alice owns all the tokens.
assert_eq!(erc20.balance_of(accounts.alice), 100);
assert_eq!(erc20.balance_of(accounts.bob), 0);
assert_eq!(erc20.balance_of(accounts.eve), 0);
}
#[test]
#[ink::test]
fn transfer_from_works() {
run_test(|| {
// Constructor works.
let mut erc20 = Erc20::new(100);
// Transfer event triggered during initial construction.
assert_transfer_event(env::test::recorded_events(), 0, 100);
let accounts = env::test::default_accounts::<env::DefaultEnvTypes>()
.expect("Cannot get accounts");
// Bob fails to transfer tokens owned by Alice.
assert_eq!(erc20.transfer_from(accounts.alice, accounts.eve, 10), false);
// Alice approves Bob for token transfers on her behalf.
assert_eq!(erc20.approve(accounts.bob, 10), true);
// The approve event takes place.
assert_eq!(env::test::recorded_events().count(), 2);
// Get contract address.
let callee =
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call.
let mut data =
env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Bob as caller.
assert_eq!(
env::test::push_execution_context::<env::DefaultEnvTypes>(
accounts.bob,
callee,
1000000,
1000000,
data
),
()
);
// Bob transfers tokens from Alice to Eve.
assert_eq!(erc20.transfer_from(accounts.alice, accounts.eve, 10), true);
// The third event takes place.
assert_transfer_event(env::test::recorded_events(), 2, 10);
// Eve owns tokens.
assert_eq!(erc20.balance_of(accounts.eve), 10);
})
// Constructor works.
let mut erc20 = Erc20::new(100);
// Transfer event triggered during initial construction.
assert_transfer_event(env::test::recorded_events(), 0, 100);
let accounts = env::test::default_accounts::<env::DefaultEnvTypes>()
.expect("Cannot get accounts");
// Bob fails to transfer tokens owned by Alice.
assert_eq!(erc20.transfer_from(accounts.alice, accounts.eve, 10), false);
// Alice approves Bob for token transfers on her behalf.
assert_eq!(erc20.approve(accounts.bob, 10), true);
// The approve event takes place.
assert_eq!(env::test::recorded_events().count(), 2);
// Get contract address.
let callee =
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call.
let mut data = env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Bob as caller.
assert_eq!(
env::test::push_execution_context::<env::DefaultEnvTypes>(
accounts.bob,
callee,
1000000,
1000000,
data
),
()
);
// Bob transfers tokens from Alice to Eve.
assert_eq!(erc20.transfer_from(accounts.alice, accounts.eve, 10), true);
// The third event takes place.
assert_transfer_event(env::test::recorded_events(), 2, 10);
// Eve owns tokens.
assert_eq!(erc20.balance_of(accounts.eve), 10);
}
}
}
This diff is collapsed.
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use derive_more::From;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
pub use crate::codegen::{
GenerateCode,
GenerateCodeUsing,
};
/// Generates code for the `[ink::test]` macro.
#[derive(From)]
pub struct InkTest<'a> {
/// The test function to generate code for.
test: &'a crate::ir::InkTest,
}
impl GenerateCode for InkTest<'_> {
/// Generates the code for `#[ink:test]`.
fn generate_code(&self) -> TokenStream2 {
let item_fn = &self.test.item_fn;
let attrs = &item_fn.attrs;
let sig = &item_fn.sig;
let fn_name = &sig.ident;
let fn_return_type = &sig.output;
let fn_block = &item_fn.block;
let vis = &item_fn.vis;
let fn_args = &sig.inputs;
let expect_msg = format!(
"{}: the off-chain testing environment returned an error",
stringify!(#fn_name)
);
match fn_return_type {
syn::ReturnType::Default => {
quote! {
#( #attrs )*
#[test]
#vis fn #fn_name( #fn_args ) {
env::test::run_test::<env::DefaultEnvTypes, _>(|_| {
{
let _: () = {
#fn_block
};
Ok(())
}
})
.expect(#expect_msg);
}
}
}
syn::ReturnType::Type(rarrow, ret_type) => {
quote! {
#( #attrs )*
#[test]
#vis fn #fn_name( #fn_args ) #rarrow #ret_type {
env::test::run_test::<env::DefaultEnvTypes, _>(|_| {
#fn_block
})
}
}
}
}
}
}
impl GenerateCode for crate::ir::InkTest {
fn generate_code(&self) -> TokenStream2 {
InkTest::from(self).generate_code()
}
}
......@@ -46,6 +46,7 @@ mod cross_calling;
mod dispatch;
mod env_types;
mod events;
mod ink_test;
mod metadata;
mod storage;
......
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{
codegen::GenerateCode as _,
ir,
lint,
};
use core::convert::TryFrom;
use ink_lang_ir::format_err_spanned;
use proc_macro2::TokenStream as TokenStream2;
use syn::Result;
pub fn generate(input: TokenStream2) -> TokenStream2 {
match generate_or_err(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
}
pub fn generate_or_err(input: TokenStream2) -> Result<TokenStream2> {
lint::idents_respect_pred(
input.clone(),
move |ident| !ident.to_string().starts_with("__ink"),
move |ident| {
format_err_spanned!(
ident,
"identifiers starting with `__ink` are forbidden in ink!"
)
},
)?;
let rust_fn = syn::parse2::<syn::ItemFn>(input)?;
let ink_ir = ir::InkTest::try_from(rust_fn)?;
Ok(ink_ir.generate_code())
}
......@@ -50,6 +50,12 @@ pub struct Contract {
pub non_ink_items: Vec<RustItem>,
}
/// The ink! test with all required information.
pub struct InkTest {
/// The function which was annotated.
pub item_fn: syn::ItemFn,
}
/// The meta information for a contract.
///
/// # Note
......
......@@ -56,6 +56,14 @@ impl Parse for ir::Marker {
}
}
impl TryFrom<syn::ItemFn> for ir::InkTest {
type Error = syn::Error;
fn try_from(item_fn: syn::ItemFn) -> Result<Self> {