Skip to content
This repository has been archived by the owner on Feb 11, 2025. It is now read-only.

#[drink::test] macro #70

Merged
merged 20 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 53 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"

members = [
"drink",
"drink/test-macro",
"drink-cli",
]

Expand All @@ -20,18 +21,22 @@ homepage = "https://github.com/Cardinal-Cryptography/drink"
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/Cardinal-Cryptography/drink"
version = "0.5.1"
version = "0.5.2"

[workspace.dependencies]
anyhow = { version = "1.0.71" }
clap = { version = "4.3.4" }
contract-build = { version = "3.0.1" }
contract-transcode = { version = "3.0.1" }
crossterm = { version = "0.26.0" }
darling = { version = "0.20.3" }
parity-scale-codec = { version = "3.4" }
parity-scale-codec-derive = { version = "3.4" }
proc-macro2 = { version = "1" }
quote = { version = "1" }
ratatui = { version = "0.21.0" }
scale-info = { version = "2.5.0" }
syn = { version = "2" }
thiserror = { version = "1.0.40" }
wat = { version = "1.0.71" }

Expand All @@ -51,4 +56,5 @@ sp-runtime-interface = { version = "19.0.0" }

# Local dependencies

drink = { version = "0.5.1", path = "drink" }
drink = { version = "0.5.2", path = "drink" }
drink-test-macro = { version = "0.5.2", path = "drink/test-macro" }
2 changes: 2 additions & 0 deletions drink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ scale-info = { workspace = true }
thiserror = { workspace = true }
wat = { workspace = true }

drink-test-macro = { workspace = true }

[features]
default = [
# This is required for the runtime-interface to work properly in the std env.
Expand Down
1 change: 1 addition & 0 deletions drink/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::{
sync::{Arc, Mutex},
};

pub use drink_test_macro::test;
pub use errors::Error;
use frame_support::sp_runtime::{traits::One, BuildStorage};
pub use frame_support::{
Expand Down
19 changes: 19 additions & 0 deletions drink/test-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "drink-test-macro"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true
description = "Procedural macro providing a `#[drink::test]` attribute for `drink`-based contract testing"

[lib]
proc-macro = true

[dependencies]
darling = { workspace = true }
proc-macro2 = { workspace = true }
syn = { workspace = true, features = ["full"] }
quote = { workspace = true }
30 changes: 30 additions & 0 deletions drink/test-macro/src/codegen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use proc_macro2::TokenStream;
use quote::quote;

use crate::{ir::IR, SynResult};

/// Interpret the intermediate representation and generate the final code.
pub fn generate_code(ir: IR) -> SynResult<TokenStream> {
let item_fn = ir.function();

// Unfortunately, we have to extract all these items, because we are going to change the body
// a bit.
let fn_signature = &item_fn.sig;
let fn_body = &item_fn.block;
let fn_vis = &item_fn.vis;
let fn_attrs = &item_fn.attrs;

// Prepare the code responsible for building the contracts.
let _manifests = ir.manifests();
let _debug_mode = ir.compile_in_debug_mode();
let build_contracts = quote! {};

Ok(quote! {
#[test]
#( #fn_attrs )*
#fn_vis #fn_signature {
#build_contracts
#fn_body
}
})
}
82 changes: 82 additions & 0 deletions drink/test-macro/src/ir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use darling::{ast::NestedMeta, FromMeta};
use proc_macro2::TokenStream as TokenStream2;
use syn::ItemFn;

/// The macro arguments (available configuration).
#[derive(FromMeta)]
struct MacroArgs {
/// The manifests of the contracts to be built for the test.
#[darling(default, multiple, rename = "manifest")]
manifests: Vec<String>,
/// Whether the contracts should be built in the debug mode.
#[darling(default)]
compile_in_debug_mode: bool,
}

/// Intermediate representation of the macro arguments and configuration.
pub struct IR {
/// Macro configuration.
args: MacroArgs,
/// The attributed function (testcase).
function: ItemFn,
}

impl TryFrom<(TokenStream2, TokenStream2)> for IR {
type Error = syn::Error;

fn try_from((attr, item): (TokenStream2, TokenStream2)) -> Result<Self, Self::Error> {
let args = MacroArgs::from_list(&NestedMeta::parse_meta_list(attr)?)?;
let item_fn = syn::parse2::<ItemFn>(item)?;
Ok(IR {
args,
function: item_fn,
})
}
}

impl IR {
pub fn manifests(&self) -> &[String] {
&self.args.manifests
}

pub fn compile_in_debug_mode(&self) -> bool {
self.args.compile_in_debug_mode
}

pub fn function(&self) -> &ItemFn {
&self.function
}
}

#[cfg(test)]
mod tests {
use quote::quote;

use super::*;

fn empty_function() -> TokenStream2 {
quote! { fn test() {} }
}

#[test]
fn argument_parsing() {
let attr = quote! {
manifest = "../foo/Cargo.toml",
manifest = "some path",
compile_in_debug_mode
};
let ir = IR::try_from((attr, empty_function())).expect("failed to parse macro args");

assert_eq!(ir.manifests(), &["../foo/Cargo.toml", "some path"]);
assert!(ir.compile_in_debug_mode());
}

#[test]
fn default_arguments() {
let attr = quote! {};
let ir = IR::try_from((attr, empty_function())).expect("failed to parse macro args");

assert_eq!(ir.manifests(), &Vec::<String>::new());
assert!(!ir.compile_in_debug_mode());
}
}
77 changes: 77 additions & 0 deletions drink/test-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Procedural macro providing a `#[drink::test]` attribute for `drink`-based contract testing.

#![warn(missing_docs)]

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;

use crate::{codegen::generate_code, ir::IR};

mod codegen;
mod ir;

type SynResult<T> = Result<T, syn::Error>;

/// Defines a drink!-based test. *WARNING: THE MACRO DOES NOTHING YET (MISSING CONTRACT BUILDING
/// FUNCTIONALITY). TO BE ADDED WITHIN A FEW DAYS.*
///
/// # Requirements
///
/// - You mustn't import `drink::test` in the scope, where the macro is used. In other words, you
/// should always use the macro only with a qualified path `#[drink::test]`.
///
/// # Impact
///
/// This macro will take care of building all needed contracts for the test. The building process
/// will be executed during runtime. This means that the test will take longer to execute if the
/// contracts are not already built.
///
/// # Example
///
/// ```rust, ignore
/// #[drink::test]
/// fn testcase() {
/// Session::<MinimalRuntime>::new()
/// .unwrap()
/// .deploy(bytes(), "new", NO_ARGS, vec![], None, &transcoder())
/// .unwrap();
/// }
/// ```
///
/// # Macro configuration
///
/// ## Build mode
///
/// You can specify whether the contracts should be built in debug or release mode. By default,
/// the contracts will be built in release mode. To change this, add the following to the macro
/// usage:
/// ```rust, ignore
/// #[drink::test(compile_in_debug_mode)]
/// ```
///
/// ## Manifests (contracts to be built)
///
/// You can specify, which contracts should be built for the test. *By default, `drink` will assume
/// that the current crate is the only contract to be built.* To change this, you can specify
/// all manifests that should be built for the test (also the current crate if this is the case).
/// The manifests are specified as paths relative to the current crate's root. For example:
/// ```rust, ignore
/// #[drink::test(
/// manifest = "./Cargo.toml",
/// manifest = "../second-contract/Cargo.toml",
/// manifest = "../third-contract/Cargo.toml",
/// )]
/// ```
#[proc_macro_attribute]
pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
match test_internal(attr.into(), item.into()) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}

/// Auxiliary function to enter ?-based error propagation.
fn test_internal(attr: TokenStream2, item: TokenStream2) -> SynResult<TokenStream2> {
let ir = IR::try_from((attr, item))?;
generate_code(ir)
}
2 changes: 1 addition & 1 deletion examples/cross-contract-call-tracing/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ mod tests {
}
}

#[test]
#[drink::test]
fn test() -> Result<(), Box<dyn Error>> {
let mut session = Session::<MinimalRuntime>::new()?;
session.override_debug_handle(TracingExt(Box::new(TestDebugger {})));
Expand Down
Loading