| // Copyright 2018 The Chromium 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 "chrome/browser/chromeos/power/auto_screen_brightness/modeller_impl.h" |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/important_file_writer.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_mock_time_task_runner.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "chrome/browser/chromeos/power/auto_screen_brightness/fake_als_reader.h" |
| #include "chrome/browser/chromeos/power/auto_screen_brightness/fake_brightness_monitor.h" |
| #include "chrome/browser/chromeos/power/auto_screen_brightness/fake_model_config_loader.h" |
| #include "chrome/browser/chromeos/power/auto_screen_brightness/monotone_cubic_spline.h" |
| #include "chrome/browser/chromeos/power/auto_screen_brightness/trainer.h" |
| #include "chrome/browser/chromeos/power/auto_screen_brightness/utils.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/user_activity/user_activity_detector.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| |
| namespace chromeos { |
| namespace power { |
| namespace auto_screen_brightness { |
| |
| namespace { |
| |
| MonotoneCubicSpline CreateTestCurveFromTrainingData( |
| const std::vector<TrainingDataPoint>& data) { |
| CHECK_GT(data.size(), 1u); |
| |
| std::vector<double> xs; |
| std::vector<double> ys; |
| |
| const auto& data_point = data[0]; |
| xs.push_back(data_point.ambient_log_lux); |
| ys.push_back((data_point.brightness_old + data_point.brightness_new) / 2); |
| |
| for (size_t i = 1; i < data.size(); ++i) { |
| xs.push_back(xs[i - 1] + 1); |
| ys.push_back(ys[i - 1] + 1); |
| } |
| |
| return *MonotoneCubicSpline::CreateMonotoneCubicSpline(xs, ys); |
| } |
| |
| void CheckOptionalCurves( |
| const base::Optional<MonotoneCubicSpline>& result_curve, |
| const base::Optional<MonotoneCubicSpline>& expected_curve) { |
| EXPECT_EQ(result_curve.has_value(), expected_curve.has_value()); |
| if (result_curve) { |
| EXPECT_EQ(*result_curve, *expected_curve); |
| } |
| } |
| |
| // Fake testing trainer that has configuration status and validity of personal |
| // curve specified in the constructor. |
| class FakeTrainer : public Trainer { |
| public: |
| FakeTrainer(bool is_configured, bool is_personal_curve_valid) |
| : is_configured_(is_configured), |
| is_personal_curve_valid_(is_personal_curve_valid) { |
| // If personal curve is valid, then the trainer must be configured. |
| DCHECK(!is_personal_curve_valid_ || is_configured_); |
| } |
| ~FakeTrainer() override = default; |
| |
| // Trainer overrides: |
| bool HasValidConfiguration() const override { return is_configured_; } |
| |
| bool SetInitialCurves(const MonotoneCubicSpline& global_curve, |
| const MonotoneCubicSpline& current_curve) override { |
| DCHECK(is_configured_); |
| global_curve_ = global_curve; |
| current_curve_ = is_personal_curve_valid_ ? current_curve : global_curve; |
| return is_personal_curve_valid_; |
| } |
| |
| MonotoneCubicSpline GetGlobalCurve() const override { |
| DCHECK(is_configured_); |
| DCHECK(global_curve_); |
| return *global_curve_; |
| } |
| |
| MonotoneCubicSpline GetCurrentCurve() const override { |
| DCHECK(is_configured_); |
| DCHECK(current_curve_); |
| return *current_curve_; |
| } |
| |
| MonotoneCubicSpline Train( |
| const std::vector<TrainingDataPoint>& data) override { |
| DCHECK(is_configured_); |
| DCHECK(current_curve_); |
| std::vector<TrainingDataPoint> used_data = data; |
| |
| // We need at least 2 points to create a MonotoneCubicSpline. Hence we |
| // insert another one if |data| has only 1 point. |
| if (data.size() == 1) { |
| used_data.push_back(data[0]); |
| } |
| current_curve_ = CreateTestCurveFromTrainingData(used_data); |
| return *current_curve_; |
| } |
| |
| private: |
| bool is_configured_; |
| bool is_personal_curve_valid_; |
| base::Optional<MonotoneCubicSpline> global_curve_; |
| base::Optional<MonotoneCubicSpline> current_curve_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakeTrainer); |
| }; |
| |
| class TestObserver : public Modeller::Observer { |
| public: |
| TestObserver() {} |
| ~TestObserver() override = default; |
| |
| // Modeller::Observer overrides: |
| void OnModelTrained(const MonotoneCubicSpline& brightness_curve) override { |
| model_.personal_curve = brightness_curve; |
| ++model_.iteration_count; |
| } |
| |
| void OnModelInitialized(const Model& model) override { |
| model_initialized_ = true; |
| model_ = model; |
| } |
| |
| base::Optional<MonotoneCubicSpline> personal_curve() const { |
| return model_.personal_curve; |
| } |
| |
| int iteration_count() const { return model_.iteration_count; } |
| |
| void CheckStatus(bool is_model_initialized, const Model& expected_model) { |
| EXPECT_EQ(is_model_initialized, model_initialized_); |
| CheckOptionalCurves(expected_model.global_curve, model_.global_curve); |
| CheckOptionalCurves(expected_model.personal_curve, model_.personal_curve); |
| EXPECT_EQ(expected_model.iteration_count, model_.iteration_count); |
| } |
| |
| private: |
| bool model_initialized_ = false; |
| Model model_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestObserver); |
| }; |
| |
| } // namespace |
| |
| class ModellerImplTest : public testing::Test { |
| public: |
| ModellerImplTest() |
| : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) { |
| CHECK(temp_dir_.CreateUniqueTempDir()); |
| TestingProfile::Builder profile_builder; |
| profile_builder.SetProfileName("testuser@gmail.com"); |
| profile_builder.SetPath(temp_dir_.GetPath().AppendASCII("TestProfile")); |
| profile_ = profile_builder.Build(); |
| test_model_config_ = GetTestModelConfig(); |
| test_initial_global_curve_ = MonotoneCubicSpline::CreateMonotoneCubicSpline( |
| test_model_config_.log_lux, test_model_config_.brightness); |
| DCHECK(test_initial_global_curve_); |
| } |
| |
| ~ModellerImplTest() override { |
| base::ThreadPoolInstance::Get()->FlushForTesting(); |
| } |
| |
| // Sets up |modeller_| with a FakeTrainer. |
| void SetUpModeller(bool is_trainer_configured, bool is_personal_curve_valid) { |
| modeller_ = ModellerImpl::CreateForTesting( |
| profile_.get(), &fake_als_reader_, &fake_brightness_monitor_, |
| &fake_model_config_loader_, &user_activity_detector_, |
| std::make_unique<FakeTrainer>(is_trainer_configured, |
| is_personal_curve_valid), |
| base::SequencedTaskRunnerHandle::Get(), |
| task_environment_.GetMockTickClock()); |
| |
| test_observer_ = std::make_unique<TestObserver>(); |
| modeller_->AddObserver(test_observer_.get()); |
| } |
| |
| void Init(AlsReader::AlsInitStatus als_reader_status, |
| BrightnessMonitor::Status brightness_monitor_status, |
| base::Optional<ModelConfig> model_config, |
| bool is_trainer_configured = true, |
| bool is_personal_curve_valid = true, |
| const std::map<std::string, std::string>& params = {}) { |
| if (!params.empty()) { |
| scoped_feature_list_.InitAndEnableFeatureWithParameters( |
| features::kAutoScreenBrightness, params); |
| } |
| |
| fake_als_reader_.set_als_init_status(als_reader_status); |
| fake_brightness_monitor_.set_status(brightness_monitor_status); |
| if (model_config) { |
| fake_model_config_loader_.set_model_config(model_config.value()); |
| } |
| |
| SetUpModeller(is_trainer_configured, is_personal_curve_valid); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| protected: |
| void WriteModelToFile(const Model& model) { |
| const ModellerImpl::ModelSavingSpec& model_saving_spec = |
| ModellerImpl::ModellerImpl::GetModelSavingSpecFromProfile( |
| profile_.get()); |
| CHECK(!model_saving_spec.global_curve.empty()); |
| CHECK(!model_saving_spec.personal_curve.empty()); |
| CHECK(!model_saving_spec.iteration_count.empty()); |
| SaveModelToDisk(model_saving_spec, model, true /* save_global_curve */, |
| true /* save_personal_curve */, true /* is_testing */); |
| } |
| |
| // Returns a valid ModelConfig. |
| ModelConfig GetTestModelConfig() { |
| ModelConfig model_config; |
| model_config.auto_brightness_als_horizon_seconds = 2.0; |
| model_config.log_lux = { |
| 3.69, 4.83, 6.54, 7.68, 8.25, 8.82, |
| }; |
| model_config.brightness = { |
| 36.14, 47.62, 85.83, 93.27, 93.27, 100, |
| }; |
| |
| model_config.metrics_key = "abc"; |
| model_config.model_als_horizon_seconds = 3.0; |
| return model_config; |
| } |
| |
| content::BrowserTaskEnvironment task_environment_; |
| base::HistogramTester histogram_tester_; |
| |
| ui::UserActivityDetector user_activity_detector_; |
| |
| base::ScopedTempDir temp_dir_; |
| std::unique_ptr<TestingProfile> profile_; |
| |
| ModelConfig test_model_config_; |
| base::Optional<MonotoneCubicSpline> test_initial_global_curve_; |
| |
| FakeAlsReader fake_als_reader_; |
| FakeBrightnessMonitor fake_brightness_monitor_; |
| FakeModelConfigLoader fake_model_config_loader_; |
| |
| std::unique_ptr<ModellerImpl> modeller_; |
| std::unique_ptr<TestObserver> test_observer_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ModellerImplTest); |
| }; |
| |
| // AlsReader is |kDisabled| when Modeller is created. |
| TEST_F(ModellerImplTest, AlsReaderDisabledOnInit) { |
| Init(AlsReader::AlsInitStatus::kDisabled, BrightnessMonitor::Status::kSuccess, |
| test_model_config_); |
| |
| // Model should be empty if modeller is disabled. |
| test_observer_->CheckStatus(true /* is_model_initialized */, Model()); |
| } |
| |
| // BrightnessMonitor is |kDisabled| when Modeller is created. |
| TEST_F(ModellerImplTest, BrightnessMonitorDisabledOnInit) { |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kDisabled, |
| test_model_config_); |
| |
| // Model should be empty if modeller is disabled. |
| test_observer_->CheckStatus(true /* is_model_initialized */, Model()); |
| } |
| |
| // ModelConfigLoader has an invalid config, hence Modeller is disabled. |
| TEST_F(ModellerImplTest, ModelConfigLoaderDisabledOnInit) { |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| ModelConfig()); |
| |
| // Model should be empty if modeller is disabled. |
| test_observer_->CheckStatus(true /* is_model_initialized */, Model()); |
| } |
| |
| // AlsReader is |kDisabled| on later notification. |
| TEST_F(ModellerImplTest, AlsReaderDisabledOnNotification) { |
| Init(AlsReader::AlsInitStatus::kInProgress, |
| BrightnessMonitor::Status::kSuccess, test_model_config_); |
| |
| test_observer_->CheckStatus(false /* is_model_initialized */, Model()); |
| |
| fake_als_reader_.set_als_init_status(AlsReader::AlsInitStatus::kDisabled); |
| fake_als_reader_.ReportReaderInitialized(); |
| task_environment_.RunUntilIdle(); |
| |
| // Model should be empty if modeller is disabled. |
| test_observer_->CheckStatus(true /* is_model_initialized */, Model()); |
| } |
| |
| // AlsReader is |kSuccess| on later notification. |
| TEST_F(ModellerImplTest, AlsReaderEnabledOnNotification) { |
| Init(AlsReader::AlsInitStatus::kInProgress, |
| BrightnessMonitor::Status::kSuccess, test_model_config_); |
| |
| test_observer_->CheckStatus(false /* is_model_initialized */, Model()); |
| |
| fake_als_reader_.set_als_init_status(AlsReader::AlsInitStatus::kSuccess); |
| fake_als_reader_.ReportReaderInitialized(); |
| task_environment_.RunUntilIdle(); |
| |
| const Model expected_model(test_initial_global_curve_, base::nullopt, 0); |
| test_observer_->CheckStatus(true /* is_model_initialized */, expected_model); |
| } |
| |
| // BrightnessMonitor is |kDisabled| on later notification. |
| TEST_F(ModellerImplTest, BrightnessMonitorDisabledOnNotification) { |
| Init(AlsReader::AlsInitStatus::kSuccess, |
| BrightnessMonitor::Status::kInitializing, test_model_config_); |
| |
| test_observer_->CheckStatus(false /* is_model_initialized */, Model()); |
| |
| fake_brightness_monitor_.set_status(BrightnessMonitor::Status::kDisabled); |
| fake_brightness_monitor_.ReportBrightnessMonitorInitialized(); |
| |
| // Model should be empty if modeller is disabled. |
| test_observer_->CheckStatus(true /* is_model_initialized */, Model()); |
| } |
| |
| // BrightnessMonitor is |kSuccess| on later notification. |
| TEST_F(ModellerImplTest, BrightnessMonitorEnabledOnNotification) { |
| Init(AlsReader::AlsInitStatus::kSuccess, |
| BrightnessMonitor::Status::kInitializing, test_model_config_); |
| |
| test_observer_->CheckStatus(false /* is_model_initialized */, Model()); |
| |
| fake_brightness_monitor_.set_status(BrightnessMonitor::Status::kSuccess); |
| fake_brightness_monitor_.ReportBrightnessMonitorInitialized(); |
| task_environment_.RunUntilIdle(); |
| |
| const Model expected_model(test_initial_global_curve_, base::nullopt, 0); |
| test_observer_->CheckStatus(true /* is_model_initialized */, expected_model); |
| } |
| |
| // ModelConfigLoader reports an invalid config on later notification. |
| TEST_F(ModellerImplTest, InvalidModelConfigOnNotification) { |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| base::nullopt /* model_config */); |
| |
| test_observer_->CheckStatus(false /* is_model_initialized */, Model()); |
| |
| // ModelConfig() creates an invalid config. |
| DCHECK(!IsValidModelConfig(ModelConfig())); |
| fake_model_config_loader_.set_model_config(ModelConfig()); |
| fake_model_config_loader_.ReportModelConfigLoaded(); |
| task_environment_.RunUntilIdle(); |
| test_observer_->CheckStatus(true /* is_model_initialized */, Model()); |
| } |
| |
| // ModelConfigLoader reports a valid config on later notification. |
| TEST_F(ModellerImplTest, ValidModelConfigOnNotification) { |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| base::nullopt /* model_config */); |
| |
| test_observer_->CheckStatus(false /* is_model_initialized */, Model()); |
| |
| fake_model_config_loader_.set_model_config(test_model_config_); |
| fake_model_config_loader_.ReportModelConfigLoaded(); |
| task_environment_.RunUntilIdle(); |
| |
| const Model expected_model(test_initial_global_curve_, base::nullopt, 0); |
| test_observer_->CheckStatus(true /* is_model_initialized */, expected_model); |
| } |
| |
| // A model is loaded from disk, this is a personal curve, and the saved global |
| // curve is the same as initial global curve set from model config, hence there |
| // is no need to reset the model. |
| TEST_F(ModellerImplTest, ModelLoadedFromProfilePath) { |
| const std::vector<double> xs = {0, 10, 20, 40, 60, 80, 90, 100}; |
| const std::vector<double> ys = {0, 5, 10, 15, 20, 25, 30, 40}; |
| const base::Optional<MonotoneCubicSpline> personal_curve = |
| MonotoneCubicSpline::CreateMonotoneCubicSpline(xs, ys); |
| DCHECK(personal_curve); |
| |
| // Use |test_initial_global_curve_| as the saved global curve. |
| const Model model(test_initial_global_curve_, personal_curve, |
| 1 /* iteration_count */); |
| WriteModelToFile(model); |
| |
| task_environment_.RunUntilIdle(); |
| |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| test_model_config_); |
| task_environment_.RunUntilIdle(); |
| |
| test_observer_->CheckStatus(true /* is_model_initialized */, model); |
| histogram_tester_.ExpectUniqueSample( |
| "AutoScreenBrightness.PersonalCurveValid", true, 1); |
| } |
| |
| // A model is loaded from disk, this is a personal curve, and the saved global |
| // curve is different from the initial global curve set from model config, hence |
| // there the model is reset. |
| TEST_F(ModellerImplTest, ModelLoadedFromProfilePathWithReset) { |
| const std::vector<double> xs = {0, 10, 20, 40, 60, 80, 90, 100}; |
| const std::vector<double> ys = {0, 5, 10, 15, 20, 25, 30, 40}; |
| const base::Optional<MonotoneCubicSpline> saved_global_curve = |
| MonotoneCubicSpline::CreateMonotoneCubicSpline(xs, ys); |
| DCHECK(saved_global_curve); |
| |
| const Model model(saved_global_curve, saved_global_curve, |
| 2 /* iteration_count */); |
| WriteModelToFile(model); |
| |
| task_environment_.RunUntilIdle(); |
| |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| test_model_config_); |
| |
| const Model expected_model(test_initial_global_curve_, base::nullopt, 0); |
| test_observer_->CheckStatus(true /* is_model_initialized */, expected_model); |
| |
| histogram_tester_.ExpectUniqueSample( |
| "AutoScreenBrightness.PersonalCurveValid", true, 1); |
| } |
| |
| // A model is loaded from disk but the personal curve doesn't satisfy Trainer |
| // slope constraint, hence it's ignored and the global curve is used instead. |
| TEST_F(ModellerImplTest, PersonalCurveError) { |
| const Model model(test_initial_global_curve_, test_initial_global_curve_, 2); |
| WriteModelToFile(model); |
| task_environment_.RunUntilIdle(); |
| |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| test_model_config_, true /* is_trainer_configured */, |
| false /* is_personal_curve_valid */); |
| |
| const Model expected_model(test_initial_global_curve_, base::nullopt, 0); |
| test_observer_->CheckStatus(true /* is_model_initialized */, expected_model); |
| |
| histogram_tester_.ExpectUniqueSample( |
| "AutoScreenBrightness.PersonalCurveValid", false, 1); |
| } |
| |
| // Ambient light values are received. We check average ambient light has been |
| // calculated from the recent samples only. |
| TEST_F(ModellerImplTest, OnAmbientLightUpdated) { |
| const int horizon_in_seconds = test_model_config_.model_als_horizon_seconds; |
| |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| test_model_config_, true /* is_trainer_configured */, |
| true /* is_personal_curve_valid */); |
| |
| // No model is saved to disk, hence the initial model only has the global |
| // curve set from the config. |
| const Model expected_model(test_initial_global_curve_, base::nullopt, 0); |
| test_observer_->CheckStatus(true /* is_model_initialized */, expected_model); |
| |
| EXPECT_EQ(modeller_->GetModelConfigForTesting(), test_model_config_); |
| |
| const int first_lux = 1000; |
| double running_sum = 0.0; |
| for (int i = 0; i < horizon_in_seconds; ++i) { |
| task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1)); |
| const int lux = i == 0 ? first_lux : i; |
| fake_als_reader_.ReportAmbientLightUpdate(lux); |
| running_sum += ConvertToLog(lux); |
| EXPECT_DOUBLE_EQ( |
| modeller_->AverageAmbientForTesting(task_environment_.NowTicks()) |
| .value(), |
| running_sum / (i + 1)); |
| } |
| EXPECT_EQ(test_observer_->iteration_count(), 0); |
| |
| // Add another one should push the oldest |first_lux| out of the horizon. |
| task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1)); |
| fake_als_reader_.ReportAmbientLightUpdate(100); |
| running_sum = running_sum + ConvertToLog(100) - ConvertToLog(first_lux); |
| EXPECT_DOUBLE_EQ( |
| modeller_->AverageAmbientForTesting(task_environment_.NowTicks()).value(), |
| running_sum / horizon_in_seconds); |
| EXPECT_EQ(test_observer_->iteration_count(), 0); |
| } |
| |
| // User brightness changes are received, training example cache reaches |
| // |max_training_data_points_| to trigger early training. This all happens |
| // within a small window shorter than |training_delay_|. |
| TEST_F(ModellerImplTest, OnUserBrightnessChanged) { |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| test_model_config_, true /* is_trainer_configured */, |
| true /* is_personal_curve_valid */, |
| {{"training_delay_in_seconds", base::NumberToString(60)}}); |
| |
| const Model expected_model(test_initial_global_curve_, base::nullopt, 0); |
| test_observer_->CheckStatus(true /* is_model_initialized */, expected_model); |
| |
| std::vector<TrainingDataPoint> expected_data; |
| |
| for (size_t i = 0; i < modeller_->GetMaxTrainingDataPointsForTesting() - 1; |
| ++i) { |
| EXPECT_EQ(i, modeller_->NumberTrainingDataPointsForTesting()); |
| task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1)); |
| const base::TimeTicks now = task_environment_.NowTicks(); |
| const int lux = i * 20; |
| fake_als_reader_.ReportAmbientLightUpdate(lux); |
| const double brightness_old = 10.0 + i; |
| const double brightness_new = 20.0 + i; |
| modeller_->OnUserBrightnessChanged(brightness_old, brightness_new); |
| expected_data.push_back({brightness_old, brightness_new, |
| modeller_->AverageAmbientForTesting(now).value(), |
| now}); |
| EXPECT_EQ(test_observer_->iteration_count(), 0); |
| } |
| |
| // Training should not have started. |
| EXPECT_EQ(modeller_->GetMaxTrainingDataPointsForTesting() - 1, |
| modeller_->NumberTrainingDataPointsForTesting()); |
| |
| // Add one more data point to trigger the training early. |
| task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1)); |
| const base::TimeTicks now = task_environment_.NowTicks(); |
| const double brightness_old = 85; |
| const double brightness_new = 95; |
| modeller_->OnUserBrightnessChanged(brightness_old, brightness_new); |
| expected_data.push_back({brightness_old, brightness_new, |
| modeller_->AverageAmbientForTesting(now).value(), |
| now}); |
| task_environment_.RunUntilIdle(); |
| |
| EXPECT_EQ(0u, modeller_->NumberTrainingDataPointsForTesting()); |
| EXPECT_EQ(test_observer_->iteration_count(), 1); |
| |
| const base::Optional<MonotoneCubicSpline>& result_curve = |
| test_observer_->personal_curve(); |
| DCHECK(result_curve); |
| |
| const MonotoneCubicSpline expected_curve = |
| CreateTestCurveFromTrainingData(expected_data); |
| EXPECT_EQ(expected_curve, *result_curve); |
| } |
| |
| // User activities resets timer used to start training. |
| TEST_F(ModellerImplTest, MultipleUserActivities) { |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| test_model_config_, true /* is_trainer_configured */, |
| true /* is_personal_curve_valid */, |
| {{"training_delay_in_seconds", base::NumberToString(60)}}); |
| |
| const Model expected_model(test_initial_global_curve_, base::nullopt, 0); |
| test_observer_->CheckStatus(true /* is_model_initialized */, expected_model); |
| |
| task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1)); |
| fake_als_reader_.ReportAmbientLightUpdate(30); |
| std::vector<TrainingDataPoint> expected_data; |
| for (size_t i = 0; i < 10; ++i) { |
| EXPECT_EQ(i, modeller_->NumberTrainingDataPointsForTesting()); |
| task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1)); |
| const base::TimeTicks now = task_environment_.NowTicks(); |
| const int lux = i * 20; |
| fake_als_reader_.ReportAmbientLightUpdate(lux); |
| const double brightness_old = 10.0 + i; |
| const double brightness_new = 20.0 + i; |
| modeller_->OnUserBrightnessChanged(brightness_old, brightness_new); |
| expected_data.push_back({brightness_old, brightness_new, |
| modeller_->AverageAmbientForTesting(now).value(), |
| now}); |
| EXPECT_EQ(test_observer_->iteration_count(), 0); |
| } |
| |
| EXPECT_EQ(modeller_->NumberTrainingDataPointsForTesting(), 10u); |
| |
| task_environment_.FastForwardBy(modeller_->GetTrainingDelayForTesting() / 2); |
| // A user activity is received, timer should be reset. |
| const ui::MouseEvent mouse_event(ui::ET_MOUSE_EXITED, gfx::Point(0, 0), |
| gfx::Point(0, 0), base::TimeTicks(), 0, 0); |
| modeller_->OnUserActivity(&mouse_event); |
| |
| task_environment_.FastForwardBy(modeller_->GetTrainingDelayForTesting() / 3); |
| EXPECT_EQ(modeller_->NumberTrainingDataPointsForTesting(), 10u); |
| EXPECT_EQ(test_observer_->iteration_count(), 0); |
| |
| // Another user event is received. |
| modeller_->OnUserActivity(&mouse_event); |
| |
| // After |training_delay_|/2, no training has started. |
| task_environment_.FastForwardBy(modeller_->GetTrainingDelayForTesting() / 2); |
| EXPECT_EQ(modeller_->NumberTrainingDataPointsForTesting(), 10u); |
| EXPECT_EQ(test_observer_->iteration_count(), 0); |
| |
| // After another |training_delay_|/2, training is scheduled. |
| task_environment_.FastForwardBy(modeller_->GetTrainingDelayForTesting() / 2); |
| |
| EXPECT_EQ(0u, modeller_->NumberTrainingDataPointsForTesting()); |
| EXPECT_EQ(test_observer_->iteration_count(), 1); |
| const base::Optional<MonotoneCubicSpline>& result_curve = |
| test_observer_->personal_curve(); |
| DCHECK(result_curve); |
| |
| const MonotoneCubicSpline expected_curve = |
| CreateTestCurveFromTrainingData(expected_data); |
| EXPECT_EQ(expected_curve, *result_curve); |
| } |
| |
| // Training delay is 0, hence we train as soon as we have 1 data point. |
| TEST_F(ModellerImplTest, ZeroTrainingDelay) { |
| Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess, |
| test_model_config_, true /* is_trainer_configured */, |
| true /* is_personal_curve_valid */, |
| { |
| {"training_delay_in_seconds", "0"}, |
| }); |
| |
| const Model expected_model(test_initial_global_curve_, base::nullopt, 0); |
| test_observer_->CheckStatus(true /* is_model_initialized */, expected_model); |
| |
| fake_als_reader_.ReportAmbientLightUpdate(30); |
| const ui::MouseEvent mouse_event(ui::ET_MOUSE_EXITED, gfx::Point(0, 0), |
| gfx::Point(0, 0), base::TimeTicks(), 0, 0); |
| modeller_->OnUserActivity(&mouse_event); |
| |
| modeller_->OnUserBrightnessChanged(10, 20); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(0u, modeller_->NumberTrainingDataPointsForTesting()); |
| EXPECT_EQ(test_observer_->iteration_count(), 1); |
| } |
| |
| } // namespace auto_screen_brightness |
| } // namespace power |
| } // namespace chromeos |