| // 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 |