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.nio.channels.ClosedChannelException; 12 import java.util.ArrayList; 13 import java.util.concurrent.Executor; 14 import java.util.concurrent.atomic.AtomicBoolean; 15 16 /** An UploadDataProvider implementation used in tests. */ 17 public class TestUploadDataProvider extends UploadDataProvider { 18 // Indicates whether all success callbacks are synchronous or asynchronous. 19 // Doesn't apply to errors. 20 public enum SuccessCallbackMode { 21 SYNC, 22 ASYNC 23 } 24 25 // Indicates whether failures should throw exceptions, invoke callbacks 26 // synchronously, or invoke callback asynchronously. 27 public enum FailMode { 28 NONE, 29 THROWN, 30 CALLBACK_SYNC, 31 CALLBACK_ASYNC 32 } 33 34 private ArrayList<byte[]> mReads = new ArrayList<byte[]>(); 35 private final SuccessCallbackMode mSuccessCallbackMode; 36 private final Executor mExecutor; 37 38 private boolean mChunked; 39 40 // Index of read to fail on. 41 private int mReadFailIndex = -1; 42 // Indicates how to fail on a read. 43 private FailMode mReadFailMode = FailMode.NONE; 44 45 private FailMode mRewindFailMode = FailMode.NONE; 46 47 private FailMode mLengthFailMode = FailMode.NONE; 48 49 private int mNumReadCalls; 50 private int mNumRewindCalls; 51 52 private int mNextRead; 53 private boolean mStarted; 54 private boolean mReadPending; 55 private boolean mRewindPending; 56 // Used to ensure there are no read/rewind requests after a failure. 57 private boolean mFailed; 58 59 private AtomicBoolean mClosed = new AtomicBoolean(false); 60 private ConditionVariable mAwaitingClose = new ConditionVariable(false); 61 TestUploadDataProvider( SuccessCallbackMode successCallbackMode, final Executor executor)62 public TestUploadDataProvider( 63 SuccessCallbackMode successCallbackMode, final Executor executor) { 64 mSuccessCallbackMode = successCallbackMode; 65 mExecutor = executor; 66 } 67 68 // Adds the result to be returned by a successful read request. The 69 // returned bytes must all fit within the read buffer provided by Cronet. 70 // After a rewind, if there is one, all reads will be repeated. addRead(byte[] read)71 public void addRead(byte[] read) { 72 if (mStarted) { 73 throw new IllegalStateException("Adding bytes after read"); 74 } 75 mReads.add(read); 76 } 77 setReadFailure(int readFailIndex, FailMode readFailMode)78 public void setReadFailure(int readFailIndex, FailMode readFailMode) { 79 mReadFailIndex = readFailIndex; 80 mReadFailMode = readFailMode; 81 } 82 setLengthFailure()83 public void setLengthFailure() { 84 mLengthFailMode = FailMode.THROWN; 85 } 86 setRewindFailure(FailMode rewindFailMode)87 public void setRewindFailure(FailMode rewindFailMode) { 88 mRewindFailMode = rewindFailMode; 89 } 90 setChunked(boolean chunked)91 public void setChunked(boolean chunked) { 92 mChunked = chunked; 93 } 94 getNumReadCalls()95 public int getNumReadCalls() { 96 return mNumReadCalls; 97 } 98 getNumRewindCalls()99 public int getNumRewindCalls() { 100 return mNumRewindCalls; 101 } 102 103 /** Returns the cumulative length of all data added by calls to addRead. */ 104 @Override getLength()105 public long getLength() throws IOException { 106 if (mClosed.get()) { 107 throw new ClosedChannelException(); 108 } 109 if (mLengthFailMode == FailMode.THROWN) { 110 throw new IllegalStateException("Sync length failure"); 111 } 112 return getUploadedLength(); 113 } 114 getUploadedLength()115 public long getUploadedLength() { 116 if (mChunked) { 117 return -1; 118 } 119 long length = 0; 120 for (byte[] read : mReads) { 121 length += read.length; 122 } 123 return length; 124 } 125 126 @Override read(final UploadDataSink uploadDataSink, final ByteBuffer byteBuffer)127 public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteBuffer) 128 throws IOException { 129 int currentReadCall = mNumReadCalls; 130 ++mNumReadCalls; 131 if (mClosed.get()) { 132 throw new ClosedChannelException(); 133 } 134 assertIdle(); 135 136 if (maybeFailRead(currentReadCall, uploadDataSink)) { 137 mFailed = true; 138 return; 139 } 140 141 mReadPending = true; 142 mStarted = true; 143 144 final boolean finalChunk = (mChunked && mNextRead == mReads.size() - 1); 145 if (mNextRead < mReads.size()) { 146 if ((byteBuffer.limit() - byteBuffer.position()) < mReads.get(mNextRead).length) { 147 throw new IllegalStateException("Read buffer smaller than expected."); 148 } 149 byteBuffer.put(mReads.get(mNextRead)); 150 ++mNextRead; 151 } else { 152 throw new IllegalStateException("Too many reads: " + mNextRead); 153 } 154 155 Runnable completeRunnable = 156 new Runnable() { 157 @Override 158 public void run() { 159 mReadPending = false; 160 uploadDataSink.onReadSucceeded(finalChunk); 161 } 162 }; 163 if (mSuccessCallbackMode == SuccessCallbackMode.SYNC) { 164 completeRunnable.run(); 165 } else { 166 mExecutor.execute(completeRunnable); 167 } 168 } 169 170 @Override rewind(final UploadDataSink uploadDataSink)171 public void rewind(final UploadDataSink uploadDataSink) throws IOException { 172 ++mNumRewindCalls; 173 if (mClosed.get()) { 174 throw new ClosedChannelException(); 175 } 176 assertIdle(); 177 178 if (maybeFailRewind(uploadDataSink)) { 179 mFailed = true; 180 return; 181 } 182 183 if (mNextRead == 0) { 184 // Should never try and rewind when rewinding does nothing. 185 throw new IllegalStateException("Unexpected rewind when already at beginning"); 186 } 187 188 mRewindPending = true; 189 mNextRead = 0; 190 191 Runnable completeRunnable = 192 new Runnable() { 193 @Override 194 public void run() { 195 mRewindPending = false; 196 uploadDataSink.onRewindSucceeded(); 197 } 198 }; 199 if (mSuccessCallbackMode == SuccessCallbackMode.SYNC) { 200 completeRunnable.run(); 201 } else { 202 mExecutor.execute(completeRunnable); 203 } 204 } 205 assertIdle()206 private void assertIdle() { 207 if (mReadPending) { 208 throw new IllegalStateException("Unexpected operation during read"); 209 } 210 if (mRewindPending) { 211 throw new IllegalStateException("Unexpected operation during rewind"); 212 } 213 if (mFailed) { 214 throw new IllegalStateException("Unexpected operation after failure"); 215 } 216 } 217 maybeFailRead(int readIndex, final UploadDataSink uploadDataSink)218 private boolean maybeFailRead(int readIndex, final UploadDataSink uploadDataSink) { 219 if (readIndex != mReadFailIndex) return false; 220 221 switch (mReadFailMode) { 222 case THROWN: 223 throw new IllegalStateException("Thrown read failure"); 224 case CALLBACK_SYNC: 225 uploadDataSink.onReadError(new IllegalStateException("Sync read failure")); 226 return true; 227 case CALLBACK_ASYNC: 228 Runnable errorRunnable = 229 new Runnable() { 230 @Override 231 public void run() { 232 uploadDataSink.onReadError( 233 new IllegalStateException("Async read failure")); 234 } 235 }; 236 mExecutor.execute(errorRunnable); 237 return true; 238 default: 239 return false; 240 } 241 } 242 maybeFailRewind(final UploadDataSink uploadDataSink)243 private boolean maybeFailRewind(final UploadDataSink uploadDataSink) { 244 switch (mRewindFailMode) { 245 case THROWN: 246 throw new IllegalStateException("Thrown rewind failure"); 247 case CALLBACK_SYNC: 248 uploadDataSink.onRewindError(new IllegalStateException("Sync rewind failure")); 249 return true; 250 case CALLBACK_ASYNC: 251 Runnable errorRunnable = 252 new Runnable() { 253 @Override 254 public void run() { 255 uploadDataSink.onRewindError( 256 new IllegalStateException("Async rewind failure")); 257 } 258 }; 259 mExecutor.execute(errorRunnable); 260 return true; 261 default: 262 return false; 263 } 264 } 265 266 @Override close()267 public void close() throws IOException { 268 if (!mClosed.compareAndSet(false, true)) { 269 throw new AssertionError("Closed twice"); 270 } 271 mAwaitingClose.open(); 272 } 273 assertClosed()274 public void assertClosed() { 275 mAwaitingClose.block(5000); 276 if (!mClosed.get()) { 277 throw new AssertionError("Was not closed"); 278 } 279 } 280 } 281