use std::path::Path;
use std::process::Command;
use assert_cmd::prelude::*;
use tempfile::TempDir;
fn mnem_global(global_root: &Path, args: &[&str]) -> Command {
let mut cmd = Command::cargo_bin("mnem").expect("built mnem binary");
cmd.arg("-R").arg(global_root);
cmd.current_dir(std::env::temp_dir());
for a in args {
cmd.arg(a);
}
cmd
}
fn init_global(global_root: &Path) {
Command::cargo_bin("mnem")
.unwrap()
.arg("init")
.arg(global_root)
.assert()
.success();
}
fn global_add_node(global_root: &Path, summary: &str) -> String {
let out = mnem_global(
global_root,
&["global", "add", "node", "--summary", summary, "--no-embed"],
)
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
for line in stdout.lines() {
if let Some(rest) = line.strip_prefix("added node ") {
return rest.trim().to_string();
}
}
panic!("global add node stdout had no 'added node <uuid>' line:\n{stdout}");
}
fn global_add_labeled_node(global_root: &Path, summary: &str, label: &str) -> String {
let out = mnem_global(
global_root,
&[
"global", "add", "node",
"--summary", summary,
"--label", label,
"--no-embed",
],
)
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
for line in stdout.lines() {
if let Some(rest) = line.strip_prefix("added node ") {
return rest.trim().to_string();
}
}
panic!("global add labeled node stdout had no 'added node <uuid>' line:\n{stdout}");
}
#[test]
fn global_uninitialised_exits_nonzero() {
let dir = TempDir::new().unwrap();
let out = mnem_global(dir.path(), &["global", "status"])
.assert()
.failure();
let stderr = String::from_utf8_lossy(&out.get_output().stderr).to_string();
assert!(
stderr.contains("not initialised"),
"error must mention 'not initialised', got:\n{stderr}"
);
assert!(
stderr.contains("mnem integrate"),
"error must mention 'mnem integrate', got:\n{stderr}"
);
}
#[test]
fn global_status_on_fresh_init() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let out = mnem_global(dir.path(), &["global", "status"])
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains("op_id"),
"status must print 'op_id', got:\n{stdout}"
);
assert!(
stdout.contains("commit"),
"status must print 'commit' line, got:\n{stdout}"
);
}
#[test]
fn global_stats_on_fresh_init() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let out = mnem_global(dir.path(), &["global", "stats"])
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains("op="),
"stats must print 'op=<cid> commit=<cid> ...' line, got:\n{stdout}"
);
assert!(
stdout.contains("commit="),
"stats must print 'commit=<cid>' in the one-liner, got:\n{stdout}"
);
assert!(stdout.contains("content="), "stats must print 'content=...' in the one-liner, got:\n{stdout}");
assert!(stdout.contains("edges="), "stats must print 'edges=...' in the one-liner, got:\n{stdout}");
assert!(stdout.contains("labels="), "stats must print 'labels=...' in the one-liner, got:\n{stdout}");
}
#[test]
fn global_stats_labels_increments_after_labeled_node() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let before_out = mnem_global(dir.path(), &["global", "stats"])
.assert()
.success();
let before_stdout = String::from_utf8_lossy(&before_out.get_output().stdout).to_string();
let before_labels: usize = before_stdout
.split_whitespace()
.find(|tok| tok.starts_with("labels="))
.and_then(|tok| tok["labels=".len()..].parse().ok())
.expect("stats must include a 'labels=N' token");
global_add_labeled_node(dir.path(), "label-stats-test", "StatsLabelCheck");
let after_out = mnem_global(dir.path(), &["global", "stats"])
.assert()
.success();
let after_stdout = String::from_utf8_lossy(&after_out.get_output().stdout).to_string();
let after_labels: usize = after_stdout
.split_whitespace()
.find(|tok| tok.starts_with("labels="))
.and_then(|tok| tok["labels=".len()..].parse().ok())
.expect("stats must include a 'labels=N' token after write");
assert!(
after_labels > before_labels,
"labels= count in stats must increase after adding a labeled node (was {before_labels}, now {after_labels})"
);
}
#[test]
fn global_stats_edges_increments_after_edge() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let src = global_add_node(dir.path(), "refs-stats-src");
let dst = global_add_node(dir.path(), "refs-stats-dst");
let before_out = mnem_global(dir.path(), &["global", "stats"])
.assert()
.success();
let before_stdout = String::from_utf8_lossy(&before_out.get_output().stdout).to_string();
let before_edges: usize = before_stdout
.split_whitespace()
.find(|tok| tok.starts_with("edges="))
.and_then(|tok| tok["edges=".len()..].parse().ok())
.expect("stats must include an 'edges=N' token");
mnem_global(
dir.path(),
&[
"global", "add", "edge",
"--from", &src,
"--to", &dst,
"--label", "refs_test_link",
],
)
.assert()
.success();
let after_out = mnem_global(dir.path(), &["global", "stats"])
.assert()
.success();
let after_stdout = String::from_utf8_lossy(&after_out.get_output().stdout).to_string();
let after_edges: usize = after_stdout
.split_whitespace()
.find(|tok| tok.starts_with("edges="))
.and_then(|tok| tok["edges=".len()..].parse().ok())
.expect("stats must include an 'edges=N' token after edge write");
assert!(
after_edges > before_edges,
"edges= count in stats must increase after adding an edge (was {before_edges}, now {after_edges})"
);
}
#[test]
fn global_status_op_id_advances_after_write() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let before_out = mnem_global(dir.path(), &["global", "status"])
.assert()
.success();
let before_stdout = String::from_utf8_lossy(&before_out.get_output().stdout).to_string();
let before_op_line = before_stdout
.lines()
.find(|l| l.contains("op_id"))
.map(|l| l.to_string())
.expect("status must print an op_id line");
global_add_node(dir.path(), "status-advance-test");
let after_out = mnem_global(dir.path(), &["global", "status"])
.assert()
.success();
let after_stdout = String::from_utf8_lossy(&after_out.get_output().stdout).to_string();
let after_op_line = after_stdout
.lines()
.find(|l| l.contains("op_id"))
.map(|l| l.to_string())
.expect("status must print an op_id line after write");
assert_ne!(
before_op_line, after_op_line,
"op_id in 'global status' must change after a write:\n before: {before_op_line}\n after: {after_op_line}"
);
}
#[test]
fn global_add_node_and_get_by_uuid() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_node(dir.path(), "global-fact-node");
let out = mnem_global(dir.path(), &["global", "get", &uuid])
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains("global-fact-node"),
"get must return the node's summary, got:\n{stdout}"
);
assert!(
stdout.contains(&uuid),
"get output must include the node UUID, got:\n{stdout}"
);
}
#[test]
fn global_labeled_node_ntype_in_get() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_labeled_node(dir.path(), "ntype-round-trip", "MyCustomLabel");
let out = mnem_global(dir.path(), &["global", "get", &uuid])
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains("ntype:"),
"get output must include 'ntype:' field, got:\n{stdout}"
);
assert!(
stdout.contains("MyCustomLabel"),
"get output must show the stored label 'MyCustomLabel', got:\n{stdout}"
);
assert!(
stdout.contains("ntype-round-trip"),
"get output must include the node summary, got:\n{stdout}"
);
}
#[test]
fn global_add_node_creates_new_op() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let before_out = mnem_global(dir.path(), &["global", "log", "-n", "100"])
.assert()
.success();
let before_count = String::from_utf8_lossy(&before_out.get_output().stdout)
.lines()
.filter(|l| l.starts_with("op "))
.count();
global_add_node(dir.path(), "op-count-test");
let after_out = mnem_global(dir.path(), &["global", "log", "-n", "100"])
.assert()
.success();
let after_count = String::from_utf8_lossy(&after_out.get_output().stdout)
.lines()
.filter(|l| l.starts_with("op "))
.count();
assert_eq!(
after_count,
before_count + 1,
"adding a node must create exactly 1 new op (was {before_count}, now {after_count})"
);
}
#[test]
fn global_get_missing_uuid_exits_nonzero() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
mnem_global(
dir.path(),
&["global", "get", "00000000-0000-7000-8000-000000000099"],
)
.assert()
.failure();
}
#[test]
fn global_tombstone_node_shows_tombstoned() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_node(dir.path(), "soft-delete-global");
let before = mnem_global(dir.path(), &["global", "get", &uuid])
.assert()
.success();
let before_stdout = String::from_utf8_lossy(&before.get_output().stdout).to_string();
assert!(
!before_stdout.contains("tombstoned: true"),
"node must not be tombstoned before tombstone op, got:\n{before_stdout}"
);
mnem_global(dir.path(), &["global", "tombstone", &uuid])
.assert()
.success();
let after = mnem_global(dir.path(), &["global", "get", &uuid])
.assert()
.success();
let after_stdout = String::from_utf8_lossy(&after.get_output().stdout).to_string();
assert!(
after_stdout.contains("tombstoned: true"),
"get must report 'tombstoned: true' after tombstone op, got:\n{after_stdout}"
);
}
#[test]
fn global_tombstone_nonexistent_uuid_exits_nonzero() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
mnem_global(
dir.path(),
&["global", "tombstone", "00000000-0000-7000-8000-000000000099"],
)
.assert()
.failure();
}
#[test]
fn global_tombstoned_node_hidden_from_query() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_labeled_node(dir.path(), "tombstone-query-check", "TombstoneQueryLabel");
let before_out = mnem_global(
dir.path(),
&["global", "query", "--where", "ntype=TombstoneQueryLabel"],
)
.assert()
.success();
let before_stdout = String::from_utf8_lossy(&before_out.get_output().stdout).to_string();
assert!(
before_stdout.contains(&uuid),
"node must appear in query before tombstone, got:\n{before_stdout}"
);
assert!(
before_stdout.contains("1 hit(s)"),
"query must show '1 hit(s)' before tombstone, got:\n{before_stdout}"
);
mnem_global(dir.path(), &["global", "tombstone", &uuid])
.assert()
.success();
let after_out = mnem_global(
dir.path(),
&["global", "query", "--where", "ntype=TombstoneQueryLabel"],
)
.assert()
.success();
let after_stdout = String::from_utf8_lossy(&after_out.get_output().stdout).to_string();
assert!(
after_stdout.contains("0 hit(s)"),
"tombstoned node must be hidden; query must report '0 hit(s)', got:\n{after_stdout}"
);
assert!(
!after_stdout.contains(&uuid),
"tombstoned node UUID must not appear in query output, got:\n{after_stdout}"
);
}
#[test]
fn global_tombstoned_node_hidden_from_retrieve() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_labeled_node(
dir.path(),
"tombstone-retrieve-check",
"TombstoneRetrieveLabel",
);
let before_out = mnem_global(
dir.path(),
&[
"global", "retrieve",
"--where", "ntype=TombstoneRetrieveLabel",
"--no-vector",
],
)
.assert()
.success();
let before_stdout = String::from_utf8_lossy(&before_out.get_output().stdout).to_string();
assert!(
before_stdout.contains(&uuid),
"node must appear in retrieve before tombstone, got:\n{before_stdout}"
);
assert!(
before_stdout.contains("1 item(s)"),
"retrieve must show '1 item(s)' before tombstone, got:\n{before_stdout}"
);
mnem_global(dir.path(), &["global", "tombstone", &uuid])
.assert()
.success();
let after_out = mnem_global(
dir.path(),
&[
"global", "retrieve",
"--where", "ntype=TombstoneRetrieveLabel",
"--no-vector",
],
)
.assert()
.success();
let after_stdout = String::from_utf8_lossy(&after_out.get_output().stdout).to_string();
assert!(
after_stdout.contains("0 item(s)"),
"tombstoned node must be hidden; retrieve must report '0 item(s)', got:\n{after_stdout}"
);
assert!(
!after_stdout.contains(&uuid),
"tombstoned node UUID must not appear in retrieve output, got:\n{after_stdout}"
);
}
#[test]
fn global_delete_node_makes_get_fail() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_node(dir.path(), "to-be-deleted-global");
mnem_global(dir.path(), &["global", "get", &uuid])
.assert()
.success();
mnem_global(dir.path(), &["global", "delete", &uuid])
.assert()
.success();
mnem_global(dir.path(), &["global", "get", &uuid])
.assert()
.failure();
}
#[test]
fn global_delete_nonexistent_uuid_exits_nonzero() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
mnem_global(
dir.path(),
&["global", "delete", "00000000-0000-7000-8000-000000000099"],
)
.assert()
.failure();
}
#[test]
fn global_deleted_node_hidden_from_query() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_labeled_node(dir.path(), "delete-query-check", "DeleteQueryLabel");
let before_out = mnem_global(
dir.path(),
&["global", "query", "--where", "ntype=DeleteQueryLabel"],
)
.assert()
.success();
let before_stdout = String::from_utf8_lossy(&before_out.get_output().stdout).to_string();
assert!(
before_stdout.contains(&uuid),
"node must appear in query before deletion, got:\n{before_stdout}"
);
assert!(
before_stdout.contains("1 hit(s)"),
"query must show '1 hit(s)' before deletion, got:\n{before_stdout}"
);
mnem_global(dir.path(), &["global", "delete", &uuid])
.assert()
.success();
let after_out = mnem_global(
dir.path(),
&["global", "query", "--where", "ntype=DeleteQueryLabel"],
)
.assert()
.success();
let after_stdout = String::from_utf8_lossy(&after_out.get_output().stdout).to_string();
assert!(
after_stdout.contains("0 hit(s)"),
"hard-deleted node must be hidden; query must report '0 hit(s)', got:\n{after_stdout}"
);
assert!(
!after_stdout.contains(&uuid),
"hard-deleted node UUID must not appear in query output, got:\n{after_stdout}"
);
}
#[test]
fn global_deleted_node_hidden_from_retrieve() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_labeled_node(
dir.path(),
"delete-retrieve-check",
"DeleteRetrieveLabel",
);
let before_out = mnem_global(
dir.path(),
&[
"global", "retrieve",
"--where", "ntype=DeleteRetrieveLabel",
"--no-vector",
],
)
.assert()
.success();
let before_stdout = String::from_utf8_lossy(&before_out.get_output().stdout).to_string();
assert!(
before_stdout.contains(&uuid),
"node must appear in retrieve before deletion, got:\n{before_stdout}"
);
assert!(
before_stdout.contains("1 item(s)"),
"retrieve must show '1 item(s)' before deletion, got:\n{before_stdout}"
);
mnem_global(dir.path(), &["global", "delete", &uuid])
.assert()
.success();
let after_out = mnem_global(
dir.path(),
&[
"global", "retrieve",
"--where", "ntype=DeleteRetrieveLabel",
"--no-vector",
],
)
.assert()
.success();
let after_stdout = String::from_utf8_lossy(&after_out.get_output().stdout).to_string();
assert!(
after_stdout.contains("0 item(s)"),
"hard-deleted node must be hidden; retrieve must report '0 item(s)', got:\n{after_stdout}"
);
assert!(
!after_stdout.contains(&uuid),
"hard-deleted node UUID must not appear in retrieve output, got:\n{after_stdout}"
);
}
#[test]
fn global_add_edge_visible_in_blame() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let src_uuid = global_add_node(dir.path(), "global-edge-src");
let dst_uuid = global_add_node(dir.path(), "global-edge-dst");
mnem_global(
dir.path(),
&[
"global",
"add",
"edge",
"--from",
&src_uuid,
"--to",
&dst_uuid,
"--label",
"global_link",
],
)
.assert()
.success();
let out = mnem_global(dir.path(), &["global", "blame", &dst_uuid])
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains("global_link"),
"blame must list the incoming 'global_link' edge, got:\n{stdout}"
);
assert!(
stdout.contains(&src_uuid),
"blame must reference the source node UUID, got:\n{stdout}"
);
assert!(
stdout.contains("-[global_link]->"),
"blame must use the '<src> -[etype]-> <dst>' format, got:\n{stdout}"
);
}
#[test]
fn global_add_edge_nonexistent_uuid_exits_nonzero() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let existing = global_add_node(dir.path(), "edge-target-exists");
mnem_global(
dir.path(),
&[
"global", "add", "edge",
"--from", "00000000-0000-7000-8000-000000000099",
"--to", &existing,
"--label", "ghost_link",
],
)
.assert()
.failure();
}
#[test]
fn global_add_edge_bad_dst_uuid_exits_nonzero() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let existing = global_add_node(dir.path(), "edge-source-exists");
mnem_global(
dir.path(),
&[
"global", "add", "edge",
"--from", &existing,
"--to", "00000000-0000-7000-8000-000000000099",
"--label", "ghost_link",
],
)
.assert()
.failure();
}
#[test]
fn global_blame_no_incoming_edges() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_node(dir.path(), "isolated-node-blame");
let out = mnem_global(dir.path(), &["global", "blame", &uuid])
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains("<no incoming edges>"),
"blame on a node with no incoming edges must print '<no incoming edges>', got:\n{stdout}"
);
}
#[test]
fn global_blame_nonexistent_uuid_default_warns_and_succeeds() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let out = mnem_global(
dir.path(),
&["global", "blame", "00000000-0000-7000-8000-000000000099"],
)
.assert()
.success();
let stderr = String::from_utf8_lossy(&out.get_output().stderr).to_string();
assert!(
stderr.contains("warning: no node with id="),
"blame on unknown uuid must emit a stderr warning, got stderr:\n{stderr}"
);
}
#[test]
fn global_blame_nonexistent_uuid_strict_exits_nonzero() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
mnem_global(
dir.path(),
&[
"global",
"blame",
"00000000-0000-7000-8000-000000000099",
"--strict",
],
)
.assert()
.failure();
}
#[test]
fn global_log_emits_op_line() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
global_add_node(dir.path(), "log-test-node");
let out = mnem_global(dir.path(), &["global", "log", "-n", "1"])
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.lines().any(|l| l.starts_with("op ")),
"global log -n 1 must emit an 'op <cid>' line, got:\n{stdout}"
);
}
#[test]
fn global_query_filters_by_label() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_labeled_node(dir.path(), "query-label-test", "FactQuery");
let query_out =
mnem_global(dir.path(), &["global", "query", "--where", "ntype=FactQuery"])
.assert()
.success();
let query_stdout = String::from_utf8_lossy(&query_out.get_output().stdout).to_string();
assert!(
query_stdout.contains("1 hit(s)"),
"query must report '1 hit(s)' for one matching node, got:\n{query_stdout}"
);
assert!(
query_stdout.contains(&uuid),
"query --where ntype=FactQuery must return the FactQuery node UUID, got:\n{query_stdout}"
);
}
#[test]
fn global_query_zero_hits_for_unknown_label() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let out = mnem_global(
dir.path(),
&["global", "query", "--where", "ntype=LabelThatNeverExists99"],
)
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains("0 hit(s)"),
"query for a non-existent label must print '0 hit(s)', got:\n{stdout}"
);
}
#[test]
fn global_query_two_nodes_same_label() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid1 = global_add_labeled_node(dir.path(), "multi-query-a", "MultiQueryLabel");
let uuid2 = global_add_labeled_node(dir.path(), "multi-query-b", "MultiQueryLabel");
let out = mnem_global(dir.path(), &["global", "query", "--where", "ntype=MultiQueryLabel"])
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains("2 hit(s)"),
"query must report '2 hit(s)' when two nodes share the same label, got:\n{stdout}"
);
assert!(
stdout.contains(&uuid1),
"query must include first node UUID, got:\n{stdout}"
);
assert!(
stdout.contains(&uuid2),
"query must include second node UUID, got:\n{stdout}"
);
}
#[test]
fn global_retrieve_returns_matching_node() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_labeled_node(
dir.path(),
"retrieve-target-unique",
"GlobalRetrieveTest",
);
let ret_out = mnem_global(
dir.path(),
&[
"global",
"retrieve",
"--where",
"ntype=GlobalRetrieveTest",
"--no-vector",
],
)
.assert()
.success();
let ret_stdout = String::from_utf8_lossy(&ret_out.get_output().stdout).to_string();
assert!(
ret_stdout.contains(&uuid),
"retrieve --where ntype=GlobalRetrieveTest must return the node UUID, got:\n{ret_stdout}"
);
assert!(
ret_stdout.contains("1 item(s)"),
"retrieve must report '1 item(s)', got:\n{ret_stdout}"
);
}
#[test]
fn global_retrieve_zero_hits_for_unknown_label() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let out = mnem_global(
dir.path(),
&[
"global", "retrieve",
"--where", "ntype=LabelThatNeverExistsInRetrieve77",
"--no-vector",
],
)
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains("0 item(s)"),
"retrieve for a non-existent label must print '0 item(s)', got:\n{stdout}"
);
}
#[test]
fn global_retrieve_text_query_returns_matching_node() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let uuid = global_add_node(dir.path(), "unique-bm25-search-target-xyz");
let out = mnem_global(
dir.path(),
&[
"global", "retrieve",
"unique-bm25-search-target-xyz",
"--no-vector",
],
)
.assert()
.success();
let stdout = String::from_utf8_lossy(&out.get_output().stdout).to_string();
assert!(
stdout.contains(&uuid),
"text-query retrieve must return the node whose summary matches, got:\n{stdout}"
);
}
#[test]
fn global_multiple_writes_advance_op_log() {
let dir = TempDir::new().unwrap();
init_global(dir.path());
let before_out = mnem_global(dir.path(), &["global", "log", "-n", "100"])
.assert()
.success();
let before_count = String::from_utf8_lossy(&before_out.get_output().stdout)
.lines()
.filter(|l| l.starts_with("op "))
.count();
let uuid1 = global_add_node(dir.path(), "multi-write-a");
mnem_global(dir.path(), &["global", "tombstone", &uuid1])
.assert()
.success();
global_add_node(dir.path(), "multi-write-b");
let after_out = mnem_global(dir.path(), &["global", "log", "-n", "100"])
.assert()
.success();
let after_count = String::from_utf8_lossy(&after_out.get_output().stdout)
.lines()
.filter(|l| l.starts_with("op "))
.count();
assert!(
after_count >= before_count + 3,
"three write ops must create at least 3 new ops (was {before_count}, now {after_count})"
);
}