blob: 226621a88e6e35db1bc360d9d586299d11696396 [file] [log] [blame]
// Copyright 2012 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.
#import "ios/chrome/browser/installation_notifier.h"
#include <stdint.h>
#import <UIKit/UIKit.h>
#include "base/ios/block_types.h"
#include "base/message_loop/message_loop.h"
#include "base/test/histogram_tester.h"
#include "ios/web/public/test/test_web_thread.h"
#include "net/base/backoff_entry.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface FakeDispatcher : NSObject<DispatcherProtocol>
- (int64_t)lastDelayInNSec;
@end
@implementation FakeDispatcher {
int _dispatchCount;
int64_t _lastDelayInNSec;
NSMutableDictionary* _blocks;
}
- (instancetype)init {
if ((self = [super init]))
_blocks = [[NSMutableDictionary alloc] init];
return self;
}
#pragma mark -
#pragma mark Testing methods
- (void)executeAfter:(int)dispatchCount block:(ProceduralBlock)block {
[_blocks setObject:[block copy]
forKey:[NSNumber numberWithInt:dispatchCount]];
}
- (int64_t)lastDelayInNSec {
return _lastDelayInNSec;
}
#pragma mark -
#pragma mark DispatcherProtocol
- (void)dispatchAfter:(int64_t)delayInNSec withBlock:(dispatch_block_t)block {
_lastDelayInNSec = delayInNSec;
void (^blockToCallForThisIteration)(void) =
[_blocks objectForKey:[NSNumber numberWithInt:_dispatchCount]];
if (blockToCallForThisIteration)
blockToCallForThisIteration();
_dispatchCount++;
block();
}
@end
@interface MockNotificationReceiver : NSObject
@end
@implementation MockNotificationReceiver {
int notificationCount_;
}
- (int)notificationCount {
return notificationCount_;
}
- (void)receivedNotification {
notificationCount_++;
}
@end
@interface MockUIApplication : NSObject
// Mocks UIApplication's canOpenURL.
@end
@implementation MockUIApplication {
BOOL canOpen_;
}
- (void)setCanOpenURL:(BOOL)canOpen {
canOpen_ = canOpen;
}
- (BOOL)canOpenURL:(NSURL*)url {
return canOpen_;
}
@end
@interface InstallationNotifier (Testing)
- (void)setDispatcher:(id<DispatcherProtocol>)dispatcher;
- (void)setSharedApplication:(id)sharedApplication;
- (void)dispatchInstallationNotifierBlock;
- (void)registerForInstallationNotifications:(id)observer
withSelector:(SEL)notificationSelector
forScheme:(NSString*)scheme
startPolling:(BOOL)poll;
- (net::BackoffEntry::Policy const*)backOffPolicy;
@end
namespace {
class InstallationNotifierTest : public PlatformTest {
public:
InstallationNotifierTest() : ui_thread_(web::WebThread::UI, &message_loop_) {}
protected:
void SetUp() override {
installationNotifier_ = [InstallationNotifier sharedInstance];
FakeDispatcher* dispatcher = [[FakeDispatcher alloc] init];
dispatcher_ = dispatcher;
notificationReceiver1_ = ([[MockNotificationReceiver alloc] init]);
notificationReceiver2_ = ([[MockNotificationReceiver alloc] init]);
sharedApplication_ = [[MockUIApplication alloc] init];
[installationNotifier_ setSharedApplication:sharedApplication_];
[installationNotifier_ setDispatcher:dispatcher_];
histogramTester_.reset(new base::HistogramTester());
}
void VerifyHistogramValidity(int expectedYes, int expectedNo) {
histogramTester_->ExpectTotalCount("NativeAppLauncher.InstallationDetected",
expectedYes + expectedNo);
histogramTester_->ExpectBucketCount(
"NativeAppLauncher.InstallationDetected", YES, expectedYes);
histogramTester_->ExpectBucketCount(
"NativeAppLauncher.InstallationDetected", NO, expectedNo);
}
void VerifyDelay(int pollingIteration) {
double delayInMSec = [dispatcher_ lastDelayInNSec] / NSEC_PER_MSEC;
double initialDelayInMSec =
[installationNotifier_ backOffPolicy]->initial_delay_ms;
double multiplyFactor =
[installationNotifier_ backOffPolicy]->multiply_factor;
double expectedDelayInMSec =
initialDelayInMSec * pow(multiplyFactor, pollingIteration);
double jitter = [installationNotifier_ backOffPolicy]->jitter_factor;
EXPECT_NEAR(delayInMSec, expectedDelayInMSec,
50 + jitter * expectedDelayInMSec);
}
base::MessageLoopForUI message_loop_;
web::TestWebThread ui_thread_;
__weak InstallationNotifier* installationNotifier_;
__weak FakeDispatcher* dispatcher_;
MockNotificationReceiver* notificationReceiver1_;
MockNotificationReceiver* notificationReceiver2_;
MockUIApplication* sharedApplication_;
std::unique_ptr<base::HistogramTester> histogramTester_;
};
TEST_F(InstallationNotifierTest, RegisterWithAppAlreadyInstalled) {
[sharedApplication_ setCanOpenURL:YES];
[installationNotifier_
registerForInstallationNotifications:notificationReceiver1_
withSelector:@selector(receivedNotification)
forScheme:@"foo-scheme"];
EXPECT_EQ(1, [notificationReceiver1_ notificationCount]);
[installationNotifier_
registerForInstallationNotifications:notificationReceiver1_
withSelector:@selector(receivedNotification)
forScheme:@"foo-scheme"];
EXPECT_EQ(2, [notificationReceiver1_ notificationCount]);
VerifyHistogramValidity(2, 0);
}
TEST_F(InstallationNotifierTest, RegisterWithAppInstalledAfterSomeTime) {
[sharedApplication_ setCanOpenURL:NO];
[dispatcher_ executeAfter:10
block:^{
[sharedApplication_ setCanOpenURL:YES];
}];
[installationNotifier_
registerForInstallationNotifications:notificationReceiver1_
withSelector:@selector(receivedNotification)
forScheme:@"foo-scheme"];
EXPECT_EQ(1, [notificationReceiver1_ notificationCount]);
VerifyHistogramValidity(1, 0);
}
TEST_F(InstallationNotifierTest, RegisterForTwoInstallations) {
[sharedApplication_ setCanOpenURL:NO];
[dispatcher_ executeAfter:10
block:^{
[sharedApplication_ setCanOpenURL:YES];
}];
[installationNotifier_
registerForInstallationNotifications:notificationReceiver1_
withSelector:@selector(receivedNotification)
forScheme:@"foo-scheme"
startPolling:NO];
[installationNotifier_
registerForInstallationNotifications:notificationReceiver2_
withSelector:@selector(receivedNotification)
forScheme:@"foo-scheme"
startPolling:NO];
[installationNotifier_
registerForInstallationNotifications:notificationReceiver2_
withSelector:@selector(receivedNotification)
forScheme:@"bar-scheme"
startPolling:NO];
[installationNotifier_ dispatchInstallationNotifierBlock];
EXPECT_EQ(1, [notificationReceiver1_ notificationCount]);
EXPECT_EQ(2, [notificationReceiver2_ notificationCount]);
VerifyHistogramValidity(2, 0);
}
TEST_F(InstallationNotifierTest, RegisterAndThenUnregister) {
[sharedApplication_ setCanOpenURL:NO];
[dispatcher_ executeAfter:10
block:^{
[installationNotifier_
unregisterForNotifications:notificationReceiver1_];
}];
[installationNotifier_
registerForInstallationNotifications:notificationReceiver1_
withSelector:@selector(receivedNotification)
forScheme:@"foo-scheme"];
EXPECT_EQ(0, [notificationReceiver1_ notificationCount]);
VerifyHistogramValidity(0, 1);
}
TEST_F(InstallationNotifierTest, TestExponentialBackoff) {
[sharedApplication_ setCanOpenURL:NO];
// Making sure that delay is multiplied by |multiplyFactor| every time.
[dispatcher_ executeAfter:0
block:^{
VerifyDelay(0);
}];
[dispatcher_ executeAfter:1
block:^{
VerifyDelay(1);
}];
[dispatcher_ executeAfter:2
block:^{
VerifyDelay(2);
}];
// Registering for the installation of another application and making sure
// that the delay is reset to the initial delay.
[dispatcher_ executeAfter:
3 block:^{
VerifyDelay(3);
[installationNotifier_
registerForInstallationNotifications:notificationReceiver1_
withSelector:@selector(receivedNotification)
forScheme:@"bar-scheme"
startPolling:NO];
}];
[dispatcher_ executeAfter:4
block:^{
VerifyDelay(0);
}];
[dispatcher_ executeAfter:5
block:^{
VerifyDelay(1);
[installationNotifier_
unregisterForNotifications:notificationReceiver1_];
}];
[installationNotifier_
registerForInstallationNotifications:notificationReceiver1_
withSelector:@selector(receivedNotification)
forScheme:@"foo-scheme"];
VerifyHistogramValidity(0, 2);
}
TEST_F(InstallationNotifierTest, TestThatEmptySchemeDoesntCrashChrome) {
[installationNotifier_
registerForInstallationNotifications:notificationReceiver1_
withSelector:@selector(receivedNotification)
forScheme:nil];
[installationNotifier_ unregisterForNotifications:notificationReceiver1_];
}
} // namespace