From cee4cd1f4da21e179318caf818197a0194b5f0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 19 Oct 2023 12:55:47 +0200 Subject: [PATCH 01/17] Create new crate for a procedural macro. Bump version --- Cargo.lock | 4 ++++ Cargo.toml | 6 ++++-- drink/test-macro/Cargo.toml | 15 +++++++++++++++ drink/test-macro/src/lib.rs | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 drink/test-macro/Cargo.toml create mode 100644 drink/test-macro/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 0ff1f3c..77664a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1051,6 +1051,10 @@ dependencies = [ "thiserror", ] +[[package]] +name = "drink-test-macro" +version = "0.5.1" + [[package]] name = "duct" version = "0.13.6" diff --git a/Cargo.toml b/Cargo.toml index 56d402e..a48cf02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "drink", + "drink/test-macro", "drink-cli", ] @@ -20,7 +21,7 @@ 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.6.0" [workspace.dependencies] anyhow = { version = "1.0.71" } @@ -51,4 +52,5 @@ sp-runtime-interface = { version = "19.0.0" } # Local dependencies -drink = { version = "0.5.1", path = "drink" } +drink = { version = "0.6.0", path = "drink" } +drink-test-macro = { version = "0.6.0", path = "drink/test-macro" } diff --git a/drink/test-macro/Cargo.toml b/drink/test-macro/Cargo.toml new file mode 100644 index 0000000..478f561 --- /dev/null +++ b/drink/test-macro/Cargo.toml @@ -0,0 +1,15 @@ +[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] diff --git a/drink/test-macro/src/lib.rs b/drink/test-macro/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/drink/test-macro/src/lib.rs @@ -0,0 +1 @@ + From d060fad85c75eaa816eb0a58492430da035b7139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 19 Oct 2023 15:05:54 +0200 Subject: [PATCH 02/17] macro flow --- Cargo.lock | 57 ++++++++++++++++++++++++++++----- drink/test-macro/Cargo.toml | 4 +++ drink/test-macro/src/codegen.rs | 7 ++++ drink/test-macro/src/ir.rs | 11 +++++++ drink/test-macro/src/lib.rs | 21 ++++++++++++ 5 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 drink/test-macro/src/codegen.rs create mode 100644 drink/test-macro/src/ir.rs diff --git a/Cargo.lock b/Cargo.lock index 77664a4..3b38c53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -889,8 +889,18 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core 0.20.3", + "darling_macro 0.20.3", ] [[package]] @@ -907,17 +917,42 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.32", +] + [[package]] name = "darling_macro" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ - "darling_core", + "darling_core 0.14.4", "quote", "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core 0.20.3", + "quote", + "syn 2.0.32", +] + [[package]] name = "der" version = "0.7.6" @@ -1015,7 +1050,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "drink" -version = "0.5.1" +version = "0.6.0" dependencies = [ "contract-transcode", "frame-metadata", @@ -1037,7 +1072,7 @@ dependencies = [ [[package]] name = "drink-cli" -version = "0.5.1" +version = "0.6.0" dependencies = [ "anyhow", "clap", @@ -1053,7 +1088,13 @@ dependencies = [ [[package]] name = "drink-test-macro" -version = "0.5.1" +version = "0.6.0" +dependencies = [ + "darling 0.20.3", + "proc-macro2", + "quote", + "syn 2.0.32", +] [[package]] name = "duct" @@ -3054,7 +3095,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b38741b2f78e4391b94eac6b102af0f6ea2b0f7fe65adb55d7f4004f507854db" dependencies = [ - "darling", + "darling 0.14.4", "proc-macro-crate", "proc-macro2", "quote", @@ -3079,7 +3120,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd983cf0a9effd76138554ead18a6de542d1af175ac12fd5e91836c5c0268082" dependencies = [ - "darling", + "darling 0.14.4", "proc-macro-crate", "proc-macro2", "quote", diff --git a/drink/test-macro/Cargo.toml b/drink/test-macro/Cargo.toml index 478f561..78b26d4 100644 --- a/drink/test-macro/Cargo.toml +++ b/drink/test-macro/Cargo.toml @@ -13,3 +13,7 @@ description = "Procedural macro providing a `#[drink::test]` attribute for `drin proc-macro = true [dependencies] +darling = "0.20.3" +proc-macro2 = "1" +syn = { version = "2", features = ["full"] } +quote = "1" diff --git a/drink/test-macro/src/codegen.rs b/drink/test-macro/src/codegen.rs new file mode 100644 index 0000000..95b04f2 --- /dev/null +++ b/drink/test-macro/src/codegen.rs @@ -0,0 +1,7 @@ +use proc_macro2::TokenStream; + +use crate::{ir::IR, SynResult}; + +pub fn generate_code(ir: IR) -> SynResult { + todo!() +} diff --git a/drink/test-macro/src/ir.rs b/drink/test-macro/src/ir.rs new file mode 100644 index 0000000..2500bac --- /dev/null +++ b/drink/test-macro/src/ir.rs @@ -0,0 +1,11 @@ +use proc_macro::TokenStream; + +pub struct IR {} + +impl TryFrom<(TokenStream, TokenStream)> for IR { + type Error = syn::Error; + + fn try_from((attr, item): (TokenStream, TokenStream)) -> Result { + todo!() + } +} diff --git a/drink/test-macro/src/lib.rs b/drink/test-macro/src/lib.rs index 8b13789..24c761b 100644 --- a/drink/test-macro/src/lib.rs +++ b/drink/test-macro/src/lib.rs @@ -1 +1,22 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use crate::{codegen::generate_code, ir::IR}; + +mod codegen; +mod ir; + +type SynResult = Result; + +#[proc_macro_attribute] +pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { + match test_internal(attr, item) { + Ok(ts) => ts.into(), + Err(e) => e.to_compile_error().into(), + } +} + +fn test_internal(attr: TokenStream, item: TokenStream) -> SynResult { + let ir = IR::try_from((attr, item))?; + generate_code(ir) +} From 484f6ddb2638506ce285c0b74e705fe58a1d7a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 19 Oct 2023 15:41:54 +0200 Subject: [PATCH 03/17] Config tests --- drink/test-macro/src/ir.rs | 61 ++++++++++++++++++++++++++++++++++--- drink/test-macro/src/lib.rs | 4 +-- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/drink/test-macro/src/ir.rs b/drink/test-macro/src/ir.rs index 2500bac..2f75bcd 100644 --- a/drink/test-macro/src/ir.rs +++ b/drink/test-macro/src/ir.rs @@ -1,11 +1,62 @@ -use proc_macro::TokenStream; +use darling::{ast::NestedMeta, FromMeta}; +use proc_macro2::TokenStream as TokenStream2; -pub struct IR {} +#[derive(FromMeta)] +struct MacroArgs { + #[darling(default, multiple, rename = "manifest")] + manifests: Vec, + #[darling(default)] + compile_in_debug_mode: bool, +} + +pub struct IR { + args: MacroArgs, +} -impl TryFrom<(TokenStream, TokenStream)> for IR { +impl TryFrom<(TokenStream2, TokenStream2)> for IR { type Error = syn::Error; - fn try_from((attr, item): (TokenStream, TokenStream)) -> Result { - todo!() + fn try_from((attr, item): (TokenStream2, TokenStream2)) -> Result { + let args = MacroArgs::from_list(&NestedMeta::parse_meta_list(attr)?)?; + Ok(IR { args }) + } +} + +impl IR { + pub fn manifests(&self) -> &[String] { + &self.args.manifests + } + + pub fn compile_in_debug_mode(&self) -> bool { + self.args.compile_in_debug_mode + } +} + +#[cfg(test)] +mod tests { + use quote::quote; + + use super::*; + + #[test] + fn argument_parsing() { + let attr = quote! { + manifest = "../foo/Cargo.toml", + manifest = "some path", + compile_in_debug_mode + }; + let ir = IR::try_from((attr, Default::default())).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, Default::default())).expect("failed to parse macro args"); + + assert_eq!(ir.manifests(), &Vec::::new()); + assert!(!ir.compile_in_debug_mode()); } } diff --git a/drink/test-macro/src/lib.rs b/drink/test-macro/src/lib.rs index 24c761b..50d7682 100644 --- a/drink/test-macro/src/lib.rs +++ b/drink/test-macro/src/lib.rs @@ -10,13 +10,13 @@ type SynResult = Result; #[proc_macro_attribute] pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { - match test_internal(attr, item) { + match test_internal(attr.into(), item.into()) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } -fn test_internal(attr: TokenStream, item: TokenStream) -> SynResult { +fn test_internal(attr: TokenStream2, item: TokenStream2) -> SynResult { let ir = IR::try_from((attr, item))?; generate_code(ir) } From e1414c9210e4dd51002b6742e4a3bdede5a496fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 19 Oct 2023 19:52:14 +0200 Subject: [PATCH 04/17] Export macro, use it in tests --- Cargo.lock | 1 + drink/Cargo.toml | 2 ++ drink/src/lib.rs | 1 + drink/test-macro/src/codegen.rs | 8 +++++++- drink/test-macro/src/ir.rs | 20 +++++++++++++++++--- examples/cross-contract-call-tracing/lib.rs | 2 +- examples/flipper/lib.rs | 4 ++-- examples/mocking/lib.rs | 2 +- 8 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b38c53..788ec8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1053,6 +1053,7 @@ name = "drink" version = "0.6.0" dependencies = [ "contract-transcode", + "drink-test-macro", "frame-metadata", "frame-support", "frame-system", diff --git a/drink/Cargo.toml b/drink/Cargo.toml index c8d5485..a40c41f 100644 --- a/drink/Cargo.toml +++ b/drink/Cargo.toml @@ -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. diff --git a/drink/src/lib.rs b/drink/src/lib.rs index a7904ac..857e7b8 100644 --- a/drink/src/lib.rs +++ b/drink/src/lib.rs @@ -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::{ diff --git a/drink/test-macro/src/codegen.rs b/drink/test-macro/src/codegen.rs index 95b04f2..87d6628 100644 --- a/drink/test-macro/src/codegen.rs +++ b/drink/test-macro/src/codegen.rs @@ -1,7 +1,13 @@ use proc_macro2::TokenStream; +use quote::quote; use crate::{ir::IR, SynResult}; pub fn generate_code(ir: IR) -> SynResult { - todo!() + let test_function = ir.function(); + + Ok(quote! { + #[test] + #test_function + }) } diff --git a/drink/test-macro/src/ir.rs b/drink/test-macro/src/ir.rs index 2f75bcd..9ae8c2f 100644 --- a/drink/test-macro/src/ir.rs +++ b/drink/test-macro/src/ir.rs @@ -1,5 +1,6 @@ use darling::{ast::NestedMeta, FromMeta}; use proc_macro2::TokenStream as TokenStream2; +use syn::ItemFn; #[derive(FromMeta)] struct MacroArgs { @@ -11,6 +12,7 @@ struct MacroArgs { pub struct IR { args: MacroArgs, + function: ItemFn, } impl TryFrom<(TokenStream2, TokenStream2)> for IR { @@ -18,7 +20,11 @@ impl TryFrom<(TokenStream2, TokenStream2)> for IR { fn try_from((attr, item): (TokenStream2, TokenStream2)) -> Result { let args = MacroArgs::from_list(&NestedMeta::parse_meta_list(attr)?)?; - Ok(IR { args }) + let item_fn = syn::parse2::(item)?; + Ok(IR { + args, + function: item_fn, + }) } } @@ -30,6 +36,10 @@ impl IR { pub fn compile_in_debug_mode(&self) -> bool { self.args.compile_in_debug_mode } + + pub fn function(&self) -> &ItemFn { + &self.function + } } #[cfg(test)] @@ -38,6 +48,10 @@ mod tests { use super::*; + fn empty_function() -> TokenStream2 { + quote! { fn test() {} } + } + #[test] fn argument_parsing() { let attr = quote! { @@ -45,7 +59,7 @@ mod tests { manifest = "some path", compile_in_debug_mode }; - let ir = IR::try_from((attr, Default::default())).expect("failed to parse macro args"); + 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()); @@ -54,7 +68,7 @@ mod tests { #[test] fn default_arguments() { let attr = quote! {}; - let ir = IR::try_from((attr, Default::default())).expect("failed to parse macro args"); + let ir = IR::try_from((attr, empty_function())).expect("failed to parse macro args"); assert_eq!(ir.manifests(), &Vec::::new()); assert!(!ir.compile_in_debug_mode()); diff --git a/examples/cross-contract-call-tracing/lib.rs b/examples/cross-contract-call-tracing/lib.rs index bacb6a1..189499e 100644 --- a/examples/cross-contract-call-tracing/lib.rs +++ b/examples/cross-contract-call-tracing/lib.rs @@ -140,7 +140,7 @@ mod tests { } } - #[test] + #[drink::test] fn test() -> Result<(), Box> { let mut session = Session::::new()?; session.override_debug_handle(TracingExt(Box::new(TestDebugger {}))); diff --git a/examples/flipper/lib.rs b/examples/flipper/lib.rs index 3c9e694..f1fc0ae 100755 --- a/examples/flipper/lib.rs +++ b/examples/flipper/lib.rs @@ -50,7 +50,7 @@ mod tests { fs::read("./target/ink/flipper.wasm").expect("Failed to find or read contract file") } - #[test] + #[drink::test] fn initialization() -> Result<(), Box> { let init_value: bool = Session::::new()? .deploy_and(bytes(), "new", &["true"], vec![], None, &transcoder())? @@ -64,7 +64,7 @@ mod tests { Ok(()) } - #[test] + #[drink::test] fn flipping() -> Result<(), Box> { let init_value: bool = Session::::new()? .deploy_and(bytes(), "new", &["true"], vec![], None, &transcoder())? diff --git a/examples/mocking/lib.rs b/examples/mocking/lib.rs index 4af36af..7c55210 100755 --- a/examples/mocking/lib.rs +++ b/examples/mocking/lib.rs @@ -59,7 +59,7 @@ mod tests { fs::read("./target/ink/mocking.wasm").expect("Failed to find or read contract file") } - #[test] + #[drink::test] fn call_mocked_message() -> Result<(), Box> { let mut session = Session::::new()?; From f3e7ecd22e548ca6be4f3cb0eb1fa4abcb2dd45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 19 Oct 2023 20:47:40 +0200 Subject: [PATCH 05/17] Finish work on macro side --- drink/src/lib.rs | 1 + drink/src/testing_utils.rs | 4 ++++ drink/test-macro/src/codegen.rs | 20 ++++++++++++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 drink/src/testing_utils.rs diff --git a/drink/src/lib.rs b/drink/src/lib.rs index 857e7b8..010efed 100644 --- a/drink/src/lib.rs +++ b/drink/src/lib.rs @@ -10,6 +10,7 @@ mod mock; pub mod runtime; #[cfg(feature = "session")] pub mod session; +pub mod testing_utils; use std::{ marker::PhantomData, diff --git a/drink/src/testing_utils.rs b/drink/src/testing_utils.rs new file mode 100644 index 0000000..9081d8e --- /dev/null +++ b/drink/src/testing_utils.rs @@ -0,0 +1,4 @@ +//! Module exposing helper functions used by the `#[drink::test]` macro. + +/// Builds the contracts required for a particular test case. +pub fn build_contracts(_manifests: Vec, _debug_mode: bool) {} diff --git a/drink/test-macro/src/codegen.rs b/drink/test-macro/src/codegen.rs index 87d6628..8256523 100644 --- a/drink/test-macro/src/codegen.rs +++ b/drink/test-macro/src/codegen.rs @@ -4,10 +4,26 @@ use quote::quote; use crate::{ir::IR, SynResult}; pub fn generate_code(ir: IR) -> SynResult { - let test_function = ir.function(); + let item_fn = ir.function(); + + let fn_name = &item_fn.sig.ident; + let fn_body = &item_fn.block; + let fn_return_type = &item_fn.sig.output; + let fn_vis = &item_fn.vis; + let fn_attrs = &item_fn.attrs; + + let manifests = ir.manifests(); + let debug_mode = ir.compile_in_debug_mode(); + let build_contracts = quote! { + ::drink::testing_utils::build_contracts(vec![ #( #manifests ),* ], #debug_mode); + }; Ok(quote! { #[test] - #test_function + #( #fn_attrs )* + #fn_vis fn #fn_name() #fn_return_type { + #build_contracts + #fn_body + } }) } From 470ec9c29b77489a04695334003ff5e0c72c8688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 19 Oct 2023 20:53:24 +0200 Subject: [PATCH 06/17] use workspace deps --- Cargo.toml | 4 ++++ drink/test-macro/Cargo.toml | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a48cf02..0a8d4a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,10 +29,14 @@ 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" } diff --git a/drink/test-macro/Cargo.toml b/drink/test-macro/Cargo.toml index 78b26d4..c6ef557 100644 --- a/drink/test-macro/Cargo.toml +++ b/drink/test-macro/Cargo.toml @@ -13,7 +13,7 @@ description = "Procedural macro providing a `#[drink::test]` attribute for `drin proc-macro = true [dependencies] -darling = "0.20.3" -proc-macro2 = "1" -syn = { version = "2", features = ["full"] } -quote = "1" +darling = { workspace = true } +proc-macro2 = { workspace = true } +syn = { workspace = true, features = ["full"] } +quote = { workspace = true } From 681f9b2c4bb1e71096346542df21dbcff5a59b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 19 Oct 2023 20:55:52 +0200 Subject: [PATCH 07/17] minor bump --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0a8d4a4..0dee9fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ homepage = "https://github.com/Cardinal-Cryptography/drink" license = "Apache-2.0" readme = "README.md" repository = "https://github.com/Cardinal-Cryptography/drink" -version = "0.6.0" +version = "0.5.2" [workspace.dependencies] anyhow = { version = "1.0.71" } @@ -56,5 +56,5 @@ sp-runtime-interface = { version = "19.0.0" } # Local dependencies -drink = { version = "0.6.0", path = "drink" } -drink-test-macro = { version = "0.6.0", path = "drink/test-macro" } +drink = { version = "0.5.2", path = "drink" } +drink-test-macro = { version = "0.5.2", path = "drink/test-macro" } From 0eb96dd827cd3ad31c656aba91c674ec4df88aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 19 Oct 2023 21:24:42 +0200 Subject: [PATCH 08/17] Documentation --- Cargo.lock | 6 ++-- drink/test-macro/src/codegen.rs | 9 ++++-- drink/test-macro/src/ir.rs | 6 ++++ drink/test-macro/src/lib.rs | 55 +++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 788ec8a..8c0ab59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1050,7 +1050,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "drink" -version = "0.6.0" +version = "0.5.2" dependencies = [ "contract-transcode", "drink-test-macro", @@ -1073,7 +1073,7 @@ dependencies = [ [[package]] name = "drink-cli" -version = "0.6.0" +version = "0.5.2" dependencies = [ "anyhow", "clap", @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "drink-test-macro" -version = "0.6.0" +version = "0.5.2" dependencies = [ "darling 0.20.3", "proc-macro2", diff --git a/drink/test-macro/src/codegen.rs b/drink/test-macro/src/codegen.rs index 8256523..a5a69fb 100644 --- a/drink/test-macro/src/codegen.rs +++ b/drink/test-macro/src/codegen.rs @@ -3,15 +3,18 @@ use quote::quote; use crate::{ir::IR, SynResult}; +/// Interpret the intermediate representation and generate the final code. pub fn generate_code(ir: IR) -> SynResult { let item_fn = ir.function(); - let fn_name = &item_fn.sig.ident; + // 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_return_type = &item_fn.sig.output; 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! { @@ -21,7 +24,7 @@ pub fn generate_code(ir: IR) -> SynResult { Ok(quote! { #[test] #( #fn_attrs )* - #fn_vis fn #fn_name() #fn_return_type { + #fn_vis #fn_signature { #build_contracts #fn_body } diff --git a/drink/test-macro/src/ir.rs b/drink/test-macro/src/ir.rs index 9ae8c2f..d6b7134 100644 --- a/drink/test-macro/src/ir.rs +++ b/drink/test-macro/src/ir.rs @@ -2,16 +2,22 @@ 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, + /// 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, } diff --git a/drink/test-macro/src/lib.rs b/drink/test-macro/src/lib.rs index 50d7682..440a0d6 100644 --- a/drink/test-macro/src/lib.rs +++ b/drink/test-macro/src/lib.rs @@ -1,3 +1,7 @@ +//! 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; @@ -8,6 +12,56 @@ mod ir; type SynResult = Result; +/// Defines a drink!-based test. +/// +/// # Requirements +/// +/// - `drink` crate should be available in the target crate's dependencies (at the path `::drink`). +/// - 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::::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()) { @@ -16,6 +70,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { } } +/// Auxiliary function to enter ?-based error propagation. fn test_internal(attr: TokenStream2, item: TokenStream2) -> SynResult { let ir = IR::try_from((attr, item))?; generate_code(ir) From 6798296000c0b95347ed85d4f4bd4b7092a3be40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 19 Oct 2023 21:26:10 +0200 Subject: [PATCH 09/17] warning --- drink/test-macro/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drink/test-macro/src/lib.rs b/drink/test-macro/src/lib.rs index 440a0d6..c2db7ca 100644 --- a/drink/test-macro/src/lib.rs +++ b/drink/test-macro/src/lib.rs @@ -12,7 +12,8 @@ mod ir; type SynResult = Result; -/// Defines a drink!-based test. +/// Defines a drink!-based test. *WARNING: THE MACRO DOES NOTHING YET (MISSING CONTRACT BUILDING +/// FUNCTIONALITY). TO BE ADDED WITHIN A FEW DAYS.* /// /// # Requirements /// From 0f8bd7074e314716e381f7793dd6ee8f69611c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Fri, 20 Oct 2023 12:12:48 +0200 Subject: [PATCH 10/17] Prepare for compile-time contract building --- drink/src/lib.rs | 1 - drink/src/testing_utils.rs | 4 ---- drink/test-macro/src/codegen.rs | 8 +++----- drink/test-macro/src/lib.rs | 1 - 4 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 drink/src/testing_utils.rs diff --git a/drink/src/lib.rs b/drink/src/lib.rs index 010efed..857e7b8 100644 --- a/drink/src/lib.rs +++ b/drink/src/lib.rs @@ -10,7 +10,6 @@ mod mock; pub mod runtime; #[cfg(feature = "session")] pub mod session; -pub mod testing_utils; use std::{ marker::PhantomData, diff --git a/drink/src/testing_utils.rs b/drink/src/testing_utils.rs deleted file mode 100644 index 9081d8e..0000000 --- a/drink/src/testing_utils.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Module exposing helper functions used by the `#[drink::test]` macro. - -/// Builds the contracts required for a particular test case. -pub fn build_contracts(_manifests: Vec, _debug_mode: bool) {} diff --git a/drink/test-macro/src/codegen.rs b/drink/test-macro/src/codegen.rs index a5a69fb..cfdeec5 100644 --- a/drink/test-macro/src/codegen.rs +++ b/drink/test-macro/src/codegen.rs @@ -15,11 +15,9 @@ pub fn generate_code(ir: IR) -> SynResult { 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! { - ::drink::testing_utils::build_contracts(vec![ #( #manifests ),* ], #debug_mode); - }; + let _manifests = ir.manifests(); + let _debug_mode = ir.compile_in_debug_mode(); + let build_contracts = quote! {}; Ok(quote! { #[test] diff --git a/drink/test-macro/src/lib.rs b/drink/test-macro/src/lib.rs index c2db7ca..c2a7c5b 100644 --- a/drink/test-macro/src/lib.rs +++ b/drink/test-macro/src/lib.rs @@ -17,7 +17,6 @@ type SynResult = Result; /// /// # Requirements /// -/// - `drink` crate should be available in the target crate's dependencies (at the path `::drink`). /// - 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]`. /// From 09dcef0bd8c100efe8d88508499769a55dd241a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Fri, 20 Oct 2023 12:34:27 +0200 Subject: [PATCH 11/17] Move to compile time in narration --- drink/test-macro/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drink/test-macro/src/lib.rs b/drink/test-macro/src/lib.rs index c2a7c5b..6d7d288 100644 --- a/drink/test-macro/src/lib.rs +++ b/drink/test-macro/src/lib.rs @@ -23,8 +23,7 @@ type SynResult = Result; /// # 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. +/// will be executed during compile time. /// /// # Example /// From 04a877c223f3085f5d94a61c15af755a067a5d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Fri, 20 Oct 2023 17:02:55 +0200 Subject: [PATCH 12/17] Minor corrections --- Cargo.lock | 6 +++--- Cargo.toml | 9 ++++----- examples/cross-contract-call-tracing/Cargo.toml | 2 +- examples/flipper/Cargo.toml | 2 +- examples/mocking/Cargo.toml | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2eb4455..df95bf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1050,7 +1050,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "drink" -version = "0.5.2" +version = "0.5.3" dependencies = [ "contract-metadata", "contract-transcode", @@ -1075,7 +1075,7 @@ dependencies = [ [[package]] name = "drink-cli" -version = "0.5.2" +version = "0.5.3" dependencies = [ "anyhow", "clap", @@ -1091,7 +1091,7 @@ dependencies = [ [[package]] name = "drink-test-macro" -version = "0.5.2" +version = "0.5.3" dependencies = [ "darling 0.20.3", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index af144b4..8d4c3b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ homepage = "https://github.com/Cardinal-Cryptography/drink" license = "Apache-2.0" readme = "README.md" repository = "https://github.com/Cardinal-Cryptography/drink" -version = "0.5.2" +version = "0.5.3" [workspace.dependencies] anyhow = { version = "1.0.71" } @@ -37,12 +37,11 @@ proc-macro2 = { version = "1" } quote = { version = "1" } ratatui = { version = "0.21.0" } scale-info = { version = "2.5.0" } +serde_json = { version = "1.0" } syn = { version = "2" } thiserror = { version = "1.0.40" } wat = { version = "1.0.71" } -serde_json = { version = "1.0.106" } - # Substrate dependencies frame-metadata = { version = "16.0.0" } @@ -59,5 +58,5 @@ sp-runtime-interface = { version = "19.0.0" } # Local dependencies -drink = { version = "0.5.2", path = "drink" } -drink-test-macro = { version = "0.5.2", path = "drink/test-macro" } +drink = { version = "0.5.3", path = "drink" } +drink-test-macro = { version = "0.5.3", path = "drink/test-macro" } diff --git a/examples/cross-contract-call-tracing/Cargo.toml b/examples/cross-contract-call-tracing/Cargo.toml index 51a4d38..6793dee 100644 --- a/examples/cross-contract-call-tracing/Cargo.toml +++ b/examples/cross-contract-call-tracing/Cargo.toml @@ -13,7 +13,7 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } [dev-dependencies] -drink = { path = "../../drink", features = ["session"] } +drink = { path = "../../drink" } [lib] path = "lib.rs" diff --git a/examples/flipper/Cargo.toml b/examples/flipper/Cargo.toml index 2463366..462721b 100755 --- a/examples/flipper/Cargo.toml +++ b/examples/flipper/Cargo.toml @@ -13,7 +13,7 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } [dev-dependencies] -drink = { path = "../../drink", features = ["session"] } +drink = { path = "../../drink" } [lib] path = "lib.rs" diff --git a/examples/mocking/Cargo.toml b/examples/mocking/Cargo.toml index 63ec680..3107fa4 100755 --- a/examples/mocking/Cargo.toml +++ b/examples/mocking/Cargo.toml @@ -13,7 +13,7 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } [dev-dependencies] -drink = { path = "../../drink", features = ["session"] } +drink = { path = "../../drink" } [lib] path = "lib.rs" From 350367be5a2c320e2a2dcf5333cbfd0bdaee7bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 23 Oct 2023 09:12:17 +0200 Subject: [PATCH 13/17] Build contract --- Cargo.lock | 65 ++++++----------- Cargo.toml | 1 + drink/test-macro/Cargo.toml | 4 +- drink/test-macro/src/codegen.rs | 30 -------- drink/test-macro/src/contract_building.rs | 89 +++++++++++++++++++++++ drink/test-macro/src/ir.rs | 82 --------------------- drink/test-macro/src/lib.rs | 56 ++++++-------- 7 files changed, 138 insertions(+), 189 deletions(-) delete mode 100644 drink/test-macro/src/codegen.rs create mode 100644 drink/test-macro/src/contract_building.rs delete mode 100644 drink/test-macro/src/ir.rs diff --git a/Cargo.lock b/Cargo.lock index df95bf7..c8b8378 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -436,6 +436,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -595,7 +609,7 @@ checksum = "4637700a17e6624672cfee158db8da5d8b46a360342d2d69610e537c79335b2b" dependencies = [ "anyhow", "blake2", - "cargo_metadata", + "cargo_metadata 0.15.4", "clap", "colored", "contract-metadata", @@ -889,18 +903,8 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", -] - -[[package]] -name = "darling" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" -dependencies = [ - "darling_core 0.20.3", - "darling_macro 0.20.3", + "darling_core", + "darling_macro", ] [[package]] @@ -917,42 +921,17 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "darling_core" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.32", -] - [[package]] name = "darling_macro" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ - "darling_core 0.14.4", + "darling_core", "quote", "syn 1.0.109", ] -[[package]] -name = "darling_macro" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" -dependencies = [ - "darling_core 0.20.3", - "quote", - "syn 2.0.32", -] - [[package]] name = "der" version = "0.7.6" @@ -1093,7 +1072,9 @@ dependencies = [ name = "drink-test-macro" version = "0.5.3" dependencies = [ - "darling 0.20.3", + "cargo_metadata 0.18.1", + "contract-build", + "contract-metadata", "proc-macro2", "quote", "syn 2.0.32", @@ -3098,7 +3079,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b38741b2f78e4391b94eac6b102af0f6ea2b0f7fe65adb55d7f4004f507854db" dependencies = [ - "darling 0.14.4", + "darling", "proc-macro-crate", "proc-macro2", "quote", @@ -3123,7 +3104,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd983cf0a9effd76138554ead18a6de542d1af175ac12fd5e91836c5c0268082" dependencies = [ - "darling 0.14.4", + "darling", "proc-macro-crate", "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 8d4c3b6..cea91ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ version = "0.5.3" [workspace.dependencies] anyhow = { version = "1.0.71" } +cargo_metadata = { version = "0.18.1" } clap = { version = "4.3.4" } contract-build = { version = "3.0.1" } contract-metadata = { version = "3.2.0" } diff --git a/drink/test-macro/Cargo.toml b/drink/test-macro/Cargo.toml index c6ef557..5e579a3 100644 --- a/drink/test-macro/Cargo.toml +++ b/drink/test-macro/Cargo.toml @@ -13,7 +13,9 @@ description = "Procedural macro providing a `#[drink::test]` attribute for `drin proc-macro = true [dependencies] -darling = { workspace = true } +cargo_metadata = { workspace = true } +contract-build = { workspace = true } +contract-metadata = { workspace = true } proc-macro2 = { workspace = true } syn = { workspace = true, features = ["full"] } quote = { workspace = true } diff --git a/drink/test-macro/src/codegen.rs b/drink/test-macro/src/codegen.rs deleted file mode 100644 index cfdeec5..0000000 --- a/drink/test-macro/src/codegen.rs +++ /dev/null @@ -1,30 +0,0 @@ -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 { - 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 - } - }) -} diff --git a/drink/test-macro/src/contract_building.rs b/drink/test-macro/src/contract_building.rs new file mode 100644 index 0000000..07b2911 --- /dev/null +++ b/drink/test-macro/src/contract_building.rs @@ -0,0 +1,89 @@ +use cargo_metadata::{Metadata, MetadataCommand, Package}; +use contract_build::{ + BuildArtifacts, BuildMode, ExecuteArgs, Features, ManifestPath, Network, OptimizationPasses, + OutputType, Target, UnstableFlags, Verbosity, +}; + +const INK_AS_DEPENDENCY_FEATURE: &str = "ink-as-dependency"; + +/// Build the current package with `cargo contract build --release` (if it is a contract package), +/// as well as all its contract dependencies. +/// +/// A package is considered as a contract package, if it has the `ink-as-dependency` feature. +/// +/// A contract dependency, is a package defined in the `Cargo.toml` file with the +/// `ink-as-dependency` feature enabled. +pub fn build_contracts() { + let metadata = MetadataCommand::new() + .exec() + .expect("Error invoking `cargo metadata`"); + + for contract_crate in get_contract_crates(&metadata) { + build_contract_crate(contract_crate); + } +} + +fn get_contract_crates(metadata: &Metadata) -> Vec<&Package> { + let pkg_lookup = |id| { + metadata + .packages + .iter() + .find(|package| package.id == id) + .expect(&format!("Error resolving package {id}")) + }; + + let dep_graph = metadata + .resolve + .as_ref() + .expect("Error resolving dependencies"); + + let contract_deps = dep_graph + .nodes + .iter() + .filter_map(|node| { + node.features + .contains(&INK_AS_DEPENDENCY_FEATURE.to_string()) + .then(|| node.id.clone()) + }) + .map(pkg_lookup); + + let root = dep_graph + .root + .as_ref() + .expect("Error resolving root package"); + let root = pkg_lookup(root.clone()); + + root.features + .contains_key(INK_AS_DEPENDENCY_FEATURE) + .then(|| root) + .into_iter() + .chain(contract_deps) + .collect() +} + +fn build_contract_crate(pkg: &Package) { + let args = ExecuteArgs { + manifest_path: get_manifest_path(pkg), + verbosity: Verbosity::Default, + build_mode: BuildMode::Release, + features: Features::default(), + network: Network::Online, + build_artifact: BuildArtifacts::All, + unstable_flags: UnstableFlags::default(), + optimization_passes: Some(OptimizationPasses::default()), + keep_debug_symbols: false, + lint: false, + output_type: OutputType::HumanReadable, + skip_wasm_validation: false, + target: Target::Wasm, + }; + + contract_build::execute(args).expect("Error building contract"); +} + +fn get_manifest_path(package: &Package) -> ManifestPath { + ManifestPath::new(package.manifest_path.clone().into_std_path_buf()).expect(&format!( + "Error resolving manifest path for package {}", + package.name + )) +} diff --git a/drink/test-macro/src/ir.rs b/drink/test-macro/src/ir.rs deleted file mode 100644 index d6b7134..0000000 --- a/drink/test-macro/src/ir.rs +++ /dev/null @@ -1,82 +0,0 @@ -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, - /// 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 { - let args = MacroArgs::from_list(&NestedMeta::parse_meta_list(attr)?)?; - let item_fn = syn::parse2::(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::::new()); - assert!(!ir.compile_in_debug_mode()); - } -} diff --git a/drink/test-macro/src/lib.rs b/drink/test-macro/src/lib.rs index 6d7d288..34a9bd6 100644 --- a/drink/test-macro/src/lib.rs +++ b/drink/test-macro/src/lib.rs @@ -2,29 +2,37 @@ #![warn(missing_docs)] +mod contract_building; + use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::ItemFn; -use crate::{codegen::generate_code, ir::IR}; - -mod codegen; -mod ir; +use crate::contract_building::build_contracts; type SynResult = Result; -/// Defines a drink!-based test. *WARNING: THE MACRO DOES NOTHING YET (MISSING CONTRACT BUILDING -/// FUNCTIONALITY). TO BE ADDED WITHIN A FEW DAYS.* +/// Defines a drink!-based test. /// /// # 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]`. +/// - Your crate cannot be part of a cargo workspace. /// /// # Impact /// /// This macro will take care of building all needed contracts for the test. The building process /// will be executed during compile time. /// +/// Contracts to be built: +/// - current cargo package if contains a `ink-as-dependency` feature +/// - all dependencies declared in the `Cargo.toml` file with the `ink-as-dependency` feature +/// enabled +/// +/// Note: Depending on a non-local contract is not tested yet. +/// /// # Example /// /// ```rust, ignore @@ -36,31 +44,6 @@ type SynResult = Result; /// .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()) { @@ -70,7 +53,12 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { } /// Auxiliary function to enter ?-based error propagation. -fn test_internal(attr: TokenStream2, item: TokenStream2) -> SynResult { - let ir = IR::try_from((attr, item))?; - generate_code(ir) +fn test_internal(_attr: TokenStream2, item: TokenStream2) -> SynResult { + let item_fn = syn::parse2::(item)?; + build_contracts(); + + Ok(quote! { + #[test] + #item_fn + }) } From f18b654da6ee55965b8480f670aa7f998a2e6b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 23 Oct 2023 09:28:18 +0200 Subject: [PATCH 14/17] Build once --- drink/test-macro/src/contract_building.rs | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drink/test-macro/src/contract_building.rs b/drink/test-macro/src/contract_building.rs index 07b2911..b949911 100644 --- a/drink/test-macro/src/contract_building.rs +++ b/drink/test-macro/src/contract_building.rs @@ -1,3 +1,9 @@ +use std::{ + collections::HashSet, + path::PathBuf, + sync::{Mutex, OnceLock}, +}; + use cargo_metadata::{Metadata, MetadataCommand, Package}; use contract_build::{ BuildArtifacts, BuildMode, ExecuteArgs, Features, ManifestPath, Network, OptimizationPasses, @@ -6,6 +12,11 @@ use contract_build::{ const INK_AS_DEPENDENCY_FEATURE: &str = "ink-as-dependency"; +/// Stores the manifest paths of all contracts which have already been built. +/// +/// This prevents from building the same contract for every testcase separately. +static CONTRACTS_BUILT: OnceLock>> = OnceLock::new(); + /// Build the current package with `cargo contract build --release` (if it is a contract package), /// as well as all its contract dependencies. /// @@ -62,8 +73,19 @@ fn get_contract_crates(metadata: &Metadata) -> Vec<&Package> { } fn build_contract_crate(pkg: &Package) { + let manifest_path = get_manifest_path(pkg); + + if !CONTRACTS_BUILT + .get_or_init(|| Mutex::new(HashSet::new())) + .lock() + .expect("Error locking mutex") + .insert(manifest_path.clone().into()) + { + return; + } + let args = ExecuteArgs { - manifest_path: get_manifest_path(pkg), + manifest_path, verbosity: Verbosity::Default, build_mode: BuildMode::Release, features: Features::default(), From b2230fd691881ddb0b8badde3a5debad91fd5c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 23 Oct 2023 09:34:46 +0200 Subject: [PATCH 15/17] No need to run cargo test in CI --- .github/workflows/rust-checks.yml | 6 ------ Makefile | 1 - drink/test-macro/src/contract_building.rs | 3 ++- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/rust-checks.yml b/.github/workflows/rust-checks.yml index fce0db0..ee68eb7 100644 --- a/.github/workflows/rust-checks.yml +++ b/.github/workflows/rust-checks.yml @@ -50,12 +50,6 @@ jobs: with: command: test - - name: Install cargo-contract - shell: bash - run: | - rustup component add rust-src - cargo install cargo-contract --version 3.2.0 - - name: Run tests for examples shell: bash run: make test_examples diff --git a/Makefile b/Makefile index e3f348b..e31a687 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,6 @@ lint: ## Run the linter test_examples: ## Run tests for the examples @for dir in $(EXAMPLES_PATHS); do \ echo "Processing $$dir" ; \ - cargo contract build --quiet --manifest-path $$dir/Cargo.toml --release ; \ cargo test --quiet --manifest-path $$dir/Cargo.toml --release; \ done diff --git a/drink/test-macro/src/contract_building.rs b/drink/test-macro/src/contract_building.rs index b949911..617b1fd 100644 --- a/drink/test-macro/src/contract_building.rs +++ b/drink/test-macro/src/contract_building.rs @@ -10,9 +10,10 @@ use contract_build::{ OutputType, Target, UnstableFlags, Verbosity, }; +/// Contract package differentiator. const INK_AS_DEPENDENCY_FEATURE: &str = "ink-as-dependency"; -/// Stores the manifest paths of all contracts which have already been built. +/// Stores the manifest paths of all contracts that have already been built. /// /// This prevents from building the same contract for every testcase separately. static CONTRACTS_BUILT: OnceLock>> = OnceLock::new(); From 60bf3314353020eda1806229ba969c8c30b23ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 23 Oct 2023 10:09:00 +0200 Subject: [PATCH 16/17] lint --- drink/test-macro/src/contract_building.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drink/test-macro/src/contract_building.rs b/drink/test-macro/src/contract_building.rs index 617b1fd..acc9803 100644 --- a/drink/test-macro/src/contract_building.rs +++ b/drink/test-macro/src/contract_building.rs @@ -41,7 +41,7 @@ fn get_contract_crates(metadata: &Metadata) -> Vec<&Package> { .packages .iter() .find(|package| package.id == id) - .expect(&format!("Error resolving package {id}")) + .unwrap_or_else(|| panic!("Error resolving package {id}")) }; let dep_graph = metadata @@ -67,7 +67,7 @@ fn get_contract_crates(metadata: &Metadata) -> Vec<&Package> { root.features .contains_key(INK_AS_DEPENDENCY_FEATURE) - .then(|| root) + .then_some(root) .into_iter() .chain(contract_deps) .collect() @@ -105,8 +105,6 @@ fn build_contract_crate(pkg: &Package) { } fn get_manifest_path(package: &Package) -> ManifestPath { - ManifestPath::new(package.manifest_path.clone().into_std_path_buf()).expect(&format!( - "Error resolving manifest path for package {}", - package.name - )) + ManifestPath::new(package.manifest_path.clone().into_std_path_buf()) + .unwrap_or_else(|_| panic!("Error resolving manifest path for package {}", package.name)) } From 8b7de411445f391d5984c17df7ab111e275af3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 23 Oct 2023 10:27:35 +0200 Subject: [PATCH 17/17] rust-src is still needed --- .github/workflows/rust-checks.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/rust-checks.yml b/.github/workflows/rust-checks.yml index ee68eb7..87025c5 100644 --- a/.github/workflows/rust-checks.yml +++ b/.github/workflows/rust-checks.yml @@ -29,6 +29,10 @@ jobs: targets: wasm32-unknown-unknown components: clippy rustfmt + - name: Add rust-src + shell: bash + run: rustup component add rust-src + - name: Run format checks uses: actions-rs/cargo@v1 with: