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    private class DelayedRequest extends Request<Object> {
176        private final long mDelayMillis;
177        private final AtomicInteger mParsedCount;
178        private final AtomicInteger mDeliveredCount;
179
180        public DelayedRequest(long delayMillis, AtomicInteger parsed, AtomicInteger delivered) {
181            super("http://buganizer/", null);
182            mDelayMillis = delayMillis;
183            mParsedCount = parsed;
184            mDeliveredCount = delivered;
185        }
186
187        @Override
188        protected Response<Object> parseNetworkResponse(NetworkResponse response) {
189            mParsedCount.incrementAndGet();
190            SystemClock.sleep(mDelayMillis);
191            return Response.success(new Object(), CacheTestUtils.makeRandomCacheEntry(null));
192        }
193
194        @Override
195        protected void deliverResponse(Object response) {
196            mDeliveredCount.incrementAndGet();
197        }
198    }
199
200}
201