| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "tools/mac/power/power_sampler/battery_sampler.h" |
| |
| #include <cstdint> |
| #include <memory> |
| |
| #include <IOKit/IOKitLib.h> |
| |
| #include "base/time/time.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace power_sampler { |
| |
| namespace { |
| |
| using testing::UnorderedElementsAre; |
| |
| class TestBatterySampler : public BatterySampler { |
| public: |
| // Make public for testing. |
| using BatterySampler::BatteryData; |
| using BatterySampler::MaybeComputeAvgConsumption; |
| static std::unique_ptr<BatterySampler> CreateForTesting(); |
| }; |
| |
| class BatterySamplerTest : public testing::Test { |
| protected: |
| using BatteryData = TestBatterySampler::BatteryData; |
| |
| void SetUp() override { set_battery_data(std::nullopt); } |
| |
| static void set_battery_data(std::optional<BatteryData> battery_data) { |
| battery_data_ = battery_data; |
| } |
| |
| static void set_seconds_since_epoch(int64_t seconds_since_epoch) { |
| seconds_since_epoch_ = seconds_since_epoch; |
| } |
| |
| // The gmock *ElementsAre* matchers are too exacting for the double values |
| // in our samples, but this poor man's substitute will do for our needs. |
| template <size_t N> |
| void ExpectSampleMatchesArray( |
| const Sampler::Sample& sample, |
| const std::pair<std::string, double> (&datums)[N]) { |
| EXPECT_EQ(N, sample.size()); |
| for (size_t i = 0; i < N; ++i) { |
| const auto& name = datums[i].first; |
| const double value = datums[i].second; |
| |
| auto it = sample.find(name); |
| EXPECT_TRUE(it != sample.end()) << " for " << name; |
| if (it != sample.end()) |
| EXPECT_DOUBLE_EQ(it->second, value) << " for " << name; |
| } |
| } |
| |
| private: |
| friend class TestBatterySampler; |
| |
| static std::optional<BatteryData> GetStaticBatteryData( |
| io_service_t power_source) { |
| return battery_data_; |
| } |
| |
| static int64_t GetSecondsSinceEpoch() { return seconds_since_epoch_; } |
| |
| static std::optional<BatteryData> battery_data_; |
| static int64_t seconds_since_epoch_; |
| }; |
| |
| std::optional<BatterySamplerTest::BatteryData> |
| BatterySamplerTest::battery_data_; |
| |
| int64_t BatterySamplerTest::seconds_since_epoch_; |
| |
| // static |
| std::unique_ptr<BatterySampler> TestBatterySampler::CreateForTesting() { |
| return BatterySampler::CreateImpl(BatterySamplerTest::GetStaticBatteryData, |
| BatterySamplerTest::GetSecondsSinceEpoch, |
| base::mac::ScopedIOObject<io_service_t>()); |
| } |
| |
| } // namespace |
| |
| TEST_F(BatterySamplerTest, CreateFailsWhenNoData) { |
| EXPECT_EQ(nullptr, TestBatterySampler::CreateForTesting()); |
| } |
| |
| TEST_F(BatterySamplerTest, CreateSucceedsWithData) { |
| set_battery_data(TestBatterySampler::BatteryData{}); |
| EXPECT_NE(nullptr, TestBatterySampler::CreateForTesting()); |
| } |
| |
| TEST_F(BatterySamplerTest, NameAndGetDatumNameUnits) { |
| set_battery_data(TestBatterySampler::BatteryData{}); |
| std::unique_ptr<BatterySampler> sampler( |
| TestBatterySampler::CreateForTesting()); |
| ASSERT_NE(nullptr, sampler.get()); |
| |
| EXPECT_EQ("battery", sampler->GetName()); |
| |
| auto datum_name_units = sampler->GetDatumNameUnits(); |
| EXPECT_THAT( |
| datum_name_units, |
| UnorderedElementsAre(std::make_pair("external_connected", "bool"), |
| std::make_pair("voltage", "V"), |
| std::make_pair("current_capacity", "Ah"), |
| std::make_pair("max_capacity", "Ah"), |
| std::make_pair("avg_power", "W"), |
| std::make_pair("electric_charge_delta", "mAh"), |
| std::make_pair("sample_age", "s"))); |
| } |
| |
| TEST_F(BatterySamplerTest, MaybeComputeAvgConsumption) { |
| TestBatterySampler::BatteryData prev_data{ |
| .voltage_mv = 11100, // 11.1V. |
| .current_capacity_mah = 2001, // 2.001 Ah remaining. |
| .max_capacity_mah = 5225 // Corresponds to 58Wh/11.1V in mAh. |
| }; |
| TestBatterySampler::BatteryData new_data = prev_data; |
| |
| base::TimeDelta delta = base::Minutes(1); |
| // No power if the data is identical. |
| auto consumption = TestBatterySampler::MaybeComputeAvgConsumption( |
| delta, prev_data, new_data); |
| EXPECT_FALSE(consumption.has_value()); |
| |
| // Adjust current capacity and max capacity by the same value, which means |
| // net zero consumption. |
| new_data.current_capacity_mah -= 51; |
| new_data.max_capacity_mah -= 51; |
| consumption = TestBatterySampler::MaybeComputeAvgConsumption(delta, prev_data, |
| new_data); |
| EXPECT_FALSE(consumption.has_value()); |
| |
| // Consume 1mAh. |
| new_data.current_capacity_mah -= 1; |
| consumption = TestBatterySampler::MaybeComputeAvgConsumption(delta, prev_data, |
| new_data); |
| ASSERT_TRUE(consumption.has_value()); |
| double expected_power_w = |
| (11.1 + 11.1) / 2.0 * // Average voltage (V). |
| (1.0 * 3600.0 / 1000.0) / // Current consumption (As). |
| 60.0; // 1 minute (s). |
| EXPECT_DOUBLE_EQ(expected_power_w, consumption->watts); |
| EXPECT_DOUBLE_EQ(1, consumption->mah); |
| |
| // Try a voltage change. |
| new_data.voltage_mv = 11200; // 11.2V. |
| |
| // And compute the consumption over two minutes. |
| consumption = TestBatterySampler::MaybeComputeAvgConsumption( |
| 2 * delta, prev_data, new_data); |
| ASSERT_TRUE(consumption.has_value()); |
| expected_power_w = (11.1 + 11.2) / 2.0 * // Average voltage (V). |
| (1.0 * 3600.0 / 1000.0) / // Current consumption (As). |
| 120.0; // 2 minutes (s). |
| EXPECT_DOUBLE_EQ(expected_power_w, consumption->watts); |
| EXPECT_DOUBLE_EQ(1, consumption->mah); |
| } |
| |
| TEST_F(BatterySamplerTest, ReturnsSamplesAndComputesPower) { |
| TestBatterySampler::BatteryData battery_data{ |
| .external_connected = true, |
| .voltage_mv = 11100, // 11.1V. |
| .current_capacity_mah = 2001, // 2.001 Ah remaining. |
| .max_capacity_mah = 5225, // Corresponds to 58Wh/11.1V in mAh. |
| .update_time_seconds_since_epoch = 42}; |
| set_battery_data(battery_data); |
| set_seconds_since_epoch(43); |
| std::unique_ptr<BatterySampler> sampler( |
| TestBatterySampler::CreateForTesting()); |
| |
| ASSERT_NE(nullptr, sampler.get()); |
| base::TimeTicks now = base::TimeTicks::Now(); |
| |
| set_battery_data(battery_data); |
| Sampler::Sample datums = sampler->GetSample(now); |
| |
| // There's no power estimate for the initial sample. |
| ExpectSampleMatchesArray(datums, {std::make_pair("external_connected", true), |
| std::make_pair("voltage", 11.1), |
| std::make_pair("current_capacity", 2.001), |
| std::make_pair("max_capacity", 5.225), |
| std::make_pair("sample_age", 1)}); |
| |
| battery_data.current_capacity_mah -= 1; |
| battery_data.update_time_seconds_since_epoch = 44; |
| set_battery_data(battery_data); |
| set_seconds_since_epoch(46); |
| constexpr base::TimeDelta kOneMinute = base::Minutes(1); |
| now += kOneMinute; |
| datums = sampler->GetSample(now); |
| double expected_power_w = |
| (11.1 + 11.1) / 2.0 * // Average voltage (V). |
| (1.0 * 3600.0 / 1000.0) / // Current consumption (As). |
| 60.0; // 1 minute (s). |
| ExpectSampleMatchesArray( |
| datums, |
| {std::make_pair("external_connected", true), |
| std::make_pair("voltage", 11.1), std::make_pair("current_capacity", 2), |
| std::make_pair("max_capacity", 5.225), |
| std::make_pair("avg_power", expected_power_w), |
| std::make_pair("electric_charge_delta", 1), |
| std::make_pair("sample_age", 2)}); |
| |
| battery_data.voltage_mv = 11200; // 11.2V. |
| battery_data.update_time_seconds_since_epoch = 47; |
| set_battery_data(battery_data); |
| set_seconds_since_epoch(48); |
| now += kOneMinute; |
| datums = sampler->GetSample(now); |
| // So long as there's no current consumption, there's no power estimate. |
| ExpectSampleMatchesArray( |
| datums, |
| {std::make_pair("external_connected", true), |
| std::make_pair("voltage", 11.2), std::make_pair("current_capacity", 2), |
| std::make_pair("max_capacity", 5.225), std::make_pair("sample_age", 1)}); |
| |
| battery_data.current_capacity_mah -= 1; |
| battery_data.update_time_seconds_since_epoch = 49; |
| set_battery_data(battery_data); |
| set_seconds_since_epoch(49); |
| now += kOneMinute; |
| datums = sampler->GetSample(now); |
| |
| expected_power_w = (11.1 + 11.2) / 2.0 * // Average voltage (V). |
| (1.0 * 3600.0 / 1000.0) / // Current consumption (As). |
| 120.0; // 2 minutes (s). |
| // The above makes roughly 330mW. |
| EXPECT_DOUBLE_EQ(expected_power_w, 0.3345); |
| ExpectSampleMatchesArray(datums, |
| {std::make_pair("external_connected", true), |
| std::make_pair("voltage", 11.2), |
| std::make_pair("current_capacity", 1.999), |
| std::make_pair("max_capacity", 5.225), |
| std::make_pair("avg_power", expected_power_w), |
| std::make_pair("electric_charge_delta", 1), |
| std::make_pair("sample_age", 0)}); |
| } |
| |
| } // namespace power_sampler |