Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 87765575 authored by Matt Buckley's avatar Matt Buckley
Browse files

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
Merged-In: I05ed15cdbd157b109667563d30627cfc4ae83777
(cherry picked from commit 0c668368)
parent db9f9d76
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -717,6 +717,7 @@ cc_test {
        "tests/unit/EglManagerTests.cpp",
        "tests/unit/EglManagerTests.cpp",
        "tests/unit/FatVectorTests.cpp",
        "tests/unit/FatVectorTests.cpp",
        "tests/unit/GraphicsStatsServiceTests.cpp",
        "tests/unit/GraphicsStatsServiceTests.cpp",
        "tests/unit/HintSessionWrapperTests.cpp",
        "tests/unit/JankTrackerTests.cpp",
        "tests/unit/JankTrackerTests.cpp",
        "tests/unit/FrameMetricsReporterTests.cpp",
        "tests/unit/FrameMetricsReporterTests.cpp",
        "tests/unit/LayerUpdateQueueTests.cpp",
        "tests/unit/LayerUpdateQueueTests.cpp",
+26 −60
Original line number Original line Diff line number Diff line
@@ -32,65 +32,30 @@ namespace android {
namespace uirenderer {
namespace uirenderer {
namespace renderthread {
namespace renderthread {


namespace {
#define BIND_APH_METHOD(name)                                         \

    name = (decltype(name))dlsym(handle_, "APerformanceHint_" #name); \
typedef APerformanceHintManager* (*APH_getManager)();
    LOG_ALWAYS_FATAL_IF(name == nullptr, "Failed to find required symbol APerformanceHint_" #name)
typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,

                                                      size_t, int64_t);
void HintSessionWrapper::HintSessionBinding::init() {
typedef void (*APH_closeSession)(APerformanceHintSession* session);
    if (mInitialized) return;
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* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
    LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
    LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");


    gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
    BIND_APH_METHOD(getManager);
    LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
    BIND_APH_METHOD(createSession);
                        "Failed to find required symbol APerformanceHint_getManager!");
    BIND_APH_METHOD(closeSession);

    BIND_APH_METHOD(updateTargetWorkDuration);
    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
    BIND_APH_METHOD(reportActualWorkDuration);
    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
    BIND_APH_METHOD(sendHint);
                        "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(
    mInitialized = true;
            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;
}
}


}  // namespace

HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
        : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {}
        : mUiThreadId(uiThreadId)
        , mRenderThreadId(renderThreadId)
        , mBinding(std::make_shared<HintSessionBinding>()) {}


HintSessionWrapper::~HintSessionWrapper() {
HintSessionWrapper::~HintSessionWrapper() {
    destroy();
    destroy();
@@ -101,7 +66,7 @@ void HintSessionWrapper::destroy() {
        mHintSession = mHintSessionFuture.get();
        mHintSession = mHintSessionFuture.get();
    }
    }
    if (mHintSession) {
    if (mHintSession) {
        gAPH_closeSessionFn(mHintSession);
        mBinding->closeSession(mHintSession);
        mSessionValid = true;
        mSessionValid = true;
        mHintSession = nullptr;
        mHintSession = nullptr;
    }
    }
@@ -133,9 +98,9 @@ bool HintSessionWrapper::init() {
    // Assume that if we return before the end, it broke
    // Assume that if we return before the end, it broke
    mSessionValid = false;
    mSessionValid = false;


    ensureAPerformanceHintBindingInitialized();
    mBinding->init();


    APerformanceHintManager* manager = gAPH_getManagerFn();
    APerformanceHintManager* manager = mBinding->getManager();
    if (!manager) return false;
    if (!manager) return false;


    std::vector<pid_t> tids = CommonPool::getThreadIds();
    std::vector<pid_t> tids = CommonPool::getThreadIds();
@@ -145,8 +110,9 @@ bool HintSessionWrapper::init() {
    // Use a placeholder target value to initialize,
    // Use a placeholder target value to initialize,
    // this will always be replaced elsewhere before it gets used
    // this will always be replaced elsewhere before it gets used
    int64_t defaultTargetDurationNanos = 16666667;
    int64_t defaultTargetDurationNanos = 16666667;
    mHintSessionFuture = CommonPool::async([=, tids = std::move(tids)] {
    mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
        return gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos);
        return mBinding->createSession(manager, tids.data(), tids.size(),
                                       defaultTargetDurationNanos);
    });
    });
    return false;
    return false;
}
}
@@ -158,7 +124,7 @@ void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos)
        targetWorkDurationNanos > kSanityCheckLowerBound &&
        targetWorkDurationNanos > kSanityCheckLowerBound &&
        targetWorkDurationNanos < kSanityCheckUpperBound) {
        targetWorkDurationNanos < kSanityCheckUpperBound) {
        mLastTargetWorkDuration = targetWorkDurationNanos;
        mLastTargetWorkDuration = targetWorkDurationNanos;
        gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos);
        mBinding->updateTargetWorkDuration(mHintSession, targetWorkDurationNanos);
    }
    }
    mLastFrameNotification = systemTime();
    mLastFrameNotification = systemTime();
}
}
@@ -168,7 +134,7 @@ void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
    mResetsSinceLastReport = 0;
    mResetsSinceLastReport = 0;
    if (actualDurationNanos > kSanityCheckLowerBound &&
    if (actualDurationNanos > kSanityCheckLowerBound &&
        actualDurationNanos < kSanityCheckUpperBound) {
        actualDurationNanos < kSanityCheckUpperBound) {
        gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
        mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
    }
    }
}
}


@@ -179,14 +145,14 @@ void HintSessionWrapper::sendLoadResetHint() {
    if (now - mLastFrameNotification > kResetHintTimeout &&
    if (now - mLastFrameNotification > kResetHintTimeout &&
        mResetsSinceLastReport <= kMaxResetsSinceLastReport) {
        mResetsSinceLastReport <= kMaxResetsSinceLastReport) {
        ++mResetsSinceLastReport;
        ++mResetsSinceLastReport;
        gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
        mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET));
    }
    }
    mLastFrameNotification = now;
    mLastFrameNotification = now;
}
}


void HintSessionWrapper::sendLoadIncreaseHint() {
void HintSessionWrapper::sendLoadIncreaseHint() {
    if (!init()) return;
    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 */
} /* namespace renderthread */
+24 −0
Original line number Original line Diff line number Diff line
@@ -29,6 +29,8 @@ namespace renderthread {


class HintSessionWrapper {
class HintSessionWrapper {
public:
public:
    friend class HintSessionWrapperTests;

    HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId);
    HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId);
    ~HintSessionWrapper();
    ~HintSessionWrapper();


@@ -55,6 +57,28 @@ private:
    static constexpr nsecs_t kResetHintTimeout = 100_ms;
    static constexpr nsecs_t kResetHintTimeout = 100_ms;
    static constexpr int64_t kSanityCheckLowerBound = 100_us;
    static constexpr int64_t kSanityCheckLowerBound = 100_us;
    static constexpr int64_t kSanityCheckUpperBound = 10_s;
    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 */
} /* namespace renderthread */
+151 −0
Original line number Original line Diff line number Diff line
/*
 * 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