Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2014 The Chromium Authors |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #import "base/ios/crb_protocol_observers.h" |
Sylvain Defresne | b20b7d5 | 2019-11-22 13:23:00 | [diff] [blame] | 6 | |
Hans Wennborg | a47ddf8 | 2020-05-05 18:08:07 | [diff] [blame] | 7 | #include "base/notreached.h" |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 8 | #include "testing/gtest/include/gtest/gtest.h" |
| 9 | #include "testing/gtest_mac.h" |
| 10 | #include "testing/platform_test.h" |
| 11 | |
| 12 | @protocol TestObserver |
| 13 | |
| 14 | @required |
| 15 | - (void)requiredMethod; |
| 16 | - (void)reset; |
| 17 | |
| 18 | @optional |
| 19 | - (void)optionalMethod; |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 20 | - (void)mutateByAddingObserver:(id<TestObserver>)observer; |
| 21 | - (void)mutateByRemovingObserver:(id<TestObserver>)observer; |
| 22 | - (void)nestedMutateByAddingObserver:(id<TestObserver>)observer; |
| 23 | - (void)nestedMutateByRemovingObserver:(id<TestObserver>)observer; |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 24 | |
| 25 | @end |
| 26 | |
| 27 | // Implements only the required methods in the TestObserver protocol. |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 28 | @interface TestPartialObserver : NSObject <TestObserver> |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 29 | @property(nonatomic, readonly) BOOL requiredMethodInvoked; |
| 30 | @end |
| 31 | |
| 32 | // Implements all the methods in the TestObserver protocol. |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 33 | @interface TestCompleteObserver : TestPartialObserver <TestObserver> |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 34 | @property(nonatomic, readonly) BOOL optionalMethodInvoked; |
| 35 | @end |
| 36 | |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 37 | @interface TestMutateObserver : TestCompleteObserver |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 38 | - (instancetype)initWithObserver:(CRBProtocolObservers*)observer |
| 39 | NS_DESIGNATED_INITIALIZER; |
justincohen | 5a862993 | 2015-06-10 17:38:16 | [diff] [blame] | 40 | - (instancetype)init NS_UNAVAILABLE; |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 41 | @end |
| 42 | |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 43 | namespace { |
| 44 | |
| 45 | class CRBProtocolObserversTest : public PlatformTest { |
| 46 | public: |
Sorin Jianu | 1ed1f6c | 2024-09-28 00:31:40 | [diff] [blame] | 47 | CRBProtocolObserversTest() = default; |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 48 | |
| 49 | protected: |
| 50 | void SetUp() override { |
| 51 | PlatformTest::SetUp(); |
| 52 | |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 53 | observers_ = (CRBProtocolObservers<TestObserver>*)[CRBProtocolObservers |
| 54 | observersWithProtocol:@protocol(TestObserver)]; |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 55 | |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 56 | partial_observer_ = [[TestPartialObserver alloc] init]; |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 57 | EXPECT_FALSE([partial_observer_ requiredMethodInvoked]); |
| 58 | |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 59 | complete_observer_ = [[TestCompleteObserver alloc] init]; |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 60 | EXPECT_FALSE([complete_observer_ requiredMethodInvoked]); |
| 61 | EXPECT_FALSE([complete_observer_ optionalMethodInvoked]); |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 62 | |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 63 | mutate_observer_ = [[TestMutateObserver alloc] initWithObserver:observers_]; |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 64 | EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]); |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 65 | } |
| 66 | |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 67 | CRBProtocolObservers<TestObserver>* observers_; |
| 68 | TestPartialObserver* partial_observer_; |
| 69 | TestCompleteObserver* complete_observer_; |
| 70 | TestMutateObserver* mutate_observer_; |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 71 | }; |
| 72 | |
| 73 | // Verifies basic functionality of -[CRBProtocolObservers addObserver:] and |
| 74 | // -[CRBProtocolObservers removeObserver:]. |
| 75 | TEST_F(CRBProtocolObserversTest, AddRemoveObserver) { |
| 76 | // Add an observer and verify that the CRBProtocolObservers instance forwards |
| 77 | // an invocation to it. |
| 78 | [observers_ addObserver:partial_observer_]; |
| 79 | [observers_ requiredMethod]; |
| 80 | EXPECT_TRUE([partial_observer_ requiredMethodInvoked]); |
| 81 | |
| 82 | [partial_observer_ reset]; |
| 83 | EXPECT_FALSE([partial_observer_ requiredMethodInvoked]); |
| 84 | |
| 85 | // Remove the observer and verify that the CRBProtocolObservers instance no |
| 86 | // longer forwards an invocation to it. |
| 87 | [observers_ removeObserver:partial_observer_]; |
| 88 | [observers_ requiredMethod]; |
| 89 | EXPECT_FALSE([partial_observer_ requiredMethodInvoked]); |
| 90 | } |
| 91 | |
| 92 | // Verifies that CRBProtocolObservers correctly forwards the invocation of a |
| 93 | // required method in the protocol. |
| 94 | TEST_F(CRBProtocolObserversTest, RequiredMethods) { |
| 95 | [observers_ addObserver:partial_observer_]; |
| 96 | [observers_ addObserver:complete_observer_]; |
| 97 | [observers_ requiredMethod]; |
| 98 | EXPECT_TRUE([partial_observer_ requiredMethodInvoked]); |
| 99 | EXPECT_TRUE([complete_observer_ requiredMethodInvoked]); |
| 100 | } |
| 101 | |
| 102 | // Verifies that CRBProtocolObservers correctly forwards the invocation of an |
| 103 | // optional method in the protocol. |
| 104 | TEST_F(CRBProtocolObserversTest, OptionalMethods) { |
| 105 | [observers_ addObserver:partial_observer_]; |
| 106 | [observers_ addObserver:complete_observer_]; |
| 107 | [observers_ optionalMethod]; |
| 108 | EXPECT_FALSE([partial_observer_ requiredMethodInvoked]); |
| 109 | EXPECT_FALSE([complete_observer_ requiredMethodInvoked]); |
| 110 | EXPECT_TRUE([complete_observer_ optionalMethodInvoked]); |
| 111 | } |
| 112 | |
| 113 | // Verifies that CRBProtocolObservers only holds a weak reference to an |
| 114 | // observer. |
| 115 | TEST_F(CRBProtocolObserversTest, WeakReference) { |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 116 | __weak TestPartialObserver* weak_observer = partial_observer_; |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 117 | EXPECT_TRUE(weak_observer); |
| 118 | |
| 119 | [observers_ addObserver:partial_observer_]; |
| 120 | |
Avi Drissman | 91160c63 | 2019-09-06 19:24:58 | [diff] [blame] | 121 | // Need an autorelease pool here, because |
| 122 | // -[CRBProtocolObservers forwardInvocation:] creates a temporary |
| 123 | // autoreleased array that holds all the observers. |
| 124 | @autoreleasepool { |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 125 | [observers_ requiredMethod]; |
| 126 | EXPECT_TRUE([partial_observer_ requiredMethodInvoked]); |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 127 | partial_observer_ = nil; |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 128 | } |
| 129 | |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 130 | EXPECT_FALSE(weak_observer); |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 131 | } |
| 132 | |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 133 | // Verifies that an observer can safely remove itself as observer while being |
| 134 | // notified. |
| 135 | TEST_F(CRBProtocolObserversTest, SelfMutateObservers) { |
| 136 | [observers_ addObserver:mutate_observer_]; |
| 137 | EXPECT_FALSE([observers_ empty]); |
| 138 | |
| 139 | [observers_ requiredMethod]; |
| 140 | EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]); |
| 141 | |
| 142 | [mutate_observer_ reset]; |
| 143 | |
| 144 | [observers_ nestedMutateByRemovingObserver:mutate_observer_]; |
| 145 | EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]); |
| 146 | |
| 147 | [observers_ addObserver:partial_observer_]; |
| 148 | |
| 149 | [observers_ requiredMethod]; |
| 150 | EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]); |
| 151 | EXPECT_TRUE([partial_observer_ requiredMethodInvoked]); |
| 152 | |
| 153 | [observers_ removeObserver:partial_observer_]; |
| 154 | EXPECT_TRUE([observers_ empty]); |
| 155 | } |
| 156 | |
| 157 | // Verifies that - [CRBProtocolObservers addObserver:] and |
| 158 | // - [CRBProtocolObservers removeObserver:] can be called while methods are |
| 159 | // being forwarded. |
| 160 | TEST_F(CRBProtocolObserversTest, MutateObservers) { |
| 161 | // Indirectly add an observer while forwarding an observer method. |
| 162 | [observers_ addObserver:mutate_observer_]; |
| 163 | |
| 164 | [observers_ mutateByAddingObserver:partial_observer_]; |
| 165 | EXPECT_FALSE([partial_observer_ requiredMethodInvoked]); |
| 166 | |
| 167 | // Check that methods are correctly forwared to the indirectly added observer. |
| 168 | [mutate_observer_ reset]; |
| 169 | [observers_ requiredMethod]; |
| 170 | EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]); |
| 171 | EXPECT_TRUE([partial_observer_ requiredMethodInvoked]); |
| 172 | |
| 173 | [mutate_observer_ reset]; |
| 174 | [partial_observer_ reset]; |
| 175 | |
| 176 | // Indirectly remove an observer while forwarding an observer method. |
| 177 | [observers_ mutateByRemovingObserver:partial_observer_]; |
| 178 | |
| 179 | // Check that method is not forwared to the indirectly removed observer. |
| 180 | [observers_ requiredMethod]; |
| 181 | EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]); |
| 182 | EXPECT_FALSE([partial_observer_ requiredMethodInvoked]); |
| 183 | } |
| 184 | |
| 185 | // Verifies that - [CRBProtocolObservers addObserver:] and |
| 186 | // - [CRBProtocolObservers removeObserver:] can be called while methods are |
| 187 | // being forwarded with a nested invocation depth > 0. |
| 188 | TEST_F(CRBProtocolObserversTest, NestedMutateObservers) { |
| 189 | // Indirectly add an observer while forwarding an observer method. |
| 190 | [observers_ addObserver:mutate_observer_]; |
| 191 | |
| 192 | [observers_ nestedMutateByAddingObserver:partial_observer_]; |
| 193 | EXPECT_FALSE([partial_observer_ requiredMethodInvoked]); |
| 194 | |
| 195 | // Check that methods are correctly forwared to the indirectly added observer. |
| 196 | [mutate_observer_ reset]; |
| 197 | [observers_ requiredMethod]; |
| 198 | EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]); |
| 199 | EXPECT_TRUE([partial_observer_ requiredMethodInvoked]); |
| 200 | |
| 201 | [mutate_observer_ reset]; |
| 202 | [partial_observer_ reset]; |
| 203 | |
| 204 | // Indirectly remove an observer while forwarding an observer method. |
| 205 | [observers_ nestedMutateByRemovingObserver:partial_observer_]; |
| 206 | |
| 207 | // Check that method is not forwared to the indirectly removed observer. |
| 208 | [observers_ requiredMethod]; |
| 209 | EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]); |
| 210 | EXPECT_FALSE([partial_observer_ requiredMethodInvoked]); |
| 211 | } |
| 212 | |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 213 | // Verifies that CRBProtocolObservers works if an observer deallocs. |
| 214 | TEST_F(CRBProtocolObserversTest, IgnoresDeallocedObservers) { |
| 215 | __weak TestPartialObserver* weak_observer = partial_observer_; |
| 216 | EXPECT_TRUE(weak_observer); |
| 217 | |
| 218 | [observers_ addObserver:partial_observer_]; |
| 219 | |
| 220 | // Need an autorelease pool here, because |
| 221 | // -[CRBProtocolObservers forwardInvocation:] creates a temporary |
| 222 | // autoreleased array that holds all the observers. |
| 223 | @autoreleasepool { |
| 224 | [observers_ requiredMethod]; |
| 225 | EXPECT_TRUE([partial_observer_ requiredMethodInvoked]); |
| 226 | partial_observer_ = nil; |
| 227 | } |
| 228 | |
| 229 | EXPECT_FALSE(weak_observer); |
| 230 | // This shouldn't crash. |
| 231 | [observers_ requiredMethod]; |
| 232 | } |
| 233 | |
stuartmorgan | 4733f55 | 2015-02-14 22:19:30 | [diff] [blame] | 234 | } // namespace |
| 235 | |
| 236 | @implementation TestPartialObserver { |
| 237 | BOOL _requiredMethodInvoked; |
| 238 | } |
| 239 | |
| 240 | - (BOOL)requiredMethodInvoked { |
| 241 | return _requiredMethodInvoked; |
| 242 | } |
| 243 | |
| 244 | - (void)requiredMethod { |
| 245 | _requiredMethodInvoked = YES; |
| 246 | } |
| 247 | |
| 248 | - (void)reset { |
| 249 | _requiredMethodInvoked = NO; |
| 250 | } |
| 251 | |
| 252 | @end |
| 253 | |
| 254 | @implementation TestCompleteObserver { |
| 255 | BOOL _optionalMethodInvoked; |
| 256 | } |
| 257 | |
| 258 | - (BOOL)optionalMethodInvoked { |
| 259 | return _optionalMethodInvoked; |
| 260 | } |
| 261 | |
| 262 | - (void)optionalMethod { |
| 263 | _optionalMethodInvoked = YES; |
| 264 | } |
| 265 | |
| 266 | - (void)reset { |
| 267 | [super reset]; |
| 268 | _optionalMethodInvoked = NO; |
| 269 | } |
| 270 | |
| 271 | @end |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 272 | |
| 273 | @implementation TestMutateObserver { |
Stepan Khapugin | 81f3a64 | 2021-02-26 11:42:13 | [diff] [blame] | 274 | __weak id _observers; |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 275 | } |
| 276 | |
| 277 | - (instancetype)initWithObserver:(CRBProtocolObservers*)observers { |
| 278 | self = [super init]; |
| 279 | if (self) { |
| 280 | _observers = observers; |
| 281 | } |
| 282 | return self; |
| 283 | } |
| 284 | |
justincohen | 5a862993 | 2015-06-10 17:38:16 | [diff] [blame] | 285 | - (instancetype)init { |
Peter Boström | de57333 | 2024-08-26 20:42:45 | [diff] [blame] | 286 | NOTREACHED(); |
justincohen | 5a862993 | 2015-06-10 17:38:16 | [diff] [blame] | 287 | } |
| 288 | |
jbbegue | 12d50e7 | 2015-06-05 08:12:43 | [diff] [blame] | 289 | - (void)mutateByAddingObserver:(id<TestObserver>)observer { |
| 290 | [_observers addObserver:observer]; |
| 291 | } |
| 292 | |
| 293 | - (void)mutateByRemovingObserver:(id<TestObserver>)observer { |
| 294 | [_observers removeObserver:observer]; |
| 295 | } |
| 296 | |
| 297 | - (void)nestedMutateByAddingObserver:(id<TestObserver>)observer { |
| 298 | [_observers mutateByAddingObserver:observer]; |
| 299 | } |
| 300 | |
| 301 | - (void)nestedMutateByRemovingObserver:(id<TestObserver>)observer { |
| 302 | [_observers mutateByRemovingObserver:observer]; |
| 303 | } |
| 304 | |
| 305 | @end |