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 android.os.ConditionVariable; 8 9 import java.io.IOException; 10 import java.nio.ByteBuffer; 11 import java.util.List; 12 import java.util.concurrent.Executor; 13 14 /** 15 * An UploadDataProvider that allows tests to invoke {@code onReadSucceeded} 16 * and {@code onRewindSucceeded} on the UploadDataSink directly. 17 * Chunked mode is not supported here, since the main interest is to test 18 * different order of init/read/rewind calls. 19 */ 20 class TestDrivenDataProvider extends UploadDataProvider { 21 private final Executor mExecutor; 22 private final List<byte[]> mReads; 23 private final ConditionVariable mWaitForReadRequest = new ConditionVariable(); 24 private final ConditionVariable mWaitForRewindRequest = new ConditionVariable(); 25 // Lock used to synchronize access to mReadPending and mRewindPending. 26 private final Object mLock = new Object(); 27 28 private int mNextRead; 29 30 // Only accessible when holding mLock. 31 32 private boolean mReadPending; 33 private boolean mRewindPending; 34 private int mNumRewindCalls; 35 private int mNumReadCalls; 36 37 /** 38 * Constructor. 39 * @param Executor executor. Executor to run callbacks of UploadDataSink. 40 * @param List<byte[]> reads. Results to be returned by successful read 41 * requests. Returned bytes must all fit within the read buffer 42 * provided by Cronet. After a rewind, if there is one, all reads 43 * will be repeated. 44 */ TestDrivenDataProvider(Executor executor, List<byte[]> reads)45 TestDrivenDataProvider(Executor executor, List<byte[]> reads) { 46 mExecutor = executor; 47 mReads = reads; 48 } 49 50 // Called by UploadDataSink on the main thread. 51 @Override getLength()52 public long getLength() { 53 long length = 0; 54 for (byte[] read : mReads) { 55 length += read.length; 56 } 57 return length; 58 } 59 60 // Called by UploadDataSink on the executor thread. 61 @Override read(final UploadDataSink uploadDataSink, final ByteBuffer byteBuffer)62 public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteBuffer) 63 throws IOException { 64 synchronized (mLock) { 65 ++mNumReadCalls; 66 assertIdle(); 67 68 mReadPending = true; 69 if (mNextRead != mReads.size()) { 70 if ((byteBuffer.limit() - byteBuffer.position()) < mReads.get(mNextRead).length) { 71 throw new IllegalStateException("Read buffer smaller than expected."); 72 } 73 byteBuffer.put(mReads.get(mNextRead)); 74 ++mNextRead; 75 } else { 76 throw new IllegalStateException("Too many reads: " + mNextRead); 77 } 78 mWaitForReadRequest.open(); 79 } 80 } 81 82 // Called by UploadDataSink on the executor thread. 83 @Override rewind(final UploadDataSink uploadDataSink)84 public void rewind(final UploadDataSink uploadDataSink) throws IOException { 85 synchronized (mLock) { 86 ++mNumRewindCalls; 87 assertIdle(); 88 89 if (mNextRead == 0) { 90 // Should never try and rewind when rewinding does nothing. 91 throw new IllegalStateException("Unexpected rewind when already at beginning"); 92 } 93 mRewindPending = true; 94 mNextRead = 0; 95 mWaitForRewindRequest.open(); 96 } 97 } 98 99 // Called by test fixture on the main thread. onReadSucceeded(final UploadDataSink uploadDataSink)100 public void onReadSucceeded(final UploadDataSink uploadDataSink) { 101 Runnable completeRunnable = 102 new Runnable() { 103 @Override 104 public void run() { 105 synchronized (mLock) { 106 if (!mReadPending) { 107 throw new IllegalStateException("No read pending."); 108 } 109 mReadPending = false; 110 uploadDataSink.onReadSucceeded(false); 111 } 112 } 113 }; 114 mExecutor.execute(completeRunnable); 115 } 116 117 // Called by test fixture on the main thread. onRewindSucceeded(final UploadDataSink uploadDataSink)118 public void onRewindSucceeded(final UploadDataSink uploadDataSink) { 119 Runnable completeRunnable = 120 new Runnable() { 121 @Override 122 public void run() { 123 synchronized (mLock) { 124 if (!mRewindPending) { 125 throw new IllegalStateException("No rewind pending."); 126 } 127 mRewindPending = false; 128 uploadDataSink.onRewindSucceeded(); 129 } 130 } 131 }; 132 mExecutor.execute(completeRunnable); 133 } 134 135 // Called by test fixture on the main thread. getNumReadCalls()136 public int getNumReadCalls() { 137 synchronized (mLock) { 138 return mNumReadCalls; 139 } 140 } 141 142 // Called by test fixture on the main thread. getNumRewindCalls()143 public int getNumRewindCalls() { 144 synchronized (mLock) { 145 return mNumRewindCalls; 146 } 147 } 148 149 // Called by test fixture on the main thread. waitForReadRequest()150 public void waitForReadRequest() { 151 mWaitForReadRequest.block(); 152 } 153 154 // Called by test fixture on the main thread. resetWaitForReadRequest()155 public void resetWaitForReadRequest() { 156 mWaitForReadRequest.close(); 157 } 158 159 // Called by test fixture on the main thread. waitForRewindRequest()160 public void waitForRewindRequest() { 161 mWaitForRewindRequest.block(); 162 } 163 164 // Called by test fixture on the main thread. assertReadNotPending()165 public void assertReadNotPending() { 166 synchronized (mLock) { 167 if (mReadPending) { 168 throw new IllegalStateException("Read is pending."); 169 } 170 } 171 } 172 173 // Called by test fixture on the main thread. assertRewindNotPending()174 public void assertRewindNotPending() { 175 synchronized (mLock) { 176 if (mRewindPending) { 177 throw new IllegalStateException("Rewind is pending."); 178 } 179 } 180 } 181 182 /** Helper method to ensure no read or rewind is in progress. */ assertIdle()183 private void assertIdle() { 184 assertReadNotPending(); 185 assertRewindNotPending(); 186 } 187 } 188