blob: 04683fd50157fe93c0b4bf8f638a9add1aa1f729 [file] [log] [blame]
// Copyright 2018 The diff-match-patch Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "external_libs/editscript.h"
#include <cstring>
#include <iostream>
#include <set>
#include <sstream>
#include <vector>
#include "gtest/gtest.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
namespace diff {
namespace {
// Define some serialization functions for ease of reading any differences.
std::ostream &operator<<(std::ostream &out, Operation operation) {
switch (operation) {
case Operation::EQUALS:
return (out << "EQUALS");
case Operation::DELETE:
return (out << "DELETE");
case Operation::INSERT:
return (out << "INSERT");
}
return out;
}
std::ostream &operator<<(std::ostream &out, const Edit &edit) {
out << "{" << edit.operation << ",[" << edit.start << "," << edit.end << ")}";
return out;
}
std::ostream &operator<<(std::ostream &out, const Edits &edits) {
out << "Edits{";
std::string outer_delim = "";
for (auto &edit : edits) {
out << outer_delim << edit;
outer_delim = ",";
}
out << "};";
return out;
}
std::string ToString(const Edits &edits) {
std::ostringstream oss;
oss << edits;
return oss.str();
}
TEST(DiffTest, CheckEmptyIntVectorDiffResults) {
std::vector<int> tokens1, tokens2;
Edits actual = GetTokenDiffs(tokens1.cbegin(), tokens1.cend(),
tokens2.cbegin(), tokens2.cend());
Edits expect = decltype(actual){};
EXPECT_EQ(ToString(actual), ToString(expect));
}
TEST(DiffTest, CheckEmptyNoCommonSubsequence) {
const char tokens1[] = "@$&~|";
const char tokens2[] = "the quick brown fox jumped over the lazy dog";
Edits actual = GetTokenDiffs(tokens1, tokens1 + strlen(tokens1), tokens2,
tokens2 + strlen(tokens2));
auto expect =
Edits{{Operation::DELETE, 0, 5}, // = tokens1[ 0, 5) = all of tokens1
{Operation::INSERT, 0, 44}}; // = tokens2[ 0, 44) = all of tokens2
EXPECT_EQ(ToString(actual), ToString(expect));
// Find the longest common subsequence.
auto max_elem_iter = std::max_element(
actual.cbegin(), actual.cend(), [](const Edit &lhs, const Edit &rhs) {
return (lhs.operation == Operation::EQUALS &&
rhs.operation == Operation::EQUALS &&
(lhs.end - lhs.start) < (rhs.end - rhs.start));
});
ASSERT_NE(0, std::distance(max_elem_iter, actual.cend()));
}
TEST(DiffTest, CheckCharArrayDiffResultsAndLongestCommonSubsequence) {
const char tokens1[] = "the fox jumped over the dog.";
const char tokens2[] = "the quick brown fox jumped the lazy dog";
Edits actual = GetTokenDiffs(tokens1, tokens1 + strlen(tokens1), tokens2,
tokens2 + strlen(tokens2));
// EQUALS and DELETE offsets point into tokens1, INSERT into tokens2.
auto expect =
Edits{{Operation::EQUALS, 0, 4}, // = tokens1[ 0, 4) = "the "
{Operation::INSERT, 4, 16}, // = tokens2[ 4, 16)= "quick brown "
{Operation::EQUALS, 4, 15}, // = tokens1[ 4, 15) = "fox jumped "
{Operation::DELETE, 15, 20}, // = tokens1[15, 20) = "over "
{Operation::EQUALS, 20, 24}, // = tokens1[20, 24) = "the "
{Operation::INSERT, 31, 36}, // = tokens2[31, 36) = "lazy "
{Operation::EQUALS, 24, 27}, // = tokens1[24, 27) = "dog"
{Operation::DELETE, 27, 28}}; // = tokens1[27, 28) = "."
EXPECT_EQ(ToString(actual), ToString(expect));
// Find the longest common subsequence.
auto max_elem_iter = std::max_element(
actual.cbegin(), actual.cend(), [](const Edit &lhs, const Edit &rhs) {
return (lhs.operation == Operation::EQUALS &&
rhs.operation == Operation::EQUALS &&
(lhs.end - lhs.start) < (rhs.end - rhs.start));
});
ASSERT_NE(0, std::distance(max_elem_iter, actual.cend()));
ASSERT_EQ(Operation::EQUALS, max_elem_iter->operation);
EXPECT_EQ(4, max_elem_iter->start);
EXPECT_EQ(15, max_elem_iter->end);
const std::string longest_common_subsequence(&tokens1[max_elem_iter->start],
&tokens1[max_elem_iter->end]);
EXPECT_EQ("fox jumped ", longest_common_subsequence);
}
TEST(DiffTest, CheckNonConstStringVectorDiffResults) {
auto tokens1 = std::vector<std::string>{"the", "fox", "jumped", "over",
"the", "dog", "."};
auto tokens2 = std::vector<std::string>{"the", "quick", "brown", "fox",
"jumped", "the", "lazy", "dog"};
// Test use of non-const iterators
Edits actual = GetTokenDiffs(tokens1.begin(), tokens1.end(), tokens2.begin(),
tokens2.end());
// EQUALS and DELETE offsets point into tokens1, INSERT into tokens2.
Edits expect = decltype(actual){
{Operation::EQUALS, 0, 1}, // = tokens1[0, 1) = {"the"}
{Operation::INSERT, 1, 3}, // = tokens2[1, 3) = {"quick", "brown"}
{Operation::EQUALS, 1, 3}, // = tokens1[1, 3) = {"fox", "jumped"}
{Operation::DELETE, 3, 4}, // = tokens1[3, 4) = {"over"}
{Operation::EQUALS, 4, 5}, // = tokens1[4, 5) = {"the"}
{Operation::INSERT, 6, 7}, // = tokens2[6, 7) = {"lazy"}
{Operation::EQUALS, 5, 6}, // = tokens1[5, 6) = {"dog"}
{Operation::DELETE, 6, 7}}; // = tokens1[6, 7) = {"."}
EXPECT_EQ(ToString(actual), ToString(expect));
// Find the longest common subsequence.
auto max_elem_iter = std::max_element(
actual.cbegin(), actual.cend(), [](const Edit &lhs, const Edit &rhs) {
return (lhs.operation == Operation::EQUALS &&
rhs.operation == Operation::EQUALS &&
(lhs.end - lhs.start) < (rhs.end - rhs.start));
});
ASSERT_NE(0, std::distance(max_elem_iter, actual.cend()));
ASSERT_EQ(Operation::EQUALS, max_elem_iter->operation);
EXPECT_EQ(1, max_elem_iter->start);
EXPECT_EQ(3, max_elem_iter->end);
const std::string longest_common_subsequence = absl::StrJoin(
&tokens1[max_elem_iter->start], &tokens1[max_elem_iter->end], " ");
EXPECT_EQ("fox jumped", longest_common_subsequence);
}
TEST(DiffTest, CompleteDeletion) {
const std::vector<absl::string_view> tokens1{"the", "fox"};
const std::vector<absl::string_view> tokens2;
const Edits actual = GetTokenDiffs(tokens1.begin(), tokens1.end(),
tokens2.begin(), tokens2.end());
// EQUALS and DELETE offsets point into tokens1, INSERT into tokens2.
const Edits expect{
{Operation::DELETE, 0, 2}, // = tokens1[0, 2) = {"the", "fox"}
};
EXPECT_EQ(ToString(actual), ToString(expect));
}
TEST(DiffTest, CompleteInsertion) {
const std::vector<absl::string_view> tokens1;
const std::vector<absl::string_view> tokens2{"jumped", "over", "me"};
const Edits actual = GetTokenDiffs(tokens1.begin(), tokens1.end(),
tokens2.begin(), tokens2.end());
// EQUALS and DELETE offsets point into tokens1, INSERT into tokens2.
const Edits expect{
{Operation::INSERT, 0, 3}, // = tokens2[0, 3) = {"jumped", "over", "me"}
};
EXPECT_EQ(ToString(actual), ToString(expect));
}
TEST(DiffTest, ReplaceFromOneDifferentElement) {
const std::vector<absl::string_view> tokens1{"fox"};
const std::vector<absl::string_view> tokens2{"jumped", "over", "me"};
const Edits actual = GetTokenDiffs(tokens1.begin(), tokens1.end(),
tokens2.begin(), tokens2.end());
// EQUALS and DELETE offsets point into tokens1, INSERT into tokens2.
const Edits expect{
{Operation::DELETE, 0, 1}, // = tokens1[0, 1) = {"fox"}
{Operation::INSERT, 0, 3}, // = tokens2[0, 3) = {"jumped", "over", "me"}
};
EXPECT_EQ(ToString(actual), ToString(expect));
}
TEST(DiffTest, ReplaceToOneDifferentElement) {
const std::vector<absl::string_view> tokens1{"jumped", "over", "me"};
const std::vector<absl::string_view> tokens2{"fox"};
const Edits actual = GetTokenDiffs(tokens1.begin(), tokens1.end(),
tokens2.begin(), tokens2.end());
// EQUALS and DELETE offsets point into tokens1, INSERT into tokens2.
const Edits expect{
{Operation::DELETE, 0, 3}, // = tokens1[0, 3) = {"jumped", "over", "me"}
{Operation::INSERT, 0, 1}, // = tokens2[0, 1) = {"fox"}
};
EXPECT_EQ(ToString(actual), ToString(expect));
}
TEST(DiffTest, CompleteReplacement) {
const std::vector<absl::string_view> tokens1{"the", "fox"};
const std::vector<absl::string_view> tokens2{"jumped", "over", "me"};
const Edits actual = GetTokenDiffs(tokens1.begin(), tokens1.end(),
tokens2.begin(), tokens2.end());
// EQUALS and DELETE offsets point into tokens1, INSERT into tokens2.
const Edits expect{
{Operation::DELETE, 0, 2}, // = tokens1[0, 2) = {"the", "fox"}
{Operation::INSERT, 0, 3}, // = tokens2[0, 3) = {"jumped", "over", "me"}
};
EXPECT_EQ(ToString(actual), ToString(expect));
}
TEST(DiffTest, StrictSubsequence) {
const std::vector<absl::string_view> tokens1{"the", "fox", "jumped", "over",
"the", "dog", "."};
const std::vector<absl::string_view> tokens2{"fox", "jumped", "over"};
const Edits actual = GetTokenDiffs(tokens1.begin(), tokens1.end(),
tokens2.begin(), tokens2.end());
// EQUALS and DELETE offsets point into tokens1, INSERT into tokens2.
const Edits expect{
{Operation::DELETE, 0, 1}, // = tokens1[0, 1) = {"the"}
{Operation::EQUALS, 1, 4}, // = tokens1[1, 4) = {"fox", "jumped", "over"}
{Operation::DELETE, 4, 7}, // = tokens1[4, 7) = {"the", "dog", "."}
};
EXPECT_EQ(ToString(actual), ToString(expect));
}
TEST(DiffTest, StrictSupersequence) {
const std::vector<absl::string_view> tokens1{"fox", "jumped", "over"};
const std::vector<absl::string_view> tokens2{"the", "fox", "jumped", "over",
"the", "dog", "."};
const Edits actual = GetTokenDiffs(tokens1.begin(), tokens1.end(),
tokens2.begin(), tokens2.end());
// EQUALS and DELETE offsets point into tokens1, INSERT into tokens2.
const Edits expect{
{Operation::INSERT, 0, 1}, // = tokens2[0, 1) = {"the"}
{Operation::EQUALS, 0, 3}, // = tokens1[0, 3) = {"fox", "jumped", "over"}
{Operation::INSERT, 4, 7}, // = tokens2[4, 7) = {"the", "dog", "."}
};
EXPECT_EQ(ToString(actual), ToString(expect));
}
} // namespace
} // namespace diff