|  | // Copyright 2022 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "base/metrics/metrics_hashes.h" | 
|  | #include "base/metrics/statistics_recorder.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/test/bind.h" | 
|  | #include "base/test/gmock_callback_support.h" | 
|  | #include "base/test/metrics/histogram_tester.h" | 
|  | #include "base/test/scoped_feature_list.h" | 
|  | #include "base/time/time.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/browser_process.h" | 
|  | #include "chrome/browser/history/history_service_factory.h" | 
|  | #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" | 
|  | #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/segmentation_platform/segmentation_platform_service_factory.h" | 
|  | #include "chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h" | 
|  | #include "chrome/browser/segmentation_platform/ukm_database_client.h" | 
|  | #include "chrome/test/base/chrome_test_utils.h" | 
|  | #include "chrome/test/base/platform_browser_test.h" | 
|  | #include "components/optimization_guide/core/delivery/model_info.h" | 
|  | #include "components/optimization_guide/core/delivery/test_model_info_builder.h" | 
|  | #include "components/optimization_guide/proto/models.pb.h" | 
|  | #include "components/prefs/pref_change_registrar.h" | 
|  | #include "components/prefs/pref_service.h" | 
|  | #include "components/segmentation_platform/embedder/default_model/database_api_clients.h" | 
|  | #include "components/segmentation_platform/embedder/default_model/optimization_target_segmentation_dummy.h" | 
|  | #include "components/segmentation_platform/internal/constants.h" | 
|  | #include "components/segmentation_platform/internal/database/client_result_prefs.h" | 
|  | #include "components/segmentation_platform/internal/database/ukm_database.h" | 
|  | #include "components/segmentation_platform/internal/execution/mock_model_provider.h" | 
|  | #include "components/segmentation_platform/internal/metadata/metadata_writer.h" | 
|  | #include "components/segmentation_platform/internal/segmentation_platform_service_impl.h" | 
|  | #include "components/segmentation_platform/internal/stats.h" | 
|  | #include "components/segmentation_platform/internal/ukm_data_manager.h" | 
|  | #include "components/segmentation_platform/public/constants.h" | 
|  | #include "components/segmentation_platform/public/database_client.h" | 
|  | #include "components/segmentation_platform/public/features.h" | 
|  | #include "components/segmentation_platform/public/model_provider.h" | 
|  | #include "components/segmentation_platform/public/proto/aggregation.pb.h" | 
|  | #include "components/segmentation_platform/public/proto/model_metadata.pb.h" | 
|  | #include "components/segmentation_platform/public/proto/segmentation_platform.pb.h" | 
|  | #include "components/segmentation_platform/public/result.h" | 
|  | #include "components/segmentation_platform/public/segmentation_platform_service.h" | 
|  | #include "components/ukm/ukm_service.h" | 
|  | #include "content/public/test/browser_test.h" | 
|  | #include "services/metrics/public/cpp/ukm_builders.h" | 
|  |  | 
|  | namespace segmentation_platform { | 
|  |  | 
|  | using ::base::test::RunOnceCallback; | 
|  | using ::testing::_; | 
|  | using ::testing::Return; | 
|  | using ::testing::SaveArg; | 
|  |  | 
|  | constexpr SegmentId kSegmentId1 = | 
|  | SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_CHROME_LOW_USER_ENGAGEMENT; | 
|  |  | 
|  | constexpr SegmentId kSegmentId2 = | 
|  | SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_DUMMY; | 
|  |  | 
|  | constexpr SegmentId kSegmentId3 = | 
|  | SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER; | 
|  |  | 
|  | constexpr char kFeatureProcessingHistogram[] = | 
|  | "SegmentationPlatform.FeatureProcessing.Error."; | 
|  |  | 
|  | constexpr char kSqlFeatureQuery[] = "SELECT COUNT(*) from metrics"; | 
|  |  | 
|  | class SegmentationPlatformTest : public PlatformBrowserTest { | 
|  | public: | 
|  | explicit SegmentationPlatformTest(bool setup_feature_list = true) { | 
|  | if (!setup_feature_list) { | 
|  | return; | 
|  | } | 
|  | // Low Engagement Segment is used to test segmentation service without multi | 
|  | // output. Search User Segment supports  multi output path. | 
|  | feature_list_.InitWithFeaturesAndParameters( | 
|  | {base::test::FeatureRefAndParams(features::kSegmentationPlatformFeature, | 
|  | {}), | 
|  | base::test::FeatureRefAndParams( | 
|  | features::kSegmentationPlatformUkmEngine, {}), | 
|  | base::test::FeatureRefAndParams( | 
|  | features::kSegmentationPlatformLowEngagementFeature, | 
|  | {{"enable_default_model", "true"}}), | 
|  | base::test::FeatureRefAndParams( | 
|  | features::kSegmentationPlatformSearchUser, | 
|  | {{"enable_default_model", "true"}}), | 
|  | base::test::FeatureRefAndParams( | 
|  | kSegmentationPlatformOptimizationTargetSegmentationDummy, {})}, | 
|  | {}); | 
|  | } | 
|  |  | 
|  | void SetUpCommandLine(base::CommandLine* command_line) override { | 
|  | command_line->AppendSwitch("segmentation-platform-refresh-results"); | 
|  | command_line->AppendSwitch( | 
|  | "segmentation-platform-disable-model-execution-delay"); | 
|  | } | 
|  |  | 
|  | SegmentationPlatformService* GetService() { | 
|  | return segmentation_platform::SegmentationPlatformServiceFactory:: | 
|  | GetForProfile(chrome_test_utils::GetProfile(this)); | 
|  | } | 
|  |  | 
|  | bool HasClientResultPref(const std::string& segmentation_key) { | 
|  | PrefService* pref_service = chrome_test_utils::GetProfile(this)->GetPrefs(); | 
|  | std::unique_ptr<ClientResultPrefs> result_prefs_ = | 
|  | std::make_unique<ClientResultPrefs>(pref_service); | 
|  | return result_prefs_->ReadClientResultFromPrefs(segmentation_key) != | 
|  | nullptr; | 
|  | } | 
|  |  | 
|  | void OnClientResultPrefUpdated() { | 
|  | if (!wait_for_pref_callback_.is_null() && | 
|  | HasClientResultPref(kSearchUserKey)) { | 
|  | std::move(wait_for_pref_callback_).Run(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void WaitForClientResultPrefUpdate() { | 
|  | if (HasClientResultPref(kSearchUserKey)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::RunLoop wait_for_pref; | 
|  | wait_for_pref_callback_ = wait_for_pref.QuitClosure(); | 
|  | pref_registrar_.Init(chrome_test_utils::GetProfile(this)->GetPrefs()); | 
|  | pref_registrar_.Add( | 
|  | kSegmentationClientResultPrefs, | 
|  | base::BindRepeating( | 
|  | &SegmentationPlatformTest::OnClientResultPrefUpdated, | 
|  | weak_ptr_factory_.GetWeakPtr())); | 
|  | wait_for_pref.Run(); | 
|  |  | 
|  | pref_registrar_.RemoveAll(); | 
|  | } | 
|  |  | 
|  | void WaitForPlatformInit() { | 
|  | base::RunLoop wait_for_init; | 
|  | SegmentationPlatformService* service = GetService(); | 
|  | while (!service->IsPlatformInitialized()) { | 
|  | wait_for_init.RunUntilIdle(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ExpectDatabaseQuery(const std::vector<std::string>& metrics, | 
|  | const ModelProvider::Request& result) { | 
|  | DatabaseClient* client = GetService()->GetDatabaseClient(); | 
|  | ASSERT_TRUE(client); | 
|  |  | 
|  | proto::SegmentationModelMetadata metadata; | 
|  | MetadataWriter writer(&metadata); | 
|  | writer.SetDefaultSegmentationMetadataConfig(); | 
|  | for (const std::string& metric : metrics) { | 
|  | DatabaseApiClients::AddSumQuery(writer, metric, /*days=*/1); | 
|  | } | 
|  |  | 
|  | base::RunLoop wait; | 
|  | client->ProcessFeatures( | 
|  | metadata, base::Time::Now() + base::Minutes(1), | 
|  | base::BindOnce( | 
|  | [](base::OnceClosure quit, | 
|  | const ModelProvider::Request& expected_result, | 
|  | DatabaseClient::ResultStatus status, | 
|  | const ModelProvider::Request& result) { | 
|  | EXPECT_EQ(status, DatabaseClient::ResultStatus::kSuccess); | 
|  | EXPECT_EQ(expected_result, result); | 
|  | std::move(quit).Run(); | 
|  | }, | 
|  | wait.QuitClosure(), result)); | 
|  | wait.Run(); | 
|  | } | 
|  |  | 
|  | void RunProcessFeaturesAndCallback( | 
|  | const proto::SegmentationModelMetadata& metadata, | 
|  | DatabaseClient::FeaturesCallback callback) { | 
|  | DatabaseClient* client = GetService()->GetDatabaseClient(); | 
|  | ASSERT_TRUE(client); | 
|  |  | 
|  | base::RunLoop wait; | 
|  | client->ProcessFeatures(metadata, base::Time::Now() + base::Minutes(1), | 
|  | base::BindOnce( | 
|  | [](base::OnceClosure quit, | 
|  | DatabaseClient::FeaturesCallback callback, | 
|  | DatabaseClient::ResultStatus status, | 
|  | const ModelProvider::Request& result) { | 
|  | std::move(callback).Run(status, result); | 
|  | std::move(quit).Run(); | 
|  | }, | 
|  | wait.QuitClosure(), std::move(callback))); | 
|  | wait.Run(); | 
|  | } | 
|  |  | 
|  | void WaitForSegmentInfoDatabaseUpdate( | 
|  | SegmentId segment_id, | 
|  | const base::HistogramTester& histogram_tester) { | 
|  | std::string database_update_histogram = | 
|  | "SegmentationPlatform.SegmentInfoDatabase.ProtoDBUpdateResult." + | 
|  | SegmentIdToHistogramVariant(segment_id); | 
|  | // Wait for model update to be written to disk. | 
|  | WaitForHistogram(database_update_histogram, histogram_tester); | 
|  | int success_count = | 
|  | histogram_tester.GetBucketCount(database_update_histogram, 1); | 
|  | ASSERT_GE(success_count, 1); | 
|  | } | 
|  |  | 
|  | void ExpectClassificationResult(const std::string& segmentation_key, | 
|  | PredictionStatus expected_prediction_status) { | 
|  | SegmentationPlatformService* service = GetService(); | 
|  | PredictionOptions options; | 
|  | options.on_demand_execution = false; | 
|  | base::RunLoop wait_for_segment; | 
|  | service->GetClassificationResult( | 
|  | segmentation_key, options, nullptr, | 
|  | base::BindOnce(&SegmentationPlatformTest::OnGetClassificationResult, | 
|  | weak_ptr_factory_.GetWeakPtr(), | 
|  | wait_for_segment.QuitClosure(), | 
|  | expected_prediction_status)); | 
|  | wait_for_segment.Run(); | 
|  | } | 
|  |  | 
|  | void OnGetClassificationResult(base::RepeatingClosure closure, | 
|  | PredictionStatus expected_prediction_status, | 
|  | const ClassificationResult& actual) { | 
|  | EXPECT_EQ(expected_prediction_status, actual.status); | 
|  | EXPECT_TRUE(actual.ordered_labels.size() > 0); | 
|  | std::move(closure).Run(); | 
|  | } | 
|  |  | 
|  | base::HistogramTester& histogram_tester() { return histogram_tester_; } | 
|  |  | 
|  | std::unique_ptr<optimization_guide::ModelInfo> | 
|  | CreateOptimizationGuideModelInfo( | 
|  | std::optional<proto::SegmentationModelMetadata> | 
|  | segmentation_model_metadata) { | 
|  | auto model_info_builder = optimization_guide::TestModelInfoBuilder(); | 
|  | if (segmentation_model_metadata.has_value()) { | 
|  | std::string serialized_metadata; | 
|  | segmentation_model_metadata.value().SerializeToString( | 
|  | &serialized_metadata); | 
|  | optimization_guide::proto::Any any_proto; | 
|  | auto any = std::make_optional(any_proto); | 
|  | any->set_value(serialized_metadata); | 
|  | any->set_type_url( | 
|  | "type.googleapis.com/" | 
|  | "segmentation_platform.proto.SegmentationModelMetadata"); | 
|  | model_info_builder.SetModelMetadata(any); | 
|  | } | 
|  | return model_info_builder.Build(); | 
|  | } | 
|  |  | 
|  | proto::SegmentationModelMetadata GetSegmentationModelMetadataWithSignals() { | 
|  | std::array<MetadataWriter::UMAFeature, 5> uma_features = { | 
|  | MetadataWriter::UMAFeature::FromUserAction("Action.Foo", 7), | 
|  | MetadataWriter::UMAFeature::FromUserAction("Action.Bar", 7), | 
|  | MetadataWriter::UMAFeature::FromUserAction("Action.Baz", 7), | 
|  | MetadataWriter::UMAFeature::FromValueHistogram("Histogram.Foo", 7, | 
|  | proto::Aggregation::SUM), | 
|  | MetadataWriter::UMAFeature::FromValueHistogram("Histogram.Bar", 7, | 
|  | proto::Aggregation::SUM), | 
|  | }; | 
|  |  | 
|  | proto::SegmentationModelMetadata search_user_metadata; | 
|  | MetadataWriter writer = MetadataWriter(&search_user_metadata); | 
|  | writer.SetSegmentationMetadataConfig(proto::TimeUnit::DAY, 1, 7, 7, 7); | 
|  | writer.AddUmaFeatures(uma_features.data(), uma_features.size()); | 
|  |  | 
|  | return search_user_metadata; | 
|  | } | 
|  |  | 
|  | void WaitForHistogram(std::string_view histogram_name, | 
|  | const base::HistogramTester& histogram_tester) { | 
|  | // Continue if histogram was already recorded. | 
|  | if (histogram_tester.GetAllSamples(histogram_name).size() > 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Else, wait until the histogram is recorded. | 
|  | base::RunLoop run_loop; | 
|  | auto histogram_observer = std::make_unique< | 
|  | base::StatisticsRecorder::ScopedHistogramSampleObserver>( | 
|  | histogram_name, | 
|  | base::BindLambdaForTesting( | 
|  | [&](std::string_view histogram_name, uint64_t name_hash, | 
|  | base::HistogramBase::Sample32 sample) { run_loop.Quit(); })); | 
|  | run_loop.Run(); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | base::HistogramTester histogram_tester_; | 
|  | base::test::ScopedFeatureList feature_list_; | 
|  | PrefChangeRegistrar pref_registrar_; | 
|  | base::OnceClosure wait_for_pref_callback_; | 
|  | base::WeakPtrFactory<SegmentationPlatformTest> weak_ptr_factory_{this}; | 
|  | }; | 
|  |  | 
|  | // https://crbug.com/1257820 -- Tests using "PRE_" don't work on Android. | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | #define MAYBE_PRE_CachedClassificationModel \ | 
|  | DISABLED_PRE_CachedClassificationModel | 
|  | #define MAYBE_CachedClassificationModel DISABLED_CachedClassificationModel | 
|  | #else | 
|  | #define MAYBE_PRE_CachedClassificationModel PRE_CachedClassificationModel | 
|  | #define MAYBE_CachedClassificationModel CachedClassificationModel | 
|  | #endif | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformTest, | 
|  | MAYBE_PRE_CachedClassificationModel) { | 
|  | WaitForPlatformInit(); | 
|  | WaitForClientResultPrefUpdate(); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformTest, | 
|  | MAYBE_CachedClassificationModel) { | 
|  | WaitForPlatformInit(); | 
|  | // Result is available from previous session's prefs. | 
|  | ExpectClassificationResult( | 
|  | kSearchUserKey, | 
|  | /*expected_prediction_status=*/PredictionStatus::kSucceeded); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformTest, RunCachedModelsOnly) { | 
|  | WaitForPlatformInit(); | 
|  | WaitForClientResultPrefUpdate(); | 
|  |  | 
|  | // Feature processing isn't called for ondemand models. | 
|  | // Note: There is no definite way to check if on-demand models do not get | 
|  | // executed. So we wait until the a default model runs and make sure the | 
|  | // on-demand model is not executed. | 
|  | histogram_tester().ExpectUniqueSample( | 
|  | kFeatureProcessingHistogram + SegmentIdToHistogramVariant(kSegmentId3), | 
|  | stats::FeatureProcessingError::kSuccess, 1); | 
|  | histogram_tester().ExpectUniqueSample( | 
|  | kFeatureProcessingHistogram + SegmentIdToHistogramVariant(kSegmentId2), | 
|  | stats::FeatureProcessingError::kSuccess, 0); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformTest, | 
|  | ReceiveModelUpdateFromOptimizationGuide) { | 
|  | WaitForPlatformInit(); | 
|  |  | 
|  | auto user_actions_tracked_before_model = histogram_tester().GetTotalSum( | 
|  | "SegmentationPlatform.Signals.ListeningCount.UserAction"); | 
|  | auto value_histograms_tracked_before_model = histogram_tester().GetTotalSum( | 
|  | "SegmentationPlatform.Signals.ListeningCount.HistogramValue"); | 
|  |  | 
|  | base::HistogramTester histogram_tester_1; | 
|  | // Create a model metadata with 5 signals, 3 user actions and 2 histograms. | 
|  | proto::SegmentationModelMetadata search_user_metadata = | 
|  | GetSegmentationModelMetadataWithSignals(); | 
|  | OptimizationGuideKeyedServiceFactory::GetForProfile( | 
|  | chrome_test_utils::GetProfile(this)) | 
|  | ->OverrideTargetModelForTesting( | 
|  | optimization_guide::proto:: | 
|  | OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER, | 
|  | CreateOptimizationGuideModelInfo(search_user_metadata)); | 
|  |  | 
|  | // Wait for model update to be written to disk. | 
|  | WaitForSegmentInfoDatabaseUpdate( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER, histogram_tester_1); | 
|  |  | 
|  | // Get the number of signals tracked after receiving the new model. Updating | 
|  | // signals happens synchronously, so there's no need to wait for these | 
|  | // histograms. | 
|  | auto user_actions_tracked_after_model = histogram_tester_1.GetTotalSum( | 
|  | "SegmentationPlatform.Signals.ListeningCount.UserAction"); | 
|  | auto value_histograms_tracked_after_model = histogram_tester_1.GetTotalSum( | 
|  | "SegmentationPlatform.Signals.ListeningCount.HistogramValue"); | 
|  |  | 
|  | EXPECT_EQ( | 
|  | user_actions_tracked_after_model - user_actions_tracked_before_model, 3); | 
|  | EXPECT_EQ(value_histograms_tracked_after_model - | 
|  | value_histograms_tracked_before_model, | 
|  | 2); | 
|  |  | 
|  | // OptimizationGuideSegmentationModelHandler should have recorded that it | 
|  | // received a model with valid SegmentationModelMetadata. | 
|  | histogram_tester_1.ExpectUniqueSample( | 
|  | "SegmentationPlatform.ModelDelivery.HasMetadata." + | 
|  | SegmentIdToHistogramVariant( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER), | 
|  | 1, 1); | 
|  | // OptimizationGuideSegmentationModelHandler should have recorded that it | 
|  | // received a model with valid metadata. | 
|  | histogram_tester_1.ExpectUniqueSample( | 
|  | "SegmentationPlatform.ModelAvailability." + | 
|  | SegmentIdToHistogramVariant( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER), | 
|  | stats::SegmentationModelAvailability::kModelAvailable, 1); | 
|  | // ModelManagerImpl should have recorded that it received an updated model. | 
|  | histogram_tester_1.ExpectUniqueSample( | 
|  | "SegmentationPlatform.ModelDelivery.Received", | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER, 1); | 
|  | // ModelManagerImpl should have stored the SegmentInfo. | 
|  | histogram_tester_1.ExpectBucketCount( | 
|  | "SegmentationPlatform.ModelDelivery.SaveResult." + | 
|  | SegmentIdToHistogramVariant( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER), | 
|  | 1, 1); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformTest, | 
|  | ReceiveNullModelUpdateFromOptimizationGuide) { | 
|  | WaitForPlatformInit(); | 
|  |  | 
|  | base::HistogramTester histogram_tester_1; | 
|  | // Create a model metadata with 5 signals, 3 user actions and 2 histograms. | 
|  | proto::SegmentationModelMetadata search_user_metadata = | 
|  | GetSegmentationModelMetadataWithSignals(); | 
|  | // Send a model update event from Optimization Guide to segmentation platform. | 
|  | OptimizationGuideKeyedServiceFactory::GetForProfile( | 
|  | chrome_test_utils::GetProfile(this)) | 
|  | ->OverrideTargetModelForTesting( | 
|  | optimization_guide::proto:: | 
|  | OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER, | 
|  | CreateOptimizationGuideModelInfo(search_user_metadata)); | 
|  | // Count how many user actions and histograms are tracked with this new model, | 
|  | // updating signals happens synchronously, so there's no need to wait for | 
|  | // these histograms. | 
|  | auto user_actions_tracked_before_model_deletion = | 
|  | histogram_tester_1.GetTotalSum( | 
|  | "SegmentationPlatform.Signals.ListeningCount.UserAction"); | 
|  | auto value_histograms_tracked_before_model_deletion = | 
|  | histogram_tester_1.GetTotalSum( | 
|  | "SegmentationPlatform.Signals.ListeningCount.HistogramValue"); | 
|  |  | 
|  | // Wait for model update to be written to disk. | 
|  | WaitForSegmentInfoDatabaseUpdate( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER, histogram_tester_1); | 
|  |  | 
|  | // Create a new HistogramTester to only count histograms recorded after | 
|  | // removing the model. | 
|  | base::HistogramTester histogram_tester_2; | 
|  | // Send another model update, this time indicating the model is no longer | 
|  | // being served. | 
|  | OptimizationGuideKeyedServiceFactory::GetForProfile( | 
|  | chrome_test_utils::GetProfile(this)) | 
|  | ->OverrideTargetModelForTesting( | 
|  | optimization_guide::proto:: | 
|  | OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER, | 
|  | nullptr); | 
|  | // Count how many user actions and histgrams are tracked after removing this | 
|  | // model. Updating signals happens synchronously, so there's no need to wait | 
|  | // for these histograms. | 
|  | auto user_actions_tracked_after_model_deletion = | 
|  | histogram_tester_2.GetTotalSum( | 
|  | "SegmentationPlatform.Signals.ListeningCount.UserAction"); | 
|  | auto value_histograms_tracked_after_model_deletion = | 
|  | histogram_tester_2.GetTotalSum( | 
|  | "SegmentationPlatform.Signals.ListeningCount.HistogramValue"); | 
|  |  | 
|  | // Wait for model to be removed to disk. | 
|  | WaitForSegmentInfoDatabaseUpdate( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER, histogram_tester_2); | 
|  |  | 
|  | // OptimizationGuideSegmentationModelHandler should not record the HasMetadata | 
|  | // histogram, as it only applies to the SegmentationModelMetadata inside | 
|  | // ModelInfo. | 
|  | histogram_tester_2.ExpectUniqueSample( | 
|  | "SegmentationPlatform.ModelDelivery.HasMetadata." + | 
|  | SegmentIdToHistogramVariant( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER), | 
|  | 1, 0); | 
|  | // OptimizationGuideSegmentationModelHandler should have recorded that the | 
|  | // optimization target has no model available. | 
|  | histogram_tester_2.ExpectUniqueSample( | 
|  | "SegmentationPlatform.ModelAvailability." + | 
|  | SegmentIdToHistogramVariant( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER), | 
|  | stats::SegmentationModelAvailability::kNoModelAvailable, 1); | 
|  |  | 
|  | // ModelManagerImpl should have recorded that it received an updated model. | 
|  | histogram_tester_2.ExpectUniqueSample( | 
|  | "SegmentationPlatform.ModelDelivery.Received", | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER, 1); | 
|  | // ModelManagerImpl should have deleted the previous SegmentInfo. | 
|  | histogram_tester_2.ExpectUniqueSample( | 
|  | "SegmentationPlatform.ModelDelivery.DeleteResult." + | 
|  | SegmentIdToHistogramVariant( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_SEARCH_USER), | 
|  | 1, 1); | 
|  |  | 
|  | // SignalFilterProcessor should be tracking 3 fewer user actions after | 
|  | // removing this model. | 
|  | EXPECT_EQ(user_actions_tracked_before_model_deletion - | 
|  | user_actions_tracked_after_model_deletion, | 
|  | 3); | 
|  | // SignalFilterProcessor should be tracking 2 fewer value histograms after | 
|  | // removing this model. | 
|  | EXPECT_EQ(value_histograms_tracked_before_model_deletion - | 
|  | value_histograms_tracked_after_model_deletion, | 
|  | 2); | 
|  |  | 
|  | // DatabaseMaintenanceImpl should have started a cleanup process, wait for it | 
|  | // to complete. | 
|  | WaitForHistogram("SegmentationPlatform.Maintenance.CleanupSignalSuccessCount", | 
|  | histogram_tester_2); | 
|  | // DatabaseMaintenanceImpl should have cleaned 5 signals from the database. | 
|  | histogram_tester_2.ExpectUniqueSample( | 
|  | "SegmentationPlatform.Maintenance.CleanupSignalSuccessCount", 5, 1); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformTest, | 
|  | NullModelUpdateForUnknownModelShouldBeNoOp) { | 
|  | WaitForPlatformInit(); | 
|  |  | 
|  | // Create a new HistogramTester to only count histograms recorded after | 
|  | // removing the model. | 
|  | base::HistogramTester histogram_tester_2; | 
|  | // Send a model update for an optimization target that wasn't registered. | 
|  | OptimizationGuideKeyedServiceFactory::GetForProfile( | 
|  | chrome_test_utils::GetProfile(this)) | 
|  | ->OverrideTargetModelForTesting( | 
|  | optimization_guide::proto:: | 
|  | OPTIMIZATION_TARGET_SEGMENTATION_ADAPTIVE_TOOLBAR, | 
|  | nullptr); | 
|  |  | 
|  | histogram_tester_2.ExpectUniqueSample( | 
|  | "SegmentationPlatform.ModelDelivery.HasMetadata." + | 
|  | SegmentIdToHistogramVariant( | 
|  | proto::OPTIMIZATION_TARGET_SEGMENTATION_ADAPTIVE_TOOLBAR), | 
|  | 1, 0); | 
|  | } | 
|  |  | 
|  | class SegmentationPlatformUkmModelTest : public SegmentationPlatformTest { | 
|  | public: | 
|  | SegmentationPlatformUkmModelTest() | 
|  | : utils_(&ukm_recorder_, /*owned_db_client=*/false) {} | 
|  |  | 
|  | void CreatedBrowserMainParts(content::BrowserMainParts* parts) override { | 
|  | PlatformBrowserTest::CreatedBrowserMainParts(parts); | 
|  | utils_.PreProfileInit( | 
|  | {{kSegmentId1, utils_.GetSamplePageLoadMetadata(kSqlFeatureQuery)}}); | 
|  | MockDefaultModelProvider* provider = utils_.GetDefaultOverride(kSegmentId1); | 
|  | EXPECT_CALL(*provider, ExecuteModelWithInput(_, _)) | 
|  | .WillRepeatedly([&](const ModelProvider::Request& inputs, | 
|  | ModelProvider::ExecutionCallback callback) { | 
|  | input_feature_in_last_execution_ = inputs; | 
|  | std::move(callback).Run(ModelProvider::Response(1, 0.5)); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void PreRunTestOnMainThread() override { | 
|  | SegmentationPlatformTest::PreRunTestOnMainThread(); | 
|  | utils_.set_history_service(HistoryServiceFactory::GetForProfile( | 
|  | chrome_test_utils::GetProfile(this), | 
|  | ServiceAccessType::IMPLICIT_ACCESS)); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | ukm::TestUkmRecorder ukm_recorder_; | 
|  | UkmDataManagerTestUtils utils_; | 
|  | std::optional<ModelProvider::Request> input_feature_in_last_execution_; | 
|  | }; | 
|  |  | 
|  | // This test is disabled in CrOS because CrOS creates a signin profile that uses | 
|  | // incognito mode. This disables the segmentation platform data collection. | 
|  | // TODO(ssid): Fix this test for CrOS by waiting for signin profile to be | 
|  | // deleted at startup before adding metrics. | 
|  | // https://crbug.com/1467530 -- Flaky on Mac | 
|  | // https://crbug.com/1257820 -- Tests using "PRE_" don't work on Android. | 
|  | #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID) | 
|  | #define MAYBE_PRE_RunUkmBasedModel DISABLED_PRE_RunUkmBasedModel | 
|  | #define MAYBE_RunUkmBasedModel DISABLED_RunUkmBasedModel | 
|  | #else | 
|  | #define MAYBE_PRE_RunUkmBasedModel PRE_RunUkmBasedModel | 
|  | #define MAYBE_RunUkmBasedModel RunUkmBasedModel | 
|  | #endif | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformUkmModelTest, | 
|  | MAYBE_PRE_RunUkmBasedModel) { | 
|  | const GURL kUrl1("https://www.url1.com"); | 
|  |  | 
|  | WaitForPlatformInit(); | 
|  |  | 
|  | utils_.WaitForUkmObserverRegistration(); | 
|  |  | 
|  | // Wait for the default model to run and save results to prefs. | 
|  | WaitForClientResultPrefUpdate(); | 
|  |  | 
|  | // Record page load UKM that should be recorded in the database, persisted | 
|  | // across sessions. | 
|  | utils_.RecordPageLoadUkm(kUrl1, base::Time::Now()); | 
|  | while (!utils_.IsUrlInDatabase(kUrl1)) { | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  | UkmDatabaseClientHolder::GetClientInstance( | 
|  | chrome_test_utils::GetProfile(this)) | 
|  | .GetUkmDataManager() | 
|  | ->GetUkmDatabase() | 
|  | ->CommitTransactionForTesting(); | 
|  | // There are no UKM metrics written to the database, count = 0. | 
|  | EXPECT_EQ(ModelProvider::Request({0}), input_feature_in_last_execution_); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformUkmModelTest, | 
|  | MAYBE_RunUkmBasedModel) { | 
|  | const GURL kUrl1("https://www.url1.com"); | 
|  |  | 
|  | WaitForPlatformInit(); | 
|  |  | 
|  | // Verify that the URL recorded in last session is still in database. | 
|  | EXPECT_TRUE(utils_.IsUrlInDatabase(kUrl1)); | 
|  |  | 
|  | // Result is available from previous session's selection. | 
|  | ExpectClassificationResult( | 
|  | kChromeLowUserEngagementSegmentationKey, | 
|  | /*expected_prediction_status=*/PredictionStatus::kSucceeded); | 
|  |  | 
|  | utils_.WaitForUkmObserverRegistration(); | 
|  | WaitForClientResultPrefUpdate(); | 
|  |  | 
|  | // There are 2 UKM metrics written to the database, count = 2. | 
|  | EXPECT_EQ(ModelProvider::Request({2}), input_feature_in_last_execution_); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformUkmModelTest, DatabaseApi) { | 
|  | WaitForPlatformInit(); | 
|  |  | 
|  | ExpectDatabaseQuery({}, {}); | 
|  | ExpectDatabaseQuery({"test1"}, {0}); | 
|  |  | 
|  | DatabaseClient::StructuredEvent e1("TestEvent", {{"test1", 1}, {"test2", 2}}); | 
|  |  | 
|  | DatabaseClient::StructuredEvent e2("TestEvent", | 
|  | {{"test1", 10}, {"test2", 20}}); | 
|  |  | 
|  | SegmentationPlatformService* service = GetService(); | 
|  | DatabaseClient* client = service->GetDatabaseClient(); | 
|  | client->AddEvent(e1); | 
|  | client->AddEvent(e2); | 
|  | ExpectDatabaseQuery({}, {}); | 
|  | ExpectDatabaseQuery({"test1"}, {11}); | 
|  | ExpectDatabaseQuery({"test1", "test2"}, {11, 22}); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformUkmModelTest, SumGroupDatabaseApi) { | 
|  | WaitForPlatformInit(); | 
|  |  | 
|  | constexpr char kSampleEventName[] = "TestEvent"; | 
|  | constexpr char kSampleTestMetric1[] = "test1"; | 
|  | constexpr char kSampleTestMetric2[] = "test2"; | 
|  | SegmentationPlatformService* service = GetService(); | 
|  | DatabaseClient* client = service->GetDatabaseClient(); | 
|  | client->AddEvent( | 
|  | {kSampleEventName, {{kSampleTestMetric1, 1}, {kSampleTestMetric2, 2}}}); | 
|  | client->AddEvent( | 
|  | {kSampleEventName, {{kSampleTestMetric1, 10}, {kSampleTestMetric2, 20}}}); | 
|  |  | 
|  | constexpr char kSampleTestMetric0[] = "test0"; | 
|  | proto::SegmentationModelMetadata metadata; | 
|  | MetadataWriter writer(&metadata); | 
|  | writer.SetDefaultSegmentationMetadataConfig(); | 
|  | DatabaseApiClients::AddSumGroupQuery( | 
|  | writer, kSampleEventName, | 
|  | {kSampleTestMetric0, kSampleTestMetric1, kSampleTestMetric2}, | 
|  | /*days=*/1); | 
|  | RunProcessFeaturesAndCallback( | 
|  | metadata, base::BindOnce([](DatabaseClient::ResultStatus status, | 
|  | const ModelProvider::Request& result) { | 
|  | EXPECT_EQ(status, DatabaseClient::ResultStatus::kSuccess); | 
|  | const std::vector<float> kExpectedResults = {0, 11, 22}; | 
|  | EXPECT_EQ(result, kExpectedResults); | 
|  | })); | 
|  | } | 
|  |  | 
|  | class SegmentationPlatformUkmDisabledTest : public SegmentationPlatformTest { | 
|  | public: | 
|  | SegmentationPlatformUkmDisabledTest() | 
|  | : SegmentationPlatformTest(/*setup_feature_list=*/false) { | 
|  | feature_list_.InitWithFeaturesAndParameters( | 
|  | {base::test::FeatureRefAndParams(features::kSegmentationPlatformFeature, | 
|  | {}), | 
|  | base::test::FeatureRefAndParams( | 
|  | kSegmentationPlatformOptimizationTargetSegmentationDummy, {})}, | 
|  | /*disabled_features=*/{ | 
|  | features::kSegmentationPlatformUkmEngine, | 
|  | features::kSegmentationPlatformUmaFromSqlDb, | 
|  | }); | 
|  | } | 
|  | }; | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SegmentationPlatformUkmDisabledTest, DatabaseApi) { | 
|  | WaitForPlatformInit(); | 
|  |  | 
|  | SegmentationPlatformService* service = GetService(); | 
|  | DatabaseClient* client = service->GetDatabaseClient(); | 
|  | EXPECT_FALSE(client); | 
|  | } | 
|  |  | 
|  | }  // namespace segmentation_platform |