use super::*;
use std::path::PathBuf;
fn metadata(root: &str, build: &str, target: &str, members: &[&str]) -> String {
let packages: Vec<String> = members.iter().map(|name| { return format!("{{\"name\":\"{name}\",\"version\":\"0.1.0\",\"id\":\"path+file://{root}/{name}#0.1.0\"}}"); }).collect();
return format!("{{\"packages\":[{}],\"workspace_root\":\"{root}\",\"build_directory\":\"{build}\",\"target_directory\":\"{target}\",\"workspace_members\":[]}}", packages.join(","));
}
fn workspace(root: &str, build: &str, members: &[&str]) -> Workspace {
return Workspace { root: PathBuf::from(root), build_directory: PathBuf::from(build), target_directory: PathBuf::from(format!("{root}/target")), members: members.iter().map(|name| { return (*name).to_owned(); }).collect() };
}
#[test]
fn parses_the_directories_and_the_member_names() {
let found: Workspace = parse_metadata(metadata("/w/ws", "/cache/ab/cd", "/w/ws/target", &["app", "lib"]).as_str()).expect("parse");
assert_eq!(found, Workspace { root: PathBuf::from("/w/ws"), build_directory: PathBuf::from("/cache/ab/cd"), target_directory: PathBuf::from("/w/ws/target"), members: vec![String::from("app"), String::from("lib")] });
}
#[test]
fn a_missing_build_directory_is_the_target_directory() {
let text: &str = "{\"packages\":[],\"workspace_root\":\"/w/ws\",\"target_directory\":\"/w/ws/target\"}";
assert_eq!(parse_metadata(text).expect("parse").build_directory, PathBuf::from("/w/ws/target"));
}
#[test]
fn malformed_metadata_is_an_error() {
assert!(parse_metadata("not json").is_err());
let error: String = parse_metadata("{\"packages\":[]}").expect_err("no root");
assert!(error.contains("workspace_root"), "{error}");
}
#[test]
fn a_workspace_is_listed_once_in_root_order() {
let found: Vec<Workspace> = vec![workspace("/w/b", "/c/b", &["b"]), workspace("/w/a", "/c/a", &["a1", "a2"]), workspace("/w/a", "/c/a", &["a1", "a2"])];
let distinct: Vec<Workspace> = distinct_workspaces(found);
assert_eq!(distinct.iter().map(|each| { return each.root.clone(); }).collect::<Vec<PathBuf>>(), vec![PathBuf::from("/w/a"), PathBuf::from("/w/b")]);
}
fn facts(source_build: &str) -> StateFacts {
return StateFacts { source_build: Some(PathBuf::from(source_build)), source_build_exists: true, destination_build_exists: false, destination_target_exists: true };
}
#[test]
fn an_external_build_directory_is_cloned_then_cleaned() {
let destination: Workspace = workspace("/w/new/ws", "/cache/new", &["app"]);
let plan: BuildStatePlan = plan_build_state(&destination, Path::new("/w/new"), &facts("/cache/old"));
assert_eq!(plan, BuildStatePlan::CloneThenClean { from: PathBuf::from("/cache/old"), to: PathBuf::from("/cache/new") });
}
#[test]
fn an_in_tree_build_directory_is_only_cleaned() {
let destination: Workspace = workspace("/w/new/ws", "/w/new/ws/target", &["app"]);
let plan: BuildStatePlan = plan_build_state(&destination, Path::new("/w/new"), &facts("/w/old/ws/target"));
assert_eq!(plan, BuildStatePlan::CleanOnly);
}
#[test]
fn an_absent_in_tree_state_needs_nothing() {
let destination: Workspace = workspace("/w/new/ws", "/w/new/ws/target", &["app"]);
let mut absent: StateFacts = facts("/w/old/ws/target");
absent.destination_target_exists = false;
assert!(matches!(plan_build_state(&destination, Path::new("/w/new"), &absent), BuildStatePlan::Nothing(_)));
}
#[test]
fn a_shared_build_directory_is_left_alone() {
let destination: Workspace = workspace("/w/new/ws", "/cache/shared", &["app"]);
assert!(matches!(plan_build_state(&destination, Path::new("/w/new"), &facts("/cache/shared")), BuildStatePlan::Nothing(_)));
}
#[test]
fn a_new_workspace_and_existing_state_are_left_alone() {
let destination: Workspace = workspace("/w/new/ws", "/cache/new", &["app"]);
let mut unknown: StateFacts = facts("/cache/old");
unknown.source_build = None;
assert!(matches!(plan_build_state(&destination, Path::new("/w/new"), &unknown), BuildStatePlan::Nothing(_)));
let mut existing: StateFacts = facts("/cache/old");
existing.destination_build_exists = true;
assert!(matches!(plan_build_state(&destination, Path::new("/w/new"), &existing), BuildStatePlan::Nothing(_)));
}
#[test]
fn an_empty_source_clones_nothing() {
let destination: Workspace = workspace("/w/new/ws", "/cache/new", &["app"]);
let mut empty: StateFacts = facts("/cache/old");
empty.source_build_exists = false;
assert_eq!(plan_build_state(&destination, Path::new("/w/new"), &empty), BuildStatePlan::CleanOnly);
empty.destination_target_exists = false;
assert!(matches!(plan_build_state(&destination, Path::new("/w/new"), &empty), BuildStatePlan::Nothing(_)));
}
#[test]
fn the_clean_names_every_member_and_stays_offline() {
let members: Vec<String> = vec![String::from("app"), String::from("lib")];
let arguments: Vec<String> = clean_arguments(&members).iter().map(|each| { return each.to_string_lossy().into_owned(); }).collect();
assert_eq!(arguments.first().map(String::as_str), Some("clean"));
assert!(arguments.iter().any(|each| { return each == "--offline"; }), "--offline missing in {arguments:?}");
assert!(!arguments.iter().any(|each| { return each == "--locked"; }), "--locked would fail on a stale lock: {arguments:?}");
for member in &members {
let position: usize = arguments.iter().position(|each| { return each == member; }).expect("member present");
assert_eq!(arguments[position - 1], "--package", "{arguments:?}");
}
}
#[test]
fn the_report_counts_the_entries_left_out() {
assert_eq!(left_out_words(0), "");
assert_eq!(left_out_words(1), "; 1 unreadable entry was left out");
assert_eq!(left_out_words(3), "; 3 unreadable entries were left out");
}
#[test]
fn a_panicking_worker_is_a_failure_naming_its_item() {
let items: Vec<usize> = (0..6).collect();
let results: Vec<Result<usize, String>> = map_concurrent(items.as_slice(), 2, &|item: &usize| { return format!("item {item}"); }, &|item: &usize| {
assert_ne!(*item, 3, "this item breaks");
return *item * 10;
});
assert_eq!(results.len(), items.len(), "{results:?}");
for (item, outcome) in items.iter().zip(&results) {
if *item == 3 {
let reason: &String = outcome.as_ref().expect_err("the broken item fails");
assert!(reason.contains("item 3") && reason.contains("this item breaks"), "{reason}");
} else {
assert_eq!(outcome.as_ref().ok(), Some(&(*item * 10)), "item {item}");
}
}
}
#[test]
fn every_item_has_its_result_in_item_order() {
let items: Vec<usize> = (0..9).collect();
for workers in [0, 1, 4, 64] {
let results: Vec<Result<usize, String>> = map_concurrent(items.as_slice(), workers, &|item: &usize| { return format!("item {item}"); }, &|item: &usize| { return *item + 1; });
assert_eq!(results, items.iter().map(|item: &usize| { return Ok(*item + 1); }).collect::<Vec<Result<usize, String>>>());
}
}