diff options
Diffstat (limited to 'src/test/java/com')
7 files changed, 1214 insertions, 2 deletions
diff --git a/src/test/java/com/android/volley/AsyncRequestQueueTest.java b/src/test/java/com/android/volley/AsyncRequestQueueTest.java new file mode 100644 index 0000000..54ff0a1 --- /dev/null +++ b/src/test/java/com/android/volley/AsyncRequestQueueTest.java @@ -0,0 +1,164 @@ +/* + * 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.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.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 AsyncRequestQueue queue; + + @Before + public void setUp() throws Exception { + ResponseDelivery mDelivery = new ImmediateResponseDelivery(); + initMocks(this); + queue = + new AsyncRequestQueue.Builder(mMockNetwork) + .setAsyncCache(new NoAsyncCache()) + .setResponseDelivery(mDelivery) + .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(); + } + + @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(); + } +} diff --git a/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java b/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java new file mode 100644 index 0000000..cedb6ff --- /dev/null +++ b/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java @@ -0,0 +1,381 @@ +/* + * 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.cronet; + +import static org.junit.Assert.assertEquals; +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.cronet.CronetHttpStack.CurlCommandLogger; +import com.android.volley.mock.TestRequest; +import com.android.volley.toolbox.AsyncHttpStack.OnRequestComplete; +import com.android.volley.toolbox.UrlRewriter; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.MoreExecutors; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.chromium.net.CronetEngine; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class CronetHttpStackTest { + @Mock private CurlCommandLogger mMockCurlCommandLogger; + @Mock private OnRequestComplete mMockOnRequestComplete; + @Mock private UrlRewriter mMockUrlRewriter; + + // A fake would be ideal here, but Cronet doesn't (yet) provide one, and at the moment we aren't + // exercising the full response flow. + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private CronetEngine mMockCronetEngine; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void curlLogging_disabled() { + CronetHttpStack stack = + createStack( + new Consumer<CronetHttpStack.Builder>() { + @Override + public void accept(CronetHttpStack.Builder builder) { + // Default parameters should not enable cURL logging. + } + }); + + stack.executeRequest( + new TestRequest.Get(), ImmutableMap.<String, String>of(), mMockOnRequestComplete); + + verify(mMockCurlCommandLogger, never()).logCurlCommand(anyString()); + } + + @Test + public void curlLogging_simpleTextRequest() { + CronetHttpStack stack = + createStack( + new Consumer<CronetHttpStack.Builder>() { + @Override + public void accept(CronetHttpStack.Builder builder) { + builder.setCurlLoggingEnabled(true); + } + }); + + stack.executeRequest( + new TestRequest.Get(), ImmutableMap.<String, String>of(), mMockOnRequestComplete); + + ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); + verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); + assertEquals("curl -X GET \"http://foo.com\"", curlCommandCaptor.getValue()); + } + + @Test + public void curlLogging_rewrittenUrl() { + CronetHttpStack stack = + createStack( + new Consumer<CronetHttpStack.Builder>() { + @Override + public void accept(CronetHttpStack.Builder builder) { + builder.setCurlLoggingEnabled(true) + .setUrlRewriter(mMockUrlRewriter); + } + }); + when(mMockUrlRewriter.rewriteUrl("http://foo.com")).thenReturn("http://bar.com"); + + stack.executeRequest( + new TestRequest.Get(), ImmutableMap.<String, String>of(), mMockOnRequestComplete); + + ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); + verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); + assertEquals("curl -X GET \"http://bar.com\"", curlCommandCaptor.getValue()); + } + + @Test + public void curlLogging_headers_withoutTokens() { + CronetHttpStack stack = + createStack( + new Consumer<CronetHttpStack.Builder>() { + @Override + public void accept(CronetHttpStack.Builder builder) { + builder.setCurlLoggingEnabled(true); + } + }); + + stack.executeRequest( + new TestRequest.Delete() { + @Override + public Map<String, String> getHeaders() { + return ImmutableMap.of( + "SomeHeader", "SomeValue", + "Authorization", "SecretToken"); + } + }, + ImmutableMap.of("SomeOtherHeader", "SomeValue"), + mMockOnRequestComplete); + + ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); + verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); + // NOTE: Header order is stable because the implementation uses a TreeMap. + assertEquals( + "curl -X DELETE --header \"Authorization: [REDACTED]\" " + + "--header \"SomeHeader: SomeValue\" " + + "--header \"SomeOtherHeader: SomeValue\" \"http://foo.com\"", + curlCommandCaptor.getValue()); + } + + @Test + public void curlLogging_headers_withTokens() { + CronetHttpStack stack = + createStack( + new Consumer<CronetHttpStack.Builder>() { + @Override + public void accept(CronetHttpStack.Builder builder) { + builder.setCurlLoggingEnabled(true) + .setLogAuthTokensInCurlCommands(true); + } + }); + + stack.executeRequest( + new TestRequest.Delete() { + @Override + public Map<String, String> getHeaders() { + return ImmutableMap.of( + "SomeHeader", "SomeValue", + "Authorization", "SecretToken"); + } + }, + ImmutableMap.of("SomeOtherHeader", "SomeValue"), + mMockOnRequestComplete); + + ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); + verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); + // NOTE: Header order is stable because the implementation uses a TreeMap. + assertEquals( + "curl -X DELETE --header \"Authorization: SecretToken\" " + + "--header \"SomeHeader: SomeValue\" " + + "--header \"SomeOtherHeader: SomeValue\" \"http://foo.com\"", + curlCommandCaptor.getValue()); + } + + @Test + public void curlLogging_textRequest() { + CronetHttpStack stack = + createStack( + new Consumer<CronetHttpStack.Builder>() { + @Override + public void accept(CronetHttpStack.Builder builder) { + builder.setCurlLoggingEnabled(true); + } + }); + + stack.executeRequest( + new TestRequest.PostWithBody() { + @Override + public byte[] getBody() { + try { + return "hello".getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getBodyContentType() { + return "text/plain; charset=UTF-8"; + } + }, + ImmutableMap.<String, String>of(), + mMockOnRequestComplete); + + ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); + verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); + assertEquals( + "curl -X POST " + + "--header \"Content-Type: text/plain; charset=UTF-8\" \"http://foo.com\" " + + "--data-ascii \"hello\"", + curlCommandCaptor.getValue()); + } + + @Test + public void curlLogging_gzipTextRequest() { + CronetHttpStack stack = + createStack( + new Consumer<CronetHttpStack.Builder>() { + @Override + public void accept(CronetHttpStack.Builder builder) { + builder.setCurlLoggingEnabled(true); + } + }); + + stack.executeRequest( + new TestRequest.PostWithBody() { + @Override + public byte[] getBody() { + return new byte[] {1, 2, 3, 4, 5}; + } + + @Override + public String getBodyContentType() { + return "text/plain"; + } + + @Override + public Map<String, String> getHeaders() { + return ImmutableMap.of("Content-Encoding", "gzip, identity"); + } + }, + ImmutableMap.<String, String>of(), + mMockOnRequestComplete); + + ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); + verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); + assertEquals( + "echo 'AQIDBAU=' | base64 -d > /tmp/$$.bin; curl -X POST " + + "--header \"Content-Encoding: gzip, identity\" " + + "--header \"Content-Type: text/plain\" \"http://foo.com\" " + + "--data-binary @/tmp/$$.bin", + curlCommandCaptor.getValue()); + } + + @Test + public void curlLogging_binaryRequest() { + CronetHttpStack stack = + createStack( + new Consumer<CronetHttpStack.Builder>() { + @Override + public void accept(CronetHttpStack.Builder builder) { + builder.setCurlLoggingEnabled(true); + } + }); + + stack.executeRequest( + new TestRequest.PostWithBody() { + @Override + public byte[] getBody() { + return new byte[] {1, 2, 3, 4, 5}; + } + + @Override + public String getBodyContentType() { + return "application/octet-stream"; + } + }, + ImmutableMap.<String, String>of(), + mMockOnRequestComplete); + + ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); + verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); + assertEquals( + "echo 'AQIDBAU=' | base64 -d > /tmp/$$.bin; curl -X POST " + + "--header \"Content-Type: application/octet-stream\" \"http://foo.com\" " + + "--data-binary @/tmp/$$.bin", + curlCommandCaptor.getValue()); + } + + @Test + public void curlLogging_largeRequest() { + CronetHttpStack stack = + createStack( + new Consumer<CronetHttpStack.Builder>() { + @Override + public void accept(CronetHttpStack.Builder builder) { + builder.setCurlLoggingEnabled(true); + } + }); + + stack.executeRequest( + new TestRequest.PostWithBody() { + @Override + public byte[] getBody() { + return new byte[2048]; + } + + @Override + public String getBodyContentType() { + return "application/octet-stream"; + } + }, + ImmutableMap.<String, String>of(), + mMockOnRequestComplete); + + ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); + verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); + assertEquals( + "curl -X POST " + + "--header \"Content-Type: application/octet-stream\" \"http://foo.com\" " + + "[REQUEST BODY TOO LARGE TO INCLUDE]", + curlCommandCaptor.getValue()); + } + + @Test + public void getHeadersEmptyTest() { + List<Map.Entry<String, String>> list = new ArrayList<>(); + List<Header> actual = CronetHttpStack.getHeaders(list); + List<Header> expected = new ArrayList<>(); + assertEquals(expected, actual); + } + + @Test + public void getHeadersNonEmptyTest() { + Map<String, String> headers = new HashMap<>(); + for (int i = 1; i < 5; i++) { + headers.put("key" + i, "value" + i); + } + List<Map.Entry<String, String>> list = new ArrayList<>(headers.entrySet()); + List<Header> actual = CronetHttpStack.getHeaders(list); + List<Header> expected = new ArrayList<>(); + for (int i = 1; i < 5; i++) { + expected.add(new Header("key" + i, "value" + i)); + } + assertHeaderListsEqual(expected, actual); + } + + private void assertHeaderListsEqual(List<Header> expected, List<Header> actual) { + assertEquals(expected.size(), actual.size()); + for (int i = 0; i < expected.size(); i++) { + assertEquals(expected.get(i).getName(), actual.get(i).getName()); + assertEquals(expected.get(i).getValue(), actual.get(i).getValue()); + } + } + + private CronetHttpStack createStack(Consumer<CronetHttpStack.Builder> stackEditor) { + CronetHttpStack.Builder builder = + new CronetHttpStack.Builder(RuntimeEnvironment.application) + .setCronetEngine(mMockCronetEngine) + .setCurlCommandLogger(mMockCurlCommandLogger); + stackEditor.accept(builder); + CronetHttpStack stack = builder.build(); + stack.setBlockingExecutor(MoreExecutors.newDirectExecutorService()); + stack.setNonBlockingExecutor(MoreExecutors.newDirectExecutorService()); + return stack; + } +} diff --git a/src/test/java/com/android/volley/mock/MockAsyncStack.java b/src/test/java/com/android/volley/mock/MockAsyncStack.java new file mode 100644 index 0000000..5ea8343 --- /dev/null +++ b/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/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java b/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java new file mode 100644 index 0000000..91d4062 --- /dev/null +++ b/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/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java b/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java index fec0694..3630379 100644 --- a/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java +++ b/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java @@ -30,6 +30,7 @@ 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; @@ -176,7 +177,7 @@ public class BasicNetworkTest { } @Test - public void noConnection() throws Exception { + public void noConnectionDefault() throws Exception { MockHttpStack mockHttpStack = new MockHttpStack(); mockHttpStack.setExceptionToThrow(new IOException()); BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack); @@ -193,6 +194,43 @@ public class BasicNetworkTest { } @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()); diff --git a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java index ccf68fa..db6e491 100644 --- a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java +++ b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java @@ -59,7 +59,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) -@Config(manifest = "src/main/AndroidManifest.xml", sdk = 16) +@Config(sdk = 16) public class DiskBasedCacheTest { private static final int MAX_SIZE = 1024 * 1024; diff --git a/src/test/java/com/android/volley/utils/CacheTestUtils.java b/src/test/java/com/android/volley/utils/CacheTestUtils.java index 49ab996..5980712 100644 --- a/src/test/java/com/android/volley/utils/CacheTestUtils.java +++ b/src/test/java/com/android/volley/utils/CacheTestUtils.java @@ -16,6 +16,11 @@ 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; @@ -51,4 +56,34 @@ public class CacheTestUtils { 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(); + } } |