/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "perfetto/ext/ipc/deferred.h" #include "perfetto/base/logging.h" #include "test/gtest_and_gmock.h" #include "src/ipc/test/deferred_unittest_messages.gen.h" namespace perfetto { namespace ipc { namespace { using ::perfetto::ipc::gen::TestMessage; #if PERFETTO_DCHECK_IS_ON() #define EXPECT_DCHECK(x) EXPECT_DEATH_IF_SUPPORTED((x), ".*") #else #define EXPECT_DCHECK(x) x #endif TEST(DeferredTest, BindAndResolve) { Deferred deferred; std::shared_ptr num_callbacks(new int{0}); deferred.Bind([num_callbacks](AsyncResult msg) { ASSERT_TRUE(msg.success()); ASSERT_TRUE(msg); ASSERT_EQ(42, msg->num()); ASSERT_EQ(13, msg.fd()); ASSERT_EQ("foo", msg->str()); (*num_callbacks)++; }); AsyncResult res = AsyncResult::Create(); res->set_num(42); res.set_fd(13); (*res).set_str("foo"); deferred.Resolve(std::move(res)); // A second call to Resolve() or Reject() shouldn't have any effect beause we // didn't set has_more. EXPECT_DCHECK(deferred.Resolve(std::move(res))); EXPECT_DCHECK(deferred.Reject()); ASSERT_EQ(1, *num_callbacks); } // In case of a Reject() a callback with a nullptr should be received. TEST(DeferredTest, BindAndFail) { Deferred deferred; std::shared_ptr num_callbacks(new int{0}); deferred.Bind([num_callbacks](AsyncResult msg) { ASSERT_EQ(-1, msg.fd()); ASSERT_FALSE(msg.success()); ASSERT_FALSE(msg); ASSERT_EQ(nullptr, msg.operator->()); (*num_callbacks)++; }); AsyncResult res = AsyncResult::Create(); res.set_fd(42); deferred.Reject(); EXPECT_DCHECK(deferred.Resolve(std::move(res))); EXPECT_DCHECK(deferred.Reject()); ASSERT_EQ(1, *num_callbacks); } // Test the RAII behavior. TEST(DeferredTest, AutoRejectIfOutOfScope) { std::shared_ptr num_callbacks(new int{0}); { Deferred deferred; deferred.Bind([num_callbacks](AsyncResult msg) { ASSERT_FALSE(msg.success()); (*num_callbacks)++; }); } ASSERT_EQ(1, *num_callbacks); } // Binds two callbacks one after the other and tests that the bind state of the // first callback is released. TEST(DeferredTest, BindTwiceDoesNotHoldBindState) { // Use shared_ptr's use_count() to infer the bind state of the callback. std::shared_ptr num_callbacks(new int{0}); Deferred deferred; deferred.Bind( [num_callbacks](AsyncResult) { (*num_callbacks)++; }); // At this point both the shared_ptr above and the callback in |deferred| are // refcounting the bind state. ASSERT_GE(num_callbacks.use_count(), 2); // Re-binding the callback should release the bind state, without invoking the // old callback. deferred.Bind([](AsyncResult) {}); ASSERT_EQ(1, num_callbacks.use_count()); ASSERT_EQ(0, *num_callbacks); // Test that the new callback is invoked when re-bindings. deferred.Bind([num_callbacks](AsyncResult msg) { ASSERT_TRUE(msg.success()); ASSERT_EQ(4242, msg->num()); (*num_callbacks)++; }); AsyncResult res = AsyncResult::Create(); res->set_num(4242); deferred.Resolve(std::move(res)); ASSERT_EQ(1, *num_callbacks); ASSERT_EQ(1, num_callbacks.use_count()); } TEST(DeferredTest, MoveOperators) { Deferred deferred; std::shared_ptr num_callbacks(new int{0}); std::function)> callback = [num_callbacks](AsyncResult msg) { ASSERT_TRUE(msg.success()); ASSERT_GE(msg->num(), 42); ASSERT_LE(msg->num(), 43); ASSERT_EQ(msg->num() * 10, msg.fd()); ASSERT_EQ(std::to_string(msg->num()), msg->str()); (*num_callbacks)++; }; deferred.Bind(callback); // Do a bit of std::move() dance with both the Deferred and the AsyncResult. AsyncResult res = AsyncResult::Create(); res.set_fd(420); res->set_num(42); AsyncResult res_moved(std::move(res)); res = std::move(res_moved); res->set_str("42"); res_moved = std::move(res); Deferred deferred_moved(std::move(deferred)); deferred = std::move(deferred_moved); deferred_moved = std::move(deferred); EXPECT_DCHECK(deferred.Reject()); // |deferred| has been cleared. ASSERT_EQ(0, *num_callbacks); deferred_moved.Resolve(std::move(res_moved)); // This, instead, should fire. ASSERT_EQ(1, *num_callbacks); // |deferred| and |res| have lost their state but should remain reusable. deferred.Bind(callback); res = AsyncResult::Create(); res.set_fd(430); res->set_num(43); res->set_str("43"); deferred.Resolve(std::move(res)); ASSERT_EQ(2, *num_callbacks); // Finally re-bind |deferred|, move it to a new scoped Deferred and verify // that the moved-into object still auto-nacks the callback. deferred.Bind([num_callbacks](AsyncResult msg) { ASSERT_FALSE(msg.success()); (*num_callbacks)++; }); { Deferred scoped_deferred(std::move(deferred)); } ASSERT_EQ(3, *num_callbacks); callback = nullptr; ASSERT_EQ(1, num_callbacks.use_count()); } // Covers the case of a streaming reply, where the deferred keeps being resolved // until has_more == true. TEST(DeferredTest, StreamingReply) { Deferred deferred; std::shared_ptr num_callbacks(new int{0}); std::function)> callback = [num_callbacks](AsyncResult msg) { ASSERT_TRUE(msg.success()); ASSERT_EQ(*num_callbacks == 0 ? 13 : -1, msg.fd()); ASSERT_EQ(*num_callbacks, msg->num()); ASSERT_EQ(std::to_string(*num_callbacks), msg->str()); ASSERT_EQ(msg->num() < 3, msg.has_more()); (*num_callbacks)++; }; deferred.Bind(callback); for (int i = 0; i < 3; i++) { AsyncResult res = AsyncResult::Create(); res.set_fd(i == 0 ? 13 : -1); res->set_num(i); res->set_str(std::to_string(i)); res.set_has_more(true); AsyncResult res_moved(std::move(res)); deferred.Resolve(std::move(res_moved)); } Deferred deferred_moved(std::move(deferred)); AsyncResult res = AsyncResult::Create(); res->set_num(3); res->set_str(std::to_string(3)); res.set_has_more(false); deferred_moved.Resolve(std::move(res)); ASSERT_EQ(4, *num_callbacks); EXPECT_DCHECK(deferred_moved.Reject()); ASSERT_EQ(4, *num_callbacks); callback = nullptr; ASSERT_EQ(1, num_callbacks.use_count()); } // Similar to the above, but checks that destroying a Deferred without having // resolved with has_more == true automatically rejects once out of scope. TEST(DeferredTest, StreamingReplyIsRejectedOutOfScope) { std::shared_ptr num_callbacks(new int{0}); { Deferred deferred; deferred.Bind([num_callbacks](AsyncResult msg) { ASSERT_EQ((*num_callbacks) < 3, msg.success()); ASSERT_EQ(msg.success(), msg.has_more()); (*num_callbacks)++; }); for (int i = 0; i < 3; i++) { AsyncResult res = AsyncResult::Create(); res.set_has_more(true); deferred.Resolve(std::move(res)); } // |deferred_moved| going out of scope should cause a Reject(). { Deferred deferred_moved = std::move(deferred); } ASSERT_EQ(4, *num_callbacks); } // |deferred| going out of scope should do noting, it has been std::move()'d. ASSERT_EQ(4, *num_callbacks); ASSERT_EQ(1, num_callbacks.use_count()); } // Tests that a Deferred still behaves sanely after it has been // moved into a DeferredBase. TEST(DeferredTest, MoveAsBase) { Deferred deferred; std::shared_ptr num_callbacks(new int{0}); deferred.Bind([num_callbacks](AsyncResult msg) { ASSERT_TRUE(msg.success()); ASSERT_EQ(13, msg.fd()); ASSERT_EQ(42, msg->num()); ASSERT_EQ("foo", msg->str()); (*num_callbacks)++; }); DeferredBase deferred_base(std::move(deferred)); ASSERT_FALSE(deferred.IsBound()); ASSERT_TRUE(deferred_base.IsBound()); std::unique_ptr msg(new TestMessage()); msg->set_num(42); msg->set_str("foo"); AsyncResult async_result_base(std::move(msg)); async_result_base.set_fd(13); deferred_base.Resolve(std::move(async_result_base)); EXPECT_DCHECK(deferred_base.Resolve(std::move(async_result_base))); EXPECT_DCHECK(deferred_base.Reject()); ASSERT_EQ(1, *num_callbacks); } } // namespace } // namespace ipc } // namespace perfetto