1 // Copyright 2015 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net; 6 7 import static com.google.common.truth.Truth.assertThat; 8 9 import static org.junit.Assert.assertThrows; 10 11 import static org.chromium.net.truth.UrlResponseInfoSubject.assertThat; 12 13 import android.os.ConditionVariable; 14 15 import androidx.test.ext.junit.runners.AndroidJUnit4; 16 import androidx.test.filters.SmallTest; 17 18 import com.google.common.collect.Range; 19 20 import org.junit.After; 21 import org.junit.Before; 22 import org.junit.Rule; 23 import org.junit.Test; 24 import org.junit.rules.ExpectedException; 25 import org.junit.runner.RunWith; 26 27 import org.chromium.base.test.util.DoNotBatch; 28 import org.chromium.net.CronetTestRule.CronetImplementation; 29 import org.chromium.net.CronetTestRule.IgnoreFor; 30 import org.chromium.net.TestUrlRequestCallback.ResponseStep; 31 import org.chromium.net.UrlRequest.Status; 32 import org.chromium.net.UrlRequest.StatusListener; 33 import org.chromium.net.impl.LoadState; 34 import org.chromium.net.impl.UrlRequestBase; 35 36 import java.io.IOException; 37 import java.util.concurrent.Executor; 38 import java.util.concurrent.Executors; 39 40 /** 41 * Tests that {@link org.chromium.net.impl.CronetUrlRequest#getStatus(StatusListener)} works as 42 * expected. 43 */ 44 @DoNotBatch(reason = "crbug/1459563") 45 @RunWith(AndroidJUnit4.class) 46 public class GetStatusTest { 47 @Rule public final CronetTestRule mTestRule = CronetTestRule.withAutomaticEngineStartup(); 48 @Rule public ExpectedException thrown = ExpectedException.none(); 49 50 @Before setUp()51 public void setUp() throws Exception { 52 assertThat( 53 NativeTestServer.startNativeTestServer( 54 mTestRule.getTestFramework().getContext())) 55 .isTrue(); 56 } 57 58 @After tearDown()59 public void tearDown() throws Exception { 60 NativeTestServer.shutdownNativeTestServer(); 61 } 62 63 @Test 64 @SmallTest testSimpleGet()65 public void testSimpleGet() throws Exception { 66 String url = NativeTestServer.getEchoMethodURL(); 67 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 68 callback.setAutoAdvance(false); 69 UrlRequest.Builder builder = 70 mTestRule 71 .getTestFramework() 72 .getEngine() 73 .newUrlRequestBuilder(url, callback, callback.getExecutor()); 74 UrlRequest urlRequest = builder.build(); 75 // Calling before request is started should give Status.INVALID, 76 // since the native adapter is not created. 77 TestStatusListener statusListener0 = new TestStatusListener(); 78 urlRequest.getStatus(statusListener0); 79 statusListener0.waitUntilOnStatusCalled(); 80 assertThat(statusListener0.mOnStatusCalled).isTrue(); 81 assertThat(statusListener0.mStatus).isEqualTo(Status.INVALID); 82 83 urlRequest.start(); 84 85 // Should receive a valid status. 86 TestStatusListener statusListener1 = new TestStatusListener(); 87 urlRequest.getStatus(statusListener1); 88 statusListener1.waitUntilOnStatusCalled(); 89 assertThat(statusListener1.mOnStatusCalled).isTrue(); 90 assertThat(statusListener1.mStatus) 91 .isIn(Range.closed(Status.IDLE, Status.READING_RESPONSE)); 92 callback.waitForNextStep(); 93 assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED); 94 callback.startNextRead(urlRequest); 95 96 // Should receive a valid status. 97 TestStatusListener statusListener2 = new TestStatusListener(); 98 urlRequest.getStatus(statusListener2); 99 statusListener2.waitUntilOnStatusCalled(); 100 assertThat(statusListener2.mOnStatusCalled).isTrue(); 101 assertThat(statusListener1.mStatus) 102 .isIn(Range.closed(Status.IDLE, Status.READING_RESPONSE)); 103 104 callback.waitForNextStep(); 105 assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_READ_COMPLETED); 106 107 callback.startNextRead(urlRequest); 108 callback.blockForDone(); 109 110 // Calling after request done should give Status.INVALID, since 111 // the native adapter is destroyed. 112 TestStatusListener statusListener3 = new TestStatusListener(); 113 urlRequest.getStatus(statusListener3); 114 statusListener3.waitUntilOnStatusCalled(); 115 assertThat(statusListener3.mOnStatusCalled).isTrue(); 116 assertThat(statusListener3.mStatus).isEqualTo(Status.INVALID); 117 118 assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200); 119 assertThat(callback.mResponseAsString).isEqualTo("GET"); 120 } 121 122 @Test 123 @SmallTest testInvalidLoadState()124 public void testInvalidLoadState() throws Exception { 125 assertThrows( 126 IllegalArgumentException.class, 127 () -> UrlRequestBase.convertLoadState(LoadState.OBSOLETE_WAITING_FOR_APPCACHE)); 128 // Expected throw because LoadState.WAITING_FOR_APPCACHE is not mapped. 129 130 thrown.expect(Throwable.class); 131 UrlRequestBase.convertLoadState(-1); 132 UrlRequestBase.convertLoadState(16); 133 } 134 135 @Test 136 @SmallTest 137 @IgnoreFor( 138 implementations = {CronetImplementation.FALLBACK}, 139 reason = "Relies on timings which are not respected by the fallback implementation") 140 // Regression test for crbug.com/606872. testGetStatusForUpload()141 public void testGetStatusForUpload() throws Exception { 142 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 143 UrlRequest.Builder builder = 144 mTestRule 145 .getTestFramework() 146 .getEngine() 147 .newUrlRequestBuilder( 148 NativeTestServer.getEchoBodyURL(), 149 callback, 150 callback.getExecutor()); 151 152 final ConditionVariable block = new ConditionVariable(); 153 // Use a separate executor for UploadDataProvider so the upload can be 154 // stalled while getStatus gets processed. 155 Executor uploadProviderExecutor = Executors.newSingleThreadExecutor(); 156 TestUploadDataProvider dataProvider = 157 new TestUploadDataProvider( 158 TestUploadDataProvider.SuccessCallbackMode.SYNC, uploadProviderExecutor) { 159 @Override 160 public long getLength() throws IOException { 161 // Pause the data provider. 162 block.block(); 163 block.close(); 164 return super.getLength(); 165 } 166 }; 167 dataProvider.addRead("test".getBytes()); 168 builder.setUploadDataProvider(dataProvider, uploadProviderExecutor); 169 builder.addHeader("Content-Type", "useless/string"); 170 UrlRequest urlRequest = builder.build(); 171 TestStatusListener statusListener = new TestStatusListener(); 172 urlRequest.start(); 173 // Call getStatus() immediately after start(), which will post 174 // startInternal() to the upload provider's executor because there is an 175 // upload. When CronetUrlRequestAdapter::GetStatusOnNetworkThread is 176 // executed, the |url_request_| is null. 177 urlRequest.getStatus(statusListener); 178 statusListener.waitUntilOnStatusCalled(); 179 assertThat(statusListener.mOnStatusCalled).isTrue(); 180 // The request should be in IDLE state because GetStatusOnNetworkThread 181 // is called before |url_request_| is initialized and started. 182 assertThat(statusListener.mStatus).isEqualTo(Status.IDLE); 183 // Resume the UploadDataProvider. 184 block.open(); 185 186 // Make sure the request is successful and there is no crash. 187 callback.blockForDone(); 188 dataProvider.assertClosed(); 189 190 assertThat(dataProvider.getUploadedLength()).isEqualTo(4); 191 assertThat(dataProvider.getNumReadCalls()).isEqualTo(1); 192 assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0); 193 194 assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200); 195 assertThat(callback.mResponseAsString).isEqualTo("test"); 196 } 197 198 private static class TestStatusListener extends StatusListener { 199 boolean mOnStatusCalled; 200 int mStatus = Integer.MAX_VALUE; 201 private final ConditionVariable mBlock = new ConditionVariable(); 202 203 @Override onStatus(int status)204 public void onStatus(int status) { 205 mOnStatusCalled = true; 206 mStatus = status; 207 mBlock.open(); 208 } 209 waitUntilOnStatusCalled()210 public void waitUntilOnStatusCalled() { 211 mBlock.block(); 212 mBlock.close(); 213 } 214 } 215 } 216