Use Boost ICL for RangeSet (RIPD-1473)

This commit is contained in:
Brad Chase
2017-05-09 12:05:30 -04:00
committed by seelabs
parent 56946e8128
commit 068048718e
8 changed files with 249 additions and 504 deletions

View File

@@ -1463,10 +1463,6 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\basics\impl\RangeSet.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\basics\impl\ResolverAsio.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>

View File

@@ -2007,9 +2007,6 @@
<ClCompile Include="..\..\src\ripple\basics\impl\mulDiv.cpp">
<Filter>ripple\basics\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\basics\impl\RangeSet.cpp">
<Filter>ripple\basics\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\basics\impl\ResolverAsio.cpp">
<Filter>ripple\basics\impl</Filter>
</ClCompile>

View File

@@ -305,7 +305,7 @@ private:
std::unique_ptr<LedgerReplay> replayData;
std::recursive_mutex mCompleteLock;
RangeSet mCompleteLedgers;
RangeSet<std::uint32_t> mCompleteLedgers;
std::unique_ptr <detail::LedgerCleaner> mLedgerCleaner;

View File

@@ -344,14 +344,14 @@ bool
LedgerMaster::haveLedger (std::uint32_t seq)
{
ScopedLockType sl (mCompleteLock);
return mCompleteLedgers.hasValue (seq);
return boost::icl::contains(mCompleteLedgers, seq);
}
void
LedgerMaster::clearLedger (std::uint32_t seq)
{
ScopedLockType sl (mCompleteLock);
return mCompleteLedgers.clearValue (seq);
mCompleteLedgers.erase (seq);
}
// returns Ledgers we have all the nodes for
@@ -365,15 +365,16 @@ LedgerMaster::getFullValidatedRange (std::uint32_t& minVal, std::uint32_t& maxVa
if (!maxVal)
return false;
boost::optional<std::uint32_t> maybeMin;
{
ScopedLockType sl (mCompleteLock);
minVal = mCompleteLedgers.prevMissing (maxVal);
maybeMin = prevMissing(mCompleteLedgers, maxVal);
}
if (minVal == RangeSet::absent)
if (maybeMin == boost::none)
minVal = maxVal;
else
++minVal;
minVal = 1 + *maybeMin;
return true;
}
@@ -382,23 +383,9 @@ LedgerMaster::getFullValidatedRange (std::uint32_t& minVal, std::uint32_t& maxVa
bool
LedgerMaster::getValidatedRange (std::uint32_t& minVal, std::uint32_t& maxVal)
{
// Validated ledger is likely not stored in the DB yet so we use the
// published ledger which is.
maxVal = mPubLedgerSeq.load();
if (!maxVal)
if (!getFullValidatedRange(minVal, maxVal))
return false;
{
ScopedLockType sl (mCompleteLock);
minVal = mCompleteLedgers.prevMissing (maxVal);
}
if (minVal == RangeSet::absent)
minVal = maxVal;
else
++minVal;
// Remove from the validated range any ledger sequences that may not be
// fully updated in the database yet
@@ -481,8 +468,8 @@ LedgerMaster::tryFill (
return;
{
ScopedLockType ml (mCompleteLock);
mCompleteLedgers.setRange (minHas, maxHas);
ScopedLockType ml(mCompleteLock);
mCompleteLedgers.insert(range(minHas, maxHas));
}
maxHas = minHas;
ledgerHashes = getHashesByIndex ((seq < 500)
@@ -502,7 +489,7 @@ LedgerMaster::tryFill (
{
ScopedLockType ml (mCompleteLock);
mCompleteLedgers.setRange (minHas, maxHas);
mCompleteLedgers.insert(range(minHas, maxHas));
}
{
ScopedLockType ml (m_mutex);
@@ -647,7 +634,7 @@ LedgerMaster::setFullLedger (
{
ScopedLockType ml (mCompleteLock);
mCompleteLedgers.setValue (ledger->info().seq);
mCompleteLedgers.insert (ledger->info().seq);
}
{
@@ -1282,7 +1269,7 @@ std::string
LedgerMaster::getCompleteLedgers ()
{
ScopedLockType sl (mCompleteLock);
return mCompleteLedgers.toString ();
return to_string(mCompleteLedgers);
}
boost::optional <NetClock::time_point>
@@ -1446,7 +1433,7 @@ void
LedgerMaster::setLedgerRangePresent (std::uint32_t minV, std::uint32_t maxV)
{
ScopedLockType sl (mCompleteLock);
mCompleteLedgers.setRange (minV, maxV);
mCompleteLedgers.insert(range(minV, maxV));
}
void
@@ -1477,12 +1464,9 @@ LedgerMaster::getPropertySource ()
void
LedgerMaster::clearPriorLedgers (LedgerIndex seq)
{
ScopedLockType sl (mCompleteLock);
for (LedgerIndex i = mCompleteLedgers.getFirst(); i < seq; ++i)
{
if (haveLedger (i))
clearLedger (i);
}
ScopedLockType sl(mCompleteLock);
if (seq > 0)
mCompleteLedgers.erase(range(0u, seq - 1));
}
void
@@ -1546,137 +1530,141 @@ void LedgerMaster::doAdvance ()
(mValidLedgerSeq == mPubLedgerSeq) &&
(getValidatedLedgerAge() < MAX_LEDGER_AGE_ACQUIRE))
{ // We are in sync, so can acquire
std::uint32_t missing;
boost::optional<std::uint32_t> maybeMissing;
{
ScopedLockType sl (mCompleteLock);
missing = mCompleteLedgers.prevMissing(
mPubLedger->info().seq);
maybeMissing =
prevMissing(mCompleteLedgers, mPubLedger->info().seq);
}
JLOG (m_journal.trace())
<< "tryAdvance discovered missing " << missing;
if ((missing != RangeSet::absent) && (missing > 0) &&
shouldAcquire (mValidLedgerSeq, ledger_history_,
app_.getSHAMapStore ().getCanDelete (), missing) &&
((mFillInProgress == 0) || (missing > mFillInProgress)))
if (maybeMissing)
{
JLOG (m_journal.trace())
<< "advanceThread should acquire";
std::uint32_t missing = *maybeMissing;
JLOG(m_journal.trace())
<< "tryAdvance discovered missing " << missing;
if ((missing > 0) &&
shouldAcquire(mValidLedgerSeq, ledger_history_,
app_.getSHAMapStore().getCanDelete(), missing) &&
((mFillInProgress == 0) || (missing > mFillInProgress)))
{
ScopedUnlockType sl(m_mutex);
auto hash = getLedgerHashForHistory (missing);
if (hash)
JLOG(m_journal.trace())
<< "advanceThread should acquire";
{
assert(hash->isNonZero());
auto ledger = getLedgerByHash (*hash);
if (!ledger)
ScopedUnlockType sl(m_mutex);
auto hash = getLedgerHashForHistory(missing);
if (hash)
{
if (!app_.getInboundLedgers().isFailure (
*hash))
assert(hash->isNonZero());
auto ledger = getLedgerByHash(*hash);
if (!ledger)
{
ledger =
app_.getInboundLedgers().acquire(
*hash, missing,
InboundLedger::fcHISTORY);
if (! ledger && (missing > 32600) &&
shouldFetchPack (missing))
if (!app_.getInboundLedgers().isFailure(
*hash))
{
JLOG (m_journal.trace()) <<
"tryAdvance want fetch pack " <<
missing;
fetch_seq_ = missing;
getFetchPack(*hash, missing);
ledger =
app_.getInboundLedgers().acquire(
*hash, missing,
InboundLedger::fcHISTORY);
if (!ledger && (missing > 32600) &&
shouldFetchPack(missing))
{
JLOG(m_journal.trace()) <<
"tryAdvance want fetch pack " <<
missing;
fetch_seq_ = missing;
getFetchPack(*hash, missing);
}
else
JLOG(m_journal.trace()) <<
"tryAdvance no fetch pack for " <<
missing;
}
else
JLOG (m_journal.trace()) <<
"tryAdvance no fetch pack for " <<
missing;
JLOG(m_journal.debug()) <<
"tryAdvance found failed acquire";
}
else
JLOG (m_journal.debug()) <<
"tryAdvance found failed acquire";
}
if (ledger)
{
auto seq = ledger->info().seq;
assert(seq == missing);
JLOG (m_journal.trace())
if (ledger)
{
auto seq = ledger->info().seq;
assert(seq == missing);
JLOG(m_journal.trace())
<< "tryAdvance acquired "
<< ledger->info().seq;
setFullLedger(
ledger,
false,
false);
auto const& parent = ledger->info().parentHash;
setFullLedger(
ledger,
false,
false);
auto const& parent = ledger->info().parentHash;
int fillInProgress;
{
ScopedLockType lock(m_mutex);
mHistLedger = ledger;
fillInProgress = mFillInProgress;
}
if (fillInProgress == 0 &&
getHashByIndex(seq - 1, app_) == parent)
{
int fillInProgress;
{
// Previous ledger is in DB
ScopedLockType lock(m_mutex);
mFillInProgress = ledger->info().seq;
mHistLedger = ledger;
fillInProgress = mFillInProgress;
}
app_.getJobQueue().addJob(
jtADVANCE, "tryFill",
[this, ledger] (Job& j) {
if (fillInProgress == 0 &&
getHashByIndex(seq - 1, app_) == parent)
{
{
// Previous ledger is in DB
ScopedLockType lock(m_mutex);
mFillInProgress = ledger->info().seq;
}
app_.getJobQueue().addJob(
jtADVANCE, "tryFill",
[this, ledger](Job& j) {
tryFill(j, ledger);
});
}
}
progress = true;
progress = true;
}
else
{
try
{
for (int i = 0; i < ledger_fetch_size_; ++i)
{
std::uint32_t seq = missing - i;
auto hash2 =
getLedgerHashForHistory(seq);
if (hash2)
{
assert(hash2->isNonZero());
app_.getInboundLedgers().acquire
(*hash2, seq,
InboundLedger::fcHISTORY);
}
}
}
catch (std::exception const&)
{
JLOG(m_journal.warn()) <<
"Threw while prefetching";
}
}
}
else
{
try
{
for (int i = 0; i < ledger_fetch_size_; ++i)
{
std::uint32_t seq = missing - i;
auto hash2 =
getLedgerHashForHistory(seq);
if (hash2)
{
assert(hash2->isNonZero());
app_.getInboundLedgers().acquire
(*hash2, seq,
InboundLedger::fcHISTORY);
}
}
}
catch (std::exception const&)
{
JLOG (m_journal.warn()) <<
"Threw while prefetching";
}
JLOG(m_journal.fatal()) <<
"Can't find ledger following prevMissing " <<
missing;
JLOG(m_journal.fatal()) << "Pub:" <<
mPubLedgerSeq << " Val:" << mValidLedgerSeq;
JLOG(m_journal.fatal()) << "Ledgers: " <<
app_.getLedgerMaster().getCompleteLedgers();
clearLedger(missing + 1);
progress = true;
}
}
else
if (mValidLedgerSeq != mPubLedgerSeq)
{
JLOG (m_journal.fatal()) <<
"Can't find ledger following prevMissing " <<
missing;
JLOG (m_journal.fatal()) << "Pub:" <<
mPubLedgerSeq << " Val:" << mValidLedgerSeq;
JLOG (m_journal.fatal()) << "Ledgers: " <<
app_.getLedgerMaster().getCompleteLedgers();
clearLedger (missing + 1);
JLOG(m_journal.debug()) <<
"tryAdvance found last valid changed";
progress = true;
}
}
if (mValidLedgerSeq != mPubLedgerSeq)
{
JLOG (m_journal.debug()) <<
"tryAdvance found last valid changed";
progress = true;
}
}
}
else

View File

@@ -20,72 +20,113 @@
#ifndef RIPPLE_BASICS_RANGESET_H_INCLUDED
#define RIPPLE_BASICS_RANGESET_H_INCLUDED
#include <cstdint>
#include <map>
#include <string>
#include <boost/optional.hpp>
#include <boost/icl/closed_interval.hpp>
#include <boost/icl/interval_set.hpp>
namespace ripple {
/** A sparse set of integers. */
// VFALCO TODO Replace with juce::SparseSet
class RangeSet
namespace ripple
{
public:
static const std::uint32_t absent = static_cast <std::uint32_t> (-1);
public:
RangeSet () { }
/** A closed interval over the domain T.
bool hasValue (std::uint32_t) const;
For an instance ClosedInterval c, this represents the closed interval
(c.first(), c.last()). A single element interval has c.first() == c.last().
std::uint32_t getFirst () const;
std::uint32_t getNext (std::uint32_t) const;
std::uint32_t getLast () const;
std::uint32_t getPrev (std::uint32_t) const;
This is simply a type-alias for boost interval container library interval
set, so users should consult that documentation for available supporting
member and free functions.
*/
template <class T>
using ClosedInterval = boost::icl::closed_interval<T>;
// largest number not in the set that is less than the given number
std::uint32_t prevMissing (std::uint32_t) const;
/** Create a closed range interval
// Add an item to the set
void setValue (std::uint32_t);
Helper function to create a closed range interval without having to qualify
the template argument.
*/
template <class T>
ClosedInterval<T>
range(T low, T high)
{
return ClosedInterval<T>(low, high);
}
// Add the closed interval to the set
void setRange (std::uint32_t, std::uint32_t);
/** A set of closed intervals over the domain T.
void clearValue (std::uint32_t);
Represents a set of values of the domain T using the minimum number
of disjoint ClosedInterval<T>. This is useful to represent ranges of
T where a few instances are missing, e.g. the set 1-5,8-9,11-14.
std::string toString () const;
This is simply a type-alias for boost interval container library interval
set, so users should consult that documentation for available supporting
member and free functions.
*/
template <class T>
using RangeSet = boost::icl::interval_set<T, std::less, ClosedInterval<T>>;
/** Returns the sum of the Lebesgue measures of all sub-ranges. */
std::size_t
lebesgue_sum() const;
/** Check invariants of the data.
/** Convert a ClosedInterval to a styled string
This is for diagnostics, and does nothing in release builds.
*/
void checkInternalConsistency () const noexcept;
The styled string is
"c.first()-c.last()" if c.first() != c.last()
"c.first()" if c.first() == c.last()
private:
void simplify ();
@param ci The closed interval to convert
@return The style string
*/
template <class T>
std::string to_string(ClosedInterval<T> const & ci)
{
if (ci.first() == ci.last())
return std::to_string(ci.first());
return std::to_string(ci.first()) + "-" + std::to_string(ci.last());
}
private:
using Map = std::map <std::uint32_t, std::uint32_t>;
/** Convert the given RangeSet to a styled string.
using const_iterator = Map::const_iterator;
using const_reverse_iterator = Map::const_reverse_iterator;
using value_type = Map::value_type;
using iterator = Map::iterator;
The styled string represention is the set of disjoint intervals joined by
commas. The string "empty" is returned if the set is empty.
static bool contains (value_type const& it, std::uint32_t v)
@param rs The rangeset to convert
@return The styled string
*/
template <class T>
std::string to_string(RangeSet<T> const & rs)
{
using ripple::to_string;
if (rs.empty())
return "empty";
std::string res = "";
for (auto const & interval : rs)
{
return (it.first <= v) && (it.second >= v);
if (!res.empty())
res += ",";
res += to_string(interval);
}
return res;
}
// First is lowest value in range, last is highest value in range
Map mRanges;
};
} // ripple
/** Find the largest value not in the set that is less than a given value.
@param rs The set of interest
@param t The value that must be larger than the result
@param minVal (Default is 0) The smallest allowed value
@return The largest v such that minV <= v < t and !contains(rs, v) or
boost::none if no such v exists.
*/
template <class T>
boost::optional<T>
prevMissing(RangeSet<T> const & rs, T t, T minVal = 0)
{
if (rs.empty() || t == minVal)
return boost::none;
RangeSet<T> tgt{ ClosedInterval<T>{minVal, t - 1} };
tgt -= rs;
if (tgt.empty())
return boost::none;
return boost::icl::last(tgt);
}
} // namespace ripple
#endif

View File

@@ -1,270 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/RangeSet.h>
#include <ripple/beast/core/LexicalCast.h>
#include <boost/foreach.hpp>
#include <cassert>
#include <cstdint>
namespace ripple {
// VFALCO NOTE std::min and std::max not good enough?
// NOTE Why isn't this written as a template?
// TODO Replace this with std calls.
//
inline std::uint32_t min (std::uint32_t x, std::uint32_t y)
{
return (x < y) ? x : y;
}
inline std::uint32_t max (std::uint32_t x, std::uint32_t y)
{
return (x > y) ? x : y;
}
bool RangeSet::hasValue (std::uint32_t v) const
{
for (auto const& it : mRanges)
{
if (contains (it, v))
return true;
}
return false;
}
std::uint32_t RangeSet::getFirst () const
{
const_iterator it = mRanges.begin ();
if (it == mRanges.end ())
return absent;
return it->first;
}
std::uint32_t RangeSet::getNext (std::uint32_t v) const
{
for (auto const& it : mRanges)
{
if (it.first > v)
return it.first;
if (contains (it, v + 1))
return v + 1;
}
return absent;
}
std::uint32_t RangeSet::getLast () const
{
const_reverse_iterator it = mRanges.rbegin ();
if (it == mRanges.rend ())
return absent;
return it->second;
}
std::uint32_t RangeSet::getPrev (std::uint32_t v) const
{
BOOST_REVERSE_FOREACH (const value_type & it, mRanges)
{
if (it.second < v)
return it.second;
if (contains (it, v + 1))
return v - 1;
}
return absent;
}
// Return the largest number not in the set that is less than the given number
//
std::uint32_t RangeSet::prevMissing (std::uint32_t v) const
{
std::uint32_t result = absent;
if (v != 0)
{
checkInternalConsistency ();
// Handle the case where the loop reaches the terminating condition
//
result = v - 1;
for (const_reverse_iterator cur = mRanges.rbegin (); cur != mRanges.rend (); ++cur)
{
// See if v-1 is in the range
if (contains (*cur, result))
{
result = cur->first - 1;
break;
}
}
}
assert (result == absent || !hasValue (result));
return result;
}
void RangeSet::setValue (std::uint32_t v)
{
if (!hasValue (v))
{
mRanges[v] = v;
simplify ();
}
}
void RangeSet::setRange (std::uint32_t minV, std::uint32_t maxV)
{
while (hasValue (minV))
{
++minV;
if (minV >= maxV)
return;
}
mRanges[minV] = maxV;
simplify ();
}
void RangeSet::clearValue (std::uint32_t v)
{
for (iterator it = mRanges.begin (); it != mRanges.end (); ++it)
{
if (contains (*it, v))
{
if (it->first == v)
{
if (it->second == v)
{
mRanges.erase (it);
}
else
{
std::uint32_t oldEnd = it->second;
mRanges.erase(it);
mRanges[v + 1] = oldEnd;
}
}
else if (it->second == v)
{
-- (it->second);
}
else
{
std::uint32_t oldEnd = it->second;
it->second = v - 1;
mRanges[v + 1] = oldEnd;
}
checkInternalConsistency();
return;
}
}
}
std::string RangeSet::toString () const
{
std::string ret;
for (auto const& it : mRanges)
{
if (!ret.empty ())
ret += ",";
if (it.first == it.second)
ret += beast::lexicalCastThrow <std::string> ((it.first));
else
ret += beast::lexicalCastThrow <std::string> (it.first) + "-"
+ beast::lexicalCastThrow <std::string> (it.second);
}
if (ret.empty ())
return "empty";
return ret;
}
void RangeSet::simplify ()
{
iterator it = mRanges.begin ();
while (1)
{
iterator nit = it;
if (++nit == mRanges.end ())
{
checkInternalConsistency();
return;
}
if (it->second >= (nit->first - 1))
{
// ranges overlap
it->second = std::max(it->second, nit->second);
mRanges.erase (nit);
}
else
{
it = nit;
}
}
}
std::size_t
RangeSet::lebesgue_sum() const
{
std::size_t sum = mRanges.size();
for (auto const& e : mRanges)
sum += e.second - e.first;
return sum;
}
void RangeSet::checkInternalConsistency () const noexcept
{
#ifndef NDEBUG
if (mRanges.size () > 1)
{
const_iterator const last = std::prev (mRanges.end ());
for (const_iterator cur = mRanges.begin (); cur != last; ++cur)
{
const_iterator const next = std::next (cur);
assert (cur->first <= cur->second);
assert (next->first <= next->second);
assert (cur->second + 1 < next->first);
}
}
else if (mRanges.size () == 1)
{
const_iterator const iter = mRanges.begin ();
assert (iter->first <= iter->second);
}
#endif
}
} // ripple

View File

@@ -26,7 +26,6 @@
#include <ripple/basics/impl/Log.cpp>
#include <ripple/basics/impl/make_SSLContext.cpp>
#include <ripple/basics/impl/mulDiv.cpp>
#include <ripple/basics/impl/RangeSet.cpp>
#include <ripple/basics/impl/ResolverAsio.cpp>
#include <ripple/basics/impl/strHex.cpp>
#include <ripple/basics/impl/StringUtilities.cpp>

View File

@@ -21,14 +21,15 @@
#include <ripple/basics/RangeSet.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace ripple
{
class RangeSet_test : public beast::unit_test::suite
{
public:
RangeSet createPredefinedSet ()
void
testPrevMissing()
{
RangeSet set;
testcase("prevMissing");
// Set will include:
// [ 0, 5]
@@ -36,59 +37,52 @@ public:
// [20,25]
// etc...
for (int i = 0; i < 10; ++i)
set.setRange (10 * i, 10 * i + 5);
RangeSet<std::uint32_t> set;
for (std::uint32_t i = 0; i < 10; ++i)
set.insert(range(10 * i, 10 * i + 5));
return set;
}
void testMembership ()
{
testcase ("membership");
RangeSet r1, r2;
r1.setRange (1, 10);
r1.clearValue (5);
r1.setRange (11, 20);
r2.setRange (1, 4);
r2.setRange (6, 10);
r2.setRange (10, 20);
BEAST_EXPECT(!r1.hasValue (5));
BEAST_EXPECT(r2.hasValue (9));
}
void testPrevMissing ()
{
testcase ("prevMissing");
RangeSet const set = createPredefinedSet ();
for (int i = 0; i < 100; ++i)
for (std::uint32_t i = 1; i < 100; ++i)
{
int const oneBelowRange = (10*(i/10))-1;
boost::optional<std::uint32_t> expected;
// no prev missing in domain for i <= 6
if (i > 6)
{
std::uint32_t const oneBelowRange = (10 * (i / 10)) - 1;
int const expectedPrevMissing =
((i % 10) > 6) ? (i-1) : oneBelowRange;
BEAST_EXPECT(set.prevMissing (i) == expectedPrevMissing);
expected = ((i % 10) > 6) ? (i - 1) : oneBelowRange;
}
BEAST_EXPECT(prevMissing(set, i) == expected);
}
}
void run ()
void
testToString()
{
testMembership ();
testcase("toString");
testPrevMissing ();
RangeSet<std::uint32_t> set;
BEAST_EXPECT(to_string(set) == "empty");
// TODO: Traverse functions must be tested
set.insert(1);
BEAST_EXPECT(to_string(set) == "1");
set.insert(range(4u, 6u));
BEAST_EXPECT(to_string(set) == "1,4-6");
set.insert(2);
BEAST_EXPECT(to_string(set) == "1-2,4-6");
set.erase(range(4u, 5u));
BEAST_EXPECT(to_string(set) == "1-2,6");
}
void
run()
{
testPrevMissing();
testToString();
}
};
BEAST_DEFINE_TESTSUITE(RangeSet,ripple_basics,ripple);
} // ripple
BEAST_DEFINE_TESTSUITE(RangeSet, ripple_basics, ripple);
} // namespace ripple