use crate::{define_plugin, load_plugin};
use interoptopus_csharp::pattern::{Try, TryExtension};
use std::error::Error;
const CHUNK: usize = 8 * 1024 * 1024;
const NUM_CYCLES: usize = 128;
const ALLOWED_GROWTH: usize = 8 * CHUNK;
interoptopus::plugin!(Memory {
fn gc();
impl Heavy {
fn new_self(size: usize, value: u32) -> Try<Self>;
async fn new_self_async(size: usize) -> Try<Self>;
fn get(&self, i: usize) -> u32;
}
impl Fliparoo {
fn create_1(heavy_1: Heavy, heavy_2: Heavy) -> Try<Self>;
async fn create_2(heavy1: &Heavy, heavy2: &Heavy) -> Try<Self>;
async fn create_3(heavy1: &Heavy, heavy3: Heavy) -> Try<Self>;
fn replace_left_1(&self, heavy: Heavy) -> Heavy;
fn replace_left_2(&self, heavy: &Heavy) -> Heavy;
fn replace_right_1(&self, heavy: Heavy) -> Heavy;
fn replace_right_2(&self, heavy: Heavy) -> Heavy;
async fn replace_left_async(&self, heavy: Heavy) -> Heavy;
async fn replace_right_async(&self, heavy: &Heavy) -> Heavy;
fn get_left(&self) -> Heavy;
fn get_right(&self) -> Heavy;
}
});
#[test]
fn build_plugin() -> Result<(), Box<dyn Error>> {
define_plugin!(Memory, "memory.dll", super::BASE);
Ok(())
}
#[test]
#[ignore = "Test flaky, hard to know exactly how much memory it is retained between rounds."]
fn load_plugin() -> Result<(), Box<dyn Error>> {
let plugin = load_plugin!(Memory, "memory.dll", super::BASE);
let rss = || memory_stats::memory_stats().map(|s| s.physical_mem).unwrap_or(0);
{
let _warm = plugin.heavy_new_self(1024, 0xDEAD).ok()?;
}
plugin.gc();
std::thread::sleep(std::time::Duration::from_millis(200));
let mut post_first_cycle = 0;
for cycle in 0..NUM_CYCLES {
let h1 = plugin.heavy_new_self(CHUNK, 1).ok()?;
let h2 = plugin.heavy_new_self(CHUNK, 2).ok()?;
let h3 = plugin.heavy_new_self(CHUNK, 3).ok()?;
let h4 = plugin.heavy_new_self(CHUNK, 4).ok()?;
assert_eq!(h1.get(0), 1, "cycle {cycle}: h1[0]");
assert_eq!(h2.get(CHUNK - 1), 2, "cycle {cycle}: h2[last]");
assert_eq!(h3.get(CHUNK / 2), 3, "cycle {cycle}: h3[mid]");
assert_eq!(h4.get(0), 4, "cycle {cycle}: h4[0]");
let flip = plugin.fliparoo_create_1(h1, h2).ok()?;
let left = flip.get_left();
let right = flip.get_right();
assert_eq!(left.get(0), 1, "cycle {cycle}: flip left should be h1");
assert_eq!(right.get(0), 2, "cycle {cycle}: flip right should be h2");
let old_left = flip.replace_left_1(h3);
assert_eq!(old_left.get(0), 1, "cycle {cycle}: old left should still be h1");
let new_left = flip.get_left();
assert_eq!(new_left.get(0), 3, "cycle {cycle}: new left should be h3");
let old_right = flip.replace_right_1(h4);
assert_eq!(old_right.get(0), 2, "cycle {cycle}: old right should still be h2");
let new_right = flip.get_right();
assert_eq!(new_right.get(0), 4, "cycle {cycle}: new right should be h4");
let copy_left = flip.replace_left_2(&old_left);
assert_eq!(copy_left.get(0), 3, "cycle {cycle}: copy of old left");
let copy_right = flip.replace_right_2(old_right);
assert_eq!(copy_right.get(0), 4, "cycle {cycle}: copy of old right");
let final_left = flip.get_left();
let final_right = flip.get_right();
assert_eq!(final_left.get(0), 1, "cycle {cycle}: final left");
assert_eq!(final_right.get(0), 2, "cycle {cycle}: final right");
drop(flip);
drop(old_left);
drop(new_left);
drop(new_right);
drop(left);
drop(right);
drop(copy_left);
drop(copy_right);
drop(final_left);
drop(final_right);
plugin.gc();
let current = rss();
if cycle == 0 {
post_first_cycle = current;
} else {
let growth_since_first = current.saturating_sub(post_first_cycle);
assert!(growth_since_first < ALLOWED_GROWTH, "cycle {cycle}: memory grew {growth_since_first} bytes since cycle 0 ({post_first_cycle}), expected stable");
}
}
Ok(())
}
#[tokio::test]
#[ignore = "Test flaky, hard to know exactly how much memory it is retained between rounds."]
async fn load_plugin_async() -> Result<(), Box<dyn Error>> {
let plugin = load_plugin!(Memory, "memory.dll", super::BASE);
let rss = || memory_stats::memory_stats().map(|s| s.physical_mem).unwrap_or(0);
{
let _warm = plugin.heavy_new_self_async(1024).await.ok()?;
}
plugin.gc();
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let mut post_first_cycle = 0;
for cycle in 0..NUM_CYCLES {
let h1 = plugin.heavy_new_self_async(CHUNK).await.ok()?;
let h2 = plugin.heavy_new_self(CHUNK, 2).ok()?;
let h3 = plugin.heavy_new_self(CHUNK, 3).ok()?;
let h4 = plugin.heavy_new_self(CHUNK, 4).ok()?;
let flip = plugin.fliparoo_create_2(&h1, &h2).await.ok()?;
assert_eq!(flip.get_left().get(0), 0, "cycle {cycle}: async-created left");
assert_eq!(flip.get_right().get(0), 2, "cycle {cycle}: async-created right");
let old_left = flip.replace_left_async(h3).await;
assert_eq!(old_left.get(0), 0, "cycle {cycle}: async old left");
assert_eq!(flip.get_left().get(0), 3, "cycle {cycle}: async new left");
let old_right = flip.replace_right_async(&h4).await;
assert_eq!(old_right.get(0), 2, "cycle {cycle}: async old right");
assert_eq!(flip.get_right().get(0), 4, "cycle {cycle}: async new right");
let flip2 = plugin.fliparoo_create_3(&old_left, old_right).await.ok()?;
assert_eq!(flip2.get_left().get(0), 0, "cycle {cycle}: flip2 left");
assert_eq!(flip2.get_right().get(0), 2, "cycle {cycle}: flip2 right");
drop(flip);
drop(flip2);
drop(h1);
drop(h2);
drop(h4);
drop(old_left);
plugin.gc();
let current = rss();
if cycle == 0 {
post_first_cycle = current;
} else {
let growth_since_first = current.saturating_sub(post_first_cycle);
assert!(growth_since_first < ALLOWED_GROWTH, "cycle {cycle}: memory grew {growth_since_first} bytes since cycle 0, expected stable");
}
}
Ok(())
}