diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d9ca12..c7a152c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,22 +51,22 @@ jobs: uses: actions/cache@v3 with: path: | - ~/.cargo/bin/substrate-node + ~/.cargo/bin/revive-dev-node ~/.cargo/bin/eth-rpc - key: polkadot-binaries-${{ matrix.os }}-${{ hashFiles('polkadot-sdk/.git') }} + key: polkadot-binaries-${{ matrix.os }}-${{ hashFiles('polkadot-sdk/.git') }}-with-dev-node - - name: Build substrate-node + - name: Build revive-dev-node if: steps.cache.outputs.cache-hit != 'true' run: | cd polkadot-sdk - cargo install --locked --force --profile=production --path substrate/bin/node/cli --bin substrate-node --features cli + cargo install --locked --force --profile=production --path substrate/frame/revive/dev-node/node --bin revive-dev-node - name: Build eth-rpc if: steps.cache.outputs.cache-hit != 'true' run: | cd polkadot-sdk cargo install --path substrate/frame/revive/rpc --bin eth-rpc - + - name: Cache downloaded Polkadot binaries id: cache-polkadot uses: actions/cache@v3 @@ -109,14 +109,16 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v4 + with: + submodules: recursive - name: Restore binaries from cache uses: actions/cache@v3 with: path: | - ~/.cargo/bin/substrate-node + ~/.cargo/bin/revive-dev-node ~/.cargo/bin/eth-rpc - key: polkadot-binaries-${{ matrix.os }}-${{ hashFiles('polkadot-sdk/.git') }} + key: polkadot-binaries-${{ matrix.os }}-${{ hashFiles('polkadot-sdk/.git') }}-with-dev-node - name: Restore downloaded Polkadot binaries from cache uses: actions/cache@v3 @@ -202,8 +204,13 @@ jobs: sudo apt update sudo apt install kurtosis-cli + - name: Install cargo-machete + uses: clechasseur/rs-cargo@v2 + with: + command: install + args: cargo-machete@0.7.0 - name: Machete - uses: bnjbvr/cargo-machete@v0.7.1 + run: cargo machete crates - name: Format run: make format @@ -211,8 +218,8 @@ jobs: - name: Clippy run: make clippy - - name: Check substrate-node version - run: substrate-node --version + - name: Check revive-dev-node version + run: revive-dev-node --version - name: Check eth-rpc version run: eth-rpc --version @@ -222,13 +229,13 @@ jobs: - name: Check polkadot version run: polkadot --version - + - name: Check polkadot-parachain version run: polkadot-parachain --version - name: Check polkadot-execute-worker version run: polkadot-execute-worker --version - + - name: Check polkadot-prepare-worker version run: polkadot-prepare-worker --version diff --git a/README.md b/README.md index a5aee14..bfa5684 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This project compiles and executes declarative smart-contract tests against multiple platforms, then compares behavior (status, return data, events, and state diffs). Today it supports: - Geth (EVM reference implementation) -- Revive Kitchensink (Substrate-based PolkaVM + `eth-rpc` proxy) +- Revive Dev Node (Substrate-based PolkaVM + `eth-rpc` proxy) Use it to: @@ -39,9 +39,9 @@ This repository contains none of the tests and only contains the testing framewo This section describes the required dependencies that this framework requires to run. Compiling this framework is pretty straightforward and no additional dependencies beyond what's specified in the `Cargo.toml` file should be required. - Stable Rust -- Geth - When doing differential testing against the PVM we submit transactions to a Geth node and to Kitchensink to compare them. -- Kitchensink - When doing differential testing against the PVM we submit transactions to a Geth node and to Kitchensink to compare them. -- ETH-RPC - All communication with Kitchensink is done through the ETH RPC. +- Geth - When doing differential testing against the PVM we submit transactions to a Geth node and to Revive Dev Node to compare them. +- Revive Dev Node - When doing differential testing against the PVM we submit transactions to a Geth node and to Revive Dev Node to compare them. +- ETH-RPC - All communication with Revive Dev Node is done through the ETH RPC. - Solc - This is actually a transitive dependency, while this tool doesn't require solc as it downloads the versions that it requires, resolc requires that Solc is installed and available in the path. - Resolc - This is required to compile the contracts to PolkaVM bytecode. - Kurtosis - The Kurtosis CLI tool is required for the production Ethereum mainnet-like node configuration with Geth as the execution layer and lighthouse as the consensus layer. Kurtosis also requires docker to be installed since it runs everything inside of docker containers. diff --git a/crates/common/src/types/identifiers.rs b/crates/common/src/types/identifiers.rs index 2dd8563..8a6bdcd 100644 --- a/crates/common/src/types/identifiers.rs +++ b/crates/common/src/types/identifiers.rs @@ -31,10 +31,6 @@ pub enum PlatformIdentifier { GethEvmSolc, /// The Lighthouse Go-ethereum reference full node EVM implementation with the solc compiler. LighthouseGethEvmSolc, - /// The kitchensink node with the PolkaVM backend with the resolc compiler. - KitchensinkPolkavmResolc, - /// The kitchensink node with the REVM backend with the solc compiler. - KitchensinkRevmSolc, /// The revive dev node with the PolkaVM backend with the resolc compiler. ReviveDevNodePolkavmResolc, /// The revive dev node with the REVM backend with the solc compiler. @@ -95,8 +91,6 @@ pub enum NodeIdentifier { Geth, /// The go-ethereum node implementation. LighthouseGeth, - /// The Kitchensink node implementation. - Kitchensink, /// The revive dev node implementation. ReviveDevNode, /// A zombienet spawned nodes diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 4798ad2..5964b70 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -131,17 +131,6 @@ impl AsRef for Context { } } -impl AsRef for Context { - fn as_ref(&self) -> &KitchensinkConfiguration { - match self { - Self::Test(context) => context.as_ref().as_ref(), - Self::Benchmark(context) => context.as_ref().as_ref(), - Self::ExportGenesis(context) => context.as_ref().as_ref(), - Self::ExportJsonSchema => unreachable!(), - } - } -} - impl AsRef for Context { fn as_ref(&self) -> &ReviveDevNodeConfiguration { match self { @@ -283,10 +272,6 @@ pub struct TestExecutionContext { #[clap(flatten, next_help_heading = "Lighthouse Configuration")] pub lighthouse_configuration: KurtosisConfiguration, - /// Configuration parameters for the Kitchensink. - #[clap(flatten, next_help_heading = "Kitchensink Configuration")] - pub kitchensink_configuration: KitchensinkConfiguration, - /// Configuration parameters for the Revive Dev Node. #[clap(flatten, next_help_heading = "Revive Dev Node Configuration")] pub revive_dev_node_configuration: ReviveDevNodeConfiguration, @@ -409,10 +394,6 @@ pub struct BenchmarkingContext { #[clap(flatten, next_help_heading = "Lighthouse Configuration")] pub lighthouse_configuration: KurtosisConfiguration, - /// Configuration parameters for the Kitchensink. - #[clap(flatten, next_help_heading = "Kitchensink Configuration")] - pub kitchensink_configuration: KitchensinkConfiguration, - /// Configuration parameters for the Polkadot Parachain. #[clap(flatten, next_help_heading = "Polkadot Parachain Configuration")] pub polkadot_parachain_configuration: PolkadotParachainConfiguration, @@ -491,10 +472,6 @@ pub struct ExportGenesisContext { #[clap(flatten, next_help_heading = "Lighthouse Configuration")] pub lighthouse_configuration: KurtosisConfiguration, - /// Configuration parameters for the Kitchensink. - #[clap(flatten, next_help_heading = "Kitchensink Configuration")] - pub kitchensink_configuration: KitchensinkConfiguration, - /// Configuration parameters for the Polkadot Parachain. #[clap(flatten, next_help_heading = "Polkadot Parachain Configuration")] pub polkadot_parachain_configuration: PolkadotParachainConfiguration, @@ -556,12 +533,6 @@ impl AsRef for TestExecutionContext { } } -impl AsRef for TestExecutionContext { - fn as_ref(&self) -> &KitchensinkConfiguration { - &self.kitchensink_configuration - } -} - impl AsRef for TestExecutionContext { fn as_ref(&self) -> &ReviveDevNodeConfiguration { &self.revive_dev_node_configuration @@ -658,12 +629,6 @@ impl AsRef for BenchmarkingContext { } } -impl AsRef for BenchmarkingContext { - fn as_ref(&self) -> &KitchensinkConfiguration { - &self.kitchensink_configuration - } -} - impl AsRef for BenchmarkingContext { fn as_ref(&self) -> &ReviveDevNodeConfiguration { &self.revive_dev_node_configuration @@ -718,12 +683,6 @@ impl AsRef for ExportGenesisContext { } } -impl AsRef for ExportGenesisContext { - fn as_ref(&self) -> &KitchensinkConfiguration { - &self.kitchensink_configuration - } -} - impl AsRef for ExportGenesisContext { fn as_ref(&self) -> &PolkadotParachainConfiguration { &self.polkadot_parachain_configuration @@ -842,30 +801,6 @@ pub struct KurtosisConfiguration { pub path: PathBuf, } -/// A set of configuration parameters for Kitchensink. -#[derive(Clone, Debug, Parser, Serialize, Deserialize)] -pub struct KitchensinkConfiguration { - /// Specifies the path of the kitchensink node to be used by the tool. - /// - /// If this is not specified, then the tool assumes that it should use the kitchensink binary - /// that's provided in the user's $PATH. - #[clap( - id = "kitchensink.path", - long = "kitchensink.path", - default_value = "substrate-node" - )] - pub path: PathBuf, - - /// The amount of time to wait upon startup before considering that the node timed out. - #[clap( - id = "kitchensink.start-timeout-ms", - long = "kitchensink.start-timeout-ms", - default_value = "30000", - value_parser = parse_duration - )] - pub start_timeout_ms: Duration, -} - /// A set of configuration parameters for the revive dev node. #[derive(Clone, Debug, Parser, Serialize, Deserialize)] pub struct ReviveDevNodeConfiguration { @@ -1161,35 +1096,6 @@ fn parse_duration(s: &str) -> anyhow::Result { .map_err(Into::into) } -/// The Solidity compatible node implementation. -/// -/// This describes the solutions to be tested against on a high level. -#[derive( - Clone, - Copy, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - ValueEnum, - EnumString, - Display, - AsRefStr, - IntoStaticStr, -)] -#[strum(serialize_all = "kebab-case")] -pub enum TestingPlatform { - /// The go-ethereum reference full node EVM implementation. - Geth, - /// The kitchensink runtime provides the PolkaVM (PVM) based node implementation. - Kitchensink, - /// A polkadot/Substrate based network - Zombienet, -} - /// The output format to use for the test execution output. #[derive( Clone, diff --git a/crates/core/src/differential_tests/driver.rs b/crates/core/src/differential_tests/driver.rs index 1c63651..2fadf1a 100644 --- a/crates/core/src/differential_tests/driver.rs +++ b/crates/core/src/differential_tests/driver.rs @@ -353,7 +353,8 @@ where .execute_account_allocation(step_path, step.as_ref()) .await .context("Account Allocation Step Failed"), - }?; + } + .context(format!("Failure on step {step_path}"))?; self.steps_executed += steps_executed; Ok(()) } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index fc6332b..12c8b1b 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -172,134 +172,6 @@ impl Platform for LighthouseGethEvmSolcPlatform { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] -pub struct KitchensinkPolkavmResolcPlatform; - -impl Platform for KitchensinkPolkavmResolcPlatform { - fn platform_identifier(&self) -> PlatformIdentifier { - PlatformIdentifier::KitchensinkPolkavmResolc - } - - fn node_identifier(&self) -> NodeIdentifier { - NodeIdentifier::Kitchensink - } - - fn vm_identifier(&self) -> VmIdentifier { - VmIdentifier::PolkaVM - } - - fn compiler_identifier(&self) -> CompilerIdentifier { - CompilerIdentifier::Resolc - } - - fn new_node( - &self, - context: Context, - ) -> anyhow::Result>>> { - let genesis_configuration = AsRef::::as_ref(&context); - let kitchensink_path = AsRef::::as_ref(&context) - .path - .clone(); - let genesis = genesis_configuration.genesis()?.clone(); - Ok(thread::spawn(move || { - let node = SubstrateNode::new( - kitchensink_path, - SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND, - None, - context, - &[], - ); - let node = spawn_node(node, genesis)?; - Ok(Box::new(node) as Box<_>) - })) - } - - fn new_compiler( - &self, - context: Context, - version: Option, - ) -> Pin>>>> { - Box::pin(async move { - let compiler = Resolc::new(context, version).await; - compiler.map(|compiler| Box::new(compiler) as Box) - }) - } - - fn export_genesis(&self, context: Context) -> anyhow::Result { - let kitchensink_path = AsRef::::as_ref(&context) - .path - .as_path(); - let wallet = AsRef::::as_ref(&context).wallet(); - let export_chainspec_command = SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND; - - SubstrateNode::node_genesis(kitchensink_path, export_chainspec_command, &wallet) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] -pub struct KitchensinkRevmSolcPlatform; - -impl Platform for KitchensinkRevmSolcPlatform { - fn platform_identifier(&self) -> PlatformIdentifier { - PlatformIdentifier::KitchensinkRevmSolc - } - - fn node_identifier(&self) -> NodeIdentifier { - NodeIdentifier::Kitchensink - } - - fn vm_identifier(&self) -> VmIdentifier { - VmIdentifier::Evm - } - - fn compiler_identifier(&self) -> CompilerIdentifier { - CompilerIdentifier::Solc - } - - fn new_node( - &self, - context: Context, - ) -> anyhow::Result>>> { - let genesis_configuration = AsRef::::as_ref(&context); - let kitchensink_path = AsRef::::as_ref(&context) - .path - .clone(); - let genesis = genesis_configuration.genesis()?.clone(); - Ok(thread::spawn(move || { - let node = SubstrateNode::new( - kitchensink_path, - SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND, - None, - context, - &[], - ); - let node = spawn_node(node, genesis)?; - Ok(Box::new(node) as Box<_>) - })) - } - - fn new_compiler( - &self, - context: Context, - version: Option, - ) -> Pin>>>> { - Box::pin(async move { - let compiler = Solc::new(context, version).await; - compiler.map(|compiler| Box::new(compiler) as Box) - }) - } - - fn export_genesis(&self, context: Context) -> anyhow::Result { - let kitchensink_path = AsRef::::as_ref(&context) - .path - .as_path(); - let wallet = AsRef::::as_ref(&context).wallet(); - let export_chainspec_command = SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND; - - SubstrateNode::node_genesis(kitchensink_path, export_chainspec_command, &wallet) - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] pub struct ReviveDevNodePolkavmResolcPlatform; @@ -557,12 +429,6 @@ impl From for Box { PlatformIdentifier::LighthouseGethEvmSolc => { Box::new(LighthouseGethEvmSolcPlatform) as Box<_> } - PlatformIdentifier::KitchensinkPolkavmResolc => { - Box::new(KitchensinkPolkavmResolcPlatform) as Box<_> - } - PlatformIdentifier::KitchensinkRevmSolc => { - Box::new(KitchensinkRevmSolcPlatform) as Box<_> - } PlatformIdentifier::ReviveDevNodePolkavmResolc => { Box::new(ReviveDevNodePolkavmResolcPlatform) as Box<_> } @@ -584,12 +450,6 @@ impl From for &dyn Platform { PlatformIdentifier::LighthouseGethEvmSolc => { &LighthouseGethEvmSolcPlatform as &dyn Platform } - PlatformIdentifier::KitchensinkPolkavmResolc => { - &KitchensinkPolkavmResolcPlatform as &dyn Platform - } - PlatformIdentifier::KitchensinkRevmSolc => { - &KitchensinkRevmSolcPlatform as &dyn Platform - } PlatformIdentifier::ReviveDevNodePolkavmResolc => { &ReviveDevNodePolkavmResolcPlatform as &dyn Platform } diff --git a/crates/node/src/node_implementations/substrate.rs b/crates/node/src/node_implementations/substrate.rs index 69f64de..787e6a5 100644 --- a/crates/node/src/node_implementations/substrate.rs +++ b/crates/node/src/node_implementations/substrate.rs @@ -92,7 +92,6 @@ impl SubstrateNode { const SUBSTRATE_LOG_ENV: &str = "error,evm=debug,sc_rpc_server=info,runtime::revive=debug"; const PROXY_LOG_ENV: &str = "info,eth-rpc=debug"; - pub const KITCHENSINK_EXPORT_CHAINSPEC_COMMAND: &str = "export-chain-spec"; pub const REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND: &str = "build-spec"; pub fn new( @@ -333,7 +332,7 @@ impl SubstrateNode { trace!("Waiting for chainspec export"); if !output.status.success() { anyhow::bail!( - "Substrate-node export-chain-spec failed: {}", + "substrate-node export-chain-spec failed: {}", String::from_utf8_lossy(&output.stderr) ); } @@ -800,8 +799,8 @@ mod tests { let context = test_config(); let mut node = SubstrateNode::new( - context.kitchensink_configuration.path.clone(), - SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND, + context.revive_dev_node_configuration.path.clone(), + SubstrateNode::REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND, None, &context, &[], @@ -823,6 +822,7 @@ mod tests { } #[tokio::test] + #[ignore = "Ignored since it takes a long time to run"] async fn node_mines_simple_transfer_transaction_and_returns_receipt() { // Arrange let (context, node) = shared_state(); @@ -839,11 +839,14 @@ mod tests { .value(U256::from(100_000_000_000_000u128)); // Act - let receipt = provider.send_transaction(transaction).await; + let mut pending_transaction = provider + .send_transaction(transaction) + .await + .expect("Submission failed"); + pending_transaction.set_timeout(Some(Duration::from_secs(60))); // Assert - let _ = receipt - .expect("Failed to send the transfer transaction") + let _ = pending_transaction .get_receipt() .await .expect("Failed to get the receipt for the transfer"); @@ -867,8 +870,8 @@ mod tests { let context = test_config(); let mut dummy_node = SubstrateNode::new( - context.kitchensink_configuration.path.clone(), - SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND, + context.revive_dev_node_configuration.path.clone(), + SubstrateNode::REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND, None, &context, &[], @@ -961,7 +964,7 @@ mod tests { assert!( version.starts_with("substrate-node"), - "Expected Substrate-node version string, got: {version}" + "Expected substrate-node version string, got: {version}" ); } diff --git a/crates/node/src/node_implementations/zombienet.rs b/crates/node/src/node_implementations/zombienet.rs index ada0a01..b41645a 100644 --- a/crates/node/src/node_implementations/zombienet.rs +++ b/crates/node/src/node_implementations/zombienet.rs @@ -356,7 +356,7 @@ impl ZombienetNode { if !output.status.success() { anyhow::bail!( - "Substrate-node export-chain-spec failed: {}", + "substrate-node export-chain-spec failed: {}", String::from_utf8_lossy(&output.stderr) ); } @@ -858,6 +858,7 @@ mod tests { #[tokio::test] #[ignore = "Ignored for the time being"] async fn test_transfer_transaction_should_return_receipt() { + // Arrange let (ctx, node) = new_node().await; let provider = node.provider().await.expect("Failed to create provider"); @@ -866,9 +867,15 @@ mod tests { .to(account_address) .value(U256::from(100_000_000_000_000u128)); - let receipt = provider.send_transaction(transaction).await; - let _ = receipt - .expect("Failed to send the transfer transaction") + // Act + let mut pending_transaction = provider + .send_transaction(transaction) + .await + .expect("Submission failed"); + pending_transaction.set_timeout(Some(Duration::from_secs(60))); + + // Assert + let _ = pending_transaction .get_receipt() .await .expect("Failed to get the receipt for the transfer"); diff --git a/polkadot-sdk b/polkadot-sdk index dc3d0e5..f268e32 160000 --- a/polkadot-sdk +++ b/polkadot-sdk @@ -1 +1 @@ -Subproject commit dc3d0e5ab702fabd2b19a9f3f76e736e76ed551d +Subproject commit f268e327685e04ad4134631a8b547a52f931fb83 diff --git a/run_tests.sh b/run_tests.sh index 1295d86..fe33a8a 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -22,7 +22,6 @@ POLKADOT_SDK_DIR="${1:-}" # Binary paths (default to names in $PATH) REVIVE_DEV_NODE_BIN="revive-dev-node" ETH_RPC_BIN="eth-rpc" -SUBSTRATE_NODE_BIN="substrate-node" echo -e "${GREEN}=== Revive Differential Tests Quick Start ===${NC}" echo "" @@ -50,14 +49,13 @@ if [ -n "$POLKADOT_SDK_DIR" ]; then REVIVE_DEV_NODE_BIN="$POLKADOT_SDK_DIR/target/release/revive-dev-node" ETH_RPC_BIN="$POLKADOT_SDK_DIR/target/release/eth-rpc" - SUBSTRATE_NODE_BIN="$POLKADOT_SDK_DIR/target/release/substrate-node" - if [ ! -x "$REVIVE_DEV_NODE_BIN" ] || [ ! -x "$ETH_RPC_BIN" ] || [ ! -x "$SUBSTRATE_NODE_BIN" ]; then + if [ ! -x "$REVIVE_DEV_NODE_BIN" ] || [ ! -x "$ETH_RPC_BIN" ]; then echo -e "${YELLOW}Required binaries not found in release target. Building...${NC}" (cd "$POLKADOT_SDK_DIR" && cargo build --release --package staging-node-cli --package pallet-revive-eth-rpc --package revive-dev-node) fi - for bin in "$REVIVE_DEV_NODE_BIN" "$ETH_RPC_BIN" "$SUBSTRATE_NODE_BIN"; do + for bin in "$REVIVE_DEV_NODE_BIN" "$ETH_RPC_BIN"; do if [ ! -x "$bin" ]; then echo -e "${RED}Expected binary not found after build: $bin${NC}" exit 1 @@ -84,7 +82,6 @@ RUST_LOG="info,alloy_pubsub::service=error" ./target/release/retester test \ --concurrency.number-of-threads 5 \ --concurrency.number-of-concurrent-tasks 500 \ --wallet.additional-keys 100000 \ - --kitchensink.path "$SUBSTRATE_NODE_BIN" \ --revive-dev-node.path "$REVIVE_DEV_NODE_BIN" \ --eth-rpc.path "$ETH_RPC_BIN" \ > logs.log \ diff --git a/scripts/process-differential-tests-report.py b/scripts/process-differential-tests-report.py new file mode 100644 index 0000000..87991fb --- /dev/null +++ b/scripts/process-differential-tests-report.py @@ -0,0 +1,226 @@ +""" +This script is used to turn the JSON report produced by the revive differential tests tool into an +easy to consume markdown document for the purpose of reporting this information in the Polkadot SDK +CI. The full models used in the JSON report can be found in the revive differential tests repo and +the models used in this script are just a partial reproduction of the full report models. +""" + +from typing import TypedDict, Literal, Union + +import json, io + + +class Report(TypedDict): + context: "Context" + execution_information: dict[ + "MetadataFilePathString", + dict["ModeString", dict["CaseIdxString", "CaseReport"]], + ] + + +class Context(TypedDict): + Test: "TestContext" + + +class TestContext(TypedDict): + corpus_configuration: "CorpusConfiguration" + + +class CorpusConfiguration(TypedDict): + test_specifiers: list["TestSpecifier"] + + +class CaseReport(TypedDict): + status: "CaseStatus" + + +class CaseStatusSuccess(TypedDict): + status: Literal["Succeeded"] + steps_executed: int + + +class CaseStatusFailure(TypedDict): + status: Literal["Failed"] + reason: str + + +class CaseStatusIgnored(TypedDict): + status: Literal["Ignored"] + reason: str + + +CaseStatus = Union[CaseStatusSuccess, CaseStatusFailure, CaseStatusIgnored] +"""A union type of all of the possible statuses that could be reported for a case.""" + +TestSpecifier = str +"""A test specifier string. For example resolc-compiler-tests/fixtures/solidity/test.json::0::Y+""" + +ModeString = str +"""The mode string. For example Y+ >=0.8.13""" + +MetadataFilePathString = str +"""The path to a metadata file. For example resolc-compiler-tests/fixtures/solidity/test.json""" + +CaseIdxString = str +"""The index of a case as a string. For example '0'""" + + +def path_relative_to_resolc_compiler_test_directory(path: str) -> str: + """ + Given a path, this function returns the path relative to the resolc-compiler-test directory. The + following is an example of an input and an output: + + Input: ~/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/test.json + Output: test.json + """ + + return f"{path.split('resolc-compiler-tests/fixtures/solidity')[-1].strip('/')}" + + +def main() -> None: + with open("report.json", "r") as file: + report: Report = json.load(file) + + # Starting the markdown document and adding information to it as we go. + markdown_document: io.TextIOWrapper = open("report.md", "w") + print("# Differential Tests Results", file=markdown_document) + + # Getting all of the test specifiers from the report and making them relative to the tests dir. + test_specifiers: list[str] = list( + map( + path_relative_to_resolc_compiler_test_directory, + report["context"]["Test"]["corpus_configuration"]["test_specifiers"], + ) + ) + print("## Specified Tests", file=markdown_document) + for test_specifier in test_specifiers: + print(f"* `{test_specifier}`", file=markdown_document) + + # Counting the total number of test cases, successes, failures, and ignored tests + total_number_of_cases: int = 0 + total_number_of_successes: int = 0 + total_number_of_failures: int = 0 + total_number_of_ignores: int = 0 + for _, mode_to_case_mapping in report["execution_information"].items(): + for _, case_idx_to_report_mapping in mode_to_case_mapping.items(): + for _, case_report in case_idx_to_report_mapping.items(): + status: CaseStatus = case_report["status"] + + total_number_of_cases += 1 + if status["status"] == "Succeeded": + total_number_of_successes += 1 + elif status["status"] == "Failed": + total_number_of_failures += 1 + elif status["status"] == "Ignored": + total_number_of_ignores += 1 + else: + raise Exception( + f"Encountered a status that's unknown to the script: {status}" + ) + + print("## Counts", file=markdown_document) + print( + f"* **Total Number of Test Cases:** {total_number_of_cases}", + file=markdown_document, + ) + print( + f"* **Total Number of Successes:** {total_number_of_successes}", + file=markdown_document, + ) + print( + f"* **Total Number of Failures:** {total_number_of_failures}", + file=markdown_document, + ) + print( + f"* **Total Number of Ignores:** {total_number_of_ignores}", + file=markdown_document, + ) + + # Grouping the various test cases into dictionaries and groups depending on their status to make + # them easier to include in the markdown document later on. + successful_cases: dict[ + MetadataFilePathString, dict[CaseIdxString, set[ModeString]] + ] = {} + for metadata_file_path, mode_to_case_mapping in report[ + "execution_information" + ].items(): + for mode_string, case_idx_to_report_mapping in mode_to_case_mapping.items(): + for case_idx_string, case_report in case_idx_to_report_mapping.items(): + status: CaseStatus = case_report["status"] + metadata_file_path: str = ( + path_relative_to_resolc_compiler_test_directory(metadata_file_path) + ) + mode_string: str = mode_string.replace(" M3", "+").replace(" M0", "-") + + if status["status"] == "Succeeded": + successful_cases.setdefault( + metadata_file_path, + {}, + ).setdefault( + case_idx_string, set() + ).add(mode_string) + + print("## Failures", file=markdown_document) + print( + "The test specifiers seen in this section have the format 'path::case_idx::compilation_mode'\ + and they're compatible with the revive differential tests framework and can be specified\ + to it directly in the same way that they're provided through the `--test` argument of the\ + framework.\n", + file=markdown_document, + ) + print( + "The failures are provided in an expandable section to ensure that the PR does not get \ + polluted with information. Please click on the section below for more information", + file=markdown_document, + ) + print( + "
Detailed Differential Tests Failure Information\n\n", + file=markdown_document, + ) + print("| Test Specifier | Failure Reason | Note |", file=markdown_document) + print("| -- | -- | -- |", file=markdown_document) + + for metadata_file_path, mode_to_case_mapping in report[ + "execution_information" + ].items(): + for mode_string, case_idx_to_report_mapping in mode_to_case_mapping.items(): + for case_idx_string, case_report in case_idx_to_report_mapping.items(): + status: CaseStatus = case_report["status"] + metadata_file_path: str = ( + path_relative_to_resolc_compiler_test_directory(metadata_file_path) + ) + mode_string: str = mode_string.replace(" M3", "+").replace(" M0", "-") + + if status["status"] != "Failed": + continue + + failure_reason: str = status["reason"].replace("\n", " ") + + note: str = "" + modes_where_this_case_succeeded: set[ModeString] = ( + successful_cases.setdefault( + metadata_file_path, + {}, + ).setdefault(case_idx_string, set()) + ) + if len(modes_where_this_case_succeeded) != 0: + note: str = ( + f"This test case succeeded with other compilation modes: {modes_where_this_case_succeeded}" + ) + + test_specifier: str = ( + f"{metadata_file_path}::{case_idx_string}::{mode_string}" + ) + print( + f"| `{test_specifier}` | `{failure_reason}` | {note} |", + file=markdown_document, + ) + print("\n\n
", file=markdown_document) + + # The primary downside of not using `with`, but I guess it's better since I don't want to over + # indent the code. + markdown_document.close() + + +if __name__ == "__main__": + main()