mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 01:05:42 +00:00
Build the read-ahead mechanism into SftpFileStream (#1705)
* Build the read-ahead mechanism into SftpFileStream This change unifies the SFTP download implementations that exist via DownloadFile and via SftpFileStream, by rewriting SftpFileStream to perform the same "read-aheads" as DownloadFile. This brings the performance of downloads via SftpFileStream in line with DownloadFile, such that the latter is now effectively SftpFileStream.CopyTo. It also brings the recently added DownloadFileAsync up to speed since that was implemented via SftpFileStream.CopyToAsync. The methodology is a mix of the previous one and that within OpenSSH: the first call to SftpFileStream.Read sends one read request to the server. The second sends two and when not interrupted by Write or similar, the number of in-flight read requests continues to scale up in this fashion. I have measured CopyTo to be 3-20x faster than before, depending on file size and server round-trip time. * Check CanSeek in ReadAllBytes * Squeeze out some performance
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
#if !NET
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
@@ -398,6 +399,29 @@ namespace Renci.SshNet.Common
|
||||
totalRead += read;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Task<T> WaitAsync<T>(this Task<T> task, CancellationToken cancellationToken)
|
||||
{
|
||||
if (task.IsCompleted || !cancellationToken.CanBeCanceled)
|
||||
{
|
||||
return task;
|
||||
}
|
||||
|
||||
return WaitCore();
|
||||
|
||||
async Task<T> WaitCore()
|
||||
{
|
||||
TaskCompletionSource<T> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
using var reg = cancellationToken.Register(
|
||||
() => tcs.TrySetCanceled(cancellationToken),
|
||||
useSynchronizationContext: false);
|
||||
|
||||
var completedTask = await Task.WhenAny(task, tcs.Task).ConfigureAwait(false);
|
||||
|
||||
return await completedTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,18 +83,6 @@ namespace Renci.SshNet
|
||||
/// <exception cref="SshConnectionException">No key exchange algorithm is supported by both client and server.</exception>
|
||||
IKeyExchange CreateKeyExchange(IDictionary<string, Func<IKeyExchange>> clientAlgorithms, string[] serverAlgorithms);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ISftpFileReader"/> for the specified file and with the specified
|
||||
/// buffer size.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The file to read.</param>
|
||||
/// <param name="sftpSession">The SFTP session to use.</param>
|
||||
/// <param name="bufferSize">The size of buffer.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="ISftpFileReader"/>.
|
||||
/// </returns>
|
||||
ISftpFileReader CreateSftpFileReader(string fileName, ISftpSession sftpSession, uint bufferSize);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ISftpResponseFactory"/> instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -4,8 +4,6 @@ using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Renci.SshNet.Common;
|
||||
using Renci.SshNet.Connection;
|
||||
using Renci.SshNet.Messages.Transport;
|
||||
@@ -118,51 +116,6 @@ namespace Renci.SshNet
|
||||
return new NetConfSession(session, operationTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ISftpFileReader"/> for the specified file and with the specified
|
||||
/// buffer size.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The file to read.</param>
|
||||
/// <param name="sftpSession">The SFTP session to use.</param>
|
||||
/// <param name="bufferSize">The size of buffer.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="ISftpFileReader"/>.
|
||||
/// </returns>
|
||||
public ISftpFileReader CreateSftpFileReader(string fileName, ISftpSession sftpSession, uint bufferSize)
|
||||
{
|
||||
const int defaultMaxPendingReads = 10;
|
||||
|
||||
// Issue #292: Avoid overlapping SSH_FXP_OPEN and SSH_FXP_LSTAT requests for the same file as this
|
||||
// causes a performance degradation on Sun SSH
|
||||
var openAsyncResult = sftpSession.BeginOpen(fileName, Flags.Read, callback: null, state: null);
|
||||
var handle = sftpSession.EndOpen(openAsyncResult);
|
||||
|
||||
var statAsyncResult = sftpSession.BeginLStat(fileName, callback: null, state: null);
|
||||
|
||||
long? fileSize;
|
||||
int maxPendingReads;
|
||||
|
||||
var chunkSize = sftpSession.CalculateOptimalReadLength(bufferSize);
|
||||
|
||||
// fallback to a default maximum of pending reads when remote server does not allow us to obtain
|
||||
// the attributes of the file
|
||||
try
|
||||
{
|
||||
var fileAttributes = sftpSession.EndLStat(statAsyncResult);
|
||||
fileSize = fileAttributes.Size;
|
||||
maxPendingReads = Math.Min(100, (int)Math.Ceiling((double)fileAttributes.Size / chunkSize) + 1);
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
fileSize = null;
|
||||
maxPendingReads = defaultMaxPendingReads;
|
||||
|
||||
sftpSession.SessionLoggerFactory.CreateLogger<ServiceFactory>().LogInformation(ex, "Failed to obtain size of file. Allowing maximum {MaxPendingReads} pending reads", maxPendingReads);
|
||||
}
|
||||
|
||||
return sftpSession.CreateFileReader(handle, sftpSession, chunkSize, maxPendingReads, fileSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ISftpResponseFactory"/> instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace Renci.SshNet.Sftp
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a given file.
|
||||
/// </summary>
|
||||
internal interface ISftpFileReader : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from the current file and advances the position within the file by the number of bytes read.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The sequence of bytes read from the file, or a zero-length array if the end of the file
|
||||
/// has been reached.
|
||||
/// </returns>
|
||||
/// <exception cref="ObjectDisposedException">The current <see cref="ISftpFileReader"/> is disposed.</exception>
|
||||
/// <exception cref="SshException">Attempting to read beyond the end of the file.</exception>
|
||||
byte[] Read();
|
||||
}
|
||||
}
|
||||
@@ -67,11 +67,10 @@ namespace Renci.SshNet.Sftp
|
||||
/// Asynchronously performs a <c>SSH_FXP_FSTAT</c> request.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle.</param>
|
||||
/// <param name="nullOnError">If set to <see langword="true"/>, <see langword="null"/> is returned in case of an error.</param>
|
||||
/// <returns>
|
||||
/// The file attributes.
|
||||
/// </returns>
|
||||
SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError);
|
||||
SftpFileAttributes RequestFStat(byte[] handle);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously performs a <c>SSH_FXP_FSTAT</c> request.
|
||||
@@ -522,19 +521,5 @@ namespace Renci.SshNet.Sftp
|
||||
/// Currently, we do not take the remote window size into account.
|
||||
/// </remarks>
|
||||
uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ISftpFileReader"/> for reading the content of the file represented by a given <paramref name="handle"/>.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the file to read.</param>
|
||||
/// <param name="sftpSession">The SFTP session.</param>
|
||||
/// <param name="chunkSize">The maximum number of bytes to read with each chunk.</param>
|
||||
/// <param name="maxPendingReads">The maximum number of pending reads.</param>
|
||||
/// <param name="fileSize">The size of the file or <see langword="null"/> when the size could not be determined.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="ISftpFileReader"/> for reading the content of the file represented by the
|
||||
/// specified <paramref name="handle"/>.
|
||||
/// </returns>
|
||||
ISftpFileReader CreateFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,469 +1,205 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
#if !NET
|
||||
using Renci.SshNet.Common;
|
||||
#endif
|
||||
|
||||
namespace Renci.SshNet.Sftp
|
||||
{
|
||||
internal sealed class SftpFileReader : ISftpFileReader
|
||||
public sealed partial class SftpFileStream
|
||||
{
|
||||
private const int ReadAheadWaitTimeoutInMilliseconds = 1000;
|
||||
|
||||
private readonly byte[] _handle;
|
||||
private readonly ISftpSession _sftpSession;
|
||||
private readonly uint _chunkSize;
|
||||
private readonly SemaphoreSlim _semaphore;
|
||||
private readonly object _readLock;
|
||||
private readonly ManualResetEvent _disposingWaitHandle;
|
||||
private readonly ManualResetEvent _readAheadCompleted;
|
||||
private readonly Dictionary<int, BufferedRead> _queue;
|
||||
private readonly WaitHandle[] _waitHandles;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the size of the file, when available.
|
||||
/// </summary>
|
||||
private readonly long? _fileSize;
|
||||
|
||||
private ulong _offset;
|
||||
private int _readAheadChunkIndex;
|
||||
private ulong _readAheadOffset;
|
||||
private int _nextChunkIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Holds a value indicating whether EOF has already been signaled by the SSH server.
|
||||
/// </summary>
|
||||
private bool _endOfFileReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Holds a value indicating whether the client has read up to the end of the file.
|
||||
/// </summary>
|
||||
private bool _isEndOfFileRead;
|
||||
|
||||
private bool _disposingOrDisposed;
|
||||
|
||||
private Exception _exception;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SftpFileReader"/> class with the specified handle,
|
||||
/// <see cref="ISftpSession"/> and the maximum number of pending reads.
|
||||
/// </summary>
|
||||
/// <param name="handle">The file handle.</param>
|
||||
/// <param name="sftpSession">The SFT session.</param>
|
||||
/// <param name="chunkSize">The size of a individual read-ahead chunk.</param>
|
||||
/// <param name="maxPendingReads">The maximum number of pending reads.</param>
|
||||
/// <param name="fileSize">The size of the file, if known; otherwise, <see langword="null"/>.</param>
|
||||
public SftpFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSize)
|
||||
private sealed class SftpFileReader : IDisposable
|
||||
{
|
||||
_handle = handle;
|
||||
_sftpSession = sftpSession;
|
||||
_chunkSize = chunkSize;
|
||||
_fileSize = fileSize;
|
||||
_semaphore = new SemaphoreSlim(maxPendingReads);
|
||||
_queue = new Dictionary<int, BufferedRead>(maxPendingReads);
|
||||
_readLock = new object();
|
||||
_readAheadCompleted = new ManualResetEvent(initialState: false);
|
||||
_disposingWaitHandle = new ManualResetEvent(initialState: false);
|
||||
_waitHandles = _sftpSession.CreateWaitHandleArray(_disposingWaitHandle, _semaphore.AvailableWaitHandle);
|
||||
_logger = sftpSession.SessionLoggerFactory.CreateLogger<SftpFileReader>();
|
||||
private readonly byte[] _handle;
|
||||
private readonly ISftpSession _sftpSession;
|
||||
private readonly int _maxPendingReads;
|
||||
private readonly ulong? _fileSize;
|
||||
private readonly Dictionary<ulong, Request> _requests = [];
|
||||
private readonly CancellationTokenSource _cts;
|
||||
|
||||
StartReadAhead();
|
||||
}
|
||||
private uint _chunkSize;
|
||||
|
||||
public byte[] Read()
|
||||
{
|
||||
ThrowHelper.ThrowObjectDisposedIf(_disposingOrDisposed, this);
|
||||
private ulong _offset;
|
||||
private ulong _readAheadOffset;
|
||||
private int _currentMaxRequests;
|
||||
private ExceptionDispatchInfo? _exception;
|
||||
|
||||
if (_exception is not null)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SftpFileReader"/> class with the specified handle,
|
||||
/// <see cref="ISftpSession"/> and the maximum number of pending reads.
|
||||
/// </summary>
|
||||
/// <param name="handle">The file handle.</param>
|
||||
/// <param name="sftpSession">The SFTP session.</param>
|
||||
/// <param name="chunkSize">The size of a individual read-ahead chunk.</param>
|
||||
/// <param name="position">The starting offset in the file.</param>
|
||||
/// <param name="maxPendingReads">The maximum number of pending reads.</param>
|
||||
/// <param name="fileSize">The size of the file, if known; otherwise, <see langword="null"/>.</param>
|
||||
/// <param name="initialMaxRequests">The initial number of pending reads.</param>
|
||||
public SftpFileReader(byte[] handle, ISftpSession sftpSession, int chunkSize, long position, int maxPendingReads, ulong? fileSize = null, int initialMaxRequests = 1)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(_exception).Throw();
|
||||
Debug.Assert(chunkSize > 0);
|
||||
Debug.Assert(position >= 0);
|
||||
Debug.Assert(initialMaxRequests >= 1);
|
||||
|
||||
_handle = handle;
|
||||
_sftpSession = sftpSession;
|
||||
_chunkSize = (uint)chunkSize;
|
||||
_offset = _readAheadOffset = (ulong)position;
|
||||
_maxPendingReads = maxPendingReads;
|
||||
_fileSize = fileSize;
|
||||
_currentMaxRequests = initialMaxRequests;
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
if (_isEndOfFileRead)
|
||||
public async Task<byte[]> ReadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new SshException("Attempting to read beyond the end of the file.");
|
||||
}
|
||||
_exception?.Throw();
|
||||
|
||||
BufferedRead nextChunk;
|
||||
|
||||
lock (_readLock)
|
||||
{
|
||||
// wait until either the next chunk is available, an exception has occurred or the current
|
||||
// instance is already disposed
|
||||
while (!_queue.TryGetValue(_nextChunkIndex, out nextChunk) && _exception is null)
|
||||
try
|
||||
{
|
||||
_ = Monitor.Wait(_readLock);
|
||||
}
|
||||
// Fill up the requests buffer with as many requests as we currently allow.
|
||||
// On the first call to Read, that number is 1. On the second it is 2 etc.
|
||||
while (_requests.Count < _currentMaxRequests)
|
||||
{
|
||||
AddRequest(_readAheadOffset, _chunkSize);
|
||||
|
||||
// throw when exception occured in read-ahead, or the current instance is already disposed
|
||||
if (_exception != null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(_exception).Throw();
|
||||
}
|
||||
_readAheadOffset += _chunkSize;
|
||||
}
|
||||
|
||||
var data = nextChunk.Data;
|
||||
var request = _requests[_offset];
|
||||
|
||||
var data = await request.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (nextChunk.Offset == _offset)
|
||||
{
|
||||
// have we reached EOF?
|
||||
if (data.Length == 0)
|
||||
{
|
||||
// PERF: we do not bother updating all of the internal state when we've reached EOF
|
||||
_isEndOfFileRead = true;
|
||||
// EOF. We effectively disable this instance - further reads will
|
||||
// continue to return EOF.
|
||||
_currentMaxRequests = 0;
|
||||
return data;
|
||||
}
|
||||
else
|
||||
|
||||
_ = _requests.Remove(_offset);
|
||||
|
||||
_offset += (ulong)data.Length;
|
||||
|
||||
if (data.Length < request.Count)
|
||||
{
|
||||
// remove processed chunk
|
||||
_ = _queue.Remove(_nextChunkIndex);
|
||||
// We didn't receive all the data we requested.
|
||||
|
||||
// update offset
|
||||
_offset += (ulong)data.Length;
|
||||
// If we've read exactly up to our known file size and the next
|
||||
// request is already in-flight, then wait for it and if it signals
|
||||
// EOF (as is likely), then call EOF here and omit a final round-trip.
|
||||
// This optimisation is mostly only beneficial to smaller files on
|
||||
// higher latency connections.
|
||||
|
||||
// move to next chunk
|
||||
_nextChunkIndex++;
|
||||
var nextRequestOffset = _offset - (ulong)data.Length + request.Count;
|
||||
|
||||
if (_offset == _fileSize &&
|
||||
_requests.TryGetValue(nextRequestOffset, out var nextRequest))
|
||||
{
|
||||
var nextRequestData = await nextRequest.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (nextRequestData.Length == 0)
|
||||
{
|
||||
_offset = nextRequestOffset;
|
||||
_currentMaxRequests = 0;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, add another request to fill in the gap.
|
||||
AddRequest(_offset, request.Count - (uint)data.Length);
|
||||
|
||||
if (data.Length < _chunkSize)
|
||||
{
|
||||
// Right-size the buffer to match the amount that the server
|
||||
// is willing to return.
|
||||
// Note that this also happens near EOF.
|
||||
_chunkSize = Math.Max(512, (uint)data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
// unblock wait in read-ahead
|
||||
_ = _semaphore.Release();
|
||||
if (_currentMaxRequests > 0)
|
||||
{
|
||||
if (_readAheadOffset > _fileSize + _chunkSize)
|
||||
{
|
||||
// If the file size is known and we've got requests
|
||||
// out beyond that (plus a buffer for EOD read), then
|
||||
// restrict the number of outgoing requests.
|
||||
// This does nothing for the performance of this download
|
||||
// but may reduce traffic for other downloads.
|
||||
_currentMaxRequests = 1;
|
||||
}
|
||||
else if (_currentMaxRequests < _maxPendingReads)
|
||||
{
|
||||
_currentMaxRequests++;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// When we received an EOF for the next chunk and the size of the file is known, then
|
||||
// we only complete the current chunk if we haven't already read up to the file size.
|
||||
// This way we save an extra round-trip to the server.
|
||||
if (data.Length == 0 && _fileSize.HasValue && _offset == (ulong)_fileSize.Value)
|
||||
catch (Exception ex) when (!(ex is OperationCanceledException oce && oce.CancellationToken == cancellationToken))
|
||||
{
|
||||
// avoid future reads
|
||||
_isEndOfFileRead = true;
|
||||
|
||||
// unblock wait in read-ahead
|
||||
_ = _semaphore.Release();
|
||||
|
||||
// signal EOF to caller
|
||||
return nextChunk.Data;
|
||||
// If the wait was cancelled then we will allow subsequent reads as normal.
|
||||
// For any other errors, we prevent further read requests, effectively disabling
|
||||
// this instance.
|
||||
_currentMaxRequests = 0;
|
||||
_exception = ExceptionDispatchInfo.Capture(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When the server returned less bytes than requested (for the previous chunk)
|
||||
* we'll synchronously request the remaining data.
|
||||
*
|
||||
* Due to the optimization above, we'll only get here in one of the following cases:
|
||||
* - an EOF situation for files for which we were unable to obtain the file size
|
||||
* - fewer bytes that requested were returned
|
||||
*
|
||||
* According to the SSH specification, this last case should never happen for normal
|
||||
* disk files (but can happen for device files). In practice, OpenSSH - for example -
|
||||
* returns less bytes than requested when requesting more than 64 KB.
|
||||
*
|
||||
* Important:
|
||||
* To avoid a deadlock, this read must be done outside of the read lock.
|
||||
*/
|
||||
|
||||
var bytesToCatchUp = nextChunk.Offset - _offset;
|
||||
|
||||
/*
|
||||
* TODO: break loop and interrupt blocking wait in case of exception
|
||||
*/
|
||||
|
||||
var read = _sftpSession.RequestRead(_handle, _offset, (uint)bytesToCatchUp);
|
||||
if (read.Length == 0)
|
||||
private void AddRequest(ulong offset, uint count)
|
||||
{
|
||||
// process data in read lock to avoid ObjectDisposedException while releasing semaphore
|
||||
lock (_readLock)
|
||||
{
|
||||
// a zero-length (EOF) response is only valid for the read-back when EOF has
|
||||
// been signaled for the next read-ahead chunk
|
||||
if (nextChunk.Data.Length == 0)
|
||||
{
|
||||
_isEndOfFileRead = true;
|
||||
|
||||
// ensure we've not yet disposed the current instance
|
||||
if (!_disposingOrDisposed)
|
||||
{
|
||||
// unblock wait in read-ahead
|
||||
_ = _semaphore.Release();
|
||||
}
|
||||
|
||||
// signal EOF to caller
|
||||
return read;
|
||||
}
|
||||
|
||||
// move reader to error state
|
||||
_exception = new SshException("Unexpectedly reached end of file.");
|
||||
|
||||
// ensure we've not yet disposed the current instance
|
||||
if (!_disposingOrDisposed)
|
||||
{
|
||||
// unblock wait in read-ahead
|
||||
_ = _semaphore.Release();
|
||||
}
|
||||
|
||||
// notify caller of error
|
||||
throw _exception;
|
||||
}
|
||||
_requests.Add(
|
||||
offset,
|
||||
new Request(
|
||||
offset,
|
||||
count,
|
||||
_sftpSession.RequestReadAsync(_handle, offset, count, _cts.Token)));
|
||||
}
|
||||
|
||||
_offset += (uint)read.Length;
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposingOrDisposed)
|
||||
public void Dispose()
|
||||
{
|
||||
return;
|
||||
}
|
||||
_exception ??= ExceptionDispatchInfo.Capture(new ObjectDisposedException(GetType().FullName));
|
||||
|
||||
// transition to disposing state
|
||||
_disposingOrDisposed = true;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// record exception to break prevent future Read()
|
||||
_exception = new ObjectDisposedException(GetType().FullName);
|
||||
|
||||
// signal that we're disposing to interrupt wait in read-ahead
|
||||
_ = _disposingWaitHandle.Set();
|
||||
|
||||
// wait until the read-ahead thread has completed
|
||||
_ = _readAheadCompleted.WaitOne();
|
||||
|
||||
// unblock the Read()
|
||||
lock (_readLock)
|
||||
if (_requests.Count > 0)
|
||||
{
|
||||
// dispose semaphore in read lock to ensure we don't run into an ObjectDisposedException
|
||||
// in Read()
|
||||
_semaphore.Dispose();
|
||||
// Cancel outstanding requests and observe the exception on them
|
||||
// as an effort to prevent unhandled exceptions.
|
||||
|
||||
// awake Read
|
||||
Monitor.PulseAll(_readLock);
|
||||
_cts.Cancel();
|
||||
|
||||
foreach (var request in _requests.Values)
|
||||
{
|
||||
_ = request.Task.Exception;
|
||||
}
|
||||
|
||||
_requests.Clear();
|
||||
}
|
||||
|
||||
_readAheadCompleted.Dispose();
|
||||
_disposingWaitHandle.Dispose();
|
||||
|
||||
if (_sftpSession.IsOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
var closeAsyncResult = _sftpSession.BeginClose(_handle, callback: null, state: null);
|
||||
_sftpSession.EndClose(closeAsyncResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogInformation(ex, "Failure closing handle");
|
||||
}
|
||||
}
|
||||
_cts.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartReadAhead()
|
||||
{
|
||||
ThreadAbstraction.ExecuteThread(() =>
|
||||
private sealed class Request
|
||||
{
|
||||
while (!_endOfFileReceived && _exception is null)
|
||||
public Request(ulong offset, uint count, Task<byte[]> task)
|
||||
{
|
||||
// check if we should continue with the read-ahead loop
|
||||
// note that the EOF and exception check are not included
|
||||
// in this check as they do not require Read() to be
|
||||
// unblocked (or have already done this)
|
||||
if (!ContinueReadAhead())
|
||||
{
|
||||
// unblock the Read()
|
||||
lock (_readLock)
|
||||
{
|
||||
Monitor.PulseAll(_readLock);
|
||||
}
|
||||
|
||||
// break the read-ahead loop
|
||||
break;
|
||||
}
|
||||
|
||||
// attempt to obtain the semaphore; this may time out when all semaphores are
|
||||
// in use due to pending read-aheads (which in turn can happen when the server
|
||||
// is slow to respond or when the session is broken)
|
||||
if (!_semaphore.Wait(ReadAheadWaitTimeoutInMilliseconds))
|
||||
{
|
||||
// re-evaluate whether an exception occurred, and - if not - wait again
|
||||
continue;
|
||||
}
|
||||
|
||||
// don't bother reading any more chunks if we received EOF, an exception has occurred
|
||||
// or the current instance is disposed
|
||||
if (_endOfFileReceived || _exception != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// start reading next chunk
|
||||
var bufferedRead = new BufferedRead(_readAheadChunkIndex, _readAheadOffset);
|
||||
|
||||
try
|
||||
{
|
||||
// even if we know the size of the file and have read up to EOF, we still want
|
||||
// to keep reading (ahead) until we receive zero bytes from the remote host as
|
||||
// we do not want to rely purely on the reported file size
|
||||
//
|
||||
// if the offset of the read-ahead chunk is greater than that file size, then
|
||||
// we can expect to be reading the last (zero-byte) chunk and switch to synchronous
|
||||
// mode to avoid having multiple read-aheads that read beyond EOF
|
||||
if (_fileSize != null && (long)_readAheadOffset > _fileSize.Value)
|
||||
{
|
||||
var asyncResult = _sftpSession.BeginRead(_handle, _readAheadOffset, _chunkSize, callback: null, bufferedRead);
|
||||
var data = _sftpSession.EndRead(asyncResult);
|
||||
ReadCompletedCore(bufferedRead, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = _sftpSession.BeginRead(_handle, _readAheadOffset, _chunkSize, ReadCompleted, bufferedRead);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleFailure(ex);
|
||||
break;
|
||||
}
|
||||
|
||||
// advance read-ahead offset
|
||||
_readAheadOffset += _chunkSize;
|
||||
|
||||
// increment index of read-ahead chunk
|
||||
_readAheadChunkIndex++;
|
||||
Offset = offset;
|
||||
Count = count;
|
||||
Task = task;
|
||||
}
|
||||
|
||||
_ = _readAheadCompleted.Set();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the read-ahead loop should be continued.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the read-ahead loop should be continued; otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
private bool ContinueReadAhead()
|
||||
{
|
||||
try
|
||||
{
|
||||
var waitResult = _sftpSession.WaitAny(_waitHandles, _sftpSession.OperationTimeout);
|
||||
switch (waitResult)
|
||||
{
|
||||
case 0: // disposing
|
||||
return false;
|
||||
case 1: // semaphore available
|
||||
return true;
|
||||
default:
|
||||
throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "WaitAny return value '{0}' is not implemented.", waitResult));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_ = Interlocked.CompareExchange(ref _exception, ex, comparand: null);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadCompleted(IAsyncResult result)
|
||||
{
|
||||
if (_disposingOrDisposed)
|
||||
{
|
||||
// skip further processing if we're disposing the current instance
|
||||
// to avoid accessing disposed members
|
||||
return;
|
||||
}
|
||||
|
||||
var readAsyncResult = (SftpReadAsyncResult)result;
|
||||
|
||||
byte[] data;
|
||||
|
||||
try
|
||||
{
|
||||
data = readAsyncResult.EndInvoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleFailure(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// a read that completes with a zero-byte result signals EOF
|
||||
// but there may be pending reads before that read
|
||||
var bufferedRead = (BufferedRead)readAsyncResult.AsyncState;
|
||||
ReadCompletedCore(bufferedRead, data);
|
||||
}
|
||||
|
||||
private void ReadCompletedCore(BufferedRead bufferedRead, byte[] data)
|
||||
{
|
||||
bufferedRead.Complete(data);
|
||||
|
||||
lock (_readLock)
|
||||
{
|
||||
// add item to queue
|
||||
_queue.Add(bufferedRead.ChunkIndex, bufferedRead);
|
||||
|
||||
// Signal that a chunk has been read or EOF has been reached.
|
||||
// In both cases, Read() will eventually also unblock the "read-ahead" thread.
|
||||
Monitor.PulseAll(_readLock);
|
||||
}
|
||||
|
||||
// check if server signaled EOF
|
||||
if (data.Length == 0)
|
||||
{
|
||||
// set a flag to stop read-aheads
|
||||
_endOfFileReceived = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleFailure(Exception cause)
|
||||
{
|
||||
_ = Interlocked.CompareExchange(ref _exception, cause, comparand: null);
|
||||
|
||||
// unblock read-ahead
|
||||
_ = _semaphore.Release();
|
||||
|
||||
// unblock Read()
|
||||
lock (_readLock)
|
||||
{
|
||||
Monitor.PulseAll(_readLock);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class BufferedRead
|
||||
{
|
||||
public int ChunkIndex { get; }
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
public ulong Offset { get; }
|
||||
|
||||
public BufferedRead(int chunkIndex, ulong offset)
|
||||
{
|
||||
ChunkIndex = chunkIndex;
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
public void Complete(byte[] data)
|
||||
{
|
||||
Data = data;
|
||||
public ulong Offset { get; }
|
||||
public uint Count { get; }
|
||||
public Task<byte[]> Task { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -251,23 +251,6 @@ namespace Renci.SshNet.Sftp
|
||||
return canonizedPath + slash + pathParts[pathParts.Length - 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ISftpFileReader"/> for reading the content of the file represented by a given <paramref name="handle"/>.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the file to read.</param>
|
||||
/// <param name="sftpSession">The SFTP session.</param>
|
||||
/// <param name="chunkSize">The maximum number of bytes to read with each chunk.</param>
|
||||
/// <param name="maxPendingReads">The maximum number of pending reads.</param>
|
||||
/// <param name="fileSize">The size of the file or <see langword="null"/> when the size could not be determined.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="ISftpFileReader"/> for reading the content of the file represented by the
|
||||
/// specified <paramref name="handle"/>.
|
||||
/// </returns>
|
||||
public ISftpFileReader CreateFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSize)
|
||||
{
|
||||
return new SftpFileReader(handle, sftpSession, chunkSize, maxPendingReads, fileSize);
|
||||
}
|
||||
|
||||
internal string GetFullRemotePath(string path)
|
||||
{
|
||||
var fullPath = path;
|
||||
@@ -820,6 +803,8 @@ namespace Renci.SshNet.Sftp
|
||||
/// </returns>
|
||||
public Task<byte[]> RequestReadAsync(byte[] handle, ulong offset, uint length, CancellationToken cancellationToken)
|
||||
{
|
||||
Debug.Assert(length > 0, "This implementation cannot distinguish between EOF and zero-length reads");
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<byte[]>(cancellationToken);
|
||||
@@ -1075,15 +1060,8 @@ namespace Renci.SshNet.Sftp
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs SSH_FXP_FSTAT request.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle.</param>
|
||||
/// <param name="nullOnError">If set to <see langword="true"/>, returns <see langword="null"/> instead of throwing an exception.</param>
|
||||
/// <returns>
|
||||
/// File attributes.
|
||||
/// </returns>
|
||||
public SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError)
|
||||
/// <inheritdoc/>
|
||||
public SftpFileAttributes RequestFStat(byte[] handle)
|
||||
{
|
||||
SshException exception = null;
|
||||
SftpFileAttributes attributes = null;
|
||||
@@ -1109,7 +1087,7 @@ namespace Renci.SshNet.Sftp
|
||||
WaitOnHandle(wait, OperationTimeout);
|
||||
}
|
||||
|
||||
if (!nullOnError && exception is not null)
|
||||
if (exception is not null)
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
+122
-47
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -899,17 +900,33 @@ namespace Renci.SshNet
|
||||
/// <inheritdoc />
|
||||
public void DownloadFile(string path, Stream output, Action<ulong>? downloadCallback = null)
|
||||
{
|
||||
ThrowHelper.ThrowIfNullOrWhiteSpace(path);
|
||||
ThrowHelper.ThrowIfNull(output);
|
||||
CheckDisposed();
|
||||
|
||||
InternalDownloadFile(path, output, asyncResult: null, downloadCallback);
|
||||
InternalDownloadFile(
|
||||
path,
|
||||
output,
|
||||
asyncResult: null,
|
||||
downloadCallback,
|
||||
isAsync: false,
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DownloadFileAsync(string path, Stream output, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowHelper.ThrowIfNullOrWhiteSpace(path);
|
||||
ThrowHelper.ThrowIfNull(output);
|
||||
CheckDisposed();
|
||||
|
||||
return InternalDownloadFileAsync(path, output, cancellationToken);
|
||||
return InternalDownloadFile(
|
||||
path,
|
||||
output,
|
||||
asyncResult: null,
|
||||
downloadCallback: null,
|
||||
isAsync: true,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -976,17 +993,25 @@ namespace Renci.SshNet
|
||||
/// </remarks>
|
||||
public IAsyncResult BeginDownloadFile(string path, Stream output, AsyncCallback? asyncCallback, object? state, Action<ulong>? downloadCallback = null)
|
||||
{
|
||||
CheckDisposed();
|
||||
ThrowHelper.ThrowIfNullOrWhiteSpace(path);
|
||||
ThrowHelper.ThrowIfNull(output);
|
||||
CheckDisposed();
|
||||
|
||||
var asyncResult = new SftpDownloadAsyncResult(asyncCallback, state);
|
||||
|
||||
ThreadAbstraction.ExecuteThread(() =>
|
||||
_ = DoDownloadAndSetResult();
|
||||
|
||||
async Task DoDownloadAndSetResult()
|
||||
{
|
||||
try
|
||||
{
|
||||
InternalDownloadFile(path, output, asyncResult, downloadCallback);
|
||||
await InternalDownloadFile(
|
||||
path,
|
||||
output,
|
||||
asyncResult,
|
||||
downloadCallback,
|
||||
isAsync: true,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
asyncResult.SetAsCompleted(exception: null, completedSynchronously: false);
|
||||
}
|
||||
@@ -994,7 +1019,7 @@ namespace Renci.SshNet
|
||||
{
|
||||
asyncResult.SetAsCompleted(exp, completedSynchronously: false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return asyncResult;
|
||||
}
|
||||
@@ -1050,7 +1075,7 @@ namespace Renci.SshNet
|
||||
asyncResult: null,
|
||||
uploadCallback,
|
||||
isAsync: false,
|
||||
default).GetAwaiter().GetResult();
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1689,8 +1714,20 @@ namespace Renci.SshNet
|
||||
{
|
||||
using (var stream = OpenRead(path))
|
||||
{
|
||||
var buffer = new byte[stream.Length];
|
||||
stream.ReadExactly(buffer, 0, buffer.Length);
|
||||
byte[] buffer;
|
||||
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
buffer = new byte[stream.Length];
|
||||
stream.ReadExactly(buffer, 0, buffer.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryStream ms = new();
|
||||
stream.CopyTo(ms);
|
||||
buffer = ms.ToArray();
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -2233,32 +2270,59 @@ namespace Renci.SshNet
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internals the download file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="output">The output.</param>
|
||||
/// <param name="asyncResult">An <see cref="IAsyncResult"/> that references the asynchronous request.</param>
|
||||
/// <param name="downloadCallback">The download callback.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="output" /> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path" /> is <see langword="null"/> or contains whitespace.</exception>
|
||||
/// <exception cref="SshConnectionException">Client not connected.</exception>
|
||||
private void InternalDownloadFile(string path, Stream output, SftpDownloadAsyncResult? asyncResult, Action<ulong>? downloadCallback)
|
||||
#pragma warning disable S6966 // Awaitable method should be used
|
||||
private async Task InternalDownloadFile(
|
||||
string path,
|
||||
Stream output,
|
||||
SftpDownloadAsyncResult? asyncResult,
|
||||
Action<ulong>? downloadCallback,
|
||||
bool isAsync,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(output);
|
||||
ThrowHelper.ThrowIfNullOrWhiteSpace(path);
|
||||
Debug.Assert(!string.IsNullOrWhiteSpace(path));
|
||||
Debug.Assert(output is not null);
|
||||
Debug.Assert(isAsync || cancellationToken == default);
|
||||
|
||||
if (_sftpSession is null)
|
||||
{
|
||||
throw new SshConnectionException("Client not connected.");
|
||||
}
|
||||
|
||||
var fullPath = _sftpSession.GetCanonicalPath(path);
|
||||
SftpFileStream sftpStream;
|
||||
|
||||
using (var fileReader = ServiceFactory.CreateSftpFileReader(fullPath, _sftpSession, _bufferSize))
|
||||
if (isAsync)
|
||||
{
|
||||
var totalBytesRead = 0UL;
|
||||
var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
sftpStream = await SftpFileStream.OpenAsync(
|
||||
_sftpSession,
|
||||
fullPath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
(int)_bufferSize,
|
||||
cancellationToken,
|
||||
isDownloadFile: true).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var fullPath = _sftpSession.GetCanonicalPath(path);
|
||||
|
||||
sftpStream = SftpFileStream.Open(
|
||||
_sftpSession,
|
||||
fullPath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
(int)_bufferSize,
|
||||
isDownloadFile: true);
|
||||
}
|
||||
|
||||
// The below is effectively sftpStream.CopyTo{Async}(output) with consideration
|
||||
// for downloadCallback/asyncResult.
|
||||
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(81920);
|
||||
try
|
||||
{
|
||||
ulong totalBytesRead = 0;
|
||||
while (true)
|
||||
{
|
||||
// Cancel download
|
||||
@@ -2267,15 +2331,33 @@ namespace Renci.SshNet
|
||||
break;
|
||||
}
|
||||
|
||||
var data = fileReader.Read();
|
||||
if (data.Length == 0)
|
||||
var bytesRead = isAsync
|
||||
#if NET
|
||||
? await sftpStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)
|
||||
#else
|
||||
? await sftpStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)
|
||||
#endif
|
||||
: sftpStream.Read(buffer, 0, buffer.Length);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
output.Write(data, 0, data.Length);
|
||||
if (isAsync)
|
||||
{
|
||||
#if NET
|
||||
await output.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
await output.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
totalBytesRead += (ulong)data.Length;
|
||||
totalBytesRead += (ulong)bytesRead;
|
||||
|
||||
asyncResult?.Update(totalBytesRead);
|
||||
|
||||
@@ -2289,28 +2371,21 @@ namespace Renci.SshNet
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InternalDownloadFileAsync(string path, Stream output, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(output);
|
||||
ThrowHelper.ThrowIfNullOrWhiteSpace(path);
|
||||
|
||||
if (_sftpSession is null)
|
||||
finally
|
||||
{
|
||||
throw new SshConnectionException("Client not connected.");
|
||||
}
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false);
|
||||
var openStreamTask = SftpFileStream.OpenAsync(_sftpSession, fullPath, FileMode.Open, FileAccess.Read, (int)_bufferSize, cancellationToken);
|
||||
|
||||
using (var input = await openStreamTask.ConfigureAwait(false))
|
||||
{
|
||||
await input.CopyToAsync(output, 81920, cancellationToken).ConfigureAwait(false);
|
||||
if (isAsync)
|
||||
{
|
||||
await sftpStream.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
sftpStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore S6966 // Awaitable method should be used
|
||||
|
||||
#pragma warning disable S6966 // Awaitable method should be used
|
||||
private async Task InternalUploadFile(
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
|
||||
|
||||
var cancelledToken = new CancellationToken(true);
|
||||
|
||||
await Assert.ThrowsExactlyAsync<OperationCanceledException>(() => sftp.DownloadFileAsync("/xxx/eee/yyy", Stream.Null, cancelledToken));
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(() => sftp.DownloadFileAsync("/xxx/eee/yyy", Stream.Null, cancelledToken));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4353,11 +4353,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[seekOffset - 1];
|
||||
Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[soughtOverReadBuffer.Length], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -4397,11 +4397,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[seekOffset - 1];
|
||||
Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[soughtOverReadBuffer.Length], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -4438,7 +4438,7 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x00, fs.ReadByte());
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -4474,11 +4474,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[550 - 1];
|
||||
Assert.AreEqual(550 - 1, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[550 - 1].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[550 - 1], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -4599,11 +4599,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[seekOffset];
|
||||
Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[soughtOverReadBuffer.Length], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -4641,11 +4641,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[seekOffset];
|
||||
Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[soughtOverReadBuffer.Length], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -4681,11 +4681,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[seekOffset];
|
||||
Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[soughtOverReadBuffer.Length], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -4722,11 +4722,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[seekOffset];
|
||||
Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[soughtOverReadBuffer.Length], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -4813,7 +4813,7 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(writeBuffer.Length, fs.Length);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -4844,8 +4844,8 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(writeBuffer.Length + 1, fs.Length);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length - 3];
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
Assert.IsTrue(readBuffer.SequenceEqual(writeBuffer.Take(readBuffer.Length)));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer.Take(readBuffer.Length), readBuffer);
|
||||
|
||||
Assert.AreEqual(0x01, fs.ReadByte());
|
||||
Assert.AreEqual(0x05, fs.ReadByte());
|
||||
@@ -4884,8 +4884,8 @@ namespace Renci.SshNet.IntegrationTests
|
||||
|
||||
// First part of file should not have been touched
|
||||
var readBuffer = new byte[(int)client.BufferSize * 2];
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
Assert.IsTrue(readBuffer.SequenceEqual(writeBuffer.Take(readBuffer.Length)));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer.Take(readBuffer.Length), readBuffer);
|
||||
|
||||
// Check part that should have been updated
|
||||
Assert.AreEqual(0x01, fs.ReadByte());
|
||||
@@ -4895,8 +4895,10 @@ namespace Renci.SshNet.IntegrationTests
|
||||
|
||||
// Remaining bytes should not have been touched
|
||||
readBuffer = new byte[((int)client.BufferSize * 2) - 4];
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
Assert.IsTrue(readBuffer.SequenceEqual(writeBuffer.Skip(((int)client.BufferSize * 2) + 4).Take(readBuffer.Length)));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(
|
||||
writeBuffer.Skip(((int)client.BufferSize * 2) + 4).Take(readBuffer.Length).ToArray(),
|
||||
readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
Assert.AreEqual(-1, fs.ReadByte());
|
||||
@@ -4987,7 +4989,7 @@ namespace Renci.SshNet.IntegrationTests
|
||||
{
|
||||
var readBuffer = new byte[200];
|
||||
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
|
||||
var newPosition = fs.Seek(offset: 3L, SeekOrigin.Begin);
|
||||
|
||||
@@ -5062,11 +5064,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[seekOffset - 1];
|
||||
Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[soughtOverReadBuffer.Length], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -5104,11 +5106,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[seekOffset - 1];
|
||||
Assert.AreEqual(soughtOverReadBuffer.Length, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[soughtOverReadBuffer.Length].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[soughtOverReadBuffer.Length], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(readBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -5148,7 +5150,7 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x00, fs.ReadByte());
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -5187,11 +5189,11 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(0x04, fs.ReadByte());
|
||||
|
||||
var soughtOverReadBuffer = new byte[seekOffset - 1];
|
||||
Assert.AreEqual(seekOffset - 1, fs.Read(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length));
|
||||
Assert.IsTrue(new byte[seekOffset - 1].IsEqualTo(soughtOverReadBuffer));
|
||||
fs.ReadExactly(soughtOverReadBuffer, offset: 0, soughtOverReadBuffer.Length);
|
||||
CollectionAssert.AreEqual(new byte[seekOffset - 1], soughtOverReadBuffer);
|
||||
|
||||
var readBuffer = new byte[writeBuffer.Length];
|
||||
Assert.AreEqual(writeBuffer.Length, fs.Read(readBuffer, offset: 0, readBuffer.Length));
|
||||
fs.ReadExactly(readBuffer, offset: 0, readBuffer.Length);
|
||||
CollectionAssert.AreEqual(writeBuffer, readBuffer);
|
||||
|
||||
// Ensure we've reached end of the stream
|
||||
@@ -6203,6 +6205,136 @@ namespace Renci.SshNet.IntegrationTests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Sftp_SftpFileStream_Fuzz()
|
||||
{
|
||||
const int OperationCount = 100;
|
||||
const int MaxBufferSize = 1000;
|
||||
const int MaxFileSize = 15_000;
|
||||
|
||||
int seed = Environment.TickCount;
|
||||
|
||||
Console.WriteLine("Using seed " + seed);
|
||||
|
||||
var random = new Random(seed);
|
||||
|
||||
using var client = new SftpClient(_connectionInfoFactory.Create())
|
||||
{
|
||||
BufferSize = 100
|
||||
};
|
||||
client.Connect();
|
||||
|
||||
// We will perform operations on an SftpFileStream and a local
|
||||
// System.IO.FileStream, and check that the results are the same.
|
||||
// This could use a MemoryStream for the local side, except for the
|
||||
// fact that performing a 0-byte write at a position beyond the length
|
||||
// of the MemoryStream causes its length to increase, which is not the
|
||||
// case for FileStream. Since we've got 'FileStream' in the name, we
|
||||
// check that we align with FileStream's behaviour.
|
||||
|
||||
string remoteFilePath = GenerateUniqueRemoteFileName();
|
||||
string localFilePath = Path.GetTempFileName();
|
||||
|
||||
byte[] fileBytes = new byte[1024];
|
||||
random.NextBytes(fileBytes);
|
||||
|
||||
File.WriteAllBytes(localFilePath, fileBytes);
|
||||
client.WriteAllBytes(remoteFilePath, fileBytes);
|
||||
|
||||
try
|
||||
{
|
||||
using (var local = File.Open(localFilePath, FileMode.Open, FileAccess.ReadWrite))
|
||||
using (var remote = client.Open(remoteFilePath, FileMode.Open, FileAccess.ReadWrite))
|
||||
{
|
||||
for (int i = 0; i < OperationCount; i++)
|
||||
{
|
||||
#pragma warning disable IDE0010 // Add missing cases
|
||||
int op = random.Next(5);
|
||||
switch (op)
|
||||
{
|
||||
case 0 when local.Length < MaxFileSize: // Write
|
||||
{
|
||||
var buffer = new byte[random.Next(0, MaxBufferSize)];
|
||||
random.NextBytes(buffer);
|
||||
int offset = random.Next(0, buffer.Length + 1);
|
||||
int count = random.Next(0, buffer.Length - offset + 1);
|
||||
|
||||
remote.Write(buffer, offset, count);
|
||||
local.Write(buffer, offset, count);
|
||||
break;
|
||||
}
|
||||
case 1: // Read
|
||||
{
|
||||
var remoteBuffer = new byte[random.Next(0, MaxBufferSize)];
|
||||
var localBuffer = new byte[remoteBuffer.Length];
|
||||
int offset = random.Next(0, remoteBuffer.Length + 1);
|
||||
int count = random.Next(0, remoteBuffer.Length - offset + 1);
|
||||
|
||||
int remoteRead = ReadExactly(remote, remoteBuffer, offset, count);
|
||||
int localRead = ReadExactly(local, localBuffer, offset, count);
|
||||
|
||||
Assert.AreEqual(localRead, remoteRead);
|
||||
CollectionAssert.AreEqual(localBuffer, remoteBuffer);
|
||||
break;
|
||||
}
|
||||
case 2 when local.Length < MaxFileSize: // Seek
|
||||
{
|
||||
int position = (int)local.Position;
|
||||
int length = (int)local.Length;
|
||||
|
||||
SeekOrigin origin = (SeekOrigin)random.Next(0, 3);
|
||||
long offset = 0;
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
offset = random.Next(0, length * 2);
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
offset = random.Next(-position, position);
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
offset = random.Next(-length, length);
|
||||
break;
|
||||
}
|
||||
long newPosRemote = remote.Seek(offset, origin);
|
||||
long newPosLocal = local.Seek(offset, origin);
|
||||
Assert.AreEqual(newPosLocal, newPosRemote);
|
||||
Assert.AreEqual(local.Length, remote.Length);
|
||||
break;
|
||||
}
|
||||
case 3: // SetLength
|
||||
{
|
||||
long newLength = random.Next(0, MaxFileSize);
|
||||
remote.SetLength(newLength);
|
||||
local.SetLength(newLength);
|
||||
Assert.AreEqual(local.Length, remote.Length);
|
||||
Assert.AreEqual(local.Position, remote.Position);
|
||||
break;
|
||||
}
|
||||
case 4: // Flush
|
||||
{
|
||||
remote.Flush();
|
||||
local.Flush();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0010 // Add missing cases
|
||||
}
|
||||
}
|
||||
|
||||
CollectionAssert.AreEqual(File.ReadAllBytes(localFilePath), client.ReadAllBytes(remoteFilePath));
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(localFilePath);
|
||||
|
||||
if (client.Exists(remoteFilePath))
|
||||
{
|
||||
client.DeleteFile(remoteFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<object[]> GetSftpUploadFileFileStreamData()
|
||||
{
|
||||
yield return new object[] { 0 };
|
||||
@@ -6228,7 +6360,7 @@ namespace Renci.SshNet.IntegrationTests
|
||||
{
|
||||
Console.Write($"Downloading '{path}'");
|
||||
|
||||
var random = new Random().Next(1, 6);
|
||||
var random = new Random().Next(1, 7);
|
||||
switch (random)
|
||||
{
|
||||
case 1:
|
||||
@@ -6258,8 +6390,7 @@ namespace Renci.SshNet.IntegrationTests
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(random == 5);
|
||||
case 5:
|
||||
Console.WriteLine($" with {nameof(SftpFileStream.CopyToAsync)}");
|
||||
|
||||
using (var fs = client.OpenAsync(path, FileMode.Open, FileAccess.Read, CancellationToken.None).GetAwaiter().GetResult())
|
||||
@@ -6267,6 +6398,15 @@ namespace Renci.SshNet.IntegrationTests
|
||||
fs.CopyToAsync(output).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(random == 6);
|
||||
Console.WriteLine($" with {nameof(SftpClient.ReadAllBytes)}");
|
||||
|
||||
byte[] bytes = client.ReadAllBytes(path);
|
||||
|
||||
output.Write(bytes, 0, bytes.Length);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -6292,21 +6432,24 @@ namespace Renci.SshNet.IntegrationTests
|
||||
return (length / 1024m) / (elapsedMilliseconds / 1000m);
|
||||
}
|
||||
|
||||
private static void SftpCreateRemoteFile(SftpClient client, string remoteFile, int size)
|
||||
/// <summary>
|
||||
/// Similar to the netcore ReadExactly but without throwing on end of stream.
|
||||
/// </summary>
|
||||
private static int ReadExactly(Stream stream, byte[] buffer, int offset, int count)
|
||||
{
|
||||
var file = CreateTempFile(size);
|
||||
|
||||
try
|
||||
int totalRead = 0;
|
||||
while (totalRead < count)
|
||||
{
|
||||
using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
int read = stream.Read(buffer, offset + totalRead, count - totalRead);
|
||||
if (read == 0)
|
||||
{
|
||||
client.UploadFile(fs, remoteFile);
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
totalRead += read;
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
private static byte[] GenerateRandom(int size)
|
||||
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Common;
|
||||
using Renci.SshNet.Sftp;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceFactoryTest_CreateSftpFileReader_EndLStatThrowsSshException
|
||||
{
|
||||
private ServiceFactory _serviceFactory;
|
||||
private Mock<ISftpSession> _sftpSessionMock;
|
||||
private Mock<ISftpFileReader> _sftpFileReaderMock;
|
||||
private uint _bufferSize;
|
||||
private string _fileName;
|
||||
private SftpOpenAsyncResult _openAsyncResult;
|
||||
private byte[] _handle;
|
||||
private SFtpStatAsyncResult _statAsyncResult;
|
||||
private uint _chunkSize;
|
||||
private ISftpFileReader _actual;
|
||||
|
||||
private void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_bufferSize = (uint)random.Next(1, int.MaxValue);
|
||||
_openAsyncResult = new SftpOpenAsyncResult(null, null);
|
||||
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
|
||||
_statAsyncResult = new SFtpStatAsyncResult(null, null);
|
||||
_fileName = random.Next().ToString();
|
||||
_chunkSize = (uint)random.Next(1, int.MaxValue);
|
||||
}
|
||||
|
||||
private void CreateMocks()
|
||||
{
|
||||
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
_sftpSessionMock.Setup(p => p.SessionLoggerFactory).Returns(NullLoggerFactory.Instance);
|
||||
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
private void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
|
||||
.Returns(_openAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndOpen(_openAsyncResult))
|
||||
.Returns(_handle);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginLStat(_fileName, null, null))
|
||||
.Returns(_statAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
|
||||
.Returns(_chunkSize);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndLStat(_statAsyncResult))
|
||||
.Throws(new SshException());
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 10, null))
|
||||
.Returns(_sftpFileReaderMock.Object);
|
||||
}
|
||||
|
||||
private void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
|
||||
_serviceFactory = new ServiceFactory();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
private void Act()
|
||||
{
|
||||
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateSftpFileReaderShouldReturnCreatedInstance()
|
||||
{
|
||||
Assert.IsNotNull(_actual);
|
||||
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Sftp;
|
||||
using Renci.SshNet.Tests.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceFactoryTest_CreateSftpFileReader_FileSizeIsAlmostSixTimesGreaterThanChunkSize
|
||||
{
|
||||
private ServiceFactory _serviceFactory;
|
||||
private Mock<ISftpSession> _sftpSessionMock;
|
||||
private Mock<ISftpFileReader> _sftpFileReaderMock;
|
||||
private uint _bufferSize;
|
||||
private string _fileName;
|
||||
private SftpOpenAsyncResult _openAsyncResult;
|
||||
private byte[] _handle;
|
||||
private SFtpStatAsyncResult _statAsyncResult;
|
||||
private uint _chunkSize;
|
||||
private SftpFileAttributes _fileAttributes;
|
||||
private long _fileSize;
|
||||
private ISftpFileReader _actual;
|
||||
|
||||
private void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_bufferSize = (uint)random.Next(1, int.MaxValue);
|
||||
_openAsyncResult = new SftpOpenAsyncResult(null, null);
|
||||
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
|
||||
_statAsyncResult = new SFtpStatAsyncResult(null, null);
|
||||
_fileName = random.Next().ToString();
|
||||
_chunkSize = (uint)random.Next(1000, 5000);
|
||||
_fileSize = (_chunkSize * 6) - 10;
|
||||
_fileAttributes = new SftpFileAttributesBuilder().WithSize(_fileSize).Build();
|
||||
}
|
||||
|
||||
private void CreateMocks()
|
||||
{
|
||||
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
private void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
|
||||
.Returns(_openAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndOpen(_openAsyncResult))
|
||||
.Returns(_handle);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginLStat(_fileName, null, null))
|
||||
.Returns(_statAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
|
||||
.Returns(_chunkSize);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndLStat(_statAsyncResult))
|
||||
.Returns(_fileAttributes);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 7, _fileSize))
|
||||
.Returns(_sftpFileReaderMock.Object);
|
||||
}
|
||||
|
||||
private void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
|
||||
_serviceFactory = new ServiceFactory();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
private void Act()
|
||||
{
|
||||
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateSftpFileReaderShouldReturnCreatedInstance()
|
||||
{
|
||||
Assert.IsNotNull(_actual);
|
||||
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Sftp;
|
||||
using Renci.SshNet.Tests.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceFactoryTest_CreateSftpFileReader_FileSizeIsEqualToChunkSize
|
||||
{
|
||||
private ServiceFactory _serviceFactory;
|
||||
private Mock<ISftpSession> _sftpSessionMock;
|
||||
private Mock<ISftpFileReader> _sftpFileReaderMock;
|
||||
private uint _bufferSize;
|
||||
private string _fileName;
|
||||
private SftpOpenAsyncResult _openAsyncResult;
|
||||
private byte[] _handle;
|
||||
private SFtpStatAsyncResult _statAsyncResult;
|
||||
private uint _chunkSize;
|
||||
private SftpFileAttributes _fileAttributes;
|
||||
private long _fileSize;
|
||||
private ISftpFileReader _actual;
|
||||
|
||||
private void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_bufferSize = (uint)random.Next(1, int.MaxValue);
|
||||
_openAsyncResult = new SftpOpenAsyncResult(null, null);
|
||||
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
|
||||
_statAsyncResult = new SFtpStatAsyncResult(null, null);
|
||||
_fileName = random.Next().ToString();
|
||||
_chunkSize = (uint)random.Next(1000, int.MaxValue);
|
||||
_fileSize = _chunkSize;
|
||||
_fileAttributes = new SftpFileAttributesBuilder().WithSize(_fileSize).Build();
|
||||
}
|
||||
|
||||
private void CreateMocks()
|
||||
{
|
||||
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
private void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
|
||||
.Returns(_openAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndOpen(_openAsyncResult))
|
||||
.Returns(_handle);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginLStat(_fileName, null, null))
|
||||
.Returns(_statAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
|
||||
.Returns(_chunkSize);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndLStat(_statAsyncResult))
|
||||
.Returns(_fileAttributes);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 2, _fileSize))
|
||||
.Returns(_sftpFileReaderMock.Object);
|
||||
}
|
||||
|
||||
private void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
|
||||
_serviceFactory = new ServiceFactory();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
private void Act()
|
||||
{
|
||||
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateSftpFileReaderShouldReturnCreatedInstance()
|
||||
{
|
||||
Assert.IsNotNull(_actual);
|
||||
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Sftp;
|
||||
using Renci.SshNet.Tests.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceFactoryTest_CreateSftpFileReader_FileSizeIsExactlyFiveTimesGreaterThanChunkSize
|
||||
{
|
||||
private ServiceFactory _serviceFactory;
|
||||
private Mock<ISftpSession> _sftpSessionMock;
|
||||
private Mock<ISftpFileReader> _sftpFileReaderMock;
|
||||
private uint _bufferSize;
|
||||
private string _fileName;
|
||||
private SftpOpenAsyncResult _openAsyncResult;
|
||||
private byte[] _handle;
|
||||
private SFtpStatAsyncResult _statAsyncResult;
|
||||
private uint _chunkSize;
|
||||
private SftpFileAttributes _fileAttributes;
|
||||
private long _fileSize;
|
||||
private ISftpFileReader _actual;
|
||||
|
||||
private void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_bufferSize = (uint)random.Next(1, int.MaxValue);
|
||||
_openAsyncResult = new SftpOpenAsyncResult(null, null);
|
||||
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
|
||||
_statAsyncResult = new SFtpStatAsyncResult(null, null);
|
||||
_fileName = random.Next().ToString();
|
||||
_chunkSize = (uint)random.Next(1000, 5000);
|
||||
_fileSize = _chunkSize * 5;
|
||||
_fileAttributes = new SftpFileAttributesBuilder().WithSize(_fileSize).Build();
|
||||
}
|
||||
|
||||
private void CreateMocks()
|
||||
{
|
||||
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
private void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
|
||||
.Returns(_openAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndOpen(_openAsyncResult))
|
||||
.Returns(_handle);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginLStat(_fileName, null, null))
|
||||
.Returns(_statAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
|
||||
.Returns(_chunkSize);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndLStat(_statAsyncResult))
|
||||
.Returns(_fileAttributes);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 6, _fileSize))
|
||||
.Returns(_sftpFileReaderMock.Object);
|
||||
}
|
||||
|
||||
private void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
|
||||
_serviceFactory = new ServiceFactory();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
private void Act()
|
||||
{
|
||||
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateSftpFileReaderShouldReturnCreatedInstance()
|
||||
{
|
||||
Assert.IsNotNull(_actual);
|
||||
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Sftp;
|
||||
using Renci.SshNet.Tests.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceFactoryTest_CreateSftpFileReader_FileSizeIsLessThanChunkSize
|
||||
{
|
||||
private ServiceFactory _serviceFactory;
|
||||
private Mock<ISftpSession> _sftpSessionMock;
|
||||
private Mock<ISftpFileReader> _sftpFileReaderMock;
|
||||
private uint _bufferSize;
|
||||
private string _fileName;
|
||||
private SftpOpenAsyncResult _openAsyncResult;
|
||||
private byte[] _handle;
|
||||
private SFtpStatAsyncResult _statAsyncResult;
|
||||
private uint _chunkSize;
|
||||
private SftpFileAttributes _fileAttributes;
|
||||
private long _fileSize;
|
||||
private ISftpFileReader _actual;
|
||||
|
||||
private void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_bufferSize = (uint)random.Next(1, int.MaxValue);
|
||||
_openAsyncResult = new SftpOpenAsyncResult(null, null);
|
||||
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
|
||||
_statAsyncResult = new SFtpStatAsyncResult(null, null);
|
||||
_fileName = random.Next().ToString();
|
||||
_chunkSize = (uint)random.Next(1000, int.MaxValue);
|
||||
_fileSize = _chunkSize - random.Next(1, 10);
|
||||
_fileAttributes = new SftpFileAttributesBuilder().WithSize(_fileSize).Build();
|
||||
}
|
||||
|
||||
private void CreateMocks()
|
||||
{
|
||||
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
private void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
|
||||
.Returns(_openAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndOpen(_openAsyncResult))
|
||||
.Returns(_handle);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginLStat(_fileName, null, null))
|
||||
.Returns(_statAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
|
||||
.Returns(_chunkSize);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndLStat(_statAsyncResult))
|
||||
.Returns(_fileAttributes);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 2, _fileSize))
|
||||
.Returns(_sftpFileReaderMock.Object);
|
||||
}
|
||||
|
||||
private void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
|
||||
_serviceFactory = new ServiceFactory();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
private void Act()
|
||||
{
|
||||
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateSftpFileReaderShouldReturnCreatedInstance()
|
||||
{
|
||||
Assert.IsNotNull(_actual);
|
||||
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Sftp;
|
||||
using Renci.SshNet.Tests.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceFactoryTest_CreateSftpFileReader_FileSizeIsLittleMoreThanFiveTimesGreaterThanChunkSize
|
||||
{
|
||||
private ServiceFactory _serviceFactory;
|
||||
private Mock<ISftpSession> _sftpSessionMock;
|
||||
private Mock<ISftpFileReader> _sftpFileReaderMock;
|
||||
private uint _bufferSize;
|
||||
private string _fileName;
|
||||
private SftpOpenAsyncResult _openAsyncResult;
|
||||
private byte[] _handle;
|
||||
private SFtpStatAsyncResult _statAsyncResult;
|
||||
private uint _chunkSize;
|
||||
private SftpFileAttributes _fileAttributes;
|
||||
private long _fileSize;
|
||||
private ISftpFileReader _actual;
|
||||
|
||||
private void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_bufferSize = (uint)random.Next(1, int.MaxValue);
|
||||
_openAsyncResult = new SftpOpenAsyncResult(null, null);
|
||||
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
|
||||
_statAsyncResult = new SFtpStatAsyncResult(null, null);
|
||||
_fileName = random.Next().ToString();
|
||||
_chunkSize = (uint)random.Next(1000, 5000);
|
||||
_fileSize = (_chunkSize * 5) + 10;
|
||||
_fileAttributes = new SftpFileAttributesBuilder().WithSize(_fileSize).Build();
|
||||
}
|
||||
|
||||
private void CreateMocks()
|
||||
{
|
||||
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
private void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
|
||||
.Returns(_openAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndOpen(_openAsyncResult))
|
||||
.Returns(_handle);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginLStat(_fileName, null, null))
|
||||
.Returns(_statAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
|
||||
.Returns(_chunkSize);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndLStat(_statAsyncResult))
|
||||
.Returns(_fileAttributes);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 7, _fileSize))
|
||||
.Returns(_sftpFileReaderMock.Object);
|
||||
}
|
||||
|
||||
private void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
|
||||
_serviceFactory = new ServiceFactory();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
private void Act()
|
||||
{
|
||||
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateSftpFileReaderShouldReturnCreatedInstance()
|
||||
{
|
||||
Assert.IsNotNull(_actual);
|
||||
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Sftp;
|
||||
using Renci.SshNet.Tests.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceFactoryTest_CreateSftpFileReader_FileSizeIsMoreThanMaxPendingReadsTimesChunkSize
|
||||
{
|
||||
private ServiceFactory _serviceFactory;
|
||||
private Mock<ISftpSession> _sftpSessionMock;
|
||||
private Mock<ISftpFileReader> _sftpFileReaderMock;
|
||||
private uint _bufferSize;
|
||||
private string _fileName;
|
||||
private SftpOpenAsyncResult _openAsyncResult;
|
||||
private byte[] _handle;
|
||||
private SFtpStatAsyncResult _statAsyncResult;
|
||||
private uint _chunkSize;
|
||||
private SftpFileAttributes _fileAttributes;
|
||||
private long _fileSize;
|
||||
private ISftpFileReader _actual;
|
||||
private int _maxPendingReads;
|
||||
|
||||
private void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_maxPendingReads = 100;
|
||||
_bufferSize = (uint)random.Next(1, int.MaxValue);
|
||||
_openAsyncResult = new SftpOpenAsyncResult(null, null);
|
||||
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
|
||||
_statAsyncResult = new SFtpStatAsyncResult(null, null);
|
||||
_fileName = random.Next().ToString();
|
||||
_chunkSize = (uint)random.Next(1000, 5000);
|
||||
_fileSize = _chunkSize * random.Next(_maxPendingReads + 1, _maxPendingReads * 2);
|
||||
_fileAttributes = new SftpFileAttributesBuilder().WithSize(_fileSize).Build();
|
||||
}
|
||||
|
||||
private void CreateMocks()
|
||||
{
|
||||
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
private void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
|
||||
.Returns(_openAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndOpen(_openAsyncResult))
|
||||
.Returns(_handle);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginLStat(_fileName, null, null))
|
||||
.Returns(_statAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
|
||||
.Returns(_chunkSize);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndLStat(_statAsyncResult))
|
||||
.Returns(_fileAttributes);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, _maxPendingReads, _fileSize))
|
||||
.Returns(_sftpFileReaderMock.Object);
|
||||
}
|
||||
|
||||
private void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
|
||||
_serviceFactory = new ServiceFactory();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
private void Act()
|
||||
{
|
||||
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateSftpFileReaderShouldReturnCreatedInstance()
|
||||
{
|
||||
Assert.IsNotNull(_actual);
|
||||
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Sftp;
|
||||
using Renci.SshNet.Tests.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceFactoryTest_CreateSftpFileReader_FileSizeIsZero
|
||||
{
|
||||
private ServiceFactory _serviceFactory;
|
||||
private Mock<ISftpSession> _sftpSessionMock;
|
||||
private Mock<ISftpFileReader> _sftpFileReaderMock;
|
||||
private uint _bufferSize;
|
||||
private string _fileName;
|
||||
private SftpOpenAsyncResult _openAsyncResult;
|
||||
private byte[] _handle;
|
||||
private SFtpStatAsyncResult _statAsyncResult;
|
||||
private uint _chunkSize;
|
||||
private long _fileSize;
|
||||
private SftpFileAttributes _fileAttributes;
|
||||
private ISftpFileReader _actual;
|
||||
|
||||
private void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_bufferSize = (uint)random.Next(1, int.MaxValue);
|
||||
_openAsyncResult = new SftpOpenAsyncResult(null, null);
|
||||
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
|
||||
_statAsyncResult = new SFtpStatAsyncResult(null, null);
|
||||
_fileName = random.Next().ToString();
|
||||
_chunkSize = (uint)random.Next(1, int.MaxValue);
|
||||
_fileSize = 0L;
|
||||
_fileAttributes = new SftpFileAttributesBuilder().WithSize(_fileSize).Build();
|
||||
}
|
||||
|
||||
private void CreateMocks()
|
||||
{
|
||||
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
private void SetupMocks()
|
||||
{
|
||||
var seq = new MockSequence();
|
||||
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
|
||||
.Returns(_openAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndOpen(_openAsyncResult))
|
||||
.Returns(_handle);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.BeginLStat(_fileName, null, null))
|
||||
.Returns(_statAsyncResult);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
|
||||
.Returns(_chunkSize);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.EndLStat(_statAsyncResult))
|
||||
.Returns(_fileAttributes);
|
||||
_sftpSessionMock.InSequence(seq)
|
||||
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 1, _fileSize))
|
||||
.Returns(_sftpFileReaderMock.Object);
|
||||
}
|
||||
|
||||
private void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
|
||||
_serviceFactory = new ServiceFactory();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
private void Act()
|
||||
{
|
||||
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateSftpFileReaderShouldReturnCreatedInstance()
|
||||
{
|
||||
Assert.IsNotNull(_actual);
|
||||
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Common;
|
||||
using Renci.SshNet.Sftp;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
public abstract class SftpFileReaderTestBase
|
||||
{
|
||||
internal Mock<ISftpSession> SftpSessionMock { get; private set; }
|
||||
|
||||
protected abstract void SetupData();
|
||||
|
||||
protected void CreateMocks()
|
||||
{
|
||||
SftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
SftpSessionMock.Setup(s => s.SessionLoggerFactory).Returns(NullLoggerFactory.Instance);
|
||||
}
|
||||
|
||||
protected abstract void SetupMocks();
|
||||
|
||||
protected virtual void Arrange()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void SetUp()
|
||||
{
|
||||
Arrange();
|
||||
Act();
|
||||
}
|
||||
|
||||
protected abstract void Act();
|
||||
|
||||
protected static byte[] CreateByteArray(Random random, int length)
|
||||
{
|
||||
var chunk = new byte[length];
|
||||
random.NextBytes(chunk);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
protected static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout)
|
||||
{
|
||||
var result = WaitHandle.WaitAny(waitHandles, millisecondsTimeout);
|
||||
|
||||
if (result == WaitHandle.WaitTimeout)
|
||||
{
|
||||
throw new SshOperationTimeoutException();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Sftp;
|
||||
|
||||
using BufferedRead = Renci.SshNet.Sftp.SftpFileReader.BufferedRead;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
[TestClass]
|
||||
public class SftpFileReaderTest_DisposeShouldUnblockReadAndReadAhead : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
private SftpFileReader _reader;
|
||||
private ObjectDisposedException _actualException;
|
||||
private AsyncCallback _readAsyncCallback;
|
||||
private EventWaitHandle _disposeCompleted;
|
||||
|
||||
[TestCleanup]
|
||||
public void TearDown()
|
||||
{
|
||||
_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);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
_disposeCompleted = new ManualResetEvent(false);
|
||||
_readAsyncCallback = null;
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Returns<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_readAsyncCallback = callback;
|
||||
return null;
|
||||
});
|
||||
_ = 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.IsOpen)
|
||||
.Returns(true);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.BeginClose(_handle, null, null))
|
||||
.Returns(_closeAsyncResult);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.EndClose(_closeAsyncResult));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 1, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
ThreadAbstraction.ExecuteThread(() =>
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
_reader.Dispose();
|
||||
_ = _disposeCompleted.Set();
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
_ = _reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
|
||||
// Dispose may unblock Read() before the dispose has fully completed, so
|
||||
// let's wait until it has completed
|
||||
_ = _disposeCompleted.WaitOne(500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadShouldHaveThrownObjectDisposedException()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.AreEqual(typeof(SftpFileReader).FullName, _actualException.ObjectName);
|
||||
}
|
||||
|
||||
[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 HandleShouldHaveBeenClosed()
|
||||
{
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCompleteImmediatelyAndNotAttemptToCloseHandleAgain()
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InvokeOfReadAheadCallbackShouldCompleteImmediately()
|
||||
{
|
||||
Assert.IsNotNull(_readAsyncCallback);
|
||||
|
||||
_readAsyncCallback(new SftpReadAsyncResult(null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Sftp;
|
||||
|
||||
using BufferedRead = Renci.SshNet.Sftp.SftpFileReader.BufferedRead;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
[TestClass]
|
||||
public class SftpFileReaderTest_Dispose_SftpSessionIsNotOpen : 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<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback(() =>
|
||||
{
|
||||
// harden test by making sure that we've invoked BeginRead before Dispose is invoked
|
||||
_ = _beginReadInvoked.Set();
|
||||
})
|
||||
.Returns<byte[], ulong, uint, AsyncCallback, object>((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(false);
|
||||
}
|
||||
|
||||
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 BeginCloseOnSftpSessionShouldNeverHaveBeenInvoked()
|
||||
{
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Never);
|
||||
}
|
||||
}
|
||||
}
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
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<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback(() =>
|
||||
{
|
||||
// harden test by making sure that we've invoked BeginRead before Dispose is invoked
|
||||
_ = _beginReadInvoked.Set();
|
||||
})
|
||||
.Returns<byte[], ulong, uint, AsyncCallback, object>((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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
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_EndCloseThrowsException : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
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);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
_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<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback(() =>
|
||||
{
|
||||
// harden test by making sure that we've invoked BeginRead before Dispose is invoked
|
||||
_ = _beginReadInvoked.Set();
|
||||
})
|
||||
.Returns<byte[], ulong, uint, AsyncCallback, object>((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))
|
||||
.Returns(_closeAsyncResult);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.EndClose(_closeAsyncResult))
|
||||
.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 EndCloseOnSftpSessionShouldHaveBeenInvokedOnce()
|
||||
{
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
-168
@@ -1,168 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
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_LastChunkBeforeEofIsComplete : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpReadAsyncResult _readAsyncResultBeyondEof;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
private byte[] _chunk1;
|
||||
private byte[] _chunk2;
|
||||
private byte[] _chunk3;
|
||||
private SftpFileReader _reader;
|
||||
private byte[] _actualChunk1;
|
||||
private byte[] _actualChunk2;
|
||||
private byte[] _actualChunk3;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_handle = CreateByteArray(random, 5);
|
||||
_chunk1 = CreateByteArray(random, ChunkLength);
|
||||
// chunk is less than the requested length, but - together with chunk 1 - contains all data up to the EOF
|
||||
_chunk2 = CreateByteArray(random, ChunkLength - 10);
|
||||
_chunk3 = new byte[0];
|
||||
_fileSize = _chunk1.Length + _chunk2.Length;
|
||||
_waitHandleArray = new WaitHandle[2];
|
||||
_operationTimeout = random.Next(10000, 20000);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
_readAsyncResultBeyondEof = new SftpReadAsyncResult(null, null);
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk1, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
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, ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk2, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
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, 2 * ChunkLength, ChunkLength, null, It.IsAny<BufferedRead>()))
|
||||
.Returns(_readAsyncResultBeyondEof);
|
||||
SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.EndRead(_readAsyncResultBeyondEof))
|
||||
.Returns(_chunk3);
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 15, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
_actualChunk1 = _reader.Read();
|
||||
_actualChunk2 = _reader.Read();
|
||||
_actualChunk3 = _reader.Read();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FirstReadShouldReturnChunk1()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk1);
|
||||
Assert.AreSame(_chunk1, _actualChunk1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SecondReadShouldReturnChunk2()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk2);
|
||||
Assert.AreSame(_chunk2, _actualChunk2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ThirdReadShouldReturnChunk3()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk3);
|
||||
Assert.AreSame(_chunk3, _actualChunk3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAfterEndOfFileShouldThrowSshException()
|
||||
{
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual("Attempting to read beyond the end of the file.", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCloseHandleAndCompleteImmediately()
|
||||
{
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.IsOpen).Returns(true);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.BeginClose(_handle, null, null)).Returns(_closeAsyncResult);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.EndClose(_closeAsyncResult));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
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_LastChunkBeforeEofIsPartial : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
private byte[] _chunk1;
|
||||
private byte[] _chunk2;
|
||||
private byte[] _chunk3;
|
||||
private SftpFileReader _reader;
|
||||
private byte[] _actualChunk1;
|
||||
private byte[] _actualChunk2;
|
||||
private byte[] _actualChunk3;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_handle = CreateByteArray(random, 5);
|
||||
_chunk1 = CreateByteArray(random, ChunkLength);
|
||||
_chunk2 = CreateByteArray(random, ChunkLength);
|
||||
_chunk3 = new byte[0];
|
||||
_fileSize = _chunk1.Length + _chunk2.Length;
|
||||
_waitHandleArray = new WaitHandle[2];
|
||||
_operationTimeout = random.Next(10000, 20000);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk1, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
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, ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk2, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
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, 2 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk3, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 15, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
_actualChunk1 = _reader.Read();
|
||||
_actualChunk2 = _reader.Read();
|
||||
_actualChunk3 = _reader.Read();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FirstReadShouldReturnChunk1()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk1);
|
||||
Assert.AreSame(_chunk1, _actualChunk1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SecondReadShouldReturnChunk2()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk2);
|
||||
Assert.AreSame(_chunk2, _actualChunk2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ThirdReadShouldReturnChunk3()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk3);
|
||||
Assert.AreSame(_chunk3, _actualChunk3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAfterEndOfFileShouldThrowSshException()
|
||||
{
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual("Attempting to read beyond the end of the file.", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCloseHandleAndCompleteImmediately()
|
||||
{
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.IsOpen).Returns(true);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.BeginClose(_handle, null, null)).Returns(_closeAsyncResult);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.EndClose(_closeAsyncResult));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
-323
@@ -1,323 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
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_PreviousChunkIsIncompleteAndEofIsNotReached : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
private byte[] _chunk1;
|
||||
private byte[] _chunk2;
|
||||
private byte[] _chunk2CatchUp1;
|
||||
private byte[] _chunk2CatchUp2;
|
||||
private byte[] _chunk3;
|
||||
private byte[] _chunk4;
|
||||
private byte[] _chunk5;
|
||||
private SftpFileReader _reader;
|
||||
private byte[] _actualChunk1;
|
||||
private byte[] _actualChunk2;
|
||||
private byte[] _actualChunk3;
|
||||
private ManualResetEvent _chunk1BeginRead;
|
||||
private ManualResetEvent _chunk2BeginRead;
|
||||
private ManualResetEvent _chunk3BeginRead;
|
||||
private ManualResetEvent _chunk4BeginRead;
|
||||
private ManualResetEvent _chunk5BeginRead;
|
||||
private ManualResetEvent _waitBeforeChunk6;
|
||||
private ManualResetEvent _chunk6BeginRead;
|
||||
private byte[] _actualChunk4;
|
||||
private byte[] _actualChunk2CatchUp1;
|
||||
private byte[] _actualChunk2CatchUp2;
|
||||
private byte[] _chunk6;
|
||||
private byte[] _actualChunk5;
|
||||
private byte[] _actualChunk6;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_handle = CreateByteArray(random, 3);
|
||||
_chunk1 = CreateByteArray(random, ChunkLength);
|
||||
_chunk2 = CreateByteArray(random, ChunkLength - 17);
|
||||
_chunk2CatchUp1 = CreateByteArray(random, 10);
|
||||
_chunk2CatchUp2 = CreateByteArray(random, 7);
|
||||
_chunk3 = CreateByteArray(random, ChunkLength);
|
||||
_chunk4 = CreateByteArray(random, ChunkLength);
|
||||
_chunk5 = CreateByteArray(random, ChunkLength);
|
||||
_chunk6 = new byte[0];
|
||||
_chunk1BeginRead = new ManualResetEvent(false);
|
||||
_chunk2BeginRead = new ManualResetEvent(false);
|
||||
_chunk3BeginRead = new ManualResetEvent(false);
|
||||
_chunk4BeginRead = new ManualResetEvent(false);
|
||||
_chunk5BeginRead = new ManualResetEvent(false);
|
||||
_waitBeforeChunk6 = new ManualResetEvent(false);
|
||||
_chunk6BeginRead = new ManualResetEvent(false);
|
||||
_fileSize = _chunk1.Length + _chunk2.Length + _chunk2CatchUp1.Length + _chunk2CatchUp2.Length + _chunk3.Length + _chunk4.Length + _chunk5.Length;
|
||||
_waitHandleArray = new WaitHandle[2];
|
||||
_operationTimeout = random.Next(10000, 20000);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_ = _chunk1BeginRead.Set();
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk1, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = 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, ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_ = _chunk2BeginRead.Set();
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk2, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = 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, 2 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_ = _chunk3BeginRead.Set();
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk3, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = 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, 3 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_ = _chunk4BeginRead.Set();
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk4, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = 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, 4 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_ = _chunk5BeginRead.Set();
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk5, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.OperationTimeout)
|
||||
.Returns(_operationTimeout);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
|
||||
.Callback(() => _waitBeforeChunk6.Set())
|
||||
.Returns(() => WaitAny(_waitHandleArray, _operationTimeout));
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.RequestRead(_handle, (2 * ChunkLength) - 17, 17))
|
||||
.Returns(_chunk2CatchUp1);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.RequestRead(_handle, (2 * ChunkLength) - 7, 7))
|
||||
.Returns(_chunk2CatchUp2);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.BeginRead(_handle, 5 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_ = _chunk6BeginRead.Set();
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk6, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 3, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
// reader is configured to read-ahead max. 3 chunks, so chunk4 should not have been read
|
||||
Assert.IsFalse(_chunk4BeginRead.WaitOne(0));
|
||||
// consume chunk 1
|
||||
_actualChunk1 = _reader.Read();
|
||||
// consuming chunk1 allows chunk4 to be read-ahead
|
||||
Assert.IsTrue(_chunk4BeginRead.WaitOne(200));
|
||||
// verify that chunk5 has not yet been read-ahead
|
||||
Assert.IsFalse(_chunk5BeginRead.WaitOne(0));
|
||||
// consume chunk 2
|
||||
_actualChunk2 = _reader.Read();
|
||||
// consuming chunk2 allows chunk5 to be read-ahead
|
||||
Assert.IsTrue(_chunk5BeginRead.WaitOne(200));
|
||||
// pauze until the read-ahead has started waiting a semaphore to become available
|
||||
Assert.IsTrue(_waitBeforeChunk6.WaitOne(200));
|
||||
// consume remaining parts of chunk 2
|
||||
_actualChunk2CatchUp1 = _reader.Read();
|
||||
_actualChunk2CatchUp2 = _reader.Read();
|
||||
// verify that chunk6 has not yet been read-ahead
|
||||
Assert.IsFalse(_chunk6BeginRead.WaitOne(0));
|
||||
// consume chunk 3
|
||||
_actualChunk3 = _reader.Read();
|
||||
// consuming chunk3 allows chunk6 to be read-ahead
|
||||
Assert.IsTrue(_chunk6BeginRead.WaitOne(200));
|
||||
// consume chunk 4
|
||||
_actualChunk4 = _reader.Read();
|
||||
// consume chunk 5
|
||||
_actualChunk5 = _reader.Read();
|
||||
// consume chunk 6
|
||||
_actualChunk6 = _reader.Read();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FirstReadShouldReturnChunk1()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk1);
|
||||
Assert.AreSame(_chunk1, _actualChunk1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SecondReadShouldReturnChunk2()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk2);
|
||||
Assert.AreSame(_chunk2, _actualChunk2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ThirdReadShouldReturnChunk2CatchUp1()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk2CatchUp1);
|
||||
Assert.AreSame(_chunk2CatchUp1, _actualChunk2CatchUp1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FourthReadShouldReturnChunk2CatchUp2()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk2CatchUp2);
|
||||
Assert.AreSame(_chunk2CatchUp2, _actualChunk2CatchUp2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FifthReadShouldReturnChunk3()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk3);
|
||||
Assert.AreSame(_chunk3, _actualChunk3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SixthReadShouldReturnChunk4()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk4);
|
||||
Assert.AreSame(_chunk4, _actualChunk4);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SeventhReadShouldReturnChunk5()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk5);
|
||||
Assert.AreSame(_chunk5, _actualChunk5);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EightReadShouldReturnChunk6()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk6);
|
||||
Assert.AreSame(_chunk6, _actualChunk6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAfterEndOfFileShouldThrowSshException()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = _reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual("Attempting to read beyond the end of the file.", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCloseHandleAndCompleteImmediately()
|
||||
{
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.IsOpen)
|
||||
.Returns(true);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.BeginClose(_handle, null, null))
|
||||
.Returns(_closeAsyncResult);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.EndClose(_closeAsyncResult));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
-207
@@ -1,207 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
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_PreviousChunkIsIncompleteAndEofIsReached : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
private byte[] _chunk1;
|
||||
private byte[] _chunk2;
|
||||
private byte[] _chunk2CatchUp;
|
||||
private byte[] _chunk3;
|
||||
private SftpFileReader _reader;
|
||||
private byte[] _actualChunk1;
|
||||
private byte[] _actualChunk2;
|
||||
private byte[] _actualChunk2CatchUp;
|
||||
private byte[] _actualChunk3;
|
||||
private ManualResetEvent _chunk1BeginRead;
|
||||
private ManualResetEvent _chunk2BeginRead;
|
||||
private ManualResetEvent _chunk3BeginRead;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_handle = CreateByteArray(random, 3);
|
||||
_chunk1 = CreateByteArray(random, ChunkLength);
|
||||
_chunk2 = CreateByteArray(random, ChunkLength - 10);
|
||||
_chunk2CatchUp = CreateByteArray(random, 10);
|
||||
_chunk3 = new byte[0];
|
||||
_chunk1BeginRead = new ManualResetEvent(false);
|
||||
_chunk2BeginRead = new ManualResetEvent(false);
|
||||
_chunk3BeginRead = new ManualResetEvent(false);
|
||||
_fileSize = _chunk1.Length + _chunk2.Length + _chunk2CatchUp.Length + _chunk3.Length;
|
||||
_waitHandleArray = new WaitHandle[2];
|
||||
_operationTimeout = random.Next(10000, 20000);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_ = _chunk1BeginRead.Set();
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk1, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = 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, ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_ = _chunk2BeginRead.Set();
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk2, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = 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, 2 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_ = _chunk3BeginRead.Set();
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk3, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.RequestRead(_handle, (2 * ChunkLength) - 10, 10))
|
||||
.Returns(_chunk2CatchUp);
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 5, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
// consume chunk 1
|
||||
_actualChunk1 = _reader.Read();
|
||||
// consume chunk 2
|
||||
_actualChunk2 = _reader.Read();
|
||||
// wait until chunk3 has been read-ahead
|
||||
Assert.IsTrue(_chunk3BeginRead.WaitOne(200));
|
||||
// consume remaining parts of chunk 2
|
||||
_actualChunk2CatchUp = _reader.Read();
|
||||
// consume chunk 3
|
||||
_actualChunk3 = _reader.Read();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FirstReadShouldReturnChunk1()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk1);
|
||||
Assert.AreSame(_chunk1, _actualChunk1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SecondReadShouldReturnChunk2()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk2);
|
||||
Assert.AreSame(_chunk2, _actualChunk2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ThirdReadShouldReturnChunk2CatchUp()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk2CatchUp);
|
||||
Assert.AreSame(_chunk2CatchUp, _actualChunk2CatchUp);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FourthReadShouldReturnChunk3()
|
||||
{
|
||||
Assert.IsNotNull(_actualChunk3);
|
||||
Assert.AreSame(_chunk3, _actualChunk3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAfterEndOfFileShouldThrowSshException()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = _reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual("Attempting to read beyond the end of the file.", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCloseHandleAndCompleteImmediately()
|
||||
{
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.IsOpen)
|
||||
.Returns(true);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.BeginClose(_handle, null, null))
|
||||
.Returns(_closeAsyncResult);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.EndClose(_closeAsyncResult));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
class SftpFileReaderTest_ReadAheadBeginReadException
|
||||
{
|
||||
}
|
||||
}
|
||||
-213
@@ -1,213 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Common;
|
||||
using Renci.SshNet.Sftp;
|
||||
|
||||
using BufferedRead = Renci.SshNet.Sftp.SftpFileReader.BufferedRead;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
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;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_handle = CreateByteArray(random, 5);
|
||||
_chunk1 = CreateByteArray(random, ChunkLength);
|
||||
_chunk3 = CreateByteArray(random, 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();
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk1, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
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, ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
ThreadAbstraction.ExecuteThread(() =>
|
||||
{
|
||||
// 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);
|
||||
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, 2 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
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()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 2, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
_reader.Read();
|
||||
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadOfSecondChunkShouldThrowExceptionThatOccurredInReadAhead()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.AreSame(_exception, _actualException);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReahAheadOfChunk3ShouldHaveStarted()
|
||||
{
|
||||
SftpSessionMock.Verify(p => p.BeginRead(_handle, 2 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAfterReadAheadExceptionShouldRethrowExceptionThatOccurredInReadAhead()
|
||||
{
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
Assert.AreSame(_exception, ex);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WaitAnyOFSftpSessionShouldHaveBeenInvokedFourTimes()
|
||||
{
|
||||
SftpSessionMock.Verify(p => p.WaitAny(_waitHandleArray, _operationTimeout), Times.Exactly(4));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCloseHandleAndCompleteImmediately()
|
||||
{
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.IsOpen).Returns(true);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.BeginClose(_handle, null, null)).Returns(_closeAsyncResult);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.EndClose(_closeAsyncResult));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
-189
@@ -1,189 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
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_ReadAheadEndInvokeException_PreventsFurtherReadAheads : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
private byte[] _chunk1;
|
||||
private SftpFileReader _reader;
|
||||
private ManualResetEvent _readAheadChunk2;
|
||||
private ManualResetEvent _readChunk2;
|
||||
private SshException _exception;
|
||||
private SshException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_handle = CreateByteArray(random, 5);
|
||||
_chunk1 = CreateByteArray(random, ChunkLength);
|
||||
_fileSize = 3 * _chunk1.Length;
|
||||
_waitHandleArray = new WaitHandle[2];
|
||||
_operationTimeout = random.Next(10000, 20000);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
|
||||
_readAheadChunk2 = new ManualResetEvent(false);
|
||||
_readChunk2 = new ManualResetEvent(false);
|
||||
|
||||
_exception = new SshException();
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk1, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = 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, ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
ThreadAbstraction.ExecuteThread(() =>
|
||||
{
|
||||
// signal that we're in the read-ahead for chunk2
|
||||
_ = _readAheadChunk2.Set();
|
||||
// wait for client to start reading this chunk
|
||||
_ = _readChunk2.WaitOne(TimeSpan.FromSeconds(5));
|
||||
// sleep a short time to make sure the client is in the blocking wait
|
||||
Thread.Sleep(500);
|
||||
// complete async read of chunk2 with exception
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_exception, false);
|
||||
});
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.OperationTimeout)
|
||||
.Returns(_operationTimeout);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
|
||||
.Returns(() => WaitAny(_waitHandleArray, _operationTimeout));
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
// use a max. read-ahead of 1 to allow us to verify that the next read-ahead is not done
|
||||
// when a read-ahead has failed
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 1, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
_ = _reader.Read();
|
||||
|
||||
// wait until SftpFileReader has starting reading ahead chunk 2
|
||||
Assert.IsTrue(_readAheadChunk2.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
// signal that we are about to read chunk 2
|
||||
_ = _readChunk2.Set();
|
||||
|
||||
try
|
||||
{
|
||||
_ = _reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadOfSecondChunkShouldThrowExceptionThatOccurredInReadAhead()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.AreSame(_exception, _actualException);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAfterReadAheadExceptionShouldRethrowExceptionThatOccurredInReadAhead()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = _reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
Assert.AreSame(_exception, ex);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCloseHandleAndCompleteImmediately()
|
||||
{
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.IsOpen)
|
||||
.Returns(true);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.BeginClose(_handle, null, null))
|
||||
.Returns(_closeAsyncResult);
|
||||
_ = SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.EndClose(_closeAsyncResult));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ExceptionInReadAheadShouldPreventFurtherReadAheads()
|
||||
{
|
||||
SftpSessionMock.Verify(p => p.BeginRead(_handle, 2 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()), Times.Never);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
class SftpFileReaderTest_ReadBackBeginReadException
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
class SftpFileReaderTest_ReadBackEndInvokeException
|
||||
{
|
||||
}
|
||||
}
|
||||
-172
@@ -1,172 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
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_Read_ReadAheadExceptionInBeginRead : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
private byte[] _chunk1;
|
||||
private byte[] _chunk2;
|
||||
private SftpFileReader _reader;
|
||||
private ManualResetEvent _readAheadChunk3;
|
||||
private ManualResetEvent _readChunk3;
|
||||
private SshException _exception;
|
||||
private SshException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_handle = CreateByteArray(random, 5);
|
||||
_chunk1 = CreateByteArray(random, ChunkLength);
|
||||
_chunk2 = CreateByteArray(random, ChunkLength);
|
||||
_fileSize = _chunk1.Length + _chunk2.Length + 1;
|
||||
_waitHandleArray = new WaitHandle[2];
|
||||
_operationTimeout = random.Next(10000, 20000);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
|
||||
_readAheadChunk3 = new ManualResetEvent(false);
|
||||
_readChunk3 = new ManualResetEvent(false);
|
||||
|
||||
_exception = new SshException();
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk1, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
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, ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk2, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
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, 2 * ChunkLength, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
_readAheadChunk3.Set();
|
||||
_readChunk3.WaitOne(TimeSpan.FromSeconds(5));
|
||||
// sleep a short time to make sure the client is in the blocking wait
|
||||
Thread.Sleep(500);
|
||||
})
|
||||
.Throws(_exception);
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 3, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
_reader.Read();
|
||||
_reader.Read();
|
||||
|
||||
// wait until we've the SftpFileReader has starting reading ahead chunk 3
|
||||
Assert.IsTrue(_readAheadChunk3.WaitOne(TimeSpan.FromSeconds(5)));
|
||||
// signal that we are about to read chunk 3
|
||||
_readChunk3.Set();
|
||||
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadOfThirdChunkShouldThrowExceptionThatOccurredInReadAhead()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.AreSame(_exception, _actualException);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadAfterReadAheadExceptionShouldRethrowExceptionThatOccurredInReadAhead()
|
||||
{
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
Assert.AreSame(_exception, ex);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCloseHandleAndCompleteImmediately()
|
||||
{
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.IsOpen).Returns(true);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.BeginClose(_handle, null, null)).Returns(_closeAsyncResult);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.EndClose(_closeAsyncResult));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
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_Read_ReadAheadExceptionInWaitOnHandle_ChunkAvailable : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
private byte[] _chunk1;
|
||||
private byte[] _chunk2;
|
||||
private SftpFileReader _reader;
|
||||
private SshException _exception;
|
||||
private ManualResetEvent _exceptionSignaled;
|
||||
private SshException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_handle = CreateByteArray(random, 5);
|
||||
_chunk1 = CreateByteArray(random, ChunkLength);
|
||||
_chunk2 = CreateByteArray(random, ChunkLength);
|
||||
_fileSize = _chunk1.Length + _chunk2.Length + 1;
|
||||
_waitHandleArray = new WaitHandle[2];
|
||||
_operationTimeout = random.Next(10000, 20000);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
|
||||
_exception = new SshException();
|
||||
_exceptionSignaled = new ManualResetEvent(false);
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
asyncResult.SetAsCompleted(_chunk1, false);
|
||||
})
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.OperationTimeout).Returns(_operationTimeout);
|
||||
SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
|
||||
.Callback(() => _exceptionSignaled.Set())
|
||||
.Throws(_exception);
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 2, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
// wait for the exception to be signaled by the second call to WaitAny
|
||||
_exceptionSignaled.WaitOne(5000);
|
||||
// allow a little time to allow SftpFileReader to process exception
|
||||
Thread.Sleep(100);
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadShouldHaveRethrownExceptionThrownByWaitAny()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.AreSame(_exception, _actualException);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadShouldRethrowExceptionThrownByWaitAny()
|
||||
{
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
Assert.AreSame(_exception, ex);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCloseHandleAndCompleteImmediately()
|
||||
{
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.IsOpen).Returns(true);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.BeginClose(_handle, null, null)).Returns(_closeAsyncResult);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.EndClose(_closeAsyncResult));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
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_Read_ReadAheadExceptionInWaitOnHandle_NoChunkAvailable : SftpFileReaderTestBase
|
||||
{
|
||||
private const int ChunkLength = 32 * 1024;
|
||||
|
||||
private MockSequence _seq;
|
||||
private byte[] _handle;
|
||||
private int _fileSize;
|
||||
private WaitHandle[] _waitHandleArray;
|
||||
private int _operationTimeout;
|
||||
private SftpCloseAsyncResult _closeAsyncResult;
|
||||
private SftpFileReader _reader;
|
||||
private SshException _exception;
|
||||
private SshException _actualException;
|
||||
|
||||
protected override void SetupData()
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
_handle = CreateByteArray(random, 5);
|
||||
_fileSize = 1234;
|
||||
_waitHandleArray = new WaitHandle[2];
|
||||
_operationTimeout = random.Next(10000, 20000);
|
||||
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
|
||||
|
||||
_exception = new SshException();
|
||||
}
|
||||
|
||||
protected override void SetupMocks()
|
||||
{
|
||||
_seq = new MockSequence();
|
||||
|
||||
SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.CreateWaitHandleArray(It.IsNotNull<WaitHandle>(), It.IsNotNull<WaitHandle>()))
|
||||
.Returns<WaitHandle, WaitHandle>((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<AsyncCallback>(), It.IsAny<BufferedRead>()))
|
||||
.Returns((SftpReadAsyncResult)null);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.OperationTimeout).Returns(_operationTimeout);
|
||||
SftpSessionMock.InSequence(_seq)
|
||||
.Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
|
||||
.Throws(_exception);
|
||||
}
|
||||
|
||||
protected override void Arrange()
|
||||
{
|
||||
base.Arrange();
|
||||
|
||||
_reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 1, _fileSize);
|
||||
}
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
_actualException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadShouldHaveRethrownExceptionThrownByWaitOnHandle()
|
||||
{
|
||||
Assert.IsNotNull(_actualException);
|
||||
Assert.AreSame(_exception, _actualException);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadShouldRethrowExceptionThrownByWaitOnHandle()
|
||||
{
|
||||
try
|
||||
{
|
||||
_reader.Read();
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
Assert.AreSame(_exception, ex);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DisposeShouldCloseHandleAndCompleteImmediately()
|
||||
{
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.IsOpen).Returns(true);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.BeginClose(_handle, null, null)).Returns(_closeAsyncResult);
|
||||
SftpSessionMock.InSequence(_seq).Setup(p => p.EndClose(_closeAsyncResult));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_reader.Dispose();
|
||||
stopwatch.Stop();
|
||||
|
||||
Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200, "Dispose took too long to complete: " + stopwatch.ElapsedMilliseconds);
|
||||
|
||||
SftpSessionMock.Verify(p => p.IsOpen, Times.Once);
|
||||
SftpSessionMock.Verify(p => p.BeginClose(_handle, null, null), Times.Once);
|
||||
SftpSessionMock.Verify(p => p.EndClose(_closeAsyncResult), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Renci.SshNet.Sftp;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
public abstract class SftpFileStreamAsyncTestBase
|
||||
{
|
||||
internal Mock<ISftpSession> SftpSessionMock;
|
||||
protected MockSequence MockSequence;
|
||||
|
||||
protected virtual Task ArrangeAsync()
|
||||
{
|
||||
SetupData();
|
||||
CreateMocks();
|
||||
SetupMocks();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual void SetupData()
|
||||
{
|
||||
MockSequence = new MockSequence();
|
||||
}
|
||||
|
||||
protected abstract void SetupMocks();
|
||||
|
||||
private void CreateMocks()
|
||||
{
|
||||
SftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public async Task SetUpAsync()
|
||||
{
|
||||
await ArrangeAsync();
|
||||
await ActAsync();
|
||||
}
|
||||
|
||||
protected abstract Task ActAsync();
|
||||
|
||||
protected byte[] GenerateRandom(int length)
|
||||
{
|
||||
return GenerateRandom(length, new Random());
|
||||
}
|
||||
|
||||
protected byte[] GenerateRandom(int length, Random random)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
random.NextBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
protected byte[] GenerateRandom(uint length)
|
||||
{
|
||||
return GenerateRandom(length, new Random());
|
||||
}
|
||||
|
||||
protected byte[] GenerateRandom(uint length, Random random)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
random.NextBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
@@ -85,7 +86,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
SftpFileStream.Open(new Mock<ISftpSession>().Object, "file.txt", mode, access, bufferSize: 1024));
|
||||
}
|
||||
|
||||
Assert.AreEqual("mode", ex.ParamName);
|
||||
Assert.AreEqual("access", ex.ParamName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -93,6 +94,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
var sessionMock = new Mock<ISftpSession>();
|
||||
|
||||
sessionMock.Setup(s => s.CalculateOptimalWriteLength(It.IsAny<uint>(), It.IsAny<byte[]>())).Returns<uint, byte[]>((x, _) => x);
|
||||
sessionMock.Setup(s => s.IsOpen).Returns(true);
|
||||
|
||||
SetupRemoteSize(sessionMock, 128);
|
||||
@@ -118,6 +120,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
var sessionMock = new Mock<ISftpSession>();
|
||||
|
||||
sessionMock.Setup(s => s.CalculateOptimalWriteLength(It.IsAny<uint>(), It.IsAny<byte[]>())).Returns<uint, byte[]>((x, _) => x);
|
||||
sessionMock.Setup(s => s.IsOpen).Returns(true);
|
||||
|
||||
var s = SftpFileStream.Open(sessionMock.Object, "file.txt", FileMode.Open, FileAccess.Read, bufferSize: 1024);
|
||||
@@ -135,7 +138,6 @@ namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
Assert.Throws<NotSupportedException>(() => s.SetLength(1024));
|
||||
}
|
||||
|
||||
[Ignore("TODO Currently throws EndOfStreamException in all cases.")]
|
||||
[TestMethod]
|
||||
[DataRow(-1, SeekOrigin.Begin)]
|
||||
[DataRow(-1, SeekOrigin.Current)]
|
||||
@@ -144,6 +146,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
var sessionMock = new Mock<ISftpSession>();
|
||||
|
||||
sessionMock.Setup(s => s.CalculateOptimalReadLength(It.IsAny<uint>())).Returns<uint>(x => x);
|
||||
sessionMock.Setup(s => s.CalculateOptimalWriteLength(It.IsAny<uint>(), It.IsAny<byte[]>())).Returns<uint, byte[]>((x, _) => x);
|
||||
sessionMock.Setup(s => s.IsOpen).Returns(true);
|
||||
|
||||
SetupRemoteSize(sessionMock, 128);
|
||||
@@ -155,7 +159,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
|
||||
private static void SetupRemoteSize(Mock<ISftpSession> sessionMock, long size)
|
||||
{
|
||||
sessionMock.Setup(s => s.RequestFStat(It.IsAny<byte[]>(), It.IsAny<bool>())).Returns(new SftpFileAttributes(
|
||||
sessionMock.Setup(s => s.RequestFStat(It.IsAny<byte[]>())).Returns(new SftpFileAttributes(
|
||||
default, default, size: size, default, default, default, default
|
||||
));
|
||||
}
|
||||
@@ -210,6 +214,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
var sessionMock = new Mock<ISftpSession>();
|
||||
|
||||
sessionMock.Setup(s => s.CalculateOptimalReadLength(It.IsAny<uint>())).Returns<uint>(x => x);
|
||||
sessionMock.Setup(s => s.CalculateOptimalWriteLength(It.IsAny<uint>(), It.IsAny<byte[]>())).Returns<uint, byte[]>((x, _) => x);
|
||||
sessionMock.Setup(s => s.IsOpen).Returns(true);
|
||||
SetupRemoteSize(sessionMock, 0);
|
||||
@@ -246,12 +251,12 @@ namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
{
|
||||
var sessionMock = new Mock<ISftpSession>();
|
||||
|
||||
sessionMock.Setup(s => s.CalculateOptimalWriteLength(It.IsAny<uint>(), It.IsAny<byte[]>())).Returns<uint, byte[]>((x, _) => x);
|
||||
sessionMock.Setup(s => s.IsOpen).Returns(true);
|
||||
|
||||
var s = SftpFileStream.Open(sessionMock.Object, "file.txt", FileMode.Create, FileAccess.ReadWrite, bufferSize: 1024);
|
||||
|
||||
Assert.IsTrue(s.CanRead);
|
||||
Assert.IsTrue(s.CanSeek);
|
||||
Assert.IsTrue(s.CanWrite);
|
||||
|
||||
s.Dispose();
|
||||
@@ -276,6 +281,46 @@ namespace Renci.SshNet.Tests.Classes.Sftp
|
||||
sessionMock.Verify(p => p.RequestClose(It.IsAny<byte[]>()), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FstatFailure_DisablesSeek()
|
||||
{
|
||||
TestFstatFailure(fstat => fstat.Throws<SftpPermissionDeniedException>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FstatSizeNotReturned_DisablesSeek()
|
||||
{
|
||||
TestFstatFailure(fstat => fstat.Returns(SftpFileAttributes.FromBytes([0, 0, 0, 0])));
|
||||
}
|
||||
|
||||
private void TestFstatFailure(Action<Moq.Language.Flow.ISetup<ISftpSession, SftpFileAttributes>> fstatSetup)
|
||||
{
|
||||
var sessionMock = new Mock<ISftpSession>();
|
||||
|
||||
sessionMock.Setup(s => s.CalculateOptimalReadLength(It.IsAny<uint>())).Returns<uint>(x => x);
|
||||
sessionMock.Setup(s => s.CalculateOptimalWriteLength(It.IsAny<uint>(), It.IsAny<byte[]>())).Returns<uint, byte[]>((x, _) => x);
|
||||
sessionMock.Setup(p => p.SessionLoggerFactory).Returns(NullLoggerFactory.Instance);
|
||||
sessionMock.Setup(s => s.IsOpen).Returns(true);
|
||||
|
||||
fstatSetup(sessionMock.Setup(s => s.RequestFStat(It.IsAny<byte[]>())));
|
||||
|
||||
var s = SftpFileStream.Open(sessionMock.Object, "file.txt", FileMode.Open, FileAccess.ReadWrite, bufferSize: 1024);
|
||||
|
||||
Assert.IsFalse(s.CanSeek);
|
||||
Assert.IsTrue(s.CanRead);
|
||||
Assert.IsTrue(s.CanWrite);
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => s.Position);
|
||||
Assert.Throws<NotSupportedException>(() => s.Length);
|
||||
Assert.Throws<NotSupportedException>(() => s.Seek(0, SeekOrigin.Begin));
|
||||
Assert.Throws<NotSupportedException>(() => s.SetLength(1024));
|
||||
|
||||
// Reads and writes still succeed.
|
||||
_ = s.Read(new byte[16], 0, 16);
|
||||
s.Write(new byte[16], 0, 16);
|
||||
s.Flush();
|
||||
}
|
||||
|
||||
private static void VerifyRequestWrite(Mock<ISftpSession> sessionMock, ReadOnlyMemory<byte> newData, int serverOffset)
|
||||
{
|
||||
sessionMock.Verify(s => s.RequestWrite(
|
||||
|
||||
Reference in New Issue
Block a user