/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "healthd_mode_charger_hidl.h" using android::hardware::Return; using android::hardware::health::InitHealthdConfig; using std::string_literals::operator""s; using testing::_; using testing::Invoke; using testing::NiceMock; using testing::StrEq; using testing::Test; namespace android { // A replacement to ASSERT_* to be used in a forked process. When the condition is not met, // print a gtest message, then exit abnormally. class ChildAssertHelper : public std::stringstream { public: ChildAssertHelper(bool res, const char* expr, const char* file, int line) : res_(res) { (*this) << file << ":" << line << ": `" << expr << "` evaluates to false\n"; } ~ChildAssertHelper() { EXPECT_TRUE(res_) << str(); if (!res_) exit(EX_SOFTWARE); } private: bool res_; DISALLOW_COPY_AND_ASSIGN(ChildAssertHelper); }; #define CHILD_ASSERT_TRUE(expr) ChildAssertHelper(expr, #expr, __FILE__, __LINE__) // Run |test_body| in a chroot jail in a forked process. |subdir| is a sub-directory in testdata. // Within |test_body|, // - non-fatal errors may be reported using EXPECT_* macro as usual. // - fatal errors must be reported using CHILD_ASSERT_TRUE macro. ASSERT_* must not be used. void ForkTest(const std::string& subdir, const std::function& test_body) { pid_t pid = fork(); ASSERT_GE(pid, 0) << "Fork fails: " << strerror(errno); if (pid == 0) { // child CHILD_ASSERT_TRUE( chroot((android::base::GetExecutableDirectory() + "/" + subdir).c_str()) != -1) << "Failed to chroot to " << subdir << ": " << strerror(errno); test_body(); // EXPECT_* macros may set the HasFailure bit without calling exit(). Set exit status // accordingly. exit(::testing::Test::HasFailure() ? EX_SOFTWARE : EX_OK); } // parent int status; ASSERT_NE(-1, waitpid(pid, &status, 0)) << "waitpid() fails: " << strerror(errno); ASSERT_TRUE(WIFEXITED(status)) << "Test fails, waitpid() returns " << status; ASSERT_EQ(EX_OK, WEXITSTATUS(status)) << "Test fails, child process returns " << status; } class MockHealth : public android::hardware::health::V2_1::IHealth { MOCK_METHOD(Return<::android::hardware::health::V2_0::Result>, registerCallback, (const sp<::android::hardware::health::V2_0::IHealthInfoCallback>& callback)); MOCK_METHOD(Return<::android::hardware::health::V2_0::Result>, unregisterCallback, (const sp<::android::hardware::health::V2_0::IHealthInfoCallback>& callback)); MOCK_METHOD(Return<::android::hardware::health::V2_0::Result>, update, ()); MOCK_METHOD(Return, getChargeCounter, (getChargeCounter_cb _hidl_cb)); MOCK_METHOD(Return, getCurrentNow, (getCurrentNow_cb _hidl_cb)); MOCK_METHOD(Return, getCurrentAverage, (getCurrentAverage_cb _hidl_cb)); MOCK_METHOD(Return, getCapacity, (getCapacity_cb _hidl_cb)); MOCK_METHOD(Return, getEnergyCounter, (getEnergyCounter_cb _hidl_cb)); MOCK_METHOD(Return, getChargeStatus, (getChargeStatus_cb _hidl_cb)); MOCK_METHOD(Return, getStorageInfo, (getStorageInfo_cb _hidl_cb)); MOCK_METHOD(Return, getDiskStats, (getDiskStats_cb _hidl_cb)); MOCK_METHOD(Return, getHealthInfo, (getHealthInfo_cb _hidl_cb)); MOCK_METHOD(Return, getHealthConfig, (getHealthConfig_cb _hidl_cb)); MOCK_METHOD(Return, getHealthInfo_2_1, (getHealthInfo_2_1_cb _hidl_cb)); MOCK_METHOD(Return, shouldKeepScreenOn, (shouldKeepScreenOn_cb _hidl_cb)); }; class TestCharger : public ChargerHidl { public: // Inherit constructor. using ChargerHidl::ChargerHidl; // Expose protected functions to be used in tests. void Init(struct healthd_config* config) override { ChargerHidl::Init(config); } MOCK_METHOD(int, CreateDisplaySurface, (const std::string& name, GRSurface** surface)); MOCK_METHOD(int, CreateMultiDisplaySurface, (const std::string& name, int* frames, int* fps, GRSurface*** surface)); }; // Intentionally leak TestCharger instance to avoid calling ~HealthLoop() because ~HealthLoop() // should never be called. But still verify expected calls upon destruction. class VerifiedTestCharger { public: VerifiedTestCharger(TestCharger* charger) : charger_(charger) { testing::Mock::AllowLeak(charger_); } TestCharger& operator*() { return *charger_; } TestCharger* operator->() { return charger_; } ~VerifiedTestCharger() { testing::Mock::VerifyAndClearExpectations(charger_); } private: TestCharger* charger_; }; // Do not use SetUp and TearDown of a test suite, as they will be invoked in the parent process, not // the child process. In particular, if the test suite contains mocks, they will not be verified in // the child process. Instead, create mocks within closures in each tests. void ExpectChargerResAt(const std::string& root) { sp> health(new NiceMock()); VerifiedTestCharger charger(new NiceMock(health)); // Only one frame in all testdata/**/animation.txt GRSurface* multi[] = {nullptr}; EXPECT_CALL(*charger, CreateDisplaySurface(StrEq(root + "charger/battery_fail.png"), _)) .WillRepeatedly(Invoke([](const auto&, GRSurface** surface) { *surface = nullptr; return 0; })); EXPECT_CALL(*charger, CreateMultiDisplaySurface(StrEq(root + "charger/battery_scale.png"), _, _, _)) .WillRepeatedly(Invoke([&](const auto&, int* frames, int* fps, GRSurface*** surface) { *frames = arraysize(multi); *fps = 60; // Unused fps value *surface = multi; return 0; })); struct healthd_config healthd_config; InitHealthdConfig(&healthd_config); charger->Init(&healthd_config); }; // Test that if resources does not exist in /res or in /product/etc/res, load from /system. TEST(ChargerLoadAnimationRes, Empty) { ForkTest("empty", std::bind(&ExpectChargerResAt, "/system/etc/res/images/")); } // Test loading everything from /res TEST(ChargerLoadAnimationRes, Legacy) { ForkTest("legacy", std::bind(&ExpectChargerResAt, "/res/images/")); } // Test loading animation text from /res but images from /system if images does not exist under // /res. TEST(ChargerLoadAnimationRes, LegacyTextSystemImages) { ForkTest("legacy_text_system_images", std::bind(&ExpectChargerResAt, "/system/etc/res/images/")); } // Test loading everything from /product TEST(ChargerLoadAnimationRes, Product) { ForkTest("product", std::bind(&ExpectChargerResAt, "/product/etc/res/images/")); } } // namespace android