From 04b7bf4a702b8c91e395989e0cf0883812ac2013 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Wed, 18 Sep 2024 21:17:13 -0400 Subject: [PATCH 01/12] wip --- Cargo.lock | 187 +++++++++--------- Cargo.toml | 3 +- crates/katana/runner/Cargo.toml | 5 +- crates/katana/runner/macro/Cargo.toml | 15 ++ .../runner/{runner-macro => macro}/src/lib.rs | 0 crates/katana/runner/macro/tests/macro.rs | 59 ++++++ crates/katana/runner/runner-macro/Cargo.toml | 12 -- crates/katana/runner/src/lib.rs | 70 ++++++- crates/katana/runner/src/prefunded.rs | 53 +---- 9 files changed, 236 insertions(+), 168 deletions(-) create mode 100644 crates/katana/runner/macro/Cargo.toml rename crates/katana/runner/{runner-macro => macro}/src/lib.rs (100%) create mode 100644 crates/katana/runner/macro/tests/macro.rs delete mode 100644 crates/katana/runner/runner-macro/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index bb7394efc4..25e7ab9bab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -522,7 +522,7 @@ checksum = "d83524c1f6162fcb5b0decf775498a125066c86dda6066ed609531b0e912f85a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -685,7 +685,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -702,7 +702,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", "syn-solidity", "tiny-keccak", ] @@ -720,7 +720,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.71", + "syn 2.0.77", "syn-solidity", ] @@ -1168,7 +1168,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", "synstructure 0.13.1", ] @@ -1191,7 +1191,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -1329,7 +1329,7 @@ dependencies = [ "proc-macro2", "quote", "strum 0.25.0", - "syn 2.0.71", + "syn 2.0.77", "thiserror", ] @@ -1436,7 +1436,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -1484,7 +1484,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -1501,7 +1501,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -1551,7 +1551,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -1823,7 +1823,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -1845,7 +1845,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.71", + "syn 2.0.77", "which 4.4.2", ] @@ -2025,7 +2025,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", "syn_derive", ] @@ -2303,7 +2303,7 @@ dependencies = [ "quote", "serde_json", "starknet 0.11.0", - "syn 2.0.71", + "syn 2.0.77", "thiserror", ] @@ -2316,7 +2316,7 @@ dependencies = [ "quote", "serde_json", "starknet 0.11.0", - "syn 2.0.71", + "syn 2.0.77", "thiserror", ] @@ -2334,7 +2334,7 @@ dependencies = [ "quote", "serde_json", "starknet 0.11.0", - "syn 2.0.71", + "syn 2.0.77", "thiserror", ] @@ -2352,7 +2352,7 @@ dependencies = [ "quote", "serde_json", "starknet 0.11.0", - "syn 2.0.71", + "syn 2.0.77", "thiserror", ] @@ -2370,7 +2370,7 @@ dependencies = [ "quote", "serde_json", "starknet 0.11.0", - "syn 2.0.71", + "syn 2.0.77", "thiserror", ] @@ -2387,7 +2387,7 @@ dependencies = [ "quote", "serde_json", "starknet 0.11.0", - "syn 2.0.71", + "syn 2.0.77", "thiserror", ] @@ -2617,7 +2617,7 @@ checksum = "e32e958decd95ae122ee64daa26721da2f76e83231047f947fd9cdc5d3c90cc6" dependencies = [ "quote", "scarb-stable-hash 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -2673,7 +2673,7 @@ checksum = "3d55dcf98a6e1a03e0b36129fad4253f9e6666a1746ab9c075d212ba68a4e9c1" dependencies = [ "cairo-lang-debug", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -3434,7 +3434,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -4005,7 +4005,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -4066,7 +4066,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -4088,7 +4088,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -4234,7 +4234,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -4244,7 +4244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -4257,7 +4257,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -4394,7 +4394,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -4795,7 +4795,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -4807,7 +4807,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -5268,7 +5268,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -5358,7 +5358,7 @@ checksum = "553630feadf7b76442b0849fd25fdf89b860d933623aec9693fed19af0400c78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -5816,7 +5816,7 @@ checksum = "999ce923619f88194171a67fb3e6d613653b8d4d6078b529b15a765da0edcc17" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -7874,7 +7874,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -8166,7 +8166,7 @@ dependencies = [ "assert_fs", "chrono", "katana-node-bindings", - "runner-macro", + "katana-runner-macro", "serde", "serde_json", "starknet 0.11.0", @@ -8174,6 +8174,15 @@ dependencies = [ "url", ] +[[package]] +name = "katana-runner-macro" +version = "1.0.0-alpha.12" +dependencies = [ + "quote", + "starknet 0.11.0", + "syn 2.0.77", +] + [[package]] name = "katana-slot-controller" version = "1.0.0-alpha.12" @@ -8772,7 +8781,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -9001,7 +9010,7 @@ checksum = "f8dccda732e04fa3baf2e17cf835bfe2601c7c2edafd64417c627dabae3a8cda" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -9816,7 +9825,7 @@ checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -9917,7 +9926,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -10207,7 +10216,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -10261,7 +10270,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -10305,7 +10314,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -10560,7 +10569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -10704,7 +10713,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -10786,7 +10795,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.71", + "syn 2.0.77", "tempfile", ] @@ -10813,7 +10822,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -11007,9 +11016,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -11404,7 +11413,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -11597,7 +11606,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.0", - "syn 2.0.71", + "syn 2.0.77", "unicode-ident", ] @@ -11610,7 +11619,7 @@ dependencies = [ "quote", "rand", "rustc_version 0.4.0", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -11705,14 +11714,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" -[[package]] -name = "runner-macro" -version = "1.0.0-alpha.12" -dependencies = [ - "quote", - "syn 2.0.71", -] - [[package]] name = "rust_decimal" version = "1.35.0" @@ -12297,7 +12298,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -12488,7 +12489,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -12499,7 +12500,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -12543,7 +12544,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -12610,7 +12611,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -12622,7 +12623,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -12657,7 +12658,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -13587,7 +13588,7 @@ checksum = "bbc159a1934c7be9761c237333a57febe060ace2bc9e3b337a59a37af206d19f" dependencies = [ "starknet-curve 0.4.2", "starknet-ff", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -13598,7 +13599,7 @@ checksum = "2e179dedc3fa6da064e56811d3e05d446aa2f7459e4eb0e3e49378a337235437" dependencies = [ "starknet-curve 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "starknet-types-core 0.1.5 (git+https://github.com/dojoengine/types-rs?rev=289e2f0)", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -13658,7 +13659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95d549d3078bdbe775d0deaa8ddb57a19942989ce7c1f2dfd60beeb322bb4945" dependencies = [ "starknet-core 0.10.0", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -13667,7 +13668,7 @@ version = "0.2.0" source = "git+https://github.com/xJonathanLEI/starknet-rs?rev=2ddc694#2ddc69479d326ed154df438d22f2d720fbba746e" dependencies = [ "starknet-core 0.11.1", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -13887,7 +13888,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -13900,7 +13901,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -14007,9 +14008,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.71" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -14025,7 +14026,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -14037,7 +14038,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -14072,7 +14073,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -14176,7 +14177,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -14196,7 +14197,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -14341,7 +14342,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -14580,7 +14581,7 @@ dependencies = [ "proc-macro2", "prost-build 0.12.6", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -15017,7 +15018,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -15046,7 +15047,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -15196,7 +15197,7 @@ checksum = "f9534daa9fd3ed0bd911d462a37f172228077e7abf18c18a5f67199d959205f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -15567,7 +15568,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -15601,7 +15602,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -15634,7 +15635,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -16108,7 +16109,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -16119,7 +16120,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -16554,7 +16555,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] @@ -16574,7 +16575,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.77", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 82b866625b..a8fb45efa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ members = [ "crates/katana/rpc/rpc-types", "crates/katana/rpc/rpc-types-builder", "crates/katana/runner", - "crates/katana/runner/runner-macro", + "crates/katana/runner/macro", "crates/katana/storage/codecs", "crates/katana/storage/codecs/derive", "crates/katana/storage/db", @@ -86,6 +86,7 @@ katana-core = { path = "crates/katana/core", default-features = false } katana-db = { path = "crates/katana/storage/db" } katana-executor = { path = "crates/katana/executor" } katana-node = { path = "crates/katana/node", default-features = false } +katana-node-bindings = { path = "crates/katana/node-bindings" } katana-pool = { path = "crates/katana/pool" } katana-primitives = { path = "crates/katana/primitives" } katana-provider = { path = "crates/katana/storage/provider" } diff --git a/crates/katana/runner/Cargo.toml b/crates/katana/runner/Cargo.toml index 284da3e854..9bd5323b50 100644 --- a/crates/katana/runner/Cargo.toml +++ b/crates/katana/runner/Cargo.toml @@ -6,11 +6,12 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +katana-node-bindings.workspace = true +katana-runner-macro = { path = "macro" } + anyhow.workspace = true assert_fs.workspace = true chrono.workspace = true -katana-node-bindings = { path = "../node-bindings" } -runner-macro = { path = "./runner-macro" } serde.workspace = true serde_json.workspace = true starknet.workspace = true diff --git a/crates/katana/runner/macro/Cargo.toml b/crates/katana/runner/macro/Cargo.toml new file mode 100644 index 0000000000..ce9ea5a505 --- /dev/null +++ b/crates/katana/runner/macro/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition.workspace = true +name = "katana-runner-macro" +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +syn = { version = "2.0", features = [ "fold", "full" ] } + +[dev-dependencies] +starknet.workspace = true diff --git a/crates/katana/runner/runner-macro/src/lib.rs b/crates/katana/runner/macro/src/lib.rs similarity index 100% rename from crates/katana/runner/runner-macro/src/lib.rs rename to crates/katana/runner/macro/src/lib.rs diff --git a/crates/katana/runner/macro/tests/macro.rs b/crates/katana/runner/macro/tests/macro.rs new file mode 100644 index 0000000000..0e9cd40b44 --- /dev/null +++ b/crates/katana/runner/macro/tests/macro.rs @@ -0,0 +1,59 @@ +// macro usage example + +// user can set the individual fields when using the macro +#[katana_runner::test( + flavor = "embedded", // binary | embedded + fee = false, + accounts = 1, + chain_id = 0x12345, + validation = false, + db_dir = "path/to/db", + binary = "path/to/binary", + output = "path/to/output.log", + genesis = "path/to/genesis.json", + classes = ["path/to/erc20.json", "path/to/account.json"] // will be declared at startup +)] +async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { + let url = node.url(); + let provider = node.provider(); + let account = node.account(); + Ok(()) +} + +// but they could also just pass the whole RunnerCfg struct if they want to allow defining the configuration +// once and reuse it across different tests + +#[katana_runner::test(RunnerCfg { })] +async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { + let url = node.url(); + let provider = node.provider(); + let account = node.account(); + Ok(()) +} + +// This is the rough expanded code of the proc macro + +#[tokio::test] +async fn foo() -> Result<(), Box> { + struct RunnerCtx(KatanaRunnerHandle); + + impl std::ops::Deref for RunnerCtx { + type Target = KatanaRunnerHandle; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl std::ops::DerefMut for RunnerCtx { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + let handle = KatanaRunner::todo(); + + async fn __foo(node: &mut RunnerCtx) -> Result<(), Box> { + Ok(()) + } + + __foo(&mut RunnerCtx(handle)).await +} diff --git a/crates/katana/runner/runner-macro/Cargo.toml b/crates/katana/runner/runner-macro/Cargo.toml deleted file mode 100644 index bdc2b2250a..0000000000 --- a/crates/katana/runner/runner-macro/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -edition = "2021" -name = "runner-macro" -version = "1.0.0-alpha.12" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -proc-macro = true - -[dependencies] -quote = "1.0.35" -syn = { version = "2.0.48", features = [ "fold", "full" ] } diff --git a/crates/katana/runner/src/lib.rs b/crates/katana/runner/src/lib.rs index 45c3d38785..852610f410 100644 --- a/crates/katana/runner/src/lib.rs +++ b/crates/katana/runner/src/lib.rs @@ -9,15 +9,19 @@ use std::thread; use anyhow::{Context, Result}; use assert_fs::TempDir; -use katana_node_bindings::{Katana, KatanaInstance}; -pub use runner_macro::{katana_test, runner}; +use starknet::accounts::{ExecutionEncoding, SingleOwnerAccount}; use starknet::core::types::Felt; +use starknet::core::types::{BlockId, BlockTag}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; +use starknet::signers::LocalWallet; use tokio::sync::Mutex; use url::Url; use utils::find_free_port; +use katana_node_bindings::{Katana, KatanaInstance}; +pub use katana_runner_macro::{katana_test, runner}; + #[derive(Debug)] pub struct KatanaRunner { instance: KatanaInstance, @@ -119,14 +123,15 @@ impl KatanaRunner { builder = builder.dev(config.dev); - let mut katana = builder.spawn(); + // start the katana instance + let mut instance = builder.spawn(); let stdout = - katana.child_mut().stdout.take().context("failed to take subprocess stdout")?; + instance.child_mut().stdout.take().context("failed to take subprocess stdout")?; let log_filename = PathBuf::from(format!( "katana-{}.log", - config.run_name.clone().unwrap_or_else(|| port.to_string()) + config.run_name.unwrap_or_else(|| port.to_string()) )); let log_file_path = if let Some(log_path) = config.log_path { @@ -141,10 +146,10 @@ impl KatanaRunner { utils::listen_to_stdout(&log_file_path_sent, stdout); }); - let provider = JsonRpcClient::new(HttpTransport::new(katana.endpoint_url())); + let provider = JsonRpcClient::new(HttpTransport::new(instance.endpoint_url())); let contract = Mutex::new(Option::None); - Ok(KatanaRunner { instance: katana, provider, log_file_path, contract }) + Ok(KatanaRunner { instance, provider, log_file_path, contract }) } pub fn log_file_path(&self) -> &PathBuf { @@ -178,12 +183,61 @@ impl KatanaRunner { pub async fn contract(&self) -> Option { *self.contract.lock().await } + + pub fn accounts_data(&self) -> &[katana_node_bindings::Account] { + self.instance.accounts() + } + + pub fn accounts(&self) -> Vec, LocalWallet>> { + self.accounts_data().iter().map(|account| self.account_to_single_owned(account)).collect() + } + + pub fn account_data(&self, index: usize) -> &katana_node_bindings::Account { + &self.accounts_data()[index] + } + + pub fn account( + &self, + index: usize, + ) -> SingleOwnerAccount, LocalWallet> { + self.account_to_single_owned(&self.accounts_data()[index]) + } + + fn account_to_single_owned( + &self, + account: &katana_node_bindings::Account, + ) -> SingleOwnerAccount, LocalWallet> { + let signer = if let Some(private_key) = &account.private_key { + LocalWallet::from(private_key.clone()) + } else { + panic!("Account does not have a private key") + }; + + let chain_id = self.instance.chain_id(); + let provider = self.owned_provider(); + + let mut account = SingleOwnerAccount::new( + provider, + signer, + account.address, + chain_id, + ExecutionEncoding::New, + ); + + account.set_block_id(BlockId::Tag(BlockTag::Pending)); + + account + } } /// Determines the default program path for the katana runner based on the KATANA_RUNNER_BIN /// environment variable. If not set, try to to use katana from the PATH. fn determine_default_program_path() -> String { - if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { bin } else { "katana".to_string() } + if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { + bin + } else { + "katana".to_string() + } } #[cfg(test)] diff --git a/crates/katana/runner/src/prefunded.rs b/crates/katana/runner/src/prefunded.rs index 033ae50fc6..e7f68ab9fd 100644 --- a/crates/katana/runner/src/prefunded.rs +++ b/crates/katana/runner/src/prefunded.rs @@ -1,54 +1,3 @@ -use starknet::accounts::{ExecutionEncoding, SingleOwnerAccount}; -use starknet::core::types::{BlockId, BlockTag}; -use starknet::providers::jsonrpc::HttpTransport; -use starknet::providers::JsonRpcClient; -use starknet::signers::LocalWallet; - use crate::KatanaRunner; -impl KatanaRunner { - pub fn accounts_data(&self) -> &[katana_node_bindings::Account] { - self.instance.accounts() - } - - pub fn accounts(&self) -> Vec, LocalWallet>> { - self.accounts_data().iter().map(|account| self.account_to_single_owned(account)).collect() - } - - pub fn account_data(&self, index: usize) -> &katana_node_bindings::Account { - &self.accounts_data()[index] - } - - pub fn account( - &self, - index: usize, - ) -> SingleOwnerAccount, LocalWallet> { - self.account_to_single_owned(&self.accounts_data()[index]) - } - - fn account_to_single_owned( - &self, - account: &katana_node_bindings::Account, - ) -> SingleOwnerAccount, LocalWallet> { - let signer = if let Some(private_key) = &account.private_key { - LocalWallet::from(private_key.clone()) - } else { - panic!("Account does not have a private key") - }; - - let chain_id = self.instance.chain_id(); - let provider = self.owned_provider(); - - let mut account = SingleOwnerAccount::new( - provider, - signer, - account.address, - chain_id, - ExecutionEncoding::New, - ); - - account.set_block_id(BlockId::Tag(BlockTag::Pending)); - - account - } -} +impl KatanaRunner {} From 229c54812bb0bde67e6b6a92107ffd9dcb4cb54d Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Wed, 18 Sep 2024 22:35:15 -0400 Subject: [PATCH 02/12] wip --- Cargo.lock | 3 + crates/katana/runner/macro/Cargo.toml | 3 + crates/katana/runner/macro/src/config.rs | 162 ++++++++++ crates/katana/runner/macro/src/entry.rs | 125 ++++++++ crates/katana/runner/macro/src/lib.rs | 345 ++++++++++++++-------- crates/katana/runner/macro/tests/macro.rs | 6 +- crates/katana/runner/src/lib.rs | 14 +- 7 files changed, 526 insertions(+), 132 deletions(-) create mode 100644 crates/katana/runner/macro/src/config.rs create mode 100644 crates/katana/runner/macro/src/entry.rs diff --git a/Cargo.lock b/Cargo.lock index 25e7ab9bab..3ffecbe5df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8178,8 +8178,11 @@ dependencies = [ name = "katana-runner-macro" version = "1.0.0-alpha.12" dependencies = [ + "proc-macro2", "quote", "starknet 0.11.0", + "strum 0.25.0", + "strum_macros 0.25.3", "syn 2.0.77", ] diff --git a/crates/katana/runner/macro/Cargo.toml b/crates/katana/runner/macro/Cargo.toml index ce9ea5a505..b7789d2f33 100644 --- a/crates/katana/runner/macro/Cargo.toml +++ b/crates/katana/runner/macro/Cargo.toml @@ -8,7 +8,10 @@ version.workspace = true proc-macro = true [dependencies] +proc-macro2 = "1.0.86" quote = "1.0" +strum.workspace = true +strum_macros.workspace = true syn = { version = "2.0", features = [ "fold", "full" ] } [dev-dependencies] diff --git a/crates/katana/runner/macro/src/config.rs b/crates/katana/runner/macro/src/config.rs new file mode 100644 index 0000000000..da64ec5cb1 --- /dev/null +++ b/crates/katana/runner/macro/src/config.rs @@ -0,0 +1,162 @@ +use std::path::PathBuf; + +pub const DEFAULT_ERROR_CONFIG: Configuration = Configuration::new(true); + +pub struct Configuration { + dev: bool, + fee: bool, + is_test: bool, + accounts: u16, + validation: bool, + db_dir: Option, + binary: Option, + block_time: Option, + log_path: Option, +} + +impl Configuration { + const fn new(is_test: bool) -> Self { + Configuration { + is_test, + fee: true, + db_dir: None, + binary: None, + accounts: 10, + dev: is_test, + log_path: None, + validation: true, + block_time: None, + } + } + + // fn set_flavor(&mut self, runtime: syn::Lit, span: Span) -> Result<(), syn::Error> { + // if self.flavor.is_some() { + // return Err(syn::Error::new(span, "`flavor` set multiple times.")); + // } + + // let runtime_str = parse_string(runtime, span, "flavor")?; + // let runtime = + // RuntimeFlavor::from_str(&runtime_str).map_err(|err| syn::Error::new(span, err))?; + // self.flavor = Some(runtime); + // Ok(()) + // } + + // fn set_worker_threads( + // &mut self, + // worker_threads: syn::Lit, + // span: Span, + // ) -> Result<(), syn::Error> { + // if self.worker_threads.is_some() { + // return Err(syn::Error::new(span, "`worker_threads` set multiple times.")); + // } + + // let worker_threads = parse_int(worker_threads, span, "worker_threads")?; + // if worker_threads == 0 { + // return Err(syn::Error::new(span, "`worker_threads` may not be 0.")); + // } + // self.worker_threads = Some((worker_threads, span)); + // Ok(()) + // } + + // fn set_start_paused(&mut self, start_paused: syn::Lit, span: Span) -> Result<(), syn::Error> + // { if self.start_paused.is_some() { + // return Err(syn::Error::new(span, "`start_paused` set multiple times.")); + // } + + // let start_paused = parse_bool(start_paused, span, "start_paused")?; + // self.start_paused = Some((start_paused, span)); + // Ok(()) + // } + + // fn set_crate_name(&mut self, name: syn::Lit, span: Span) -> Result<(), syn::Error> { + // if self.crate_name.is_some() { + // return Err(syn::Error::new(span, "`crate` set multiple times.")); + // } + // let name_path = parse_path(name, span, "crate")?; + // self.crate_name = Some(name_path); + // Ok(()) + // } + + // fn set_unhandled_panic( + // &mut self, + // unhandled_panic: syn::Lit, + // span: Span, + // ) -> Result<(), syn::Error> { + // if self.unhandled_panic.is_some() { + // return Err(syn::Error::new(span, "`unhandled_panic` set multiple times.")); + // } + + // let unhandled_panic = parse_string(unhandled_panic, span, "unhandled_panic")?; + // let unhandled_panic = + // UnhandledPanic::from_str(&unhandled_panic).map_err(|err| syn::Error::new(span, + // err))?; self.unhandled_panic = Some((unhandled_panic, span)); + // Ok(()) + // } + + // fn macro_name(&self) -> &'static str { + // if self.is_test { + // "tokio::test" + // } else { + // "tokio::main" + // } + // } + + // fn build(&self) -> Result { + // use RuntimeFlavor as F; + + // let flavor = self.flavor.unwrap_or(self.default_flavor); + // let worker_threads = match (flavor, self.worker_threads) { + // (F::CurrentThread, Some((_, worker_threads_span))) => { + // let msg = format!( + // "The `worker_threads` option requires the `multi_thread` runtime flavor. Use + // `#[{}(flavor = \"multi_thread\")]`", self.macro_name(), + // ); + // return Err(syn::Error::new(worker_threads_span, msg)); + // } + // (F::CurrentThread, None) => None, + // (F::Threaded, worker_threads) if self.rt_multi_thread_available => { + // worker_threads.map(|(val, _span)| val) + // } + // (F::Threaded, _) => { + // let msg = if self.flavor.is_none() { + // "The default runtime flavor is `multi_thread`, but the `rt-multi-thread` + // feature is disabled." } else { + // "The runtime flavor `multi_thread` requires the `rt-multi-thread` feature." + // }; + // return Err(syn::Error::new(Span::call_site(), msg)); + // } + // }; + + // let start_paused = match (flavor, self.start_paused) { + // (F::Threaded, Some((_, start_paused_span))) => { + // let msg = format!( + // "The `start_paused` option requires the `current_thread` runtime flavor. Use + // `#[{}(flavor = \"current_thread\")]`", self.macro_name(), + // ); + // return Err(syn::Error::new(start_paused_span, msg)); + // } + // (F::CurrentThread, Some((start_paused, _))) => Some(start_paused), + // (_, None) => None, + // }; + + // let unhandled_panic = match (flavor, self.unhandled_panic) { + // (F::Threaded, Some((_, unhandled_panic_span))) => { + // let msg = format!( + // "The `unhandled_panic` option requires the `current_thread` runtime flavor. + // Use `#[{}(flavor = \"current_thread\")]`", self.macro_name(), + // ); + // return Err(syn::Error::new(unhandled_panic_span, msg)); + // } + // (F::CurrentThread, Some((unhandled_panic, _))) => Some(unhandled_panic), + // (_, None) => None, + // }; + + // Ok(FinalConfig { + // crate_name: self.crate_name.clone(), + // flavor, + // worker_threads, + // start_paused, + // unhandled_panic, + // }) + // } +} diff --git a/crates/katana/runner/macro/src/entry.rs b/crates/katana/runner/macro/src/entry.rs new file mode 100644 index 0000000000..4efcd15dfe --- /dev/null +++ b/crates/katana/runner/macro/src/entry.rs @@ -0,0 +1,125 @@ +use std::str::FromStr; + +use proc_macro2::TokenStream; +use quote::quote; +use strum_macros::{AsRefStr, EnumString}; +use syn::parse::Parser; +use syn::ItemFn; + +use crate::config::{Configuration, DEFAULT_ERROR_CONFIG}; + +// Because syn::AttributeArgs does not implement syn::Parse +type AttributeArgs = syn::punctuated::Punctuated; + +#[derive(EnumString, AsRefStr)] +#[strum(serialize_all = "snake_case")] +enum RunnerArg { + BlockTime, + Fee, + Validation, + ChainId, + Accounts, + DbDir, + Binary, + Output, + Genesis, +} + +pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + + let input: ItemFn = match syn::parse2(item.clone()) { + Ok(it) => it, + Err(e) => return token_stream_with_error(item, e), + }; + + // parse the attribute arguments + let config = AttributeArgs::parse_terminated + .parse2(args.into()) + .and_then(|args| build_config(&input, args)); + + match config { + Ok(config) => todo!(), + Err(e) => token_stream_with_error(parse_knobs(input, true, DEFAULT_ERROR_CONFIG), e), + } +} + +fn build_config(input: &ItemFn, args: AttributeArgs) -> Result { + if input.sig.asyncness.is_none() { + let msg = "the `async` keyword is missing from the function declaration"; + return Err(syn::Error::new_spanned(input.sig.fn_token, msg)); + } + + // let mut config = Configuration::new(is_test, rt_multi_thread); + // let macro_name = config.macro_name(); + + for arg in args { + match arg { + syn::Meta::NameValue(namevalue) => { + let ident = namevalue + .path + .get_ident() + .ok_or_else(|| { + syn::Error::new_spanned(&namevalue, "Must have specified ident") + })? + .to_string() + .to_lowercase(); + + // the value of the attribute + let lit = match &namevalue.value { + syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit, + expr => return Err(syn::Error::new_spanned(expr, "Must be a literal")), + }; + + // the ident of the attribute + let ident = ident.as_str(); + let arg = RunnerArg::from_str(ident); + + match arg { + Ok(arg) => match arg { + RunnerArg::BlockTime => { + // config.set_worker_threads(lit.clone(), + // syn::spanned::Spanned::span(lit))?; + } + RunnerArg::Fee => {} + RunnerArg::Validation => {} + RunnerArg::Accounts => {} + RunnerArg::ChainId => {} + RunnerArg::DbDir => {} + RunnerArg::Genesis => {} + RunnerArg::Output => {} + RunnerArg::Binary => {} + }, + + Err(_) => { + let msg = format!( + "Unknown attribute {ident} is specified; expected one of: `fee`, \ + `validation`, `accounts`, `chain_id`, `db_dir`, `genesis`, `output`, \ + binary`", + ); + return Err(syn::Error::new_spanned(namevalue, msg)); + } + } + } + + other => { + return Err(syn::Error::new_spanned(other, "Unknown attribute inside the macro")); + } + } + } + + // config.build(); + + todo!() +} + +fn parse_knobs(mut input: ItemFn, is_test: bool, config: Configuration) -> TokenStream { + todo!() +} + +fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { + tokens.extend(error.into_compile_error()); + tokens +} diff --git a/crates/katana/runner/macro/src/lib.rs b/crates/katana/runner/macro/src/lib.rs index 77867a284f..02bf90dc52 100644 --- a/crates/katana/runner/macro/src/lib.rs +++ b/crates/katana/runner/macro/src/lib.rs @@ -1,124 +1,231 @@ +mod config; +mod entry; + use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, parse_quote, Stmt}; - -/// Default runner block interval -const DEFAULT_BLOCK_TIME: u64 = 3000; // 3 seconds - -/// Parses the metadata string into the number of accounts and the block time. -/// -/// # Arguments -/// -/// * `metadata` - The metadata string to parse. The string is expected to be in the format of -/// `n_accounts,executable,block_time` where `block_time` is either a number (time block is ms) or -/// a boolean with `false` to use instand mining, and `true` to use the default block time. -/// -/// # Returns -/// -/// A tuple containing the number of accounts, the path to the katana program and the block time. -fn parse_metadata(metadata: String) -> (u16, Option, Option) { - if metadata.is_empty() { - return (2, None, None); - } - - let args = metadata.split(',').collect::>(); - let n_accounts = if !args.is_empty() { args[0].parse::().unwrap() } else { 1 }; - - // Block time can be `false` to be `None`, or a number to be `Some(block_time_ms)`. - // if set to `true`, we use a default block time. - let block_time = if args.len() >= 2 { - if let Ok(b) = args[1].trim().parse::() { - if !b { None } else { Some(DEFAULT_BLOCK_TIME) } - } else if let Ok(block_time_ms) = args[1].trim().parse::() { - Some(block_time_ms) - } else { - None - } - } else { - None - }; - - let executable = if args.len() >= 3 { - args[2].trim() - } else { - return (2, None, None); - }; - - let executable = executable.replace('"', ""); - - // plus one as the first account is used for deployment - (n_accounts + 1, Some(executable), block_time) -} #[proc_macro_attribute] -pub fn katana_test(metadata: TokenStream, input: TokenStream) -> TokenStream { - let mut test_function = parse_macro_input!(input as syn::ItemFn); - let function_name = test_function.sig.ident.to_string(); - - let (n_accounts, executable, block_time) = parse_metadata(metadata.to_string()); - - let block_time = block_time.map(|b| quote!(Some(#b))).unwrap_or(quote!(None)); - - let program_name = executable.map(|b| quote!(Some(String::from(#b)))).unwrap_or(quote!(None)); - - let header: Stmt = parse_quote! { - let runner = - katana_runner::KatanaRunner::new_with_config( - katana_runner::KatanaRunnerConfig { - program_name: #program_name, - run_name: Some(String::from(#function_name)), - block_time: #block_time, - n_accounts: #n_accounts, - ..Default::default() - } - ) - .expect("failed to start katana"); - }; - - test_function.block.stmts.insert(0, header); - - if test_function.sig.asyncness.is_none() { - TokenStream::from(quote! { - #[test] - #test_function - }) - } else { - TokenStream::from(quote! { - #[tokio::test] - #test_function - }) - } +pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { + entry::test(args.into(), input.into()).into() + + // let mut fee = true; + // let mut accounts = 0; + // let mut chain_id = 1; + // let mut validation = true; + // let mut db_dir = String::new(); + // let mut binary = String::new(); + // let mut output = String::new(); + // let mut genesis = String::new(); + // let mut classes = Vec::new(); + + // for arg in args { + // match arg { + // NestedMeta::Meta(Meta::NameValue(nv)) => { + // let ident = nv.path.get_ident().unwrap().to_string(); + // match ident.as_str() { + // "fee" => { + // if let Lit::Bool(lit) = nv.lit { + // fee = lit.value; + // } + // } + // "accounts" => { + // if let Lit::Int(lit) = nv.lit { + // accounts = lit.base10_parse().unwrap(); + // } + // } + // "chain_id" => { + // if let Lit::Int(lit) = nv.lit { + // chain_id = lit.base10_parse().unwrap(); + // } + // } + // "validation" => { + // if let Lit::Bool(lit) = nv.lit { + // validation = lit.value; + // } + // } + // "db_dir" => { + // if let Lit::Str(lit) = nv.lit { + // db_dir = lit.value(); + // } + // } + // "binary" => { + // if let Lit::Str(lit) = nv.lit { + // binary = lit.value(); + // } + // } + // "output" => { + // if let Lit::Str(lit) = nv.lit { + // output = lit.value(); + // } + // } + // "genesis" => { + // if let Lit::Str(lit) = nv.lit { + // genesis = lit.value(); + // } + // } + // "classes" => { + // if let Lit::Str(lit) = nv.lit { + // classes.push(lit.value()); + // } + // } + // _ => {} + // } + // } + // _ => {} + // } + // } + + // let fn_name = &input_fn.sig.ident; + // let fn_body = &input_fn.block; + + // let expanded = quote! { + // #[tokio::test] + // async fn #fn_name() -> Result<(), Box> { + // let mut node = RunnerCtx::new() + // .with_fee(#fee) + // .with_accounts(#accounts) + // .with_chain_id(#chain_id) + // .with_validation(#validation) + // .with_db_dir(#db_dir) + // .with_binary(#binary) + // .with_output(#output) + // .with_genesis(#genesis); + + // #(node.add_class(#classes);)* + + // async fn inner(node: &mut RunnerCtx) -> Result<(), Box> { + // #fn_body + // } + + // inner(&mut node).await + // } + // }; + + // TokenStream::from(expanded) } -#[proc_macro] // Needed because the main macro doesn't work with proptest -pub fn runner(metadata: TokenStream) -> TokenStream { - let metadata = metadata.to_string(); - let mut args = metadata.split(',').collect::>(); - let function_name = args.remove(0); - - let (n_accounts, executable, block_time) = parse_metadata(args.join(",")); - - let block_time = block_time.map(|b| quote!(Some(#b))).unwrap_or(quote!(None)); - - let program_name = executable.map(|b| quote!(Some(String::from(#b)))).unwrap_or(quote!(None)); - - TokenStream::from(quote! { - lazy_static::lazy_static! { - pub static ref RUNNER: std::sync::Arc = std::sync::Arc::new( - katana_runner::KatanaRunner::new_with_config( - katana_runner::KatanaRunnerConfig { - program_name: #program_name, - run_name: Some(String::from(#function_name)), - block_time: #block_time, - n_accounts: #n_accounts, - ..Default::default() - } - ) - .expect("failed to start katana") - ); - - } - - let runner = &RUNNER; - }) -} +// /// Default runner block interval +// const DEFAULT_BLOCK_TIME: u64 = 3000; // 3 seconds + +// /// Parses the metadata string into the number of accounts and the block time. +// /// +// /// # Arguments +// /// +// /// * `metadata` - The metadata string to parse. The string is expected to be in the format of +// /// `n_accounts,executable,block_time` where `block_time` is either a number (time block is ms) +// or /// a boolean with `false` to use instand mining, and `true` to use the default block time. +// /// +// /// # Returns +// /// +// /// A tuple containing the number of accounts, the path to the katana program and the block time. +// fn parse_metadata(metadata: String) -> (u16, Option, Option) { +// if metadata.is_empty() { +// return (2, None, None); +// } + +// let args = metadata.split(',').collect::>(); +// let n_accounts = if !args.is_empty() { args[0].parse::().unwrap() } else { 1 }; + +// // Block time can be `false` to be `None`, or a number to be `Some(block_time_ms)`. +// // if set to `true`, we use a default block time. +// let block_time = if args.len() >= 2 { +// if let Ok(b) = args[1].trim().parse::() { +// if !b { +// None +// } else { +// Some(DEFAULT_BLOCK_TIME) +// } +// } else if let Ok(block_time_ms) = args[1].trim().parse::() { +// Some(block_time_ms) +// } else { +// None +// } +// } else { +// None +// }; + +// let executable = if args.len() >= 3 { +// args[2].trim() +// } else { +// return (2, None, None); +// }; + +// let executable = executable.replace('"', ""); + +// // plus one as the first account is used for deployment +// (n_accounts + 1, Some(executable), block_time) +// } + +// #[proc_macro_attribute] +// pub fn katana_test(metadata: TokenStream, input: TokenStream) -> TokenStream { +// let mut test_function = parse_macro_input!(input as syn::ItemFn); +// let function_name = test_function.sig.ident.to_string(); + +// let (n_accounts, executable, block_time) = parse_metadata(metadata.to_string()); + +// let block_time = block_time.map(|b| quote!(Some(#b))).unwrap_or(quote!(None)); + +// let program_name = executable.map(|b| +// quote!(Some(String::from(#b)))).unwrap_or(quote!(None)); + +// let header: Stmt = parse_quote! { +// let runner = +// katana_runner::KatanaRunner::new_with_config( +// katana_runner::KatanaRunnerConfig { +// program_name: #program_name, +// run_name: Some(String::from(#function_name)), +// block_time: #block_time, +// n_accounts: #n_accounts, +// ..Default::default() +// } +// ) +// .expect("failed to start katana"); +// }; + +// test_function.block.stmts.insert(0, header); + +// if test_function.sig.asyncness.is_none() { +// TokenStream::from(quote! { +// #[test] +// #test_function +// }) +// } else { +// TokenStream::from(quote! { +// #[tokio::test] +// #test_function +// }) +// } +// } + +// #[proc_macro] // Needed because the main macro doesn't work with proptest +// pub fn runner(metadata: TokenStream) -> TokenStream { +// let metadata = metadata.to_string(); +// let mut args = metadata.split(',').collect::>(); +// let function_name = args.remove(0); + +// let (n_accounts, executable, block_time) = parse_metadata(args.join(",")); + +// let block_time = block_time.map(|b| quote!(Some(#b))).unwrap_or(quote!(None)); + +// let program_name = executable.map(|b| +// quote!(Some(String::from(#b)))).unwrap_or(quote!(None)); + +// TokenStream::from(quote! { +// lazy_static::lazy_static! { +// pub static ref RUNNER: std::sync::Arc = +// std::sync::Arc::new( katana_runner::KatanaRunner::new_with_config( +// katana_runner::KatanaRunnerConfig { +// program_name: #program_name, +// run_name: Some(String::from(#function_name)), +// block_time: #block_time, +// n_accounts: #n_accounts, +// ..Default::default() +// } +// ) +// .expect("failed to start katana") +// ); + +// } + +// let runner = &RUNNER; +// }) +// } diff --git a/crates/katana/runner/macro/tests/macro.rs b/crates/katana/runner/macro/tests/macro.rs index 0e9cd40b44..5f4d2b47f3 100644 --- a/crates/katana/runner/macro/tests/macro.rs +++ b/crates/katana/runner/macro/tests/macro.rs @@ -2,7 +2,7 @@ // user can set the individual fields when using the macro #[katana_runner::test( - flavor = "embedded", // binary | embedded + block_time = 1000, fee = false, accounts = 1, chain_id = 0x12345, @@ -20,8 +20,8 @@ async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { Ok(()) } -// but they could also just pass the whole RunnerCfg struct if they want to allow defining the configuration -// once and reuse it across different tests +// but they could also just pass the whole RunnerCfg struct if they want to allow defining the +// configuration once and reuse it across different tests #[katana_runner::test(RunnerCfg { })] async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { diff --git a/crates/katana/runner/src/lib.rs b/crates/katana/runner/src/lib.rs index 852610f410..b9f580c7da 100644 --- a/crates/katana/runner/src/lib.rs +++ b/crates/katana/runner/src/lib.rs @@ -9,9 +9,10 @@ use std::thread; use anyhow::{Context, Result}; use assert_fs::TempDir; +use katana_node_bindings::{Katana, KatanaInstance}; +pub use katana_runner_macro::{katana_test, runner}; use starknet::accounts::{ExecutionEncoding, SingleOwnerAccount}; -use starknet::core::types::Felt; -use starknet::core::types::{BlockId, BlockTag}; +use starknet::core::types::{BlockId, BlockTag, Felt}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use starknet::signers::LocalWallet; @@ -19,9 +20,6 @@ use tokio::sync::Mutex; use url::Url; use utils::find_free_port; -use katana_node_bindings::{Katana, KatanaInstance}; -pub use katana_runner_macro::{katana_test, runner}; - #[derive(Debug)] pub struct KatanaRunner { instance: KatanaInstance, @@ -233,11 +231,7 @@ impl KatanaRunner { /// Determines the default program path for the katana runner based on the KATANA_RUNNER_BIN /// environment variable. If not set, try to to use katana from the PATH. fn determine_default_program_path() -> String { - if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { - bin - } else { - "katana".to_string() - } + if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { bin } else { "katana".to_string() } } #[cfg(test)] From 2f760dcd83c0697f64f64f8de7e251acfc957918 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Fri, 20 Sep 2024 20:43:47 -0400 Subject: [PATCH 03/12] parse config values from macro --- crates/katana/runner/macro/src/config.rs | 260 ++++++++++------------ crates/katana/runner/macro/src/entry.rs | 62 +++--- crates/katana/runner/macro/src/lib.rs | 228 +------------------ crates/katana/runner/macro/src/parse.rs | 268 +++++++++++++++++++++++ 4 files changed, 420 insertions(+), 398 deletions(-) create mode 100644 crates/katana/runner/macro/src/parse.rs diff --git a/crates/katana/runner/macro/src/config.rs b/crates/katana/runner/macro/src/config.rs index da64ec5cb1..cc3664eb5d 100644 --- a/crates/katana/runner/macro/src/config.rs +++ b/crates/katana/runner/macro/src/config.rs @@ -1,162 +1,138 @@ -use std::path::PathBuf; +use std::str::FromStr; +use crate::parse::{parse_bool, parse_int, parse_path, parse_string}; pub const DEFAULT_ERROR_CONFIG: Configuration = Configuration::new(true); +pub enum RunnerFlavor { + Embedded, + Binary, +} + pub struct Configuration { dev: bool, - fee: bool, is_test: bool, - accounts: u16, - validation: bool, - db_dir: Option, - binary: Option, + accounts: Option, + fee: Option, + validation: Option, + db_dir: Option, block_time: Option, - log_path: Option, + log_path: Option, + flavor: Option, } impl Configuration { - const fn new(is_test: bool) -> Self { + pub const fn new(is_test: bool) -> Self { Configuration { is_test, - fee: true, + fee: None, db_dir: None, - binary: None, - accounts: 10, + accounts: None, dev: is_test, + flavor: None, log_path: None, - validation: true, + validation: None, block_time: None, } } - // fn set_flavor(&mut self, runtime: syn::Lit, span: Span) -> Result<(), syn::Error> { - // if self.flavor.is_some() { - // return Err(syn::Error::new(span, "`flavor` set multiple times.")); - // } - - // let runtime_str = parse_string(runtime, span, "flavor")?; - // let runtime = - // RuntimeFlavor::from_str(&runtime_str).map_err(|err| syn::Error::new(span, err))?; - // self.flavor = Some(runtime); - // Ok(()) - // } - - // fn set_worker_threads( - // &mut self, - // worker_threads: syn::Lit, - // span: Span, - // ) -> Result<(), syn::Error> { - // if self.worker_threads.is_some() { - // return Err(syn::Error::new(span, "`worker_threads` set multiple times.")); - // } - - // let worker_threads = parse_int(worker_threads, span, "worker_threads")?; - // if worker_threads == 0 { - // return Err(syn::Error::new(span, "`worker_threads` may not be 0.")); - // } - // self.worker_threads = Some((worker_threads, span)); - // Ok(()) - // } - - // fn set_start_paused(&mut self, start_paused: syn::Lit, span: Span) -> Result<(), syn::Error> - // { if self.start_paused.is_some() { - // return Err(syn::Error::new(span, "`start_paused` set multiple times.")); - // } - - // let start_paused = parse_bool(start_paused, span, "start_paused")?; - // self.start_paused = Some((start_paused, span)); - // Ok(()) - // } - - // fn set_crate_name(&mut self, name: syn::Lit, span: Span) -> Result<(), syn::Error> { - // if self.crate_name.is_some() { - // return Err(syn::Error::new(span, "`crate` set multiple times.")); - // } - // let name_path = parse_path(name, span, "crate")?; - // self.crate_name = Some(name_path); - // Ok(()) - // } - - // fn set_unhandled_panic( - // &mut self, - // unhandled_panic: syn::Lit, - // span: Span, - // ) -> Result<(), syn::Error> { - // if self.unhandled_panic.is_some() { - // return Err(syn::Error::new(span, "`unhandled_panic` set multiple times.")); - // } - - // let unhandled_panic = parse_string(unhandled_panic, span, "unhandled_panic")?; - // let unhandled_panic = - // UnhandledPanic::from_str(&unhandled_panic).map_err(|err| syn::Error::new(span, - // err))?; self.unhandled_panic = Some((unhandled_panic, span)); - // Ok(()) - // } - - // fn macro_name(&self) -> &'static str { - // if self.is_test { - // "tokio::test" - // } else { - // "tokio::main" - // } - // } - - // fn build(&self) -> Result { - // use RuntimeFlavor as F; - - // let flavor = self.flavor.unwrap_or(self.default_flavor); - // let worker_threads = match (flavor, self.worker_threads) { - // (F::CurrentThread, Some((_, worker_threads_span))) => { - // let msg = format!( - // "The `worker_threads` option requires the `multi_thread` runtime flavor. Use - // `#[{}(flavor = \"multi_thread\")]`", self.macro_name(), - // ); - // return Err(syn::Error::new(worker_threads_span, msg)); - // } - // (F::CurrentThread, None) => None, - // (F::Threaded, worker_threads) if self.rt_multi_thread_available => { - // worker_threads.map(|(val, _span)| val) - // } - // (F::Threaded, _) => { - // let msg = if self.flavor.is_none() { - // "The default runtime flavor is `multi_thread`, but the `rt-multi-thread` - // feature is disabled." } else { - // "The runtime flavor `multi_thread` requires the `rt-multi-thread` feature." - // }; - // return Err(syn::Error::new(Span::call_site(), msg)); - // } - // }; - - // let start_paused = match (flavor, self.start_paused) { - // (F::Threaded, Some((_, start_paused_span))) => { - // let msg = format!( - // "The `start_paused` option requires the `current_thread` runtime flavor. Use - // `#[{}(flavor = \"current_thread\")]`", self.macro_name(), - // ); - // return Err(syn::Error::new(start_paused_span, msg)); - // } - // (F::CurrentThread, Some((start_paused, _))) => Some(start_paused), - // (_, None) => None, - // }; - - // let unhandled_panic = match (flavor, self.unhandled_panic) { - // (F::Threaded, Some((_, unhandled_panic_span))) => { - // let msg = format!( - // "The `unhandled_panic` option requires the `current_thread` runtime flavor. - // Use `#[{}(flavor = \"current_thread\")]`", self.macro_name(), - // ); - // return Err(syn::Error::new(unhandled_panic_span, msg)); - // } - // (F::CurrentThread, Some((unhandled_panic, _))) => Some(unhandled_panic), - // (_, None) => None, - // }; - - // Ok(FinalConfig { - // crate_name: self.crate_name.clone(), - // flavor, - // worker_threads, - // start_paused, - // unhandled_panic, - // }) - // } + pub fn set_flavor( + &mut self, + flavor: syn::Lit, + span: proc_macro2::Span, + ) -> Result<(), syn::Error> { + if self.flavor.is_some() { + return Err(syn::Error::new(span, "`flavor` set multiple times.")); + } + + let runner = parse_string(flavor, span, "flavor")?; + let runner = RunnerFlavor::from_str(&runner).map_err(|err| syn::Error::new(span, err))?; + + self.flavor = Some(runner); + Ok(()) + } + + pub fn set_db_dir( + &mut self, + db_dir: syn::Lit, + span: proc_macro2::Span, + ) -> Result<(), syn::Error> { + if self.db_dir.is_some() { + return Err(syn::Error::new(span, "`db_dir` set multiple times.")); + } + + let db_dir = parse_path(db_dir, span, "db_dir")?; + self.db_dir = Some(db_dir); + + Ok(()) + } + + pub fn set_fee(&mut self, fee: syn::Lit, span: proc_macro2::Span) -> Result<(), syn::Error> { + if self.fee.is_some() { + return Err(syn::Error::new(span, "`fee` set multiple times.")); + } + + let fee = parse_bool(fee, span, "fee")?; + self.fee = Some(fee); + + Ok(()) + } + + pub fn set_validation( + &mut self, + validation: syn::Lit, + span: proc_macro2::Span, + ) -> Result<(), syn::Error> { + if self.validation.is_some() { + return Err(syn::Error::new(span, "`validation` set multiple times.")); + } + + let validation = parse_bool(validation, span, "validation")?; + self.validation = Some(validation); + + Ok(()) + } + + pub fn set_block_time( + &mut self, + block_time: syn::Lit, + span: proc_macro2::Span, + ) -> Result<(), syn::Error> { + if self.block_time.is_some() { + return Err(syn::Error::new(span, "`block_time` set multiple times.")); + } + + let block_time = parse_int(block_time, span, "block_time")? as u64; + self.block_time = Some(block_time); + + Ok(()) + } + + pub fn set_accounts( + &mut self, + accounts: syn::Lit, + span: proc_macro2::Span, + ) -> Result<(), syn::Error> { + if self.accounts.is_some() { + return Err(syn::Error::new(span, "`accounts` set multiple times.")); + } + + let accounts = parse_int(accounts, span, "accounts")? as u16; + self.accounts = Some(accounts); + + Ok(()) + } +} + +impl std::str::FromStr for RunnerFlavor { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "binary" => Ok(RunnerFlavor::Binary), + "embedded" => Ok(RunnerFlavor::Embedded), + _ => Err(format!( + "No such runner flavor `{s}`. The runner flavors are `embedded` and `binary`." + )), + } + } } diff --git a/crates/katana/runner/macro/src/entry.rs b/crates/katana/runner/macro/src/entry.rs index 4efcd15dfe..2a788881f8 100644 --- a/crates/katana/runner/macro/src/entry.rs +++ b/crates/katana/runner/macro/src/entry.rs @@ -1,12 +1,12 @@ use std::str::FromStr; use proc_macro2::TokenStream; -use quote::quote; use strum_macros::{AsRefStr, EnumString}; use syn::parse::Parser; -use syn::ItemFn; +use syn::spanned::Spanned; use crate::config::{Configuration, DEFAULT_ERROR_CONFIG}; +use crate::parse::parse_knobs; // Because syn::AttributeArgs does not implement syn::Parse type AttributeArgs = syn::punctuated::Punctuated; @@ -17,12 +17,9 @@ enum RunnerArg { BlockTime, Fee, Validation, - ChainId, Accounts, DbDir, - Binary, - Output, - Genesis, + Flavor, } pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { @@ -30,7 +27,7 @@ pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { // to the expected output as possible. This helps out IDEs such that completions and other // related features keep working. - let input: ItemFn = match syn::parse2(item.clone()) { + let input: crate::parse::ItemFn = match syn::parse2(item.clone()) { Ok(it) => it, Err(e) => return token_stream_with_error(item, e), }; @@ -38,22 +35,25 @@ pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { // parse the attribute arguments let config = AttributeArgs::parse_terminated .parse2(args.into()) - .and_then(|args| build_config(&input, args)); + .and_then(|args| build_config(&input, args, true)); match config { - Ok(config) => todo!(), - Err(e) => token_stream_with_error(parse_knobs(input, true, DEFAULT_ERROR_CONFIG), e), + Ok(config) => parse_knobs(input, true, config), + Err(e) => token_stream_with_error(parse_knobs(input, false, DEFAULT_ERROR_CONFIG), e), } } -fn build_config(input: &ItemFn, args: AttributeArgs) -> Result { +fn build_config( + input: &crate::parse::ItemFn, + args: AttributeArgs, + is_test: bool, +) -> Result { if input.sig.asyncness.is_none() { let msg = "the `async` keyword is missing from the function declaration"; return Err(syn::Error::new_spanned(input.sig.fn_token, msg)); } - // let mut config = Configuration::new(is_test, rt_multi_thread); - // let macro_name = config.macro_name(); + let mut config = Configuration::new(is_test); for arg in args { match arg { @@ -79,25 +79,27 @@ fn build_config(input: &ItemFn, args: AttributeArgs) -> Result match arg { + RunnerArg::Flavor => { + config.set_flavor(lit.clone(), Spanned::span(lit))?; + } RunnerArg::BlockTime => { - // config.set_worker_threads(lit.clone(), - // syn::spanned::Spanned::span(lit))?; + config.set_block_time(lit.clone(), Spanned::span(lit))? + } + RunnerArg::Validation => { + config.set_validation(lit.clone(), Spanned::span(lit))? } - RunnerArg::Fee => {} - RunnerArg::Validation => {} - RunnerArg::Accounts => {} - RunnerArg::ChainId => {} - RunnerArg::DbDir => {} - RunnerArg::Genesis => {} - RunnerArg::Output => {} - RunnerArg::Binary => {} + RunnerArg::Accounts => { + config.set_accounts(lit.clone(), Spanned::span(lit))?; + } + + RunnerArg::Fee => config.set_fee(lit.clone(), Spanned::span(lit))?, + RunnerArg::DbDir => config.set_db_dir(lit.clone(), Spanned::span(lit))?, }, Err(_) => { let msg = format!( - "Unknown attribute {ident} is specified; expected one of: `fee`, \ - `validation`, `accounts`, `chain_id`, `db_dir`, `genesis`, `output`, \ - binary`", + "Unknown attribute {ident} is specified; expected one of: `flavor`, \ + `fee`, `validation`, `accounts`, `db_dir`, `block_time`", ); return Err(syn::Error::new_spanned(namevalue, msg)); } @@ -110,13 +112,7 @@ fn build_config(input: &ItemFn, args: AttributeArgs) -> Result TokenStream { - todo!() + Ok(config) } fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { diff --git a/crates/katana/runner/macro/src/lib.rs b/crates/katana/runner/macro/src/lib.rs index 02bf90dc52..6f7ddf720c 100644 --- a/crates/katana/runner/macro/src/lib.rs +++ b/crates/katana/runner/macro/src/lib.rs @@ -1,231 +1,13 @@ -mod config; +//! Implementation of the proc macro in this module is highly adapted from `tokio-macros` crate. +//! `tokio-macro`: https://docs.rs/tokio-macros/2.4.0/tokio_macros/ + +pub(crate) mod config; mod entry; +pub(crate) mod parse; use proc_macro::TokenStream; #[proc_macro_attribute] pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { entry::test(args.into(), input.into()).into() - - // let mut fee = true; - // let mut accounts = 0; - // let mut chain_id = 1; - // let mut validation = true; - // let mut db_dir = String::new(); - // let mut binary = String::new(); - // let mut output = String::new(); - // let mut genesis = String::new(); - // let mut classes = Vec::new(); - - // for arg in args { - // match arg { - // NestedMeta::Meta(Meta::NameValue(nv)) => { - // let ident = nv.path.get_ident().unwrap().to_string(); - // match ident.as_str() { - // "fee" => { - // if let Lit::Bool(lit) = nv.lit { - // fee = lit.value; - // } - // } - // "accounts" => { - // if let Lit::Int(lit) = nv.lit { - // accounts = lit.base10_parse().unwrap(); - // } - // } - // "chain_id" => { - // if let Lit::Int(lit) = nv.lit { - // chain_id = lit.base10_parse().unwrap(); - // } - // } - // "validation" => { - // if let Lit::Bool(lit) = nv.lit { - // validation = lit.value; - // } - // } - // "db_dir" => { - // if let Lit::Str(lit) = nv.lit { - // db_dir = lit.value(); - // } - // } - // "binary" => { - // if let Lit::Str(lit) = nv.lit { - // binary = lit.value(); - // } - // } - // "output" => { - // if let Lit::Str(lit) = nv.lit { - // output = lit.value(); - // } - // } - // "genesis" => { - // if let Lit::Str(lit) = nv.lit { - // genesis = lit.value(); - // } - // } - // "classes" => { - // if let Lit::Str(lit) = nv.lit { - // classes.push(lit.value()); - // } - // } - // _ => {} - // } - // } - // _ => {} - // } - // } - - // let fn_name = &input_fn.sig.ident; - // let fn_body = &input_fn.block; - - // let expanded = quote! { - // #[tokio::test] - // async fn #fn_name() -> Result<(), Box> { - // let mut node = RunnerCtx::new() - // .with_fee(#fee) - // .with_accounts(#accounts) - // .with_chain_id(#chain_id) - // .with_validation(#validation) - // .with_db_dir(#db_dir) - // .with_binary(#binary) - // .with_output(#output) - // .with_genesis(#genesis); - - // #(node.add_class(#classes);)* - - // async fn inner(node: &mut RunnerCtx) -> Result<(), Box> { - // #fn_body - // } - - // inner(&mut node).await - // } - // }; - - // TokenStream::from(expanded) } - -// /// Default runner block interval -// const DEFAULT_BLOCK_TIME: u64 = 3000; // 3 seconds - -// /// Parses the metadata string into the number of accounts and the block time. -// /// -// /// # Arguments -// /// -// /// * `metadata` - The metadata string to parse. The string is expected to be in the format of -// /// `n_accounts,executable,block_time` where `block_time` is either a number (time block is ms) -// or /// a boolean with `false` to use instand mining, and `true` to use the default block time. -// /// -// /// # Returns -// /// -// /// A tuple containing the number of accounts, the path to the katana program and the block time. -// fn parse_metadata(metadata: String) -> (u16, Option, Option) { -// if metadata.is_empty() { -// return (2, None, None); -// } - -// let args = metadata.split(',').collect::>(); -// let n_accounts = if !args.is_empty() { args[0].parse::().unwrap() } else { 1 }; - -// // Block time can be `false` to be `None`, or a number to be `Some(block_time_ms)`. -// // if set to `true`, we use a default block time. -// let block_time = if args.len() >= 2 { -// if let Ok(b) = args[1].trim().parse::() { -// if !b { -// None -// } else { -// Some(DEFAULT_BLOCK_TIME) -// } -// } else if let Ok(block_time_ms) = args[1].trim().parse::() { -// Some(block_time_ms) -// } else { -// None -// } -// } else { -// None -// }; - -// let executable = if args.len() >= 3 { -// args[2].trim() -// } else { -// return (2, None, None); -// }; - -// let executable = executable.replace('"', ""); - -// // plus one as the first account is used for deployment -// (n_accounts + 1, Some(executable), block_time) -// } - -// #[proc_macro_attribute] -// pub fn katana_test(metadata: TokenStream, input: TokenStream) -> TokenStream { -// let mut test_function = parse_macro_input!(input as syn::ItemFn); -// let function_name = test_function.sig.ident.to_string(); - -// let (n_accounts, executable, block_time) = parse_metadata(metadata.to_string()); - -// let block_time = block_time.map(|b| quote!(Some(#b))).unwrap_or(quote!(None)); - -// let program_name = executable.map(|b| -// quote!(Some(String::from(#b)))).unwrap_or(quote!(None)); - -// let header: Stmt = parse_quote! { -// let runner = -// katana_runner::KatanaRunner::new_with_config( -// katana_runner::KatanaRunnerConfig { -// program_name: #program_name, -// run_name: Some(String::from(#function_name)), -// block_time: #block_time, -// n_accounts: #n_accounts, -// ..Default::default() -// } -// ) -// .expect("failed to start katana"); -// }; - -// test_function.block.stmts.insert(0, header); - -// if test_function.sig.asyncness.is_none() { -// TokenStream::from(quote! { -// #[test] -// #test_function -// }) -// } else { -// TokenStream::from(quote! { -// #[tokio::test] -// #test_function -// }) -// } -// } - -// #[proc_macro] // Needed because the main macro doesn't work with proptest -// pub fn runner(metadata: TokenStream) -> TokenStream { -// let metadata = metadata.to_string(); -// let mut args = metadata.split(',').collect::>(); -// let function_name = args.remove(0); - -// let (n_accounts, executable, block_time) = parse_metadata(args.join(",")); - -// let block_time = block_time.map(|b| quote!(Some(#b))).unwrap_or(quote!(None)); - -// let program_name = executable.map(|b| -// quote!(Some(String::from(#b)))).unwrap_or(quote!(None)); - -// TokenStream::from(quote! { -// lazy_static::lazy_static! { -// pub static ref RUNNER: std::sync::Arc = -// std::sync::Arc::new( katana_runner::KatanaRunner::new_with_config( -// katana_runner::KatanaRunnerConfig { -// program_name: #program_name, -// run_name: Some(String::from(#function_name)), -// block_time: #block_time, -// n_accounts: #n_accounts, -// ..Default::default() -// } -// ) -// .expect("failed to start katana") -// ); - -// } - -// let runner = &RUNNER; -// }) -// } diff --git a/crates/katana/runner/macro/src/parse.rs b/crates/katana/runner/macro/src/parse.rs new file mode 100644 index 0000000000..0e7db25167 --- /dev/null +++ b/crates/katana/runner/macro/src/parse.rs @@ -0,0 +1,268 @@ +use proc_macro2::{Span, TokenStream, TokenTree}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::parse::{Parse, ParseStream}; +use syn::{braced, Attribute, Ident, Signature, Visibility}; + +use crate::config::{Configuration, RunnerFlavor}; + +pub struct ItemFn { + pub outer_attrs: Vec, + pub vis: Visibility, + pub sig: Signature, + pub brace_token: syn::token::Brace, + pub inner_attrs: Vec, + pub stmts: Vec, +} + +impl ItemFn { + /// Access all attributes of the function item. + fn attrs(&self) -> impl Iterator { + self.outer_attrs.iter().chain(self.inner_attrs.iter()) + } + + /// Get the body of the function item in a manner so that it can be + /// conveniently used with the `quote!` macro. + fn body(&self) -> Body<'_> { + Body { brace_token: self.brace_token, stmts: &self.stmts } + } + + /// Convert our local function item into a token stream. + fn into_tokens( + self, + generated_attrs: proc_macro2::TokenStream, + body: proc_macro2::TokenStream, + last_block: proc_macro2::TokenStream, + ) -> TokenStream { + let mut tokens = proc_macro2::TokenStream::new(); + // Outer attributes are simply streamed as-is. + for attr in self.outer_attrs { + attr.to_tokens(&mut tokens); + } + + // Inner attributes require extra care, since they're not supported on + // blocks (which is what we're expanded into) we instead lift them + // outside of the function. This matches the behavior of `syn`. + for mut attr in self.inner_attrs { + attr.style = syn::AttrStyle::Outer; + attr.to_tokens(&mut tokens); + } + + // Add generated macros at the end, so macros processed later are aware of them. + generated_attrs.to_tokens(&mut tokens); + + self.vis.to_tokens(&mut tokens); + self.sig.to_tokens(&mut tokens); + + self.brace_token.surround(&mut tokens, |tokens| { + body.to_tokens(tokens); + last_block.to_tokens(tokens); + }); + + tokens + } +} + +impl Parse for ItemFn { + #[inline] + fn parse(input: ParseStream<'_>) -> syn::Result { + // This parse implementation has been largely lifted from `syn`, with + // the exception of: + // * We don't have access to the plumbing necessary to parse inner attributes in-place. + // * We do our own statements parsing to avoid recursively parsing entire statements and + // only look for the parts we're interested in. + + let outer_attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let sig: Signature = input.parse()?; + + let content; + let brace_token = braced!(content in input); + let inner_attrs = Attribute::parse_inner(&content)?; + + let mut buf = proc_macro2::TokenStream::new(); + let mut stmts = Vec::new(); + + while !content.is_empty() { + if let Some(semi) = content.parse::>()? { + semi.to_tokens(&mut buf); + stmts.push(buf); + buf = proc_macro2::TokenStream::new(); + continue; + } + + // Parse a single token tree and extend our current buffer with it. + // This avoids parsing the entire content of the sub-tree. + buf.extend([content.parse::()?]); + } + + if !buf.is_empty() { + stmts.push(buf); + } + + Ok(Self { outer_attrs, vis, sig, brace_token, inner_attrs, stmts }) + } +} + +struct Body<'a> { + brace_token: syn::token::Brace, + // Statements, with terminating `;`. + stmts: &'a [TokenStream], +} + +impl ToTokens for Body<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.brace_token.surround(tokens, |tokens| { + for stmt in self.stmts { + stmt.to_tokens(tokens); + } + }); + } +} + +pub fn parse_knobs(mut input: ItemFn, is_test: bool, config: Configuration) -> TokenStream { + // input.sig.asyncness = None; + + // // If type mismatch occurs, the current rustc points to the last statement. + // let (last_stmt_start_span, last_stmt_end_span) = { + // let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter(); + + // // `Span` on stable Rust has a limitation that only points to the first + // // token, not the whole tokens. We can work around this limitation by + // // using the first/last span of the tokens like + // // `syn::Error::new_spanned` does. + // let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); + // let end = last_stmt.last().map_or(start, |t| t.span()); + // (start, end) + // }; + + // let crate_path = config + // .crate_name + // .map(ToTokens::into_token_stream) + // .unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream()); + + // let mut rt = match config.flavor { + // RunnerFlavor::Binary => quote_spanned! {last_stmt_start_span=> + // #crate_path::runtime::Builder::new_current_thread() + // }, + // RunnerFlavor::Embedded => quote_spanned! {last_stmt_start_span=> + // #crate_path::runtime::Builder::new_multi_thread() + // }, + // }; + // if let Some(v) = config.worker_threads { + // rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) }; + // } + // if let Some(v) = config.start_paused { + // rt = quote_spanned! {last_stmt_start_span=> #rt.start_paused(#v) }; + // } + // if let Some(v) = config.unhandled_panic { + // let unhandled_panic = v.into_tokens(&crate_path); + // rt = quote_spanned! {last_stmt_start_span=> #rt.unhandled_panic(#unhandled_panic) }; + // } + + // let generated_attrs = if is_test { + // quote! { + // #[::core::prelude::v1::test] + // } + // } else { + // quote! {} + // }; + + // let body_ident = quote! { body }; + // let last_block = quote_spanned! {last_stmt_end_span=> + // #[allow(clippy::expect_used, clippy::diverging_sub_expression)] + // { + // return #rt + // .enable_all() + // .build() + // .expect("Failed building the Runtime") + // .block_on(#body_ident); + // } + // }; + + // let body = input.body(); + + // // For test functions pin the body to the stack and use `Pin<&mut dyn + // // Future>` to reduce the amount of `Runtime::block_on` (and related + // // functions) copies we generate during compilation due to the generic + // // parameter `F` (the future to block on). This could have an impact on + // // performance, but because it's only for testing it's unlikely to be very + // // large. + // // + // // We don't do this for the main function as it should only be used once so + // // there will be no benefit. + // let body = if is_test { + // let output_type = match &input.sig.output { + // // For functions with no return value syn doesn't print anything, + // // but that doesn't work as `Output` for our boxed `Future`, so + // // default to `()` (the same type as the function output). + // syn::ReturnType::Default => quote! { () }, + // syn::ReturnType::Type(_, ret_type) => quote! { #ret_type }, + // }; + // quote! { + // let body = async #body; + // #crate_path::pin!(body); + // let body: ::core::pin::Pin<&mut dyn ::core::future::Future> = + // body; } + // } else { + // quote! { + // let body = async #body; + // } + // }; + + // input.into_tokens(generated_attrs, body, last_block) + + todo!() +} + +pub fn parse_string( + int: syn::Lit, + span: proc_macro2::Span, + field: &str, +) -> Result { + match int { + syn::Lit::Str(s) => Ok(s.value()), + syn::Lit::Verbatim(s) => Ok(s.to_string()), + _ => Err(syn::Error::new(span, format!("Failed to parse value of `{field}` as string."))), + } +} + +pub fn parse_path( + lit: syn::Lit, + span: proc_macro2::Span, + field: &str, +) -> Result { + match lit { + syn::Lit::Str(s) => { + let err = syn::Error::new( + span, + format!("Failed to parse value of `{field}` as path: \"{}\"", s.value()), + ); + s.parse::().map_err(|_| err.clone()) + } + _ => Err(syn::Error::new(span, format!("Failed to parse value of `{}` as path.", field))), + } +} + +pub fn parse_bool( + bool: syn::Lit, + span: proc_macro2::Span, + field: &str, +) -> Result { + match bool { + syn::Lit::Bool(b) => Ok(b.value), + _ => Err(syn::Error::new(span, format!("Failed to parse value of `{field}` as bool."))), + } +} + +pub fn parse_int(int: syn::Lit, span: proc_macro2::Span, field: &str) -> Result { + match int { + syn::Lit::Int(lit) => match lit.base10_parse::() { + Ok(value) => Ok(value), + Err(e) => Err(syn::Error::new( + span, + format!("Failed to parse value of `{field}` as integer: {e}"), + )), + }, + _ => Err(syn::Error::new(span, format!("Failed to parse value of `{field}` as integer."))), + } +} From 2a3b360e1632469eb32561d9300da7d932b99e82 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sat, 21 Sep 2024 21:25:10 -0400 Subject: [PATCH 04/12] working edition --- Cargo.lock | 2 - crates/katana/runner/macro/Cargo.toml | 2 - crates/katana/runner/macro/src/config.rs | 143 +++++++++++----- crates/katana/runner/macro/src/entry.rs | 91 +--------- crates/katana/runner/macro/src/parse.rs | 194 +++++++++++----------- crates/katana/runner/macro/tests/macro.rs | 114 ++++++------- crates/katana/runner/src/lib.rs | 10 +- crates/katana/runner/tests/runner.rs | 41 +++-- 8 files changed, 292 insertions(+), 305 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e306815d30..ce4063f100 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8192,8 +8192,6 @@ dependencies = [ "proc-macro2", "quote", "starknet 0.12.0", - "strum 0.25.0", - "strum_macros 0.25.3", "syn 2.0.77", ] diff --git a/crates/katana/runner/macro/Cargo.toml b/crates/katana/runner/macro/Cargo.toml index b7789d2f33..45c0ce5064 100644 --- a/crates/katana/runner/macro/Cargo.toml +++ b/crates/katana/runner/macro/Cargo.toml @@ -10,8 +10,6 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.86" quote = "1.0" -strum.workspace = true -strum_macros.workspace = true syn = { version = "2.0", features = [ "fold", "full" ] } [dev-dependencies] diff --git a/crates/katana/runner/macro/src/config.rs b/crates/katana/runner/macro/src/config.rs index cc3664eb5d..e5218540d3 100644 --- a/crates/katana/runner/macro/src/config.rs +++ b/crates/katana/runner/macro/src/config.rs @@ -1,72 +1,67 @@ use std::str::FromStr; +use syn::spanned::Spanned; + use crate::parse::{parse_bool, parse_int, parse_path, parse_string}; -pub const DEFAULT_ERROR_CONFIG: Configuration = Configuration::new(true); -pub enum RunnerFlavor { - Embedded, - Binary, -} +pub const DEFAULT_ERROR_CONFIG: Configuration = Configuration::new(false); +/// Partial configuration for extracting the individual configuration values from the attribute +/// arguments. pub struct Configuration { - dev: bool, - is_test: bool, - accounts: Option, - fee: Option, - validation: Option, - db_dir: Option, - block_time: Option, - log_path: Option, - flavor: Option, + pub crate_name: Option, + pub dev: bool, + pub is_test: bool, + pub accounts: Option, + pub fee: Option, + pub validation: Option, + pub db_dir: Option, + pub block_time: Option, + pub log_path: Option, } impl Configuration { - pub const fn new(is_test: bool) -> Self { - Configuration { + const fn new(is_test: bool) -> Self { + Self { is_test, fee: None, db_dir: None, accounts: None, dev: is_test, - flavor: None, log_path: None, validation: None, block_time: None, + crate_name: None, } } - pub fn set_flavor( + fn set_crate_name( &mut self, - flavor: syn::Lit, + name: syn::Lit, span: proc_macro2::Span, ) -> Result<(), syn::Error> { - if self.flavor.is_some() { - return Err(syn::Error::new(span, "`flavor` set multiple times.")); + if self.crate_name.is_some() { + return Err(syn::Error::new(span, "`crate` set multiple times.")); } - let runner = parse_string(flavor, span, "flavor")?; - let runner = RunnerFlavor::from_str(&runner).map_err(|err| syn::Error::new(span, err))?; + let name_path = parse_path(name, span, "crate")?; + self.crate_name = Some(name_path); - self.flavor = Some(runner); Ok(()) } - pub fn set_db_dir( - &mut self, - db_dir: syn::Lit, - span: proc_macro2::Span, - ) -> Result<(), syn::Error> { + fn set_db_dir(&mut self, db_dir: syn::Lit, span: proc_macro2::Span) -> Result<(), syn::Error> { if self.db_dir.is_some() { return Err(syn::Error::new(span, "`db_dir` set multiple times.")); } - let db_dir = parse_path(db_dir, span, "db_dir")?; + let db_dir = parse_string(db_dir, span, "db_dir")?; self.db_dir = Some(db_dir); Ok(()) } - pub fn set_fee(&mut self, fee: syn::Lit, span: proc_macro2::Span) -> Result<(), syn::Error> { + fn set_fee(&mut self, fee: syn::Lit, span: proc_macro2::Span) -> Result<(), syn::Error> { if self.fee.is_some() { return Err(syn::Error::new(span, "`fee` set multiple times.")); } @@ -77,7 +72,7 @@ impl Configuration { Ok(()) } - pub fn set_validation( + fn set_validation( &mut self, validation: syn::Lit, span: proc_macro2::Span, @@ -92,7 +87,7 @@ impl Configuration { Ok(()) } - pub fn set_block_time( + fn set_block_time( &mut self, block_time: syn::Lit, span: proc_macro2::Span, @@ -107,7 +102,7 @@ impl Configuration { Ok(()) } - pub fn set_accounts( + fn set_accounts( &mut self, accounts: syn::Lit, span: proc_macro2::Span, @@ -123,16 +118,88 @@ impl Configuration { } } -impl std::str::FromStr for RunnerFlavor { +enum RunnerArg { + BlockTime, + Fee, + Validation, + Accounts, + DbDir, +} + +impl std::str::FromStr for RunnerArg { type Err = String; fn from_str(s: &str) -> Result { match s { - "binary" => Ok(RunnerFlavor::Binary), - "embedded" => Ok(RunnerFlavor::Embedded), + "block_time" => Ok(RunnerArg::BlockTime), + "fee" => Ok(RunnerArg::Fee), + "validation" => Ok(RunnerArg::Validation), + "accounts" => Ok(RunnerArg::Accounts), + "db_dir" => Ok(RunnerArg::DbDir), _ => Err(format!( - "No such runner flavor `{s}`. The runner flavors are `embedded` and `binary`." + "Unknown attribute {s} is specified; expected one of: `fee`, `validation`, \ + `accounts`, `db_dir`, `block_time`", )), } } } + +pub fn build_config( + input: &crate::parse::ItemFn, + args: crate::entry::AttributeArgs, + is_test: bool, +) -> Result { + // if input.sig.asyncness.is_none() { + // let msg = "the `async` keyword is missing from the function declaration"; + // return Err(syn::Error::new_spanned(input.sig.fn_token, msg)); + // } + + let mut config = Configuration::new(is_test); + + for arg in args { + match arg { + syn::Meta::NameValue(namevalue) => { + let ident = namevalue + .path + .get_ident() + .ok_or_else(|| { + syn::Error::new_spanned(&namevalue, "Must have specified ident") + })? + .to_string() + .to_lowercase(); + + // the value of the attribute + let lit = match &namevalue.value { + syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit, + expr => return Err(syn::Error::new_spanned(expr, "Must be a literal")), + }; + + // the ident of the attribute + let ident = ident.as_str(); + let arg = RunnerArg::from_str(ident) + .map_err(|err| syn::Error::new_spanned(&namevalue, err))?; + + match arg { + RunnerArg::BlockTime => { + config.set_block_time(lit.clone(), Spanned::span(lit))? + } + RunnerArg::Validation => { + config.set_validation(lit.clone(), Spanned::span(lit))? + } + RunnerArg::Accounts => { + config.set_accounts(lit.clone(), Spanned::span(lit))?; + } + + RunnerArg::Fee => config.set_fee(lit.clone(), Spanned::span(lit))?, + RunnerArg::DbDir => config.set_db_dir(lit.clone(), Spanned::span(lit))?, + } + } + + other => { + return Err(syn::Error::new_spanned(other, "Unknown attribute inside the macro")); + } + } + } + + Ok(config) +} diff --git a/crates/katana/runner/macro/src/entry.rs b/crates/katana/runner/macro/src/entry.rs index 2a788881f8..8332d85c4b 100644 --- a/crates/katana/runner/macro/src/entry.rs +++ b/crates/katana/runner/macro/src/entry.rs @@ -1,26 +1,11 @@ -use std::str::FromStr; - use proc_macro2::TokenStream; -use strum_macros::{AsRefStr, EnumString}; use syn::parse::Parser; -use syn::spanned::Spanned; -use crate::config::{Configuration, DEFAULT_ERROR_CONFIG}; +use crate::config::{build_config, DEFAULT_ERROR_CONFIG}; use crate::parse::parse_knobs; // Because syn::AttributeArgs does not implement syn::Parse -type AttributeArgs = syn::punctuated::Punctuated; - -#[derive(EnumString, AsRefStr)] -#[strum(serialize_all = "snake_case")] -enum RunnerArg { - BlockTime, - Fee, - Validation, - Accounts, - DbDir, - Flavor, -} +pub type AttributeArgs = syn::punctuated::Punctuated; pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { // If any of the steps for this macro fail, we still want to expand to an item that is as close @@ -43,78 +28,6 @@ pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { } } -fn build_config( - input: &crate::parse::ItemFn, - args: AttributeArgs, - is_test: bool, -) -> Result { - if input.sig.asyncness.is_none() { - let msg = "the `async` keyword is missing from the function declaration"; - return Err(syn::Error::new_spanned(input.sig.fn_token, msg)); - } - - let mut config = Configuration::new(is_test); - - for arg in args { - match arg { - syn::Meta::NameValue(namevalue) => { - let ident = namevalue - .path - .get_ident() - .ok_or_else(|| { - syn::Error::new_spanned(&namevalue, "Must have specified ident") - })? - .to_string() - .to_lowercase(); - - // the value of the attribute - let lit = match &namevalue.value { - syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit, - expr => return Err(syn::Error::new_spanned(expr, "Must be a literal")), - }; - - // the ident of the attribute - let ident = ident.as_str(); - let arg = RunnerArg::from_str(ident); - - match arg { - Ok(arg) => match arg { - RunnerArg::Flavor => { - config.set_flavor(lit.clone(), Spanned::span(lit))?; - } - RunnerArg::BlockTime => { - config.set_block_time(lit.clone(), Spanned::span(lit))? - } - RunnerArg::Validation => { - config.set_validation(lit.clone(), Spanned::span(lit))? - } - RunnerArg::Accounts => { - config.set_accounts(lit.clone(), Spanned::span(lit))?; - } - - RunnerArg::Fee => config.set_fee(lit.clone(), Spanned::span(lit))?, - RunnerArg::DbDir => config.set_db_dir(lit.clone(), Spanned::span(lit))?, - }, - - Err(_) => { - let msg = format!( - "Unknown attribute {ident} is specified; expected one of: `flavor`, \ - `fee`, `validation`, `accounts`, `db_dir`, `block_time`", - ); - return Err(syn::Error::new_spanned(namevalue, msg)); - } - } - } - - other => { - return Err(syn::Error::new_spanned(other, "Unknown attribute inside the macro")); - } - } - } - - Ok(config) -} - fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { tokens.extend(error.into_compile_error()); tokens diff --git a/crates/katana/runner/macro/src/parse.rs b/crates/katana/runner/macro/src/parse.rs index 0e7db25167..9f6f385a5e 100644 --- a/crates/katana/runner/macro/src/parse.rs +++ b/crates/katana/runner/macro/src/parse.rs @@ -1,10 +1,13 @@ use proc_macro2::{Span, TokenStream, TokenTree}; +use quote::TokenStreamExt; use quote::{quote, quote_spanned, ToTokens}; use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; use syn::{braced, Attribute, Ident, Signature, Visibility}; -use crate::config::{Configuration, RunnerFlavor}; +use crate::config::Configuration; +#[derive(Clone)] pub struct ItemFn { pub outer_attrs: Vec, pub vis: Visibility, @@ -28,11 +31,14 @@ impl ItemFn { /// Convert our local function item into a token stream. fn into_tokens( - self, + mut self, generated_attrs: proc_macro2::TokenStream, - body: proc_macro2::TokenStream, + func: proc_macro2::TokenStream, last_block: proc_macro2::TokenStream, ) -> TokenStream { + // empty out the arguments + self.sig.inputs.clear(); + let mut tokens = proc_macro2::TokenStream::new(); // Outer attributes are simply streamed as-is. for attr in self.outer_attrs { @@ -54,7 +60,7 @@ impl ItemFn { self.sig.to_tokens(&mut tokens); self.brace_token.surround(&mut tokens, |tokens| { - body.to_tokens(tokens); + func.to_tokens(tokens); last_block.to_tokens(tokens); }); @@ -120,98 +126,94 @@ impl ToTokens for Body<'_> { } pub fn parse_knobs(mut input: ItemFn, is_test: bool, config: Configuration) -> TokenStream { - // input.sig.asyncness = None; - - // // If type mismatch occurs, the current rustc points to the last statement. - // let (last_stmt_start_span, last_stmt_end_span) = { - // let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter(); - - // // `Span` on stable Rust has a limitation that only points to the first - // // token, not the whole tokens. We can work around this limitation by - // // using the first/last span of the tokens like - // // `syn::Error::new_spanned` does. - // let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); - // let end = last_stmt.last().map_or(start, |t| t.span()); - // (start, end) - // }; - - // let crate_path = config - // .crate_name - // .map(ToTokens::into_token_stream) - // .unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream()); - - // let mut rt = match config.flavor { - // RunnerFlavor::Binary => quote_spanned! {last_stmt_start_span=> - // #crate_path::runtime::Builder::new_current_thread() - // }, - // RunnerFlavor::Embedded => quote_spanned! {last_stmt_start_span=> - // #crate_path::runtime::Builder::new_multi_thread() - // }, - // }; - // if let Some(v) = config.worker_threads { - // rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) }; - // } - // if let Some(v) = config.start_paused { - // rt = quote_spanned! {last_stmt_start_span=> #rt.start_paused(#v) }; - // } - // if let Some(v) = config.unhandled_panic { - // let unhandled_panic = v.into_tokens(&crate_path); - // rt = quote_spanned! {last_stmt_start_span=> #rt.unhandled_panic(#unhandled_panic) }; - // } - - // let generated_attrs = if is_test { - // quote! { - // #[::core::prelude::v1::test] - // } - // } else { - // quote! {} - // }; - - // let body_ident = quote! { body }; - // let last_block = quote_spanned! {last_stmt_end_span=> - // #[allow(clippy::expect_used, clippy::diverging_sub_expression)] - // { - // return #rt - // .enable_all() - // .build() - // .expect("Failed building the Runtime") - // .block_on(#body_ident); - // } - // }; - - // let body = input.body(); - - // // For test functions pin the body to the stack and use `Pin<&mut dyn - // // Future>` to reduce the amount of `Runtime::block_on` (and related - // // functions) copies we generate during compilation due to the generic - // // parameter `F` (the future to block on). This could have an impact on - // // performance, but because it's only for testing it's unlikely to be very - // // large. - // // - // // We don't do this for the main function as it should only be used once so - // // there will be no benefit. - // let body = if is_test { - // let output_type = match &input.sig.output { - // // For functions with no return value syn doesn't print anything, - // // but that doesn't work as `Output` for our boxed `Future`, so - // // default to `()` (the same type as the function output). - // syn::ReturnType::Default => quote! { () }, - // syn::ReturnType::Type(_, ret_type) => quote! { #ret_type }, - // }; - // quote! { - // let body = async #body; - // #crate_path::pin!(body); - // let body: ::core::pin::Pin<&mut dyn ::core::future::Future> = - // body; } - // } else { - // quote! { - // let body = async #body; - // } - // }; - - // input.into_tokens(generated_attrs, body, last_block) - - todo!() + // If type mismatch occurs, the current rustc points to the last statement. + let (last_stmt_start_span, last_stmt_end_span) = { + let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter(); + + // `Span` on stable Rust has a limitation that only points to the first + // token, not the whole tokens. We can work around this limitation by + // using the first/last span of the tokens like + // `syn::Error::new_spanned` does. + let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); + let end = last_stmt.last().map_or(start, |t| t.span()); + (start, end) + }; + + let crate_path = config + .crate_name + .map(ToTokens::into_token_stream) + .unwrap_or_else(|| Ident::new("katana_runner", last_stmt_start_span).into_token_stream()); + + let mut cfg: TokenStream = quote! {}; + + if let Some(value) = config.block_time { + cfg = quote_spanned! (last_stmt_start_span=> #cfg block_time: Some(#value), ); + } + + if let Some(value) = config.fee { + cfg = quote_spanned! (last_stmt_start_span=> #cfg disable_fee: #value, ); + } + + if let Some(value) = config.db_dir { + cfg = quote_spanned! (last_stmt_start_span=> #cfg db_dir: Some(#value), ); + } + + if let Some(value) = config.accounts { + cfg = quote_spanned! (last_stmt_start_span=> #cfg n_accounts: #value, ); + } + + if let Some(value) = config.log_path { + cfg = quote_spanned! (last_stmt_start_span=> #cfg, log_path: Some(#value), ); + } + + if config.dev { + cfg = quote_spanned! (last_stmt_start_span=> #cfg dev: true, ); + } + + cfg = quote_spanned! {last_stmt_start_span=> + #crate_path::KatanaRunnerConfig { #cfg ..Default::default() } + }; + + let generated_attrs = if is_test { + quote! { + #[::core::prelude::v1::test] + } + } else { + quote! {} + }; + + let ident = Ident::new(&format!("__{}__", input.sig.ident), input.sig.ident.span()); + + let last_block = quote_spanned! {last_stmt_end_span=> + let runner = #crate_path::KatanaRunner::new_with_config(#cfg).expect("Failed to start runner."); + let mut ctx = RunnerCtx(runner); + #ident(&mut ctx); + }; + + let ret = &input.sig.output; + let body = input.body(); + + let func = quote! { + struct RunnerCtx(#crate_path::KatanaRunner); + + impl core::ops::Deref for RunnerCtx { + type Target = #crate_path::KatanaRunner; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl core::ops::DerefMut for RunnerCtx { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + fn #ident(ctx: &mut RunnerCtx) #ret + #body + }; + + input.into_tokens(generated_attrs, func, last_block) } pub fn parse_string( diff --git a/crates/katana/runner/macro/tests/macro.rs b/crates/katana/runner/macro/tests/macro.rs index 5f4d2b47f3..f88d7c6f63 100644 --- a/crates/katana/runner/macro/tests/macro.rs +++ b/crates/katana/runner/macro/tests/macro.rs @@ -1,59 +1,59 @@ // macro usage example -// user can set the individual fields when using the macro -#[katana_runner::test( - block_time = 1000, - fee = false, - accounts = 1, - chain_id = 0x12345, - validation = false, - db_dir = "path/to/db", - binary = "path/to/binary", - output = "path/to/output.log", - genesis = "path/to/genesis.json", - classes = ["path/to/erc20.json", "path/to/account.json"] // will be declared at startup -)] -async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { - let url = node.url(); - let provider = node.provider(); - let account = node.account(); - Ok(()) -} - -// but they could also just pass the whole RunnerCfg struct if they want to allow defining the -// configuration once and reuse it across different tests - -#[katana_runner::test(RunnerCfg { })] -async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { - let url = node.url(); - let provider = node.provider(); - let account = node.account(); - Ok(()) -} - -// This is the rough expanded code of the proc macro - -#[tokio::test] -async fn foo() -> Result<(), Box> { - struct RunnerCtx(KatanaRunnerHandle); - - impl std::ops::Deref for RunnerCtx { - type Target = KatanaRunnerHandle; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl std::ops::DerefMut for RunnerCtx { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - - let handle = KatanaRunner::todo(); - - async fn __foo(node: &mut RunnerCtx) -> Result<(), Box> { - Ok(()) - } - - __foo(&mut RunnerCtx(handle)).await -} +// // user can set the individual fields when using the macro +// #[katana_runner::test( +// block_time = 1000, +// fee = false, +// accounts = 1, +// chain_id = 0x12345, +// validation = false, +// db_dir = "path/to/db", +// binary = "path/to/binary", +// output = "path/to/output.log", +// genesis = "path/to/genesis.json", +// classes = ["path/to/erc20.json", "path/to/account.json"] // will be declared at startup +// )] +// async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { +// let url = node.url(); +// let provider = node.provider(); +// let account = node.account(); +// Ok(()) +// } + +// // but they could also just pass the whole RunnerCfg struct if they want to allow defining the +// // configuration once and reuse it across different tests + +// #[katana_runner::test(RunnerCfg { })] +// async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { +// let url = node.url(); +// let provider = node.provider(); +// let account = node.account(); +// Ok(()) +// } + +// // This is the rough expanded code of the proc macro + +// #[tokio::test] +// async fn foo() -> Result<(), Box> { +// struct RunnerCtx(KatanaRunnerHandle); + +// impl std::ops::Deref for RunnerCtx { +// type Target = KatanaRunnerHandle; +// fn deref(&self) -> &Self::Target { +// &self.0 +// } +// } +// impl std::ops::DerefMut for RunnerCtx { +// fn deref_mut(&mut self) -> &mut Self::Target { +// &mut self.0 +// } +// } + +// let handle = KatanaRunner::todo(); + +// async fn __foo(node: &mut RunnerCtx) -> Result<(), Box> { +// Ok(()) +// } + +// __foo(&mut RunnerCtx(handle)).await +// } diff --git a/crates/katana/runner/src/lib.rs b/crates/katana/runner/src/lib.rs index b9f580c7da..f8f229e8bd 100644 --- a/crates/katana/runner/src/lib.rs +++ b/crates/katana/runner/src/lib.rs @@ -10,7 +10,7 @@ use std::thread; use anyhow::{Context, Result}; use assert_fs::TempDir; use katana_node_bindings::{Katana, KatanaInstance}; -pub use katana_runner_macro::{katana_test, runner}; +pub use katana_runner_macro::test; use starknet::accounts::{ExecutionEncoding, SingleOwnerAccount}; use starknet::core::types::{BlockId, BlockTag, Felt}; use starknet::providers::jsonrpc::HttpTransport; @@ -231,12 +231,16 @@ impl KatanaRunner { /// Determines the default program path for the katana runner based on the KATANA_RUNNER_BIN /// environment variable. If not set, try to to use katana from the PATH. fn determine_default_program_path() -> String { - if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { bin } else { "katana".to_string() } + if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { + bin + } else { + "katana".to_string() + } } #[cfg(test)] mod tests { - use super::*; + use crate::determine_default_program_path; #[test] fn test_determine_default_program_path() { diff --git a/crates/katana/runner/tests/runner.rs b/crates/katana/runner/tests/runner.rs index 684230dbe2..7dc3005a3e 100644 --- a/crates/katana/runner/tests/runner.rs +++ b/crates/katana/runner/tests/runner.rs @@ -1,23 +1,28 @@ use katana_runner::*; use starknet::providers::Provider; -#[katana_test(2, false)] -async fn test_run() { - for i in 0..10 { - let logname = format!("katana-test_run-{}", i); - let runner = KatanaRunner::new_with_config(KatanaRunnerConfig { - run_name: Some(logname), - ..Default::default() - }) - .expect("failed to start another katana"); - - let _block_number = runner.provider().block_number().await.unwrap(); - // created by the macro at the beginning of the test - let _other_block_number = runner.provider().block_number().await.unwrap(); - } +#[katana_runner::test] +fn some_test(ctx: &mut RunnerCtx) { + println!("Ohayo!"); } -#[katana_test(2, true)] -async fn basic_macro_usage() { - let _block_number = runner.provider().block_number().await.unwrap(); -} +// #[katana_test(2, false)] +// async fn test_run() { +// for i in 0..10 { +// let logname = format!("katana-test_run-{}", i); +// let runner = KatanaRunner::new_with_config(KatanaRunnerConfig { +// run_name: Some(logname), +// ..Default::default() +// }) +// .expect("failed to start another katana"); + +// let _block_number = runner.provider().block_number().await.unwrap(); +// // created by the macro at the beginning of the test +// let _other_block_number = runner.provider().block_number().await.unwrap(); +// } +// } + +// #[katana_test(2, true)] +// async fn basic_macro_usage() { +// let _block_number = runner.provider().block_number().await.unwrap(); +// } From eceb97fd5582d9be071632205055b7c87ecdbec7 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sat, 21 Sep 2024 22:30:02 -0400 Subject: [PATCH 05/12] working edition part 2 --- crates/katana/runner/macro/src/config.rs | 2 +- crates/katana/runner/macro/src/parse.rs | 59 ++++++++++++++---------- crates/katana/runner/tests/runner.rs | 6 ++- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/crates/katana/runner/macro/src/config.rs b/crates/katana/runner/macro/src/config.rs index e5218540d3..fd92f2c94e 100644 --- a/crates/katana/runner/macro/src/config.rs +++ b/crates/katana/runner/macro/src/config.rs @@ -145,7 +145,7 @@ impl std::str::FromStr for RunnerArg { } pub fn build_config( - input: &crate::parse::ItemFn, + _input: &crate::parse::ItemFn, args: crate::entry::AttributeArgs, is_test: bool, ) -> Result { diff --git a/crates/katana/runner/macro/src/parse.rs b/crates/katana/runner/macro/src/parse.rs index 9f6f385a5e..ee6815eed4 100644 --- a/crates/katana/runner/macro/src/parse.rs +++ b/crates/katana/runner/macro/src/parse.rs @@ -33,12 +33,16 @@ impl ItemFn { fn into_tokens( mut self, generated_attrs: proc_macro2::TokenStream, - func: proc_macro2::TokenStream, + // func: proc_macro2::TokenStream, last_block: proc_macro2::TokenStream, ) -> TokenStream { + self.sig.asyncness = None; // empty out the arguments self.sig.inputs.clear(); + // remove duplicate outer attributes + self.outer_attrs.dedup_by(|a, b| a.path() == b.path()); + let mut tokens = proc_macro2::TokenStream::new(); // Outer attributes are simply streamed as-is. for attr in self.outer_attrs { @@ -60,7 +64,7 @@ impl ItemFn { self.sig.to_tokens(&mut tokens); self.brace_token.surround(&mut tokens, |tokens| { - func.to_tokens(tokens); + // func.to_tokens(tokens); last_block.to_tokens(tokens); }); @@ -182,38 +186,45 @@ pub fn parse_knobs(mut input: ItemFn, is_test: bool, config: Configuration) -> T quote! {} }; - let ident = Ident::new(&format!("__{}__", input.sig.ident), input.sig.ident.span()); - - let last_block = quote_spanned! {last_stmt_end_span=> - let runner = #crate_path::KatanaRunner::new_with_config(#cfg).expect("Failed to start runner."); - let mut ctx = RunnerCtx(runner); - #ident(&mut ctx); - }; - - let ret = &input.sig.output; let body = input.body(); + let body = if input.sig.asyncness.is_some() { + quote! { + struct RunnerCtx(#crate_path::KatanaRunner); - let func = quote! { - struct RunnerCtx(#crate_path::KatanaRunner); - - impl core::ops::Deref for RunnerCtx { - type Target = #crate_path::KatanaRunner; - fn deref(&self) -> &Self::Target { - &self.0 + impl core::ops::Deref for RunnerCtx { + type Target = #crate_path::KatanaRunner; + fn deref(&self) -> &Self::Target { + &self.0 + } } + + let body = async #body; } + } else { + quote! { + struct RunnerCtx(#crate_path::KatanaRunner); - impl core::ops::DerefMut for RunnerCtx { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + impl core::ops::Deref for RunnerCtx { + type Target = #crate_path::KatanaRunner; + fn deref(&self) -> &Self::Target { + &self.0 + } } + + let body = || #body; } + }; - fn #ident(ctx: &mut RunnerCtx) #ret - #body + let last_block = quote_spanned! {last_stmt_end_span=> + { + let runner = #crate_path::KatanaRunner::new_with_config(#cfg).expect("Failed to start runner."); + let ctx = RunnerCtx(runner); + #body + return body(); + } }; - input.into_tokens(generated_attrs, func, last_block) + input.into_tokens(generated_attrs, last_block) } pub fn parse_string( diff --git a/crates/katana/runner/tests/runner.rs b/crates/katana/runner/tests/runner.rs index 7dc3005a3e..a21f7c3930 100644 --- a/crates/katana/runner/tests/runner.rs +++ b/crates/katana/runner/tests/runner.rs @@ -1,8 +1,10 @@ use katana_runner::*; use starknet::providers::Provider; -#[katana_runner::test] -fn some_test(ctx: &mut RunnerCtx) { +#[tokio::test] +#[katana_runner::test(fee = false)] +async fn some_test(ctx: &mut RunnerCtx) { + let account = ctx.accounts(); println!("Ohayo!"); } From ffca1de2a9dd414b6d14f3bd3356a0676354c842 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sun, 22 Sep 2024 01:17:51 -0400 Subject: [PATCH 06/12] wip --- Cargo.lock | 1 + crates/katana/runner/Cargo.toml | 1 + crates/katana/runner/macro/src/parse.rs | 209 ++++++++++++------------ crates/katana/runner/src/lib.rs | 6 +- crates/katana/runner/tests/runner.rs | 25 +-- 5 files changed, 121 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce4063f100..8a48c0f594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8178,6 +8178,7 @@ dependencies = [ "chrono", "katana-node-bindings", "katana-runner-macro", + "rstest 0.18.2", "serde", "serde_json", "starknet 0.12.0", diff --git a/crates/katana/runner/Cargo.toml b/crates/katana/runner/Cargo.toml index 9bd5323b50..92b0b6615c 100644 --- a/crates/katana/runner/Cargo.toml +++ b/crates/katana/runner/Cargo.toml @@ -12,6 +12,7 @@ katana-runner-macro = { path = "macro" } anyhow.workspace = true assert_fs.workspace = true chrono.workspace = true +rstest.workspace = true serde.workspace = true serde_json.workspace = true starknet.workspace = true diff --git a/crates/katana/runner/macro/src/parse.rs b/crates/katana/runner/macro/src/parse.rs index ee6815eed4..f5a90de2cf 100644 --- a/crates/katana/runner/macro/src/parse.rs +++ b/crates/katana/runner/macro/src/parse.rs @@ -1,12 +1,98 @@ +#![allow(unused)] + use proc_macro2::{Span, TokenStream, TokenTree}; -use quote::TokenStreamExt; -use quote::{quote, quote_spanned, ToTokens}; +use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; use syn::parse::{Parse, ParseStream}; -use syn::spanned::Spanned; use syn::{braced, Attribute, Ident, Signature, Visibility}; use crate::config::Configuration; +pub fn parse_knobs(input: ItemFn, is_test: bool, config: Configuration) -> TokenStream { + // If type mismatch occurs, the current rustc points to the last statement. + let (last_stmt_start_span, last_stmt_end_span) = { + let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter(); + + // `Span` on stable Rust has a limitation that only points to the first + // token, not the whole tokens. We can work around this limitation by + // using the first/last span of the tokens like + // `syn::Error::new_spanned` does. + let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); + let end = last_stmt.last().map_or(start, |t| t.span()); + (start, end) + }; + + let crate_path = config + .crate_name + .map(ToTokens::into_token_stream) + .unwrap_or_else(|| Ident::new("katana_runner", last_stmt_start_span).into_token_stream()); + + let mut cfg: TokenStream = quote! {}; + + if let Some(value) = config.block_time { + cfg = quote_spanned! (last_stmt_start_span=> #cfg block_time: Some(#value), ); + } + + if let Some(value) = config.fee { + cfg = quote_spanned! (last_stmt_start_span=> #cfg disable_fee: #value, ); + } + + if let Some(value) = config.db_dir { + cfg = quote_spanned! (last_stmt_start_span=> #cfg db_dir: Some(std::path::PathBuf::from(#value)), ); + } + + if let Some(value) = config.accounts { + cfg = quote_spanned! (last_stmt_start_span=> #cfg n_accounts: #value, ); + } + + if let Some(value) = config.log_path { + cfg = quote_spanned! (last_stmt_start_span=> #cfg, log_path: Some(#value), ); + } + + if config.dev { + cfg = quote_spanned! (last_stmt_start_span=> #cfg dev: true, ); + } + + cfg = quote_spanned! {last_stmt_start_span=> + #crate_path::KatanaRunnerConfig { #cfg ..Default::default() } + }; + + let generated_attrs = if is_test { + quote! { + #[::core::prelude::v1::test] + } + } else { + quote! {} + }; + + let mut inner = input.clone(); + inner.sig.ident = Ident::new(&format!("___{}", inner.sig.ident), inner.sig.ident.span()); + inner.outer_attrs.clear(); + let inner_name = &inner.sig.ident; + + let last_block = quote_spanned! {last_stmt_end_span=> + { + let runner = #crate_path::KatanaRunner::new_with_config(#cfg).expect("Failed to start runner."); + let ctx = RunnerCtx(runner); + return #inner_name(&ctx); + } + }; + + let inner = quote! { + struct RunnerCtx(#crate_path::KatanaRunner); + + impl core::ops::Deref for RunnerCtx { + type Target = #crate_path::KatanaRunner; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + #inner + }; + + input.into_tokens(generated_attrs, inner, last_block) +} + #[derive(Clone)] pub struct ItemFn { pub outer_attrs: Vec, @@ -33,16 +119,13 @@ impl ItemFn { fn into_tokens( mut self, generated_attrs: proc_macro2::TokenStream, - // func: proc_macro2::TokenStream, + inner: proc_macro2::TokenStream, last_block: proc_macro2::TokenStream, ) -> TokenStream { self.sig.asyncness = None; // empty out the arguments self.sig.inputs.clear(); - // remove duplicate outer attributes - self.outer_attrs.dedup_by(|a, b| a.path() == b.path()); - let mut tokens = proc_macro2::TokenStream::new(); // Outer attributes are simply streamed as-is. for attr in self.outer_attrs { @@ -64,7 +147,7 @@ impl ItemFn { self.sig.to_tokens(&mut tokens); self.brace_token.surround(&mut tokens, |tokens| { - // func.to_tokens(tokens); + inner.to_tokens(tokens); last_block.to_tokens(tokens); }); @@ -72,6 +155,18 @@ impl ItemFn { } } +impl ToTokens for ItemFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(self.outer_attrs.iter()); + self.vis.to_tokens(tokens); + self.sig.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.inner_attrs.iter()); + tokens.append_all(&self.stmts); + }); + } +} + impl Parse for ItemFn { #[inline] fn parse(input: ParseStream<'_>) -> syn::Result { @@ -129,104 +224,6 @@ impl ToTokens for Body<'_> { } } -pub fn parse_knobs(mut input: ItemFn, is_test: bool, config: Configuration) -> TokenStream { - // If type mismatch occurs, the current rustc points to the last statement. - let (last_stmt_start_span, last_stmt_end_span) = { - let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter(); - - // `Span` on stable Rust has a limitation that only points to the first - // token, not the whole tokens. We can work around this limitation by - // using the first/last span of the tokens like - // `syn::Error::new_spanned` does. - let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); - let end = last_stmt.last().map_or(start, |t| t.span()); - (start, end) - }; - - let crate_path = config - .crate_name - .map(ToTokens::into_token_stream) - .unwrap_or_else(|| Ident::new("katana_runner", last_stmt_start_span).into_token_stream()); - - let mut cfg: TokenStream = quote! {}; - - if let Some(value) = config.block_time { - cfg = quote_spanned! (last_stmt_start_span=> #cfg block_time: Some(#value), ); - } - - if let Some(value) = config.fee { - cfg = quote_spanned! (last_stmt_start_span=> #cfg disable_fee: #value, ); - } - - if let Some(value) = config.db_dir { - cfg = quote_spanned! (last_stmt_start_span=> #cfg db_dir: Some(#value), ); - } - - if let Some(value) = config.accounts { - cfg = quote_spanned! (last_stmt_start_span=> #cfg n_accounts: #value, ); - } - - if let Some(value) = config.log_path { - cfg = quote_spanned! (last_stmt_start_span=> #cfg, log_path: Some(#value), ); - } - - if config.dev { - cfg = quote_spanned! (last_stmt_start_span=> #cfg dev: true, ); - } - - cfg = quote_spanned! {last_stmt_start_span=> - #crate_path::KatanaRunnerConfig { #cfg ..Default::default() } - }; - - let generated_attrs = if is_test { - quote! { - #[::core::prelude::v1::test] - } - } else { - quote! {} - }; - - let body = input.body(); - let body = if input.sig.asyncness.is_some() { - quote! { - struct RunnerCtx(#crate_path::KatanaRunner); - - impl core::ops::Deref for RunnerCtx { - type Target = #crate_path::KatanaRunner; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - let body = async #body; - } - } else { - quote! { - struct RunnerCtx(#crate_path::KatanaRunner); - - impl core::ops::Deref for RunnerCtx { - type Target = #crate_path::KatanaRunner; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - let body = || #body; - } - }; - - let last_block = quote_spanned! {last_stmt_end_span=> - { - let runner = #crate_path::KatanaRunner::new_with_config(#cfg).expect("Failed to start runner."); - let ctx = RunnerCtx(runner); - #body - return body(); - } - }; - - input.into_tokens(generated_attrs, last_block) -} - pub fn parse_string( int: syn::Lit, span: proc_macro2::Span, diff --git a/crates/katana/runner/src/lib.rs b/crates/katana/runner/src/lib.rs index f8f229e8bd..53798b973f 100644 --- a/crates/katana/runner/src/lib.rs +++ b/crates/katana/runner/src/lib.rs @@ -231,11 +231,7 @@ impl KatanaRunner { /// Determines the default program path for the katana runner based on the KATANA_RUNNER_BIN /// environment variable. If not set, try to to use katana from the PATH. fn determine_default_program_path() -> String { - if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { - bin - } else { - "katana".to_string() - } + if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { bin } else { "katana".to_string() } } #[cfg(test)] diff --git a/crates/katana/runner/tests/runner.rs b/crates/katana/runner/tests/runner.rs index a21f7c3930..0c5e755d5b 100644 --- a/crates/katana/runner/tests/runner.rs +++ b/crates/katana/runner/tests/runner.rs @@ -1,11 +1,21 @@ -use katana_runner::*; use starknet::providers::Provider; +#[katana_runner::test(fee = false, accounts = 10)] +fn simple(runner: &RunnerCtx) { + assert_eq!(runner.accounts().len(), 10); +} + +#[katana_runner::test] +fn with_return(_: &RunnerCtx) -> Result<(), Box> { + Ok(()) +} + #[tokio::test] -#[katana_runner::test(fee = false)] -async fn some_test(ctx: &mut RunnerCtx) { - let account = ctx.accounts(); - println!("Ohayo!"); +#[katana_runner::test] +async fn with_async(ctx: &RunnerCtx) -> Result<(), Box> { + let provider = ctx.provider(); + let _ = provider.chain_id().await?; + Ok(()) } // #[katana_test(2, false)] @@ -23,8 +33,3 @@ async fn some_test(ctx: &mut RunnerCtx) { // let _other_block_number = runner.provider().block_number().await.unwrap(); // } // } - -// #[katana_test(2, true)] -// async fn basic_macro_usage() { -// let _block_number = runner.provider().block_number().await.unwrap(); -// } From 2ac9c55ed60248bf94384c2aded785989e5f5a78 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sun, 22 Sep 2024 14:38:08 -0400 Subject: [PATCH 07/12] refactor module --- Cargo.lock | 1 - bin/sozo/src/commands/options/account/mod.rs | 11 +- bin/sozo/tests/test_account.rs | 6 +- crates/katana/runner/Cargo.toml | 1 - crates/katana/runner/macro/src/config.rs | 11 +- crates/katana/runner/macro/src/entry.rs | 95 ++++++- crates/katana/runner/macro/src/item.rs | 140 ++++++++++ crates/katana/runner/macro/src/lib.rs | 3 +- crates/katana/runner/macro/src/parse.rs | 278 ------------------- crates/katana/runner/macro/src/utils.rs | 46 +++ crates/katana/runner/macro/tests/macro.rs | 59 ---- crates/katana/runner/src/lib.rs | 17 ++ crates/katana/runner/tests/runner.rs | 21 +- 13 files changed, 313 insertions(+), 376 deletions(-) create mode 100644 crates/katana/runner/macro/src/item.rs delete mode 100644 crates/katana/runner/macro/src/parse.rs create mode 100644 crates/katana/runner/macro/src/utils.rs delete mode 100644 crates/katana/runner/macro/tests/macro.rs diff --git a/Cargo.lock b/Cargo.lock index 8a48c0f594..ce4063f100 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8178,7 +8178,6 @@ dependencies = [ "chrono", "katana-node-bindings", "katana-runner-macro", - "rstest 0.18.2", "serde", "serde_json", "starknet 0.12.0", diff --git a/bin/sozo/src/commands/options/account/mod.rs b/bin/sozo/src/commands/options/account/mod.rs index 9f73d74d8a..89231243b5 100644 --- a/bin/sozo/src/commands/options/account/mod.rs +++ b/bin/sozo/src/commands/options/account/mod.rs @@ -151,6 +151,7 @@ impl AccountOptions { #[cfg(test)] mod tests { use clap::Parser; + use katana_runner::RunnerCtx; use starknet::accounts::ExecutionEncoder; use starknet::core::types::Call; use starknet_crypto::Felt; @@ -211,8 +212,9 @@ mod tests { assert!(cmd.account.account_address(None).is_err()); } - #[katana_runner::katana_test(2, true)] - async fn legacy_flag_works_as_expected() { + #[tokio::test] + #[katana_runner::test(accounts = 2, fee = false)] + async fn legacy_flag_works_as_expected(runner: &RunnerCtx) { let cmd = Command::parse_from([ "sozo", "--legacy", @@ -235,8 +237,9 @@ mod tests { assert!(*result.get(3).unwrap() == Felt::from_hex("0x0").unwrap()); } - #[katana_runner::katana_test(2, true)] - async fn without_legacy_flag_works_as_expected() { + #[tokio::test] + #[katana_runner::test(accounts = 2, fee = false)] + async fn without_legacy_flag_works_as_expected(runner: &RunnerCtx) { let cmd = Command::parse_from(["sozo", "--account-address", "0x0", "--private-key", "0x1"]); let dummy_call = vec![Call { to: Felt::from_hex("0x0").unwrap(), diff --git a/bin/sozo/tests/test_account.rs b/bin/sozo/tests/test_account.rs index 0d42870286..7564b228ec 100644 --- a/bin/sozo/tests/test_account.rs +++ b/bin/sozo/tests/test_account.rs @@ -3,6 +3,7 @@ mod utils; use std::fs; use assert_fs::fixture::PathChild; +use katana_runner::RunnerCtx; use sozo_ops::account; use starknet::accounts::Account; use utils::snapbox::get_snapbox; @@ -29,8 +30,9 @@ fn test_account_new() { assert!(pt.child("account.json").exists()); } -#[katana_runner::katana_test(1, true)] -async fn test_account_fetch() { +#[tokio::test] +#[katana_runner::test(accounts = 1, fee = false)] +async fn test_account_fetch(runner: &RunnerCtx) { let pt = assert_fs::TempDir::new().unwrap(); account::fetch( diff --git a/crates/katana/runner/Cargo.toml b/crates/katana/runner/Cargo.toml index 92b0b6615c..9bd5323b50 100644 --- a/crates/katana/runner/Cargo.toml +++ b/crates/katana/runner/Cargo.toml @@ -12,7 +12,6 @@ katana-runner-macro = { path = "macro" } anyhow.workspace = true assert_fs.workspace = true chrono.workspace = true -rstest.workspace = true serde.workspace = true serde_json.workspace = true starknet.workspace = true diff --git a/crates/katana/runner/macro/src/config.rs b/crates/katana/runner/macro/src/config.rs index fd92f2c94e..e06368057a 100644 --- a/crates/katana/runner/macro/src/config.rs +++ b/crates/katana/runner/macro/src/config.rs @@ -1,8 +1,10 @@ +#![allow(unused)] + use std::str::FromStr; use syn::spanned::Spanned; -use crate::parse::{parse_bool, parse_int, parse_path, parse_string}; +use crate::utils::{parse_bool, parse_int, parse_path, parse_string}; pub const DEFAULT_ERROR_CONFIG: Configuration = Configuration::new(false); @@ -145,15 +147,10 @@ impl std::str::FromStr for RunnerArg { } pub fn build_config( - _input: &crate::parse::ItemFn, + _input: &crate::item::ItemFn, args: crate::entry::AttributeArgs, is_test: bool, ) -> Result { - // if input.sig.asyncness.is_none() { - // let msg = "the `async` keyword is missing from the function declaration"; - // return Err(syn::Error::new_spanned(input.sig.fn_token, msg)); - // } - let mut config = Configuration::new(is_test); for arg in args { diff --git a/crates/katana/runner/macro/src/entry.rs b/crates/katana/runner/macro/src/entry.rs index 8332d85c4b..e233ffb1da 100644 --- a/crates/katana/runner/macro/src/entry.rs +++ b/crates/katana/runner/macro/src/entry.rs @@ -1,8 +1,11 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned, ToTokens}; use syn::parse::Parser; +use syn::{parse_quote, Ident}; -use crate::config::{build_config, DEFAULT_ERROR_CONFIG}; -use crate::parse::parse_knobs; +use crate::config::{build_config, Configuration, DEFAULT_ERROR_CONFIG}; +use crate::item::ItemFn; +use crate::utils::attr_ends_with; // Because syn::AttributeArgs does not implement syn::Parse pub type AttributeArgs = syn::punctuated::Punctuated; @@ -12,14 +15,14 @@ pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { // to the expected output as possible. This helps out IDEs such that completions and other // related features keep working. - let input: crate::parse::ItemFn = match syn::parse2(item.clone()) { + let input: ItemFn = match syn::parse2(item.clone()) { Ok(it) => it, Err(e) => return token_stream_with_error(item, e), }; // parse the attribute arguments let config = AttributeArgs::parse_terminated - .parse2(args.into()) + .parse2(args) .and_then(|args| build_config(&input, args, true)); match config { @@ -32,3 +35,85 @@ fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenS tokens.extend(error.into_compile_error()); tokens } + +pub fn parse_knobs(input: ItemFn, is_test: bool, config: Configuration) -> TokenStream { + // If type mismatch occurs, the current rustc points to the last statement. + let (last_stmt_start_span, last_stmt_end_span) = { + let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter(); + + // `Span` on stable Rust has a limitation that only points to the first + // token, not the whole tokens. We can work around this limitation by + // using the first/last span of the tokens like + // `syn::Error::new_spanned` does. + let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); + let end = last_stmt.last().map_or(start, |t| t.span()); + (start, end) + }; + + let crate_path = config + .crate_name + .map(ToTokens::into_token_stream) + .unwrap_or_else(|| Ident::new("katana_runner", last_stmt_start_span).into_token_stream()); + + let mut cfg: TokenStream = quote! {}; + + if let Some(value) = config.block_time { + cfg = quote_spanned! (last_stmt_start_span=> #cfg block_time: Some(#value), ); + } + + if let Some(value) = config.fee { + cfg = quote_spanned! (last_stmt_start_span=> #cfg disable_fee: #value, ); + } + + if let Some(value) = config.db_dir { + cfg = quote_spanned! (last_stmt_start_span=> #cfg db_dir: Some(std::path::PathBuf::from(#value)), ); + } + + if let Some(value) = config.accounts { + cfg = quote_spanned! (last_stmt_start_span=> #cfg n_accounts: #value, ); + } + + if let Some(value) = config.log_path { + cfg = quote_spanned! (last_stmt_start_span=> #cfg, log_path: Some(#value), ); + } + + if config.dev { + cfg = quote_spanned! (last_stmt_start_span=> #cfg dev: true, ); + } + + cfg = quote_spanned! {last_stmt_start_span=> + #crate_path::KatanaRunnerConfig { #cfg ..Default::default() } + }; + + let generated_attrs = if is_test { + // Don't include the #[test] attribute if it already exists. + // Otherwise, if we use a proc macro that applies the #[test] attribute (eg. + // #[tokio::test]), the test would be executed twice. + if input.attrs().any(|a| attr_ends_with(a, &parse_quote! {test})) { + quote! {} + } else { + quote! { + #[::core::prelude::v1::test] + } + } + } else { + quote! {} + }; + + let mut inner = input.clone(); + inner.sig.ident = Ident::new(&format!("___{}", inner.sig.ident), inner.sig.ident.span()); + inner.outer_attrs.clear(); + let inner_name = &inner.sig.ident; + + let last_block = quote_spanned! {last_stmt_end_span=> + { + let runner = #crate_path::KatanaRunner::new_with_config(#cfg).expect("Failed to start runner."); + let ctx = #crate_path::RunnerCtx::new(runner); + #[allow(clippy::needless_return)] + return #inner_name(&ctx); + } + }; + let inner = quote! { #inner }; + + input.into_tokens(generated_attrs, inner, last_block) +} diff --git a/crates/katana/runner/macro/src/item.rs b/crates/katana/runner/macro/src/item.rs new file mode 100644 index 0000000000..77d4d4b1c2 --- /dev/null +++ b/crates/katana/runner/macro/src/item.rs @@ -0,0 +1,140 @@ +#![allow(unused)] + +use proc_macro2::{Span, TokenStream, TokenTree}; +use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; +use syn::parse::{Parse, ParseStream}; +use syn::{braced, parse_quote, Attribute, Ident, Signature, Visibility}; + +use crate::config::Configuration; +use crate::utils::{attr_ends_with, parse_bool, parse_int, parse_string}; + +#[derive(Clone)] +pub struct ItemFn { + pub outer_attrs: Vec, + pub vis: Visibility, + pub sig: Signature, + pub brace_token: syn::token::Brace, + pub inner_attrs: Vec, + pub stmts: Vec, +} + +impl ItemFn { + /// Access all attributes of the function item. + pub fn attrs(&self) -> impl Iterator { + self.outer_attrs.iter().chain(self.inner_attrs.iter()) + } + + /// Get the body of the function item in a manner so that it can be + /// conveniently used with the `quote!` macro. + pub fn body(&self) -> Body<'_> { + Body { brace_token: self.brace_token, stmts: &self.stmts } + } + + /// Convert our local function item into a token stream. + pub fn into_tokens( + mut self, + generated_attrs: proc_macro2::TokenStream, + inner: proc_macro2::TokenStream, + last_block: proc_macro2::TokenStream, + ) -> TokenStream { + self.sig.asyncness = None; + // empty out the arguments + self.sig.inputs.clear(); + + let mut tokens = proc_macro2::TokenStream::new(); + // Outer attributes are simply streamed as-is. + for attr in self.outer_attrs { + attr.to_tokens(&mut tokens); + } + + // Inner attributes require extra care, since they're not supported on + // blocks (which is what we're expanded into) we instead lift them + // outside of the function. This matches the behavior of `syn`. + for mut attr in self.inner_attrs { + attr.style = syn::AttrStyle::Outer; + attr.to_tokens(&mut tokens); + } + + // Add generated macros at the end, so macros processed later are aware of them. + generated_attrs.to_tokens(&mut tokens); + + self.vis.to_tokens(&mut tokens); + self.sig.to_tokens(&mut tokens); + + self.brace_token.surround(&mut tokens, |tokens| { + inner.to_tokens(tokens); + last_block.to_tokens(tokens); + }); + + tokens + } +} + +impl ToTokens for ItemFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(self.outer_attrs.iter()); + self.vis.to_tokens(tokens); + self.sig.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.inner_attrs.iter()); + tokens.append_all(&self.stmts); + }); + } +} + +impl Parse for ItemFn { + #[inline] + fn parse(input: ParseStream<'_>) -> syn::Result { + // This parse implementation has been largely lifted from `syn`, with + // the exception of: + // * We don't have access to the plumbing necessary to parse inner attributes in-place. + // * We do our own statements parsing to avoid recursively parsing entire statements and + // only look for the parts we're interested in. + + let outer_attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let sig: Signature = input.parse()?; + + let content; + let brace_token = braced!(content in input); + let inner_attrs = Attribute::parse_inner(&content)?; + + let mut buf = proc_macro2::TokenStream::new(); + let mut stmts = Vec::new(); + + while !content.is_empty() { + if let Some(semi) = content.parse::>()? { + semi.to_tokens(&mut buf); + stmts.push(buf); + buf = proc_macro2::TokenStream::new(); + continue; + } + + // Parse a single token tree and extend our current buffer with it. + // This avoids parsing the entire content of the sub-tree. + buf.extend([content.parse::()?]); + } + + if !buf.is_empty() { + stmts.push(buf); + } + + Ok(Self { outer_attrs, vis, sig, brace_token, inner_attrs, stmts }) + } +} + +pub struct Body<'a> { + brace_token: syn::token::Brace, + // Statements, with terminating `;`. + stmts: &'a [TokenStream], +} + +impl ToTokens for Body<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.brace_token.surround(tokens, |tokens| { + for stmt in self.stmts { + stmt.to_tokens(tokens); + } + }); + } +} diff --git a/crates/katana/runner/macro/src/lib.rs b/crates/katana/runner/macro/src/lib.rs index 6f7ddf720c..0dbe742091 100644 --- a/crates/katana/runner/macro/src/lib.rs +++ b/crates/katana/runner/macro/src/lib.rs @@ -3,7 +3,8 @@ pub(crate) mod config; mod entry; -pub(crate) mod parse; +pub(crate) mod item; +pub(crate) mod utils; use proc_macro::TokenStream; diff --git a/crates/katana/runner/macro/src/parse.rs b/crates/katana/runner/macro/src/parse.rs deleted file mode 100644 index f5a90de2cf..0000000000 --- a/crates/katana/runner/macro/src/parse.rs +++ /dev/null @@ -1,278 +0,0 @@ -#![allow(unused)] - -use proc_macro2::{Span, TokenStream, TokenTree}; -use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; -use syn::parse::{Parse, ParseStream}; -use syn::{braced, Attribute, Ident, Signature, Visibility}; - -use crate::config::Configuration; - -pub fn parse_knobs(input: ItemFn, is_test: bool, config: Configuration) -> TokenStream { - // If type mismatch occurs, the current rustc points to the last statement. - let (last_stmt_start_span, last_stmt_end_span) = { - let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter(); - - // `Span` on stable Rust has a limitation that only points to the first - // token, not the whole tokens. We can work around this limitation by - // using the first/last span of the tokens like - // `syn::Error::new_spanned` does. - let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); - let end = last_stmt.last().map_or(start, |t| t.span()); - (start, end) - }; - - let crate_path = config - .crate_name - .map(ToTokens::into_token_stream) - .unwrap_or_else(|| Ident::new("katana_runner", last_stmt_start_span).into_token_stream()); - - let mut cfg: TokenStream = quote! {}; - - if let Some(value) = config.block_time { - cfg = quote_spanned! (last_stmt_start_span=> #cfg block_time: Some(#value), ); - } - - if let Some(value) = config.fee { - cfg = quote_spanned! (last_stmt_start_span=> #cfg disable_fee: #value, ); - } - - if let Some(value) = config.db_dir { - cfg = quote_spanned! (last_stmt_start_span=> #cfg db_dir: Some(std::path::PathBuf::from(#value)), ); - } - - if let Some(value) = config.accounts { - cfg = quote_spanned! (last_stmt_start_span=> #cfg n_accounts: #value, ); - } - - if let Some(value) = config.log_path { - cfg = quote_spanned! (last_stmt_start_span=> #cfg, log_path: Some(#value), ); - } - - if config.dev { - cfg = quote_spanned! (last_stmt_start_span=> #cfg dev: true, ); - } - - cfg = quote_spanned! {last_stmt_start_span=> - #crate_path::KatanaRunnerConfig { #cfg ..Default::default() } - }; - - let generated_attrs = if is_test { - quote! { - #[::core::prelude::v1::test] - } - } else { - quote! {} - }; - - let mut inner = input.clone(); - inner.sig.ident = Ident::new(&format!("___{}", inner.sig.ident), inner.sig.ident.span()); - inner.outer_attrs.clear(); - let inner_name = &inner.sig.ident; - - let last_block = quote_spanned! {last_stmt_end_span=> - { - let runner = #crate_path::KatanaRunner::new_with_config(#cfg).expect("Failed to start runner."); - let ctx = RunnerCtx(runner); - return #inner_name(&ctx); - } - }; - - let inner = quote! { - struct RunnerCtx(#crate_path::KatanaRunner); - - impl core::ops::Deref for RunnerCtx { - type Target = #crate_path::KatanaRunner; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #inner - }; - - input.into_tokens(generated_attrs, inner, last_block) -} - -#[derive(Clone)] -pub struct ItemFn { - pub outer_attrs: Vec, - pub vis: Visibility, - pub sig: Signature, - pub brace_token: syn::token::Brace, - pub inner_attrs: Vec, - pub stmts: Vec, -} - -impl ItemFn { - /// Access all attributes of the function item. - fn attrs(&self) -> impl Iterator { - self.outer_attrs.iter().chain(self.inner_attrs.iter()) - } - - /// Get the body of the function item in a manner so that it can be - /// conveniently used with the `quote!` macro. - fn body(&self) -> Body<'_> { - Body { brace_token: self.brace_token, stmts: &self.stmts } - } - - /// Convert our local function item into a token stream. - fn into_tokens( - mut self, - generated_attrs: proc_macro2::TokenStream, - inner: proc_macro2::TokenStream, - last_block: proc_macro2::TokenStream, - ) -> TokenStream { - self.sig.asyncness = None; - // empty out the arguments - self.sig.inputs.clear(); - - let mut tokens = proc_macro2::TokenStream::new(); - // Outer attributes are simply streamed as-is. - for attr in self.outer_attrs { - attr.to_tokens(&mut tokens); - } - - // Inner attributes require extra care, since they're not supported on - // blocks (which is what we're expanded into) we instead lift them - // outside of the function. This matches the behavior of `syn`. - for mut attr in self.inner_attrs { - attr.style = syn::AttrStyle::Outer; - attr.to_tokens(&mut tokens); - } - - // Add generated macros at the end, so macros processed later are aware of them. - generated_attrs.to_tokens(&mut tokens); - - self.vis.to_tokens(&mut tokens); - self.sig.to_tokens(&mut tokens); - - self.brace_token.surround(&mut tokens, |tokens| { - inner.to_tokens(tokens); - last_block.to_tokens(tokens); - }); - - tokens - } -} - -impl ToTokens for ItemFn { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.append_all(self.outer_attrs.iter()); - self.vis.to_tokens(tokens); - self.sig.to_tokens(tokens); - self.brace_token.surround(tokens, |tokens| { - tokens.append_all(self.inner_attrs.iter()); - tokens.append_all(&self.stmts); - }); - } -} - -impl Parse for ItemFn { - #[inline] - fn parse(input: ParseStream<'_>) -> syn::Result { - // This parse implementation has been largely lifted from `syn`, with - // the exception of: - // * We don't have access to the plumbing necessary to parse inner attributes in-place. - // * We do our own statements parsing to avoid recursively parsing entire statements and - // only look for the parts we're interested in. - - let outer_attrs = input.call(Attribute::parse_outer)?; - let vis: Visibility = input.parse()?; - let sig: Signature = input.parse()?; - - let content; - let brace_token = braced!(content in input); - let inner_attrs = Attribute::parse_inner(&content)?; - - let mut buf = proc_macro2::TokenStream::new(); - let mut stmts = Vec::new(); - - while !content.is_empty() { - if let Some(semi) = content.parse::>()? { - semi.to_tokens(&mut buf); - stmts.push(buf); - buf = proc_macro2::TokenStream::new(); - continue; - } - - // Parse a single token tree and extend our current buffer with it. - // This avoids parsing the entire content of the sub-tree. - buf.extend([content.parse::()?]); - } - - if !buf.is_empty() { - stmts.push(buf); - } - - Ok(Self { outer_attrs, vis, sig, brace_token, inner_attrs, stmts }) - } -} - -struct Body<'a> { - brace_token: syn::token::Brace, - // Statements, with terminating `;`. - stmts: &'a [TokenStream], -} - -impl ToTokens for Body<'_> { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - self.brace_token.surround(tokens, |tokens| { - for stmt in self.stmts { - stmt.to_tokens(tokens); - } - }); - } -} - -pub fn parse_string( - int: syn::Lit, - span: proc_macro2::Span, - field: &str, -) -> Result { - match int { - syn::Lit::Str(s) => Ok(s.value()), - syn::Lit::Verbatim(s) => Ok(s.to_string()), - _ => Err(syn::Error::new(span, format!("Failed to parse value of `{field}` as string."))), - } -} - -pub fn parse_path( - lit: syn::Lit, - span: proc_macro2::Span, - field: &str, -) -> Result { - match lit { - syn::Lit::Str(s) => { - let err = syn::Error::new( - span, - format!("Failed to parse value of `{field}` as path: \"{}\"", s.value()), - ); - s.parse::().map_err(|_| err.clone()) - } - _ => Err(syn::Error::new(span, format!("Failed to parse value of `{}` as path.", field))), - } -} - -pub fn parse_bool( - bool: syn::Lit, - span: proc_macro2::Span, - field: &str, -) -> Result { - match bool { - syn::Lit::Bool(b) => Ok(b.value), - _ => Err(syn::Error::new(span, format!("Failed to parse value of `{field}` as bool."))), - } -} - -pub fn parse_int(int: syn::Lit, span: proc_macro2::Span, field: &str) -> Result { - match int { - syn::Lit::Int(lit) => match lit.base10_parse::() { - Ok(value) => Ok(value), - Err(e) => Err(syn::Error::new( - span, - format!("Failed to parse value of `{field}` as integer: {e}"), - )), - }, - _ => Err(syn::Error::new(span, format!("Failed to parse value of `{field}` as integer."))), - } -} diff --git a/crates/katana/runner/macro/src/utils.rs b/crates/katana/runner/macro/src/utils.rs new file mode 100644 index 0000000000..ed2e1f211a --- /dev/null +++ b/crates/katana/runner/macro/src/utils.rs @@ -0,0 +1,46 @@ +use proc_macro2::Span; +use syn::{Attribute, Error, Lit, PathSegment}; + +pub fn parse_string(int: Lit, span: Span, field: &str) -> Result { + match int { + Lit::Str(s) => Ok(s.value()), + Lit::Verbatim(s) => Ok(s.to_string()), + _ => Err(Error::new(span, format!("Failed to parse value of `{field}` as string."))), + } +} + +pub fn parse_path(lit: Lit, span: Span, field: &str) -> Result { + match lit { + Lit::Str(s) => { + let err = Error::new( + span, + format!("Failed to parse value of `{field}` as path: \"{}\"", s.value()), + ); + s.parse::().map_err(|_| err.clone()) + } + _ => Err(Error::new(span, format!("Failed to parse value of `{}` as path.", field))), + } +} + +pub fn parse_bool(bool: Lit, span: proc_macro2::Span, field: &str) -> Result { + match bool { + Lit::Bool(b) => Ok(b.value), + _ => Err(Error::new(span, format!("Failed to parse value of `{field}` as bool."))), + } +} + +pub fn parse_int(int: Lit, span: proc_macro2::Span, field: &str) -> Result { + match int { + Lit::Int(lit) => match lit.base10_parse::() { + Ok(value) => Ok(value), + Err(e) => { + Err(Error::new(span, format!("Failed to parse value of `{field}` as integer: {e}"))) + } + }, + _ => Err(Error::new(span, format!("Failed to parse value of `{field}` as integer."))), + } +} + +pub fn attr_ends_with(attr: &Attribute, segment: &PathSegment) -> bool { + attr.path().segments.iter().last() == Some(segment) +} diff --git a/crates/katana/runner/macro/tests/macro.rs b/crates/katana/runner/macro/tests/macro.rs deleted file mode 100644 index f88d7c6f63..0000000000 --- a/crates/katana/runner/macro/tests/macro.rs +++ /dev/null @@ -1,59 +0,0 @@ -// macro usage example - -// // user can set the individual fields when using the macro -// #[katana_runner::test( -// block_time = 1000, -// fee = false, -// accounts = 1, -// chain_id = 0x12345, -// validation = false, -// db_dir = "path/to/db", -// binary = "path/to/binary", -// output = "path/to/output.log", -// genesis = "path/to/genesis.json", -// classes = ["path/to/erc20.json", "path/to/account.json"] // will be declared at startup -// )] -// async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { -// let url = node.url(); -// let provider = node.provider(); -// let account = node.account(); -// Ok(()) -// } - -// // but they could also just pass the whole RunnerCfg struct if they want to allow defining the -// // configuration once and reuse it across different tests - -// #[katana_runner::test(RunnerCfg { })] -// async fn foo(node: &mut RunnerCtx) -> Result<(), Box> { -// let url = node.url(); -// let provider = node.provider(); -// let account = node.account(); -// Ok(()) -// } - -// // This is the rough expanded code of the proc macro - -// #[tokio::test] -// async fn foo() -> Result<(), Box> { -// struct RunnerCtx(KatanaRunnerHandle); - -// impl std::ops::Deref for RunnerCtx { -// type Target = KatanaRunnerHandle; -// fn deref(&self) -> &Self::Target { -// &self.0 -// } -// } -// impl std::ops::DerefMut for RunnerCtx { -// fn deref_mut(&mut self) -> &mut Self::Target { -// &mut self.0 -// } -// } - -// let handle = KatanaRunner::todo(); - -// async fn __foo(node: &mut RunnerCtx) -> Result<(), Box> { -// Ok(()) -// } - -// __foo(&mut RunnerCtx(handle)).await -// } diff --git a/crates/katana/runner/src/lib.rs b/crates/katana/runner/src/lib.rs index 53798b973f..52c0368ea3 100644 --- a/crates/katana/runner/src/lib.rs +++ b/crates/katana/runner/src/lib.rs @@ -20,6 +20,23 @@ use tokio::sync::Mutex; use url::Url; use utils::find_free_port; +#[allow(dead_code)] +#[derive(Debug)] +pub struct RunnerCtx(KatanaRunner); + +impl RunnerCtx { + pub fn new(runner: KatanaRunner) -> Self { + Self(runner) + } +} + +impl core::ops::Deref for RunnerCtx { + type Target = KatanaRunner; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Debug)] pub struct KatanaRunner { instance: KatanaInstance, diff --git a/crates/katana/runner/tests/runner.rs b/crates/katana/runner/tests/runner.rs index 0c5e755d5b..55586399fe 100644 --- a/crates/katana/runner/tests/runner.rs +++ b/crates/katana/runner/tests/runner.rs @@ -1,8 +1,9 @@ +use katana_runner::RunnerCtx; use starknet::providers::Provider; -#[katana_runner::test(fee = false, accounts = 10)] +#[katana_runner::test(fee = false, accounts = 7)] fn simple(runner: &RunnerCtx) { - assert_eq!(runner.accounts().len(), 10); + assert_eq!(runner.accounts().len(), 7); } #[katana_runner::test] @@ -17,19 +18,3 @@ async fn with_async(ctx: &RunnerCtx) -> Result<(), Box> { let _ = provider.chain_id().await?; Ok(()) } - -// #[katana_test(2, false)] -// async fn test_run() { -// for i in 0..10 { -// let logname = format!("katana-test_run-{}", i); -// let runner = KatanaRunner::new_with_config(KatanaRunnerConfig { -// run_name: Some(logname), -// ..Default::default() -// }) -// .expect("failed to start another katana"); - -// let _block_number = runner.provider().block_number().await.unwrap(); -// // created by the macro at the beginning of the test -// let _other_block_number = runner.provider().block_number().await.unwrap(); -// } -// } From 1d336c612a3e5d5f0d508dc22a22e24f76139a99 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sun, 22 Sep 2024 14:44:35 -0400 Subject: [PATCH 08/12] fix test --- bin/sozo/tests/test_account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/sozo/tests/test_account.rs b/bin/sozo/tests/test_account.rs index 7564b228ec..1ed467e9b6 100644 --- a/bin/sozo/tests/test_account.rs +++ b/bin/sozo/tests/test_account.rs @@ -31,7 +31,7 @@ fn test_account_new() { } #[tokio::test] -#[katana_runner::test(accounts = 1, fee = false)] +#[katana_runner::test(accounts = 2, fee = false)] async fn test_account_fetch(runner: &RunnerCtx) { let pt = assert_fs::TempDir::new().unwrap(); From 28bcb6049af3a2da705abbf4bfdcfbd7ee0e215e Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sun, 22 Sep 2024 14:56:12 -0400 Subject: [PATCH 09/12] remove unused module --- crates/katana/runner/src/lib.rs | 7 +++++-- crates/katana/runner/src/prefunded.rs | 3 --- 2 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 crates/katana/runner/src/prefunded.rs diff --git a/crates/katana/runner/src/lib.rs b/crates/katana/runner/src/lib.rs index 52c0368ea3..f82957d4e3 100644 --- a/crates/katana/runner/src/lib.rs +++ b/crates/katana/runner/src/lib.rs @@ -1,7 +1,6 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod logs; -mod prefunded; mod utils; use std::path::PathBuf; @@ -248,7 +247,11 @@ impl KatanaRunner { /// Determines the default program path for the katana runner based on the KATANA_RUNNER_BIN /// environment variable. If not set, try to to use katana from the PATH. fn determine_default_program_path() -> String { - if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { bin } else { "katana".to_string() } + if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { + bin + } else { + "katana".to_string() + } } #[cfg(test)] diff --git a/crates/katana/runner/src/prefunded.rs b/crates/katana/runner/src/prefunded.rs deleted file mode 100644 index e7f68ab9fd..0000000000 --- a/crates/katana/runner/src/prefunded.rs +++ /dev/null @@ -1,3 +0,0 @@ -use crate::KatanaRunner; - -impl KatanaRunner {} From d8068bc901f4c751642ce574e9599bcb80ab886a Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sun, 22 Sep 2024 15:19:44 -0400 Subject: [PATCH 10/12] fmt --- crates/katana/runner/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/katana/runner/src/lib.rs b/crates/katana/runner/src/lib.rs index f82957d4e3..fc063e01cc 100644 --- a/crates/katana/runner/src/lib.rs +++ b/crates/katana/runner/src/lib.rs @@ -247,11 +247,7 @@ impl KatanaRunner { /// Determines the default program path for the katana runner based on the KATANA_RUNNER_BIN /// environment variable. If not set, try to to use katana from the PATH. fn determine_default_program_path() -> String { - if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { - bin - } else { - "katana".to_string() - } + if let Ok(bin) = std::env::var("KATANA_RUNNER_BIN") { bin } else { "katana".to_string() } } #[cfg(test)] From 0bc4d367c292b13c4c919d547fcdf6f089a6cc86 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sun, 22 Sep 2024 15:22:41 -0400 Subject: [PATCH 11/12] return error if not valid u16 --- crates/katana/runner/macro/src/config.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/katana/runner/macro/src/config.rs b/crates/katana/runner/macro/src/config.rs index e06368057a..c8a2fc484a 100644 --- a/crates/katana/runner/macro/src/config.rs +++ b/crates/katana/runner/macro/src/config.rs @@ -113,7 +113,10 @@ impl Configuration { return Err(syn::Error::new(span, "`accounts` set multiple times.")); } - let accounts = parse_int(accounts, span, "accounts")? as u16; + let int = parse_int(accounts, span, "accounts")?; + let accounts = u16::try_from(int) + .map_err(|_| syn::Error::new(span, "`accounts` must be a valid u16."))?; + self.accounts = Some(accounts); Ok(()) From c0163931c9f133af304a67f30bb31cb2b55df920 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Sun, 22 Sep 2024 15:24:54 -0400 Subject: [PATCH 12/12] clippy --- crates/katana/runner/macro/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/katana/runner/macro/src/lib.rs b/crates/katana/runner/macro/src/lib.rs index 0dbe742091..3ee2c73b37 100644 --- a/crates/katana/runner/macro/src/lib.rs +++ b/crates/katana/runner/macro/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + //! Implementation of the proc macro in this module is highly adapted from `tokio-macros` crate. //! `tokio-macro`: https://docs.rs/tokio-macros/2.4.0/tokio_macros/