311 lines
9.3 KiB
Plaintext
311 lines
9.3 KiB
Plaintext
|
|
// 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"
|
||
|
|
|
||
|
|
#if !defined(__has_feature) || !__has_feature(objc_arc)
|
||
|
|
#error "This file requires ARC support."
|
||
|
|
#endif
|
||
|
|
|
||
|
|
@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();
|
||
|
|
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
|