SftpFileReaderTest_PreviousChunkIsIncompleteAndEofIsNotReached.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. using System;
  2. using System.Diagnostics;
  3. using System.Threading;
  4. using Microsoft.VisualStudio.TestTools.UnitTesting;
  5. using Moq;
  6. using Renci.SshNet.Common;
  7. using Renci.SshNet.Sftp;
  8. using BufferedRead = Renci.SshNet.Sftp.SftpFileReader.BufferedRead;
  9. namespace Renci.SshNet.Tests.Classes.Sftp
  10. {
  11. [TestClass]
  12. public class SftpFileReaderTest_PreviousChunkIsIncompleteAndEofIsNotReached : SftpFileReaderTestBase
  13. {
  14. private const int ChunkLength = 32 * 1024;
  15. private MockSequence _seq;
  16. private byte[] _handle;
  17. private int _fileSize;
  18. private WaitHandle[] _waitHandleArray;
  19. private int _operationTimeout;
  20. private SftpCloseAsyncResult _closeAsyncResult;
  21. private byte[] _chunk1;
  22. private byte[] _chunk2;
  23. private byte[] _chunk2CatchUp1;
  24. private byte[] _chunk2CatchUp2;
  25. private byte[] _chunk3;
  26. private byte[] _chunk4;
  27. private byte[] _chunk5;
  28. private SftpFileReader _reader;
  29. private byte[] _actualChunk1;
  30. private byte[] _actualChunk2;
  31. private byte[] _actualChunk3;
  32. private ManualResetEvent _chunk1BeginRead;
  33. private ManualResetEvent _chunk2BeginRead;
  34. private ManualResetEvent _chunk3BeginRead;
  35. private ManualResetEvent _chunk4BeginRead;
  36. private ManualResetEvent _chunk5BeginRead;
  37. private ManualResetEvent _waitBeforeChunk6;
  38. private ManualResetEvent _chunk6BeginRead;
  39. private byte[] _actualChunk4;
  40. private byte[] _actualChunk2CatchUp1;
  41. private byte[] _actualChunk2CatchUp2;
  42. private byte[] _chunk6;
  43. private byte[] _actualChunk5;
  44. private byte[] _actualChunk6;
  45. protected override void SetupData()
  46. {
  47. var random = new Random();
  48. _handle = CreateByteArray(random, 3);
  49. _chunk1 = CreateByteArray(random, ChunkLength);
  50. _chunk2 = CreateByteArray(random, ChunkLength - 17);
  51. _chunk2CatchUp1 = CreateByteArray(random, 10);
  52. _chunk2CatchUp2 = CreateByteArray(random, 7);
  53. _chunk3 = CreateByteArray(random, ChunkLength);
  54. _chunk4 = CreateByteArray(random, ChunkLength);
  55. _chunk5 = CreateByteArray(random, ChunkLength);
  56. _chunk6 = new byte[0];
  57. _chunk1BeginRead = new ManualResetEvent(false);
  58. _chunk2BeginRead = new ManualResetEvent(false);
  59. _chunk3BeginRead = new ManualResetEvent(false);
  60. _chunk4BeginRead = new ManualResetEvent(false);
  61. _chunk5BeginRead = new ManualResetEvent(false);
  62. _waitBeforeChunk6 = new ManualResetEvent(false);
  63. _chunk6BeginRead = new ManualResetEvent(false);
  64. _fileSize = _chunk1.Length + _chunk2.Length + _chunk2CatchUp1.Length + _chunk2CatchUp2.Length + _chunk3.Length + _chunk4.Length + _chunk5.Length;
  65. _waitHandleArray = new WaitHandle[2];
  66. _operationTimeout = random.Next(10000, 20000);
  67. _closeAsyncResult = new SftpCloseAsyncResult(null, null);
  68. }
  69. protected override void SetupMocks()
  70. {
  71. _seq = new MockSequence();
  72. _ = SftpSessionMock.InSequence(_seq)
  73. .Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
  74. .Returns<WaitHandle, WaitHandle>((disposingWaitHandle, semaphoreAvailableWaitHandle) =>
  75. {
  76. _waitHandleArray[0] = disposingWaitHandle;
  77. _waitHandleArray[1] = semaphoreAvailableWaitHandle;
  78. return _waitHandleArray;
  79. });
  80. _ = SftpSessionMock.InSequence(_seq)
  81. .Setup(p => p.OperationTimeout)
  82. .Returns(_operationTimeout);
  83. _ = SftpSessionMock.InSequence(_seq)
  84. .Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
  85. .Returns(() => WaitAny(_waitHandleArray, _operationTimeout));
  86. _ = SftpSessionMock.InSequence(_seq)
  87. .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
  88. .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
  89. {
  90. _ = _chunk1BeginRead.Set();
  91. var asyncResult = new SftpReadAsyncResult(callback, state);
  92. asyncResult.SetAsCompleted(_chunk1, false);
  93. })
  94. .Returns((SftpReadAsyncResult)null);
  95. _ = SftpSessionMock.InSequence(_seq)
  96. .Setup(p => p.OperationTimeout)
  97. .Returns(_operationTimeout);
  98. _ = SftpSessionMock.InSequence(_seq)
  99. .Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
  100. .Returns(() => WaitAny(_waitHandleArray, _operationTimeout));
  101. _ = SftpSessionMock.InSequence(_seq)
  102. .Setup(p => p.BeginRead(_handle, ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
  103. .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
  104. {
  105. _ = _chunk2BeginRead.Set();
  106. var asyncResult = new SftpReadAsyncResult(callback, state);
  107. asyncResult.SetAsCompleted(_chunk2, false);
  108. })
  109. .Returns((SftpReadAsyncResult)null);
  110. _ = SftpSessionMock.InSequence(_seq)
  111. .Setup(p => p.OperationTimeout)
  112. .Returns(_operationTimeout);
  113. _ = SftpSessionMock.InSequence(_seq)
  114. .Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
  115. .Returns(() => WaitAny(_waitHandleArray, _operationTimeout));
  116. _ = SftpSessionMock.InSequence(_seq)
  117. .Setup(p => p.BeginRead(_handle, 2 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
  118. .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
  119. {
  120. _ = _chunk3BeginRead.Set();
  121. var asyncResult = new SftpReadAsyncResult(callback, state);
  122. asyncResult.SetAsCompleted(_chunk3, false);
  123. })
  124. .Returns((SftpReadAsyncResult)null);
  125. _ = SftpSessionMock.InSequence(_seq)
  126. .Setup(p => p.OperationTimeout)
  127. .Returns(_operationTimeout);
  128. _ = SftpSessionMock.InSequence(_seq)
  129. .Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
  130. .Returns(() => WaitAny(_waitHandleArray, _operationTimeout));
  131. _ = SftpSessionMock.InSequence(_seq)
  132. .Setup(p => p.BeginRead(_handle, 3 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
  133. .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
  134. {
  135. _ = _chunk4BeginRead.Set();
  136. var asyncResult = new SftpReadAsyncResult(callback, state);
  137. asyncResult.SetAsCompleted(_chunk4, false);
  138. })
  139. .Returns((SftpReadAsyncResult)null);
  140. _ = SftpSessionMock.InSequence(_seq)
  141. .Setup(p => p.OperationTimeout)
  142. .Returns(_operationTimeout);
  143. _ = SftpSessionMock.InSequence(_seq)
  144. .Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
  145. .Returns(() => WaitAny(_waitHandleArray, _operationTimeout));
  146. _ = SftpSessionMock.InSequence(_seq)
  147. .Setup(p => p.BeginRead(_handle, 4 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
  148. .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
  149. {
  150. _ = _chunk5BeginRead.Set();
  151. var asyncResult = new SftpReadAsyncResult(callback, state);
  152. asyncResult.SetAsCompleted(_chunk5, false);
  153. })
  154. .Returns((SftpReadAsyncResult)null);
  155. _ = SftpSessionMock.InSequence(_seq)
  156. .Setup(p => p.OperationTimeout)
  157. .Returns(_operationTimeout);
  158. _ = SftpSessionMock.InSequence(_seq)
  159. .Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
  160. .Callback(() => _waitBeforeChunk6.Set())
  161. .Returns(() => WaitAny(_waitHandleArray, _operationTimeout));
  162. _ = SftpSessionMock.InSequence(_seq)
  163. .Setup(p => p.RequestRead(_handle, (2 * ChunkLength) - 17, 17))
  164. .Returns(_chunk2CatchUp1);
  165. _ = SftpSessionMock.InSequence(_seq)
  166. .Setup(p => p.RequestRead(_handle, (2 * ChunkLength) - 7, 7))
  167. .Returns(_chunk2CatchUp2);
  168. _ = SftpSessionMock.InSequence(_seq)
  169. .Setup(p => p.BeginRead(_handle, 5 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
  170. .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
  171. {
  172. _ = _chunk6BeginRead.Set();
  173. var asyncResult = new SftpReadAsyncResult(callback, state);
  174. asyncResult.SetAsCompleted(_chunk6, false);
  175. })
  176. .Returns((SftpReadAsyncResult)null);
  177. }
  178. protected override void Arrange()
  179. {
  180. base.Arrange();
  181. _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 3, _fileSize);
  182. }
  183. protected override void Act()
  184. {
  185. // reader is configured to read-ahead max. 3 chunks, so chunk4 should not have been read
  186. Assert.IsFalse(_chunk4BeginRead.WaitOne(0));
  187. // consume chunk 1
  188. _actualChunk1 = _reader.Read();
  189. // consuming chunk1 allows chunk4 to be read-ahead
  190. Assert.IsTrue(_chunk4BeginRead.WaitOne(200));
  191. // verify that chunk5 has not yet been read-ahead
  192. Assert.IsFalse(_chunk5BeginRead.WaitOne(0));
  193. // consume chunk 2
  194. _actualChunk2 = _reader.Read();
  195. // consuming chunk2 allows chunk5 to be read-ahead
  196. Assert.IsTrue(_chunk5BeginRead.WaitOne(200));
  197. // pauze until the read-ahead has started waiting a semaphore to become available
  198. Assert.IsTrue(_waitBeforeChunk6.WaitOne(200));
  199. // consume remaining parts of chunk 2
  200. _actualChunk2CatchUp1 = _reader.Read();
  201. _actualChunk2CatchUp2 = _reader.Read();
  202. // verify that chunk6 has not yet been read-ahead
  203. Assert.IsFalse(_chunk6BeginRead.WaitOne(0));
  204. // consume chunk 3
  205. _actualChunk3 = _reader.Read();
  206. // consuming chunk3 allows chunk6 to be read-ahead
  207. Assert.IsTrue(_chunk6BeginRead.WaitOne(200));
  208. // consume chunk 4
  209. _actualChunk4 = _reader.Read();
  210. // consume chunk 5
  211. _actualChunk5 = _reader.Read();
  212. // consume chunk 6
  213. _actualChunk6 = _reader.Read();
  214. }
  215. [TestMethod]
  216. public void FirstReadShouldReturnChunk1()
  217. {
  218. Assert.IsNotNull(_actualChunk1);
  219. Assert.AreSame(_chunk1, _actualChunk1);
  220. }
  221. [TestMethod]
  222. public void SecondReadShouldReturnChunk2()
  223. {
  224. Assert.IsNotNull(_actualChunk2);
  225. Assert.AreSame(_chunk2, _actualChunk2);
  226. }
  227. [TestMethod]
  228. public void ThirdReadShouldReturnChunk2CatchUp1()
  229. {
  230. Assert.IsNotNull(_actualChunk2CatchUp1);
  231. Assert.AreSame(_chunk2CatchUp1, _actualChunk2CatchUp1);
  232. }
  233. [TestMethod]
  234. public void FourthReadShouldReturnChunk2CatchUp2()
  235. {
  236. Assert.IsNotNull(_actualChunk2CatchUp2);
  237. Assert.AreSame(_chunk2CatchUp2, _actualChunk2CatchUp2);
  238. }
  239. [TestMethod]
  240. public void FifthReadShouldReturnChunk3()
  241. {
  242. Assert.IsNotNull(_actualChunk3);
  243. Assert.AreSame(_chunk3, _actualChunk3);
  244. }
  245. [TestMethod]
  246. public void SixthReadShouldReturnChunk4()
  247. {
  248. Assert.IsNotNull(_actualChunk4);
  249. Assert.AreSame(_chunk4, _actualChunk4);
  250. }
  251. [TestMethod]
  252. public void SeventhReadShouldReturnChunk5()
  253. {
  254. Assert.IsNotNull(_actualChunk5);
  255. Assert.AreSame(_chunk5, _actualChunk5);
  256. }
  257. [TestMethod]
  258. public void EightReadShouldReturnChunk6()
  259. {
  260. Assert.IsNotNull(_actualChunk6);
  261. Assert.AreSame(_chunk6, _actualChunk6);
  262. }
  263. [TestMethod]
  264. public void ReadAfterEndOfFileShouldThrowSshException()
  265. {
  266. try
  267. {
  268. _ = _reader.Read();
  269. Assert.Fail();
  270. }
  271. catch (SshException ex)
  272. {
  273. Assert.IsNull(ex.InnerException);
  274. Assert.AreEqual("Attempting to read beyond the end of the file.", ex.Message);
  275. }
  276. }
  277. [TestMethod]
  278. public void DisposeShouldCloseHandleAndCompleteImmediately()
  279. {
  280. _ = SftpSessionMock.InSequence(_seq)
  281. .Setup(p => p.IsOpen)
  282. .Returns(true);
  283. _ = SftpSessionMock.InSequence(_seq)
  284. .Setup(p => p.BeginClose(_handle, null, null))
  285. .Returns(_closeAsyncResult);
  286. _ = SftpSessionMock.InSequence(_seq)
  287. .Setup(p => p.EndClose(_closeAsyncResult));
  288. var stopwatch = Stopwatch.StartNew();
  289. _reader.Dispose();
  290. stopwatch.Stop();
  291. Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
  292. SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
  293. SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
  294. SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
  295. }
  296. }
  297. }