Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions crates/herkos-tests/data/rust/rust_e2e_memory_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,15 @@ static mut BUF: [i32; 1024] = [0i32; 1024];
pub extern "C" fn mem_fill_sort_sum(n: i32, seed: i32) -> i32 {
unsafe { fill_sort_sum_impl(&mut BUF, n, seed) }
}

/// Read one element from the work buffer by index.
///
/// Returns 0 for out-of-range indices. Intended for tests that need to
/// inspect the buffer after a `mem_fill_sort_sum` call.
#[no_mangle]
pub extern "C" fn mem_read_element(idx: i32) -> i32 {
if idx < 0 || idx as usize >= 1024 {
return 0;
}
unsafe { BUF[idx as usize] }
}
176 changes: 176 additions & 0 deletions crates/herkos-tests/tests/rust_e2e_memory_bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
//! End-to-end tests: Rust → Wasm → Rust (fill / bubble-sort / sum over Wasm memory).
//!
//! The source module (`data/rust/rust_e2e_memory_bench.rs`) exports a single
//! function:
//!
//! ```text
//! mem_fill_sort_sum(n: i32, seed: i32) -> i32
//! ```
//!
//! It fills the first `n` elements of a 1024-element static buffer with LCG
//! pseudo-random values, bubble-sorts them in place, then returns a wrapping
//! checksum. Tests verify the transpiled output against a native reference.

use herkos_tests::rust_e2e_memory_bench;

fn new_module() -> rust_e2e_memory_bench::WasmModule {
rust_e2e_memory_bench::new().expect("module instantiation should succeed")
}

// ── Reference implementation ──────────────────────────────────────────────────

include!("../data/rust/common/fill_sort_sum.rs");

fn fill_sort_sum_ref(n: i32, seed: i32) -> i32 {
let mut buf = [0i32; 1024];
fill_sort_sum_impl(&mut buf, n, seed)
}

// ── Edge cases ────────────────────────────────────────────────────────────────

#[test]
fn test_zero_elements_returns_zero() {
let mut m = new_module();
assert_eq!(m.mem_fill_sort_sum(0, 0).unwrap(), 0);
assert_eq!(m.mem_fill_sort_sum(0, 42).unwrap(), 0);
}

#[test]
fn test_negative_n_returns_zero() {
let mut m = new_module();
assert_eq!(m.mem_fill_sort_sum(-1, 0).unwrap(), 0);
assert_eq!(m.mem_fill_sort_sum(i32::MIN, 1).unwrap(), 0);
}

#[test]
fn test_single_element() {
let mut m = new_module();
// With n=1 there is nothing to sort; checksum is just the one LCG value.
let seed: i32 = 1;
let expected = seed.wrapping_mul(1103515245_i32).wrapping_add(12345);
assert_eq!(m.mem_fill_sort_sum(1, seed).unwrap(), expected);
}

// ── Cross-validation against reference ───────────────────────────────────────

#[test]
fn test_matches_reference_small_n() {
let mut m = new_module();
for n in 1i32..=16 {
assert_eq!(
m.mem_fill_sort_sum(n, 0).unwrap(),
fill_sort_sum_ref(n, 0),
"n={n} seed=0"
);
}
}

#[test]
fn test_matches_reference_various_seeds() {
let mut m = new_module();
let cases: &[(i32, i32)] = &[
(10, 0),
(10, 1),
(10, -1),
(10, i32::MAX),
(10, i32::MIN),
(32, 42),
(64, 12345),
(128, -99999),
];
for &(n, seed) in cases {
assert_eq!(
m.mem_fill_sort_sum(n, seed).unwrap(),
fill_sort_sum_ref(n, seed),
"n={n} seed={seed}"
);
}
}

#[test]
fn test_matches_reference_full_buffer() {
let mut m = new_module();
// n=1024 exercises every element of the static buffer.
assert_eq!(
m.mem_fill_sort_sum(1024, 7).unwrap(),
fill_sort_sum_ref(1024, 7)
);
}

#[test]
fn test_n_capped_at_1024() {
let mut m = new_module();
// Values beyond 1024 must be clamped; result should equal n=1024.
assert_eq!(
m.mem_fill_sort_sum(2048, 7).unwrap(),
fill_sort_sum_ref(1024, 7),
"n>1024 must be clamped to 1024"
);
}

// ── Sorting invariant ─────────────────────────────────────────────────────────
//
// After each call we read back every element via mem_read_element and confirm
// the buffer is non-decreasing. Note: a wrapping sum is commutative, so sum
// equality alone is not sufficient to verify that sorting occurred.

fn assert_sorted(m: &mut rust_e2e_memory_bench::WasmModule, n: i32, label: &str) {
for i in 0..(n - 1) {
let a = m.mem_read_element(i).unwrap();
let b = m.mem_read_element(i + 1).unwrap();
assert!(
a <= b,
"{label}: buf[{i}]={a} > buf[{}]={b} — buffer is not sorted",
i + 1
);
}
}

#[test]
fn test_buffer_is_sorted_after_call() {
let mut m = new_module();
let cases: &[(i32, i32)] = &[
(1, 0),
(2, 0),
(10, 42),
(32, -99),
(64, 9999),
(128, i32::MAX),
(256, i32::MIN),
(1024, 7),
];
for &(n, seed) in cases {
m.mem_fill_sort_sum(n, seed).unwrap();
assert_sorted(&mut m, n, &format!("n={n} seed={seed}"));
}
}

#[test]
fn test_deterministic_across_calls() {
let mut m = new_module();
let first = m.mem_fill_sort_sum(64, 9999).unwrap();
let second = m.mem_fill_sort_sum(64, 9999).unwrap();
assert_eq!(
first, second,
"same (n, seed) must always yield the same checksum"
);
}

// ── Sequential calls with distinct inputs remain independent ──────────────────
//
// The module keeps a static buffer; verify that successive calls with different
// seeds still match the reference (no stale state from a previous call leaks
// into the next fill).

#[test]
fn test_sequential_calls_independent() {
let mut m = new_module();
let seeds = [0i32, 1, -1, 100, 999_999, i32::MAX, i32::MIN];
for seed in seeds {
assert_eq!(
m.mem_fill_sort_sum(32, seed).unwrap(),
fill_sort_sum_ref(32, seed),
"seed={seed}"
);
}
}
Loading