use std::path::{Path, PathBuf};
use std::process::Command;
fn blue(dist: &Path) -> Command {
let mut c = Command::new(env!("CARGO_BIN_EXE_blue"));
c.env("BLUE_PATH", dist)
.env_remove("BLUE_CONFIG")
.env_remove("BLUE_TIER");
c
}
fn package(dist: &Path, name: &str, needs: &[&str], src: &str) -> PathBuf {
let dir = dist.join(name);
std::fs::create_dir_all(&dir).expect("mkdir");
let mut bf = format!("package(\"{name}\", \"0.1.0\")\n");
for n in needs {
bf.push_str(&format!("needs(\"{n}\", \"^0.1\")\n"));
}
std::fs::write(dir.join("Bluefile"), bf).expect("write Bluefile");
let file = dir.join(format!("{name}.b"));
std::fs::write(&file, src).expect("write source");
file
}
fn run(cmd: &mut Command) -> (bool, String, String) {
let out = cmd.output().expect("spawn");
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
)
}
#[test]
fn a_transitive_reference_is_listed_needed_and_proven() {
let root = std::env::temp_dir().join(format!("blue-migrate-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
let dist = root.join("bidamas");
package(&dist, "mk_a", &[], "def one()\n 1\nend\n");
package(
&dist,
"mk_b",
&["mk_a"],
"use(\"mk_a\", [:one])\n\ndef two()\n one() + one()\nend\n",
);
let c = package(
&dist,
"mk_c",
&["mk_b"],
"use(\"mk_b\")\n\ndef three()\n two + one\nend\n\ntest \"three\"\n assert three == 3\nend\n",
);
for p in ["mk_a", "mk_b", "mk_c"] {
let (ok, _, err) = run(blue(&dist).arg("lock").arg(dist.join(p)));
assert!(ok, "lock {p}: {err}");
}
let (ok, out, err) = run(blue(&dist).arg("migrate").arg(&c));
assert!(ok, "{out}{err}");
assert!(out.contains("2 name(s) made explicit"), "{out}");
let src = std::fs::read_to_string(&c).expect("read");
assert!(
src.starts_with("use(\"mk_a\", [:one])\nuse(\"mk_b\", [:two])\n"),
"{src}"
);
let bf = std::fs::read_to_string(dist.join("mk_c/Bluefile")).expect("read");
assert!(bf.contains("needs(\"mk_a\", \"^0.1\")"), "{bf}");
let (fresh, _, err) = run(blue(&dist)
.args(["bluefile", "--confirm"])
.arg(dist.join("mk_c/Bluefile")));
assert!(fresh, "the lock must follow the Bluefile: {err}");
let (ok, json, _) = run(blue(&dist).args(["ast", "--resolved", "--json"]).arg(&c));
assert!(ok);
assert!(!json.contains("\"kind\":\"unbound\""), "{json}");
let (ok, out, err) = run(blue(&dist).arg("test").arg(&c));
assert!(ok, "{out}{err}");
let (ok, out, err) = run(blue(&dist).arg("migrate").arg(&c));
assert!(ok, "{err}");
assert!(out.contains("unchanged"), "{out}");
let _ = std::fs::remove_dir_all(&root);
}
fn swap_dist(tag: &str, caller: &str) -> (PathBuf, PathBuf, PathBuf) {
let root = std::env::temp_dir().join(format!("blue-swap-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
let dist = root.join("bidamas");
let lib = package(
&dist,
"mk_a",
&[],
"def apply_all(f, xs)\n map(f, xs)\nend\n",
);
let user = package(&dist, "mk_b", &["mk_a"], caller);
for p in ["mk_a", "mk_b"] {
let (ok, _, err) = run(blue(&dist).arg("lock").arg(dist.join(p)));
assert!(ok, "lock {p}: {err}");
}
(dist, lib, user)
}
const CALLER: &str = "use(\"mk_a\", [:apply_all])
def twice(xs)
apply_all(
(x) -> x * 2,
xs
)
end
def nested(xs)
apply_all(
(s) -> \"a,)#{s}\",
apply_all(
(x) -> [x, x],
xs
)
)
end
test \"twice and nested\"
assert twice([1, 2]) == [2, 4]
assert nested([1]) == [\"a,)[1, 1]\"]
end
";
#[test]
fn a_swap_rewrites_every_call_and_proves_the_tree_moved_only_that_way() {
let (dist, lib, user) = swap_dist("ok", CALLER);
let (ok, out, err) = run(blue(&dist)
.args(["migrate", "--swap", "mk_a/apply_all"])
.arg(&user));
assert!(ok, "{out}{err}");
assert!(out.contains("swapped"), "{out}");
assert!(out.contains("3 call(s)"), "{out}");
let src = std::fs::read_to_string(&user).expect("read");
assert!(!src.contains("(x) -> x * 2,\n xs"), "{src}");
std::fs::write(&lib, "def apply_all(xs, f)\n map(f, xs)\nend\n").expect("write");
let (ok, out, err) = run(blue(&dist).arg("test").arg(&user));
assert!(ok, "{out}{err}");
}
#[test]
fn a_swap_flags_a_comment_inside_a_call_and_leaves_the_file_alone() {
let caller = "use(\"mk_a\", [:apply_all])
def twice(xs)
apply_all(
(x) -> x * 2, # keep this
xs
)
end
";
let (dist, _, user) = swap_dist("comment", caller);
let (ok, out, err) = run(blue(&dist)
.args(["migrate", "--swap", "mk_a/apply_all"])
.arg(&user));
assert!(!ok, "{out}{err}");
assert!(out.contains("flagged"), "{out}");
assert!(out.contains("a comment inside the call"), "{out}");
assert_eq!(std::fs::read_to_string(&user).expect("read"), caller);
}
#[test]
fn a_swap_flags_a_splat_call_and_leaves_the_file_alone() {
let caller = "use(\"mk_a\", [:apply_all])
def twice(xs)
args = [(x) -> x * 2, xs]
apply_all(*args)
end
";
let (dist, _, user) = swap_dist("pipe", caller);
let (ok, out, err) = run(blue(&dist)
.args(["migrate", "--swap", "mk_a/apply_all"])
.arg(&user));
assert!(!ok, "{out}{err}");
assert!(out.contains("flagged"), "{out}");
assert_eq!(std::fs::read_to_string(&user).expect("read"), caller);
}
#[test]
fn a_swap_leaves_a_file_that_calls_nothing_it_names_unchanged() {
let (dist, _, user) = swap_dist("none", "def one()\n 1\nend\n");
let (ok, out, err) = run(blue(&dist)
.args(["migrate", "--swap", "mk_a/apply_all"])
.arg(&user));
assert!(ok, "{out}{err}");
assert!(out.contains("unchanged"), "{out}");
}