SF: Test coverage for EventThread

Add a unit test to cover EventThread.cpp

Test: atest libsurfaceflinger_unittest
Bug: 74827900
Change-Id: If9479cd9deedff836068cb53e7da2cb64041aea1
diff --git a/services/surfaceflinger/EventThread.cpp b/services/surfaceflinger/EventThread.cpp
index bb9c070..bc271c8 100644
--- a/services/surfaceflinger/EventThread.cpp
+++ b/services/surfaceflinger/EventThread.cpp
@@ -26,14 +26,12 @@
 #include <cutils/sched_policy.h>
 
 #include <gui/DisplayEventReceiver.h>
-#include <gui/IDisplayEventConnection.h>
 
 #include <utils/Errors.h>
 #include <utils/String8.h>
 #include <utils/Trace.h>
 
 #include "EventThread.h"
-#include "SurfaceFlinger.h"
 
 using namespace std::chrono_literals;
 
@@ -47,9 +45,11 @@
 
 namespace impl {
 
-EventThread::EventThread(VSyncSource* src, SurfaceFlinger& flinger, bool interceptVSyncs,
-                         const char* threadName)
-      : mVSyncSource(src), mFlinger(flinger), mInterceptVSyncs(interceptVSyncs) {
+EventThread::EventThread(VSyncSource* src, ResyncWithRateLimitCallback resyncWithRateLimitCallback,
+                         InterceptVSyncsCallback interceptVSyncsCallback, const char* threadName)
+      : mVSyncSource(src),
+        mResyncWithRateLimitCallback(resyncWithRateLimitCallback),
+        mInterceptVSyncsCallback(interceptVSyncsCallback) {
     for (auto& event : mVSyncEvent) {
         event.header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
         event.header.id = 0;
@@ -118,7 +118,9 @@
 void EventThread::requestNextVsync(const sp<EventThread::Connection>& connection) {
     std::lock_guard<std::mutex> lock(mMutex);
 
-    mFlinger.resyncWithRateLimit();
+    if (mResyncWithRateLimitCallback) {
+        mResyncWithRateLimitCallback();
+    }
 
     if (connection->count < 0) {
         connection->count = 0;
@@ -216,8 +218,8 @@
             timestamp = mVSyncEvent[i].header.timestamp;
             if (timestamp) {
                 // we have a vsync event to dispatch
-                if (mInterceptVSyncs) {
-                    mFlinger.mInterceptor->saveVSyncEvent(timestamp);
+                if (mInterceptVSyncsCallback) {
+                    mInterceptVSyncsCallback(timestamp);
                 }
                 *event = mVSyncEvent[i];
                 mVSyncEvent[i].header.timestamp = 0;
diff --git a/services/surfaceflinger/EventThread.h b/services/surfaceflinger/EventThread.h
index 97f0a35..9c13ed2 100644
--- a/services/surfaceflinger/EventThread.h
+++ b/services/surfaceflinger/EventThread.h
@@ -37,6 +37,7 @@
 namespace android {
 // ---------------------------------------------------------------------------
 
+class EventThreadTest;
 class SurfaceFlinger;
 class String8;
 
@@ -82,7 +83,9 @@
     class Connection : public BnDisplayEventConnection {
     public:
         explicit Connection(EventThread* eventThread);
-        status_t postEvent(const DisplayEventReceiver::Event& event);
+        virtual ~Connection();
+
+        virtual status_t postEvent(const DisplayEventReceiver::Event& event);
 
         // count >= 1 : continuous event. count is the vsync rate
         // count == 0 : one-shot event that has not fired
@@ -90,7 +93,6 @@
         int32_t count;
 
     private:
-        virtual ~Connection();
         virtual void onFirstRef();
         status_t stealReceiveChannel(gui::BitTube* outChannel) override;
         status_t setVsyncRate(uint32_t count) override;
@@ -100,8 +102,11 @@
     };
 
 public:
-    EventThread(VSyncSource* src, SurfaceFlinger& flinger, bool interceptVSyncs,
-                const char* threadName);
+    using ResyncWithRateLimitCallback = std::function<void()>;
+    using InterceptVSyncsCallback = std::function<void(nsecs_t)>;
+
+    EventThread(VSyncSource* src, ResyncWithRateLimitCallback resyncWithRateLimitCallback,
+                InterceptVSyncsCallback interceptVSyncsCallback, const char* threadName);
     ~EventThread();
 
     sp<BnDisplayEventConnection> createEventConnection() const override;
@@ -124,6 +129,8 @@
     void setPhaseOffset(nsecs_t phaseOffset) override;
 
 private:
+    friend EventThreadTest;
+
     void threadMain();
     Vector<sp<EventThread::Connection>> waitForEventLocked(std::unique_lock<std::mutex>* lock,
                                                            DisplayEventReceiver::Event* event)
@@ -137,8 +144,9 @@
     void onVSyncEvent(nsecs_t timestamp) override;
 
     // constants
-    VSyncSource* mVSyncSource GUARDED_BY(mMutex) = nullptr;
-    SurfaceFlinger& mFlinger;
+    VSyncSource* const mVSyncSource GUARDED_BY(mMutex) = nullptr;
+    const ResyncWithRateLimitCallback mResyncWithRateLimitCallback;
+    const InterceptVSyncsCallback mInterceptVSyncsCallback;
 
     std::thread mThread;
     mutable std::mutex mMutex;
@@ -155,8 +163,6 @@
 
     // for debugging
     bool mDebugVsyncEnabled GUARDED_BY(mMutex) = false;
-
-    const bool mInterceptVSyncs = false;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 588d24c..a837d01 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -636,14 +636,21 @@
     mEventThreadSource =
             std::make_unique<DispSyncSource>(&mPrimaryDispSync, SurfaceFlinger::vsyncPhaseOffsetNs,
                                              true, "app");
-    mEventThread = std::make_unique<impl::EventThread>(mEventThreadSource.get(), *this, false,
+    mEventThread = std::make_unique<impl::EventThread>(mEventThreadSource.get(),
+                                                       [this]() { resyncWithRateLimit(); },
+                                                       impl::EventThread::InterceptVSyncsCallback(),
                                                        "appEventThread");
     mSfEventThreadSource =
             std::make_unique<DispSyncSource>(&mPrimaryDispSync,
                                              SurfaceFlinger::sfVsyncPhaseOffsetNs, true, "sf");
 
-    mSFEventThread = std::make_unique<impl::EventThread>(mSfEventThreadSource.get(), *this, true,
-                                                         "sfEventThread");
+    mSFEventThread =
+            std::make_unique<impl::EventThread>(mSfEventThreadSource.get(),
+                                                [this]() { resyncWithRateLimit(); },
+                                                [this](nsecs_t timestamp) {
+                                                    mInterceptor->saveVSyncEvent(timestamp);
+                                                },
+                                                "sfEventThread");
     mEventQueue->setEventThread(mSFEventThread.get());
     mVsyncModulator.setEventThread(mSFEventThread.get());
 
@@ -1132,9 +1139,11 @@
             ALOGV("VSync Injections enabled");
             if (mVSyncInjector.get() == nullptr) {
                 mVSyncInjector = std::make_unique<InjectVSyncSource>();
-                mInjectorEventThread =
-                        std::make_unique<impl::EventThread>(mVSyncInjector.get(), *this, false,
-                                                            "injEventThread");
+                mInjectorEventThread = std::make_unique<
+                        impl::EventThread>(mVSyncInjector.get(),
+                                           [this]() { resyncWithRateLimit(); },
+                                           impl::EventThread::InterceptVSyncsCallback(),
+                                           "injEventThread");
             }
             mEventQueue->setEventThread(mInjectorEventThread.get());
         } else {
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index bcabe0d..3e9f6a4 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -20,6 +20,7 @@
     srcs: [
         ":libsurfaceflinger_sources",
         "DisplayTransactionTest.cpp",
+        "EventThreadTest.cpp",
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockDisplaySurface.cpp",
         "mock/gui/MockGraphicBufferConsumer.cpp",
diff --git a/services/surfaceflinger/tests/unittests/AsyncCallRecorder.h b/services/surfaceflinger/tests/unittests/AsyncCallRecorder.h
new file mode 100644
index 0000000..2245ee1
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/AsyncCallRecorder.h
@@ -0,0 +1,165 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <deque>
+#include <mutex>
+#include <optional>
+#include <thread>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include <android-base/thread_annotations.h>
+
+namespace android {
+
+// This class helps record calls made by another thread when they are made
+// asynchronously, with no other way for the tests to verify that the calls have
+// been made.
+//
+// A normal Google Mock recorder, while thread safe, does not allow you to wait
+// for asynchronous calls to be made.
+//
+// Usage:
+//
+// In the test, use a Google Mock expectation to invoke an instance of the
+// recorder:
+//
+//     AsyncCallRecorder<void(int)> recorder;
+//
+//     EXPECT_CALL(someMock, someFunction(_)).
+//             .WillRepeatedly(Invoke(recorder.getInvocable()));
+//
+// Then you can invoke the functionality being tested:
+//
+//     threadUnderTest.doSomethingAsync()
+//
+// And afterwards make a number of assertions using the recorder:
+//
+//     // Wait for one call (with reasonable default timeout), and get the args
+//     // as a std::tuple inside a std::optional.
+//     auto args = recorder.waitForCall();
+//     // The returned std::optional will have a value if the recorder function
+//     // was called.
+//     ASSERT_TRUE(args.has_value());
+//     // The arguments can be checked if needed using standard tuple
+//     // operations.
+//     EXPECT_EQ(123, std::get<0>(args.value()));
+//
+// Alternatively maybe you want to assert that a call was not made.
+//
+//     EXPECT_FALSE(recorder.waitForUnexpectedCall().has_value());
+//
+// However this check uses a really short timeout so as not to block the test
+// unnecessarily. And it could be possible for the check to return false and
+// then the recorder could observe a call being made after.
+template <typename Func>
+class AsyncCallRecorder;
+
+template <typename... Args>
+class AsyncCallRecorder<void (*)(Args...)> {
+public:
+    // For the tests, we expect the wait for an expected change to be signaled
+    // to be much shorter than this.
+    static constexpr std::chrono::milliseconds DEFAULT_CALL_EXPECTED_TIMEOUT{10};
+
+    // The wait here is tricky. We don't expect a change, but we don't want to
+    // wait forever (or for longer than the typical test function runtime). As
+    // even the simplest Google Test can take 1ms (1000us) to run, we wait for
+    // half that time.
+    static constexpr std::chrono::microseconds UNEXPECTED_CALL_TIMEOUT{500};
+
+    using ArgTuple = std::tuple<std::remove_cv_t<std::remove_reference_t<Args>>...>;
+
+    void recordCall(Args... args) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mCalls.emplace_back(std::make_tuple(args...));
+        mCondition.notify_all();
+    }
+
+    // Returns a functor which can be used with the Google Mock Invoke()
+    // function, or as a std::function to record calls.
+    auto getInvocable() {
+        return [this](Args... args) { recordCall(args...); };
+    }
+
+    // Returns a set of arguments as a std::optional<std::tuple<...>> for the
+    // oldest call, waiting for the given timeout if necessary if there are no
+    // arguments in the FIFO.
+    std::optional<ArgTuple> waitForCall(
+            std::chrono::microseconds timeout = DEFAULT_CALL_EXPECTED_TIMEOUT)
+            NO_THREAD_SAFETY_ANALYSIS {
+        std::unique_lock<std::mutex> lock(mMutex);
+
+        // Wait if necessary for us to have a record from a call.
+        mCondition.wait_for(lock, timeout,
+                            [this]() NO_THREAD_SAFETY_ANALYSIS { return !mCalls.empty(); });
+
+        // Return the arguments from the oldest call, if one was made
+        bool called = !mCalls.empty();
+        std::optional<ArgTuple> result;
+        if (called) {
+            result.emplace(std::move(mCalls.front()));
+            mCalls.pop_front();
+        }
+        return result;
+    }
+
+    // Waits using a small default timeout for when a call is not expected to be
+    // made. The returned std::optional<std:tuple<...>> should not have a value
+    // except if a set of arguments was unexpectedly received because a call was
+    // actually made.
+    //
+    // Note this function uses a small timeout to not block test execution, and
+    // it is possible the code under test could make the call AFTER the timeout
+    // expires.
+    std::optional<ArgTuple> waitForUnexpectedCall() { return waitForCall(UNEXPECTED_CALL_TIMEOUT); }
+
+private:
+    std::mutex mMutex;
+    std::condition_variable mCondition;
+    std::deque<ArgTuple> mCalls GUARDED_BY(mMutex);
+};
+
+// Like AsyncCallRecorder, but for when the function being invoked
+// asynchronously is expected to return a value.
+//
+// This helper allows a single constant return value to be set to be returned by
+// all calls that were made.
+template <typename Func>
+class AsyncCallRecorderWithCannedReturn;
+
+template <typename Ret, typename... Args>
+class AsyncCallRecorderWithCannedReturn<Ret (*)(Args...)>
+      : public AsyncCallRecorder<void (*)(Args...)> {
+public:
+    explicit AsyncCallRecorderWithCannedReturn(Ret returnvalue) : mReturnValue(returnvalue) {}
+
+    auto getInvocable() {
+        return [this](Args... args) {
+            this->recordCall(args...);
+            return mReturnValue;
+        };
+    }
+
+private:
+    const Ret mReturnValue;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
new file mode 100644
index 0000000..80fdb80
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -0,0 +1,422 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <log/log.h>
+
+#include <utils/Errors.h>
+
+#include "AsyncCallRecorder.h"
+#include "EventThread.h"
+
+using namespace std::chrono_literals;
+using namespace std::placeholders;
+
+using testing::_;
+using testing::Invoke;
+
+namespace android {
+namespace {
+
+class MockVSyncSource : public VSyncSource {
+public:
+    MOCK_METHOD1(setVSyncEnabled, void(bool));
+    MOCK_METHOD1(setCallback, void(VSyncSource::Callback*));
+    MOCK_METHOD1(setPhaseOffset, void(nsecs_t));
+};
+
+} // namespace
+
+class EventThreadTest : public testing::Test {
+protected:
+    class MockEventThreadConnection : public android::impl::EventThread::Connection {
+    public:
+        explicit MockEventThreadConnection(android::impl::EventThread* eventThread)
+              : android::impl::EventThread::Connection(eventThread) {}
+        MOCK_METHOD1(postEvent, status_t(const DisplayEventReceiver::Event& event));
+    };
+
+    using ConnectionEventRecorder =
+            AsyncCallRecorderWithCannedReturn<status_t (*)(const DisplayEventReceiver::Event&)>;
+
+    EventThreadTest();
+    ~EventThreadTest() override;
+
+    void createThread();
+    sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder);
+
+    void expectVSyncSetEnabledCallReceived(bool expectedState);
+    void expectVSyncSetPhaseOffsetCallReceived(nsecs_t expectedPhaseOffset);
+    VSyncSource::Callback* expectVSyncSetCallbackCallReceived();
+    void expectInterceptCallReceived(nsecs_t expectedTimestamp);
+    void expectVsyncEventReceivedByConnection(const char* name,
+                                              ConnectionEventRecorder& connectionEventRecorder,
+                                              nsecs_t expectedTimestamp, unsigned expectedCount);
+    void expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp, unsigned expectedCount);
+    void expectHotplugEventReceivedByConnection(int expectedDisplayType, bool expectedConnected);
+
+    AsyncCallRecorder<void (*)(bool)> mVSyncSetEnabledCallRecorder;
+    AsyncCallRecorder<void (*)(VSyncSource::Callback*)> mVSyncSetCallbackCallRecorder;
+    AsyncCallRecorder<void (*)(nsecs_t)> mVSyncSetPhaseOffsetCallRecorder;
+    AsyncCallRecorder<void (*)()> mResyncCallRecorder;
+    AsyncCallRecorder<void (*)(nsecs_t)> mInterceptVSyncCallRecorder;
+    ConnectionEventRecorder mConnectionEventCallRecorder{0};
+
+    MockVSyncSource mVSyncSource;
+    std::unique_ptr<android::impl::EventThread> mThread;
+    sp<MockEventThreadConnection> mConnection;
+};
+
+EventThreadTest::EventThreadTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+
+    EXPECT_CALL(mVSyncSource, setVSyncEnabled(_))
+            .WillRepeatedly(Invoke(mVSyncSetEnabledCallRecorder.getInvocable()));
+
+    EXPECT_CALL(mVSyncSource, setCallback(_))
+            .WillRepeatedly(Invoke(mVSyncSetCallbackCallRecorder.getInvocable()));
+
+    EXPECT_CALL(mVSyncSource, setPhaseOffset(_))
+            .WillRepeatedly(Invoke(mVSyncSetPhaseOffsetCallRecorder.getInvocable()));
+
+    createThread();
+    mConnection = createConnection(mConnectionEventCallRecorder);
+}
+
+EventThreadTest::~EventThreadTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+}
+
+void EventThreadTest::createThread() {
+    mThread =
+            std::make_unique<android::impl::EventThread>(&mVSyncSource,
+                                                         mResyncCallRecorder.getInvocable(),
+                                                         mInterceptVSyncCallRecorder.getInvocable(),
+                                                         "unit-test-event-thread");
+}
+
+sp<EventThreadTest::MockEventThreadConnection> EventThreadTest::createConnection(
+        ConnectionEventRecorder& recorder) {
+    sp<MockEventThreadConnection> connection = new MockEventThreadConnection(mThread.get());
+    EXPECT_CALL(*connection, postEvent(_)).WillRepeatedly(Invoke(recorder.getInvocable()));
+    return connection;
+}
+
+void EventThreadTest::expectVSyncSetEnabledCallReceived(bool expectedState) {
+    auto args = mVSyncSetEnabledCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    EXPECT_EQ(expectedState, std::get<0>(args.value()));
+}
+
+void EventThreadTest::expectVSyncSetPhaseOffsetCallReceived(nsecs_t expectedPhaseOffset) {
+    auto args = mVSyncSetPhaseOffsetCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    EXPECT_EQ(expectedPhaseOffset, std::get<0>(args.value()));
+}
+
+VSyncSource::Callback* EventThreadTest::expectVSyncSetCallbackCallReceived() {
+    auto callbackSet = mVSyncSetCallbackCallRecorder.waitForCall();
+    return callbackSet.has_value() ? std::get<0>(callbackSet.value()) : nullptr;
+}
+
+void EventThreadTest::expectInterceptCallReceived(nsecs_t expectedTimestamp) {
+    auto args = mInterceptVSyncCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    EXPECT_EQ(expectedTimestamp, std::get<0>(args.value()));
+}
+
+void EventThreadTest::expectVsyncEventReceivedByConnection(
+        const char* name, ConnectionEventRecorder& connectionEventRecorder,
+        nsecs_t expectedTimestamp, unsigned expectedCount) {
+    auto args = connectionEventRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value()) << name << " did not receive an event for timestamp "
+                                  << expectedTimestamp;
+    const auto& event = std::get<0>(args.value());
+    EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_VSYNC, event.header.type)
+            << name << " did not get the correct event for timestamp " << expectedTimestamp;
+    EXPECT_EQ(expectedTimestamp, event.header.timestamp)
+            << name << " did not get the expected timestamp for timestamp " << expectedTimestamp;
+    EXPECT_EQ(expectedCount, event.vsync.count)
+            << name << " did not get the expected count for timestamp " << expectedTimestamp;
+}
+
+void EventThreadTest::expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp,
+                                                           unsigned expectedCount) {
+    expectVsyncEventReceivedByConnection("mConnectionEventCallRecorder",
+                                         mConnectionEventCallRecorder, expectedTimestamp,
+                                         expectedCount);
+}
+
+void EventThreadTest::expectHotplugEventReceivedByConnection(int expectedDisplayType,
+                                                             bool expectedConnected) {
+    auto args = mConnectionEventCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    const auto& event = std::get<0>(args.value());
+    EXPECT_EQ(DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, event.header.type);
+    EXPECT_EQ(static_cast<unsigned>(expectedDisplayType), event.header.id);
+    EXPECT_EQ(expectedConnected, event.hotplug.connected);
+}
+
+namespace {
+
+/* ------------------------------------------------------------------------
+ * Test cases
+ */
+
+TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) {
+    EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+    EXPECT_FALSE(mVSyncSetCallbackCallRecorder.waitForCall(0us).has_value());
+    EXPECT_FALSE(mVSyncSetPhaseOffsetCallRecorder.waitForCall(0us).has_value());
+    EXPECT_FALSE(mResyncCallRecorder.waitForCall(0us).has_value());
+    EXPECT_FALSE(mInterceptVSyncCallRecorder.waitForCall(0us).has_value());
+    EXPECT_FALSE(mConnectionEventCallRecorder.waitForCall(0us).has_value());
+}
+
+TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) {
+    // Signal that we want the next vsync event to be posted to the connection
+    mThread->requestNextVsync(mConnection);
+
+    // EventThread should immediately request a resync.
+    EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
+
+    // EventThread should enable vsync callbacks, and set a callback interface
+    // pointer to use them with the VSync source.
+    expectVSyncSetEnabledCallReceived(true);
+    auto callback = expectVSyncSetCallbackCallReceived();
+    ASSERT_TRUE(callback);
+
+    // Use the received callback to signal a first vsync event.
+    // The interceptor should receive the event, as well as the connection.
+    callback->onVSyncEvent(123);
+    expectInterceptCallReceived(123);
+    expectVsyncEventReceivedByConnection(123, 1u);
+
+    // Use the received callback to signal a second vsync event.
+    // The interceptor should receive the event, but the the connection should
+    // not as it was only interested in the first.
+    callback->onVSyncEvent(456);
+    expectInterceptCallReceived(456);
+    EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+
+    // EventThread should also detect that at this point that it does not need
+    // any more vsync events, and should disable their generation.
+    expectVSyncSetEnabledCallReceived(false);
+}
+
+TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) {
+    // Create a first connection, register it, and request a vsync rate of zero.
+    ConnectionEventRecorder firstConnectionEventRecorder{0};
+    sp<MockEventThreadConnection> firstConnection = createConnection(firstConnectionEventRecorder);
+    mThread->setVsyncRate(0, firstConnection);
+
+    // By itself, this should not enable vsync events
+    EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+    EXPECT_FALSE(mVSyncSetCallbackCallRecorder.waitForCall(0us).has_value());
+
+    // However if there is another connection which wants events at a nonzero rate.....
+    ConnectionEventRecorder secondConnectionEventRecorder{0};
+    sp<MockEventThreadConnection> secondConnection =
+            createConnection(secondConnectionEventRecorder);
+    mThread->setVsyncRate(1, secondConnection);
+
+    // EventThread should enable vsync callbacks, and set a callback interface
+    // pointer to use them with the VSync source.
+    expectVSyncSetEnabledCallReceived(true);
+    auto callback = expectVSyncSetCallbackCallReceived();
+    ASSERT_TRUE(callback);
+
+    // Send a vsync event. EventThread should then make a call to the
+    // interceptor, and the second connection. The first connection should not
+    // get the event.
+    callback->onVSyncEvent(123);
+    expectInterceptCallReceived(123);
+    EXPECT_FALSE(firstConnectionEventRecorder.waitForUnexpectedCall().has_value());
+    expectVsyncEventReceivedByConnection("secondConnection", secondConnectionEventRecorder, 123,
+                                         1u);
+}
+
+TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) {
+    mThread->setVsyncRate(1, mConnection);
+
+    // EventThread should enable vsync callbacks, and set a callback interface
+    // pointer to use them with the VSync source.
+    expectVSyncSetEnabledCallReceived(true);
+    auto callback = expectVSyncSetCallbackCallReceived();
+    ASSERT_TRUE(callback);
+
+    // Send a vsync event. EventThread should then make a call to the
+    // interceptor, and the connection.
+    callback->onVSyncEvent(123);
+    expectInterceptCallReceived(123);
+    expectVsyncEventReceivedByConnection(123, 1u);
+
+    // A second event should go to the same places.
+    callback->onVSyncEvent(456);
+    expectInterceptCallReceived(456);
+    expectVsyncEventReceivedByConnection(456, 2u);
+
+    // A third event should go to the same places.
+    callback->onVSyncEvent(789);
+    expectInterceptCallReceived(789);
+    expectVsyncEventReceivedByConnection(789, 3u);
+}
+
+TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) {
+    mThread->setVsyncRate(2, mConnection);
+
+    // EventThread should enable vsync callbacks, and set a callback interface
+    // pointer to use them with the VSync source.
+    expectVSyncSetEnabledCallReceived(true);
+    auto callback = expectVSyncSetCallbackCallReceived();
+    ASSERT_TRUE(callback);
+
+    // The first event will be seen by the interceptor, and not the connection.
+    callback->onVSyncEvent(123);
+    expectInterceptCallReceived(123);
+    EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+
+    // The second event will be seen by the interceptor and the connection.
+    callback->onVSyncEvent(456);
+    expectInterceptCallReceived(456);
+    expectVsyncEventReceivedByConnection(456, 2u);
+
+    // The third event will be seen by the interceptor, and not the connection.
+    callback->onVSyncEvent(789);
+    expectInterceptCallReceived(789);
+    EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+
+    // The fourth event will be seen by the interceptor and the connection.
+    callback->onVSyncEvent(101112);
+    expectInterceptCallReceived(101112);
+    expectVsyncEventReceivedByConnection(101112, 4u);
+}
+
+TEST_F(EventThreadTest, connectionsRemovedIfInstanceDestroyed) {
+    mThread->setVsyncRate(1, mConnection);
+
+    // EventThread should enable vsync callbacks, and set a callback interface
+    // pointer to use them with the VSync source.
+    expectVSyncSetEnabledCallReceived(true);
+    auto callback = expectVSyncSetCallbackCallReceived();
+    ASSERT_TRUE(callback);
+
+    // Destroy the only (strong) reference to the connection.
+    mConnection = nullptr;
+
+    // The first event will be seen by the interceptor, and not the connection.
+    callback->onVSyncEvent(123);
+    expectInterceptCallReceived(123);
+    EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+
+    // EventThread should disable vsync callbacks
+    expectVSyncSetEnabledCallReceived(false);
+}
+
+TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) {
+    ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
+    sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
+    mThread->setVsyncRate(1, errorConnection);
+
+    // EventThread should enable vsync callbacks, and set a callback interface
+    // pointer to use them with the VSync source.
+    expectVSyncSetEnabledCallReceived(true);
+    auto callback = expectVSyncSetCallbackCallReceived();
+    ASSERT_TRUE(callback);
+
+    // The first event will be seen by the interceptor, and by the connection,
+    // which then returns an error.
+    callback->onVSyncEvent(123);
+    expectInterceptCallReceived(123);
+    expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
+
+    // A subsequent event will be seen by the interceptor and not by the
+    // connection.
+    callback->onVSyncEvent(456);
+    expectInterceptCallReceived(456);
+    EXPECT_FALSE(errorConnectionEventRecorder.waitForUnexpectedCall().has_value());
+
+    // EventThread should disable vsync callbacks with the second event
+    expectVSyncSetEnabledCallReceived(false);
+}
+
+TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) {
+    ConnectionEventRecorder errorConnectionEventRecorder{WOULD_BLOCK};
+    sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
+    mThread->setVsyncRate(1, errorConnection);
+
+    // EventThread should enable vsync callbacks, and set a callback interface
+    // pointer to use them with the VSync source.
+    expectVSyncSetEnabledCallReceived(true);
+    auto callback = expectVSyncSetCallbackCallReceived();
+    ASSERT_TRUE(callback);
+
+    // The first event will be seen by the interceptor, and by the connection,
+    // which then returns an non-fatal error.
+    callback->onVSyncEvent(123);
+    expectInterceptCallReceived(123);
+    expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
+
+    // A subsequent event will be seen by the interceptor, and by the connection,
+    // which still then returns an non-fatal error.
+    callback->onVSyncEvent(456);
+    expectInterceptCallReceived(456);
+    expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 456, 2u);
+
+    // EventThread will not disable vsync callbacks as the errors are non-fatal.
+    EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+}
+
+TEST_F(EventThreadTest, setPhaseOffsetForwardsToVSyncSource) {
+    mThread->setPhaseOffset(321);
+    expectVSyncSetPhaseOffsetCallReceived(321);
+}
+
+TEST_F(EventThreadTest, postHotplugPrimaryDisconnect) {
+    mThread->onHotplugReceived(DisplayDevice::DISPLAY_PRIMARY, false);
+    expectHotplugEventReceivedByConnection(DisplayDevice::DISPLAY_PRIMARY, false);
+}
+
+TEST_F(EventThreadTest, postHotplugPrimaryConnect) {
+    mThread->onHotplugReceived(DisplayDevice::DISPLAY_PRIMARY, true);
+    expectHotplugEventReceivedByConnection(DisplayDevice::DISPLAY_PRIMARY, true);
+}
+
+TEST_F(EventThreadTest, postHotplugExternalDisconnect) {
+    mThread->onHotplugReceived(DisplayDevice::DISPLAY_EXTERNAL, false);
+    expectHotplugEventReceivedByConnection(DisplayDevice::DISPLAY_EXTERNAL, false);
+}
+
+TEST_F(EventThreadTest, postHotplugExternalConnect) {
+    mThread->onHotplugReceived(DisplayDevice::DISPLAY_EXTERNAL, true);
+    expectHotplugEventReceivedByConnection(DisplayDevice::DISPLAY_EXTERNAL, true);
+}
+
+TEST_F(EventThreadTest, postHotplugVirtualDisconnectIsFilteredOut) {
+    mThread->onHotplugReceived(DisplayDevice::DISPLAY_VIRTUAL, false);
+    EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+}
+
+} // namespace
+} // namespace android