Skip to content

Commit 68c1e59

Browse files
authored
Merge pull request #272 from sshnet/sftpclient-setlength
SftpFileStream: Improve SetLength(long) compatibility with FileStream
2 parents f38c7ce + 3005446 commit 68c1e59

13 files changed

+892
-12
lines changed

src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<DefineConstants>TRACE;DEBUG</DefineConstants>
2424
<ErrorReport>prompt</ErrorReport>
2525
<WarningLevel>4</WarningLevel>
26+
<LangVersion>5</LangVersion>
2627
</PropertyGroup>
2728
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
2829
<DebugType>pdbonly</DebugType>
@@ -1232,6 +1233,18 @@
12321233
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_Closed.cs">
12331234
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_Closed.cs</Link>
12341235
</Compile>
1236+
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs">
1237+
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs</Link>
1238+
</Compile>
1239+
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs">
1240+
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs</Link>
1241+
</Compile>
1242+
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs">
1243+
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs</Link>
1244+
</Compile>
1245+
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs">
1246+
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs</Link>
1247+
</Compile>
12351248
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileStreamTest_SetLength_Disposed.cs">
12361249
<Link>Classes\Sftp\SftpFileStreamTest_SetLength_Disposed.cs</Link>
12371250
</Compile>
@@ -1424,6 +1437,9 @@
14241437
<Compile Include="..\Renci.SshNet.Tests\Common\AsyncSocketListener.cs">
14251438
<Link>Common\AsyncSocketListener.cs</Link>
14261439
</Compile>
1440+
<Compile Include="..\Renci.SshNet.Tests\Common\DictionaryAssert.cs">
1441+
<Link>Common\DictionaryAssert.cs</Link>
1442+
</Compile>
14271443
<Compile Include="..\Renci.SshNet.Tests\Common\Extensions.cs">
14281444
<Link>Common\Extensions.cs</Link>
14291445
</Compile>
@@ -1511,7 +1527,7 @@
15111527
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
15121528
<ProjectExtensions>
15131529
<VisualStudio>
1514-
<UserProperties ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
1530+
<UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" />
15151531
</VisualStudio>
15161532
</ProjectExtensions>
15171533
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceived.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceiv
2121
private uint _remotePacketSize;
2222
private ChannelStub _channel;
2323
private Stopwatch _closeTimer;
24+
private ManualResetEvent _channelClosedWaitHandle;
2425
private List<ChannelEventArgs> _channelClosedRegister;
2526
private IList<ExceptionEventArgs> _channelExceptionRegister;
2627

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

4749
_sessionMock = new Mock<ISession>(MockBehavior.Strict);
@@ -61,8 +63,7 @@ private void Arrange()
6163
// SSH_MSG_CHANNEL_CLOSE message from server which is waited on after
6264
// sending the SSH_MSG_CHANNEL_CLOSE message to the server
6365
_sessionMock.Raise(s => s.ChannelCloseReceived += null,
64-
new MessageEventArgs<ChannelCloseMessage>(
65-
new ChannelCloseMessage(_localChannelNumber)));
66+
new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(_localChannelNumber)));
6667
}).Start();
6768
_closeTimer.Start();
6869
try
@@ -76,7 +77,11 @@ private void Arrange()
7677
});
7778

7879
_channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
79-
_channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
80+
_channel.Closed += (sender, args) =>
81+
{
82+
_channelClosedRegister.Add(args);
83+
_channelClosedWaitHandle.Set();
84+
};
8085
_channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
8186
_channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
8287
_channel.SetIsOpen(true);
@@ -124,6 +129,8 @@ public void WaitOnHandleOnSessionShouldWaitForChannelCloseMessageToBeReceived()
124129
[TestMethod]
125130
public void ClosedEventShouldHaveFiredOnce()
126131
{
132+
_channelClosedWaitHandle.WaitOne(100);
133+
127134
Assert.AreEqual(1, _channelClosedRegister.Count);
128135
Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
129136
}

src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceived_SendEofInvoked.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceiv
2121
private uint _remotePacketSize;
2222
private ChannelStub _channel;
2323
private Stopwatch _closeTimer;
24+
private ManualResetEvent _channelClosedWaitHandle;
2425
private List<ChannelEventArgs> _channelClosedRegister;
2526
private IList<ExceptionEventArgs> _channelExceptionRegister;
2627

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

@@ -75,7 +77,11 @@ private void Arrange()
7577
});
7678

7779
_channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
78-
_channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
80+
_channel.Closed += (sender, args) =>
81+
{
82+
_channelClosedRegister.Add(args);
83+
_channelClosedWaitHandle.Set();
84+
};
7985
_channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
8086
_channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
8187
_channel.SetIsOpen(true);
@@ -124,6 +130,8 @@ public void WaitOnHandleOnSessionShouldWaitForChannelCloseMessageToBeReceived()
124130
[TestMethod]
125131
public void ClosedEventShouldHaveFiredOnce()
126132
{
133+
_channelClosedWaitHandle.WaitOne(100);
134+
127135
Assert.AreEqual(1, _channelClosedRegister.Count);
128136
Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
129137
}

src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTestBase.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,28 @@ public void SetUp()
3838

3939
protected abstract void Act();
4040

41+
protected byte[] GenerateRandom(int length)
42+
{
43+
return GenerateRandom(length, new Random());
44+
}
45+
4146
protected byte[] GenerateRandom(int length, Random random)
4247
{
4348
var buffer = new byte[length];
4449
random.NextBytes(buffer);
4550
return buffer;
4651
}
52+
53+
protected byte[] GenerateRandom(uint length)
54+
{
55+
return GenerateRandom(length, new Random());
56+
}
57+
58+
protected byte[] GenerateRandom(uint length, Random random)
59+
{
60+
var buffer = new byte[length];
61+
random.NextBytes(buffer);
62+
return buffer;
63+
}
4764
}
4865
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using System;
2+
using System.Globalization;
3+
using System.IO;
4+
using Microsoft.VisualStudio.TestTools.UnitTesting;
5+
using Moq;
6+
using Renci.SshNet.Sftp;
7+
using Renci.SshNet.Tests.Common;
8+
using System.Threading;
9+
using Renci.SshNet.Sftp.Responses;
10+
11+
namespace Renci.SshNet.Tests.Classes.Sftp
12+
{
13+
/// <summary>
14+
/// - In read mode
15+
/// - Bytes in (read) buffer
16+
/// - New length greater than client position and greater than server position
17+
/// </summary>
18+
[TestClass]
19+
public class SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition : SftpFileStreamTestBase
20+
{
21+
private string _path;
22+
private SftpFileStream _sftpFileStream;
23+
private byte[] _handle;
24+
private uint _bufferSize;
25+
private uint _readBufferSize;
26+
private uint _writeBufferSize;
27+
private MockSequence _sequence;
28+
private long _length;
29+
30+
private SftpFileAttributes _fileAttributes;
31+
private SftpFileAttributes _originalFileAttributes;
32+
private SftpFileAttributes _newFileAttributes;
33+
private byte[] _readBytes;
34+
private byte[] _actualReadBytes;
35+
36+
protected override void SetupData()
37+
{
38+
var random = new Random();
39+
40+
_path = random.Next().ToString(CultureInfo.InvariantCulture);
41+
_handle = GenerateRandom(random.Next(2, 6), random);
42+
_bufferSize = (uint) random.Next(1, 1000);
43+
_readBufferSize = (uint) random.Next(1, 1000);
44+
_writeBufferSize = (uint) random.Next(100, 1000);
45+
_readBytes = new byte[5];
46+
_actualReadBytes = GenerateRandom(_readBytes.Length, random);
47+
_length = _readBytes.Length + 2;
48+
49+
_fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
50+
.WithExtension("V", "VValue")
51+
.WithGroupId(random.Next())
52+
.WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
53+
.WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
54+
.WithPermissions((uint)random.Next())
55+
.WithSize(_length + 100)
56+
.WithUserId(random.Next())
57+
.Build();
58+
_originalFileAttributes = _fileAttributes.Clone();
59+
_newFileAttributes = null;
60+
}
61+
62+
protected override void SetupMocks()
63+
{
64+
_sequence = new MockSequence();
65+
SftpSessionMock.InSequence(_sequence)
66+
.Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write, false))
67+
.Returns(_handle);
68+
SftpSessionMock.InSequence(_sequence)
69+
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
70+
.Returns(_readBufferSize);
71+
SftpSessionMock.InSequence(_sequence)
72+
.Setup(p => p.CalculateOptimalWriteLength(_bufferSize, _handle))
73+
.Returns(_writeBufferSize);
74+
SftpSessionMock.InSequence(_sequence)
75+
.Setup(p => p.IsOpen)
76+
.Returns(true);
77+
SftpSessionMock.InSequence(_sequence)
78+
.Setup(p => p.RequestRead(_handle, 0, _readBufferSize))
79+
.Returns(_actualReadBytes);
80+
SftpSessionMock.InSequence(_sequence)
81+
.Setup(p => p.IsOpen)
82+
.Returns(true);
83+
SftpSessionMock.InSequence(_sequence)
84+
.Setup(p => p.RequestFStat(_handle, false))
85+
.Returns(_fileAttributes);
86+
SftpSessionMock.InSequence(_sequence)
87+
.Setup(p => p.RequestFSetStat(_handle, _fileAttributes))
88+
.Callback<byte[], SftpFileAttributes>((bytes, attributes) => _newFileAttributes = attributes.Clone());
89+
}
90+
91+
protected override void Arrange()
92+
{
93+
base.Arrange();
94+
95+
_sftpFileStream = new SftpFileStream(SftpSessionMock.Object, _path, FileMode.Open, FileAccess.ReadWrite, (int)_bufferSize);
96+
_sftpFileStream.Read(_readBytes, 0, _readBytes.Length);
97+
}
98+
99+
protected override void Act()
100+
{
101+
_sftpFileStream.SetLength(_length);
102+
}
103+
104+
[TestMethod]
105+
public void PositionShouldReturnSamePositionAsBeforeSetLength()
106+
{
107+
SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
108+
109+
Assert.AreEqual(_readBytes.Length, _sftpFileStream.Position);
110+
111+
SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
112+
}
113+
114+
[TestMethod]
115+
public void RequestFSetStatOnSftpSessionShouldBeInvokedOnce()
116+
{
117+
SftpSessionMock.Verify(p => p.RequestFSetStat(_handle, _fileAttributes), Times.Once);
118+
}
119+
120+
[TestMethod]
121+
public void SizeOfSftpFileAttributesShouldBeModifiedToNewLengthBeforePassedToRequestFSetStat()
122+
{
123+
DictionaryAssert.AreEqual(_originalFileAttributes.Extensions, _newFileAttributes.Extensions);
124+
Assert.AreEqual(_originalFileAttributes.GroupId, _newFileAttributes.GroupId);
125+
Assert.AreEqual(_originalFileAttributes.LastAccessTime, _newFileAttributes.LastAccessTime);
126+
Assert.AreEqual(_originalFileAttributes.LastWriteTime, _newFileAttributes.LastWriteTime);
127+
Assert.AreEqual(_originalFileAttributes.Permissions, _newFileAttributes.Permissions);
128+
Assert.AreEqual(_originalFileAttributes.UserId, _newFileAttributes.UserId);
129+
130+
Assert.AreEqual(_length, _newFileAttributes.Size);
131+
}
132+
133+
[TestMethod]
134+
public void ReadShouldReadStartFromSamePositionAsBeforeSetLength()
135+
{
136+
SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
137+
SftpSessionMock.InSequence(_sequence)
138+
.Setup(p => p.RequestRead(_handle, (uint) _readBytes.Length, _readBufferSize))
139+
.Returns(new byte[] { 0x0f });
140+
141+
var byteRead = _sftpFileStream.ReadByte();
142+
143+
Assert.AreEqual(0x0f, byteRead);
144+
145+
SftpSessionMock.Verify(p => p.RequestRead(_handle, (uint) _readBytes.Length, _readBufferSize), Times.Once);
146+
SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3));
147+
}
148+
149+
[TestMethod]
150+
public void WriteShouldStartFromSamePositionAsBeforeSetLength()
151+
{
152+
var bytesToWrite = GenerateRandom(5);
153+
154+
SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
155+
SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true);
156+
SftpSessionMock.InSequence(_sequence)
157+
.Setup(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null))
158+
.Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverOffset, data, offset, length, wait, writeCompleted) =>
159+
{
160+
wait.Set();
161+
});
162+
163+
_sftpFileStream.Write(bytesToWrite, 0, bytesToWrite.Length);
164+
_sftpFileStream.Flush();
165+
166+
SftpSessionMock.Verify(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny<byte[]>(), 0, bytesToWrite.Length, It.IsAny<AutoResetEvent>(), null), Times.Once);
167+
SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
168+
}
169+
}
170+
}

0 commit comments

Comments
 (0)