RTBKit
0.9
Open-source framework to create real-time ad bidding systems.
|
00001 /* banker_account_test.cc 00002 Jeremy Barnes, 16 November 2012 00003 Copyright (c) 2012 Datacratic. All rights reserved. 00004 00005 Test for Banker accounts. 00006 */ 00007 00008 #define BOOST_TEST_MAIN 00009 #define BOOST_TEST_DYN_LINK 00010 00011 #include <boost/test/unit_test.hpp> 00012 #include "jml/arch/format.h" 00013 #include "jml/arch/exception_handler.h" 00014 #include "jml/utils/guard.h" 00015 #include "rtbkit/common/account_key.h" 00016 #include "rtbkit/core/banker/account.h" 00017 #include "jml/utils/environment.h" 00018 #include <boost/thread/thread.hpp> 00019 #include "jml/arch/atomic_ops.h" 00020 #include "jml/arch/timers.h" 00021 #include "jml/utils/ring_buffer.h" 00022 00023 00024 using namespace std; 00025 using namespace ML; 00026 using namespace Datacratic; 00027 using namespace RTBKIT; 00028 00029 BOOST_AUTO_TEST_CASE( test_account_set_budget ) 00030 { 00031 Account account; 00032 00033 /* set initial budget */ 00034 account.setBudget(USD(8)); 00035 BOOST_CHECK_EQUAL(account.balance, USD(8)); 00036 BOOST_CHECK_EQUAL(account.budgetIncreases, USD(8)); 00037 BOOST_CHECK_EQUAL(account.budgetDecreases, USD(0)); 00038 00039 /* adjust budget down: 00040 1 usd added to adjustmentsOut (deduced from budget) */ 00041 account.setBudget(USD(7)); 00042 BOOST_CHECK_EQUAL(account.balance, USD(7)); 00043 BOOST_CHECK_EQUAL(account.budgetIncreases, USD(8)); 00044 BOOST_CHECK_EQUAL(account.budgetDecreases, USD(1)); 00045 00046 /* adjust budget up: 00047 1 usd added to adjustmentsIn to balance with adj.Out */ 00048 account.setBudget(USD(8)); 00049 BOOST_CHECK_EQUAL(account.balance, USD(8)); 00050 BOOST_CHECK_EQUAL(account.budgetIncreases, USD(9)); 00051 BOOST_CHECK_EQUAL(account.budgetDecreases, USD(1)); 00052 00053 /* adjust budget up: 00054 3 usd put in budget */ 00055 account.setBudget(USD(13)); 00056 BOOST_CHECK_EQUAL(account.balance, USD(13)); 00057 BOOST_CHECK_EQUAL(account.budgetIncreases, USD(14)); 00058 BOOST_CHECK_EQUAL(account.budgetDecreases, USD(1)); 00059 00060 /* negative adjustments must be limited by "available": 00061 of the previous 13 usd budget, 10 already have been spent, which means 00062 we cannot go below 10 USD, even though 3 USD are still available 00063 */ 00064 account.allocatedOut = USD(10); 00065 account.balance = USD(3); 00066 account.checkInvariants(); 00067 { 00068 auto notrace = Set_Trace_Exceptions(false); 00069 BOOST_CHECK_THROW(account.setBudget(USD(9)), 00070 std::exception); 00071 } 00072 00073 /* we adjust the budget down the the least possible value and ensure that 00074 "available" is adjusted by taking the "allocatedOut" into account */ 00075 account.setBudget(USD(10)); 00076 BOOST_CHECK_EQUAL(account.balance, USD(0)); 00077 } 00078 00079 BOOST_AUTO_TEST_CASE( test_account_tojson ) 00080 { 00081 Account account; 00082 00083 Json::Value testState = Json::parse( 00084 "{ 'md': { 'objectType': 'Account'," 00085 " 'version': 1 }," 00086 " 'type': 'none'," 00087 " 'budgetIncreases': {}," 00088 " 'budgetDecreases': {}," 00089 " 'spent': {}," 00090 " 'recycledIn': {}," 00091 " 'recycledOut': {}," 00092 " 'allocatedIn': {}," 00093 " 'allocatedOut': {}," 00094 " 'commitmentsMade': {}," 00095 " 'commitmentsRetired': {}," 00096 " 'adjustmentsIn': {}," 00097 " 'adjustmentsOut': {}," 00098 " 'lineItems': {}," 00099 " 'adjustmentLineItems': {}}"); 00100 00101 /* fresh and clean account */ 00102 BOOST_CHECK_EQUAL(account.toJson(), testState); 00103 00104 /* account with a 10 USD budget */ 00105 account.setBudget(USD(10)); 00106 testState["budgetIncreases"]["USD/1M"] = 10000000; 00107 BOOST_CHECK_EQUAL(account.toJson(), testState); 00108 } 00109 00110 BOOST_AUTO_TEST_CASE( test_account_hierarchy ) 00111 { 00112 Account budgetAccount; 00113 budgetAccount.setBudget(USD(10)); 00114 00115 Account commitmentAccount, spendAccount; 00116 00117 ShadowAccount shadowCommitmentAccount; 00118 ShadowAccount shadowSpendAccount; 00119 00120 commitmentAccount.setBalance(budgetAccount, USD(2)); 00121 00122 BOOST_CHECK_EQUAL(budgetAccount.balance, USD(8)); 00123 BOOST_CHECK_EQUAL(commitmentAccount.balance, USD(2)); 00124 00125 shadowCommitmentAccount.syncFromMaster(commitmentAccount); 00126 shadowSpendAccount.syncFromMaster(spendAccount); 00127 00128 BOOST_CHECK_EQUAL(shadowCommitmentAccount.balance, USD(2)); 00129 BOOST_CHECK_EQUAL(shadowSpendAccount.balance, USD(0)); 00130 00131 00132 auto doBidding = [&] () 00133 { 00134 bool auth1 = shadowCommitmentAccount.authorizeBid("ad1", USD(1)); 00135 bool auth2 = shadowCommitmentAccount.authorizeBid("ad2", USD(1)); 00136 bool auth3 = shadowCommitmentAccount.authorizeBid("ad3", USD(1)); 00137 00138 BOOST_CHECK_EQUAL(auth1, true); 00139 BOOST_CHECK_EQUAL(auth2, true); 00140 BOOST_CHECK_EQUAL(auth3, false); 00141 00142 Amount detached = shadowCommitmentAccount.detachBid("ad1"); 00143 BOOST_CHECK_EQUAL(detached, USD(1)); 00144 00145 shadowCommitmentAccount.cancelBid("ad2"); 00146 00147 shadowSpendAccount.commitDetachedBid(detached, USD(0.50), LineItems()); 00148 00149 shadowCommitmentAccount.syncToMaster(commitmentAccount); 00150 shadowSpendAccount.syncToMaster(spendAccount); 00151 }; 00152 00153 // Do the same kind of bid 5 times 00154 for (unsigned i = 0; i < 5; ++i) { 00155 00156 doBidding(); 00157 00158 cerr << "budget" << budgetAccount << endl; 00159 cerr << "spend " << spendAccount << endl; 00160 cerr << "commitment " << commitmentAccount << endl; 00161 cerr << "shadow spend" << shadowSpendAccount << endl; 00162 cerr << "shadow commitment" << shadowCommitmentAccount << endl; 00163 00164 spendAccount.recuperateTo(budgetAccount); 00165 00166 cerr << "after recuperation" << endl; 00167 cerr << "budget" << budgetAccount << endl; 00168 cerr << "spend " << spendAccount << endl; 00169 00170 commitmentAccount.setBalance(budgetAccount, USD(2)); 00171 00172 cerr << "after setBalance" << endl; 00173 cerr << "budget" << budgetAccount << endl; 00174 cerr << "spend " << spendAccount << endl; 00175 cerr << "commitment " << commitmentAccount << endl; 00176 00177 shadowCommitmentAccount.syncFromMaster(commitmentAccount); 00178 shadowSpendAccount.syncFromMaster(spendAccount); 00179 00180 cerr << "after sync" << endl; 00181 cerr << "shadow spend" << shadowSpendAccount << endl; 00182 cerr << "shadow commitment" << shadowCommitmentAccount << endl; 00183 00184 BOOST_CHECK_EQUAL(commitmentAccount.balance, USD(2)); 00185 BOOST_CHECK_EQUAL(shadowCommitmentAccount.balance, USD(2)); 00186 BOOST_CHECK_EQUAL(spendAccount.balance, USD(0)); 00187 BOOST_CHECK_EQUAL(shadowSpendAccount.balance, USD(0)); 00188 } 00189 } 00190 00191 BOOST_AUTO_TEST_CASE( test_account_recycling ) 00192 { 00193 Accounts accounts; 00194 00195 AccountKey campaign("campaign"); 00196 AccountKey strategy("campaign:strategy"); 00197 AccountKey strategy2("campaign:strategy2"); 00198 AccountKey spend("campaign:strategy:spend"); 00199 AccountKey spend2("campaign:strategy2:spend"); 00200 00201 accounts.createBudgetAccount(campaign); 00202 accounts.createBudgetAccount(strategy); 00203 accounts.createBudgetAccount(strategy2); 00204 accounts.createSpendAccount(spend); 00205 accounts.createSpendAccount(spend2); 00206 00207 // Top level budget of $10 00208 accounts.setBudget(campaign, USD(10)); 00209 00210 // Make $2 available in the strategy account 00211 accounts.setBalance(strategy, USD(2), AT_NONE); 00212 accounts.setBalance(strategy2, USD(2), AT_NONE); 00213 00214 BOOST_CHECK_EQUAL(accounts.getBalance(campaign), USD(6)); 00215 BOOST_CHECK_EQUAL(accounts.getBalance(strategy), USD(2)); 00216 BOOST_CHECK_EQUAL(accounts.getBalance(strategy2), USD(2)); 00217 00218 accounts.setBalance(spend, USD(1), AT_NONE); 00219 //accounts.setBalance(spend2, USD(1), AT_NONE); 00220 00221 BOOST_CHECK_EQUAL(accounts.getBalance(campaign), USD(6)); 00222 BOOST_CHECK_EQUAL(accounts.getBalance(strategy), USD(1)); 00223 BOOST_CHECK_EQUAL(accounts.getBalance(strategy2), USD(2)); 00224 BOOST_CHECK_EQUAL(accounts.getBalance(spend), USD(1)); 00225 BOOST_CHECK_EQUAL(accounts.getBalance(spend2), USD(0)); 00226 00227 accounts.setBalance(spend, USD(1), AT_NONE); 00228 //accounts.setBalance(spend2, USD(1), AT_NONE); 00229 00230 BOOST_CHECK_EQUAL(accounts.getBalance(campaign), USD(6)); 00231 BOOST_CHECK_EQUAL(accounts.getBalance(strategy), USD(1)); 00232 BOOST_CHECK_EQUAL(accounts.getBalance(strategy2), USD(2)); 00233 BOOST_CHECK_EQUAL(accounts.getBalance(spend), USD(1)); 00234 BOOST_CHECK_EQUAL(accounts.getBalance(spend2), USD(0)); 00235 00236 accounts.setBalance(strategy, USD(2), AT_NONE); 00237 //accounts.setBalance(strategy2, USD(2), AT_NONE); 00238 00239 BOOST_CHECK_EQUAL(accounts.getBalance(campaign), USD(5)); 00240 BOOST_CHECK_EQUAL(accounts.getBalance(strategy), USD(2)); 00241 BOOST_CHECK_EQUAL(accounts.getBalance(strategy2), USD(2)); 00242 BOOST_CHECK_EQUAL(accounts.getBalance(spend), USD(1)); 00243 BOOST_CHECK_EQUAL(accounts.getBalance(spend2), USD(0)); 00244 } 00245 00246 BOOST_AUTO_TEST_CASE( test_accounts ) 00247 { 00248 Accounts accounts; 00249 00250 AccountKey budget("budget"); 00251 AccountKey commitment("budget:commitment"); 00252 AccountKey spend("budget:spend"); 00253 00254 ShadowAccounts shadow; 00255 00256 accounts.createBudgetAccount(budget); 00257 accounts.createSpendAccount(commitment); 00258 accounts.createSpendAccount(spend); 00259 00260 // Top level budget of $10 00261 accounts.setBudget(budget, USD(10)); 00262 00263 // Make $2 available in the commitment account 00264 accounts.setBalance(commitment, USD(2), AT_SPEND); 00265 00266 BOOST_CHECK_EQUAL(accounts.getBalance(budget), USD(8)); 00267 BOOST_CHECK_EQUAL(accounts.getBalance(commitment), USD(2)); 00268 00269 shadow.activateAccount(commitment); 00270 shadow.activateAccount(spend); 00271 00272 auto doBidding = [&] () 00273 { 00274 shadow.syncFrom(accounts); 00275 00276 bool auth1 = shadow.authorizeBid(commitment, "ad1", USD(1)); 00277 bool auth2 = shadow.authorizeBid(commitment, "ad2", USD(1)); 00278 bool auth3 = shadow.authorizeBid(commitment, "ad3", USD(1)); 00279 00280 BOOST_CHECK_EQUAL(auth1, true); 00281 BOOST_CHECK_EQUAL(auth2, true); 00282 BOOST_CHECK_EQUAL(auth3, false); 00283 00284 shadow.checkInvariants(); 00285 00286 Amount detached = shadow.detachBid(commitment, "ad1"); 00287 BOOST_CHECK_EQUAL(detached, USD(1)); 00288 00289 shadow.checkInvariants(); 00290 00291 shadow.cancelBid(commitment, "ad2"); 00292 00293 shadow.checkInvariants(); 00294 00295 shadow.commitDetachedBid(spend, detached, USD(0.50), LineItems()); 00296 00297 shadow.syncTo(accounts); 00298 00299 accounts.checkInvariants(); 00300 00301 cerr << accounts.getAccountSummary(budget) << endl; 00302 00303 }; 00304 00305 // Do the same kind of bid 5 times 00306 for (unsigned i = 0; i < 5; ++i) { 00307 00308 cerr << accounts.getAccountSummary(budget) << endl; 00309 cerr << accounts.getAccount(budget) << endl; 00310 cerr << accounts.getAccount(commitment) << endl; 00311 cerr << accounts.getAccount(spend) << endl; 00312 00313 doBidding(); 00314 00315 //cerr << "budget" << budgetAccount << endl; 00316 //cerr << "spend " << spendAccount << endl; 00317 //cerr << "commitment " << commitmentAccount << endl; 00318 00319 accounts.recuperate(spend); 00320 00321 accounts.checkInvariants(); 00322 00323 //cerr << "after recuperation" << endl; 00324 //cerr << "budget" << budgetAccount << endl; 00325 //cerr << "spend " << spendAccount << endl; 00326 00327 accounts.setBalance(commitment, USD(2), AT_SPEND); 00328 00329 accounts.checkInvariants(); 00330 00331 //cerr << "after setBalance" << endl; 00332 //cerr << "budget" << budgetAccount << endl; 00333 //cerr << "spend " << spendAccount << endl; 00334 //cerr << "commitment " << commitmentAccount << endl; 00335 } 00336 00337 cerr << accounts.getAccountSummary(budget) << endl; 00338 } 00339 00340 BOOST_AUTO_TEST_CASE( test_multiple_bidder_threads ) 00341 { 00342 Accounts master; 00343 00344 AccountKey campaign("campaign"); 00345 AccountKey strategy("campaign:strategy"); 00346 00347 // Create a budget for the campaign 00348 master.createBudgetAccount(strategy); 00349 master.setBudget(campaign, USD(10)); 00350 00351 // Do 1,000 topup transfers of one micro 00352 00353 int nTopupThreads = 2; 00354 int nAddBudgetThreads = 2; 00355 int nBidThreads = 2; 00356 //int nSpendThreads = 2; 00357 int numTransfersPerThread = 10000; 00358 int numAddBudgetsPerThread = 10; 00359 00360 volatile bool finished = false; 00361 00362 auto runTopupThread = [&] () 00363 { 00364 while (!finished) { 00365 master.setBalance(strategy, USD(0.10), AT_BUDGET); 00366 } 00367 }; 00368 00369 auto runAddBudgetThread = [&] () 00370 { 00371 for (unsigned i = 0; i < numAddBudgetsPerThread; ++i) { 00372 00373 AccountSummary summary = master.getAccountSummary(campaign); 00374 cerr << summary << endl; 00375 master.setBudget(campaign, summary.budget + USD(1)); 00376 00377 ML::sleep(1.0); 00378 } 00379 }; 00380 00381 uint64_t numBidsCommitted = 0; 00382 00383 ML::RingBufferSRMW<Amount> toCommitThread(1000000); 00384 00385 00386 auto runBidThread = [&] (int threadNum) 00387 { 00388 ShadowAccounts shadow; 00389 AccountKey account = strategy; 00390 account.push_back("bid" + to_string(threadNum)); 00391 00392 master.createSpendAccount(account); 00393 shadow.activateAccount(account); 00394 shadow.syncFrom(master); 00395 00396 int done = 0; 00397 for (; !finished; ++done) { 00398 string item = "item"; 00399 00400 // Every little bit, do a sync and a re-up 00401 if (done && done % 1000 == 0) { 00402 shadow.syncTo(master); 00403 master.setBalance(account, USD(0.10), AT_NONE); 00404 shadow.syncFrom(master); 00405 //cerr << "done " << done << " bids" << endl; 00406 } 00407 00408 // Authorize 10 00409 if (!shadow.authorizeBid(account, item, MicroUSD(1))) { 00410 continue; 00411 } 00412 00413 // In half of the cases, we cancel. In the other half, we 00414 // transfer it off to the commit thread 00415 00416 if (done % 2 == 0) { 00417 // Commit 1 00418 shadow.commitBid(account, item, MicroUSD(1), LineItems()); 00419 ML::atomic_inc(numBidsCommitted); 00420 } 00421 else { 00422 Amount amount = shadow.detachBid(account, item); 00423 toCommitThread.push(amount); 00424 } 00425 } 00426 00427 shadow.sync(master); 00428 00429 cerr << "finished shadow account with " 00430 << done << " bids" << endl; 00431 cerr << master.getAccount(account) << endl; 00432 00433 }; 00434 00435 auto runCommitThread = [&] (int threadNum) 00436 { 00437 ShadowAccounts shadow; 00438 AccountKey account = strategy; 00439 account.push_back("commit" + to_string(threadNum)); 00440 00441 master.createSpendAccount(account); 00442 shadow.activateAccount(account); 00443 shadow.syncFrom(master); 00444 00445 while (!finished || toCommitThread.couldPop()) { 00446 Amount amount; 00447 if (toCommitThread.tryPop(amount, 0.1)) { 00448 shadow.commitDetachedBid(account, amount, MicroUSD(1), LineItems()); 00449 ML::atomic_inc(numBidsCommitted); 00450 } 00451 shadow.syncTo(master); 00452 } 00453 00454 shadow.syncTo(master); 00455 cerr << "done commit thread" << endl; 00456 }; 00457 00458 boost::thread_group budgetThreads; 00459 00460 for (unsigned i = 0; i < nAddBudgetThreads; ++i) 00461 budgetThreads.create_thread(runAddBudgetThread); 00462 00463 boost::thread_group bidThreads; 00464 for (unsigned i = 0; i < nBidThreads; ++i) 00465 bidThreads.create_thread(std::bind(runBidThread, i)); 00466 00467 for (unsigned i = 0; i < nTopupThreads; ++i) 00468 bidThreads.create_thread(runTopupThread); 00469 00470 bidThreads.create_thread(std::bind(runCommitThread, 0)); 00471 00472 00473 budgetThreads.join_all(); 00474 00475 finished = true; 00476 00477 bidThreads.join_all(); 00478 00479 uint32_t amountAdded = nAddBudgetThreads * numAddBudgetsPerThread; 00480 uint32_t amountTransferred = nTopupThreads * numTransfersPerThread; 00481 00482 cerr << "numBidsCommitted = " << numBidsCommitted << endl; 00483 cerr << "amountTransferred = " << amountTransferred << endl; 00484 cerr << "amountAdded = " << amountAdded << endl; 00485 00486 cerr << "campaign" << endl; 00487 cerr << master.getAccountSummary(campaign) << endl; 00488 cerr << master.getAccount(campaign) << endl; 00489 00490 cerr << "strategy" << endl; 00491 cerr << master.getAccountSummary(strategy) << endl; 00492 cerr << master.getAccount(strategy) << endl; 00493 00494 00495 #if 0 00496 RedisBanker banker("bankerTest", "b", s, redis); 00497 banker.sync(); 00498 Json::Value status = banker.getCampaignStatusJson("testCampaign", ""); 00499 00500 cerr << status << endl; 00501 00502 00503 00504 00505 BOOST_CHECK_EQUAL(status["available"]["micro-USD"].asInt(), 1000000 - amountTransferred + amountAdded); 00506 BOOST_CHECK_EQUAL(status["strategies"][0]["available"]["micro-USD"].asInt(), 00507 amountTransferred - numBidsCommitted); 00508 BOOST_CHECK_EQUAL(status["strategies"][0]["transferred"]["micro-USD"].asInt(), 00509 amountTransferred); 00510 BOOST_CHECK_EQUAL(status["strategies"][0]["spent"]["micro-USD"].asInt(), 00511 numBidsCommitted); 00512 BOOST_CHECK_EQUAL(status["spent"]["micro-USD"].asInt(), numBidsCommitted); 00513 00514 //BOOST_CHECK_EQUAL(status["available"]. 00515 #endif 00516 } 00517 00518 BOOST_AUTO_TEST_CASE( test_recycling ) 00519 { 00520 Accounts accounts; 00521 Account t, s, sp; /* "top", "sub" and "spend" */ 00522 00523 /* setup */ 00524 accounts.createAccount({"t"}, AT_BUDGET); 00525 accounts.setBudget({"t"}, USD(666)); 00526 00527 accounts.setBalance({"t", "s"}, USD(10), AT_BUDGET); 00528 00529 // s.setBalance(10) 00530 // -> as.budgetIncrease increased by 10 00531 s = accounts.getAccount({"t", "s"}); 00532 BOOST_CHECK_EQUAL(s.budgetIncreases, USD(10)); 00533 00534 // s.setBalance(7) 00535 // -> t.recycledIn increased by 3 (total: 3) 00536 // and s.recycledOut increased by 3 (total: 3) 00537 accounts.setBalance({"t", "s"}, USD(7), AT_NONE); 00538 t = accounts.getAccount({"t"}); 00539 BOOST_CHECK_EQUAL(t.recycledIn, USD(3)); 00540 s = accounts.getAccount({"t", "s"}); 00541 BOOST_CHECK_EQUAL(s.recycledOut, USD(3)); 00542 00543 // s.setBalance(8) 00544 // -> t.recycledOut increased by 1 (total: 1) 00545 // and s.recycledIn increased by 1 (total: 1) 00546 accounts.setBalance({"t", "s"}, USD(8), AT_NONE); 00547 t = accounts.getAccount({"t"}); 00548 BOOST_CHECK_EQUAL(t.recycledOut, USD(1)); 00549 s = accounts.getAccount({"t", "s"}); 00550 BOOST_CHECK_EQUAL(s.recycledIn, USD(1)); 00551 00552 // sp.setBalance(5) 00553 // -> sp.budgetIncrease increased by 5 (total: 5), 00554 // s.allocatedOut increased by 5 (total: 5) 00555 accounts.setBalance({"t", "s", "sp"}, USD(5), AT_SPEND); 00556 sp = accounts.getAccount({"t", "s", "sp"}); 00557 BOOST_CHECK_EQUAL(sp.budgetIncreases, USD(5)); 00558 s = accounts.getAccount({"t", "s"}); 00559 BOOST_CHECK_EQUAL(s.allocatedOut, USD(5)); 00560 00561 /* mixup */ 00562 // sp.setBalance(4) 00563 // -> sp.recycledOut by 1 (total: 1), 00564 // s.recycledIn increased by 1 (total: 2) 00565 accounts.setBalance({"t", "s", "sp"}, USD(4), AT_NONE); 00566 sp = accounts.getAccount({"t", "s", "sp"}); 00567 BOOST_CHECK_EQUAL(sp.recycledOut, USD(1)); 00568 s = accounts.getAccount({"t", "s"}); 00569 BOOST_CHECK_EQUAL(s.recycledIn, USD(2)); 00570 } 00571 00572 BOOST_AUTO_TEST_CASE( test_getRecycledUp ) 00573 { 00574 Accounts accounts; 00575 CurrencyPool recycledIn, recycledOut; 00576 00577 /* setup */ 00578 accounts.createAccount({"t"}, AT_BUDGET); 00579 accounts.setBudget({"t"}, USD(666)); 00580 00581 // s.setBalance(10) 00582 accounts.setBalance({"t", "s"}, USD(10), AT_BUDGET); 00583 accounts.getRecycledUp({"t", "s"}, recycledIn, recycledOut); 00584 BOOST_CHECK_EQUAL(recycledIn, USD(0)); 00585 BOOST_CHECK_EQUAL(recycledOut, USD(0)); 00586 00587 // s.setBalance(7) 00588 // t.recycledIn == 3 but t.recycledIn(up) == 0 00589 // s.recycledOut(up) == 3 00590 accounts.setBalance({"t", "s"}, USD(7), AT_NONE); 00591 accounts.getRecycledUp({"t"}, recycledIn, recycledOut); 00592 BOOST_CHECK_EQUAL(recycledIn, USD(0)); 00593 accounts.getRecycledUp({"t", "s"}, recycledIn, recycledOut); 00594 BOOST_CHECK_EQUAL(recycledOut, USD(3)); 00595 00596 // s.setBalance(8) 00597 // t.recycledOut == 1 but t.recycledOut(up) == 0 00598 // s.recycledIn == 1 and s.recycledIn(up) == 1 00599 accounts.setBalance({"t", "s"}, USD(8), AT_NONE); 00600 accounts.getRecycledUp({"t"}, recycledIn, recycledOut); 00601 BOOST_CHECK_EQUAL(recycledOut, USD(0)); 00602 accounts.getRecycledUp({"t", "s"}, recycledIn, recycledOut); 00603 BOOST_CHECK_EQUAL(recycledIn, USD(1)); 00604 00605 // sp.setBalance(5) 00606 // sp.recycleX untouched and sp.budgetIncreases increased 00607 accounts.setBalance({"t", "s", "sp"}, USD(5), AT_SPEND); 00608 accounts.getRecycledUp({"t", "s", "sp"}, recycledIn, recycledOut); 00609 BOOST_CHECK_EQUAL(recycledIn, USD(0)); 00610 BOOST_CHECK_EQUAL(recycledOut, USD(0)); 00611 00612 // sp.setBalance(4) 00613 // s.recycledIn == 2 but s.recycledIn(up) == 1 00614 // sp.recycledOut == 1 and sp.recycledOut(up) == 1 00615 accounts.setBalance({"t", "s", "sp"}, USD(4), AT_NONE); 00616 accounts.getRecycledUp({"t", "s"}, recycledIn, recycledOut); 00617 BOOST_CHECK_EQUAL(recycledIn, USD(1)); 00618 accounts.getRecycledUp({"t", "s", "sp"}, recycledIn, recycledOut); 00619 BOOST_CHECK_EQUAL(recycledOut, USD(1)); 00620 } 00621 00622 /* ensure values of simple account summaries matches those in non-simple 00623 ones */ 00624 BOOST_AUTO_TEST_CASE( test_account_simple_summaries ) 00625 { 00626 Accounts accounts; 00627 00628 /* NOTE: the accounts are not particularly consistent with one another */ 00629 Json::Value jsonValue 00630 = Json::parse("{'adjustmentLineItems':{}," 00631 "'adjustmentsIn':{}," 00632 "'adjustmentsOut':{}," 00633 "'allocatedIn':{}," 00634 "'allocatedOut':{'USD/1M':46571708796}," 00635 "'budgetDecreases':{}," 00636 "'budgetIncreases':{'USD/1M':52947000000}," 00637 "'commitmentsMade':{}," 00638 "'commitmentsRetired':{}," 00639 "'lineItems':{}," 00640 "'md':{'objectType':'Account','version':1}," 00641 "'recycledIn':{}," 00642 "'recycledOut':{}," 00643 "'spent':{}," 00644 "'type':'budget'}"); 00645 accounts.restoreAccount({"top"}, jsonValue); 00646 jsonValue = Json::parse("{'adjustmentLineItems':{}," 00647 "'adjustmentsIn':{}," 00648 "'adjustmentsOut':{}," 00649 "'allocatedIn':{}," 00650 "'allocatedOut':{'USD/1M':582053135}," 00651 "'budgetDecreases':{}," 00652 "'budgetIncreases':{'USD/1M':614502770}," 00653 "'commitmentsMade':{}," 00654 "'commitmentsRetired':{}," 00655 "'lineItems':{}," 00656 "'md':{'objectType':'Account','version':1}," 00657 "'recycledIn':{}," 00658 "'recycledOut':{}," 00659 "'spent':{}," 00660 "'type':'budget'}"); 00661 accounts.restoreAccount({"top", "sub"}, jsonValue); 00662 jsonValue = Json::parse("{'adjustmentLineItems':{}," 00663 "'adjustmentsIn':{}," 00664 "'adjustmentsOut':{}," 00665 "'allocatedIn':{}," 00666 "'allocatedOut':{}," 00667 "'budgetDecreases':{}," 00668 "'budgetIncreases':{'USD/1M':582053135}," 00669 "'commitmentsMade':{}," 00670 "'commitmentsRetired':{}," 00671 "'lineItems':{}," 00672 "'md':{'objectType':'Account','version':1}," 00673 "'recycledIn':{}," 00674 "'recycledOut':{}," 00675 "'spent':{'USD/1M':582053135}," 00676 "'type':'spend'}"); 00677 accounts.restoreAccount({"top", "sub", "spent"}, jsonValue); 00678 00679 vector<string> aNames = { "top", "top:sub", "top:sub:spent" }; 00680 for (const string & aName: aNames) { 00681 AccountSummary accountS 00682 = accounts.getAccountSummary(aName); 00683 Json::Value summary = accountS.toJson(); 00684 BOOST_CHECK_EQUAL(summary["md"]["objectType"].asString(), 00685 "AccountSummary"); 00686 BOOST_CHECK_EQUAL(summary["md"]["version"].asInt(), 1); 00687 00688 Json::Value simpleSummary = accountS.toJson(true); 00689 BOOST_CHECK_EQUAL(simpleSummary["md"]["objectType"].asString(), 00690 "AccountSimpleSummary"); 00691 BOOST_CHECK_EQUAL(simpleSummary["md"]["version"].asInt(), 1); 00692 vector<string> keys = {"budget", "spend", "available", "inFlight"}; 00693 for (const string & key: keys) { 00694 BOOST_CHECK_EQUAL(summary[key], simpleSummary[key]); 00695 } 00696 } 00697 } 00698 00699 /* ensure values of account summaries, both normal and simple, matches the 00700 * values of the account tree they represent */ 00701 BOOST_AUTO_TEST_CASE( test_account_summary_values ) 00702 { 00703 Accounts accounts; 00704 00705 /* NOTE: the accounts are not particularly consistent with one another */ 00706 Json::Value jsonValue 00707 = Json::parse("{'adjustmentLineItems':{}," 00708 "'adjustmentsIn':{}," 00709 "'adjustmentsOut':{}," 00710 "'allocatedIn':{}," 00711 "'allocatedOut':{'USD/1M':46571708796}," 00712 "'budgetDecreases':{}," 00713 "'budgetIncreases':{'USD/1M':52947000000}," 00714 "'commitmentsMade':{}," 00715 "'commitmentsRetired':{}," 00716 "'lineItems':{}," 00717 "'md':{'objectType':'Account','version':1}," 00718 "'recycledIn':{}," 00719 "'recycledOut':{}," 00720 "'spent':{}," 00721 "'type':'budget'}"); 00722 accounts.restoreAccount({"top"}, jsonValue); 00723 jsonValue = Json::parse("{'adjustmentLineItems':{}," 00724 "'adjustmentsIn':{}," 00725 "'adjustmentsOut':{}," 00726 "'allocatedIn':{}," 00727 "'allocatedOut':{'USD/1M':582053135}," 00728 "'budgetDecreases':{}," 00729 "'budgetIncreases':{'USD/1M':614502770}," 00730 "'commitmentsMade':{}," 00731 "'commitmentsRetired':{}," 00732 "'lineItems':{}," 00733 "'md':{'objectType':'Account','version':1}," 00734 "'recycledIn':{}," 00735 "'recycledOut':{}," 00736 "'spent':{}," 00737 "'type':'budget'}"); 00738 accounts.restoreAccount({"top", "sub"}, jsonValue); 00739 jsonValue = Json::parse("{'adjustmentLineItems':{}," 00740 "'adjustmentsIn':{}," 00741 "'adjustmentsOut':{}," 00742 "'allocatedIn':{}," 00743 "'allocatedOut':{}," 00744 "'budgetDecreases':{}," 00745 "'budgetIncreases':{'USD/1M':582053135}," 00746 "'commitmentsMade':{}," 00747 "'commitmentsRetired':{}," 00748 "'lineItems':{}," 00749 "'md':{'objectType':'Account','version':1}," 00750 "'recycledIn':{}," 00751 "'recycledOut':{}," 00752 "'spent':{'USD/1M':582053135}," 00753 "'type':'spend'}"); 00754 accounts.restoreAccount({"top", "sub", "spent"}, jsonValue); 00755 00756 AccountSummary summary = accounts.getAccountSummary({"top"}, 0); 00757 Json::Value normalValue = summary.toJson(false); 00758 Json::Value expected 00759 = Json::parse("{'account':" 00760 " {'adjustmentLineItems':{}," 00761 " 'adjustmentsIn':{}," 00762 " 'adjustmentsOut':{}," 00763 " 'allocatedIn':{}," 00764 " 'allocatedOut': {'USD/1M':46571708796}," 00765 " 'budgetDecreases':{}," 00766 " 'budgetIncreases': {'USD/1M':52947000000}," 00767 " 'commitmentsMade':{}," 00768 " 'commitmentsRetired':{}," 00769 " 'lineItems':{}," 00770 " 'md': {'objectType':'Account','version':1}," 00771 " 'recycledIn':{}," 00772 " 'recycledOut':{}," 00773 " 'spent':{}," 00774 " 'type':'budget'}," 00775 " 'available': {'USD/1M':52364946865}," 00776 " 'budget': {'USD/1M':52947000000}," 00777 " 'inFlight':{}," 00778 " 'md': {'objectType':'AccountSummary'," 00779 " 'version':1}," 00780 " 'spent': {'USD/1M':582053135}}"); 00781 BOOST_CHECK_EQUAL(normalValue, expected); 00782 00783 Json::Value simpleValue = summary.toJson(true); 00784 expected = Json::parse("{'available': {'USD/1M':52364946865}," 00785 " 'budget': {'USD/1M':52947000000}," 00786 " 'inFlight': {}," 00787 " 'md': {'objectType':'AccountSimpleSummary'," 00788 " 'version':1}," 00789 " 'spent': {'USD/1M':582053135}}"); 00790 BOOST_CHECK_EQUAL(simpleValue, expected); 00791 } 00792