diff --git a/src/Renci.SshNet/Common/Extensions.cs b/src/Renci.SshNet/Common/Extensions.cs
index ae390d604..80fa8323d 100644
--- a/src/Renci.SshNet/Common/Extensions.cs
+++ b/src/Renci.SshNet/Common/Extensions.cs
@@ -261,6 +261,44 @@ public static byte[] TrimLeadingZeros(this byte[] value)
return value;
}
+#if NETFRAMEWORK || NETSTANDARD2_0
+ public static int IndexOf(this byte[] array, byte[] value, int startIndex, int count)
+ {
+ if (value.Length > count)
+ {
+ return -1;
+ }
+
+ if (value.Length == 0)
+ {
+ return 0;
+ }
+
+ for (var i = startIndex; i < startIndex + count - value.Length + 1; i++)
+ {
+ if (MatchesAtIndex(i))
+ {
+ return i - startIndex;
+ }
+ }
+
+ return -1;
+
+ bool MatchesAtIndex(int i)
+ {
+ for (var j = 0; j < value.Length; j++)
+ {
+ if (array[i + j] != value[j])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+#endif
+
///
/// Pads with leading zeros if needed.
///
diff --git a/src/Renci.SshNet/Common/TaskToAsyncResult.cs b/src/Renci.SshNet/Common/TaskToAsyncResult.cs
new file mode 100644
index 000000000..947c9376c
--- /dev/null
+++ b/src/Renci.SshNet/Common/TaskToAsyncResult.cs
@@ -0,0 +1,178 @@
+#pragma warning disable
+#if !NET8_0_OR_GREATER
+// Copied verbatim from https://github.com/dotnet/runtime/blob/78bd7debe6d8b454294c673c9cb969c6b8a14692/src/libraries/Common/src/System/Threading/Tasks/TaskToAsyncResult.cs
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Threading.Tasks
+{
+ ///
+ /// Provides methods for using to implement the Asynchronous Programming Model
+ /// pattern based on "Begin" and "End" methods.
+ ///
+#if SYSTEM_PRIVATE_CORELIB
+ public
+#else
+ internal
+#endif
+ static class TaskToAsyncResult
+ {
+ /// Creates a new from the specified , optionally invoking when the task has completed.
+ /// The to be wrapped in an .
+ /// The callback to be invoked upon 's completion. If , no callback will be invoked.
+ /// The state to be stored in the .
+ /// An to represent the task's asynchronous operation. This instance will also be passed to when it's invoked.
+ /// is null.
+ ///
+ /// In conjunction with the or methods, this method may be used
+ /// to implement the Begin/End pattern (also known as the Asynchronous Programming Model pattern, or APM). It is recommended to not expose this pattern
+ /// in new code; the methods on are intended only to help implement such Begin/End methods when they must be exposed, for example
+ /// because a base class provides virtual methods for the pattern, or when they've already been exposed and must remain for compatibility. These methods enable
+ /// implementing all of the core asynchronous logic via s and then easily implementing Begin/End methods around that functionality.
+ ///
+ public static IAsyncResult Begin(Task task, AsyncCallback? callback, object? state)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(task);
+#else
+ if (task is null)
+ {
+ throw new ArgumentNullException(nameof(task));
+ }
+#endif
+
+ return new TaskAsyncResult(task, state, callback);
+ }
+
+ /// Waits for the wrapped by the returned by to complete.
+ /// The for which to wait.
+ /// is null.
+ /// was not produced by a call to .
+ /// This will propagate any exception stored in the wrapped .
+ public static void End(IAsyncResult asyncResult) =>
+ Unwrap(asyncResult).GetAwaiter().GetResult();
+
+ /// Waits for the wrapped by the returned by to complete.
+ /// The type of the result produced.
+ /// The for which to wait.
+ /// The result of the wrapped by the .
+ /// is null.
+ /// was not produced by a call to .
+ /// This will propagate any exception stored in the wrapped .
+ public static TResult End(IAsyncResult asyncResult) =>
+ Unwrap(asyncResult).GetAwaiter().GetResult();
+
+ /// Extracts the underlying from an created by .
+ /// The created by .
+ /// The wrapped by the .
+ /// is null.
+ /// was not produced by a call to .
+ public static Task Unwrap(IAsyncResult asyncResult)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(asyncResult);
+#else
+ if (asyncResult is null)
+ {
+ throw new ArgumentNullException(nameof(asyncResult));
+ }
+#endif
+
+ if ((asyncResult as TaskAsyncResult)?._task is not Task task)
+ {
+ throw new ArgumentException(null, nameof(asyncResult));
+ }
+
+ return task;
+ }
+
+ /// Extracts the underlying from an created by .
+ /// The type of the result produced by the returned task.
+ /// The created by .
+ /// The wrapped by the .
+ /// is null.
+ ///
+ /// was not produced by a call to ,
+ /// or the provided to was used a generic type parameter
+ /// that's different from the supplied to this call.
+ ///
+ public static Task Unwrap(IAsyncResult asyncResult)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(asyncResult);
+#else
+ if (asyncResult is null)
+ {
+ throw new ArgumentNullException(nameof(asyncResult));
+ }
+#endif
+
+ if ((asyncResult as TaskAsyncResult)?._task is not Task task)
+ {
+ throw new ArgumentException(null, nameof(asyncResult));
+ }
+
+ return task;
+ }
+
+ /// Provides a simple that wraps a .
+ ///
+ /// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state,
+ /// but that's very rare, in particular in a situation where someone cares about allocation, and always
+ /// using TaskAsyncResult simplifies things and enables additional optimizations.
+ ///
+ private sealed class TaskAsyncResult : IAsyncResult
+ {
+ /// The wrapped Task.
+ internal readonly Task _task;
+ /// Callback to invoke when the wrapped task completes.
+ private readonly AsyncCallback? _callback;
+
+ /// Initializes the IAsyncResult with the Task to wrap and the associated object state.
+ /// The Task to wrap.
+ /// The new AsyncState value.
+ /// Callback to invoke when the wrapped task completes.
+ internal TaskAsyncResult(Task task, object? state, AsyncCallback? callback)
+ {
+ Debug.Assert(task is not null);
+
+ _task = task;
+ AsyncState = state;
+
+ if (task.IsCompleted)
+ {
+ // The task has already completed. Treat this as synchronous completion.
+ // Invoke the callback; no need to store it.
+ CompletedSynchronously = true;
+ callback?.Invoke(this);
+ }
+ else if (callback is not null)
+ {
+ // Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in
+ // order to avoid running synchronously if the task has already completed by the time we get here but still run
+ // synchronously as part of the task's completion if the task completes after (the more common case).
+ _callback = callback;
+ _task.ConfigureAwait(continueOnCapturedContext: false)
+ .GetAwaiter()
+ .OnCompleted(() => _callback.Invoke(this));
+ }
+ }
+
+ ///
+ public object? AsyncState { get; }
+
+ ///
+ public bool CompletedSynchronously { get; }
+
+ ///
+ public bool IsCompleted => _task.IsCompleted;
+
+ ///
+ public WaitHandle AsyncWaitHandle => ((IAsyncResult) _task).AsyncWaitHandle;
+ }
+ }
+}
+#endif
diff --git a/src/Renci.SshNet/ExpectAsyncResult.cs b/src/Renci.SshNet/ExpectAsyncResult.cs
deleted file mode 100644
index f83d32f75..000000000
--- a/src/Renci.SshNet/ExpectAsyncResult.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet
-{
- ///
- /// Provides additional information for asynchronous command execution.
- ///
- public class ExpectAsyncResult : AsyncResult
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The async callback.
- /// The state.
- internal ExpectAsyncResult(AsyncCallback asyncCallback, object state)
- : base(asyncCallback, state)
- {
- }
- }
-}
diff --git a/src/Renci.SshNet/IServiceFactory.cs b/src/Renci.SshNet/IServiceFactory.cs
index c071e8c25..f19110d7f 100644
--- a/src/Renci.SshNet/IServiceFactory.cs
+++ b/src/Renci.SshNet/IServiceFactory.cs
@@ -114,7 +114,6 @@ internal partial interface IServiceFactory
/// The terminal height in pixels.
/// The terminal mode values.
/// Size of the buffer.
- /// Size of the expect buffer.
///
/// The created instance.
///
@@ -136,8 +135,7 @@ ShellStream CreateShellStream(ISession session,
uint width,
uint height,
IDictionary terminalModeValues,
- int bufferSize,
- int expectSize);
+ int bufferSize);
///
/// Creates an that encloses a path in double quotes, and escapes
diff --git a/src/Renci.SshNet/ServiceFactory.cs b/src/Renci.SshNet/ServiceFactory.cs
index 1ef0da1fa..0e279873a 100644
--- a/src/Renci.SshNet/ServiceFactory.cs
+++ b/src/Renci.SshNet/ServiceFactory.cs
@@ -187,7 +187,6 @@ public ISftpResponseFactory CreateSftpResponseFactory()
/// The terminal height in pixels.
/// The terminal mode values.
/// The size of the buffer.
- /// The size of the expect buffer.
///
/// The created instance.
///
@@ -202,9 +201,9 @@ public ISftpResponseFactory CreateSftpResponseFactory()
/// to the drawable area of the window.
///
///
- public ShellStream CreateShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModeValues, int bufferSize, int expectSize)
+ public ShellStream CreateShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModeValues, int bufferSize)
{
- return new ShellStream(session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize, expectSize);
+ return new ShellStream(session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize);
}
///
diff --git a/src/Renci.SshNet/ShellStream.cs b/src/Renci.SshNet/ShellStream.cs
index a737cc674..e8945bf9c 100644
--- a/src/Renci.SshNet/ShellStream.cs
+++ b/src/Renci.SshNet/ShellStream.cs
@@ -1,11 +1,13 @@
-using System;
+#nullable enable
+using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
+using System.Threading.Tasks;
-using Renci.SshNet.Abstractions;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
@@ -16,28 +18,28 @@ namespace Renci.SshNet
///
public class ShellStream : Stream
{
- private const string CrLf = "\r\n";
-
private readonly ISession _session;
private readonly Encoding _encoding;
- private readonly int _bufferSize;
- private readonly Queue _incoming;
- private readonly int _expectSize;
- private readonly Queue _expect;
- private readonly Queue _outgoing;
- private IChannelSession _channel;
- private AutoResetEvent _dataReceived = new AutoResetEvent(initialState: false);
- private bool _isDisposed;
+ private readonly IChannelSession _channel;
+ private readonly byte[] _carriageReturnBytes;
+ private readonly byte[] _lineFeedBytes;
+
+ private readonly object _sync = new object();
+
+ private byte[] _buffer;
+ private int _head; // The index from which the data starts in _buffer.
+ private int _tail; // The index at which to add new data into _buffer.
+ private bool _disposed;
///
/// Occurs when data was received.
///
- public event EventHandler DataReceived;
+ public event EventHandler? DataReceived;
///
/// Occurs when an error occurred.
///
- public event EventHandler ErrorOccurred;
+ public event EventHandler? ErrorOccurred;
///
/// Gets a value indicating whether data is available on the to be read.
@@ -49,23 +51,26 @@ public bool DataAvailable
{
get
{
- lock (_incoming)
+ lock (_sync)
{
- return _incoming.Count > 0;
+ AssertValid();
+ return _tail != _head;
}
}
}
- ///
- /// Gets the number of bytes that will be written to the internal buffer.
- ///
- ///
- /// The number of bytes that will be written to the internal buffer.
- ///
- internal int BufferSize
+#pragma warning disable MA0076 // Do not use implicit culture-sensitive ToString in interpolated strings
+ [Conditional("DEBUG")]
+ private void AssertValid()
{
- get { return _bufferSize; }
+ Debug.Assert(Monitor.IsEntered(_sync), $"Should be in lock on {nameof(_sync)}");
+ Debug.Assert(_head >= 0, $"{nameof(_head)} should be non-negative but is {_head}");
+ Debug.Assert(_tail >= 0, $"{nameof(_tail)} should be non-negative but is {_tail}");
+ Debug.Assert(_head < _buffer.Length || _buffer.Length == 0, $"{nameof(_head)} should be < {nameof(_buffer)}.Length but is {_head}");
+ Debug.Assert(_tail <= _buffer.Length, $"{nameof(_tail)} should be <= {nameof(_buffer)}.Length but is {_tail}");
+ Debug.Assert(_head <= _tail, $"Should have {nameof(_head)} <= {nameof(_tail)} but have {_head} <= {_tail}");
}
+#pragma warning restore MA0076 // Do not use implicit culture-sensitive ToString in interpolated strings
///
/// Initializes a new instance of the class.
@@ -78,29 +83,24 @@ internal int BufferSize
/// The terminal height in pixels.
/// The terminal mode values.
/// The size of the buffer.
- /// The size of the expect buffer.
/// The channel could not be opened.
/// The pseudo-terminal request was not accepted by the server.
/// The request to start a shell was not accepted by the server.
- internal ShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModeValues, int bufferSize, int expectSize)
+ internal ShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModeValues, int bufferSize)
{
+#if NET8_0_OR_GREATER
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize);
+#else
if (bufferSize <= 0)
{
- throw new ArgumentException($"{nameof(bufferSize)} must be between 1 and {int.MaxValue}.");
- }
-
- if (expectSize <= 0)
- {
- throw new ArgumentException($"{nameof(expectSize)} must be between 1 and {int.MaxValue}.");
+ throw new ArgumentOutOfRangeException(nameof(bufferSize));
}
+#endif
_encoding = session.ConnectionInfo.Encoding;
_session = session;
- _bufferSize = bufferSize;
- _incoming = new Queue();
- _expectSize = expectSize;
- _expect = new Queue(_expectSize);
- _outgoing = new Queue();
+ _carriageReturnBytes = _encoding.GetBytes("\r");
+ _lineFeedBytes = _encoding.GetBytes("\n");
_channel = _session.CreateChannelSession();
_channel.DataReceived += Channel_DataReceived;
@@ -108,6 +108,8 @@ internal ShellStream(ISession session, string terminalName, uint columns, uint r
_session.Disconnected += Session_Disconnected;
_session.ErrorOccured += Session_ErrorOccured;
+ _buffer = new byte[bufferSize];
+
try
{
_channel.Open();
@@ -124,8 +126,7 @@ internal ShellStream(ISession session, string terminalName, uint columns, uint r
}
catch
{
- UnsubscribeFromSessionEvents(session);
- _channel.Dispose();
+ Dispose();
throw;
}
}
@@ -134,8 +135,11 @@ internal ShellStream(ISession session, string terminalName, uint columns, uint r
/// Gets a value indicating whether the current stream supports reading.
///
///
- /// if the stream supports reading; otherwise, .
+ /// .
///
+ ///
+ /// It is safe to read from even after disposal.
+ ///
public override bool CanRead
{
get { return true; }
@@ -145,7 +149,7 @@ public override bool CanRead
/// Gets a value indicating whether the current stream supports seeking.
///
///
- /// if the stream supports seeking; otherwise, .
+ /// .
///
public override bool CanSeek
{
@@ -156,91 +160,75 @@ public override bool CanSeek
/// Gets a value indicating whether the current stream supports writing.
///
///
- /// if the stream supports writing; otherwise, .
+ /// if this stream has not been disposed and the underlying channel
+ /// is still open, otherwise .
///
+ ///
+ /// A value of does not necessarily mean a write will succeed. It is possible
+ /// that the channel is closed and/or the stream is disposed by another thread between a call to
+ /// and the call to write.
+ ///
public override bool CanWrite
{
- get { return true; }
+ get { return !_disposed; }
}
///
- /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
+ /// This method does nothing.
///
- /// An I/O error occurs.
- /// Methods were called after the stream was closed.
public override void Flush()
{
-#if NET7_0_OR_GREATER
- ObjectDisposedException.ThrowIf(_channel is null, this);
-#else
- if (_channel is null)
- {
- throw new ObjectDisposedException(GetType().FullName);
- }
-#endif // NET7_0_OR_GREATER
-
- if (_outgoing.Count > 0)
- {
- _channel.SendData(_outgoing.ToArray());
- _outgoing.Clear();
- }
}
///
- /// Gets the length in bytes of the stream.
+ /// Gets the number of bytes currently available for reading.
///
/// A long value representing the length of the stream in bytes.
- /// A class derived from Stream does not support seeking.
- /// Methods were called after the stream was closed.
public override long Length
{
get
{
- lock (_incoming)
+ lock (_sync)
{
- return _incoming.Count;
+ AssertValid();
+ return _tail - _head;
}
}
}
///
- /// Gets or sets the position within the current stream.
+ /// This property always returns 0, and throws
+ /// when calling the setter.
///
///
- /// The current position within the stream.
+ /// 0.
///
- /// An I/O error occurs.
- /// The stream does not support seeking.
- /// Methods were called after the stream was closed.
+ /// The setter is called.
+#pragma warning disable SA1623 // The property's documentation should begin with 'Gets or sets'
public override long Position
+#pragma warning restore SA1623 // The property's documentation should begin with 'Gets or sets'
{
get { return 0; }
set { throw new NotSupportedException(); }
}
///
- /// This method is not supported.
+ /// This method always throws .
///
/// A byte offset relative to the parameter.
/// A value of type indicating the reference point used to obtain the new position.
- ///
- /// The new position within the current stream.
- ///
- /// An I/O error occurs.
- /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output.
- /// Methods were called after the stream was closed.
+ /// Never.
+ /// Always.
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
///
- /// This method is not supported.
+ /// This method always throws .
///
/// The desired length of the current stream in bytes.
- /// An I/O error occurs.
- /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.
- /// Methods were called after the stream was closed.
+ /// Always.
public override void SetLength(long value)
{
throw new NotSupportedException();
@@ -252,68 +240,49 @@ public override void SetLength(long value)
/// The expected expressions and actions to perform.
public void Expect(params ExpectAction[] expectActions)
{
- Expect(TimeSpan.Zero, expectActions);
+ Expect(Timeout.InfiniteTimeSpan, expectActions);
}
///
/// Expects the specified expression and performs action when one is found.
///
- /// Time to wait for input.
+ /// Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).
/// The expected expressions and actions to perform, if the specified time elapsed and expected condition have not met, that method will exit without executing any action.
+ ///
+ /// If a TimeSpan representing -1 millisecond is specified for the parameter,
+ /// this method blocks indefinitely until either the regex matches the data in the buffer, or the stream
+ /// is closed (via disposal or via the underlying channel closing).
+ ///
public void Expect(TimeSpan timeout, params ExpectAction[] expectActions)
{
- var expectedFound = false;
- var matchText = string.Empty;
-
- do
- {
- lock (_incoming)
- {
- if (_expect.Count > 0)
- {
- matchText = _encoding.GetString(_expect.ToArray(), 0, _expect.Count);
- }
-
- if (matchText.Length > 0)
- {
- foreach (var expectAction in expectActions)
- {
- var match = expectAction.Expect.Match(matchText);
-
- if (match.Success)
- {
-#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
- var returnLength = _encoding.GetByteCount(matchText.AsSpan(0, match.Index + match.Length));
-#else
- var returnLength = _encoding.GetByteCount(matchText.Substring(0, match.Index + match.Length));
-#endif
-
- // Remove processed items from the queue
- var returnText = SyncQueuesAndReturn(returnLength);
-
- expectAction.Action(returnText);
- expectedFound = true;
- }
- }
- }
- }
+ _ = ExpectRegex(timeout, lookback: -1, expectActions);
+ }
- if (!expectedFound)
- {
- if (timeout.Ticks > 0)
- {
- if (!_dataReceived.WaitOne(timeout))
- {
- return;
- }
- }
- else
- {
- _ = _dataReceived.WaitOne();
- }
- }
- }
- while (!expectedFound);
+ ///
+ /// Expects the specified expression and performs action when one is found.
+ ///
+ /// Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).
+ /// The amount of data to search through from the most recent data in the buffer, or -1 to always search the entire buffer.
+ /// The expected expressions and actions to perform, if the specified time elapsed and expected condition have not met, that method will exit without executing any action.
+ ///
+ ///
+ /// If a TimeSpan representing -1 millisecond is specified for the parameter,
+ /// this method blocks indefinitely until either the regex matches the data in the buffer, or the stream
+ /// is closed (via disposal or via the underlying channel closing).
+ ///
+ ///
+ /// Use the parameter to constrain the search space to a fixed-size rolling window at the end of the buffer.
+ /// This can reduce the amount of work done in cases where lots of output from the shell is expected to be received before the matching expression is found.
+ ///
+ ///
+ /// Note: in situations with high volumes of data and a small value for , some data may not be searched through.
+ /// It is recommended to set to a large enough value to be able to search all data as it comes in,
+ /// but which still places a limit on the amount of work needed.
+ ///
+ ///
+ public void Expect(TimeSpan timeout, int lookback, params ExpectAction[] expectActions)
+ {
+ _ = ExpectRegex(timeout, lookback, expectActions);
}
///
@@ -321,24 +290,84 @@ public void Expect(TimeSpan timeout, params ExpectAction[] expectActions)
///
/// The text to expect.
///
- /// Text available in the shell that ends with expected text.
+ /// The text available in the shell up to and including the expected text,
+ /// or if the the stream is closed without a match.
///
- public string Expect(string text)
+ public string? Expect(string text)
{
- return Expect(new Regex(Regex.Escape(text)), Session.InfiniteTimeSpan);
+ return Expect(text, Timeout.InfiniteTimeSpan);
}
///
/// Expects the expression specified by text.
///
/// The text to expect.
- /// Time to wait for input.
+ /// Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).
+ /// The amount of data to search through from the most recent data in the buffer, or -1 to always search the entire buffer.
///
- /// The text available in the shell that ends with expected text, or if the specified time has elapsed.
+ /// The text available in the shell up to and including the expected expression,
+ /// or if the specified time has elapsed or the stream is closed
+ /// without a match.
///
- public string Expect(string text, TimeSpan timeout)
+ ///
+ public string? Expect(string text, TimeSpan timeout, int lookback = -1)
{
- return Expect(new Regex(Regex.Escape(text)), timeout);
+ ValidateTimeout(timeout);
+ ValidateLookback(lookback);
+
+ var timeoutTime = DateTime.Now.Add(timeout);
+
+ var expectBytes = _encoding.GetBytes(text);
+
+ lock (_sync)
+ {
+ while (true)
+ {
+ AssertValid();
+
+ var searchHead = lookback == -1
+ ? _head
+ : Math.Max(_tail - lookback, _head);
+
+ Debug.Assert(_head <= searchHead && searchHead <= _tail);
+
+#if NETFRAMEWORK || NETSTANDARD2_0
+ var indexOfMatch = _buffer.IndexOf(expectBytes, searchHead, _tail - searchHead);
+#else
+ var indexOfMatch = _buffer.AsSpan(searchHead, _tail - searchHead).IndexOf(expectBytes);
+#endif
+
+ if (indexOfMatch >= 0)
+ {
+ var returnText = _encoding.GetString(_buffer, _head, searchHead - _head + indexOfMatch + expectBytes.Length);
+
+ _head = searchHead + indexOfMatch + expectBytes.Length;
+
+ AssertValid();
+
+ return returnText;
+ }
+
+ if (_disposed)
+ {
+ return null;
+ }
+
+ if (timeout == Timeout.InfiniteTimeSpan)
+ {
+ Monitor.Wait(_sync);
+ }
+ else
+ {
+ var waitTimeout = timeoutTime - DateTime.Now;
+
+ if (waitTimeout < TimeSpan.Zero || !Monitor.Wait(_sync, waitTimeout))
+ {
+ return null;
+ }
+ }
+ }
+ }
}
///
@@ -346,67 +375,99 @@ public string Expect(string text, TimeSpan timeout)
///
/// The regular expression to expect.
///
- /// The text available in the shell that contains all the text that ends with expected expression.
+ /// The text available in the shell up to and including the expected expression,
+ /// or if the stream is closed without a match.
///
- public string Expect(Regex regex)
+ public string? Expect(Regex regex)
{
- return Expect(regex, TimeSpan.Zero);
+ return Expect(regex, Timeout.InfiniteTimeSpan);
}
///
/// Expects the expression specified by regular expression.
///
/// The regular expression to expect.
- /// Time to wait for input.
+ /// Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).
+ /// The amount of data to search through from the most recent data in the buffer, or -1 to always search the entire buffer.
///
- /// The text available in the shell that contains all the text that ends with expected expression,
- /// or if the specified time has elapsed.
+ /// The text available in the shell up to and including the expected expression,
+ /// or if the specified timeout has elapsed or the stream
+ /// is closed without a match.
///
- public string Expect(Regex regex, TimeSpan timeout)
+ ///
+ ///
+ ///
+ public string? Expect(Regex regex, TimeSpan timeout, int lookback = -1)
+ {
+ return ExpectRegex(timeout, lookback, [new ExpectAction(regex, s => { })]);
+ }
+
+ private string? ExpectRegex(TimeSpan timeout, int lookback, ExpectAction[] expectActions)
{
- var matchText = string.Empty;
- string returnText;
+ ValidateTimeout(timeout);
+ ValidateLookback(lookback);
+
+ var timeoutTime = DateTime.Now.Add(timeout);
- while (true)
+ lock (_sync)
{
- lock (_incoming)
+ while (true)
{
- if (_expect.Count > 0)
- {
- matchText = _encoding.GetString(_expect.ToArray(), 0, _expect.Count);
- }
+ AssertValid();
+
+ var bufferText = _encoding.GetString(_buffer, _head, _tail - _head);
- var match = regex.Match(matchText);
+ var searchStart = lookback == -1
+ ? 0
+ : Math.Max(bufferText.Length - lookback, 0);
- if (match.Success)
+ foreach (var expectAction in expectActions)
{
-#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
- var returnLength = _encoding.GetByteCount(matchText.AsSpan(0, match.Index + match.Length));
+#if NET7_0_OR_GREATER
+ var matchEnumerator = expectAction.Expect.EnumerateMatches(bufferText.AsSpan(searchStart));
+
+ if (matchEnumerator.MoveNext())
+ {
+ var match = matchEnumerator.Current;
+
+ var returnText = bufferText.Substring(0, searchStart + match.Index + match.Length);
#else
- var returnLength = _encoding.GetByteCount(matchText.Substring(0, match.Index + match.Length));
+ var match = expectAction.Expect.Match(bufferText, searchStart);
+
+ if (match.Success)
+ {
+ var returnText = bufferText.Substring(0, match.Index + match.Length);
#endif
+ _head += _encoding.GetByteCount(returnText);
+
+ AssertValid();
- // Remove processed items from the queue
- returnText = SyncQueuesAndReturn(returnLength);
+ expectAction.Action(returnText);
- break;
+ return returnText;
+ }
}
- }
- if (timeout.Ticks > 0)
- {
- if (!_dataReceived.WaitOne(timeout))
+ if (_disposed)
{
return null;
}
- }
- else
- {
- _ = _dataReceived.WaitOne();
+
+ if (timeout == Timeout.InfiniteTimeSpan)
+ {
+ Monitor.Wait(_sync);
+ }
+ else
+ {
+ var waitTimeout = timeoutTime - DateTime.Now;
+
+ if (waitTimeout < TimeSpan.Zero || !Monitor.Wait(_sync, waitTimeout))
+ {
+ return null;
+ }
+ }
}
}
-
- return returnText;
}
///
@@ -418,7 +479,7 @@ public string Expect(Regex regex, TimeSpan timeout)
///
public IAsyncResult BeginExpect(params ExpectAction[] expectActions)
{
- return BeginExpect(TimeSpan.Zero, callback: null, state: null, expectActions);
+ return BeginExpect(Timeout.InfiniteTimeSpan, callback: null, state: null, expectActions);
}
///
@@ -429,9 +490,9 @@ public IAsyncResult BeginExpect(params ExpectAction[] expectActions)
///
/// An that references the asynchronous operation.
///
- public IAsyncResult BeginExpect(AsyncCallback callback, params ExpectAction[] expectActions)
+ public IAsyncResult BeginExpect(AsyncCallback? callback, params ExpectAction[] expectActions)
{
- return BeginExpect(TimeSpan.Zero, callback, state: null, expectActions);
+ return BeginExpect(Timeout.InfiniteTimeSpan, callback, state: null, expectActions);
}
///
@@ -443,100 +504,40 @@ public IAsyncResult BeginExpect(AsyncCallback callback, params ExpectAction[] ex
///
/// An that references the asynchronous operation.
///
- public IAsyncResult BeginExpect(AsyncCallback callback, object state, params ExpectAction[] expectActions)
+ public IAsyncResult BeginExpect(AsyncCallback? callback, object? state, params ExpectAction[] expectActions)
{
- return BeginExpect(TimeSpan.Zero, callback, state, expectActions);
+ return BeginExpect(Timeout.InfiniteTimeSpan, callback, state, expectActions);
}
///
/// Begins the expect.
///
- /// The timeout.
+ /// The timeout. Must non-negative or equal to -1 millisecond (for infinite timeout).
/// The callback.
/// The state.
/// The expect actions.
///
/// An that references the asynchronous operation.
///
-#pragma warning disable CA1859 // Use concrete types when possible for improved performance
- public IAsyncResult BeginExpect(TimeSpan timeout, AsyncCallback callback, object state, params ExpectAction[] expectActions)
-#pragma warning restore CA1859 // Use concrete types when possible for improved performance
+ public IAsyncResult BeginExpect(TimeSpan timeout, AsyncCallback? callback, object? state, params ExpectAction[] expectActions)
{
- var matchText = string.Empty;
- string returnText;
-
- // Create new AsyncResult object
- var asyncResult = new ExpectAsyncResult(callback, state);
-
- // Execute callback on different thread
- ThreadAbstraction.ExecuteThread(() =>
- {
- string expectActionResult = null;
- try
- {
- do
- {
- lock (_incoming)
- {
- if (_expect.Count > 0)
- {
- matchText = _encoding.GetString(_expect.ToArray(), 0, _expect.Count);
- }
-
- if (matchText.Length > 0)
- {
- foreach (var expectAction in expectActions)
- {
- var match = expectAction.Expect.Match(matchText);
-
- if (match.Success)
- {
-#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
- var returnLength = _encoding.GetByteCount(matchText.AsSpan(0, match.Index + match.Length));
-#else
- var returnLength = _encoding.GetByteCount(matchText.Substring(0, match.Index + match.Length));
-#endif
-
- // Remove processed items from the queue
- returnText = SyncQueuesAndReturn(returnLength);
-
- expectAction.Action(returnText);
- callback?.Invoke(asyncResult);
- expectActionResult = returnText;
- }
- }
- }
- }
-
- if (expectActionResult != null)
- {
- break;
- }
-
- if (timeout.Ticks > 0)
- {
- if (!_dataReceived.WaitOne(timeout))
- {
- callback?.Invoke(asyncResult);
- break;
- }
- }
- else
- {
- _ = _dataReceived.WaitOne();
- }
- }
- while (true);
-
- asyncResult.SetAsCompleted(expectActionResult, completedSynchronously: true);
- }
- catch (Exception exp)
- {
- asyncResult.SetAsCompleted(exp, completedSynchronously: true);
- }
- });
+ return BeginExpect(timeout, lookback: -1, callback, state, expectActions);
+ }
- return asyncResult;
+ ///
+ /// Begins the expect.
+ ///
+ /// The timeout. Must non-negative or equal to -1 millisecond (for infinite timeout).
+ /// The amount of data to search through from the most recent data in the buffer, or -1 to always search the entire buffer.
+ /// The callback.
+ /// The state.
+ /// The expect actions.
+ ///
+ /// An that references the asynchronous operation.
+ ///
+ public IAsyncResult BeginExpect(TimeSpan timeout, int lookback, AsyncCallback? callback, object? state, params ExpectAction[] expectActions)
+ {
+ return TaskToAsyncResult.Begin(Task.Run(() => ExpectRegex(timeout, lookback, expectActions)), callback, state);
}
///
@@ -544,136 +545,222 @@ public IAsyncResult BeginExpect(TimeSpan timeout, AsyncCallback callback, object
///
/// The async result.
///
- /// Text available in the shell that ends with expected text.
+ /// The text available in the shell up to and including the expected expression.
///
- /// Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult.
- public string EndExpect(IAsyncResult asyncResult)
+ public string? EndExpect(IAsyncResult asyncResult)
{
- if (asyncResult is not ExpectAsyncResult ar || ar.EndInvokeCalled)
- {
- throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult.");
- }
-
- // Wait for operation to complete, then return result or throw exception
- return ar.EndInvoke();
+ return TaskToAsyncResult.End(asyncResult);
}
///
- /// Reads the line from the shell. If line is not available it will block the execution and will wait for new line.
+ /// Reads the next line from the shell. If a line is not available it will block and wait for a new line.
///
///
/// The line read from the shell.
///
- public string ReadLine()
+ ///
+ ///
+ /// This method blocks indefinitely until either a line is available in the buffer, or the stream is closed
+ /// (via disposal or via the underlying channel closing).
+ ///
+ ///
+ /// When the stream is closed and there are no more newlines in the buffer, this method returns the remaining data
+ /// (if any) and then indicating that no more data is in the buffer.
+ ///
+ ///
+ public string? ReadLine()
{
- return ReadLine(TimeSpan.Zero);
+ return ReadLine(Timeout.InfiniteTimeSpan);
}
///
/// Reads a line from the shell. If line is not available it will block the execution and will wait for new line.
///
- /// Time to wait for input.
+ /// Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).
///
/// The line read from the shell, or when no input is received for the specified timeout.
///
- public string ReadLine(TimeSpan timeout)
+ ///
+ ///
+ /// If a TimeSpan representing -1 millisecond is specified for the parameter, this method
+ /// blocks indefinitely until either a line is available in the buffer, or the stream is closed (via disposal or via
+ /// the underlying channel closing).
+ ///
+ ///
+ /// When the stream is closed and there are no more newlines in the buffer, this method returns the remaining data
+ /// (if any) and then indicating that no more data is in the buffer.
+ ///
+ ///
+ public string? ReadLine(TimeSpan timeout)
{
- var text = string.Empty;
+ ValidateTimeout(timeout);
+
+ var timeoutTime = DateTime.Now.Add(timeout);
- while (true)
+ lock (_sync)
{
- lock (_incoming)
+ while (true)
{
- if (_incoming.Count > 0)
+ AssertValid();
+
+#if NETFRAMEWORK || NETSTANDARD2_0
+ var indexOfCr = _buffer.IndexOf(_carriageReturnBytes, _head, _tail - _head);
+#else
+ var indexOfCr = _buffer.AsSpan(_head, _tail - _head).IndexOf(_carriageReturnBytes);
+#endif
+ if (indexOfCr >= 0)
{
- text = _encoding.GetString(_incoming.ToArray(), 0, _incoming.Count);
- }
+ // We have found \r. We only need to search for \n up to and just after the \r
+ // (in order to consume \r\n if we can).
+#if NETFRAMEWORK || NETSTANDARD2_0
+ var indexOfLf = indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length <= _tail - _head
+ ? _buffer.IndexOf(_lineFeedBytes, _head, indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length)
+ : _buffer.IndexOf(_lineFeedBytes, _head, indexOfCr);
+#else
+ var indexOfLf = indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length <= _tail - _head
+ ? _buffer.AsSpan(_head, indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length).IndexOf(_lineFeedBytes)
+ : _buffer.AsSpan(_head, indexOfCr).IndexOf(_lineFeedBytes);
+#endif
+ if (indexOfLf >= 0 && indexOfLf < indexOfCr)
+ {
+ // If there is \n before the \r, then return up to the \n
+ var returnText = _encoding.GetString(_buffer, _head, indexOfLf);
+
+ _head += indexOfLf + _lineFeedBytes.Length;
- var index = text.IndexOf(CrLf, StringComparison.Ordinal);
+ AssertValid();
- if (index >= 0)
+ return returnText;
+ }
+ else if (indexOfLf == indexOfCr + _carriageReturnBytes.Length)
+ {
+ // If we have \r\n, then consume both
+ var returnText = _encoding.GetString(_buffer, _head, indexOfCr);
+
+ _head += indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length;
+
+ AssertValid();
+
+ return returnText;
+ }
+ else
+ {
+ // Return up to the \r
+ var returnText = _encoding.GetString(_buffer, _head, indexOfCr);
+
+ _head += indexOfCr + _carriageReturnBytes.Length;
+
+ AssertValid();
+
+ return returnText;
+ }
+ }
+ else
{
- text = text.Substring(0, index);
+ // There is no \r. What about \n?
+#if NETFRAMEWORK || NETSTANDARD2_0
+ var indexOfLf = _buffer.IndexOf(_lineFeedBytes, _head, _tail - _head);
+#else
+ var indexOfLf = _buffer.AsSpan(_head, _tail - _head).IndexOf(_lineFeedBytes);
+#endif
+ if (indexOfLf >= 0)
+ {
+ var returnText = _encoding.GetString(_buffer, _head, indexOfLf);
- // determine how many bytes to remove from buffer
- var bytesProcessed = _encoding.GetByteCount(text + CrLf);
+ _head += indexOfLf + _lineFeedBytes.Length;
- // remove processed bytes from the queue
- SyncQueuesAndDequeue(bytesProcessed);
+ AssertValid();
- break;
+ return returnText;
+ }
}
- }
- if (timeout.Ticks > 0)
- {
- if (!_dataReceived.WaitOne(timeout))
+ if (_disposed)
{
- return null;
+ var lastLine = _head == _tail
+ ? null
+ : _encoding.GetString(_buffer, _head, _tail - _head);
+
+ _head = _tail = 0;
+
+ return lastLine;
+ }
+
+ if (timeout == Timeout.InfiniteTimeSpan)
+ {
+ _ = Monitor.Wait(_sync);
+ }
+ else
+ {
+ var waitTimeout = timeoutTime - DateTime.Now;
+
+ if (waitTimeout < TimeSpan.Zero || !Monitor.Wait(_sync, waitTimeout))
+ {
+ return null;
+ }
}
}
- else
- {
- _ = _dataReceived.WaitOne();
- }
}
+ }
+
+ private static void ValidateTimeout(TimeSpan timeout)
+ {
+ if (timeout < TimeSpan.Zero && timeout != Timeout.InfiniteTimeSpan)
+ {
+ throw new ArgumentOutOfRangeException(nameof(timeout), "Value must be non-negative or equal to -1 millisecond (for infinite timeout)");
+ }
+ }
- return text;
+ private static void ValidateLookback(int lookback)
+ {
+ if (lookback is <= 0 and not -1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(lookback), "Value must be positive or equal to -1 (for no window)");
+ }
}
///
- /// Reads text available in the shell.
+ /// Reads all of the text currently available in the shell.
///
///
/// The text available in the shell.
///
public string Read()
{
- string text;
-
- lock (_incoming)
+ lock (_sync)
{
- text = _encoding.GetString(_incoming.ToArray(), 0, _incoming.Count);
- _expect.Clear();
- _incoming.Clear();
- }
+ AssertValid();
+
+ var text = _encoding.GetString(_buffer, _head, _tail - _head);
+
+ _head = _tail = 0;
- return text;
+ return text;
+ }
}
- ///
- /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
- ///
- /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source.
- /// The zero-based byte offset in at which to begin storing the data read from the current stream.
- /// The maximum number of bytes to be read from the current stream.
- ///
- /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
- ///
- /// The sum of and is larger than the buffer length.
- /// is .
- /// or is negative.
- /// An I/O error occurs.
- /// The stream does not support reading.
- /// Methods were called after the stream was closed.
+ ///
public override int Read(byte[] buffer, int offset, int count)
{
- var i = 0;
-
- lock (_incoming)
+ lock (_sync)
{
- for (; i < count && _incoming.Count > 0; i++)
+ while (_head == _tail && !_disposed)
{
- if (_incoming.Count == _expect.Count)
- {
- _ = _expect.Dequeue();
- }
-
- buffer[offset + i] = _incoming.Dequeue();
+ _ = Monitor.Wait(_sync);
}
- }
- return i;
+ AssertValid();
+
+ var bytesRead = Math.Min(count, _tail - _head);
+
+ Buffer.BlockCopy(_buffer, _head, buffer, offset, bytesRead);
+
+ _head += bytesRead;
+
+ AssertValid();
+
+ return bytesRead;
+ }
}
///
@@ -681,51 +768,50 @@ public override int Read(byte[] buffer, int offset, int count)
///
/// The text to be written to the shell.
///
+ ///
/// If is , nothing is written.
+ ///
+ ///
+ /// Data is not buffered before being written to the shell. If you have text to send in many pieces,
+ /// consider wrapping this stream in a .
+ ///
///
- public void Write(string text)
+ /// The stream is closed.
+ public void Write(string? text)
{
if (text is null)
{
return;
}
-#if NET7_0_OR_GREATER
- ObjectDisposedException.ThrowIf(_channel is null, this);
-#else
- if (_channel is null)
- {
- throw new ObjectDisposedException(GetType().FullName);
- }
-#endif // NET7_0_OR_GREATER
-
var data = _encoding.GetBytes(text);
- _channel.SendData(data);
+
+ Write(data, 0, data.Length);
}
///
- /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
+ /// Writes a sequence of bytes to the shell.
///
- /// An array of bytes. This method copies bytes from to the current stream.
- /// The zero-based byte offset in at which to begin copying bytes to the current stream.
- /// The number of bytes to be written to the current stream.
- /// The sum of and is greater than the buffer length.
- /// is .
- /// or is negative.
- /// An I/O error occurs.
- /// The stream does not support writing.
- /// Methods were called after the stream was closed.
+ /// An array of bytes. This method sends bytes from buffer to the shell.
+ /// The zero-based byte offset in at which to begin sending bytes to the shell.
+ /// The number of bytes to be sent to the shell.
+ ///
+ /// Data is not buffered before being written to the shell. If you have data to send in many pieces,
+ /// consider wrapping this stream in a .
+ ///
+ /// The stream is closed.
public override void Write(byte[] buffer, int offset, int count)
{
- foreach (var b in buffer.Take(offset, count))
+#if NET7_0_OR_GREATER
+ ObjectDisposedException.ThrowIf(_disposed, this);
+#else
+ if (_disposed)
{
- if (_outgoing.Count == _bufferSize)
- {
- Flush();
- }
-
- _outgoing.Enqueue(b);
+ throw new ObjectDisposedException(GetType().FullName);
}
+#endif // NET7_0_OR_GREATER
+
+ _channel.SendData(buffer, offset, count);
}
///
@@ -735,148 +821,108 @@ public override void Write(byte[] buffer, int offset, int count)
///
/// If is , only the line terminator is written.
///
+ /// The stream is closed.
public void WriteLine(string line)
{
Write(line + "\r");
}
- ///
- /// Releases the unmanaged resources used by the and optionally releases the managed resources.
- ///
- /// to release both managed and unmanaged resources; to release only unmanaged resources.
+ ///
protected override void Dispose(bool disposing)
{
- base.Dispose(disposing);
-
- if (_isDisposed)
+ if (!disposing)
{
+ base.Dispose(disposing);
return;
}
- if (disposing)
+ lock (_sync)
{
- UnsubscribeFromSessionEvents(_session);
-
- if (_channel != null)
+ if (_disposed)
{
- _channel.DataReceived -= Channel_DataReceived;
- _channel.Closed -= Channel_Closed;
- _channel.Dispose();
- _channel = null;
+ return;
}
- if (_dataReceived != null)
- {
- _dataReceived.Dispose();
- _dataReceived = null;
- }
+ _disposed = true;
- _isDisposed = true;
- }
- else
- {
- UnsubscribeFromSessionEvents(_session);
- }
- }
+ // Do not dispose _session (we don't own it)
+ _session.Disconnected -= Session_Disconnected;
+ _session.ErrorOccured -= Session_ErrorOccured;
- ///
- /// Unsubscribes the current from session events.
- ///
- /// The session.
- ///
- /// Does nothing when is .
- ///
- private void UnsubscribeFromSessionEvents(ISession session)
- {
- if (session is null)
- {
- return;
+ // But we do own _channel
+ _channel.DataReceived -= Channel_DataReceived;
+ _channel.Closed -= Channel_Closed;
+ _channel.Dispose();
+
+ Monitor.PulseAll(_sync);
}
- session.Disconnected -= Session_Disconnected;
- session.ErrorOccured -= Session_ErrorOccured;
+ base.Dispose(disposing);
}
- private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
+ private void Session_ErrorOccured(object? sender, ExceptionEventArgs e)
{
- OnRaiseError(e);
+ ErrorOccurred?.Invoke(this, e);
}
- private void Session_Disconnected(object sender, EventArgs e)
+ private void Session_Disconnected(object? sender, EventArgs e)
{
- _channel?.Dispose();
+ Dispose();
}
- private void Channel_Closed(object sender, ChannelEventArgs e)
+ private void Channel_Closed(object? sender, ChannelEventArgs e)
{
- // TODO: Do we need to call dispose here ??
Dispose();
}
- private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
+ private void Channel_DataReceived(object? sender, ChannelDataEventArgs e)
{
- lock (_incoming)
+ lock (_sync)
{
- foreach (var b in e.Data)
- {
- _incoming.Enqueue(b);
- if (_expect.Count == _expectSize)
- {
- _ = _expect.Dequeue();
- }
+ AssertValid();
- _expect.Enqueue(b);
- }
- }
+ // Ensure sufficient buffer space and copy the new data in.
- if (_dataReceived != null)
- {
- _ = _dataReceived.Set();
- }
+ if (_buffer.Length - _tail >= e.Data.Length)
+ {
+ // If there is enough space after _tail for the new data,
+ // then copy the data there.
+ Buffer.BlockCopy(e.Data, 0, _buffer, _tail, e.Data.Length);
+ _tail += e.Data.Length;
+ }
+ else
+ {
+ // We can't fit the new data after _tail.
- OnDataReceived(e.Data);
- }
+ var newLength = _tail - _head + e.Data.Length;
- private void OnRaiseError(ExceptionEventArgs e)
- {
- ErrorOccurred?.Invoke(this, e);
- }
+ if (newLength <= _buffer.Length)
+ {
+ // If there is sufficient space at the start of the buffer,
+ // then move the current data to the start of the buffer.
+ Buffer.BlockCopy(_buffer, _head, _buffer, 0, _tail - _head);
+ }
+ else
+ {
+ // Otherwise, we're gonna need a bigger buffer.
+ var newBuffer = new byte[_buffer.Length * 2];
+ Buffer.BlockCopy(_buffer, _head, newBuffer, 0, _tail - _head);
+ _buffer = newBuffer;
+ }
- private void OnDataReceived(byte[] data)
- {
- DataReceived?.Invoke(this, new ShellDataEventArgs(data));
- }
+ // Copy the new data into the freed-up space.
+ Buffer.BlockCopy(e.Data, 0, _buffer, _tail - _head, e.Data.Length);
- private string SyncQueuesAndReturn(int bytesToDequeue)
- {
- string incomingText;
+ _head = 0;
+ _tail = newLength;
+ }
- lock (_incoming)
- {
- var incomingLength = _incoming.Count - _expect.Count + bytesToDequeue;
- incomingText = _encoding.GetString(_incoming.ToArray(), 0, incomingLength);
+ AssertValid();
- SyncQueuesAndDequeue(bytesToDequeue);
+ Monitor.PulseAll(_sync);
}
- return incomingText;
- }
-
- private void SyncQueuesAndDequeue(int bytesToDequeue)
- {
- lock (_incoming)
- {
- while (_incoming.Count > _expect.Count)
- {
- _ = _incoming.Dequeue();
- }
-
- for (var count = 0; count < bytesToDequeue && _incoming.Count > 0; count++)
- {
- _ = _incoming.Dequeue();
- _ = _expect.Dequeue();
- }
- }
+ DataReceived?.Invoke(this, new ShellDataEventArgs(e.Data));
}
}
}
diff --git a/src/Renci.SshNet/SshClient.cs b/src/Renci.SshNet/SshClient.cs
index f2bf408fb..3f7ffb3c3 100644
--- a/src/Renci.SshNet/SshClient.cs
+++ b/src/Renci.SshNet/SshClient.cs
@@ -416,38 +416,7 @@ public Shell CreateShell(Encoding encoding, string input, Stream output, Stream
///
public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize)
{
- return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, bufferSize * 2, terminalModeValues: null);
- }
-
- ///
- /// Creates the shell stream.
- ///
- /// The TERM environment variable.
- /// The terminal width in columns.
- /// The terminal width in rows.
- /// The terminal width in pixels.
- /// The terminal height in pixels.
- /// The size of the buffer.
- /// The size of the expect buffer.
- ///
- /// The created instance.
- ///
- /// Client is not connected.
- ///
- ///
- /// The TERM environment variable contains an identifier for the text window's capabilities.
- /// You can get a detailed list of these capabilities by using the ‘infocmp’ command.
- ///
- ///
- /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer
- /// to the drawable area of the window.
- ///
- ///
- public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, int expectSize)
- {
- EnsureSessionIsOpen();
-
- return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, expectSize, terminalModeValues: null);
+ return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, terminalModeValues: null);
}
///
@@ -478,39 +447,7 @@ public ShellStream CreateShellStream(string terminalName, uint columns, uint row
{
EnsureSessionIsOpen();
- return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, bufferSize * 2, terminalModeValues);
- }
-
- ///
- /// Creates the shell stream.
- ///
- /// The TERM environment variable.
- /// The terminal width in columns.
- /// The terminal width in rows.
- /// The terminal width in pixels.
- /// The terminal height in pixels.
- /// The size of the buffer.
- /// The size of the expect buffer.
- /// The terminal mode values.
- ///
- /// The created instance.
- ///
- /// Client is not connected.
- ///
- ///
- /// The TERM environment variable contains an identifier for the text window's capabilities.
- /// You can get a detailed list of these capabilities by using the ‘infocmp’ command.
- ///
- ///
- /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer
- /// to the drawable area of the window.
- ///
- ///
- public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, int expectSize, IDictionary terminalModeValues)
- {
- EnsureSessionIsOpen();
-
- return ServiceFactory.CreateShellStream(Session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize, expectSize);
+ return ServiceFactory.CreateShellStream(Session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize);
}
///
diff --git a/test/.editorconfig b/test/.editorconfig
index d04fb3f83..e0a4a8b8f 100644
--- a/test/.editorconfig
+++ b/test/.editorconfig
@@ -22,6 +22,9 @@ dotnet_diagnostic.S1215.severity = none
# For unit tests, we do not care about this diagnostic.
dotnet_diagnostic.S1854.severity = none
+# S3966: Objects should not be disposed more than once
+dotnet_diagnostic.S3966.severity = suggestion
+
#### Meziantou.Analyzer rules ####
# MA0089: Optimize string method usage
@@ -144,3 +147,6 @@ dotnet_diagnostic.CA5351.severity = none
#
# We do not care about this for unit tests.
dotnet_diagnostic.CA5394.severity = none
+
+# IDE0078: Use pattern matching (may change code meaning)
+dotnet_diagnostic.IDE0078.severity = none
diff --git a/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs b/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs
index 32659ba11..68c8cf034 100644
--- a/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs
+++ b/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs
@@ -81,7 +81,7 @@ public string ShellStreamReadLine()
while (true)
{
- var line = shellStream.ReadLine();
+ var line = shellStream.ReadLine()!;
if (line.EndsWith("500", StringComparison.Ordinal))
{
@@ -92,7 +92,7 @@ public string ShellStreamReadLine()
}
[Benchmark]
- public string ShellStreamExpect()
+ public string? ShellStreamExpect()
{
using (var shellStream = _sshClient!.CreateShellStream("xterm", 80, 24, 800, 600, 1024, ShellStreamTerminalModes))
{
diff --git a/test/Renci.SshNet.IntegrationTests/SshTests.cs b/test/Renci.SshNet.IntegrationTests/SshTests.cs
index 6c1ddfb0f..fe05e3482 100644
--- a/test/Renci.SshNet.IntegrationTests/SshTests.cs
+++ b/test/Renci.SshNet.IntegrationTests/SshTests.cs
@@ -71,16 +71,7 @@ public void Ssh_ShellStream_Exit()
Assert.IsNotNull(line);
Assert.IsTrue(line.EndsWith("Hello!"), line);
- // TODO: ReadLine should return null when the buffer is empty and the channel has been closed (issue #672)
- try
- {
- line = shellStream.ReadLine();
- Assert.Fail(line);
- }
- catch (NullReferenceException)
- {
-
- }
+ Assert.IsTrue(shellStream.ReadLine() is null || shellStream.ReadLine() is null); // we might first get e.g. "renci-ssh-tests-server:~$"
}
}
}
@@ -90,18 +81,17 @@ public void Ssh_ShellStream_Exit()
///
[TestMethod]
[Category("Reproduction Tests")]
- [Ignore]
public void Ssh_ShellStream_IntermittendOutput()
{
const string remoteFile = "/home/sshnet/test.sh";
- var expectedResult = string.Join("\n",
- "Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
- "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
- "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
- "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
- "Line 5 ",
- "Line 6");
+ List expectedLines = ["renci-ssh-tests-server:~$ Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "Line 5 ",
+ "Line 6",
+ "renci-ssh-tests-server:~$ "]; // No idea how stable this is.
var scriptBuilder = new StringBuilder();
scriptBuilder.Append("#!/bin/sh\n");
@@ -131,8 +121,8 @@ public void Ssh_ShellStream_IntermittendOutput()
using (var shellStream = sshClient.CreateShellStream("xterm", 80, 24, 800, 600, 1024, terminalModes))
{
shellStream.WriteLine(remoteFile);
- Thread.Sleep(1200);
- using (var reader = new StreamReader(shellStream, new UTF8Encoding(false), false, 10))
+ shellStream.WriteLine("exit");
+ using (var reader = new StreamReader(shellStream))
{
var lines = new List();
string line = null;
@@ -140,8 +130,8 @@ public void Ssh_ShellStream_IntermittendOutput()
{
lines.Add(line);
}
- Assert.AreEqual(6, lines.Count, string.Join("\n", lines));
- Assert.AreEqual(expectedResult, string.Join("\n", lines));
+
+ CollectionAssert.AreEqual(expectedLines, lines, string.Join("\n", lines));
}
}
}
diff --git a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_ChannelOpenThrowsException.cs b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_ChannelOpenThrowsException.cs
index 423c08a82..651f2fbc2 100644
--- a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_ChannelOpenThrowsException.cs
+++ b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_ChannelOpenThrowsException.cs
@@ -22,7 +22,6 @@ public class ServiceFactoryTest_CreateShellStream_ChannelOpenThrowsException
private uint _height;
private IDictionary _terminalModeValues;
private int _bufferSize;
- private int _expectSize;
private SshException _channelOpenException;
private SshException _actualException;
@@ -37,7 +36,6 @@ private void SetupData()
_height = (uint) random.Next();
_terminalModeValues = new Dictionary();
_bufferSize = random.Next();
- _expectSize = _bufferSize;
_channelOpenException = new SshException();
_actualException = null;
@@ -97,8 +95,7 @@ private void Act()
_width,
_height,
_terminalModeValues,
- _bufferSize,
- _expectSize);
+ _bufferSize);
Assert.Fail();
}
catch (SshException ex)
diff --git a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestReturnsFalse.cs b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestReturnsFalse.cs
index e5614bb31..362351a5f 100644
--- a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestReturnsFalse.cs
+++ b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestReturnsFalse.cs
@@ -22,7 +22,6 @@ public class ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestRetur
private uint _height;
private IDictionary _terminalModeValues;
private int _bufferSize;
- private int _expectSize;
private SshException _actualException;
private void SetupData()
@@ -36,7 +35,6 @@ private void SetupData()
_height = (uint)random.Next();
_terminalModeValues = new Dictionary();
_bufferSize = random.Next();
- _expectSize = _bufferSize;
_actualException = null;
}
@@ -96,8 +94,7 @@ private void Act()
_width,
_height,
_terminalModeValues,
- _bufferSize,
- _expectSize);
+ _bufferSize);
Assert.Fail();
}
catch (SshException ex)
diff --git a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestThrowsException.cs b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestThrowsException.cs
index 09caab028..67606992a 100644
--- a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestThrowsException.cs
+++ b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestThrowsException.cs
@@ -22,7 +22,6 @@ public class ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestThrow
private uint _height;
private IDictionary _terminalModeValues;
private int _bufferSize;
- private int _expectSize;
private SshException _sendPseudoTerminalRequestException;
private SshException _actualException;
@@ -37,7 +36,6 @@ private void SetupData()
_height = (uint) random.Next();
_terminalModeValues = new Dictionary();
_bufferSize = random.Next();
- _expectSize = _bufferSize;
_sendPseudoTerminalRequestException = new SshException();
_actualException = null;
@@ -99,8 +97,7 @@ private void Act()
_width,
_height,
_terminalModeValues,
- _bufferSize,
- _expectSize);
+ _bufferSize);
Assert.Fail();
}
catch (SshException ex)
diff --git a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestReturnsFalse.cs b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestReturnsFalse.cs
index 875faf752..5a6d43871 100644
--- a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestReturnsFalse.cs
+++ b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestReturnsFalse.cs
@@ -22,7 +22,6 @@ public class ServiceFactoryTest_CreateShellStream_SendShellRequestReturnsFalse
private uint _height;
private IDictionary _terminalModeValues;
private int _bufferSize;
- private int _expectSize;
private SshException _actualException;
private void SetupData()
@@ -36,7 +35,6 @@ private void SetupData()
_height = (uint) random.Next();
_terminalModeValues = new Dictionary();
_bufferSize = random.Next();
- _expectSize = _bufferSize;
_actualException = null;
}
@@ -99,8 +97,7 @@ private void Act()
_width,
_height,
_terminalModeValues,
- _bufferSize,
- _expectSize);
+ _bufferSize);
Assert.Fail();
}
catch (SshException ex)
diff --git a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestThrowsException.cs b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestThrowsException.cs
index 240f18510..da9cd6be8 100644
--- a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestThrowsException.cs
+++ b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestThrowsException.cs
@@ -22,7 +22,6 @@ public class ServiceFactoryTest_CreateShellStream_SendShellRequestThrowsExceptio
private uint _height;
private IDictionary _terminalModeValues;
private int _bufferSize;
- private int _expectSize;
private SshException _sendShellRequestException;
private SshException _actualException;
@@ -37,7 +36,6 @@ private void SetupData()
_height = (uint) random.Next();
_terminalModeValues = new Dictionary();
_bufferSize = random.Next();
- _expectSize = _bufferSize;
_sendShellRequestException = new SshException();
_actualException = null;
}
@@ -101,8 +99,7 @@ private void Act()
_width,
_height,
_terminalModeValues,
- _bufferSize,
- _expectSize);
+ _bufferSize);
Assert.Fail();
}
catch (SshException ex)
diff --git a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_Success.cs b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_Success.cs
index 75990b608..3f2f92a7a 100644
--- a/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_Success.cs
+++ b/test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_Success.cs
@@ -22,7 +22,6 @@ public class ServiceFactoryTest_CreateShellStream_Success
private uint _height;
private IDictionary _terminalModeValues;
private int _bufferSize;
- private int _expectSize;
private ShellStream _shellStream;
private void SetupData()
@@ -36,7 +35,6 @@ private void SetupData()
_height = (uint) random.Next();
_terminalModeValues = new Dictionary();
_bufferSize = random.Next();
- _expectSize = _bufferSize;
}
private void CreateMocks()
@@ -99,8 +97,7 @@ private void Act()
_width,
_height,
_terminalModeValues,
- _bufferSize,
- _expectSize);
+ _bufferSize);
}
[TestMethod]
@@ -109,12 +106,6 @@ public void CreateShellStreamShouldNotReturnNull()
Assert.IsNotNull(_shellStream);
}
- [TestMethod]
- public void BufferSizeOfShellStreamShouldBeValuePassedToCreateShellStream()
- {
- Assert.AreEqual(_bufferSize, _shellStream.BufferSize);
- }
-
[TestMethod]
public void SendPseudoTerminalRequestShouldHaveBeenInvokedOnce()
{
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest.cs
index edbef2d37..f0cd18dac 100644
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest.cs
+++ b/test/Renci.SshNet.Tests/Classes/ShellStreamTest.cs
@@ -27,7 +27,6 @@ public class ShellStreamTest : TestBase
private uint _heightPixels;
private Dictionary _terminalModes;
private int _bufferSize;
- private int _expectSize;
private Mock _channelSessionMock;
protected override void OnInit()
@@ -42,7 +41,6 @@ protected override void OnInit()
_heightPixels = (uint)random.Next();
_terminalModes = new Dictionary();
_bufferSize = random.Next(100, 500);
- _expectSize = random.Next(100, _bufferSize);
_encoding = Encoding.UTF8;
_sessionMock = new Mock(MockBehavior.Strict);
@@ -98,11 +96,46 @@ public void WriteLine_Line_ShouldOnlyWriteLineTerminatorWhenLineIsNull()
const string line = null;
var lineTerminator = _encoding.GetBytes("\r");
- _channelSessionMock.Setup(p => p.SendData(lineTerminator));
+ _channelSessionMock.Setup(p => p.SendData(lineTerminator, 0, lineTerminator.Length));
shellStream.WriteLine(line);
- _channelSessionMock.Verify(p => p.SendData(lineTerminator), Times.Once);
+ _channelSessionMock.Verify(p => p.SendData(lineTerminator, 0, lineTerminator.Length), Times.Once);
+ }
+
+ [TestMethod]
+ public void Write_Bytes_SendsToChannel()
+ {
+ var shellStream = CreateShellStream();
+
+ var bytes1 = _encoding.GetBytes("Hello World!");
+ var bytes2 = _encoding.GetBytes("Some more bytes!");
+
+ _channelSessionMock.Setup(p => p.SendData(bytes1, 0, bytes1.Length));
+ _channelSessionMock.Setup(p => p.SendData(bytes2, 0, bytes2.Length));
+
+ shellStream.Write(bytes1, 0, bytes1.Length);
+
+ _channelSessionMock.Verify(p => p.SendData(bytes1, 0, bytes1.Length), Times.Once);
+
+ shellStream.Write(bytes2, 0, bytes2.Length);
+
+ _channelSessionMock.Verify(p => p.SendData(bytes1, 0, bytes1.Length), Times.Once);
+ _channelSessionMock.Verify(p => p.SendData(bytes2, 0, bytes2.Length), Times.Once);
+ }
+
+ [TestMethod]
+ public void Write_AfterDispose_ThrowsObjectDisposedException()
+ {
+ var shellStream = CreateShellStream();
+
+ _channelSessionMock.Setup(p => p.Dispose());
+
+ shellStream.Dispose();
+
+ var bytes = _encoding.GetBytes("Hello World!");
+
+ Assert.ThrowsException(() => shellStream.Write(bytes, 0, bytes.Length));
}
private ShellStream CreateShellStream()
@@ -122,8 +155,7 @@ private ShellStream CreateShellStream()
_widthPixels,
_heightPixels,
_terminalModes,
- _bufferSize,
- _expectSize);
+ _bufferSize);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_ReadExpect.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_ReadExpect.cs
index 40fc8c883..a7f60bb8a 100644
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_ReadExpect.cs
+++ b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_ReadExpect.cs
@@ -18,7 +18,6 @@ namespace Renci.SshNet.Tests.Classes
public class ShellStreamTest_ReadExpect
{
private const int BufferSize = 1024;
- private const int ExpectSize = BufferSize * 2;
private ShellStream _shellStream;
private ChannelSessionStub _channelSessionStub;
@@ -44,8 +43,7 @@ public void Initialize()
width: 800,
height: 600,
terminalModeValues: null,
- bufferSize: BufferSize,
- expectSize: ExpectSize);
+ bufferSize: BufferSize);
}
[TestMethod]
@@ -74,26 +72,48 @@ public void Read_Bytes()
[DataTestMethod]
[DataRow("\r\n")]
- //[DataRow("\r")] These currently fail.
- //[DataRow("\n")]
+ [DataRow("\r")]
+ [DataRow("\n")]
public void ReadLine(string newLine)
{
_channelSessionStub.Receive(Encoding.UTF8.GetBytes("Hello "));
_channelSessionStub.Receive(Encoding.UTF8.GetBytes("World!"));
- // We specify a nonzero timeout to avoid waiting infinitely.
- Assert.IsNull(_shellStream.ReadLine(TimeSpan.FromTicks(1)));
+ // We specify a timeout to avoid waiting infinitely.
+ Assert.IsNull(_shellStream.ReadLine(TimeSpan.Zero));
_channelSessionStub.Receive(Encoding.UTF8.GetBytes(newLine));
- Assert.AreEqual("Hello World!", _shellStream.ReadLine(TimeSpan.FromTicks(1)));
- Assert.IsNull(_shellStream.ReadLine(TimeSpan.FromTicks(1)));
+ Assert.AreEqual("Hello World!", _shellStream.ReadLine(TimeSpan.Zero));
+ Assert.IsNull(_shellStream.ReadLine(TimeSpan.Zero));
_channelSessionStub.Receive(Encoding.UTF8.GetBytes("Second line!" + newLine + "Third line!" + newLine));
- Assert.AreEqual("Second line!", _shellStream.ReadLine(TimeSpan.FromTicks(1)));
- Assert.AreEqual("Third line!", _shellStream.ReadLine(TimeSpan.FromTicks(1)));
- Assert.IsNull(_shellStream.ReadLine(TimeSpan.FromTicks(1)));
+ Assert.AreEqual("Second line!", _shellStream.ReadLine(TimeSpan.Zero));
+ Assert.AreEqual("Third line!", _shellStream.ReadLine(TimeSpan.Zero));
+ Assert.IsNull(_shellStream.ReadLine(TimeSpan.Zero));
+
+ _channelSessionStub.Receive(Encoding.UTF8.GetBytes("Last line!")); // no newLine at the end
+
+ Assert.IsNull(_shellStream.ReadLine(TimeSpan.Zero));
+
+ _channelSessionStub.Close();
+
+ Assert.AreEqual("Last line!", _shellStream.ReadLine(TimeSpan.Zero));
+ }
+
+ [TestMethod]
+ public void ReadLine_DifferentTerminators()
+ {
+ _channelSessionStub.Receive(Encoding.UTF8.GetBytes("Hello\rWorld!\nWhat's\r\ngoing\n\ron?\n"));
+
+ Assert.AreEqual("Hello", _shellStream.ReadLine());
+ Assert.AreEqual("World!", _shellStream.ReadLine());
+ Assert.AreEqual("What's", _shellStream.ReadLine());
+ Assert.AreEqual("going", _shellStream.ReadLine());
+ Assert.AreEqual("", _shellStream.ReadLine());
+ Assert.AreEqual("on?", _shellStream.ReadLine());
+ Assert.IsNull(_shellStream.ReadLine(TimeSpan.Zero));
}
[DataTestMethod]
@@ -111,54 +131,31 @@ public void Read_MultipleLines(string newLine)
}
[TestMethod]
- [Ignore] // Currently returns 0 immediately
- public void Read_NonEmptyArray_OnlyReturnsZeroAfterClose()
+ public async Task Read_NonEmptyArray_OnlyReturnsZeroAfterClose()
{
- Task closeTask = Task.Run(async () =>
- {
- // For the test to have meaning, we should be in
- // the call to Read before closing the channel.
- // Impose a short delay to make that more likely.
- await Task.Delay(50);
-
- _channelSessionStub.Close();
- });
+ Task readTask = _shellStream.ReadAsync(new byte[16], 0, 16);
- Assert.AreEqual(0, _shellStream.Read(new byte[16], 0, 16));
- Assert.AreEqual(TaskStatus.RanToCompletion, closeTask.Status);
- }
+ await Task.Delay(50);
- [TestMethod]
- [Ignore] // Currently returns 0 immediately
- public void Read_EmptyArray_OnlyReturnsZeroWhenDataAvailable()
- {
- Task receiveTask = Task.Run(async () =>
- {
- // For the test to have meaning, we should be in
- // the call to Read before receiving the data.
- // Impose a short delay to make that more likely.
- await Task.Delay(50);
+ Assert.IsFalse(readTask.IsCompleted);
- _channelSessionStub.Receive(Encoding.UTF8.GetBytes("Hello World!"));
- });
+ _channelSessionStub.Close();
- Assert.AreEqual(0, _shellStream.Read(Array.Empty(), 0, 0));
- Assert.AreEqual(TaskStatus.RanToCompletion, receiveTask.Status);
+ Assert.AreEqual(0, await readTask);
}
[TestMethod]
- [Ignore] // Currently hangs
- public void ReadLine_NoData_ReturnsNullAfterClose()
+ public async Task Read_EmptyArray_OnlyReturnsZeroWhenDataAvailable()
{
- Task closeTask = Task.Run(async () =>
- {
- await Task.Delay(50);
+ Task readTask = _shellStream.ReadAsync(Array.Empty(), 0, 0);
- _channelSessionStub.Close();
- });
+ await Task.Delay(50);
- Assert.IsNull(_shellStream.ReadLine());
- Assert.AreEqual(TaskStatus.RanToCompletion, closeTask.Status);
+ Assert.IsFalse(readTask.IsCompleted);
+
+ _channelSessionStub.Receive(Encoding.UTF8.GetBytes("Hello World!"));
+
+ Assert.AreEqual(0, await readTask);
}
[TestMethod]
@@ -167,14 +164,24 @@ public void Expect()
_channelSessionStub.Receive(Encoding.UTF8.GetBytes("Hello "));
_channelSessionStub.Receive(Encoding.UTF8.GetBytes("World!"));
- Assert.IsNull(_shellStream.Expect("123", TimeSpan.FromTicks(1)));
+ Assert.IsNull(_shellStream.Expect("123", TimeSpan.Zero));
_channelSessionStub.Receive(Encoding.UTF8.GetBytes("\r\n12345"));
- // Both of these cases fail
- // Case 1 above.
- Assert.AreEqual("Hello World!\r\n123", _shellStream.Expect("123")); // Fails, returns "Hello World!\r\n12345"
- Assert.AreEqual("45", _shellStream.Read()); // Passes, but should probably fail and return ""
+ Assert.AreEqual("Hello World!\r\n123", _shellStream.Expect("123"));
+ Assert.AreEqual("45", _shellStream.Read());
+ }
+
+ [TestMethod]
+ public void Read_AfterDispose_StillWorks()
+ {
+ _channelSessionStub.Receive(Encoding.UTF8.GetBytes("Hello World!"));
+
+ _shellStream.Dispose();
+ _shellStream.Dispose(); // Check that multiple Dispose is OK.
+
+ Assert.AreEqual("Hello World!", _shellStream.ReadLine());
+ Assert.IsNull(_shellStream.ReadLine());
}
[TestMethod]
@@ -221,7 +228,7 @@ public void Expect_String_MultiByte()
}
[TestMethod]
- public void Expect_String_non_ASCII_characters()
+ public void Expect_Regex_non_ASCII_characters()
{
_channelSessionStub.Receive(Encoding.UTF8.GetBytes("Hello, こんにちは, Bonjour"));
@@ -247,13 +254,12 @@ public void Expect_String_LargeExpect()
}
[TestMethod]
- public void Expect_String_DequeueChecks()
+ public void Expect_String_WithLookback()
{
const string expected = "ccccc";
// Prime buffer
_channelSessionStub.Receive(Encoding.UTF8.GetBytes(new string(' ', BufferSize)));
- _channelSessionStub.Receive(Encoding.UTF8.GetBytes(new string(' ', ExpectSize)));
// Test data
_channelSessionStub.Receive(Encoding.UTF8.GetBytes(new string('a', 100)));
@@ -263,14 +269,34 @@ public void Expect_String_DequeueChecks()
_channelSessionStub.Receive(Encoding.UTF8.GetBytes(new string('e', 100)));
// Expected result
- var expectedResult = $"{new string(' ', BufferSize)}{new string(' ', ExpectSize)}{new string('a', 100)}{new string('b', 100)}{expected}";
+ var expectedResult = $"{new string(' ', BufferSize)}{new string('a', 100)}{new string('b', 100)}{expected}";
var expectedRead = $"{new string('d', 100)}{new string('e', 100)}";
- Assert.AreEqual(expectedResult, _shellStream.Expect(expected));
+ Assert.AreEqual(expectedResult, _shellStream.Expect(expected, TimeSpan.Zero, lookback: 250));
Assert.AreEqual(expectedRead, _shellStream.Read());
}
+ [TestMethod]
+ public void Expect_Regex_WithLookback()
+ {
+ _channelSessionStub.Receive(Encoding.UTF8.GetBytes("0123456789"));
+
+ Assert.AreEqual("01234567", _shellStream.Expect(new Regex(@"\d"), TimeSpan.Zero, lookback: 3));
+
+ Assert.AreEqual("89", _shellStream.Read());
+ }
+
+ [TestMethod]
+ public void Expect_Regex_WithLookback_non_ASCII_characters()
+ {
+ _channelSessionStub.Receive(Encoding.UTF8.GetBytes("Hello, こんにちは, Bonjour"));
+
+ Assert.AreEqual("Hello, こんにち", _shellStream.Expect(new Regex(@"[^\u0000-\u007F]"), TimeSpan.Zero, lookback: 11));
+
+ Assert.AreEqual("は, Bonjour", _shellStream.Read());
+ }
+
[TestMethod]
public void Expect_Timeout()
{
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs
deleted file mode 100644
index d08a8dcc3..000000000
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using Renci.SshNet.Abstractions;
-using Renci.SshNet.Channels;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Tests.Classes
-{
- [TestClass]
- public class ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize
- {
- private Mock _sessionMock;
- private Mock _connectionInfoMock;
- private Mock _channelSessionMock;
- private string _terminalName;
- private uint _widthColumns;
- private uint _heightRows;
- private uint _widthPixels;
- private uint _heightPixels;
- private Dictionary _terminalModes;
- private ShellStream _shellStream;
- private int _bufferSize;
- private int _expectSize;
-
- private byte[] _data;
- private int _offset;
- private int _count;
- private MockSequence _mockSequence;
-
- [TestInitialize]
- public void Initialize()
- {
- Arrange();
- Act();
- }
-
- private void SetupData()
- {
- var random = new Random();
-
- _terminalName = random.Next().ToString();
- _widthColumns = (uint) random.Next();
- _heightRows = (uint) random.Next();
- _widthPixels = (uint) random.Next();
- _heightPixels = (uint) random.Next();
- _terminalModes = new Dictionary();
- _bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
-
- _data = CryptoAbstraction.GenerateRandom(_bufferSize - 10);
- _offset = random.Next(1, 5);
- _count = _data.Length - _offset - random.Next(1, 10);
- }
-
- private void CreateMocks()
- {
- _sessionMock = new Mock(MockBehavior.Strict);
- _connectionInfoMock = new Mock(MockBehavior.Strict);
- _channelSessionMock = new Mock(MockBehavior.Strict);
- }
-
- private void SetupMocks()
- {
- _mockSequence = new MockSequence();
-
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.ConnectionInfo)
- .Returns(_connectionInfoMock.Object);
- _connectionInfoMock.InSequence(_mockSequence)
- .Setup(p => p.Encoding)
- .Returns(new UTF8Encoding());
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.CreateChannelSession())
- .Returns(_channelSessionMock.Object);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.Open());
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes))
- .Returns(true);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendShellRequest())
- .Returns(true);
- }
-
- private void Arrange()
- {
- SetupData();
- CreateMocks();
- SetupMocks();
-
- _shellStream = new ShellStream(_sessionMock.Object,
- _terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes,
- _bufferSize,
- _expectSize);
- }
-
- private void Act()
- {
- _shellStream.Write(_data, _offset, _count);
- }
-
- [TestMethod]
- public void NoDataShouldBeSentToServer()
- {
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Never);
- }
-
- [TestMethod]
- public void FlushShouldSendWrittenBytesToServer()
- {
- byte[] bytesSent = null;
-
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(It.IsAny()))
- .Callback(data => bytesSent = data);
-
- _shellStream.Flush();
-
- Assert.IsNotNull(bytesSent);
- Assert.IsTrue(_data.Take(_offset, _count).IsEqualTo(bytesSent));
-
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Once);
- }
- }
-}
\ No newline at end of file
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs
deleted file mode 100644
index dfff5cf7f..000000000
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using Renci.SshNet.Abstractions;
-using Renci.SshNet.Channels;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Tests.Classes
-{
- [TestClass]
- public class ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize
- {
- private Mock _sessionMock;
- private Mock _connectionInfoMock;
- private Mock _channelSessionMock;
- private MockSequence _mockSequence;
- private string _terminalName;
- private uint _widthColumns;
- private uint _heightRows;
- private uint _widthPixels;
- private uint _heightPixels;
- private Dictionary _terminalModes;
- private ShellStream _shellStream;
- private int _bufferSize;
- private int _expectSize;
-
- private byte[] _data;
- private int _offset;
- private int _count;
-
- private byte[] _expectedBytesSent1;
- private byte[] _expectedBytesSent2;
-
- [TestInitialize]
- public void Initialize()
- {
- Arrange();
- Act();
- }
-
- private void SetupData()
- {
- var random = new Random();
-
- _terminalName = random.Next().ToString();
- _widthColumns = (uint)random.Next();
- _heightRows = (uint)random.Next();
- _widthPixels = (uint)random.Next();
- _heightPixels = (uint)random.Next();
- _terminalModes = new Dictionary();
- _bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
-
- _data = CryptoAbstraction.GenerateRandom((_bufferSize * 2) + 10);
- _offset = 0;
- _count = _data.Length;
-
- _expectedBytesSent1 = _data.Take(0, _bufferSize);
- _expectedBytesSent2 = _data.Take(_bufferSize, _bufferSize);
- }
-
- private void CreateMocks()
- {
- _sessionMock = new Mock(MockBehavior.Strict);
- _connectionInfoMock = new Mock(MockBehavior.Strict);
- _channelSessionMock = new Mock(MockBehavior.Strict);
- }
-
- private void SetupMocks()
- {
- _mockSequence = new MockSequence();
-
- _ = _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.ConnectionInfo)
- .Returns(_connectionInfoMock.Object);
- _ = _connectionInfoMock.InSequence(_mockSequence)
- .Setup(p => p.Encoding)
- .Returns(new UTF8Encoding());
- _ = _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.CreateChannelSession())
- .Returns(_channelSessionMock.Object);
- _ = _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.Open());
- _ = _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes))
- .Returns(true);
- _ = _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendShellRequest())
- .Returns(true);
- _ = _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(_expectedBytesSent1));
- _ = _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(_expectedBytesSent2));
- }
-
- private void Arrange()
- {
- SetupData();
- CreateMocks();
- SetupMocks();
-
- _shellStream = new ShellStream(_sessionMock.Object,
- _terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes,
- _bufferSize,
- _expectSize);
- }
-
- private void Act()
- {
- _shellStream.Write(_data, _offset, _count);
- }
-
- [TestMethod]
- public void BufferShouldHaveBeenFlushedTwice()
- {
- _channelSessionMock.Verify(p => p.SendData(_expectedBytesSent1), Times.Once);
- _channelSessionMock.Verify(p => p.SendData(_expectedBytesSent2), Times.Once);
- }
-
- [TestMethod]
- public void FlushShouldSendRemaningBytesToServer()
- {
- var expectedBytesSent = _data.Take(_bufferSize * 2, _data.Length - (_bufferSize * 2));
-
- _ = _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(expectedBytesSent));
-
- _shellStream.Flush();
-
- _channelSessionMock.Verify(p => p.SendData(expectedBytesSent), Times.Once);
- }
- }
-}
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs
deleted file mode 100644
index be5e80185..000000000
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs
+++ /dev/null
@@ -1,132 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using Renci.SshNet.Abstractions;
-using Renci.SshNet.Channels;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Tests.Classes
-{
- [TestClass]
- public class ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize
- {
- private Mock _sessionMock;
- private Mock _connectionInfoMock;
- private Mock _channelSessionMock;
- private string _terminalName;
- private uint _widthColumns;
- private uint _heightRows;
- private uint _widthPixels;
- private uint _heightPixels;
- private Dictionary _terminalModes;
- private ShellStream _shellStream;
- private int _bufferSize;
- private int _expectSize;
-
- private byte[] _data;
- private int _offset;
- private int _count;
- private MockSequence _mockSequence;
-
- [TestInitialize]
- public void Initialize()
- {
- Arrange();
- Act();
- }
-
- private void SetupData()
- {
- var random = new Random();
-
- _terminalName = random.Next().ToString();
- _widthColumns = (uint)random.Next();
- _heightRows = (uint)random.Next();
- _widthPixels = (uint)random.Next();
- _heightPixels = (uint)random.Next();
- _terminalModes = new Dictionary();
- _bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
-
- _data = CryptoAbstraction.GenerateRandom(_bufferSize);
- _offset = 0;
- _count = _data.Length;
- }
-
- private void CreateMocks()
- {
- _sessionMock = new Mock(MockBehavior.Strict);
- _connectionInfoMock = new Mock(MockBehavior.Strict);
- _channelSessionMock = new Mock(MockBehavior.Strict);
- }
-
- private void SetupMocks()
- {
- _mockSequence = new MockSequence();
-
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.ConnectionInfo)
- .Returns(_connectionInfoMock.Object);
- _connectionInfoMock.InSequence(_mockSequence)
- .Setup(p => p.Encoding)
- .Returns(new UTF8Encoding());
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.CreateChannelSession())
- .Returns(_channelSessionMock.Object);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.Open());
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes))
- .Returns(true);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendShellRequest())
- .Returns(true);
- }
-
- private void Arrange()
- {
- SetupData();
- CreateMocks();
- SetupMocks();
-
- _shellStream = new ShellStream(_sessionMock.Object,
- _terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes,
- _bufferSize,
- _expectSize);
- }
-
- private void Act()
- {
- _shellStream.Write(_data, _offset, _count);
- }
-
- [TestMethod]
- public void NoDataShouldBeSentToServer()
- {
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Never);
- }
-
- [TestMethod]
- public void FlushShouldSendWrittenBytesToServer()
- {
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(_data));
-
- _shellStream.Flush();
-
- _channelSessionMock.Verify(p => p.SendData(_data), Times.Once);
- }
- }
-}
\ No newline at end of file
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs
deleted file mode 100644
index 3bd86ded4..000000000
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-using Moq;
-
-using Renci.SshNet.Channels;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Tests.Classes
-{
- [TestClass]
- public class ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes
- {
- private Mock _sessionMock;
- private Mock _connectionInfoMock;
- private Mock _channelSessionMock;
- private string _terminalName;
- private uint _widthColumns;
- private uint _heightRows;
- private uint _widthPixels;
- private uint _heightPixels;
- private Dictionary _terminalModes;
- private ShellStream _shellStream;
- private int _bufferSize;
- private int _expectSize;
-
- private byte[] _data;
- private int _offset;
- private int _count;
- private MockSequence _mockSequence;
-
- [TestInitialize]
- public void Initialize()
- {
- Arrange();
- Act();
- }
-
- private void SetupData()
- {
- var random = new Random();
-
- _terminalName = random.Next().ToString();
- _widthColumns = (uint)random.Next();
- _heightRows = (uint)random.Next();
- _widthPixels = (uint)random.Next();
- _heightPixels = (uint)random.Next();
- _terminalModes = new Dictionary();
- _bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
-
- _data = new byte[0];
- _offset = 0;
- _count = _data.Length;
- }
-
- private void CreateMocks()
- {
- _sessionMock = new Mock(MockBehavior.Strict);
- _connectionInfoMock = new Mock(MockBehavior.Strict);
- _channelSessionMock = new Mock(MockBehavior.Strict);
- }
-
- private void SetupMocks()
- {
- _mockSequence = new MockSequence();
-
- _ = _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.ConnectionInfo)
- .Returns(_connectionInfoMock.Object);
- _ = _connectionInfoMock.InSequence(_mockSequence)
- .Setup(p => p.Encoding)
- .Returns(new UTF8Encoding());
- _ = _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.CreateChannelSession())
- .Returns(_channelSessionMock.Object);
- _ = _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.Open());
- _ = _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes))
- .Returns(true);
- _ = _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendShellRequest())
- .Returns(true);
- }
-
- private void Arrange()
- {
- SetupData();
- CreateMocks();
- SetupMocks();
-
- _shellStream = new ShellStream(_sessionMock.Object,
- _terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes,
- _bufferSize,
- _expectSize);
- }
-
- private void Act()
- {
- _shellStream.Write(_data, _offset, _count);
- }
-
- [TestMethod]
- public void NoDataShouldBeSentToServer()
- {
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Never);
- }
-
- [TestMethod]
- public void FlushShouldSendNoBytesToServer()
- {
- _shellStream.Flush();
-
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Never);
- }
- }
-}
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs
deleted file mode 100644
index 2fd52d33d..000000000
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using Renci.SshNet.Abstractions;
-using Renci.SshNet.Channels;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Tests.Classes
-{
- [TestClass]
- public class ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize
- {
- private Mock _sessionMock;
- private Mock _connectionInfoMock;
- private Mock _channelSessionMock;
- private string _terminalName;
- private uint _widthColumns;
- private uint _heightRows;
- private uint _widthPixels;
- private uint _heightPixels;
- private Dictionary _terminalModes;
- private ShellStream _shellStream;
- private int _bufferSize;
- private int _expectSize;
-
- private byte[] _data;
- private int _offset;
- private int _count;
- private MockSequence _mockSequence;
- private byte[] _bufferData;
-
- [TestInitialize]
- public void Initialize()
- {
- Arrange();
- Act();
- }
-
- private void SetupData()
- {
- var random = new Random();
-
- _terminalName = random.Next().ToString();
- _widthColumns = (uint)random.Next();
- _heightRows = (uint)random.Next();
- _widthPixels = (uint)random.Next();
- _heightPixels = (uint)random.Next();
- _terminalModes = new Dictionary();
- _bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
-
- _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize);
- _data = CryptoAbstraction.GenerateRandom(_bufferSize - 10);
- _offset = 0;
- _count = _data.Length;
- }
-
- private void CreateMocks()
- {
- _sessionMock = new Mock(MockBehavior.Strict);
- _connectionInfoMock = new Mock(MockBehavior.Strict);
- _channelSessionMock = new Mock(MockBehavior.Strict);
- }
-
- private void SetupMocks()
- {
- _mockSequence = new MockSequence();
-
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.ConnectionInfo)
- .Returns(_connectionInfoMock.Object);
- _connectionInfoMock.InSequence(_mockSequence)
- .Setup(p => p.Encoding)
- .Returns(new UTF8Encoding());
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.CreateChannelSession())
- .Returns(_channelSessionMock.Object);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.Open());
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes))
- .Returns(true);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendShellRequest())
- .Returns(true);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(_bufferData));
- }
-
- private void Arrange()
- {
- SetupData();
- CreateMocks();
- SetupMocks();
-
- _shellStream = new ShellStream(_sessionMock.Object,
- _terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes,
- _bufferSize,
- _expectSize);
-
- _shellStream.Write(_bufferData, 0, _bufferData.Length);
- }
-
- private void Act()
- {
- _shellStream.Write(_data, _offset, _count);
- }
-
- [TestMethod]
- public void BufferShouldBeSentToServer()
- {
- _channelSessionMock.Verify(p => p.SendData(_bufferData), Times.Once);
- }
-
- [TestMethod]
- public void FlushShouldSendRemainingBytesInBufferToServer()
- {
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(_data));
-
- _shellStream.Flush();
-
- _channelSessionMock.Verify(p => p.SendData(_data), Times.Once);
- }
- }
-}
\ No newline at end of file
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs
deleted file mode 100644
index 7ad30c063..000000000
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using Renci.SshNet.Abstractions;
-using Renci.SshNet.Channels;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Tests.Classes
-{
- [TestClass]
- public class ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes
- {
- private Mock _sessionMock;
- private Mock _connectionInfoMock;
- private Mock _channelSessionMock;
- private string _terminalName;
- private uint _widthColumns;
- private uint _heightRows;
- private uint _widthPixels;
- private uint _heightPixels;
- private Dictionary _terminalModes;
- private ShellStream _shellStream;
- private int _bufferSize;
- private int _expectSize;
-
- private byte[] _data;
- private int _offset;
- private int _count;
- private MockSequence _mockSequence;
- private byte[] _bufferData;
-
- [TestInitialize]
- public void Initialize()
- {
- Arrange();
- Act();
- }
-
- private void SetupData()
- {
- var random = new Random();
-
- _terminalName = random.Next().ToString();
- _widthColumns = (uint) random.Next();
- _heightRows = (uint) random.Next();
- _widthPixels = (uint) random.Next();
- _heightPixels = (uint) random.Next();
- _terminalModes = new Dictionary();
- _bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
-
- _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize);
- _data = new byte[0];
- _offset = 0;
- _count = _data.Length;
- }
-
- private void CreateMocks()
- {
- _sessionMock = new Mock(MockBehavior.Strict);
- _connectionInfoMock = new Mock(MockBehavior.Strict);
- _channelSessionMock = new Mock(MockBehavior.Strict);
- }
-
- private void SetupMocks()
- {
- _mockSequence = new MockSequence();
-
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.ConnectionInfo)
- .Returns(_connectionInfoMock.Object);
- _connectionInfoMock.InSequence(_mockSequence)
- .Setup(p => p.Encoding)
- .Returns(new UTF8Encoding());
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.CreateChannelSession())
- .Returns(_channelSessionMock.Object);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.Open());
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes))
- .Returns(true);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendShellRequest())
- .Returns(true);
- }
-
- private void Arrange()
- {
- SetupData();
- CreateMocks();
- SetupMocks();
-
- _shellStream = new ShellStream(_sessionMock.Object,
- _terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes,
- _bufferSize,
- _expectSize);
-
- _shellStream.Write(_bufferData, 0, _bufferData.Length);
- }
-
- private void Act()
- {
- _shellStream.Write(_data, _offset, _count);
- }
-
- [TestMethod]
- public void NoDataShouldBeSentToServer()
- {
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Never);
- }
-
- [TestMethod]
- public void FlushShouldSendBufferToServer()
- {
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(_bufferData));
-
- _shellStream.Flush();
-
- _channelSessionMock.Verify(p => p.SendData(_bufferData), Times.Once);
- }
- }
-}
\ No newline at end of file
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs
deleted file mode 100644
index 34f9b5299..000000000
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs
+++ /dev/null
@@ -1,144 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using Renci.SshNet.Abstractions;
-using Renci.SshNet.Channels;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Tests.Classes
-{
- [TestClass]
- public class ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain
- {
- private Mock _sessionMock;
- private Mock _connectionInfoMock;
- private Mock _channelSessionMock;
- private string _terminalName;
- private uint _widthColumns;
- private uint _heightRows;
- private uint _widthPixels;
- private uint _heightPixels;
- private Dictionary _terminalModes;
- private ShellStream _shellStream;
- private int _bufferSize;
- private int _expectSize;
-
- private byte[] _data;
- private int _offset;
- private int _count;
- private MockSequence _mockSequence;
- private byte[] _bufferData;
-
- [TestInitialize]
- public void Initialize()
- {
- Arrange();
- Act();
- }
-
- private void SetupData()
- {
- var random = new Random();
-
- _terminalName = random.Next().ToString();
- _widthColumns = (uint) random.Next();
- _heightRows = (uint) random.Next();
- _widthPixels = (uint) random.Next();
- _heightPixels = (uint) random.Next();
- _terminalModes = new Dictionary();
- _bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
-
- _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize - 60);
- _data = CryptoAbstraction.GenerateRandom(_bufferSize + 100);
- _offset = 0;
- _count = _bufferSize - _bufferData.Length - random.Next(1, 10);
- }
-
- private void CreateMocks()
- {
- _sessionMock = new Mock(MockBehavior.Strict);
- _connectionInfoMock = new Mock(MockBehavior.Strict);
- _channelSessionMock = new Mock(MockBehavior.Strict);
- }
-
- private void SetupMocks()
- {
- _mockSequence = new MockSequence();
-
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.ConnectionInfo)
- .Returns(_connectionInfoMock.Object);
- _connectionInfoMock.InSequence(_mockSequence)
- .Setup(p => p.Encoding)
- .Returns(new UTF8Encoding());
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.CreateChannelSession())
- .Returns(_channelSessionMock.Object);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.Open());
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes))
- .Returns(true);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendShellRequest())
- .Returns(true);
- }
-
- private void Arrange()
- {
- SetupData();
- CreateMocks();
- SetupMocks();
-
- _shellStream = new ShellStream(_sessionMock.Object,
- _terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes,
- _bufferSize,
- _expectSize);
-
- _shellStream.Write(_bufferData, 0, _bufferData.Length);
- }
-
- private void Act()
- {
- _shellStream.Write(_data, _offset, _count);
- }
-
- [TestMethod]
- public void NoDataShouldBeSentToServer()
- {
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Never);
- }
-
- [TestMethod]
- public void FlushShouldSendWrittenBytesToServer()
- {
- byte[] bytesSent = null;
-
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(It.IsAny()))
- .Callback(data => bytesSent = data);
-
- _shellStream.Flush();
-
- Assert.IsNotNull(bytesSent);
- Assert.AreEqual(_bufferData.Length + _count, bytesSent.Length);
- Assert.IsTrue(_bufferData.IsEqualTo(bytesSent.Take(_bufferData.Length)));
- Assert.IsTrue(_data.Take(0, _count).IsEqualTo(bytesSent.Take(_bufferData.Length, _count)));
-
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Once);
- }
- }
-}
\ No newline at end of file
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs
deleted file mode 100644
index b571a084b..000000000
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs
+++ /dev/null
@@ -1,152 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using Renci.SshNet.Abstractions;
-using Renci.SshNet.Channels;
-using Renci.SshNet.Common;
-using Renci.SshNet.Tests.Common;
-
-namespace Renci.SshNet.Tests.Classes
-{
- [TestClass]
- public class ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain
- {
- private Mock _sessionMock;
- private Mock _connectionInfoMock;
- private Mock _channelSessionMock;
- private string _terminalName;
- private uint _widthColumns;
- private uint _heightRows;
- private uint _widthPixels;
- private uint _heightPixels;
- private Dictionary _terminalModes;
- private ShellStream _shellStream;
- private int _bufferSize;
- private int _expectSize;
-
- private byte[] _data;
- private int _offset;
- private int _count;
- private MockSequence _mockSequence;
- private byte[] _bufferData;
- private byte[] _expectedBytesSent;
-
- [TestInitialize]
- public void Initialize()
- {
- Arrange();
- Act();
- }
-
- private void SetupData()
- {
- var random = new Random();
-
- _terminalName = random.Next().ToString();
- _widthColumns = (uint) random.Next();
- _heightRows = (uint) random.Next();
- _widthPixels = (uint) random.Next();
- _heightPixels = (uint) random.Next();
- _terminalModes = new Dictionary();
- _bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
-
- _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize - 60);
- _data = CryptoAbstraction.GenerateRandom(_bufferSize - _bufferData.Length + random.Next(1, 10));
- _offset = 0;
- _count = _data.Length;
-
- _expectedBytesSent = new ArrayBuilder().Add(_bufferData)
- .Add(_data, 0, _bufferSize - _bufferData.Length)
- .Build();
- }
-
- private void CreateMocks()
- {
- _sessionMock = new Mock(MockBehavior.Strict);
- _connectionInfoMock = new Mock(MockBehavior.Strict);
- _channelSessionMock = new Mock(MockBehavior.Strict);
- }
-
- private void SetupMocks()
- {
- _mockSequence = new MockSequence();
-
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.ConnectionInfo)
- .Returns(_connectionInfoMock.Object);
- _connectionInfoMock.InSequence(_mockSequence)
- .Setup(p => p.Encoding)
- .Returns(new UTF8Encoding());
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.CreateChannelSession())
- .Returns(_channelSessionMock.Object);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.Open());
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes))
- .Returns(true);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendShellRequest())
- .Returns(true);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(_expectedBytesSent));
- }
-
- private void Arrange()
- {
- SetupData();
- CreateMocks();
- SetupMocks();
-
- _shellStream = new ShellStream(_sessionMock.Object,
- _terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes,
- _bufferSize,
- _expectSize);
-
- _shellStream.Write(_bufferData, 0, _bufferData.Length);
- }
-
- private void Act()
- {
- _shellStream.Write(_data, _offset, _count);
- }
-
- [TestMethod]
- public void BufferShouldBeSentToServer()
- {
- _channelSessionMock.Verify(p => p.SendData(_expectedBytesSent), Times.Once);
- }
-
- [TestMethod]
- public void FlushShouldSendRemainingBytesInBufferToServer()
- {
- var expectedBytesSent = _data.Take(_bufferSize - _bufferData.Length, _data.Length + _bufferData.Length - _bufferSize);
- byte[] actualBytesSent = null;
-
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(It.IsAny()))
- .Callback(data => actualBytesSent = data);
-
- _shellStream.Flush();
-
- Assert.IsNotNull(actualBytesSent);
- Assert.AreEqual(expectedBytesSent.Length, actualBytesSent.Length);
- Assert.IsTrue(expectedBytesSent.IsEqualTo(actualBytesSent));
-
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Exactly(2));
- }
- }
-}
\ No newline at end of file
diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs
deleted file mode 100644
index 50305a412..000000000
--- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
-using Renci.SshNet.Abstractions;
-using Renci.SshNet.Channels;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Tests.Classes
-{
- [TestClass]
- public class ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes
- {
- private Mock _sessionMock;
- private Mock _connectionInfoMock;
- private Mock _channelSessionMock;
- private string _terminalName;
- private uint _widthColumns;
- private uint _heightRows;
- private uint _widthPixels;
- private uint _heightPixels;
- private Dictionary _terminalModes;
- private ShellStream _shellStream;
- private int _bufferSize;
- private int _expectSize;
-
- private byte[] _data;
- private int _offset;
- private int _count;
- private MockSequence _mockSequence;
- private byte[] _bufferData;
-
- [TestInitialize]
- public void Initialize()
- {
- Arrange();
- Act();
- }
-
- private void SetupData()
- {
- var random = new Random();
-
- _terminalName = random.Next().ToString();
- _widthColumns = (uint)random.Next();
- _heightRows = (uint)random.Next();
- _widthPixels = (uint)random.Next();
- _heightPixels = (uint)random.Next();
- _terminalModes = new Dictionary();
- _bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
-
- _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize - 60);
- _data = new byte[0];
- _offset = 0;
- _count = _data.Length;
- }
-
- private void CreateMocks()
- {
- _sessionMock = new Mock(MockBehavior.Strict);
- _connectionInfoMock = new Mock(MockBehavior.Strict);
- _channelSessionMock = new Mock(MockBehavior.Strict);
- }
-
- private void SetupMocks()
- {
- _mockSequence = new MockSequence();
-
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.ConnectionInfo)
- .Returns(_connectionInfoMock.Object);
- _connectionInfoMock.InSequence(_mockSequence)
- .Setup(p => p.Encoding)
- .Returns(new UTF8Encoding());
- _sessionMock.InSequence(_mockSequence)
- .Setup(p => p.CreateChannelSession())
- .Returns(_channelSessionMock.Object);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.Open());
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendPseudoTerminalRequest(_terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes))
- .Returns(true);
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendShellRequest())
- .Returns(true);
- }
-
- private void Arrange()
- {
- SetupData();
- CreateMocks();
- SetupMocks();
-
- _shellStream = new ShellStream(_sessionMock.Object,
- _terminalName,
- _widthColumns,
- _heightRows,
- _widthPixels,
- _heightPixels,
- _terminalModes,
- _bufferSize,
- _expectSize);
-
- _shellStream.Write(_bufferData, 0, _bufferData.Length);
- }
-
- private void Act()
- {
- _shellStream.Write(_data, _offset, _count);
- }
-
- [TestMethod]
- public void NoDataShouldBeSentToServer()
- {
- _channelSessionMock.Verify(p => p.SendData(It.IsAny()), Times.Never);
- }
-
- [TestMethod]
- public void FlushShouldSendWrittenBytesToServer()
- {
- _channelSessionMock.InSequence(_mockSequence)
- .Setup(p => p.SendData(_bufferData));
-
- _shellStream.Flush();
-
- _channelSessionMock.Verify(p => p.SendData(_bufferData), Times.Once);
- }
- }
-}
\ No newline at end of file
diff --git a/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs b/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs
index b13189687..4dc5a2063 100644
--- a/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs
+++ b/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs
@@ -19,7 +19,6 @@ public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWid
private uint _heightPixels;
private Dictionary _terminalModes;
private int _bufferSize;
- private int _expectSize;
private ShellStream _expected;
private ShellStream _actual;
@@ -36,7 +35,6 @@ protected override void SetupData()
_heightPixels = (uint) random.Next();
_terminalModes = new Dictionary();
_bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
_expected = CreateShellStream();
}
@@ -61,8 +59,7 @@ protected override void SetupMocks()
_widthPixels,
_heightPixels,
_terminalModes,
- _bufferSize,
- _expectSize))
+ _bufferSize))
.Returns(_expected);
}
@@ -82,7 +79,6 @@ protected override void Act()
_widthPixels,
_heightPixels,
_bufferSize,
- _expectSize,
_terminalModes);
}
@@ -96,8 +92,7 @@ public void CreateShellStreamOnServiceFactoryShouldBeInvokedOnce()
_widthPixels,
_heightPixels,
_terminalModes,
- _bufferSize,
- _expectSize),
+ _bufferSize),
Times.Once);
}
@@ -135,7 +130,6 @@ private ShellStream CreateShellStream()
_widthPixels,
_heightPixels,
_terminalModes,
- 1,
1);
}
}
diff --git a/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs b/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs
index 7d393b84b..861c8c725 100644
--- a/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs
+++ b/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs
@@ -19,7 +19,6 @@ public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWid
private uint _widthPixels;
private uint _heightPixels;
private int _bufferSize;
- private int _expectSize;
private ShellStream _expected;
private ShellStream _actual;
@@ -35,7 +34,6 @@ protected override void SetupData()
_widthPixels = (uint)random.Next();
_heightPixels = (uint)random.Next();
_bufferSize = random.Next(100, 1000);
- _expectSize = random.Next(100, _bufferSize);
_expected = CreateShellStream();
}
@@ -60,8 +58,7 @@ protected override void SetupMocks()
_widthPixels,
_heightPixels,
null,
- _bufferSize,
- _expectSize))
+ _bufferSize))
.Returns(_expected);
}
@@ -80,8 +77,7 @@ protected override void Act()
_heightRows,
_widthPixels,
_heightPixels,
- _bufferSize,
- _expectSize);
+ _bufferSize);
}
[TestMethod]
@@ -94,8 +90,7 @@ public void CreateShellStreamOnServiceFactoryShouldBeInvokedOnce()
_widthPixels,
_heightPixels,
null,
- _bufferSize,
- _expectSize),
+ _bufferSize),
Times.Once);
}
@@ -133,7 +128,6 @@ private ShellStream CreateShellStream()
_widthPixels,
_heightPixels,
null,
- 1,
1);
}
}