Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions source/test/bulkdata/profileTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1482,3 +1482,176 @@ TEST_F(ProfileTest, createComponentDataElements) {
createComponentDataElements();
}

//============================== Async Delete Path Test ===========================
// Test for the async delete path triggered by repeated RBUS_METHOD failures
TEST_F(ProfileTest, CollectAndReport_AsyncDeleteOnRBUSMethodFailure) {
/*
* Test: Verify that when an RBUS_METHOD profile fails SendErr > 3 times
* and rbusCheckMethodExists returns false, the profile is queued for
* async deletion (not deleted synchronously from CollectAndReport).
*
* This test ensures the fix prevents use-after-free by deferring deletion
* to a separate thread after CollectAndReport exits.
*/
Comment on lines +1488 to +1495
Comment on lines +1487 to +1495

// Setup: Create a Profile with RBUS_METHOD protocol
Profile* testProfile = (Profile*)malloc(sizeof(Profile));
ASSERT_NE(testProfile, nullptr);
memset(testProfile, 0, sizeof(Profile));

testProfile->name = strdup("RBUSFailProfile");
testProfile->protocol = strdup("RBUS_METHOD");
testProfile->encodingType = strdup("JSON");
testProfile->enable = true;
testProfile->threadExists = false;
testProfile->reportInProgress = false;
testProfile->SendErr = 4; // > 3, triggering delete condition

// Setup RBUS destination
testProfile->t2RBUSDest = (T2RBUS*)malloc(sizeof(T2RBUS));
ASSERT_NE(testProfile->t2RBUSDest, nullptr);
testProfile->t2RBUSDest->rbusMethodName = strdup("Device.X_FAILED_Method");
testProfile->t2RBUSDest->rbusMethodParamList = nullptr;

// Setup grep profile (required for collection)
testProfile->grepSeekProfile = (GrepSeekProfile*)malloc(sizeof(GrepSeekProfile));
ASSERT_NE(testProfile->grepSeekProfile, nullptr);
memset(testProfile->grepSeekProfile, 0, sizeof(GrepSeekProfile));
testProfile->grepSeekProfile->execCounter = 0;
testProfile->grepSeekProfile->logFileSeekMap = hash_map_create();

// Setup marker lists
testProfile->paramList = nullptr;
testProfile->staticParamList = nullptr;
testProfile->gMarkerList = nullptr;
testProfile->eMarkerList = nullptr;
testProfile->topMarkerList = nullptr;
testProfile->cachedReportList = nullptr;

// Setup JSON encoding and report object
testProfile->jsonEncoding = (JSONEncoding*)malloc(sizeof(JSONEncoding));
ASSERT_NE(testProfile->jsonEncoding, nullptr);
testProfile->jsonEncoding->reportFormat = JSONRF_KEYVALUEPAIR;
testProfile->jsonReportObj = nullptr;

// Mock expectations for the report generation path
EXPECT_CALL(*g_vectorMock, Vector_Size(_))
.Times(::testing::AtLeast(5))
.WillRepeatedly(Return(0));
Comment on lines +1538 to +1540

Comment on lines +1538 to +1541
EXPECT_CALL(*g_vectorMock, Vector_At(_, _))
.Times(::testing::AtMost(2))
.WillRepeatedly(Return(nullptr));

// Mock rbusCheckMethodExists to return false (method provider not found)
// This is the key condition for triggering the async delete path
EXPECT_CALL(*g_rbusMock, rbusCheckMethodExists(StrEq("Device.X_FAILED_Method")))
.Times(::testing::AtLeast(1))
.WillRepeatedly(Return(false));
Comment on lines +1546 to +1550

// Mock pthread_create for the async delete thread
// We don't actually create threads in the test, just verify the intent
pthread_t mockThread = 0;

// Verify that the profile marked for deletion is handled correctly
// by checking that SendErr exceeds 3 and method doesn't exist
ASSERT_GT(testProfile->SendErr, 3);
ASSERT_FALSE(rbusCheckMethodExists(testProfile->t2RBUSDest->rbusMethodName));

Comment on lines +1558 to +1560
// Cleanup
free(testProfile->name);
free(testProfile->protocol);
free(testProfile->encodingType);
free(testProfile->t2RBUSDest->rbusMethodName);
free(testProfile->t2RBUSDest);
if (testProfile->grepSeekProfile) {
// logFileSeekMap cleanup: hash_map_remove requires a key argument,
// so we skip it here since we're freeing the entire structure
Comment on lines +1567 to +1569
free(testProfile->grepSeekProfile);
}
free(testProfile->jsonEncoding);
free(testProfile);

SUCCEED();
}

//============================== Async Delete Thread Exit Test =====================
// Test to verify the report thread exits cleanly after queuing async delete
TEST_F(ProfileTest, CollectAndReport_ThreadExitsCleanly_AfterAsyncDelete) {
/*
* Test: Verify that CollectAndReport exits the do/while loop correctly
* after the async delete is queued, not attempting to access freed
* profile memory.
*
* This test ensures profile->enable is not read from freed memory,
* preventing the garbage profile name log observed in the bug.
*/
Comment on lines +1581 to +1588

// Create a profile that will trigger the async delete path
Profile* testProfile = (Profile*)malloc(sizeof(Profile));
ASSERT_NE(testProfile, nullptr);
memset(testProfile, 0, sizeof(Profile));

testProfile->name = strdup("AsyncDelThreadTest");
testProfile->enable = true; // Initially enabled
testProfile->protocol = strdup("RBUS_METHOD");
testProfile->encodingType = strdup("JSON");
testProfile->SendErr = 4; // > 3

testProfile->t2RBUSDest = (T2RBUS*)malloc(sizeof(T2RBUS));
ASSERT_NE(testProfile->t2RBUSDest, nullptr);
testProfile->t2RBUSDest->rbusMethodName = strdup("Device.X_NotFound");
testProfile->t2RBUSDest->rbusMethodParamList = nullptr;

testProfile->grepSeekProfile = (GrepSeekProfile*)malloc(sizeof(GrepSeekProfile));
ASSERT_NE(testProfile->grepSeekProfile, nullptr);
memset(testProfile->grepSeekProfile, 0, sizeof(GrepSeekProfile));
testProfile->grepSeekProfile->logFileSeekMap = hash_map_create();
testProfile->grepSeekProfile->execCounter = 0;

testProfile->jsonEncoding = (JSONEncoding*)malloc(sizeof(JSONEncoding));
ASSERT_NE(testProfile->jsonEncoding, nullptr);
testProfile->jsonEncoding->reportFormat = JSONRF_KEYVALUEPAIR;

testProfile->paramList = nullptr;
testProfile->staticParamList = nullptr;
testProfile->gMarkerList = nullptr;
testProfile->eMarkerList = nullptr;
testProfile->topMarkerList = nullptr;
testProfile->cachedReportList = nullptr;
testProfile->jsonReportObj = nullptr;

// Mock expectations
EXPECT_CALL(*g_vectorMock, Vector_Size(_))
.Times(::testing::AtLeast(5))
.WillRepeatedly(Return(0));
Comment on lines +1625 to +1627
Comment on lines +1624 to +1627

EXPECT_CALL(*g_rbusMock, rbusCheckMethodExists(_))
.Times(::testing::AtLeast(1))
Comment on lines +1628 to +1630
.WillRepeatedly(Return(false));

// Verify the conditions that trigger async delete
ASSERT_GT(testProfile->SendErr, 3);
ASSERT_FALSE(rbusCheckMethodExists(testProfile->t2RBUSDest->rbusMethodName));
ASSERT_TRUE(testProfile->enable); // Should be true before delete path
Comment on lines +1633 to +1636

// After async delete is queued, the thread should exit without
// accessing the freed profile object

// Cleanup
free(testProfile->name);
free(testProfile->protocol);
free(testProfile->encodingType);
free(testProfile->t2RBUSDest->rbusMethodName);
free(testProfile->t2RBUSDest);
if (testProfile->grepSeekProfile) {
// logFileSeekMap cleanup: hash_map_remove requires a key argument,
Comment on lines +1646 to +1648
// so we skip it here since we're freeing the entire structure
free(testProfile->grepSeekProfile);
}
free(testProfile->jsonEncoding);
free(testProfile);

SUCCEED();
}

Loading