Add crate test-utils (#82)

This commit is contained in:
Han
2025-08-14 22:00:23 +08:00
committed by GitHub
parent 360a59bd67
commit a0a29cbb7d
18 changed files with 233 additions and 237 deletions

10
Cargo.lock generated
View File

@@ -2276,6 +2276,7 @@ dependencies = [
"risc0-zkvm",
"serde",
"tempfile",
"test-utils",
"thiserror 2.0.12",
"zkvm-interface",
]
@@ -2367,6 +2368,7 @@ dependencies = [
"build-utils",
"sp1-sdk",
"tempfile",
"test-utils",
"thiserror 2.0.12",
"toml",
"tracing",
@@ -9410,6 +9412,14 @@ dependencies = [
"test-case-core",
]
[[package]]
name = "test-utils"
version = "0.0.11"
dependencies = [
"serde",
"zkvm-interface",
]
[[package]]
name = "thiserror"
version = "1.0.69"

View File

@@ -1,6 +1,7 @@
[workspace]
members = [
"crates/build-utils",
"crates/test-utils",
# zkVMs
"crates/ere-jolt",
"crates/ere-nexus",
@@ -77,6 +78,7 @@ sp1-sdk = "5.1.0"
# Local dependencies
zkvm-interface = { path = "crates/zkvm-interface" }
build-utils = { path = "crates/build-utils" }
test-utils = { path = "crates/test-utils" }
ere-cli = { path = "crates/ere-cli", default-features = false }
ere-dockerized = { path = "crates/ere-dockerized" }
ere-jolt = { path = "crates/ere-jolt" }

View File

@@ -20,6 +20,7 @@ zkvm-interface = { workspace = true, features = ["clap"] }
ere-cli.workspace = true
[dev-dependencies]
test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
build-utils.workspace = true

View File

@@ -453,6 +453,9 @@ fn workspace_dir() -> PathBuf {
#[cfg(test)]
mod test {
use crate::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM, workspace_dir};
use test_utils::host::{
BasicProgramInputGen, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
};
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
// TODO: Test other ere-{zkvm} when they are end-to-end ready:
@@ -506,22 +509,16 @@ mod test {
fn dockerized_sp1() {
let zkvm = ErezkVM::SP1;
let guest_directory = workspace_dir().join(format!("tests/{zkvm}/prove/basic"));
let guest_directory = testing_guest_directory(zkvm.as_str(), "basic");
let program = EreDockerizedCompiler::new(zkvm, workspace_dir())
.compile(&guest_directory)
.unwrap();
let zkvm = EreDockerizedzkVM::new(zkvm, program, ProverResourceType::Cpu).unwrap();
let mut inputs = Input::new();
inputs.write(42u32);
inputs.write(42u16);
let _report = zkvm.execute(&inputs).unwrap();
let (proof, _report) = zkvm.prove(&inputs).unwrap();
zkvm.verify(&proof).unwrap();
let inputs = BasicProgramInputGen::valid();
run_zkvm_execute(&zkvm, &inputs);
run_zkvm_prove(&zkvm, &inputs);
}
#[test]

View File

@@ -1,9 +1,9 @@
[package]
name = "ere-sp1"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
name = "ere-sp1"
rust-version.workspace = true
version.workspace = true
[dependencies]
bincode.workspace = true
@@ -18,5 +18,8 @@ sp1-sdk.workspace = true
# Local dependencies
zkvm-interface.workspace = true
[dev-dependencies]
test-utils = { workspace = true, features = ["host"] }
[build-dependencies]
build-utils.workspace = true

View File

@@ -95,50 +95,14 @@ pub fn compile(guest_directory: &Path) -> Result<Vec<u8>, CompileError> {
#[cfg(test)]
mod tests {
use crate::RV32_IM_SUCCINCT_ZKVM_ELF;
use test_utils::host::testing_guest_directory;
use zkvm_interface::Compiler;
use crate::RV32_IM_SUCCINCT_ZKVM_ELF;
use super::*;
use std::path::PathBuf;
// TODO: for now, we just get one test file
// TODO: but this should get the whole directory and compile each test
fn get_compile_test_guest_program_path() -> PathBuf {
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
PathBuf::from(workspace_dir)
.join("tests")
.join("sp1")
.join("compile")
.join("basic")
.canonicalize()
.expect("Failed to find or canonicalize test guest program at <CARGO_WORKSPACE_DIR>/tests/compile/sp1")
}
#[test]
fn test_compile_sp1_program() {
let test_guest_path = get_compile_test_guest_program_path();
match compile(&test_guest_path) {
Ok(elf_bytes) => {
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
Err(err) => {
panic!("compile failed for dedicated guest: {err}");
}
}
}
#[test]
fn test_compile_trait() {
let test_guest_path = get_compile_test_guest_program_path();
match RV32_IM_SUCCINCT_ZKVM_ELF.compile(&test_guest_path) {
Ok(elf_bytes) => {
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
Err(err) => {
panic!("compile_sp1_program direct call failed for dedicated guest: {err}");
}
}
fn test_compiler_impl() {
let guest_directory = testing_guest_directory("sp1", "basic");
let elf_bytes = RV32_IM_SUCCINCT_ZKVM_ELF.compile(&guest_directory).unwrap();
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
}

View File

@@ -225,127 +225,74 @@ fn serialize_inputs(stdin: &mut SP1Stdin, inputs: &Input) {
}
#[cfg(test)]
mod execute_tests {
use std::path::PathBuf;
mod tests {
use super::*;
use zkvm_interface::Input;
use std::{panic, sync::OnceLock};
use test_utils::host::{
BasicProgramInputGen, run_zkvm_execute, run_zkvm_prove, testing_guest_directory,
};
fn get_compiled_test_sp1_elf() -> Result<Vec<u8>, SP1Error> {
let test_guest_path = get_execute_test_guest_program_path();
RV32_IM_SUCCINCT_ZKVM_ELF.compile(&test_guest_path)
}
static BASIC_PRORGAM: OnceLock<Vec<u8>> = OnceLock::new();
fn get_execute_test_guest_program_path() -> PathBuf {
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
PathBuf::from(workspace_dir)
.join("tests")
.join("sp1")
.join("execute")
.join("basic")
.canonicalize()
.expect("Failed to find or canonicalize test guest program at <CARGO_WORKSPACE_DIR>/tests/execute/sp1")
fn basic_program() -> Vec<u8> {
BASIC_PRORGAM
.get_or_init(|| {
RV32_IM_SUCCINCT_ZKVM_ELF
.compile(&testing_guest_directory("sp1", "basic"))
.unwrap()
})
.to_vec()
}
#[test]
fn test_execute_sp1_dummy_input() {
let elf_bytes = get_compiled_test_sp1_elf()
.expect("Failed to compile test SP1 guest for execution test");
fn test_execute() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
let mut input_builder = Input::new();
let n: u32 = 42;
let a: u16 = 42;
input_builder.write(n);
input_builder.write(a);
let inputs = BasicProgramInputGen::valid();
run_zkvm_execute(&zkvm, &inputs);
}
let zkvm = EreSP1::new(elf_bytes, ProverResourceType::Cpu);
#[test]
fn test_execute_invalid_inputs() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
let result = zkvm.execute(&input_builder);
if let Err(err) = &result {
panic!("Execution error: {err}");
for inputs in [
BasicProgramInputGen::empty(),
BasicProgramInputGen::invalid_string(),
BasicProgramInputGen::invalid_type(),
] {
zkvm.execute(&inputs).unwrap_err();
}
}
#[test]
fn test_execute_sp1_no_input_for_guest_expecting_input() {
let elf_bytes = get_compiled_test_sp1_elf()
.expect("Failed to compile test SP1 guest for execution test");
fn test_prove() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
let empty_input = Input::new();
let zkvm = EreSP1::new(elf_bytes, ProverResourceType::Cpu);
let result = zkvm.execute(&empty_input);
assert!(
result.is_err(),
"execute should fail if guest expects input but none is provided."
);
}
}
#[cfg(test)]
mod prove_tests {
use std::path::PathBuf;
use super::*;
use zkvm_interface::Input;
fn get_prove_test_guest_program_path() -> PathBuf {
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
PathBuf::from(workspace_dir)
.join("tests")
.join("sp1")
.join("prove")
.join("basic")
.canonicalize()
.expect("Failed to find or canonicalize test guest program at <CARGO_WORKSPACE_DIR>/tests/execute/sp1")
}
fn get_compiled_test_sp1_elf_for_prove() -> Result<Vec<u8>, SP1Error> {
let test_guest_path = get_prove_test_guest_program_path();
RV32_IM_SUCCINCT_ZKVM_ELF.compile(&test_guest_path)
let inputs = BasicProgramInputGen::valid();
run_zkvm_prove(&zkvm, &inputs);
}
#[test]
fn test_prove_sp1_dummy_input() {
let elf_bytes = get_compiled_test_sp1_elf_for_prove()
.expect("Failed to compile test SP1 guest for proving test");
fn test_prove_invalid_inputs() {
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Cpu);
let mut input_builder = Input::new();
let n: u32 = 42;
let a: u16 = 42;
input_builder.write(n);
input_builder.write(a);
let zkvm = EreSP1::new(elf_bytes, ProverResourceType::Cpu);
let proof_bytes = match zkvm.prove(&input_builder) {
Ok((prove_result, _)) => prove_result,
Err(err) => {
panic!("Proving error in test: {err}");
}
};
assert!(!proof_bytes.is_empty(), "Proof bytes should not be empty.");
let verify_results = zkvm.verify(&proof_bytes).is_ok();
assert!(verify_results);
// TODO: Check public inputs
}
#[test]
#[should_panic]
fn test_prove_sp1_fails_on_bad_input_causing_execution_failure() {
let elf_bytes = get_compiled_test_sp1_elf_for_prove()
.expect("Failed to compile test SP1 guest for proving test");
let empty_input = Input::new();
let zkvm = EreSP1::new(elf_bytes, ProverResourceType::Cpu);
let prove_result = zkvm.prove(&empty_input);
assert!(prove_result.is_err())
// On invalid inputs SP1 prove will panics, the issue for tracking:
// https://github.com/eth-act/ere/issues/16.
//
// Note that we iterate on methods because `InputItem::Object` doesn't
// implement `RefUnwindSafe`.
for inputs_gen in [
BasicProgramInputGen::empty,
BasicProgramInputGen::invalid_string,
BasicProgramInputGen::invalid_type,
] {
panic::catch_unwind(|| zkvm.prove(&inputs_gen())).unwrap_err();
}
}
#[test]
@@ -357,42 +304,15 @@ mod prove_tests {
return;
}
let elf_bytes = get_compiled_test_sp1_elf_for_prove()
.expect("Failed to compile test SP1 guest for proving test");
let mut input_builder = Input::new();
let n: u32 = 42;
let a: u16 = 42;
input_builder.write(n);
input_builder.write(a);
// Create a network prover configuration
let network_config = NetworkProverConfig {
endpoint: std::env::var("NETWORK_RPC_URL").unwrap_or_default(),
api_key: std::env::var("NETWORK_PRIVATE_KEY").ok(),
};
let program = basic_program();
let zkvm = EreSP1::new(program, ProverResourceType::Network(network_config));
let zkvm = EreSP1::new(elf_bytes, ProverResourceType::Network(network_config));
// Execute first to ensure the program works
let exec_result = zkvm.execute(&input_builder);
assert!(exec_result.is_ok(), "Execution should succeed");
// Now prove using the network
let proof_bytes = match zkvm.prove(&input_builder) {
Ok((prove_result, report)) => {
println!("Network proving completed in {:?}", report.proving_time);
prove_result
}
Err(err) => {
panic!("Network proving error: {err}");
}
};
assert!(!proof_bytes.is_empty(), "Proof bytes should not be empty.");
// Verify the proof
let verify_result = zkvm.verify(&proof_bytes);
assert!(verify_result.is_ok(), "Verification should succeed");
let inputs = BasicProgramInputGen::valid();
run_zkvm_prove(&zkvm, &inputs);
}
}

View File

@@ -0,0 +1,19 @@
[package]
name = "test-utils"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
[dependencies]
serde = { workspace = true, features = ["derive"] }
# Local dependencies
zkvm-interface = { workspace = true, optional = true }
[lints]
workspace = true
[features]
default = []
host = ["dep:zkvm-interface"]

View File

@@ -0,0 +1,28 @@
use alloc::vec::Vec;
use core::iter;
use serde::{Deserialize, Serialize};
#[derive(Default, Serialize, Deserialize)]
pub struct BasicStruct {
pub a: u8,
pub b: u16,
pub c: u32,
pub d: u64,
pub e: Vec<u8>,
}
impl BasicStruct {
/// Performs some computation (Xoring all fields as bytes into `[u8; 32]`).
pub fn output(&self) -> [u8; 32] {
let mut output = [0; 32];
iter::empty()
.chain(self.a.to_le_bytes())
.chain(self.b.to_le_bytes())
.chain(self.c.to_le_bytes())
.chain(self.d.to_le_bytes())
.chain(self.e.iter().copied())
.enumerate()
.for_each(|(idx, byte)| output[idx % output.len()] ^= byte);
output
}
}

View File

@@ -0,0 +1,73 @@
use crate::guest::BasicStruct;
use std::path::PathBuf;
use zkvm_interface::{Input, zkVM};
fn workspace() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.pop();
path.pop();
path
}
pub fn testing_guest_directory(zkvm_name: &str, program: &str) -> PathBuf {
workspace().join("tests").join(zkvm_name).join(program)
}
pub fn run_zkvm_execute(zkvm: &impl zkVM, inputs: &Input) {
let _report = zkvm
.execute(inputs)
.expect("execute should not fail with valid input");
// TODO: Check output are expected.
}
pub fn run_zkvm_prove(zkvm: &impl zkVM, inputs: &Input) {
let (proof, _report) = zkvm
.prove(inputs)
.expect("prove should not fail with valid input");
zkvm.verify(&proof)
.expect("verify should not fail with valid input");
// TODO: Check output are expected.
}
/// The basic program takes 2 inputs:
/// - `Vec<u8>` that supposed to be "Hello world"
/// - `BasicStruct`
///
/// Outputs `[u8; 32]` which computed by xoring fields of `BasicStruct`.
pub struct BasicProgramInputGen;
impl BasicProgramInputGen {
pub fn valid() -> Input {
let mut inputs = Input::new();
inputs.write_bytes("Hello world".as_bytes().to_vec());
inputs.write(BasicStruct {
a: 0xff,
b: 0x7777,
c: 0xffffffff,
d: 0x7777777777777777,
e: (0..u8::MAX).collect(),
});
inputs
}
pub fn invalid_string() -> Input {
let mut inputs = Input::new();
inputs.write_bytes("Unexpected string".as_bytes().to_vec());
inputs.write(BasicStruct::default());
inputs
}
pub fn invalid_type() -> Input {
let mut inputs = Input::new();
inputs.write(BasicStruct::default());
inputs.write_bytes("Hello world".as_bytes().to_vec());
inputs
}
pub fn empty() -> Input {
Input::new()
}
}

View File

@@ -0,0 +1,8 @@
#![cfg_attr(not(feature = "host"), no_std)]
extern crate alloc;
pub mod guest;
#[cfg(feature = "host")]
pub mod host;

View File

@@ -7,3 +7,4 @@ edition = "2021"
[dependencies]
sp1-zkvm = "5.1.0"
test-utils = { path = "../../../crates/test-utils" }

View File

@@ -0,0 +1,18 @@
#![no_main]
use test_utils::guest::BasicStruct;
sp1_zkvm::entrypoint!(main);
pub fn main() {
// Read `Hello world` bytes.
let bytes = sp1_zkvm::io::read_vec();
assert_eq!(String::from_utf8_lossy(&bytes), "Hello world");
// Read `BasicStruct`.
let basic_struct = sp1_zkvm::io::read::<BasicStruct>();
let output = basic_struct.output();
// Write `output`
sp1_zkvm::io::commit(&output);
}

View File

@@ -1,10 +0,0 @@
#![no_main]
sp1_zkvm::entrypoint!(main);
pub fn main() {
// Read an input
let n = sp1_zkvm::io::read::<u32>();
// Write n*2 to output
sp1_zkvm::io::commit(&(n * 2));
}

View File

@@ -1,9 +0,0 @@
[package]
name = "ere-test-sp1-guest"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
sp1-zkvm = "5.1.0"

View File

@@ -1,10 +0,0 @@
#![no_main]
sp1_zkvm::entrypoint!(main);
pub fn main() {
// Read an input
let n = sp1_zkvm::io::read::<u32>();
let a = sp1_zkvm::io::read::<u16>() as u32;
sp1_zkvm::io::commit(&((n + a) * 2));
}

View File

@@ -1,9 +0,0 @@
[package]
name = "ere-test-sp1-guest"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
sp1-zkvm = "5.1.0"

View File

@@ -1,10 +0,0 @@
#![no_main]
sp1_zkvm::entrypoint!(main);
pub fn main() {
// Read an input
let n = sp1_zkvm::io::read::<u32>();
let a = sp1_zkvm::io::read::<u16>() as u32;
sp1_zkvm::io::commit(&((n + a) * 2));
}