app_metrics_app_state_agent_unittest.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #import "ios/chrome/app/app_metrics_app_state_agent.h"
  5. #import "base/test/task_environment.h"
  6. #import "ios/chrome/app/application_delegate/app_state.h"
  7. #import "ios/chrome/app/application_delegate/metrics_mediator.h"
  8. #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
  9. #import "ios/chrome/browser/metrics/ios_profile_session_durations_service.h"
  10. #import "ios/chrome/browser/metrics/ios_profile_session_durations_service_factory.h"
  11. #import "ios/chrome/browser/ui/main/scene_state.h"
  12. #include "testing/platform_test.h"
  13. #import "third_party/ocmock/OCMock/OCMock.h"
  14. #include "third_party/ocmock/gtest_support.h"
  15. #if !defined(__has_feature) || !__has_feature(objc_arc)
  16. #error "This file requires ARC support."
  17. #endif
  18. namespace {
  19. class FakeProfileSessionDurationsService
  20. : public IOSProfileSessionDurationsService {
  21. public:
  22. FakeProfileSessionDurationsService()
  23. : IOSProfileSessionDurationsService(nullptr, nullptr) {}
  24. FakeProfileSessionDurationsService(
  25. const FakeProfileSessionDurationsService&) = delete;
  26. FakeProfileSessionDurationsService& operator=(
  27. const FakeProfileSessionDurationsService&) = delete;
  28. ~FakeProfileSessionDurationsService() override = default;
  29. static std::unique_ptr<KeyedService> Create(
  30. web::BrowserState* browser_state) {
  31. return std::make_unique<FakeProfileSessionDurationsService>();
  32. }
  33. void OnSessionStarted(base::TimeTicks session_start) override {
  34. ++session_started_count_;
  35. }
  36. void OnSessionEnded(base::TimeDelta session_length) override {
  37. ++session_ended_count_;
  38. }
  39. bool IsSessionActive() override {
  40. return session_started_count_ > session_ended_count_;
  41. }
  42. // IOSProfileSessionDurationsService:
  43. int session_started_count() const { return session_started_count_; }
  44. int session_ended_count() const { return session_ended_count_; }
  45. private:
  46. int session_started_count_ = 0;
  47. int session_ended_count_ = 0;
  48. };
  49. } // namespace
  50. // A fake that allows overriding connectedScenes.
  51. @interface FakeAppState : AppState
  52. @property(nonatomic, strong) NSArray<SceneState*>* connectedScenes;
  53. // Init stage that will be returned by the initStage getter when testing.
  54. @property(nonatomic, assign) InitStage initStageForTesting;
  55. @end
  56. @implementation FakeAppState
  57. - (InitStage)initStage {
  58. return self.initStageForTesting;
  59. }
  60. @end
  61. InitStage GetMinimalInitStageThatAllowsLogging() {
  62. return static_cast<InitStage>(InitStageSafeMode + 1);
  63. }
  64. InitStage GetMaximalInitStageThatDontAllowLogging() {
  65. return static_cast<InitStage>(InitStageSafeMode);
  66. }
  67. class AppMetricsAppStateAgentTest : public PlatformTest {
  68. protected:
  69. AppMetricsAppStateAgentTest() {
  70. agent_ = [[AppMetricsAppStateAgent alloc] init];
  71. TestChromeBrowserState::Builder test_cbs_builder;
  72. test_cbs_builder.AddTestingFactory(
  73. IOSProfileSessionDurationsServiceFactory::GetInstance(),
  74. base::BindRepeating(&FakeProfileSessionDurationsService::Create));
  75. browser_state_ = test_cbs_builder.Build();
  76. app_state_ = [[FakeAppState alloc] initWithBrowserLauncher:nil
  77. startupInformation:nil
  78. applicationDelegate:nil];
  79. }
  80. void SetUp() override {
  81. PlatformTest::SetUp();
  82. app_state_.mainBrowserState = browser_state_.get();
  83. app_state_.initStageForTesting = GetMinimalInitStageThatAllowsLogging();
  84. [agent_ setAppState:app_state_];
  85. }
  86. FakeProfileSessionDurationsService* getProfileSessionDurationsService() {
  87. return static_cast<FakeProfileSessionDurationsService*>(
  88. IOSProfileSessionDurationsServiceFactory::GetForBrowserState(
  89. browser_state_.get()));
  90. }
  91. void SimulateTransitionToCurrentStage() {
  92. InitStage previousStage =
  93. app_state_.initStage == InitStageStart
  94. ? InitStageStart
  95. : static_cast<InitStage>(app_state_.initStage - 1);
  96. [agent_ appState:app_state_ didTransitionFromInitStage:previousStage];
  97. }
  98. AppMetricsAppStateAgent* agent_;
  99. std::unique_ptr<TestChromeBrowserState> browser_state_;
  100. FakeAppState* app_state_;
  101. base::test::TaskEnvironment task_environment_;
  102. };
  103. TEST_F(AppMetricsAppStateAgentTest, CountSessionDuration) {
  104. SceneState* scene = [[SceneState alloc] initWithAppState:app_state_];
  105. app_state_.connectedScenes = @[ scene ];
  106. [agent_ appState:app_state_ sceneConnected:scene];
  107. EXPECT_EQ(0, getProfileSessionDurationsService()->session_started_count());
  108. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  109. // Going to background at app start doesn't log anything.
  110. scene.activationLevel = SceneActivationLevelBackground;
  111. EXPECT_EQ(0, getProfileSessionDurationsService()->session_started_count());
  112. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  113. SimulateTransitionToCurrentStage();
  114. // Going foreground starts the session.
  115. scene.activationLevel = SceneActivationLevelForegroundInactive;
  116. EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count());
  117. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  118. // Going to background stops the session.
  119. scene.activationLevel = SceneActivationLevelBackground;
  120. EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count());
  121. EXPECT_EQ(1, getProfileSessionDurationsService()->session_ended_count());
  122. }
  123. TEST_F(AppMetricsAppStateAgentTest, CountSessionDurationMultiwindow) {
  124. SceneState* sceneA = [[SceneState alloc] initWithAppState:app_state_];
  125. SceneState* sceneB = [[SceneState alloc] initWithAppState:app_state_];
  126. app_state_.connectedScenes = @[ sceneA, sceneB ];
  127. [agent_ appState:app_state_ sceneConnected:sceneA];
  128. [agent_ appState:app_state_ sceneConnected:sceneB];
  129. EXPECT_EQ(0, getProfileSessionDurationsService()->session_started_count());
  130. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  131. SimulateTransitionToCurrentStage();
  132. // One scene is enough to start a session.
  133. sceneA.activationLevel = SceneActivationLevelForegroundInactive;
  134. EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count());
  135. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  136. // Two scenes at the same time, still the session goes on.
  137. sceneB.activationLevel = SceneActivationLevelForegroundInactive;
  138. EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count());
  139. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  140. // Only scene B in foreground, session still going.
  141. sceneA.activationLevel = SceneActivationLevelBackground;
  142. EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count());
  143. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  144. // No sessions in foreground, session is over.
  145. sceneB.activationLevel = SceneActivationLevelBackground;
  146. EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count());
  147. EXPECT_EQ(1, getProfileSessionDurationsService()->session_ended_count());
  148. }
  149. TEST_F(AppMetricsAppStateAgentTest, CountSessionDurationSafeMode) {
  150. SceneState* scene = [[SceneState alloc] initWithAppState:app_state_];
  151. app_state_.connectedScenes = @[ scene ];
  152. app_state_.initStageForTesting = InitStageSafeMode;
  153. [agent_ appState:app_state_ sceneConnected:scene];
  154. EXPECT_EQ(0, getProfileSessionDurationsService()->session_started_count());
  155. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  156. SimulateTransitionToCurrentStage();
  157. // Going to background at app start doesn't log anything.
  158. scene.activationLevel = SceneActivationLevelBackground;
  159. EXPECT_EQ(0, getProfileSessionDurationsService()->session_started_count());
  160. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  161. // Going foreground doesn't start the session while in safe mode.
  162. scene.activationLevel = SceneActivationLevelForegroundInactive;
  163. EXPECT_EQ(0, getProfileSessionDurationsService()->session_started_count());
  164. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  165. // Session starts when safe mode completes.
  166. app_state_.initStageForTesting = GetMinimalInitStageThatAllowsLogging();
  167. SimulateTransitionToCurrentStage();
  168. EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count());
  169. EXPECT_EQ(0, getProfileSessionDurationsService()->session_ended_count());
  170. // Going to background stops the session.
  171. scene.activationLevel = SceneActivationLevelBackground;
  172. EXPECT_EQ(1, getProfileSessionDurationsService()->session_started_count());
  173. EXPECT_EQ(1, getProfileSessionDurationsService()->session_ended_count());
  174. }
  175. // Tests that -logStartupDuration: is called and only once for a regular
  176. // startup (no safe mode).
  177. TEST_F(AppMetricsAppStateAgentTest, logStartupDuration) {
  178. id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
  179. [[metricsMediator expect] logStartupDuration:nil connectionInformation:nil];
  180. SceneState* sceneA = [[SceneState alloc] initWithAppState:app_state_];
  181. SceneState* sceneB = [[SceneState alloc] initWithAppState:app_state_];
  182. app_state_.connectedScenes = @[ sceneA, sceneB ];
  183. [agent_ appState:app_state_ sceneConnected:sceneA];
  184. [agent_ appState:app_state_ sceneConnected:sceneB];
  185. // Simulate transitioning to the current app init stage before scenes going on
  186. // the foreground.
  187. [agent_ appState:app_state_
  188. didTransitionFromInitStage:static_cast<InitStage>(app_state_.initStage -
  189. 1)];
  190. // Should not log startup duration until the scene is active.
  191. sceneA.activationLevel = SceneActivationLevelForegroundInactive;
  192. sceneB.activationLevel = SceneActivationLevelForegroundInactive;
  193. // Should only log startup once when the first scene becomes active.
  194. sceneA.activationLevel = SceneActivationLevelForegroundActive;
  195. sceneB.activationLevel = SceneActivationLevelForegroundActive;
  196. // Should not log startup when scene becomes active again.
  197. sceneA.activationLevel = SceneActivationLevelBackground;
  198. sceneB.activationLevel = SceneActivationLevelBackground;
  199. sceneA.activationLevel = SceneActivationLevelForegroundActive;
  200. sceneB.activationLevel = SceneActivationLevelForegroundActive;
  201. EXPECT_OCMOCK_VERIFY(metricsMediator);
  202. }
  203. // Tests that -logStartupDuration: is not called when there is safe mode
  204. // during startup.
  205. TEST_F(AppMetricsAppStateAgentTest, logStartupDurationWhenSafeMode) {
  206. id metricsMediator = [OCMockObject mockForClass:[MetricsMediator class]];
  207. [[metricsMediator reject] logStartupDuration:nil connectionInformation:nil];
  208. app_state_.initStageForTesting = GetMaximalInitStageThatDontAllowLogging();
  209. SceneState* sceneA = [[SceneState alloc] initWithAppState:app_state_];
  210. app_state_.connectedScenes = @[ sceneA ];
  211. [agent_ appState:app_state_ sceneConnected:sceneA];
  212. // Simulate transitioning to the current app init stage before scenes going on
  213. // the foreground.
  214. [agent_ appState:app_state_
  215. didTransitionFromInitStage:static_cast<InitStage>(app_state_.initStage -
  216. 1)];
  217. // This would normally log startup information, but not when in safe mode.
  218. sceneA.activationLevel = SceneActivationLevelForegroundActive;
  219. EXPECT_OCMOCK_VERIFY(metricsMediator);
  220. }