tests/trailer_test.rs
Ref: Size: 4.6 KiB History
//! One set of tests for the one git-trailer parser.
//!
//! `Issue:` (commit linking) and `Patch:` (merge recording) share
//! `trailer::parse_trailers`; every case below is run against both tokens so a
//! change that fixes one and breaks the other cannot pass.
use git_collab::trailer::parse_trailers;
/// Run one parser case against both tokens that use it. `message` is written
/// with `{}` where the token goes, and `expected` is the list of values the
/// parser must return.
fn both_tokens(message_template: &str, expected: &[&str]) {
for token in ["issue", "patch"] {
// Capitalized in the message, lowercase as the token argument: the
// match is case-insensitive.
let capitalized = format!("{}{}", token[..1].to_uppercase(), &token[1..]);
let message = message_template.replace("{}", &capitalized);
let got = parse_trailers(&message, token);
assert_eq!(
got,
expected.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
"token {} on message {:?}",
token,
message
);
}
}
#[test]
fn no_trailer_block() {
both_tokens("Just a plain commit", &[]);
}
#[test]
fn empty_message() {
both_tokens("", &[]);
}
#[test]
fn single_trailer_in_pure_block() {
both_tokens(
"Fix thing\n\nSome context in the body.\n\n{}: abc",
&["abc"],
);
}
#[test]
fn case_and_spacing_variants() {
for token in ["issue", "patch"] {
let upper = token.to_uppercase();
assert_eq!(
parse_trailers(&format!("subject\n\n{}: abc", token), token),
vec!["abc".to_string()]
);
assert_eq!(
parse_trailers(&format!("subject\n\n{} : abc", upper), token),
vec!["abc".to_string()]
);
assert_eq!(
parse_trailers(&format!("subject\n\n {}: abc ", upper), token),
vec!["abc".to_string()]
);
}
}
#[test]
fn two_trailers_in_pure_block() {
both_tokens("subject\n\n{}: abc\n{}: def", &["abc", "def"]);
}
#[test]
fn trailer_present_but_not_in_final_paragraph() {
// The final paragraph is the signed-off-by block. It is a valid trailer
// block, but it carries no matching key, so nothing is extracted.
both_tokens(
"subject\n\n{}: abc\n\nSigned-off-by: alice <a@example.com>",
&[],
);
}
#[test]
fn wrong_key() {
for token in ["issue", "patch"] {
// `Issues:` / `Patchs:` — trailer-shaped, but not our token.
let msg = format!("subject\n\n{}s: abc", token);
assert_eq!(parse_trailers(&msg, token), Vec::<String>::new());
}
}
#[test]
fn prose_mention() {
for token in ["issue", "patch"] {
let msg = format!("subject\n\nthis fixes {} abc in the body", token);
assert_eq!(parse_trailers(&msg, token), Vec::<String>::new());
}
}
#[test]
fn single_paragraph_whole_message_is_trailer_block() {
both_tokens("{}: abc", &["abc"]);
}
#[test]
fn mixed_final_paragraph_rejects_all() {
// A prose line in the final paragraph disqualifies the whole paragraph.
both_tokens("subject\n\nThanks to Bob for the catch.\n{}: a3f9", &[]);
}
#[test]
fn trailing_whitespace_paragraph_does_not_shadow_trailer_block() {
both_tokens("subject\n\n{}: abc\n\n \n", &["abc"]);
}
#[test]
fn pure_block_with_mixed_keys() {
both_tokens(
"subject\n\nSigned-off-by: alice <a@example.com>\n{}: abc",
&["abc"],
);
}
#[test]
fn rejects_value_with_interior_whitespace() {
// `Patch: abc merged by me` must parse to *nothing*, not silently to
// `abc`. Silently truncating would record a merge the author did not
// name; parsing to nothing makes the ignored commentary visible.
both_tokens("subject\n\n{}: abc merged by me", &[]);
both_tokens("subject\n\n{}: abc fixes thing", &[]);
}
#[test]
fn rejects_empty_value() {
both_tokens("subject\n\n{}: ", &[]);
}
#[test]
fn a_trailer_for_the_other_token_is_not_matched() {
// The two tokens genuinely select: an `Issue:` line is not a `Patch:` one.
let msg = "subject\n\nIssue: abc\nPatch: def";
assert_eq!(parse_trailers(msg, "issue"), vec!["abc".to_string()]);
assert_eq!(parse_trailers(msg, "patch"), vec!["def".to_string()]);
}
#[test]
fn squashed_message_keeps_only_the_final_trailer_block() {
// `git merge --squash` concatenates the source messages. Only the last
// commit's trailer block is the final paragraph — which is fine, because
// every commit of a patch carries the same id.
let msg = "\
Squashed commit of the following:
first commit
Patch: aaaa1111
second commit
Patch: aaaa1111";
assert_eq!(parse_trailers(msg, "patch"), vec!["aaaa1111".to_string()]);
}