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 androidx.test.ext.junit.runners.AndroidJUnit4; 10 import androidx.test.filters.SmallTest; 11 12 import org.junit.After; 13 import org.junit.Before; 14 import org.junit.Rule; 15 import org.junit.Test; 16 import org.junit.runner.RunWith; 17 18 import org.chromium.base.test.util.DoNotBatch; 19 import org.chromium.net.CronetTestRule.CronetImplementation; 20 import org.chromium.net.CronetTestRule.IgnoreFor; 21 import org.chromium.net.impl.CronetUploadDataStream; 22 import org.chromium.net.impl.CronetUrlRequest; 23 24 import java.util.Arrays; 25 import java.util.List; 26 import java.util.concurrent.ExecutorService; 27 import java.util.concurrent.Executors; 28 29 /** 30 * Tests that directly drive {@code CronetUploadDataStream} and {@code UploadDataProvider} to 31 * simulate different ordering of reset, init, read, and rewind calls. 32 */ 33 @DoNotBatch(reason = "crbug/1459563") 34 @RunWith(AndroidJUnit4.class) 35 @IgnoreFor( 36 implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, 37 reason = "crbug.com/1494845: Testing internals of the native implementation") 38 public class CronetUploadTest { 39 @Rule public final CronetTestRule mTestRule = CronetTestRule.withAutomaticEngineStartup(); 40 41 private TestDrivenDataProvider mDataProvider; 42 private CronetUploadDataStream mUploadDataStream; 43 private TestUploadDataStreamHandler mHandler; 44 45 @Before 46 @SuppressWarnings({"PrimitiveArrayPassedToVarargsMethod", "ArraysAsListPrimitiveArray"}) setUp()47 public void setUp() throws Exception { 48 ExecutorService executor = Executors.newSingleThreadExecutor(); 49 List<byte[]> reads = Arrays.asList("hello".getBytes()); 50 mDataProvider = new TestDrivenDataProvider(executor, reads); 51 52 // Creates a no-op CronetUrlRequest, which is not used to drive CronetUploadDataStream. 53 TestUrlRequestCallback callback = new TestUrlRequestCallback(); 54 UrlRequest.Builder builder = 55 mTestRule 56 .getTestFramework() 57 .getEngine() 58 .newUrlRequestBuilder( 59 "https://no-op.url", callback, callback.getExecutor()); 60 UrlRequest urlRequest = builder.build(); 61 62 mUploadDataStream = 63 new CronetUploadDataStream(mDataProvider, executor, (CronetUrlRequest) urlRequest); 64 mHandler = 65 new TestUploadDataStreamHandler( 66 mTestRule.getTestFramework().getContext(), 67 mUploadDataStream.createUploadDataStreamForTesting()); 68 } 69 70 @After tearDown()71 public void tearDown() throws Exception { 72 // Destroy handler's native objects. 73 mHandler.destroyNativeObjects(); 74 } 75 76 /** 77 * Tests that after some data is read, init triggers a rewind, and that before the rewind 78 * completes, init blocks. 79 */ 80 @Test 81 @SmallTest testInitTriggersRewindAndInitBeforeRewindCompletes()82 public void testInitTriggersRewindAndInitBeforeRewindCompletes() throws Exception { 83 // Init completes synchronously and read succeeds. 84 assertThat(mHandler.init()).isTrue(); 85 mHandler.read(); 86 mDataProvider.waitForReadRequest(); 87 mHandler.checkReadCallbackNotInvoked(); 88 mDataProvider.onReadSucceeded(mUploadDataStream); 89 mHandler.waitForReadComplete(); 90 mDataProvider.assertReadNotPending(); 91 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(0); 92 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 93 assertThat(mHandler.getData()).isEqualTo("hello"); 94 95 // Reset and then init, which should trigger a rewind. 96 mHandler.reset(); 97 assertThat(mHandler.getData()).isEmpty(); 98 assertThat(mHandler.init()).isFalse(); 99 mDataProvider.waitForRewindRequest(); 100 mHandler.checkInitCallbackNotInvoked(); 101 102 // Before rewind completes, reset and init should block. 103 mHandler.reset(); 104 assertThat(mHandler.init()).isFalse(); 105 106 // Signal rewind completes, and wait for init to complete. 107 mHandler.checkInitCallbackNotInvoked(); 108 mDataProvider.onRewindSucceeded(mUploadDataStream); 109 mHandler.waitForInitComplete(); 110 mDataProvider.assertRewindNotPending(); 111 112 // Read should complete successfully since init has completed. 113 mHandler.read(); 114 mDataProvider.waitForReadRequest(); 115 mHandler.checkReadCallbackNotInvoked(); 116 mDataProvider.onReadSucceeded(mUploadDataStream); 117 mHandler.waitForReadComplete(); 118 mDataProvider.assertReadNotPending(); 119 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(1); 120 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(2); 121 assertThat(mHandler.getData()).isEqualTo("hello"); 122 } 123 124 /** 125 * Tests that after some data is read, init triggers a rewind, and that after the rewind 126 * completes, init does not block. 127 */ 128 @Test 129 @SmallTest testInitTriggersRewindAndInitAfterRewindCompletes()130 public void testInitTriggersRewindAndInitAfterRewindCompletes() throws Exception { 131 // Init completes synchronously and read succeeds. 132 assertThat(mHandler.init()).isTrue(); 133 mHandler.read(); 134 mDataProvider.waitForReadRequest(); 135 mHandler.checkReadCallbackNotInvoked(); 136 mDataProvider.onReadSucceeded(mUploadDataStream); 137 mHandler.waitForReadComplete(); 138 mDataProvider.assertReadNotPending(); 139 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(0); 140 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 141 assertThat(mHandler.getData()).isEqualTo("hello"); 142 143 // Reset and then init, which should trigger a rewind. 144 mHandler.reset(); 145 assertThat(mHandler.getData()).isEmpty(); 146 assertThat(mHandler.init()).isFalse(); 147 mDataProvider.waitForRewindRequest(); 148 mHandler.checkInitCallbackNotInvoked(); 149 150 // Signal rewind completes, and wait for init to complete. 151 mDataProvider.onRewindSucceeded(mUploadDataStream); 152 mHandler.waitForInitComplete(); 153 mDataProvider.assertRewindNotPending(); 154 155 // Reset and init should not block, since rewind has completed. 156 mHandler.reset(); 157 assertThat(mHandler.init()).isTrue(); 158 159 // Read should complete successfully since init has completed. 160 mHandler.read(); 161 mDataProvider.waitForReadRequest(); 162 mHandler.checkReadCallbackNotInvoked(); 163 mDataProvider.onReadSucceeded(mUploadDataStream); 164 mHandler.waitForReadComplete(); 165 mDataProvider.assertReadNotPending(); 166 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(1); 167 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(2); 168 assertThat(mHandler.getData()).isEqualTo("hello"); 169 } 170 171 /** Tests that if init before read completes, a rewind is triggered when read completes. */ 172 @Test 173 @SmallTest testReadCompleteTriggerRewind()174 public void testReadCompleteTriggerRewind() throws Exception { 175 // Reset and init before read completes. 176 assertThat(mHandler.init()).isTrue(); 177 mHandler.read(); 178 mDataProvider.waitForReadRequest(); 179 mHandler.checkReadCallbackNotInvoked(); 180 mHandler.reset(); 181 // Init should return asynchronously, since there is a pending read. 182 assertThat(mHandler.init()).isFalse(); 183 mDataProvider.assertRewindNotPending(); 184 mHandler.checkInitCallbackNotInvoked(); 185 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(0); 186 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 187 assertThat(mHandler.getData()).isEmpty(); 188 189 // Read completes should trigger a rewind. 190 mDataProvider.onReadSucceeded(mUploadDataStream); 191 mDataProvider.waitForRewindRequest(); 192 mHandler.checkInitCallbackNotInvoked(); 193 mDataProvider.onRewindSucceeded(mUploadDataStream); 194 mHandler.waitForInitComplete(); 195 mDataProvider.assertRewindNotPending(); 196 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(1); 197 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 198 assertThat(mHandler.getData()).isEmpty(); 199 } 200 201 /** 202 * Tests that when init again after rewind completes, no additional rewind is triggered. This 203 * test is the same as testReadCompleteTriggerRewind except that this test invokes reset and 204 * init again in the end. 205 */ 206 @Test 207 @SmallTest testReadCompleteTriggerRewindOnlyOneRewind()208 public void testReadCompleteTriggerRewindOnlyOneRewind() throws Exception { 209 testReadCompleteTriggerRewind(); 210 // Reset and Init again, no rewind should happen. 211 mHandler.reset(); 212 assertThat(mHandler.init()).isTrue(); 213 mDataProvider.assertRewindNotPending(); 214 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(1); 215 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 216 assertThat(mHandler.getData()).isEmpty(); 217 } 218 219 /** 220 * Tests that if reset before read completes, no rewind is triggered, and that a following init 221 * triggers rewind. 222 */ 223 @Test 224 @SmallTest testResetBeforeReadCompleteAndInitTriggerRewind()225 public void testResetBeforeReadCompleteAndInitTriggerRewind() throws Exception { 226 // Reset before read completes. Rewind is not triggered. 227 assertThat(mHandler.init()).isTrue(); 228 mHandler.read(); 229 mDataProvider.waitForReadRequest(); 230 mHandler.checkReadCallbackNotInvoked(); 231 mHandler.reset(); 232 mDataProvider.onReadSucceeded(mUploadDataStream); 233 mDataProvider.assertRewindNotPending(); 234 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(0); 235 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 236 assertThat(mHandler.getData()).isEmpty(); 237 238 // Init should trigger a rewind. 239 assertThat(mHandler.init()).isFalse(); 240 mDataProvider.waitForRewindRequest(); 241 mHandler.checkInitCallbackNotInvoked(); 242 mDataProvider.onRewindSucceeded(mUploadDataStream); 243 mHandler.waitForInitComplete(); 244 mDataProvider.assertRewindNotPending(); 245 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(1); 246 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 247 assertThat(mHandler.getData()).isEmpty(); 248 } 249 250 /** 251 * Tests that there is no crash when native CronetUploadDataStream is destroyed while read is 252 * pending. The test is racy since the read could complete either before or after the Java 253 * CronetUploadDataStream's onDestroyUploadDataStream() method is invoked. However, the test 254 * should pass either way, though we are interested in the latter case. 255 */ 256 @Test 257 @SmallTest testDestroyNativeStreamBeforeReadComplete()258 public void testDestroyNativeStreamBeforeReadComplete() throws Exception { 259 // Start a read and wait for it to be pending. 260 assertThat(mHandler.init()).isTrue(); 261 mHandler.read(); 262 mDataProvider.waitForReadRequest(); 263 mHandler.checkReadCallbackNotInvoked(); 264 265 // Destroy the C++ TestUploadDataStreamHandler. The handler will then 266 // destroy the C++ CronetUploadDataStream it owns on the network thread. 267 // That will result in calling the Java CronetUploadDataSteam's 268 // onUploadDataStreamDestroyed() method on its executor thread, which 269 // will then destroy the CronetUploadDataStreamAdapter. 270 mHandler.destroyNativeObjects(); 271 272 // Make the read complete should not encounter a crash. 273 mDataProvider.onReadSucceeded(mUploadDataStream); 274 275 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(0); 276 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 277 } 278 279 /** 280 * Tests that there is no crash when native CronetUploadDataStream is destroyed while rewind is 281 * pending. The test is racy since rewind could complete either before or after the Java 282 * CronetUploadDataStream's onDestroyUploadDataStream() method is invoked. However, the test 283 * should pass either way, though we are interested in the latter case. 284 */ 285 @Test 286 @SmallTest testDestroyNativeStreamBeforeRewindComplete()287 public void testDestroyNativeStreamBeforeRewindComplete() throws Exception { 288 // Start a read and wait for it to complete. 289 assertThat(mHandler.init()).isTrue(); 290 mHandler.read(); 291 mDataProvider.waitForReadRequest(); 292 mHandler.checkReadCallbackNotInvoked(); 293 mDataProvider.onReadSucceeded(mUploadDataStream); 294 mHandler.waitForReadComplete(); 295 mDataProvider.assertReadNotPending(); 296 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(0); 297 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 298 assertThat(mHandler.getData()).isEqualTo("hello"); 299 300 // Reset and then init, which should trigger a rewind. 301 mHandler.reset(); 302 assertThat(mHandler.getData()).isEmpty(); 303 assertThat(mHandler.init()).isFalse(); 304 mDataProvider.waitForRewindRequest(); 305 mHandler.checkInitCallbackNotInvoked(); 306 307 // Destroy the C++ TestUploadDataStreamHandler. The handler will then 308 // destroy the C++ CronetUploadDataStream it owns on the network thread. 309 // That will result in calling the Java CronetUploadDataSteam's 310 // onUploadDataStreamDestroyed() method on its executor thread, which 311 // will then destroy the CronetUploadDataStreamAdapter. 312 mHandler.destroyNativeObjects(); 313 314 // Signal rewind completes, and wait for init to complete. 315 mDataProvider.onRewindSucceeded(mUploadDataStream); 316 317 assertThat(mDataProvider.getNumRewindCalls()).isEqualTo(1); 318 assertThat(mDataProvider.getNumReadCalls()).isEqualTo(1); 319 } 320 } 321