aboutsummaryrefslogtreecommitdiff
path: root/core/src/test/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/test/java')
-rw-r--r--core/src/test/java/com/android/volley/AsyncRequestQueueTest.java200
-rw-r--r--core/src/test/java/com/android/volley/CacheDispatcherTest.java276
-rw-r--r--core/src/test/java/com/android/volley/NetworkDispatcherTest.java146
-rw-r--r--core/src/test/java/com/android/volley/NetworkResponseTest.java61
-rw-r--r--core/src/test/java/com/android/volley/RequestQueueIntegrationTest.java197
-rw-r--r--core/src/test/java/com/android/volley/RequestQueueTest.java129
-rw-r--r--core/src/test/java/com/android/volley/RequestTest.java232
-rw-r--r--core/src/test/java/com/android/volley/ResponseDeliveryTest.java71
-rw-r--r--core/src/test/java/com/android/volley/mock/MockAsyncStack.java86
-rw-r--r--core/src/test/java/com/android/volley/mock/MockHttpStack.java80
-rw-r--r--core/src/test/java/com/android/volley/mock/MockRequest.java99
-rw-r--r--core/src/test/java/com/android/volley/mock/ShadowSystemClock.java27
-rw-r--r--core/src/test/java/com/android/volley/toolbox/AdaptedHttpStackTest.java128
-rw-r--r--core/src/test/java/com/android/volley/toolbox/AndroidAuthenticatorTest.java111
-rw-r--r--core/src/test/java/com/android/volley/toolbox/BaseHttpStackTest.java104
-rw-r--r--core/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java508
-rw-r--r--core/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java384
-rw-r--r--core/src/test/java/com/android/volley/toolbox/ByteArrayPoolTest.java78
-rw-r--r--core/src/test/java/com/android/volley/toolbox/CacheTest.java39
-rw-r--r--core/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java646
-rw-r--r--core/src/test/java/com/android/volley/toolbox/HttpClientStackTest.java156
-rw-r--r--core/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java317
-rw-r--r--core/src/test/java/com/android/volley/toolbox/HttpStackConformanceTest.java192
-rw-r--r--core/src/test/java/com/android/volley/toolbox/HurlStackTest.java337
-rw-r--r--core/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java121
-rw-r--r--core/src/test/java/com/android/volley/toolbox/ImageRequestTest.java194
-rw-r--r--core/src/test/java/com/android/volley/toolbox/JsonRequestCharsetTest.java119
-rw-r--r--core/src/test/java/com/android/volley/toolbox/JsonRequestTest.java73
-rw-r--r--core/src/test/java/com/android/volley/toolbox/NetworkImageViewTest.java101
-rw-r--r--core/src/test/java/com/android/volley/toolbox/PoolingByteArrayOutputStreamTest.java81
-rw-r--r--core/src/test/java/com/android/volley/toolbox/RequestFutureTest.java35
-rw-r--r--core/src/test/java/com/android/volley/toolbox/RequestQueueTest.java51
-rw-r--r--core/src/test/java/com/android/volley/toolbox/RequestTest.java77
-rw-r--r--core/src/test/java/com/android/volley/toolbox/ResponseTest.java55
-rw-r--r--core/src/test/java/com/android/volley/toolbox/StringRequestTest.java42
-rw-r--r--core/src/test/java/com/android/volley/utils/CacheTestUtils.java89
-rw-r--r--core/src/test/java/com/android/volley/utils/ImmediateResponseDelivery.java37
37 files changed, 5679 insertions, 0 deletions
diff --git a/core/src/test/java/com/android/volley/AsyncRequestQueueTest.java b/core/src/test/java/com/android/volley/AsyncRequestQueueTest.java
new file mode 100644
index 0000000..aef4f01
--- /dev/null
+++ b/core/src/test/java/com/android/volley/AsyncRequestQueueTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.AsyncCache.OnGetCompleteCallback;
+import com.android.volley.AsyncCache.OnWriteCompleteCallback;
+import com.android.volley.mock.ShadowSystemClock;
+import com.android.volley.toolbox.NoAsyncCache;
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.utils.ImmediateResponseDelivery;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Unit tests for AsyncRequestQueue, with all dependencies mocked out */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowSystemClock.class})
+public class AsyncRequestQueueTest {
+
+ @Mock private AsyncNetwork mMockNetwork;
+ @Mock private ScheduledExecutorService mMockScheduledExecutor;
+ private final ResponseDelivery mDelivery = new ImmediateResponseDelivery();
+ private AsyncRequestQueue queue;
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ queue = createRequestQueue(new NoAsyncCache());
+ }
+
+ @Test
+ public void cancelAll_onlyCorrectTag() throws Exception {
+ queue.start();
+ Object tagA = new Object();
+ Object tagB = new Object();
+ StringRequest req1 = mock(StringRequest.class);
+ when(req1.getTag()).thenReturn(tagA);
+ StringRequest req2 = mock(StringRequest.class);
+ when(req2.getTag()).thenReturn(tagB);
+ StringRequest req3 = mock(StringRequest.class);
+ when(req3.getTag()).thenReturn(tagA);
+ StringRequest req4 = mock(StringRequest.class);
+ when(req4.getTag()).thenReturn(tagA);
+
+ queue.add(req1); // A
+ queue.add(req2); // B
+ queue.add(req3); // A
+ queue.cancelAll(tagA);
+ queue.add(req4); // A
+
+ verify(req1).cancel(); // A cancelled
+ verify(req3).cancel(); // A cancelled
+ verify(req2, never()).cancel(); // B not cancelled
+ verify(req4, never()).cancel(); // A added after cancel not cancelled
+ queue.stop();
+ }
+
+ @Test
+ public void add_notifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ queue.start();
+ queue.addRequestEventListener(listener);
+ StringRequest req = mock(StringRequest.class);
+
+ queue.add(req);
+
+ verify(listener).onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_QUEUED);
+ verifyNoMoreInteractions(listener);
+ queue.stop();
+ }
+
+ @Test
+ public void finish_notifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ queue.start();
+ queue.addRequestEventListener(listener);
+ StringRequest req = mock(StringRequest.class);
+
+ queue.finish(req);
+
+ verify(listener).onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_FINISHED);
+ verifyNoMoreInteractions(listener);
+ queue.stop();
+ }
+
+ @Test
+ public void sendRequestEvent_notifiesListener() throws Exception {
+ StringRequest req = mock(StringRequest.class);
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ queue.start();
+ queue.addRequestEventListener(listener);
+
+ queue.sendRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+
+ verify(listener)
+ .onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+ verifyNoMoreInteractions(listener);
+ queue.stop();
+ }
+
+ @Test
+ public void removeRequestEventListener_removesListener() throws Exception {
+ StringRequest req = mock(StringRequest.class);
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ queue.start();
+ queue.addRequestEventListener(listener);
+ queue.removeRequestEventListener(listener);
+
+ queue.sendRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+
+ verifyNoMoreInteractions(listener);
+ queue.stop();
+ }
+
+ @Test
+ public void requestsQueuedBeforeCacheInitialization_asyncCache() {
+ // Create a new queue with a mock cache in order to verify the initialization.
+ AsyncCache mockAsyncCache = mock(AsyncCache.class);
+ AsyncRequestQueue queue = createRequestQueue(mockAsyncCache);
+ queue.start();
+
+ ArgumentCaptor<OnWriteCompleteCallback> callbackCaptor =
+ ArgumentCaptor.forClass(OnWriteCompleteCallback.class);
+ verify(mockAsyncCache).initialize(callbackCaptor.capture());
+
+ StringRequest req = mock(StringRequest.class);
+ req.setShouldCache(true);
+ when(req.getCacheKey()).thenReturn("cache-key");
+ queue.add(req);
+
+ // Cache should not be read before initialization completes.
+ verify(mockAsyncCache, never()).get(anyString(), any(OnGetCompleteCallback.class));
+
+ callbackCaptor.getValue().onWriteComplete();
+
+ // Once the write completes, the request should be kicked off (in the form of a cache
+ // lookup).
+ verify(mockAsyncCache).get(eq("cache-key"), any(OnGetCompleteCallback.class));
+
+ queue.stop();
+ }
+
+ private AsyncRequestQueue createRequestQueue(AsyncCache asyncCache) {
+ return new AsyncRequestQueue.Builder(mMockNetwork)
+ .setResponseDelivery(mDelivery)
+ .setAsyncCache(asyncCache)
+ .setExecutorFactory(
+ new AsyncRequestQueue.ExecutorFactory() {
+ @Override
+ public ExecutorService createNonBlockingExecutor(
+ BlockingQueue<Runnable> taskQueue) {
+ return MoreExecutors.newDirectExecutorService();
+ }
+
+ @Override
+ public ExecutorService createBlockingExecutor(
+ BlockingQueue<Runnable> taskQueue) {
+ return MoreExecutors.newDirectExecutorService();
+ }
+
+ @Override
+ public ScheduledExecutorService createNonBlockingScheduledExecutor() {
+ return mMockScheduledExecutor;
+ }
+ })
+ .build();
+ }
+}
diff --git a/core/src/test/java/com/android/volley/CacheDispatcherTest.java b/core/src/test/java/com/android/volley/CacheDispatcherTest.java
new file mode 100644
index 0000000..aef6785
--- /dev/null
+++ b/core/src/test/java/com/android/volley/CacheDispatcherTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.utils.CacheTestUtils;
+import java.util.concurrent.BlockingQueue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+@SuppressWarnings("rawtypes")
+public class CacheDispatcherTest {
+ private CacheDispatcher mDispatcher;
+ private @Mock BlockingQueue<Request<?>> mCacheQueue;
+ private @Mock BlockingQueue<Request<?>> mNetworkQueue;
+ private @Mock Cache mCache;
+ private @Mock ResponseDelivery mDelivery;
+ private @Mock Network mNetwork;
+ private StringRequest mRequest;
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+
+ mRequest = new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
+ }
+
+ private static class WaitForever implements Answer {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Thread.sleep(Long.MAX_VALUE);
+ return null;
+ }
+ }
+
+ @Test
+ public void runStopsOnQuit() throws Exception {
+ when(mCacheQueue.take()).then(new WaitForever());
+ mDispatcher.start();
+ mDispatcher.quit();
+ mDispatcher.join(1000);
+ }
+
+ private static void verifyNoResponse(ResponseDelivery delivery) {
+ verify(delivery, never()).postResponse(any(Request.class), any(Response.class));
+ verify(delivery, never())
+ .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
+ verify(delivery, never()).postError(any(Request.class), any(VolleyError.class));
+ }
+
+ // A cancelled request should not be processed at all.
+ @Test
+ public void cancelledRequest() throws Exception {
+ mRequest.cancel();
+ mDispatcher.processRequest(mRequest);
+ verify(mCache, never()).get(anyString());
+ verifyNoResponse(mDelivery);
+ }
+
+ // A cache miss does not post a response and puts the request on the network queue.
+ @Test
+ public void cacheMiss() throws Exception {
+ mDispatcher.processRequest(mRequest);
+ verifyNoResponse(mDelivery);
+ verify(mNetworkQueue).put(mRequest);
+ assertNull(mRequest.getCacheEntry());
+ }
+
+ // A non-expired cache hit posts a response and does not queue to the network.
+ @Test
+ public void nonExpiredCacheHit() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
+ when(mCache.get(anyString())).thenReturn(entry);
+ mDispatcher.processRequest(mRequest);
+ verify(mDelivery).postResponse(any(Request.class), any(Response.class));
+ verify(mDelivery, never()).postError(any(Request.class), any(VolleyError.class));
+ }
+
+ // A soft-expired cache hit posts a response and queues to the network.
+ @Test
+ public void softExpiredCacheHit() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, true);
+ when(mCache.get(anyString())).thenReturn(entry);
+ mDispatcher.processRequest(mRequest);
+
+ // Soft expiration needs to use the deferred Runnable variant of postResponse,
+ // so make sure it gets to run.
+ ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mDelivery).postResponse(any(Request.class), any(Response.class), runnable.capture());
+ runnable.getValue().run();
+ // This way we can verify the behavior of the Runnable as well.
+ verify(mNetworkQueue).put(mRequest);
+ assertSame(entry, mRequest.getCacheEntry());
+
+ verify(mDelivery, never()).postError(any(Request.class), any(VolleyError.class));
+ }
+
+ // An expired cache hit does not post a response and queues to the network.
+ @Test
+ public void expiredCacheHit() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, true, true);
+ when(mCache.get(anyString())).thenReturn(entry);
+ mDispatcher.processRequest(mRequest);
+ verifyNoResponse(mDelivery);
+ verify(mNetworkQueue).put(mRequest);
+ assertSame(entry, mRequest.getCacheEntry());
+ }
+
+ // An fresh cache hit with parse error, does not post a response and queues to the network.
+ @Test
+ public void freshCacheHit_parseError() throws Exception {
+ Request request = mock(Request.class);
+ when(request.parseNetworkResponse(any(NetworkResponse.class)))
+ .thenReturn(Response.error(new ParseError()));
+ when(request.getCacheKey()).thenReturn("cache/key");
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
+ when(mCache.get(anyString())).thenReturn(entry);
+
+ mDispatcher.processRequest(request);
+
+ verifyNoResponse(mDelivery);
+ verify(mNetworkQueue).put(request);
+ assertNull(request.getCacheEntry());
+ verify(mCache).invalidate("cache/key", true);
+ verify(request).addMarker("cache-parsing-failed");
+ }
+
+ @Test
+ public void duplicateCacheMiss() throws Exception {
+ StringRequest secondRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mRequest.setSequence(1);
+ secondRequest.setSequence(2);
+ mDispatcher.processRequest(mRequest);
+ mDispatcher.processRequest(secondRequest);
+ verify(mNetworkQueue).put(mRequest);
+ verifyNoResponse(mDelivery);
+ }
+
+ @Test
+ public void tripleCacheMiss_networkErrorOnFirst() throws Exception {
+ StringRequest secondRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ StringRequest thirdRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mRequest.setSequence(1);
+ secondRequest.setSequence(2);
+ thirdRequest.setSequence(3);
+ mDispatcher.processRequest(mRequest);
+ mDispatcher.processRequest(secondRequest);
+ mDispatcher.processRequest(thirdRequest);
+
+ verify(mNetworkQueue).put(mRequest);
+ verifyNoResponse(mDelivery);
+
+ ((Request<?>) mRequest).notifyListenerResponseNotUsable();
+ // Second request should now be in network queue.
+ verify(mNetworkQueue).put(secondRequest);
+ // Another unusable response, third request should now be added.
+ ((Request<?>) secondRequest).notifyListenerResponseNotUsable();
+ verify(mNetworkQueue).put(thirdRequest);
+ }
+
+ @Test
+ public void duplicateSoftExpiredCacheHit_failedRequest() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, true);
+ when(mCache.get(anyString())).thenReturn(entry);
+
+ StringRequest secondRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mRequest.setSequence(1);
+ secondRequest.setSequence(2);
+
+ mDispatcher.processRequest(mRequest);
+ mDispatcher.processRequest(secondRequest);
+
+ // Soft expiration needs to use the deferred Runnable variant of postResponse,
+ // so make sure it gets to run.
+ ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mDelivery).postResponse(any(Request.class), any(Response.class), runnable.capture());
+ runnable.getValue().run();
+ // This way we can verify the behavior of the Runnable as well.
+
+ verify(mNetworkQueue).put(mRequest);
+ verify(mDelivery)
+ .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
+
+ ((Request<?>) mRequest).notifyListenerResponseNotUsable();
+ // Second request should now be in network queue.
+ verify(mNetworkQueue).put(secondRequest);
+ }
+
+ @Test
+ public void duplicateSoftExpiredCacheHit_successfulRequest() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, true);
+ when(mCache.get(anyString())).thenReturn(entry);
+
+ StringRequest secondRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mRequest.setSequence(1);
+ secondRequest.setSequence(2);
+
+ mDispatcher.processRequest(mRequest);
+ mDispatcher.processRequest(secondRequest);
+
+ // Soft expiration needs to use the deferred Runnable variant of postResponse,
+ // so make sure it gets to run.
+ ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mDelivery).postResponse(any(Request.class), any(Response.class), runnable.capture());
+ runnable.getValue().run();
+ // This way we can verify the behavior of the Runnable as well.
+
+ verify(mNetworkQueue).put(mRequest);
+ verify(mDelivery)
+ .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
+
+ ((Request<?>) mRequest).notifyListenerResponseReceived(Response.success(null, entry));
+ // Second request should have delivered response.
+ verify(mNetworkQueue, never()).put(secondRequest);
+ verify(mDelivery)
+ .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
+ }
+
+ @Test
+ public void processRequestNotifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ RequestQueue queue = new RequestQueue(mCache, mNetwork, 0, mDelivery);
+ queue.addRequestEventListener(listener);
+ mRequest.setRequestQueue(queue);
+
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
+ when(mCache.get(anyString())).thenReturn(entry);
+ mDispatcher.processRequest(mRequest);
+
+ InOrder inOrder = inOrder(listener);
+ inOrder.verify(listener)
+ .onRequestEvent(mRequest, RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED);
+ inOrder.verify(listener)
+ .onRequestEvent(mRequest, RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
+ inOrder.verifyNoMoreInteractions();
+ }
+}
diff --git a/core/src/test/java/com/android/volley/NetworkDispatcherTest.java b/core/src/test/java/com/android/volley/NetworkDispatcherTest.java
new file mode 100644
index 0000000..74dfe8a
--- /dev/null
+++ b/core/src/test/java/com/android/volley/NetworkDispatcherTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.toolbox.NoCache;
+import com.android.volley.toolbox.StringRequest;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.BlockingQueue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class NetworkDispatcherTest {
+ private NetworkDispatcher mDispatcher;
+ private @Mock ResponseDelivery mDelivery;
+ private @Mock BlockingQueue<Request<?>> mNetworkQueue;
+ private @Mock Network mNetwork;
+ private @Mock Cache mCache;
+ private StringRequest mRequest;
+
+ private static final byte[] CANNED_DATA =
+ "Ceci n'est pas une vraie reponse".getBytes(StandardCharsets.UTF_8);
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ mRequest = new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
+ }
+
+ @Test
+ public void successPostsResponse() throws Exception {
+ when(mNetwork.performRequest(any(Request.class)))
+ .thenReturn(new NetworkResponse(CANNED_DATA));
+ mDispatcher.processRequest(mRequest);
+
+ ArgumentCaptor<Response> response = ArgumentCaptor.forClass(Response.class);
+ verify(mDelivery).postResponse(any(Request.class), response.capture());
+ assertTrue(response.getValue().isSuccess());
+ assertEquals(response.getValue().result, new String(CANNED_DATA, StandardCharsets.UTF_8));
+
+ verify(mDelivery, never()).postError(any(Request.class), any(VolleyError.class));
+ }
+
+ @Test
+ public void successNotifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ RequestQueue queue = new RequestQueue(new NoCache(), mNetwork, 0, mDelivery);
+ queue.addRequestEventListener(listener);
+ mRequest.setRequestQueue(queue);
+
+ when(mNetwork.performRequest(any(Request.class)))
+ .thenReturn(new NetworkResponse(CANNED_DATA));
+
+ mDispatcher.processRequest(mRequest);
+
+ InOrder inOrder = inOrder(listener);
+ inOrder.verify(listener)
+ .onRequestEvent(
+ mRequest, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+ inOrder.verify(listener)
+ .onRequestEvent(
+ mRequest, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void exceptionPostsError() throws Exception {
+ when(mNetwork.performRequest(any(Request.class))).thenThrow(new ServerError());
+ mDispatcher.processRequest(mRequest);
+
+ verify(mDelivery).postError(any(Request.class), any(VolleyError.class));
+ verify(mDelivery, never()).postResponse(any(Request.class), any(Response.class));
+ }
+
+ @Test
+ public void exceptionNotifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ RequestQueue queue = new RequestQueue(new NoCache(), mNetwork, 0, mDelivery);
+ queue.addRequestEventListener(listener);
+ mRequest.setRequestQueue(queue);
+
+ when(mNetwork.performRequest(any(Request.class))).thenThrow(new ServerError());
+
+ mDispatcher.processRequest(mRequest);
+
+ InOrder inOrder = inOrder(listener);
+ inOrder.verify(listener)
+ .onRequestEvent(
+ mRequest, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+ inOrder.verify(listener)
+ .onRequestEvent(
+ mRequest, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void shouldCacheFalse() throws Exception {
+ mRequest.setShouldCache(false);
+ mDispatcher.processRequest(mRequest);
+ verify(mCache, never()).put(anyString(), any(Cache.Entry.class));
+ }
+
+ @Test
+ public void shouldCacheTrue() throws Exception {
+ when(mNetwork.performRequest(any(Request.class)))
+ .thenReturn(new NetworkResponse(CANNED_DATA));
+ mRequest.setShouldCache(true);
+ mDispatcher.processRequest(mRequest);
+ ArgumentCaptor<Cache.Entry> entry = ArgumentCaptor.forClass(Cache.Entry.class);
+ verify(mCache).put(eq(mRequest.getCacheKey()), entry.capture());
+ assertTrue(Arrays.equals(entry.getValue().data, CANNED_DATA));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/NetworkResponseTest.java b/core/src/test/java/com/android/volley/NetworkResponseTest.java
new file mode 100644
index 0000000..70210da
--- /dev/null
+++ b/core/src/test/java/com/android/volley/NetworkResponseTest.java
@@ -0,0 +1,61 @@
+package com.android.volley;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class NetworkResponseTest {
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void mapToList() {
+ Map<String, String> headers = new HashMap<>();
+ headers.put("key1", "value1");
+ headers.put("key2", "value2");
+
+ NetworkResponse resp = new NetworkResponse(200, null, headers, false);
+
+ List<Header> expectedHeaders = new ArrayList<>();
+ expectedHeaders.add(new Header("key1", "value1"));
+ expectedHeaders.add(new Header("key2", "value2"));
+
+ assertThat(expectedHeaders, containsInAnyOrder(resp.allHeaders.toArray(new Header[0])));
+ }
+
+ @Test
+ public void listToMap() {
+ List<Header> headers = new ArrayList<>();
+ headers.add(new Header("key1", "value1"));
+ // Later values should be preferred.
+ headers.add(new Header("key2", "ignoredvalue"));
+ headers.add(new Header("key2", "value2"));
+
+ NetworkResponse resp = new NetworkResponse(200, null, false, 0L, headers);
+
+ Map<String, String> expectedHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ expectedHeaders.put("key1", "value1");
+ expectedHeaders.put("key2", "value2");
+
+ assertEquals(expectedHeaders, resp.headers);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void nullValuesDontCrash() {
+ new NetworkResponse(null);
+ new NetworkResponse(null, null);
+ new NetworkResponse(200, null, null, false);
+ new NetworkResponse(200, null, null, false, 0L);
+ new NetworkResponse(200, null, false, 0L, null);
+ }
+}
diff --git a/core/src/test/java/com/android/volley/RequestQueueIntegrationTest.java b/core/src/test/java/com/android/volley/RequestQueueIntegrationTest.java
new file mode 100644
index 0000000..a2bfbc6
--- /dev/null
+++ b/core/src/test/java/com/android/volley/RequestQueueIntegrationTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.Request.Priority;
+import com.android.volley.RequestQueue.RequestFinishedListener;
+import com.android.volley.mock.MockRequest;
+import com.android.volley.mock.ShadowSystemClock;
+import com.android.volley.toolbox.NoCache;
+import com.android.volley.utils.ImmediateResponseDelivery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/**
+ * Integration tests for {@link RequestQueue} that verify its behavior in conjunction with real
+ * dispatcher, queues and Requests.
+ *
+ * <p>The Network is mocked out.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowSystemClock.class})
+public class RequestQueueIntegrationTest {
+
+ private ResponseDelivery mDelivery;
+ @Mock private Network mMockNetwork;
+ @Mock private RequestFinishedListener<byte[]> mMockListener;
+ @Mock private RequestFinishedListener<byte[]> mMockListener2;
+
+ @Before
+ public void setUp() throws Exception {
+ mDelivery = new ImmediateResponseDelivery();
+ initMocks(this);
+ }
+
+ @Test
+ public void add_requestProcessedInCorrectOrder() throws Exception {
+ // Enqueue 2 requests with different cache keys, and different priorities. The second,
+ // higher priority request takes 20ms.
+ // Assert that the first request is only handled after the first one has been parsed and
+ // delivered.
+ MockRequest lowerPriorityReq = new MockRequest();
+ MockRequest higherPriorityReq = new MockRequest();
+ lowerPriorityReq.setCacheKey("1");
+ higherPriorityReq.setCacheKey("2");
+ lowerPriorityReq.setPriority(Priority.LOW);
+ higherPriorityReq.setPriority(Priority.HIGH);
+
+ Answer<NetworkResponse> delayAnswer =
+ new Answer<NetworkResponse>() {
+ @Override
+ public NetworkResponse answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ Thread.sleep(20);
+ return mock(NetworkResponse.class);
+ }
+ };
+ // delay only for higher request
+ when(mMockNetwork.performRequest(higherPriorityReq)).thenAnswer(delayAnswer);
+ when(mMockNetwork.performRequest(lowerPriorityReq)).thenReturn(mock(NetworkResponse.class));
+
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 1, mDelivery);
+ queue.addRequestFinishedListener(mMockListener);
+ queue.add(lowerPriorityReq);
+ queue.add(higherPriorityReq);
+ queue.start();
+
+ InOrder inOrder = inOrder(mMockListener);
+ // verify higherPriorityReq goes through first
+ inOrder.verify(mMockListener, timeout(10000)).onRequestFinished(higherPriorityReq);
+ // verify lowerPriorityReq goes last
+ inOrder.verify(mMockListener, timeout(10000)).onRequestFinished(lowerPriorityReq);
+
+ queue.stop();
+ }
+
+ /** Asserts that requests with same cache key are processed in order. */
+ @Test
+ public void add_dedupeByCacheKey() throws Exception {
+ // Enqueue 2 requests with the same cache key. The first request takes 20ms. Assert that the
+ // second request is only handled after the first one has been parsed and delivered.
+ MockRequest req1 = new MockRequest();
+ MockRequest req2 = new MockRequest();
+ Answer<NetworkResponse> delayAnswer =
+ new Answer<NetworkResponse>() {
+ @Override
+ public NetworkResponse answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ Thread.sleep(20);
+ return mock(NetworkResponse.class);
+ }
+ };
+ // delay only for first
+ when(mMockNetwork.performRequest(req1)).thenAnswer(delayAnswer);
+ when(mMockNetwork.performRequest(req2)).thenReturn(mock(NetworkResponse.class));
+
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 3, mDelivery);
+ queue.addRequestFinishedListener(mMockListener);
+ queue.add(req1);
+ queue.add(req2);
+ queue.start();
+
+ InOrder inOrder = inOrder(mMockListener);
+ // verify req1 goes through first
+ inOrder.verify(mMockListener, timeout(10000)).onRequestFinished(req1);
+ // verify req2 goes last
+ inOrder.verify(mMockListener, timeout(10000)).onRequestFinished(req2);
+
+ queue.stop();
+ }
+
+ /** Verify RequestFinishedListeners are informed when requests are canceled. */
+ @Test
+ public void add_requestFinishedListenerCanceled() throws Exception {
+ MockRequest request = new MockRequest();
+ Answer<NetworkResponse> delayAnswer =
+ new Answer<NetworkResponse>() {
+ @Override
+ public NetworkResponse answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ Thread.sleep(200);
+ return mock(NetworkResponse.class);
+ }
+ };
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 1, mDelivery);
+
+ when(mMockNetwork.performRequest(request)).thenAnswer(delayAnswer);
+
+ queue.addRequestFinishedListener(mMockListener);
+ queue.start();
+ queue.add(request);
+
+ request.cancel();
+ verify(mMockListener, timeout(10000)).onRequestFinished(request);
+ queue.stop();
+ }
+
+ /** Verify RequestFinishedListeners are informed when requests are successfully delivered. */
+ @Test
+ public void add_requestFinishedListenerSuccess() throws Exception {
+ MockRequest request = new MockRequest();
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 1, mDelivery);
+
+ queue.addRequestFinishedListener(mMockListener);
+ queue.addRequestFinishedListener(mMockListener2);
+ queue.start();
+ queue.add(request);
+
+ verify(mMockListener, timeout(10000)).onRequestFinished(request);
+ verify(mMockListener2, timeout(10000)).onRequestFinished(request);
+
+ queue.stop();
+ }
+
+ /** Verify RequestFinishedListeners are informed when request errors. */
+ @Test
+ public void add_requestFinishedListenerError() throws Exception {
+ MockRequest request = new MockRequest();
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 1, mDelivery);
+
+ when(mMockNetwork.performRequest(request)).thenThrow(new VolleyError());
+
+ queue.addRequestFinishedListener(mMockListener);
+ queue.start();
+ queue.add(request);
+
+ verify(mMockListener, timeout(10000)).onRequestFinished(request);
+ queue.stop();
+ }
+}
diff --git a/core/src/test/java/com/android/volley/RequestQueueTest.java b/core/src/test/java/com/android/volley/RequestQueueTest.java
new file mode 100644
index 0000000..ba9b0f8
--- /dev/null
+++ b/core/src/test/java/com/android/volley/RequestQueueTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.mock.ShadowSystemClock;
+import com.android.volley.toolbox.NoCache;
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.utils.ImmediateResponseDelivery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Unit tests for RequestQueue, with all dependencies mocked out */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowSystemClock.class})
+public class RequestQueueTest {
+
+ private ResponseDelivery mDelivery;
+ @Mock private Network mMockNetwork;
+
+ @Before
+ public void setUp() throws Exception {
+ mDelivery = new ImmediateResponseDelivery();
+ initMocks(this);
+ }
+
+ @Test
+ public void cancelAll_onlyCorrectTag() throws Exception {
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 0, mDelivery);
+ Object tagA = new Object();
+ Object tagB = new Object();
+ StringRequest req1 = mock(StringRequest.class);
+ when(req1.getTag()).thenReturn(tagA);
+ StringRequest req2 = mock(StringRequest.class);
+ when(req2.getTag()).thenReturn(tagB);
+ StringRequest req3 = mock(StringRequest.class);
+ when(req3.getTag()).thenReturn(tagA);
+ StringRequest req4 = mock(StringRequest.class);
+ when(req4.getTag()).thenReturn(tagA);
+
+ queue.add(req1); // A
+ queue.add(req2); // B
+ queue.add(req3); // A
+ queue.cancelAll(tagA);
+ queue.add(req4); // A
+
+ verify(req1).cancel(); // A cancelled
+ verify(req3).cancel(); // A cancelled
+ verify(req2, never()).cancel(); // B not cancelled
+ verify(req4, never()).cancel(); // A added after cancel not cancelled
+ }
+
+ @Test
+ public void add_notifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 0, mDelivery);
+ queue.addRequestEventListener(listener);
+ StringRequest req = mock(StringRequest.class);
+
+ queue.add(req);
+
+ verify(listener).onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_QUEUED);
+ verifyNoMoreInteractions(listener);
+ }
+
+ @Test
+ public void finish_notifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 0, mDelivery);
+ queue.addRequestEventListener(listener);
+ StringRequest req = mock(StringRequest.class);
+
+ queue.finish(req);
+
+ verify(listener).onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_FINISHED);
+ verifyNoMoreInteractions(listener);
+ }
+
+ @Test
+ public void sendRequestEvent_notifiesListener() throws Exception {
+ StringRequest req = mock(StringRequest.class);
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 0, mDelivery);
+ queue.addRequestEventListener(listener);
+
+ queue.sendRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+
+ verify(listener)
+ .onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+ verifyNoMoreInteractions(listener);
+ }
+
+ @Test
+ public void removeRequestEventListener_removesListener() throws Exception {
+ StringRequest req = mock(StringRequest.class);
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 0, mDelivery);
+ queue.addRequestEventListener(listener);
+ queue.removeRequestEventListener(listener);
+
+ queue.sendRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+
+ verifyNoMoreInteractions(listener);
+ }
+}
diff --git a/core/src/test/java/com/android/volley/RequestTest.java b/core/src/test/java/com/android/volley/RequestTest.java
new file mode 100644
index 0000000..cced39f
--- /dev/null
+++ b/core/src/test/java/com/android/volley/RequestTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.Request.Method;
+import com.android.volley.Request.Priority;
+import com.android.volley.toolbox.NoCache;
+import java.util.Collections;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class RequestTest {
+ private @Mock ResponseDelivery mDelivery;
+ private @Mock Network mNetwork;
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ }
+
+ @Test
+ public void compareTo() {
+ int sequence = 0;
+ TestRequest low = new TestRequest(Priority.LOW);
+ low.setSequence(sequence++);
+ TestRequest low2 = new TestRequest(Priority.LOW);
+ low2.setSequence(sequence++);
+ TestRequest high = new TestRequest(Priority.HIGH);
+ high.setSequence(sequence++);
+ TestRequest immediate = new TestRequest(Priority.IMMEDIATE);
+ immediate.setSequence(sequence++);
+
+ // "Low" should sort higher because it's really processing order.
+ assertTrue(low.compareTo(high) > 0);
+ assertTrue(high.compareTo(low) < 0);
+ assertTrue(low.compareTo(low2) < 0);
+ assertTrue(low.compareTo(immediate) > 0);
+ assertTrue(immediate.compareTo(high) < 0);
+ }
+
+ private static class TestRequest extends Request<Object> {
+ private Priority mPriority = Priority.NORMAL;
+
+ public TestRequest(Priority priority) {
+ super(Request.Method.GET, "", null);
+ mPriority = priority;
+ }
+
+ @Override
+ public Priority getPriority() {
+ return mPriority;
+ }
+
+ @Override
+ protected void deliverResponse(Object response) {}
+
+ @Override
+ protected Response<Object> parseNetworkResponse(NetworkResponse response) {
+ return null;
+ }
+ }
+
+ @Test
+ public void urlParsing() {
+ UrlParseRequest nullUrl = new UrlParseRequest(null);
+ assertEquals(0, nullUrl.getTrafficStatsTag());
+ UrlParseRequest emptyUrl = new UrlParseRequest("");
+ assertEquals(0, emptyUrl.getTrafficStatsTag());
+ UrlParseRequest noHost = new UrlParseRequest("http:///");
+ assertEquals(0, noHost.getTrafficStatsTag());
+ UrlParseRequest badProtocol = new UrlParseRequest("bad:http://foo");
+ assertEquals(0, badProtocol.getTrafficStatsTag());
+ UrlParseRequest goodProtocol = new UrlParseRequest("http://foo");
+ assertFalse(0 == goodProtocol.getTrafficStatsTag());
+ }
+
+ @Test
+ public void getCacheKey() {
+ assertEquals(
+ "http://example.com",
+ new UrlParseRequest(Method.GET, "http://example.com").getCacheKey());
+ assertEquals(
+ "http://example.com",
+ new UrlParseRequest(Method.DEPRECATED_GET_OR_POST, "http://example.com")
+ .getCacheKey());
+ assertEquals(
+ "1-http://example.com",
+ new UrlParseRequest(Method.POST, "http://example.com").getCacheKey());
+ assertEquals(
+ "2-http://example.com",
+ new UrlParseRequest(Method.PUT, "http://example.com").getCacheKey());
+ }
+
+ private static class UrlParseRequest extends Request<Object> {
+ UrlParseRequest(String url) {
+ this(Method.GET, url);
+ }
+
+ UrlParseRequest(int method, String url) {
+ super(method, url, null);
+ }
+
+ @Override
+ protected void deliverResponse(Object response) {}
+
+ @Override
+ protected Response<Object> parseNetworkResponse(NetworkResponse response) {
+ return null;
+ }
+ }
+
+ @Test
+ public void nullKeyInPostParams() throws Exception {
+ Request<Object> request =
+ new Request<Object>(Method.POST, "url", null) {
+ @Override
+ protected void deliverResponse(Object response) {}
+
+ @Override
+ protected Response<Object> parseNetworkResponse(NetworkResponse response) {
+ return null;
+ }
+
+ @Override
+ protected Map<String, String> getParams() {
+ return Collections.singletonMap(null, "value");
+ }
+
+ @Override
+ protected Map<String, String> getPostParams() {
+ return Collections.singletonMap(null, "value");
+ }
+ };
+ try {
+ request.getBody();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ request.getPostBody();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void nullValueInPostParams() throws Exception {
+ Request<Object> request =
+ new Request<Object>(Method.POST, "url", null) {
+ @Override
+ protected void deliverResponse(Object response) {}
+
+ @Override
+ protected Response<Object> parseNetworkResponse(NetworkResponse response) {
+ return null;
+ }
+
+ @Override
+ protected Map<String, String> getParams() {
+ return Collections.singletonMap("key", null);
+ }
+
+ @Override
+ protected Map<String, String> getPostParams() {
+ return Collections.singletonMap("key", null);
+ }
+ };
+ try {
+ request.getBody();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ request.getPostBody();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void sendEvent_notifiesListeners() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ RequestQueue queue = new RequestQueue(new NoCache(), mNetwork, 0, mDelivery);
+ queue.addRequestEventListener(listener);
+
+ Request<Object> request =
+ new Request<Object>(Method.POST, "url", null) {
+ @Override
+ protected void deliverResponse(Object response) {}
+
+ @Override
+ protected Response<Object> parseNetworkResponse(NetworkResponse response) {
+ return null;
+ }
+ };
+ request.setRequestQueue(queue);
+
+ request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+
+ verify(listener)
+ .onRequestEvent(
+ request, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
+ verifyNoMoreInteractions(listener);
+ }
+}
diff --git a/core/src/test/java/com/android/volley/ResponseDeliveryTest.java b/core/src/test/java/com/android/volley/ResponseDeliveryTest.java
new file mode 100644
index 0000000..6e71c3b
--- /dev/null
+++ b/core/src/test/java/com/android/volley/ResponseDeliveryTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.volley.mock.MockRequest;
+import com.android.volley.utils.CacheTestUtils;
+import com.android.volley.utils.ImmediateResponseDelivery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ResponseDeliveryTest {
+
+ private ExecutorDelivery mDelivery;
+ private MockRequest mRequest;
+ private Response<byte[]> mSuccessResponse;
+
+ @Before
+ public void setUp() throws Exception {
+ // Make the delivery just run its posted responses immediately.
+ mDelivery = new ImmediateResponseDelivery();
+ mRequest = new MockRequest();
+ mRequest.setSequence(1);
+ byte[] data = new byte[16];
+ Cache.Entry cacheEntry = CacheTestUtils.makeRandomCacheEntry(data);
+ mSuccessResponse = Response.success(data, cacheEntry);
+ }
+
+ @Test
+ public void postResponseCallsDeliverResponse() {
+ mDelivery.postResponse(mRequest, mSuccessResponse);
+ assertTrue(mRequest.deliverResponse_called);
+ assertFalse(mRequest.deliverError_called);
+ }
+
+ @Test
+ public void postResponseSuppressesCanceled() {
+ mRequest.cancel();
+ mDelivery.postResponse(mRequest, mSuccessResponse);
+ assertFalse(mRequest.deliverResponse_called);
+ assertFalse(mRequest.deliverError_called);
+ }
+
+ @Test
+ public void postErrorCallsDeliverError() {
+ Response<byte[]> errorResponse = Response.error(new ServerError());
+
+ mDelivery.postResponse(mRequest, errorResponse);
+ assertTrue(mRequest.deliverError_called);
+ assertFalse(mRequest.deliverResponse_called);
+ }
+}
diff --git a/core/src/test/java/com/android/volley/mock/MockAsyncStack.java b/core/src/test/java/com/android/volley/mock/MockAsyncStack.java
new file mode 100644
index 0000000..5ea8343
--- /dev/null
+++ b/core/src/test/java/com/android/volley/mock/MockAsyncStack.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.mock;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.toolbox.AsyncHttpStack;
+import com.android.volley.toolbox.HttpResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MockAsyncStack extends AsyncHttpStack {
+
+ private HttpResponse mResponseToReturn;
+
+ private IOException mExceptionToThrow;
+
+ private String mLastUrl;
+
+ private Map<String, String> mLastHeaders;
+
+ private byte[] mLastPostBody;
+
+ public String getLastUrl() {
+ return mLastUrl;
+ }
+
+ public Map<String, String> getLastHeaders() {
+ return mLastHeaders;
+ }
+
+ public byte[] getLastPostBody() {
+ return mLastPostBody;
+ }
+
+ public void setResponseToReturn(HttpResponse response) {
+ mResponseToReturn = response;
+ }
+
+ public void setExceptionToThrow(IOException exception) {
+ mExceptionToThrow = exception;
+ }
+
+ @Override
+ public void executeRequest(
+ Request<?> request, Map<String, String> additionalHeaders, OnRequestComplete callback) {
+ if (mExceptionToThrow != null) {
+ callback.onError(mExceptionToThrow);
+ return;
+ }
+ mLastUrl = request.getUrl();
+ mLastHeaders = new HashMap<>();
+ try {
+ if (request.getHeaders() != null) {
+ mLastHeaders.putAll(request.getHeaders());
+ }
+ } catch (AuthFailureError authFailureError) {
+ callback.onAuthError(authFailureError);
+ return;
+ }
+ if (additionalHeaders != null) {
+ mLastHeaders.putAll(additionalHeaders);
+ }
+ try {
+ mLastPostBody = request.getBody();
+ } catch (AuthFailureError e) {
+ mLastPostBody = null;
+ }
+ callback.onSuccess(mResponseToReturn);
+ }
+}
diff --git a/core/src/test/java/com/android/volley/mock/MockHttpStack.java b/core/src/test/java/com/android/volley/mock/MockHttpStack.java
new file mode 100644
index 0000000..b86e7a0
--- /dev/null
+++ b/core/src/test/java/com/android/volley/mock/MockHttpStack.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.mock;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.toolbox.BaseHttpStack;
+import com.android.volley.toolbox.HttpResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MockHttpStack extends BaseHttpStack {
+
+ private HttpResponse mResponseToReturn;
+
+ private IOException mExceptionToThrow;
+
+ private String mLastUrl;
+
+ private Map<String, String> mLastHeaders;
+
+ private byte[] mLastPostBody;
+
+ public String getLastUrl() {
+ return mLastUrl;
+ }
+
+ public Map<String, String> getLastHeaders() {
+ return mLastHeaders;
+ }
+
+ public byte[] getLastPostBody() {
+ return mLastPostBody;
+ }
+
+ public void setResponseToReturn(HttpResponse response) {
+ mResponseToReturn = response;
+ }
+
+ public void setExceptionToThrow(IOException exception) {
+ mExceptionToThrow = exception;
+ }
+
+ @Override
+ public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
+ throws IOException, AuthFailureError {
+ if (mExceptionToThrow != null) {
+ throw mExceptionToThrow;
+ }
+ mLastUrl = request.getUrl();
+ mLastHeaders = new HashMap<>();
+ if (request.getHeaders() != null) {
+ mLastHeaders.putAll(request.getHeaders());
+ }
+ if (additionalHeaders != null) {
+ mLastHeaders.putAll(additionalHeaders);
+ }
+ try {
+ mLastPostBody = request.getBody();
+ } catch (AuthFailureError e) {
+ mLastPostBody = null;
+ }
+ return mResponseToReturn;
+ }
+}
diff --git a/core/src/test/java/com/android/volley/mock/MockRequest.java b/core/src/test/java/com/android/volley/mock/MockRequest.java
new file mode 100644
index 0000000..6fc26b4
--- /dev/null
+++ b/core/src/test/java/com/android/volley/mock/MockRequest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.mock;
+
+import com.android.volley.NetworkResponse;
+import com.android.volley.Request;
+import com.android.volley.Response;
+import com.android.volley.Response.ErrorListener;
+import com.android.volley.VolleyError;
+import com.android.volley.utils.CacheTestUtils;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MockRequest extends Request<byte[]> {
+ public MockRequest() {
+ super(Request.Method.GET, "http://foo.com", null);
+ }
+
+ public MockRequest(String url, ErrorListener listener) {
+ super(Request.Method.GET, url, listener);
+ }
+
+ private Map<String, String> mPostParams = new HashMap<String, String>();
+
+ public void setPostParams(Map<String, String> postParams) {
+ mPostParams = postParams;
+ }
+
+ @Override
+ public Map<String, String> getPostParams() {
+ return mPostParams;
+ }
+
+ private String mCacheKey = super.getCacheKey();
+
+ public void setCacheKey(String cacheKey) {
+ mCacheKey = cacheKey;
+ }
+
+ @Override
+ public String getCacheKey() {
+ return mCacheKey;
+ }
+
+ public boolean deliverResponse_called = false;
+ public boolean parseResponse_called = false;
+
+ @Override
+ protected void deliverResponse(byte[] response) {
+ deliverResponse_called = true;
+ }
+
+ public boolean deliverError_called = false;
+
+ @Override
+ public void deliverError(VolleyError error) {
+ super.deliverError(error);
+ deliverError_called = true;
+ }
+
+ public boolean cancel_called = false;
+
+ @Override
+ public void cancel() {
+ cancel_called = true;
+ super.cancel();
+ }
+
+ private Priority mPriority = super.getPriority();
+
+ public void setPriority(Priority priority) {
+ mPriority = priority;
+ }
+
+ @Override
+ public Priority getPriority() {
+ return mPriority;
+ }
+
+ @Override
+ protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {
+ parseResponse_called = true;
+ return Response.success(response.data, CacheTestUtils.makeRandomCacheEntry(response.data));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/mock/ShadowSystemClock.java b/core/src/test/java/com/android/volley/mock/ShadowSystemClock.java
new file mode 100644
index 0000000..6d75d4b
--- /dev/null
+++ b/core/src/test/java/com/android/volley/mock/ShadowSystemClock.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.mock;
+
+import android.os.SystemClock;
+import org.robolectric.annotation.Implements;
+
+@Implements(value = SystemClock.class, callThroughByDefault = true)
+public class ShadowSystemClock {
+ public static long elapsedRealtime() {
+ return 0;
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/AdaptedHttpStackTest.java b/core/src/test/java/com/android/volley/toolbox/AdaptedHttpStackTest.java
new file mode 100644
index 0000000..dbd6535
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/AdaptedHttpStackTest.java
@@ -0,0 +1,128 @@
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.when;
+
+import com.android.volley.Header;
+import com.android.volley.Request;
+import com.android.volley.mock.TestRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.message.BasicHeader;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AdaptedHttpStackTest {
+ private static final Request<?> REQUEST = new TestRequest.Get();
+ private static final Map<String, String> ADDITIONAL_HEADERS = Collections.emptyMap();
+
+ @Mock private HttpStack mHttpStack;
+ @Mock private HttpResponse mHttpResponse;
+ @Mock private StatusLine mStatusLine;
+ @Mock private HttpEntity mHttpEntity;
+ @Mock private InputStream mContent;
+
+ private AdaptedHttpStack mAdaptedHttpStack;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mAdaptedHttpStack = new AdaptedHttpStack(mHttpStack);
+ when(mHttpResponse.getStatusLine()).thenReturn(mStatusLine);
+ }
+
+ @Test(expected = SocketTimeoutException.class)
+ public void requestTimeout() throws Exception {
+ when(mHttpStack.performRequest(REQUEST, ADDITIONAL_HEADERS))
+ .thenThrow(new ConnectTimeoutException());
+
+ mAdaptedHttpStack.executeRequest(REQUEST, ADDITIONAL_HEADERS);
+ }
+
+ @Test
+ public void emptyResponse() throws Exception {
+ when(mHttpStack.performRequest(REQUEST, ADDITIONAL_HEADERS)).thenReturn(mHttpResponse);
+ when(mStatusLine.getStatusCode()).thenReturn(12345);
+ when(mHttpResponse.getAllHeaders()).thenReturn(new org.apache.http.Header[0]);
+
+ com.android.volley.toolbox.HttpResponse response =
+ mAdaptedHttpStack.executeRequest(REQUEST, ADDITIONAL_HEADERS);
+
+ assertEquals(12345, response.getStatusCode());
+ assertEquals(Collections.emptyList(), response.getHeaders());
+ assertNull(response.getContent());
+ }
+
+ @Test
+ public void nonEmptyResponse() throws Exception {
+ when(mHttpStack.performRequest(REQUEST, ADDITIONAL_HEADERS)).thenReturn(mHttpResponse);
+ when(mStatusLine.getStatusCode()).thenReturn(12345);
+ when(mHttpResponse.getAllHeaders()).thenReturn(new org.apache.http.Header[0]);
+ when(mHttpResponse.getEntity()).thenReturn(mHttpEntity);
+ when(mHttpEntity.getContentLength()).thenReturn((long) Integer.MAX_VALUE);
+ when(mHttpEntity.getContent()).thenReturn(mContent);
+
+ com.android.volley.toolbox.HttpResponse response =
+ mAdaptedHttpStack.executeRequest(REQUEST, ADDITIONAL_HEADERS);
+
+ assertEquals(12345, response.getStatusCode());
+ assertEquals(Collections.emptyList(), response.getHeaders());
+ assertEquals(Integer.MAX_VALUE, response.getContentLength());
+ assertSame(mContent, response.getContent());
+ }
+
+ @Test(expected = IOException.class)
+ public void responseTooBig() throws Exception {
+ when(mHttpStack.performRequest(REQUEST, ADDITIONAL_HEADERS)).thenReturn(mHttpResponse);
+ when(mStatusLine.getStatusCode()).thenReturn(12345);
+ when(mHttpResponse.getAllHeaders()).thenReturn(new org.apache.http.Header[0]);
+ when(mHttpResponse.getEntity()).thenReturn(mHttpEntity);
+ when(mHttpEntity.getContentLength()).thenReturn(Integer.MAX_VALUE + 1L);
+ when(mHttpEntity.getContent()).thenReturn(mContent);
+
+ mAdaptedHttpStack.executeRequest(REQUEST, ADDITIONAL_HEADERS);
+ }
+
+ @Test
+ public void responseWithHeaders() throws Exception {
+ when(mHttpStack.performRequest(REQUEST, ADDITIONAL_HEADERS)).thenReturn(mHttpResponse);
+ when(mStatusLine.getStatusCode()).thenReturn(12345);
+ when(mHttpResponse.getAllHeaders())
+ .thenReturn(
+ new org.apache.http.Header[] {
+ new BasicHeader("header1", "value1_B"),
+ new BasicHeader("header3", "value3"),
+ new BasicHeader("HEADER2", "value2"),
+ new BasicHeader("header1", "value1_A")
+ });
+
+ com.android.volley.toolbox.HttpResponse response =
+ mAdaptedHttpStack.executeRequest(REQUEST, ADDITIONAL_HEADERS);
+
+ assertEquals(12345, response.getStatusCode());
+ assertNull(response.getContent());
+
+ List<Header> expectedHeaders = new ArrayList<>();
+ expectedHeaders.add(new Header("header1", "value1_B"));
+ expectedHeaders.add(new Header("header3", "value3"));
+ expectedHeaders.add(new Header("HEADER2", "value2"));
+ expectedHeaders.add(new Header("header1", "value1_A"));
+ assertEquals(expectedHeaders, response.getHeaders());
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/AndroidAuthenticatorTest.java b/core/src/test/java/com/android/volley/toolbox/AndroidAuthenticatorTest.java
new file mode 100644
index 0000000..982eda2
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/AndroidAuthenticatorTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import com.android.volley.AuthFailureError;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AndroidAuthenticatorTest {
+ @Mock private AccountManager mAccountManager;
+ @Mock private AccountManagerFuture<Bundle> mFuture;
+ private Account mAccount;
+ private AndroidAuthenticator mAuthenticator;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mAccount = new Account("coolperson", "cooltype");
+ mAuthenticator = new AndroidAuthenticator(mAccountManager, mAccount, "cooltype", false);
+ }
+
+ @Test(expected = AuthFailureError.class)
+ public void failedGetAuthToken() throws Exception {
+ when(mAccountManager.getAuthToken(mAccount, "cooltype", false, null, null))
+ .thenReturn(mFuture);
+ when(mFuture.getResult()).thenThrow(new AuthenticatorException("sadness!"));
+ mAuthenticator.getAuthToken();
+ }
+
+ @Test(expected = AuthFailureError.class)
+ public void resultContainsIntent() throws Exception {
+ Intent intent = new Intent();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+ when(mAccountManager.getAuthToken(mAccount, "cooltype", false, null, null))
+ .thenReturn(mFuture);
+ when(mFuture.getResult()).thenReturn(bundle);
+ when(mFuture.isDone()).thenReturn(true);
+ when(mFuture.isCancelled()).thenReturn(false);
+ mAuthenticator.getAuthToken();
+ }
+
+ @Test(expected = AuthFailureError.class)
+ public void missingAuthToken() throws Exception {
+ Bundle bundle = new Bundle();
+ when(mAccountManager.getAuthToken(mAccount, "cooltype", false, null, null))
+ .thenReturn(mFuture);
+ when(mFuture.getResult()).thenReturn(bundle);
+ when(mFuture.isDone()).thenReturn(true);
+ when(mFuture.isCancelled()).thenReturn(false);
+ mAuthenticator.getAuthToken();
+ }
+
+ @Test
+ public void invalidateAuthToken() throws Exception {
+ mAuthenticator.invalidateAuthToken("monkey");
+ verify(mAccountManager).invalidateAuthToken("cooltype", "monkey");
+ }
+
+ @Test
+ public void goodToken() throws Exception {
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_AUTHTOKEN, "monkey");
+ when(mAccountManager.getAuthToken(mAccount, "cooltype", false, null, null))
+ .thenReturn(mFuture);
+ when(mFuture.getResult()).thenReturn(bundle);
+ when(mFuture.isDone()).thenReturn(true);
+ when(mFuture.isCancelled()).thenReturn(false);
+ Assert.assertEquals("monkey", mAuthenticator.getAuthToken());
+ }
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ Context context = mock(Context.class);
+ new AndroidAuthenticator(context, mAccount, "cooltype");
+ new AndroidAuthenticator(context, mAccount, "cooltype", true);
+ Assert.assertSame(mAccount, mAuthenticator.getAccount());
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/BaseHttpStackTest.java b/core/src/test/java/com/android/volley/toolbox/BaseHttpStackTest.java
new file mode 100644
index 0000000..1049ad0
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/BaseHttpStackTest.java
@@ -0,0 +1,104 @@
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Header;
+import com.android.volley.Request;
+import com.android.volley.mock.TestRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class BaseHttpStackTest {
+ private static final Request<?> REQUEST = new TestRequest.Get();
+ private static final Map<String, String> ADDITIONAL_HEADERS = Collections.emptyMap();
+
+ @Mock private InputStream mContent;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void legacyRequestWithoutBody() throws Exception {
+ BaseHttpStack stack =
+ new BaseHttpStack() {
+ @Override
+ public HttpResponse executeRequest(
+ Request<?> request, Map<String, String> additionalHeaders)
+ throws IOException, AuthFailureError {
+ assertSame(REQUEST, request);
+ assertSame(ADDITIONAL_HEADERS, additionalHeaders);
+ return new HttpResponse(12345, Collections.<Header>emptyList());
+ }
+ };
+ org.apache.http.HttpResponse resp = stack.performRequest(REQUEST, ADDITIONAL_HEADERS);
+ assertEquals(12345, resp.getStatusLine().getStatusCode());
+ assertEquals(0, resp.getAllHeaders().length);
+ assertNull(resp.getEntity());
+ }
+
+ @Test
+ public void legacyResponseWithBody() throws Exception {
+ BaseHttpStack stack =
+ new BaseHttpStack() {
+ @Override
+ public HttpResponse executeRequest(
+ Request<?> request, Map<String, String> additionalHeaders)
+ throws IOException, AuthFailureError {
+ assertSame(REQUEST, request);
+ assertSame(ADDITIONAL_HEADERS, additionalHeaders);
+ return new HttpResponse(
+ 12345, Collections.<Header>emptyList(), 555, mContent);
+ }
+ };
+ org.apache.http.HttpResponse resp = stack.performRequest(REQUEST, ADDITIONAL_HEADERS);
+ assertEquals(12345, resp.getStatusLine().getStatusCode());
+ assertEquals(0, resp.getAllHeaders().length);
+ assertEquals(555L, resp.getEntity().getContentLength());
+ assertSame(mContent, resp.getEntity().getContent());
+ }
+
+ @Test
+ public void legacyResponseHeaders() throws Exception {
+ BaseHttpStack stack =
+ new BaseHttpStack() {
+ @Override
+ public HttpResponse executeRequest(
+ Request<?> request, Map<String, String> additionalHeaders)
+ throws IOException, AuthFailureError {
+ assertSame(REQUEST, request);
+ assertSame(ADDITIONAL_HEADERS, additionalHeaders);
+ List<Header> headers = new ArrayList<>();
+ headers.add(new Header("HeaderA", "ValueA"));
+ headers.add(new Header("HeaderB", "ValueB_1"));
+ headers.add(new Header("HeaderB", "ValueB_2"));
+ return new HttpResponse(12345, headers);
+ }
+ };
+ org.apache.http.HttpResponse resp = stack.performRequest(REQUEST, ADDITIONAL_HEADERS);
+ assertEquals(12345, resp.getStatusLine().getStatusCode());
+ assertEquals(3, resp.getAllHeaders().length);
+ assertEquals("HeaderA", resp.getAllHeaders()[0].getName());
+ assertEquals("ValueA", resp.getAllHeaders()[0].getValue());
+ assertEquals("HeaderB", resp.getAllHeaders()[1].getName());
+ assertEquals("ValueB_1", resp.getAllHeaders()[1].getValue());
+ assertEquals("HeaderB", resp.getAllHeaders()[2].getName());
+ assertEquals("ValueB_2", resp.getAllHeaders()[2].getValue());
+ assertNull(resp.getEntity());
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java b/core/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java
new file mode 100644
index 0000000..91d4062
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.AsyncNetwork;
+import com.android.volley.AuthFailureError;
+import com.android.volley.Cache.Entry;
+import com.android.volley.Header;
+import com.android.volley.NetworkResponse;
+import com.android.volley.NoConnectionError;
+import com.android.volley.Request;
+import com.android.volley.Response;
+import com.android.volley.RetryPolicy;
+import com.android.volley.ServerError;
+import com.android.volley.TimeoutError;
+import com.android.volley.VolleyError;
+import com.android.volley.mock.MockAsyncStack;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class BasicAsyncNetworkTest {
+
+ @Mock private RetryPolicy mMockRetryPolicy;
+ @Mock private AsyncNetwork.OnRequestComplete mockCallback;
+ private ExecutorService executor = MoreExecutors.newDirectExecutorService();
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ }
+
+ @Test
+ public void headersAndPostParams() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse =
+ new HttpResponse(
+ 200,
+ Collections.<Header>emptyList(),
+ "foobar".getBytes(StandardCharsets.UTF_8));
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.etag = "foobar";
+ entry.lastModified = 1503102002000L;
+ request.setCacheEntry(entry);
+ perform(request, httpNetwork).get();
+ assertEquals("foo", mockAsyncStack.getLastHeaders().get("requestheader"));
+ assertEquals("foobar", mockAsyncStack.getLastHeaders().get("If-None-Match"));
+ assertEquals(
+ "Sat, 19 Aug 2017 00:20:02 GMT",
+ mockAsyncStack.getLastHeaders().get("If-Modified-Since"));
+ assertEquals(
+ "requestpost=foo&",
+ new String(mockAsyncStack.getLastPostBody(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void headersAndPostParamsStream() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ ByteArrayInputStream stream = new ByteArrayInputStream("foobar".getBytes("UTF-8"));
+ HttpResponse fakeResponse =
+ new HttpResponse(200, Collections.<Header>emptyList(), 6, stream);
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.etag = "foobar";
+ entry.lastModified = 1503102002000L;
+ request.setCacheEntry(entry);
+ perform(request, httpNetwork).get();
+ assertEquals("foo", mockAsyncStack.getLastHeaders().get("requestheader"));
+ assertEquals("foobar", mockAsyncStack.getLastHeaders().get("If-None-Match"));
+ assertEquals(
+ "Sat, 19 Aug 2017 00:20:02 GMT",
+ mockAsyncStack.getLastHeaders().get("If-Modified-Since"));
+ assertEquals(
+ "requestpost=foo&",
+ new String(mockAsyncStack.getLastPostBody(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void notModified() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ List<Header> headers = new ArrayList<>();
+ headers.add(new Header("ServerKeyA", "ServerValueA"));
+ headers.add(new Header("ServerKeyB", "ServerValueB"));
+ headers.add(new Header("SharedKey", "ServerValueShared"));
+ headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
+ headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
+ HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers);
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.allResponseHeaders = new ArrayList<>();
+ entry.allResponseHeaders.add(new Header("CachedKeyA", "CachedValueA"));
+ entry.allResponseHeaders.add(new Header("CachedKeyB", "CachedValueB"));
+ entry.allResponseHeaders.add(new Header("SharedKey", "CachedValueShared"));
+ entry.allResponseHeaders.add(new Header("SHAREDCASEINSENSITIVEKEY", "CachedValueShared1"));
+ entry.allResponseHeaders.add(new Header("shAREDcaSEinSENSITIVEkeY", "CachedValueShared2"));
+ request.setCacheEntry(entry);
+ httpNetwork.performRequest(request, mockCallback);
+ NetworkResponse response = perform(request, httpNetwork).get();
+ List<Header> expectedHeaders = new ArrayList<>();
+ // Should have all server headers + cache headers that didn't show up in server response.
+ expectedHeaders.add(new Header("ServerKeyA", "ServerValueA"));
+ expectedHeaders.add(new Header("ServerKeyB", "ServerValueB"));
+ expectedHeaders.add(new Header("SharedKey", "ServerValueShared"));
+ expectedHeaders.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
+ expectedHeaders.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
+ expectedHeaders.add(new Header("CachedKeyA", "CachedValueA"));
+ expectedHeaders.add(new Header("CachedKeyB", "CachedValueB"));
+ assertThat(expectedHeaders, containsInAnyOrder(response.allHeaders.toArray(new Header[0])));
+ }
+
+ @Test
+ public void notModified_legacyCache() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ List<Header> headers = new ArrayList<>();
+ headers.add(new Header("ServerKeyA", "ServerValueA"));
+ headers.add(new Header("ServerKeyB", "ServerValueB"));
+ headers.add(new Header("SharedKey", "ServerValueShared"));
+ headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
+ headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
+ HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers);
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.responseHeaders = new HashMap<>();
+ entry.responseHeaders.put("CachedKeyA", "CachedValueA");
+ entry.responseHeaders.put("CachedKeyB", "CachedValueB");
+ entry.responseHeaders.put("SharedKey", "CachedValueShared");
+ entry.responseHeaders.put("SHAREDCASEINSENSITIVEKEY", "CachedValueShared1");
+ entry.responseHeaders.put("shAREDcaSEinSENSITIVEkeY", "CachedValueShared2");
+ request.setCacheEntry(entry);
+ NetworkResponse response = perform(request, httpNetwork).get();
+ List<Header> expectedHeaders = new ArrayList<>();
+ // Should have all server headers + cache headers that didn't show up in server response.
+ expectedHeaders.add(new Header("ServerKeyA", "ServerValueA"));
+ expectedHeaders.add(new Header("ServerKeyB", "ServerValueB"));
+ expectedHeaders.add(new Header("SharedKey", "ServerValueShared"));
+ expectedHeaders.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
+ expectedHeaders.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
+ expectedHeaders.add(new Header("CachedKeyA", "CachedValueA"));
+ expectedHeaders.add(new Header("CachedKeyB", "CachedValueB"));
+ assertThat(expectedHeaders, containsInAnyOrder(response.allHeaders.toArray(new Header[0])));
+ }
+
+ @Test
+ public void socketTimeout() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new SocketTimeoutException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry socket timeouts
+ verify(mMockRetryPolicy).retry(any(TimeoutError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void noConnectionDefault() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new IOException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should not retry when there is no connection
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void noConnectionRetry() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new IOException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ request.setShouldRetryConnectionErrors(true);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry when there is no connection
+ verify(mMockRetryPolicy).retry(any(NoConnectionError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void noConnectionNoRetry() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new IOException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ request.setShouldRetryConnectionErrors(false);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should not retry when there is no connection
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void unauthorized() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(401, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry in case it's an auth failure.
+ verify(mMockRetryPolicy).retry(any(AuthFailureError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void malformedUrlRequest() throws VolleyError, ExecutionException, InterruptedException {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ mockAsyncStack.setExceptionToThrow(new MalformedURLException());
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ perform(request, httpNetwork).get();
+ }
+
+ @Test
+ public void forbidden() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(403, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry in case it's an auth failure.
+ verify(mMockRetryPolicy).retry(any(AuthFailureError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void redirect() throws Exception {
+ for (int i = 300; i <= 399; i++) {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ if (i != 304) {
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ } else {
+ verify(mockCallback, never()).onError(any(VolleyError.class));
+ verify(mockCallback, times(1)).onSuccess(any(NetworkResponse.class));
+ }
+ // should not retry 300 responses.
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+ }
+
+ @Test
+ public void otherClientError() throws Exception {
+ for (int i = 400; i <= 499; i++) {
+ if (i == 401 || i == 403) {
+ // covered above.
+ continue;
+ }
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should not retry other 400 errors.
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+ }
+
+ @Test
+ public void serverError_enableRetries() throws Exception {
+ for (int i = 500; i <= 599; i++) {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork =
+ new BasicAsyncNetwork.Builder(mockAsyncStack)
+ .setPool(new ByteArrayPool(4096))
+ .build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ request.setShouldRetryServerErrors(true);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should retry all 500 errors
+ verify(mMockRetryPolicy).retry(any(ServerError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+ }
+
+ @Test
+ public void serverError_disableRetries() throws Exception {
+ for (int i = 500; i <= 599; i++) {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onError(any(VolleyError.class));
+ verify(mockCallback, never()).onSuccess(any(NetworkResponse.class));
+ // should not retry any 500 error w/ HTTP 500 retries turned off (the default).
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+ }
+
+ @Test
+ public void notModifiedShortCircuit() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ List<Header> headers = new ArrayList<>();
+ headers.add(new Header("ServerKeyA", "ServerValueA"));
+ headers.add(new Header("ServerKeyB", "ServerValueB"));
+ headers.add(new Header("SharedKey", "ServerValueShared"));
+ headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
+ headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
+ HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers);
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onSuccess(any(NetworkResponse.class));
+ verify(mockCallback, never()).onError(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test
+ public void performRequestSuccess() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse =
+ new HttpResponse(
+ 200,
+ Collections.<Header>emptyList(),
+ "foobar".getBytes(StandardCharsets.UTF_8));
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ httpNetwork.setBlockingExecutor(executor);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.etag = "foobar";
+ entry.lastModified = 1503102002000L;
+ request.setCacheEntry(entry);
+ httpNetwork.performRequest(request, mockCallback);
+ verify(mockCallback, times(1)).onSuccess(any(NetworkResponse.class));
+ verify(mockCallback, never()).onError(any(VolleyError.class));
+ reset(mMockRetryPolicy, mockCallback);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void performRequestNeverSetExecutorTest() throws Exception {
+ MockAsyncStack mockAsyncStack = new MockAsyncStack();
+ HttpResponse fakeResponse = new HttpResponse(200, Collections.<Header>emptyList());
+ mockAsyncStack.setResponseToReturn(fakeResponse);
+ BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build();
+ Request<String> request = buildRequest();
+ perform(request, httpNetwork).get();
+ }
+
+ /** Helper functions */
+ private CompletableFuture<NetworkResponse> perform(Request<?> request, AsyncNetwork network)
+ throws VolleyError {
+ final CompletableFuture<NetworkResponse> future = new CompletableFuture<>();
+ network.performRequest(
+ request,
+ new AsyncNetwork.OnRequestComplete() {
+ @Override
+ public void onSuccess(NetworkResponse networkResponse) {
+ future.complete(networkResponse);
+ }
+
+ @Override
+ public void onError(VolleyError volleyError) {
+ future.complete(null);
+ }
+ });
+ return future;
+ }
+
+ private static Request<String> buildRequest() {
+ return new Request<String>(Request.Method.GET, "http://foo", null) {
+
+ @Override
+ protected Response<String> parseNetworkResponse(NetworkResponse response) {
+ return null;
+ }
+
+ @Override
+ protected void deliverResponse(String response) {}
+
+ @Override
+ public Map<String, String> getHeaders() {
+ Map<String, String> result = new HashMap<String, String>();
+ result.put("requestheader", "foo");
+ return result;
+ }
+
+ @Override
+ public Map<String, String> getParams() {
+ Map<String, String> result = new HashMap<String, String>();
+ result.put("requestpost", "foo");
+ return result;
+ }
+ };
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java b/core/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java
new file mode 100644
index 0000000..3630379
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Cache.Entry;
+import com.android.volley.Header;
+import com.android.volley.NetworkResponse;
+import com.android.volley.NoConnectionError;
+import com.android.volley.Request;
+import com.android.volley.Response;
+import com.android.volley.RetryPolicy;
+import com.android.volley.ServerError;
+import com.android.volley.TimeoutError;
+import com.android.volley.VolleyError;
+import com.android.volley.mock.MockHttpStack;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class BasicNetworkTest {
+
+ @Mock private Request<String> mMockRequest;
+ @Mock private RetryPolicy mMockRetryPolicy;
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+ }
+
+ @Test
+ public void headersAndPostParams() throws Exception {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ InputStream responseStream =
+ new ByteArrayInputStream("foobar".getBytes(StandardCharsets.UTF_8));
+ HttpResponse fakeResponse =
+ new HttpResponse(200, Collections.<Header>emptyList(), 6, responseStream);
+ mockHttpStack.setResponseToReturn(fakeResponse);
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.etag = "foobar";
+ entry.lastModified = 1503102002000L;
+ request.setCacheEntry(entry);
+ httpNetwork.performRequest(request);
+ assertEquals("foo", mockHttpStack.getLastHeaders().get("requestheader"));
+ assertEquals("foobar", mockHttpStack.getLastHeaders().get("If-None-Match"));
+ assertEquals(
+ "Sat, 19 Aug 2017 00:20:02 GMT",
+ mockHttpStack.getLastHeaders().get("If-Modified-Since"));
+ assertEquals(
+ "requestpost=foo&",
+ new String(mockHttpStack.getLastPostBody(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void notModified() throws Exception {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ List<Header> headers = new ArrayList<>();
+ headers.add(new Header("ServerKeyA", "ServerValueA"));
+ headers.add(new Header("ServerKeyB", "ServerValueB"));
+ headers.add(new Header("SharedKey", "ServerValueShared"));
+ headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
+ headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
+ HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers);
+ mockHttpStack.setResponseToReturn(fakeResponse);
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.allResponseHeaders = new ArrayList<>();
+ entry.allResponseHeaders.add(new Header("CachedKeyA", "CachedValueA"));
+ entry.allResponseHeaders.add(new Header("CachedKeyB", "CachedValueB"));
+ entry.allResponseHeaders.add(new Header("SharedKey", "CachedValueShared"));
+ entry.allResponseHeaders.add(new Header("SHAREDCASEINSENSITIVEKEY", "CachedValueShared1"));
+ entry.allResponseHeaders.add(new Header("shAREDcaSEinSENSITIVEkeY", "CachedValueShared2"));
+ request.setCacheEntry(entry);
+ NetworkResponse response = httpNetwork.performRequest(request);
+ List<Header> expectedHeaders = new ArrayList<>();
+ // Should have all server headers + cache headers that didn't show up in server response.
+ expectedHeaders.add(new Header("ServerKeyA", "ServerValueA"));
+ expectedHeaders.add(new Header("ServerKeyB", "ServerValueB"));
+ expectedHeaders.add(new Header("SharedKey", "ServerValueShared"));
+ expectedHeaders.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
+ expectedHeaders.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
+ expectedHeaders.add(new Header("CachedKeyA", "CachedValueA"));
+ expectedHeaders.add(new Header("CachedKeyB", "CachedValueB"));
+ assertThat(expectedHeaders, containsInAnyOrder(response.allHeaders.toArray(new Header[0])));
+ }
+
+ @Test
+ public void notModified_legacyCache() throws Exception {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ List<Header> headers = new ArrayList<>();
+ headers.add(new Header("ServerKeyA", "ServerValueA"));
+ headers.add(new Header("ServerKeyB", "ServerValueB"));
+ headers.add(new Header("SharedKey", "ServerValueShared"));
+ headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
+ headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
+ HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers);
+ mockHttpStack.setResponseToReturn(fakeResponse);
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ Entry entry = new Entry();
+ entry.responseHeaders = new HashMap<>();
+ entry.responseHeaders.put("CachedKeyA", "CachedValueA");
+ entry.responseHeaders.put("CachedKeyB", "CachedValueB");
+ entry.responseHeaders.put("SharedKey", "CachedValueShared");
+ entry.responseHeaders.put("SHAREDCASEINSENSITIVEKEY", "CachedValueShared1");
+ entry.responseHeaders.put("shAREDcaSEinSENSITIVEkeY", "CachedValueShared2");
+ request.setCacheEntry(entry);
+ NetworkResponse response = httpNetwork.performRequest(request);
+ List<Header> expectedHeaders = new ArrayList<>();
+ // Should have all server headers + cache headers that didn't show up in server response.
+ expectedHeaders.add(new Header("ServerKeyA", "ServerValueA"));
+ expectedHeaders.add(new Header("ServerKeyB", "ServerValueB"));
+ expectedHeaders.add(new Header("SharedKey", "ServerValueShared"));
+ expectedHeaders.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1"));
+ expectedHeaders.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2"));
+ expectedHeaders.add(new Header("CachedKeyA", "CachedValueA"));
+ expectedHeaders.add(new Header("CachedKeyB", "CachedValueB"));
+ assertThat(expectedHeaders, containsInAnyOrder(response.allHeaders.toArray(new Header[0])));
+ }
+
+ @Test
+ public void socketTimeout() throws Exception {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ mockHttpStack.setExceptionToThrow(new SocketTimeoutException());
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should retry socket timeouts
+ verify(mMockRetryPolicy).retry(any(TimeoutError.class));
+ }
+
+ @Test
+ public void noConnectionDefault() throws Exception {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ mockHttpStack.setExceptionToThrow(new IOException());
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should not retry when there is no connection
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ }
+
+ @Test
+ public void noConnectionRetry() throws Exception {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ mockHttpStack.setExceptionToThrow(new IOException());
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ request.setShouldRetryConnectionErrors(true);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should retry when there is no connection
+ verify(mMockRetryPolicy).retry(any(NoConnectionError.class));
+ reset(mMockRetryPolicy);
+ }
+
+ @Test
+ public void noConnectionNoRetry() throws Exception {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ mockHttpStack.setExceptionToThrow(new IOException());
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ request.setShouldRetryConnectionErrors(false);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should not retry when there is no connection
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ }
+
+ @Test
+ public void unauthorized() throws Exception {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ HttpResponse fakeResponse = new HttpResponse(401, Collections.<Header>emptyList());
+ mockHttpStack.setResponseToReturn(fakeResponse);
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should retry in case it's an auth failure.
+ verify(mMockRetryPolicy).retry(any(AuthFailureError.class));
+ }
+
+ @Test
+ public void forbidden() throws Exception {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ HttpResponse fakeResponse = new HttpResponse(403, Collections.<Header>emptyList());
+ mockHttpStack.setResponseToReturn(fakeResponse);
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should retry in case it's an auth failure.
+ verify(mMockRetryPolicy).retry(any(AuthFailureError.class));
+ }
+
+ @Test
+ public void redirect() throws Exception {
+ for (int i = 300; i <= 399; i++) {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockHttpStack.setResponseToReturn(fakeResponse);
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should not retry 300 responses.
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy);
+ }
+ }
+
+ @Test
+ public void otherClientError() throws Exception {
+ for (int i = 400; i <= 499; i++) {
+ if (i == 401 || i == 403) {
+ // covered above.
+ continue;
+ }
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockHttpStack.setResponseToReturn(fakeResponse);
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should not retry other 400 errors.
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy);
+ }
+ }
+
+ @Test
+ public void serverError_enableRetries() throws Exception {
+ for (int i = 500; i <= 599; i++) {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockHttpStack.setResponseToReturn(fakeResponse);
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack, new ByteArrayPool(4096));
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ request.setShouldRetryServerErrors(true);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should retry all 500 errors
+ verify(mMockRetryPolicy).retry(any(ServerError.class));
+ reset(mMockRetryPolicy);
+ }
+ }
+
+ @Test
+ public void serverError_disableRetries() throws Exception {
+ for (int i = 500; i <= 599; i++) {
+ MockHttpStack mockHttpStack = new MockHttpStack();
+ HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList());
+ mockHttpStack.setResponseToReturn(fakeResponse);
+ BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack);
+ Request<String> request = buildRequest();
+ request.setRetryPolicy(mMockRetryPolicy);
+ doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class));
+ try {
+ httpNetwork.performRequest(request);
+ } catch (VolleyError e) {
+ // expected
+ }
+ // should not retry any 500 error w/ HTTP 500 retries turned off (the default).
+ verify(mMockRetryPolicy, never()).retry(any(VolleyError.class));
+ reset(mMockRetryPolicy);
+ }
+ }
+
+ private static Request<String> buildRequest() {
+ return new Request<String>(Request.Method.GET, "http://foo", null) {
+
+ @Override
+ protected Response<String> parseNetworkResponse(NetworkResponse response) {
+ return null;
+ }
+
+ @Override
+ protected void deliverResponse(String response) {}
+
+ @Override
+ public Map<String, String> getHeaders() {
+ Map<String, String> result = new HashMap<String, String>();
+ result.put("requestheader", "foo");
+ return result;
+ }
+
+ @Override
+ public Map<String, String> getParams() {
+ Map<String, String> result = new HashMap<String, String>();
+ result.put("requestpost", "foo");
+ return result;
+ }
+ };
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/ByteArrayPoolTest.java b/core/src/test/java/com/android/volley/toolbox/ByteArrayPoolTest.java
new file mode 100644
index 0000000..62da207
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/ByteArrayPoolTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class ByteArrayPoolTest {
+ @Test
+ public void reusesBuffer() {
+ ByteArrayPool pool = new ByteArrayPool(32);
+
+ byte[] buf1 = pool.getBuf(16);
+ byte[] buf2 = pool.getBuf(16);
+
+ pool.returnBuf(buf1);
+ pool.returnBuf(buf2);
+
+ byte[] buf3 = pool.getBuf(16);
+ byte[] buf4 = pool.getBuf(16);
+ assertTrue(buf3 == buf1 || buf3 == buf2);
+ assertTrue(buf4 == buf1 || buf4 == buf2);
+ assertTrue(buf3 != buf4);
+ }
+
+ @Test
+ public void obeysSizeLimit() {
+ ByteArrayPool pool = new ByteArrayPool(32);
+
+ byte[] buf1 = pool.getBuf(16);
+ byte[] buf2 = pool.getBuf(16);
+ byte[] buf3 = pool.getBuf(16);
+
+ pool.returnBuf(buf1);
+ pool.returnBuf(buf2);
+ pool.returnBuf(buf3);
+
+ byte[] buf4 = pool.getBuf(16);
+ byte[] buf5 = pool.getBuf(16);
+ byte[] buf6 = pool.getBuf(16);
+
+ assertTrue(buf4 == buf2 || buf4 == buf3);
+ assertTrue(buf5 == buf2 || buf5 == buf3);
+ assertTrue(buf4 != buf5);
+ assertTrue(buf6 != buf1 && buf6 != buf2 && buf6 != buf3);
+ }
+
+ @Test
+ public void returnsBufferWithRightSize() {
+ ByteArrayPool pool = new ByteArrayPool(32);
+
+ byte[] buf1 = pool.getBuf(16);
+ pool.returnBuf(buf1);
+
+ byte[] buf2 = pool.getBuf(17);
+ assertNotSame(buf2, buf1);
+
+ byte[] buf3 = pool.getBuf(15);
+ assertSame(buf3, buf1);
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/CacheTest.java b/core/src/test/java/com/android/volley/toolbox/CacheTest.java
new file mode 100644
index 0000000..22dae22
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/CacheTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.volley.Cache;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class CacheTest {
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(Cache.class.getMethod("get", String.class));
+ assertNotNull(Cache.class.getMethod("put", String.class, Cache.Entry.class));
+ assertNotNull(Cache.class.getMethod("initialize"));
+ assertNotNull(Cache.class.getMethod("invalidate", String.class, boolean.class));
+ assertNotNull(Cache.class.getMethod("remove", String.class));
+ assertNotNull(Cache.class.getMethod("clear"));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java b/core/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
new file mode 100644
index 0000000..db6e491
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java
@@ -0,0 +1,646 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.hamcrest.Matchers.arrayWithSize;
+import static org.hamcrest.Matchers.emptyArray;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import com.android.volley.Cache;
+import com.android.volley.Header;
+import com.android.volley.toolbox.DiskBasedCache.CacheHeader;
+import com.android.volley.toolbox.DiskBasedCache.CountingInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = 16)
+public class DiskBasedCacheTest {
+
+ private static final int MAX_SIZE = 1024 * 1024;
+
+ private Cache cache;
+
+ @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Rule public ExpectedException exception = ExpectedException.none();
+
+ @Before
+ public void setup() throws IOException {
+ // Initialize empty cache
+ cache = new DiskBasedCache(temporaryFolder.getRoot(), MAX_SIZE);
+ cache.initialize();
+ }
+
+ @After
+ public void teardown() {
+ cache = null;
+ }
+
+ @Test
+ public void testEmptyInitialize() {
+ assertThat(cache.get("key"), is(nullValue()));
+ }
+
+ @Test
+ public void testPutGetZeroBytes() {
+ Cache.Entry entry = new Cache.Entry();
+ entry.data = new byte[0];
+ entry.serverDate = 1234567L;
+ entry.lastModified = 13572468L;
+ entry.ttl = 9876543L;
+ entry.softTtl = 8765432L;
+ entry.etag = "etag";
+ entry.responseHeaders = new HashMap<>();
+ entry.responseHeaders.put("fruit", "banana");
+ entry.responseHeaders.put("color", "yellow");
+ cache.put("my-magical-key", entry);
+
+ assertThatEntriesAreEqual(cache.get("my-magical-key"), entry);
+ assertThat(cache.get("unknown-key"), is(nullValue()));
+ }
+
+ @Test
+ public void testPutRemoveGet() {
+ Cache.Entry entry = randomData(511);
+ cache.put("key", entry);
+
+ assertThatEntriesAreEqual(cache.get("key"), entry);
+
+ cache.remove("key");
+ assertThat(cache.get("key"), is(nullValue()));
+ assertThat(listCachedFiles(), is(emptyArray()));
+ }
+
+ @Test
+ public void testPutClearGet() {
+ Cache.Entry entry = randomData(511);
+ cache.put("key", entry);
+
+ assertThatEntriesAreEqual(cache.get("key"), entry);
+
+ cache.clear();
+ assertThat(cache.get("key"), is(nullValue()));
+ assertThat(listCachedFiles(), is(emptyArray()));
+ }
+
+ @Test
+ public void testReinitialize() {
+ Cache.Entry entry = randomData(1023);
+ cache.put("key", entry);
+
+ Cache copy = new DiskBasedCache(temporaryFolder.getRoot(), MAX_SIZE);
+ copy.initialize();
+
+ assertThatEntriesAreEqual(copy.get("key"), entry);
+ }
+
+ @Test
+ public void testInvalidate() {
+ Cache.Entry entry = randomData(32);
+ entry.softTtl = 8765432L;
+ entry.ttl = 9876543L;
+ cache.put("key", entry);
+
+ cache.invalidate("key", false);
+ entry.softTtl = 0; // expired
+ assertThatEntriesAreEqual(cache.get("key"), entry);
+ }
+
+ @Test
+ public void testInvalidateFullExpire() {
+ Cache.Entry entry = randomData(32);
+ entry.softTtl = 8765432L;
+ entry.ttl = 9876543L;
+ cache.put("key", entry);
+
+ cache.invalidate("key", true);
+ entry.softTtl = 0; // expired
+ entry.ttl = 0; // expired
+ assertThatEntriesAreEqual(cache.get("key"), entry);
+ }
+
+ @Test
+ public void testTooLargeEntry() {
+ Cache.Entry entry = randomData(MAX_SIZE - getEntrySizeOnDisk("oversize"));
+ cache.put("oversize", entry);
+
+ assertThat(cache.get("oversize"), is(nullValue()));
+ }
+
+ @Test
+ public void testMaxSizeEntry() {
+ Cache.Entry entry = randomData(MAX_SIZE - getEntrySizeOnDisk("maxsize") - 1);
+ cache.put("maxsize", entry);
+
+ assertThatEntriesAreEqual(cache.get("maxsize"), entry);
+ }
+
+ @Test
+ public void testTrimAtThreshold() {
+ // Start with the largest possible entry.
+ Cache.Entry entry = randomData(MAX_SIZE - getEntrySizeOnDisk("maxsize") - 1);
+ cache.put("maxsize", entry);
+
+ assertThatEntriesAreEqual(cache.get("maxsize"), entry);
+
+ // Now any new entry should cause the first one to be cleared.
+ entry = randomData(0);
+ cache.put("bit", entry);
+
+ assertThat(cache.get("goodsize"), is(nullValue()));
+ assertThatEntriesAreEqual(cache.get("bit"), entry);
+ }
+
+ @Test
+ public void testTrimWithMultipleEvictions_underHysteresisThreshold() {
+ Cache.Entry entry1 = randomData(MAX_SIZE / 3 - getEntrySizeOnDisk("entry1") - 1);
+ cache.put("entry1", entry1);
+ Cache.Entry entry2 = randomData(MAX_SIZE / 3 - getEntrySizeOnDisk("entry2") - 1);
+ cache.put("entry2", entry2);
+ Cache.Entry entry3 = randomData(MAX_SIZE / 3 - getEntrySizeOnDisk("entry3") - 1);
+ cache.put("entry3", entry3);
+
+ assertThatEntriesAreEqual(cache.get("entry1"), entry1);
+ assertThatEntriesAreEqual(cache.get("entry2"), entry2);
+ assertThatEntriesAreEqual(cache.get("entry3"), entry3);
+
+ Cache.Entry entry =
+ randomData(
+ (int) (DiskBasedCache.HYSTERESIS_FACTOR * MAX_SIZE)
+ - getEntrySizeOnDisk("max"));
+ cache.put("max", entry);
+
+ assertThat(cache.get("entry1"), is(nullValue()));
+ assertThat(cache.get("entry2"), is(nullValue()));
+ assertThat(cache.get("entry3"), is(nullValue()));
+ assertThatEntriesAreEqual(cache.get("max"), entry);
+ }
+
+ @Test
+ public void testTrimWithMultipleEvictions_atHysteresisThreshold() {
+ Cache.Entry entry1 = randomData(MAX_SIZE / 3 - getEntrySizeOnDisk("entry1") - 1);
+ cache.put("entry1", entry1);
+ Cache.Entry entry2 = randomData(MAX_SIZE / 3 - getEntrySizeOnDisk("entry2") - 1);
+ cache.put("entry2", entry2);
+ Cache.Entry entry3 = randomData(MAX_SIZE / 3 - getEntrySizeOnDisk("entry3") - 1);
+ cache.put("entry3", entry3);
+
+ assertThatEntriesAreEqual(cache.get("entry1"), entry1);
+ assertThatEntriesAreEqual(cache.get("entry2"), entry2);
+ assertThatEntriesAreEqual(cache.get("entry3"), entry3);
+
+ Cache.Entry entry =
+ randomData(
+ (int) (DiskBasedCache.HYSTERESIS_FACTOR * MAX_SIZE)
+ - getEntrySizeOnDisk("max")
+ + 1);
+ cache.put("max", entry);
+
+ assertThat(cache.get("entry1"), is(nullValue()));
+ assertThat(cache.get("entry2"), is(nullValue()));
+ assertThat(cache.get("entry3"), is(nullValue()));
+ assertThat(cache.get("max"), is(nullValue()));
+ }
+
+ @Test
+ public void testTrimWithPartialEvictions() {
+ Cache.Entry entry1 = randomData(MAX_SIZE / 3 - getEntrySizeOnDisk("entry1") - 1);
+ cache.put("entry1", entry1);
+ Cache.Entry entry2 = randomData(MAX_SIZE / 3 - getEntrySizeOnDisk("entry2") - 1);
+ cache.put("entry2", entry2);
+ Cache.Entry entry3 = randomData(MAX_SIZE / 3 - getEntrySizeOnDisk("entry3") - 1);
+ cache.put("entry3", entry3);
+
+ assertThatEntriesAreEqual(cache.get("entry1"), entry1);
+ assertThatEntriesAreEqual(cache.get("entry2"), entry2);
+ assertThatEntriesAreEqual(cache.get("entry3"), entry3);
+
+ Cache.Entry entry4 = randomData((MAX_SIZE - getEntrySizeOnDisk("entry4") - 1) / 2);
+ cache.put("entry4", entry4);
+
+ assertThat(cache.get("entry1"), is(nullValue()));
+ assertThat(cache.get("entry2"), is(nullValue()));
+ assertThatEntriesAreEqual(cache.get("entry3"), entry3);
+ assertThatEntriesAreEqual(cache.get("entry4"), entry4);
+ }
+
+ @Test
+ public void testLargeEntryDoesntClearCache() {
+ // Writing a large entry to an empty cache should succeed
+ Cache.Entry largeEntry = randomData(MAX_SIZE - getEntrySizeOnDisk("largeEntry") - 1);
+ cache.put("largeEntry", largeEntry);
+
+ assertThatEntriesAreEqual(cache.get("largeEntry"), largeEntry);
+
+ // Reset and fill up ~half the cache.
+ cache.clear();
+ Cache.Entry entry = randomData(MAX_SIZE / 2 - getEntrySizeOnDisk("entry") - 1);
+ cache.put("entry", entry);
+
+ assertThatEntriesAreEqual(cache.get("entry"), entry);
+
+ // Writing the large entry should no-op, because otherwise the pruning algorithm would clear
+ // the whole cache, since the large entry is above the hysteresis threshold.
+ cache.put("largeEntry", largeEntry);
+
+ assertThat(cache.get("largeEntry"), is(nullValue()));
+ assertThatEntriesAreEqual(cache.get("entry"), entry);
+ }
+
+ @Test
+ @SuppressWarnings("TryFinallyCanBeTryWithResources")
+ public void testGetBadMagic() throws IOException {
+ // Cache something
+ Cache.Entry entry = randomData(1023);
+ cache.put("key", entry);
+ assertThatEntriesAreEqual(cache.get("key"), entry);
+
+ // Overwrite the magic header
+ File cacheFolder = temporaryFolder.getRoot();
+ File file = cacheFolder.listFiles()[0];
+ FileOutputStream fos = new FileOutputStream(file);
+ try {
+ DiskBasedCache.writeInt(fos, 0); // overwrite magic
+ } finally {
+ //noinspection ThrowFromFinallyBlock
+ fos.close();
+ }
+
+ assertThat(cache.get("key"), is(nullValue()));
+ assertThat(listCachedFiles(), is(emptyArray()));
+ }
+
+ @Test
+ @SuppressWarnings("TryFinallyCanBeTryWithResources")
+ public void testGetWrongKey() throws IOException {
+ // Cache something
+ Cache.Entry entry = randomData(1023);
+ cache.put("key", entry);
+ assertThatEntriesAreEqual(cache.get("key"), entry);
+
+ // Access the cached file
+ File cacheFolder = temporaryFolder.getRoot();
+ File file = cacheFolder.listFiles()[0];
+ FileOutputStream fos = new FileOutputStream(file);
+ try {
+ // Overwrite with a different key
+ CacheHeader wrongHeader = new CacheHeader("bad", entry);
+ wrongHeader.writeHeader(fos);
+ } finally {
+ //noinspection ThrowFromFinallyBlock
+ fos.close();
+ }
+
+ // key is gone, but file is still there
+ assertThat(cache.get("key"), is(nullValue()));
+ assertThat(listCachedFiles(), is(arrayWithSize(1)));
+
+ // Note: file is now a zombie because its key does not map to its name
+ }
+
+ @Test
+ public void testStreamToBytesNegativeLength() throws IOException {
+ byte[] data = new byte[1];
+ CountingInputStream cis =
+ new CountingInputStream(new ByteArrayInputStream(data), data.length);
+ exception.expect(IOException.class);
+ DiskBasedCache.streamToBytes(cis, -1);
+ }
+
+ @Test
+ public void testStreamToBytesExcessiveLength() throws IOException {
+ byte[] data = new byte[1];
+ CountingInputStream cis =
+ new CountingInputStream(new ByteArrayInputStream(data), data.length);
+ exception.expect(IOException.class);
+ DiskBasedCache.streamToBytes(cis, 2);
+ }
+
+ @Test
+ public void testStreamToBytesOverflow() throws IOException {
+ byte[] data = new byte[0];
+ CountingInputStream cis =
+ new CountingInputStream(new ByteArrayInputStream(data), 0x100000000L);
+ exception.expect(IOException.class);
+ DiskBasedCache.streamToBytes(cis, 0x100000000L); // int value is 0
+ }
+
+ @Test
+ public void testReadHeaderListWithNegativeSize() throws IOException {
+ // If a cached header list is corrupted and begins with a negative size,
+ // verify that readHeaderList will throw an IOException.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DiskBasedCache.writeInt(baos, -1); // negative size
+ CountingInputStream cis =
+ new CountingInputStream(
+ new ByteArrayInputStream(baos.toByteArray()), Integer.MAX_VALUE);
+ // Expect IOException due to negative size
+ exception.expect(IOException.class);
+ DiskBasedCache.readHeaderList(cis);
+ }
+
+ @Test
+ public void testReadHeaderListWithGinormousSize() throws IOException {
+ // If a cached header list is corrupted and begins with 2GB size, verify
+ // that readHeaderList will throw EOFException rather than OutOfMemoryError.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DiskBasedCache.writeInt(baos, Integer.MAX_VALUE); // 2GB size
+ CountingInputStream cis =
+ new CountingInputStream(new ByteArrayInputStream(baos.toByteArray()), baos.size());
+ // Expect EOFException when end of stream is reached
+ exception.expect(EOFException.class);
+ DiskBasedCache.readHeaderList(cis);
+ }
+
+ @Test
+ public void testFileIsDeletedWhenWriteHeaderFails() throws IOException {
+ // Create DataOutputStream that throws IOException
+ OutputStream mockedOutputStream = spy(OutputStream.class);
+ doThrow(IOException.class).when(mockedOutputStream).write(anyInt());
+
+ // Create read-only copy that fails to write anything
+ DiskBasedCache readonly = spy((DiskBasedCache) cache);
+ doReturn(mockedOutputStream).when(readonly).createOutputStream(any(File.class));
+
+ // Attempt to write
+ readonly.put("key", randomData(1111));
+
+ // write is called at least once because each linked stream flushes when closed
+ verify(mockedOutputStream, atLeastOnce()).write(anyInt());
+ assertThat(readonly.get("key"), is(nullValue()));
+ assertThat(listCachedFiles(), is(emptyArray()));
+
+ // Note: original cache will try (without success) to read from file
+ assertThat(cache.get("key"), is(nullValue()));
+ }
+
+ @Test
+ public void testIOExceptionInInitialize() throws IOException {
+ // Cache a few kilobytes
+ cache.put("kilobyte", randomData(1024));
+ cache.put("kilobyte2", randomData(1024));
+ cache.put("kilobyte3", randomData(1024));
+
+ // Create DataInputStream that throws IOException
+ InputStream mockedInputStream = spy(InputStream.class);
+ //noinspection ResultOfMethodCallIgnored
+ doThrow(IOException.class).when(mockedInputStream).read();
+
+ // Create broken cache that fails to read anything
+ DiskBasedCache broken = spy(new DiskBasedCache(temporaryFolder.getRoot()));
+ doReturn(mockedInputStream).when(broken).createInputStream(any(File.class));
+
+ // Attempt to initialize
+ broken.initialize();
+
+ // Everything is gone
+ assertThat(broken.get("kilobyte"), is(nullValue()));
+ assertThat(broken.get("kilobyte2"), is(nullValue()));
+ assertThat(broken.get("kilobyte3"), is(nullValue()));
+ assertThat(listCachedFiles(), is(emptyArray()));
+
+ // Verify that original cache can cope with missing files
+ assertThat(cache.get("kilobyte"), is(nullValue()));
+ assertThat(cache.get("kilobyte2"), is(nullValue()));
+ assertThat(cache.get("kilobyte3"), is(nullValue()));
+ }
+
+ @Test
+ public void testManyResponseHeaders() {
+ Cache.Entry entry = new Cache.Entry();
+ entry.data = new byte[0];
+ entry.responseHeaders = new HashMap<>();
+ for (int i = 0; i < 0xFFFF; i++) {
+ entry.responseHeaders.put(Integer.toString(i), "");
+ }
+ cache.put("key", entry);
+ }
+
+ @Test
+ @SuppressWarnings("TryFinallyCanBeTryWithResources")
+ public void testCountingInputStreamByteCount() throws IOException {
+ // Write some bytes
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ //noinspection ThrowFromFinallyBlock
+ try {
+ DiskBasedCache.writeInt(out, 1);
+ DiskBasedCache.writeLong(out, -1L);
+ DiskBasedCache.writeString(out, "hamburger");
+ } finally {
+ //noinspection ThrowFromFinallyBlock
+ out.close();
+ }
+ long bytesWritten = out.size();
+
+ // Read the bytes and compare the counts
+ CountingInputStream cis =
+ new CountingInputStream(new ByteArrayInputStream(out.toByteArray()), bytesWritten);
+ try {
+ assertThat(cis.bytesRemaining(), is(bytesWritten));
+ assertThat(cis.bytesRead(), is(0L));
+ assertThat(DiskBasedCache.readInt(cis), is(1));
+ assertThat(DiskBasedCache.readLong(cis), is(-1L));
+ assertThat(DiskBasedCache.readString(cis), is("hamburger"));
+ assertThat(cis.bytesRead(), is(bytesWritten));
+ assertThat(cis.bytesRemaining(), is(0L));
+ } finally {
+ //noinspection ThrowFromFinallyBlock
+ cis.close();
+ }
+ }
+
+ /* Serialization tests */
+
+ @Test
+ public void testEmptyReadThrowsEOF() throws IOException {
+ ByteArrayInputStream empty = new ByteArrayInputStream(new byte[] {});
+ exception.expect(EOFException.class);
+ DiskBasedCache.readInt(empty);
+ }
+
+ @Test
+ public void serializeInt() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DiskBasedCache.writeInt(baos, 0);
+ DiskBasedCache.writeInt(baos, 19791214);
+ DiskBasedCache.writeInt(baos, -20050711);
+ DiskBasedCache.writeInt(baos, Integer.MIN_VALUE);
+ DiskBasedCache.writeInt(baos, Integer.MAX_VALUE);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ assertEquals(DiskBasedCache.readInt(bais), 0);
+ assertEquals(DiskBasedCache.readInt(bais), 19791214);
+ assertEquals(DiskBasedCache.readInt(bais), -20050711);
+ assertEquals(DiskBasedCache.readInt(bais), Integer.MIN_VALUE);
+ assertEquals(DiskBasedCache.readInt(bais), Integer.MAX_VALUE);
+ }
+
+ @Test
+ public void serializeLong() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DiskBasedCache.writeLong(baos, 0);
+ DiskBasedCache.writeLong(baos, 31337);
+ DiskBasedCache.writeLong(baos, -4160);
+ DiskBasedCache.writeLong(baos, 4295032832L);
+ DiskBasedCache.writeLong(baos, -4314824046L);
+ DiskBasedCache.writeLong(baos, Long.MIN_VALUE);
+ DiskBasedCache.writeLong(baos, Long.MAX_VALUE);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ assertEquals(DiskBasedCache.readLong(bais), 0);
+ assertEquals(DiskBasedCache.readLong(bais), 31337);
+ assertEquals(DiskBasedCache.readLong(bais), -4160);
+ assertEquals(DiskBasedCache.readLong(bais), 4295032832L);
+ assertEquals(DiskBasedCache.readLong(bais), -4314824046L);
+ assertEquals(DiskBasedCache.readLong(bais), Long.MIN_VALUE);
+ assertEquals(DiskBasedCache.readLong(bais), Long.MAX_VALUE);
+ }
+
+ @Test
+ public void serializeString() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DiskBasedCache.writeString(baos, "");
+ DiskBasedCache.writeString(baos, "This is a string.");
+ DiskBasedCache.writeString(baos, "ファイカス");
+ CountingInputStream cis =
+ new CountingInputStream(new ByteArrayInputStream(baos.toByteArray()), baos.size());
+ assertEquals(DiskBasedCache.readString(cis), "");
+ assertEquals(DiskBasedCache.readString(cis), "This is a string.");
+ assertEquals(DiskBasedCache.readString(cis), "ファイカス");
+ }
+
+ @Test
+ public void serializeHeaders() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ List<Header> empty = new ArrayList<>();
+ DiskBasedCache.writeHeaderList(empty, baos);
+ DiskBasedCache.writeHeaderList(null, baos);
+ List<Header> twoThings = new ArrayList<>();
+ twoThings.add(new Header("first", "thing"));
+ twoThings.add(new Header("second", "item"));
+ DiskBasedCache.writeHeaderList(twoThings, baos);
+ List<Header> emptyKey = new ArrayList<>();
+ emptyKey.add(new Header("", "value"));
+ DiskBasedCache.writeHeaderList(emptyKey, baos);
+ List<Header> emptyValue = new ArrayList<>();
+ emptyValue.add(new Header("key", ""));
+ DiskBasedCache.writeHeaderList(emptyValue, baos);
+ List<Header> sameKeys = new ArrayList<>();
+ sameKeys.add(new Header("key", "value"));
+ sameKeys.add(new Header("key", "value2"));
+ DiskBasedCache.writeHeaderList(sameKeys, baos);
+ CountingInputStream cis =
+ new CountingInputStream(new ByteArrayInputStream(baos.toByteArray()), baos.size());
+ assertEquals(DiskBasedCache.readHeaderList(cis), empty);
+ assertEquals(DiskBasedCache.readHeaderList(cis), empty); // null reads back empty
+ assertEquals(DiskBasedCache.readHeaderList(cis), twoThings);
+ assertEquals(DiskBasedCache.readHeaderList(cis), emptyKey);
+ assertEquals(DiskBasedCache.readHeaderList(cis), emptyValue);
+ assertEquals(DiskBasedCache.readHeaderList(cis), sameKeys);
+ }
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(DiskBasedCache.class.getConstructor(File.class, int.class));
+ assertNotNull(
+ DiskBasedCache.class.getConstructor(DiskBasedCache.FileSupplier.class, int.class));
+ assertNotNull(DiskBasedCache.class.getConstructor(File.class));
+ assertNotNull(DiskBasedCache.class.getConstructor(DiskBasedCache.FileSupplier.class));
+
+ assertNotNull(DiskBasedCache.class.getMethod("getFileForKey", String.class));
+ }
+
+ @Test
+ public void initializeIfRootDirectoryDeleted() {
+ temporaryFolder.delete();
+
+ Cache.Entry entry = randomData(101);
+ cache.put("key1", entry);
+
+ assertThat(cache.get("key1"), is(nullValue()));
+
+ // confirm that we can now store entries
+ cache.put("key2", entry);
+ assertThatEntriesAreEqual(cache.get("key2"), entry);
+ }
+
+ /* Test helpers */
+
+ private void assertThatEntriesAreEqual(Cache.Entry actual, Cache.Entry expected) {
+ assertThat(actual.data, is(equalTo(expected.data)));
+ assertThat(actual.etag, is(equalTo(expected.etag)));
+ assertThat(actual.lastModified, is(equalTo(expected.lastModified)));
+ assertThat(actual.responseHeaders, is(equalTo(expected.responseHeaders)));
+ assertThat(actual.serverDate, is(equalTo(expected.serverDate)));
+ assertThat(actual.softTtl, is(equalTo(expected.softTtl)));
+ assertThat(actual.ttl, is(equalTo(expected.ttl)));
+ }
+
+ private Cache.Entry randomData(int length) {
+ Cache.Entry entry = new Cache.Entry();
+ byte[] data = new byte[length];
+ new Random(42).nextBytes(data); // explicit seed for reproducible results
+ entry.data = data;
+ return entry;
+ }
+
+ private File[] listCachedFiles() {
+ return temporaryFolder.getRoot().listFiles();
+ }
+
+ private int getEntrySizeOnDisk(String key) {
+ // Header size is:
+ // 4 bytes for magic int
+ // 8 + len(key) bytes for key (long length)
+ // 8 bytes for etag (long length + 0 characters)
+ // 32 bytes for serverDate, lastModified, ttl, and softTtl longs
+ // 4 bytes for length of header list int
+ // == 56 + len(key) bytes total.
+ return 56 + key.length();
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/HttpClientStackTest.java b/core/src/test/java/com/android/volley/toolbox/HttpClientStackTest.java
new file mode 100644
index 0000000..2a451dc
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/HttpClientStackTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.volley.Request.Method;
+import com.android.volley.mock.TestRequest;
+import com.android.volley.toolbox.HttpClientStack.HttpPatch;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpTrace;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class HttpClientStackTest {
+
+ @Test
+ public void createDeprecatedGetRequest() throws Exception {
+ TestRequest.DeprecatedGet request = new TestRequest.DeprecatedGet();
+ assertEquals(request.getMethod(), Method.DEPRECATED_GET_OR_POST);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpGet);
+ }
+
+ @Test
+ public void createDeprecatedPostRequest() throws Exception {
+ TestRequest.DeprecatedPost request = new TestRequest.DeprecatedPost();
+ assertEquals(request.getMethod(), Method.DEPRECATED_GET_OR_POST);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpPost);
+ }
+
+ @Test
+ public void createGetRequest() throws Exception {
+ TestRequest.Get request = new TestRequest.Get();
+ assertEquals(request.getMethod(), Method.GET);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpGet);
+ }
+
+ @Test
+ public void createPostRequest() throws Exception {
+ TestRequest.Post request = new TestRequest.Post();
+ assertEquals(request.getMethod(), Method.POST);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpPost);
+ }
+
+ @Test
+ public void createPostRequestWithBody() throws Exception {
+ TestRequest.PostWithBody request = new TestRequest.PostWithBody();
+ assertEquals(request.getMethod(), Method.POST);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpPost);
+ }
+
+ @Test
+ public void createPutRequest() throws Exception {
+ TestRequest.Put request = new TestRequest.Put();
+ assertEquals(request.getMethod(), Method.PUT);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpPut);
+ }
+
+ @Test
+ public void createPutRequestWithBody() throws Exception {
+ TestRequest.PutWithBody request = new TestRequest.PutWithBody();
+ assertEquals(request.getMethod(), Method.PUT);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpPut);
+ }
+
+ @Test
+ public void createDeleteRequest() throws Exception {
+ TestRequest.Delete request = new TestRequest.Delete();
+ assertEquals(request.getMethod(), Method.DELETE);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpDelete);
+ }
+
+ @Test
+ public void createHeadRequest() throws Exception {
+ TestRequest.Head request = new TestRequest.Head();
+ assertEquals(request.getMethod(), Method.HEAD);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpHead);
+ }
+
+ @Test
+ public void createOptionsRequest() throws Exception {
+ TestRequest.Options request = new TestRequest.Options();
+ assertEquals(request.getMethod(), Method.OPTIONS);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpOptions);
+ }
+
+ @Test
+ public void createTraceRequest() throws Exception {
+ TestRequest.Trace request = new TestRequest.Trace();
+ assertEquals(request.getMethod(), Method.TRACE);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpTrace);
+ }
+
+ @Test
+ public void createPatchRequest() throws Exception {
+ TestRequest.Patch request = new TestRequest.Patch();
+ assertEquals(request.getMethod(), Method.PATCH);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpPatch);
+ }
+
+ @Test
+ public void createPatchRequestWithBody() throws Exception {
+ TestRequest.PatchWithBody request = new TestRequest.PatchWithBody();
+ assertEquals(request.getMethod(), Method.PATCH);
+
+ HttpUriRequest httpRequest = HttpClientStack.createHttpRequest(request, null);
+ assertTrue(httpRequest instanceof HttpPatch);
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java b/core/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java
new file mode 100644
index 0000000..7780c3e
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.volley.Cache;
+import com.android.volley.Header;
+import com.android.volley.NetworkResponse;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class HttpHeaderParserTest {
+
+ private static long ONE_MINUTE_MILLIS = 1000L * 60;
+ private static long ONE_HOUR_MILLIS = 1000L * 60 * 60;
+ private static long ONE_DAY_MILLIS = ONE_HOUR_MILLIS * 24;
+ private static long ONE_WEEK_MILLIS = ONE_DAY_MILLIS * 7;
+
+ private NetworkResponse response;
+ private Map<String, String> headers;
+
+ @Before
+ public void setUp() throws Exception {
+ headers = new HashMap<String, String>();
+ response = new NetworkResponse(0, null, headers, false);
+ }
+
+ @Test
+ public void parseCacheHeaders_noHeaders() {
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertNotNull(entry);
+ assertNull(entry.etag);
+ assertEquals(0, entry.serverDate);
+ assertEquals(0, entry.lastModified);
+ assertEquals(0, entry.ttl);
+ assertEquals(0, entry.softTtl);
+ }
+
+ @Test
+ public void parseCacheHeaders_nullHeaders() {
+ response = new NetworkResponse(0, null, null, false);
+ assertNull(HttpHeaderParser.parseCacheHeaders(response));
+ }
+
+ @Test
+ public void parseCacheHeaders_headersSet() {
+ headers.put("MyCustomHeader", "42");
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertNotNull(entry);
+ assertNotNull(entry.responseHeaders);
+ assertEquals(1, entry.responseHeaders.size());
+ assertEquals("42", entry.responseHeaders.get("MyCustomHeader"));
+ }
+
+ @Test
+ public void parseCacheHeaders_etag() {
+ headers.put("ETag", "Yow!");
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertNotNull(entry);
+ assertEquals("Yow!", entry.etag);
+ }
+
+ @Test
+ public void parseCacheHeaders_normalExpire() {
+ long now = System.currentTimeMillis();
+ headers.put("Date", rfc1123Date(now));
+ headers.put("Last-Modified", rfc1123Date(now - ONE_DAY_MILLIS));
+ headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertNotNull(entry);
+ assertNull(entry.etag);
+ assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
+ assertEqualsWithin(entry.lastModified, (now - ONE_DAY_MILLIS), ONE_MINUTE_MILLIS);
+ assertTrue(entry.softTtl >= (now + ONE_HOUR_MILLIS));
+ assertTrue(entry.ttl == entry.softTtl);
+ }
+
+ @Test
+ public void parseCacheHeaders_expiresInPast() {
+ long now = System.currentTimeMillis();
+ headers.put("Date", rfc1123Date(now));
+ headers.put("Expires", rfc1123Date(now - ONE_HOUR_MILLIS));
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertNotNull(entry);
+ assertNull(entry.etag);
+ assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
+ assertEquals(0, entry.ttl);
+ assertEquals(0, entry.softTtl);
+ }
+
+ @Test
+ public void parseCacheHeaders_serverRelative() {
+
+ long now = System.currentTimeMillis();
+ // Set "current" date as one hour in the future
+ headers.put("Date", rfc1123Date(now + ONE_HOUR_MILLIS));
+ // TTL four hours in the future, so should be three hours from now
+ headers.put("Expires", rfc1123Date(now + 4 * ONE_HOUR_MILLIS));
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertEqualsWithin(now + 3 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
+ assertEquals(entry.softTtl, entry.ttl);
+ }
+
+ @Test
+ public void parseCacheHeaders_cacheControlOverridesExpires() {
+ long now = System.currentTimeMillis();
+ headers.put("Date", rfc1123Date(now));
+ headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
+ headers.put("Cache-Control", "public, max-age=86400");
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertNotNull(entry);
+ assertNull(entry.etag);
+ assertEqualsWithin(now + ONE_DAY_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
+ assertEquals(entry.softTtl, entry.ttl);
+ }
+
+ @Test
+ public void testParseCacheHeaders_staleWhileRevalidate() {
+ long now = System.currentTimeMillis();
+ headers.put("Date", rfc1123Date(now));
+ headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
+
+ // - max-age (entry.softTtl) indicates that the asset is fresh for 1 day
+ // - stale-while-revalidate (entry.ttl) indicates that the asset may
+ // continue to be served stale for up to additional 7 days
+ headers.put("Cache-Control", "max-age=86400, stale-while-revalidate=604800");
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertNotNull(entry);
+ assertNull(entry.etag);
+ assertEqualsWithin(now + ONE_DAY_MILLIS, entry.softTtl, ONE_MINUTE_MILLIS);
+ assertEqualsWithin(now + ONE_DAY_MILLIS + ONE_WEEK_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
+ }
+
+ @Test
+ public void parseCacheHeaders_cacheControlNoCache() {
+ long now = System.currentTimeMillis();
+ headers.put("Date", rfc1123Date(now));
+ headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
+ headers.put("Cache-Control", "no-cache");
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertNull(entry);
+ }
+
+ @Test
+ public void parseCacheHeaders_cacheControlMustRevalidateNoMaxAge() {
+ long now = System.currentTimeMillis();
+ headers.put("Date", rfc1123Date(now));
+ headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
+ headers.put("Cache-Control", "must-revalidate");
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+ assertNotNull(entry);
+ assertNull(entry.etag);
+ assertEqualsWithin(now, entry.ttl, ONE_MINUTE_MILLIS);
+ assertEquals(entry.softTtl, entry.ttl);
+ }
+
+ @Test
+ public void parseCacheHeaders_cacheControlMustRevalidateWithMaxAge() {
+ long now = System.currentTimeMillis();
+ headers.put("Date", rfc1123Date(now));
+ headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
+ headers.put("Cache-Control", "must-revalidate, max-age=3600");
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+ assertNotNull(entry);
+ assertNull(entry.etag);
+ assertEqualsWithin(now + ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
+ assertEquals(entry.softTtl, entry.ttl);
+ }
+
+ @Test
+ public void parseCacheHeaders_cacheControlMustRevalidateWithMaxAgeAndStale() {
+ long now = System.currentTimeMillis();
+ headers.put("Date", rfc1123Date(now));
+ headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
+
+ // - max-age (entry.softTtl) indicates that the asset is fresh for 1 day
+ // - stale-while-revalidate (entry.ttl) indicates that the asset may
+ // continue to be served stale for up to additional 7 days, but this is
+ // ignored in this case because of the must-revalidate header.
+ headers.put(
+ "Cache-Control", "must-revalidate, max-age=86400, stale-while-revalidate=604800");
+
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+ assertNotNull(entry);
+ assertNull(entry.etag);
+ assertEqualsWithin(now + ONE_DAY_MILLIS, entry.softTtl, ONE_MINUTE_MILLIS);
+ assertEquals(entry.softTtl, entry.ttl);
+ }
+
+ private void assertEqualsWithin(long expected, long value, long fudgeFactor) {
+ long diff = Math.abs(expected - value);
+ assertTrue(diff < fudgeFactor);
+ }
+
+ private static String rfc1123Date(long millis) {
+ DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
+ return df.format(new Date(millis));
+ }
+
+ // --------------------------
+
+ @Test
+ public void parseCharset() {
+ // Like the ones we usually see
+ headers.put("Content-Type", "text/plain; charset=utf-8");
+ assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
+
+ // Charset specified, ignore default charset
+ headers.put("Content-Type", "text/plain; charset=utf-8");
+ assertEquals("utf-8", HttpHeaderParser.parseCharset(headers, "ISO-8859-1"));
+
+ // Extra whitespace
+ headers.put("Content-Type", "text/plain; charset=utf-8 ");
+ assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
+
+ // Extra parameters
+ headers.put("Content-Type", "text/plain; charset=utf-8; frozzle=bar");
+ assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
+
+ // No Content-Type header
+ headers.clear();
+ assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
+
+ // No Content-Type header, use default charset
+ headers.clear();
+ assertEquals("utf-8", HttpHeaderParser.parseCharset(headers, "utf-8"));
+
+ // Empty value
+ headers.put("Content-Type", "text/plain; charset=");
+ assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
+
+ // None specified
+ headers.put("Content-Type", "text/plain");
+ assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
+
+ // None charset specified, use default charset
+ headers.put("Content-Type", "application/json");
+ assertEquals("utf-8", HttpHeaderParser.parseCharset(headers, "utf-8"));
+
+ // None specified, extra semicolon
+ headers.put("Content-Type", "text/plain;");
+ assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
+
+ // No headers, use default charset
+ assertEquals("utf-8", HttpHeaderParser.parseCharset(null, "utf-8"));
+ }
+
+ @Test
+ public void parseCaseInsensitive() {
+ long now = System.currentTimeMillis();
+
+ List<Header> headers = new ArrayList<>();
+ headers.add(new Header("eTAG", "Yow!"));
+ headers.add(new Header("DATE", rfc1123Date(now)));
+ headers.add(new Header("expires", rfc1123Date(now + ONE_HOUR_MILLIS)));
+ headers.add(new Header("cache-control", "public, max-age=86400"));
+ headers.add(new Header("content-type", "text/plain"));
+
+ NetworkResponse response = new NetworkResponse(0, null, false, 0, headers);
+ Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
+
+ assertNotNull(entry);
+ assertEquals("Yow!", entry.etag);
+ assertEqualsWithin(now + ONE_DAY_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
+ assertEquals(entry.softTtl, entry.ttl);
+ assertEquals(
+ "ISO-8859-1", HttpHeaderParser.parseCharset(HttpHeaderParser.toHeaderMap(headers)));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/HttpStackConformanceTest.java b/core/src/test/java/com/android/volley/toolbox/HttpStackConformanceTest.java
new file mode 100644
index 0000000..6794af8
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/HttpStackConformanceTest.java
@@ -0,0 +1,192 @@
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import com.android.volley.Request;
+import com.android.volley.RetryPolicy;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests to validate that HttpStack implementations conform with expected behavior. */
+@RunWith(RobolectricTestRunner.class)
+public class HttpStackConformanceTest {
+ @Mock private RetryPolicy mMockRetryPolicy;
+ @Mock private Request mMockRequest;
+
+ @Mock private HttpURLConnection mMockConnection;
+ @Mock private OutputStream mMockOutputStream;
+ @Spy private HurlStack mHurlStack = new HurlStack();
+
+ @Mock private HttpClient mMockHttpClient;
+ private HttpClientStack mHttpClientStack;
+
+ private final TestCase[] mTestCases =
+ new TestCase[] {
+ // TestCase for HurlStack.
+ new TestCase() {
+ @Override
+ public HttpStack getStack() {
+ return mHurlStack;
+ }
+
+ @Override
+ public void setOutputHeaderMap(final Map<String, String> outputHeaderMap) {
+ doAnswer(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ outputHeaderMap.put(
+ invocation.<String>getArgument(0),
+ invocation.<String>getArgument(1));
+ return null;
+ }
+ })
+ .when(mMockConnection)
+ .setRequestProperty(anyString(), anyString());
+ doAnswer(
+ new Answer<Map<String, List<String>>>() {
+ @Override
+ public Map<String, List<String>> answer(
+ InvocationOnMock invocation) {
+ Map<String, List<String>> result = new HashMap<>();
+ for (Map.Entry<String, String> entry :
+ outputHeaderMap.entrySet()) {
+ result.put(
+ entry.getKey(),
+ Collections.singletonList(
+ entry.getValue()));
+ }
+ return result;
+ }
+ })
+ .when(mMockConnection)
+ .getRequestProperties();
+ }
+ },
+
+ // TestCase for HttpClientStack.
+ new TestCase() {
+ @Override
+ public HttpStack getStack() {
+ return mHttpClientStack;
+ }
+
+ @Override
+ public void setOutputHeaderMap(final Map<String, String> outputHeaderMap) {
+ try {
+ doAnswer(
+ new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation)
+ throws Throwable {
+ HttpRequest request = invocation.getArgument(0);
+ for (Header header : request.getAllHeaders()) {
+ if (outputHeaderMap.containsKey(
+ header.getName())) {
+ fail(
+ "Multiple values for header "
+ + header.getName());
+ }
+ outputHeaderMap.put(
+ header.getName(),
+ header.getValue());
+ }
+ return null;
+ }
+ })
+ .when(mMockHttpClient)
+ .execute(any(HttpUriRequest.class));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mHttpClientStack = spy(new HttpClientStack(mMockHttpClient));
+
+ doReturn(mMockConnection).when(mHurlStack).createConnection(any(URL.class));
+ doReturn(mMockOutputStream).when(mMockConnection).getOutputStream();
+ when(mMockRequest.getUrl()).thenReturn("http://127.0.0.1");
+ when(mMockRequest.getRetryPolicy()).thenReturn(mMockRetryPolicy);
+ }
+
+ @Test
+ public void headerPrecedence() throws Exception {
+ Map<String, String> additionalHeaders = new HashMap<>();
+ additionalHeaders.put("A", "AddlA");
+ additionalHeaders.put("B", "AddlB");
+
+ Map<String, String> requestHeaders = new HashMap<>();
+ requestHeaders.put("A", "RequestA");
+ requestHeaders.put("C", "RequestC");
+ when(mMockRequest.getHeaders()).thenReturn(requestHeaders);
+
+ when(mMockRequest.getMethod()).thenReturn(Request.Method.POST);
+ when(mMockRequest.getBody()).thenReturn(new byte[0]);
+ when(mMockRequest.getBodyContentType()).thenReturn("BodyContentType");
+
+ for (TestCase testCase : mTestCases) {
+ // Test once without a Content-Type header in getHeaders().
+ Map<String, String> combinedHeaders = new HashMap<>();
+ testCase.setOutputHeaderMap(combinedHeaders);
+
+ testCase.getStack().performRequest(mMockRequest, additionalHeaders);
+
+ Map<String, String> expectedHeaders = new HashMap<>();
+ expectedHeaders.put("A", "RequestA");
+ expectedHeaders.put("B", "AddlB");
+ expectedHeaders.put("C", "RequestC");
+ expectedHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, "BodyContentType");
+
+ assertEquals(expectedHeaders, combinedHeaders);
+
+ // Reset and test again with a Content-Type header in getHeaders().
+ combinedHeaders.clear();
+
+ requestHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, "RequestContentType");
+ expectedHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, "RequestContentType");
+
+ testCase.getStack().performRequest(mMockRequest, additionalHeaders);
+ assertEquals(expectedHeaders, combinedHeaders);
+
+ // Clear the Content-Type header for the next TestCase.
+ requestHeaders.remove(HttpHeaderParser.HEADER_CONTENT_TYPE);
+ }
+ }
+
+ private interface TestCase {
+ HttpStack getStack();
+
+ void setOutputHeaderMap(Map<String, String> outputHeaderMap);
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/HurlStackTest.java b/core/src/test/java/com/android/volley/toolbox/HurlStackTest.java
new file mode 100644
index 0000000..7508244
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/HurlStackTest.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.volley.Header;
+import com.android.volley.Request;
+import com.android.volley.Request.Method;
+import com.android.volley.mock.TestRequest;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class HurlStackTest {
+
+ @Mock private HttpURLConnection mMockConnection;
+ private HurlStack mHurlStack;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mMockConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream());
+
+ mHurlStack =
+ new HurlStack() {
+ @Override
+ protected HttpURLConnection createConnection(URL url) {
+ return mMockConnection;
+ }
+
+ @Override
+ protected InputStream createInputStream(
+ Request<?> request, HttpURLConnection connection) {
+ return new MonitoringInputStream(
+ super.createInputStream(request, connection));
+ }
+
+ @Override
+ protected OutputStream createOutputStream(
+ Request<?> request, HttpURLConnection connection, int length)
+ throws IOException {
+ if (request instanceof MonitoredRequest) {
+ return new MonitoringOutputStream(
+ super.createOutputStream(request, connection, length),
+ (MonitoredRequest) request,
+ length);
+ }
+ return super.createOutputStream(request, connection, length);
+ }
+ };
+ }
+
+ @Test
+ public void connectionForDeprecatedGetRequest() throws Exception {
+ TestRequest.DeprecatedGet request = new TestRequest.DeprecatedGet();
+ assertEquals(request.getMethod(), Method.DEPRECATED_GET_OR_POST);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection, never()).setRequestMethod(anyString());
+ verify(mMockConnection, never()).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForDeprecatedPostRequest() throws Exception {
+ TestRequest.DeprecatedPost request = new TestRequest.DeprecatedPost();
+ assertEquals(request.getMethod(), Method.DEPRECATED_GET_OR_POST);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("POST");
+ verify(mMockConnection).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForGetRequest() throws Exception {
+ TestRequest.Get request = new TestRequest.Get();
+ assertEquals(request.getMethod(), Method.GET);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("GET");
+ verify(mMockConnection, never()).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForPostRequest() throws Exception {
+ TestRequest.Post request = new TestRequest.Post();
+ assertEquals(request.getMethod(), Method.POST);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("POST");
+ verify(mMockConnection, never()).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForPostWithBodyRequest() throws Exception {
+ TestRequest.PostWithBody request = new TestRequest.PostWithBody();
+ assertEquals(request.getMethod(), Method.POST);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("POST");
+ verify(mMockConnection).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForPutRequest() throws Exception {
+ TestRequest.Put request = new TestRequest.Put();
+ assertEquals(request.getMethod(), Method.PUT);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("PUT");
+ verify(mMockConnection, never()).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForPutWithBodyRequest() throws Exception {
+ TestRequest.PutWithBody request = new TestRequest.PutWithBody();
+ assertEquals(request.getMethod(), Method.PUT);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("PUT");
+ verify(mMockConnection).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForDeleteRequest() throws Exception {
+ TestRequest.Delete request = new TestRequest.Delete();
+ assertEquals(request.getMethod(), Method.DELETE);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("DELETE");
+ verify(mMockConnection, never()).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForHeadRequest() throws Exception {
+ TestRequest.Head request = new TestRequest.Head();
+ assertEquals(request.getMethod(), Method.HEAD);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("HEAD");
+ verify(mMockConnection, never()).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForOptionsRequest() throws Exception {
+ TestRequest.Options request = new TestRequest.Options();
+ assertEquals(request.getMethod(), Method.OPTIONS);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("OPTIONS");
+ verify(mMockConnection, never()).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForTraceRequest() throws Exception {
+ TestRequest.Trace request = new TestRequest.Trace();
+ assertEquals(request.getMethod(), Method.TRACE);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("TRACE");
+ verify(mMockConnection, never()).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForPatchRequest() throws Exception {
+ TestRequest.Patch request = new TestRequest.Patch();
+ assertEquals(request.getMethod(), Method.PATCH);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("PATCH");
+ verify(mMockConnection, never()).setDoOutput(true);
+ }
+
+ @Test
+ public void connectionForPatchWithBodyRequest() throws Exception {
+ TestRequest.PatchWithBody request = new TestRequest.PatchWithBody();
+ assertEquals(request.getMethod(), Method.PATCH);
+
+ mHurlStack.setConnectionParametersForRequest(mMockConnection, request);
+ verify(mMockConnection).setRequestMethod("PATCH");
+ verify(mMockConnection).setDoOutput(true);
+ }
+
+ @Test
+ public void executeRequestClosesConnection_connectionError() throws Exception {
+ when(mMockConnection.getResponseCode()).thenThrow(new SocketTimeoutException());
+ try {
+ mHurlStack.executeRequest(
+ new TestRequest.Get(), Collections.<String, String>emptyMap());
+ fail("Should have thrown exception");
+ } catch (IOException e) {
+ verify(mMockConnection).disconnect();
+ }
+ }
+
+ @Test
+ public void executeRequestClosesConnection_invalidResponseCode() throws Exception {
+ when(mMockConnection.getResponseCode()).thenReturn(-1);
+ try {
+ mHurlStack.executeRequest(
+ new TestRequest.Get(), Collections.<String, String>emptyMap());
+ fail("Should have thrown exception");
+ } catch (IOException e) {
+ verify(mMockConnection).disconnect();
+ }
+ }
+
+ @Test
+ public void executeRequestClosesConnection_noResponseBody() throws Exception {
+ when(mMockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_NO_CONTENT);
+ mHurlStack.executeRequest(new TestRequest.Get(), Collections.<String, String>emptyMap());
+ verify(mMockConnection).disconnect();
+ }
+
+ @Test
+ public void executeRequestClosesConnection_hasResponseBody() throws Exception {
+ when(mMockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
+ when(mMockConnection.getInputStream())
+ .thenReturn(new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)));
+ HttpResponse response =
+ mHurlStack.executeRequest(
+ new TestRequest.Get(), Collections.<String, String>emptyMap());
+ // Shouldn't be disconnected until the stream is consumed.
+ verify(mMockConnection, never()).disconnect();
+ response.getContent().close();
+ verify(mMockConnection).disconnect();
+ }
+
+ @Test
+ public void convertHeaders() {
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put(null, Collections.singletonList("Ignored"));
+ headers.put("HeaderA", Collections.singletonList("ValueA"));
+ List<String> values = new ArrayList<>();
+ values.add("ValueB_1");
+ values.add("ValueB_2");
+ headers.put("HeaderB", values);
+ List<Header> result = HurlStack.convertHeaders(headers);
+ List<Header> expected = new ArrayList<>();
+ expected.add(new Header("HeaderA", "ValueA"));
+ expected.add(new Header("HeaderB", "ValueB_1"));
+ expected.add(new Header("HeaderB", "ValueB_2"));
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void interceptResponseStream() throws Exception {
+ when(mMockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
+ when(mMockConnection.getInputStream())
+ .thenReturn(new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)));
+ HttpResponse response =
+ mHurlStack.executeRequest(
+ new TestRequest.Get(), Collections.<String, String>emptyMap());
+ assertTrue(response.getContent() instanceof MonitoringInputStream);
+ }
+
+ @Test
+ public void interceptRequestStream() throws Exception {
+ MonitoredRequest request = new MonitoredRequest();
+ mHurlStack.executeRequest(request, Collections.<String, String>emptyMap());
+ assertTrue(request.totalRequestBytes > 0);
+ assertEquals(request.totalRequestBytes, request.requestBytesRead);
+ }
+
+ private static class MonitoringInputStream extends FilterInputStream {
+ private MonitoringInputStream(InputStream in) {
+ super(in);
+ }
+ }
+
+ private static class MonitoringOutputStream extends FilterOutputStream {
+ private MonitoredRequest request;
+
+ private MonitoringOutputStream(OutputStream out, MonitoredRequest request, int length) {
+ super(out);
+ this.request = request;
+ this.request.totalRequestBytes = length;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ this.request.requestBytesRead++;
+ out.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ this.request.requestBytesRead += len;
+ out.write(b, off, len);
+ }
+ }
+
+ private static class MonitoredRequest extends TestRequest.PostWithBody {
+ int requestBytesRead = 0;
+ int totalRequestBytes = 0;
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java b/core/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java
new file mode 100644
index 0000000..59a0b1b
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/ImageLoaderTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.widget.ImageView;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ImageLoaderTest {
+ private RequestQueue mRequestQueue;
+ private ImageLoader.ImageCache mImageCache;
+ private ImageLoader mImageLoader;
+
+ @Before
+ public void setUp() {
+ mRequestQueue = mock(RequestQueue.class);
+ mImageCache = mock(ImageLoader.ImageCache.class);
+ mImageLoader = new ImageLoader(mRequestQueue, mImageCache);
+ }
+
+ @Test
+ public void isCachedChecksCache() throws Exception {
+ when(mImageCache.getBitmap(anyString())).thenReturn(null);
+ Assert.assertFalse(mImageLoader.isCached("http://foo", 0, 0));
+ }
+
+ @Test
+ public void getWithCacheHit() throws Exception {
+ Bitmap bitmap = Bitmap.createBitmap(1, 1, null);
+ ImageLoader.ImageListener listener = mock(ImageLoader.ImageListener.class);
+ when(mImageCache.getBitmap(anyString())).thenReturn(bitmap);
+ ImageLoader.ImageContainer ic = mImageLoader.get("http://foo", listener);
+ Assert.assertSame(bitmap, ic.getBitmap());
+ verify(listener).onResponse(ic, true);
+ }
+
+ @Test
+ public void getWithCacheMiss() throws Exception {
+ when(mImageCache.getBitmap(anyString())).thenReturn(null);
+ ImageLoader.ImageListener listener = mock(ImageLoader.ImageListener.class);
+ // Ask for the image to be loaded.
+ mImageLoader.get("http://foo", listener);
+ // Second pass to test deduping logic.
+ mImageLoader.get("http://foo", listener);
+ // Response callback should be called both times.
+ verify(listener, times(2)).onResponse(any(ImageLoader.ImageContainer.class), eq(true));
+ // But request should be enqueued only once.
+ verify(mRequestQueue, times(1)).add(Mockito.<Request<?>>any());
+ }
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch API breaking changes.
+ ImageLoader.getImageListener(null, -1, -1);
+ mImageLoader.setBatchedResponseDelay(1000);
+
+ assertNotNull(
+ ImageLoader.class.getConstructor(RequestQueue.class, ImageLoader.ImageCache.class));
+
+ assertNotNull(
+ ImageLoader.class.getMethod(
+ "getImageListener", ImageView.class, int.class, int.class));
+ assertNotNull(ImageLoader.class.getMethod("isCached", String.class, int.class, int.class));
+ assertNotNull(
+ ImageLoader.class.getMethod(
+ "isCached", String.class, int.class, int.class, ImageView.ScaleType.class));
+ assertNotNull(
+ ImageLoader.class.getMethod("get", String.class, ImageLoader.ImageListener.class));
+ assertNotNull(
+ ImageLoader.class.getMethod(
+ "get",
+ String.class,
+ ImageLoader.ImageListener.class,
+ int.class,
+ int.class));
+ assertNotNull(
+ ImageLoader.class.getMethod(
+ "get",
+ String.class,
+ ImageLoader.ImageListener.class,
+ int.class,
+ int.class,
+ ImageView.ScaleType.class));
+ assertNotNull(ImageLoader.class.getMethod("setBatchedResponseDelay", int.class));
+
+ assertNotNull(
+ ImageLoader.ImageListener.class.getMethod(
+ "onResponse", ImageLoader.ImageContainer.class, boolean.class));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/ImageRequestTest.java b/core/src/test/java/com/android/volley/toolbox/ImageRequestTest.java
new file mode 100644
index 0000000..6b50319
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/ImageRequestTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import com.android.volley.NetworkResponse;
+import com.android.volley.Response;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.shadows.ShadowBitmapFactory;
+
+@RunWith(RobolectricTestRunner.class)
+public class ImageRequestTest {
+
+ @Test
+ public void parseNetworkResponse_resizing() throws Exception {
+ // This is a horrible hack but Robolectric doesn't have a way to provide
+ // width and height hints for decodeByteArray. It works because the byte array
+ // "file:fake" is ASCII encodable and thus the name in Robolectric's fake
+ // bitmap creator survives as-is, and provideWidthAndHeightHints puts
+ // "file:" + name in its lookaside map. I write all this because it will
+ // probably break mysteriously at some point and I feel terrible about your
+ // having to debug it.
+ byte[] jpegBytes = "file:fake".getBytes(StandardCharsets.UTF_8);
+ ShadowBitmapFactory.provideWidthAndHeightHints("fake", 1024, 500);
+ NetworkResponse jpeg = new NetworkResponse(jpegBytes);
+
+ // Scale the image uniformly (maintain the image's aspect ratio) so that
+ // both dimensions (width and height) of the image will be equal to or
+ // less than the corresponding dimension of the view.
+ ScaleType scalteType = ScaleType.CENTER_INSIDE;
+
+ // Exact sizes
+ verifyResize(jpeg, 512, 250, scalteType, 512, 250); // exactly half
+ verifyResize(jpeg, 511, 249, scalteType, 509, 249); // just under half
+ verifyResize(jpeg, 1080, 500, scalteType, 1024, 500); // larger
+ verifyResize(jpeg, 500, 500, scalteType, 500, 244); // keep same ratio
+
+ // Specify only width, preserve aspect ratio
+ verifyResize(jpeg, 512, 0, scalteType, 512, 250);
+ verifyResize(jpeg, 800, 0, scalteType, 800, 390);
+ verifyResize(jpeg, 1024, 0, scalteType, 1024, 500);
+
+ // Specify only height, preserve aspect ratio
+ verifyResize(jpeg, 0, 250, scalteType, 512, 250);
+ verifyResize(jpeg, 0, 391, scalteType, 800, 391);
+ verifyResize(jpeg, 0, 500, scalteType, 1024, 500);
+
+ // No resize
+ verifyResize(jpeg, 0, 0, scalteType, 1024, 500);
+
+ // Scale the image uniformly (maintain the image's aspect ratio) so that
+ // both dimensions (width and height) of the image will be equal to or
+ // larger than the corresponding dimension of the view.
+ scalteType = ScaleType.CENTER_CROP;
+
+ // Exact sizes
+ verifyResize(jpeg, 512, 250, scalteType, 512, 250);
+ verifyResize(jpeg, 511, 249, scalteType, 511, 249);
+ verifyResize(jpeg, 1080, 500, scalteType, 1024, 500);
+ verifyResize(jpeg, 500, 500, scalteType, 1024, 500);
+
+ // Specify only width
+ verifyResize(jpeg, 512, 0, scalteType, 512, 250);
+ verifyResize(jpeg, 800, 0, scalteType, 800, 390);
+ verifyResize(jpeg, 1024, 0, scalteType, 1024, 500);
+
+ // Specify only height
+ verifyResize(jpeg, 0, 250, scalteType, 512, 250);
+ verifyResize(jpeg, 0, 391, scalteType, 800, 391);
+ verifyResize(jpeg, 0, 500, scalteType, 1024, 500);
+
+ // No resize
+ verifyResize(jpeg, 0, 0, scalteType, 1024, 500);
+
+ // Scale in X and Y independently, so that src matches dst exactly. This
+ // may change the aspect ratio of the src.
+ scalteType = ScaleType.FIT_XY;
+
+ // Exact sizes
+ verifyResize(jpeg, 512, 250, scalteType, 512, 250);
+ verifyResize(jpeg, 511, 249, scalteType, 511, 249);
+ verifyResize(jpeg, 1080, 500, scalteType, 1024, 500);
+ verifyResize(jpeg, 500, 500, scalteType, 500, 500);
+
+ // Specify only width
+ verifyResize(jpeg, 512, 0, scalteType, 512, 500);
+ verifyResize(jpeg, 800, 0, scalteType, 800, 500);
+ verifyResize(jpeg, 1024, 0, scalteType, 1024, 500);
+
+ // Specify only height
+ verifyResize(jpeg, 0, 250, scalteType, 1024, 250);
+ verifyResize(jpeg, 0, 391, scalteType, 1024, 391);
+ verifyResize(jpeg, 0, 500, scalteType, 1024, 500);
+
+ // No resize
+ verifyResize(jpeg, 0, 0, scalteType, 1024, 500);
+ }
+
+ private void verifyResize(
+ NetworkResponse networkResponse,
+ int maxWidth,
+ int maxHeight,
+ ScaleType scaleType,
+ int expectedWidth,
+ int expectedHeight) {
+ ImageRequest request =
+ new ImageRequest("", null, maxWidth, maxHeight, scaleType, Config.RGB_565, null);
+ Response<Bitmap> response = request.parseNetworkResponse(networkResponse);
+ assertNotNull(response);
+ assertTrue(response.isSuccess());
+ Bitmap bitmap = response.result;
+ assertNotNull(bitmap);
+ assertEquals(expectedWidth, bitmap.getWidth());
+ assertEquals(expectedHeight, bitmap.getHeight());
+ }
+
+ @Test
+ public void findBestSampleSize() {
+ // desired == actual == 1
+ assertEquals(1, ImageRequest.findBestSampleSize(100, 150, 100, 150));
+
+ // exactly half == 2
+ assertEquals(2, ImageRequest.findBestSampleSize(280, 160, 140, 80));
+
+ // just over half == 1
+ assertEquals(1, ImageRequest.findBestSampleSize(1000, 800, 501, 401));
+
+ // just under 1/4 == 4
+ assertEquals(4, ImageRequest.findBestSampleSize(100, 200, 24, 50));
+ }
+
+ private static byte[] readInputStream(InputStream in) throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int count;
+ while ((count = in.read(buffer)) != -1) {
+ bytes.write(buffer, 0, count);
+ }
+ in.close();
+ return bytes.toByteArray();
+ }
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(
+ ImageRequest.class.getConstructor(
+ String.class,
+ Response.Listener.class,
+ int.class,
+ int.class,
+ Bitmap.Config.class,
+ Response.ErrorListener.class));
+ assertNotNull(
+ ImageRequest.class.getConstructor(
+ String.class,
+ Response.Listener.class,
+ int.class,
+ int.class,
+ ImageView.ScaleType.class,
+ Bitmap.Config.class,
+ Response.ErrorListener.class));
+ assertEquals(ImageRequest.DEFAULT_IMAGE_TIMEOUT_MS, 1000);
+ assertEquals(ImageRequest.DEFAULT_IMAGE_MAX_RETRIES, 2);
+ assertEquals(ImageRequest.DEFAULT_IMAGE_BACKOFF_MULT, 2f, 0);
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/JsonRequestCharsetTest.java b/core/src/test/java/com/android/volley/toolbox/JsonRequestCharsetTest.java
new file mode 100644
index 0000000..70bb2ea
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/JsonRequestCharsetTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.volley.NetworkResponse;
+import com.android.volley.Response;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class JsonRequestCharsetTest {
+
+ /** String in Czech - "Retezec v cestine." */
+ private static final String TEXT_VALUE = "\u0158et\u011bzec v \u010de\u0161tin\u011b.";
+
+ private static final String TEXT_NAME = "text";
+ private static final int TEXT_INDEX = 0;
+
+ /**
+ * Copyright symbol has different encoding in utf-8 and ISO-8859-1, and it doesn't exists in
+ * ISO-8859-2
+ */
+ private static final String COPY_VALUE = "\u00a9";
+
+ private static final String COPY_NAME = "copyright";
+ private static final int COPY_INDEX = 1;
+
+ @Test
+ public void defaultCharsetJsonObject() throws Exception {
+ // UTF-8 is default charset for JSON
+ byte[] data = jsonObjectString().getBytes(Charset.forName("UTF-8"));
+ NetworkResponse network = new NetworkResponse(data);
+ JsonObjectRequest objectRequest = new JsonObjectRequest("", null, null, null);
+ Response<JSONObject> objectResponse = objectRequest.parseNetworkResponse(network);
+
+ assertNotNull(objectResponse);
+ assertTrue(objectResponse.isSuccess());
+ assertEquals(TEXT_VALUE, objectResponse.result.getString(TEXT_NAME));
+ assertEquals(COPY_VALUE, objectResponse.result.getString(COPY_NAME));
+ }
+
+ @Test
+ public void defaultCharsetJsonArray() throws Exception {
+ // UTF-8 is default charset for JSON
+ byte[] data = jsonArrayString().getBytes(Charset.forName("UTF-8"));
+ NetworkResponse network = new NetworkResponse(data);
+ JsonArrayRequest arrayRequest = new JsonArrayRequest("", null, null);
+ Response<JSONArray> arrayResponse = arrayRequest.parseNetworkResponse(network);
+
+ assertNotNull(arrayResponse);
+ assertTrue(arrayResponse.isSuccess());
+ assertEquals(TEXT_VALUE, arrayResponse.result.getString(TEXT_INDEX));
+ assertEquals(COPY_VALUE, arrayResponse.result.getString(COPY_INDEX));
+ }
+
+ @Test
+ public void specifiedCharsetJsonObject() throws Exception {
+ byte[] data = jsonObjectString().getBytes(Charset.forName("ISO-8859-1"));
+ Map<String, String> headers = new HashMap<String, String>();
+ headers.put("Content-Type", "application/json; charset=iso-8859-1");
+ NetworkResponse network = new NetworkResponse(data, headers);
+ JsonObjectRequest objectRequest = new JsonObjectRequest("", null, null, null);
+ Response<JSONObject> objectResponse = objectRequest.parseNetworkResponse(network);
+
+ assertNotNull(objectResponse);
+ assertTrue(objectResponse.isSuccess());
+ // don't check the text in Czech, ISO-8859-1 doesn't support some Czech characters
+ assertEquals(COPY_VALUE, objectResponse.result.getString(COPY_NAME));
+ }
+
+ @Test
+ public void specifiedCharsetJsonArray() throws Exception {
+ byte[] data = jsonArrayString().getBytes(Charset.forName("ISO-8859-2"));
+ Map<String, String> headers = new HashMap<String, String>();
+ headers.put("Content-Type", "application/json; charset=iso-8859-2");
+ NetworkResponse network = new NetworkResponse(data, headers);
+ JsonArrayRequest arrayRequest = new JsonArrayRequest("", null, null);
+ Response<JSONArray> arrayResponse = arrayRequest.parseNetworkResponse(network);
+
+ assertNotNull(arrayResponse);
+ assertTrue(arrayResponse.isSuccess());
+ assertEquals(TEXT_VALUE, arrayResponse.result.getString(TEXT_INDEX));
+ // don't check the copyright symbol, ISO-8859-2 doesn't have it, but it has Czech characters
+ }
+
+ private static String jsonObjectString() throws Exception {
+ JSONObject json = new JSONObject().put(TEXT_NAME, TEXT_VALUE).put(COPY_NAME, COPY_VALUE);
+ return json.toString();
+ }
+
+ private static String jsonArrayString() throws Exception {
+ JSONArray json = new JSONArray().put(TEXT_INDEX, TEXT_VALUE).put(COPY_INDEX, COPY_VALUE);
+ return json.toString();
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/JsonRequestTest.java b/core/src/test/java/com/android/volley/toolbox/JsonRequestTest.java
new file mode 100644
index 0000000..44c0ad9
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/JsonRequestTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.volley.Response;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class JsonRequestTest {
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(
+ JsonRequest.class.getConstructor(
+ String.class,
+ String.class,
+ Response.Listener.class,
+ Response.ErrorListener.class));
+ assertNotNull(
+ JsonRequest.class.getConstructor(
+ int.class,
+ String.class,
+ String.class,
+ Response.Listener.class,
+ Response.ErrorListener.class));
+
+ assertNotNull(
+ JsonArrayRequest.class.getConstructor(
+ String.class, Response.Listener.class, Response.ErrorListener.class));
+ assertNotNull(
+ JsonArrayRequest.class.getConstructor(
+ int.class,
+ String.class,
+ JSONArray.class,
+ Response.Listener.class,
+ Response.ErrorListener.class));
+
+ assertNotNull(
+ JsonObjectRequest.class.getConstructor(
+ String.class,
+ JSONObject.class,
+ Response.Listener.class,
+ Response.ErrorListener.class));
+ assertNotNull(
+ JsonObjectRequest.class.getConstructor(
+ int.class,
+ String.class,
+ JSONObject.class,
+ Response.Listener.class,
+ Response.ErrorListener.class));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/NetworkImageViewTest.java b/core/src/test/java/com/android/volley/toolbox/NetworkImageViewTest.java
new file mode 100644
index 0000000..fd2073e
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/NetworkImageViewTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ImageView.ScaleType;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class NetworkImageViewTest {
+ private NetworkImageView mNIV;
+ private MockImageLoader mMockImageLoader;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockImageLoader = new MockImageLoader();
+ mNIV = new NetworkImageView(RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void setImageUrl_requestsImage() {
+ mNIV.setLayoutParams(
+ new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ mNIV.setImageUrl("http://foo", mMockImageLoader);
+ assertEquals("http://foo", mMockImageLoader.lastRequestUrl);
+ assertEquals(0, mMockImageLoader.lastMaxWidth);
+ assertEquals(0, mMockImageLoader.lastMaxHeight);
+ }
+
+ // public void testSetImageUrl_setsMaxSize() {
+ // // TODO: Not sure how to make getWidth() return something from an
+ // // instrumentation test. Write this test once it's figured out.
+ // }
+
+ private static class MockImageLoader extends ImageLoader {
+ public MockImageLoader() {
+ super(null, null);
+ }
+
+ public String lastRequestUrl;
+ public int lastMaxWidth;
+ public int lastMaxHeight;
+
+ @Override
+ public ImageContainer get(
+ String requestUrl,
+ ImageListener imageListener,
+ int maxWidth,
+ int maxHeight,
+ ScaleType scaleType) {
+ lastRequestUrl = requestUrl;
+ lastMaxWidth = maxWidth;
+ lastMaxHeight = maxHeight;
+ return null;
+ }
+ }
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(NetworkImageView.class.getConstructor(Context.class));
+ assertNotNull(NetworkImageView.class.getConstructor(Context.class, AttributeSet.class));
+ assertNotNull(
+ NetworkImageView.class.getConstructor(
+ Context.class, AttributeSet.class, int.class));
+
+ assertNotNull(
+ NetworkImageView.class.getMethod("setImageUrl", String.class, ImageLoader.class));
+ assertNotNull(NetworkImageView.class.getMethod("setDefaultImageDrawable", Drawable.class));
+ assertNotNull(NetworkImageView.class.getMethod("setDefaultImageBitmap", Bitmap.class));
+ assertNotNull(NetworkImageView.class.getMethod("setDefaultImageResId", int.class));
+ assertNotNull(NetworkImageView.class.getMethod("setErrorImageDrawable", Drawable.class));
+ assertNotNull(NetworkImageView.class.getMethod("setErrorImageBitmap", Bitmap.class));
+ assertNotNull(NetworkImageView.class.getMethod("setErrorImageResId", int.class));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/PoolingByteArrayOutputStreamTest.java b/core/src/test/java/com/android/volley/toolbox/PoolingByteArrayOutputStreamTest.java
new file mode 100644
index 0000000..266edcd
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/PoolingByteArrayOutputStreamTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class PoolingByteArrayOutputStreamTest {
+ @Test
+ public void pooledOneBuffer() throws IOException {
+ ByteArrayPool pool = new ByteArrayPool(32768);
+ writeOneBuffer(pool);
+ writeOneBuffer(pool);
+ writeOneBuffer(pool);
+ }
+
+ @Test
+ public void pooledIndividualWrites() throws IOException {
+ ByteArrayPool pool = new ByteArrayPool(32768);
+ writeBytesIndividually(pool);
+ writeBytesIndividually(pool);
+ writeBytesIndividually(pool);
+ }
+
+ @Test
+ public void unpooled() throws IOException {
+ ByteArrayPool pool = new ByteArrayPool(0);
+ writeOneBuffer(pool);
+ writeOneBuffer(pool);
+ writeOneBuffer(pool);
+ }
+
+ @Test
+ public void unpooledIndividualWrites() throws IOException {
+ ByteArrayPool pool = new ByteArrayPool(0);
+ writeBytesIndividually(pool);
+ writeBytesIndividually(pool);
+ writeBytesIndividually(pool);
+ }
+
+ private void writeOneBuffer(ByteArrayPool pool) throws IOException {
+ byte[] data = new byte[16384];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte) (i & 0xff);
+ }
+ PoolingByteArrayOutputStream os = new PoolingByteArrayOutputStream(pool);
+ os.write(data);
+
+ assertTrue(Arrays.equals(data, os.toByteArray()));
+ }
+
+ private void writeBytesIndividually(ByteArrayPool pool) {
+ byte[] data = new byte[16384];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte) (i & 0xff);
+ }
+ PoolingByteArrayOutputStream os = new PoolingByteArrayOutputStream(pool);
+ for (int i = 0; i < data.length; i++) {
+ os.write(data[i]);
+ }
+
+ assertTrue(Arrays.equals(data, os.toByteArray()));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/RequestFutureTest.java b/core/src/test/java/com/android/volley/toolbox/RequestFutureTest.java
new file mode 100644
index 0000000..5b5c975
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/RequestFutureTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.volley.Request;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class RequestFutureTest {
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(RequestFuture.class.getMethod("newFuture"));
+ assertNotNull(RequestFuture.class.getMethod("setRequest", Request.class));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/RequestQueueTest.java b/core/src/test/java/com/android/volley/toolbox/RequestQueueTest.java
new file mode 100644
index 0000000..1899b71
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/RequestQueueTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.volley.Cache;
+import com.android.volley.Network;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.ResponseDelivery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class RequestQueueTest {
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(
+ RequestQueue.class.getConstructor(
+ Cache.class, Network.class, int.class, ResponseDelivery.class));
+ assertNotNull(RequestQueue.class.getConstructor(Cache.class, Network.class, int.class));
+ assertNotNull(RequestQueue.class.getConstructor(Cache.class, Network.class));
+
+ assertNotNull(RequestQueue.class.getMethod("start"));
+ assertNotNull(RequestQueue.class.getMethod("stop"));
+ assertNotNull(RequestQueue.class.getMethod("getSequenceNumber"));
+ assertNotNull(RequestQueue.class.getMethod("getCache"));
+ assertNotNull(RequestQueue.class.getMethod("cancelAll", RequestQueue.RequestFilter.class));
+ assertNotNull(RequestQueue.class.getMethod("cancelAll", Object.class));
+ assertNotNull(RequestQueue.class.getMethod("add", Request.class));
+ assertNotNull(RequestQueue.class.getDeclaredMethod("finish", Request.class));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/RequestTest.java b/core/src/test/java/com/android/volley/toolbox/RequestTest.java
new file mode 100644
index 0000000..0911ad6
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/RequestTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.volley.Cache;
+import com.android.volley.NetworkResponse;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.RetryPolicy;
+import com.android.volley.VolleyError;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class RequestTest {
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(
+ Request.class.getConstructor(
+ int.class, String.class, Response.ErrorListener.class));
+
+ assertNotNull(Request.class.getMethod("getMethod"));
+ assertNotNull(Request.class.getMethod("setTag", Object.class));
+ assertNotNull(Request.class.getMethod("getTag"));
+ assertNotNull(Request.class.getMethod("getErrorListener"));
+ assertNotNull(Request.class.getMethod("getTrafficStatsTag"));
+ assertNotNull(Request.class.getMethod("setRetryPolicy", RetryPolicy.class));
+ assertNotNull(Request.class.getMethod("addMarker", String.class));
+ assertNotNull(Request.class.getDeclaredMethod("finish", String.class));
+ assertNotNull(Request.class.getMethod("setRequestQueue", RequestQueue.class));
+ assertNotNull(Request.class.getMethod("setSequence", int.class));
+ assertNotNull(Request.class.getMethod("getSequence"));
+ assertNotNull(Request.class.getMethod("getUrl"));
+ assertNotNull(Request.class.getMethod("getCacheKey"));
+ assertNotNull(Request.class.getMethod("setCacheEntry", Cache.Entry.class));
+ assertNotNull(Request.class.getMethod("getCacheEntry"));
+ assertNotNull(Request.class.getMethod("cancel"));
+ assertNotNull(Request.class.getMethod("isCanceled"));
+ assertNotNull(Request.class.getMethod("getHeaders"));
+ assertNotNull(Request.class.getDeclaredMethod("getParams"));
+ assertNotNull(Request.class.getDeclaredMethod("getParamsEncoding"));
+ assertNotNull(Request.class.getMethod("getBodyContentType"));
+ assertNotNull(Request.class.getMethod("getBody"));
+ assertNotNull(Request.class.getMethod("setShouldCache", boolean.class));
+ assertNotNull(Request.class.getMethod("shouldCache"));
+ assertNotNull(Request.class.getMethod("getPriority"));
+ assertNotNull(Request.class.getMethod("getTimeoutMs"));
+ assertNotNull(Request.class.getMethod("getRetryPolicy"));
+ assertNotNull(Request.class.getMethod("markDelivered"));
+ assertNotNull(Request.class.getMethod("hasHadResponseDelivered"));
+ assertNotNull(
+ Request.class.getDeclaredMethod("parseNetworkResponse", NetworkResponse.class));
+ assertNotNull(Request.class.getDeclaredMethod("parseNetworkError", VolleyError.class));
+ assertNotNull(Request.class.getDeclaredMethod("deliverResponse", Object.class));
+ assertNotNull(Request.class.getMethod("deliverError", VolleyError.class));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/ResponseTest.java b/core/src/test/java/com/android/volley/toolbox/ResponseTest.java
new file mode 100644
index 0000000..44438fa
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/ResponseTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.volley.Cache;
+import com.android.volley.NetworkResponse;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ResponseTest {
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(Response.class.getMethod("success", Object.class, Cache.Entry.class));
+ assertNotNull(Response.class.getMethod("error", VolleyError.class));
+ assertNotNull(Response.class.getMethod("isSuccess"));
+
+ assertNotNull(Response.Listener.class.getDeclaredMethod("onResponse", Object.class));
+
+ assertNotNull(
+ Response.ErrorListener.class.getDeclaredMethod(
+ "onErrorResponse", VolleyError.class));
+
+ assertNotNull(
+ NetworkResponse.class.getConstructor(
+ int.class, byte[].class, Map.class, boolean.class, long.class));
+ assertNotNull(
+ NetworkResponse.class.getConstructor(
+ int.class, byte[].class, Map.class, boolean.class));
+ assertNotNull(NetworkResponse.class.getConstructor(byte[].class));
+ assertNotNull(NetworkResponse.class.getConstructor(byte[].class, Map.class));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/toolbox/StringRequestTest.java b/core/src/test/java/com/android/volley/toolbox/StringRequestTest.java
new file mode 100644
index 0000000..0ecb06b
--- /dev/null
+++ b/core/src/test/java/com/android/volley/toolbox/StringRequestTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.toolbox;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.volley.Response;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class StringRequestTest {
+
+ @Test
+ public void publicMethods() throws Exception {
+ // Catch-all test to find API-breaking changes.
+ assertNotNull(
+ StringRequest.class.getConstructor(
+ String.class, Response.Listener.class, Response.ErrorListener.class));
+ assertNotNull(
+ StringRequest.class.getConstructor(
+ int.class,
+ String.class,
+ Response.Listener.class,
+ Response.ErrorListener.class));
+ }
+}
diff --git a/core/src/test/java/com/android/volley/utils/CacheTestUtils.java b/core/src/test/java/com/android/volley/utils/CacheTestUtils.java
new file mode 100644
index 0000000..5980712
--- /dev/null
+++ b/core/src/test/java/com/android/volley/utils/CacheTestUtils.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.utils;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import com.android.volley.Cache;
+import java.util.Random;
+
+public class CacheTestUtils {
+
+ /**
+ * Makes a random cache entry.
+ *
+ * @param data Data to use, or null to use random data
+ * @param isExpired Whether the TTLs should be set such that this entry is expired
+ * @param needsRefresh Whether the TTLs should be set such that this entry needs refresh
+ */
+ public static Cache.Entry makeRandomCacheEntry(
+ byte[] data, boolean isExpired, boolean needsRefresh) {
+ Random random = new Random();
+ Cache.Entry entry = new Cache.Entry();
+ if (data != null) {
+ entry.data = data;
+ } else {
+ entry.data = new byte[random.nextInt(1024)];
+ }
+ entry.etag = String.valueOf(random.nextLong());
+ entry.lastModified = random.nextLong();
+ entry.ttl = isExpired ? 0 : Long.MAX_VALUE;
+ entry.softTtl = needsRefresh ? 0 : Long.MAX_VALUE;
+ return entry;
+ }
+
+ /**
+ * Like {@link #makeRandomCacheEntry(byte[], boolean, boolean)} but defaults to an unexpired
+ * entry.
+ */
+ public static Cache.Entry makeRandomCacheEntry(byte[] data) {
+ return makeRandomCacheEntry(data, false, false);
+ }
+
+ public static void assertThatEntriesAreEqual(Cache.Entry actual, Cache.Entry expected) {
+ assertNotNull(actual);
+ assertThat(actual.data, is(equalTo(expected.data)));
+ assertThat(actual.etag, is(equalTo(expected.etag)));
+ assertThat(actual.lastModified, is(equalTo(expected.lastModified)));
+ assertThat(actual.responseHeaders, is(equalTo(expected.responseHeaders)));
+ assertThat(actual.serverDate, is(equalTo(expected.serverDate)));
+ assertThat(actual.softTtl, is(equalTo(expected.softTtl)));
+ assertThat(actual.ttl, is(equalTo(expected.ttl)));
+ }
+
+ public static Cache.Entry randomData(int length) {
+ Cache.Entry entry = new Cache.Entry();
+ byte[] data = new byte[length];
+ new Random(42).nextBytes(data); // explicit seed for reproducible results
+ entry.data = data;
+ return entry;
+ }
+
+ public static int getEntrySizeOnDisk(String key) {
+ // Header size is:
+ // 4 bytes for magic int
+ // 8 + len(key) bytes for key (long length)
+ // 8 bytes for etag (long length + 0 characters)
+ // 32 bytes for serverDate, lastModified, ttl, and softTtl longs
+ // 4 bytes for length of header list int
+ // == 56 + len(key) bytes total.
+ return 56 + key.length();
+ }
+}
diff --git a/core/src/test/java/com/android/volley/utils/ImmediateResponseDelivery.java b/core/src/test/java/com/android/volley/utils/ImmediateResponseDelivery.java
new file mode 100644
index 0000000..67e5923
--- /dev/null
+++ b/core/src/test/java/com/android/volley/utils/ImmediateResponseDelivery.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley.utils;
+
+import com.android.volley.ExecutorDelivery;
+import java.util.concurrent.Executor;
+
+/**
+ * A ResponseDelivery for testing that immediately delivers responses instead of posting back to the
+ * main thread.
+ */
+public class ImmediateResponseDelivery extends ExecutorDelivery {
+
+ public ImmediateResponseDelivery() {
+ super(
+ new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ });
+ }
+}