| // Copyright 2017-2020 The Verible 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 "common/strings/patch.h" | |
| #include <initializer_list> | |
| #include <sstream> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_join.h" | |
| #include "absl/strings/string_view.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| namespace verible { | |
| using ::testing::ElementsAre; | |
| using ::testing::ElementsAreArray; | |
| namespace internal { | |
| namespace { | |
| static std::vector<MarkedLine> MakeMarkedLines( | |
| const std::vector<absl::string_view>& lines) { | |
| std::vector<MarkedLine> result; | |
| for (const auto& line : lines) { | |
| result.emplace_back(line); | |
| } | |
| return result; | |
| } | |
| TEST(MarkedLineConstructionTest, BadLine) { | |
| EXPECT_DEATH(MarkedLine(""), "must start with a marker"); | |
| EXPECT_DEATH(MarkedLine("?"), "Unexpected marker '?'"); | |
| } | |
| TEST(MarkedLineEquality, EqualityTests) { | |
| EXPECT_NE(MarkedLine(" "), MarkedLine("+")); | |
| EXPECT_NE(MarkedLine(" "), MarkedLine("-")); | |
| EXPECT_EQ(MarkedLine(" "), MarkedLine(" ")); | |
| EXPECT_NE(MarkedLine(" "), MarkedLine(" ")); | |
| EXPECT_NE(MarkedLine(" 1"), MarkedLine(" 2")); | |
| } | |
| TEST(MarkedLineParseTest, InvalidInputs) { | |
| constexpr absl::string_view kTestCases[] = { | |
| "", "x", "x213", "abc", "diff", "====", | |
| }; | |
| for (const auto& test : kTestCases) { | |
| MarkedLine m; | |
| EXPECT_FALSE(m.Parse(test).ok()) << " input: \"" << test << '"'; | |
| } | |
| } | |
| struct MarkedLineTestCase { | |
| absl::string_view input; | |
| char expected_mark; | |
| absl::string_view expected_text; | |
| }; | |
| TEST(MarkedLineParseTest, ValidInputs) { | |
| constexpr MarkedLineTestCase kTestCases[] = { | |
| {" ", ' ', ""}, {" x", ' ', "x"}, {" x213", ' ', "x213"}, | |
| {" abc", ' ', " abc"}, {"-abc", '-', "abc"}, {"+abc", '+', "abc"}, | |
| {"- abc", '-', " abc"}, {"+ abc", '+', " abc"}, {"---", '-', "--"}, | |
| {"+++", '+', "++"}, {"-", '-', ""}, {"+", '+', ""}, | |
| }; | |
| for (const auto& test : kTestCases) { | |
| MarkedLine m; | |
| EXPECT_TRUE(m.Parse(test.input).ok()) << " input: \"" << test.input << '"'; | |
| EXPECT_EQ(m.Marker(), test.expected_mark); | |
| EXPECT_EQ(m.Text(), test.expected_text); | |
| } | |
| } | |
| TEST(MarkedLinePrintTest, Print) { | |
| constexpr absl::string_view kTestCases[] = { | |
| " ", "+", "-", " 1 2 3", "-xyz", "+\tabc", | |
| }; | |
| for (const auto& test : kTestCases) { | |
| MarkedLine m; | |
| const auto status = m.Parse(test); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| std::ostringstream stream; | |
| stream << m; | |
| EXPECT_EQ(stream.str(), test); | |
| } | |
| } | |
| TEST(HunkIndicesEqualityTest, Comparisons) { | |
| EXPECT_EQ((HunkIndices{1, 2}), (HunkIndices{1, 2})); | |
| EXPECT_NE((HunkIndices{1, 1}), (HunkIndices{1, 2})); | |
| EXPECT_NE((HunkIndices{1, 2}), (HunkIndices{1, 1})); | |
| EXPECT_NE((HunkIndices{1, 2}), (HunkIndices{2, 2})); | |
| EXPECT_NE((HunkIndices{3, 2}), (HunkIndices{1, 2})); | |
| } | |
| TEST(HunkIndicesParseTest, InvalidInputs) { | |
| constexpr absl::string_view kTestCases[] = { | |
| "", ",", "4,", ",5", "2,b", "x,2", "4,5,", "1,2,3", | |
| }; | |
| for (const auto& test : kTestCases) { | |
| HunkIndices h; | |
| EXPECT_FALSE(h.Parse(test).ok()) << " input: \"" << test << '"'; | |
| } | |
| } | |
| struct HunkIndicesTestCase { | |
| absl::string_view input; | |
| int expected_start; | |
| int expected_count; | |
| }; | |
| TEST(HunkIndicesParseAndPrintTest, ValidInputs) { | |
| constexpr HunkIndicesTestCase kTestCases[] = { | |
| {"1,1", 1, 1}, | |
| {"14,92", 14, 92}, | |
| }; | |
| for (const auto& test : kTestCases) { | |
| HunkIndices h; | |
| EXPECT_TRUE(h.Parse(test.input).ok()) << " input: \"" << test.input << '"'; | |
| EXPECT_EQ(h.start, test.expected_start); | |
| EXPECT_EQ(h.count, test.expected_count); | |
| // Use same data to test printing | |
| std::ostringstream stream; | |
| stream << h; | |
| EXPECT_EQ(stream.str(), test.input); | |
| } | |
| } | |
| TEST(HunkHeaderEqualityTest, Comparisons) { | |
| EXPECT_EQ((HunkHeader{{1, 2}, {3, 4}, ""}), (HunkHeader{{1, 2}, {3, 4}, ""})); | |
| EXPECT_NE((HunkHeader{{1, 2}, {3, 4}, ""}), (HunkHeader{{2, 2}, {3, 4}, ""})); | |
| EXPECT_NE((HunkHeader{{1, 2}, {3, 4}, ""}), (HunkHeader{{1, 1}, {3, 4}, ""})); | |
| EXPECT_NE((HunkHeader{{1, 2}, {3, 4}, ""}), (HunkHeader{{1, 2}, {1, 4}, ""})); | |
| EXPECT_NE((HunkHeader{{1, 2}, {3, 4}, ""}), (HunkHeader{{1, 2}, {3, 5}, ""})); | |
| EXPECT_NE((HunkHeader{{1, 2}, {3, 4}, ""}), | |
| (HunkHeader{{1, 2}, {3, 4}, " "})); | |
| } | |
| TEST(HunkHeaderParseTest, InvalidInputs) { | |
| // If any one character is deleted from this example, it becomes invalid. | |
| constexpr absl::string_view kValidText = "@@ -4,8 +5,6 @@"; | |
| for (size_t i = 0; i < kValidText.length(); ++i) { | |
| std::string deleted(kValidText); | |
| deleted.erase(i); | |
| HunkHeader h; | |
| EXPECT_FALSE(h.Parse(deleted).ok()) << " input: \"" << deleted << '"'; | |
| } | |
| for (size_t i = 0; i < kValidText.length(); ++i) { | |
| absl::string_view deleted(kValidText.substr(0, i)); | |
| HunkHeader h; | |
| EXPECT_FALSE(h.Parse(deleted).ok()) << " input: \"" << deleted << '"'; | |
| } | |
| for (size_t i = 1; i < kValidText.length(); ++i) { | |
| absl::string_view deleted(kValidText.substr(i)); | |
| HunkHeader h; | |
| EXPECT_FALSE(h.Parse(deleted).ok()) << " input: \"" << deleted << '"'; | |
| } | |
| } | |
| TEST(HunkHeaderParseTest, MalformedOldRange) { | |
| constexpr absl::string_view kInvalidText = "@@ 4,8 +5,6 @@"; | |
| HunkHeader h; | |
| const auto status = h.Parse(kInvalidText); | |
| EXPECT_FALSE(status.ok()) << " input: \"" << kInvalidText << '"'; | |
| EXPECT_TRUE(absl::StrContains(status.message(), | |
| "old-file range should start with '-'")) | |
| << " got: " << status.message(); | |
| } | |
| TEST(HunkHeaderParseTest, MalformedNewRange) { | |
| constexpr absl::string_view kInvalidText = "@@ -4,8 5,6 @@"; | |
| HunkHeader h; | |
| const auto status = h.Parse(kInvalidText); | |
| EXPECT_FALSE(status.ok()) << " input: \"" << kInvalidText << '"'; | |
| EXPECT_TRUE(absl::StrContains(status.message(), | |
| "new-file range should start with '+'")) | |
| << " got: " << status.message(); | |
| } | |
| TEST(HunkHeaderParseAndPrintTest, ValidInput) { | |
| constexpr absl::string_view kValidText = "@@ -14,8 +5,16 @@"; | |
| HunkHeader h; | |
| EXPECT_TRUE(h.Parse(kValidText).ok()) << " input: \"" << kValidText << '"'; | |
| EXPECT_EQ(h.old_range.start, 14); | |
| EXPECT_EQ(h.old_range.count, 8); | |
| EXPECT_EQ(h.new_range.start, 5); | |
| EXPECT_EQ(h.new_range.count, 16); | |
| EXPECT_EQ(h.context, ""); | |
| // Validate reversibility. | |
| std::ostringstream stream; | |
| stream << h; | |
| EXPECT_EQ(stream.str(), kValidText); | |
| } | |
| TEST(HunkHeaderParseAndPrintTest, ValidInputWithContext) { | |
| constexpr absl::string_view kValidText("@@ -4,28 +51,6 @@ void foo::bar() {"); | |
| HunkHeader h; | |
| EXPECT_TRUE(h.Parse(kValidText).ok()) << " input: \"" << kValidText << '"'; | |
| EXPECT_EQ(h.old_range.start, 4); | |
| EXPECT_EQ(h.old_range.count, 28); | |
| EXPECT_EQ(h.new_range.start, 51); | |
| EXPECT_EQ(h.new_range.count, 6); | |
| EXPECT_EQ(h.context, " void foo::bar() {"); | |
| // Validate reversibility. | |
| std::ostringstream stream; | |
| stream << h; | |
| EXPECT_EQ(stream.str(), kValidText); | |
| } | |
| TEST(SourceInfoParseTest, InvalidInputs) { | |
| constexpr absl::string_view kTestCases[] = { | |
| "", // path must be non-empty | |
| }; | |
| for (const auto& test : kTestCases) { | |
| SourceInfo info; | |
| EXPECT_FALSE(info.Parse(test).ok()) << " input: \"" << test << '"'; | |
| } | |
| } | |
| TEST(SourceInfoParseAndPrintTest, ValidInputsPathOnly) { | |
| constexpr absl::string_view kPaths[] = { | |
| "a.txt", | |
| "p/q/a.txt", | |
| "/p/q/a.txt", | |
| }; | |
| for (const auto& path : kPaths) { | |
| SourceInfo info; | |
| EXPECT_TRUE(info.Parse(path).ok()); | |
| EXPECT_EQ(info.path, path); | |
| EXPECT_TRUE(info.timestamp.empty()); | |
| // Validate reversibility. | |
| std::ostringstream stream; | |
| stream << info; | |
| EXPECT_EQ(stream.str(), path); | |
| } | |
| } | |
| TEST(SourceInfoParseAndPrintTest, ValidInputsWithTimestamps) { | |
| constexpr absl::string_view kPaths[] = { | |
| "a.txt", | |
| "p/q/a.txt", | |
| "/p/q/a.txt", | |
| }; | |
| constexpr absl::string_view kTimes[] = { | |
| "2020-02-02", | |
| "2020-02-02 20:22:02", | |
| "2020-02-02 20:22:02.000000", | |
| "2020-02-02 20:22:02.000000 -0700", | |
| }; | |
| for (const auto& path : kPaths) { | |
| for (const auto& time : kTimes) { | |
| SourceInfo info; | |
| const std::string text(absl::StrCat(path, "\t", time)); | |
| EXPECT_TRUE(info.Parse(text).ok()); | |
| EXPECT_EQ(info.path, path); | |
| EXPECT_EQ(info.timestamp, time); | |
| // Validate reversibility. | |
| std::ostringstream stream; | |
| stream << info; | |
| EXPECT_EQ(stream.str(), text); | |
| } | |
| } | |
| } | |
| TEST(HunkEqualityTest, Comparisons) { | |
| const std::vector<absl::string_view> lines{ | |
| " a", | |
| "-b", | |
| "+c", | |
| " d", | |
| }; | |
| EXPECT_EQ(Hunk(1, 1, lines.begin(), lines.end()), // | |
| Hunk(1, 1, lines.begin(), lines.end())); | |
| EXPECT_NE(Hunk(1, 1, lines.begin(), lines.end()), // | |
| Hunk(2, 1, lines.begin(), lines.end())); | |
| EXPECT_NE(Hunk(1, 1, lines.begin(), lines.end()), // | |
| Hunk(1, 3, lines.begin(), lines.end())); | |
| EXPECT_NE(Hunk(2, 4, lines.begin(), lines.end()), // | |
| Hunk(2, 4, lines.begin() + 1, lines.end())); | |
| EXPECT_NE(Hunk(2, 4, lines.begin(), lines.end()), // | |
| Hunk(2, 4, lines.begin(), lines.end() - 1)); | |
| } | |
| TEST(HunkParseTest, InvalidInputs) { | |
| const std::vector<absl::string_view> kTestCases[] = { | |
| // malformed headers: | |
| {"@@ -1,0 +2,0 @"}, | |
| {"@ -1,0 +2,0 @@"}, | |
| {"@@ -1,0+2,0 @@"}, | |
| // malformed MarkedLines: | |
| {"@@ -1,1 +2,1 @@", ""}, // missing marker character | |
| {"@@ -1,1 +2,1 @@", "missing leading marker character"}, | |
| // inconsistent line counts: | |
| {"@@ -1,0 +2,0 @@", "-unexpected"}, | |
| {"@@ -1,0 +2,0 @@", "+unexpected"}, | |
| {"@@ -1,0 +2,0 @@", " unexpected"}, | |
| { | |
| "@@ -1,1 +2,0 @@", | |
| // missing: "-..." | |
| }, | |
| { | |
| "@@ -1,0 +2,1 @@", | |
| // missing: "+..." | |
| }, | |
| { | |
| "@@ -1,1 +2,1 @@", | |
| // missing: " ..." | |
| }, | |
| }; | |
| for (const auto& lines : kTestCases) { | |
| Hunk hunk; | |
| const LineRange range(lines.begin(), lines.end()); | |
| EXPECT_FALSE(hunk.Parse(range).ok()); | |
| } | |
| } | |
| struct UpdateHeaderTestCase { | |
| absl::string_view fixed_header; | |
| std::vector<absl::string_view> payload; | |
| }; | |
| TEST(HunkUpdateHeaderTest, Various) { | |
| constexpr absl::string_view kNonsenseHeader = "@@ -222,999 +333,999 @@"; | |
| const UpdateHeaderTestCase kTestCases[] = { | |
| {"@@ -222,0 +333,0 @@", {/* empty lines */}}, | |
| {"@@ -222,1 +333,0 @@", | |
| { | |
| "-removed", | |
| }}, | |
| {"@@ -222,0 +333,1 @@", | |
| { | |
| "+added", | |
| }}, | |
| {"@@ -222,1 +333,1 @@", | |
| { | |
| " common", | |
| }}, | |
| {"@@ -222,4 +333,3 @@", | |
| {" common", "-removed", "-removed2", "+added", " common again"}}, | |
| }; | |
| for (const auto& test : kTestCases) { | |
| std::vector<absl::string_view> lines; | |
| lines.push_back(kNonsenseHeader); | |
| lines.insert(lines.end(), test.payload.begin(), test.payload.end()); | |
| const LineRange range(lines.begin(), lines.end()); | |
| Hunk hunk; | |
| EXPECT_FALSE(hunk.Parse(range).ok()); | |
| hunk.UpdateHeader(); | |
| const auto status = hunk.IsValid(); | |
| EXPECT_TRUE(status.ok()) << status.message(); | |
| std::ostringstream stream; | |
| stream << hunk.Header(); | |
| EXPECT_EQ(stream.str(), test.fixed_header); | |
| } | |
| } | |
| struct AddedLinesTestCase { | |
| std::vector<absl::string_view> hunk_text; | |
| LineNumberSet expected_added_lines; | |
| }; | |
| TEST(HunkAddedLinesTest, Various) { | |
| const AddedLinesTestCase kTestCases[] = { | |
| { | |
| { | |
| "@@ -7,1 +8,1 @@", | |
| " common line, not added", | |
| }, | |
| {}, | |
| }, | |
| { | |
| { | |
| "@@ -7,2 +8,1 @@", | |
| "-deleted line", | |
| " common line, not added", | |
| }, | |
| {}, | |
| }, | |
| { | |
| {"@@ -7,2 +8,1 @@", " common line, not added", "-deleted line"}, | |
| {}, | |
| }, | |
| { | |
| { | |
| "@@ -7,4 +8,2 @@", | |
| " common line, not added", | |
| "-deleted line", | |
| "-deleted line 2", | |
| " common line, not added", | |
| }, | |
| {}, | |
| }, | |
| { | |
| { | |
| "@@ -7,1 +8,2 @@", | |
| " common line, not added", | |
| "+added line", | |
| }, | |
| {{9, 10}}, | |
| }, | |
| { | |
| { | |
| "@@ -7,1 +8,2 @@", | |
| "+added line", | |
| " common line, not added", | |
| }, | |
| {{8, 9}}, | |
| }, | |
| { | |
| { | |
| "@@ -17,2 +28,4 @@", | |
| " common line, not added", | |
| "+added line", | |
| "+added line 2", | |
| " common line, not added", | |
| }, | |
| {{29, 31}}, | |
| }, | |
| { | |
| { | |
| "@@ -7,3 +4,3 @@", | |
| " common line, not added", | |
| "-deleted line", | |
| "+added line", | |
| " common line, not added", | |
| }, | |
| {{5, 6}}, | |
| }, | |
| { | |
| { | |
| "@@ -7,3 +4,3 @@", | |
| " common line, not added", | |
| "+added line", | |
| "-deleted line", | |
| " common line, not added", | |
| }, | |
| {{5, 6}}, | |
| }, | |
| { | |
| { | |
| "@@ -380,8 +401,12 @@", | |
| " common line, not added", | |
| "+added line", | |
| "+added line 2", | |
| " nothing interesting", | |
| " ", | |
| "-delete me", | |
| "+replacement", | |
| " ", | |
| " nothing interesting", | |
| " ", | |
| "+added line", | |
| "+added line 2", | |
| " common line, not added", | |
| }, | |
| {{402, 404}, {406, 407}, {410, 412}}, | |
| }, | |
| }; | |
| for (const auto& test : kTestCases) { | |
| const LineRange range(test.hunk_text.begin(), test.hunk_text.end()); | |
| Hunk hunk; | |
| const auto status = hunk.Parse(range); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| EXPECT_EQ(hunk.AddedLines(), test.expected_added_lines); | |
| } | |
| } | |
| TEST(HunkSplitTest, ExpectedSingletons) { | |
| const std::vector<absl::string_view> kTestCases[] = { | |
| {" no-change"}, | |
| {"+one addition"}, | |
| {"-one deletion"}, | |
| {" no-change", // | |
| " multi-no-change"}, | |
| {" same", // | |
| "+added", // | |
| " same"}, | |
| {" same", // | |
| "+added", // | |
| "+added more", // | |
| " same"}, | |
| {" same", // | |
| "-removed", // | |
| " same"}, | |
| {" same", // | |
| "-removed", // | |
| "-removed more", // | |
| " same"}, | |
| {" same", // | |
| "-replace this", // | |
| "+with this", // | |
| " same"}, | |
| {" same", // | |
| "-replace this", // | |
| "-and this", // | |
| "+with this", // | |
| " same"}, | |
| {" same", // | |
| "-replace this", // | |
| "+with this", // | |
| "+and this", // | |
| " same"}, | |
| {" same", // | |
| " same more context", // | |
| "-replace this", // | |
| "-and this", // | |
| "+with this", // | |
| "+and this", // | |
| " same more context", // | |
| " same"}, | |
| }; | |
| for (const auto& test : kTestCases) { | |
| const std::vector<MarkedLine> lines(MakeMarkedLines(test)); | |
| // can pick any starting line numbers, doesn't matter | |
| const Hunk hunk(10, 12, lines.begin(), lines.end()); | |
| const std::vector<Hunk> splits(hunk.Split()); | |
| // expect returned list contains exactly the original hunk | |
| EXPECT_THAT(splits, ElementsAre(hunk)); | |
| } | |
| } | |
| struct ExpectedHunk { | |
| int old_starting_line; | |
| int new_starting_line; | |
| std::pair<int, int> marked_line_offsets; | |
| }; | |
| static std::vector<Hunk> MakeExpectedHunks( | |
| const Hunk& original, const std::vector<ExpectedHunk>& hunk_infos) { | |
| const auto& marked_lines = original.MarkedLines(); | |
| std::vector<Hunk> results; | |
| for (const auto& hunk_info : hunk_infos) { | |
| results.emplace_back( | |
| hunk_info.old_starting_line, hunk_info.new_starting_line, | |
| marked_lines.begin() + hunk_info.marked_line_offsets.first, | |
| marked_lines.begin() + hunk_info.marked_line_offsets.second); | |
| } | |
| return results; | |
| } | |
| struct HunkSplitTestCase { | |
| std::vector<MarkedLine> marked_lines; | |
| Hunk original_hunk; | |
| std::vector<Hunk> expected_sub_hunks; | |
| HunkSplitTestCase(int old_starting_line, int new_starting_line, | |
| const std::vector<absl::string_view>& lines, | |
| const std::vector<ExpectedHunk>& sub_hunks) | |
| : marked_lines(MakeMarkedLines(lines)), | |
| original_hunk(old_starting_line, new_starting_line, | |
| marked_lines.begin(), marked_lines.end()), | |
| expected_sub_hunks(MakeExpectedHunks(original_hunk, sub_hunks)) {} | |
| }; | |
| TEST(HunkSplitTest, MultipleSubHunks) { | |
| const HunkSplitTestCase kTestCases[] = { | |
| HunkSplitTestCase(4, 6, | |
| {" no-change", // | |
| "-remove", // | |
| "+replace", // | |
| " no-change"}, | |
| { | |
| // one hunk (no split) | |
| {4, 6, {0, 4}}, | |
| }), | |
| HunkSplitTestCase(4, 5, | |
| {"+insert", // | |
| " coins", // | |
| "+to continue"}, | |
| { | |
| // two hunks | |
| {4, 5, {0, 1}}, | |
| {4, 6, {1, 3}}, | |
| }), | |
| HunkSplitTestCase(4, 5, | |
| {"+insert", // | |
| " more", // | |
| " coins", // | |
| "+to continue"}, | |
| { | |
| // two hunks | |
| {4, 5, {0, 1}}, | |
| {4, 6, {1, 4}}, | |
| }), | |
| HunkSplitTestCase(7, 7, | |
| {"-insert", // | |
| " coins", // | |
| "-to continue"}, | |
| { | |
| // two hunks | |
| {7, 7, {0, 1}}, | |
| {8, 7, {1, 3}}, | |
| }), | |
| HunkSplitTestCase(7, 7, | |
| {"-move", // | |
| "-these", // | |
| " untouched", // | |
| "+move", "+these"}, | |
| { | |
| // two hunks | |
| {7, 7, {0, 2}}, | |
| {9, 7, {2, 5}}, | |
| }), | |
| HunkSplitTestCase(2, 1, | |
| {" context", // | |
| "-in", // | |
| "+space", // | |
| " no one", // | |
| "-can", // | |
| "-hear", // | |
| "+you", // | |
| "+scream"}, | |
| { | |
| // two hunks | |
| {2, 1, {0, 3}}, | |
| {4, 3, {3, 8}}, | |
| }), | |
| HunkSplitTestCase(3, 4, | |
| {" delete", // | |
| "-every", // | |
| " other", // | |
| "-line", // | |
| " ", // | |
| "-delete", // | |
| " every", // | |
| "-other", // | |
| " line"}, | |
| { | |
| // many hunks | |
| {3, 4, {0, 2}}, | |
| {5, 5, {2, 4}}, | |
| {7, 6, {4, 6}}, | |
| {9, 7, {6, 9}}, | |
| }), | |
| HunkSplitTestCase(10, 10, | |
| {" a", // | |
| "-long long", // | |
| "+time", // | |
| "-ago", // | |
| " in", // | |
| "+a", // | |
| "+galaxy", // | |
| " far", // | |
| "-far", // | |
| " away"}, | |
| { | |
| // many hunks | |
| {10, 10, {0, 4}}, | |
| {13, 12, {4, 7}}, | |
| {14, 15, {7, 10}}, | |
| }), | |
| }; | |
| for (const auto& test : kTestCases) { | |
| EXPECT_THAT(test.original_hunk.Split(), | |
| ElementsAreArray(test.expected_sub_hunks)); | |
| } | |
| } | |
| TEST(HunkParseAndPrintTest, ValidInputs) { | |
| const std::vector<absl::string_view> kTestCases[] = { | |
| {"@@ -1,0 +2,0 @@"}, // 0 line counts, technically consistent | |
| {"@@ -1,2 +2,2 @@", // 2 lines of context, common to before/after | |
| " same1", // | |
| " same2"}, | |
| {"@@ -1,2 +2,2 @@ int foo(void) {", // additional context | |
| " same1", // | |
| " same2"}, | |
| {"@@ -1,2 +2,0 @@", // only deletions, no context lines | |
| "-erase me", // | |
| "-erase me too"}, | |
| {"@@ -1,0 +2,2 @@", // only additions, no context lines | |
| "+new line 1", // | |
| "+new line 2"}, | |
| {"@@ -1,1 +2,1 @@", // 0 lines of context, 1 line changed | |
| "-at first I was like whoa", // | |
| "+and then I was like WHOA"}, | |
| {"@@ -1,3 +2,4 @@", // with 1 line of surrounding context | |
| " common line1", // | |
| "-at first I was like whoa", // | |
| "+and then I was like WHOA", // | |
| "+ and then like whoa", // | |
| " common line2"}, | |
| }; | |
| for (const auto& lines : kTestCases) { | |
| Hunk hunk; | |
| const LineRange range(lines.begin(), lines.end()); | |
| const auto status = hunk.Parse(range); | |
| EXPECT_TRUE(status.ok()) << status.message(); | |
| // Validate reversibility. | |
| std::ostringstream stream; | |
| stream << hunk; | |
| EXPECT_EQ(stream.str(), absl::StrJoin(lines, "\n") + "\n"); | |
| } | |
| } | |
| TEST(HunkVerifyAgainstOriginalLinesTest, LineNumberOutOfBounds) { | |
| const std::vector<absl::string_view> kHunkText = { | |
| { | |
| "@@ -2,3 +4,3 @@", // dont' care about position in new-file | |
| " line2", // | |
| "-line3", // | |
| "+line pi", // | |
| " line4", // this line doesn't exist in original | |
| }, | |
| }; | |
| const std::vector<absl::string_view> kOriginal = { | |
| "line1", "line2", "line3", | |
| // no line4 | |
| }; | |
| Hunk hunk; | |
| { | |
| const LineRange range(kHunkText.begin(), kHunkText.end()); | |
| const auto status = hunk.Parse(range); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| { | |
| const auto status = hunk.VerifyAgainstOriginalLines(kOriginal); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kOutOfRange); | |
| EXPECT_TRUE(absl::StrContains(status.message(), "references line 4")); | |
| EXPECT_TRUE(absl::StrContains(status.message(), "with only 3 lines")); | |
| } | |
| } | |
| TEST(HunkVerifyAgainstOriginalLinesTest, InconsistentRetainedLine) { | |
| const std::vector<absl::string_view> kHunkText = { | |
| { | |
| "@@ -2,2 +4,2 @@", | |
| " line2", // | |
| "-line3", // | |
| "+line pi", // | |
| }, | |
| }; | |
| const std::vector<absl::string_view> kOriginal = { | |
| "line1", | |
| "line2 different", | |
| "line3", | |
| }; | |
| Hunk hunk; | |
| { | |
| const LineRange range(kHunkText.begin(), kHunkText.end()); | |
| const auto status = hunk.Parse(range); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| { | |
| const auto status = hunk.VerifyAgainstOriginalLines(kOriginal); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kDataLoss); | |
| EXPECT_TRUE(absl::StrContains(status.message(), | |
| "Patch is inconsistent with original file")); | |
| } | |
| } | |
| TEST(HunkVerifyAgainstOriginalLinesTest, InconsistentDeletedLine) { | |
| const std::vector<absl::string_view> kHunkText = { | |
| { | |
| "@@ -2,2 +4,2 @@", | |
| " line2", // | |
| "-line3", // | |
| "+line pi", // | |
| }, | |
| }; | |
| const std::vector<absl::string_view> kOriginal = { | |
| "line1", | |
| "line2", | |
| "line3 different", | |
| }; | |
| Hunk hunk; | |
| { | |
| const LineRange range(kHunkText.begin(), kHunkText.end()); | |
| const auto status = hunk.Parse(range); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| { | |
| const auto status = hunk.VerifyAgainstOriginalLines(kOriginal); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kDataLoss); | |
| EXPECT_TRUE(absl::StrContains(status.message(), | |
| "Patch is inconsistent with original file")); | |
| } | |
| } | |
| TEST(FilePatchParseTest, InvalidInputs) { | |
| const std::vector<absl::string_view> kTestCases[] = { | |
| {}, // empty range is invalid | |
| {""}, // no "---" marker for source info | |
| { | |
| "--- /path/to/file.txt\t2020-03-30", // no "+++" marker for source | |
| // info | |
| }, | |
| { | |
| "--- /path/to/file.txt\t2020-03-29", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -2,1 +3,1 @@", // hunk line counts are inconsistent | |
| }, | |
| { | |
| "--- /path/to/file.txt\t2020-03-29", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,0 +13,0 @@", // empty, but ok | |
| "@@ -42,1 +43,1 @@", // hunk line counts are inconsistent | |
| }, | |
| { | |
| "--- /path/to/file.txt\t2020-03-29", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -2,1 +3,1 @@", | |
| "malformed line does not begin with [ -+]", | |
| }, | |
| }; | |
| for (const auto& lines : kTestCases) { | |
| const LineRange range(lines.begin(), lines.end()); | |
| FilePatch file_patch; | |
| EXPECT_FALSE(file_patch.Parse(range).ok()) << "lines:\n" | |
| << absl::StrJoin(lines, "\n"); | |
| } | |
| } | |
| TEST(FilePatchParseAndPrintTest, ValidInputs) { | |
| const std::vector<absl::string_view> kTestCases[] = { | |
| { | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| // no hunks, but still valid | |
| }, | |
| { | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,0 +13,0 @@", // empty, but ok | |
| }, | |
| { | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,1 +13,1 @@", | |
| " no change here", | |
| }, | |
| { | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,3 +13,2 @@", | |
| " no change here", | |
| "-delete me", | |
| " no change here", | |
| }, | |
| { | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,2 +13,3 @@", | |
| " no change here", | |
| "+add me", | |
| " no change here", | |
| }, | |
| { | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,3 +13,2 @@", // first hunk | |
| " no change here", | |
| "-delete me", | |
| " no change here", | |
| "@@ -52,2 +53,3 @@", // second hunk | |
| " no change here", | |
| "+add me", | |
| " no change here", | |
| }, | |
| { | |
| // one line of file metadata | |
| "==== //depot/p4/style/path/to/file.txt#4 - local/path/to/file.txt " | |
| "====", | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,1 +13,1 @@", | |
| " no change here", | |
| }, | |
| { | |
| // one line of file metadata | |
| "diff -u a/path/to/file.txt b/path/to/file.txt", | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,1 +13,1 @@", | |
| " no change here", | |
| }, | |
| }; | |
| for (const auto& lines : kTestCases) { | |
| const LineRange range(lines.begin(), lines.end()); | |
| FilePatch file_patch; | |
| const auto status = file_patch.Parse(range); | |
| EXPECT_TRUE(status.ok()) << status.message(); | |
| // Validate reversibility. | |
| std::ostringstream stream; | |
| stream << file_patch; | |
| EXPECT_EQ(stream.str(), absl::StrJoin(lines, "\n") + "\n"); | |
| } | |
| } | |
| TEST(FilePatchIsNewFileTest, NewFile) { | |
| const std::vector<absl::string_view> kInput = { | |
| "--- /dev/null\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -0,0 +1,2 @@", | |
| "+new content 1", | |
| "+new content 2", | |
| }; | |
| const LineRange range(kInput.begin(), kInput.end()); | |
| FilePatch file_patch; | |
| const auto status = file_patch.Parse(range); | |
| EXPECT_TRUE(status.ok()) << status.message(); | |
| EXPECT_TRUE(file_patch.IsNewFile()); | |
| } | |
| TEST(FilePatchIsNewFileTest, ExistingFile) { | |
| const std::vector<absl::string_view> kInput = { | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,1 +13,1 @@", | |
| " no change here", | |
| }; | |
| const LineRange range(kInput.begin(), kInput.end()); | |
| FilePatch file_patch; | |
| const auto status = file_patch.Parse(range); | |
| EXPECT_TRUE(status.ok()) << status.message(); | |
| EXPECT_FALSE(file_patch.IsNewFile()); | |
| } | |
| TEST(FilePatchIsDeletedFileTest, DeletedFile) { | |
| const std::vector<absl::string_view> kInput = { | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /dev/null\t2020-03-30", | |
| "@@ -1,2 +0,0 @@", | |
| "-deleted content 1", | |
| "-deleted content 2", | |
| }; | |
| const LineRange range(kInput.begin(), kInput.end()); | |
| FilePatch file_patch; | |
| const auto status = file_patch.Parse(range); | |
| EXPECT_TRUE(status.ok()) << status.message(); | |
| EXPECT_TRUE(file_patch.IsDeletedFile()); | |
| } | |
| TEST(FilePatchIsDeletedFileTest, ExistingFile) { | |
| const std::vector<absl::string_view> kInput = { | |
| "--- /path/to/file.txt\t2020-03-30", | |
| "+++ /path/to/file.txt\t2020-03-30", | |
| "@@ -12,2 +13,2 @@", | |
| " no change here", | |
| "+you win some", | |
| "-you lose some", | |
| }; | |
| const LineRange range(kInput.begin(), kInput.end()); | |
| FilePatch file_patch; | |
| const auto status = file_patch.Parse(range); | |
| EXPECT_TRUE(status.ok()) << status.message(); | |
| EXPECT_FALSE(file_patch.IsDeletedFile()); | |
| } | |
| TEST(FilePatchAddedLinesTest, Various) { | |
| const AddedLinesTestCase kTestCases[] = { | |
| { | |
| { | |
| "--- /path/to/file.txt\t2019-12-01", | |
| "+++ /path/to/file.txt\t2019-12-31", | |
| "@@ -12,1 +13,1 @@", | |
| " no change here", | |
| }, | |
| {}, | |
| }, | |
| { | |
| { | |
| "--- /path/to/file.txt\t2019-12-01", | |
| "+++ /path/to/file.txt\t2019-12-31", | |
| "@@ -12,1 +13,1 @@", | |
| " no change here", | |
| "@@ -21,3 +20,2 @@", | |
| " ", | |
| "-bye", | |
| " ", | |
| "@@ -31,2 +43,4 @@", | |
| " ", | |
| "+hello", // line 45 | |
| "+world", // line 46 | |
| " ", | |
| "@@ -61,3 +80,3 @@", | |
| " ", | |
| "-adios", | |
| "+hola", // line 81 | |
| " ", | |
| }, | |
| {{44, 46}, {81, 82}}, | |
| }, | |
| }; | |
| for (const auto& test : kTestCases) { | |
| const LineRange range(test.hunk_text.begin(), test.hunk_text.end()); | |
| FilePatch file_patch; | |
| const auto status = file_patch.Parse(range); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| EXPECT_EQ(file_patch.AddedLines(), test.expected_added_lines); | |
| } | |
| } | |
| class FilePatchPickApplyTest : public FilePatch, public ::testing::Test { | |
| protected: | |
| absl::Status ParseLines(const std::vector<absl::string_view>& lines) { | |
| const LineRange range(lines.begin(), lines.end()); | |
| return Parse(range); | |
| } | |
| static absl::Status NullFileReader(absl::string_view filename, | |
| std::string* contents) { | |
| contents->clear(); | |
| return absl::OkStatus(); | |
| } | |
| static absl::Status NullFileWriter(absl::string_view filename, | |
| absl::string_view contents) { | |
| return absl::OkStatus(); | |
| } | |
| }; | |
| // Takes the place of a real file on the filesystem. | |
| // TODO(fangism): move this to a "file_test_util" library. | |
| struct StringFile { | |
| const absl::string_view path; | |
| const absl::string_view contents; | |
| }; | |
| class StringFileSequence { | |
| public: | |
| explicit StringFileSequence(const std::vector<StringFile>& files) | |
| : files_(files) {} | |
| StringFileSequence(std::initializer_list<StringFile> files) : files_(files) {} | |
| protected: | |
| const std::vector<StringFile> files_; | |
| // stateful value needed inside std::function to emulate a sequence of calls | |
| size_t index_ = 0; | |
| }; | |
| // Functor that can mimic a sequence of calls to file::GetContents() | |
| // Can be passed anywhere that takes a FileReaderFunction. | |
| // TODO(fangism): move this to a "file_test_util" library. | |
| struct ReadStringFileSequence : public StringFileSequence { | |
| explicit ReadStringFileSequence(const std::vector<StringFile>& files) | |
| : StringFileSequence(files) {} | |
| ReadStringFileSequence(std::initializer_list<StringFile> files) | |
| : StringFileSequence(files) {} | |
| // like file::GetContents() | |
| absl::Status operator()(absl::string_view filename, std::string* dest) { | |
| // ASSERT_LT(i, files_.size()); // can't use ASSERT_* which returns void | |
| if (index_ >= files_.size()) { | |
| return absl::OutOfRangeError( | |
| absl::StrCat("No more files to read beyond index=", index_)); | |
| } | |
| const auto& file = files_[index_]; | |
| EXPECT_EQ(filename, file.path) << " at index " << index_; | |
| dest->assign(file.contents.begin(), file.contents.end()); | |
| ++index_; | |
| return absl::OkStatus(); // "file" is successfully read | |
| } | |
| }; | |
| // Functor that can mimic a sequence of calls to file::SetContents() | |
| // Can be passed anywhere that takes a FileWriterFunction. | |
| // TODO(fangism): move this to a "file_test_util" library. | |
| struct ExpectStringFileSequence : public StringFileSequence { | |
| explicit ExpectStringFileSequence(const std::vector<StringFile>& files) | |
| : StringFileSequence(files) {} | |
| ExpectStringFileSequence(std::initializer_list<StringFile> files) | |
| : StringFileSequence(files) {} | |
| // like file::SetContents() | |
| absl::Status operator()(absl::string_view filename, absl::string_view src) { | |
| // ASSERT_LT(i, files_.size()); // can't use ASSERT_* which returns void | |
| if (index_ >= files_.size()) { | |
| return absl::OutOfRangeError( | |
| absl::StrCat("No more files to compare beyond index=", index_)); | |
| } | |
| const auto& file = files_[index_]; | |
| EXPECT_EQ(filename, file.path) << " at index " << index_; | |
| EXPECT_EQ(file.contents, src) << " at index " << index_; | |
| ++index_; | |
| return absl::OkStatus(); // "file" is successfully written | |
| } | |
| }; | |
| TEST_F(FilePatchPickApplyTest, ErrorReadingFile) { | |
| std::istringstream ins; | |
| std::ostringstream outs; | |
| constexpr absl::string_view kErrorMessage = "File not found."; | |
| auto error_file_reader = [=](absl::string_view filename, | |
| std::string* contents) { | |
| return absl::NotFoundError(kErrorMessage); | |
| }; | |
| const auto status = PickApply(ins, outs, error_file_reader, &NullFileWriter); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_EQ(status.message(), kErrorMessage); | |
| EXPECT_TRUE(outs.str().empty()) << "Unexpected: " << outs.str(); | |
| } | |
| TEST_F(FilePatchPickApplyTest, IgnoreNewFile) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- /dev/null\t2012-01-01", | |
| "+++ foo.txt\t2012-01-01", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins; | |
| std::ostringstream outs; | |
| const auto status = PickApply(ins, outs, &NullFileReader, &NullFileWriter); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_TRUE(outs.str().empty()) << "Unexpected: " << outs.str(); | |
| } | |
| TEST_F(FilePatchPickApplyTest, IgnoreDeletedFile) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- bar.txt\t2012-01-01", | |
| "+++ /dev/null\t2012-01-01", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins; | |
| std::ostringstream outs; | |
| const auto status = PickApply(ins, outs, &NullFileReader, &NullFileWriter); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_TRUE(outs.str().empty()) << "Unexpected: " << outs.str(); | |
| } | |
| TEST_F(FilePatchPickApplyTest, EmptyPatchNoPrompt) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins; | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = "aaa\nbbb\nccc\n"; | |
| constexpr absl::string_view kExpected = kOriginal; // no change | |
| const auto status = | |
| PickApply(ins, outs, // | |
| internal::ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| internal::ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_TRUE(outs.str().empty()) << "Unexpected: " << outs.str(); | |
| } | |
| TEST_F(FilePatchPickApplyTest, ErrorWritingFileInPlace) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins; | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = "aaa\nbbb\nccc\n"; | |
| constexpr absl::string_view kErrorMessage = "Cannot write file."; | |
| auto error_file_writer = [=](absl::string_view, absl::string_view) { | |
| return absl::PermissionDeniedError(kErrorMessage); | |
| }; | |
| const auto status = PickApply( | |
| ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), error_file_writer); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_EQ(status.message(), kErrorMessage); | |
| EXPECT_TRUE(outs.str().empty()) << "Unexpected: " << outs.str(); | |
| } | |
| TEST_F(FilePatchPickApplyTest, OneHunkNotApplied) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,2 @@", | |
| " bbb", | |
| "-ccc", // patch proposes to delete this line | |
| " ddd", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("n\n"); // user declines patch hunk | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n"; | |
| constexpr absl::string_view kExpected = kOriginal; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, PatchInconsistentWithOriginalText) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,2 @@", | |
| " bbb", // inconsistent with original | |
| "-ccc", // patch proposes to delete this line | |
| " ddd", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\n"); // user accepts hunk | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb-different\n" // inconsistent with patch | |
| "ccc\n" // deleted by hunk | |
| "ddd\n" | |
| "eee\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "ddd\n" | |
| "eee\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kDataLoss); | |
| } | |
| TEST_F(FilePatchPickApplyTest, OneDeletionAccepted) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,2 @@", | |
| " bbb", | |
| "-ccc", // patch proposes to delete this line | |
| " ddd", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\n"); // user accepts hunk | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // deleted by hunk | |
| "ddd\n" | |
| "eee\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "ddd\n" | |
| "eee\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, OneInsertionAccepted) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,2 +2,3 @@", | |
| " bbb", | |
| "+bbb.5", // patch proposes to insert this line | |
| " ccc", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\n"); // user accepts hunk | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "bbb.5\n" // inserted | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, OneReplacementAccepted) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,3 @@", | |
| " bbb", | |
| "-ccc", // patch proposes to replace this line | |
| "+C++", // with this line | |
| " ddd", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\n"); // user accepts hunk | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // replaced | |
| "ddd\n" | |
| "eee\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "C++\n" // replaced | |
| "ddd\n" | |
| "eee\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, HelpFirstThenAcceptHunk) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,3 @@", | |
| " bbb", | |
| "-ccc", // patch proposes to replace this line | |
| "+C++", // with this line | |
| " ddd", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("?\ny\n"); // help, accept | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // replaced | |
| "ddd\n" | |
| "eee\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "C++\n" // replaced | |
| "ddd\n" | |
| "eee\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| EXPECT_TRUE(absl::StrContains(outs.str(), "print this help")); | |
| } | |
| TEST_F(FilePatchPickApplyTest, HunksOutOfOrder) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -5,3 +5,3 @@", | |
| " eee", | |
| "-fff", | |
| "+fangism", | |
| " ggg", | |
| "@@ -2,3 +2,3 @@", // bad ordering | |
| " bbb", | |
| "-ccc", // patch proposes to replace this line | |
| "+C++", // with this line | |
| " ddd", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\nn\n"); // accept, reject | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // replaced | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "C++\n" // replaced | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_TRUE(absl::StrContains(status.message(), "not properly ordered")); | |
| } | |
| TEST_F(FilePatchPickApplyTest, AcceptOnlyFirstOfTwoHunks) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,3 @@", | |
| " bbb", | |
| "-ccc", // patch proposes to replace this line | |
| "+C++", // with this line | |
| " ddd", | |
| "@@ -5,3 +5,3 @@", | |
| " eee", | |
| "-fff", | |
| "+fangism", | |
| " ggg", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\nn\n"); // accept, reject | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // replaced | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "C++\n" // replaced | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, AcceptOnlySecondOfTwoHunks) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,3 @@", | |
| " bbb", | |
| "-ccc", // patch proposes to replace this line | |
| "+C++", // with this line | |
| " ddd", | |
| "@@ -5,3 +5,3 @@", | |
| " eee", | |
| "-fff", | |
| "+fangism", | |
| " ggg", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("n\ny\n"); // reject, accept | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // kept | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" // changed | |
| "ggg\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // kept | |
| "ddd\n" | |
| "eee\n" | |
| "fangism\n" // changed | |
| "ggg\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, SplitThenAcceptOnlyFirstOfTwoHunks) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,6 +2,6 @@", // one large splittable hunk | |
| " bbb", | |
| "-ccc", // elect to apply | |
| "+C++", // this change | |
| " ddd", | |
| " eee", | |
| "-fff", // but not | |
| "+fangism", // this change | |
| " ggg", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("s\ny\nn\n"); // split, accept, reject | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // replaced | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "C++\n" // replaced | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, SplitThenAcceptOnlySecondOfTwoHunks) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,6 +2,6 @@", // one large splittable hunk | |
| " bbb", | |
| "-ccc", // elect to reject | |
| "+C++", // this change | |
| " ddd", | |
| " eee", | |
| "-fff", // but accept | |
| "+fangism", // this change | |
| " ggg", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("s\nn\ny\n"); // split, reject, accept | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // kept | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" // replaced | |
| "ggg\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // kept | |
| "ddd\n" | |
| "eee\n" | |
| "fangism\n" // replaced | |
| "ggg\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, AbortRightAway) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,3 @@", | |
| " bbb", | |
| "-ccc", // patch proposes to replace this line | |
| "+C++", // with this line | |
| " ddd", | |
| "@@ -5,3 +5,3 @@", | |
| " eee", | |
| "-fff", | |
| "+fangism", | |
| " ggg", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("q\n"); // quit (abandon) | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n"; | |
| constexpr absl::string_view kExpected = kOriginal; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, TreatEndOfUserInputAsAbort) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,3 @@", | |
| " bbb", | |
| "-ccc", // patch proposes to replace this line | |
| "+C++", // with this line | |
| " ddd", | |
| "@@ -5,3 +5,3 @@", | |
| " eee", | |
| "-fff", | |
| "+fangism", | |
| " ggg", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins; // empty, end of user input | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n"; | |
| constexpr absl::string_view kExpected = kOriginal; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, AbortFileAfterAcceptingOneHunk) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,3 @@", | |
| " bbb", | |
| "-ccc", // patch proposes to replace this line | |
| "+C++", // with this line | |
| " ddd", | |
| "@@ -5,3 +5,3 @@", | |
| " eee", | |
| "-fff", | |
| "+fangism", | |
| " ggg", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\nq\n"); // accept, quit (abandon) | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" // replaced | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n"; | |
| constexpr absl::string_view kExpected = kOriginal; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, AcceptTwoDeletions) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,2 @@", | |
| " bbb", | |
| "-ccc", // delete this line | |
| " ddd", | |
| "@@ -6,3 +5,2 @@", | |
| " fff", | |
| "-ggg", // delete this line | |
| " hhh", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\ny\n"); // accept, accept | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n" | |
| "hhh\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "hhh\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, AcceptAllDeletions) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,2 @@", | |
| " bbb", | |
| "-ccc", // delete this line | |
| " ddd", | |
| "@@ -6,3 +5,2 @@", | |
| " fff", | |
| "-ggg", // delete this line | |
| " hhh", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("a\n"); // accept all | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n" | |
| "hhh\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "hhh\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, RejectAllDeletions) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,3 +2,2 @@", | |
| " bbb", | |
| "-ccc", // delete this line | |
| " ddd", | |
| "@@ -6,3 +5,2 @@", | |
| " fff", | |
| "-ggg", // delete this line | |
| " hhh", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("d\n"); // accept all | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n" | |
| "hhh\n"; | |
| constexpr absl::string_view kExpected = kOriginal; // no changes | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, AcceptTwoInsertions) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,2 +2,3 @@", | |
| " bbb", | |
| "+ccc", // delete this line | |
| " ddd", | |
| "@@ -5,2 +6,3 @@", | |
| " fff", | |
| "+ggg", // delete this line | |
| " hhh", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\ny\n"); // accept, accept | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "hhh\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n" | |
| "hhh\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, AcceptAllInsertions) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,2 +2,3 @@", | |
| " bbb", | |
| "+ccc", // delete this line | |
| " ddd", | |
| "@@ -5,2 +6,3 @@", | |
| " fff", | |
| "+ggg", // delete this line | |
| " hhh", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("a\n"); // accept all | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "hhh\n"; | |
| constexpr absl::string_view kExpected = | |
| "aaa\n" | |
| "bbb\n" | |
| "ccc\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "ggg\n" | |
| "hhh\n"; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(FilePatchPickApplyTest, RejectAllInsertions) { | |
| { | |
| const std::vector<absl::string_view> kHunkText{ | |
| "--- foo.txt\t2012-01-01", | |
| "+++ foo-formatted.txt\t2012-01-01", | |
| "@@ -2,2 +2,3 @@", | |
| " bbb", | |
| "+ccc", // delete this line | |
| " ddd", | |
| "@@ -5,2 +6,3 @@", | |
| " fff", | |
| "+ggg", // delete this line | |
| " hhh", | |
| }; | |
| const auto status = ParseLines(kHunkText); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("d\n"); // reject all | |
| std::ostringstream outs; | |
| constexpr absl::string_view kOriginal = | |
| "aaa\n" | |
| "bbb\n" | |
| "ddd\n" | |
| "eee\n" | |
| "fff\n" | |
| "hhh\n"; | |
| constexpr absl::string_view kExpected = kOriginal; | |
| const auto status = | |
| PickApply(ins, outs, // | |
| ReadStringFileSequence({{"foo.txt", kOriginal}}), | |
| ExpectStringFileSequence({{"foo.txt", kExpected}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| } // namespace | |
| } // namespace internal | |
| namespace { | |
| // public interface tests | |
| TEST(PatchSetParseTest, InvalidInputs) { | |
| constexpr absl::string_view kTestCases[] = { | |
| // no "+++" marker for source | |
| "--- /path/to/file.txt\t2020-03-30\n", | |
| // hunk line counts are inconsistent | |
| "--- /path/to/file.txt\t2020-03-29\n" | |
| "+++ /path/to/file.txt\t2020-03-30\n" | |
| "@@ -2,1 +3,1 @@\n", | |
| // second hunk line counts are inconsistent | |
| "--- /path/to/file.txt\t2020-03-29\n" | |
| "+++ /path/to/file.txt\t2020-03-30\n" | |
| "@@ -12,0 +13,0 @@\n" | |
| "@@ -42,1 +43,1 @@\n", | |
| // malformed hunk marked-line | |
| "--- /path/to/file.txt\t2020-03-29\n" | |
| "+++ /path/to/file.txt\t2020-03-30\n" | |
| "@@ -2,1 +3,1 @@\n" | |
| "malformed line does not begin with [ -+]", | |
| }; | |
| for (const auto& patch_contents : kTestCases) { | |
| PatchSet patch_set; | |
| EXPECT_FALSE(patch_set.Parse(patch_contents).ok()) << "contents:\n" | |
| << patch_contents; | |
| } | |
| } | |
| TEST(PatchSetParseAndPrintTest, ValidInputs) { | |
| constexpr absl::string_view kTestCases[] = { | |
| // no metadata here | |
| "--- /path/to/file.txt\t2020-03-30\n" | |
| "+++ /path/to/file.txt\t2020-03-30\n" | |
| "@@ -12,3 +13,2 @@\n" | |
| " no change here\n" | |
| "-delete me\n" | |
| " no change here\n" | |
| "@@ -52,2 +53,3 @@\n" | |
| " no change here\n" | |
| "+add me\n" | |
| " no change here\n", | |
| // with patchset metadata here | |
| "metadata\n" | |
| "From: hobbit@fryingpan.org\n" | |
| "To: hobbit@fire.org\n" | |
| "metadata\n" | |
| "\n" | |
| "--- /path/to/file.txt\t2020-03-30\n" | |
| "+++ /path/to/file.txt\t2020-03-30\n" | |
| "@@ -12,3 +13,2 @@\n" | |
| " no change here\n" | |
| "-delete me\n" | |
| " no change here\n" | |
| "@@ -52,2 +53,3 @@\n" | |
| " no change here\n" | |
| "+add me\n" | |
| " no change here\n", | |
| // with file metadata in two files | |
| "diff -u /path/to/file1.txt local/path/to/file1.txt\n" | |
| "--- /path/to/file1.txt\t2020-03-30\n" | |
| "+++ /path/to/file1.txt\t2020-03-30\n" | |
| "@@ -12,3 +13,2 @@\n" | |
| " no change here\n" | |
| "-delete me\n" | |
| " no change here\n" | |
| "diff -u /path/to/file2.txt local/path/to/file2.txt\n" | |
| "--- /path/to/file2.txt\t2020-03-30\n" | |
| "+++ /path/to/file2.txt\t2020-03-30\n" | |
| "@@ -52,2 +53,3 @@\n" | |
| " no change here\n" | |
| "+add me\n" | |
| " no change here\n", | |
| // with patchset metadata and file metadata in two files | |
| "From: author@fryingpan.org\n" | |
| "To: reviewer@fire.org\n" | |
| "\n" | |
| "diff -u /path/to/file1.txt local/path/to/file1.txt\n" | |
| "--- /path/to/file1.txt\t2020-03-30\n" | |
| "+++ /path/to/file1.txt\t2020-03-30\n" | |
| "@@ -12,3 +13,2 @@\n" | |
| " no change here\n" | |
| "-delete me\n" | |
| " no change here\n" | |
| "diff -u /path/to/file2.txt local/path/to/file2.txt\n" | |
| "--- /path/to/file2.txt\t2020-03-30\n" | |
| "+++ /path/to/file2.txt\t2020-03-30\n" | |
| "@@ -52,2 +53,3 @@\n" | |
| " no change here\n" | |
| "+add me\n" | |
| " no change here\n", | |
| }; | |
| for (const auto& patch_contents : kTestCases) { | |
| PatchSet patch_set; | |
| const auto status = patch_set.Parse(patch_contents); | |
| EXPECT_TRUE(status.ok()) << status.message(); | |
| // Validate reversibility. | |
| std::ostringstream stream; | |
| stream << patch_set; | |
| EXPECT_EQ(stream.str(), patch_contents); | |
| } | |
| } | |
| TEST(PatchSetAddedLinesMapTest, NewAndExistingFile) { | |
| constexpr absl::string_view patch_contents = // | |
| "diff -u /dev/null local/path/to/file1.txt\n" | |
| "--- /dev/null\t2020-03-30\n" | |
| "+++ /path/to/file1.txt\t2020-03-30\n" // new file | |
| "@@ -0,0 +1,2 @@\n" | |
| "+add me\n" | |
| "+add me too\n" | |
| "--- /path/to/file2.txt\t2020-03-30\n" | |
| "+++ /path/to/file2.txt\t2020-03-30\n" // existing file | |
| "@@ -52,2 +53,4 @@\n" | |
| " no change here\n" | |
| "+add me\n" | |
| "+add me too\n" | |
| " no change here\n" | |
| "diff -u local/path/to/file3.txt /dev/null\n" | |
| "--- /path/to/file3.txt\t2020-03-30\n" // deleted file | |
| "+++ /dev/null\t2020-03-30\n" | |
| "@@ -1,2 +0,0 @@\n" | |
| "-bye\n" | |
| "-bye\n"; | |
| PatchSet patch_set; | |
| const auto status = patch_set.Parse(patch_contents); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| typedef FileLineNumbersMap::value_type P; | |
| EXPECT_THAT(patch_set.AddedLinesMap(false), | |
| ElementsAre(P{"/path/to/file1.txt", {}}, | |
| P{"/path/to/file2.txt", {{54, 56}}})); | |
| EXPECT_THAT(patch_set.AddedLinesMap(true), | |
| ElementsAre(P{"/path/to/file1.txt", {{1, 3}}}, | |
| P{"/path/to/file2.txt", {{54, 56}}})); | |
| // Neither case should include deleted files like file3.txt | |
| } | |
| class PatchSetPickApplyTest : public PatchSet, public ::testing::Test {}; | |
| TEST_F(PatchSetPickApplyTest, EmptyFilePatchHunks) { | |
| { | |
| constexpr absl::string_view patch_contents = // | |
| "diff -u /dev/null local/path/to/file1.txt\n" | |
| "--- foo/bar.txt\t2020-03-30\n" | |
| "+++ foo/bar-formatted.txt\t2020-03-30\n"; | |
| const auto status = Parse(patch_contents); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins; | |
| std::ostringstream outs; | |
| // No file I/O or prompting because patch is empty. | |
| const auto status = PickApply( | |
| ins, outs, // | |
| internal::ReadStringFileSequence({{"foo/bar.txt", "don't care\n"}}), | |
| internal::ExpectStringFileSequence({{"foo/bar.txt", "don't care\n"}})); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_TRUE(outs.str().empty()) << "Unexpected: " << outs.str(); | |
| } | |
| TEST_F(PatchSetPickApplyTest, MultipleEmptyFilePatchHunks) { | |
| { | |
| constexpr absl::string_view patch_contents = // | |
| "diff -u /dev/null local/path/to/file1.txt\n" | |
| "--- foo/bar.txt\t2020-03-30\n" | |
| "+++ foo/bar-formatted.txt\t2020-03-30\n" | |
| "--- bar/foo.txt\t2020-03-30\n" | |
| "+++ bar/foo-formatted.txt\t2020-03-30\n"; | |
| const auto status = Parse(patch_contents); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins; | |
| std::ostringstream outs; | |
| // No file I/O or prompting because patches are empty. | |
| const auto status = PickApply(ins, outs, // | |
| internal::ReadStringFileSequence({ | |
| {"foo/bar.txt", "don't care\n"}, | |
| {"bar/foo.txt", "don't care\n"}, | |
| }), | |
| internal::ExpectStringFileSequence({ | |
| {"foo/bar.txt", "don't care\n"}, | |
| {"bar/foo.txt", "don't care\n"}, | |
| })); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_TRUE(outs.str().empty()) << "Unexpected: " << outs.str(); | |
| } | |
| TEST_F(PatchSetPickApplyTest, MultipleNonEmptyFilePatchHunks) { | |
| { | |
| constexpr absl::string_view patch_contents = // | |
| "diff -u /dev/null local/path/to/file1.txt\n" | |
| "--- foo/bar.txt\t2020-03-30\n" | |
| "+++ foo/bar-formatted.txt\t2020-03-30\n" | |
| "@@ -1,3 +1,2 @@\n" | |
| " you\n" | |
| "-lose\n" | |
| " some\n" | |
| "--- bar/foo.txt\t2020-03-30\n" | |
| "+++ bar/foo-formatted.txt\t2020-03-30\n" | |
| "@@ -1,2 +1,3 @@\n" | |
| " you\n" | |
| "+win\n" | |
| " some\n"; | |
| const auto status = Parse(patch_contents); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\ny\n"); // accept one hunk in each file | |
| std::ostringstream outs; | |
| // No file I/O or prompting because patches are empty. | |
| const auto status = PickApply(ins, outs, // | |
| internal::ReadStringFileSequence({ | |
| {"foo/bar.txt", "you\nlose\nsome\n"}, | |
| {"bar/foo.txt", "you\nsome\n"}, | |
| }), | |
| internal::ExpectStringFileSequence({ | |
| {"foo/bar.txt", "you\nsome\n"}, | |
| {"bar/foo.txt", "you\nwin\nsome\n"}, | |
| })); | |
| EXPECT_TRUE(status.ok()) << "Got: " << status.message(); | |
| EXPECT_FALSE(outs.str().empty()); | |
| } | |
| TEST_F(PatchSetPickApplyTest, FirstFilePatchOutOfOrder) { | |
| { | |
| constexpr absl::string_view patch_contents = // | |
| "diff -u /dev/null local/path/to/file1.txt\n" | |
| "--- foo/bar.txt\t2020-03-30\n" | |
| "+++ foo/bar-formatted.txt\t2020-03-30\n" | |
| "@@ -4,3 +3,2 @@\n" // out-of-order | |
| " out\n" | |
| "-of\n" | |
| " order\n" | |
| "@@ -1,3 +1,2 @@\n" | |
| " you\n" | |
| "-lose\n" | |
| " some\n" | |
| "--- bar/foo.txt\t2020-03-30\n" | |
| "+++ bar/foo-formatted.txt\t2020-03-30\n" | |
| "@@ -1,2 +1,3 @@\n" | |
| " you\n" | |
| "+win\n" | |
| " some\n"; | |
| const auto status = Parse(patch_contents); | |
| ASSERT_TRUE(status.ok()) << status.message(); | |
| } | |
| std::istringstream ins("y\ny\n"); // accept | |
| std::ostringstream outs; | |
| // No file I/O or prompting because patches are empty. | |
| const auto status = | |
| PickApply(ins, outs, // | |
| internal::ReadStringFileSequence({ | |
| {"foo/bar.txt", "you\nlose\nsome\nout\nof\norder"}, | |
| {"bar/foo.txt", "you\nsome\n"}, | |
| }), | |
| internal::ExpectStringFileSequence({ | |
| {"foo/bar.txt", "you\nsome\n"}, | |
| {"bar/foo.txt", "you\nwin\nsome\n"}, | |
| })); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_TRUE(absl::StrContains(status.message(), "not properly ordered")); | |
| } | |
| } // namespace | |
| } // namespace verible |