1 // Copyright 2012 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.
5 #import "ios/chrome/browser/installation_notifier.h"
7 #import <UIKit/UIKit.h>
9 #include "base/ios/block_types.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/test/histogram_tester.h"
13 #include "ios/web/public/test/test_web_thread.h"
14 #include "net/base/backoff_entry.h"
15 #include "testing/platform_test.h"
17 @interface MockDispatcher : NSObject<DispatcherProtocol>
18 - (int64_t)lastDelayInNSec;
21 @implementation MockDispatcher {
23 int64_t _lastDelayInNSec;
24 base::scoped_nsobject<NSMutableDictionary> _blocks;
27 - (instancetype)init {
28 if ((self = [super init]))
29 _blocks.reset([[NSMutableDictionary alloc] init]);
34 #pragma mark Testing methods
36 - (void)executeAfter:(int)dispatchCount block:(ProceduralBlock)block {
37 [_blocks setObject:[[block copy] autorelease]
38 forKey:[NSNumber numberWithInt:dispatchCount]];
41 - (int64_t)lastDelayInNSec {
42 return _lastDelayInNSec;
46 #pragma mark DispatcherProtocol
48 - (void)dispatchAfter:(int64_t)delayInNSec withBlock:(dispatch_block_t)block {
49 _lastDelayInNSec = delayInNSec;
50 void (^blockToCallForThisIteration)(void) =
51 [_blocks objectForKey:[NSNumber numberWithInt:_dispatchCount]];
52 if (blockToCallForThisIteration)
53 blockToCallForThisIteration();
60 @interface MockNotificationReceiver : NSObject
63 @implementation MockNotificationReceiver {
64 int notificationCount_;
67 - (int)notificationCount {
68 return notificationCount_;
71 - (void)receivedNotification {
77 @interface MockUIApplication : NSObject
78 // Mocks UIApplication's canOpenURL.
81 @implementation MockUIApplication {
85 - (void)setCanOpenURL:(BOOL)canOpen {
89 - (BOOL)canOpenURL:(NSURL*)url {
95 @interface InstallationNotifier (Testing)
96 - (void)setDispatcher:(id<DispatcherProtocol>)dispatcher;
97 - (void)setSharedApplication:(id)sharedApplication;
98 - (void)dispatchInstallationNotifierBlock;
99 - (void)registerForInstallationNotifications:(id)observer
100 withSelector:(SEL)notificationSelector
101 forScheme:(NSString*)scheme
102 startPolling:(BOOL)poll;
103 - (net::BackoffEntry::Policy const*)backOffPolicy;
108 class InstallationNotifierTest : public PlatformTest {
110 InstallationNotifierTest() : ui_thread_(web::WebThread::UI, &message_loop_) {}
113 void SetUp() override {
114 installationNotifier_ = [InstallationNotifier sharedInstance];
115 dispatcher_ = [[MockDispatcher alloc] init];
116 notificationReceiver1_.reset(([[MockNotificationReceiver alloc] init]));
117 notificationReceiver2_.reset(([[MockNotificationReceiver alloc] init]));
118 sharedApplication_.reset([[MockUIApplication alloc] init]);
119 [installationNotifier_ setSharedApplication:sharedApplication_];
120 [installationNotifier_ setDispatcher:dispatcher_];
121 histogramTester_.reset(new base::HistogramTester());
124 void VerifyHistogramValidity(int expectedYes, int expectedNo) {
125 histogramTester_->ExpectTotalCount("NativeAppLauncher.InstallationDetected",
126 expectedYes + expectedNo);
127 histogramTester_->ExpectBucketCount(
128 "NativeAppLauncher.InstallationDetected", YES, expectedYes);
129 histogramTester_->ExpectBucketCount(
130 "NativeAppLauncher.InstallationDetected", NO, expectedNo);
133 void VerifyDelay(int pollingIteration) {
134 double delayInMSec = [dispatcher_ lastDelayInNSec] / NSEC_PER_MSEC;
135 double initialDelayInMSec =
136 [installationNotifier_ backOffPolicy]->initial_delay_ms;
137 double multiplyFactor =
138 [installationNotifier_ backOffPolicy]->multiply_factor;
139 double expectedDelayInMSec =
140 initialDelayInMSec * pow(multiplyFactor, pollingIteration);
141 double jitter = [installationNotifier_ backOffPolicy]->jitter_factor;
142 EXPECT_NEAR(delayInMSec, expectedDelayInMSec,
143 50 + jitter * expectedDelayInMSec);
146 base::MessageLoopForUI message_loop_;
147 web::TestWebThread ui_thread_;
148 InstallationNotifier* installationNotifier_; // Weak pointer to singleton.
149 MockDispatcher* dispatcher_; // Weak. installationNotifier_ owns it.
150 base::scoped_nsobject<MockNotificationReceiver> notificationReceiver1_;
151 base::scoped_nsobject<MockNotificationReceiver> notificationReceiver2_;
152 base::scoped_nsobject<MockUIApplication> sharedApplication_;
153 scoped_ptr<base::HistogramTester> histogramTester_;
156 TEST_F(InstallationNotifierTest, RegisterWithAppAlreadyInstalled) {
157 [sharedApplication_ setCanOpenURL:YES];
158 [installationNotifier_
159 registerForInstallationNotifications:notificationReceiver1_
160 withSelector:@selector(receivedNotification)
161 forScheme:@"foo-scheme"];
162 EXPECT_EQ(1, [notificationReceiver1_ notificationCount]);
163 [installationNotifier_
164 registerForInstallationNotifications:notificationReceiver1_
165 withSelector:@selector(receivedNotification)
166 forScheme:@"foo-scheme"];
167 EXPECT_EQ(2, [notificationReceiver1_ notificationCount]);
168 VerifyHistogramValidity(2, 0);
171 TEST_F(InstallationNotifierTest, RegisterWithAppInstalledAfterSomeTime) {
172 [sharedApplication_ setCanOpenURL:NO];
173 [dispatcher_ executeAfter:10
175 [sharedApplication_ setCanOpenURL:YES];
177 [installationNotifier_
178 registerForInstallationNotifications:notificationReceiver1_
179 withSelector:@selector(receivedNotification)
180 forScheme:@"foo-scheme"];
181 EXPECT_EQ(1, [notificationReceiver1_ notificationCount]);
182 VerifyHistogramValidity(1, 0);
185 TEST_F(InstallationNotifierTest, RegisterForTwoInstallations) {
186 [sharedApplication_ setCanOpenURL:NO];
187 [dispatcher_ executeAfter:10
189 [sharedApplication_ setCanOpenURL:YES];
191 [installationNotifier_
192 registerForInstallationNotifications:notificationReceiver1_
193 withSelector:@selector(receivedNotification)
194 forScheme:@"foo-scheme"
196 [installationNotifier_
197 registerForInstallationNotifications:notificationReceiver2_
198 withSelector:@selector(receivedNotification)
199 forScheme:@"foo-scheme"
201 [installationNotifier_
202 registerForInstallationNotifications:notificationReceiver2_
203 withSelector:@selector(receivedNotification)
204 forScheme:@"bar-scheme"
206 [installationNotifier_ dispatchInstallationNotifierBlock];
207 EXPECT_EQ(1, [notificationReceiver1_ notificationCount]);
208 EXPECT_EQ(2, [notificationReceiver2_ notificationCount]);
209 VerifyHistogramValidity(2, 0);
212 TEST_F(InstallationNotifierTest, RegisterAndThenUnregister) {
213 [sharedApplication_ setCanOpenURL:NO];
214 [dispatcher_ executeAfter:10
216 [installationNotifier_
217 unregisterForNotifications:notificationReceiver1_];
219 [installationNotifier_
220 registerForInstallationNotifications:notificationReceiver1_
221 withSelector:@selector(receivedNotification)
222 forScheme:@"foo-scheme"];
223 EXPECT_EQ(0, [notificationReceiver1_ notificationCount]);
224 VerifyHistogramValidity(0, 1);
227 TEST_F(InstallationNotifierTest, TestExponentialBackoff) {
228 [sharedApplication_ setCanOpenURL:NO];
229 // Making sure that delay is multiplied by |multiplyFactor| every time.
230 [dispatcher_ executeAfter:0
234 [dispatcher_ executeAfter:1
238 [dispatcher_ executeAfter:2
242 // Registering for the installation of another application and making sure
243 // that the delay is reset to the initial delay.
244 [dispatcher_ executeAfter:
247 [installationNotifier_
248 registerForInstallationNotifications:notificationReceiver1_
249 withSelector:@selector(receivedNotification)
250 forScheme:@"bar-scheme"
253 [dispatcher_ executeAfter:4
257 [dispatcher_ executeAfter:5
260 [installationNotifier_
261 unregisterForNotifications:notificationReceiver1_];
264 [installationNotifier_
265 registerForInstallationNotifications:notificationReceiver1_
266 withSelector:@selector(receivedNotification)
267 forScheme:@"foo-scheme"];
268 VerifyHistogramValidity(0, 2);
271 TEST_F(InstallationNotifierTest, TestThatEmptySchemeDoesntCrashChrome) {
272 [installationNotifier_
273 registerForInstallationNotifications:notificationReceiver1_
274 withSelector:@selector(receivedNotification)
276 [installationNotifier_ unregisterForNotifications:notificationReceiver1_];