fix: Convert vendor/pezkuwi-subxt from submodule to regular directory

This commit is contained in:
2025-12-19 16:45:24 +03:00
parent 9a52edf0df
commit fdd023c499
393 changed files with 154124 additions and 1 deletions
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "ui-tests"
version.workspace = true
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[dev-dependencies]
trybuild = { workspace = true }
hex = { workspace = true }
scale-info = { workspace = true, features = ["bit-vec"] }
frame-metadata = { workspace = true }
codec = { package = "parity-scale-codec", workspace = true, features = ["derive", "bit-vec"] }
pezkuwi-subxt = { workspace = true, features = ["native", "jsonrpsee", "runtime-wasm-path"] }
pezkuwi-subxt-metadata = { workspace = true }
subxt-utils-stripmetadata = { workspace = true }
generate-custom-metadata = { path = "../generate-custom-metadata" }
@@ -0,0 +1,48 @@
use codec::{Decode};
use subxt::{config::substrate::H256, OfflineClient, PolkadotConfig};
use pezkuwi_subxt_metadata::Metadata;
#[subxt::subxt(runtime_metadata_path = "../../../../artifacts/metadata_with_custom_values.scale", derive_for_all_types = "Eq, PartialEq")]
pub mod node {}
use node::runtime_types::generate_custom_metadata::Foo;
fn main() {
let api = construct_offline_client();
let expected_foo = Foo {
a: 42,
b: "Have a great day!".into(),
};
// static query:
let foo_address = node::custom().foo();
let foo = api.custom_values().at(&foo_address).unwrap();
assert_eq!(foo, expected_foo);
// dynamic query:
let foo_address = subxt::dynamic::custom_value::<Foo>("Foo");
let foo = api.custom_values().at(&foo_address).unwrap();
assert_eq!(foo, expected_foo);
// static query for some custom value that has an invalid type id: (we can still access the bytes)
let custom_bytes = api.custom_values().bytes_at("InvalidTypeId").unwrap();
assert_eq!(vec![0,1,2,3], custom_bytes);
}
fn construct_offline_client() -> OfflineClient<PolkadotConfig> {
let genesis_hash = {
let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
let bytes = hex::decode(h).unwrap();
H256::from_slice(&bytes)
};
let runtime_version = subxt::client::RuntimeVersion {
spec_version: 9370,
transaction_version: 20,
};
let metadata = {
let bytes = std::fs::read("../../../../artifacts/metadata_with_custom_values.scale").unwrap();
Metadata::decode(&mut &*bytes).unwrap()
};
OfflineClient::<PolkadotConfig>::new(genesis_hash, runtime_version, metadata)
}
@@ -0,0 +1,135 @@
use codec::{Decode, Encode};
use subxt::utils::AccountId32;
#[derive(Encode, Decode, subxt::ext::scale_encode::EncodeAsType, subxt::ext::scale_decode::DecodeAsType, Debug)]
#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
pub struct CustomAddress(u16);
#[derive(Encode, Decode, subxt::ext::scale_encode::EncodeAsType, subxt::ext::scale_decode::DecodeAsType, Debug)]
#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
pub struct Generic<T>(T);
#[derive(Encode, Decode, subxt::ext::scale_encode::EncodeAsType, subxt::ext::scale_decode::DecodeAsType, Debug)]
#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
#[decode_as_type(crate_path = "subxt::ext::scale_decode")]
pub struct Second<T, U>(T, U);
#[derive(Encode, Decode, Debug)]
pub struct DoesntImplEncodeDecodeAsType(u16);
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
// Discarding both params:
with = "crate::CustomAddress"
)
)]
pub mod node_runtime {}
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
// Discarding second param:
with = "crate::Generic<A>"
)
)]
pub mod node_runtime2 {}
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
// Discarding first param:
with = "crate::Generic<B>"
)
)]
pub mod node_runtime3 {}
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
// Swapping params:
with = "crate::Second<B, A>"
)
)]
pub mod node_runtime4 {}
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "sp_runtime::multiaddress::MultiAddress",
// Ignore input params and just use concrete types on output:
with = "crate::Second<bool, ::std::vec::Vec<u8>>"
)
)]
pub mod node_runtime5 {}
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
// We can put a static type in, too:
with = "crate::Second<B, u16>"
)
)]
pub mod node_runtime6 {}
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
// Check that things can be wrapped in our Static type:
with = "::pezkuwi_subxt::utils::Static<crate::DoesntImplEncodeDecodeAsType>"
)
)]
pub mod node_runtime7 {}
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
// Recursive type param substitution should work too (swapping out nested A and B):
with = "::pezkuwi_subxt::utils::Static<crate::Second<A, B>>"
)
)]
pub mod node_runtime8 {}
fn main() {
// We assume Polkadot's config of MultiAddress<AccountId32, ()> here
let _ = node_runtime::tx()
.balances()
.transfer_allow_death(CustomAddress(1337), 123);
let _ = node_runtime2::tx()
.balances()
.transfer_allow_death(Generic(AccountId32::from([0x01;32])), 123);
let _ = node_runtime3::tx()
.balances()
.transfer_allow_death(Generic(()), 123);
let _ = node_runtime4::tx()
.balances()
.transfer_allow_death(Second((), AccountId32::from([0x01;32])), 123);
let _ = node_runtime5::tx()
.balances()
.transfer_allow_death(Second(true, vec![1u8, 2u8]), 123);
let _ = node_runtime6::tx()
.balances()
.transfer_allow_death(Second((), 1234u16), 123);
let _ = node_runtime7::tx()
.balances()
.transfer_allow_death(subxt::utils::Static(DoesntImplEncodeDecodeAsType(1337)), 123);
let _ = node_runtime8::tx()
.balances()
.transfer_allow_death(subxt::utils::Static(Second(AccountId32::from([0x01;32]), ())), 123);
}
@@ -0,0 +1,30 @@
#[subxt::subxt(runtime_metadata_path = "../../../../artifacts/polkadot_metadata_tiny.scale")]
pub mod node_runtime {
pub struct SomeStruct;
pub enum SomeEnum {
A,
B,
}
pub trait SomeTrait {
fn some_func(&self) -> u32;
}
impl SomeTrait for SomeStruct {
fn some_func(&self) -> u32 {
1
}
}
impl SomeTrait for SomeEnum {
fn some_func(&self) -> u32 {
2
}
}
}
fn main() {
use node_runtime::SomeTrait;
let unit = node_runtime::SomeStruct;
assert_eq!(unit.some_func(), 1);
let enumeration = node_runtime::SomeEnum::A;
assert_eq!(enumeration.some_func(), 2);
}
@@ -0,0 +1,13 @@
#[subxt::subxt(runtime_path = "../../../../artifacts/westend_runtime.wasm")]
mod runtime {}
#[subxt::subxt(runtime_path = "../../../../artifacts/westend_runtime.compact.compressed.wasm")]
mod runtime_compressed {}
fn main() {
use runtime;
use runtime_compressed;
let _ = runtime::system::events::CodeUpdated;
let _ = runtime_compressed::system::events::CodeUpdated;
}
@@ -0,0 +1,23 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::utils::generate_metadata_from_pallets_custom_dispatch_error;
use generate_custom_metadata::dispatch_error::{
ArrayDispatchError, LegacyDispatchError, NamedFieldDispatchError,
};
use frame_metadata::RuntimeMetadataPrefixed;
pub fn metadata_array_dispatch_error() -> RuntimeMetadataPrefixed {
generate_metadata_from_pallets_custom_dispatch_error::<ArrayDispatchError>(vec![], vec![])
}
pub fn metadata_legacy_dispatch_error() -> RuntimeMetadataPrefixed {
generate_metadata_from_pallets_custom_dispatch_error::<LegacyDispatchError>(vec![], vec![])
}
pub fn metadata_named_field_dispatch_error() -> RuntimeMetadataPrefixed {
generate_metadata_from_pallets_custom_dispatch_error::<NamedFieldDispatchError>(vec![], vec![])
}
@@ -0,0 +1,4 @@
#[subxt::subxt()]
pub mod node_runtime {}
fn main() {}
@@ -0,0 +1,7 @@
error: At least one of 'runtime_metadata_path', 'runtime_metadata_insecure_url' or 'runtime_path` can be provided
--> src/incorrect/need_url_or_path.rs:1:1
|
1 | #[subxt::subxt()]
| ^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `subxt::subxt` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -0,0 +1,10 @@
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "sp_runtime::multiaddress::Event",
with = "crate::MyEvent"
)
)]
pub mod node_runtime {}
fn main() {}
@@ -0,0 +1,61 @@
error: Type `Event` does not exist at path `sp_runtime::multiaddress::Event`
A type with the same name is present at:
frame_system::pallet::Event
pallet_indices::pallet::Event
pallet_balances::pallet::Event
pallet_parameters::pallet::Event
pallet_transaction_payment::pallet::Event
pallet_offences::pallet::Event
pallet_session::historical::pallet::Event
pallet_session::pallet::Event
pallet_grandpa::pallet::Event
pallet_treasury::pallet::Event
pallet_conviction_voting::pallet::Event
pallet_ranked_collective::pallet::Event
pallet_whitelist::pallet::Event
polkadot_runtime_common::claims::pallet::Event
pallet_utility::pallet::Event
pallet_identity::pallet::Event
pallet_society::pallet::Event
pallet_recovery::pallet::Event
pallet_vesting::pallet::Event
pallet_scheduler::pallet::Event
pallet_proxy::pallet::Event
pallet_multisig::pallet::Event
pallet_preimage::pallet::Event
pallet_asset_rate::pallet::Event
pallet_bounties::pallet::Event
pallet_child_bounties::pallet::Event
pallet_nis::pallet::Event
pallet_balances::pallet::Event
polkadot_runtime_parachains::inclusion::pallet::Event
polkadot_runtime_parachains::paras::pallet::Event
polkadot_runtime_parachains::hrmp::pallet::Event
polkadot_runtime_parachains::disputes::pallet::Event
pallet_message_queue::pallet::Event
polkadot_runtime_parachains::on_demand::pallet::Event
polkadot_runtime_common::paras_registrar::pallet::Event
polkadot_runtime_common::slots::pallet::Event
polkadot_runtime_common::auctions::pallet::Event
polkadot_runtime_common::crowdloan::pallet::Event
polkadot_runtime_parachains::coretime::pallet::Event
pallet_migrations::pallet::Event
pallet_xcm::pallet::Event
polkadot_runtime_common::identity_migrator::pallet::Event
polkadot_runtime_common::assigned_slots::pallet::Event
rococo_runtime::validator_manager::pallet::Event
pallet_state_trie_migration::pallet::Event
pallet_root_testing::pallet::Event
pallet_sudo::pallet::Event
--> src/incorrect/substitute_at_wrong_path.rs:1:1
|
1 | / #[subxt::subxt(
2 | | runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
3 | | substitute_type(
4 | | path = "sp_runtime::multiaddress::Event",
... |
7 | | )]
| |__^
|
= note: this error originates in the attribute macro `subxt::subxt` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -0,0 +1,10 @@
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
substitute_type(
path = "frame_support::dispatch::DispatchInfo",
with = "my_mod::DispatchInfo"
)
)]
pub mod node_runtime {}
fn main() {}
@@ -0,0 +1,14 @@
error: Type `DispatchInfo` does not exist at path `frame_support::dispatch::DispatchInfo`
There is no Type with name `DispatchInfo` in the provided metadata.
--> src/incorrect/substitute_path_not_absolute.rs:1:1
|
1 | / #[subxt::subxt(
2 | | runtime_metadata_path = "../../../../artifacts/polkadot_metadata_small.scale",
3 | | substitute_type(
4 | | path = "frame_support::dispatch::DispatchInfo",
... |
7 | | )]
| |__^
|
= note: this error originates in the attribute macro `subxt::subxt` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -0,0 +1,14 @@
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_tiny.scale",
runtime_metadata_insecure_url = "wss://rpc.polkadot.io:443"
)]
pub mod node_runtime {}
#[subxt::subxt(
runtime_metadata_path = "../../../../artifacts/polkadot_metadata_tiny.scale",
runtime_metadata_insecure_url = "wss://rpc.polkadot.io:443",
runtime_path = "../../../../artifacts/westend_runtime.wasm"
)]
pub mod node_runtime2 {}
fn main() {}
@@ -0,0 +1,22 @@
error: Only one of 'runtime_metadata_path', 'runtime_metadata_insecure_url' or 'runtime_path` can be provided
--> src/incorrect/url_and_path_provided.rs:1:1
|
1 | / #[subxt::subxt(
2 | | runtime_metadata_path = "../../../../artifacts/polkadot_metadata_tiny.scale",
3 | | runtime_metadata_insecure_url = "wss://rpc.polkadot.io:443"
4 | | )]
| |__^
|
= note: this error originates in the attribute macro `subxt::subxt` (in Nightly builds, run with -Z macro-backtrace for more info)
error: Only one of 'runtime_metadata_path', 'runtime_metadata_insecure_url' or `runtime_path` must be provided
--> src/incorrect/url_and_path_provided.rs:7:1
|
7 | / #[subxt::subxt(
8 | | runtime_metadata_path = "../../../../artifacts/polkadot_metadata_tiny.scale",
9 | | runtime_metadata_insecure_url = "wss://rpc.polkadot.io:443",
10 | | runtime_path = "../../../../artifacts/westend_runtime.wasm"
11 | | )]
| |__^
|
= note: this error originates in the attribute macro `subxt::subxt` (in Nightly builds, run with -Z macro-backtrace for more info)
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
#![cfg(test)]
//! UI test set uses [`trybuild`](https://docs.rs/trybuild/latest/trybuild/index.html) to
//! check whether expected valid examples of code compile correctly, and for incorrect ones
//! errors are helpful and valid (e.g. have correct spans).
//!
//!
//! Use with `TRYBUILD=overwrite` after updating codebase (see `trybuild` docs for more details on that)
//! to automatically regenerate `stderr` files, but don't forget to check that new files make sense.
mod dispatch_errors;
mod runtime_apis;
mod storage;
mod utils;
use crate::utils::MetadataTestRunner;
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
use pezkuwi_subxt_utils_stripmetadata::StripMetadata;
// Each of these tests leads to some rust code being compiled and
// executed to test that compilation is successful (or errors in the
// way that we'd expect).
fn strip_metadata<Pallets, Apis>(
metadata: &mut RuntimeMetadataPrefixed,
pallets: Pallets,
apis: Apis,
) where
Pallets: Fn(&str) -> bool,
Apis: Fn(&str) -> bool,
{
match &mut metadata.1 {
RuntimeMetadata::V14(m) => m.strip_metadata(pallets, apis),
RuntimeMetadata::V15(m) => m.strip_metadata(pallets, apis),
RuntimeMetadata::V16(m) => m.strip_metadata(pallets, apis),
m => panic!(
"Metadata should be V14, V15 or V16, but is V{}",
m.version()
),
}
}
#[test]
fn ui_tests() {
let mut m = MetadataTestRunner::default();
let t = trybuild::TestCases::new();
t.pass("src/correct/*.rs");
// Check that storage maps with no keys are handled properly.
t.pass(
m.new_test_case()
.name("storage_map_no_keys")
.build(storage::metadata_storage_map_no_keys()),
);
// Check runtime APIs with _ in method names work
t.pass(
m.new_test_case()
.name("runtime_api_underscore_method_name")
.build(runtime_apis::metadata_runtime_api_underscore_method_name()),
);
// Test that the codegen can handle the different types of DispatchError.
t.pass(
m.new_test_case()
.name("named_field_dispatch_error")
.build(dispatch_errors::metadata_named_field_dispatch_error()),
);
t.pass(
m.new_test_case()
.name("legacy_dispatch_error")
.build(dispatch_errors::metadata_legacy_dispatch_error()),
);
t.pass(
m.new_test_case()
.name("array_dispatch_error")
.build(dispatch_errors::metadata_array_dispatch_error()),
);
// Test retaining only specific pallets and ensure that works.
for pallet in ["Babe", "Claims", "Grandpa", "Balances"] {
let mut metadata = MetadataTestRunner::load_metadata();
strip_metadata(&mut metadata, |p| p == pallet, |_| true);
t.pass(
m.new_test_case()
.name(format!("retain_pallet_{pallet}"))
.build(metadata),
);
}
// Test retaining only specific runtime APIs to ensure that works.
for runtime_api in ["Core", "Metadata"] {
let mut metadata = MetadataTestRunner::load_metadata();
strip_metadata(&mut metadata, |_| true, |r| r == runtime_api);
t.pass(
m.new_test_case()
.name(format!("retain_runtime_api_{runtime_api}"))
.build(metadata),
);
}
// Validation should succeed when metadata we codegen from is stripped and
// client state is full:
{
let mut metadata = MetadataTestRunner::load_metadata();
strip_metadata(
&mut metadata,
|p| ["Babe", "Claims"].contains(&p),
|r| ["Core", "Metadata"].contains(&r),
);
t.pass(
m.new_test_case()
.name("stripped_metadata_validates_against_full")
.validation_metadata(MetadataTestRunner::load_metadata())
.build(metadata),
);
}
// Finally as a sanity check, codegen against stripped metadata should
// _not_ compare valid against client with differently stripped metadata.
{
let mut codegen_metadata = MetadataTestRunner::load_metadata();
strip_metadata(
&mut codegen_metadata,
|p| ["Babe", "Claims"].contains(&p),
|r| ["Core", "Metadata"].contains(&r),
);
let mut validation_metadata = MetadataTestRunner::load_metadata();
strip_metadata(
&mut validation_metadata,
|p| p != "Claims",
|r| r != "Metadata",
);
t.pass(
m.new_test_case()
.name("stripped_metadata_doesnt_validate_against_different")
.validation_metadata(validation_metadata)
.expects_invalid()
.build(codegen_metadata),
);
}
}
#[test]
fn ui_fail() {
let t = trybuild::TestCases::new();
t.compile_fail("src/incorrect/*.rs");
}
@@ -0,0 +1,29 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use frame_metadata::{
v15::{RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata},
RuntimeMetadataPrefixed,
};
use crate::utils::generate_metadata_from_runtime_apis;
/// Generate metadata which contains a `Map` storage entry with no hashers/values.
/// This is a bit of an odd case, but it was raised in https://github.com/paritytech/subxt/issues/552,
/// and this test will fail before the fix and should pass once the fix is applied.
pub fn metadata_runtime_api_underscore_method_name() -> RuntimeMetadataPrefixed {
generate_metadata_from_runtime_apis(vec![RuntimeApiMetadata {
name: "MyApi".to_owned(),
docs: vec![],
methods: vec![RuntimeApiMethodMetadata {
name: "my_method".to_owned(),
inputs: vec![RuntimeApiMethodParamMetadata {
name: "_".to_owned(), // The important bit we're testing.
ty: 0.into(), // we don't care what type this is.
}],
output: 0.into(), // we don't care what type this is.
docs: vec![],
}],
}])
}
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use frame_metadata::{
v15::{StorageEntryMetadata, StorageEntryModifier, StorageEntryType},
RuntimeMetadataPrefixed,
};
use scale_info::meta_type;
use crate::utils::generate_metadata_from_storage_entries;
/// Generate metadata which contains a `Map` storage entry with no hashers/values.
/// This is a bit of an odd case, but it was raised in https://github.com/paritytech/subxt/issues/552,
/// and this test will fail before the fix and should pass once the fix is applied.
pub fn metadata_storage_map_no_keys() -> RuntimeMetadataPrefixed {
generate_metadata_from_storage_entries(vec![StorageEntryMetadata {
name: "MapWithNoKeys",
modifier: StorageEntryModifier::Optional,
ty: StorageEntryType::Map {
hashers: vec![],
key: meta_type::<()>(),
value: meta_type::<u32>(),
},
default: vec![0],
docs: vec![],
}])
}
@@ -0,0 +1,182 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use codec::{Decode, Encode};
use frame_metadata::RuntimeMetadataPrefixed;
use std::io::Read;
static TEST_DIR_PREFIX: &str = "subxt_generated_ui_tests_";
static METADATA_FILE: &str = "../../artifacts/polkadot_metadata_full.scale";
#[derive(Default)]
pub struct MetadataTestRunner {
index: usize,
}
impl MetadataTestRunner {
/// Loads metadata that we can use in our tests. Panics if
/// there is some issue decoding the metadata.
pub fn load_metadata() -> RuntimeMetadataPrefixed {
let mut file =
std::fs::File::open(METADATA_FILE).expect("Cannot open metadata.scale artifact");
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)
.expect("Failed to read metadata.scale file");
RuntimeMetadataPrefixed::decode(&mut &*bytes).expect("Cannot decode metadata bytes")
}
/// Create a new test case.
pub fn new_test_case(&mut self) -> MetadataTestRunnerCaseBuilder {
let index = self.index;
// increment index so that each test case gets its own folder path.
self.index += 1;
MetadataTestRunnerCaseBuilder::new(index)
}
}
// `trybuild` runs all tests once it's dropped. So, we defer all cleanup until we
// are dropped too, to make sure that cleanup happens after tests are ran.
impl Drop for MetadataTestRunner {
fn drop(&mut self) {
for i in 0..self.index {
let mut tmp_dir = std::env::temp_dir();
tmp_dir.push(format!("{TEST_DIR_PREFIX}{i}"));
std::fs::remove_dir_all(tmp_dir).expect("cannot cleanup temp files");
}
}
}
/// Build a single test case.
pub struct MetadataTestRunnerCaseBuilder {
index: usize,
name: String,
validation_metadata: Option<RuntimeMetadataPrefixed>,
should_be_valid: bool,
}
impl MetadataTestRunnerCaseBuilder {
fn new(index: usize) -> Self {
MetadataTestRunnerCaseBuilder {
index,
name: format!("Test {index}"),
validation_metadata: None,
should_be_valid: true,
}
}
/// Set the test name.
pub fn name(mut self, name: impl AsRef<str>) -> Self {
name.as_ref().clone_into(&mut self.name);
self
}
/// Set metadata to be validated against the generated code.
/// By default, we'll validate the same metadata used to generate the code.
pub fn validation_metadata(mut self, md: impl Into<RuntimeMetadataPrefixed>) -> Self {
self.validation_metadata = Some(md.into());
self
}
/// Expect the validation metadata provided to _not_ be valid.
pub fn expects_invalid(mut self) -> Self {
self.should_be_valid = false;
self
}
/// At the minimum, takes some metadata and a test name, generates the code
/// and hands back a path to some generated code that `trybuild` can be pointed at.
/// validation metadata and expected validity can also be provided.
///
/// The generated code:
/// - checks that the subxt macro can perform codegen given the
/// provided macro_metadata without running into any issues.
/// - checks that the `runtime::is_codegen_valid_for` function returns
/// true or false when compared to the `validation_metadata`, according
/// to whether `expects_invalid()` is set or not.
///
/// The generated code will be tidied up when the `MetadataTestRunner` that
/// this was handed out from is dropped.
pub fn build(self, macro_metadata: frame_metadata::RuntimeMetadataPrefixed) -> String {
let validation_metadata = self.validation_metadata.unwrap_or_else(|| {
// RuntimeMetadataPrefixed doesn't implement Clone for some reason (we should prob fix that).
// until then, this hack clones it by encoding and then decoding it again from bytes..
clone_via_encode(&macro_metadata)
});
let index = self.index;
let mut tmp_dir = std::env::temp_dir();
tmp_dir.push(format!("{TEST_DIR_PREFIX}{index}"));
let tmp_macro_metadata_path = {
let mut t = tmp_dir.clone();
t.push("macro_metadata.scale");
t.to_string_lossy().into_owned()
};
let tmp_validation_metadata_path = {
let mut t = tmp_dir.clone();
t.push("validation_metadata.scale");
t.to_string_lossy().into_owned()
};
let tmp_rust_path = {
let mut t = tmp_dir.clone();
let test_name = &self.name;
t.push(format!("{test_name}.rs"));
t.to_string_lossy().into_owned()
};
let encoded_macro_metadata = macro_metadata.encode();
let encoded_validation_metadata = validation_metadata.encode();
let should_be_valid_str = if self.should_be_valid {
"true"
} else {
"false"
};
let rust_file = format!(
r#"
use subxt;
use subxt::ext::codec::Decode;
use std::io::Read;
#[subxt::subxt(runtime_metadata_path = "{tmp_macro_metadata_path}")]
pub mod polkadot {{}}
fn main() {{
// load validation metadata:
let mut file = std::fs::File::open("{tmp_validation_metadata_path}")
.expect("validation_metadata exists");
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)
.expect("Failed to read metadata.scale file");
let metadata = subxt::Metadata::decode(&mut &*bytes)
.expect("Cannot decode metadata bytes");
// validate it:
let is_valid = polkadot::is_codegen_valid_for(&metadata);
assert_eq!(is_valid, {should_be_valid_str}, "expected validity to line up");
}}
"#
);
std::fs::create_dir_all(&tmp_dir).expect("could not create tmp ui test dir");
// Write metadatas to tmp folder:
std::fs::write(&tmp_macro_metadata_path, encoded_macro_metadata).unwrap();
std::fs::write(&tmp_validation_metadata_path, encoded_validation_metadata).unwrap();
// Write test file to tmp folder (it'll be moved by trybuild):
std::fs::write(&tmp_rust_path, rust_file).unwrap();
tmp_rust_path
}
}
fn clone_via_encode<T: codec::Encode + codec::Decode>(item: &T) -> T {
let bytes = item.encode();
T::decode(&mut &*bytes).unwrap()
}
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
mod metadata_test_runner;
use frame_metadata::{
v15::{
CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, PalletStorageMetadata,
RuntimeApiMetadata, RuntimeMetadataV15, StorageEntryMetadata,
},
RuntimeMetadataPrefixed,
};
use generate_custom_metadata::dispatch_error::ArrayDispatchError;
use scale_info::{form::PortableForm, meta_type, IntoPortable, TypeInfo};
pub use metadata_test_runner::MetadataTestRunner;
/// Given some pallet metadata, generate a [`RuntimeMetadataPrefixed`] struct.
/// We default to a useless extrinsic type, and register a fake `DispatchError`
/// type matching the generic type param provided.
pub fn generate_metadata_from_pallets_custom_dispatch_error<DispatchError: TypeInfo + 'static>(
pallets: Vec<PalletMetadata>,
runtime_apis: Vec<RuntimeApiMetadata<PortableForm>>,
) -> RuntimeMetadataPrefixed {
// We don't care about the extrinsic type.
let extrinsic = ExtrinsicMetadata {
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<()>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
// Construct metadata manually from our types (See `RuntimeMetadataV15::new()`).
// Add any extra types we need to the registry.
let mut registry = scale_info::Registry::new();
let pallets = registry.map_into_portable(pallets);
let extrinsic = extrinsic.into_portable(&mut registry);
#[derive(TypeInfo)]
struct Runtime;
#[derive(TypeInfo)]
enum RuntimeCall {}
#[derive(TypeInfo)]
enum RuntimeEvent {}
#[derive(TypeInfo)]
enum RuntimeError {}
let ty = registry.register_type(&meta_type::<Runtime>());
let runtime_call = registry.register_type(&meta_type::<RuntimeCall>());
let runtime_event = registry.register_type(&meta_type::<RuntimeEvent>());
let runtime_error = registry.register_type(&meta_type::<RuntimeError>());
// Metadata needs to contain this DispatchError, since codegen looks for it.
registry.register_type(&meta_type::<DispatchError>());
let metadata = RuntimeMetadataV15 {
types: registry.into(),
pallets,
extrinsic,
ty,
apis: runtime_apis,
outer_enums: OuterEnums {
call_enum_ty: runtime_call,
event_enum_ty: runtime_event,
error_enum_ty: runtime_error,
},
custom: CustomMetadata {
map: Default::default(),
},
};
RuntimeMetadataPrefixed::from(metadata)
}
/// Given some pallet metadata, generate a [`RuntimeMetadataPrefixed`] struct.
/// We default to a useless extrinsic type, and register a fake `DispatchError`
/// type so that codegen is happy with the metadata generated.
pub fn generate_metadata_from_pallets(pallets: Vec<PalletMetadata>) -> RuntimeMetadataPrefixed {
generate_metadata_from_pallets_custom_dispatch_error::<ArrayDispatchError>(pallets, vec![])
}
/// Given some runtime API metadata, generate a [`RuntimeMetadataPrefixed`] struct.
/// We default to a useless extrinsic type, and register a fake `DispatchError`
/// type so that codegen is happy with the metadata generated.
pub fn generate_metadata_from_runtime_apis(
runtime_apis: Vec<RuntimeApiMetadata<PortableForm>>,
) -> RuntimeMetadataPrefixed {
generate_metadata_from_pallets_custom_dispatch_error::<ArrayDispatchError>(vec![], runtime_apis)
}
/// Given some storage entries, generate a [`RuntimeMetadataPrefixed`] struct.
/// We default to a useless extrinsic type, mock a pallet out, and register a
/// fake `DispatchError` type so that codegen is happy with the metadata generated.
pub fn generate_metadata_from_storage_entries(
storage_entries: Vec<StorageEntryMetadata>,
) -> RuntimeMetadataPrefixed {
let storage = PalletStorageMetadata {
prefix: "System",
entries: storage_entries,
};
let pallet = PalletMetadata {
index: 0,
name: "System",
storage: Some(storage),
constants: vec![],
calls: None,
event: None,
error: None,
docs: vec![],
};
generate_metadata_from_pallets(vec![pallet])
}