Compare commits

..

5 Commits

Author SHA1 Message Date
Omar Abdulla a542beaafa Remoe unneeded dependencies 2026-01-13 22:32:43 +03:00
Omar Abdulla c60b6af50a Remove outdated polling function 2026-01-13 22:23:14 +03:00
Omar Abdulla 0ad80b981f Rename the retry layer 2026-01-13 22:14:59 +03:00
Omar Abdulla 2568f7f828 Fix the retry layer 2026-01-13 22:13:48 +03:00
Omar Abdulla 6550c5ff7f Add a ReceiptRetryLayer for providers 2026-01-13 16:11:30 +03:00
20 changed files with 1243 additions and 1802 deletions
@@ -18,9 +18,10 @@ inputs:
required: false required: false
default: "main" default: "main"
type: string type: string
resolc-path: resolc-version:
description: "The path of the resolc compiler." description: "The version of resolc to install and use in tests."
required: true required: false
default: "0.5.0"
type: string type: string
use-compilation-caches: use-compilation-caches:
description: "Controls if the compilation caches will be used for the test run or not." description: "Controls if the compilation caches will be used for the test run or not."
@@ -28,26 +29,18 @@ inputs:
default: true default: true
type: boolean type: boolean
# Test Execution Arguments # Test Execution Arguments
# TODO: We need a better way for people to pass arguments to retester. This way is not very good
# because we need to add support for each argument separately and support defaults and all of that
# perhaps having people pass in a JSON String of the arguments is the better long term solution
# for this.
platform: platform:
description: "The identifier of the platform to run the tests on (e.g., geth-evm-solc, revive-dev-node-revm-solc)" description: "The identifier of the platform to run the tests on (e.g., geth-evm-solc, revive-dev-node-revm-solc)"
required: true required: true
type: string type: string
polkadot-omnichain-node-chain-spec-path: polkadot-omnichain-node-runtime-path:
description: "The path of the chain-spec of the chain we're spawning'. This is only required if the polkadot-omni-node is one of the selected platforms." description: "The path of the WASM runtime to use with the polkadot-omni-node. This is only required if the polkadot-omni-node is one of the selected platforms."
required: false required: false
type: string type: string
polkadot-omnichain-node-parachain-id: polkadot-omnichain-node-parachain-id:
description: "The id of the parachain to spawn with the polkadot-omni-node. This is only required if the polkadot-omni-node is one of the selected platforms." description: "The id of the parachain to spawn with the polkadot-omni-node. This is only required if the polkadot-omni-node is one of the selected platforms."
type: number type: number
required: false required: false
expectations-file-path:
description: "Path to the expectations file to use to compare against."
type: string
required: false
runs: runs:
using: "composite" using: "composite"
@@ -59,6 +52,16 @@ runs:
ref: ${{ inputs['revive-differential-tests-ref'] }} ref: ${{ inputs['revive-differential-tests-ref'] }}
path: revive-differential-tests path: revive-differential-tests
submodules: recursive submodules: recursive
- name: Installing the Latest Resolc
shell: bash
if: ${{ runner.os == 'Linux' && runner.arch == 'X64' }}
run: |
VERSION="${{ inputs['resolc-version'] }}"
ASSET_URL="https://github.com/paritytech/revive/releases/download/v$VERSION/resolc-x86_64-unknown-linux-musl"
echo "Downloading resolc v$VERSION from $ASSET_URL"
curl -Lsf --show-error -o resolc "$ASSET_URL"
chmod +x resolc
./resolc --version
- name: Installing Retester - name: Installing Retester
shell: bash shell: bash
run: ${{ inputs['cargo-command'] }} install --locked --path revive-differential-tests/crates/core run: ${{ inputs['cargo-command'] }} install --locked --path revive-differential-tests/crates/core
@@ -76,12 +79,6 @@ runs:
run: | run: |
${{ inputs['cargo-command'] }} build --locked --profile release -p pallet-revive-eth-rpc -p revive-dev-node --manifest-path ${{ inputs['polkadot-sdk-path'] }}/Cargo.toml ${{ inputs['cargo-command'] }} build --locked --profile release -p pallet-revive-eth-rpc -p revive-dev-node --manifest-path ${{ inputs['polkadot-sdk-path'] }}/Cargo.toml
${{ inputs['cargo-command'] }} build --locked --profile release --bin polkadot-omni-node --manifest-path ${{ inputs['polkadot-sdk-path'] }}/Cargo.toml ${{ inputs['cargo-command'] }} build --locked --profile release --bin polkadot-omni-node --manifest-path ${{ inputs['polkadot-sdk-path'] }}/Cargo.toml
- name: Installing retester
shell: bash
run: ${{ inputs['cargo-command'] }} install --path ./revive-differential-tests/crates/core
- name: Installing report-processor
shell: bash
run: ${{ inputs['cargo-command'] }} install --path ./revive-differential-tests/crates/report-processor
- name: Running the Differential Tests - name: Running the Differential Tests
shell: bash shell: bash
run: | run: |
@@ -92,19 +89,18 @@ runs:
"${{ inputs['polkadot-omnichain-node-parachain-id'] }}" "${{ inputs['polkadot-omnichain-node-parachain-id'] }}"
) )
fi fi
if [[ -n "${{ inputs['polkadot-omnichain-node-chain-spec-path'] }}" ]]; then if [[ -n "${{ inputs['polkadot-omnichain-node-runtime-path'] }}" ]]; then
OMNI_ARGS+=( OMNI_ARGS+=(
--polkadot-omni-node.chain-spec-path --polkadot-omni-node.runtime-wasm-path
"${{ inputs['polkadot-omnichain-node-chain-spec-path'] }}" "${{ inputs['polkadot-omnichain-node-runtime-path'] }}"
) )
fi fi
retester test \ ${{ inputs['cargo-command'] }} run --locked --manifest-path revive-differential-tests/Cargo.toml -- test \
--test ./revive-differential-tests/resolc-compiler-tests/fixtures/solidity/simple \ --test ./revive-differential-tests/resolc-compiler-tests/fixtures/solidity/simple \
--test ./revive-differential-tests/resolc-compiler-tests/fixtures/solidity/complex \ --test ./revive-differential-tests/resolc-compiler-tests/fixtures/solidity/complex \
--test ./revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests \ --test ./revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests \
--platform ${{ inputs['platform'] }} \ --platform ${{ inputs['platform'] }} \
--report.file-name report.json \
--concurrency.number-of-nodes 10 \ --concurrency.number-of-nodes 10 \
--concurrency.number-of-threads 10 \ --concurrency.number-of-threads 10 \
--concurrency.number-of-concurrent-tasks 100 \ --concurrency.number-of-concurrent-tasks 100 \
@@ -113,23 +109,23 @@ runs:
--revive-dev-node.path ${{ inputs['polkadot-sdk-path'] }}/target/release/revive-dev-node \ --revive-dev-node.path ${{ inputs['polkadot-sdk-path'] }}/target/release/revive-dev-node \
--eth-rpc.path ${{ inputs['polkadot-sdk-path'] }}/target/release/eth-rpc \ --eth-rpc.path ${{ inputs['polkadot-sdk-path'] }}/target/release/eth-rpc \
--polkadot-omni-node.path ${{ inputs['polkadot-sdk-path'] }}/target/release/polkadot-omni-node \ --polkadot-omni-node.path ${{ inputs['polkadot-sdk-path'] }}/target/release/polkadot-omni-node \
--resolc.path ${{ inputs['resolc-path'] }} \ --resolc.path ./resolc \
--resolc.heap-size 500000 \ "${OMNI_ARGS[@]}"
"${OMNI_ARGS[@]}" || true - name: Creating a markdown report of the test execution
- name: Generate the expectation file
shell: bash shell: bash
run: report-processor generate-expectations-file --report-path ./workdir/report.json --output-path ./workdir/expectations.json --remove-prefix ./revive-differential-tests/resolc-compiler-tests --include-status failed if: ${{ always() }}
run: |
mv ./workdir/*.json report.json
python3 revive-differential-tests/scripts/process-differential-tests-report.py report.json ${{ inputs['platform'] }}
- name: Upload the Report to the CI - name: Upload the Report to the CI
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
if: ${{ always() }}
with: with:
name: ${{ inputs['platform'] }}-report.json name: report-${{ inputs['platform'] }}.md
path: ./workdir/report.json path: report.md
- name: Upload the Report to the CI - name: Posting the report as a comment on the PR
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405
if: ${{ always() }}
with: with:
name: ${{ inputs['platform'] }}.json header: diff-tests-report-${{ inputs['platform'] }}
path: ./workdir/expectations.json path: report.md
- name: Check Expectations
shell: bash
if: ${{ inputs['expectations-file-path'] != '' }}
run: report-processor compare-expectation-files --base-expectation-path ${{ inputs['expectations-file-path'] }} --other-expectation-path ./workdir/expectations.json
Generated
+1073 -1074
View File
File diff suppressed because it is too large Load Diff
+3 -9
View File
@@ -21,7 +21,6 @@ revive-dt-node-interaction = { version = "0.1.0", path = "crates/node-interactio
revive-dt-node-pool = { version = "0.1.0", path = "crates/node-pool" } revive-dt-node-pool = { version = "0.1.0", path = "crates/node-pool" }
revive-dt-report = { version = "0.1.0", path = "crates/report" } revive-dt-report = { version = "0.1.0", path = "crates/report" }
revive-dt-solc-binaries = { version = "0.1.0", path = "crates/solc-binaries" } revive-dt-solc-binaries = { version = "0.1.0", path = "crates/solc-binaries" }
revive-dt-report-processor = { version = "0.1.0", path = "crates/report-processor" }
alloy = { version = "1.4.1", features = ["full", "genesis", "json-rpc"] } alloy = { version = "1.4.1", features = ["full", "genesis", "json-rpc"] }
ansi_term = "0.12.1" ansi_term = "0.12.1"
@@ -74,20 +73,15 @@ indexmap = { version = "2.10.0", default-features = false }
itertools = { version = "0.14.0" } itertools = { version = "0.14.0" }
# revive compiler # revive compiler
revive-solc-json-interface = { version = "0.5.0" } revive-solc-json-interface = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
revive-common = { version = "0.3.0" } revive-common = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
revive-differential = { version = "0.3.0" } revive-differential = { git = "https://github.com/paritytech/revive", rev = "3389865af7c3ff6f29a586d82157e8bc573c1a8e" }
zombienet-sdk = { git = "https://github.com/paritytech/zombienet-sdk.git", rev = "891f6554354ce466abd496366dbf8b4f82141241" } zombienet-sdk = { git = "https://github.com/paritytech/zombienet-sdk.git", rev = "891f6554354ce466abd496366dbf8b4f82141241" }
[profile.bench] [profile.bench]
inherits = "release" inherits = "release"
codegen-units = 1
lto = true lto = true
[profile.production]
inherits = "release"
codegen-units = 1 codegen-units = 1
lto = true
[workspace.lints.clippy] [workspace.lints.clippy]
-6
View File
@@ -32,12 +32,8 @@ pub enum PlatformIdentifier {
/// The Lighthouse Go-ethereum reference full node EVM implementation with the solc compiler. /// The Lighthouse Go-ethereum reference full node EVM implementation with the solc compiler.
LighthouseGethEvmSolc, LighthouseGethEvmSolc,
/// The revive dev node with the PolkaVM backend with the resolc compiler. /// The revive dev node with the PolkaVM backend with the resolc compiler.
#[strum(serialize = "revive-dev-node-polkavm-resolc", serialize = "pez-revive-dev-node-polkavm-resolc")]
#[serde(alias = "pez-revive-dev-node-polkavm-resolc")]
ReviveDevNodePolkavmResolc, ReviveDevNodePolkavmResolc,
/// The revive dev node with the REVM backend with the solc compiler. /// The revive dev node with the REVM backend with the solc compiler.
#[strum(serialize = "revive-dev-node-revm-solc", serialize = "pez-revive-dev-node-revm-solc")]
#[serde(alias = "pez-revive-dev-node-revm-solc")]
ReviveDevNodeRevmSolc, ReviveDevNodeRevmSolc,
/// A zombienet based Substrate/Polkadot node with the PolkaVM backend with the resolc compiler. /// A zombienet based Substrate/Polkadot node with the PolkaVM backend with the resolc compiler.
ZombienetPolkavmResolc, ZombienetPolkavmResolc,
@@ -102,8 +98,6 @@ pub enum NodeIdentifier {
/// The go-ethereum node implementation. /// The go-ethereum node implementation.
LighthouseGeth, LighthouseGeth,
/// The revive dev node implementation. /// The revive dev node implementation.
#[strum(serialize = "revive-dev-node", serialize = "pez-revive-dev-node")]
#[serde(alias = "pez-revive-dev-node")]
ReviveDevNode, ReviveDevNode,
/// A zombienet spawned nodes /// A zombienet spawned nodes
Zombienet, Zombienet,
-12
View File
@@ -23,18 +23,6 @@ pub struct Mode {
pub version: Option<semver::VersionReq>, pub version: Option<semver::VersionReq>,
} }
impl Ord for Mode {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.to_string().cmp(&other.to_string())
}
}
impl PartialOrd for Mode {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Display for Mode { impl Display for Mode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.pipeline.fmt(f)?; self.pipeline.fmt(f)?;
@@ -1,15 +1,10 @@
use std::{ use std::{fmt::Display, path::PathBuf, str::FromStr};
fmt::Display,
path::{Path, PathBuf},
str::FromStr,
};
use anyhow::{Context as _, bail}; use anyhow::{Context as _, bail};
use serde::{Deserialize, Serialize};
use crate::types::Mode; use crate::types::Mode;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ParsedTestSpecifier { pub enum ParsedTestSpecifier {
/// All of the test cases in the file should be ran across all of the specified modes /// All of the test cases in the file should be ran across all of the specified modes
FileOrDirectory { FileOrDirectory {
@@ -39,22 +34,6 @@ pub enum ParsedTestSpecifier {
}, },
} }
impl ParsedTestSpecifier {
pub fn metadata_path(&self) -> &Path {
match self {
ParsedTestSpecifier::FileOrDirectory {
metadata_or_directory_file_path: metadata_file_path,
}
| ParsedTestSpecifier::Case {
metadata_file_path, ..
}
| ParsedTestSpecifier::CaseWithMode {
metadata_file_path, ..
} => metadata_file_path,
}
}
}
impl Display for ParsedTestSpecifier { impl Display for ParsedTestSpecifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
@@ -152,22 +131,3 @@ impl TryFrom<&str> for ParsedTestSpecifier {
value.parse() value.parse()
} }
} }
impl Serialize for ParsedTestSpecifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ParsedTestSpecifier {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
string.parse().map_err(serde::de::Error::custom)
}
}
+25 -73
View File
@@ -12,13 +12,9 @@ use dashmap::DashMap;
use revive_dt_common::types::VersionOrRequirement; use revive_dt_common::types::VersionOrRequirement;
use revive_dt_config::{ResolcConfiguration, SolcConfiguration, WorkingDirectoryConfiguration}; use revive_dt_config::{ResolcConfiguration, SolcConfiguration, WorkingDirectoryConfiguration};
use revive_solc_json_interface::{ use revive_solc_json_interface::{
PolkaVMDefaultHeapMemorySize, PolkaVMDefaultStackMemorySize, SolcStandardJsonInput, SolcStandardJsonInput, SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings,
SolcStandardJsonInputLanguage, SolcStandardJsonInputSettings, SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsSelection,
SolcStandardJsonInputSettingsLibraries, SolcStandardJsonInputSettingsMetadata, SolcStandardJsonOutput,
SolcStandardJsonInputSettingsOptimizer, SolcStandardJsonInputSettingsPolkaVM,
SolcStandardJsonInputSettingsPolkaVMMemory, SolcStandardJsonInputSettingsSelection,
SolcStandardJsonOutput, standard_json::input::settings::optimizer::Optimizer,
standard_json::input::settings::optimizer::details::Details,
}; };
use tracing::{Span, field::display}; use tracing::{Span, field::display};
@@ -29,7 +25,6 @@ use crate::{
use alloy::json_abi::JsonAbi; use alloy::json_abi::JsonAbi;
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use semver::Version; use semver::Version;
use std::collections::BTreeSet;
use tokio::{io::AsyncWriteExt, process::Command as AsyncCommand}; use tokio::{io::AsyncWriteExt, process::Command as AsyncCommand};
/// A wrapper around the `resolc` binary, emitting PVM-compatible bytecode. /// A wrapper around the `resolc` binary, emitting PVM-compatible bytecode.
@@ -42,10 +37,6 @@ struct ResolcInner {
solc: Solc, solc: Solc,
/// Path to the `resolc` executable /// Path to the `resolc` executable
resolc_path: PathBuf, resolc_path: PathBuf,
/// The PVM heap size in bytes.
pvm_heap_size: u32,
/// The PVM stack size in bytes.
pvm_stack_size: u32,
} }
impl Resolc { impl Resolc {
@@ -72,35 +63,10 @@ impl Resolc {
Self(Arc::new(ResolcInner { Self(Arc::new(ResolcInner {
solc, solc,
resolc_path: resolc_configuration.path.clone(), resolc_path: resolc_configuration.path.clone(),
pvm_heap_size: resolc_configuration
.heap_size
.unwrap_or(PolkaVMDefaultHeapMemorySize),
pvm_stack_size: resolc_configuration
.stack_size
.unwrap_or(PolkaVMDefaultStackMemorySize),
})) }))
}) })
.clone()) .clone())
} }
fn polkavm_settings(&self) -> SolcStandardJsonInputSettingsPolkaVM {
SolcStandardJsonInputSettingsPolkaVM::new(
Some(SolcStandardJsonInputSettingsPolkaVMMemory::new(
Some(self.0.pvm_heap_size),
Some(self.0.pvm_stack_size),
)),
false,
)
}
fn inject_polkavm_settings(&self, input: &SolcStandardJsonInput) -> Result<serde_json::Value> {
let mut input_value = serde_json::to_value(input)
.context("Failed to serialize Standard JSON input for resolc")?;
if let Some(settings) = input_value.get_mut("settings") {
settings["polkavm"] = serde_json::to_value(self.polkavm_settings()).unwrap();
}
Ok(input_value)
}
} }
impl SolidityCompiler for Resolc { impl SolidityCompiler for Resolc {
@@ -155,8 +121,8 @@ impl SolidityCompiler for Resolc {
.collect(), .collect(),
settings: SolcStandardJsonInputSettings { settings: SolcStandardJsonInputSettings {
evm_version, evm_version,
libraries: SolcStandardJsonInputSettingsLibraries { libraries: Some(
inner: libraries libraries
.into_iter() .into_iter()
.map(|(source_code, libraries_map)| { .map(|(source_code, libraries_map)| {
( (
@@ -170,29 +136,23 @@ impl SolidityCompiler for Resolc {
) )
}) })
.collect(), .collect(),
}, ),
remappings: BTreeSet::<String>::new(), remappings: None,
output_selection: SolcStandardJsonInputSettingsSelection::new_required(), output_selection: Some(SolcStandardJsonInputSettingsSelection::new_required()),
via_ir: Some(true), via_ir: Some(true),
optimizer: SolcStandardJsonInputSettingsOptimizer::new( optimizer: SolcStandardJsonInputSettingsOptimizer::new(
optimization optimization
.unwrap_or(ModeOptimizerSetting::M0) .unwrap_or(ModeOptimizerSetting::M0)
.optimizations_enabled(), .optimizations_enabled(),
Optimizer::default_mode(), None,
Details::disabled(&Version::new(0, 0, 0)), &Version::new(0, 0, 0),
false,
), ),
polkavm: self.polkavm_settings(), metadata: None,
metadata: SolcStandardJsonInputSettingsMetadata::default(), polkavm: None,
detect_missing_libraries: false,
}, },
}; };
// Manually inject polkavm settings since it's marked skip_serializing in the upstream crate Span::current().record("json_in", display(serde_json::to_string(&input).unwrap()));
let std_input_json = self.inject_polkavm_settings(&input)?;
Span::current().record(
"json_in",
display(serde_json::to_string(&std_input_json).unwrap()),
);
let path = &self.0.resolc_path; let path = &self.0.resolc_path;
let mut command = AsyncCommand::new(path); let mut command = AsyncCommand::new(path);
@@ -221,9 +181,8 @@ impl SolidityCompiler for Resolc {
.with_context(|| format!("Failed to spawn resolc at {}", path.display()))?; .with_context(|| format!("Failed to spawn resolc at {}", path.display()))?;
let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped"); let stdin_pipe = child.stdin.as_mut().expect("stdin must be piped");
let serialized_input = serde_json::to_vec(&std_input_json) let serialized_input = serde_json::to_vec(&input)
.context("Failed to serialize Standard JSON input for resolc")?; .context("Failed to serialize Standard JSON input for resolc")?;
stdin_pipe stdin_pipe
.write_all(&serialized_input) .write_all(&serialized_input)
.await .await
@@ -269,7 +228,7 @@ impl SolidityCompiler for Resolc {
// Detecting if the compiler output contained errors and reporting them through logs and // Detecting if the compiler output contained errors and reporting them through logs and
// errors instead of returning the compiler output that might contain errors. // errors instead of returning the compiler output that might contain errors.
for error in parsed.errors.iter() { for error in parsed.errors.iter().flatten() {
if error.severity == "error" { if error.severity == "error" {
tracing::error!( tracing::error!(
?error, ?error,
@@ -281,12 +240,12 @@ impl SolidityCompiler for Resolc {
} }
} }
if parsed.contracts.is_empty() { let Some(contracts) = parsed.contracts else {
anyhow::bail!("Unexpected error - resolc output doesn't have a contracts section"); anyhow::bail!("Unexpected error - resolc output doesn't have a contracts section");
} };
let mut compiler_output = CompilerOutput::default(); let mut compiler_output = CompilerOutput::default();
for (source_path, contracts) in parsed.contracts.into_iter() { for (source_path, contracts) in contracts.into_iter() {
let src_for_msg = source_path.clone(); let src_for_msg = source_path.clone();
let source_path = PathBuf::from(source_path) let source_path = PathBuf::from(source_path)
.canonicalize() .canonicalize()
@@ -294,22 +253,15 @@ impl SolidityCompiler for Resolc {
let map = compiler_output.contracts.entry(source_path).or_default(); let map = compiler_output.contracts.entry(source_path).or_default();
for (contract_name, contract_information) in contracts.into_iter() { for (contract_name, contract_information) in contracts.into_iter() {
let Some(bytecode) = contract_information let bytecode = contract_information
.evm .evm
.and_then(|evm| evm.bytecode.clone()) .and_then(|evm| evm.bytecode.clone())
else { .context("Unexpected - Contract compiled with resolc has no bytecode")?;
tracing::debug!(
"Skipping abstract or interface contract {} - no bytecode",
contract_name
);
continue;
};
let abi = { let abi = {
let metadata = &contract_information.metadata; let metadata = contract_information
if metadata.is_null() { .metadata
anyhow::bail!("No metadata found for the contract"); .as_ref()
} .context("No metadata found for the contract")?;
let solc_metadata_str = match metadata { let solc_metadata_str = match metadata {
serde_json::Value::String(solc_metadata_str) => { serde_json::Value::String(solc_metadata_str) => {
solc_metadata_str.as_str() solc_metadata_str.as_str()
+5 -20
View File
@@ -800,18 +800,6 @@ pub struct ResolcConfiguration {
/// provided in the user's $PATH. /// provided in the user's $PATH.
#[clap(id = "resolc.path", long = "resolc.path", default_value = "resolc")] #[clap(id = "resolc.path", long = "resolc.path", default_value = "resolc")]
pub path: PathBuf, pub path: PathBuf,
/// Specifies the PVM heap size in bytes.
///
/// If unspecified, the revive compiler default is used
#[clap(id = "resolc.heap-size", long = "resolc.heap-size")]
pub heap_size: Option<u32>,
/// Specifies the PVM stack size in bytes.
///
/// If unspecified, the revive compiler default is used
#[clap(id = "resolc.stack-size", long = "resolc.stack-size")]
pub stack_size: Option<u32>,
} }
/// A set of configuration parameters for Polkadot Parachain. /// A set of configuration parameters for Polkadot Parachain.
@@ -955,12 +943,13 @@ pub struct PolkadotOmnichainNodeConfiguration {
)] )]
pub block_time: Duration, pub block_time: Duration,
/// The path of the chainspec of the chain that we're spawning /// The path of the WASM runtime to use for the polkadot-omni-node. This argument is required if
/// the polkadot-omni-node is one of the selected platforms for running the tests or benchmarks.
#[clap( #[clap(
id = "polkadot-omni-node.chain-spec-path", id = "polkadot-omni-node.runtime-wasm-path",
long = "polkadot-omni-node.chain-spec-path" long = "polkadot-omni-node.runtime-wasm-path"
)] )]
pub chain_spec_path: Option<PathBuf>, pub runtime_wasm_path: Option<PathBuf>,
/// The ID of the parachain that the polkadot-omni-node will spawn. This argument is required if /// The ID of the parachain that the polkadot-omni-node will spawn. This argument is required if
/// the polkadot-omni-node is one of the selected platforms for running the tests or benchmarks. /// the polkadot-omni-node is one of the selected platforms for running the tests or benchmarks.
@@ -1125,10 +1114,6 @@ pub struct ReportConfiguration {
/// Controls if the compiler output is included in the final report. /// Controls if the compiler output is included in the final report.
#[clap(long = "report.include-compiler-output")] #[clap(long = "report.include-compiler-output")]
pub include_compiler_output: bool, pub include_compiler_output: bool,
/// The filename to use for the report.
#[clap(long = "report.file-name")]
pub file_name: Option<String>,
} }
#[derive(Clone, Debug, Parser, Serialize, Deserialize)] #[derive(Clone, Debug, Parser, Serialize, Deserialize)]
+9 -9
View File
@@ -482,16 +482,15 @@ where
.context("Failed to find deployment receipt for constructor call"), .context("Failed to find deployment receipt for constructor call"),
Method::Fallback | Method::FunctionName(_) => { Method::Fallback | Method::FunctionName(_) => {
let resolver = self.platform_information.node.resolver().await?; let resolver = self.platform_information.node.resolver().await?;
let mut tx = step let tx = match step
.as_transaction(resolver.as_ref(), self.default_resolution_context()) .as_transaction(resolver.as_ref(), self.default_resolution_context())
.await?; .await
{
let gas_overrides = step Ok(tx) => tx,
.gas_overrides Err(err) => {
.get(&self.platform_information.platform.platform_identifier()) return Err(err);
.copied() }
.unwrap_or_default(); };
gas_overrides.apply_to::<Ethereum>(&mut tx);
self.platform_information.node.execute_transaction(tx).await self.platform_information.node.execute_transaction(tx).await
} }
@@ -912,6 +911,7 @@ where
.get(contract_instance) .get(contract_instance)
{ {
info!( info!(
%address, %address,
"Contract instance already deployed." "Contract instance already deployed."
); );
+7 -14
View File
@@ -223,24 +223,17 @@ impl<'a> TestDefinition<'a> {
/// Checks if the platforms all support the desired targets in the metadata file. /// Checks if the platforms all support the desired targets in the metadata file.
fn check_target_compatibility(&self) -> TestCheckFunctionResult { fn check_target_compatibility(&self) -> TestCheckFunctionResult {
// The case targets takes presence over the metadata targets.
let Some(targets) = self
.case
.targets
.as_ref()
.or(self.metadata.targets.as_ref())
else {
return Ok(());
};
let mut error_map = indexmap! { let mut error_map = indexmap! {
"test_desired_targets" => json!(targets), "test_desired_targets" => json!(self.metadata.targets.as_ref()),
}; };
let mut is_allowed = true; let mut is_allowed = true;
for (_, platform_information) in self.platforms.iter() { for (_, platform_information) in self.platforms.iter() {
let is_allowed_for_platform = let is_allowed_for_platform = match self.metadata.targets.as_ref() {
targets.contains(&platform_information.platform.vm_identifier()); None => true,
Some(required_vm_identifiers) => {
required_vm_identifiers.contains(&platform_information.platform.vm_identifier())
}
};
is_allowed &= is_allowed_for_platform; is_allowed &= is_allowed_for_platform;
error_map.insert( error_map.insert(
platform_information.platform.platform_identifier().into(), platform_information.platform.platform_identifier().into(),
+10 -2
View File
@@ -486,9 +486,13 @@ impl Platform for PolkadotOmniNodePolkavmResolcPlatform {
let wallet = AsRef::<WalletConfiguration>::as_ref(&context).wallet(); let wallet = AsRef::<WalletConfiguration>::as_ref(&context).wallet();
PolkadotOmnichainNode::node_genesis( PolkadotOmnichainNode::node_genesis(
&polkadot_omnichain_node_configuration.path,
&wallet, &wallet,
polkadot_omnichain_node_configuration polkadot_omnichain_node_configuration
.chain_spec_path .parachain_id
.context("No parachain id found in the configuration of the polkadot-omni-node")?,
polkadot_omnichain_node_configuration
.runtime_wasm_path
.as_ref() .as_ref()
.context("No WASM runtime path found in the polkadot-omni-node configuration")?, .context("No WASM runtime path found in the polkadot-omni-node configuration")?,
) )
@@ -546,9 +550,13 @@ impl Platform for PolkadotOmniNodeRevmSolcPlatform {
let wallet = AsRef::<WalletConfiguration>::as_ref(&context).wallet(); let wallet = AsRef::<WalletConfiguration>::as_ref(&context).wallet();
PolkadotOmnichainNode::node_genesis( PolkadotOmnichainNode::node_genesis(
&polkadot_omnichain_node_configuration.path,
&wallet, &wallet,
polkadot_omnichain_node_configuration polkadot_omnichain_node_configuration
.chain_spec_path .parachain_id
.context("No parachain id found in the configuration of the polkadot-omni-node")?,
polkadot_omnichain_node_configuration
.runtime_wasm_path
.as_ref() .as_ref()
.context("No WASM runtime path found in the polkadot-omni-node configuration")?, .context("No WASM runtime path found in the polkadot-omni-node configuration")?,
) )
+2 -8
View File
@@ -1,22 +1,16 @@
use alloy::primitives::{Address, map::HashSet}; use alloy::primitives::Address;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use revive_dt_common::{ use revive_dt_common::{
macros::define_wrapper_type, macros::define_wrapper_type,
types::{Mode, ParsedMode, VmIdentifier}, types::{Mode, ParsedMode},
}; };
use crate::steps::*; use crate::steps::*;
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)] #[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
pub struct Case { pub struct Case {
/// An optional vector of targets that this Metadata file's cases can be executed on. As an
/// example, if we wish for the metadata file's cases to only be run on PolkaVM then we'd
/// specify a target of "PolkaVM" in here.
#[serde(skip_serializing_if = "Option::is_none")]
pub targets: Option<HashSet<VmIdentifier>>,
/// An optional name of the test case. /// An optional name of the test case.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>, pub name: Option<String>,
+1 -2
View File
@@ -8,7 +8,6 @@ use std::{
str::FromStr, str::FromStr,
}; };
use alloy::primitives::map::HashSet;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -84,7 +83,7 @@ pub struct Metadata {
/// example, if we wish for the metadata file's cases to only be run on PolkaVM then we'd /// example, if we wish for the metadata file's cases to only be run on PolkaVM then we'd
/// specify a target of "PolkaVM" in here. /// specify a target of "PolkaVM" in here.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub targets: Option<HashSet<VmIdentifier>>, pub targets: Option<Vec<VmIdentifier>>,
/// A vector of the test cases and workloads contained within the metadata file. This is their /// A vector of the test cases and workloads contained within the metadata file. This is their
/// primary description. /// primary description.
-63
View File
@@ -1,7 +1,6 @@
use std::{collections::HashMap, fmt::Display, str::FromStr}; use std::{collections::HashMap, fmt::Display, str::FromStr};
use alloy::hex::ToHexExt; use alloy::hex::ToHexExt;
use alloy::network::Network;
use alloy::primitives::{FixedBytes, utils::parse_units}; use alloy::primitives::{FixedBytes, utils::parse_units};
use alloy::{ use alloy::{
eips::BlockNumberOrTag, eips::BlockNumberOrTag,
@@ -12,7 +11,6 @@ use alloy::{
}; };
use anyhow::Context as _; use anyhow::Context as _;
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, stream}; use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, stream};
use revive_dt_common::types::PlatformIdentifier;
use schemars::JsonSchema; use schemars::JsonSchema;
use semver::VersionReq; use semver::VersionReq;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -154,11 +152,6 @@ pub struct FunctionCallStep {
/// during the execution. /// during the execution.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub variable_assignments: Option<VariableAssignments>, pub variable_assignments: Option<VariableAssignments>,
/// Allows for the test to set a specific value for the various gas parameter for each one of
/// the platforms we support. This is ignored for steps that perform contract deployments.
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub gas_overrides: HashMap<PlatformIdentifier, GasOverrides>,
} }
/// This represents a balance assertion step where the framework needs to query the balance of some /// This represents a balance assertion step where the framework needs to query the balance of some
@@ -972,62 +965,6 @@ impl<'de> Deserialize<'de> for EtherValue {
} }
} }
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
pub struct GasOverrides {
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_price: Option<u128>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_fee_per_gas: Option<u128>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_priority_fee_per_gas: Option<u128>,
}
impl GasOverrides {
pub fn new() -> Self {
Default::default()
}
pub fn with_gas_limit(mut self, value: impl Into<Option<u64>>) -> Self {
self.gas_limit = value.into();
self
}
pub fn with_gas_price(mut self, value: impl Into<Option<u128>>) -> Self {
self.gas_price = value.into();
self
}
pub fn with_max_fee_per_gas(mut self, value: impl Into<Option<u128>>) -> Self {
self.max_fee_per_gas = value.into();
self
}
pub fn with_max_priority_fee_per_gas(mut self, value: impl Into<Option<u128>>) -> Self {
self.max_priority_fee_per_gas = value.into();
self
}
pub fn apply_to<N: Network>(&self, transaction_request: &mut N::TransactionRequest) {
if let Some(gas_limit) = self.gas_limit {
transaction_request.set_gas_limit(gas_limit);
}
if let Some(gas_price) = self.gas_price {
transaction_request.set_gas_price(gas_price);
}
if let Some(max_fee_per_gas) = self.max_fee_per_gas {
transaction_request.set_max_fee_per_gas(max_fee_per_gas);
}
if let Some(max_priority_fee_per_gas) = self.max_priority_fee_per_gas {
transaction_request.set_max_priority_fee_per_gas(max_priority_fee_per_gas)
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
+1
View File
@@ -31,6 +31,7 @@ serde_yaml_ng = { workspace = true }
sp-core = { workspace = true } sp-core = { workspace = true }
sp-runtime = { workspace = true } sp-runtime = { workspace = true }
subxt = { workspace = true } subxt = { workspace = true }
temp-dir = { workspace = true }
zombienet-sdk = { workspace = true } zombienet-sdk = { workspace = true }
[dev-dependencies] [dev-dependencies]
@@ -42,6 +42,7 @@ use revive_dt_report::{
EthereumMinedBlockInformation, MinedBlockInformation, SubstrateMinedBlockInformation, EthereumMinedBlockInformation, MinedBlockInformation, SubstrateMinedBlockInformation,
}; };
use subxt::{OnlineClient, SubstrateConfig}; use subxt::{OnlineClient, SubstrateConfig};
use temp_dir::TempDir;
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
use tracing::{instrument, trace}; use tracing::{instrument, trace};
@@ -69,7 +70,7 @@ pub struct PolkadotOmnichainNode {
/// The path of the eth-rpc binary. /// The path of the eth-rpc binary.
eth_rpc_binary_path: PathBuf, eth_rpc_binary_path: PathBuf,
/// The path of the runtime's WASM that this node will be spawned with. /// The path of the runtime's WASM that this node will be spawned with.
chain_spec_path: Option<PathBuf>, runtime_wasm_path: Option<PathBuf>,
/// The path of the base directory which contains all of the stored data for this node. /// The path of the base directory which contains all of the stored data for this node.
base_directory_path: PathBuf, base_directory_path: PathBuf,
/// The path of the logs directory which contains all of the stored logs. /// The path of the logs directory which contains all of the stored logs.
@@ -143,8 +144,8 @@ impl PolkadotOmnichainNode {
.path .path
.to_path_buf(), .to_path_buf(),
eth_rpc_binary_path: eth_rpc_path.to_path_buf(), eth_rpc_binary_path: eth_rpc_path.to_path_buf(),
chain_spec_path: polkadot_omnichain_node_configuration runtime_wasm_path: polkadot_omnichain_node_configuration
.chain_spec_path .runtime_wasm_path
.clone(), .clone(),
base_directory_path: base_directory, base_directory_path: base_directory,
logs_directory_path: logs_directory, logs_directory_path: logs_directory,
@@ -176,8 +177,10 @@ impl PolkadotOmnichainNode {
let template_chainspec_path = self.base_directory_path.join(Self::CHAIN_SPEC_JSON_FILE); let template_chainspec_path = self.base_directory_path.join(Self::CHAIN_SPEC_JSON_FILE);
let chainspec_json = Self::node_genesis( let chainspec_json = Self::node_genesis(
&self.polkadot_omnichain_node_binary_path,
&self.wallet, &self.wallet,
self.chain_spec_path self.parachain_id.context("No parachain id provided")?,
self.runtime_wasm_path
.as_ref() .as_ref()
.context("No runtime path provided")?, .context("No runtime path provided")?,
) )
@@ -196,7 +199,7 @@ impl PolkadotOmnichainNode {
fn spawn_process(&mut self) -> anyhow::Result<()> { fn spawn_process(&mut self) -> anyhow::Result<()> {
// Error out if the runtime's path or the parachain id are not set which means that the // Error out if the runtime's path or the parachain id are not set which means that the
// arguments we require were not provided. // arguments we require were not provided.
self.chain_spec_path self.runtime_wasm_path
.as_ref() .as_ref()
.context("No WASM path provided for the runtime")?; .context("No WASM path provided for the runtime")?;
self.parachain_id self.parachain_id
@@ -355,11 +358,40 @@ impl PolkadotOmnichainNode {
} }
pub fn node_genesis( pub fn node_genesis(
node_path: &Path,
wallet: &EthereumWallet, wallet: &EthereumWallet,
chain_spec_path: &Path, parachain_id: usize,
runtime_wasm_path: &Path,
) -> anyhow::Result<serde_json::Value> { ) -> anyhow::Result<serde_json::Value> {
let unmodified_chainspec_file = let tempdir = TempDir::new().context("Failed to create a temporary directory")?;
File::open(chain_spec_path).context("Failed to open the unmodified chainspec file")?; let unmodified_chainspec_path = tempdir.path().join("chainspec.json");
let output = Command::new(node_path)
.arg("chain-spec-builder")
.arg("-c")
.arg(unmodified_chainspec_path.as_path())
.arg("create")
.arg("--para-id")
.arg(parachain_id.to_string())
.arg("--relay-chain")
.arg("dontcare")
.arg("--runtime")
.arg(runtime_wasm_path)
.arg("named-preset")
.arg("development")
.env_remove("RUST_LOG")
.output()
.context("Failed to export the chain-spec")?;
if !output.status.success() {
anyhow::bail!(
"Exporting chainspec from polkadot-omni-node failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let unmodified_chainspec_file = File::open(unmodified_chainspec_path.as_path())
.context("Failed to open the unmodified chainspec file")?;
let mut chainspec_json = let mut chainspec_json =
serde_json::from_reader::<_, serde_json::Value>(&unmodified_chainspec_file) serde_json::from_reader::<_, serde_json::Value>(&unmodified_chainspec_file)
.context("Failed to read the unmodified chainspec JSON")?; .context("Failed to read the unmodified chainspec JSON")?;
-26
View File
@@ -1,26 +0,0 @@
[package]
name = "revive-dt-report-processor"
description = "revive differential testing report processor utility"
version.workspace = true
authors.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
rust-version.workspace = true
[[bin]]
name = "report-processor"
path = "src/main.rs"
[dependencies]
revive-dt-report = { workspace = true }
revive-dt-common = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true }
strum = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
[lints]
workspace = true
-357
View File
@@ -1,357 +0,0 @@
use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet, HashSet},
fmt::Display,
fs::{File, OpenOptions},
ops::{Deref, DerefMut},
path::{Path, PathBuf},
str::FromStr,
};
use anyhow::{Context as _, Error, Result, bail};
use clap::{Parser, ValueEnum};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use revive_dt_common::types::{Mode, ParsedTestSpecifier};
use revive_dt_report::{Report, TestCaseStatus};
use strum::EnumString;
fn main() -> Result<()> {
let cli = Cli::try_parse().context("Failed to parse the CLI arguments")?;
match cli {
Cli::GenerateExpectationsFile {
report_path,
output_path: output_file,
remove_prefix,
include_status,
} => {
let remove_prefix = remove_prefix
.into_iter()
.map(|path| path.canonicalize().context("Failed to canonicalize path"))
.collect::<Result<Vec<_>>>()?;
let include_status =
include_status.map(|value| value.into_iter().collect::<HashSet<_>>());
let expectations = report_path
.execution_information
.iter()
.flat_map(|(metadata_file_path, metadata_file_report)| {
metadata_file_report
.case_reports
.iter()
.map(move |(case_idx, case_report)| {
(metadata_file_path, case_idx, case_report)
})
})
.flat_map(|(metadata_file_path, case_idx, case_report)| {
case_report.mode_execution_reports.iter().map(
move |(mode, execution_report)| {
(
metadata_file_path,
case_idx,
mode,
execution_report.status.as_ref(),
)
},
)
})
.filter_map(|(metadata_file_path, case_idx, mode, status)| {
status.map(|status| (metadata_file_path, case_idx, mode, status))
})
.map(|(metadata_file_path, case_idx, mode, status)| {
(
TestSpecifier {
metadata_file_path: Cow::Borrowed(
remove_prefix
.iter()
.filter_map(|prefix| {
metadata_file_path.as_inner().strip_prefix(prefix).ok()
})
.next()
.unwrap_or(metadata_file_path.as_inner()),
),
case_idx: case_idx.into_inner(),
mode: Cow::Borrowed(mode),
},
Status::from(status),
)
})
.filter(|(_, status)| {
include_status
.as_ref()
.map(|allowed_status| allowed_status.contains(status))
.unwrap_or(true)
})
.collect::<Expectations>();
let output_file = OpenOptions::new()
.truncate(true)
.create(true)
.write(true)
.open(output_file)
.context("Failed to create the output file")?;
serde_json::to_writer_pretty(output_file, &expectations)
.context("Failed to write the expectations to file")?;
}
Cli::CompareExpectationFiles {
base_expectation_path,
other_expectation_path,
} => {
let keys = base_expectation_path
.keys()
.chain(other_expectation_path.keys())
.collect::<BTreeSet<_>>();
for key in keys {
let base_status = base_expectation_path.get(key).context(format!(
"Entry not found in the base expectations: \"{}\"",
key
))?;
let other_status = other_expectation_path.get(key).context(format!(
"Entry not found in the other expectations: \"{}\"",
key
))?;
if base_status != other_status {
bail!(
"Expectations for entry \"{}\" have changed. They were {:?} and now they are {:?}",
key,
base_status,
other_status
)
}
}
}
};
Ok(())
}
type Expectations<'a> = BTreeMap<TestSpecifier<'a>, Status>;
/// A tool that's used to process the reports generated by the retester binary in various ways.
#[derive(Clone, Debug, Parser)]
#[command(name = "retester", term_width = 100)]
pub enum Cli {
/// Generates an expectation file out of a given report.
GenerateExpectationsFile {
/// The path of the report's JSON file to generate the expectation's file for.
#[clap(long)]
report_path: JsonFile<Report>,
/// The path of the output file to generate.
///
/// Note that we expect that:
/// 1. The provided path points to a JSON file.
/// 1. The ancestor's of the provided path already exist such that no directory creations
/// are required.
#[clap(long)]
output_path: PathBuf,
/// Prefix paths to remove from the paths in the final expectations file.
#[clap(long)]
remove_prefix: Vec<PathBuf>,
/// Controls which test case statuses are included in the generated expectations file. If
/// nothing is specified then it will include all of the test case status.
#[clap(long)]
include_status: Option<Vec<Status>>,
},
/// Compares two expectation files to ensure that they match each other.
CompareExpectationFiles {
/// The path of the base expectation file.
#[clap(long)]
base_expectation_path: JsonFile<Expectations<'static>>,
/// The path of the other expectation file.
#[clap(long)]
other_expectation_path: JsonFile<Expectations<'static>>,
},
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
ValueEnum,
EnumString,
)]
#[strum(serialize_all = "kebab-case")]
pub enum Status {
Succeeded,
Failed,
Ignored,
}
impl From<TestCaseStatus> for Status {
fn from(value: TestCaseStatus) -> Self {
match value {
TestCaseStatus::Succeeded { .. } => Self::Succeeded,
TestCaseStatus::Failed { .. } => Self::Failed,
TestCaseStatus::Ignored { .. } => Self::Ignored,
}
}
}
impl<'a> From<&'a TestCaseStatus> for Status {
fn from(value: &'a TestCaseStatus) -> Self {
match value {
TestCaseStatus::Succeeded { .. } => Self::Succeeded,
TestCaseStatus::Failed { .. } => Self::Failed,
TestCaseStatus::Ignored { .. } => Self::Ignored,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct JsonFile<T> {
path: PathBuf,
content: Box<T>,
}
impl<T> Deref for JsonFile<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.content
}
}
impl<T> DerefMut for JsonFile<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.content
}
}
impl<T> FromStr for JsonFile<T>
where
T: DeserializeOwned,
{
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let path = PathBuf::from(s);
let file = File::open(&path).context("Failed to open the file")?;
serde_json::from_reader(&file)
.map(|content| Self { path, content })
.context(format!(
"Failed to deserialize file's content as {}",
std::any::type_name::<T>()
))
}
}
impl<T> Display for JsonFile<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.path.display(), f)
}
}
impl<T> From<JsonFile<T>> for String {
fn from(value: JsonFile<T>) -> Self {
value.to_string()
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TestSpecifier<'a> {
pub metadata_file_path: Cow<'a, Path>,
pub case_idx: usize,
pub mode: Cow<'a, Mode>,
}
impl<'a> Display for TestSpecifier<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}::{}::{}",
self.metadata_file_path.display(),
self.case_idx,
self.mode
)
}
}
impl<'a> From<TestSpecifier<'a>> for ParsedTestSpecifier {
fn from(
TestSpecifier {
metadata_file_path,
case_idx,
mode,
}: TestSpecifier,
) -> Self {
Self::CaseWithMode {
metadata_file_path: metadata_file_path.to_path_buf(),
case_idx,
mode: mode.into_owned(),
}
}
}
impl TryFrom<ParsedTestSpecifier> for TestSpecifier<'static> {
type Error = Error;
fn try_from(value: ParsedTestSpecifier) -> Result<Self> {
let ParsedTestSpecifier::CaseWithMode {
metadata_file_path,
case_idx,
mode,
} = value
else {
bail!("Expected a full test case specifier")
};
Ok(Self {
metadata_file_path: Cow::Owned(metadata_file_path),
case_idx,
mode: Cow::Owned(mode),
})
}
}
impl<'a> Serialize for TestSpecifier<'a> {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_string().serialize(serializer)
}
}
impl<'d, 'a> Deserialize<'d> for TestSpecifier<'a> {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'d>,
{
let string = String::deserialize(deserializer)?;
let mut splitted = string.split("::");
let (Some(metadata_file_path), Some(case_idx), Some(mode), None) = (
splitted.next(),
splitted.next(),
splitted.next(),
splitted.next(),
) else {
return Err(serde::de::Error::custom(
"Test specifier doesn't contain the components required",
));
};
let metadata_file_path = PathBuf::from(metadata_file_path);
let case_idx = usize::from_str(case_idx)
.map_err(|_| serde::de::Error::custom("Case idx is not a usize"))?;
let mode = Mode::from_str(mode).map_err(|_| serde::de::Error::custom("Invalid mode"))?;
Ok(Self {
metadata_file_path: Cow::Owned(metadata_file_path),
case_idx,
mode: Cow::Owned(mode),
})
}
}
+29 -37
View File
@@ -36,8 +36,6 @@ pub struct ReportAggregator {
runner_tx: Option<UnboundedSender<RunnerEvent>>, runner_tx: Option<UnboundedSender<RunnerEvent>>,
runner_rx: UnboundedReceiver<RunnerEvent>, runner_rx: UnboundedReceiver<RunnerEvent>,
listener_tx: Sender<ReporterEvent>, listener_tx: Sender<ReporterEvent>,
/* Context */
file_name: Option<String>,
} }
impl ReportAggregator { impl ReportAggregator {
@@ -45,11 +43,6 @@ impl ReportAggregator {
let (runner_tx, runner_rx) = unbounded_channel::<RunnerEvent>(); let (runner_tx, runner_rx) = unbounded_channel::<RunnerEvent>();
let (listener_tx, _) = channel::<ReporterEvent>(0xFFFF); let (listener_tx, _) = channel::<ReporterEvent>(0xFFFF);
Self { Self {
file_name: match context {
Context::Test(ref context) => context.report_configuration.file_name.clone(),
Context::Benchmark(ref context) => context.report_configuration.file_name.clone(),
Context::ExportJsonSchema | Context::ExportGenesis(..) => None,
},
report: Report::new(context), report: Report::new(context),
remaining_cases: Default::default(), remaining_cases: Default::default(),
runner_tx: Some(runner_tx), runner_tx: Some(runner_tx),
@@ -128,7 +121,7 @@ impl ReportAggregator {
self.handle_completion(CompletionEvent {}); self.handle_completion(CompletionEvent {});
debug!("Report aggregation completed"); debug!("Report aggregation completed");
let default_file_name = { let file_name = {
let current_timestamp = SystemTime::now() let current_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.context("System clock is before UNIX_EPOCH; cannot compute report timestamp")? .context("System clock is before UNIX_EPOCH; cannot compute report timestamp")?
@@ -137,7 +130,6 @@ impl ReportAggregator {
file_name.push_str(".json"); file_name.push_str(".json");
file_name file_name
}; };
let file_name = self.file_name.unwrap_or(default_file_name);
let file_path = self let file_path = self
.report .report
.context .context
@@ -570,7 +562,7 @@ pub struct Report {
/// The list of metadata files that were found by the tool. /// The list of metadata files that were found by the tool.
pub metadata_files: BTreeSet<MetadataFilePath>, pub metadata_files: BTreeSet<MetadataFilePath>,
/// Metrics from the execution. /// Metrics from the execution.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub metrics: Option<Metrics>, pub metrics: Option<Metrics>,
/// Information relating to each test case. /// Information relating to each test case.
pub execution_information: BTreeMap<MetadataFilePath, MetadataFileReport>, pub execution_information: BTreeMap<MetadataFilePath, MetadataFileReport>,
@@ -590,7 +582,7 @@ impl Report {
#[derive(Clone, Debug, Serialize, Deserialize, Default)] #[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct MetadataFileReport { pub struct MetadataFileReport {
/// Metrics from the execution. /// Metrics from the execution.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub metrics: Option<Metrics>, pub metrics: Option<Metrics>,
/// The report of each case keyed by the case idx. /// The report of each case keyed by the case idx.
pub case_reports: BTreeMap<CaseIdx, CaseReport>, pub case_reports: BTreeMap<CaseIdx, CaseReport>,
@@ -600,7 +592,7 @@ pub struct MetadataFileReport {
#[derive(Clone, Debug, Serialize, Deserialize, Default)] #[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct CaseReport { pub struct CaseReport {
/// Metrics from the execution. /// Metrics from the execution.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub metrics: Option<Metrics>, pub metrics: Option<Metrics>,
/// The [`ExecutionReport`] for each one of the [`Mode`]s. /// The [`ExecutionReport`] for each one of the [`Mode`]s.
#[serde_as(as = "HashMap<DisplayFromStr, _>")] #[serde_as(as = "HashMap<DisplayFromStr, _>")]
@@ -610,31 +602,31 @@ pub struct CaseReport {
#[derive(Clone, Debug, Serialize, Deserialize, Default)] #[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct ExecutionReport { pub struct ExecutionReport {
/// Information on the status of the test case and whether it succeeded, failed, or was ignored. /// Information on the status of the test case and whether it succeeded, failed, or was ignored.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<TestCaseStatus>, pub status: Option<TestCaseStatus>,
/// Metrics from the execution. /// Metrics from the execution.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub metrics: Option<Metrics>, pub metrics: Option<Metrics>,
/// Information related to the execution on one of the platforms. /// Information related to the execution on one of the platforms.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")] #[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub platform_execution: PlatformKeyedInformation<Option<ExecutionInformation>>, pub platform_execution: PlatformKeyedInformation<Option<ExecutionInformation>>,
/// Information on the compiled contracts. /// Information on the compiled contracts.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")] #[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub compiled_contracts: BTreeMap<PathBuf, BTreeMap<String, ContractInformation>>, pub compiled_contracts: BTreeMap<PathBuf, BTreeMap<String, ContractInformation>>,
/// The addresses of the deployed contracts /// The addresses of the deployed contracts
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")] #[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub contract_addresses: BTreeMap<ContractInstance, PlatformKeyedInformation<Vec<Address>>>, pub contract_addresses: BTreeMap<ContractInstance, PlatformKeyedInformation<Vec<Address>>>,
/// Information on the mined blocks as part of this execution. /// Information on the mined blocks as part of this execution.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")] #[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub mined_block_information: PlatformKeyedInformation<Vec<MinedBlockInformation>>, pub mined_block_information: PlatformKeyedInformation<Vec<MinedBlockInformation>>,
/// Information tracked for each step that was executed. /// Information tracked for each step that was executed.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")] #[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub steps: BTreeMap<StepPath, StepReport>, pub steps: BTreeMap<StepPath, StepReport>,
} }
/// Information related to the status of the test. Could be that the test succeeded, failed, or that /// Information related to the status of the test. Could be that the test succeeded, failed, or that
/// it was ignored. /// it was ignored.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "status")] #[serde(tag = "status")]
pub enum TestCaseStatus { pub enum TestCaseStatus {
/// The test case succeeded. /// The test case succeeded.
@@ -672,19 +664,19 @@ pub struct TestCaseNodeInformation {
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ExecutionInformation { pub struct ExecutionInformation {
/// Information related to the node assigned to this test case. /// Information related to the node assigned to this test case.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub node: Option<TestCaseNodeInformation>, pub node: Option<TestCaseNodeInformation>,
/// Information on the pre-link compiled contracts. /// Information on the pre-link compiled contracts.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub pre_link_compilation_status: Option<CompilationStatus>, pub pre_link_compilation_status: Option<CompilationStatus>,
/// Information on the post-link compiled contracts. /// Information on the post-link compiled contracts.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub post_link_compilation_status: Option<CompilationStatus>, pub post_link_compilation_status: Option<CompilationStatus>,
/// Information on the deployed libraries. /// Information on the deployed libraries.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub deployed_libraries: Option<BTreeMap<ContractInstance, Address>>, pub deployed_libraries: Option<BTreeMap<ContractInstance, Address>>,
/// Information on the deployed contracts. /// Information on the deployed contracts.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub deployed_contracts: Option<BTreeMap<ContractInstance, Address>>, pub deployed_contracts: Option<BTreeMap<ContractInstance, Address>>,
} }
@@ -703,11 +695,11 @@ pub enum CompilationStatus {
/// The input provided to the compiler to compile the contracts. This is only included if /// The input provided to the compiler to compile the contracts. This is only included if
/// the appropriate flag is set in the CLI context and if the contracts were not cached and /// the appropriate flag is set in the CLI context and if the contracts were not cached and
/// the compiler was invoked. /// the compiler was invoked.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
compiler_input: Option<CompilerInput>, compiler_input: Option<CompilerInput>,
/// The output of the compiler. This is only included if the appropriate flag is set in the /// The output of the compiler. This is only included if the appropriate flag is set in the
/// CLI contexts. /// CLI contexts.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
compiler_output: Option<CompilerOutput>, compiler_output: Option<CompilerOutput>,
}, },
/// The compilation failed. /// The compilation failed.
@@ -715,15 +707,15 @@ pub enum CompilationStatus {
/// The failure reason. /// The failure reason.
reason: String, reason: String,
/// The version of the compiler used to compile the contracts. /// The version of the compiler used to compile the contracts.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
compiler_version: Option<Version>, compiler_version: Option<Version>,
/// The path of the compiler used to compile the contracts. /// The path of the compiler used to compile the contracts.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
compiler_path: Option<PathBuf>, compiler_path: Option<PathBuf>,
/// The input provided to the compiler to compile the contracts. This is only included if /// The input provided to the compiler to compile the contracts. This is only included if
/// the appropriate flag is set in the CLI context and if the contracts were not cached and /// the appropriate flag is set in the CLI context and if the contracts were not cached and
/// the compiler was invoked. /// the compiler was invoked.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
compiler_input: Option<CompilerInput>, compiler_input: Option<CompilerInput>,
}, },
} }
@@ -751,24 +743,24 @@ pub struct Metrics {
pub gas_per_second: Metric<u64>, pub gas_per_second: Metric<u64>,
/* Block Fullness */ /* Block Fullness */
pub gas_block_fullness: Metric<u64>, pub gas_block_fullness: Metric<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub ref_time_block_fullness: Option<Metric<u64>>, pub ref_time_block_fullness: Option<Metric<u64>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub proof_size_block_fullness: Option<Metric<u64>>, pub proof_size_block_fullness: Option<Metric<u64>>,
} }
/// The data that we store for a given metric (e.g., TPS). /// The data that we store for a given metric (e.g., TPS).
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Metric<T> { pub struct Metric<T> {
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<PlatformKeyedInformation<T>>, pub minimum: Option<PlatformKeyedInformation<T>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<PlatformKeyedInformation<T>>, pub maximum: Option<PlatformKeyedInformation<T>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub mean: Option<PlatformKeyedInformation<T>>, pub mean: Option<PlatformKeyedInformation<T>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub median: Option<PlatformKeyedInformation<T>>, pub median: Option<PlatformKeyedInformation<T>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub raw: Option<PlatformKeyedInformation<Vec<T>>>, pub raw: Option<PlatformKeyedInformation<Vec<T>>>,
} }