chromium/ios/chrome/browser/metrics/model/demographics_egtest.mm

// 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/time/time.h"
#import "components/metrics/demographics/demographic_metrics_provider.h"
#import "components/ukm/ukm_service.h"
#import "ios/chrome/browser/metrics/model/metrics_app_interface.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/ui/authentication/signin_earl_grey.h"
#import "ios/chrome/browser/ui/authentication/signin_earl_grey_ui_test_util.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];
  GREYAssertNil([MetricsAppInterface setupHistogramTester],
                @"Failed to set up histogram tester.");
  [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)tearDown {
  [ChromeEarlGrey clearFakeSyncServerData];
  [MetricsAppInterface stopOverridingMetricsAndCrashReportingForTesting];
  GREYAssertNil([MetricsAppInterface releaseHistogramTester],
                @"Failed to release histogram tester.");
  [super tearDown];
}

- (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);
  }
  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
      waitForSyncEngineInitialized:YES
                       syncTimeout: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.
//
// Corresponds to AddSyncedUserBirthYearAndGenderToProtoData in
// //chrome/browser/metrics/ukm_browsertest.cc with features enabled.
- (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");
}

// Tests that user demographics are neither recorded by UKM nor logged in
// histograms when the user is signed-in and history sync is on.
//
// Corresponds to AddSyncedUserBirthYearAndGenderToProtoData in
// //chrome/browser/metrics/ukm_browsertest.cc with features disabled.
- (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.");
}

// Tests that user demographics are synced, recorded by UMA, and logged in
// histograms.
//
// Corresponds to AddSyncedUserBirthYearAndGenderToProtoData in
// //chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc
// with features enabled.
- (void)testUMADemographicsReportingWithFeatureEnabled {
  // See `appConfigurationForTestCase` for feature set-up. The kUkmFeature is
  // enabled by default.
  GREYAssertTrue([ChromeEarlGrey isDemographicMetricsReportingEnabled],
                 @"Failed to enable kDemographicMetricsReporting.");

  [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");

  const int success =
      static_cast<int>(metrics::UserDemographicsStatus::kSuccess);
  GREYAssertNil([MetricsAppInterface
                    expectUniqueSampleWithCount:1
                                      forBucket:success
                                   forHistogram:@"UMA.UserDemographics.Status"],
                @"Unexpected histogram contents");
}

// Tests that user demographics are neither recorded by UMA nor logged in
// histograms when the user is signed-in and history sync is on.
//
// Corresponds to AddSyncedUserBirthYearAndGenderToProtoData in
// //chrome/browser/metrics/metrics_service_user_demographics_browsertest.cc
// with features disabled.
- (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.");
}

@end