From e1475772d2f0fddad92d842bc7912e633cee2984 Mon Sep 17 00:00:00 2001 From: Soumava Bera Date: Mon, 15 Jul 2024 16:31:35 +0000 Subject: [PATCH] test refactor v1 --- .../TableOperationTest.cpp | 565 +++++++++++++++++- .../include/aws/testing/AwsTestHelpers.h | 2 +- 2 files changed, 560 insertions(+), 7 deletions(-) diff --git a/tests/aws-cpp-sdk-dynamodb-integration-tests/TableOperationTest.cpp b/tests/aws-cpp-sdk-dynamodb-integration-tests/TableOperationTest.cpp index b3697bc704f..5fe9b5b44aa 100644 --- a/tests/aws-cpp-sdk-dynamodb-integration-tests/TableOperationTest.cpp +++ b/tests/aws-cpp-sdk-dynamodb-integration-tests/TableOperationTest.cpp @@ -74,11 +74,135 @@ Aws::String BuildTableName(const char* baseName) return GetTablePrefix() + baseName; } + +//create a template class which will take the request, response, expected response +//if response dosnt match expected response retry upto n times +//try till all have been tried upto n times +//use a map of reqid to numRetriesLeft +//req id maps to request functions and their expected responses +// + + +/* + workflow is add requests to queue + process them using validator and retry appropriate requests + + add requests with tag in order + if any request processing fails validation redo the sequence +*/ + + +template +class RetryPlanner +{ + + public: + + using RetryFunction_t = std::function, const Aws::String& id, int numRetriesLeft)>; + + struct Entry{ + Aws::String entryId; + RetryFunction_t candidate; + int retryCount; + bool stopOnFail; + ~Entry(){ + std::cout<<"destructor for "< context):_contextSp{context}{} + + bool addFunction(const Entry& item) + { + bool status = false; + if(item.candidate) + { + auto ret = _uniqueEntrySet.insert(item.entryId); + + if(ret.second) + { + _list.push_back(item); + status = true; + } + } + AWS_LOGSTREAM_TRACE(ALLOCATION_TAG, "added function with id=" << item.entryId ); + std::cout<<"added function with id=" << item.entryId< 0 && !_list.empty()) + { + status = true; + int attemptsLeft = 0; + for(auto it = _list.begin(); it != _list.end();) + { + if(!it->candidate) + { + std::cout<<"invalid function with id=" << it->entryId<retryCount < 0) + { + ++it; + continue; + } + + auto runResult = it->candidate(_contextSp, it->entryId, it->retryCount--); + + attemptsLeft += it->retryCount; + + AWS_LOGSTREAM_TRACE(ALLOCATION_TAG, "execute function with id=" << it->entryId<<" result="<(ALLOCATION_TAG, 4); + config.disableExpectHeader = true; + config.enableHttpClientTrace = true; + + //to test proxy functionality, uncomment the next two lines. + //config.proxyHost = "localhost"; + //config.proxyPort = 8080; + m_client = Aws::MakeShared(ALLOCATION_TAG, config); + DYNAMODB_INTEGRATION_TEST_ID = Aws::String(Aws::Utils::UUID::RandomUUID()).c_str(); + } + + ~TableOperationTest(){ + std::cout<<"TearDownTestCase called"<(ALLOCATION_TAG, config); } + static void CleanupTables() + { + + DeleteTable(BuildTableName(BASE_SIMPLE_TABLE)); + DeleteTable(BuildTableName(BASE_THROUGHPUT_TABLE)); + DeleteTable(BuildTableName(BASE_CONDITION_TABLE)); + DeleteTable(BuildTableName(BASE_VALIDATION_TABLE)); + DeleteTable(BuildTableName(BASE_CRUD_TEST_TABLE)); + DeleteTable(BuildTableName(BASE_CRUD_CALLBACKS_TEST_TABLE)); + DeleteTable(BuildTableName(BASE_THROTTLED_TEST_TABLE)); + DeleteTable(BuildTableName(BASE_LIMITER_TEST_TABLE)); + DeleteTable(BuildTableName(BASE_ATTRIBUTEVALUE_TEST_TABLE)); + } + static void SetUpTestCase() { m_limiter = Aws::MakeShared>(ALLOCATION_TAG, 200000); @@ -196,6 +369,7 @@ class TableOperationTest : public ::testing::Test { m_limiter = nullptr; m_client = nullptr; } + static void DeleteAllTables() { @@ -209,6 +383,7 @@ class TableOperationTest : public ::testing::Test { DeleteTable(BuildTableName(BASE_LIMITER_TEST_TABLE)); DeleteTable(BuildTableName(BASE_ATTRIBUTEVALUE_TEST_TABLE)); } + */ void CreateTable(Aws::String tableName, long readCap, long writeCap) { @@ -231,6 +406,7 @@ class TableOperationTest : public ::testing::Test { if (createTableOutcome.IsSuccess()) { ASSERT_EQ(tableName, createTableOutcome.GetResult().GetTableDescription().GetTableName()); + m_tablesCreated.emplace_back(tableName); } else { @@ -241,7 +417,7 @@ class TableOperationTest : public ::testing::Test { WaitUntilActive(tableName); } - static void DeleteTable(Aws::String tableName) + void DeleteTable(Aws::String tableName) { DeleteTableRequest deleteTableRequest; deleteTableRequest.SetTableName(tableName); @@ -275,8 +451,8 @@ class TableOperationTest : public ::testing::Test { } }; -std::shared_ptr TableOperationTest::m_client(nullptr); -std::shared_ptr TableOperationTest::m_limiter(nullptr); +//std::shared_ptr TableOperationTest::m_client(nullptr); +//std::shared_ptr TableOperationTest::m_limiter(nullptr); TEST_F(TableOperationTest, TestListTable) { @@ -459,6 +635,379 @@ TEST_F(TableOperationTest, TestThrottling) } } + + + + +/* + + maintain in context all requests + + on retry, take only leftover requests + +*/ + + +TEST_F(TableOperationTest, TestCrudOperations2) +{ + AWS_LOGSTREAM_TRACE(ALLOCATION_TAG, "TestCrudOperations") + + struct TestContext{ + Aws::Vector putItemResults; + Aws::Vector getItemOutcomes; + Aws::Vector updateItemOutcomes; + Aws::Vector deleteItemOutcomes; + }; + + Aws::String crudTestTableName = BuildTableName(BASE_CRUD_TEST_TABLE); + const int numItems = 50; + CreateTable(crudTestTableName, numItems, numItems); + + //now put 50 items in the table asynchronously + Aws::String testValueColumnName = "TestValue"; + + auto crudTestTableNameSp = Aws::MakeShared(ALLOCATION_TAG, crudTestTableName); + + + //Create context + auto contextSp = Aws::MakeShared< TestContext > ( + ALLOCATION_TAG + ); + + RetryPlanner retryObj(contextSp); + + Aws::List requestIds; + Aws::List deleteRequestIds; + + auto prepareRequests = [numItems, &requestIds](){ + for(auto i = 0; i < numItems; ++i) + { + requestIds.push_back(i); + } + }; + + prepareRequests(); + + deleteRequestIds = requestIds; + + auto putItemReqFunction = [testValueColumnName, crudTestTableName, this, &requestIds]( + std::shared_ptr contextSp, + const Aws::String& id, + int numRetriesLeft) + { + AWS_UNREFERENCED_PARAM(id); + AWS_UNREFERENCED_PARAM(numRetriesLeft); + std::cout<<"function with id="<putItemResults.push_back(m_client->PutItemCallable(putItemRequest)); + } + + //wait for put operations to finish + //isn't c++ 11 nice! + for (auto& putItemResult : contextSp->putItemResults) + { + putItemResult.get(); + } + return true; + }; + + + retryObj.addFunction(RetryPlanner::Entry{ + "putrequests", + putItemReqFunction, + 0, + true + }); + + + + + //after put requests have been made, it's not necessary that put requests may have been processed to completion before get is called + //for a failing read query we attempt it N times before deeming it fail + //we can generalize retry of a request + + //now we get the items we were supposed to be putting and make sure + //they were put successfully. + + //add get and verify, ones that fail retry + + + auto getItemFunc = [this, testValueColumnName, crudTestTableName, numItems , &requestIds]( + std::shared_ptr contextSp, + const Aws::String& id, + int numRetriesLeft) + { + AWS_UNREFERENCED_PARAM(id); + AWS_UNREFERENCED_PARAM(numRetriesLeft); + Aws::StringStream ss; + contextSp->getItemOutcomes.clear(); + for (auto i : requestIds) + { + GetItemRequest getItemRequest; + ss << HASH_KEY_NAME << i; + AttributeValue hashKey; + hashKey.SetS(ss.str()); + getItemRequest.AddKey(HASH_KEY_NAME, hashKey); + getItemRequest.SetTableName(crudTestTableName); + + Aws::Vector attributesToGet; + attributesToGet.push_back(HASH_KEY_NAME); + attributesToGet.push_back(testValueColumnName); + ss.str(""); + contextSp->getItemOutcomes.push_back(m_client->GetItemCallable(getItemRequest)); + + } + + bool status = true; + + for (auto it = requestIds.begin(); it != requestIds.end(); ) + { + auto i = *it; + GetItemOutcome outcome = contextSp->getItemOutcomes[i].get(); + bool res = outcome.IsSuccess(); + + AWS_EXPECT_SUCCESS(outcome); + GetItemResult result = outcome.GetResult(); + ss << HASH_KEY_NAME << i; + Aws::Map returnedItemCollection = result.GetItem(); + EXPECT_EQ(ss.str(), returnedItemCollection[HASH_KEY_NAME].GetS()); + res = res && (ss.str() == returnedItemCollection[HASH_KEY_NAME].GetS()); + + ss.str(""); + ss << testValueColumnName << i; + EXPECT_EQ(ss.str(), returnedItemCollection[testValueColumnName].GetS()); + res = res && (ss.str() == returnedItemCollection[testValueColumnName].GetS()); + + ss.str(""); + + status = status && res; + //if success, remove + //remove from list on success else retian for retry + if(!res) + { + ++it; + } + else + { + it = requestIds.erase(it); + } + } + std::cout<<"function with id="<Scan(scanRequest); + AWS_EXPECT_SUCCESS(scanOutcome); + EXPECT_EQ(numItems, scanOutcome.GetResult().GetCount()); + + + prepareRequests(); + + //now update the existing values + auto updateItemFunc = [this, testValueColumnName, crudTestTableName, &requestIds]( + std::shared_ptr contextSp, + const Aws::String& id, + int numRetriesLeft) + { + AWS_UNREFERENCED_PARAM(id); + AWS_UNREFERENCED_PARAM(numRetriesLeft); + std::cout<<"function with id="<updateItemOutcomes.push_back(m_client->UpdateItemCallable(updateItemRequest)); + } + + //wait for operations to finish. + for (auto& updateItemOutcome : contextSp->updateItemOutcomes) + { + updateItemOutcome.get(); + } + return true; + }; + + + retryObj.addFunction(RetryPlanner::Entry{ + "updaterequests", + updateItemFunc, + 0, + true + }); + + auto getItemFunc2 = [this, testValueColumnName, crudTestTableName, numItems , &requestIds]( + std::shared_ptr contextSp, + const Aws::String& id, + int numRetriesLeft) + { + AWS_UNREFERENCED_PARAM(id); + AWS_UNREFERENCED_PARAM(numRetriesLeft); + + Aws::StringStream ss; + contextSp->getItemOutcomes.clear(); + for (auto i : requestIds) + { + GetItemRequest getItemRequest; + ss << HASH_KEY_NAME << i; + AttributeValue hashKey; + hashKey.SetS(ss.str()); + getItemRequest.AddKey(HASH_KEY_NAME, hashKey); + getItemRequest.SetTableName(crudTestTableName); + + Aws::Vector attributesToGet; + attributesToGet.push_back(HASH_KEY_NAME); + attributesToGet.push_back(testValueColumnName); + ss.str(""); + contextSp->getItemOutcomes.push_back(m_client->GetItemCallable(getItemRequest)); + + } + + bool status = true; + + for (auto it = requestIds.begin(); it != requestIds.end();) + { + auto i = *it; + GetItemOutcome outcome = contextSp->getItemOutcomes[i].get(); + bool res = outcome.IsSuccess(); + + AWS_EXPECT_SUCCESS(outcome); + GetItemResult result = outcome.GetResult(); + ss << HASH_KEY_NAME << i; + Aws::Map returnedItemCollection = result.GetItem(); + EXPECT_EQ(ss.str(), returnedItemCollection[HASH_KEY_NAME].GetS()); + res = res && (ss.str() == returnedItemCollection[HASH_KEY_NAME].GetS()); + ss.str(""); + ss << testValueColumnName << i * 2; + EXPECT_EQ(ss.str(), returnedItemCollection[testValueColumnName].GetS()); + res = res && (ss.str() == returnedItemCollection[testValueColumnName].GetS()); + ss.str(""); + + status = status && res; + //if success, remove + //remove from list on success else retian for retry + if(!res) + { + ++it; + } + else + { + it = requestIds.erase(it); + } + } + std::cout<<"function with id="<::Entry{ + "getrequests2", + getItemFunc2, + 3, + false + }); + + +#if 1 + + //now delete all the items we added. + auto deleteItemFunc = [this, testValueColumnName, crudTestTableName, numItems, &deleteRequestIds]( + std::shared_ptr contextSp, + const Aws::String& id, + int numRetriesLeft) + { + AWS_UNREFERENCED_PARAM(id); + AWS_UNREFERENCED_PARAM(numRetriesLeft); + std::cout<<"function with id="<deleteItemOutcomes.push_back(m_client->DeleteItemCallable(deleteItemRequest)); + } + unsigned count = 0; + for (auto it = deleteRequestIds.begin(); it != deleteRequestIds.end(); ++it) + { + auto i = *it; + DeleteItemOutcome outcome = contextSp->deleteItemOutcomes[i].get(); + AWS_EXPECT_SUCCESS(outcome); + DeleteItemResult deleteItemResult = outcome.GetResult(); + Aws::Map attributes = deleteItemResult.GetAttributes(); + ss << HASH_KEY_NAME << count++; + EXPECT_EQ(ss.str(), attributes[HASH_KEY_NAME].GetS()); + ss.str(""); + + } + return true; + }; + + retryObj.addFunction(RetryPlanner::Entry{ + "deleterequests", + deleteItemFunc, + 0, + true + }); + +#endif + EXPECT_EQ(retryObj.execute(), true); + +} + TEST_F(TableOperationTest, TestCrudOperations) { AWS_LOGSTREAM_TRACE(ALLOCATION_TAG, "TestCrudOperations") @@ -495,6 +1044,10 @@ TEST_F(TableOperationTest, TestCrudOperations) putItemResult.get(); } + //after put requests have been made, it's not necessary that put requests may have been processed to completion before get is called + //for a failing read query we attempt it N times before deeming it fail + //we can generalize retry of a request + //now we get the items we were supposed to be putting and make sure //they were put successfully. Aws::Vector getItemOutcomes; diff --git a/tests/testing-resources/include/aws/testing/AwsTestHelpers.h b/tests/testing-resources/include/aws/testing/AwsTestHelpers.h index 89c4c446be0..dbb17331439 100644 --- a/tests/testing-resources/include/aws/testing/AwsTestHelpers.h +++ b/tests/testing-resources/include/aws/testing/AwsTestHelpers.h @@ -68,4 +68,4 @@ OutcomeT CallOperationWithUnconditionalRetry(const ClientT* client, std::function func = std::bind(OperationFunc, client, request); return CallOperationWithUnconditionalRetry(func, retries, sleepBetween); -} \ No newline at end of file +}