Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore the write buffer to ShellStream #1327

Merged
merged 3 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 105 additions & 84 deletions src/Renci.SshNet/ShellStream.cs

Large diffs are not rendered by default.

32 changes: 7 additions & 25 deletions test/Renci.SshNet.Tests/Classes/ShellStreamTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void Write_Text_ShouldWriteNothingWhenTextIsNull()

shellStream.Write(text);

_channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>()), Times.Never);
_channelSessionMock.VerifyAll();
}

[TestMethod]
Expand All @@ -95,33 +95,15 @@ public void WriteLine_Line_ShouldOnlyWriteLineTerminatorWhenLineIsNull()
var shellStream = CreateShellStream();
const string line = null;
var lineTerminator = _encoding.GetBytes("\r");

_channelSessionMock.Setup(p => p.SendData(lineTerminator, 0, lineTerminator.Length));

_ = _channelSessionMock.Setup(p => p.SendData(
It.Is<byte[]>(data => data.Take(lineTerminator.Length).IsEqualTo(lineTerminator)),
0,
lineTerminator.Length));

shellStream.WriteLine(line);

_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);
_channelSessionMock.VerifyAll();
}

[TestMethod]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
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<ISession> _sessionMock;
private Mock<IConnectionInfo> _connectionInfoMock;
private Mock<IChannelSession> _channelSessionMock;
private string _terminalName;
private uint _widthColumns;
private uint _heightRows;
private uint _widthPixels;
private uint _heightPixels;
private Dictionary<TerminalModes, uint> _terminalModes;
private ShellStream _shellStream;
private int _bufferSize;

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<TerminalModes, uint>();
_bufferSize = random.Next(100, 1000);

_data = CryptoAbstraction.GenerateRandom(_bufferSize - 10);
_offset = random.Next(1, 5);
_count = _data.Length - _offset - random.Next(1, 10);
}

private void CreateMocks()
{
_sessionMock = new Mock<ISession>(MockBehavior.Strict);
_connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
_channelSessionMock = new Mock<IChannelSession>(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);
}

private void Act()
{
_shellStream.Write(_data, _offset, _count);
}

[TestMethod]
public void NoDataShouldBeSentToServer()
{
_channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never);
}

[TestMethod]
public void FlushShouldSendWrittenBytesToServer()
{
byte[] bytesSent = null;

_channelSessionMock.InSequence(_mockSequence)
.Setup(p => p.SendData(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()))
.Callback<byte[], int, int>((data, offset, count) => bytesSent = data.Take(offset, count));

_shellStream.Flush();

Assert.IsNotNull(bytesSent);
Assert.IsTrue(_data.Take(_offset, _count).IsEqualTo(bytesSent));

_channelSessionMock.Verify(p => p.SendData(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
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<ISession> _sessionMock;
private Mock<IConnectionInfo> _connectionInfoMock;
private Mock<IChannelSession> _channelSessionMock;
private MockSequence _mockSequence;
private string _terminalName;
private uint _widthColumns;
private uint _heightRows;
private uint _widthPixels;
private uint _heightPixels;
private Dictionary<TerminalModes, uint> _terminalModes;
private ShellStream _shellStream;
private int _bufferSize;

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<TerminalModes, uint>();
_bufferSize = random.Next(100, 1000);

_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<ISession>(MockBehavior.Strict);
_connectionInfoMock = new Mock<IConnectionInfo>(MockBehavior.Strict);
_channelSessionMock = new Mock<IChannelSession>(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, 0, _bufferSize));
_ = _channelSessionMock.InSequence(_mockSequence)
.Setup(p => p.SendData(_expectedBytesSent2, 0, _bufferSize));
}

private void Arrange()
{
SetupData();
CreateMocks();
SetupMocks();

_shellStream = new ShellStream(_sessionMock.Object,
_terminalName,
_widthColumns,
_heightRows,
_widthPixels,
_heightPixels,
_terminalModes,
_bufferSize);
}

private void Act()
{
_shellStream.Write(_data, _offset, _count);
}

[TestMethod]
public void BufferShouldHaveBeenFlushedTwice()
{
_channelSessionMock.VerifyAll();
}

[TestMethod]
public void FlushShouldSendRemaningBytesToServer()
{
var expectedBytesSent = _data.Take(_bufferSize * 2, _data.Length - (_bufferSize * 2));

_ = _channelSessionMock.InSequence(_mockSequence)
.Setup(p => p.SendData(
It.Is<byte[]>(data => data.Take(expectedBytesSent.Length).IsEqualTo(expectedBytesSent)),
0,
expectedBytesSent.Length));

_shellStream.Flush();

_channelSessionMock.VerifyAll();
}
}
}
Loading