mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-23 20:54:50 +03:00
Merge pull request #3344 from gitbutlerapp/separate-integration-tests
separate integration tests for 'changeset' crate
This commit is contained in:
commit
b9c07265c2
@ -2,6 +2,10 @@
|
|||||||
name = "gitbutler-changeset"
|
name = "gitbutler-changeset"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
@ -101,137 +101,3 @@ pub trait FormatHunk: RawHunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> FormatHunk for T where T: RawHunk {}
|
impl<T> FormatHunk for T where T: RawHunk {}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
struct TestHunk {
|
|
||||||
removal_start: usize,
|
|
||||||
addition_start: usize,
|
|
||||||
changes: Vec<super::Change>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::RawHunk for TestHunk {
|
|
||||||
type ChangeIterator = std::vec::IntoIter<super::Change>;
|
|
||||||
|
|
||||||
fn get_removal_start(&self) -> usize {
|
|
||||||
self.removal_start
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_addition_start(&self) -> usize {
|
|
||||||
self.addition_start
|
|
||||||
}
|
|
||||||
|
|
||||||
fn changes(&self) -> Self::ChangeIterator {
|
|
||||||
self.changes.clone().into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for TestHunk {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.fmt_unified(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_hunk() {
|
|
||||||
let hunk = TestHunk {
|
|
||||||
removal_start: 1,
|
|
||||||
addition_start: 1,
|
|
||||||
changes: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(format!("{hunk}"), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_removal() {
|
|
||||||
let hunk = TestHunk {
|
|
||||||
removal_start: 30,
|
|
||||||
addition_start: 38,
|
|
||||||
changes: vec![super::Change::Removal("Hello, world!".to_string())],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
format!("{hunk}"),
|
|
||||||
"@@ -30 +38,0 @@\n-Hello, world!\n\\ No newline at end of file\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_removal_trailing_nl() {
|
|
||||||
let hunk = TestHunk {
|
|
||||||
removal_start: 30,
|
|
||||||
addition_start: 38,
|
|
||||||
changes: vec![super::Change::Removal("Hello, world!\n".to_string())],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(format!("{hunk}"), "@@ -30 +38,0 @@\n-Hello, world!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_addition() {
|
|
||||||
let hunk = TestHunk {
|
|
||||||
removal_start: 30,
|
|
||||||
addition_start: 38,
|
|
||||||
changes: vec![super::Change::Addition("Hello, world!".to_string())],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
format!("{hunk}"),
|
|
||||||
"@@ -30,0 +38 @@\n+Hello, world!\n\\ No newline at end of file\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_addition_trailing_nl() {
|
|
||||||
let hunk = TestHunk {
|
|
||||||
removal_start: 30,
|
|
||||||
addition_start: 38,
|
|
||||||
changes: vec![super::Change::Addition("Hello, world!\n".to_string())],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(format!("{hunk}"), "@@ -30,0 +38 @@\n+Hello, world!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_modified_line() {
|
|
||||||
let hunk = TestHunk {
|
|
||||||
removal_start: 30,
|
|
||||||
addition_start: 38,
|
|
||||||
changes: vec![
|
|
||||||
super::Change::Removal("Hello, world!".to_string()),
|
|
||||||
super::Change::Addition("Hello, GitButler!\n".to_string()),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
format!("{hunk}"),
|
|
||||||
"@@ -30 +38 @@\n-Hello, world!\n\\ No newline at end of file\n+Hello, GitButler!\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn preserve_change_order() {
|
|
||||||
let hunk = TestHunk {
|
|
||||||
removal_start: 30,
|
|
||||||
addition_start: 20,
|
|
||||||
changes: vec![
|
|
||||||
super::Change::Addition("Hello, GitButler!\n".to_string()),
|
|
||||||
super::Change::Removal("Hello, world!\n".to_string()),
|
|
||||||
super::Change::Removal("Hello, world 2!\n".to_string()),
|
|
||||||
super::Change::Addition("Hello, GitButler 2!\n".to_string()),
|
|
||||||
super::Change::Removal("Hello, world 3!".to_string()),
|
|
||||||
super::Change::Addition("Hello, GitButler 3!\n".to_string()),
|
|
||||||
super::Change::Addition("Hello, GitButler 4!".to_string()),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
format!("{hunk}"),
|
|
||||||
"@@ -30,3 +20,4 @@\n+Hello, GitButler!\n-Hello, world!\n-Hello, world 2!\n+Hello, GitButler 2!\n-Hello, world 3!\n\\ No newline at end of file\n+Hello, GitButler 3!\n+Hello, GitButler 4!\n\\ No newline at end of file\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -175,131 +175,3 @@ impl<S: AsRef<str>> From<S> for Signature {
|
|||||||
fn bigrams(s: &[u8]) -> impl Iterator<Item = (u8, u8)> + '_ {
|
fn bigrams(s: &[u8]) -> impl Iterator<Item = (u8, u8)> + '_ {
|
||||||
s.iter().copied().zip(s.iter().skip(1).copied())
|
s.iter().copied().zip(s.iter().skip(1).copied())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
macro_rules! assert_score {
|
|
||||||
($sig:ident, $s:expr, $e:expr) => {
|
|
||||||
let score = $sig.score_str($s);
|
|
||||||
if (score - $e).abs() >= 0.1 {
|
|
||||||
panic!(
|
|
||||||
"expected score of {} for string {:?}, got {}",
|
|
||||||
$e, $s, score
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn score_signature() {
|
|
||||||
let sig = Signature::from("hello world");
|
|
||||||
|
|
||||||
// NOTE: The scores here are not exact, but are close enough
|
|
||||||
// to be useful for testing purposes, hence why some have the same
|
|
||||||
// "score" but different strings.
|
|
||||||
assert_score!(sig, "hello world", 1.0);
|
|
||||||
assert_score!(sig, "hello world!", 0.95);
|
|
||||||
assert_score!(sig, "hello world!!", 0.9);
|
|
||||||
assert_score!(sig, "hello world!!!", 0.85);
|
|
||||||
assert_score!(sig, "hello world!!!!", 0.8);
|
|
||||||
assert_score!(sig, "hello world!!!!!", 0.75);
|
|
||||||
assert_score!(sig, "hello world!!!!!!", 0.7);
|
|
||||||
assert_score!(sig, "hello world!!!!!!!", 0.65);
|
|
||||||
assert_score!(sig, "hello world!!!!!!!!", 0.62);
|
|
||||||
assert_score!(sig, "hello world!!!!!!!!!", 0.6);
|
|
||||||
assert_score!(sig, "hello world!!!!!!!!!!", 0.55);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn score_ignores_whitespace() {
|
|
||||||
let sig = Signature::from("hello world");
|
|
||||||
|
|
||||||
assert_score!(sig, "hello world", 1.0);
|
|
||||||
assert_score!(sig, "hello world ", 1.0);
|
|
||||||
assert_score!(sig, "hello\nworld ", 1.0);
|
|
||||||
assert_score!(sig, "hello\n\tworld ", 1.0);
|
|
||||||
assert_score!(sig, "\t\t hel lo\n\two rld \t\t", 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const TEXT1: &str = include_str!("../fixture/text1.txt");
|
|
||||||
const TEXT2: &str = include_str!("../fixture/text2.txt");
|
|
||||||
const TEXT3: &str = include_str!("../fixture/text3.txt");
|
|
||||||
const CODE1: &str = include_str!("../fixture/code1.txt");
|
|
||||||
const CODE2: &str = include_str!("../fixture/code2.txt");
|
|
||||||
const CODE3: &str = include_str!("../fixture/code3.txt");
|
|
||||||
const CODE4: &str = include_str!("../fixture/code4.txt");
|
|
||||||
const LARGE1: &str = include_str!("../fixture/large1.txt");
|
|
||||||
const LARGE2: &str = include_str!("../fixture/large2.txt");
|
|
||||||
|
|
||||||
macro_rules! real_test {
|
|
||||||
($a: ident, $b: ident, are_similar) => {
|
|
||||||
paste::paste! {
|
|
||||||
#[test]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn [<test_ $a _ $b _are_similar>]() {
|
|
||||||
let a = Signature::from($a);
|
|
||||||
let b = Signature::from($b);
|
|
||||||
assert!(a.score_str($b) >= 0.95);
|
|
||||||
assert!(b.score_str($a) >= 0.95);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($a: ident, $b: ident, are_not_similar) => {
|
|
||||||
paste::paste! {
|
|
||||||
#[test]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn [<test_ $a _ $b _are_not_similar>]() {
|
|
||||||
let a = Signature::from($a);
|
|
||||||
let b = Signature::from($b);
|
|
||||||
assert!(a.score_str($b) < 0.95);
|
|
||||||
assert!(b.score_str($a) < 0.95);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only similar pairs:
|
|
||||||
// - TEXT1, TEXT2
|
|
||||||
// - CODE1, CODE2
|
|
||||||
// - LARGE1, LARGE2
|
|
||||||
real_test!(TEXT1, TEXT2, are_similar);
|
|
||||||
real_test!(CODE1, CODE2, are_similar);
|
|
||||||
real_test!(LARGE1, LARGE2, are_similar);
|
|
||||||
|
|
||||||
// Check all other combos
|
|
||||||
real_test!(TEXT1, TEXT3, are_not_similar);
|
|
||||||
real_test!(TEXT1, CODE1, are_not_similar);
|
|
||||||
real_test!(TEXT1, CODE2, are_not_similar);
|
|
||||||
real_test!(TEXT1, CODE3, are_not_similar);
|
|
||||||
real_test!(TEXT1, CODE4, are_not_similar);
|
|
||||||
real_test!(TEXT1, LARGE1, are_not_similar);
|
|
||||||
real_test!(TEXT1, LARGE2, are_not_similar);
|
|
||||||
real_test!(TEXT2, TEXT3, are_not_similar);
|
|
||||||
real_test!(TEXT2, CODE1, are_not_similar);
|
|
||||||
real_test!(TEXT2, CODE2, are_not_similar);
|
|
||||||
real_test!(TEXT2, CODE3, are_not_similar);
|
|
||||||
real_test!(TEXT2, CODE4, are_not_similar);
|
|
||||||
real_test!(TEXT2, LARGE1, are_not_similar);
|
|
||||||
real_test!(TEXT2, LARGE2, are_not_similar);
|
|
||||||
real_test!(TEXT3, CODE1, are_not_similar);
|
|
||||||
real_test!(TEXT3, CODE2, are_not_similar);
|
|
||||||
real_test!(TEXT3, CODE3, are_not_similar);
|
|
||||||
real_test!(TEXT3, CODE4, are_not_similar);
|
|
||||||
real_test!(TEXT3, LARGE1, are_not_similar);
|
|
||||||
real_test!(TEXT3, LARGE2, are_not_similar);
|
|
||||||
real_test!(CODE1, CODE3, are_not_similar);
|
|
||||||
real_test!(CODE1, CODE4, are_not_similar);
|
|
||||||
real_test!(CODE1, LARGE1, are_not_similar);
|
|
||||||
real_test!(CODE1, LARGE2, are_not_similar);
|
|
||||||
real_test!(CODE2, CODE3, are_not_similar);
|
|
||||||
real_test!(CODE2, CODE4, are_not_similar);
|
|
||||||
real_test!(CODE2, LARGE1, are_not_similar);
|
|
||||||
real_test!(CODE2, LARGE2, are_not_similar);
|
|
||||||
real_test!(CODE3, CODE4, are_not_similar);
|
|
||||||
real_test!(CODE3, LARGE1, are_not_similar);
|
|
||||||
real_test!(CODE3, LARGE2, are_not_similar);
|
|
||||||
real_test!(CODE4, LARGE1, are_not_similar);
|
|
||||||
real_test!(CODE4, LARGE2, are_not_similar);
|
|
||||||
}
|
|
||||||
|
@ -105,101 +105,3 @@ impl LineSpan {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn span_new() {
|
|
||||||
for s in 0..20 {
|
|
||||||
for e in s + 1..=20 {
|
|
||||||
let span = LineSpan::new(s, e);
|
|
||||||
assert_eq!(span.start(), s);
|
|
||||||
assert_eq!(span.end(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn span_extract() {
|
|
||||||
let lines = [
|
|
||||||
"Hello, world!",
|
|
||||||
"This is a test.",
|
|
||||||
"This is another test.\r",
|
|
||||||
"This is a third test.\r",
|
|
||||||
"This is a fourth test.",
|
|
||||||
"This is a fifth test.\r",
|
|
||||||
"This is a sixth test.",
|
|
||||||
"This is a seventh test.\r",
|
|
||||||
"This is an eighth test.",
|
|
||||||
"This is a ninth test.\r",
|
|
||||||
"This is a tenth test.", // note no newline at end
|
|
||||||
];
|
|
||||||
|
|
||||||
let full_text = lines.join("\n");
|
|
||||||
|
|
||||||
// calculate the known character offsets of each line
|
|
||||||
let mut offsets = vec![];
|
|
||||||
let mut start = 0;
|
|
||||||
for (i, line) in lines.iter().enumerate() {
|
|
||||||
// If it's not the last line, add 1 for the newline character.
|
|
||||||
let end = start + line.len() + (i != (lines.len() - 1)) as usize;
|
|
||||||
offsets.push((start, end));
|
|
||||||
start = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test single-line extraction
|
|
||||||
for i in 0..lines.len() - 1 {
|
|
||||||
let span = LineSpan::new(i, i + 1);
|
|
||||||
let expected = &full_text[offsets[i].0..offsets[i].1];
|
|
||||||
let (extracted, start_offset, end_offset) = span.extract(&full_text).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(extracted, expected);
|
|
||||||
assert_eq!((start_offset, end_offset), (offsets[i].0, offsets[i].1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test multi-line extraction
|
|
||||||
for i in 0..lines.len() {
|
|
||||||
for j in i..=lines.len() {
|
|
||||||
let span = LineSpan::new(i, j);
|
|
||||||
|
|
||||||
assert!(span.line_count() == (j - i));
|
|
||||||
|
|
||||||
if i == j {
|
|
||||||
assert!(span.is_empty());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let expected_start = offsets[i].0;
|
|
||||||
let expected_end = offsets[j - 1].1;
|
|
||||||
let expected_text = &full_text[expected_start..expected_end];
|
|
||||||
|
|
||||||
let (extracted, start_offset, end_offset) = span.extract(&full_text).unwrap();
|
|
||||||
assert_eq!(extracted, expected_text);
|
|
||||||
assert_eq!((start_offset, end_offset), (expected_start, expected_end));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn span_intersects() {
|
|
||||||
let span = LineSpan::new(5, 11); // Exclusive end
|
|
||||||
|
|
||||||
assert!(span.intersects(&LineSpan::new(10, 11))); // Intersect at start
|
|
||||||
assert!(span.intersects(&LineSpan::new(0, 11))); // Fully contained
|
|
||||||
assert!(span.intersects(&LineSpan::new(10, 15))); // Partial overlap
|
|
||||||
assert!(span.intersects(&LineSpan::new(4, 6))); // Intersect at end
|
|
||||||
assert!(span.intersects(&LineSpan::new(5, 6))); // Exact match start
|
|
||||||
assert!(span.intersects(&LineSpan::new(0, 6))); // Overlap at end
|
|
||||||
assert!(span.intersects(&LineSpan::new(0, 8))); // Overlap middle
|
|
||||||
assert!(span.intersects(&LineSpan::new(0, 10))); // Overlap up to end
|
|
||||||
assert!(span.intersects(&LineSpan::new(9, 10))); // Overlap at single point
|
|
||||||
assert!(span.intersects(&LineSpan::new(7, 9))); // Overlap inside
|
|
||||||
|
|
||||||
// Test cases where there should be no intersection due to exclusive end
|
|
||||||
assert!(!span.intersects(&LineSpan::new(0, 5))); // Before start
|
|
||||||
assert!(!span.intersects(&LineSpan::new(11, 20))); // After end
|
|
||||||
assert!(!span.intersects(&LineSpan::new(11, 12))); // Just after end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
3
crates/gitbutler-changeset/tests/changeset.rs
Normal file
3
crates/gitbutler-changeset/tests/changeset.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mod diff;
|
||||||
|
mod signature;
|
||||||
|
mod span;
|
133
crates/gitbutler-changeset/tests/diff/mod.rs
Normal file
133
crates/gitbutler-changeset/tests/diff/mod.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
mod hunk {
|
||||||
|
use gitbutler_changeset::{Change, FormatHunk, RawHunk};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct TestHunk {
|
||||||
|
removal_start: usize,
|
||||||
|
addition_start: usize,
|
||||||
|
changes: Vec<Change>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawHunk for TestHunk {
|
||||||
|
type ChangeIterator = std::vec::IntoIter<Change>;
|
||||||
|
|
||||||
|
fn get_removal_start(&self) -> usize {
|
||||||
|
self.removal_start
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_addition_start(&self) -> usize {
|
||||||
|
self.addition_start
|
||||||
|
}
|
||||||
|
|
||||||
|
fn changes(&self) -> Self::ChangeIterator {
|
||||||
|
self.changes.clone().into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TestHunk {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.fmt_unified(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_hunk() {
|
||||||
|
let hunk = TestHunk {
|
||||||
|
removal_start: 1,
|
||||||
|
addition_start: 1,
|
||||||
|
changes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(format!("{hunk}"), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_removal() {
|
||||||
|
let hunk = TestHunk {
|
||||||
|
removal_start: 30,
|
||||||
|
addition_start: 38,
|
||||||
|
changes: vec![Change::Removal("Hello, world!".to_string())],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format!("{hunk}"),
|
||||||
|
"@@ -30 +38,0 @@\n-Hello, world!\n\\ No newline at end of file\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_removal_trailing_nl() {
|
||||||
|
let hunk = TestHunk {
|
||||||
|
removal_start: 30,
|
||||||
|
addition_start: 38,
|
||||||
|
changes: vec![Change::Removal("Hello, world!\n".to_string())],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(format!("{hunk}"), "@@ -30 +38,0 @@\n-Hello, world!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_addition() {
|
||||||
|
let hunk = TestHunk {
|
||||||
|
removal_start: 30,
|
||||||
|
addition_start: 38,
|
||||||
|
changes: vec![Change::Addition("Hello, world!".to_string())],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format!("{hunk}"),
|
||||||
|
"@@ -30,0 +38 @@\n+Hello, world!\n\\ No newline at end of file\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_addition_trailing_nl() {
|
||||||
|
let hunk = TestHunk {
|
||||||
|
removal_start: 30,
|
||||||
|
addition_start: 38,
|
||||||
|
changes: vec![Change::Addition("Hello, world!\n".to_string())],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(format!("{hunk}"), "@@ -30,0 +38 @@\n+Hello, world!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_modified_line() {
|
||||||
|
let hunk = TestHunk {
|
||||||
|
removal_start: 30,
|
||||||
|
addition_start: 38,
|
||||||
|
changes: vec![
|
||||||
|
Change::Removal("Hello, world!".to_string()),
|
||||||
|
Change::Addition("Hello, GitButler!\n".to_string()),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format!("{hunk}"),
|
||||||
|
"@@ -30 +38 @@\n-Hello, world!\n\\ No newline at end of file\n+Hello, GitButler!\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preserve_change_order() {
|
||||||
|
let hunk = TestHunk {
|
||||||
|
removal_start: 30,
|
||||||
|
addition_start: 20,
|
||||||
|
changes: vec![
|
||||||
|
Change::Addition("Hello, GitButler!\n".to_string()),
|
||||||
|
Change::Removal("Hello, world!\n".to_string()),
|
||||||
|
Change::Removal("Hello, world 2!\n".to_string()),
|
||||||
|
Change::Addition("Hello, GitButler 2!\n".to_string()),
|
||||||
|
Change::Removal("Hello, world 3!".to_string()),
|
||||||
|
Change::Addition("Hello, GitButler 3!\n".to_string()),
|
||||||
|
Change::Addition("Hello, GitButler 4!".to_string()),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format!("{hunk}"),
|
||||||
|
"@@ -30,3 +20,4 @@\n+Hello, GitButler!\n-Hello, world!\n-Hello, world 2!\n+Hello, GitButler 2!\n-Hello, world 3!\n\\ No newline at end of file\n+Hello, GitButler 3!\n+Hello, GitButler 4!\n\\ No newline at end of file\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
124
crates/gitbutler-changeset/tests/signature/mod.rs
Normal file
124
crates/gitbutler-changeset/tests/signature/mod.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
use gitbutler_changeset::Signature;
|
||||||
|
|
||||||
|
macro_rules! assert_score {
|
||||||
|
($sig:ident, $s:expr, $e:expr) => {
|
||||||
|
let score = $sig.score_str($s);
|
||||||
|
if (score - $e).abs() >= 0.1 {
|
||||||
|
panic!(
|
||||||
|
"expected score of {} for string {:?}, got {}",
|
||||||
|
$e, $s, score
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn score_signature() {
|
||||||
|
let sig = Signature::from("hello world");
|
||||||
|
|
||||||
|
// NOTE: The scores here are not exact, but are close enough
|
||||||
|
// to be useful for testing purposes, hence why some have the same
|
||||||
|
// "score" but different strings.
|
||||||
|
assert_score!(sig, "hello world", 1.0);
|
||||||
|
assert_score!(sig, "hello world!", 0.95);
|
||||||
|
assert_score!(sig, "hello world!!", 0.9);
|
||||||
|
assert_score!(sig, "hello world!!!", 0.85);
|
||||||
|
assert_score!(sig, "hello world!!!!", 0.8);
|
||||||
|
assert_score!(sig, "hello world!!!!!", 0.75);
|
||||||
|
assert_score!(sig, "hello world!!!!!!", 0.7);
|
||||||
|
assert_score!(sig, "hello world!!!!!!!", 0.65);
|
||||||
|
assert_score!(sig, "hello world!!!!!!!!", 0.62);
|
||||||
|
assert_score!(sig, "hello world!!!!!!!!!", 0.6);
|
||||||
|
assert_score!(sig, "hello world!!!!!!!!!!", 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn score_ignores_whitespace() {
|
||||||
|
let sig = Signature::from("hello world");
|
||||||
|
|
||||||
|
assert_score!(sig, "hello world", 1.0);
|
||||||
|
assert_score!(sig, "hello world ", 1.0);
|
||||||
|
assert_score!(sig, "hello\nworld ", 1.0);
|
||||||
|
assert_score!(sig, "hello\n\tworld ", 1.0);
|
||||||
|
assert_score!(sig, "\t\t hel lo\n\two rld \t\t", 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEXT1: &str = include_str!("../fixtures/text1.txt");
|
||||||
|
const TEXT2: &str = include_str!("../fixtures/text2.txt");
|
||||||
|
const TEXT3: &str = include_str!("../fixtures/text3.txt");
|
||||||
|
const CODE1: &str = include_str!("../fixtures/code1.txt");
|
||||||
|
const CODE2: &str = include_str!("../fixtures/code2.txt");
|
||||||
|
const CODE3: &str = include_str!("../fixtures/code3.txt");
|
||||||
|
const CODE4: &str = include_str!("../fixtures/code4.txt");
|
||||||
|
const LARGE1: &str = include_str!("../fixtures/large1.txt");
|
||||||
|
const LARGE2: &str = include_str!("../fixtures/large2.txt");
|
||||||
|
|
||||||
|
macro_rules! real_test {
|
||||||
|
($a: ident, $b: ident, are_similar) => {
|
||||||
|
paste::paste! {
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn [<$a _ $b _are_similar>]() {
|
||||||
|
let a = Signature::from($a);
|
||||||
|
let b = Signature::from($b);
|
||||||
|
assert!(a.score_str($b) >= 0.95);
|
||||||
|
assert!(b.score_str($a) >= 0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($a: ident, $b: ident, are_not_similar) => {
|
||||||
|
paste::paste! {
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn [<$a _ $b _are_not_similar>]() {
|
||||||
|
let a = Signature::from($a);
|
||||||
|
let b = Signature::from($b);
|
||||||
|
assert!(a.score_str($b) < 0.95);
|
||||||
|
assert!(b.score_str($a) < 0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only similar pairs:
|
||||||
|
// - TEXT1, TEXT2
|
||||||
|
// - CODE1, CODE2
|
||||||
|
// - LARGE1, LARGE2
|
||||||
|
real_test!(TEXT1, TEXT2, are_similar);
|
||||||
|
real_test!(CODE1, CODE2, are_similar);
|
||||||
|
real_test!(LARGE1, LARGE2, are_similar);
|
||||||
|
|
||||||
|
// Check all other combos
|
||||||
|
real_test!(TEXT1, TEXT3, are_not_similar);
|
||||||
|
real_test!(TEXT1, CODE1, are_not_similar);
|
||||||
|
real_test!(TEXT1, CODE2, are_not_similar);
|
||||||
|
real_test!(TEXT1, CODE3, are_not_similar);
|
||||||
|
real_test!(TEXT1, CODE4, are_not_similar);
|
||||||
|
real_test!(TEXT1, LARGE1, are_not_similar);
|
||||||
|
real_test!(TEXT1, LARGE2, are_not_similar);
|
||||||
|
real_test!(TEXT2, TEXT3, are_not_similar);
|
||||||
|
real_test!(TEXT2, CODE1, are_not_similar);
|
||||||
|
real_test!(TEXT2, CODE2, are_not_similar);
|
||||||
|
real_test!(TEXT2, CODE3, are_not_similar);
|
||||||
|
real_test!(TEXT2, CODE4, are_not_similar);
|
||||||
|
real_test!(TEXT2, LARGE1, are_not_similar);
|
||||||
|
real_test!(TEXT2, LARGE2, are_not_similar);
|
||||||
|
real_test!(TEXT3, CODE1, are_not_similar);
|
||||||
|
real_test!(TEXT3, CODE2, are_not_similar);
|
||||||
|
real_test!(TEXT3, CODE3, are_not_similar);
|
||||||
|
real_test!(TEXT3, CODE4, are_not_similar);
|
||||||
|
real_test!(TEXT3, LARGE1, are_not_similar);
|
||||||
|
real_test!(TEXT3, LARGE2, are_not_similar);
|
||||||
|
real_test!(CODE1, CODE3, are_not_similar);
|
||||||
|
real_test!(CODE1, CODE4, are_not_similar);
|
||||||
|
real_test!(CODE1, LARGE1, are_not_similar);
|
||||||
|
real_test!(CODE1, LARGE2, are_not_similar);
|
||||||
|
real_test!(CODE2, CODE3, are_not_similar);
|
||||||
|
real_test!(CODE2, CODE4, are_not_similar);
|
||||||
|
real_test!(CODE2, LARGE1, are_not_similar);
|
||||||
|
real_test!(CODE2, LARGE2, are_not_similar);
|
||||||
|
real_test!(CODE3, CODE4, are_not_similar);
|
||||||
|
real_test!(CODE3, LARGE1, are_not_similar);
|
||||||
|
real_test!(CODE3, LARGE2, are_not_similar);
|
||||||
|
real_test!(CODE4, LARGE1, are_not_similar);
|
||||||
|
real_test!(CODE4, LARGE2, are_not_similar);
|
94
crates/gitbutler-changeset/tests/span/mod.rs
Normal file
94
crates/gitbutler-changeset/tests/span/mod.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
use gitbutler_changeset::LineSpan;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new() {
|
||||||
|
for s in 0..20 {
|
||||||
|
for e in s + 1..=20 {
|
||||||
|
let span = LineSpan::new(s, e);
|
||||||
|
assert_eq!(span.start(), s);
|
||||||
|
assert_eq!(span.end(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract() {
|
||||||
|
let lines = [
|
||||||
|
"Hello, world!",
|
||||||
|
"This is a test.",
|
||||||
|
"This is another test.\r",
|
||||||
|
"This is a third test.\r",
|
||||||
|
"This is a fourth test.",
|
||||||
|
"This is a fifth test.\r",
|
||||||
|
"This is a sixth test.",
|
||||||
|
"This is a seventh test.\r",
|
||||||
|
"This is an eighth test.",
|
||||||
|
"This is a ninth test.\r",
|
||||||
|
"This is a tenth test.", // note no newline at end
|
||||||
|
];
|
||||||
|
|
||||||
|
let full_text = lines.join("\n");
|
||||||
|
|
||||||
|
// calculate the known character offsets of each line
|
||||||
|
let mut offsets = vec![];
|
||||||
|
let mut start = 0;
|
||||||
|
for (i, line) in lines.iter().enumerate() {
|
||||||
|
// If it's not the last line, add 1 for the newline character.
|
||||||
|
let end = start + line.len() + (i != (lines.len() - 1)) as usize;
|
||||||
|
offsets.push((start, end));
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test single-line extraction
|
||||||
|
for i in 0..lines.len() - 1 {
|
||||||
|
let span = LineSpan::new(i, i + 1);
|
||||||
|
let expected = &full_text[offsets[i].0..offsets[i].1];
|
||||||
|
let (extracted, start_offset, end_offset) = span.extract(&full_text).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(extracted, expected);
|
||||||
|
assert_eq!((start_offset, end_offset), (offsets[i].0, offsets[i].1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test multi-line extraction
|
||||||
|
for i in 0..lines.len() {
|
||||||
|
for j in i..=lines.len() {
|
||||||
|
let span = LineSpan::new(i, j);
|
||||||
|
|
||||||
|
assert!(span.line_count() == (j - i));
|
||||||
|
|
||||||
|
if i == j {
|
||||||
|
assert!(span.is_empty());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_start = offsets[i].0;
|
||||||
|
let expected_end = offsets[j - 1].1;
|
||||||
|
let expected_text = &full_text[expected_start..expected_end];
|
||||||
|
|
||||||
|
let (extracted, start_offset, end_offset) = span.extract(&full_text).unwrap();
|
||||||
|
assert_eq!(extracted, expected_text);
|
||||||
|
assert_eq!((start_offset, end_offset), (expected_start, expected_end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn intersects() {
|
||||||
|
let span = LineSpan::new(5, 11); // Exclusive end
|
||||||
|
|
||||||
|
assert!(span.intersects(&LineSpan::new(10, 11))); // Intersect at start
|
||||||
|
assert!(span.intersects(&LineSpan::new(0, 11))); // Fully contained
|
||||||
|
assert!(span.intersects(&LineSpan::new(10, 15))); // Partial overlap
|
||||||
|
assert!(span.intersects(&LineSpan::new(4, 6))); // Intersect at end
|
||||||
|
assert!(span.intersects(&LineSpan::new(5, 6))); // Exact match start
|
||||||
|
assert!(span.intersects(&LineSpan::new(0, 6))); // Overlap at end
|
||||||
|
assert!(span.intersects(&LineSpan::new(0, 8))); // Overlap middle
|
||||||
|
assert!(span.intersects(&LineSpan::new(0, 10))); // Overlap up to end
|
||||||
|
assert!(span.intersects(&LineSpan::new(9, 10))); // Overlap at single point
|
||||||
|
assert!(span.intersects(&LineSpan::new(7, 9))); // Overlap inside
|
||||||
|
|
||||||
|
// Test cases where there should be no intersection due to exclusive end
|
||||||
|
assert!(!span.intersects(&LineSpan::new(0, 5))); // Before start
|
||||||
|
assert!(!span.intersects(&LineSpan::new(11, 20))); // After end
|
||||||
|
assert!(!span.intersects(&LineSpan::new(11, 12))); // Just after end
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user