use super::*;
fn assert_merge(
merge_options: Option<&MergeOptions>,
original: &str,
ours: &str,
theirs: &str,
expected: Result<&str, &str>,
msg: &str,
) {
let opts = match merge_options {
None => &Default::default(),
Some(opts) => opts,
};
let solution = opts.merge(original, ours, theirs);
assert!(
same_merge(expected, &solution),
"{msg}\nexpected={expected:#?}\nactual={solution:#?}"
);
let expected_bytes = expected.map(str::as_bytes).map_err(str::as_bytes);
let solution_bytes = opts.merge_bytes(original.as_bytes(), ours.as_bytes(), theirs.as_bytes());
assert!(
same_merge_bytes(expected_bytes, &solution_bytes),
"{msg}\nexpected={expected_bytes:#?}\nactual={solution_bytes:#?}"
);
}
fn same_merge(expected: Result<&str, &str>, actual: &Result<String, String>) -> bool {
match (expected, actual) {
(Ok(expected), Ok(actual)) => expected == actual,
(Err(expected), Err(actual)) => expected == actual,
(_, _) => false,
}
}
fn same_merge_bytes(expected: Result<&[u8], &[u8]>, actual: &Result<Vec<u8>, Vec<u8>>) -> bool {
match (expected, actual) {
(Ok(expected), Ok(actual)) => expected == &actual[..],
(Err(expected), Err(actual)) => expected == &actual[..],
(_, _) => false,
}
}
#[test]
fn test_merge() {
let original = "\
carrots
garlic
onions
salmon
mushrooms
tomatoes
salt
";
let a = "\
carrots
salmon
mushrooms
tomatoes
garlic
onions
salt
";
let b = "\
carrots
salmon
garlic
onions
mushrooms
tomatoes
salt
";
#[rustfmt::skip]
assert_merge( None, original, original, original, Ok(original), "Equal case #1",);
assert_merge(None, original, a, a, Ok(a), "Equal case #2");
assert_merge(None, original, b, b, Ok(b), "Equal case #3");
let expected = "\
carrots
<<<<<<< ours
salmon
||||||| original
garlic
onions
salmon
=======
salmon
garlic
onions
>>>>>>> theirs
mushrooms
tomatoes
garlic
onions
salt
";
assert_merge(None, original, a, b, Err(expected), "Single Conflict case");
let expected = "\
carrots
<<<<<<< ours
salmon
garlic
onions
||||||| original
garlic
onions
salmon
=======
salmon
>>>>>>> theirs
mushrooms
tomatoes
garlic
onions
salt
";
assert_merge(
None,
original,
b,
a,
Err(expected),
"Reverse Single Conflict case",
);
}
#[test]
fn test_merge_multiple_conflicts() {
let original = "\
carrots
garlic
onions
salmon
tomatoes
salt
";
let a = "\
carrots
salmon
tomatoes
garlic
onions
salt
";
let b = "\
carrots
salmon
garlic
onions
tomatoes
salt
";
let expected_myers = "\
carrots
<<<<<<< ours
salmon
||||||| original
garlic
onions
salmon
=======
salmon
garlic
onions
>>>>>>> theirs
tomatoes
garlic
onions
salt
";
let opts_myers = MergeOptions {
algorithm: Algorithm::Myers,
..Default::default()
};
assert_merge(
Some(&opts_myers),
original,
a,
b,
Err(expected_myers),
"Multiple Conflict case",
);
let expected_histogram = expected_myers;
assert_merge(
None,
original,
a,
b,
Err(expected_histogram),
"Multiple Conflict case",
);
let expected_myers = "\
carrots
<<<<<<< ours
salmon
garlic
onions
||||||| original
garlic
onions
salmon
=======
salmon
>>>>>>> theirs
tomatoes
garlic
onions
salt
";
assert_merge(
Some(&opts_myers),
original,
b,
a,
Err(expected_myers),
"Reverse Multiple Conflict case",
);
let expected_histogram = expected_myers;
assert_merge(
None,
original,
b,
a,
Err(expected_histogram),
"Reverse Multiple Conflict case",
);
}
#[test]
fn diffy_vs_git() {
let original = "\
void Chunk_copy(Chunk *src, size_t src_start, Chunk *dst, size_t dst_start, size_t n)
{
if (!Chunk_bounds_check(src, src_start, n)) return;
if (!Chunk_bounds_check(dst, dst_start, n)) return;
memcpy(dst->data + dst_start, src->data + src_start, n);
}
int Chunk_bounds_check(Chunk *chunk, size_t start, size_t n)
{
if (chunk == NULL) return 0;
return start <= chunk->length && n <= chunk->length - start;
}
";
let a = "\
int Chunk_bounds_check(Chunk *chunk, size_t start, size_t n)
{
if (chunk == NULL) return 0;
return start <= chunk->length && n <= chunk->length - start;
}
void Chunk_copy(Chunk *src, size_t src_start, Chunk *dst, size_t dst_start, size_t n)
{
if (!Chunk_bounds_check(src, src_start, n)) return;
if (!Chunk_bounds_check(dst, dst_start, n)) return;
memcpy(dst->data + dst_start, src->data + src_start, n);
}
";
let b = "\
void Chunk_copy(Chunk *src, size_t src_start, Chunk *dst, size_t dst_start, size_t n)
{
if (!Chunk_bounds_check(src, src_start, n)) return;
if (!Chunk_bounds_check(dst, dst_start, n)) return;
// copy the bytes
memcpy(dst->data + dst_start, src->data + src_start, n);
}
int Chunk_bounds_check(Chunk *chunk, size_t start, size_t n)
{
if (chunk == NULL) return 0;
return start <= chunk->length && n <= chunk->length - start;
}
";
let expected_myers = "\
int Chunk_bounds_check(Chunk *chunk, size_t start, size_t n)
{
if (chunk == NULL) return 0;
<<<<<<< ours
return start <= chunk->length && n <= chunk->length - start;
||||||| original
memcpy(dst->data + dst_start, src->data + src_start, n);
=======
// copy the bytes
memcpy(dst->data + dst_start, src->data + src_start, n);
>>>>>>> theirs
}
void Chunk_copy(Chunk *src, size_t src_start, Chunk *dst, size_t dst_start, size_t n)
{
if (!Chunk_bounds_check(src, src_start, n)) return;
if (!Chunk_bounds_check(dst, dst_start, n)) return;
memcpy(dst->data + dst_start, src->data + src_start, n);
}
";
let opts_myers = MergeOptions {
algorithm: Algorithm::Myers,
..Default::default()
};
assert_merge(
Some(&opts_myers),
original,
a,
b,
Err(expected_myers),
"Myers diffy merge",
);
let expected_histogram = "\
int Chunk_bounds_check(Chunk *chunk, size_t start, size_t n)
{
if (chunk == NULL) return 0;
return start <= chunk->length && n <= chunk->length - start;
}
void Chunk_copy(Chunk *src, size_t src_start, Chunk *dst, size_t dst_start, size_t n)
{
if (!Chunk_bounds_check(src, src_start, n)) return;
if (!Chunk_bounds_check(dst, dst_start, n)) return;
// copy the bytes
memcpy(dst->data + dst_start, src->data + src_start, n);
}
";
assert_merge(
None,
original,
a,
b,
Ok(expected_histogram),
"Histogram diffy merge",
);
}
#[test]
fn correct_range_is_used_for_both_case() {
let base = r#"
class GithubCall(db.Model):
`url`: URL of request Example.`https://api.github.com`
"#;
let theirs = r#"
class GithubCall(db.Model):
`repo`: String field. Github repository fields. Example: `amitu/python`
"#;
let ours = r#"
class Call(models.Model):
`body`: String field. The payload of the webhook call from the github.
`repo`: String field. Github repository fields. Example: `amitu/python`
"#;
let expected = r#"
class Call(models.Model):
`body`: String field. The payload of the webhook call from the github.
`repo`: String field. Github repository fields. Example: `amitu/python`
"#;
assert_merge(
None,
base,
ours,
theirs,
Ok(expected),
"MergeRange::Both case",
);
}
#[test]
fn delete_and_insert_conflict() {
let base = r#"
{
int a = 2;
}
"#;
let ours = r#"
{
}
"#;
let theirs = r#"
{
int a = 2;
int b = 3;
}
"#;
let expected = r#"
{
<<<<<<< ours
||||||| original
int a = 2;
=======
int a = 2;
int b = 3;
>>>>>>> theirs
}
"#;
assert_merge(
None,
base,
ours,
theirs,
Err(expected),
"MergeRange (Ours::delete, Theirs::insert) conflict",
);
let expected = r#"
{
<<<<<<< ours
int a = 2;
int b = 3;
||||||| original
int a = 2;
=======
>>>>>>> theirs
}
"#;
assert_merge(
None,
base,
theirs,
ours,
Err(expected),
"MergeRange (Theirs::delete, Ours::insert) conflict",
);
}
#[test]
fn one_line() {
let base = "[1, 2]";
let ours = "[1]";
let theirs = "[2]";
let expected = "\
<<<<<<< ours
[1]
||||||| original
[1, 2]
=======
[2]
>>>>>>> theirs";
assert_merge(None, base, ours, theirs, Err(expected), "One-line");
let expected = "\
<<<<<<< ours
[2]
||||||| original
[1, 2]
=======
[1]
>>>>>>> theirs";
assert_merge(None, base, theirs, ours, Err(expected), "One-line reverse");
}
#[test]
fn no_newline_at_the_end() {
let base_wo = "\
object Foo:
def bar(input: String) = input";
let base_w = "\
object Foo:
def bar(input: String) = input
";
let ours_wo = "\
object Foo:
def bar(newname: String) = newname";
let ours_w = "\
object Foo:
def bar(newname: String) = newname
";
let theirs_wo = "\
object Foo:
def baz(input: String) = input";
let theirs_w = "\
object Foo:
def baz(input: String) = input
";
let expected_wo_wo_wo = "\
object Foo:
<<<<<<< ours
def bar(newname: String) = newname
||||||| original
def bar(input: String) = input
=======
def baz(input: String) = input
>>>>>>> theirs";
assert_merge(
None,
base_wo,
ours_wo,
theirs_wo,
Err(expected_wo_wo_wo),
"without/without/without",
);
let expected_wo_w_wo = "\
object Foo:
<<<<<<< ours
def bar(newname: String) = newname
||||||| original
def bar(input: String) = input
=======
def baz(input: String) = input
>>>>>>> theirs";
assert_merge(
None,
base_wo,
ours_w,
theirs_wo,
Err(expected_wo_w_wo),
"without/with/without",
);
let expected_wo_w_w = "\
object Foo:
<<<<<<< ours
def bar(newname: String) = newname
||||||| original
def bar(input: String) = input
=======
def baz(input: String) = input
>>>>>>> theirs";
assert_merge(
None,
base_wo,
ours_w,
theirs_w,
Err(expected_wo_w_w),
"without/with/with",
);
let expected_w_wo_wo = "\
object Foo:
<<<<<<< ours
def bar(newname: String) = newname
||||||| original
def bar(input: String) = input
=======
def baz(input: String) = input
>>>>>>> theirs";
assert_merge(
None,
base_w,
ours_wo,
theirs_wo,
Err(expected_w_wo_wo),
"with/without/without",
);
let expected_w_w_wo = "\
object Foo:
<<<<<<< ours
def bar(newname: String) = newname
||||||| original
def bar(input: String) = input
=======
def baz(input: String) = input
>>>>>>> theirs";
assert_merge(
None,
base_w,
ours_w,
theirs_wo,
Err(expected_w_w_wo),
"with/with/without",
);
let expected_w_w_w = "\
object Foo:
<<<<<<< ours
def bar(newname: String) = newname
||||||| original
def bar(input: String) = input
=======
def baz(input: String) = input
>>>>>>> theirs
";
assert_merge(
None,
base_w,
ours_w,
theirs_w,
Err(expected_w_w_w),
"with/with/with",
);
}