From 2830ba9c6c86b5dc5f669fec33dfc4f8fafdade5 Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Thu, 30 Aug 2018 01:25:30 -0500 Subject: [PATCH] Fix for issues #180 #444 #301 #302 --- .../Classes/ShellStreamTest.cs | 113 ++++++++++++++++++ src/Renci.SshNet/ShellStream.cs | 21 +++- 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/src/Renci.SshNet.Tests/Classes/ShellStreamTest.cs b/src/Renci.SshNet.Tests/Classes/ShellStreamTest.cs index 7a9160c2e..3131ec187 100644 --- a/src/Renci.SshNet.Tests/Classes/ShellStreamTest.cs +++ b/src/Renci.SshNet.Tests/Classes/ShellStreamTest.cs @@ -101,6 +101,7 @@ public void WriteLine_Line_ShouldOnlyWriteLineTerminatorWhenLineIsNull() _channelSessionMock.Setup(p => p.SendData(lineTerminator)); shellStream.WriteLine(line); + shellStream.Flush(); _channelSessionMock.Verify(p => p.SendData(lineTerminator), Times.Once); } @@ -153,6 +154,118 @@ public void Disconnection_Event_ShouldRaiseWhenIChannelDisconnected() Assert.IsTrue(shellStream.Disposed); } + [TestMethod] + public void Write_AlwaysUsesUnderlyingBuffer() + { + var shellStream = CreateShellStream(); + var command1 = "abcd\r"; + var command2 = "efgh\r"; + var command1Bytes = _encoding.GetBytes(command1); + var command2Bytes = _encoding.GetBytes(command2); + var expectedBytes = command1Bytes.Concat(command2Bytes); + + _channelSessionMock.Setup(p => p.SendData(It.IsAny())); + + shellStream.Write(command1Bytes, 0, command1Bytes.Length); + shellStream.Write(command2); + shellStream.Flush(); + + _channelSessionMock.Verify(p => p.SendData(expectedBytes), Times.Once); + } + + [TestMethod] + public void Expect_ShouldAlwaysFlushTheWriteBuffer() + { + var shellStream = CreateShellStream(); + var command1 = "abcd\r"; + var command2 = "efgh\r"; + var command1Bytes = _encoding.GetBytes(command1); + var command2Bytes = _encoding.GetBytes(command2); + var expectedBytes = command1Bytes.Concat(command2Bytes); + + _channelSessionMock.Setup(p => p.SendData(It.IsAny())) + .Raises(p => p.DataReceived += null, new ChannelDataEventArgs(0, expectedBytes)); + + + shellStream.Write(command1Bytes, 0, command1Bytes.Length); + shellStream.Write(command2); + var output = shellStream.Expect("h\r", TimeSpan.FromMilliseconds(1)); + + _channelSessionMock.Verify(p => p.SendData(expectedBytes), Times.Once); + Assert.AreEqual(command1 + command2, output); + } + + [TestMethod] + public void Read_ShouldAlwaysFlushTheWriteBuffer() + { + var shellStream = CreateShellStream(); + var command1 = "abcd\r"; + var command2 = "efgh\r"; + var command1Bytes = _encoding.GetBytes(command1); + var command2Bytes = _encoding.GetBytes(command2); + var expectedBytes = command1Bytes.Concat(command2Bytes); + + _channelSessionMock.Setup(p => p.SendData(It.IsAny())) + .Raises(p => p.DataReceived += null, new ChannelDataEventArgs(0, expectedBytes)); + + + shellStream.Write(command1Bytes, 0, command1Bytes.Length); + shellStream.Write(command2); + var output = shellStream.Read(); + + _channelSessionMock.Verify(p => p.SendData(expectedBytes), Times.Once); + Assert.AreEqual(command1 + command2, output); + } + + [TestMethod] + public void Read_Bytes_ShouldAlwaysFlushTheWriteBuffer() + { + var shellStream = CreateShellStream(); + var command1 = "abcd\r"; + var command2 = "efgh\r"; + var command1Bytes = _encoding.GetBytes(command1); + var command2Bytes = _encoding.GetBytes(command2); + var expectedBytes = command1Bytes.Concat(command2Bytes); + + _channelSessionMock.Setup(p => p.SendData(It.IsAny())) + .Raises(p => p.DataReceived += null, new ChannelDataEventArgs(0, expectedBytes)); + + + shellStream.Write(command1Bytes, 0, command1Bytes.Length); + shellStream.Write(command2); + var output = new byte[expectedBytes.Length]; + var count = shellStream.Read(output, 0, output.Length); + + _channelSessionMock.Verify(p => p.SendData(expectedBytes), Times.Once); + CollectionAssert.AreEqual(expectedBytes, output); + Assert.AreEqual(expectedBytes.Length, count); + } + + [TestMethod] + public void ReadLine_ShouldAlwaysFlushTheWriteBuffer() + { + var shellStream = CreateShellStream(); + var command1 = "abcd\r\n"; + var command2 = "efgh\r\n"; + var command1Bytes = _encoding.GetBytes(command1); + var command2Bytes = _encoding.GetBytes(command2); + var expectedBytes = command1Bytes.Concat(command2Bytes); + + _channelSessionMock.Setup(p => p.SendData(It.IsAny())) + .Raises(p => p.DataReceived += null, new ChannelDataEventArgs(0, expectedBytes)); + + + shellStream.Write(command1Bytes, 0, command1Bytes.Length); + shellStream.Write(command2); + + var output1 = shellStream.ReadLine(TimeSpan.FromMilliseconds(1)); + var output2 = shellStream.ReadLine(TimeSpan.FromMilliseconds(1)); + + _channelSessionMock.Verify(p => p.SendData(expectedBytes), Times.Once); + Assert.AreEqual(command1.Substring(0, command1.Length - 2), output1); + Assert.AreEqual(command2.Substring(0, command2.Length - 2), output2); + } + private ShellStream CreateShellStream() { _sessionMock.Setup(p => p.ConnectionInfo).Returns(_connectionInfoMock.Object); diff --git a/src/Renci.SshNet/ShellStream.cs b/src/Renci.SshNet/ShellStream.cs index 324065c29..71e4fcf1d 100644 --- a/src/Renci.SshNet/ShellStream.cs +++ b/src/Renci.SshNet/ShellStream.cs @@ -231,6 +231,9 @@ public override long Position /// Methods were called after the stream was closed. public override int Read(byte[] buffer, int offset, int count) { + // Flush the write buffer + if (!_isDisposed) Flush(); + var i = 0; lock (_incoming) @@ -318,6 +321,9 @@ public void Expect(TimeSpan timeout, params ExpectAction[] expectActions) var expectedFound = false; var text = string.Empty; + // Flush the write buffer in case expecting output from written data + if (!_isDisposed) Flush(); + do { lock (_incoming) @@ -424,6 +430,9 @@ public IAsyncResult BeginExpect(TimeSpan timeout, AsyncCallback callback, object // Create new AsyncResult object var asyncResult = new ExpectAsyncResult(callback, state); + // Flush the write buffer in case expecting output from previous write + if (!_isDisposed) Flush(); + // Execute callback on different thread ThreadAbstraction.ExecuteThread(() => { @@ -566,6 +575,9 @@ public string Expect(Regex regex, TimeSpan timeout) { var text = string.Empty; + // Flush the write buffer in case expecting output from previous write. + if (!_isDisposed) Flush(); + while (true) { lock (_incoming) @@ -631,6 +643,9 @@ public string ReadLine(TimeSpan timeout) { var text = string.Empty; + // Flush the write buffer + if(!_isDisposed) Flush(); + while (true) { lock (_incoming) @@ -683,6 +698,9 @@ public string ReadLine(TimeSpan timeout) public string Read() { string text; + + // Flush the write buffer + Flush(); lock (_incoming) { @@ -711,7 +729,8 @@ public void Write(string text) } var data = _encoding.GetBytes(text); - _channel.SendData(data); + + Write(data, 0, data.Length); } ///