1717package com .ctrip .framework .apollo .internals ;
1818
1919import static org .junit .Assert .assertEquals ;
20+ import static org .junit .Assert .assertNotSame ;
21+ import static org .junit .Assert .assertThrows ;
22+ import static org .junit .Assert .assertTrue ;
2023import static org .mockito .ArgumentMatchers .any ;
2124import static org .mockito .ArgumentMatchers .eq ;
2225import static org .mockito .Mockito .spy ;
2831import com .ctrip .framework .apollo .enums .PropertyChangeType ;
2932import com .ctrip .framework .apollo .model .ConfigChange ;
3033import com .ctrip .framework .apollo .model .ConfigChangeEvent ;
34+ import com .ctrip .framework .apollo .spring .config .CachedCompositePropertySource ;
3135import com .google .common .util .concurrent .SettableFuture ;
3236import java .util .Collections ;
3337import java .util .HashMap ;
@@ -122,6 +126,88 @@ public void onChange(ConfigChangeEvent changeEvent) {
122126 verify (configChangeListener2 , times (1 )).onChange (eq (configChangeEvent ));
123127 }
124128
129+ @ Test
130+ public void testFireConfigChange_twoCachedCompositePropertySourcesWithSameName_shouldBothBeNotified ()
131+ throws ExecutionException , InterruptedException , TimeoutException {
132+ AbstractConfig abstractConfig = new ErrorConfig ();
133+ final String namespace = "app-namespace-listener-equals" ;
134+ final String key = "great-key" ;
135+
136+ ListenerPair listenerPair = createSameNameListeners ();
137+ final CountingCachedCompositePropertySource listener1 = listenerPair .listener1 ;
138+ final CountingCachedCompositePropertySource listener2 = listenerPair .listener2 ;
139+
140+ abstractConfig .addChangeListener (listener1 , Collections .singleton (key ));
141+ abstractConfig .addChangeListener (listener2 , Collections .singleton (key ));
142+
143+ Map <String , ConfigChange > changes = createSingleKeyChanges (namespace , key );
144+
145+ abstractConfig .fireConfigChange (someAppId , namespace , changes );
146+
147+ assertEquals (Collections .singleton (key ), listener1 .awaitChange (500 , TimeUnit .MILLISECONDS ).changedKeys ());
148+ assertEquals (Collections .singleton (key ), listener2 .awaitChange (500 , TimeUnit .MILLISECONDS ).changedKeys ());
149+
150+ assertEquals (1 , listener1 .changeCount .get ());
151+ assertEquals (1 , listener2 .changeCount .get ());
152+ }
153+
154+ @ Test
155+ public void testFireConfigChange_twoCachedCompositePropertySourcesWithSameNameAndDifferentInterestedKeys_shouldNotConflict ()
156+ throws ExecutionException , InterruptedException , TimeoutException {
157+ AbstractConfig abstractConfig = new ErrorConfig ();
158+ final String namespace = "app-namespace-listener-interested-keys" ;
159+ final String key1 = "great-key-1" ;
160+ final String key2 = "great-key-2" ;
161+
162+ ListenerPair listenerPair = createSameNameListeners ();
163+ final CountingCachedCompositePropertySource listener1 = listenerPair .listener1 ;
164+ final CountingCachedCompositePropertySource listener2 = listenerPair .listener2 ;
165+
166+ abstractConfig .addChangeListener (listener1 , Collections .singleton (key1 ));
167+ abstractConfig .addChangeListener (listener2 , Collections .singleton (key2 ));
168+
169+ abstractConfig .fireConfigChange (someAppId , namespace , createSingleKeyChanges (namespace , key1 ));
170+
171+ assertEquals (Collections .singleton (key1 ), listener1 .awaitChange (500 , TimeUnit .MILLISECONDS ).changedKeys ());
172+ assertThrows (TimeoutException .class , () -> listener2 .awaitChange (200 , TimeUnit .MILLISECONDS ));
173+
174+ listener1 .resetChangeFuture ();
175+ listener2 .resetChangeFuture ();
176+
177+ abstractConfig .fireConfigChange (someAppId , namespace , createSingleKeyChanges (namespace , key2 ));
178+
179+ assertThrows (TimeoutException .class , () -> listener1 .awaitChange (200 , TimeUnit .MILLISECONDS ));
180+ assertEquals (Collections .singleton (key2 ), listener2 .awaitChange (500 , TimeUnit .MILLISECONDS ).changedKeys ());
181+
182+ assertEquals (1 , listener1 .changeCount .get ());
183+ assertEquals (1 , listener2 .changeCount .get ());
184+ }
185+
186+ @ Test
187+ public void testRemoveChangeListener_twoCachedCompositePropertySourcesWithSameName_shouldRemoveSpecifiedInstance ()
188+ throws ExecutionException , InterruptedException , TimeoutException {
189+ AbstractConfig abstractConfig = new ErrorConfig ();
190+ final String namespace = "app-namespace-listener-remove" ;
191+ final String key = "great-key" ;
192+
193+ ListenerPair listenerPair = createSameNameListeners ();
194+ final CountingCachedCompositePropertySource listener1 = listenerPair .listener1 ;
195+ final CountingCachedCompositePropertySource listener2 = listenerPair .listener2 ;
196+
197+ abstractConfig .addChangeListener (listener1 , Collections .singleton (key ));
198+ abstractConfig .addChangeListener (listener2 , Collections .singleton (key ));
199+
200+ assertTrue (abstractConfig .removeChangeListener (listener2 ));
201+
202+ abstractConfig .fireConfigChange (someAppId , namespace , createSingleKeyChanges (namespace , key ));
203+
204+ assertEquals (Collections .singleton (key ), listener1 .awaitChange (500 , TimeUnit .MILLISECONDS ).changedKeys ());
205+ assertThrows (TimeoutException .class , () -> listener2 .awaitChange (200 , TimeUnit .MILLISECONDS ));
206+
207+ assertEquals (1 , listener1 .changeCount .get ());
208+ assertEquals (0 , listener2 .changeCount .get ());
209+ }
210+
125211 @ Test
126212 public void testFireConfigChange_changes_notify_once ()
127213 throws ExecutionException , InterruptedException , TimeoutException {
@@ -188,4 +274,57 @@ public ConfigSourceType getSourceType() {
188274 throw new UnsupportedOperationException ();
189275 }
190276 }
191- }
277+
278+ private static class CountingCachedCompositePropertySource extends CachedCompositePropertySource {
279+ private final AtomicInteger changeCount = new AtomicInteger ();
280+ private volatile SettableFuture <ConfigChangeEvent > changeFuture = SettableFuture .create ();
281+
282+ private CountingCachedCompositePropertySource (String name ) {
283+ super (name );
284+ }
285+
286+ @ Override
287+ public void onChange (ConfigChangeEvent changeEvent ) {
288+ changeCount .incrementAndGet ();
289+ changeFuture .set (changeEvent );
290+ super .onChange (changeEvent );
291+ }
292+
293+ private void resetChangeFuture () {
294+ changeFuture = SettableFuture .create ();
295+ }
296+
297+ private ConfigChangeEvent awaitChange (long timeout , TimeUnit unit )
298+ throws ExecutionException , InterruptedException , TimeoutException {
299+ return changeFuture .get (timeout , unit );
300+ }
301+ }
302+
303+ private static ListenerPair createSameNameListeners () {
304+ CountingCachedCompositePropertySource listener1 =
305+ new CountingCachedCompositePropertySource ("ApolloBootstrapPropertySources" );
306+ CountingCachedCompositePropertySource listener2 =
307+ new CountingCachedCompositePropertySource ("ApolloBootstrapPropertySources" );
308+ assertNotSame (listener1 , listener2 );
309+ assertEquals (listener1 , listener2 );
310+ return new ListenerPair (listener1 , listener2 );
311+ }
312+
313+ private static class ListenerPair {
314+ private final CountingCachedCompositePropertySource listener1 ;
315+ private final CountingCachedCompositePropertySource listener2 ;
316+
317+ private ListenerPair (CountingCachedCompositePropertySource listener1 ,
318+ CountingCachedCompositePropertySource listener2 ) {
319+ this .listener1 = listener1 ;
320+ this .listener2 = listener2 ;
321+ }
322+ }
323+
324+ private static Map <String , ConfigChange > createSingleKeyChanges (String namespace , String key ) {
325+ Map <String , ConfigChange > changes = new HashMap <>();
326+ changes .put (key ,
327+ new ConfigChange (someAppId , namespace , key , "old-value" , "new-value" , PropertyChangeType .MODIFIED ));
328+ return changes ;
329+ }
330+ }
0 commit comments