#[path = "../tests/common/mod.rs"]
mod common;
use std::fmt::Write as _;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;
use common::*;
type Run = fn(&mut Scenario);
const SCENARIOS: [(&str, Run); 5] = [
("one-session", one_session),
("two-agents", two_agents),
("dead-agent", dead_agent),
("milestone-mid-task", milestone_mid_task),
("big-backlog", big_backlog),
];
fn main() {
let name = std::env::args().nth(1).unwrap_or_default();
let chosen: Vec<_> = SCENARIOS
.iter()
.filter(|(n, _)| name == "all" || *n == name)
.collect();
if chosen.is_empty() {
eprintln!("usage: cargo run --example sandbox <scenario>|all");
eprintln!("scenarios:");
for (n, _) in SCENARIOS {
eprintln!(" {n}");
}
std::process::exit(2);
}
build_foam();
for (n, run) in chosen {
let mut scenario = Scenario::new(n);
run(&mut scenario);
scenario.finish();
}
}
fn build_foam() {
let ok = Command::new(env!("CARGO"))
.args(["build", "-q", "--bin", "foam"])
.status()
.unwrap()
.success();
assert!(ok, "cargo build failed");
}
struct Scenario {
name: &'static str,
root: PathBuf,
sessions: usize,
report: String,
}
impl Scenario {
fn new(name: &'static str) -> Scenario {
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
.join(".sandbox")
.join(name);
if root.exists() {
fs::remove_dir_all(&root).unwrap();
}
fs::create_dir_all(&root).unwrap();
println!("{name}: building in {}", root.display());
Scenario {
name,
root,
sessions: 0,
report: String::new(),
}
}
fn repo(&self, name: &str) -> PathBuf {
let dir = self.root.join(name);
fs::create_dir_all(&dir).unwrap();
init_repo(&dir);
commit(&dir, "one");
dir
}
fn clones(&self) -> (PathBuf, PathBuf) {
two_clones_in(&self.root)
}
fn session(&mut self, dir: &Path, who: &str) {
self.sessions += 1;
let text = stdout(actor(dir, who).arg("prime"));
let file = format!("prime-{:02}-{who}.md", self.sessions);
fs::write(self.root.join(&file), format!("{text}\n")).unwrap();
let _ = writeln!(self.report, "session {} as {who}: {file}", self.sessions);
}
fn note(&mut self, line: impl AsRef<str>) {
let _ = writeln!(self.report, "{}", line.as_ref());
}
fn finish(&self) {
fs::write(self.root.join("report.txt"), &self.report).unwrap();
print!("{}", self.report);
println!("{}: done", self.name);
}
}
fn one_session(s: &mut Scenario) {
let r = s.repo("repo");
stdout(foam(&r).args(["init", "--prefix", "s"]));
let a = stdout(foam(&r).args(["create", "Wire the parser", "-p", "1"]));
stdout(foam(&r).args(["create", "Test the parser", "--blocked-by", &a]));
s.session(&r, "ann");
stdout(actor(&r, "ann").args(["claim", &a]));
stdout(actor(&r, "ann").args(["note", &a, "tried PEG first, went with winnow"]));
stdout(actor(&r, "ann").args(["close", &a, "--reason", "parser wired"]));
stdout(actor(&r, "ann").args(["remember", "parser-lib", "winnow, not nom"]));
commit(&r, "wire the parser");
s.session(&r, "ann");
}
fn two_agents(s: &mut Scenario) {
let (a, b) = s.clones();
stdout(foam(&a).args(["init", "--prefix", "s"]));
let x = stdout(foam(&a).args(["create", "Shared work"]));
stdout(foam(&a).arg("sync"));
stdout(foam(&b).arg("init"));
s.session(&a, "ann");
s.session(&b, "bob");
stdout(actor(&a, "ann").args(["claim", &x]));
stdout(actor(&b, "bob").args(["claim", &x]));
commit(&a, "ann's work");
git(&a, &["push", "-q"]);
git(&b, &["checkout", "-q", "-b", "bob"]);
commit(&b, "bob's work");
git(&b, &["push", "-q", "-u", "origin", "bob"]);
s.note("after both pushed, b's view:");
s.note(stdout(foam(&b).args(["show", &x])));
git(&a, &["fetch", "-q"]);
s.note("a's view after a fetch:");
s.note(stdout(foam(&a).args(["show", &x])));
s.session(&a, "ann");
s.session(&b, "bob");
}
fn dead_agent(s: &mut Scenario) {
let r = s.repo("repo");
stdout(foam(&r).args(["init", "--prefix", "s"]));
stdout(foam(&r).args(["config", "lease-minutes", "0"]));
let a = stdout(foam(&r).args(["create", "Abandoned"]));
s.session(&r, "ann");
stdout(actor(&r, "ann").args(["claim", &a]));
stdout(actor(&r, "ann").args(["note", &a, "halfway"]));
s.session(&r, "bob");
s.note(stdout(foam(&r).args(["show", &a])));
}
fn milestone_mid_task(s: &mut Scenario) {
let r = s.repo("repo");
stdout(foam(&r).args(["init", "--prefix", "s"]));
let a = stdout(foam(&r).args(["create", "Add the export command"]));
s.session(&r, "ann");
stdout(actor(&r, "ann").args(["claim", &a]));
let milestone =
stdout(actor(&r, "ann").args(["create", "Export formats", "--type", "milestone"]));
let csv = stdout(actor(&r, "ann").args(["create", "CSV export", "--parent", &milestone]));
stdout(actor(&r, "ann").args(["create", "JSON export", "--parent", &milestone]));
stdout(actor(&r, "ann").args(["dep", "add", &a, &milestone]));
stdout(actor(&r, "ann").args(["claim", &csv]));
stdout(actor(&r, "ann").args(["close", &csv, "--reason", "writes rows"]));
stdout(actor(&r, "ann").args(["note", &a, "export waits on the formats milestone"]));
s.session(&r, "ann");
s.note(stdout(foam(&r).arg("blocked")));
s.note(stdout(foam(&r).args(["dep", "tree", &a])));
}
fn big_backlog(s: &mut Scenario) {
let r = s.repo("repo");
stdout(foam(&r).args(["init", "--prefix", "s"]));
let start = Instant::now();
let kinds = ["task", "bug", "feature", "chore"];
for i in 0..200 {
let p = (i % 5).to_string();
stdout(foam(&r).args([
"create",
&format!("Issue number {i} with a title of ordinary length"),
"--type",
kinds[i % 4],
"-p",
&p,
"--body",
&"Body text. ".repeat(20),
]));
}
for i in 0..30 {
stdout(foam(&r).args([
"remember",
&format!("fact-{i:02}"),
&format!("memory {i}: {}", "some words ".repeat(8)),
]));
}
s.note(format!("230 writes took {:?}", start.elapsed()));
for args in [
vec!["prime"],
vec!["ready"],
vec!["list", "--all"],
vec!["search", "number 19"],
vec!["search", "memory 2"],
vec!["board"],
] {
let t = Instant::now();
let out = stdout(foam(&r).args(&args));
s.note(format!(
"{:<20} {:>6} bytes {:>4} lines {:?}",
args.join(" "),
out.len(),
out.lines().count(),
t.elapsed()
));
}
s.session(&r, "ann");
}