From 9444f214619a3cc4bc3de7a48748a7e9d1ef44fc Mon Sep 17 00:00:00 2001 From: Gert Driesen Date: Mon, 14 Aug 2017 20:23:01 +0200 Subject: [PATCH] Added more tests for SftpFileStream.SetLength(long). --- .../Classes/Sftp/SftpFileStreamTestBase.cs | 17 ++ ...eadBuffer_NewLengthGreatherThanPosition.cs | 170 +++++++++++++++ ...aInReadBuffer_NewLengthLessThanPosition.cs | 171 +++++++++++++++ ...iteBuffer_NewLengthGreatherThanPosition.cs | 193 +++++++++++++++++ ...InWriteBuffer_NewLengthLessThanPosition.cs | 194 ++++++++++++++++++ .../Common/DictionaryAssert.cs | 38 ++++ src/Renci.SshNet.Tests/Common/Extensions.cs | 33 +++ .../Common/SftpFileAttributesBuilder.cs | 11 +- .../Renci.SshNet.Tests.csproj | 5 + 9 files changed, 828 insertions(+), 4 deletions(-) create mode 100644 src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs create mode 100644 src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs create mode 100644 src/Renci.SshNet.Tests/Common/DictionaryAssert.cs diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTestBase.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTestBase.cs index a77222cf..19497c6d 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTestBase.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTestBase.cs @@ -38,11 +38,28 @@ namespace Renci.SshNet.Tests.Classes.Sftp 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; + } } } diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs new file mode 100644 index 00000000..aafde5c2 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs @@ -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 +{ + /// + /// - In read mode + /// - Bytes in (read) buffer + /// - New length greater than client position and greater than server position + /// + [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((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(), 0, bytesToWrite.Length, It.IsAny(), null)) + .Callback>((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(), 0, bytesToWrite.Length, It.IsAny(), null), Times.Once); + SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4)); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs new file mode 100644 index 00000000..ca2df880 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs @@ -0,0 +1,171 @@ +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; +using Renci.SshNet.Common; + +namespace Renci.SshNet.Tests.Classes.Sftp +{ + /// + /// - In read mode + /// - Bytes in (read) buffer + /// - New length less than client position and greater than server position + /// + [TestClass] + public class SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition : 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((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 PositionShouldReturnLengthOfStream() + { + SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true); + + Assert.AreEqual(_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 ReadShouldStartFromEndOfStream() + { + SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true); + SftpSessionMock.InSequence(_sequence) + .Setup(p => p.RequestRead(_handle, (uint) _length, _readBufferSize)) + .Returns(Array.Empty); + + var byteRead = _sftpFileStream.ReadByte(); + + Assert.AreEqual(-1, byteRead); + + SftpSessionMock.Verify(p => p.RequestRead(_handle, (uint) _length, _readBufferSize), Times.Once); + SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(3)); + } + + [TestMethod] + public void WriteShouldStartFromEndOfStream() + { + 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) _length, It.IsAny(), 0, bytesToWrite.Length, It.IsAny(), null)) + .Callback>((handle, serverOffset, data, offset, length, wait, writeCompleted) => + { + wait.Set(); + }); + + _sftpFileStream.Write(bytesToWrite, 0, bytesToWrite.Length); + _sftpFileStream.Flush(); + + SftpSessionMock.Verify(p => p.RequestWrite(_handle, (uint)_length, It.IsAny(), 0, bytesToWrite.Length, It.IsAny(), null), Times.Once); + SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4)); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs new file mode 100644 index 00000000..863bae29 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs @@ -0,0 +1,193 @@ +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 +{ + /// + /// - In write mode + /// - Bytes in (write) buffer + /// - New length greater than client position and greater than server position + /// + [TestClass] + public class SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreaterThanPosition : 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; + private byte[] _writeBytes; + private byte[] _actualWrittenBytes; + + 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); + _writeBytes = new byte[] { 0x01, 0x02, 0x03, 0x04 }; + _length = _readBytes.Length + _writeBytes.Length + 2; + _actualWrittenBytes = null; + + _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.IsOpen) + .Returns(true); + SftpSessionMock.InSequence(_sequence) + .Setup(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny(), 0, _writeBytes.Length, It.IsAny(), null)) + .Callback>((handle, serverOffset, data, offset, length, wait, writeCompleted) + => + { + _actualWrittenBytes = data.Take(0, _writeBytes.Length); + wait.Set(); + }); + SftpSessionMock.InSequence(_sequence) + .Setup(p => p.RequestFStat(_handle, false)) + .Returns(_fileAttributes); + SftpSessionMock.InSequence(_sequence) + .Setup(p => p.RequestFSetStat(_handle, _fileAttributes)) + .Callback((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); + _sftpFileStream.Write(new byte[] { 0x01, 0x02, 0x03, 0x04 }, 0, 4); + } + + protected override void Act() + { + _sftpFileStream.SetLength(_length); + } + + [TestMethod] + public void PositionShouldReturnSamePositionAsBeforeSetLength() + { + SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true); + + Assert.AreEqual(_readBytes.Length + _writeBytes.Length, _sftpFileStream.Position); + + SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4)); + } + + [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 WrittenBytesShouldByFlushedToServer() + { + Assert.IsNotNull(_actualWrittenBytes); + CollectionAssert.AreEqual(_writeBytes, _actualWrittenBytes); + } + + [TestMethod] + public void ReadShouldReadStartFromSamePositionAsBeforeSetLength() + { + SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true); + SftpSessionMock.InSequence(_sequence) + .Setup(p => p.RequestRead(_handle, (uint) (_readBytes.Length + _writeBytes.Length), _readBufferSize)) + .Returns(new byte[] { 0x0f }); + + var byteRead = _sftpFileStream.ReadByte(); + + Assert.AreEqual(0x0f, byteRead); + + SftpSessionMock.Verify(p => p.RequestRead(_handle, (uint) (_readBytes.Length + _writeBytes.Length), _readBufferSize), Times.Once); + SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4)); + } + + [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 + _writeBytes.Length), It.IsAny(), 0, bytesToWrite.Length, It.IsAny(), null)) + .Callback>((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 + _writeBytes.Length), It.IsAny(), 0, bytesToWrite.Length, It.IsAny(), null), Times.Once); + SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(5)); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs new file mode 100644 index 00000000..434fc0ab --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs @@ -0,0 +1,194 @@ +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; +using Renci.SshNet.Common; + +namespace Renci.SshNet.Tests.Classes.Sftp +{ + /// + /// - In write mode + /// - Bytes in (write) buffer + /// - New length less than client position and less than server position + /// + [TestClass] + public class SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition : 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; + private byte[] _writeBytes; + private byte[] _actualWrittenBytes; + + 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); + _writeBytes = new byte[] { 0x01, 0x02, 0x03, 0x04 }; + _length = _readBytes.Length - 2; + _actualWrittenBytes = null; + + _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.IsOpen) + .Returns(true); + SftpSessionMock.InSequence(_sequence) + .Setup(p => p.RequestWrite(_handle, (uint) _readBytes.Length, It.IsAny(), 0, _writeBytes.Length, It.IsAny(), null)) + .Callback>((handle, serverOffset, data, offset, length, wait, writeCompleted) + => + { + _actualWrittenBytes = data.Take(0, _writeBytes.Length); + wait.Set(); + }); + SftpSessionMock.InSequence(_sequence) + .Setup(p => p.RequestFStat(_handle, false)) + .Returns(_fileAttributes); + SftpSessionMock.InSequence(_sequence) + .Setup(p => p.RequestFSetStat(_handle, _fileAttributes)) + .Callback((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); + _sftpFileStream.Write(new byte[] { 0x01, 0x02, 0x03, 0x04 }, 0, 4); + } + + protected override void Act() + { + _sftpFileStream.SetLength(_length); + } + + [TestMethod] + public void PositionShouldReturnLengthOfStream() + { + SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true); + + Assert.AreEqual(_length, _sftpFileStream.Position); + + SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4)); + } + + [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 WrittenBytesShouldByFlushedToServer() + { + Assert.IsNotNull(_actualWrittenBytes); + CollectionAssert.AreEqual(_writeBytes, _actualWrittenBytes); + } + + [TestMethod] + public void ReadShouldStartFromEndOfStream() + { + SftpSessionMock.InSequence(_sequence).Setup(p => p.IsOpen).Returns(true); + SftpSessionMock.InSequence(_sequence) + .Setup(p => p.RequestRead(_handle, (uint) _length, _readBufferSize)) + .Returns(Array.Empty); + + var byteRead = _sftpFileStream.ReadByte(); + + Assert.AreEqual(-1, byteRead); + + SftpSessionMock.Verify(p => p.RequestRead(_handle, (uint)_length, _readBufferSize), Times.Once); + SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4)); + } + + [TestMethod] + public void WriteShouldStartFromEndOfStream() + { + 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) _length, It.IsAny(), 0, bytesToWrite.Length, It.IsAny(), null)) + .Callback>((handle, serverOffset, data, offset, length, wait, writeCompleted) => + { + wait.Set(); + }); + + _sftpFileStream.Write(bytesToWrite, 0, bytesToWrite.Length); + _sftpFileStream.Flush(); + + SftpSessionMock.Verify(p => p.RequestWrite(_handle, (uint) _length, It.IsAny(), 0, bytesToWrite.Length, It.IsAny(), null), Times.Once); + SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(5)); + } + } +} diff --git a/src/Renci.SshNet.Tests/Common/DictionaryAssert.cs b/src/Renci.SshNet.Tests/Common/DictionaryAssert.cs new file mode 100644 index 00000000..0df1c504 --- /dev/null +++ b/src/Renci.SshNet.Tests/Common/DictionaryAssert.cs @@ -0,0 +1,38 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace Renci.SshNet.Tests.Common +{ + public static class DictionaryAssert + { + public static void AreEqual(IDictionary expected, IDictionary actual) + { + if (ReferenceEquals(expected, actual)) + return; + + if (expected == null) + throw new AssertFailedException("Expected dictionary to be null, but was not null."); + + if (actual == null) + throw new AssertFailedException("Expected dictionary not to be null, but was null."); + + if (expected.Count != actual.Count) + throw new AssertFailedException(string.Format("Expected dictionary to contain {0} entries, but was {1}.", + expected.Count, actual.Count)); + + foreach (var expectedEntry in expected) + { + TValue actualValue; + if (!actual.TryGetValue(expectedEntry.Key, out actualValue)) + { + throw new AssertFailedException(string.Format("Dictionary contains no entry with key '{0}'.", expectedEntry.Key)); + } + + if (!Equals(expectedEntry.Value, actualValue)) + { + throw new AssertFailedException(string.Format("Value for key '{0}' does not match.", expectedEntry.Key)); + } + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Common/Extensions.cs b/src/Renci.SshNet.Tests/Common/Extensions.cs index 6bb1e45e..911f3413 100644 --- a/src/Renci.SshNet.Tests/Common/Extensions.cs +++ b/src/Renci.SshNet.Tests/Common/Extensions.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Renci.SshNet.Common; using System; +using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Common { @@ -24,5 +25,37 @@ namespace Renci.SshNet.Tests.Common Buffer.BlockCopy(buffer, 0, copy, 0, buffer.Length); return copy; } + + /// + /// Creates a deep clone of the current instance. + /// + /// + /// A deep clone of the current instance. + /// + internal static SftpFileAttributes Clone(this SftpFileAttributes value) + { + Dictionary clonedExtensions; + + if (value.Extensions != null) + { + clonedExtensions = new Dictionary(value.Extensions.Count); + foreach (var entry in value.Extensions) + { + clonedExtensions.Add(entry.Key, entry.Value); + } + } + else + { + clonedExtensions = null; + } + + return new SftpFileAttributes(value.LastAccessTime, + value.LastWriteTime, + value.Size, + value.UserId, + value.GroupId, + value.Permissions, + clonedExtensions); + } } } diff --git a/src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs b/src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs index 9003fc3d..2468c827 100644 --- a/src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs +++ b/src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs @@ -14,6 +14,11 @@ namespace Renci.SshNet.Tests.Common private uint? _permissions; private IDictionary _extensions; + public SftpFileAttributesBuilder() + { + _extensions = new Dictionary(); + } + public SftpFileAttributesBuilder WithLastAccessTime(DateTime lastAccessTime) { _lastAccessTime = lastAccessTime; @@ -50,9 +55,9 @@ namespace Renci.SshNet.Tests.Common return this; } - public SftpFileAttributesBuilder WithExtensions(IDictionary extensions) + public SftpFileAttributesBuilder WithExtension(string name, string value) { - _extensions = extensions; + _extensions.Add(name, value); return this; } @@ -79,7 +84,5 @@ namespace Renci.SshNet.Tests.Common _permissions.Value, _extensions); } - - } } diff --git a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj index 9fc8daef..5076c4b4 100644 --- a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj +++ b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj @@ -444,6 +444,10 @@ + + + + @@ -513,6 +517,7 @@ +