132 lines
4.4 KiB
Rust
132 lines
4.4 KiB
Rust
//! Durable event transactions, crash recovery, and world extension reactors.
|
|
use sausage::{harness::run_module, world::World};
|
|
use std::{
|
|
path::Path,
|
|
process::Command,
|
|
time::{Duration, Instant},
|
|
};
|
|
const BUNDLE: &str = include_str!("../../js/events-test-bundle.js");
|
|
struct Author(std::sync::Arc<dyn sausage::exec::Exec>);
|
|
#[async_trait::async_trait]
|
|
impl sausage::exec::Exec for Author {
|
|
async fn exec(
|
|
&self,
|
|
command: sausage::exec::Command,
|
|
mut options: sausage::exec::Options,
|
|
output: &mut (dyn for<'a> FnMut(sausage::exec::Stream, &'a [u8]) + Send),
|
|
) -> anyhow::Result<i32> {
|
|
for (key, value) in [
|
|
("GIT_AUTHOR_NAME", "Test"),
|
|
("GIT_AUTHOR_EMAIL", "test@example.com"),
|
|
("GIT_COMMITTER_NAME", "Test"),
|
|
("GIT_COMMITTER_EMAIL", "test@example.com"),
|
|
] {
|
|
options.env.push((key.into(), value.into()));
|
|
}
|
|
self.0.exec(command, options, output).await
|
|
}
|
|
}
|
|
async fn run(dir: &Path, mode: &str) -> anyhow::Result<()> {
|
|
let world = World::open(dir)?;
|
|
run_module(
|
|
world,
|
|
sausage::process::exec(World::open(dir)?)?,
|
|
&format!("globalThis.eventsMode={mode:?};\n{BUNDLE}"),
|
|
)
|
|
.await
|
|
}
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn transactions_and_feed() -> anyhow::Result<()> {
|
|
let dir = tempfile::tempdir()?;
|
|
run(dir.path(), "normal").await
|
|
}
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn restart_child() -> anyhow::Result<()> {
|
|
if let Ok(dir) = std::env::var("EVENTS_RESTART_DIR") {
|
|
run(Path::new(&dir), &std::env::var("EVENTS_MODE")?).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
#[test]
|
|
fn sigkill_between_emit_and_reaction() -> anyhow::Result<()> {
|
|
let dir = tempfile::tempdir()?;
|
|
let mut child = Command::new(std::env::current_exe()?)
|
|
.args(["--exact", "events::restart_child", "--nocapture"])
|
|
.env("EVENTS_RESTART_DIR", dir.path())
|
|
.env("EVENTS_MODE", "emit")
|
|
.spawn()?;
|
|
let deadline = Instant::now() + Duration::from_secs(30);
|
|
while !dir.path().join("world/emitted").is_file() {
|
|
if Instant::now() > deadline {
|
|
child.kill()?;
|
|
child.wait()?;
|
|
anyhow::bail!("Event did not commit");
|
|
}
|
|
std::thread::yield_now();
|
|
}
|
|
child.kill()?;
|
|
child.wait()?;
|
|
for _ in 0..2 {
|
|
let mut child = Command::new(std::env::current_exe()?)
|
|
.args(["--exact", "events::restart_child", "--nocapture"])
|
|
.env("EVENTS_RESTART_DIR", dir.path())
|
|
.env("EVENTS_MODE", "consume")
|
|
.spawn()?;
|
|
let deadline = Instant::now() + Duration::from_secs(30);
|
|
loop {
|
|
if let Some(status) = child.try_wait()? {
|
|
assert!(status.success());
|
|
break;
|
|
}
|
|
if Instant::now() > deadline {
|
|
child.kill()?;
|
|
child.wait()?;
|
|
anyhow::bail!("Reaction did not finish");
|
|
}
|
|
std::thread::yield_now();
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn world_extensions() -> anyhow::Result<()> {
|
|
let target = crate::common::build_git();
|
|
let dir = tempfile::tempdir()?;
|
|
let world = World::open(dir.path())?;
|
|
let disk = dir.path().join("world");
|
|
std::fs::create_dir_all(disk.join("bin"))?;
|
|
let git = target.join("wasm32-wasip2/release/git.wasm");
|
|
std::fs::copy(git, disk.join("bin/git.wasm"))?;
|
|
let repo = disk.join("src/sausage");
|
|
std::fs::create_dir_all(&repo)?;
|
|
assert!(
|
|
Command::new("git")
|
|
.args(["init", "-b", "main"])
|
|
.arg(&repo)
|
|
.status()?
|
|
.success()
|
|
);
|
|
assert!(
|
|
Command::new("git")
|
|
.args(["commit", "--no-gpg-sign", "--allow-empty", "-m", "empty"])
|
|
.env("GIT_AUTHOR_NAME", "Test")
|
|
.env("GIT_AUTHOR_EMAIL", "test@example.com")
|
|
.env("GIT_COMMITTER_NAME", "Test")
|
|
.env("GIT_COMMITTER_EMAIL", "test@example.com")
|
|
.current_dir(&repo)
|
|
.status()?
|
|
.success()
|
|
);
|
|
let exec = std::sync::Arc::new(Author(sausage::process::exec(World {
|
|
root: world.root.clone(),
|
|
host_dir: world.host_dir.clone(),
|
|
processes: world.processes.clone(),
|
|
})?));
|
|
run_module(
|
|
world,
|
|
exec,
|
|
&format!("globalThis.eventsMode='world';\n{BUNDLE}"),
|
|
)
|
|
.await
|
|
}
|