use super::*;
use crate::test_support::{fixture, remove};
use std::collections::BTreeMap;
use std::path::PathBuf;
const APPENDERS: usize = 6;
const ENTRIES_PER_APPENDER: usize = 5;
const LINES_PER_ENTRY: usize = 200;
fn sha(tag: &str) -> String {
return format!("{tag:0<40}");
}
fn entry(tag: &str) -> String {
return format!("- Date:\n D\n Subject:\n {} `s`\n\n", token(sha(tag).as_str()));
}
fn absent_log(root: &Path) -> PathBuf {
return root.join("cli-git").join("standards-review-shadow.md");
}
#[test]
fn an_entry_goes_after_the_existing_content() {
let root: PathBuf = fixture("append-after");
let log: PathBuf = root.join("log.md");
std::fs::write(log.as_path(), "# Log\nlast line without newline").expect("log");
assert_eq!(deliver(log.as_path(), sha("aaa").as_str(), entry("aaa").as_str()), Ok(Delivery::Appended));
let text: String = std::fs::read_to_string(log.as_path()).expect("log");
assert_eq!(text, format!("# Log\nlast line without newline{}", entry("aaa")));
remove(root.as_path());
}
#[test]
fn a_missing_log_and_its_directory_are_created() {
let root: PathBuf = fixture("append-created");
let log: PathBuf = absent_log(root.as_path());
assert!(!log.parent().expect("parent").exists());
assert_eq!(deliver(log.as_path(), sha("aaa").as_str(), entry("aaa").as_str()), Ok(Delivery::Appended));
assert_eq!(deliver(log.as_path(), sha("bbb").as_str(), entry("bbb").as_str()), Ok(Delivery::Appended));
assert_eq!(std::fs::read_to_string(log.as_path()).expect("log"), format!("{}{}", entry("aaa"), entry("bbb")));
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn a_created_log_is_private() {
use std::os::unix::fs::PermissionsExt;
let root: PathBuf = fixture("append-private");
let log: PathBuf = absent_log(root.as_path());
deliver(log.as_path(), sha("aaa").as_str(), entry("aaa").as_str()).expect("delivery");
let mode: u32 = std::fs::metadata(log.as_path()).expect("log metadata").permissions().mode();
let others: u32 = 0o077;
assert_eq!(mode & others, 0, "{mode:o}");
remove(root.as_path());
}
#[test]
fn a_commit_is_added_once_even_after_a_reflow() {
let root: PathBuf = fixture("append-once");
let log: PathBuf = absent_log(root.as_path());
deliver(log.as_path(), sha("aaa").as_str(), entry("aaa").as_str()).expect("first");
let reflowed: String = std::fs::read_to_string(log.as_path()).expect("log").replace(" Subject:\n ", " Subject: ");
std::fs::write(log.as_path(), reflowed.as_str()).expect("reflow");
assert_eq!(deliver(log.as_path(), sha("aaa").as_str(), entry("aaa").as_str()), Ok(Delivery::AlreadyThere));
assert_eq!(std::fs::read_to_string(log.as_path()).expect("log"), reflowed);
remove(root.as_path());
}
#[test]
fn an_unreadable_log_is_reported_and_left_alone() {
let root: PathBuf = fixture("append-unreadable");
let log: PathBuf = root.join("log.md");
std::fs::create_dir(log.as_path()).expect("directory in place of the log");
let result: Result<Delivery, String> = deliver(log.as_path(), sha("aaa").as_str(), entry("aaa").as_str());
assert!(matches!(&result, Err(message) if message.contains("reading")), "{result:?}");
assert!(log.is_dir());
remove(root.as_path());
}
fn concurrent_entry(thread: usize, index: usize) -> (String, String) {
let tag: String = format!("{thread:06}{index:06}");
let lines: String = (0..LINES_PER_ENTRY).map(|line| return format!(" {tag} line {line}\n")).collect::<String>();
return (tag.clone(), format!("- Entry {tag}:\n{lines}\n"));
}
#[test]
fn concurrent_appends_land_whole() {
let root: PathBuf = fixture("append-concurrent");
let log: PathBuf = absent_log(root.as_path());
let start: std::sync::Barrier = std::sync::Barrier::new(APPENDERS);
std::thread::scope(|scope| {
for thread in 0..APPENDERS {
let (shared, barrier) = (log.as_path(), &start);
scope.spawn(move || {
barrier.wait();
for index in 0..ENTRIES_PER_APPENDER {
let (tag, text) = concurrent_entry(thread, index);
let delivered: Result<Delivery, String> = deliver(shared, tag.as_str(), text.as_str());
assert_eq!(delivered, Ok(Delivery::Appended));
}
});
}
});
let written: String = std::fs::read_to_string(log.as_path()).expect("log");
let mut expected: BTreeMap<String, String> = BTreeMap::new();
for thread in 0..APPENDERS {
for index in 0..ENTRIES_PER_APPENDER {
let (tag, text) = concurrent_entry(thread, index);
expected.insert(tag, text);
}
}
let mut found: BTreeMap<String, String> = BTreeMap::new();
for chunk in written.split_inclusive("\n\n") {
let tag: String = chunk.lines().next().unwrap_or_default().trim_start_matches("- Entry ").trim_end_matches(':').to_string();
assert!(found.insert(tag.clone(), chunk.to_string()).is_none(), "entry {tag} appears twice");
}
assert_eq!(found.len(), expected.len());
assert_eq!(found, expected);
assert_eq!(written.len(), expected.values().map(String::len).sum::<usize>(), "no byte outside an expected entry");
remove(root.as_path());
}