// Copyright 2014 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/ios/crb_protocol_observers.h"
#include "base/notreached.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"
@protocol TestObserver
@required
- (void)requiredMethod;
- (void)reset;
@optional
- (void)optionalMethod;
- (void)mutateByAddingObserver:(id<TestObserver>)observer;
- (void)mutateByRemovingObserver:(id<TestObserver>)observer;
- (void)nestedMutateByAddingObserver:(id<TestObserver>)observer;
- (void)nestedMutateByRemovingObserver:(id<TestObserver>)observer;
@end
// Implements only the required methods in the TestObserver protocol.
@interface TestPartialObserver : NSObject<TestObserver>
@property(nonatomic, readonly) BOOL requiredMethodInvoked;
@end
// Implements all the methods in the TestObserver protocol.
@interface TestCompleteObserver : TestPartialObserver<TestObserver>
@property(nonatomic, readonly) BOOL optionalMethodInvoked;
@end
@interface TestMutateObserver : TestCompleteObserver
- (instancetype)initWithObserver:(CRBProtocolObservers*)observer
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@end
namespace {
class CRBProtocolObserversTest : public PlatformTest {
public:
CRBProtocolObserversTest() {}
protected:
void SetUp() override {
PlatformTest::SetUp();
observers_ = (CRBProtocolObservers<TestObserver>*)[CRBProtocolObservers
observersWithProtocol:@protocol(TestObserver)];
partial_observer_ = [[TestPartialObserver alloc] init];
EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
complete_observer_ = [[TestCompleteObserver alloc] init];
EXPECT_FALSE([complete_observer_ requiredMethodInvoked]);
EXPECT_FALSE([complete_observer_ optionalMethodInvoked]);
mutate_observer_ = [[TestMutateObserver alloc] initWithObserver:observers_];
EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]);
}
CRBProtocolObservers<TestObserver>* observers_;
TestPartialObserver* partial_observer_;
TestCompleteObserver* complete_observer_;
TestMutateObserver* mutate_observer_;
};
// Verifies basic functionality of -[CRBProtocolObservers addObserver:] and
// -[CRBProtocolObservers removeObserver:].
TEST_F(CRBProtocolObserversTest, AddRemoveObserver) {
// Add an observer and verify that the CRBProtocolObservers instance forwards
// an invocation to it.
[observers_ addObserver:partial_observer_];
[observers_ requiredMethod];
EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
[partial_observer_ reset];
EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
// Remove the observer and verify that the CRBProtocolObservers instance no
// longer forwards an invocation to it.
[observers_ removeObserver:partial_observer_];
[observers_ requiredMethod];
EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
}
// Verifies that CRBProtocolObservers correctly forwards the invocation of a
// required method in the protocol.
TEST_F(CRBProtocolObserversTest, RequiredMethods) {
[observers_ addObserver:partial_observer_];
[observers_ addObserver:complete_observer_];
[observers_ requiredMethod];
EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
EXPECT_TRUE([complete_observer_ requiredMethodInvoked]);
}
// Verifies that CRBProtocolObservers correctly forwards the invocation of an
// optional method in the protocol.
TEST_F(CRBProtocolObserversTest, OptionalMethods) {
[observers_ addObserver:partial_observer_];
[observers_ addObserver:complete_observer_];
[observers_ optionalMethod];
EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
EXPECT_FALSE([complete_observer_ requiredMethodInvoked]);
EXPECT_TRUE([complete_observer_ optionalMethodInvoked]);
}
// Verifies that CRBProtocolObservers only holds a weak reference to an
// observer.
TEST_F(CRBProtocolObserversTest, WeakReference) {
__weak TestPartialObserver* weak_observer = partial_observer_;
EXPECT_TRUE(weak_observer);
[observers_ addObserver:partial_observer_];
// Need an autorelease pool here, because
// -[CRBProtocolObservers forwardInvocation:] creates a temporary
// autoreleased array that holds all the observers.
@autoreleasepool {
[observers_ requiredMethod];
EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
partial_observer_ = nil;
}
EXPECT_FALSE(weak_observer);
}
// Verifies that an observer can safely remove itself as observer while being
// notified.
TEST_F(CRBProtocolObserversTest, SelfMutateObservers) {
[observers_ addObserver:mutate_observer_];
EXPECT_FALSE([observers_ empty]);
[observers_ requiredMethod];
EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);
[mutate_observer_ reset];
[observers_ nestedMutateByRemovingObserver:mutate_observer_];
EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]);
[observers_ addObserver:partial_observer_];
[observers_ requiredMethod];
EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]);
EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
[observers_ removeObserver:partial_observer_];
EXPECT_TRUE([observers_ empty]);
}
// Verifies that - [CRBProtocolObservers addObserver:] and
// - [CRBProtocolObservers removeObserver:] can be called while methods are
// being forwarded.
TEST_F(CRBProtocolObserversTest, MutateObservers) {
// Indirectly add an observer while forwarding an observer method.
[observers_ addObserver:mutate_observer_];
[observers_ mutateByAddingObserver:partial_observer_];
EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
// Check that methods are correctly forwared to the indirectly added observer.
[mutate_observer_ reset];
[observers_ requiredMethod];
EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);
EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
[mutate_observer_ reset];
[partial_observer_ reset];
// Indirectly remove an observer while forwarding an observer method.
[observers_ mutateByRemovingObserver:partial_observer_];
// Check that method is not forwared to the indirectly removed observer.
[observers_ requiredMethod];
EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);
EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
}
// Verifies that - [CRBProtocolObservers addObserver:] and
// - [CRBProtocolObservers removeObserver:] can be called while methods are
// being forwarded with a nested invocation depth > 0.
TEST_F(CRBProtocolObserversTest, NestedMutateObservers) {
// Indirectly add an observer while forwarding an observer method.
[observers_ addObserver:mutate_observer_];
[observers_ nestedMutateByAddingObserver:partial_observer_];
EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
// Check that methods are correctly forwared to the indirectly added observer.
[mutate_observer_ reset];
[observers_ requiredMethod];
EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);
EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
[mutate_observer_ reset];
[partial_observer_ reset];
// Indirectly remove an observer while forwarding an observer method.
[observers_ nestedMutateByRemovingObserver:partial_observer_];
// Check that method is not forwared to the indirectly removed observer.
[observers_ requiredMethod];
EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);
EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
}
// Verifies that CRBProtocolObservers works if an observer deallocs.
TEST_F(CRBProtocolObserversTest, IgnoresDeallocedObservers) {
__weak TestPartialObserver* weak_observer = partial_observer_;
EXPECT_TRUE(weak_observer);
[observers_ addObserver:partial_observer_];
// Need an autorelease pool here, because
// -[CRBProtocolObservers forwardInvocation:] creates a temporary
// autoreleased array that holds all the observers.
@autoreleasepool {
[observers_ requiredMethod];
EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
partial_observer_ = nil;
}
EXPECT_FALSE(weak_observer);
// This shouldn't crash.
[observers_ requiredMethod];
}
} // namespace
@implementation TestPartialObserver {
BOOL _requiredMethodInvoked;
}
- (BOOL)requiredMethodInvoked {
return _requiredMethodInvoked;
}
- (void)requiredMethod {
_requiredMethodInvoked = YES;
}
- (void)reset {
_requiredMethodInvoked = NO;
}
@end
@implementation TestCompleteObserver {
BOOL _optionalMethodInvoked;
}
- (BOOL)optionalMethodInvoked {
return _optionalMethodInvoked;
}
- (void)optionalMethod {
_optionalMethodInvoked = YES;
}
- (void)reset {
[super reset];
_optionalMethodInvoked = NO;
}
@end
@implementation TestMutateObserver {
__weak id _observers;
}
- (instancetype)initWithObserver:(CRBProtocolObservers*)observers {
self = [super init];
if (self) {
_observers = observers;
}
return self;
}
- (instancetype)init {
NOTREACHED();
}
- (void)mutateByAddingObserver:(id<TestObserver>)observer {
[_observers addObserver:observer];
}
- (void)mutateByRemovingObserver:(id<TestObserver>)observer {
[_observers removeObserver:observer];
}
- (void)nestedMutateByAddingObserver:(id<TestObserver>)observer {
[_observers mutateByAddingObserver:observer];
}
- (void)nestedMutateByRemovingObserver:(id<TestObserver>)observer {
[_observers mutateByRemovingObserver:observer];
}
@end