diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs index 1d7708ae..ffb07990 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs @@ -10,6 +10,21 @@ using BufferedRead = Renci.SshNet.Sftp.SftpFileReader.BufferedRead; namespace Renci.SshNet.Tests.Classes.Sftp { + /// + /// Runs a reader with max. 2 pending reads. + /// The read-ahead of chunk1 starts followed by the read-ahead of chunk2. + /// The read-ahead of chunk1 completes successfully and the resulting chunk is read. + /// The read of this first chunk allows a third ahead-head to start. + /// The second read-ahead uses signals to forcefully block a failure completion until the read + /// ahead of the third chunk has completed and the semaphore is waiting for a slot to start + /// the read-ahead of chunk4. + /// The second read does not consume check3 as it is out of order, but instead waits for + /// the outcome of the read-ahead of chunk2. + /// + /// The completion with exception of chunk2 causes the second read to throw that same exception, and + /// signals the semaphore that was waiting to start the read-ahead of chunk4. However, due to the fact + /// that chunk2 completed with an exception, the read-ahead loop is stopped. + /// [TestClass] public class SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads : SftpFileReaderTestBase { @@ -23,7 +38,9 @@ namespace Renci.SshNet.Tests.Classes.Sftp private SftpCloseAsyncResult _closeAsyncResult; private byte[] _chunk1; private byte[] _chunk3; + private ManualResetEvent _readAheadChunk2Completed; private ManualResetEvent _readAheadChunk3Completed; + private ManualResetEvent _waitingForSemaphoreAfterCompletingChunk3; private SftpFileReader _reader; private SshException _exception; private SshException _actualException; @@ -35,12 +52,14 @@ namespace Renci.SshNet.Tests.Classes.Sftp _handle = CreateByteArray(random, 5); _chunk1 = CreateByteArray(random, ChunkLength); _chunk3 = CreateByteArray(random, ChunkLength); - _fileSize = 3 * ChunkLength; + _fileSize = 4 * ChunkLength; _waitHandleArray = new WaitHandle[2]; _operationTimeout = random.Next(10000, 20000); _closeAsyncResult = new SftpCloseAsyncResult(null, null); + _readAheadChunk2Completed = new ManualResetEvent(false); _readAheadChunk3Completed = new ManualResetEvent(false); + _waitingForSemaphoreAfterCompletingChunk3 = new ManualResetEvent(false); _exception = new SshException(); } @@ -79,12 +98,16 @@ namespace Renci.SshNet.Tests.Classes.Sftp { ThreadAbstraction.ExecuteThread(() => { - // wait until the read-ahead for chunk3 has completed - _readAheadChunk3Completed.WaitOne(TimeSpan.FromSeconds(5)); - + // wait until the read-ahead for chunk3 has completed; this should allow + // the read-ahead of chunk4 to start + _readAheadChunk3Completed.WaitOne(TimeSpan.FromSeconds(3)); + // wait until the semaphore wait to start with chunk4 has started + _waitingForSemaphoreAfterCompletingChunk3.WaitOne(TimeSpan.FromSeconds(7)); // complete async read of chunk2 with exception var asyncResult = new SftpReadAsyncResult(callback, state); asyncResult.SetAsCompleted(_exception, false); + // signal that read-ahead of chunk 2 has completed + _readAheadChunk2Completed.Set(); }); }) .Returns((SftpReadAsyncResult)null); @@ -98,11 +121,16 @@ namespace Renci.SshNet.Tests.Classes.Sftp { var asyncResult = new SftpReadAsyncResult(callback, state); asyncResult.SetAsCompleted(_chunk3, false); - // signal that we've completed the read-ahead for chunk3 _readAheadChunk3Completed.Set(); }) .Returns((SftpReadAsyncResult)null); + SftpSessionMock.InSequence(_seq).Setup(p => p.OperationTimeout).Returns(_operationTimeout); + SftpSessionMock.InSequence(_seq) + .Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout)) + .Callback(() => _waitingForSemaphoreAfterCompletingChunk3.Set()) + .Returns(() => WaitAny(_waitHandleArray, _operationTimeout)); + } protected override void Arrange() @@ -135,7 +163,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp } [TestMethod] - public void ReahAheadOfChunk3ShouldHaveBeenDone() + public void ReahAheadOfChunk3ShouldHaveStarted() { SftpSessionMock.Verify(p => p.BeginRead(_handle, 2 * ChunkLength, ChunkLength, It.IsNotNull(), It.IsAny()), Times.Once); } @@ -154,6 +182,12 @@ namespace Renci.SshNet.Tests.Classes.Sftp } } + [TestMethod] + public void WaitAnyOFSftpSessionShouldHaveBeenInvokedFourTimes() + { + SftpSessionMock.Verify(p => p.WaitAny(_waitHandleArray, _operationTimeout), Times.Exactly(4)); + } + [TestMethod] public void DisposeShouldCloseHandleAndCompleteImmediately() {