API for improved Unit Testing (RIPD-432):

* Added new test APIs allowing easy ways to create ledgers, apply
  transactions to them, close and advance them.

* Moved Ledger tests from Ledger.cpp to Ledger.test.cpp.

* Changed several TransactionEngine log priorities from lsINFO to lsDEBUG to
  reduce unnecessary verbosity in the log messages while running these tests.

* Moved LedgerConsensus:applyTransactions from a private member function to a
  free function so that it could be accessed externally, and without having to
  reference a LedgerConsensus object.  This was done to facilitate the new
  testing API.
This commit is contained in:
Howard Hinnant
2014-11-19 14:04:36 -05:00
committed by Nik Bougalis
parent c62ccf4870
commit eafa6f960f
8 changed files with 505 additions and 210 deletions

View File

@@ -1452,174 +1452,6 @@ private:
msg, protocol::mtHAVE_SET)));
}
/** Apply a set of transactions to a ledger
@param set The set of transactions to apply
@param applyLedger The ledger to which the transactions should
be applied.
@param checkLedger A reference ledger for determining error
messages (typically new last closed ledger).
@param retriableTransactions collect failed transactions in this set
@param openLgr true if applyLedger is open, else false.
*/
void applyTransactions (SHAMap::ref set, Ledger::ref applyLedger,
Ledger::ref checkLedger, CanonicalTXSet& retriableTransactions,
bool openLgr)
{
TransactionEngine engine (applyLedger);
if (set)
{
for (SHAMapItem::pointer item = set->peekFirstItem (); !!item;
item = set->peekNextItem (item->getTag ()))
{
// If the checkLedger doesn't have the transaction
if (!checkLedger->hasTransaction (item->getTag ()))
{
// Then try to apply the transaction to applyLedger
WriteLog (lsINFO, LedgerConsensus) <<
"Processing candidate transaction: " << item->getTag ();
try
{
SerializerIterator sit (item->peekSerializer ());
STTx::pointer txn
= std::make_shared<STTx>(sit);
if (applyTransaction (engine, txn,
openLgr, true) == resultRetry)
{
// On failure, stash the failed transaction for
// later retry.
retriableTransactions.push_back (txn);
}
}
catch (...)
{
WriteLog (lsWARNING, LedgerConsensus) << " Throws";
}
}
}
}
int changes;
bool certainRetry = true;
// Attempt to apply all of the retriable transactions
for (int pass = 0; pass < LEDGER_TOTAL_PASSES; ++pass)
{
WriteLog (lsDEBUG, LedgerConsensus) << "Pass: " << pass << " Txns: "
<< retriableTransactions.size ()
<< (certainRetry ? " retriable" : " final");
changes = 0;
auto it = retriableTransactions.begin ();
while (it != retriableTransactions.end ())
{
try
{
switch (applyTransaction (engine, it->second,
openLgr, certainRetry))
{
case resultSuccess:
it = retriableTransactions.erase (it);
++changes;
break;
case resultFail:
it = retriableTransactions.erase (it);
break;
case resultRetry:
++it;
}
}
catch (...)
{
WriteLog (lsWARNING, LedgerConsensus)
<< "Transaction throws";
it = retriableTransactions.erase (it);
}
}
WriteLog (lsDEBUG, LedgerConsensus) << "Pass: "
<< pass << " finished " << changes << " changes";
// A non-retry pass made no changes
if (!changes && !certainRetry)
return;
// Stop retriable passes
if ((!changes) || (pass >= LEDGER_RETRY_PASSES))
certainRetry = false;
}
// If there are any transactions left, we must have
// tried them in at least one final pass
assert (retriableTransactions.empty() || !certainRetry);
}
/** Apply a transaction to a ledger
@param engine The transaction engine containing the ledger.
@param txn The transaction to be applied to ledger.
@param openLedger true if ledger is open
@param retryAssured true if the transaction should be retried on failure.
@return One of resultSuccess, resultFail or resultRetry.
*/
int applyTransaction (TransactionEngine& engine
, STTx::ref txn, bool openLedger, bool retryAssured)
{
// Returns false if the transaction has need not be retried.
TransactionEngineParams parms = openLedger ? tapOPEN_LEDGER : tapNONE;
if (retryAssured)
{
parms = static_cast<TransactionEngineParams> (parms | tapRETRY);
}
if (getApp().getHashRouter ().setFlag (txn->getTransactionID ()
, SF_SIGGOOD))
{
parms = static_cast<TransactionEngineParams>
(parms | tapNO_CHECK_SIGN);
}
WriteLog (lsDEBUG, LedgerConsensus) << "TXN "
<< txn->getTransactionID ()
<< (openLedger ? " open" : " closed")
<< (retryAssured ? "/retry" : "/final");
WriteLog (lsTRACE, LedgerConsensus) << txn->getJson (0);
try
{
bool didApply;
TER result = engine.applyTransaction (*txn, parms, didApply);
if (didApply)
{
WriteLog (lsDEBUG, LedgerConsensus)
<< "Transaction success: " << transHuman (result);
return resultSuccess;
}
if (isTefFailure (result) || isTemMalformed (result) ||
isTelLocal (result))
{
// failure
WriteLog (lsDEBUG, LedgerConsensus)
<< "Transaction failure: " << transHuman (result);
return resultFail;
}
WriteLog (lsDEBUG, LedgerConsensus)
<< "Transaction retry: " << transHuman (result);
return resultRetry;
}
catch (...)
{
WriteLog (lsWARNING, LedgerConsensus) << "Throws";
return resultFail;
}
}
/**
Round the close time to the close time resolution.
@@ -2164,4 +1996,173 @@ make_LedgerConsensus (LedgerConsensus::clock_type& clock, LocalTxs& localtx,
prevLCLHash, previousLedger, closeTime, feeVote);
}
/** Apply a transaction to a ledger
@param engine The transaction engine containing the ledger.
@param txn The transaction to be applied to ledger.
@param openLedger true if ledger is open
@param retryAssured true if the transaction should be retried on failure.
@return One of resultSuccess, resultFail or resultRetry.
*/
static
int applyTransaction (TransactionEngine& engine
, STTx::ref txn, bool openLedger, bool retryAssured)
{
// Returns false if the transaction has need not be retried.
TransactionEngineParams parms = openLedger ? tapOPEN_LEDGER : tapNONE;
if (retryAssured)
{
parms = static_cast<TransactionEngineParams> (parms | tapRETRY);
}
if (getApp().getHashRouter ().setFlag (txn->getTransactionID ()
, SF_SIGGOOD))
{
parms = static_cast<TransactionEngineParams>
(parms | tapNO_CHECK_SIGN);
}
WriteLog (lsDEBUG, LedgerConsensus) << "TXN "
<< txn->getTransactionID ()
<< (openLedger ? " open" : " closed")
<< (retryAssured ? "/retry" : "/final");
WriteLog (lsTRACE, LedgerConsensus) << txn->getJson (0);
try
{
bool didApply;
TER result = engine.applyTransaction (*txn, parms, didApply);
if (didApply)
{
WriteLog (lsDEBUG, LedgerConsensus)
<< "Transaction success: " << transHuman (result);
return LedgerConsensusImp::resultSuccess;
}
if (isTefFailure (result) || isTemMalformed (result) ||
isTelLocal (result))
{
// failure
WriteLog (lsDEBUG, LedgerConsensus)
<< "Transaction failure: " << transHuman (result);
return LedgerConsensusImp::resultFail;
}
WriteLog (lsDEBUG, LedgerConsensus)
<< "Transaction retry: " << transHuman (result);
return LedgerConsensusImp::resultRetry;
}
catch (...)
{
WriteLog (lsWARNING, LedgerConsensus) << "Throws";
return LedgerConsensusImp::resultFail;
}
}
/** Apply a set of transactions to a ledger
@param set The set of transactions to apply
@param applyLedger The ledger to which the transactions should
be applied.
@param checkLedger A reference ledger for determining error
messages (typically new last closed ledger).
@param retriableTransactions collect failed transactions in this set
@param openLgr true if applyLedger is open, else false.
*/
void applyTransactions (SHAMap::ref set, Ledger::ref applyLedger,
Ledger::ref checkLedger, CanonicalTXSet& retriableTransactions,
bool openLgr)
{
TransactionEngine engine (applyLedger);
if (set)
{
for (SHAMapItem::pointer item = set->peekFirstItem (); !!item;
item = set->peekNextItem (item->getTag ()))
{
// If the checkLedger doesn't have the transaction
if (!checkLedger->hasTransaction (item->getTag ()))
{
// Then try to apply the transaction to applyLedger
WriteLog (lsINFO, LedgerConsensus) <<
"Processing candidate transaction: " << item->getTag ();
try
{
SerializerIterator sit (item->peekSerializer ());
STTx::pointer txn
= std::make_shared<STTx>(sit);
if (applyTransaction (engine, txn,
openLgr, true) == LedgerConsensusImp::resultRetry)
{
// On failure, stash the failed transaction for
// later retry.
retriableTransactions.push_back (txn);
}
}
catch (...)
{
WriteLog (lsWARNING, LedgerConsensus) << " Throws";
}
}
}
}
int changes;
bool certainRetry = true;
// Attempt to apply all of the retriable transactions
for (int pass = 0; pass < LEDGER_TOTAL_PASSES; ++pass)
{
WriteLog (lsDEBUG, LedgerConsensus) << "Pass: " << pass << " Txns: "
<< retriableTransactions.size ()
<< (certainRetry ? " retriable" : " final");
changes = 0;
auto it = retriableTransactions.begin ();
while (it != retriableTransactions.end ())
{
try
{
switch (applyTransaction (engine, it->second,
openLgr, certainRetry))
{
case LedgerConsensusImp::resultSuccess:
it = retriableTransactions.erase (it);
++changes;
break;
case LedgerConsensusImp::resultFail:
it = retriableTransactions.erase (it);
break;
case LedgerConsensusImp::resultRetry:
++it;
}
}
catch (...)
{
WriteLog (lsWARNING, LedgerConsensus)
<< "Transaction throws";
it = retriableTransactions.erase (it);
}
}
WriteLog (lsDEBUG, LedgerConsensus) << "Pass: "
<< pass << " finished " << changes << " changes";
// A non-retry pass made no changes
if (!changes && !certainRetry)
return;
// Stop retriable passes
if ((!changes) || (pass >= LEDGER_RETRY_PASSES))
certainRetry = false;
}
// If there are any transactions left, we must have
// tried them in at least one final pass
assert (retriableTransactions.empty() || !certainRetry);
}
} // ripple