diff --git a/healthd/Android.bp b/healthd/Android.bp index a4300f7f3..b3de9c4a8 100644 --- a/healthd/Android.bp +++ b/healthd/Android.bp @@ -243,19 +243,22 @@ cc_test { cc_test { name: "libhealthd_charger_test", - srcs: ["AnimationParser_test.cpp"], - shared_libs: [ - "liblog", - "libbase", - "libcutils", + defaults: ["charger_defaults"], + srcs: [ + "AnimationParser_test.cpp", + "healthd_mode_charger_test.cpp" ], static_libs: [ - "libhealthd_charger", + "libgmock", ], test_suites: [ "general-tests", "device-tests", ], + data: [ + ":libhealthd_charger_test_data", + ], + require_root: true, } // /system/etc/res/images/charger/battery_fail.png diff --git a/healthd/healthd_mode_charger_test.cpp b/healthd/healthd_mode_charger_test.cpp new file mode 100644 index 000000000..f444f66ad --- /dev/null +++ b/healthd/healthd_mode_charger_test.cpp @@ -0,0 +1,181 @@ +/* + * 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 "healthd_mode_charger.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 Charger { + public: + // Inherit constructor. + using Charger::Charger; + // Expose protected functions to be used in tests. + void Init(struct healthd_config* config) override { Charger::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 diff --git a/healthd/testdata/Android.bp b/healthd/testdata/Android.bp new file mode 100644 index 000000000..110c79a87 --- /dev/null +++ b/healthd/testdata/Android.bp @@ -0,0 +1,20 @@ +// +// 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. +// + +filegroup { + name: "libhealthd_charger_test_data", + srcs: ["**/*.*"], +} diff --git a/healthd/testdata/empty/ensure_directory_creation.txt b/healthd/testdata/empty/ensure_directory_creation.txt new file mode 100644 index 000000000..36ceff465 --- /dev/null +++ b/healthd/testdata/empty/ensure_directory_creation.txt @@ -0,0 +1 @@ +File is placed to ensure directory is created on the device. diff --git a/healthd/testdata/legacy/res/images/charger/battery_fail.png b/healthd/testdata/legacy/res/images/charger/battery_fail.png new file mode 100644 index 000000000..e69de29bb diff --git a/healthd/testdata/legacy/res/images/charger/battery_scale.png b/healthd/testdata/legacy/res/images/charger/battery_scale.png new file mode 100644 index 000000000..e69de29bb diff --git a/healthd/testdata/legacy/res/values/charger/animation.txt b/healthd/testdata/legacy/res/values/charger/animation.txt new file mode 100644 index 000000000..075333676 --- /dev/null +++ b/healthd/testdata/legacy/res/values/charger/animation.txt @@ -0,0 +1,9 @@ +# Sample Animation file for testing. + +# animation: num_cycles, first_frame_repeats, animation_file +animation: 2 1 charger/battery_scale + +fail: charger/battery_fail + +# frame: disp_time min_level max_level +frame: 15 0 100 diff --git a/healthd/testdata/legacy_text_system_images/res/values/charger/animation.txt b/healthd/testdata/legacy_text_system_images/res/values/charger/animation.txt new file mode 100644 index 000000000..075333676 --- /dev/null +++ b/healthd/testdata/legacy_text_system_images/res/values/charger/animation.txt @@ -0,0 +1,9 @@ +# Sample Animation file for testing. + +# animation: num_cycles, first_frame_repeats, animation_file +animation: 2 1 charger/battery_scale + +fail: charger/battery_fail + +# frame: disp_time min_level max_level +frame: 15 0 100 diff --git a/healthd/testdata/product/product/etc/res/images/charger/battery_fail.png b/healthd/testdata/product/product/etc/res/images/charger/battery_fail.png new file mode 100644 index 000000000..e69de29bb diff --git a/healthd/testdata/product/product/etc/res/images/charger/battery_scale.png b/healthd/testdata/product/product/etc/res/images/charger/battery_scale.png new file mode 100644 index 000000000..e69de29bb diff --git a/healthd/testdata/product/product/etc/res/values/charger/animation.txt b/healthd/testdata/product/product/etc/res/values/charger/animation.txt new file mode 100644 index 000000000..075333676 --- /dev/null +++ b/healthd/testdata/product/product/etc/res/values/charger/animation.txt @@ -0,0 +1,9 @@ +# Sample Animation file for testing. + +# animation: num_cycles, first_frame_repeats, animation_file +animation: 2 1 charger/battery_scale + +fail: charger/battery_fail + +# frame: disp_time min_level max_level +frame: 15 0 100