| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "base/test/ios/wait_util.h" |
| #import "base/time/time.h" |
| #import "components/metrics/demographics/demographic_metrics_provider.h" |
| #import "components/ukm/ukm_service.h" |
| #import "ios/chrome/browser/authentication/test/signin_earl_grey.h" |
| #import "ios/chrome/browser/authentication/test/signin_earl_grey_ui_test_util.h" |
| #import "ios/chrome/browser/metrics/model/metrics_app_interface.h" |
| #import "ios/chrome/browser/signin/model/fake_system_identity.h" |
| #import "ios/chrome/test/earl_grey/chrome_earl_grey.h" |
| #import "ios/chrome/test/earl_grey/chrome_test_case.h" |
| #import "ios/testing/earl_grey/app_launch_configuration.h" |
| #import "ios/testing/earl_grey/earl_grey_test.h" |
| #import "third_party/metrics_proto/user_demographics.pb.h" |
| |
| namespace { |
| |
| const metrics::UserDemographicsProto::Gender kTestGender = |
| metrics::UserDemographicsProto::GENDER_MALE; |
| |
| } // namespace |
| |
| @interface DemographicsTestCase : ChromeTestCase { |
| int birthYear_; |
| } |
| @end |
| |
| @implementation DemographicsTestCase |
| |
| - (void)setUp { |
| [super setUp]; |
| chrome_test_util::GREYAssertErrorNil( |
| [MetricsAppInterface setupHistogramTester]); |
| [MetricsAppInterface overrideMetricsAndCrashReportingForTesting]; |
| |
| // Set a network time so that SyncPrefs::GetUserNoisedBirthYearAndGender does |
| // not return a UserDemographicsResult for the kCannotGetTime status. |
| const base::Time now = base::Time::Now(); |
| [MetricsAppInterface updateNetworkTime:now]; |
| |
| // Get the maximum eligible birth year for reporting demographics. |
| birthYear_ = [MetricsAppInterface maximumEligibleBirthYearForTime:now]; |
| [self addUserDemographicsToSyncServerWithBirthYear:birthYear_ |
| gender:kTestGender]; |
| [self signInAndEnableHistorySync]; |
| [self grantMetricsConsent]; |
| } |
| |
| - (void)tearDownHelper { |
| [ChromeEarlGrey clearFakeSyncServerData]; |
| [MetricsAppInterface stopOverridingMetricsAndCrashReportingForTesting]; |
| chrome_test_util::GREYAssertErrorNil( |
| [MetricsAppInterface releaseHistogramTester]); |
| [super tearDownHelper]; |
| } |
| |
| - (AppLaunchConfiguration)appConfigurationForTestCase { |
| AppLaunchConfiguration config; |
| |
| // Features are enabled or disabled based on the name of the test that is |
| // running. This is done because (A) parameterized tests do not exist in Earl |
| // Grey and (B) it is inefficient to use ensureAppLaunchedWithConfiguration |
| // for each test. |
| // |
| // Note that in the if statements, @selector(testSomething) is used rather |
| // than @"testSomething" because the former checks that the testSomething |
| // method exists somewhere--but not necessarily in this class. |
| if ([self isRunningTest:@selector |
| (testUKMDemographicsReportingWithFeatureEnabled)]) { |
| config.features_enabled.push_back(metrics::kDemographicMetricsReporting); |
| config.features_enabled.push_back( |
| ukm::kReportUserNoisedUserBirthYearAndGender); |
| } else if ([self isRunningTest:@selector |
| (testUKMDemographicsReportingWithFeatureDisabled)]) { |
| config.features_disabled.push_back(metrics::kDemographicMetricsReporting); |
| config.features_disabled.push_back( |
| ukm::kReportUserNoisedUserBirthYearAndGender); |
| } else if ([self isRunningTest:@selector |
| (testUMADemographicsReportingWithFeatureEnabled)]) { |
| config.features_enabled.push_back(metrics::kDemographicMetricsReporting); |
| } else if ([self isRunningTest:@selector |
| (testUMADemographicsReportingWithFeatureDisabled)]) { |
| config.features_disabled.push_back(metrics::kDemographicMetricsReporting); |
| } |
| // Note: Can't use the actual feature definition, because its build target |
| // depends on a bunch of stuff that mustn't make it into the EG test target. |
| config.additional_args.push_back( |
| "--enable-features=ManualLogUploadsInTheFRE"); |
| return config; |
| } |
| |
| #pragma mark - Helpers |
| |
| // Adds user demographics, which are DataType::PRIORITY_PREFERENCES, to the |
| // fake sync server. `rawBirthYear` is the true birth year, pre-noise, and the |
| // gender corresponds to the options in UserDemographicsProto::Gender. |
| // |
| // Also, verifies (A) that before adding the demographics, the server has no |
| // priority preferences and (B) that after adding the demographics, the server |
| // has one priority preference. |
| - (void) |
| addUserDemographicsToSyncServerWithBirthYear:(int)rawBirthYear |
| gender: |
| (metrics::UserDemographicsProto:: |
| Gender)gender { |
| GREYAssertEqual( |
| [ChromeEarlGrey |
| numberOfSyncEntitiesWithType:syncer::PRIORITY_PREFERENCES], |
| 0, @"The fake sync server should have no priority preferences."); |
| |
| [ChromeEarlGrey addUserDemographicsToSyncServerWithBirthYear:rawBirthYear |
| gender:gender]; |
| |
| GREYAssertEqual( |
| [ChromeEarlGrey |
| numberOfSyncEntitiesWithType:syncer::PRIORITY_PREFERENCES], |
| 1, @"The fake sync server should have one priority preference."); |
| } |
| |
| // Signs into Chrome with a fake identity, enables history sync, and then waits |
| // up to kSyncUKMOperationsTimeout for sync to initialize. |
| - (void)signInAndEnableHistorySync { |
| // Note that there is only one profile on iOS. Additionally, URL-keyed |
| // anonymized data collection is turned on as part of the flow to Sign in to |
| // Chrome and enable history sync. This matches the main user flow that |
| // enables UKM. |
| [SigninEarlGreyUI signinWithFakeIdentity:[FakeSystemIdentity fakeIdentity1] |
| enableHistorySync:YES]; |
| [ChromeEarlGrey waitForSyncTransportStateActiveWithTimeout: |
| syncher::kSyncUKMOperationsTimeout]; |
| } |
| |
| // Adds a dummy UKM source to the UKM service's recordings. The presence of this |
| // dummy source allows UKM reports to be built and logged. |
| - (void)addDummyUKMSource { |
| const uint64_t sourceId = 0x54321; |
| [MetricsAppInterface UKMRecordDummySource:sourceId]; |
| GREYAssert([MetricsAppInterface UKMHasDummySource:sourceId], |
| @"Failed to record dummy source."); |
| } |
| |
| // Turns on metrics collection for testing and verifies that this has been |
| // successfully done. |
| - (void)grantMetricsConsent { |
| GREYAssertFalse( |
| [MetricsAppInterface setMetricsAndCrashReportingForTesting:YES], |
| @"User consent has already been granted."); |
| GREYAssert([MetricsAppInterface checkUKMRecordingEnabled:YES], |
| @"Failed to assert that UKM recording is enabled."); |
| |
| // The client ID is non-zero after metrics uploading permissions are updated. |
| GREYAssertNotEqual(0U, [MetricsAppInterface UKMClientID], |
| @"Client ID should be non-zero."); |
| } |
| |
| // Adds dummy data, stores it in the UKM service's UnsentLogStore, and verifies |
| // that the UnsentLogStore has an unsent log. |
| - (void)buildAndStoreUKMLog { |
| // Record a source in the UKM service so that there is data with which to |
| // generate a UKM Report. |
| [self addDummyUKMSource]; |
| [MetricsAppInterface buildAndStoreUKMLog]; |
| GREYAssertTrue([MetricsAppInterface hasUnsentUKMLogs], |
| @"The UKM service should have unsent logs."); |
| } |
| |
| #pragma mark - Tests |
| |
| // The tests in this file should correspond to the demographics-related tests in |
| // //chrome/browser/metrics/ukm_browsertest.cc and |
| // //chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc. |
| |
| // Tests that user demographics are synced, recorded by UKM, and logged in |
| // histograms. |
| // LINT.IfChange(AddSyncedUserBirthYearAndGenderToProtoDataEnabled_ukmBrowsertest) |
| - (void)testUKMDemographicsReportingWithFeatureEnabled { |
| // See `appConfigurationForTestCase` for feature set-up. The kUkmFeature is |
| // enabled by default. |
| GREYAssertTrue([ChromeEarlGrey isDemographicMetricsReportingEnabled] && |
| [MetricsAppInterface |
| isReportUserNoisedUserBirthYearAndGenderEnabled] && |
| [ChromeEarlGrey isUKMEnabled], |
| @"Failed to enable the requisite features."); |
| |
| [self buildAndStoreUKMLog]; |
| |
| GREYAssertTrue([MetricsAppInterface UKMReportHasBirthYear:birthYear_ |
| gender:kTestGender], |
| @"The report should contain the specified user demographics"); |
| } |
| // LINT.ThenChange(/chrome/browser/metrics/ukm_browsertest.cc:AddSyncedUserBirthYearAndGenderToProtoData) |
| |
| // Tests that user demographics are neither recorded by UKM nor logged in |
| // histograms when the user is signed-in and history sync is on. |
| // LINT.IfChange(AddSyncedUserBirthYearAndGenderToProtoDataDisabled_ukmBrowsertest) |
| - (void)testUKMDemographicsReportingWithFeatureDisabled { |
| // See `appConfigurationForTestCase` for feature set-up. The kUkmFeature is |
| // enabled by default. |
| GREYAssertFalse([ChromeEarlGrey isDemographicMetricsReportingEnabled], |
| @"Failed to disable kDemographicMetricsReporting."); |
| GREYAssertFalse( |
| [MetricsAppInterface isReportUserNoisedUserBirthYearAndGenderEnabled], |
| @"Failed to disable kReportUserNoisedUserBirthYearAndGender."); |
| GREYAssertTrue([ChromeEarlGrey isUKMEnabled], |
| @"Failed to enable kUkmFeature."); |
| |
| [self buildAndStoreUKMLog]; |
| |
| GREYAssertFalse([MetricsAppInterface UKMReportHasUserDemographics], |
| @"The report should not contain user demographics."); |
| } |
| // LINT.ThenChange(/chrome/browser/metrics/ukm_browsertest.cc:AddSyncedUserBirthYearAndGenderToProtoData) |
| |
| // Tests that user demographics are synced, recorded by UMA, and logged in |
| // histograms. |
| // |
| // LINT.IfChange(AddSyncedUserBirthYearAndGenderToProtoDataEnabled_msudBrowsertest) |
| - (void)testUMADemographicsReportingWithFeatureEnabled { |
| // See `appConfigurationForTestCase` for feature set-up. The kUkmFeature is |
| // enabled by default. |
| GREYAssertTrue([ChromeEarlGrey isDemographicMetricsReportingEnabled], |
| @"Failed to enable kDemographicMetricsReporting."); |
| |
| const int success = |
| static_cast<int>(metrics::UserDemographicsStatus::kSuccess); |
| ConditionBlock condition = ^{ |
| NSError* error = [MetricsAppInterface |
| expectUniqueSampleWithCount:1 |
| forBucket:success |
| forHistogram:@"UMA.UserDemographics.Status"]; |
| return error == nil; |
| }; |
| |
| GREYAssert(base::test::ios::WaitUntilConditionOrTimeout( |
| base::test::ios::kWaitForActionTimeout, condition), |
| @"iOS First Run failed to upload metric"); |
| |
| [MetricsAppInterface buildAndStoreUMALog]; |
| GREYAssertTrue([MetricsAppInterface hasUnsentUMALogs], |
| @"The UKM service should have unsent logs."); |
| |
| GREYAssertTrue([MetricsAppInterface UMALogHasBirthYear:birthYear_ |
| gender:kTestGender], |
| @"The report should contain the specified user demographics"); |
| |
| // Expect 2 counts because in the iOS First Run, the MetricsService is started |
| // quicker, which causes two metrics log uploads to happen by this point. |
| NSError* error = [MetricsAppInterface |
| expectUniqueSampleWithCount:2 |
| forBucket:success |
| forHistogram:@"UMA.UserDemographics.Status"]; |
| chrome_test_util::GREYAssertErrorNil(error); |
| } |
| // LINT.ThenChange(/chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc:AddSyncedUserBirthYearAndGenderToProtoData) |
| |
| // Tests that user demographics are neither recorded by UMA nor logged in |
| // histograms when the user is signed-in and history sync is on. |
| // |
| // LINT.IfChange(AddSyncedUserBirthYearAndGenderToProtoDataDisabled_msudBrowsertest) |
| - (void)testUMADemographicsReportingWithFeatureDisabled { |
| // See `appConfigurationForTestCase` for feature set-up. |
| GREYAssertFalse([ChromeEarlGrey isDemographicMetricsReportingEnabled], |
| @"Failed to disable kDemographicMetricsReporting."); |
| |
| [MetricsAppInterface buildAndStoreUMALog]; |
| GREYAssertTrue([MetricsAppInterface hasUnsentUMALogs], |
| @"The UKM service should have unsent logs."); |
| |
| GREYAssertNil([MetricsAppInterface expectSum:0 |
| forHistogram:@"UMA.UserDemographics.Status"], |
| @"Unexpected histogram contents."); |
| } |
| // LINT.ThenChange(/chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc:AddSyncedUserBirthYearAndGenderToProtoData) |
| |
| @end |