mod common;
use common::cli::{BrRun, BrWorkspace, parse_created_id, run_br, run_br_with_env};
use serde_json::{Value, json};
use std::fs::OpenOptions;
use std::time::{Duration, Instant};
const DISABLE_FAST_OPEN_ENV: (&str, &str) = ("BR_DISABLE_READ_ONLY_FAST_OPEN", "1");
struct SeededWorkspace {
workspace: BrWorkspace,
blocker_id: String,
blocked_id: String,
}
#[derive(Clone, Copy)]
enum CompareMode {
Exact,
JsonWithoutKeys(&'static [&'static str]),
}
struct MatrixCommand {
label: &'static str,
args: Vec<String>,
compare_mode: CompareMode,
}
fn assert_success(run: &BrRun, label: &str) {
assert!(
run.status.success(),
"{label} failed\nstdout:\n{}\nstderr:\n{}",
run.stdout,
run.stderr
);
}
fn run_success(workspace: &BrWorkspace, args: &[&str], label: &str) -> BrRun {
let run = run_br(workspace, args.iter().copied(), label);
assert_success(&run, label);
run
}
fn create_issue(workspace: &BrWorkspace, args: &[&str], label: &str) -> String {
parse_created_id(&run_success(workspace, args, label).stdout)
}
fn seed_workspace() -> SeededWorkspace {
let workspace = BrWorkspace::new();
run_success(&workspace, &["init"], "init");
let epic_id = create_issue(
&workspace,
&[
"create",
"Fast-open roadmap epic",
"-p",
"0",
"--type",
"epic",
"-l",
"roadmap,fast-open",
],
"create_epic",
);
let blocker_id = create_issue(
&workspace,
&[
"create",
"Fast-open blocker issue",
"-p",
"1",
"--type",
"bug",
"-l",
"backend,fast-open",
],
"create_blocker",
);
let blocked_id = create_issue(
&workspace,
&[
"create",
"Fast-open blocked issue",
"-p",
"2",
"--type",
"task",
"-l",
"backend",
"--parent",
&epic_id,
],
"create_blocked",
);
create_issue(
&workspace,
&[
"create",
"Fast-open ready issue",
"-p",
"0",
"--type",
"feature",
"-l",
"ready,fast-open",
"--parent",
&epic_id,
],
"create_ready",
);
run_success(
&workspace,
&[
"comments",
"add",
&blocker_id,
"--author",
"fast-open-test",
"Snapshot matrix comment",
],
"add_comment",
);
run_success(
&workspace,
&["dep", "add", &blocked_id, &blocker_id],
"dep_add",
);
run_success(
&workspace,
&["query", "save", "fast-open-p1", "--priority", "1"],
"query_save",
);
run_success(
&workspace,
&["sync", "--flush-only", "--json"],
"sync_flush",
);
SeededWorkspace {
workspace,
blocker_id,
blocked_id,
}
}
fn matrix_commands(seed: &SeededWorkspace) -> Vec<MatrixCommand> {
let mut commands = Vec::new();
commands.extend(core_read_commands(seed));
commands.extend(status_and_report_commands());
commands.extend(relation_and_query_commands(seed));
commands
}
fn core_read_commands(seed: &SeededWorkspace) -> Vec<MatrixCommand> {
vec![
exact_command("list_json", strings(["list", "--json", "--limit", "5"])),
exact_command(
"show_json",
vec![
"show".into(),
seed.blocker_id.clone(),
"--format".into(),
"json".into(),
],
),
exact_command(
"search_json",
strings(["search", "Fast-open", "--format", "json", "--limit", "5"]),
),
exact_command("ready_json", strings(["ready", "--json", "--limit", "5"])),
normalized_json_command(
"scheduler_json",
strings([
"scheduler",
"--json",
"--limit",
"5",
"--candidate-limit",
"10",
]),
&["generated_at"],
),
exact_command(
"blocked_json",
strings(["blocked", "--json", "--limit", "5"]),
),
]
}
fn status_and_report_commands() -> Vec<MatrixCommand> {
vec![
exact_command("count_json", strings(["count", "--json"])),
exact_command(
"count_by_label_json",
strings(["count", "--by", "label", "--json"]),
),
exact_command("stale_json", strings(["stale", "--days", "0", "--json"])),
exact_command("lint_json", strings(["lint", "--json"])),
exact_command("sync_status_json", strings(["sync", "--status", "--json"])),
exact_command(
"stats_no_activity_json",
strings(["stats", "--no-activity", "--json"]),
),
exact_command(
"status_no_activity_json",
strings(["status", "--no-activity", "--json"]),
),
normalized_json_command(
"changelog_robot",
strings(["changelog", "--since", "2100-01-01", "--robot"]),
&["until"],
),
exact_command(
"graph_all_compact",
strings(["graph", "--all", "--compact"]),
),
exact_command("orphans_robot", strings(["orphans", "--robot"])),
]
}
fn relation_and_query_commands(seed: &SeededWorkspace) -> Vec<MatrixCommand> {
vec![
exact_command(
"comments_json",
vec![
"comments".into(),
"list".into(),
seed.blocker_id.clone(),
"--json".into(),
],
),
exact_command(
"comments_shorthand_json",
vec!["comments".into(), seed.blocker_id.clone(), "--json".into()],
),
exact_command("epic_status_json", strings(["epic", "status", "--json"])),
exact_command("label_list_unique", strings(["label", "list"])),
exact_command(
"label_list_all_json",
strings(["label", "list-all", "--json"]),
),
exact_command(
"dep_list_json",
vec![
"dep".into(),
"list".into(),
seed.blocked_id.clone(),
"--format".into(),
"json".into(),
],
),
exact_command(
"dep_tree_json",
vec![
"dep".into(),
"tree".into(),
seed.blocked_id.clone(),
"--json".into(),
],
),
exact_command("dep_cycles_json", strings(["dep", "cycles", "--json"])),
exact_command(
"query_run_json",
strings(["query", "run", "fast-open-p1", "--format", "json"]),
),
exact_command("query_list_json", strings(["query", "list", "--json"])),
]
}
fn exact_command(label: &'static str, args: Vec<String>) -> MatrixCommand {
MatrixCommand {
label,
args,
compare_mode: CompareMode::Exact,
}
}
fn normalized_json_command(
label: &'static str,
args: Vec<String>,
ignored_keys: &'static [&'static str],
) -> MatrixCommand {
MatrixCommand {
label,
args,
compare_mode: CompareMode::JsonWithoutKeys(ignored_keys),
}
}
fn strings<const N: usize>(values: [&str; N]) -> Vec<String> {
values.into_iter().map(str::to_string).collect()
}
fn run_command(workspace: &BrWorkspace, command: &MatrixCommand, disable_fast_open: bool) -> BrRun {
let args = command.args.iter().map(String::as_str);
if disable_fast_open {
run_br_with_env(
workspace,
args,
[DISABLE_FAST_OPEN_ENV],
&format!("{}_conservative", command.label),
)
} else {
run_br(workspace, args, &format!("{}_fast", command.label))
}
}
fn assert_outputs_match(command: &MatrixCommand, fast: &BrRun, conservative: &BrRun) {
match command.compare_mode {
CompareMode::Exact => assert_eq!(
fast.stdout, conservative.stdout,
"{} stdout changed between read-only fast-open and conservative locked path",
command.label
),
CompareMode::JsonWithoutKeys(keys) => {
let mut fast_json: Value = serde_json::from_str(&fast.stdout).unwrap_or_else(|err| {
panic!("{} fast-open stdout is not JSON: {err}", command.label)
});
let mut conservative_json: Value = serde_json::from_str(&conservative.stdout)
.unwrap_or_else(|err| {
panic!("{} conservative stdout is not JSON: {err}", command.label)
});
remove_json_keys(&mut fast_json, keys);
remove_json_keys(&mut conservative_json, keys);
assert_eq!(
fast_json, conservative_json,
"{} normalized JSON changed between read-only fast-open and conservative locked path",
command.label
);
}
}
}
fn remove_json_keys(value: &mut Value, ignored_keys: &[&str]) {
match value {
Value::Object(object) => {
for key in ignored_keys {
object.remove(*key);
}
for nested in object.values_mut() {
remove_json_keys(nested, ignored_keys);
}
}
Value::Array(items) => {
for item in items {
remove_json_keys(item, ignored_keys);
}
}
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
}
}
#[test]
fn cli_read_only_fast_open_matrix_matches_conservative_outputs() {
let _log = common::test_log("cli_read_only_fast_open_matrix_matches_conservative_outputs");
let seed = seed_workspace();
for command in matrix_commands(&seed) {
let conservative = run_command(&seed.workspace, &command, true);
assert_success(&conservative, command.label);
let fast = run_command(&seed.workspace, &command, false);
assert_success(&fast, command.label);
assert_outputs_match(&command, &fast, &conservative);
}
}
#[test]
fn cli_read_only_fast_open_matrix_bypasses_held_write_lock() {
let _log = common::test_log("cli_read_only_fast_open_matrix_bypasses_held_write_lock");
let seed = seed_workspace();
let lock_path = seed.workspace.root.join(".beads/.write.lock");
let write_lock = OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.open(&lock_path)
.expect("open write lock");
write_lock.lock().expect("hold write lock");
for command in matrix_commands(&seed) {
let fast = run_command(&seed.workspace, &command, false);
assert_success(&fast, command.label);
}
let blocked_conservative = run_command(
&seed.workspace,
&exact_command(
"list_json_locked_conservative",
strings(["--lock-timeout", "50", "list", "--json", "--limit", "1"]),
),
true,
);
assert!(
!blocked_conservative.status.success(),
"disabled fast-open should wait for the held write lock and time out"
);
let combined = format!(
"{} {}",
blocked_conservative.stdout, blocked_conservative.stderr
)
.to_ascii_lowercase();
assert!(
combined.contains("lock") || combined.contains("timed out"),
"conservative failure should mention lock contention, got: {combined}"
);
}
fn run_matrix_round(workspace: &BrWorkspace, commands: &[MatrixCommand], disable_fast_open: bool) {
for command in commands {
let run = run_command(workspace, command, disable_fast_open);
assert_success(&run, command.label);
}
}
fn duration_ns_u64(duration: Duration) -> u64 {
u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX)
}
#[test]
#[ignore = "perf probe for CLI read-only fast-open matrix evidence"]
fn cli_read_only_fast_open_matrix_perf_probe() {
let seed = seed_workspace();
let commands = matrix_commands(&seed);
let rounds = 5_u32;
let conservative_start = Instant::now();
for _ in 0..rounds {
run_matrix_round(&seed.workspace, &commands, true);
}
let conservative = conservative_start.elapsed();
let fast_start = Instant::now();
for _ in 0..rounds {
run_matrix_round(&seed.workspace, &commands, false);
}
let fast = fast_start.elapsed();
let conservative_ns = duration_ns_u64(conservative);
let fast_ns = duration_ns_u64(fast);
println!(
"{}",
json!({
"commands": commands.iter().map(|command| command.label).collect::<Vec<_>>(),
"rounds": rounds,
"conservative_total_ns": conservative_ns,
"fast_open_total_ns": fast_ns,
"speedup_milli": conservative_ns.saturating_mul(1000) / fast_ns.max(1),
"equality": "routine matrix test asserts byte-identical stdout per command",
})
);
}