using System; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Renci.SshNet.Common; using Renci.SshNet.Sftp; using BufferedRead = Renci.SshNet.Sftp.SftpFileReader.BufferedRead; namespace Renci.SshNet.Tests.Classes.Sftp { [TestClass] public class SftpFileReaderTest_Dispose_SftpSessionIsOpen_BeginCloseThrowsException : SftpFileReaderTestBase { private const int ChunkLength = 32 * 1024; private MockSequence _seq; private byte[] _handle; private int _fileSize; private WaitHandle[] _waitHandleArray; private int _operationTimeout; private SftpFileReader _reader; private AsyncCallback _readAsyncCallback; private ManualResetEvent _beginReadInvoked; private EventWaitHandle _disposeCompleted; [TestCleanup] public void TearDown() { _beginReadInvoked?.Dispose(); _disposeCompleted?.Dispose(); } protected override void SetupData() { var random = new Random(); _handle = CreateByteArray(random, 5); _fileSize = 5000; _waitHandleArray = new WaitHandle[2]; _operationTimeout = random.Next(10000, 20000); _beginReadInvoked = new ManualResetEvent(false); _disposeCompleted = new ManualResetEvent(false); _readAsyncCallback = null; } protected override void SetupMocks() { _seq = new MockSequence(); _ = SftpSessionMock.InSequence(_seq) .Setup(p => p.CreateWaitHandleArray(It.IsNotNull(), It.IsNotNull())) .Returns((disposingWaitHandle, semaphoreAvailableWaitHandle) => { _waitHandleArray[0] = disposingWaitHandle; _waitHandleArray[1] = semaphoreAvailableWaitHandle; return _waitHandleArray; }); _ = SftpSessionMock.InSequence(_seq) .Setup(p => p.OperationTimeout) .Returns(_operationTimeout); _ = SftpSessionMock.InSequence(_seq) .Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout)) .Returns(() => WaitAny(_waitHandleArray, _operationTimeout)); _ = SftpSessionMock.InSequence(_seq) .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull(), It.IsAny())) .Callback(() => { // harden test by making sure that we've invoked BeginRead before Dispose is invoked _ = _beginReadInvoked.Set(); }) .Returns((handle, offset, length, callback, state) => { _readAsyncCallback = callback; return null; }) .Callback(() => { // wait until Dispose has been invoked on reader to allow us to harden test, and // verify whether Dispose will prevent us from entering the read-ahead loop again _ = _waitHandleArray[0].WaitOne(); }); _ = SftpSessionMock.InSequence(_seq) .Setup(p => p.IsOpen) .Returns(true); _ = SftpSessionMock.InSequence(_seq) .Setup(p => p.BeginClose(_handle, null, null)) .Throws(new SshException()); } protected override void Arrange() { base.Arrange(); _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 1, _fileSize); } protected override void Act() { Assert.IsTrue(_beginReadInvoked.WaitOne(5000)); _reader.Dispose(); } [TestMethod] public void ReadAfterDisposeShouldThrowObjectDisposedException() { try { _ = _reader.Read(); Assert.Fail(); } catch (ObjectDisposedException ex) { Assert.IsNull(ex.InnerException); Assert.AreEqual(typeof(SftpFileReader).FullName, ex.ObjectName); } } [TestMethod] public void InvokeOfReadAheadCallbackShouldCompleteImmediately() { Assert.IsNotNull(_readAsyncCallback); _readAsyncCallback(new SftpReadAsyncResult(null, null)); } [TestMethod] public void BeginCloseOnSftpSessionShouldHaveBeenInvokedOnce() { SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once); } } }