use super::*;
use git2::Oid;
fn oid(byte: u8) -> Oid {
let mut bytes = [0u8; 20];
bytes[0] = byte;
Oid::from_bytes(&bytes).unwrap()
}
#[test]
fn unstaged_is_always_zz() {
let alloc = IdAllocator::new(vec![Entity::Unstaged]);
assert_eq!(alloc.get_unstaged(), "zz");
}
#[test]
fn branch_word_initials() {
let alloc = IdAllocator::new(vec![
Entity::Branch("feature-a".to_string()),
Entity::Branch("bugfix-123".to_string()),
]);
assert_eq!(alloc.get_branch("feature-a"), "fa");
assert_eq!(alloc.get_branch("bugfix-123"), "b1");
}
#[test]
fn single_word_branch_uses_first_two_chars() {
let alloc = IdAllocator::new(vec![
Entity::Branch("alpha".to_string()),
Entity::Branch("beta".to_string()),
]);
assert_eq!(alloc.get_branch("alpha"), "al");
assert_eq!(alloc.get_branch("beta"), "be");
}
#[test]
fn commit_uses_first_two_hex_chars() {
let alloc = IdAllocator::new(vec![Entity::commit(oid(0xAB))]);
let id = alloc.get_commit(oid(0xAB));
assert_eq!(id.len(), 2);
assert_eq!(id, "ab");
}
#[test]
fn file_uses_stem_not_extension() {
let alloc = IdAllocator::new(vec![
Entity::File("src/main.rs".to_string()),
Entity::File("tests/integration.rs".to_string()),
]);
assert_eq!(alloc.get_file("src/main.rs"), "ma");
assert_eq!(alloc.get_file("tests/integration.rs"), "in");
}
#[test]
fn common_prefix_resolved_at_two_chars() {
let alloc = IdAllocator::new(vec![
Entity::Branch("feature-a".to_string()),
Entity::Branch("feature-b".to_string()),
]);
assert_eq!(alloc.get_branch("feature-a"), "fa");
assert_eq!(alloc.get_branch("feature-b"), "fb");
}
#[test]
fn same_first_word_different_second_word_stays_two_chars() {
let alloc = IdAllocator::new(vec![
Entity::Branch("feature-alpha".to_string()),
Entity::Branch("feature-awesome".to_string()),
]);
let id_alpha = alloc.get_branch("feature-alpha");
let id_awesome = alloc.get_branch("feature-awesome");
assert_eq!(id_alpha, "fa");
assert_eq!(
id_awesome.len(),
2,
"expected 2-char ID, got '{}'",
id_awesome
);
assert_ne!(id_alpha, id_awesome);
}
#[test]
fn triple_collision_all_two_chars() {
let alloc = IdAllocator::new(vec![
Entity::Branch("main".to_string()),
Entity::Branch("master".to_string()),
Entity::Branch("maintenance".to_string()),
]);
let id_main = alloc.get_branch("main");
let id_master = alloc.get_branch("master");
let id_maint = alloc.get_branch("maintenance");
assert_ne!(id_main, id_master);
assert_ne!(id_main, id_maint);
assert_ne!(id_master, id_maint);
assert_eq!(id_main.len(), 2, "main: '{}'", id_main);
assert_eq!(id_master.len(), 2, "master: '{}'", id_master);
assert_eq!(id_maint.len(), 2, "maintenance: '{}'", id_maint);
}
#[test]
fn cross_entity_collision_resolved() {
let alloc = IdAllocator::new(vec![
Entity::Branch("main".to_string()),
Entity::File("src/main.rs".to_string()),
]);
let branch_id = alloc.get_branch("main");
let file_id = alloc.get_file("src/main.rs");
assert_ne!(branch_id, file_id);
assert_eq!(branch_id.len(), 2);
assert_eq!(file_id.len(), 2);
}
#[test]
fn no_collision_stays_two_chars() {
let alloc = IdAllocator::new(vec![
Entity::Branch("alpha".to_string()),
Entity::Branch("beta".to_string()),
Entity::File("src/config.rs".to_string()),
Entity::commit(oid(0xFF)),
]);
assert_eq!(alloc.get_branch("alpha"), "al");
assert_eq!(alloc.get_branch("beta"), "be");
assert_eq!(alloc.get_file("src/config.rs"), "co");
assert_eq!(alloc.get_commit(oid(0xFF)).len(), 2);
}
#[test]
fn exhausted_source_uses_numeric_suffix() {
let alloc = IdAllocator::new(vec![
Entity::Branch("ab".to_string()),
Entity::File("ab".to_string()),
]);
let id_branch = alloc.get_branch("ab");
let id_file = alloc.get_file("ab");
assert_ne!(id_branch, id_file);
}
#[test]
fn short_source_does_not_hang() {
let alloc = IdAllocator::new(vec![
Entity::Branch("a".to_string()),
Entity::File("a".to_string()),
]);
let id_branch = alloc.get_branch("a");
let id_file = alloc.get_file("a");
assert_ne!(id_branch, id_file);
assert!(id_branch.len() >= 2, "branch 'a': got '{}'", id_branch);
assert!(id_file.len() >= 2, "file 'a': got '{}'", id_file);
}
#[test]
fn single_char_file_gets_two_char_id() {
let alloc = IdAllocator::new(vec![Entity::File("a".to_string())]);
let id = alloc.get_file("a");
assert_eq!(id, "aa");
}
#[test]
fn commits_allocated_before_branches() {
let commit_oid = Oid::from_str("eaec409000000000000000000000000000000000").unwrap();
let alloc = IdAllocator::new(vec![
Entity::Branch("feat2".to_string()),
Entity::commit(commit_oid),
]);
assert_eq!(alloc.get_commit(commit_oid), "ea");
let branch_id = alloc.get_branch("feat2");
assert_ne!(branch_id, "ea");
assert_eq!(branch_id.len(), 2, "feat2: got '{}'", branch_id);
}
#[test]
fn slash_separator_works() {
let alloc = IdAllocator::new(vec![
Entity::Branch("feature/login".to_string()),
Entity::Branch("feature/logout".to_string()),
]);
let id_login = alloc.get_branch("feature/login");
let id_logout = alloc.get_branch("feature/logout");
assert_ne!(id_login, id_logout);
assert_eq!(id_login.len(), 2);
assert_eq!(id_logout.len(), 2);
}
#[test]
fn underscore_separator_works() {
let alloc = IdAllocator::new(vec![
Entity::Branch("my_feature".to_string()),
Entity::Branch("my_fix".to_string()),
]);
let id_feat = alloc.get_branch("my_feature");
let id_fix = alloc.get_branch("my_fix");
assert_ne!(id_feat, id_fix);
assert_eq!(id_feat.len(), 2);
assert_eq!(id_fix.len(), 2);
}
#[test]
fn file_with_underscores_uses_word_initials() {
let alloc = IdAllocator::new(vec![Entity::File("new_file.txt".to_string())]);
assert_eq!(alloc.get_file("new_file.txt"), "nf");
}
#[test]
fn collision_shifts_to_second_letter_multi_word() {
let alloc = IdAllocator::new(vec![
Entity::Branch("feature-a".to_string()),
Entity::Branch("feature-b".to_string()),
]);
assert_eq!(alloc.get_branch("feature-a"), "fa");
assert_eq!(alloc.get_branch("feature-b"), "fb");
}
#[test]
fn collision_shifts_forward_single_word() {
let alloc = IdAllocator::new(vec![
Entity::Branch("main".to_string()),
Entity::Branch("mainstream".to_string()),
]);
assert_eq!(alloc.get_branch("main"), "ma");
assert_eq!(alloc.get_branch("mainstream"), "mi");
}
#[test]
fn zz_is_never_generated_for_other_entities() {
let alloc = IdAllocator::new(vec![
Entity::Branch("zoo-zulu".to_string()),
Entity::File("zz.rs".to_string()),
]);
assert_ne!(alloc.get_branch("zoo-zulu"), "zz");
assert_eq!(alloc.get_file("zz.rs"), "zz1");
}
const ID_A: &str = "I3ac7000000000000000000000000000000000000";
const ID_B: &str = "I3ac8000000000000000000000000000000000000";
const ID_C: &str = "Ib710000000000000000000000000000000000000";
fn persistent(byte: u8, change_id: &str) -> Entity {
Entity::Commit {
oid: oid(byte),
change_id: Some(change_id.to_string()),
}
}
#[test]
fn commit_with_change_id_gets_three_letters() {
let alloc = IdAllocator::new(vec![persistent(1, ID_A), persistent(2, ID_C)]);
assert_eq!(alloc.get_commit(oid(1)), "wpn");
assert_eq!(alloc.get_commit(oid(2)), "osy");
}
#[test]
fn a_colliding_newcomer_lengthens_both_ids() {
let before = IdAllocator::new(vec![persistent(1, ID_A)]);
assert_eq!(before.get_commit(oid(1)), "wpn");
let after = IdAllocator::new(vec![persistent(2, ID_B), persistent(1, ID_A)]);
assert_eq!(after.get_commit(oid(1)), "wpns");
assert_eq!(after.get_commit(oid(2)), "wpnr");
}
#[test]
fn persistent_ids_do_not_depend_on_commit_order() {
let one = IdAllocator::new(vec![persistent(1, ID_A), persistent(2, ID_B)]);
let two = IdAllocator::new(vec![persistent(2, ID_B), persistent(1, ID_A)]);
assert_eq!(one.get_commit(oid(1)), two.get_commit(oid(1)));
assert_eq!(one.get_commit(oid(2)), two.get_commit(oid(2)));
}
#[test]
fn twins_sharing_a_change_id_fall_back_to_hash_ids() {
let alloc = IdAllocator::new(vec![persistent(0x11, ID_A), persistent(0x22, ID_A)]);
assert_eq!(alloc.get_commit(oid(0x11)), "11");
assert_eq!(alloc.get_commit(oid(0x22)), "22");
}
#[test]
fn letter_and_hex_commits_coexist_with_a_branch() {
let alloc = IdAllocator::new(vec![
Entity::commit(oid(0xAB)),
persistent(1, ID_A),
Entity::Branch("wp-n".to_string()),
]);
assert_eq!(alloc.get_commit(oid(0xAB)), "ab");
assert_eq!(alloc.get_commit(oid(1)), "wpn");
assert_eq!(alloc.get_branch("wp-n"), "wn");
}
#[test]
fn find_persistent_by_prefix_or_literal() {
let alloc = IdAllocator::new(vec![
persistent(2, ID_B),
persistent(1, ID_A),
Entity::commit(oid(0xAB)),
]);
assert_eq!(alloc.find_persistent("wpns"), vec![oid(1)]);
assert_eq!(alloc.find_persistent("wpnszzz"), vec![oid(1)]);
assert_eq!(alloc.find_persistent(ID_B), vec![oid(2)]);
assert_eq!(alloc.find_persistent(&ID_B.to_uppercase()), vec![oid(2)]);
assert_eq!(sorted(alloc.find_persistent("wpn")), vec![oid(1), oid(2)]);
assert!(
alloc.find_persistent("wp").is_empty(),
"shorter than MIN_LEN"
);
assert!(
alloc.find_persistent("ab").is_empty(),
"hex ids are not letters"
);
assert!(alloc.find_persistent("osy").is_empty());
}
#[test]
fn find_persistent_returns_both_twins() {
let alloc = IdAllocator::new(vec![persistent(0x11, ID_A), persistent(0x22, ID_A)]);
assert_eq!(
sorted(alloc.find_persistent(ID_A)),
vec![oid(0x11), oid(0x22)]
);
assert_eq!(
sorted(alloc.find_persistent("wpn")),
vec![oid(0x11), oid(0x22)]
);
}
fn sorted(mut oids: Vec<git2::Oid>) -> Vec<git2::Oid> {
oids.sort();
oids
}