Bring the wrapper-function call and return prototypes into line with the
convention adopted for orc_rt_Session_callController: the session handle
comes first, then the payload buffer, then the completion callback
immediately followed by its threaded token, with the token last.
orc_rt_WrapperFunctionReturn: (S, CallId, ResultBytes)
-> (S, ResultBytes, CallId)
orc_rt_WrapperFunction: (S, CallId, Return, ArgBytes)
-> (S, ArgBytes, Return, CallId)
The C++ adapters that mirror these signatures are updated to match --
WrapperFunction::handle, SPSWrapperFunction::handle, the
ORC_RT_SPS_WRAPPER macro, and StructuredYield -- along with the Session
dispatch path and the affected unit tests. No functional change beyond
argument order.
319 lines
11 KiB
C++
319 lines
11 KiB
C++
//===- InProcessControllerAccessTest.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 InProcessControllerAccess.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "orc-rt/InProcessControllerAccess.h"
|
|
#include "orc-rt/QueueingRunner.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "CommonTestUtils.h"
|
|
|
|
#include <deque>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
using namespace orc_rt;
|
|
|
|
namespace {
|
|
|
|
// A minimal stand-in for llvm::orc::InProcessEPC. Registers itself on the
|
|
// Connection during OnConnect, exposes hooks for tests to drive cross-calls
|
|
// in either direction, and tears the connection down on destruction.
|
|
class MockIPEPC {
|
|
public:
|
|
using Connection = InProcessControllerAccess::Connection;
|
|
|
|
using OnCallJITDispatchFn =
|
|
move_only_function<void(uint64_t CallId, orc_rt_ControllerHandlerTag T,
|
|
WrapperFunctionBuffer ArgBytes)>;
|
|
using OnReturnWrapperResultFn = move_only_function<void(
|
|
uint64_t CallId, WrapperFunctionBuffer ResultBytes)>;
|
|
|
|
MockIPEPC(Connection *C) : C(C) {
|
|
C->Retain(C);
|
|
C->IPEPC = this;
|
|
C->CallJITDispatch = &callJITDispatchEntry;
|
|
C->ReturnWrapperResult = &returnWrapperResultEntry;
|
|
}
|
|
|
|
MockIPEPC(const MockIPEPC &) = delete;
|
|
MockIPEPC &operator=(const MockIPEPC &) = delete;
|
|
|
|
~MockIPEPC() {
|
|
C->Disconnect(C);
|
|
C->Release(C);
|
|
}
|
|
|
|
void setOnCallJITDispatch(OnCallJITDispatchFn H) {
|
|
OnCallJITDispatch = std::move(H);
|
|
}
|
|
void setOnReturnWrapperResult(OnReturnWrapperResultFn H) {
|
|
OnReturnWrapperResult = std::move(H);
|
|
}
|
|
|
|
// Send a result back to a CallJITDispatch invocation.
|
|
void respondToCall(uint64_t CallId, WrapperFunctionBuffer Result) {
|
|
if (C->EnterMessageScope(C)) {
|
|
C->ReturnJITDispatchResult(C->IPCA, CallId, Result.release());
|
|
C->LeaveMessageScope(C);
|
|
}
|
|
}
|
|
|
|
// Initiate a wrapper call into the executor, simulating a controller-side
|
|
// wrapper invocation. Returns the CallId that ReturnWrapperResult will
|
|
// refer back to.
|
|
uint64_t callIntoExecutor(orc_rt_WrapperFunction Fn,
|
|
WrapperFunctionBuffer ArgBytes) {
|
|
uint64_t CallId = NextCallId++;
|
|
if (C->EnterMessageScope(C)) {
|
|
C->CallWrapper(C->IPCA, CallId, reinterpret_cast<void *>(Fn),
|
|
ArgBytes.release());
|
|
C->LeaveMessageScope(C);
|
|
}
|
|
return CallId;
|
|
}
|
|
|
|
private:
|
|
static void callJITDispatchEntry(void *IPEPC, uint64_t CallId,
|
|
orc_rt_ControllerHandlerTag T,
|
|
orc_rt_WrapperFunctionBuffer ArgBytes) {
|
|
auto *Self = static_cast<MockIPEPC *>(IPEPC);
|
|
WrapperFunctionBuffer Buf(ArgBytes);
|
|
if (Self->OnCallJITDispatch)
|
|
Self->OnCallJITDispatch(CallId, T, std::move(Buf));
|
|
}
|
|
|
|
static void
|
|
returnWrapperResultEntry(void *IPEPC, uint64_t CallId,
|
|
orc_rt_WrapperFunctionBuffer ResultBytes) {
|
|
auto *Self = static_cast<MockIPEPC *>(IPEPC);
|
|
WrapperFunctionBuffer Buf(ResultBytes);
|
|
if (Self->OnReturnWrapperResult)
|
|
Self->OnReturnWrapperResult(CallId, std::move(Buf));
|
|
}
|
|
|
|
Connection *C;
|
|
uint64_t NextCallId = 0;
|
|
OnCallJITDispatchFn OnCallJITDispatch;
|
|
OnReturnWrapperResultFn OnReturnWrapperResult;
|
|
};
|
|
|
|
// Convenience: attach an InProcessControllerAccess to S, constructing a
|
|
// MockIPEPC into MockOut from inside OnConnect.
|
|
void attachWithMock(Session &S, std::unique_ptr<MockIPEPC> &MockOut) {
|
|
S.attach<InProcessControllerAccess>(
|
|
BootstrapInfo(S),
|
|
[&MockOut](InProcessControllerAccess &, BootstrapInfo &,
|
|
InProcessControllerAccess::Connection *C,
|
|
InProcessControllerAccess::BootstrapInfoAccess *) -> Error {
|
|
MockOut = std::make_unique<MockIPEPC>(C);
|
|
return Error::success();
|
|
});
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(InProcessControllerAccessTest, ConstructAndDestroyWithoutConnect) {
|
|
// An InProcessControllerAccess that is never attached to a Session (so its
|
|
// connect method is never called) must still destroy cleanly. The
|
|
// destructor's `if (C)` guard is what makes this safe.
|
|
Session S(mockExecutorProcessInfo(), noDispatch, noErrors);
|
|
|
|
InProcessControllerAccess IPCA(
|
|
S,
|
|
[](InProcessControllerAccess &, BootstrapInfo &,
|
|
InProcessControllerAccess::Connection *,
|
|
InProcessControllerAccess::BootstrapInfoAccess *) -> Error {
|
|
ADD_FAILURE() << "OnConnect should not be called";
|
|
return Error::success();
|
|
});
|
|
|
|
// IPCA is destroyed at scope exit; test passes if there's no crash.
|
|
}
|
|
|
|
TEST(InProcessControllerAccessTest, AttachAndShutdownViaSession) {
|
|
// Smoke test: attach with a successful OnConnect, verify the mock was
|
|
// constructed, then let scope exit drive shutdown.
|
|
Session S(mockExecutorProcessInfo(), noDispatch, noErrors);
|
|
|
|
std::unique_ptr<MockIPEPC> Mock;
|
|
attachWithMock(S, Mock);
|
|
EXPECT_TRUE(Mock) << "Expected OnConnect to construct MockIPEPC";
|
|
}
|
|
|
|
TEST(InProcessControllerAccessTest, OnConnectFailureIsReportedAndDetaches) {
|
|
// If OnConnect returns an Error, IPCA::connect should:
|
|
// (1) Forward the error through Session::reportError.
|
|
// (2) Trigger disconnect (so the Session ends up detached and no further
|
|
// calls into the controller succeed).
|
|
Error Reported = Error::success();
|
|
cantFail(std::move(Reported)); // force checked state
|
|
|
|
Session S(mockExecutorProcessInfo(), noDispatch,
|
|
[&](Error E) { Reported = std::move(E); });
|
|
|
|
S.attach<InProcessControllerAccess>(
|
|
BootstrapInfo(S),
|
|
[](InProcessControllerAccess &, BootstrapInfo &,
|
|
InProcessControllerAccess::Connection *,
|
|
InProcessControllerAccess::BootstrapInfoAccess *) -> Error {
|
|
return make_error<StringError>("fake connect failure");
|
|
});
|
|
|
|
if (Reported)
|
|
EXPECT_EQ(toString(std::move(Reported)), "fake connect failure");
|
|
else
|
|
ADD_FAILURE() << "Expected OnConnect error to be reported";
|
|
|
|
// A subsequent call to the controller should now fail with "no controller
|
|
// attached" (i.e. the Session detached on the OnConnect error).
|
|
std::optional<std::string> CallErr;
|
|
S.callController(
|
|
[&](WrapperFunctionBuffer R) {
|
|
if (const char *Msg = R.getOutOfBandError())
|
|
CallErr = Msg;
|
|
},
|
|
reinterpret_cast<orc_rt_ControllerHandlerTag>(0xdeadbeef),
|
|
WrapperFunctionBuffer::copyFrom("x", 1));
|
|
|
|
ASSERT_TRUE(CallErr);
|
|
EXPECT_EQ(*CallErr, "no controller attached");
|
|
}
|
|
|
|
TEST(InProcessControllerAccessTest, CallControllerSuccess) {
|
|
// A callController call routed through MockIPEPC, which echoes the args
|
|
// back as the result. Verify OnComplete fires with the payload.
|
|
Session S(mockExecutorProcessInfo(), inlineDispatch, noErrors);
|
|
|
|
std::unique_ptr<MockIPEPC> Mock;
|
|
attachWithMock(S, Mock);
|
|
ASSERT_TRUE(Mock);
|
|
|
|
Mock->setOnCallJITDispatch(
|
|
[&](uint64_t CallId, void *, WrapperFunctionBuffer ArgBytes) {
|
|
Mock->respondToCall(CallId, std::move(ArgBytes));
|
|
});
|
|
|
|
std::optional<std::string> Result;
|
|
S.callController(
|
|
[&](WrapperFunctionBuffer R) {
|
|
ASSERT_FALSE(R.getOutOfBandError())
|
|
<< "Unexpected out-of-band error: " << R.getOutOfBandError();
|
|
Result = std::string(R.data(), R.size());
|
|
},
|
|
reinterpret_cast<orc_rt_ControllerHandlerTag>(0xdeadbeef),
|
|
WrapperFunctionBuffer::copyFrom("hello", 5));
|
|
|
|
ASSERT_TRUE(Result);
|
|
EXPECT_EQ(*Result, "hello");
|
|
}
|
|
|
|
TEST(InProcessControllerAccessTest, CallControllerOutOfBandError) {
|
|
// A callController call where the mock responds with an out-of-band error.
|
|
// OnComplete should observe the error message intact.
|
|
Session S(mockExecutorProcessInfo(), inlineDispatch, noErrors);
|
|
|
|
std::unique_ptr<MockIPEPC> Mock;
|
|
attachWithMock(S, Mock);
|
|
ASSERT_TRUE(Mock);
|
|
|
|
Mock->setOnCallJITDispatch(
|
|
[&](uint64_t CallId, void *, WrapperFunctionBuffer) {
|
|
Mock->respondToCall(CallId, WrapperFunctionBuffer::createOutOfBandError(
|
|
"simulated failure"));
|
|
});
|
|
|
|
std::optional<std::string> ErrMsg;
|
|
S.callController(
|
|
[&](WrapperFunctionBuffer R) {
|
|
if (const char *Msg = R.getOutOfBandError())
|
|
ErrMsg = Msg;
|
|
},
|
|
reinterpret_cast<orc_rt_ControllerHandlerTag>(0xdeadbeef),
|
|
WrapperFunctionBuffer::copyFrom("payload", 7));
|
|
|
|
ASSERT_TRUE(ErrMsg);
|
|
EXPECT_EQ(*ErrMsg, "simulated failure");
|
|
}
|
|
|
|
TEST(InProcessControllerAccessTest, DisconnectDrainsPendingCalls) {
|
|
// A callController call is in-flight when the connection drops (the mock
|
|
// never responds). Verify that doDisconnect drains the pending handler with
|
|
// a "disconnected" out-of-band error rather than leaving it stranded.
|
|
Session S(mockExecutorProcessInfo(), inlineDispatch, noErrors);
|
|
|
|
std::unique_ptr<MockIPEPC> Mock;
|
|
attachWithMock(S, Mock);
|
|
ASSERT_TRUE(Mock);
|
|
|
|
// Mock receives the call but never sends a result.
|
|
Mock->setOnCallJITDispatch([](uint64_t, void *, WrapperFunctionBuffer) {});
|
|
|
|
std::optional<std::string> ErrMsg;
|
|
S.callController(
|
|
[&](WrapperFunctionBuffer R) {
|
|
if (const char *Msg = R.getOutOfBandError())
|
|
ErrMsg = Msg;
|
|
},
|
|
reinterpret_cast<orc_rt_ControllerHandlerTag>(0xdeadbeef),
|
|
WrapperFunctionBuffer::copyFrom("payload", 7));
|
|
|
|
ASSERT_FALSE(ErrMsg) << "OnComplete fired prematurely";
|
|
|
|
// Tearing down the mock triggers C->Disconnect, which routes through
|
|
// ConnectionImpl::disconnect → IPCA::doDisconnect and drains the pending
|
|
// OnComplete with a "disconnected" error.
|
|
Mock.reset();
|
|
|
|
ASSERT_TRUE(ErrMsg);
|
|
EXPECT_EQ(*ErrMsg, "disconnected");
|
|
}
|
|
|
|
// Wrapper function that echoes ArgBytes back as the result. Used to exercise
|
|
// the controller-initiated wrapper-call path without pulling in SPS.
|
|
static void echoWrapper(orc_rt_SessionRef S,
|
|
orc_rt_WrapperFunctionBuffer ArgBytes,
|
|
orc_rt_WrapperFunctionReturn Return, uint64_t CallId) {
|
|
Return(S, ArgBytes, CallId);
|
|
}
|
|
|
|
TEST(InProcessControllerAccessTest, CallFromControllerSuccess) {
|
|
// The mock IPEPC initiates a wrapper call into IPCA. The Session's
|
|
// dispatch hook (a QueueingRunner over `Tasks`) enqueues the
|
|
// invocation; draining the queue runs the wrapper, which echoes its
|
|
// arguments back. Verify the mock receives the echoed bytes via
|
|
// ReturnWrapperResult.
|
|
QueueingRunner<>::WorkQueue Tasks;
|
|
Session S(mockExecutorProcessInfo(), QueueingRunner(Tasks), noErrors);
|
|
|
|
std::unique_ptr<MockIPEPC> Mock;
|
|
attachWithMock(S, Mock);
|
|
ASSERT_TRUE(Mock);
|
|
|
|
std::optional<std::string> Result;
|
|
Mock->setOnReturnWrapperResult(
|
|
[&](uint64_t, WrapperFunctionBuffer ResultBytes) {
|
|
Result = std::string(ResultBytes.data(), ResultBytes.size());
|
|
});
|
|
|
|
Mock->callIntoExecutor(echoWrapper,
|
|
WrapperFunctionBuffer::copyFrom("world", 5));
|
|
|
|
// Nothing has run yet -- the wrapper is sitting in `Tasks` waiting to be
|
|
// dispatched.
|
|
ASSERT_FALSE(Result);
|
|
|
|
QueueingRunner<>::runFIFOUntilEmpty(Tasks);
|
|
|
|
ASSERT_TRUE(Result);
|
|
EXPECT_EQ(*Result, "world");
|
|
}
|