| // Copyright 2014 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 "base/ios/crb_protocol_observers.h" |
| #include "base/ios/weak_nsobject.h" |
| #include "base/logging.h" |
| #include "base/mac/scoped_nsautorelease_pool.h" |
| #include "base/mac/scoped_nsobject.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_.reset([[CRBProtocolObservers observersWithProtocol: |
| @protocol(TestObserver)] retain]); |
| |
| partial_observer_.reset([[TestPartialObserver alloc] init]); |
| EXPECT_FALSE([partial_observer_ requiredMethodInvoked]); |
| |
| complete_observer_.reset([[TestCompleteObserver alloc] init]); |
| EXPECT_FALSE([complete_observer_ requiredMethodInvoked]); |
| EXPECT_FALSE([complete_observer_ optionalMethodInvoked]); |
| |
| mutate_observer_.reset( |
| [[TestMutateObserver alloc] initWithObserver:observers_.get()]); |
| EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]); |
| } |
| |
| base::scoped_nsobject<id> observers_; |
| base::scoped_nsobject<TestPartialObserver> partial_observer_; |
| base::scoped_nsobject<TestCompleteObserver> complete_observer_; |
| base::scoped_nsobject<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) { |
| base::WeakNSObject<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. |
| base::mac::ScopedNSAutoreleasePool pool; |
| [observers_ requiredMethod]; |
| EXPECT_TRUE([partial_observer_ requiredMethodInvoked]); |
| } |
| |
| partial_observer_.reset(); |
| EXPECT_FALSE(weak_observer.get()); |
| } |
| |
| // 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]); |
| } |
| |
| } // 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 { |
| id _observers; // weak |
| } |
| |
| - (instancetype)initWithObserver:(CRBProtocolObservers*)observers { |
| self = [super init]; |
| if (self) { |
| _observers = observers; |
| } |
| return self; |
| } |
| |
| - (instancetype)init { |
| NOTREACHED(); |
| return nil; |
| } |
| |
| - (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 |