| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/zucchini/equivalence_map.h" |
| |
| #include <cstring> |
| #include <deque> |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "components/zucchini/encoded_view.h" |
| #include "components/zucchini/image_index.h" |
| #include "components/zucchini/suffix_array.h" |
| #include "components/zucchini/targets_affinity.h" |
| #include "components/zucchini/test_disassembler.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace zucchini { |
| |
| namespace { |
| |
| using OffsetDeque = std::deque<offset_t>; |
| |
| // Make all references 2 bytes long. |
| constexpr offset_t kReferenceSize = 2; |
| |
| // Creates and initialize an ImageIndex from |a| and with 2 types of references. |
| // The result is populated with |refs0| and |refs1|. |a| is expected to be a |
| // string literal valid for the lifetime of the object. |
| ImageIndex MakeImageIndexForTesting(const char* a, |
| std::vector<Reference>&& refs0, |
| std::vector<Reference>&& refs1) { |
| TestDisassembler disasm( |
| {kReferenceSize, TypeTag(0), PoolTag(0)}, std::move(refs0), |
| {kReferenceSize, TypeTag(1), PoolTag(0)}, std::move(refs1), |
| {kReferenceSize, TypeTag(2), PoolTag(1)}, {}); |
| |
| ImageIndex image_index( |
| ConstBufferView(reinterpret_cast<const uint8_t*>(a), std::strlen(a))); |
| |
| EXPECT_TRUE(image_index.Initialize(&disasm)); |
| return image_index; |
| } |
| |
| std::vector<TargetsAffinity> MakeTargetsAffinitiesForTesting( |
| const ImageIndex& old_image_index, |
| const ImageIndex& new_image_index, |
| const EquivalenceMap& equivalence_map) { |
| std::vector<TargetsAffinity> target_affinities(old_image_index.PoolCount()); |
| for (const auto& old_pool_tag_and_targets : old_image_index.target_pools()) { |
| PoolTag pool_tag = old_pool_tag_and_targets.first; |
| target_affinities[pool_tag.value()].InferFromSimilarities( |
| equivalence_map, old_pool_tag_and_targets.second.targets(), |
| new_image_index.pool(pool_tag).targets()); |
| } |
| return target_affinities; |
| } |
| |
| } // namespace |
| |
| TEST(EquivalenceMapTest, GetTokenSimilarity) { |
| ImageIndex old_index = MakeImageIndexForTesting( |
| "ab1122334455", {{2, 0}, {4, 1}, {6, 2}, {8, 2}}, {{10, 3}}); |
| // Note: {4, 1} -> {6, 3} and {6, 2} -> {4, 1}, then result is sorted. |
| ImageIndex new_index = MakeImageIndexForTesting( |
| "a11b33224455", {{1, 0}, {4, 1}, {6, 3}, {8, 1}}, {{10, 2}}); |
| std::vector<TargetsAffinity> affinities = MakeTargetsAffinitiesForTesting( |
| old_index, new_index, |
| EquivalenceMap({{{0, 0, 1}, 1.0}, {{1, 3, 1}, 1.0}})); |
| |
| // Raw match. |
| EXPECT_LT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 0, 0)); |
| // Raw mismatch. |
| EXPECT_GT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 0, 1)); |
| EXPECT_GT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 1, 0)); |
| |
| // Type mismatch. |
| EXPECT_EQ(kMismatchFatal, |
| GetTokenSimilarity(old_index, new_index, affinities, 0, 1)); |
| EXPECT_EQ(kMismatchFatal, |
| GetTokenSimilarity(old_index, new_index, affinities, 2, 0)); |
| EXPECT_EQ(kMismatchFatal, |
| GetTokenSimilarity(old_index, new_index, affinities, 2, 10)); |
| EXPECT_EQ(kMismatchFatal, |
| GetTokenSimilarity(old_index, new_index, affinities, 10, 1)); |
| |
| // Reference strong match. |
| EXPECT_LT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 2, 1)); |
| EXPECT_LT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 4, 6)); |
| |
| // Reference weak match. |
| EXPECT_LT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 6, 4)); |
| EXPECT_LT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 6, 8)); |
| EXPECT_LT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 8, 4)); |
| |
| // Weak match is not greater than strong match. |
| EXPECT_LE(GetTokenSimilarity(old_index, new_index, affinities, 6, 4), |
| GetTokenSimilarity(old_index, new_index, affinities, 2, 1)); |
| |
| // Reference mismatch. |
| EXPECT_GT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 2, 4)); |
| EXPECT_GT(0.0, GetTokenSimilarity(old_index, new_index, affinities, 2, 6)); |
| } |
| |
| TEST(EquivalenceMapTest, GetEquivalenceSimilarity) { |
| ImageIndex image_index = |
| MakeImageIndexForTesting("abcdef1122", {{6, 0}}, {{8, 1}}); |
| std::vector<TargetsAffinity> affinities = |
| MakeTargetsAffinitiesForTesting(image_index, image_index, {}); |
| |
| // Sanity check. These are no-op with length-0 equivalences. |
| EXPECT_EQ(0.0, GetEquivalenceSimilarity(image_index, image_index, affinities, |
| {0, 0, 0})); |
| EXPECT_EQ(0.0, GetEquivalenceSimilarity(image_index, image_index, affinities, |
| {0, 3, 0})); |
| EXPECT_EQ(0.0, GetEquivalenceSimilarity(image_index, image_index, affinities, |
| {3, 0, 0})); |
| |
| // Now examine larger equivalences. |
| EXPECT_LT(0.0, GetEquivalenceSimilarity(image_index, image_index, affinities, |
| {0, 0, 3})); |
| EXPECT_GE(0.0, GetEquivalenceSimilarity(image_index, image_index, affinities, |
| {0, 3, 3})); |
| EXPECT_GE(0.0, GetEquivalenceSimilarity(image_index, image_index, affinities, |
| {3, 0, 3})); |
| |
| EXPECT_LT(0.0, GetEquivalenceSimilarity(image_index, image_index, affinities, |
| {6, 6, 4})); |
| } |
| |
| TEST(EquivalenceMapTest, ExtendEquivalenceForward) { |
| auto test_extend_forward = |
| [](const ImageIndex old_index, const ImageIndex new_index, |
| const EquivalenceCandidate& equivalence, double base_similarity) { |
| return ExtendEquivalenceForward( |
| old_index, new_index, |
| MakeTargetsAffinitiesForTesting(old_index, new_index, {}), |
| equivalence, base_similarity) |
| .eq; |
| }; |
| |
| EXPECT_EQ(Equivalence({0, 0, 0}), |
| test_extend_forward(MakeImageIndexForTesting("", {}, {}), |
| MakeImageIndexForTesting("", {}, {}), |
| {{0, 0, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ(Equivalence({0, 0, 0}), |
| test_extend_forward(MakeImageIndexForTesting("banana", {}, {}), |
| MakeImageIndexForTesting("zzzz", {}, {}), |
| {{0, 0, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ(Equivalence({0, 0, 6}), |
| test_extend_forward(MakeImageIndexForTesting("banana", {}, {}), |
| MakeImageIndexForTesting("banana", {}, {}), |
| {{0, 0, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ(Equivalence({2, 2, 4}), |
| test_extend_forward(MakeImageIndexForTesting("banana", {}, {}), |
| MakeImageIndexForTesting("banana", {}, {}), |
| {{2, 2, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ(Equivalence({0, 0, 6}), |
| test_extend_forward(MakeImageIndexForTesting("bananaxx", {}, {}), |
| MakeImageIndexForTesting("bananayy", {}, {}), |
| {{0, 0, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ( |
| Equivalence({0, 0, 8}), |
| test_extend_forward(MakeImageIndexForTesting("banana11", {{6, 0}}, {}), |
| MakeImageIndexForTesting("banana11", {{6, 0}}, {}), |
| {{0, 0, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ( |
| Equivalence({0, 0, 6}), |
| test_extend_forward(MakeImageIndexForTesting("banana11", {{6, 0}}, {}), |
| MakeImageIndexForTesting("banana22", {}, {{6, 0}}), |
| {{0, 0, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ( |
| Equivalence({0, 0, 17}), |
| test_extend_forward(MakeImageIndexForTesting("bananaxxpineapple", {}, {}), |
| MakeImageIndexForTesting("bananayypineapple", {}, {}), |
| {{0, 0, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ( |
| Equivalence({3, 0, 19}), |
| test_extend_forward( |
| MakeImageIndexForTesting("foobanana11xxpineapplexx", {{9, 0}}, {}), |
| MakeImageIndexForTesting("banana11yypineappleyy", {{6, 0}}, {}), |
| {{3, 0, 0}, 0.0}, 8.0)); |
| } |
| |
| TEST(EquivalenceMapTest, ExtendEquivalenceBackward) { |
| auto test_extend_backward = |
| [](const ImageIndex old_index, const ImageIndex new_index, |
| const EquivalenceCandidate& equivalence, double base_similarity) { |
| return ExtendEquivalenceBackward( |
| old_index, new_index, |
| MakeTargetsAffinitiesForTesting(old_index, new_index, {}), |
| equivalence, base_similarity) |
| .eq; |
| }; |
| |
| EXPECT_EQ(Equivalence({0, 0, 0}), |
| test_extend_backward(MakeImageIndexForTesting("", {}, {}), |
| MakeImageIndexForTesting("", {}, {}), |
| {{0, 0, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ(Equivalence({6, 4, 0}), |
| test_extend_backward(MakeImageIndexForTesting("banana", {}, {}), |
| MakeImageIndexForTesting("zzzz", {}, {}), |
| {{6, 4, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ(Equivalence({0, 0, 6}), |
| test_extend_backward(MakeImageIndexForTesting("banana", {}, {}), |
| MakeImageIndexForTesting("banana", {}, {}), |
| {{6, 6, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ(Equivalence({2, 2, 6}), |
| test_extend_backward(MakeImageIndexForTesting("xxbanana", {}, {}), |
| MakeImageIndexForTesting("yybanana", {}, {}), |
| {{8, 8, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ( |
| Equivalence({0, 0, 8}), |
| test_extend_backward(MakeImageIndexForTesting("11banana", {{0, 0}}, {}), |
| MakeImageIndexForTesting("11banana", {{0, 0}}, {}), |
| {{8, 8, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ( |
| Equivalence({2, 2, 6}), |
| test_extend_backward(MakeImageIndexForTesting("11banana", {{0, 0}}, {}), |
| MakeImageIndexForTesting("22banana", {}, {{0, 0}}), |
| {{8, 8, 0}, 0.0}, 8.0)); |
| |
| EXPECT_EQ(Equivalence({0, 0, 17}), |
| test_extend_backward( |
| MakeImageIndexForTesting("bananaxxpineapple", {}, {}), |
| MakeImageIndexForTesting("bananayypineapple", {}, {}), |
| {{8, 8, 9}, 9.0}, 8.0)); |
| |
| EXPECT_EQ( |
| Equivalence({3, 0, 19}), |
| test_extend_backward( |
| MakeImageIndexForTesting("foobanana11xxpineapplexx", {{9, 0}}, {}), |
| MakeImageIndexForTesting("banana11yypineappleyy", {{6, 0}}, {}), |
| {{22, 19, 0}, 0.0}, 8.0)); |
| } |
| |
| TEST(EquivalenceMapTest, PruneEquivalencesAndSortBySource) { |
| auto PruneEquivalencesAndSortBySourceTest = |
| [](std::deque<Equivalence>&& equivalences) { |
| OffsetMapper::PruneEquivalencesAndSortBySource(&equivalences); |
| return std::move(equivalences); |
| }; |
| |
| EXPECT_EQ(std::deque<Equivalence>(), |
| PruneEquivalencesAndSortBySourceTest({})); |
| EXPECT_EQ(std::deque<Equivalence>({{0, 10, 1}}), |
| PruneEquivalencesAndSortBySourceTest({{0, 10, 1}})); |
| EXPECT_EQ(std::deque<Equivalence>(), |
| PruneEquivalencesAndSortBySourceTest({{0, 10, 0}})); |
| EXPECT_EQ(std::deque<Equivalence>({{0, 10, 1}, {1, 11, 1}}), |
| PruneEquivalencesAndSortBySourceTest({{0, 10, 1}, {1, 11, 1}})); |
| EXPECT_EQ(std::deque<Equivalence>({{0, 10, 2}, {2, 13, 1}}), |
| PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 2}})); |
| EXPECT_EQ(std::deque<Equivalence>({{0, 10, 2}}), |
| PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 1}})); |
| EXPECT_EQ(std::deque<Equivalence>({{0, 10, 2}, {2, 14, 1}}), |
| PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 13, 2}})); |
| EXPECT_EQ(std::deque<Equivalence>({{0, 10, 1}, {1, 12, 3}}), |
| PruneEquivalencesAndSortBySourceTest({{0, 10, 2}, {1, 12, 3}})); |
| EXPECT_EQ(std::deque<Equivalence>({{0, 10, 3}, {3, 16, 2}}), |
| PruneEquivalencesAndSortBySourceTest( |
| {{0, 10, 3}, {1, 13, 3}, {3, 16, 2}})); // Pruning is greedy |
| |
| // Consider following pattern that may cause O(n^2) behavior if not handled |
| // properly. |
| // *************** |
| // ********** |
| // ******** |
| // ****** |
| // **** |
| // ** |
| // *************** |
| // This test case makes sure the function does not stall on a large instance |
| // of this pattern. |
| EXPECT_EQ(std::deque<Equivalence>({{0, 10, +300000}, {300000, 30, +300000}}), |
| PruneEquivalencesAndSortBySourceTest([] { |
| std::deque<Equivalence> equivalenses; |
| equivalenses.push_back({0, 10, +300000}); |
| for (offset_t i = 0; i < 100000; ++i) |
| equivalenses.push_back({200000 + i, 20, +200000 - 2 * i}); |
| equivalenses.push_back({300000, 30, +300000}); |
| return equivalenses; |
| }())); |
| } |
| |
| TEST(EquivalenceMapTest, NaiveExtendedForwardProject) { |
| constexpr size_t kOldImageSize = 1000U; |
| constexpr size_t kNewImageSize = 1000U; |
| OffsetMapper offset_mapper(std::deque<Equivalence>(), kOldImageSize, |
| kNewImageSize); |
| |
| // Convenience function to declutter. |
| auto project = [&offset_mapper](const Equivalence& eq, offset_t offset) { |
| return offset_mapper.NaiveExtendedForwardProject(eq, offset); |
| }; |
| |
| // Equivalence with delta = 0. |
| Equivalence eq_stay = {10, 10, +5}; // [10,15) -> [10,15). |
| for (offset_t offset = 0U; offset < 1000U; ++offset) { |
| EXPECT_EQ(offset, project(eq_stay, offset)); |
| } |
| // Saturate since result would overflow "new". |
| EXPECT_EQ(999U, project(eq_stay, 1000U)); |
| EXPECT_EQ(999U, project(eq_stay, 2000U)); |
| EXPECT_EQ(999U, project(eq_stay, kOffsetBound - 1)); |
| |
| // Equivalence with delta = -10. |
| Equivalence eq_dec = {20, 10, +12}; // [20,32) --> [10,22). |
| // Offsets in "old" block. |
| EXPECT_EQ(10U, project(eq_dec, 20U)); |
| EXPECT_EQ(11U, project(eq_dec, 21U)); |
| EXPECT_EQ(21U, project(eq_dec, 31U)); |
| // Offsets before "old" block, no underflow |
| EXPECT_EQ(9U, project(eq_dec, 19U)); |
| EXPECT_EQ(1U, project(eq_dec, 11U)); |
| EXPECT_EQ(0U, project(eq_dec, 10U)); |
| // Offsets before "old" block, underflow (possible since delta < 0). |
| EXPECT_EQ(0U, project(eq_dec, 9U)); |
| EXPECT_EQ(0U, project(eq_dec, 5U)); |
| EXPECT_EQ(0U, project(eq_dec, 0U)); |
| // Offsets after "old" block, no overflow. |
| EXPECT_EQ(20U, project(eq_dec, 30U)); |
| EXPECT_EQ(64U, project(eq_dec, 74U)); |
| EXPECT_EQ(90U, project(eq_dec, 100U)); |
| EXPECT_EQ(490U, project(eq_dec, 500U)); |
| EXPECT_EQ(999U, project(eq_dec, 1009U)); |
| // Offsets after "old" block, overflow. |
| EXPECT_EQ(999U, project(eq_dec, 1010U)); |
| EXPECT_EQ(999U, project(eq_dec, 2000U)); |
| EXPECT_EQ(999U, project(eq_dec, kOffsetBound - 1)); |
| |
| // Equivalence with delta = +10. |
| Equivalence eq_inc = {7, 17, +80}; // [7,87) --> [17,97). |
| // Offsets in "old" block. |
| EXPECT_EQ(17U, project(eq_inc, 7U)); |
| EXPECT_EQ(60U, project(eq_inc, 50U)); |
| EXPECT_EQ(96U, project(eq_inc, 86U)); |
| // Offsets before "old" block, underflow impossible since delta >= 0. |
| EXPECT_EQ(16U, project(eq_inc, 6U)); |
| EXPECT_EQ(10U, project(eq_inc, 0U)); |
| // Offsets after "old" block, no overflow. |
| EXPECT_EQ(97U, project(eq_inc, 87U)); |
| EXPECT_EQ(510U, project(eq_inc, 500U)); |
| EXPECT_EQ(999U, project(eq_inc, 989U)); |
| // Offsets after "old" block, overflow. |
| EXPECT_EQ(999U, project(eq_inc, 990U)); |
| EXPECT_EQ(999U, project(eq_inc, 2000U)); |
| EXPECT_EQ(999U, project(eq_inc, kOffsetBound - 1)); |
| } |
| |
| TEST(EquivalenceMapTest, ExtendedForwardProject) { |
| // EquivalenceMaps provided must be sorted by "old" offset, and pruned. |
| // [0,2) --> [10,12), [2,3) --> [13,14), [4,6) --> [16,18). |
| OffsetMapper offset_mapper1({{0, 10, +2}, {2, 13, +1}, {4, 16, +2}}, 20U, |
| 25U); |
| EXPECT_EQ(10U, offset_mapper1.ExtendedForwardProject(0U)); |
| EXPECT_EQ(11U, offset_mapper1.ExtendedForwardProject(1U)); |
| EXPECT_EQ(13U, offset_mapper1.ExtendedForwardProject(2U)); |
| EXPECT_EQ(14U, offset_mapper1.ExtendedForwardProject(3U)); // Previous equiv. |
| EXPECT_EQ(16U, offset_mapper1.ExtendedForwardProject(4U)); |
| EXPECT_EQ(17U, offset_mapper1.ExtendedForwardProject(5U)); |
| EXPECT_EQ(18U, offset_mapper1.ExtendedForwardProject(6U)); // Previous equiv. |
| // Fake offsets. |
| EXPECT_EQ(25U, offset_mapper1.ExtendedForwardProject(20U)); |
| EXPECT_EQ(26U, offset_mapper1.ExtendedForwardProject(21U)); |
| EXPECT_EQ(1005U, offset_mapper1.ExtendedForwardProject(1000U)); |
| EXPECT_EQ(kOffsetBound - 1, |
| offset_mapper1.ExtendedForwardProject(kOffsetBound - 1)); |
| |
| // [0,2) --> [10,12), [13,14) --> [2,3), [16,18) --> [4,6). |
| OffsetMapper offset_mapper2({{0, 10, +2}, {13, 2, +1}, {16, 4, +2}}, 25U, |
| 20U); |
| EXPECT_EQ(10U, offset_mapper2.ExtendedForwardProject(0U)); |
| EXPECT_EQ(11U, offset_mapper2.ExtendedForwardProject(1U)); |
| EXPECT_EQ(2U, offset_mapper2.ExtendedForwardProject(13U)); |
| EXPECT_EQ(3U, offset_mapper2.ExtendedForwardProject(14U)); // Previous equiv. |
| EXPECT_EQ(4U, offset_mapper2.ExtendedForwardProject(16U)); |
| EXPECT_EQ(5U, offset_mapper2.ExtendedForwardProject(17U)); |
| EXPECT_EQ(6U, offset_mapper2.ExtendedForwardProject(18U)); // Previous equiv. |
| // Fake offsets. |
| EXPECT_EQ(20U, offset_mapper2.ExtendedForwardProject(25U)); |
| EXPECT_EQ(21U, offset_mapper2.ExtendedForwardProject(26U)); |
| EXPECT_EQ(995U, offset_mapper2.ExtendedForwardProject(1000U)); |
| EXPECT_EQ(kOffsetBound - 1 - 5, |
| offset_mapper2.ExtendedForwardProject(kOffsetBound - 1)); |
| } |
| |
| TEST(EquivalenceMapTest, ExtendedForwardProjectEncoding) { |
| // Tests OffsetMapper::ExtendedForwardProject(), which maps every "old" offset |
| // to a "new" offset, with possible overlap (even though blocks don't |
| // overlap). Not testing real offsets only (no fake offsets). |
| // |old_spec| is a string like "<<aaAAaabbBBbcCCc>>": |
| // - Upper case letters are covered "old" offsets. |
| // - Lower case letters are non-covered offsets that are properly mapped using |
| // nearest "old" block. |
| // - '<' denotes underflow (clamped to 0). |
| // - '>' denotes overflow (clampled to "new" size - 1). |
| // |new_spec| is a string like "aaAA(ab)(ab)BBb..cCCc": |
| // - Upper and lower case letters are mapped "new" targets, occurring in the |
| // order that they appear in |old_spec|. |
| // - '.' are "new" offsets that appear as output. |
| // - '(' and ')' surround a single "new" location that are repeated as output. |
| int case_no = 0; |
| auto run_test = [&case_no](std::deque<Equivalence>&& equivalences, |
| const std::string& old_spec, |
| const std::string& new_spec) { |
| const size_t old_size = old_spec.length(); |
| // Build expected "new" offsets, queue up for each letter. |
| std::map<char, std::deque<offset_t>> expected; |
| offset_t cur_new_offset = 0; |
| char state = ')'; // ')' = increase offset, '(' = stay. |
| for (char ch : new_spec) { |
| if (ch == '(' || ch == ')') |
| state = ch; |
| else |
| expected[ch].push_back(cur_new_offset); |
| cur_new_offset += (state == ')') ? 1 : 0; |
| } |
| const size_t new_size = cur_new_offset; |
| // Forward-project for each "old" index, pull from queue from matching |
| // letter, and compare. |
| OffsetMapper offset_mapper(std::move(equivalences), old_size, new_size); |
| for (offset_t old_offset = 0; old_offset < old_size; ++old_offset) { |
| offset_t new_offset = offset_mapper.ExtendedForwardProject(old_offset); |
| char ch = old_spec[old_offset]; |
| if (ch == '<') { // Special case: Underflow. |
| EXPECT_EQ(0U, new_offset) << "in case " << case_no; |
| } else if (ch == '>') { // Special case: Overflow. |
| EXPECT_EQ(static_cast<offset_t>(new_size - 1), new_offset) |
| << "in case " << case_no; |
| } else { |
| std::deque<offset_t>& q = expected[ch]; |
| ASSERT_FALSE(q.empty()); |
| EXPECT_EQ(q.front(), new_offset) << "in case " << case_no; |
| q.pop_front(); |
| if (q.empty()) |
| expected.erase(ch); |
| } |
| } |
| // Clear useless '.', and ensure everything is consumed. |
| expected.erase('.'); |
| EXPECT_TRUE(expected.empty()) << "in case " << case_no; |
| ++case_no; |
| }; |
| |
| // Trivial: [5,9) --> [5,9). |
| run_test({{5, 5, +4}}, "aaaaaAAAAaaaaa", "aaaaaAAAAaaaaa"); |
| // Swap: [0,4) --> [6,10), [4,10) --> [0,6). |
| run_test({{0, 6, +4}, {4, 0, +6}}, "AAAABBBBBB", "BBBBBBAAAA"); |
| // Overlap: [0,4) --> [2,6), [4,10) --> [3,9). |
| run_test({{0, 2, +4}, {4, 3, +6}}, "AAAABBBBBB", "..A(AB)(AB)(AB)BBB."); |
| // Converge: [1,3) --> [2,4), [7,8) --> [6,7). |
| run_test({{1, 2, +2}, {7, 6, +1}}, "aAAaabbBbb", ".aAA(ab)(ab)Bbb."); |
| // Converge with tie-breaker: [1,3) --> [2,4), [8,9) --> [7,8). |
| run_test({{1, 2, +2}, {8, 7, +1}}, "aAAaaabbBb", ".aAAa(ab)(ab)Bb."); |
| // Shift left: [6,8) --> [2,4): Underflow occurs. |
| run_test({{6, 2, +2}}, "<<<<aaAAaa", "aaAAaa...."); |
| // Shift right: [2,5) --> [6,9): Overflow occurs. |
| run_test({{2, 6, +3}}, "aaAAAa>>>>", "....aaAAAa"); |
| // Diverge: [3,5) --> [1,3], [7,9) --> [9,11). |
| run_test({{3, 1, +2}, {7, 9, +2}}, "<<aAAabBBb>>", "aAAa....bBBb"); |
| // Pile-up: [0,2) --> [7,9), [9,11) --> [9,11), [18,20) --> [11,13). |
| run_test({{0, 7, +2}, {9, 9, +2}, {18, 11, +2}}, "AAaaaabbbBBbbbbcccCC", |
| "......b(Ab)(Abc)(Bac)(Bac)(Cab)(Cab)bb....."); |
| // Inverse pile-up: [7,9) --> [0,2), [9,11) --> [9,11), [13,15) --> [18,20). |
| run_test({{7, 0, +2}, {9, 9, +2}, {11, 18, +2}}, "<<<<<<<AABBCC>>>>>>>", |
| "AA.......BB.......CC"); |
| // Sparse rotate: [3,4) -> [10,11), [10,11) --> [17,18), [17,18) --> [3,4). |
| run_test({{3, 10, +1}, {10, 17, +1}, {17, 3, +1}}, "aaaAaaabbbBbbbcccCccc", |
| "cccCcccaaaAaaabbbBbbb"); |
| // Messy swap: [2,4) --> [10,12), [12,16) --> [3,7). |
| run_test({{2, 10, +2}, {12, 3, +4}}, "aaAAaa>><bbbBBBBbb", |
| "bbbBBBBb(ab)aAAaa"); |
| // Messy expand: [6,8) --> [3,5), [10,11) -> [11,12), [14,17) --> [16,19). |
| run_test({{6, 3, +2}, {10, 11, +1}, {14, 16, +3}}, "<<<aaaAAabBbbcCCCc>>>>>", |
| "aaaAAa....bBbb.cCCCc"); |
| // Interleave: [1,2) --> [0,1), [5,6) --> [10,11), [6,8) --> [3,5), |
| // [11,13) --> [12,14), [14,16) --> [6,8), [17,18) --> [17,18). |
| run_test({{1, 0, +1}, |
| {5, 10, +1}, |
| {6, 3, +2}, |
| {11, 12, +2}, |
| {14, 6, +2}, |
| {17, 17, +1}}, |
| "<AaabBCCccdDDdEEeFf>", "AaaCCc(Ec)EebBdDDd..Ff"); |
| } |
| |
| TEST(EquivalenceMapTest, ForwardProjectAll) { |
| auto ForwardProjectAllTest = [](const OffsetMapper& offset_mapper, |
| std::initializer_list<offset_t> offsets) { |
| std::deque<offset_t> offsets_vec(offsets); |
| offset_mapper.ForwardProjectAll(&offsets_vec); |
| return offsets_vec; |
| }; |
| |
| // [0,2) --> [10,12), [2,3) --> [13,14), [4,6) --> [16,18). |
| OffsetMapper offset_mapper1({{0, 10, +2}, {2, 13, +1}, {4, 16, +2}}, 100U, |
| 100U); |
| EXPECT_EQ(OffsetDeque({10}), ForwardProjectAllTest(offset_mapper1, {0})); |
| EXPECT_EQ(OffsetDeque({13}), ForwardProjectAllTest(offset_mapper1, {2})); |
| EXPECT_EQ(OffsetDeque({}), ForwardProjectAllTest(offset_mapper1, {3})); |
| EXPECT_EQ(OffsetDeque({10, 13}), |
| ForwardProjectAllTest(offset_mapper1, {0, 2})); |
| EXPECT_EQ(OffsetDeque({11, 13, 17}), |
| ForwardProjectAllTest(offset_mapper1, {1, 2, 5})); |
| EXPECT_EQ(OffsetDeque({11, 17}), |
| ForwardProjectAllTest(offset_mapper1, {1, 3, 5})); |
| EXPECT_EQ(OffsetDeque({10, 11, 13, 16, 17}), |
| ForwardProjectAllTest(offset_mapper1, {0, 1, 2, 3, 4, 5, 6})); |
| |
| // [0,2) --> [10,12), [13,14) --> [2,3), [16,18) --> [4,6). |
| OffsetMapper offset_mapper2({{0, 10, +2}, {13, 2, +1}, {16, 4, +2}}, 100U, |
| 100U); |
| EXPECT_EQ(OffsetDeque({2}), ForwardProjectAllTest(offset_mapper2, {13})); |
| EXPECT_EQ(OffsetDeque({10, 2}), |
| ForwardProjectAllTest(offset_mapper2, {0, 13})); |
| EXPECT_EQ(OffsetDeque({11, 2, 5}), |
| ForwardProjectAllTest(offset_mapper2, {1, 13, 17})); |
| EXPECT_EQ(OffsetDeque({11, 5}), |
| ForwardProjectAllTest(offset_mapper2, {1, 14, 17})); |
| EXPECT_EQ(OffsetDeque({10, 11, 2, 4, 5}), |
| ForwardProjectAllTest(offset_mapper2, {0, 1, 13, 14, 16, 17, 18})); |
| } |
| |
| TEST(EquivalenceMapTest, Build) { |
| auto test_build_equivalence = [](const ImageIndex old_index, |
| const ImageIndex new_index, |
| double minimum_similarity) { |
| auto affinities = MakeTargetsAffinitiesForTesting(old_index, new_index, {}); |
| |
| EncodedView old_view(old_index); |
| EncodedView new_view(new_index); |
| |
| for (const auto& old_pool_tag_and_targets : old_index.target_pools()) { |
| PoolTag pool_tag = old_pool_tag_and_targets.first; |
| std::vector<uint32_t> old_labels; |
| std::vector<uint32_t> new_labels; |
| size_t label_bound = affinities[pool_tag.value()].AssignLabels( |
| 1.0, &old_labels, &new_labels); |
| old_view.SetLabels(pool_tag, std::move(old_labels), label_bound); |
| new_view.SetLabels(pool_tag, std::move(new_labels), label_bound); |
| } |
| |
| std::vector<offset_t> old_sa = |
| MakeSuffixArray<InducedSuffixSort>(old_view, old_view.Cardinality()); |
| |
| EquivalenceMap equivalence_map; |
| equivalence_map.Build(old_sa, old_view, new_view, affinities, |
| minimum_similarity); |
| |
| offset_t current_dst_offset = 0; |
| offset_t coverage = 0; |
| for (const auto& candidate : equivalence_map) { |
| EXPECT_GE(candidate.eq.dst_offset, current_dst_offset); |
| EXPECT_GT(candidate.eq.length, offset_t(0)); |
| EXPECT_LE(candidate.eq.src_offset + candidate.eq.length, |
| old_index.size()); |
| EXPECT_LE(candidate.eq.dst_offset + candidate.eq.length, |
| new_index.size()); |
| EXPECT_GE(candidate.similarity, minimum_similarity); |
| current_dst_offset = candidate.eq.dst_offset; |
| coverage += candidate.eq.length; |
| } |
| return coverage; |
| }; |
| |
| EXPECT_EQ(0U, |
| test_build_equivalence(MakeImageIndexForTesting("", {}, {}), |
| MakeImageIndexForTesting("", {}, {}), 4.0)); |
| |
| EXPECT_EQ(0U, test_build_equivalence( |
| MakeImageIndexForTesting("", {}, {}), |
| MakeImageIndexForTesting("banana", {}, {}), 4.0)); |
| |
| EXPECT_EQ(0U, |
| test_build_equivalence(MakeImageIndexForTesting("banana", {}, {}), |
| MakeImageIndexForTesting("", {}, {}), 4.0)); |
| |
| EXPECT_EQ(0U, test_build_equivalence( |
| MakeImageIndexForTesting("banana", {}, {}), |
| MakeImageIndexForTesting("zzzz", {}, {}), 4.0)); |
| |
| EXPECT_EQ(6U, test_build_equivalence( |
| MakeImageIndexForTesting("banana", {}, {}), |
| MakeImageIndexForTesting("banana", {}, {}), 4.0)); |
| |
| EXPECT_EQ(6U, test_build_equivalence( |
| MakeImageIndexForTesting("bananaxx", {}, {}), |
| MakeImageIndexForTesting("bananayy", {}, {}), 4.0)); |
| |
| EXPECT_EQ(8U, test_build_equivalence( |
| MakeImageIndexForTesting("banana11", {{6, 0}}, {}), |
| MakeImageIndexForTesting("banana11", {{6, 0}}, {}), 4.0)); |
| |
| EXPECT_EQ(6U, test_build_equivalence( |
| MakeImageIndexForTesting("banana11", {{6, 0}}, {}), |
| MakeImageIndexForTesting("banana22", {}, {{6, 0}}), 4.0)); |
| |
| EXPECT_EQ( |
| 15U, |
| test_build_equivalence( |
| MakeImageIndexForTesting("banana11pineapple", {{6, 0}}, {}), |
| MakeImageIndexForTesting("banana22pineapple", {}, {{6, 0}}), 4.0)); |
| |
| EXPECT_EQ( |
| 15U, |
| test_build_equivalence( |
| MakeImageIndexForTesting("bananaxxxxxxxxpineapple", {}, {}), |
| MakeImageIndexForTesting("bananayyyyyyyypineapple", {}, {}), 4.0)); |
| |
| EXPECT_EQ( |
| 19U, |
| test_build_equivalence( |
| MakeImageIndexForTesting("foobanana11xxpineapplexx", {{9, 0}}, {}), |
| MakeImageIndexForTesting("banana11yypineappleyy", {{6, 0}}, {}), |
| 4.0)); |
| } |
| |
| } // namespace zucchini |