소스 검색

Significantly improve performance of ShellStream's Expect methods (#1207)

* Significantly improved performance and fixed bug with ShellStream's Expect methods.

* Fix whitespace.

* Fixed test.

* Improve ShellStream Expect

* Fixed typo.

* Doubled expectBuffer's default size and added a large expect test.

* Added guard clauses to ShellStream constructor and adjusted Common_CreateMoreChannelsThanMaxSessions test.

* Fixed XMLDoc spelling mistakes.

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>
Co-authored-by: Wojciech Nagórski <wojciech.nagorski@intel.com>
Jean-Sebastien Carle 1 년 전
부모
커밋
bcaf354ccb
24개의 변경된 파일253개의 추가작업 그리고 61개의 파일을 삭제
  1. 3 1
      src/Renci.SshNet/IServiceFactory.cs
  2. 4 3
      src/Renci.SshNet/ServiceFactory.cs
  3. 79 30
      src/Renci.SshNet/ShellStream.cs
  4. 67 4
      src/Renci.SshNet/SshClient.cs
  5. 1 1
      test/Renci.SshNet.IntegrationTests/ConnectivityTests.cs
  6. 4 1
      test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_ChannelOpenThrowsException.cs
  7. 4 1
      test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestReturnsFalse.cs
  8. 4 1
      test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestThrowsException.cs
  9. 4 1
      test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestReturnsFalse.cs
  10. 4 1
      test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestThrowsException.cs
  11. 4 1
      test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_Success.cs
  12. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest.cs
  13. 18 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_ReadExpect.cs
  14. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs
  15. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs
  16. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs
  17. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs
  18. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs
  19. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs
  20. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs
  21. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs
  22. 4 1
      test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs
  23. 8 2
      test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs
  24. 9 3
      test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs

+ 3 - 1
src/Renci.SshNet/IServiceFactory.cs

@@ -114,6 +114,7 @@ namespace Renci.SshNet
         /// <param name="height">The terminal height in pixels.</param>
         /// <param name="terminalModeValues">The terminal mode values.</param>
         /// <param name="bufferSize">Size of the buffer.</param>
+        /// <param name="expectSize">Size of the expect buffer.</param>
         /// <returns>
         /// The created <see cref="ShellStream"/> instance.
         /// </returns>
@@ -135,7 +136,8 @@ namespace Renci.SshNet
                                       uint width,
                                       uint height,
                                       IDictionary<TerminalModes, uint> terminalModeValues,
-                                      int bufferSize);
+                                      int bufferSize,
+                                      int expectSize);
 
         /// <summary>
         /// Creates an <see cref="IRemotePathTransformation"/> that encloses a path in double quotes, and escapes

+ 4 - 3
src/Renci.SshNet/ServiceFactory.cs

@@ -187,6 +187,7 @@ namespace Renci.SshNet
         /// <param name="height">The terminal height in pixels.</param>
         /// <param name="terminalModeValues">The terminal mode values.</param>
         /// <param name="bufferSize">The size of the buffer.</param>
+        /// <param name="expectSize">The size of the expect buffer.</param>
         /// <returns>
         /// The created <see cref="ShellStream"/> instance.
         /// </returns>
@@ -194,16 +195,16 @@ namespace Renci.SshNet
         /// <remarks>
         /// <para>
         /// The <c>TERM</c> environment variable contains an identifier for the text window's capabilities.
-        /// You can get a detailed list of these cababilities by using the ‘infocmp’ command.
+        /// You can get a detailed list of these capabilities by using the ‘infocmp’ command.
         /// </para>
         /// <para>
         /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer
         /// to the drawable area of the window.
         /// </para>
         /// </remarks>
-        public ShellStream CreateShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues, int bufferSize)
+        public ShellStream CreateShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues, int bufferSize, int expectSize)
         {
-            return new ShellStream(session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize);
+            return new ShellStream(session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize, expectSize);
         }
 
         /// <summary>

+ 79 - 30
src/Renci.SshNet/ShellStream.cs

@@ -22,6 +22,8 @@ namespace Renci.SshNet
         private readonly Encoding _encoding;
         private readonly int _bufferSize;
         private readonly Queue<byte> _incoming;
+        private readonly int _expectSize;
+        private readonly Queue<byte> _expect;
         private readonly Queue<byte> _outgoing;
         private IChannelSession _channel;
         private AutoResetEvent _dataReceived = new AutoResetEvent(initialState: false);
@@ -76,15 +78,28 @@ namespace Renci.SshNet
         /// <param name="height">The terminal height in pixels.</param>
         /// <param name="terminalModeValues">The terminal mode values.</param>
         /// <param name="bufferSize">The size of the buffer.</param>
+        /// <param name="expectSize">The size of the expect buffer.</param>
         /// <exception cref="SshException">The channel could not be opened.</exception>
         /// <exception cref="SshException">The pseudo-terminal request was not accepted by the server.</exception>
         /// <exception cref="SshException">The request to start a shell was not accepted by the server.</exception>
-        internal ShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues, int bufferSize)
+        internal ShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues, int bufferSize, int expectSize)
         {
+            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}.");
+            }
+
             _encoding = session.ConnectionInfo.Encoding;
             _session = session;
             _bufferSize = bufferSize;
             _incoming = new Queue<byte>();
+            _expectSize = expectSize;
+            _expect = new Queue<byte>(_expectSize);
             _outgoing = new Queue<byte>();
 
             _channel = _session.CreateChannelSession();
@@ -248,35 +263,40 @@ namespace Renci.SshNet
         public void Expect(TimeSpan timeout, params ExpectAction[] expectActions)
         {
             var expectedFound = false;
-            var text = string.Empty;
+            var matchText = string.Empty;
 
             do
             {
                 lock (_incoming)
                 {
-                    if (_incoming.Count > 0)
+                    if (_expect.Count > 0)
                     {
-                        text = _encoding.GetString(_incoming.ToArray(), 0, _incoming.Count);
+                        matchText = _encoding.GetString(_expect.ToArray(), 0, _expect.Count);
                     }
 
-                    if (text.Length > 0)
+                    if (matchText.Length > 0)
                     {
                         foreach (var expectAction in expectActions)
                         {
-                            var match = expectAction.Expect.Match(text);
+                            var match = expectAction.Expect.Match(matchText);
 
                             if (match.Success)
                             {
-                                var result = text.Substring(0, match.Index + match.Length);
-                                var charCount = _encoding.GetByteCount(result);
+                                var returnText = matchText.Substring(0, match.Index + match.Length);
+                                var returnLength = _encoding.GetByteCount(returnText);
 
-                                for (var i = 0; i < charCount && _incoming.Count > 0; i++)
+                                // Remove processed items from the queue
+                                for (var i = 0; i < returnLength && _incoming.Count > 0; i++)
                                 {
-                                    // Remove processed items from the queue
+                                    if (_expect.Count == _incoming.Count)
+                                    {
+                                        _ = _expect.Dequeue();
+                                    }
+
                                     _ = _incoming.Dequeue();
                                 }
 
-                                expectAction.Action(result);
+                                expectAction.Action(returnText);
                                 expectedFound = true;
                             }
                         }
@@ -349,27 +369,33 @@ namespace Renci.SshNet
         /// </returns>
         public string Expect(Regex regex, TimeSpan timeout)
         {
-            var result = string.Empty;
+            var matchText = string.Empty;
+            string returnText;
 
             while (true)
             {
                 lock (_incoming)
                 {
-                    if (_incoming.Count > 0)
+                    if (_expect.Count > 0)
                     {
-                        result = _encoding.GetString(_incoming.ToArray(), 0, _incoming.Count);
+                        matchText = _encoding.GetString(_expect.ToArray(), 0, _expect.Count);
                     }
 
-                    var match = regex.Match(result);
+                    var match = regex.Match(matchText);
 
                     if (match.Success)
                     {
-                        result = result.Substring(0, match.Index + match.Length);
-                        var charCount = _encoding.GetByteCount(result);
+                        returnText = matchText.Substring(0, match.Index + match.Length);
+                        var returnLength = _encoding.GetByteCount(returnText);
 
                         // Remove processed items from the queue
-                        for (var i = 0; i < charCount && _incoming.Count > 0; i++)
+                        for (var i = 0; i < returnLength && _incoming.Count > 0; i++)
                         {
+                            if (_expect.Count == _incoming.Count)
+                            {
+                                _ = _expect.Dequeue();
+                            }
+
                             _ = _incoming.Dequeue();
                         }
 
@@ -390,7 +416,7 @@ namespace Renci.SshNet
                 }
             }
 
-            return result;
+            return returnText;
         }
 
         /// <summary>
@@ -446,7 +472,8 @@ namespace Renci.SshNet
         public IAsyncResult BeginExpect(TimeSpan timeout, AsyncCallback callback, object state, params ExpectAction[] expectActions)
 #pragma warning restore CA1859 // Use concrete types when possible for improved performance
         {
-            var text = string.Empty;
+            var matchText = string.Empty;
+            string returnText;
 
             // Create new AsyncResult object
             var asyncResult = new ExpectAsyncResult(callback, state);
@@ -461,31 +488,36 @@ namespace Renci.SshNet
                     {
                         lock (_incoming)
                         {
-                            if (_incoming.Count > 0)
+                            if (_expect.Count > 0)
                             {
-                                text = _encoding.GetString(_incoming.ToArray(), 0, _incoming.Count);
+                                matchText = _encoding.GetString(_expect.ToArray(), 0, _expect.Count);
                             }
 
-                            if (text.Length > 0)
+                            if (matchText.Length > 0)
                             {
                                 foreach (var expectAction in expectActions)
                                 {
-                                    var match = expectAction.Expect.Match(text);
+                                    var match = expectAction.Expect.Match(matchText);
 
                                     if (match.Success)
                                     {
-                                        var result = text.Substring(0, match.Index + match.Length);
-                                        var charCount = _encoding.GetByteCount(result);
+                                        returnText = matchText.Substring(0, match.Index + match.Length);
+                                        var returnLength = _encoding.GetByteCount(returnText);
 
-                                        for (var i = 0; i < match.Index + match.Length && _incoming.Count > 0; i++)
+                                        // Remove processed items from the queue
+                                        for (var i = 0; i < returnLength && _incoming.Count > 0; i++)
                                         {
-                                            // Remove processed items from the queue
+                                            if (_expect.Count == _incoming.Count)
+                                            {
+                                                _ = _expect.Dequeue();
+                                            }
+
                                             _ = _incoming.Dequeue();
                                         }
 
-                                        expectAction.Action(result);
+                                        expectAction.Action(returnText);
                                         callback?.Invoke(asyncResult);
-                                        expectActionResult = result;
+                                        expectActionResult = returnText;
                                     }
                                 }
                             }
@@ -584,6 +616,11 @@ namespace Renci.SshNet
                         // remove processed bytes from the queue
                         for (var i = 0; i < bytesProcessed; i++)
                         {
+                            if (_expect.Count == _incoming.Count)
+                            {
+                                _ = _expect.Dequeue();
+                            }
+
                             _ = _incoming.Dequeue();
                         }
 
@@ -620,6 +657,7 @@ namespace Renci.SshNet
             lock (_incoming)
             {
                 text = _encoding.GetString(_incoming.ToArray(), 0, _incoming.Count);
+                _expect.Clear();
                 _incoming.Clear();
             }
 
@@ -649,6 +687,11 @@ namespace Renci.SshNet
             {
                 for (; i < count && _incoming.Count > 0; i++)
                 {
+                    if (_expect.Count == _incoming.Count)
+                    {
+                        _ = _expect.Dequeue();
+                    }
+
                     buffer[offset + i] = _incoming.Dequeue();
                 }
             }
@@ -800,6 +843,12 @@ namespace Renci.SshNet
                 foreach (var b in e.Data)
                 {
                     _incoming.Enqueue(b);
+                    if (_expect.Count == _expectSize)
+                    {
+                        _ = _expect.Dequeue();
+                    }
+
+                    _expect.Enqueue(b);
                 }
             }
 

+ 67 - 4
src/Renci.SshNet/SshClient.cs

@@ -407,7 +407,7 @@ namespace Renci.SshNet
         /// <remarks>
         /// <para>
         /// The <c>TERM</c> environment variable contains an identifier for the text window's capabilities.
-        /// You can get a detailed list of these cababilities by using the ‘infocmp’ command.
+        /// You can get a detailed list of these capabilities by using the ‘infocmp’ command.
         /// </para>
         /// <para>
         /// The column/row dimensions override the pixel dimensions(when nonzero). Pixel dimensions refer
@@ -416,7 +416,38 @@ namespace Renci.SshNet
         /// </remarks>
         public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize)
         {
-            return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, terminalModeValues: null);
+            return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, bufferSize * 2, terminalModeValues: null);
+        }
+
+        /// <summary>
+        /// Creates the shell stream.
+        /// </summary>
+        /// <param name="terminalName">The <c>TERM</c> environment variable.</param>
+        /// <param name="columns">The terminal width in columns.</param>
+        /// <param name="rows">The terminal width in rows.</param>
+        /// <param name="width">The terminal width in pixels.</param>
+        /// <param name="height">The terminal height in pixels.</param>
+        /// <param name="bufferSize">The size of the buffer.</param>
+        /// <param name="expectSize">The size of the expect buffer.</param>
+        /// <returns>
+        /// The created <see cref="ShellStream"/> instance.
+        /// </returns>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
+        /// <remarks>
+        /// <para>
+        /// The <c>TERM</c> 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.
+        /// </para>
+        /// <para>
+        /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer
+        /// to the drawable area of the window.
+        /// </para>
+        /// </remarks>
+        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);
         }
 
         /// <summary>
@@ -436,7 +467,7 @@ namespace Renci.SshNet
         /// <remarks>
         /// <para>
         /// The <c>TERM</c> environment variable contains an identifier for the text window's capabilities.
-        /// You can get a detailed list of these cababilities by using the ‘infocmp’ command.
+        /// You can get a detailed list of these capabilities by using the ‘infocmp’ command.
         /// </para>
         /// <para>
         /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer
@@ -447,7 +478,39 @@ namespace Renci.SshNet
         {
             EnsureSessionIsOpen();
 
-            return ServiceFactory.CreateShellStream(Session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize);
+            return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, bufferSize * 2, terminalModeValues);
+        }
+
+        /// <summary>
+        /// Creates the shell stream.
+        /// </summary>
+        /// <param name="terminalName">The <c>TERM</c> environment variable.</param>
+        /// <param name="columns">The terminal width in columns.</param>
+        /// <param name="rows">The terminal width in rows.</param>
+        /// <param name="width">The terminal width in pixels.</param>
+        /// <param name="height">The terminal height in pixels.</param>
+        /// <param name="bufferSize">The size of the buffer.</param>
+        /// <param name="expectSize">The size of the expect buffer.</param>
+        /// <param name="terminalModeValues">The terminal mode values.</param>
+        /// <returns>
+        /// The created <see cref="ShellStream"/> instance.
+        /// </returns>
+        /// <exception cref="SshConnectionException">Client is not connected.</exception>
+        /// <remarks>
+        /// <para>
+        /// The <c>TERM</c> 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.
+        /// </para>
+        /// <para>
+        /// The column/row dimensions override the pixel dimensions(when non-zero). Pixel dimensions refer
+        /// to the drawable area of the window.
+        /// </para>
+        /// </remarks>
+        public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, int expectSize, IDictionary<TerminalModes, uint> terminalModeValues)
+        {
+            EnsureSessionIsOpen();
+
+            return ServiceFactory.CreateShellStream(Session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize, expectSize);
         }
 
         /// <summary>

+ 1 - 1
test/Renci.SshNet.IntegrationTests/ConnectivityTests.cs

@@ -44,7 +44,7 @@ namespace Renci.SshNet.IntegrationTests
                 // if the channel would not be properly closed
                 for (var i = 0; i < connectionInfo.MaxSessions + 1; i++)
                 {
-                    using (var stream = client.CreateShellStream("vt220", 20, 20, 20, 20, 0))
+                    using (var stream = client.CreateShellStream("vt220", 20, 20, 20, 20, 20))
                     {
                         stream.WriteLine("echo test");
                         stream.ReadLine();

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_ChannelOpenThrowsException.cs

@@ -22,6 +22,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _height;
         private IDictionary<TerminalModes, uint> _terminalModeValues;
         private int _bufferSize;
+        private int _expectSize;
         private SshException _channelOpenException;
         private SshException _actualException;
 
@@ -36,6 +37,7 @@ namespace Renci.SshNet.Tests.Classes
             _height = (uint) random.Next();
             _terminalModeValues = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next();
+            _expectSize = _bufferSize;
             _channelOpenException = new SshException();
 
             _actualException = null;
@@ -95,7 +97,8 @@ namespace Renci.SshNet.Tests.Classes
                                                   _width,
                                                   _height,
                                                   _terminalModeValues,
-                                                  _bufferSize);
+                                                  _bufferSize,
+                                                  _expectSize);
                 Assert.Fail();
             }
             catch (SshException ex)

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestReturnsFalse.cs

@@ -22,6 +22,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _height;
         private IDictionary<TerminalModes, uint> _terminalModeValues;
         private int _bufferSize;
+        private int _expectSize;
         private SshException _actualException;
 
         private void SetupData()
@@ -35,6 +36,7 @@ namespace Renci.SshNet.Tests.Classes
             _height = (uint)random.Next();
             _terminalModeValues = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next();
+            _expectSize = _bufferSize;
             _actualException = null;
         }
 
@@ -94,7 +96,8 @@ namespace Renci.SshNet.Tests.Classes
                                                   _width,
                                                   _height,
                                                   _terminalModeValues,
-                                                  _bufferSize);
+                                                  _bufferSize,
+                                                  _expectSize);
                 Assert.Fail();
             }
             catch (SshException ex)

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendPseudoTerminalRequestThrowsException.cs

@@ -22,6 +22,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _height;
         private IDictionary<TerminalModes, uint> _terminalModeValues;
         private int _bufferSize;
+        private int _expectSize;
         private SshException _sendPseudoTerminalRequestException;
         private SshException _actualException;
 
@@ -36,6 +37,7 @@ namespace Renci.SshNet.Tests.Classes
             _height = (uint) random.Next();
             _terminalModeValues = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next();
+            _expectSize = _bufferSize;
             _sendPseudoTerminalRequestException = new SshException();
 
             _actualException = null;
@@ -97,7 +99,8 @@ namespace Renci.SshNet.Tests.Classes
                                                   _width,
                                                   _height,
                                                   _terminalModeValues,
-                                                  _bufferSize);
+                                                  _bufferSize,
+                                                  _expectSize);
                 Assert.Fail();
             }
             catch (SshException ex)

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestReturnsFalse.cs

@@ -22,6 +22,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _height;
         private IDictionary<TerminalModes, uint> _terminalModeValues;
         private int _bufferSize;
+        private int _expectSize;
         private SshException _actualException;
 
         private void SetupData()
@@ -35,6 +36,7 @@ namespace Renci.SshNet.Tests.Classes
             _height = (uint) random.Next();
             _terminalModeValues = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next();
+            _expectSize = _bufferSize;
             _actualException = null;
         }
 
@@ -97,7 +99,8 @@ namespace Renci.SshNet.Tests.Classes
                                                   _width,
                                                   _height,
                                                   _terminalModeValues,
-                                                  _bufferSize);
+                                                  _bufferSize,
+                                                  _expectSize);
                 Assert.Fail();
             }
             catch (SshException ex)

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_SendShellRequestThrowsException.cs

@@ -22,6 +22,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _height;
         private IDictionary<TerminalModes, uint> _terminalModeValues;
         private int _bufferSize;
+        private int _expectSize;
         private SshException _sendShellRequestException;
         private SshException _actualException;
 
@@ -36,6 +37,7 @@ namespace Renci.SshNet.Tests.Classes
             _height = (uint) random.Next();
             _terminalModeValues = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next();
+            _expectSize = _bufferSize;
             _sendShellRequestException = new SshException();
             _actualException = null;
         }
@@ -99,7 +101,8 @@ namespace Renci.SshNet.Tests.Classes
                                                   _width,
                                                   _height,
                                                   _terminalModeValues,
-                                                  _bufferSize);
+                                                  _bufferSize,
+                                                  _expectSize);
                 Assert.Fail();
             }
             catch (SshException ex)

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateShellStream_Success.cs

@@ -22,6 +22,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _height;
         private IDictionary<TerminalModes, uint> _terminalModeValues;
         private int _bufferSize;
+        private int _expectSize;
         private ShellStream _shellStream;
 
         private void SetupData()
@@ -35,6 +36,7 @@ namespace Renci.SshNet.Tests.Classes
             _height = (uint) random.Next();
             _terminalModeValues = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next();
+            _expectSize = _bufferSize;
         }
 
         private void CreateMocks()
@@ -97,7 +99,8 @@ namespace Renci.SshNet.Tests.Classes
                                                              _width,
                                                              _height,
                                                              _terminalModeValues,
-                                                             _bufferSize);
+                                                             _bufferSize,
+                                                             _expectSize);
         }
 
         [TestMethod]

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest.cs

@@ -27,6 +27,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _heightPixels;
         private Dictionary<TerminalModes, uint> _terminalModes;
         private int _bufferSize;
+        private int _expectSize;
         private Mock<IChannelSession> _channelSessionMock;
 
         protected override void OnInit()
@@ -41,6 +42,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint)random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 500);
+            _expectSize = random.Next(100, _bufferSize);
 
             _encoding = Encoding.UTF8;
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
@@ -120,7 +122,8 @@ namespace Renci.SshNet.Tests.Classes
                                    _widthPixels,
                                    _heightPixels,
                                    _terminalModes,
-                                   _bufferSize);
+                                   _bufferSize,
+                                   _expectSize);
         }
     }
 }

+ 18 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_ReadExpect.cs

@@ -42,7 +42,8 @@ namespace Renci.SshNet.Tests.Classes
                 width: 800,
                 height: 600,
                 terminalModeValues: null,
-                bufferSize: 1024);
+                bufferSize: 1024,
+                expectSize: 2048);
         }
 
         [TestMethod]
@@ -227,6 +228,22 @@ namespace Renci.SshNet.Tests.Classes
             Assert.AreEqual("んにちは, Bonjour", _shellStream.Read());
         }
 
+        [TestMethod]
+        public void Expect_String_LargeExpect()
+        {
+            _channelSessionStub.Receive(Encoding.UTF8.GetBytes(new string('a', 100)));
+            for (var i = 0; i < 10; i++)
+            {
+                _channelSessionStub.Receive(Encoding.UTF8.GetBytes(new string('b', 100)));
+            }
+            _channelSessionStub.Receive(Encoding.UTF8.GetBytes("Hello, こんにちは, Bonjour"));
+            _channelSessionStub.Receive(Encoding.UTF8.GetBytes(new string('c', 100)));
+
+            Assert.AreEqual($"{new string('a', 100)}{new string('b', 1000)}Hello, こんにちは, Bonjour", _shellStream.Expect($"{new string('b', 1000)}Hello, こんにちは, Bonjour"));
+
+            Assert.AreEqual($"{new string('c', 100)}", _shellStream.Read());
+        }
+
         [TestMethod]
         public void Expect_Timeout()
         {

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteLessBytesThanBufferSize.cs

@@ -23,6 +23,7 @@ namespace Renci.SshNet.Tests.Classes
         private Dictionary<TerminalModes, uint> _terminalModes;
         private ShellStream _shellStream;
         private int _bufferSize;
+        private int _expectSize;
 
         private byte[] _data;
         private int _offset;
@@ -47,6 +48,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint) random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _data = CryptoAbstraction.GenerateRandom(_bufferSize - 10);
             _offset = random.Next(1, 5);
@@ -101,7 +103,8 @@ namespace Renci.SshNet.Tests.Classes
                                            _widthPixels,
                                            _heightPixels,
                                            _terminalModes,
-                                           _bufferSize);
+                                           _bufferSize,
+                                           _expectSize);
         }
 
         private void Act()

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteMoreBytesThanBufferSize.cs

@@ -24,6 +24,7 @@ namespace Renci.SshNet.Tests.Classes
         private Dictionary<TerminalModes, uint> _terminalModes;
         private ShellStream _shellStream;
         private int _bufferSize;
+        private int _expectSize;
 
         private byte[] _data;
         private int _offset;
@@ -50,6 +51,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint)random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _data = CryptoAbstraction.GenerateRandom((_bufferSize * 2) + 10);
             _offset = 0;
@@ -111,7 +113,8 @@ namespace Renci.SshNet.Tests.Classes
                                            _widthPixels,
                                            _heightPixels,
                                            _terminalModes,
-                                           _bufferSize);
+                                           _bufferSize,
+                                           _expectSize);
         }
 
         private void Act()

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteNumberOfBytesEqualToBufferSize.cs

@@ -23,6 +23,7 @@ namespace Renci.SshNet.Tests.Classes
         private Dictionary<TerminalModes, uint> _terminalModes;
         private ShellStream _shellStream;
         private int _bufferSize;
+        private int _expectSize;
 
         private byte[] _data;
         private int _offset;
@@ -47,6 +48,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint)random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _data = CryptoAbstraction.GenerateRandom(_bufferSize);
             _offset = 0;
@@ -101,7 +103,8 @@ namespace Renci.SshNet.Tests.Classes
                                            _widthPixels,
                                            _heightPixels,
                                            _terminalModes,
-                                           _bufferSize);
+                                           _bufferSize,
+                                           _expectSize);
         }
 
         private void Act()

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferEmptyAndWriteZeroBytes.cs

@@ -25,6 +25,7 @@ namespace Renci.SshNet.Tests.Classes
         private Dictionary<TerminalModes, uint> _terminalModes;
         private ShellStream _shellStream;
         private int _bufferSize;
+        private int _expectSize;
 
         private byte[] _data;
         private int _offset;
@@ -49,6 +50,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint)random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _data = new byte[0];
             _offset = 0;
@@ -103,7 +105,8 @@ namespace Renci.SshNet.Tests.Classes
                                            _widthPixels,
                                            _heightPixels,
                                            _terminalModes,
-                                           _bufferSize);
+                                           _bufferSize,
+                                           _expectSize);
         }
 
         private void Act()

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteLessBytesThanBufferSize.cs

@@ -23,6 +23,7 @@ namespace Renci.SshNet.Tests.Classes
         private Dictionary<TerminalModes, uint> _terminalModes;
         private ShellStream _shellStream;
         private int _bufferSize;
+        private int _expectSize;
 
         private byte[] _data;
         private int _offset;
@@ -48,6 +49,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint)random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize);
             _data = CryptoAbstraction.GenerateRandom(_bufferSize - 10);
@@ -105,7 +107,8 @@ namespace Renci.SshNet.Tests.Classes
                                            _widthPixels,
                                            _heightPixels,
                                            _terminalModes,
-                                           _bufferSize);
+                                           _bufferSize,
+                                           _expectSize);
 
             _shellStream.Write(_bufferData, 0, _bufferData.Length);
         }

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferFullAndWriteZeroBytes.cs

@@ -23,6 +23,7 @@ namespace Renci.SshNet.Tests.Classes
         private Dictionary<TerminalModes, uint> _terminalModes;
         private ShellStream _shellStream;
         private int _bufferSize;
+        private int _expectSize;
 
         private byte[] _data;
         private int _offset;
@@ -48,6 +49,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint) random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize);
             _data = new byte[0];
@@ -103,7 +105,8 @@ namespace Renci.SshNet.Tests.Classes
                                            _widthPixels,
                                            _heightPixels,
                                            _terminalModes,
-                                           _bufferSize);
+                                           _bufferSize,
+                                           _expectSize);
 
             _shellStream.Write(_bufferData, 0, _bufferData.Length);
         }

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteLessBytesThanBufferCanContain.cs

@@ -23,6 +23,7 @@ namespace Renci.SshNet.Tests.Classes
         private Dictionary<TerminalModes, uint> _terminalModes;
         private ShellStream _shellStream;
         private int _bufferSize;
+        private int _expectSize;
 
         private byte[] _data;
         private int _offset;
@@ -48,6 +49,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint) random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize - 60);
             _data = CryptoAbstraction.GenerateRandom(_bufferSize + 100);
@@ -103,7 +105,8 @@ namespace Renci.SshNet.Tests.Classes
                                            _widthPixels,
                                            _heightPixels,
                                            _terminalModes,
-                                           _bufferSize);
+                                           _bufferSize,
+                                           _expectSize);
 
             _shellStream.Write(_bufferData, 0, _bufferData.Length);
         }

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs

@@ -24,6 +24,7 @@ namespace Renci.SshNet.Tests.Classes
         private Dictionary<TerminalModes, uint> _terminalModes;
         private ShellStream _shellStream;
         private int _bufferSize;
+        private int _expectSize;
 
         private byte[] _data;
         private int _offset;
@@ -50,6 +51,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint) random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _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));
@@ -111,7 +113,8 @@ namespace Renci.SshNet.Tests.Classes
                                            _widthPixels,
                                            _heightPixels,
                                            _terminalModes,
-                                           _bufferSize);
+                                           _bufferSize,
+                                           _expectSize);
 
             _shellStream.Write(_bufferData, 0, _bufferData.Length);
         }

+ 4 - 1
test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteZeroBytes.cs

@@ -23,6 +23,7 @@ namespace Renci.SshNet.Tests.Classes
         private Dictionary<TerminalModes, uint> _terminalModes;
         private ShellStream _shellStream;
         private int _bufferSize;
+        private int _expectSize;
 
         private byte[] _data;
         private int _offset;
@@ -48,6 +49,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint)random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _bufferData = CryptoAbstraction.GenerateRandom(_bufferSize - 60);
             _data = new byte[0];
@@ -103,7 +105,8 @@ namespace Renci.SshNet.Tests.Classes
                                            _widthPixels,
                                            _heightPixels,
                                            _terminalModes,
-                                           _bufferSize);
+                                           _bufferSize,
+                                           _expectSize);
 
             _shellStream.Write(_bufferData, 0, _bufferData.Length);
         }

+ 8 - 2
test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs

@@ -19,6 +19,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _heightPixels;
         private Dictionary<TerminalModes, uint> _terminalModes;
         private int _bufferSize;
+        private int _expectSize;
         private ShellStream _expected;
         private ShellStream _actual;
 
@@ -35,6 +36,7 @@ namespace Renci.SshNet.Tests.Classes
             _heightPixels = (uint) random.Next();
             _terminalModes = new Dictionary<TerminalModes, uint>();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _expected = CreateShellStream();
         }
@@ -59,7 +61,8 @@ namespace Renci.SshNet.Tests.Classes
                                                                _widthPixels,
                                                                _heightPixels,
                                                                _terminalModes,
-                                                               _bufferSize))
+                                                               _bufferSize,
+                                                               _expectSize))
                                .Returns(_expected);
         }
 
@@ -79,6 +82,7 @@ namespace Renci.SshNet.Tests.Classes
                                                    _widthPixels,
                                                    _heightPixels,
                                                    _bufferSize,
+                                                   _expectSize,
                                                    _terminalModes);
         }
 
@@ -92,7 +96,8 @@ namespace Renci.SshNet.Tests.Classes
                                                                 _widthPixels,
                                                                 _heightPixels,
                                                                 _terminalModes,
-                                                                _bufferSize),
+                                                                _bufferSize,
+                                                                _expectSize),
                                        Times.Once);
         }
 
@@ -130,6 +135,7 @@ namespace Renci.SshNet.Tests.Classes
                                    _widthPixels,
                                    _heightPixels,
                                    _terminalModes,
+                                   1,
                                    1);
         }
     }

+ 9 - 3
test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs

@@ -19,6 +19,7 @@ namespace Renci.SshNet.Tests.Classes
         private uint _widthPixels;
         private uint _heightPixels;
         private int _bufferSize;
+        private int _expectSize;
         private ShellStream _expected;
         private ShellStream _actual;
 
@@ -34,6 +35,7 @@ namespace Renci.SshNet.Tests.Classes
             _widthPixels = (uint)random.Next();
             _heightPixels = (uint)random.Next();
             _bufferSize = random.Next(100, 1000);
+            _expectSize = random.Next(100, _bufferSize);
 
             _expected = CreateShellStream();
         }
@@ -58,7 +60,8 @@ namespace Renci.SshNet.Tests.Classes
                                                                    _widthPixels,
                                                                    _heightPixels,
                                                                    null,
-                                                                   _bufferSize))
+                                                                   _bufferSize,
+                                                                   _expectSize))
                                    .Returns(_expected);
         }
 
@@ -77,7 +80,8 @@ namespace Renci.SshNet.Tests.Classes
                                                    _heightRows,
                                                    _widthPixels,
                                                    _heightPixels,
-                                                   _bufferSize);
+                                                   _bufferSize,
+                                                   _expectSize);
         }
 
         [TestMethod]
@@ -90,7 +94,8 @@ namespace Renci.SshNet.Tests.Classes
                                                                 _widthPixels,
                                                                 _heightPixels,
                                                                 null,
-                                                                _bufferSize),
+                                                                _bufferSize,
+                                                                _expectSize),
                                        Times.Once);
         }
 
@@ -128,6 +133,7 @@ namespace Renci.SshNet.Tests.Classes
                                    _widthPixels,
                                    _heightPixels,
                                    null,
+                                   1,
                                    1);
         }
     }