blob: e9d81e55fb92aa992b0cf74a2eee112ad75984d8 [file] [edit]
//===- AllocActionTest.cpp ------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Tests for orc-rt's AllocAction.h APIs.
//
// These tests exercise the AllocAction layer directly, using a small bespoke
// (de)serializer that exchanges raw int* values via memcpy. SPS-layer tests
// live in SPSAllocActionTest.cpp.
//
//===----------------------------------------------------------------------===//
#include "orc-rt/AllocAction.h"
#include "gtest/gtest.h"
#include "CommonTestUtils.h"
#include <cstring>
using namespace orc_rt;
namespace {
// A minimal AllocActionFunction (de)serializer pair that exchanges a single
// int* via memcpy. Used to drive AllocActionFunction::handle without pulling
// in SPS.
struct IntPtrDeserializer {
bool deserialize(const char *ArgData, size_t ArgSize,
std::tuple<int *> &Args) {
if (ArgSize != sizeof(int *))
return false;
memcpy(&std::get<0>(Args), ArgData, sizeof(int *));
return true;
}
};
struct IdentitySerializer {
static WrapperFunctionBuffer serialize(WrapperFunctionBuffer B) { return B; }
};
WrapperFunctionBuffer makeIntPtrArgBuffer(int *P) {
auto B = WrapperFunctionBuffer::allocate(sizeof(int *));
memcpy(B.data(), &P, sizeof(int *));
return B;
}
} // namespace
static orc_rt_WrapperFunctionBuffer noopAction(const char *ArgData,
size_t ArgSize) {
return WrapperFunctionBuffer().release();
}
// Increments an int via pointer.
static orc_rt_WrapperFunctionBuffer
increment_int_ptr_action(const char *ArgData, size_t ArgSize) {
return AllocActionFunction::handle(ArgData, ArgSize, IntPtrDeserializer(),
IdentitySerializer(),
[](int *P) {
++*P;
return WrapperFunctionBuffer();
})
.release();
}
// Decrements an int via pointer.
static orc_rt_WrapperFunctionBuffer
decrement_int_ptr_action(const char *ArgData, size_t ArgSize) {
return AllocActionFunction::handle(ArgData, ArgSize, IntPtrDeserializer(),
IdentitySerializer(),
[](int *P) {
--*P;
return WrapperFunctionBuffer();
})
.release();
}
// Always returns an out-of-band error.
static orc_rt_WrapperFunctionBuffer fail_action(const char *ArgData,
size_t ArgSize) {
return WrapperFunctionBuffer::createOutOfBandError("failed").release();
}
// Always returns an out-of-band error with a distinct message. Used to tell
// finalize-path and dealloc-path failures apart in error-reporting tests.
static orc_rt_WrapperFunctionBuffer fail_action_2(const char *ArgData,
size_t ArgSize) {
return WrapperFunctionBuffer::createOutOfBandError("failed_2").release();
}
TEST(AllocActionTest, DefaultConstruct) {
AllocAction AA;
EXPECT_FALSE(AA);
}
TEST(AllocActionTest, ConstructWithAction) {
AllocAction AA(noopAction, WrapperFunctionBuffer());
EXPECT_TRUE(AA);
}
TEST(AllocActionTest, RunBasicAction) {
int Val = 0;
AllocAction IncVal(increment_int_ptr_action, makeIntPtrArgBuffer(&Val));
EXPECT_TRUE(IncVal);
auto B = IncVal();
EXPECT_TRUE(B.empty());
EXPECT_EQ(Val, 1);
}
TEST(AllocActionTest, RunFinalizationActionsComplete) {
int Val = 0;
std::vector<AllocActionPair> InitialActions;
auto MakeAAOnVal = [&](AllocActionFn Fn) {
return AllocAction(Fn, makeIntPtrArgBuffer(&Val));
};
InitialActions.push_back({MakeAAOnVal(increment_int_ptr_action),
MakeAAOnVal(decrement_int_ptr_action)});
InitialActions.push_back({MakeAAOnVal(increment_int_ptr_action),
MakeAAOnVal(decrement_int_ptr_action)});
auto DeallocActions =
cantFail(runFinalizeActions(std::move(InitialActions), noErrors));
EXPECT_EQ(Val, 2);
runDeallocActions(std::move(DeallocActions), noErrors);
EXPECT_EQ(Val, 0);
}
TEST(AllocActionTest, RunFinalizeActionsFail) {
int SucceedingPairVal = 0;
int FailingPairDeallocVal = 0;
int AfterFailurePairVal = 0;
std::vector<AllocActionPair> InitialActions;
auto MakeAA = [&](AllocActionFn Fn, int *P) {
return AllocAction(Fn, makeIntPtrArgBuffer(P));
};
// First pair's finalize and dealloc actions both increment SucceedingPairVal.
InitialActions.push_back(
{MakeAA(increment_int_ptr_action, &SucceedingPairVal),
MakeAA(increment_int_ptr_action, &SucceedingPairVal)});
// Second pair's finalize fails, so its dealloc action must not run.
InitialActions.push_back(
{AllocAction(fail_action, WrapperFunctionBuffer()), // finalize fails
MakeAA(increment_int_ptr_action, &FailingPairDeallocVal)});
// Third pair: finalize actions past the failure point must not run.
InitialActions.push_back(
{MakeAA(increment_int_ptr_action, &AfterFailurePairVal),
MakeAA(increment_int_ptr_action, &AfterFailurePairVal)});
auto DeallocActions = runFinalizeActions(std::move(InitialActions), noErrors);
if (DeallocActions) {
ADD_FAILURE() << "Failed to report error from runFinalizeActions";
return;
}
EXPECT_EQ(toString(DeallocActions.takeError()), std::string("failed"));
// First pair fully ran: +1 from its finalize action, +1 from its dealloc
// action during cleanup.
EXPECT_EQ(SucceedingPairVal, 2);
// Second pair's finalize failed, so its dealloc action must not run.
EXPECT_EQ(FailingPairDeallocVal, 0);
// Third pair's finalize is past the failure point, so it must not run.
EXPECT_EQ(AfterFailurePairVal, 0);
}
TEST(AllocActionTest, RunFinalizeActionsNullFinalize) {
int Val = 0;
std::vector<AllocActionPair> InitialActions;
auto MakeAAOnVal = [&](AllocActionFn Fn) {
return AllocAction(Fn, makeIntPtrArgBuffer(&Val));
};
InitialActions.push_back({MakeAAOnVal(increment_int_ptr_action),
MakeAAOnVal(decrement_int_ptr_action)});
InitialActions.push_back({AllocAction(nullptr, WrapperFunctionBuffer()),
MakeAAOnVal(decrement_int_ptr_action)});
auto DeallocActions =
cantFail(runFinalizeActions(std::move(InitialActions), noErrors));
// Both dealloc actions should be included in the returned list, despite one
// of them having a null finalize action.
EXPECT_EQ(DeallocActions.size(), 2U);
runDeallocActions(std::move(DeallocActions), noErrors);
EXPECT_EQ(Val, -1);
}
TEST(AllocActionTest, RunFinalizeActionsNullDealloc) {
int Val = 0;
std::vector<AllocActionPair> InitialActions;
auto MakeAAOnVal = [&](AllocActionFn Fn) {
return AllocAction(Fn, makeIntPtrArgBuffer(&Val));
};
InitialActions.push_back({MakeAAOnVal(increment_int_ptr_action),
MakeAAOnVal(decrement_int_ptr_action)});
InitialActions.push_back({MakeAAOnVal(increment_int_ptr_action),
AllocAction(nullptr, WrapperFunctionBuffer())});
auto DeallocActions =
cantFail(runFinalizeActions(std::move(InitialActions), noErrors));
// Null dealloc actions should be filtered out of the returned list.
EXPECT_EQ(DeallocActions.size(), 1U);
runDeallocActions(std::move(DeallocActions), noErrors);
EXPECT_EQ(Val, 1);
}
TEST(AllocActionTest, RunDeallocActionsReportsError) {
int Val = 0;
std::vector<AllocAction> DeallocActions;
// First dealloc action runs second (dealloc runs in reverse), but must still
// run despite the failure reported below.
DeallocActions.push_back(
AllocAction(increment_int_ptr_action, makeIntPtrArgBuffer(&Val)));
// Runs first: fails and should be reported.
DeallocActions.push_back(AllocAction(fail_action, WrapperFunctionBuffer()));
std::vector<std::string> ErrMsgs;
runDeallocActions(std::move(DeallocActions), AccumulateErrors(ErrMsgs));
ASSERT_EQ(ErrMsgs.size(), 1U);
EXPECT_EQ(ErrMsgs[0], "failed");
// The non-failing dealloc action still ran.
EXPECT_EQ(Val, 1);
}
TEST(AllocActionTest, RunDeallocActionsReportsAllErrors) {
std::vector<AllocAction> DeallocActions;
DeallocActions.push_back(AllocAction(fail_action, WrapperFunctionBuffer()));
DeallocActions.push_back(AllocAction(fail_action_2, WrapperFunctionBuffer()));
std::vector<std::string> ErrMsgs;
runDeallocActions(std::move(DeallocActions), AccumulateErrors(ErrMsgs));
// Both failures reported; dealloc runs in reverse, so fail_action_2 first.
ASSERT_EQ(ErrMsgs.size(), 2U);
EXPECT_EQ(ErrMsgs[0], "failed_2");
EXPECT_EQ(ErrMsgs[1], "failed");
}
TEST(AllocActionTest, RunFinalizeActionsFailReportsCleanupErrors) {
int Val = 0;
std::vector<AllocActionPair> InitialActions;
// Finalize succeeds; its dealloc decrements Val during cleanup. Proves
// cleanup continues past the failure of the pair below.
InitialActions.push_back(
{AllocAction(increment_int_ptr_action, makeIntPtrArgBuffer(&Val)),
AllocAction(decrement_int_ptr_action, makeIntPtrArgBuffer(&Val))});
// Finalize succeeds; its dealloc fails during the cleanup triggered below.
InitialActions.push_back(
{AllocAction(increment_int_ptr_action, makeIntPtrArgBuffer(&Val)),
AllocAction(fail_action, WrapperFunctionBuffer())});
// Finalize fails, triggering cleanup of the accumulated dealloc actions.
InitialActions.push_back({AllocAction(fail_action_2, WrapperFunctionBuffer()),
AllocAction(nullptr, WrapperFunctionBuffer())});
std::vector<std::string> ErrMsgs;
auto DeallocActions =
runFinalizeActions(std::move(InitialActions), AccumulateErrors(ErrMsgs));
// The finalize failure is the returned error...
ASSERT_FALSE(DeallocActions);
EXPECT_EQ(toString(DeallocActions.takeError()), std::string("failed_2"));
// ...while the cleanup dealloc failure is reported out-of-band.
ASSERT_EQ(ErrMsgs.size(), 1U);
EXPECT_EQ(ErrMsgs[0], "failed");
// Both finalize actions ran (Val += 2) and both cleanup dealloc actions ran:
// the failing one left Val untouched, the surviving one decremented it. That
// the decrement took effect proves cleanup continued past the failure.
EXPECT_EQ(Val, 1);
}