• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.volley;
18 
19 import static org.junit.Assert.assertNull;
20 import static org.junit.Assert.assertSame;
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.Matchers.anyString;
23 import static org.mockito.Mockito.inOrder;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.never;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 import static org.mockito.MockitoAnnotations.initMocks;
29 
30 import com.android.volley.toolbox.StringRequest;
31 import com.android.volley.utils.CacheTestUtils;
32 import java.util.concurrent.BlockingQueue;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.mockito.ArgumentCaptor;
37 import org.mockito.InOrder;
38 import org.mockito.Mock;
39 import org.mockito.invocation.InvocationOnMock;
40 import org.mockito.stubbing.Answer;
41 import org.robolectric.RobolectricTestRunner;
42 
43 @RunWith(RobolectricTestRunner.class)
44 @SuppressWarnings("rawtypes")
45 public class CacheDispatcherTest {
46     private CacheDispatcher mDispatcher;
47     private @Mock BlockingQueue<Request<?>> mCacheQueue;
48     private @Mock BlockingQueue<Request<?>> mNetworkQueue;
49     private @Mock Cache mCache;
50     private @Mock ResponseDelivery mDelivery;
51     private @Mock Network mNetwork;
52     private StringRequest mRequest;
53 
54     @Before
setUp()55     public void setUp() throws Exception {
56         initMocks(this);
57 
58         mRequest = new StringRequest(Request.Method.GET, "http://foo", null, null);
59         mDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
60     }
61 
62     private static class WaitForever implements Answer {
63         @Override
answer(InvocationOnMock invocationOnMock)64         public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
65             Thread.sleep(Long.MAX_VALUE);
66             return null;
67         }
68     }
69 
70     @Test
runStopsOnQuit()71     public void runStopsOnQuit() throws Exception {
72         when(mCacheQueue.take()).then(new WaitForever());
73         mDispatcher.start();
74         mDispatcher.quit();
75         mDispatcher.join(1000);
76     }
77 
verifyNoResponse(ResponseDelivery delivery)78     private static void verifyNoResponse(ResponseDelivery delivery) {
79         verify(delivery, never()).postResponse(any(Request.class), any(Response.class));
80         verify(delivery, never())
81                 .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
82         verify(delivery, never()).postError(any(Request.class), any(VolleyError.class));
83     }
84 
85     // A cancelled request should not be processed at all.
86     @Test
cancelledRequest()87     public void cancelledRequest() throws Exception {
88         mRequest.cancel();
89         mDispatcher.processRequest(mRequest);
90         verify(mCache, never()).get(anyString());
91         verifyNoResponse(mDelivery);
92     }
93 
94     // A cache miss does not post a response and puts the request on the network queue.
95     @Test
cacheMiss()96     public void cacheMiss() throws Exception {
97         mDispatcher.processRequest(mRequest);
98         verifyNoResponse(mDelivery);
99         verify(mNetworkQueue).put(mRequest);
100         assertNull(mRequest.getCacheEntry());
101     }
102 
103     // A non-expired cache hit posts a response and does not queue to the network.
104     @Test
nonExpiredCacheHit()105     public void nonExpiredCacheHit() throws Exception {
106         Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
107         when(mCache.get(anyString())).thenReturn(entry);
108         mDispatcher.processRequest(mRequest);
109         verify(mDelivery).postResponse(any(Request.class), any(Response.class));
110         verify(mDelivery, never()).postError(any(Request.class), any(VolleyError.class));
111     }
112 
113     // A soft-expired cache hit posts a response and queues to the network.
114     @Test
softExpiredCacheHit()115     public void softExpiredCacheHit() throws Exception {
116         Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, true);
117         when(mCache.get(anyString())).thenReturn(entry);
118         mDispatcher.processRequest(mRequest);
119 
120         // Soft expiration needs to use the deferred Runnable variant of postResponse,
121         // so make sure it gets to run.
122         ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
123         verify(mDelivery).postResponse(any(Request.class), any(Response.class), runnable.capture());
124         runnable.getValue().run();
125         // This way we can verify the behavior of the Runnable as well.
126         verify(mNetworkQueue).put(mRequest);
127         assertSame(entry, mRequest.getCacheEntry());
128 
129         verify(mDelivery, never()).postError(any(Request.class), any(VolleyError.class));
130     }
131 
132     // An expired cache hit does not post a response and queues to the network.
133     @Test
expiredCacheHit()134     public void expiredCacheHit() throws Exception {
135         Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, true, true);
136         when(mCache.get(anyString())).thenReturn(entry);
137         mDispatcher.processRequest(mRequest);
138         verifyNoResponse(mDelivery);
139         verify(mNetworkQueue).put(mRequest);
140         assertSame(entry, mRequest.getCacheEntry());
141     }
142 
143     // An fresh cache hit with parse error, does not post a response and queues to the network.
144     @Test
freshCacheHit_parseError()145     public void freshCacheHit_parseError() throws Exception {
146         Request request = mock(Request.class);
147         when(request.parseNetworkResponse(any(NetworkResponse.class)))
148                 .thenReturn(Response.error(new ParseError()));
149         when(request.getCacheKey()).thenReturn("cache/key");
150         Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
151         when(mCache.get(anyString())).thenReturn(entry);
152 
153         mDispatcher.processRequest(request);
154 
155         verifyNoResponse(mDelivery);
156         verify(mNetworkQueue).put(request);
157         assertNull(request.getCacheEntry());
158         verify(mCache).invalidate("cache/key", true);
159         verify(request).addMarker("cache-parsing-failed");
160     }
161 
162     @Test
duplicateCacheMiss()163     public void duplicateCacheMiss() throws Exception {
164         StringRequest secondRequest =
165                 new StringRequest(Request.Method.GET, "http://foo", null, null);
166         mRequest.setSequence(1);
167         secondRequest.setSequence(2);
168         mDispatcher.processRequest(mRequest);
169         mDispatcher.processRequest(secondRequest);
170         verify(mNetworkQueue).put(mRequest);
171         verifyNoResponse(mDelivery);
172     }
173 
174     @Test
tripleCacheMiss_networkErrorOnFirst()175     public void tripleCacheMiss_networkErrorOnFirst() throws Exception {
176         StringRequest secondRequest =
177                 new StringRequest(Request.Method.GET, "http://foo", null, null);
178         StringRequest thirdRequest =
179                 new StringRequest(Request.Method.GET, "http://foo", null, null);
180         mRequest.setSequence(1);
181         secondRequest.setSequence(2);
182         thirdRequest.setSequence(3);
183         mDispatcher.processRequest(mRequest);
184         mDispatcher.processRequest(secondRequest);
185         mDispatcher.processRequest(thirdRequest);
186 
187         verify(mNetworkQueue).put(mRequest);
188         verifyNoResponse(mDelivery);
189 
190         ((Request<?>) mRequest).notifyListenerResponseNotUsable();
191         // Second request should now be in network queue.
192         verify(mNetworkQueue).put(secondRequest);
193         // Another unusable response, third request should now be added.
194         ((Request<?>) secondRequest).notifyListenerResponseNotUsable();
195         verify(mNetworkQueue).put(thirdRequest);
196     }
197 
198     @Test
duplicateSoftExpiredCacheHit_failedRequest()199     public void duplicateSoftExpiredCacheHit_failedRequest() throws Exception {
200         Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, true);
201         when(mCache.get(anyString())).thenReturn(entry);
202 
203         StringRequest secondRequest =
204                 new StringRequest(Request.Method.GET, "http://foo", null, null);
205         mRequest.setSequence(1);
206         secondRequest.setSequence(2);
207 
208         mDispatcher.processRequest(mRequest);
209         mDispatcher.processRequest(secondRequest);
210 
211         // Soft expiration needs to use the deferred Runnable variant of postResponse,
212         // so make sure it gets to run.
213         ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
214         verify(mDelivery).postResponse(any(Request.class), any(Response.class), runnable.capture());
215         runnable.getValue().run();
216         // This way we can verify the behavior of the Runnable as well.
217 
218         verify(mNetworkQueue).put(mRequest);
219         verify(mDelivery)
220                 .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
221 
222         ((Request<?>) mRequest).notifyListenerResponseNotUsable();
223         // Second request should now be in network queue.
224         verify(mNetworkQueue).put(secondRequest);
225     }
226 
227     @Test
duplicateSoftExpiredCacheHit_successfulRequest()228     public void duplicateSoftExpiredCacheHit_successfulRequest() throws Exception {
229         Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, true);
230         when(mCache.get(anyString())).thenReturn(entry);
231 
232         StringRequest secondRequest =
233                 new StringRequest(Request.Method.GET, "http://foo", null, null);
234         mRequest.setSequence(1);
235         secondRequest.setSequence(2);
236 
237         mDispatcher.processRequest(mRequest);
238         mDispatcher.processRequest(secondRequest);
239 
240         // Soft expiration needs to use the deferred Runnable variant of postResponse,
241         // so make sure it gets to run.
242         ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
243         verify(mDelivery).postResponse(any(Request.class), any(Response.class), runnable.capture());
244         runnable.getValue().run();
245         // This way we can verify the behavior of the Runnable as well.
246 
247         verify(mNetworkQueue).put(mRequest);
248         verify(mDelivery)
249                 .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
250 
251         ((Request<?>) mRequest).notifyListenerResponseReceived(Response.success(null, entry));
252         // Second request should have delivered response.
253         verify(mNetworkQueue, never()).put(secondRequest);
254         verify(mDelivery)
255                 .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
256     }
257 
258     @Test
processRequestNotifiesListener()259     public void processRequestNotifiesListener() throws Exception {
260         RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
261         RequestQueue queue = new RequestQueue(mCache, mNetwork, 0, mDelivery);
262         queue.addRequestEventListener(listener);
263         mRequest.setRequestQueue(queue);
264 
265         Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
266         when(mCache.get(anyString())).thenReturn(entry);
267         mDispatcher.processRequest(mRequest);
268 
269         InOrder inOrder = inOrder(listener);
270         inOrder.verify(listener)
271                 .onRequestEvent(mRequest, RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED);
272         inOrder.verify(listener)
273                 .onRequestEvent(mRequest, RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
274         inOrder.verifyNoMoreInteractions();
275     }
276 }
277