Add unit tests for HintSessionWrapper

This patch adds several unit tests for HintSessionWrapper, including
one specifically for a recent race condition memory leak.

It also refactors HintSessionWrapper to better support testing, and
creates a macro to shorten the dlsym bindings.

Bug: 299541959
Test: hwuiunit

Change-Id: I05ed15cdbd157b109667563d30627cfc4ae83777
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ce6b4b7..502ff87 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -737,6 +737,7 @@
         "tests/unit/EglManagerTests.cpp",
         "tests/unit/FatVectorTests.cpp",
         "tests/unit/GraphicsStatsServiceTests.cpp",
+        "tests/unit/HintSessionWrapperTests.cpp",
         "tests/unit/JankTrackerTests.cpp",
         "tests/unit/FrameMetricsReporterTests.cpp",
         "tests/unit/LayerUpdateQueueTests.cpp",
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 1f338ee..b34da51 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -32,65 +32,30 @@
 namespace uirenderer {
 namespace renderthread {
 
-namespace {
+#define BIND_APH_METHOD(name)                                         \
+    name = (decltype(name))dlsym(handle_, "APerformanceHint_" #name); \
+    LOG_ALWAYS_FATAL_IF(name == nullptr, "Failed to find required symbol APerformanceHint_" #name)
 
-typedef APerformanceHintManager* (*APH_getManager)();
-typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
-                                                      size_t, int64_t);
-typedef void (*APH_closeSession)(APerformanceHintSession* session);
-typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t);
-
-bool gAPerformanceHintBindingInitialized = false;
-APH_getManager gAPH_getManagerFn = nullptr;
-APH_createSession gAPH_createSessionFn = nullptr;
-APH_closeSession gAPH_closeSessionFn = nullptr;
-APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
-APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
-APH_sendHint gAPH_sendHintFn = nullptr;
-
-void ensureAPerformanceHintBindingInitialized() {
-    if (gAPerformanceHintBindingInitialized) return;
+void HintSessionWrapper::HintSessionBinding::init() {
+    if (mInitialized) return;
 
     void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
     LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
 
-    gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
-    LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_getManager!");
+    BIND_APH_METHOD(getManager);
+    BIND_APH_METHOD(createSession);
+    BIND_APH_METHOD(closeSession);
+    BIND_APH_METHOD(updateTargetWorkDuration);
+    BIND_APH_METHOD(reportActualWorkDuration);
+    BIND_APH_METHOD(sendHint);
 
-    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
-    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_createSession!");
-
-    gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
-    LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_closeSession!");
-
-    gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
-            handle_, "APerformanceHint_updateTargetWorkDuration");
-    LOG_ALWAYS_FATAL_IF(
-            gAPH_updateTargetWorkDurationFn == nullptr,
-            "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
-
-    gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
-            handle_, "APerformanceHint_reportActualWorkDuration");
-    LOG_ALWAYS_FATAL_IF(
-            gAPH_reportActualWorkDurationFn == nullptr,
-            "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
-
-    gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
-    LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_sendHint!");
-
-    gAPerformanceHintBindingInitialized = true;
+    mInitialized = true;
 }
 
-}  // namespace
-
 HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
-        : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {}
+        : mUiThreadId(uiThreadId)
+        , mRenderThreadId(renderThreadId)
+        , mBinding(std::make_shared<HintSessionBinding>()) {}
 
 HintSessionWrapper::~HintSessionWrapper() {
     destroy();
@@ -101,7 +66,7 @@
         mHintSession = mHintSessionFuture.get();
     }
     if (mHintSession) {
-        gAPH_closeSessionFn(mHintSession);
+        mBinding->closeSession(mHintSession);
         mSessionValid = true;
         mHintSession = nullptr;
     }
@@ -133,9 +98,9 @@
     // Assume that if we return before the end, it broke
     mSessionValid = false;
 
-    ensureAPerformanceHintBindingInitialized();
+    mBinding->init();
 
-    APerformanceHintManager* manager = gAPH_getManagerFn();
+    APerformanceHintManager* manager = mBinding->getManager();
     if (!manager) return false;
 
     std::vector<pid_t> tids = CommonPool::getThreadIds();
@@ -145,8 +110,9 @@
     // Use a placeholder target value to initialize,
     // this will always be replaced elsewhere before it gets used
     int64_t defaultTargetDurationNanos = 16666667;
-    mHintSessionFuture = CommonPool::async([=, tids = std::move(tids)] {
-        return gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos);
+    mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
+        return mBinding->createSession(manager, tids.data(), tids.size(),
+                                       defaultTargetDurationNanos);
     });
     return false;
 }
@@ -158,7 +124,7 @@
         targetWorkDurationNanos > kSanityCheckLowerBound &&
         targetWorkDurationNanos < kSanityCheckUpperBound) {
         mLastTargetWorkDuration = targetWorkDurationNanos;
-        gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos);
+        mBinding->updateTargetWorkDuration(mHintSession, targetWorkDurationNanos);
     }
     mLastFrameNotification = systemTime();
 }
@@ -168,7 +134,7 @@
     mResetsSinceLastReport = 0;
     if (actualDurationNanos > kSanityCheckLowerBound &&
         actualDurationNanos < kSanityCheckUpperBound) {
-        gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
+        mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
     }
 }
 
@@ -179,14 +145,14 @@
     if (now - mLastFrameNotification > kResetHintTimeout &&
         mResetsSinceLastReport <= kMaxResetsSinceLastReport) {
         ++mResetsSinceLastReport;
-        gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
+        mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET));
     }
     mLastFrameNotification = now;
 }
 
 void HintSessionWrapper::sendLoadIncreaseHint() {
     if (!init()) return;
-    gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_UP));
+    mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
 }
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index bdb9959..f8b876e 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -29,6 +29,8 @@
 
 class HintSessionWrapper {
 public:
+    friend class HintSessionWrapperTests;
+
     HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId);
     ~HintSessionWrapper();
 
@@ -55,6 +57,28 @@
     static constexpr nsecs_t kResetHintTimeout = 100_ms;
     static constexpr int64_t kSanityCheckLowerBound = 100_us;
     static constexpr int64_t kSanityCheckUpperBound = 10_s;
+
+    // Allows easier stub when testing
+    class HintSessionBinding {
+    public:
+        virtual ~HintSessionBinding() = default;
+        virtual void init();
+        APerformanceHintManager* (*getManager)();
+        APerformanceHintSession* (*createSession)(APerformanceHintManager* manager,
+                                                  const int32_t* tids, size_t tidCount,
+                                                  int64_t defaultTarget) = nullptr;
+        void (*closeSession)(APerformanceHintSession* session) = nullptr;
+        void (*updateTargetWorkDuration)(APerformanceHintSession* session,
+                                         int64_t targetDuration) = nullptr;
+        void (*reportActualWorkDuration)(APerformanceHintSession* session,
+                                         int64_t actualDuration) = nullptr;
+        void (*sendHint)(APerformanceHintSession* session, int32_t hintId) = nullptr;
+
+    private:
+        bool mInitialized = false;
+    };
+
+    std::shared_ptr<HintSessionBinding> mBinding;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
new file mode 100644
index 0000000..623be1e
--- /dev/null
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2023 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <private/performance_hint_private.h>
+#include <renderthread/HintSessionWrapper.h>
+#include <utils/Log.h>
+
+#include <chrono>
+
+#include "Properties.h"
+
+using namespace testing;
+using namespace std::chrono_literals;
+
+APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
+APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
+int uiThreadId = 1;
+int renderThreadId = 2;
+
+namespace android::uirenderer::renderthread {
+
+class HintSessionWrapperTests : public testing::Test {
+public:
+    void SetUp() override;
+    void TearDown() override;
+
+protected:
+    std::shared_ptr<HintSessionWrapper> mWrapper;
+
+    class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
+    public:
+        void init() override;
+
+        MOCK_METHOD(APerformanceHintManager*, fakeGetManager, ());
+        MOCK_METHOD(APerformanceHintSession*, fakeCreateSession,
+                    (APerformanceHintManager*, const int32_t*, size_t, int64_t));
+        MOCK_METHOD(void, fakeCloseSession, (APerformanceHintSession*));
+        MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
+        MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
+        MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
+    };
+
+    // Must be static so it can have function pointers we can point to with static methods
+    static std::shared_ptr<MockHintSessionBinding> sMockBinding;
+
+    // Must be static so we can point to them as normal fn pointers with HintSessionBinding
+    static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
+    static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager,
+                                                      const int32_t* ids, size_t idsSize,
+                                                      int64_t initialTarget) {
+        return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+    }
+    static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager,
+                                                          const int32_t* ids, size_t idsSize,
+                                                          int64_t initialTarget) {
+        std::this_thread::sleep_for(50ms);
+        return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+    }
+    static void stubCloseSession(APerformanceHintSession* session) {
+        sMockBinding->fakeCloseSession(session);
+    };
+    static void stubUpdateTargetWorkDuration(APerformanceHintSession* session,
+                                             int64_t workDuration) {
+        sMockBinding->fakeUpdateTargetWorkDuration(session, workDuration);
+    }
+    static void stubReportActualWorkDuration(APerformanceHintSession* session,
+                                             int64_t workDuration) {
+        sMockBinding->fakeReportActualWorkDuration(session, workDuration);
+    }
+    static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
+        sMockBinding->fakeSendHint(session, hintId);
+    };
+    void waitForWrapperReady() { mWrapper->mHintSessionFuture.wait(); }
+};
+
+std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding>
+        HintSessionWrapperTests::sMockBinding;
+
+void HintSessionWrapperTests::SetUp() {
+    // Pretend it's supported even if we're in an emulator
+    Properties::useHintManager = true;
+    sMockBinding = std::make_shared<NiceMock<MockHintSessionBinding>>();
+    mWrapper = std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId);
+    mWrapper->mBinding = sMockBinding;
+    EXPECT_CALL(*sMockBinding, fakeGetManager).WillOnce(Return(managerPtr));
+    ON_CALL(*sMockBinding, fakeCreateSession).WillByDefault(Return(sessionPtr));
+}
+
+void HintSessionWrapperTests::MockHintSessionBinding::init() {
+    sMockBinding->getManager = &stubGetManager;
+    if (sMockBinding->createSession == nullptr) {
+        sMockBinding->createSession = &stubCreateSession;
+    }
+    sMockBinding->closeSession = &stubCloseSession;
+    sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration;
+    sMockBinding->reportActualWorkDuration = &stubReportActualWorkDuration;
+    sMockBinding->sendHint = &stubSendHint;
+}
+
+void HintSessionWrapperTests::TearDown() {
+    mWrapper = nullptr;
+    sMockBinding = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+    sMockBinding->createSession = stubSlowCreateSession;
+    mWrapper->init();
+    mWrapper = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
+    EXPECT_CALL(*sMockBinding, fakeCreateSession(managerPtr, _, Gt(1), _)).Times(1);
+    mWrapper->init();
+    waitForWrapperReady();
+}
+
+TEST_F(HintSessionWrapperTests, loadUpHintsSendCorrectly) {
+    EXPECT_CALL(*sMockBinding,
+                fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
+            .Times(1);
+    mWrapper->init();
+    waitForWrapperReady();
+    mWrapper->sendLoadIncreaseHint();
+}
+
+TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) {
+    EXPECT_CALL(*sMockBinding,
+                fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET)))
+            .Times(1);
+    mWrapper->init();
+    waitForWrapperReady();
+    mWrapper->sendLoadResetHint();
+}
+
+}  // namespace android::uirenderer::renderthread
\ No newline at end of file