| /* |
| * Copyright 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 "SystemSuspend.h" |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/unique_fd.h> |
| #include <cutils/native_handle.h> |
| #include <gmock/gmock.h> |
| #include <google/protobuf/text_format.h> |
| #include <gtest/gtest.h> |
| #include <hidl/HidlTransportSupport.h> |
| |
| #include <sys/poll.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| |
| #include <chrono> |
| #include <csignal> |
| #include <cstdlib> |
| #include <future> |
| #include <string> |
| #include <thread> |
| |
| using android::sp; |
| using android::base::Socketpair; |
| using android::base::unique_fd; |
| using android::base::WriteStringToFd; |
| using android::hardware::configureRpcThreadpool; |
| using android::hardware::joinRpcThreadpool; |
| using android::hardware::Return; |
| using android::hardware::Void; |
| using android::system::suspend::V1_0::ISystemSuspend; |
| using android::system::suspend::V1_0::ISystemSuspendCallback; |
| using android::system::suspend::V1_0::IWakeLock; |
| using android::system::suspend::V1_0::readFd; |
| using android::system::suspend::V1_0::SystemSuspend; |
| using android::system::suspend::V1_0::WakeLockType; |
| using namespace std::chrono_literals; |
| |
| namespace android { |
| |
| static constexpr char kServiceName[] = "TestService"; |
| |
| static bool isReadBlocked(int fd) { |
| struct pollfd pfd { |
| .fd = fd, .events = POLLIN, |
| }; |
| int timeout_ms = 20; |
| return poll(&pfd, 1, timeout_ms) == 0; |
| } |
| |
| class SystemSuspendTestEnvironment : public ::testing::Environment { |
| public: |
| using Env = SystemSuspendTestEnvironment; |
| static Env* Instance() { |
| static Env* instance = new Env{}; |
| return instance; |
| } |
| |
| SystemSuspendTestEnvironment() { |
| Socketpair(SOCK_STREAM, &wakeupCountFds[0], &wakeupCountFds[1]); |
| Socketpair(SOCK_STREAM, &stateFds[0], &stateFds[1]); |
| } |
| |
| void registerTestService() { |
| std::thread testService([this] { |
| configureRpcThreadpool(1, true /* callerWillJoin */); |
| sp<ISystemSuspend> suspend = new SystemSuspend( |
| std::move(wakeupCountFds[1]), std::move(stateFds[1]), 0ms /* baseSleepTime */); |
| status_t status = suspend->registerAsService(kServiceName); |
| if (android::OK != status) { |
| LOG(FATAL) << "Unable to register service: " << status; |
| } |
| joinRpcThreadpool(); |
| }); |
| testService.detach(); |
| } |
| |
| virtual void SetUp() { |
| registerTestService(); |
| ::android::hardware::details::waitForHwService(ISystemSuspend::descriptor, kServiceName); |
| sp<ISystemSuspend> suspendService = ISystemSuspend::getService(kServiceName); |
| ASSERT_NE(suspendService, nullptr) << "failed to get suspend service"; |
| ASSERT_EQ(suspendService->enableAutosuspend(), true) << "failed to start autosuspend"; |
| } |
| |
| unique_fd wakeupCountFds[2]; |
| unique_fd stateFds[2]; |
| }; |
| |
| class SystemSuspendTest : public ::testing::Test { |
| public: |
| virtual void SetUp() override { |
| ::android::hardware::details::waitForHwService(ISystemSuspend::descriptor, kServiceName); |
| suspendService = ISystemSuspend::getService(kServiceName); |
| ASSERT_NE(suspendService, nullptr) << "failed to get suspend service"; |
| |
| auto* environment = SystemSuspendTestEnvironment::Instance(); |
| wakeupCountFd = environment->wakeupCountFds[0]; |
| stateFd = environment->stateFds[0]; |
| |
| // SystemSuspend HAL should not have written back to wakeupCountFd or stateFd yet. |
| ASSERT_TRUE(isReadBlocked(wakeupCountFd)); |
| ASSERT_TRUE(isReadBlocked(stateFd)); |
| } |
| |
| virtual void TearDown() override { |
| if (!isReadBlocked(wakeupCountFd)) readFd(wakeupCountFd); |
| if (!isReadBlocked(stateFd)) readFd(stateFd).empty(); |
| ASSERT_TRUE(isReadBlocked(wakeupCountFd)); |
| ASSERT_TRUE(isReadBlocked(stateFd)); |
| } |
| |
| void unblockSystemSuspendFromWakeupCount() { |
| std::string wakeupCount = std::to_string(rand()); |
| ASSERT_TRUE(WriteStringToFd(wakeupCount, wakeupCountFd)); |
| } |
| |
| bool isSystemSuspendBlocked() { return isReadBlocked(stateFd); } |
| |
| sp<IWakeLock> acquireWakeLock() { |
| return suspendService->acquireWakeLock(WakeLockType::PARTIAL, "TestLock"); |
| } |
| |
| SystemSuspendStats getDebugDump() { |
| // Index 0 corresponds to the read end of the pipe; 1 to the write end. |
| int fds[2]; |
| pipe2(fds, O_NONBLOCK); |
| native_handle_t* handle = native_handle_create(1, 0); |
| handle->data[0] = fds[1]; |
| |
| suspendService->debug(handle, {}); |
| SystemSuspendStats stats{}; |
| google::protobuf::TextFormat::ParseFromString(readFd(fds[0]), &stats); |
| |
| native_handle_close(handle); |
| close(fds[0]); |
| close(fds[1]); |
| |
| return stats; |
| } |
| |
| size_t getWakeLockCount() { return getDebugDump().wake_lock_stats().size(); } |
| |
| void checkLoop(int numIter) { |
| for (int i = 0; i < numIter; i++) { |
| // Mock value for /sys/power/wakeup_count. |
| std::string wakeupCount = std::to_string(rand()); |
| ASSERT_TRUE(WriteStringToFd(wakeupCount, wakeupCountFd)); |
| ASSERT_EQ(readFd(wakeupCountFd), wakeupCount) |
| << "wakeup count value written by SystemSuspend is not equal to value given to it"; |
| ASSERT_EQ(readFd(stateFd), "mem") |
| << "SystemSuspend failed to write correct sleep state."; |
| } |
| } |
| |
| sp<ISystemSuspend> suspendService; |
| int stateFd; |
| int wakeupCountFd; |
| }; |
| |
| // Tests that autosuspend thread can only be enabled once. |
| TEST_F(SystemSuspendTest, OnlyOneEnableAutosuspend) { |
| ASSERT_EQ(suspendService->enableAutosuspend(), false); |
| } |
| |
| TEST_F(SystemSuspendTest, AutosuspendLoop) { |
| checkLoop(5); |
| } |
| |
| // Tests that upon WakeLock destruction SystemSuspend HAL is unblocked. |
| TEST_F(SystemSuspendTest, WakeLockDestructor) { |
| { |
| sp<IWakeLock> wl = acquireWakeLock(); |
| ASSERT_NE(wl, nullptr); |
| unblockSystemSuspendFromWakeupCount(); |
| ASSERT_TRUE(isSystemSuspendBlocked()); |
| } |
| ASSERT_FALSE(isSystemSuspendBlocked()); |
| } |
| |
| // Tests that upon WakeLock::release() SystemSuspend HAL is unblocked. |
| TEST_F(SystemSuspendTest, WakeLockRelease) { |
| sp<IWakeLock> wl = acquireWakeLock(); |
| ASSERT_NE(wl, nullptr); |
| unblockSystemSuspendFromWakeupCount(); |
| ASSERT_TRUE(isSystemSuspendBlocked()); |
| wl->release(); |
| ASSERT_FALSE(isSystemSuspendBlocked()); |
| } |
| |
| // Tests that multiple WakeLocks correctly block SystemSuspend HAL. |
| TEST_F(SystemSuspendTest, MultipleWakeLocks) { |
| { |
| sp<IWakeLock> wl1 = acquireWakeLock(); |
| ASSERT_NE(wl1, nullptr); |
| ASSERT_TRUE(isSystemSuspendBlocked()); |
| unblockSystemSuspendFromWakeupCount(); |
| { |
| sp<IWakeLock> wl2 = acquireWakeLock(); |
| ASSERT_NE(wl2, nullptr); |
| ASSERT_TRUE(isSystemSuspendBlocked()); |
| } |
| ASSERT_TRUE(isSystemSuspendBlocked()); |
| } |
| ASSERT_FALSE(isSystemSuspendBlocked()); |
| } |
| |
| // Tests that upon thread deallocation WakeLock is destructed and SystemSuspend HAL is unblocked. |
| TEST_F(SystemSuspendTest, ThreadCleanup) { |
| std::thread clientThread([this] { |
| sp<IWakeLock> wl = acquireWakeLock(); |
| ASSERT_NE(wl, nullptr); |
| unblockSystemSuspendFromWakeupCount(); |
| ASSERT_TRUE(isSystemSuspendBlocked()); |
| }); |
| clientThread.join(); |
| ASSERT_FALSE(isSystemSuspendBlocked()); |
| } |
| |
| // Test that binder driver correctly deallocates acquired WakeLocks, even if the client processs |
| // is terminated without ability to do clean up. |
| TEST_F(SystemSuspendTest, CleanupOnAbort) { |
| ASSERT_EXIT( |
| { |
| sp<IWakeLock> wl = acquireWakeLock(); |
| ASSERT_NE(wl, nullptr); |
| std::abort(); |
| }, |
| ::testing::KilledBySignal(SIGABRT), ""); |
| ASSERT_TRUE(isSystemSuspendBlocked()); |
| unblockSystemSuspendFromWakeupCount(); |
| ASSERT_FALSE(isSystemSuspendBlocked()); |
| } |
| |
| // Test that debug dump has correct information about acquired WakeLocks. |
| TEST_F(SystemSuspendTest, DebugDump) { |
| { |
| sp<IWakeLock> wl = acquireWakeLock(); |
| SystemSuspendStats debugDump = getDebugDump(); |
| ASSERT_EQ(debugDump.wake_lock_stats().size(), 1); |
| ASSERT_EQ(debugDump.wake_lock_stats().begin()->second.name(), "TestLock"); |
| } |
| ASSERT_EQ(getWakeLockCount(), 0); |
| } |
| |
| // Stress test acquiring/releasing WakeLocks. |
| TEST_F(SystemSuspendTest, WakeLockStressTest) { |
| // numThreads threads will acquire/release numLocks locks each. |
| constexpr int numThreads = 10; |
| constexpr int numLocks = 10000; |
| std::thread tds[numThreads]; |
| |
| for (int i = 0; i < numThreads; i++) { |
| tds[i] = std::thread([this] { |
| for (int i = 0; i < numLocks; i++) { |
| sp<IWakeLock> wl1 = acquireWakeLock(); |
| sp<IWakeLock> wl2 = acquireWakeLock(); |
| wl2->release(); |
| } |
| }); |
| } |
| for (int i = 0; i < numThreads; i++) { |
| tds[i].join(); |
| } |
| ASSERT_EQ(getWakeLockCount(), 0); |
| } |
| |
| // Callbacks are passed around as sp<>. However, mock expectations are verified when mock objects |
| // are destroyed, i.e. the test needs to control lifetime of the mock object. |
| // MockCallbackImpl can be destroyed independently of its wrapper MockCallback which is passed to |
| // SystemSuspend. |
| struct MockCallbackImpl { |
| MOCK_METHOD1(notifyWakeup, Return<void>(bool)); |
| }; |
| |
| class MockCallback : public ISystemSuspendCallback { |
| public: |
| MockCallback(MockCallbackImpl* impl) : mImpl(impl), mDisabled(false) {} |
| Return<void> notifyWakeup(bool x) { return mDisabled ? Void() : mImpl->notifyWakeup(x); } |
| // In case we pull the rug from under MockCallback, but SystemSuspend still has an sp<> to the |
| // object. |
| void disable() { mDisabled = true; } |
| |
| private: |
| MockCallbackImpl* mImpl; |
| bool mDisabled; |
| }; |
| |
| // Tests that nullptr can't be registered as callbacks. |
| TEST_F(SystemSuspendTest, RegisterInvalidCallback) { |
| ASSERT_FALSE(suspendService->registerCallback(nullptr)); |
| } |
| |
| // Tests that SystemSuspend HAL correctly notifies wakeup events. |
| TEST_F(SystemSuspendTest, CallbackNotifyWakeup) { |
| constexpr int numWakeups = 5; |
| MockCallbackImpl impl; |
| // SystemSuspend should suspend numWakeup + 1 times. However, it might |
| // only be able to notify numWakeup times. The test case might have |
| // finished by the time last notification completes. |
| EXPECT_CALL(impl, notifyWakeup).Times(testing::AtLeast(numWakeups)); |
| sp<MockCallback> cb = new MockCallback(&impl); |
| ASSERT_TRUE(suspendService->registerCallback(cb)); |
| checkLoop(numWakeups + 1); |
| cb->disable(); |
| } |
| |
| // Tests that SystemSuspend HAL correctly deals with a dead callback. |
| TEST_F(SystemSuspendTest, DeadCallback) { |
| ASSERT_EXIT( |
| { |
| sp<MockCallback> cb = new MockCallback(nullptr); |
| ASSERT_TRUE(suspendService->registerCallback(cb)); |
| std::exit(0); |
| }, |
| ::testing::ExitedWithCode(0), ""); |
| |
| // Dead process callback must still be dealt with either by unregistering it |
| // or checking isOk() on every call. |
| checkLoop(3); |
| } |
| } // namespace android |
| |
| int main(int argc, char** argv) { |
| setenv("TREBLE_TESTING_OVERRIDE", "true", true); |
| ::testing::AddGlobalTestEnvironment(android::SystemSuspendTestEnvironment::Instance()); |
| ::testing::InitGoogleMock(&argc, argv); |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |