/* * Copyright (c) 2015, Intel Corporation * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Intel Corporation nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "gtest/gtest.h" #include "nfa/limex_ring.h" #include "nfa/repeat.h" #include "nfa/repeatcompile.h" #include "util/depth.h" #include "util/make_unique.h" #include #include #include using namespace std; using namespace testing; using namespace ue2; struct RepeatTestInfo { enum RepeatType type; depth repeatMin; depth repeatMax; }; static ostream& operator<<(ostream &os, const RepeatInfo &info) { os << "{" << info.repeatMin; if (info.repeatMin == info.repeatMax) { os << "}"; } else if (info.repeatMax == REPEAT_INF) { os << ",}"; } else { os << "," << info.repeatMax << "}"; } os << " " << repeatTypeName(info.type); os << " (period " << info.minPeriod << ")"; return os; } class RepeatTest : public TestWithParam { protected: virtual void SetUp() { test_info = GetParam(); info.type = test_info.type; info.repeatMin = test_info.repeatMin; info.repeatMax = test_info.repeatMax.is_finite() ? (u32)test_info.repeatMax : REPEAT_INF; info.minPeriod = 0; RepeatStateInfo rsi(test_info.type, test_info.repeatMin, test_info.repeatMax, 0); info.packedCtrlSize = rsi.packedCtrlSize; info.stateSize = rsi.stateSize; info.horizon = rsi.horizon; std::copy(rsi.packedFieldSizes.begin(), rsi.packedFieldSizes.end(), info.packedFieldSizes); ctrl = new RepeatControl(); state_int = new char[info.stateSize + 7]; /* state may have mmbits */ state = state_int + 7; } virtual void TearDown() { delete ctrl; delete [] state_int; } RepeatTestInfo test_info; // Test params RepeatInfo info; // Repeat info structure RepeatControl *ctrl; char *state; private: char *state_int; }; static const RepeatTestInfo repeatTests[] = { // Fixed repeats -- ring model { REPEAT_RING, 2, 2 }, { REPEAT_RING, 4, 4 }, { REPEAT_RING, 10, 10 }, { REPEAT_RING, 16, 16 }, { REPEAT_RING, 20, 20 }, { REPEAT_RING, 30, 30 }, { REPEAT_RING, 50, 50 }, { REPEAT_RING, 64, 64 }, { REPEAT_RING, 65, 65 }, { REPEAT_RING, 100, 100 }, { REPEAT_RING, 200, 200 }, { REPEAT_RING, 1000, 1000 }, { REPEAT_RING, 4100, 4100 }, { REPEAT_RING, 16000, 16000 }, // {0, N} repeats -- last model { REPEAT_LAST, 0, 4 }, { REPEAT_LAST, 0, 10 }, { REPEAT_LAST, 0, 20 }, { REPEAT_LAST, 0, 30 }, { REPEAT_LAST, 0, 50 }, { REPEAT_LAST, 0, 100 }, { REPEAT_LAST, 0, 200 }, { REPEAT_LAST, 0, 1000 }, { REPEAT_LAST, 0, 16000 }, // {0, N} repeats -- ring model (though we use 'last' model in practice) { REPEAT_RING, 0, 2 }, { REPEAT_RING, 0, 4 }, { REPEAT_RING, 0, 10 }, { REPEAT_RING, 0, 20 }, { REPEAT_RING, 0, 30 }, { REPEAT_RING, 0, 50 }, { REPEAT_RING, 0, 64 }, { REPEAT_RING, 0, 65 }, { REPEAT_RING, 0, 100 }, { REPEAT_RING, 0, 200 }, { REPEAT_RING, 0, 1000 }, { REPEAT_RING, 0, 16000 }, // {N, M} repeats -- ring model { REPEAT_RING, 2, 3 }, { REPEAT_RING, 1, 4 }, { REPEAT_RING, 5, 10 }, { REPEAT_RING, 10, 20 }, { REPEAT_RING, 10, 50 }, { REPEAT_RING, 50, 60 }, { REPEAT_RING, 100, 200 }, { REPEAT_RING, 1, 200 }, { REPEAT_RING, 10, 16000 }, { REPEAT_RING, 10000, 16000 }, // {N, M} repeats -- range model { REPEAT_RANGE, 1, 4 }, { REPEAT_RANGE, 5, 10 }, { REPEAT_RANGE, 10, 20 }, { REPEAT_RANGE, 10, 50 }, { REPEAT_RANGE, 50, 60 }, { REPEAT_RANGE, 100, 200 }, { REPEAT_RANGE, 1, 200 }, { REPEAT_RANGE, 10, 16000 }, { REPEAT_RANGE, 10000, 16000 }, // {N,M} repeats -- small bitmap model { REPEAT_BITMAP, 1, 2 }, { REPEAT_BITMAP, 5, 10 }, { REPEAT_BITMAP, 10, 20 }, { REPEAT_BITMAP, 20, 40 }, { REPEAT_BITMAP, 1, 63 }, { REPEAT_BITMAP, 50, 63 }, // {N,M} repeats -- trailer model { REPEAT_TRAILER, 1, 2 }, { REPEAT_TRAILER, 8, 8 }, { REPEAT_TRAILER, 0, 8 }, { REPEAT_TRAILER, 10, 20 }, { REPEAT_TRAILER, 1, 32 }, { REPEAT_TRAILER, 64, 64 }, { REPEAT_TRAILER, 1, 64 }, { REPEAT_TRAILER, 1, 100 }, { REPEAT_TRAILER, 1, 2000 }, { REPEAT_TRAILER, 50, 200 }, { REPEAT_TRAILER, 50, 1000 }, { REPEAT_TRAILER, 64, 1024 }, // {N,} repeats -- first model { REPEAT_FIRST, 0, depth::infinity() }, { REPEAT_FIRST, 1, depth::infinity() }, { REPEAT_FIRST, 4, depth::infinity() }, { REPEAT_FIRST, 10, depth::infinity() }, { REPEAT_FIRST, 50, depth::infinity() }, { REPEAT_FIRST, 100, depth::infinity() }, { REPEAT_FIRST, 1000, depth::infinity() }, { REPEAT_FIRST, 3000, depth::infinity() }, { REPEAT_FIRST, 10000, depth::infinity() } }; INSTANTIATE_TEST_CASE_P(Repeat, RepeatTest, ValuesIn(repeatTests)); TEST_P(RepeatTest, MatchSuccess) { SCOPED_TRACE(testing::Message() << "Repeat: " << info); u64a offset = 1000; repeatStore(&info, ctrl, state, offset, 0); for (u32 i = info.repeatMin; i <= info.repeatMax; i++) { enum TriggerResult rv = processTugTrigger(&info, ctrl, state, offset + i); if (rv == TRIGGER_SUCCESS_CACHE) { rv = TRIGGER_SUCCESS; } ASSERT_EQ(TRIGGER_SUCCESS, rv); } } TEST_P(RepeatTest, MatchNegSuccess) { SCOPED_TRACE(testing::Message() << "Repeat: " << info); // Write a top at offset 1000. const u64a offset = 1000; repeatStore(&info, ctrl, state, offset, 0); // Write another match at offset 1002, using is_alive=0 (i.e. the repeat // was killed between these two tops). repeatStore(&info, ctrl, state, offset + 2, 0); enum TriggerResult rv; // Match at offset + repeatMin should fail, while offset + repeatMin + 2 // should succeed. if (info.repeatMin > 2) { rv = processTugTrigger(&info, ctrl, state, offset + info.repeatMin); ASSERT_EQ(TRIGGER_FAIL, rv); } rv = processTugTrigger(&info, ctrl, state, offset + info.repeatMin + 2); if (rv == TRIGGER_SUCCESS_CACHE) { rv = TRIGGER_SUCCESS; } ASSERT_EQ(TRIGGER_SUCCESS, rv); } TEST_P(RepeatTest, MatchFail) { SCOPED_TRACE(testing::Message() << "Repeat: " << info); // Write a match at offset 1000. const u64a offset = 1000; repeatStore(&info, ctrl, state, offset, 0); // Test for a match, if possible, at offset + repeatMin - 1. enum TriggerResult rv; if (info.repeatMin > 0) { u64a testOffset = offset + info.repeatMin - 1; rv = processTugTrigger(&info, ctrl, state, testOffset); ASSERT_EQ(TRIGGER_FAIL, rv); } // Match after repeatMax should fail as well. if (info.repeatMax != REPEAT_INF) { u64a testOffset = offset + info.repeatMax + 1; rv = processTugTrigger(&info, ctrl, state, testOffset); ASSERT_EQ(TRIGGER_STALE, rv); } } // Fill the ring with matches. TEST_P(RepeatTest, FillRing) { if (info.repeatMax == REPEAT_INF) { return; } const u64a offset = 1000; repeatStore(&info, ctrl, state, offset, 0); for (u64a i = 1; i <= info.repeatMax; i++) { repeatStore(&info, ctrl, state, offset + i, 1); } // We should be able to see matches for all of these (beyond the last top offset). enum TriggerResult rv; for (u64a i = offset + info.repeatMax; i <= offset + info.repeatMax + info.repeatMin; i++) { rv = processTugTrigger(&info, ctrl, state, i); if (rv == TRIGGER_SUCCESS_CACHE) { rv = TRIGGER_SUCCESS; } ASSERT_EQ(TRIGGER_SUCCESS, rv); } } TEST_P(RepeatTest, FindTops) { SCOPED_TRACE(testing::Message() << "Repeat: " << info); repeatStore(&info, ctrl, state, 1000, 0); ASSERT_EQ(1000, repeatLastTop(&info, ctrl, state)); repeatStore(&info, ctrl, state, 2000, 1); if (info.type == REPEAT_FIRST) { ASSERT_EQ(1000, repeatLastTop(&info, ctrl, state)); } else { ASSERT_EQ(2000, repeatLastTop(&info, ctrl, state)); } repeatStore(&info, ctrl, state, 3000, 0); ASSERT_EQ(3000, repeatLastTop(&info, ctrl, state)); } TEST_P(RepeatTest, NextMatch) { SCOPED_TRACE(testing::Message() << "Repeat: " << info); u64a top = 10000000ULL; repeatStore(&info, ctrl, state, top, 0); u64a i = top + info.repeatMin; if (info.repeatMin != 0) { // First match after the top should be at top+min. ASSERT_EQ(i, repeatNextMatch(&info, ctrl, state, top)); } while (i < info.repeatMax) { ASSERT_EQ(i + 1, repeatNextMatch(&info, ctrl, state, i)); i++; } if (info.repeatMax != REPEAT_INF) { ASSERT_EQ(0, repeatNextMatch(&info, ctrl, state, top + info.repeatMax)); } } TEST_P(RepeatTest, NextMatchFilledRepeat) { // This test is only really appropriate for repeat models that store more // than one top. if (info.type != REPEAT_RING && info.type != REPEAT_RANGE) { return; } SCOPED_TRACE(testing::Message() << "Repeat: " << info); u64a top = 10000000ULL; repeatStore(&info, ctrl, state, top, 0); for (u64a i = 1; i <= info.repeatMax; i++) { repeatStore(&info, ctrl, state, top + i, 1); } u64a last_top = top + info.repeatMax; u64a i = top + info.repeatMin; if (info.repeatMin != 0) { // First match after the initial top should be at top+min. ASSERT_EQ(i, repeatNextMatch(&info, ctrl, state, top)); } while (i < last_top + info.repeatMax) { ASSERT_EQ(i + 1, repeatNextMatch(&info, ctrl, state, i)); i++; } if (info.repeatMax != REPEAT_INF) { ASSERT_EQ(0, repeatNextMatch(&info, ctrl, state, last_top + info.repeatMax)); } } TEST_P(RepeatTest, TwoTops) { SCOPED_TRACE(testing::Message() << "Repeat: " << info); // Only appropriate for tests that store more than one top. if (info.type == REPEAT_FIRST || info.type == REPEAT_LAST) { return; } const u32 top_range = info.repeatMax; // Limit the scope of this test for runtime brevity. For small cases, we // check every offset, but for bigger ones we use a bigger step. const u32 iter_step = std::max(1u, top_range / 256u); const u64a top1 = 10000ull; for (u32 i = 1; i < top_range; i += iter_step) { const u64a top2 = top1 + i; SCOPED_TRACE(testing::Message() << "Tops at offsets " << top1 << " and " << top2); repeatStore(&info, ctrl, state, top1, 0); ASSERT_EQ(top1, repeatLastTop(&info, ctrl, state)); repeatStore(&info, ctrl, state, top2, 1); ASSERT_EQ(top2, repeatLastTop(&info, ctrl, state)); // We should have those matches from the top1 match window that are // greater than or equal to top2. for (u64a j = std::max(top1 + info.repeatMin, top2); j <= top1 + info.repeatMax; j++) { ASSERT_EQ(REPEAT_MATCH, repeatHasMatch(&info, ctrl, state, j)) << j << " should be a match due to top 1"; } // If the two match windows don't overlap, we should have some // non-matching positions between them. for (u64a j = top1 + info.repeatMax + 1; j < top2 + info.repeatMin; j++) { ASSERT_EQ(REPEAT_NOMATCH, repeatHasMatch(&info, ctrl, state, j)) << j << " should not be a match"; } // We should have all the matches in the match window from top 2. for (u64a j = top2 + info.repeatMin; j <= top2 + info.repeatMax; j++) { ASSERT_EQ(REPEAT_MATCH, repeatHasMatch(&info, ctrl, state, j)) << j << " should be a match due to top 2"; } // One past the end should be stale. u64a past_end = top2 + info.repeatMax + 1; ASSERT_EQ(REPEAT_STALE, repeatHasMatch(&info, ctrl, state, past_end)) << "repeat should be stale at " << past_end; } } TEST_P(RepeatTest, Pack) { SCOPED_TRACE(testing::Message() << "Repeat: " << info); u64a offset = 1000; repeatStore(&info, ctrl, state, offset, 0); // We should be able to pack and then unpack the control block at any // offset up to repeatMin and get a match at both the min and max repeats. unique_ptr packed = ue2::make_unique(info.packedCtrlSize); for (u32 i = 0; i < info.repeatMax; i++) { SCOPED_TRACE(testing::Message() << "i=" << i); const u64a pack_offset = offset + i; memset(packed.get(), 0xff, info.packedCtrlSize); repeatPack(packed.get(), &info, ctrl, pack_offset); memset(ctrl, 0xff, sizeof(*ctrl)); repeatUnpack(packed.get(), &info, pack_offset, ctrl); // We should have a match at every offset in [offset + repeatMin, // offset + repeatMax]. For brevity, we just check the first one and // the last one. u64a first = offset + std::max(i, info.repeatMin); u64a last = offset + info.repeatMax; ASSERT_EQ(REPEAT_MATCH, repeatHasMatch(&info, ctrl, state, first)) << "repeat should have match at " << first; ASSERT_EQ(REPEAT_MATCH, repeatHasMatch(&info, ctrl, state, last)) << "repeat should have match at " << last; } } TEST_P(RepeatTest, LargeGap) { SCOPED_TRACE(testing::Message() << "Repeat: " << info); if (info.repeatMax == REPEAT_INF) { return; // Test not valid for FIRST-type repeats. } for (int i = 0; i < 64; i++) { u64a top1 = 1000; repeatStore(&info, ctrl, state, top1, 0); // first top ASSERT_EQ(top1, repeatLastTop(&info, ctrl, state)); // Add a second top after a gap of 2^i bytes. u64a top2 = top1 + (1ULL << i); repeatStore(&info, ctrl, state, top2, 1); // second top ASSERT_EQ(top2, repeatLastTop(&info, ctrl, state)); } } static const u32 sparsePeriods[] = { 2, 4, 6, 8, 10, 12, 15, 18, 20, 22, 24, 26, 28, 30, /* 40, 50, 60, 80, 100, 120, 150, 180, 200, 250, 300, 350, 400,*/ }; static const RepeatTestInfo sparseRepeats[] = { // Fixed repeats { REPEAT_SPARSE_OPTIMAL_P, 10, 10 }, { REPEAT_SPARSE_OPTIMAL_P, 20, 20 }, { REPEAT_SPARSE_OPTIMAL_P, 40, 40 }, { REPEAT_SPARSE_OPTIMAL_P, 80, 80 }, { REPEAT_SPARSE_OPTIMAL_P, 100, 100 }, { REPEAT_SPARSE_OPTIMAL_P, 150, 150 }, { REPEAT_SPARSE_OPTIMAL_P, 200, 200 }, { REPEAT_SPARSE_OPTIMAL_P, 250, 250 }, { REPEAT_SPARSE_OPTIMAL_P, 300, 300 }, { REPEAT_SPARSE_OPTIMAL_P, 350, 350 }, { REPEAT_SPARSE_OPTIMAL_P, 400, 400 }, { REPEAT_SPARSE_OPTIMAL_P, 500, 500 }, { REPEAT_SPARSE_OPTIMAL_P, 600, 600 }, { REPEAT_SPARSE_OPTIMAL_P, 800, 800 }, { REPEAT_SPARSE_OPTIMAL_P, 1000, 1000 }, { REPEAT_SPARSE_OPTIMAL_P, 1500, 1500 }, { REPEAT_SPARSE_OPTIMAL_P, 2000, 2000 }, { REPEAT_SPARSE_OPTIMAL_P, 2500, 2500 }, { REPEAT_SPARSE_OPTIMAL_P, 3000, 3000 }, { REPEAT_SPARSE_OPTIMAL_P, 3500, 3500 }, { REPEAT_SPARSE_OPTIMAL_P, 4000, 4000 }, { REPEAT_SPARSE_OPTIMAL_P, 4500, 4500 }, { REPEAT_SPARSE_OPTIMAL_P, 5000, 5000 }, { REPEAT_SPARSE_OPTIMAL_P, 65534, 65534 }, // {N, M} repeats { REPEAT_SPARSE_OPTIMAL_P, 10, 20 }, { REPEAT_SPARSE_OPTIMAL_P, 20, 40 }, { REPEAT_SPARSE_OPTIMAL_P, 40, 80 }, { REPEAT_SPARSE_OPTIMAL_P, 80, 100 }, { REPEAT_SPARSE_OPTIMAL_P, 100, 120 }, { REPEAT_SPARSE_OPTIMAL_P, 150, 180 }, { REPEAT_SPARSE_OPTIMAL_P, 200, 400 }, { REPEAT_SPARSE_OPTIMAL_P, 250, 500 }, { REPEAT_SPARSE_OPTIMAL_P, 300, 400 }, { REPEAT_SPARSE_OPTIMAL_P, 350, 500 }, { REPEAT_SPARSE_OPTIMAL_P, 400, 500 }, { REPEAT_SPARSE_OPTIMAL_P, 500, 600 }, { REPEAT_SPARSE_OPTIMAL_P, 600, 700 }, { REPEAT_SPARSE_OPTIMAL_P, 800, 1000 }, { REPEAT_SPARSE_OPTIMAL_P, 1000, 1200 }, { REPEAT_SPARSE_OPTIMAL_P, 1500, 1800 }, { REPEAT_SPARSE_OPTIMAL_P, 2000, 4000 }, { REPEAT_SPARSE_OPTIMAL_P, 2500, 3000 }, { REPEAT_SPARSE_OPTIMAL_P, 3000, 3500 }, { REPEAT_SPARSE_OPTIMAL_P, 3500, 4000 }, { REPEAT_SPARSE_OPTIMAL_P, 4000, 8000 }, { REPEAT_SPARSE_OPTIMAL_P, 4500, 8000 }, { REPEAT_SPARSE_OPTIMAL_P, 5000, 5001 }, { REPEAT_SPARSE_OPTIMAL_P, 60000, 65534 } }; static void test_sparse2entry(const RepeatInfo *info, RepeatControl *ctrl, char *state, u64a second) { SCOPED_TRACE(testing::Message() << "Repeat: " << *info); SCOPED_TRACE(second); if (second > info->repeatMax || second < info->minPeriod) { return; } u64a offset = 1000; u64a exit = offset + info->repeatMin; repeatStore(info, ctrl, state, offset, 0); ASSERT_EQ(offset, repeatLastTop(info, ctrl, state)); u64a offset2 = 1000 + second; u64a exit2 = offset2 + info->repeatMin; repeatStore(info, ctrl, state, offset2, 1); ASSERT_EQ(offset2, repeatLastTop(info, ctrl, state)); u32 range = info->repeatMax - info->repeatMin; for (u32 i = offset2; i < offset + info->repeatMax * 2 + 100; i++) { SCOPED_TRACE(i); RepeatMatch r = repeatHasMatch(info, ctrl, state, i); if ((i >= exit && i <= exit + range) || (i >= exit2 && i <= exit2 + range)) { ASSERT_EQ(REPEAT_MATCH, r); } else if (i > exit2 + range) { ASSERT_EQ(REPEAT_STALE, r); } else { ASSERT_EQ(REPEAT_NOMATCH, r); } } ASSERT_EQ(MAX(exit + range + 1, exit2), repeatNextMatch(info, ctrl, state, exit + range)); ASSERT_EQ(MAX(exit + range + 2, exit2), repeatNextMatch(info, ctrl, state, exit + range + 1)); ASSERT_EQ(exit2, repeatNextMatch(info, ctrl, state, exit2 - 1)); ASSERT_EQ(0, repeatNextMatch(info, ctrl, state, exit2 + range)); } static void test_sparse3entry(const RepeatInfo *info, RepeatControl *ctrl, char *state, u64a diff) { SCOPED_TRACE(testing::Message() << "Repeat:" << *info); SCOPED_TRACE(diff); if (diff * 2 > info->repeatMax || diff < info->minPeriod) { return; } u64a offset = 1000; u64a exit = offset + info->repeatMin; repeatStore(info, ctrl, state, offset, 0); ASSERT_EQ(offset, repeatLastTop(info, ctrl, state)); u64a offset2 = 1000 + diff; u64a exit2 = offset2 + info->repeatMin; repeatStore(info, ctrl, state, offset2, 1); ASSERT_EQ(offset2, repeatLastTop(info, ctrl, state)); u64a offset3 = 1000 + 2 * diff; u64a exit3 = offset3 + info->repeatMin; repeatStore(info, ctrl, state, offset3, 1); ASSERT_EQ(offset3, repeatLastTop(info, ctrl, state)); u32 range = info->repeatMax - info->repeatMin; for (u32 i = offset2; i < offset + info->repeatMax * 2 + 100; i++) { SCOPED_TRACE(i); RepeatMatch r = repeatHasMatch(info, ctrl, state, i); if((i >= exit && i <= exit + range)|| (i >= exit2 && i <= exit2 + range) || (i >= exit3 && i <= exit3 + range)) { ASSERT_EQ(REPEAT_MATCH, r); } else if (i > exit3 + range) { ASSERT_EQ(REPEAT_STALE, r); } else { ASSERT_EQ(REPEAT_NOMATCH, r); } } ASSERT_EQ(MAX(exit + range + 1, exit2), repeatNextMatch(info, ctrl, state, exit + range)); ASSERT_EQ(MAX(exit + range + 2, exit2), repeatNextMatch(info, ctrl, state, exit + range + 1)); ASSERT_EQ(exit2, repeatNextMatch(info, ctrl, state, exit2 - 1)); ASSERT_EQ(MAX(exit2 + range + 1, exit3), repeatNextMatch(info, ctrl, state, exit2 + range)); ASSERT_EQ(MAX(exit2 + range + 2, exit3), repeatNextMatch(info, ctrl, state, exit2 + range + 1)); ASSERT_EQ(exit3, repeatNextMatch(info, ctrl, state, exit3 - 1)); ASSERT_EQ(0, repeatNextMatch(info, ctrl, state, exit3 + range)); } static void test_sparse3entryNeg(const RepeatInfo *info, RepeatControl *ctrl, char *state, u64a diff) { SCOPED_TRACE(testing::Message() << "Repeat:" << *info); SCOPED_TRACE(diff); if (diff * 2 > info->repeatMax || diff < info->minPeriod) { return; } u64a offset = 1000; repeatStore(info, ctrl, state, offset, 0); ASSERT_EQ(offset, repeatLastTop(info, ctrl, state)); u64a offset2 = 1000 + diff; repeatStore(info, ctrl, state, offset2, 0); ASSERT_EQ(offset2, repeatLastTop(info, ctrl, state)); u64a offset3 = 1000 + 2 * diff; u64a exit3 = offset3 + info->repeatMin; repeatStore(info, ctrl, state, offset3, 0); ASSERT_EQ(offset3, repeatLastTop(info, ctrl, state)); u32 range = info->repeatMax - info->repeatMin; for (u32 i = offset3; i < offset + info->repeatMax * 2 + 100; i++) { SCOPED_TRACE(i); RepeatMatch r = repeatHasMatch(info, ctrl, state, i); if(i >= exit3 && i <= exit3 + range) { ASSERT_EQ(REPEAT_MATCH, r); } else if (i > exit3 + range) { ASSERT_EQ(REPEAT_STALE, r); } else { ASSERT_EQ(REPEAT_NOMATCH, r); } } } static void test_sparse3entryExpire(const RepeatInfo *info, RepeatControl *ctrl, char *state, u64a diff) { SCOPED_TRACE(testing::Message() << "Repeat:" << *info); SCOPED_TRACE(diff); if (diff * 2 > info->repeatMax || diff < info->minPeriod) { return; } u64a offset = 1000; repeatStore(info, ctrl, state, offset, 0); ASSERT_EQ(offset, repeatLastTop(info, ctrl, state)); u64a offset2 = 1000 + diff; repeatStore(info, ctrl, state, offset2, 1); ASSERT_EQ(offset2, repeatLastTop(info, ctrl, state)); u64a offset3 = 1000 + 2 * info->repeatMax; u64a exit3 = offset3 + info->repeatMin; repeatStore(info, ctrl, state, offset3, 1); ASSERT_EQ(offset3, repeatLastTop(info, ctrl, state)); u32 range = info->repeatMax - info->repeatMin; for (u32 i = offset3; i < offset3 + info->repeatMax + 100; i++) { SCOPED_TRACE(i); RepeatMatch r = repeatHasMatch(info, ctrl, state, i); if(i >= exit3 && i <= exit3 + range) { ASSERT_EQ(REPEAT_MATCH, r); } else if (i > exit3 + range) { ASSERT_EQ(REPEAT_STALE, r); } else { ASSERT_EQ(REPEAT_NOMATCH, r); } } } class SparseOptimalTest : public TestWithParam > { protected: virtual void SetUp() { u32 period; tie(period, test_info) = GetParam(); RepeatStateInfo rsi(REPEAT_SPARSE_OPTIMAL_P, test_info.repeatMin, test_info.repeatMax, period); ptr = new char[sizeof(RepeatInfo) + sizeof(u64a) * (rsi.patchSize + 2)]; info = (struct RepeatInfo *)ptr; info->type = REPEAT_SPARSE_OPTIMAL_P; info->repeatMin = test_info.repeatMin; info->repeatMax = test_info.repeatMax; info->minPeriod = period; info->packedCtrlSize = rsi.packedCtrlSize; info->stateSize = rsi.stateSize; info->horizon = rsi.horizon; std::copy(rsi.packedFieldSizes.begin(), rsi.packedFieldSizes.end(), info->packedFieldSizes); info->patchCount = rsi.patchCount; info->patchSize = rsi.patchSize; info->encodingSize = rsi.encodingSize; info->patchesOffset = rsi.patchesOffset; u32 repeatMax = info->patchSize; u64a *table = (u64a *)(ROUNDUP_PTR((ptr + sizeof(RepeatInfo)), alignof(u64a))); for (u32 i = 0; i < repeatMax + 1; i++) { table[i] = rsi.table[i]; } ctrl = new RepeatControl(); state_int = new char[info->stateSize + 7]; /* state may have mmbits */ state = state_int + 7; } virtual void TearDown() { delete ctrl; delete[] state_int; delete[] ptr; } RepeatTestInfo test_info; // Test params RepeatInfo *info; // Repeat info structure RepeatControl *ctrl; char *state; private: char *ptr; char *state_int; }; TEST_P(SparseOptimalTest, Simple1) { SCOPED_TRACE(testing::Message() << "Repeat: " << *info); u64a offset = 1000; repeatStore(info, ctrl, state, offset, 0); ASSERT_EQ(1000U, repeatLastTop(info, ctrl, state)); for (u32 i = 0; i < info->repeatMax * 2 + 100; i++) { SCOPED_TRACE(i); RepeatMatch r = repeatHasMatch(info, ctrl, state, offset + i); if (i >= info->repeatMin && i <= info->repeatMax) { ASSERT_EQ(REPEAT_MATCH, r); } else if (i > info->repeatMax) { ASSERT_EQ(REPEAT_STALE, r); } else { ASSERT_EQ(REPEAT_NOMATCH, r); } } u64a exp = 1000 + info->repeatMin; ASSERT_EQ(exp, repeatNextMatch(info, ctrl, state, 1000)); ASSERT_EQ(exp, repeatNextMatch(info, ctrl, state, 1001)); ASSERT_EQ(exp, repeatNextMatch(info, ctrl, state, 1002)); ASSERT_EQ(exp, repeatNextMatch(info, ctrl, state, 1003)); ASSERT_EQ(exp, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMin - 5)); ASSERT_EQ(exp, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMin - 4)); ASSERT_EQ(exp, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMin - 3)); ASSERT_EQ(exp, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMin - 2)); ASSERT_EQ(exp, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMin - 1)); ASSERT_EQ(0U, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMax)); ASSERT_EQ(0U, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMax + 1)); ASSERT_EQ(0U, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMax * 2 - 1)); ASSERT_EQ(0U, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMax * 2)); ASSERT_EQ(0U, repeatNextMatch(info, ctrl, state, 1000 + info->repeatMax * 2 + 1)); ASSERT_EQ(0U, repeatNextMatch(info, ctrl, state, 100000)); } TEST_P(SparseOptimalTest, TwoTopsNeg) { SCOPED_TRACE(testing::Message() << "Repeat: " << *info); u32 patch_count = info->patchCount; u32 patch_size = info->patchSize; u64a offset = 1000; u64a exit = offset + info->repeatMin; repeatStore(info, ctrl, state, offset, 0); ASSERT_EQ(offset, repeatLastTop(info, ctrl, state)); u64a offset2 = 1000 + patch_count * patch_size; u64a exit2 = offset2 + info->repeatMin; repeatStore(info, ctrl, state, offset2, 1); ASSERT_EQ(offset2, repeatLastTop(info, ctrl, state)); u32 range = info->repeatMax - info->repeatMin; for (u32 i = offset2; i < offset + info->repeatMax * 2 + 100; i++) { SCOPED_TRACE(i); RepeatMatch r = repeatHasMatch(info, ctrl, state, i); if (i >= exit2 && i <= exit2 + range) { ASSERT_EQ(REPEAT_MATCH, r); } else if (i > exit2 + range) { ASSERT_EQ(REPEAT_STALE, r); } else { ASSERT_EQ(REPEAT_NOMATCH, r); } } const struct RepeatRingControl *xs = (const struct RepeatRingControl *) ctrl; ASSERT_EQ(exit2, repeatNextMatch(info, ctrl, state, MAX(xs->offset, exit))); ASSERT_EQ(exit2, repeatNextMatch(info, ctrl, state, MAX(xs->offset, exit + 1))); ASSERT_EQ(exit2, repeatNextMatch(info, ctrl, state, exit2 - 1)); ASSERT_EQ(0, repeatNextMatch(info, ctrl, state, exit2 + range)); } TEST_P(SparseOptimalTest, Simple2a) { test_sparse2entry(info, ctrl, state, info->minPeriod); } TEST_P(SparseOptimalTest, Simple2b) { test_sparse2entry(info, ctrl, state, info->repeatMax - 1); } TEST_P(SparseOptimalTest, Simple2c) { test_sparse2entry(info, ctrl, state, info->repeatMax); } TEST_P(SparseOptimalTest, Simple2d) { test_sparse2entry(info, ctrl, state, info->minPeriod + 1); } TEST_P(SparseOptimalTest, Simple2e) { test_sparse2entry(info, ctrl, state, 2 * info->minPeriod - 1); } TEST_P(SparseOptimalTest, Simple3a) { test_sparse3entry(info, ctrl, state, info->minPeriod); test_sparse3entryNeg(info, ctrl, state, info->minPeriod); test_sparse3entryExpire(info, ctrl, state, info->minPeriod); } TEST_P(SparseOptimalTest, Simple3b) { test_sparse3entry(info, ctrl, state, info->repeatMax / 2 - 1); test_sparse3entryNeg(info, ctrl, state, info->repeatMax / 2 - 1); test_sparse3entryExpire(info, ctrl, state, info->repeatMax / 2 - 1); } TEST_P(SparseOptimalTest, Simple3c) { test_sparse3entry(info, ctrl, state, info->repeatMax / 2); test_sparse3entryNeg(info, ctrl, state, info->repeatMax / 2); test_sparse3entryExpire(info, ctrl, state, info->repeatMax / 2); } TEST_P(SparseOptimalTest, Simple3d) { test_sparse3entry(info, ctrl, state, info->minPeriod + 1); test_sparse3entryNeg(info, ctrl, state, info->minPeriod + 1); test_sparse3entryExpire(info, ctrl, state, info->minPeriod + 1); } TEST_P(SparseOptimalTest, Simple3e) { test_sparse3entry(info, ctrl, state, 2 * info->minPeriod - 1); test_sparse3entryNeg(info, ctrl, state, 2 * info->minPeriod - 1); test_sparse3entryExpire(info, ctrl, state, 2 * info->minPeriod - 1); } TEST_P(SparseOptimalTest, LargeGap) { SCOPED_TRACE(testing::Message() << "Repeat: " << *info); for (int i = 0; i < 64; i++) { u64a top1 = 1000; repeatStore(info, ctrl, state, top1, 0); // first top ASSERT_EQ(top1, repeatLastTop(info, ctrl, state)); // Add a second top after a gap of 2^i bytes. u64a top2 = top1 + (1ULL << i); if (top2 - top1 < info->minPeriod) { continue; // not a valid top } repeatStore(info, ctrl, state, top2, 1); // second top ASSERT_EQ(top2, repeatLastTop(info, ctrl, state)); } } TEST_P(SparseOptimalTest, ThreeTops) { SCOPED_TRACE(testing::Message() << "Repeat: " << *info); u32 patch_count = info->patchCount; u32 patch_size = info->patchSize; if (patch_count < 3) { return; } u64a offset = 1000; repeatStore(info, ctrl, state, offset, 0); ASSERT_EQ(offset, repeatLastTop(info, ctrl, state)); u64a offset2 = 1000 + 2 * patch_size - 1; u64a exit2 = offset2 + info->repeatMin; repeatStore(info, ctrl, state, offset2, 1); ASSERT_EQ(offset2, repeatLastTop(info, ctrl, state)); u64a offset3 = 1000 + patch_count * patch_size; u64a exit3 = offset3 + info->repeatMin; repeatStore(info, ctrl, state, offset3, 1); ASSERT_EQ(offset3, repeatLastTop(info, ctrl, state)); for (u32 i = offset2 + info->repeatMin; i <= offset2 + info->repeatMax; i++) { ASSERT_EQ(REPEAT_MATCH, repeatHasMatch(info, ctrl, state, i)); } for (u32 i = offset2 + info->repeatMax + 1; i < offset3 + info->repeatMin; i++) { ASSERT_EQ(REPEAT_NOMATCH, repeatHasMatch(info, ctrl, state, i)); } for (u32 i = offset3 + info->repeatMin; i <= offset3 + info->repeatMax; i++) { ASSERT_EQ(REPEAT_MATCH, repeatHasMatch(info, ctrl, state, i)); } u32 range = info->repeatMax - info->repeatMin; ASSERT_EQ(MAX(exit2 + range + 1, exit3), repeatNextMatch(info, ctrl, state, exit2 + range)); ASSERT_EQ(MAX(exit2 + range + 2, exit3), repeatNextMatch(info, ctrl, state, exit2 + range + 1)); ASSERT_EQ(exit3, repeatNextMatch(info, ctrl, state, exit3 - 1)); ASSERT_EQ(0, repeatNextMatch(info, ctrl, state, exit3 + range)); } TEST_P(SparseOptimalTest, FillTops) { SCOPED_TRACE(testing::Message() << "Repeat: " << *info); u32 patch_count = info->patchCount; u32 patch_size = info->patchSize; u32 min_period = info->minPeriod; u64a offset = 1000; u64a exit = offset + info->repeatMin; repeatStore(info, ctrl, state, offset, 0); ASSERT_EQ(offset, repeatLastTop(info, ctrl, state)); u64a offset2; for (u32 i = min_period; i < patch_count * patch_size; i += min_period) { offset2 = offset + i; repeatStore(info, ctrl, state, offset2, 1); ASSERT_EQ(offset2, repeatLastTop(info, ctrl, state)); } u64a exit2; for (u32 i = 0; i < patch_count * patch_size; i += min_period) { exit2 = exit + i; for (u32 j = exit2 + info->repeatMin; j <= offset + info->repeatMax; j++) { ASSERT_EQ(REPEAT_MATCH, repeatHasMatch(info, ctrl, state, j)); } } } INSTANTIATE_TEST_CASE_P(SparseOptimal, SparseOptimalTest, Combine(ValuesIn(sparsePeriods), ValuesIn(sparseRepeats)));