Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
11d8290
test: add a persona soak harness with an invariant oracle
hardbyte Nov 5, 2026
311aa2f
test(persona-soak): harden the harness after smoke runs
hardbyte Nov 5, 2026
f29a890
test(persona-soak): add admin UI and Python personas, forensics for l…
hardbyte Nov 5, 2026
b165a7d
test(persona-soak): keep Python ledger import prompt, wait for Python…
hardbyte Nov 5, 2026
2c6e5b7
test(persona-soak): size the UI and Python personas down, ignore manu…
hardbyte Nov 5, 2026
95f700d
test(persona-soak): stop the load phase when the disk runs low
hardbyte Nov 5, 2026
55f1ee7
test(persona-soak): filter the instrumented server's log, detect cras…
hardbyte Nov 5, 2026
76d7274
test(persona-soak): bound the instrumented server's log with a filter…
hardbyte Nov 5, 2026
7cf7b04
test(persona-soak): drop the instrumented server's statement echoes too
hardbyte Nov 5, 2026
c04e10f
test(persona-soak): keep worker evidence when a producer registers la…
hardbyte Nov 5, 2026
d024b7b
test(persona-soak): no parallel query on the -O0 engine
hardbyte Nov 5, 2026
681019d
test(persona-soak): stop the admin server when the harness exits
hardbyte Nov 5, 2026
9f973e1
test(persona-soak): configurable admin server pool and operator read …
hardbyte Nov 5, 2026
2d95deb
test(persona-soak): optional CPU limit for the database containers
hardbyte Nov 5, 2026
32117ac
test(persona-soak): default the assert-enabled engine to the uninstru…
hardbyte Nov 5, 2026
3b3edb8
test(persona-soak): add a fan-out persona
hardbyte Nov 5, 2026
3ff540f
test(persona-soak): add a shard operator persona
hardbyte Nov 6, 2026
ffd9773
test(persona-soak): report lane-creation races and job detail for rep…
hardbyte Nov 6, 2026
4a5fb70
test(persona-soak): add a claim-and-abandon persona
hardbyte Nov 6, 2026
e7d795a
test(persona-soak): detect claims rescued from a live process
hardbyte Nov 6, 2026
20e065e
test(persona-soak): heartbeat interval and staleness knobs, heartbeat…
hardbyte Nov 6, 2026
5a87146
test(persona-soak): measure heartbeat passes over the live process's …
hardbyte Nov 6, 2026
576de91
test(persona-soak): detect a finished heartbeat pass against the prev…
hardbyte Nov 6, 2026
c81b588
test(persona-soak): measure a heartbeat pass only from a fresh reading
hardbyte Nov 6, 2026
c73e309
Require durable sampled proof in persona soak oracle
hardbyte Nov 6, 2026
91217ad
Observe Python migration pools only after readiness
hardbyte Nov 6, 2026
3933d13
Keep materialized claims in the soak ring oracle
hardbyte Nov 6, 2026
535993a
Keep Python workers draining across startup and process restarts
hardbyte Nov 6, 2026
6d425cd
Retain durable proof when lifecycle callbacks are missed
hardbyte Nov 6, 2026
5ea5fb2
test(persona-soak): fence live-owner probes across process kill
hardbyte Nov 6, 2026
4d1065d
test(persona-soak): capture bounded claim-ring lock evidence
hardbyte Nov 6, 2026
fe1632f
test(persona-soak): fence observed terminal states by attempt
hardbyte Nov 6, 2026
412419f
test: require acknowledged close for probe leak evidence
hardbyte Nov 6, 2026
0559d2e
Keep soak backlog closure membership correlated
hardbyte Nov 6, 2026
74e2283
Merge release fixes into persona soak coverage
hardbyte Nov 7, 2026
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
1,222 changes: 1,222 additions & 0 deletions awa/tests/persona_soak/abandon_oracle.rs

Large diffs are not rendered by default.

132 changes: 132 additions & 0 deletions awa/tests/persona_soak/adminserver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//! The real admin HTTP server (`awa serve`), run as a child process against the soak
//! database so the UI personas exercise the same code the web UI talks to.

use super::config::SoakConfig;
use std::net::TcpListener;
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};

pub const APPLICATION_NAME: &str = "soak_ui_server";

pub struct AdminServer {
child: Child,
pub base_url: String,
pub statement_timeout: Duration,
pub pool_max: u32,
}

/// Looks for the `awa` binary next to the test executable, where
/// `cargo build --release -p awa-cli` puts it, unless `AWA_SOAK_AWA_BIN` names one.
pub fn locate_binary(config: &SoakConfig) -> Result<PathBuf, String> {
if let Some(path) = &config.awa_bin {
return path
.exists()
.then(|| path.clone())
.ok_or_else(|| format!("AWA_SOAK_AWA_BIN {} does not exist", path.display()));
}
let exe = std::env::current_exe().map_err(|error| error.to_string())?;
let candidate = exe
.parent()
.and_then(|deps| deps.parent())
.map(|profile| profile.join("awa"));
match candidate {
Some(path) if path.exists() => Ok(path),
_ => Err(
"the admin UI personas need the awa binary: run `cargo build --release -p awa-cli` \
or set AWA_SOAK_AWA_BIN"
.to_string(),
),
}
}

impl AdminServer {
pub async fn start(config: &SoakConfig) -> Result<Self, String> {
let binary = locate_binary(config)?;
let port = TcpListener::bind("127.0.0.1:0")
.and_then(|listener| listener.local_addr())
.map_err(|error| format!("pick a port: {error}"))?
.port();
let separator = if config.database_url.contains('?') {
'&'
} else {
'?'
};
let url = format!(
"{}{separator}application_name={APPLICATION_NAME}",
config.database_url
);
let statement_timeout = config.ui_statement_timeout;
let log = std::fs::File::create(config.report_dir.join("awa-serve.log"))
.or_else(|_| {
std::fs::create_dir_all(&config.report_dir)?;
std::fs::File::create(config.report_dir.join("awa-serve.log"))
})
.map_err(|error| format!("create awa-serve.log: {error}"))?;
let child = Command::new(&binary)
.args([
"--database-url",
&url,
"serve",
"--host",
"127.0.0.1",
"--port",
&port.to_string(),
])
.args([
"--statement-timeout",
&statement_timeout.as_secs().to_string(),
])
.args([
"--pool-max",
&config.ui_pool_max.to_string(),
"--pool-min",
"2",
])
.stdout(Stdio::from(
log.try_clone().map_err(|error| error.to_string())?,
))
.stderr(Stdio::from(log))
.spawn()
.map_err(|error| format!("spawn {}: {error}", binary.display()))?;
let mut server = Self {
child,
base_url: format!("http://127.0.0.1:{port}"),
statement_timeout,
pool_max: config.ui_pool_max,
};
let client = reqwest::Client::new();
let deadline = Instant::now() + Duration::from_secs(60);
loop {
if let Ok(Some(status)) = server.child.try_wait() {
return Err(format!("awa serve exited during startup: {status}"));
}
if let Ok(response) = client
.get(format!("{}/api/capabilities", server.base_url))
.send()
.await
{
if response.status().is_success() {
return Ok(server);
}
}
if Instant::now() >= deadline {
return Err("awa serve did not start listening within 60s".to_string());
}
tokio::time::sleep(Duration::from_millis(300)).await;
}
}

pub fn is_running(&mut self) -> bool {
matches!(self.child.try_wait(), Ok(None))
}

pub fn stop(self) {}
}

impl Drop for AdminServer {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
Loading
Loading