Index: ios/chrome/browser/metrics/tab_usage_recorder_unittest.mm |
diff --git a/ios/chrome/browser/metrics/tab_usage_recorder_unittest.mm b/ios/chrome/browser/metrics/tab_usage_recorder_unittest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ccd9cd22ef5268bb9835885960e0f468c8e49f41 |
--- /dev/null |
+++ b/ios/chrome/browser/metrics/tab_usage_recorder_unittest.mm |
@@ -0,0 +1,366 @@ |
+// Copyright 2013 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 <memory> |
+ |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/metrics/histogram_samples.h" |
+#include "base/stl_util.h" |
+#include "base/test/histogram_tester.h" |
+#import "ios/chrome/browser/metrics/previous_session_info.h" |
+#include "ios/chrome/browser/metrics/tab_usage_recorder.h" |
+#import "ios/chrome/browser/metrics/tab_usage_recorder_delegate.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#include "ios/testing/ocmock_complex_type_helper.h" |
+#include "ios/web/public/test/test_web_thread.h" |
+#import "ios/web/web_state/ui/crw_web_controller.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "testing/platform_test.h" |
+#include "third_party/ocmock/ocmock_extensions.h" |
+ |
+@interface TURTestTabMock : OCMockComplexTypeHelper { |
+ GURL _url; |
+} |
+ |
+@property(nonatomic, assign) const GURL& url; |
+@end |
+ |
+@implementation TURTestTabMock |
+- (const GURL&)url { |
+ return _url; |
+} |
+- (void)setUrl:(const GURL&)url { |
+ _url = url; |
+} |
+@end |
+ |
+// A mock TabUsageRecorderDelegate which allows the unit tests to control |
+// the count of live tabs returned from the |liveTabsCount| delegate method. |
+@interface MockTabUsageRecorderDelegate : NSObject<TabUsageRecorderDelegate> { |
+ NSUInteger _tabCount; |
+} |
+ |
+// Sets the live tab count returned from the |liveTabsCount| delegate method. |
+- (void)setLiveTabsCount:(NSUInteger)count; |
+ |
+@end |
+ |
+@implementation MockTabUsageRecorderDelegate |
+ |
+- (void)setLiveTabsCount:(NSUInteger)count { |
+ _tabCount = count; |
+} |
+ |
+- (NSUInteger)liveTabsCount { |
+ return _tabCount; |
+} |
+ |
+@end |
+ |
+namespace { |
+ |
+// The number of alive tabs at a renderer termination used by unit test. |
+const NSUInteger kAliveTabsCountAtRendererTermination = 2U; |
+ |
+// The number of timestamps added to the renderer termination timestamp list |
+// that are not counted in the RecentlyAliveTabs metric. |
+const int kExpiredTimesAddedCount = 2; |
+ |
+class TabUsageRecorderForTesting : public TabUsageRecorder { |
+ public: |
+ TabUsageRecorderForTesting(MockTabUsageRecorderDelegate* delegate) |
+ : TabUsageRecorder(delegate) {} |
+ // For testing only. |
+ base::TimeTicks RestoreStartTime() const { return restore_start_time_; } |
+ |
+ // Adds |time| to the deque keeping track of renderer termination |
+ // timestamps. |
+ void AddTimeToDeque(base::TimeTicks time) { |
+ termination_timestamps_.push_back(time); |
+ } |
+}; |
+ |
+class TabUsageRecorderTest : public PlatformTest { |
+ protected: |
+ void SetUp() override { |
+ loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_DEFAULT)); |
+ ui_thread_.reset(new web::TestWebThread(web::WebThread::UI, loop_.get())); |
+ histogram_tester_.reset(new base::HistogramTester()); |
+ // Set the delegate to nil to allow the relevant unit tests direct access to |
+ // the mock delegate. |
+ tab_usage_recorder_.reset(new TabUsageRecorderForTesting(nil)); |
+ webUrl_ = GURL("http://www.chromium.org"); |
+ nativeUrl_ = GURL("chrome://version"); |
+ } |
+ |
+ id MockTab(bool inMemory) { |
+ id tab_mock = [[TURTestTabMock alloc] |
+ initWithRepresentedObject:[OCMockObject mockForClass:[Tab class]]]; |
+ id web_controller_mock = |
+ [OCMockObject mockForClass:[CRWWebController class]]; |
+ [[[tab_mock stub] andReturn:web_controller_mock] webController]; |
+ [[[tab_mock stub] andReturnBool:false] isPrerenderTab]; |
+ [tab_mock setUrl:webUrl_]; |
+ [[[web_controller_mock stub] andReturnBool:inMemory] isViewAlive]; |
+ [[web_controller_mock stub] removeObserver:OCMOCK_ANY]; |
+ return [tab_mock autorelease]; |
+ } |
+ |
+ GURL webUrl_; |
+ GURL nativeUrl_; |
+ std::unique_ptr<base::MessageLoop> loop_; |
+ std::unique_ptr<web::TestWebThread> ui_thread_; |
+ std::unique_ptr<base::HistogramTester> histogram_tester_; |
+ std::unique_ptr<TabUsageRecorderForTesting> tab_usage_recorder_; |
+}; |
+ |
+TEST_F(TabUsageRecorderTest, SwitchBetweenInMemoryTabs) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(true); |
+ |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ histogram_tester_->ExpectUniqueSample(kSelectedTabHistogramName, |
+ TabUsageRecorder::IN_MEMORY, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, SwitchToEvictedTab) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ histogram_tester_->ExpectUniqueSample(kSelectedTabHistogramName, |
+ TabUsageRecorder::EVICTED, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, SwitchFromEvictedTab) { |
+ id tab_mock_a = MockTab(false); |
+ id tab_mock_b = MockTab(true); |
+ |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ histogram_tester_->ExpectUniqueSample(kSelectedTabHistogramName, |
+ TabUsageRecorder::IN_MEMORY, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, SwitchBetweenEvictedTabs) { |
+ id tab_mock_a = MockTab(false); |
+ id tab_mock_b = MockTab(false); |
+ |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ histogram_tester_->ExpectUniqueSample(kSelectedTabHistogramName, |
+ TabUsageRecorder::EVICTED, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, CountPageLoadsBeforeEvictedTab) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ |
+ // Call reload an arbitrary number of times. |
+ const int kNumReloads = 4; |
+ for (int i = 0; i < kNumReloads; i++) { |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_a); |
+ } |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ histogram_tester_->ExpectUniqueSample(kPageLoadsBeforeEvictedTabSelected, |
+ kNumReloads, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, CountNativePageLoadsBeforeEvictedTab) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ [tab_mock_a setUrl:nativeUrl_]; |
+ [tab_mock_b setUrl:nativeUrl_]; |
+ |
+ // Call reload an arbitrary number of times. |
+ const int kNumReloads = 4; |
+ for (int i = 0; i < kNumReloads; i++) { |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_a); |
+ } |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ histogram_tester_->ExpectTotalCount(kPageLoadsBeforeEvictedTabSelected, 0); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestColdStartTabs) { |
+ id tab_mock_a = MockTab(false); |
+ id tab_mock_b = MockTab(false); |
+ id tab_mock_c = MockTab(false); |
+ // Set A and B as cold-start evicted tabs. Leave C just evicted. |
+ NSMutableArray* cold_start_tabs = [NSMutableArray array]; |
+ [cold_start_tabs addObject:tab_mock_a]; |
+ [cold_start_tabs addObject:tab_mock_b]; |
+ tab_usage_recorder_->InitialRestoredTabs(tab_mock_a, cold_start_tabs); |
+ |
+ // Switch from A (cold start evicted) to B (cold start evicted). |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ // Switch from B (cold start evicted) to C (evicted). |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_b, tab_mock_c); |
+ histogram_tester_->ExpectTotalCount(kSelectedTabHistogramName, 2); |
+ histogram_tester_->ExpectBucketCount( |
+ kSelectedTabHistogramName, TabUsageRecorder::EVICTED_DUE_TO_COLD_START, |
+ 1); |
+ histogram_tester_->ExpectBucketCount(kSelectedTabHistogramName, |
+ TabUsageRecorder::EVICTED, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestSwitchedModeTabs) { |
+ id tab_mock_a = MockTab(false); |
+ id tab_mock_b = MockTab(false); |
+ id tab_mock_c = MockTab(false); |
+ NSMutableArray* switch_to_incognito_tabs = [NSMutableArray array]; |
+ [switch_to_incognito_tabs addObject:tab_mock_a]; |
+ [switch_to_incognito_tabs addObject:tab_mock_b]; |
+ tab_usage_recorder_->RecordPrimaryTabModelChange(false, nil); |
+ |
+ // Switch from A (incognito evicted) to B (incognito evicted). |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ // Switch from B (incognito evicted) to C (evicted). |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_b, tab_mock_c); |
+ histogram_tester_->ExpectTotalCount(kSelectedTabHistogramName, 2); |
+ histogram_tester_->ExpectBucketCount( |
+ kSelectedTabHistogramName, TabUsageRecorder::EVICTED_DUE_TO_INCOGNITO, 0); |
+ histogram_tester_->ExpectBucketCount(kSelectedTabHistogramName, |
+ TabUsageRecorder::EVICTED, 2); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestEvictedTabReloadTime) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadDone(tab_mock_b, true); |
+ histogram_tester_->ExpectTotalCount(kEvictedTabReloadTime, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestEvictedTabReloadSuccess) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadDone(tab_mock_b, true); |
+ histogram_tester_->ExpectUniqueSample(kEvictedTabReloadSuccessRate, |
+ TabUsageRecorder::LOAD_SUCCESS, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestEvictedTabReloadFailure) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadDone(tab_mock_b, false); |
+ histogram_tester_->ExpectUniqueSample(kEvictedTabReloadSuccessRate, |
+ TabUsageRecorder::LOAD_FAILURE, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestUserWaitedForEvictedTabLoad) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadDone(tab_mock_b, true); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_b, tab_mock_a); |
+ histogram_tester_->ExpectUniqueSample(kDidUserWaitForEvictedTabReload, |
+ TabUsageRecorder::USER_WAITED, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestUserDidNotWaitForEvictedTabLoad) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_b); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_b, tab_mock_a); |
+ histogram_tester_->ExpectUniqueSample(kDidUserWaitForEvictedTabReload, |
+ TabUsageRecorder::USER_DID_NOT_WAIT, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestUserBackgroundedDuringEvictedTabLoad) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_b); |
+ tab_usage_recorder_->AppDidEnterBackground(); |
+ histogram_tester_->ExpectUniqueSample(kDidUserWaitForEvictedTabReload, |
+ TabUsageRecorder::USER_LEFT_CHROME, 1); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestTimeBetweenRestores) { |
+ id tab_mock_a = MockTab(false); |
+ id tab_mock_b = MockTab(false); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ // Should record the time since launch until this page load begins. |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_b); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_b, tab_mock_a); |
+ // Should record the time since previous restore until this restore. |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_a); |
+ histogram_tester_->ExpectTotalCount(kTimeBetweenRestores, 2); |
+} |
+ |
+TEST_F(TabUsageRecorderTest, TestTimeAfterLastRestore) { |
+ id tab_mock_a = MockTab(false); |
+ id tab_mock_b = MockTab(false); |
+ // Should record time since launch until background. |
+ tab_usage_recorder_->AppDidEnterBackground(); |
+ tab_usage_recorder_->AppWillEnterForeground(); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ // Should record nothing. |
+ tab_usage_recorder_->RecordPageLoadStart(tab_mock_b); |
+ histogram_tester_->ExpectTotalCount(kTimeAfterLastRestore, 1); |
+} |
+ |
+// Verifies that metrics are recorded correctly when a renderer terminates. |
+TEST_F(TabUsageRecorderTest, RendererTerminated) { |
+ Tab* terminated_tab = MockTab(false); |
+ |
+ // Set up the delegate to return |kAliveTabsCountAtRenderTermination|. |
+ base::scoped_nsobject<MockTabUsageRecorderDelegate> delegate( |
+ [[MockTabUsageRecorderDelegate alloc] init]); |
+ [delegate setLiveTabsCount:kAliveTabsCountAtRendererTermination]; |
+ tab_usage_recorder_->SetDelegate(delegate); |
+ |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ |
+ // Add |kExpiredTimesAddedCount| expired timestamps and one recent timestamp |
+ // to the termination timestamp list. |
+ for (int seconds = kExpiredTimesAddedCount; seconds > 0; seconds--) { |
+ int expired_time_delta = kSecondsBeforeRendererTermination + seconds; |
+ tab_usage_recorder_->AddTimeToDeque( |
+ now - base::TimeDelta::FromSeconds(expired_time_delta)); |
+ } |
+ base::TimeTicks recent_time = |
+ now - base::TimeDelta::FromSeconds(kSecondsBeforeRendererTermination / 2); |
+ tab_usage_recorder_->AddTimeToDeque(recent_time); |
+ |
+ tab_usage_recorder_->RendererTerminated(terminated_tab, false); |
+ |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ BOOL saw_memory_warning = |
+ [defaults boolForKey:previous_session_info_constants:: |
+ kDidSeeMemoryWarningShortlyBeforeTerminating]; |
+ histogram_tester_->ExpectUniqueSample(kRendererTerminationSawMemoryWarning, |
+ saw_memory_warning, 1); |
+ histogram_tester_->ExpectUniqueSample(kRendererTerminationAliveRenderers, |
+ kAliveTabsCountAtRendererTermination, |
+ 1); |
+ // Tests that the logged count of recently alive renderers is equal to the |
+ // live count at termination plus the recent termination and the |
+ // renderer terminated just now. |
+ histogram_tester_->ExpectUniqueSample( |
+ kRendererTerminationRecentlyAliveRenderers, |
+ kAliveTabsCountAtRendererTermination + 2, 1); |
+} |
+ |
+// Verifies that metrics are recorded correctly when a renderer terminated tab |
+// is switched to and reloaded. |
+TEST_F(TabUsageRecorderTest, SwitchToRendererTerminatedTab) { |
+ id tab_mock_a = MockTab(true); |
+ id tab_mock_b = MockTab(false); |
+ |
+ tab_usage_recorder_->RendererTerminated(tab_mock_b, false); |
+ tab_usage_recorder_->RecordTabSwitched(tab_mock_a, tab_mock_b); |
+ |
+ histogram_tester_->ExpectUniqueSample( |
+ kSelectedTabHistogramName, |
+ TabUsageRecorder::EVICTED_DUE_TO_RENDERER_TERMINATION, 1); |
+} |
+ |
+} // namespace |