// Build preconditions: // - Have vcpkg installed // > vcpkg integrate install // > vcpkg install capnproto #include #include #include #include #include "test.capnp.h" using namespace std; using namespace capnp; using namespace kj; using namespace capnproto_test::capnp::test; void PrintUsage(const char* argv[]) { cerr << "usage: " << argv[0] << "{client|server} HOST[:PORT]" << std::endl; } inline Data::Reader data(const char* str) { return Data::Reader(reinterpret_cast(str), strlen(str)); } template void genericInitTestMessage(Builder builder) { builder.setVoidField(VOID); builder.setVoidField(); // Means the same as above. builder.setBoolField(true); builder.setInt8Field(-123); builder.setInt16Field(-12345); builder.setInt32Field(-12345678); builder.setInt64Field(-123456789012345ll); builder.setUInt8Field(234u); builder.setUInt16Field(45678u); builder.setUInt32Field(3456789012u); builder.setUInt64Field(12345678901234567890ull); builder.setFloat32Field(1234.5); builder.setFloat64Field(-123e45); builder.setTextField("foo"); builder.setDataField(data("bar")); { auto subBuilder = builder.initStructField(); subBuilder.setVoidField(VOID); subBuilder.setBoolField(true); subBuilder.setInt8Field(-12); subBuilder.setInt16Field(3456); subBuilder.setInt32Field(-78901234); subBuilder.setInt64Field(56789012345678ll); subBuilder.setUInt8Field(90u); subBuilder.setUInt16Field(1234u); subBuilder.setUInt32Field(56789012u); subBuilder.setUInt64Field(345678901234567890ull); subBuilder.setFloat32Field(-1.25e-10f); subBuilder.setFloat64Field(345); subBuilder.setTextField("baz"); subBuilder.setDataField(data("qux")); { auto subSubBuilder = subBuilder.initStructField(); subSubBuilder.setTextField("nested"); subSubBuilder.initStructField().setTextField("really nested"); } subBuilder.setEnumField(TestEnum::BAZ); subBuilder.setVoidList({ VOID, VOID, VOID }); subBuilder.setBoolList({ false, true, false, true, true }); subBuilder.setInt8List({ 12, -34, -0x80, 0x7f }); subBuilder.setInt16List({ 1234, -5678, -0x8000, 0x7fff }); // gcc warns on -0x800... and the only work-around I could find was to do -0x7ff...-1. subBuilder.setInt32List({ 12345678, -90123456, -0x7fffffff - 1, 0x7fffffff }); subBuilder.setInt64List({ 123456789012345ll, -678901234567890ll, -0x7fffffffffffffffll - 1, 0x7fffffffffffffffll }); subBuilder.setUInt8List({ 12u, 34u, 0u, 0xffu }); subBuilder.setUInt16List({ 1234u, 5678u, 0u, 0xffffu }); subBuilder.setUInt32List({ 12345678u, 90123456u, 0u, 0xffffffffu }); subBuilder.setUInt64List({ 123456789012345ull, 678901234567890ull, 0ull, 0xffffffffffffffffull }); subBuilder.setFloat32List({ 0, 1234567, 1e37f, -1e37f, 1e-37f, -1e-37f }); subBuilder.setFloat64List({ 0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306 }); subBuilder.setTextList({ "quux", "corge", "grault" }); subBuilder.setDataList({ data("garply"), data("waldo"), data("fred") }); { auto listBuilder = subBuilder.initStructList(3); listBuilder[0].setTextField("x structlist 1"); listBuilder[1].setTextField("x structlist 2"); listBuilder[2].setTextField("x structlist 3"); } subBuilder.setEnumList({ TestEnum::QUX, TestEnum::BAR, TestEnum::GRAULT }); } builder.setEnumField(TestEnum::CORGE); builder.initVoidList(6); builder.setBoolList({ true, false, false, true }); builder.setInt8List({ 111, -111 }); builder.setInt16List({ 11111, -11111 }); builder.setInt32List({ 111111111, -111111111 }); builder.setInt64List({ 1111111111111111111ll, -1111111111111111111ll }); builder.setUInt8List({ 111u, 222u }); builder.setUInt16List({ 33333u, 44444u }); builder.setUInt32List({ 3333333333u }); builder.setUInt64List({ 11111111111111111111ull }); builder.setFloat32List({ 5555.5, kj::inf(), -kj::inf(), kj::nan() }); builder.setFloat64List({ 7777.75, kj::inf(), -kj::inf(), kj::nan() }); builder.setTextList({ "plugh", "xyzzy", "thud" }); builder.setDataList({ data("oops"), data("exhausted"), data("rfc3092") }); { auto listBuilder = builder.initStructList(3); listBuilder[0].setTextField("structlist 1"); listBuilder[1].setTextField("structlist 2"); listBuilder[2].setTextField("structlist 3"); } builder.setEnumList({ TestEnum::FOO, TestEnum::GARPLY }); } template void EXPECT_EQ(const T1& expected, const T2& actual) { if (expected != actual) cout << "Fail" << endl; } void EXPECT_TRUE(bool truth) { if (!truth) cout << "Fail: Not true" << endl; } void EXPECT_FALSE(bool lie) { if (lie) cout << "Fail: Not false" << endl; } template void ASSERT_EQ(const T1& expected, const T2& actual) { if (expected != actual) { cout << "Fatal" << endl; throw runtime_error("Failed assertion"); } } void EXPECT_FLOAT_EQ(float expected, float actual) { EXPECT_EQ(expected, actual); } void EXPECT_DOUBLE_EQ(double expected, double actual) { EXPECT_EQ(expected, actual); } template inline void checkElement(T a, T b) { EXPECT_EQ(a, b); } template <> inline void checkElement(float a, float b) { EXPECT_FLOAT_EQ(a, b); } template <> inline void checkElement(double a, double b) { EXPECT_DOUBLE_EQ(a, b); } template void checkList(T reader, std::initializer_list expected) { ASSERT_EQ(expected.size(), reader.size()); for (uint i = 0; i < expected.size(); i++) { checkElement(expected.begin()[i], reader[i]); } } template void checkList(T reader, std::initializer_list expected) { ASSERT_EQ(expected.size(), reader.size()); for (uint i = 0; i < expected.size(); i++) { checkElement(expected.begin()[i], reader[i]); } } template void genericCheckTestMessage(Reader reader) { EXPECT_EQ(VOID, reader.getVoidField()); EXPECT_EQ(true, reader.getBoolField()); EXPECT_EQ(-123, reader.getInt8Field()); EXPECT_EQ(-12345, reader.getInt16Field()); EXPECT_EQ(-12345678, reader.getInt32Field()); EXPECT_EQ(-123456789012345ll, reader.getInt64Field()); EXPECT_EQ(234u, reader.getUInt8Field()); EXPECT_EQ(45678u, reader.getUInt16Field()); EXPECT_EQ(3456789012u, reader.getUInt32Field()); EXPECT_EQ(12345678901234567890ull, reader.getUInt64Field()); EXPECT_FLOAT_EQ(1234.5f, reader.getFloat32Field()); EXPECT_DOUBLE_EQ(-123e45, reader.getFloat64Field()); EXPECT_EQ("foo", reader.getTextField()); EXPECT_EQ(data("bar"), reader.getDataField()); { auto subReader = reader.getStructField(); EXPECT_EQ(VOID, subReader.getVoidField()); EXPECT_EQ(true, subReader.getBoolField()); EXPECT_EQ(-12, subReader.getInt8Field()); EXPECT_EQ(3456, subReader.getInt16Field()); EXPECT_EQ(-78901234, subReader.getInt32Field()); EXPECT_EQ(56789012345678ll, subReader.getInt64Field()); EXPECT_EQ(90u, subReader.getUInt8Field()); EXPECT_EQ(1234u, subReader.getUInt16Field()); EXPECT_EQ(56789012u, subReader.getUInt32Field()); EXPECT_EQ(345678901234567890ull, subReader.getUInt64Field()); EXPECT_FLOAT_EQ(-1.25e-10f, subReader.getFloat32Field()); EXPECT_DOUBLE_EQ(345, subReader.getFloat64Field()); EXPECT_EQ("baz", subReader.getTextField()); EXPECT_EQ(data("qux"), subReader.getDataField()); { auto subSubReader = subReader.getStructField(); EXPECT_EQ("nested", subSubReader.getTextField()); EXPECT_EQ("really nested", subSubReader.getStructField().getTextField()); } EXPECT_EQ(TestEnum::BAZ, subReader.getEnumField()); checkList(subReader.getVoidList(), { VOID, VOID, VOID }); checkList(subReader.getBoolList(), { false, true, false, true, true }); checkList(subReader.getInt8List(), { 12, -34, -0x80, 0x7f }); checkList(subReader.getInt16List(), { 1234, -5678, -0x8000, 0x7fff }); // gcc warns on -0x800... and the only work-around I could find was to do -0x7ff...-1. checkList(subReader.getInt32List(), { 12345678, -90123456, -0x7fffffff - 1, 0x7fffffff }); checkList(subReader.getInt64List(), { 123456789012345ll, -678901234567890ll, -0x7fffffffffffffffll - 1, 0x7fffffffffffffffll }); checkList(subReader.getUInt8List(), { 12u, 34u, 0u, 0xffu }); checkList(subReader.getUInt16List(), { 1234u, 5678u, 0u, 0xffffu }); checkList(subReader.getUInt32List(), { 12345678u, 90123456u, 0u, 0xffffffffu }); checkList(subReader.getUInt64List(), { 123456789012345ull, 678901234567890ull, 0ull, 0xffffffffffffffffull }); checkList(subReader.getFloat32List(), { 0.0f, 1234567.0f, 1e37f, -1e37f, 1e-37f, -1e-37f }); checkList(subReader.getFloat64List(), { 0.0, 123456789012345.0, 1e306, -1e306, 1e-306, -1e-306 }); checkList(subReader.getTextList(), { "quux", "corge", "grault" }); checkList(subReader.getDataList(), { data("garply"), data("waldo"), data("fred") }); { auto listReader = subReader.getStructList(); ASSERT_EQ(3u, listReader.size()); EXPECT_EQ("x structlist 1", listReader[0].getTextField()); EXPECT_EQ("x structlist 2", listReader[1].getTextField()); EXPECT_EQ("x structlist 3", listReader[2].getTextField()); } checkList(subReader.getEnumList(), { TestEnum::QUX, TestEnum::BAR, TestEnum::GRAULT }); } EXPECT_EQ(TestEnum::CORGE, reader.getEnumField()); EXPECT_EQ(6u, reader.getVoidList().size()); checkList(reader.getBoolList(), { true, false, false, true }); checkList(reader.getInt8List(), { 111, -111 }); checkList(reader.getInt16List(), { 11111, -11111 }); checkList(reader.getInt32List(), { 111111111, -111111111 }); checkList(reader.getInt64List(), { 1111111111111111111ll, -1111111111111111111ll }); checkList(reader.getUInt8List(), { 111u, 222u }); checkList(reader.getUInt16List(), { 33333u, 44444u }); checkList(reader.getUInt32List(), { 3333333333u }); checkList(reader.getUInt64List(), { 11111111111111111111ull }); { auto listReader = reader.getFloat32List(); ASSERT_EQ(4u, listReader.size()); EXPECT_EQ(5555.5f, listReader[0]); EXPECT_EQ(kj::inf(), listReader[1]); EXPECT_EQ(-kj::inf(), listReader[2]); EXPECT_TRUE(isNaN(listReader[3])); } { auto listReader = reader.getFloat64List(); ASSERT_EQ(4u, listReader.size()); EXPECT_EQ(7777.75, listReader[0]); EXPECT_EQ(kj::inf(), listReader[1]); EXPECT_EQ(-kj::inf(), listReader[2]); EXPECT_TRUE(isNaN(listReader[3])); } checkList(reader.getTextList(), { "plugh", "xyzzy", "thud" }); checkList(reader.getDataList(), { data("oops"), data("exhausted"), data("rfc3092") }); { auto listReader = reader.getStructList(); ASSERT_EQ(3u, listReader.size()); EXPECT_EQ("structlist 1", listReader[0].getTextField()); EXPECT_EQ("structlist 2", listReader[1].getTextField()); EXPECT_EQ("structlist 3", listReader[2].getTextField()); } checkList(reader.getEnumList(), { TestEnum::FOO, TestEnum::GARPLY }); } class TestInterfaceImpl final : public TestInterface::Server { int& callCount_; public: TestInterfaceImpl(int& callCount): callCount_(callCount) { } ~TestInterfaceImpl() { cout << "~" << endl; } kj::Promise foo(FooContext context) override { ++callCount_; auto params = context.getParams(); cout << "foo " << params.getI() << " " << params.getJ() << endl; context.initResults().setX("foo"); return kj::READY_NOW; } kj::Promise baz(BazContext context) override { ++callCount_; cout << "baz" << endl; try { genericCheckTestMessage(context.getParams().getS()); } catch (const runtime_error&) { } cout << "baz fin" << endl; return kj::READY_NOW; } }; class TestCapDestructor final : public TestInterface::Server { // Implementation of TestInterface that notifies when it is destroyed. public: TestCapDestructor(kj::Own>&& fulfiller) : fulfiller(kj::mv(fulfiller)), impl(dummy) {} ~TestCapDestructor() { fulfiller->fulfill(); } kj::Promise foo(FooContext context) { return impl.foo(context); } private: kj::Own> fulfiller; int dummy = 0; TestInterfaceImpl impl; }; class TestExtendsImpl final : public TestExtends::Server { public: ~TestExtendsImpl() { cout << "~" << endl; } kj::Promise foo(FooContext context) { cout << "foo" << endl; auto params = context.getParams(); auto result = context.getResults(); EXPECT_EQ(321, params.getI()); EXPECT_FALSE(params.getJ()); result.setX("bar"); return kj::READY_NOW; } kj::Promise grault(GraultContext context) { cout << "grault" << endl; context.releaseParams(); genericInitTestMessage(context.getResults()); return kj::READY_NOW; } }; class TestPipelineImpl final : public TestPipeline::Server { public: ~TestPipelineImpl() { cout << "~" << endl; } kj::Promise getCap(GetCapContext context) override { cout << "getCap" << endl; auto params = context.getParams(); EXPECT_EQ(234, params.getN()); auto cap = params.getInCap(); context.releaseParams(); auto request = cap.fooRequest(); request.setI(123); request.setJ(true); return request.send().then( [this, KJ_CPCAP(context)](Response&& response) mutable { EXPECT_EQ("foo", response.getX()); auto result = context.getResults(); result.setS("bar"); result.initOutBox().setCap(kj::heap()); cout << "getCap fin" << endl; }); } kj::Promise getAnyCap(GetAnyCapContext context) override { cout << "getAnyCap" << endl; auto params = context.getParams(); EXPECT_EQ(234, params.getN()); auto cap = params.getInCap(); context.releaseParams(); auto request = cap.castAs().fooRequest(); request.setI(123); request.setJ(true); return request.send().then( [this, KJ_CPCAP(context)](Response&& response) mutable { EXPECT_EQ("foo", response.getX()); auto result = context.getResults(); result.setS("bar"); result.initOutBox().setCap(kj::heap()); cout << "getAnyCap fin" << endl; }); } }; class TestCallOrderImpl final : public TestCallOrder::Server { public: uint32_t count; TestCallOrderImpl() : count(0) { } ~TestCallOrderImpl() { cout << "~" << endl; } kj::Promise getCallSequence(GetCallSequenceContext context) override { auto result = context.getResults(); result.setN(count++); return kj::READY_NOW; } }; class TestTailCallerImpl final : public TestTailCaller::Server { public: ~TestTailCallerImpl() { cout << "~" << endl; } kj::Promise foo(FooContext context) override { cout << "foo" << endl; auto params = context.getParams(); auto tailRequest = params.getCallee().fooRequest(); tailRequest.setI(params.getI()); tailRequest.setT("from TestTailCaller"); return context.tailCall(kj::mv(tailRequest)); } }; class TestTailCalleeImpl final : public TestTailCallee::Server { int& callCount_; public: TestTailCalleeImpl(int& callCount) : callCount_(callCount) { } ~TestTailCalleeImpl() { cout << "~" << endl; } kj::Promise foo(FooContext context) override { ++callCount_; cout << "foo" << endl; auto params = context.getParams(); auto results = context.getResults(); results.setI(params.getI()); results.setT(params.getT()); results.setC(kj::heap()); return kj::READY_NOW; } }; class HandleImpl final : public TestHandle::Server { public: HandleImpl() { cout << "++" << endl; } ~HandleImpl() { cout << "--" << endl; } }; class TestMoreStuffImpl final : public TestMoreStuff::Server { public: uint32_t callCount; TestInterface::Client clientToHold; TestMoreStuffImpl() : callCount(0), clientToHold(nullptr) { } ~TestMoreStuffImpl() { cout << "~" << endl; } kj::Promise getCallSequence(GetCallSequenceContext context) override { cout << "getCallSequence" << endl; auto result = context.getResults(); result.setN(callCount++); return kj::READY_NOW; } kj::Promise callFoo(CallFooContext context) override { ++callCount; cout << "callFoo" << endl; auto params = context.getParams(); auto cap = params.getCap(); auto request = cap.fooRequest(); request.setI(123); request.setJ(true); return request.send().then( [KJ_CPCAP(context)](Response&& response) mutable { EXPECT_EQ("foo", response.getX()); context.getResults().setS("bar"); cout << "fin" << endl; }); } kj::Promise callFooWhenResolved(CallFooWhenResolvedContext context) override { ++callCount; cout << "callFooWhenResolved" << endl; auto params = context.getParams(); auto cap = params.getCap(); return cap.whenResolved().then([KJ_CPCAP(cap), KJ_CPCAP(context)]() mutable { auto request = cap.fooRequest(); request.setI(123); request.setJ(true); return request.send().then( [KJ_CPCAP(context)](Response&& response) mutable { EXPECT_EQ("foo", response.getX()); context.getResults().setS("bar"); cout << "fin" << endl; }); }); } kj::Promise neverReturn(NeverReturnContext context) override { ++callCount; cout << "neverReturn" << endl; // Attach `cap` to the promise to make sure it is released. auto promise = kj::Promise(kj::NEVER_DONE).attach(context.getParams().getCap()); // Also attach `cap` to the result struct to make sure that is released. context.getResults().setCapCopy(context.getParams().getCap()); context.allowCancellation(); return kj::mv(promise); } kj::Promise hold(HoldContext context) override { ++callCount; cout << "hold" << endl; auto params = context.getParams(); clientToHold = params.getCap(); return kj::READY_NOW; } kj::Promise callHeld(CallHeldContext context) override { ++callCount; cout << "callHeld" << endl; auto request = clientToHold.fooRequest(); request.setI(123); request.setJ(true); return request.send().then( [KJ_CPCAP(context)](Response&& response) mutable { EXPECT_EQ("foo", response.getX()); context.getResults().setS("bar"); }); } kj::Promise getHeld(GetHeldContext context) { ++callCount; cout << "getHeld" << endl; auto result = context.getResults(); result.setCap(clientToHold); return kj::READY_NOW; } kj::Promise simpleLoop(int rem) { if (rem <= 0) { return kj::READY_NOW; } else { std::this_thread::sleep_for(std::chrono::milliseconds(1)); return kj::evalLater([this, rem]() mutable { return simpleLoop(rem - 1); }); } } kj::Promise echo(EchoContext context) { ++callCount; cout << "echo" << endl; auto params = context.getParams(); auto result = context.getResults(); result.setCap(params.getCap()); return simpleLoop(100); // Loop a little to provoke real promise pipelining } kj::Promise expectCancel(ExpectCancelContext context) { cout << "expectCancel" << endl; auto cap = context.getParams().getCap(); context.allowCancellation(); // return loop(0, cap, context); return kj::NEVER_DONE; } kj::Promise loop(uint depth, TestInterface::Client cap, ExpectCancelContext context) { if (depth > 100000000) { cout << "Looped too long, giving up." << endl; return kj::READY_NOW; } else { return kj::evalLater([this, depth, KJ_CPCAP(cap), KJ_CPCAP(context)]() mutable { return loop(depth + 1, cap, context); }); } } kj::Promise getHandle(GetHandleContext context) override { cout << "getHandle" << endl; context.getResults().setHandle(kj::heap()); return kj::READY_NOW; } kj::Promise getNull(GetNullContext context) { cout << "getNull" << endl; return kj::READY_NOW; } kj::Promise getEnormousString(GetEnormousStringContext context) { cout << "getEnormousString" << endl; context.getResults().initStr(100000000); // 100MB return kj::READY_NOW; } }; void BasicTest(capnp::EzRpcClient& rpc) { cout << "Basic test start" << endl; auto client = rpc.getMain(); auto request1 = client.fooRequest(); request1.setI(123); request1.setJ(true); auto promise1 = request1.send(); // We used to call bar() after baz(), hence the numbering, but this masked the case where the // RPC system actually disconnected on bar() (thus returning an exception, which we decided // was expected). bool barFailed = false; auto request3 = client.barRequest(); auto promise3 = request3.send().then( [](Response&& response) { cout << "Expected bar() call to fail." << endl; }, [&](kj::Exception&& e) { barFailed = true; }); auto request2 = client.bazRequest(); genericInitTestMessage(request2.initS()); auto promise2 = request2.send(); auto response1 = promise1.wait(rpc.getWaitScope()); EXPECT_EQ("foo", response1.getX()); auto response2 = promise2.wait(rpc.getWaitScope()); promise3.wait(rpc.getWaitScope()); EXPECT_TRUE(barFailed); cout << "Basic test end" << endl; } void PipeliningTest(capnp::EzRpcClient& rpc) { cout << "Pipelining test start" << endl; auto client = rpc.getMain(); int chainedCallCount = 0; auto request = client.getCapRequest(); request.setN(234); request.setInCap(kj::heap(chainedCallCount)); auto promise = request.send(); auto pipelineRequest = promise.getOutBox().getCap().fooRequest(); pipelineRequest.setI(321); auto pipelinePromise = pipelineRequest.send(); auto pipelineRequest2 = promise.getOutBox().getCap().castAs().graultRequest(); auto pipelinePromise2 = pipelineRequest2.send(); promise = nullptr; // Just to be annoying, drop the original promise. EXPECT_EQ(0, chainedCallCount); auto response = pipelinePromise.wait(rpc.getWaitScope()); EXPECT_EQ("bar", response.getX()); auto response2 = pipelinePromise2.wait(rpc.getWaitScope()); genericCheckTestMessage((TestAllTypes::Reader)response2); EXPECT_EQ(1, chainedCallCount); cout << "Pipelining test end" << endl; } void ReleaseTest(capnp::EzRpcClient& rpc) { cout << "Release test start" << endl; auto client = rpc.getMain(); auto& waitScope = rpc.getWaitScope(); auto handle1 = client.getHandleRequest().send().wait(waitScope).getHandle(); auto promise = client.getHandleRequest().send(); auto handle2 = promise.wait(waitScope).getHandle(); string s; cout << "sync" << endl; cin >> s; handle1 = nullptr; for (uint i = 0; i < 16; i++) kj::evalLater([]() {}).wait(waitScope); cout << "handle1 null" << endl; cin >> s; handle2 = nullptr; for (uint i = 0; i < 16; i++) kj::evalLater([]() {}).wait(waitScope); cout << "handle2 null" << endl; cin >> s; promise = nullptr; for (uint i = 0; i < 16; i++) kj::evalLater([]() {}).wait(waitScope); waitScope.poll(); cout << "promise null" << endl; cin >> s; cout << "Release test end" << endl; } void ReleaseOnCancelTest(capnp::EzRpcClient& rpc) { cout << "ReleaseOnCancel test start" << endl; auto client = rpc.getMain(); auto& waitScope = rpc.getWaitScope(); { auto promise = client.getHandleRequest().send(); // If the server receives cancellation too early, it won't even return a capability in the // results, it will just return "canceled". We want to emulate the case where the return message // and the cancel (finish) message cross paths. It turns out that exactly two evalLater()s get // us there. // // TODO(cleanup): This is fragile, but I'm not sure how else to write it without a ton // of scaffolding. //kj::evalLater([]() {}).wait(waitScope); //kj::evalLater([]() {}).wait(waitScope); //waitScope.poll(); promise.wait(waitScope); } for (uint i = 0; i < 16; i++) kj::evalLater([]() {}).wait(waitScope); waitScope.poll(); cout << "ReleaseOnCancel test end" << endl; } void TailCallTest(capnp::EzRpcClient& rpc) { cout << "TailCall test start" << endl; auto caller = rpc.getMain(); auto& waitScope = rpc.getWaitScope(); int calleeCallCount = 0; TestTailCallee::Client callee(kj::heap(calleeCallCount)); auto request = caller.fooRequest(); request.setI(456); request.setCallee(callee); auto promise = request.send(); auto dependentCall0 = promise.getC().getCallSequenceRequest().send(); auto response = promise.wait(waitScope); EXPECT_EQ(456, response.getI()); EXPECT_EQ("from TestTailCaller", response.getT()); auto dependentCall1 = promise.getC().getCallSequenceRequest().send(); auto dependentCall2 = response.getC().getCallSequenceRequest().send(); EXPECT_EQ(0, dependentCall0.wait(waitScope).getN()); EXPECT_EQ(1, dependentCall1.wait(waitScope).getN()); EXPECT_EQ(2, dependentCall2.wait(waitScope).getN()); EXPECT_EQ(1, calleeCallCount); cout << "TailCall test end" << endl; } void CancelationTest(capnp::EzRpcClient& rpc) { cout << "Cancelation test start" << endl; auto paf = kj::newPromiseAndFulfiller(); bool destroyed = false; auto destructionPromise = paf.promise.then([&]() { destroyed = true; }).eagerlyEvaluate(nullptr); auto client = rpc.getMain(); kj::Promise promise = nullptr; bool returned = false; { auto request = client.expectCancelRequest(); request.setCap(kj::heap(kj::mv(paf.fulfiller))); promise = request.send().then( [&](Response&& response) { returned = true; }).eagerlyEvaluate(nullptr); } auto& waitScope = rpc.getWaitScope(); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); // We can detect that the method was canceled because it will drop the cap. EXPECT_FALSE(destroyed); EXPECT_FALSE(returned); promise = nullptr; // request cancellation destructionPromise.wait(waitScope); EXPECT_TRUE(destroyed); EXPECT_FALSE(returned); cout << "Cancelation test end" << endl; } void PromiseResolveTest(capnp::EzRpcClient& rpc) { cout << "PromiseResolve test start" << endl; auto client = rpc.getMain(); int chainedCallCount = 0; auto request = client.callFooRequest(); auto request2 = client.callFooWhenResolvedRequest(); auto paf = kj::newPromiseAndFulfiller(); { auto fork = paf.promise.fork(); request.setCap(fork.addBranch()); request2.setCap(fork.addBranch()); } auto promise = request.send(); auto promise2 = request2.send(); auto& waitScope = rpc.getWaitScope(); // Make sure getCap() has been called on the server side by sending another call and waiting // for it. EXPECT_EQ(2, client.getCallSequenceRequest().send().wait(waitScope).getN()); // OK, now fulfill the local promise. paf.fulfiller->fulfill(kj::heap(chainedCallCount)); // We should now be able to wait for getCap() to finish. EXPECT_EQ("bar", promise.wait(waitScope).getS()); EXPECT_EQ("bar", promise2.wait(waitScope).getS()); EXPECT_EQ(2, chainedCallCount); cout << "PromiseResolve test end" << endl; } void RetainAndReleaseTest(capnp::EzRpcClient& rpc) { cout << "RetainAndRelease test start" << endl; auto& waitScope = rpc.getWaitScope(); auto paf = kj::newPromiseAndFulfiller(); bool destroyed = false; auto destructionPromise = paf.promise.then([&]() { destroyed = true; }).eagerlyEvaluate(nullptr); { auto client = rpc.getMain(); { auto request = client.holdRequest(); request.setCap(kj::heap(kj::mv(paf.fulfiller))); request.send().wait(waitScope); } // Do some other call to add a round trip. EXPECT_EQ(1, client.getCallSequenceRequest().send().wait(waitScope).getN()); // Shouldn't be destroyed because it's being held by the server. EXPECT_FALSE(destroyed); // We can ask it to call the held capability. EXPECT_EQ("bar", client.callHeldRequest().send().wait(waitScope).getS()); { // We can get the cap back from it. auto capCopy = client.getHeldRequest().send().wait(waitScope).getCap(); { // And call it, without any network communications. auto request = capCopy.fooRequest(); request.setI(123); request.setJ(true); EXPECT_EQ("foo", request.send().wait(waitScope).getX()); } { // We can send another copy of the same cap to another method, and it works. auto request = client.callFooRequest(); request.setCap(capCopy); EXPECT_EQ("bar", request.send().wait(waitScope).getS()); } } // Give some time to settle. EXPECT_EQ(5, client.getCallSequenceRequest().send().wait(waitScope).getN()); EXPECT_EQ(6, client.getCallSequenceRequest().send().wait(waitScope).getN()); EXPECT_EQ(7, client.getCallSequenceRequest().send().wait(waitScope).getN()); // Can't be destroyed, we haven't released it. EXPECT_FALSE(destroyed); // Different from original test, request the server to hold a null cap, // leading to the destruction of the former cap. auto holdNull = client.holdRequest(); holdNull.send().wait(waitScope); } destructionPromise.wait(waitScope); EXPECT_TRUE(destroyed); cout << "RetainAndRelease test end" << endl; } void CancelTest(capnp::EzRpcClient& rpc) { cout << "Cancel test start" << endl; auto& waitScope = rpc.getWaitScope(); auto client = rpc.getMain(); auto paf = kj::newPromiseAndFulfiller(); bool destroyed = false; auto destructionPromise = paf.promise.then([&]() { destroyed = true; }).eagerlyEvaluate(nullptr); { auto request = client.neverReturnRequest(); request.setCap(kj::heap(kj::mv(paf.fulfiller))); { auto responsePromise = request.send(); // Allow some time to settle. EXPECT_EQ(1u, client.getCallSequenceRequest().send().wait(waitScope).getN()); EXPECT_EQ(2u, client.getCallSequenceRequest().send().wait(waitScope).getN()); // The cap shouldn't have been destroyed yet because the call never returned. EXPECT_FALSE(destroyed); } } // Now the cap should be released. destructionPromise.wait(waitScope); EXPECT_TRUE(destroyed); cout << "Cancel test end" << endl; } void SendTwiceTest(capnp::EzRpcClient& rpc) { cout << "SendTwice test start" << endl; auto& waitScope = rpc.getWaitScope(); auto client = rpc.getMain(); auto paf = kj::newPromiseAndFulfiller(); bool destroyed = false; auto destructionPromise = paf.promise.then([&]() { destroyed = true; }).eagerlyEvaluate(nullptr); auto cap = TestInterface::Client(kj::heap(kj::mv(paf.fulfiller))); { auto request = client.callFooRequest(); request.setCap(cap); EXPECT_EQ("bar", request.send().wait(waitScope).getS()); } // Allow some time for the server to release `cap`. EXPECT_EQ(1, client.getCallSequenceRequest().send().wait(waitScope).getN()); { // More requests with the same cap. auto request = client.callFooRequest(); auto request2 = client.callFooRequest(); request.setCap(cap); request2.setCap(kj::mv(cap)); auto promise = request.send(); auto promise2 = request2.send(); EXPECT_EQ("bar", promise.wait(waitScope).getS()); EXPECT_EQ("bar", promise2.wait(waitScope).getS()); } // Now the cap should be released. destructionPromise.wait(waitScope); EXPECT_TRUE(destroyed); cout << "SendTwice test end" << endl; } RemotePromise getCallSequence( TestCallOrder::Client& client, uint expected) { auto req = client.getCallSequenceRequest(); req.setExpected(expected); return req.send(); } void EmbargoTest(capnp::EzRpcClient& rpc) { cout << "Embargo test start" << endl; auto& waitScope = rpc.getWaitScope(); auto client = rpc.getMain(); auto cap = TestCallOrder::Client(kj::heap()); auto earlyCall = client.getCallSequenceRequest().send(); auto echoRequest = client.echoRequest(); echoRequest.setCap(cap); auto echo = echoRequest.send(); auto pipeline = echo.getCap(); auto call0 = getCallSequence(pipeline, 0); auto call1 = getCallSequence(pipeline, 1); earlyCall.wait(waitScope); auto call2 = getCallSequence(pipeline, 2); auto resolved = echo.wait(waitScope).getCap(); auto call3 = getCallSequence(pipeline, 3); auto call4 = getCallSequence(pipeline, 4); auto call5 = getCallSequence(pipeline, 5); EXPECT_EQ(0, call0.wait(waitScope).getN()); EXPECT_EQ(1, call1.wait(waitScope).getN()); EXPECT_EQ(2, call2.wait(waitScope).getN()); EXPECT_EQ(3, call3.wait(waitScope).getN()); EXPECT_EQ(4, call4.wait(waitScope).getN()); EXPECT_EQ(5, call5.wait(waitScope).getN()); cout << "Embargo test end" << endl; } template void expectPromiseThrows(kj::Promise&& promise, kj::WaitScope& waitScope) { EXPECT_TRUE(promise.then([](T&&) { return false; }, [](kj::Exception&&) { return true; }) .wait(waitScope)); } template <> void expectPromiseThrows(kj::Promise&& promise, kj::WaitScope& waitScope) { EXPECT_TRUE(promise.then([]() { return false; }, [](kj::Exception&&) { return true; }) .wait(waitScope)); } void EmbargoErrorTest(capnp::EzRpcClient& rpc) { cout << "EmbargoError test start" << endl; auto& waitScope = rpc.getWaitScope(); auto client = rpc.getMain(); auto paf = kj::newPromiseAndFulfiller(); auto cap = TestCallOrder::Client(kj::mv(paf.promise)); auto earlyCall = client.getCallSequenceRequest().send(); auto echoRequest = client.echoRequest(); echoRequest.setCap(cap); auto echo = echoRequest.send(); auto pipeline = echo.getCap(); auto call0 = getCallSequence(pipeline, 0); auto call1 = getCallSequence(pipeline, 1); earlyCall.wait(waitScope); auto call2 = getCallSequence(pipeline, 2); auto resolved = echo.wait(waitScope).getCap(); auto call3 = getCallSequence(pipeline, 3); auto call4 = getCallSequence(pipeline, 4); auto call5 = getCallSequence(pipeline, 5); paf.fulfiller->rejectIfThrows([]() { KJ_FAIL_ASSERT("foo") { break; } }); expectPromiseThrows(kj::mv(call0), waitScope); expectPromiseThrows(kj::mv(call1), waitScope); expectPromiseThrows(kj::mv(call2), waitScope); expectPromiseThrows(kj::mv(call3), waitScope); expectPromiseThrows(kj::mv(call4), waitScope); expectPromiseThrows(kj::mv(call5), waitScope); // Verify that we're still connected (there were no protocol errors). getCallSequence(client, 1).wait(waitScope); cout << "EmbargoError test end" << endl; } void EmbargoNullTest(capnp::EzRpcClient& rpc) { cout << "EmbargoNull test start" << endl; auto& waitScope = rpc.getWaitScope(); auto client = rpc.getMain(); auto promise = client.getNullRequest().send(); auto cap = promise.getNullCap(); auto call0 = cap.getCallSequenceRequest().send(); promise.wait(waitScope); auto call1 = cap.getCallSequenceRequest().send(); expectPromiseThrows(kj::mv(call0), waitScope); expectPromiseThrows(kj::mv(call1), waitScope); // Verify that we're still connected (there were no protocol errors). getCallSequence(client, 0).wait(waitScope); cout << "EmbargoNull test end" << endl; } void CallBrokenPromiseTest(capnp::EzRpcClient& rpc) { cout << "CallBrokenPromise test start" << endl; auto& waitScope = rpc.getWaitScope(); auto client = rpc.getMain(); auto paf = kj::newPromiseAndFulfiller(); { auto req = client.holdRequest(); req.setCap(kj::mv(paf.promise)); req.send().wait(waitScope); } bool returned = false; auto req = client.callHeldRequest().send() .then([&](capnp::Response&&) { returned = true; }, [&](kj::Exception&& e) { returned = true; kj::throwRecoverableException(kj::mv(e)); }).eagerlyEvaluate(nullptr); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); EXPECT_FALSE(returned); paf.fulfiller->rejectIfThrows([]() { KJ_FAIL_ASSERT("foo") { break; } }); expectPromiseThrows(kj::mv(req), waitScope); EXPECT_TRUE(returned); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); kj::evalLater([]() {}).wait(waitScope); // Verify that we're still connected (there were no protocol errors). getCallSequence(client, 1).wait(waitScope); cout << "CallBrokenPromise test end" << endl; } int main(int argc, const char* argv[]) { if (argc != 3) { PrintUsage(argv); return 1; } if (strncmp(argv[1], "client", 6) == 0) { try { capnp::EzRpcClient client(argv[2], 33444); cout << "Connecting" << endl; auto& waitScope = client.getWaitScope(); if (strcmp(argv[1], "client:Basic") == 0) { BasicTest(client); } else if (strcmp(argv[1], "client:Pipelining") == 0) { PipeliningTest(client); } else if (strcmp(argv[1], "client:Release") == 0) { ReleaseTest(client); } else if (strcmp(argv[1], "client:ReleaseOnCancel") == 0) { ReleaseOnCancelTest(client); } else if (strcmp(argv[1], "client:TailCall") == 0) { TailCallTest(client); } else if (strcmp(argv[1], "client:Cancelation") == 0) { CancelationTest(client); } else if (strcmp(argv[1], "client:PromiseResolve") == 0) { PromiseResolveTest(client); } else if (strcmp(argv[1], "client:RetainAndRelease") == 0) { RetainAndReleaseTest(client); } else if (strcmp(argv[1], "client:Cancel") == 0) { CancelTest(client); } else if (strcmp(argv[1], "client:SendTwice") == 0) { SendTwiceTest(client); } else if (strcmp(argv[1], "client:Embargo") == 0) { EmbargoTest(client); } else if (strcmp(argv[1], "client:EmbargoError") == 0) { EmbargoErrorTest(client); } else if (strcmp(argv[1], "client:EmbargoNull") == 0) { EmbargoNullTest(client); } else if (strcmp(argv[1], "client:CallBrokenPromise") == 0) { CallBrokenPromiseTest(client); } } catch (const std::exception& exception) { cerr << exception.what() << endl; } } else if (strncmp(argv[1], "server", 6) == 0) { int callCount = 0; Capability::Client mainInterface = nullptr; if (strcmp(argv[1], "server:Interface") == 0) { mainInterface = kj::heap(callCount); } else if (strcmp(argv[1], "server:Pipeline") == 0) { mainInterface = kj::heap(); } else if (strcmp(argv[1], "server:MoreStuff") == 0) { mainInterface = kj::heap(); } else if (strcmp(argv[1], "server:TailCaller") == 0) { mainInterface = kj::heap(); } else { PrintUsage(argv); return 2; } capnp::EzRpcServer server(mainInterface, argv[2], 0); auto& waitScope = server.getWaitScope(); uint port = server.getPort().wait(waitScope); cout << "Listening on port " << port << "..." << endl; kj::NEVER_DONE.wait(waitScope); } else { PrintUsage(argv); return 2; } }