| /* |
| * Copyright (C) 2020 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. |
| */ |
| |
| #define LOG_TAG "PowerHalControllerTest" |
| |
| #include <aidl/android/hardware/power/Boost.h> |
| #include <aidl/android/hardware/power/IPower.h> |
| #include <aidl/android/hardware/power/Mode.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <powermanager/PowerHalController.h> |
| #include <utils/Log.h> |
| |
| #include <thread> |
| |
| using aidl::android::hardware::power::Boost; |
| using aidl::android::hardware::power::Mode; |
| using android::hardware::power::V1_0::Feature; |
| using android::hardware::power::V1_0::IPower; |
| using android::hardware::power::V1_0::PowerHint; |
| |
| using namespace android; |
| using namespace android::power; |
| using namespace std::chrono_literals; |
| using namespace testing; |
| |
| // ------------------------------------------------------------------------------------------------- |
| |
| class MockIPowerV1_0 : public IPower { |
| public: |
| MOCK_METHOD(hardware::Return<void>, setInteractive, (bool interactive), (override)); |
| MOCK_METHOD(hardware::Return<void>, powerHint, (PowerHint hint, int32_t data), (override)); |
| MOCK_METHOD(hardware::Return<void>, setFeature, (Feature feature, bool activate), (override)); |
| MOCK_METHOD(hardware::Return<void>, getPlatformLowPowerStats, |
| (getPlatformLowPowerStats_cb _hidl_cb), (override)); |
| }; |
| |
| class TestPowerHalConnector : public HalConnector { |
| public: |
| TestPowerHalConnector(sp<IPower> powerHal) : mHal(std::move(powerHal)) {} |
| virtual ~TestPowerHalConnector() = default; |
| |
| virtual std::unique_ptr<HalWrapper> connect() override { |
| mCountMutex.lock(); |
| ++mConnectedCount; |
| mCountMutex.unlock(); |
| return std::make_unique<HidlHalWrapperV1_0>(mHal); |
| } |
| |
| void reset() override { |
| mCountMutex.lock(); |
| ++mResetCount; |
| mCountMutex.unlock(); |
| } |
| |
| int getConnectCount() { return mConnectedCount; } |
| |
| int getResetCount() { return mResetCount; } |
| |
| private: |
| sp<IPower> mHal = nullptr; |
| std::mutex mCountMutex; |
| int mConnectedCount = 0; |
| int mResetCount = 0; |
| }; |
| |
| class AlwaysFailingTestPowerHalConnector : public TestPowerHalConnector { |
| public: |
| AlwaysFailingTestPowerHalConnector() : TestPowerHalConnector(nullptr) {} |
| |
| std::unique_ptr<HalWrapper> connect() override { |
| // Call parent to update counter, but ignore connected HalWrapper. |
| TestPowerHalConnector::connect(); |
| return nullptr; |
| } |
| }; |
| |
| // ------------------------------------------------------------------------------------------------- |
| |
| class PowerHalControllerTest : public Test { |
| public: |
| void SetUp() override { |
| mMockHal = new StrictMock<MockIPowerV1_0>(); |
| std::unique_ptr<TestPowerHalConnector> halConnector = |
| std::make_unique<TestPowerHalConnector>(mMockHal); |
| mHalConnector = halConnector.get(); |
| mHalController = std::make_unique<PowerHalController>(std::move(halConnector)); |
| } |
| |
| protected: |
| sp<StrictMock<MockIPowerV1_0>> mMockHal = nullptr; |
| TestPowerHalConnector* mHalConnector = nullptr; |
| std::unique_ptr<PowerHalController> mHalController = nullptr; |
| }; |
| |
| // ------------------------------------------------------------------------------------------------- |
| |
| TEST_F(PowerHalControllerTest, TestInitConnectsToPowerHalOnlyOnce) { |
| int powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 0); |
| |
| mHalController->init(); |
| mHalController->init(); |
| |
| // PowerHalConnector was called only once and never reset. |
| powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 1); |
| int powerHalResetCount = mHalConnector->getResetCount(); |
| EXPECT_EQ(powerHalResetCount, 0); |
| } |
| |
| TEST_F(PowerHalControllerTest, TestUnableToConnectToPowerHalIgnoresAllApiCalls) { |
| std::unique_ptr<AlwaysFailingTestPowerHalConnector> halConnector = |
| std::make_unique<AlwaysFailingTestPowerHalConnector>(); |
| AlwaysFailingTestPowerHalConnector* failingHalConnector = halConnector.get(); |
| PowerHalController halController(std::move(halConnector)); |
| |
| int powerHalConnectCount = failingHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 0); |
| |
| // Still works with EmptyPowerHalWrapper as fallback ignoring every api call |
| // and logging. |
| auto result = halController.setBoost(Boost::INTERACTION, 1000); |
| ASSERT_TRUE(result.isUnsupported()); |
| result = halController.setMode(Mode::LAUNCH, true); |
| ASSERT_TRUE(result.isUnsupported()); |
| |
| // PowerHalConnector was called every time to attempt to reconnect with |
| // underlying service. |
| powerHalConnectCount = failingHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 2); |
| // PowerHalConnector was never reset. |
| int powerHalResetCount = mHalConnector->getResetCount(); |
| EXPECT_EQ(powerHalResetCount, 0); |
| } |
| |
| TEST_F(PowerHalControllerTest, TestAllApiCallsDelegatedToConnectedPowerHal) { |
| int powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 0); |
| |
| { |
| InSequence seg; |
| EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::INTERACTION), Eq(100))) |
| .Times(Exactly(1)); |
| EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LAUNCH), Eq(1))).Times(Exactly(1)); |
| } |
| |
| auto result = mHalController->setBoost(Boost::INTERACTION, 100); |
| ASSERT_TRUE(result.isOk()); |
| result = mHalController->setMode(Mode::LAUNCH, true); |
| ASSERT_TRUE(result.isOk()); |
| |
| // PowerHalConnector was called only once and never reset. |
| powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 1); |
| int powerHalResetCount = mHalConnector->getResetCount(); |
| EXPECT_EQ(powerHalResetCount, 0); |
| } |
| |
| TEST_F(PowerHalControllerTest, TestPowerHalRecoversFromFailureByRecreatingPowerHal) { |
| int powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 0); |
| |
| ON_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LAUNCH), _)) |
| .WillByDefault([](PowerHint, int32_t) { |
| return hardware::Return<void>(hardware::Status::fromExceptionCode(-1)); |
| }); |
| |
| EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(Exactly(4)); |
| |
| auto result = mHalController->setBoost(Boost::INTERACTION, 1000); |
| ASSERT_TRUE(result.isOk()); |
| result = mHalController->setMode(Mode::LAUNCH, true); |
| ASSERT_TRUE(result.isFailed()); |
| result = mHalController->setMode(Mode::VR, false); |
| ASSERT_TRUE(result.isOk()); |
| result = mHalController->setMode(Mode::LOW_POWER, true); |
| ASSERT_TRUE(result.isOk()); |
| |
| // PowerHalConnector was called only twice: on first api call and after failed |
| // call. |
| powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 2); |
| // PowerHalConnector was reset once after failed call. |
| int powerHalResetCount = mHalConnector->getResetCount(); |
| EXPECT_EQ(powerHalResetCount, 1); |
| } |
| |
| TEST_F(PowerHalControllerTest, TestPowerHalDoesNotTryToRecoverFromFailureOnUnsupportedCalls) { |
| int powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 0); |
| |
| auto result = mHalController->setBoost(Boost::CAMERA_LAUNCH, 1000); |
| ASSERT_TRUE(result.isUnsupported()); |
| result = mHalController->setMode(Mode::CAMERA_STREAMING_HIGH, true); |
| ASSERT_TRUE(result.isUnsupported()); |
| |
| // PowerHalConnector was called only once and never reset. |
| powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 1); |
| int powerHalResetCount = mHalConnector->getResetCount(); |
| EXPECT_EQ(powerHalResetCount, 0); |
| } |
| |
| TEST_F(PowerHalControllerTest, TestMultiThreadConnectsOnlyOnce) { |
| int powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 0); |
| |
| EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(Exactly(10)); |
| |
| std::vector<std::thread> threads; |
| for (int i = 0; i < 10; i++) { |
| threads.push_back(std::thread([&]() { |
| auto result = mHalController->setBoost(Boost::INTERACTION, 1000); |
| ASSERT_TRUE(result.isOk()); |
| })); |
| } |
| std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); }); |
| |
| // PowerHalConnector was called only by the first thread to use the api and |
| // never reset. |
| powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 1); |
| int powerHalResetCount = mHalConnector->getResetCount(); |
| EXPECT_EQ(powerHalResetCount, 0); |
| } |
| |
| TEST_F(PowerHalControllerTest, TestMultiThreadWithFailureReconnectIsThreadSafe) { |
| int powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_EQ(powerHalConnectCount, 0); |
| |
| ON_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LAUNCH), _)) |
| .WillByDefault([](PowerHint, int32_t) { |
| return hardware::Return<void>(hardware::Status::fromExceptionCode(-1)); |
| }); |
| |
| EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(Exactly(40)); |
| |
| std::vector<std::thread> threads; |
| for (int i = 0; i < 10; i++) { |
| threads.push_back(std::thread([&]() { |
| auto result = mHalController->setBoost(Boost::INTERACTION, 1000); |
| ASSERT_TRUE(result.isOk()); |
| })); |
| threads.push_back(std::thread([&]() { |
| auto result = mHalController->setMode(Mode::LAUNCH, true); |
| ASSERT_TRUE(result.isFailed()); |
| })); |
| threads.push_back(std::thread([&]() { |
| auto result = mHalController->setMode(Mode::LOW_POWER, false); |
| ASSERT_TRUE(result.isOk()); |
| })); |
| threads.push_back(std::thread([&]() { |
| auto result = mHalController->setMode(Mode::VR, true); |
| ASSERT_TRUE(result.isOk()); |
| })); |
| } |
| std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); }); |
| |
| // PowerHalConnector was called at least once by the first thread. |
| // Reset and reconnect calls were made at most 10 times, once after each |
| // failure. |
| powerHalConnectCount = mHalConnector->getConnectCount(); |
| EXPECT_THAT(powerHalConnectCount, AllOf(Ge(1), Le(11))); |
| int powerHalResetCount = mHalConnector->getResetCount(); |
| EXPECT_THAT(powerHalResetCount, Le(10)); |
| } |