Skip to content

SftpFileStream: Improve SetLength(long) compatibility with FileStream #272

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

Merged
merged 4 commits into from
Aug 14, 2017
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
18 changes: 17 additions & 1 deletion src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>5</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
Expand Down Expand Up @@ -1232,6 +1233,18 @@
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_Closed.cs">
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_Closed.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs">
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs">
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs">
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs">
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_Disposed.cs">
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_Disposed.cs</Link>
</Compile>
Expand Down Expand Up @@ -1424,6 +1437,9 @@
<Compile Include="..\Renci.SshNet.Tests\Common\AsyncSocketListener.cs">
<Link>Common\AsyncSocketListener.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet.Tests\Common\DictionaryAssert.cs">
<Link>Common\DictionaryAssert.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet.Tests\Common\Extensions.cs">
<Link>Common\Extensions.cs</Link>
</Compile>
Expand Down Expand Up @@ -1511,7 +1527,7 @@
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
<UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceiv
private uint _remotePacketSize;
private ChannelStub _channel;
private Stopwatch _closeTimer;
private ManualResetEvent _channelClosedWaitHandle;
private List<ChannelEventArgs> _channelClosedRegister;
private IList<ExceptionEventArgs> _channelExceptionRegister;

Expand All @@ -42,6 +43,7 @@ private void Arrange()
_remotePacketSize = (uint)random.Next(0, int.MaxValue);
_closeTimer = new Stopwatch();
_channelClosedRegister = new List<ChannelEventArgs>();
_channelClosedWaitHandle = new ManualResetEvent(false);
_channelExceptionRegister = new List<ExceptionEventArgs>();

_sessionMock = new Mock<ISession>(MockBehavior.Strict);
Expand All @@ -61,8 +63,7 @@ private void Arrange()
// SSH_MSG_CHANNEL_CLOSE message from server which is waited on after
// sending the SSH_MSG_CHANNEL_CLOSE message to the server
_sessionMock.Raise(s => s.ChannelCloseReceived += null,
new MessageEventArgs<ChannelCloseMessage>(
new ChannelCloseMessage(_localChannelNumber)));
new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(_localChannelNumber)));
}).Start();
_closeTimer.Start();
try
Expand All @@ -76,7 +77,11 @@ private void Arrange()
});

_channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
_channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
_channel.Closed += (sender, args) =>
{
_channelClosedRegister.Add(args);
_channelClosedWaitHandle.Set();
};
_channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
_channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
_channel.SetIsOpen(true);
Expand Down Expand Up @@ -124,6 +129,8 @@ public void WaitOnHandleOnSessionShouldWaitForChannelCloseMessageToBeReceived()
[TestMethod]
public void ClosedEventShouldHaveFiredOnce()
{
_channelClosedWaitHandle.WaitOne(100);

Assert.AreEqual(1, _channelClosedRegister.Count);
Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceiv
private uint _remotePacketSize;
private ChannelStub _channel;
private Stopwatch _closeTimer;
private ManualResetEvent _channelClosedWaitHandle;
private List<ChannelEventArgs> _channelClosedRegister;
private IList<ExceptionEventArgs> _channelExceptionRegister;

Expand All @@ -41,6 +42,7 @@ private void Arrange()
_remoteWindowSize = (uint)random.Next(0, int.MaxValue);
_remotePacketSize = (uint)random.Next(0, int.MaxValue);
_closeTimer = new Stopwatch();
_channelClosedWaitHandle = new ManualResetEvent(false);
_channelClosedRegister = new List<ChannelEventArgs>();
_channelExceptionRegister = new List<ExceptionEventArgs>();

Expand Down Expand Up @@ -75,7 +77,11 @@ private void Arrange()
});

_channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
_channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
_channel.Closed += (sender, args) =>
{
_channelClosedRegister.Add(args);
_channelClosedWaitHandle.Set();
};
_channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
_channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
_channel.SetIsOpen(true);
Expand Down Expand Up @@ -124,6 +130,8 @@ public void WaitOnHandleOnSessionShouldWaitForChannelCloseMessageToBeReceived()
[TestMethod]
public void ClosedEventShouldHaveFiredOnce()
{
_channelClosedWaitHandle.WaitOne(100);

Assert.AreEqual(1, _channelClosedRegister.Count);
Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
}
Expand Down
17 changes: 17 additions & 0 deletions src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,28 @@ public void SetUp()

protected abstract void Act();

protected byte[] GenerateRandom(int length)
{
return GenerateRandom(length, new Random());
}

protected byte[] GenerateRandom(int length, Random random)
{
var buffer = new byte[length];
random.NextBytes(buffer);
return buffer;
}

protected byte[] GenerateRandom(uint length)
{
return GenerateRandom(length, new Random());
}

protected byte[] GenerateRandom(uint length, Random random)
{
var buffer = new byte[length];
random.NextBytes(buffer);
return buffer;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using System;
using System.Globalization;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Renci.SshNet.Sftp;
using Renci.SshNet.Tests.Common;
using System.Threading;
using Renci.SshNet.Sftp.Responses;

namespace Renci.SshNet.Tests.Classes.Sftp
{
/// <summary>
/// - In read mode
/// - Bytes in (read) buffer
/// - New length greater than client position and greater than server position
/// </summary>
[TestClass]
public class SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition : SftpFileStreamTestBase
{
private string _path;
private SftpFileStream _sftpFileStream;
private byte[] _handle;
private uint _bufferSize;
private uint _readBufferSize;
private uint _writeBufferSize;
private MockSequence _sequence;
private long _length;

private SftpFileAttributes _fileAttributes;
private SftpFileAttributes _originalFileAttributes;
private SftpFileAttributes _newFileAttributes;
private byte[] _readBytes;
private byte[] _actualReadBytes;

protected override void SetupData()
{
var random = new Random();

_path = random.Next().ToString(CultureInfo.InvariantCulture);
_handle = GenerateRandom(random.Next(2, 6), random);
_bufferSize = (uint) random.Next(1, 1000);
_readBufferSize = (uint) random.Next(1, 1000);
_writeBufferSize = (uint) random.Next(100, 1000);
_readBytes = new byte[5];
_actualReadBytes = GenerateRandom(_readBytes.Length, random);
_length = _readBytes.Length + 2;

_fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
.WithExtension("V", "VValue")
.WithGroupId(random.Next())
.WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
.WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
.WithPermissions((uint)random.Next())
.WithSize(_length + 100)
.WithUserId(random.Next())
.Build();
_originalFileAttributes = _fileAttributes.Clone();
_newFileAttributes = null;
}

protected override void SetupMocks()
{
_sequence = new MockSequence();
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write, false))
.Returns(_handle);
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
.Returns(_readBufferSize);
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
.Returns(_writeBufferSize);
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.IsOpen)
.Returns(true);
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.RequestRead(_handle, 0, _readBufferSize))
.Returns(_actualReadBytes);
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.IsOpen)
.Returns(true);
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.RequestFStat(_handle, false))
.Returns(_fileAttributes);
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.RequestFSetStat(_handle, _fileAttributes))
.Callback<byte[], SftpFileAttributes>((bytes, attributes) => _newFileAttributes = attributes.Clone());
}

protected override void Arrange()
{
base.Arrange();

_sftpFileStream = new SftpFileStream(SftpSessionMock.Object, _path, FileMode.Open, FileAccess.ReadWrite, (int)_bufferSize);
_sftpFileStream.Read(_readBytes, 0, _readBytes.Length);
}

protected override void Act()
{
_sftpFileStream.SetLength(_length);
}

[TestMethod]
public void PositionShouldReturnSamePositionAsBeforeSetLength()
{
SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);

Assert.AreEqual(_readBytes.Length, _sftpFileStream.Position);

SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
}

[TestMethod]
public void RequestFSetStatOnSftpSessionShouldBeInvokedOnce()
{
SftpSessionMock.Verify(p => p.RequestFSetStat(_handle, _fileAttributes), Times.Once);
}

[TestMethod]
public void SizeOfSftpFileAttributesShouldBeModifiedToNewLengthBeforePassedToRequestFSetStat()
{
DictionaryAssert.AreEqual(_originalFileAttributes.Extensions, _newFileAttributes.Extensions);
Assert.AreEqual(_originalFileAttributes.GroupId, _newFileAttributes.GroupId);
Assert.AreEqual(_originalFileAttributes.LastAccessTime, _newFileAttributes.LastAccessTime);
Assert.AreEqual(_originalFileAttributes.LastWriteTime, _newFileAttributes.LastWriteTime);
Assert.AreEqual(_originalFileAttributes.Permissions, _newFileAttributes.Permissions);
Assert.AreEqual(_originalFileAttributes.UserId, _newFileAttributes.UserId);

Assert.AreEqual(_length, _newFileAttributes.Size);
}

[TestMethod]
public void ReadShouldReadStartFromSamePositionAsBeforeSetLength()
{
SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.RequestRead(_handle, (uint) _readBytes.Length, _readBufferSize))
.Returns(new byte[] { 0x0f });

var byteRead = _sftpFileStream.ReadByte();

Assert.AreEqual(0x0f, byteRead);

SftpSessionMock.Verify(p => p.RequestRead(_handle, (uint) _readBytes.Length, _readBufferSize), Times.Once);
SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
}

[TestMethod]
public void WriteShouldStartFromSamePositionAsBeforeSetLength()
{
var bytesToWrite = GenerateRandom(5);

SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
SftpSessionMock.InSequence(_sequence)
.Setup(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null))
.Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverOffset, data, offset, length, wait, writeCompleted) =>
{
wait.Set();
});

_sftpFileStream.Write(bytesToWrite, 0, bytesToWrite.Length);
_sftpFileStream.Flush();

SftpSessionMock.Verify(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null), Times.Once);
SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
}
}
}
Loading