| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "power_manager/powerd/system/internal_backlight.h" |
| |
| #include <string> |
| |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/format_macros.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <gtest/gtest.h> |
| #include <linux/fb.h> |
| |
| #include "power_manager/common/clock.h" |
| #include "power_manager/common/util.h" |
| |
| namespace power_manager { |
| namespace system { |
| |
| class InternalBacklightTest : public ::testing::Test { |
| public: |
| InternalBacklightTest() {} |
| ~InternalBacklightTest() override {} |
| |
| void SetUp() override { |
| CHECK(temp_dir_.CreateUniqueTempDir()); |
| test_path_ = temp_dir_.GetPath(); |
| } |
| |
| // Create files to make the given directory look like it is a sysfs backlight |
| // dir. |
| void PopulateBacklightDir(const base::FilePath& path, |
| int64_t brightness, |
| int64_t max_brightness) { |
| ASSERT_TRUE(base::CreateDirectory(path)); |
| ASSERT_TRUE(util::WriteInt64File( |
| path.Append(InternalBacklight::kBrightnessFilename), brightness)); |
| ASSERT_TRUE(util::WriteInt64File( |
| path.Append(InternalBacklight::kMaxBrightnessFilename), |
| max_brightness)); |
| } |
| |
| // Reads and returns an int64_t value from |path|. -1 is returned on error. |
| int64_t ReadFile(const base::FilePath& path) { |
| int64_t value = -1; |
| return util::ReadInt64File(path, &value) ? value : -1; |
| } |
| |
| // Returns the value from the "brightness" file in |directory|. |
| // -1 is returned on error. |
| int64_t ReadBrightness(const base::FilePath& directory) { |
| return ReadFile(directory.Append(InternalBacklight::kBrightnessFilename)); |
| } |
| |
| protected: |
| base::ScopedTempDir temp_dir_; |
| base::FilePath test_path_; |
| }; |
| |
| // A basic test of functionality. |
| TEST_F(InternalBacklightTest, BasicTest) { |
| base::FilePath this_test_path = test_path_.Append("basic_test"); |
| const int64_t kBrightness = 128; |
| const int64_t kMaxBrightness = 255; |
| |
| base::FilePath my_path = this_test_path.Append("pwm-backlight"); |
| PopulateBacklightDir(my_path, kBrightness, kMaxBrightness); |
| |
| InternalBacklight backlight; |
| ASSERT_TRUE(backlight.Init(this_test_path, "*")); |
| EXPECT_EQ(kBrightness, backlight.GetCurrentBrightnessLevel()); |
| EXPECT_EQ(kMaxBrightness, backlight.GetMaxBrightnessLevel()); |
| EXPECT_TRUE(backlight.DeviceExists()); |
| } |
| |
| // Test that we pick the device with the greatest granularity. |
| TEST_F(InternalBacklightTest, GranularityTest) { |
| const base::FilePath this_test_path = test_path_.Append("granularity_test"); |
| const base::FilePath a_path = this_test_path.Append("a"); |
| PopulateBacklightDir(a_path, 10, 127); |
| const base::FilePath b_path = this_test_path.Append("b"); |
| PopulateBacklightDir(b_path, 20, 255); |
| const base::FilePath c_path = this_test_path.Append("c"); |
| PopulateBacklightDir(c_path, 30, 63); |
| |
| InternalBacklight backlight; |
| ASSERT_TRUE(backlight.Init(this_test_path, "*")); |
| EXPECT_EQ(20, backlight.GetCurrentBrightnessLevel()); |
| EXPECT_EQ(255, backlight.GetMaxBrightnessLevel()); |
| } |
| |
| // Test that directories starting with a "." are ignored. |
| TEST_F(InternalBacklightTest, NoDotDirsTest) { |
| base::FilePath this_test_path = test_path_.Append("no_dot_dirs_test"); |
| |
| // We'll just create one dir and it will have a dot in it. Then, we can |
| // be sure that we didn't just get lucky... |
| base::FilePath my_path = this_test_path.Append(".pwm-backlight"); |
| PopulateBacklightDir(my_path, 128, 255); |
| |
| InternalBacklight backlight; |
| EXPECT_FALSE(backlight.Init(this_test_path, "*")); |
| } |
| |
| // Test that the glob is working correctly for searching for backlight dirs. |
| TEST_F(InternalBacklightTest, GlobTest) { |
| base::FilePath this_test_path = test_path_.Append("glob_test"); |
| |
| // Purposely give my::kbd_backlight a lower "max_level" than |
| // .no::kbd_backlight so that we know that dirs starting with a "." are |
| // ignored. |
| base::FilePath my_path = this_test_path.Append("my::kbd_backlight"); |
| PopulateBacklightDir(my_path, 1, 2); |
| |
| base::FilePath ignore1_path = this_test_path.Append("ignore1"); |
| PopulateBacklightDir(ignore1_path, 3, 4); |
| |
| base::FilePath ignore2_path = this_test_path.Append(".no::kbd_backlight"); |
| PopulateBacklightDir(ignore2_path, 5, 6); |
| |
| InternalBacklight backlight; |
| ASSERT_TRUE(backlight.Init(this_test_path, "*:kbd_backlight")); |
| |
| EXPECT_EQ(1, backlight.GetCurrentBrightnessLevel()); |
| EXPECT_EQ(2, backlight.GetMaxBrightnessLevel()); |
| } |
| |
| TEST_F(InternalBacklightTest, Transitions) { |
| const int kMaxBrightness = 100; |
| base::FilePath backlight_dir = test_path_.Append("transitions_test"); |
| PopulateBacklightDir(backlight_dir, 50, kMaxBrightness); |
| |
| InternalBacklight backlight; |
| const base::TimeTicks kStartTime = base::TimeTicks::FromInternalValue(10000); |
| backlight.clock()->set_current_time_for_testing(kStartTime); |
| ASSERT_TRUE(backlight.Init(test_path_, "*")); |
| |
| // An instant transition to the maximum level shouldn't use a timer. |
| backlight.SetBrightnessLevel(kMaxBrightness, base::TimeDelta()); |
| EXPECT_FALSE(backlight.transition_timer_is_running()); |
| EXPECT_EQ(kMaxBrightness, ReadBrightness(backlight_dir)); |
| EXPECT_EQ(kMaxBrightness, backlight.GetCurrentBrightnessLevel()); |
| |
| // Start a transition to the backlight's halfway point. |
| const int64_t kHalfBrightness = kMaxBrightness / 2; |
| const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(1000); |
| backlight.SetBrightnessLevel(kHalfBrightness, kDuration); |
| |
| // If the timeout fires at this point, we should still be at the maximum |
| // level. |
| EXPECT_TRUE(backlight.transition_timer_is_running()); |
| EXPECT_EQ(kStartTime.ToInternalValue(), |
| backlight.transition_timer_start_time().ToInternalValue()); |
| EXPECT_TRUE(backlight.TriggerTransitionTimeoutForTesting()); |
| EXPECT_EQ(kMaxBrightness, ReadBrightness(backlight_dir)); |
| EXPECT_EQ(kMaxBrightness, backlight.GetCurrentBrightnessLevel()); |
| |
| // Let half of the transition duration pass. |
| const base::TimeTicks kMidpointTime = kStartTime + kDuration / 2; |
| backlight.clock()->set_current_time_for_testing(kMidpointTime); |
| EXPECT_TRUE(backlight.TriggerTransitionTimeoutForTesting()); |
| const int64_t kMidpointBrightness = (kMaxBrightness + kHalfBrightness) / 2; |
| EXPECT_EQ(kMidpointBrightness, ReadBrightness(backlight_dir)); |
| EXPECT_EQ(kMidpointBrightness, backlight.GetCurrentBrightnessLevel()); |
| |
| // At the end of the transition, we should return false to cancel the timeout. |
| const base::TimeTicks kEndTime = kStartTime + kDuration; |
| backlight.clock()->set_current_time_for_testing(kEndTime); |
| EXPECT_FALSE(backlight.TriggerTransitionTimeoutForTesting()); |
| EXPECT_FALSE(backlight.transition_timer_is_running()); |
| EXPECT_EQ(kHalfBrightness, ReadBrightness(backlight_dir)); |
| EXPECT_EQ(kHalfBrightness, backlight.GetCurrentBrightnessLevel()); |
| } |
| |
| TEST_F(InternalBacklightTest, InterruptTransition) { |
| // Make the backlight start at its max level. |
| const int kMaxBrightness = 100; |
| base::FilePath backlight_dir = test_path_.Append("backlight"); |
| PopulateBacklightDir(backlight_dir, kMaxBrightness, kMaxBrightness); |
| InternalBacklight backlight; |
| backlight.clock()->set_current_time_for_testing( |
| base::TimeTicks::FromInternalValue(10000)); |
| ASSERT_TRUE(backlight.Init(test_path_, "*")); |
| |
| // Start a one-second transition to 0. |
| const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(1); |
| backlight.SetBrightnessLevel(0, kDuration); |
| |
| // Let the timer fire after half a second. The backlight should be at half |
| // brightness. |
| backlight.clock()->set_current_time_for_testing( |
| backlight.clock()->GetCurrentTime() + kDuration / 2); |
| EXPECT_TRUE(backlight.TriggerTransitionTimeoutForTesting()); |
| const int kHalfBrightness = kMaxBrightness / 2; |
| EXPECT_EQ(kHalfBrightness, ReadBrightness(backlight_dir)); |
| |
| // If a request to use half brightness arrives at this point, the timer should |
| // be stopped. |
| backlight.SetBrightnessLevel(kHalfBrightness, kDuration); |
| EXPECT_FALSE(backlight.transition_timer_is_running()); |
| EXPECT_EQ(kHalfBrightness, ReadBrightness(backlight_dir)); |
| |
| // Set the brightness to 0 immediately and start a one-second transition to |
| // the maximum level. |
| backlight.SetBrightnessLevel(0, base::TimeDelta()); |
| const base::TimeTicks kInterruptedTransitionStartTime = |
| backlight.clock()->GetCurrentTime(); |
| backlight.SetBrightnessLevel(kMaxBrightness, kDuration); |
| EXPECT_TRUE(backlight.transition_timer_is_running()); |
| EXPECT_EQ(0, ReadBrightness(backlight_dir)); |
| |
| // At the halfway point, interrupt the transition with a new request to go to |
| // 75% of the max over a second. |
| backlight.clock()->set_current_time_for_testing( |
| backlight.clock()->GetCurrentTime() + kDuration / 2); |
| EXPECT_TRUE(backlight.TriggerTransitionTimeoutForTesting()); |
| EXPECT_EQ(kHalfBrightness, ReadBrightness(backlight_dir)); |
| const int kThreeQuartersBrightness = |
| kHalfBrightness + (kMaxBrightness - kHalfBrightness) / 2; |
| backlight.SetBrightnessLevel(kThreeQuartersBrightness, kDuration); |
| EXPECT_EQ(kHalfBrightness, ReadBrightness(backlight_dir)); |
| |
| // Since the timer was already running, it shouldn't be restarted. |
| EXPECT_EQ(kInterruptedTransitionStartTime.ToInternalValue(), |
| backlight.transition_timer_start_time().ToInternalValue()); |
| EXPECT_TRUE(backlight.transition_timer_is_running()); |
| |
| // After a second, the backlight should be at 75% and the timer should stop. |
| backlight.clock()->set_current_time_for_testing( |
| backlight.clock()->GetCurrentTime() + kDuration); |
| EXPECT_FALSE(backlight.TriggerTransitionTimeoutForTesting()); |
| EXPECT_EQ(kThreeQuartersBrightness, ReadBrightness(backlight_dir)); |
| } |
| |
| TEST_F(InternalBacklightTest, BlPower) { |
| const int kMaxBrightness = 100; |
| const base::FilePath kDir = test_path_.Append("backlight"); |
| PopulateBacklightDir(kDir, kMaxBrightness, kMaxBrightness); |
| |
| const base::FilePath kPowerFile = |
| kDir.Append(InternalBacklight::kBlPowerFilename); |
| ASSERT_EQ(0, base::WriteFile(kPowerFile, "", 0)); |
| |
| // The bl_power file shouldn't be touched initially. |
| InternalBacklight backlight; |
| backlight.clock()->set_current_time_for_testing( |
| base::TimeTicks::FromInternalValue(10000)); |
| ASSERT_TRUE(backlight.Init(test_path_, "*")); |
| std::string data; |
| ASSERT_TRUE(base::ReadFileToString(kPowerFile, &data)); |
| EXPECT_EQ("", data); |
| |
| // A transition from nonzero brightness to 0 should result in |
| // FB_BLANK_POWERDOWN being written to bl_power. This should happen before the |
| // updated level is written, but there's no easy way to test this without |
| // adding yet another delegate. |
| backlight.SetBrightnessLevel(0, base::TimeDelta()); |
| EXPECT_EQ(FB_BLANK_POWERDOWN, ReadFile(kPowerFile)); |
| |
| // When the brightness goes from 0 to nonzero, FB_BLANK_UNBLANK should be |
| // written to |
| // bl_power. |
| backlight.SetBrightnessLevel(1, base::TimeDelta()); |
| EXPECT_EQ(FB_BLANK_UNBLANK, ReadFile(kPowerFile)); |
| |
| // Clear the file and check that it doesn't get rewritten when moving between |
| // two nonzero levels. |
| const std::string kUnblankValue = base::StringPrintf("%d", FB_BLANK_UNBLANK); |
| ASSERT_EQ(0, base::WriteFile(kPowerFile, "", 0)); |
| backlight.SetBrightnessLevel(kMaxBrightness, base::TimeDelta()); |
| ASSERT_TRUE(base::ReadFileToString(kPowerFile, &data)); |
| EXPECT_EQ("", data); |
| ASSERT_EQ( |
| base::WriteFile(kPowerFile, kUnblankValue.c_str(), kUnblankValue.size()), |
| 1); |
| |
| // Now do an animated transition. bl_power should remain at FB_BLANK_UNBLANK |
| // until the backlight level reaches 0. |
| const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(1); |
| backlight.SetBrightnessLevel(0, kDuration); |
| EXPECT_EQ(FB_BLANK_UNBLANK, ReadFile(kPowerFile)); |
| |
| backlight.clock()->set_current_time_for_testing( |
| backlight.clock()->GetCurrentTime() + kDuration / 2); |
| ASSERT_TRUE(backlight.TriggerTransitionTimeoutForTesting()); |
| ASSERT_NE(0, ReadBrightness(kDir)); |
| EXPECT_EQ(FB_BLANK_UNBLANK, ReadFile(kPowerFile)); |
| |
| backlight.clock()->set_current_time_for_testing( |
| backlight.clock()->GetCurrentTime() + kDuration / 2); |
| ASSERT_FALSE(backlight.TriggerTransitionTimeoutForTesting()); |
| ASSERT_EQ(0, ReadBrightness(kDir)); |
| EXPECT_EQ(FB_BLANK_POWERDOWN, ReadFile(kPowerFile)); |
| |
| // Animate back to the max brightness and check that bl_power goes to 1 in |
| // conjunction with the first nonzero level. |
| backlight.SetBrightnessLevel(kMaxBrightness, kDuration); |
| EXPECT_EQ(FB_BLANK_POWERDOWN, ReadFile(kPowerFile)); |
| |
| backlight.clock()->set_current_time_for_testing( |
| backlight.clock()->GetCurrentTime() + kDuration / 2); |
| ASSERT_TRUE(backlight.TriggerTransitionTimeoutForTesting()); |
| ASSERT_NE(0, ReadBrightness(kDir)); |
| EXPECT_EQ(FB_BLANK_UNBLANK, ReadFile(kPowerFile)); |
| } |
| |
| } // namespace system |
| } // namespace power_manager |