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
17package com.android.volley;
18
19import com.android.volley.Request.Priority;
20import com.android.volley.mock.MockNetwork;
21import com.android.volley.mock.MockRequest;
22import com.android.volley.toolbox.NoCache;
23import com.android.volley.utils.CacheTestUtils;
24import com.android.volley.utils.ImmediateResponseDelivery;
25
26import android.os.SystemClock;
27import android.test.InstrumentationTestCase;
28import android.test.UiThreadTest;
29import android.test.suitebuilder.annotation.LargeTest;
30
31import java.util.ArrayList;
32import java.util.List;
33import java.util.Random;
34import java.util.concurrent.Semaphore;
35import java.util.concurrent.TimeUnit;
36import java.util.concurrent.atomic.AtomicInteger;
37
38@LargeTest
39public class RequestQueueTest extends InstrumentationTestCase {
40    private ResponseDelivery mDelivery;
41
42    @Override
43    protected void setUp() throws Exception {
44        super.setUp();
45
46        mDelivery = new ImmediateResponseDelivery();
47    }
48
49    /**
50     * Make a list of requests with random priorities.
51     * @param count Number of requests to make
52     */
53    private List<MockRequest> makeRequests(int count) {
54        Request.Priority[] allPriorities = Request.Priority.values();
55        Random random = new Random();
56
57        List<MockRequest> requests = new ArrayList<MockRequest>();
58        for (int i = 0; i < count; i++) {
59            MockRequest request = new MockRequest();
60            Request.Priority priority = allPriorities[random.nextInt(allPriorities.length)];
61            request.setCacheKey(String.valueOf(i));
62            request.setPriority(priority);
63            requests.add(request);
64        }
65        return requests;
66    }
67
68    @UiThreadTest
69    public void testAdd_requestProcessedInCorrectOrder() throws Exception {
70        int requestsToMake = 100;
71
72        OrderCheckingNetwork network = new OrderCheckingNetwork();
73        RequestQueue queue = new RequestQueue(new NoCache(), network, 1, mDelivery);
74
75        for (Request<?> request : makeRequests(requestsToMake)) {
76            queue.add(request);
77        }
78
79        network.setExpectedRequests(requestsToMake);
80        queue.start();
81        network.waitUntilExpectedDone(2000); // 2 seconds
82        queue.stop();
83    }
84
85    public void testAdd_dedupeByCacheKey() throws Exception {
86        OrderCheckingNetwork network = new OrderCheckingNetwork();
87        final AtomicInteger parsed = new AtomicInteger();
88        final AtomicInteger delivered = new AtomicInteger();
89        // Enqueue 2 requests with the same cache key. The first request takes 1.5s. Assert that the
90        // second request is only handled after the first one has been parsed and delivered.
91        DelayedRequest req1 = new DelayedRequest(1500, parsed, delivered);
92        DelayedRequest req2 = new DelayedRequest(0, parsed, delivered) {
93            @Override
94            protected Response<Object> parseNetworkResponse(NetworkResponse response) {
95                assertEquals(1, parsed.get());  // req1 must have been parsed.
96                assertEquals(1, delivered.get());  // req1 must have been parsed.
97                return super.parseNetworkResponse(response);
98            }
99        };
100        network.setExpectedRequests(2);
101        RequestQueue queue = new RequestQueue(new NoCache(), network, 3, mDelivery);
102        queue.add(req1);
103        queue.add(req2);
104        queue.start();
105        network.waitUntilExpectedDone(2000);
106        queue.stop();
107    }
108
109    public void testCancelAll_onlyCorrectTag() throws Exception {
110        MockNetwork network = new MockNetwork();
111        RequestQueue queue = new RequestQueue(new NoCache(), network, 3, mDelivery);
112        Object tagA = new Object();
113        Object tagB = new Object();
114        MockRequest req1 = new MockRequest();
115        req1.setTag(tagA);
116        MockRequest req2 = new MockRequest();
117        req2.setTag(tagB);
118        MockRequest req3 = new MockRequest();
119        req3.setTag(tagA);
120        MockRequest req4 = new MockRequest();
121        req4.setTag(tagA);
122
123        queue.add(req1); // A
124        queue.add(req2); // B
125        queue.add(req3); // A
126        queue.cancelAll(tagA);
127        queue.add(req4); // A
128
129        assertTrue(req1.cancel_called); // A cancelled
130        assertFalse(req2.cancel_called); // B not cancelled
131        assertTrue(req3.cancel_called); // A cancelled
132        assertFalse(req4.cancel_called); // A added after cancel not cancelled
133    }
134
135    private class OrderCheckingNetwork implements Network {
136        private Priority mLastPriority = Priority.IMMEDIATE;
137        private int mLastSequence = -1;
138        private Semaphore mSemaphore;
139
140        public void setExpectedRequests(int expectedRequests) {
141            // Leave one permit available so the waiter can find it.
142            expectedRequests--;
143            mSemaphore = new Semaphore(-expectedRequests);
144        }
145
146        public void waitUntilExpectedDone(long timeout)
147                throws InterruptedException, TimeoutError {
148            if (mSemaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS) == false) {
149                throw new TimeoutError();
150            }
151        }
152
153        @Override
154        public NetworkResponse performRequest(Request<?> request) {
155            Priority thisPriority = request.getPriority();
156            int thisSequence = request.getSequence();
157
158            int priorityDiff = thisPriority.compareTo(mLastPriority);
159
160            // Should never experience a higher priority after a lower priority
161            assertFalse(priorityDiff > 0);
162
163            // If we're not transitioning to a new priority block, check sequence numbers
164            if (priorityDiff == 0) {
165                assertTrue(thisSequence > mLastSequence);
166            }
167            mLastSequence = thisSequence;
168            mLastPriority = thisPriority;
169
170            mSemaphore.release();
171            return new NetworkResponse(new byte[16]);
172        }
173    }
174
175    @UiThreadTest
176    @SuppressWarnings("deprecation")
177    public void testDrain_suppressesEarlierResponses() {
178        MockNetwork network = new MockNetwork();
179        network.setDataToReturn(new byte[128]);
180        RequestQueue queue = new RequestQueue(new NoCache(), network, 4, mDelivery);
181
182        int requestsBefore = 40;
183        AtomicInteger parsedBefore = new AtomicInteger();
184        AtomicInteger deliveredBefore = new AtomicInteger();
185
186        // Start the queue and add a bunch of requests to it.
187        queue.start();
188        for (int i = 0; i < requestsBefore; i++) {
189            // Make each request take 50 ms.
190            DelayedRequest request = new DelayedRequest(50, parsedBefore, deliveredBefore);
191            queue.add(request);
192        }
193        // Sleep for a jiffy to let some of the slow requests be scheduled.
194        SystemClock.sleep(5);
195
196        // Drain the queue.  None of the original requests should be delivered
197        // because they are still waiting to parse.
198        queue.drain();
199
200        // Add some requests afterward to make sure not only that the ones before
201        // the drain were not delivered, but that ones afterward are.
202        int requestsAfter = 10;
203        AtomicInteger parsedAfter = new AtomicInteger();
204        AtomicInteger deliveredAfter = new AtomicInteger();
205        for (int i = 0; i < requestsAfter; i++) {
206            // These ones are much faster...
207            DelayedRequest request = new DelayedRequest(1, parsedAfter, deliveredAfter);
208            queue.add(request);
209        }
210
211        // Wait up to 1 second to finish
212        for (int i = 0; i < 10; i++) {
213            if (deliveredAfter.get() != requestsAfter) {
214                SystemClock.sleep(100);
215                continue;
216            }
217
218            assertEquals(deliveredAfter.get(), requestsAfter);
219            // Make sure that all "after" requests were parsed
220            assertEquals(parsedAfter.get(), requestsAfter);
221
222            // Make sure that at least one of the "before" requests was parsed
223            assertTrue(parsedBefore.get() > 0);
224            // And that none of them are delivered
225            assertEquals(0, deliveredBefore.get());
226            queue.stop();
227            return;
228        }
229
230        fail("Timed out waiting for requests to complete");
231    }
232
233    @UiThreadTest
234    @SuppressWarnings("deprecation")
235    public void testDrain_preservesUndrainable() {
236        MockNetwork network = new MockNetwork();
237        network.setDataToReturn(new byte[128]);
238        RequestQueue queue = new RequestQueue(new NoCache(), network, 4, mDelivery);
239
240        // Make a bunch of requests
241        List<MockRequest> requests = makeRequests(50);
242
243        // Pick one to be invincible.
244        MockRequest invincible = requests.get(new Random().nextInt(requests.size()));
245        invincible.setDrainable(false);
246
247        // Add them all to the queue.
248        for (MockRequest request : requests) {
249            queue.add(request);
250        }
251
252        // Drain the queue.
253        queue.drain();
254
255        // Start the queue.
256        queue.start();
257
258        // Wait up to 1 second to finish
259        for (int i = 0; i < 10; i++) {
260            if (!invincible.parseResponse_called) {
261                SystemClock.sleep(100);
262                continue;
263            }
264            // If parseResponse got called, that's good enough
265            queue.stop();
266            return;
267        }
268
269        fail("Undrainable request should have been parsed");
270    }
271
272    private class DelayedRequest extends Request<Object> {
273        private final long mDelayMillis;
274        private final AtomicInteger mParsedCount;
275        private final AtomicInteger mDeliveredCount;
276
277        public DelayedRequest(long delayMillis, AtomicInteger parsed, AtomicInteger delivered) {
278            super("http://buganizer/", null);
279            mDelayMillis = delayMillis;
280            mParsedCount = parsed;
281            mDeliveredCount = delivered;
282        }
283
284        @Override
285        protected Response<Object> parseNetworkResponse(NetworkResponse response) {
286            mParsedCount.incrementAndGet();
287            SystemClock.sleep(mDelayMillis);
288            return Response.success(new Object(), CacheTestUtils.makeRandomCacheEntry(null));
289        }
290
291        @Override
292        protected void deliverResponse(Object response) {
293            mDeliveredCount.incrementAndGet();
294        }
295    }
296
297}
298