334 lines
14 KiB
C#
334 lines
14 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Duplicati.Proprietary.DiskImage.Partition;
|
|
using NUnit.Framework;
|
|
using Assert = NUnit.Framework.Legacy.ClassicAssert;
|
|
using StringAssert = NUnit.Framework.Legacy.StringAssert;
|
|
|
|
namespace Duplicati.UnitTest.DiskImage.UnitTests;
|
|
|
|
public partial class DiskImageUnitTests : BasicSetupHelper
|
|
{
|
|
|
|
[Test]
|
|
public async Task Test_PartitionWriteStream_WriteAndDispose_DataFlushedToDisk()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionOffset = 10 * sectorSize;
|
|
var partitionSize = 4 * sectorSize;
|
|
|
|
// Write data using PartitionWriteStream
|
|
var writeData = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
|
|
using (var stream = new PartitionWriteStream(_writableRawDisk, partitionOffset, partitionSize))
|
|
await stream.WriteAsync(writeData, CancellationToken.None);
|
|
|
|
// Read back and verify data was flushed to disk
|
|
using var readStream = await _writableRawDisk.ReadBytesAsync(partitionOffset, writeData.Length, CancellationToken.None);
|
|
var readBuffer = new byte[writeData.Length];
|
|
var bytesRead = await readStream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None);
|
|
|
|
Assert.AreEqual(writeData.Length, bytesRead, "Should have read the written data.");
|
|
Assert.AreEqual(writeData, readBuffer, "Data should match what was written.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_SeekBegin_ValidPosition()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionSize = 4 * sectorSize;
|
|
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, partitionSize);
|
|
|
|
// Test Seek with SeekOrigin.Begin
|
|
var newPosition = stream.Seek(100, SeekOrigin.Begin);
|
|
Assert.AreEqual(100, newPosition, "Position should be 100 after seeking from Begin.");
|
|
Assert.AreEqual(100, stream.Position, "Stream.Position should match returned position.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_SeekCurrent_ValidPosition()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionSize = 4 * sectorSize;
|
|
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, partitionSize);
|
|
|
|
// Write some data first to move position
|
|
stream.Write([0x01, 0x02, 0x03], 0, 3);
|
|
Assert.AreEqual(3, stream.Position, "Position should be 3 after writing 3 bytes.");
|
|
|
|
// Test Seek with SeekOrigin.Current
|
|
var newPosition = stream.Seek(50, SeekOrigin.Current);
|
|
Assert.AreEqual(53, newPosition, "Position should be 53 after seeking 50 from current position 3.");
|
|
Assert.AreEqual(53, stream.Position, "Stream.Position should match returned position.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_SeekEnd_ValidPosition()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionSize = 4 * sectorSize;
|
|
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, partitionSize);
|
|
|
|
// Write some data first to establish length
|
|
stream.Write([0x01, 0x02, 0x03, 0x04, 0x05], 0, 5);
|
|
Assert.AreEqual(5, stream.Length, "Length should be 5 after writing 5 bytes.");
|
|
|
|
// Test Seek with SeekOrigin.End
|
|
var newPosition = stream.Seek(-2, SeekOrigin.End);
|
|
Assert.AreEqual(3, newPosition, "Position should be 3 after seeking -2 from end of length 5.");
|
|
Assert.AreEqual(3, stream.Position, "Stream.Position should match returned position.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_WriteBeyondSize_ThrowsIOException()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionOffset = 10 * sectorSize;
|
|
var partitionSize = 2 * sectorSize;
|
|
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, partitionOffset, partitionSize);
|
|
|
|
// Try to write more than partition size
|
|
var largeBuffer = new byte[partitionSize + 100];
|
|
new Random().NextBytes(largeBuffer);
|
|
|
|
var ex = Assert.Throws<IOException>(() =>
|
|
{
|
|
stream.Write(largeBuffer, 0, largeBuffer.Length);
|
|
}, "Writing beyond partition size should throw IOException.");
|
|
|
|
StringAssert.Contains("partition size", ex!.Message, "Exception message should mention partition size.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_SeekBeyondSize_ThrowsIOException()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionSize = 2 * sectorSize;
|
|
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, partitionSize);
|
|
|
|
// Try to seek beyond partition size
|
|
var ex = Assert.Throws<IOException>(() =>
|
|
{
|
|
stream.Seek(partitionSize + 100, SeekOrigin.Begin);
|
|
}, "Seeking beyond partition size should throw IOException.");
|
|
|
|
StringAssert.Contains("partition size", ex!.Message, "Exception message should mention partition size.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_SeekNegativePosition_ThrowsIOException()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionSize = 2 * sectorSize;
|
|
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, partitionSize);
|
|
|
|
// Try to seek to negative position
|
|
Assert.Throws<IOException>(() =>
|
|
{
|
|
stream.Seek(-100, SeekOrigin.Begin);
|
|
}, "Seeking to negative position should throw IOException.");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Test_PartitionWriteStream_WriteAtUnalignedPosition_DataMatches()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionOffset = 15 * sectorSize;
|
|
var partitionSize = 4 * sectorSize;
|
|
|
|
// Write data at various unaligned positions
|
|
using (var stream = new PartitionWriteStream(_writableRawDisk, partitionOffset, partitionSize))
|
|
{
|
|
// Write at position 0
|
|
stream.Write([0xAA, 0xBB], 0, 2);
|
|
|
|
// Seek to unaligned position
|
|
stream.Seek(7, SeekOrigin.Begin);
|
|
stream.Write([0xCC, 0xDD, 0xEE], 0, 3);
|
|
|
|
// Seek to another unaligned position
|
|
stream.Seek(20, SeekOrigin.Begin);
|
|
stream.Write([0x11, 0x22, 0x33, 0x44], 0, 4);
|
|
}
|
|
|
|
// Read back and verify
|
|
using var readStream = await _writableRawDisk.ReadBytesAsync(partitionOffset, 25, CancellationToken.None);
|
|
var readBuffer = new byte[25];
|
|
var bytesRead = await readStream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None);
|
|
|
|
Assert.AreEqual(25, bytesRead, "Should read exactly 25 bytes.");
|
|
|
|
// Verify data at position 0
|
|
Assert.AreEqual(0xAA, readBuffer[0], "Byte at position 0 should be 0xAA.");
|
|
Assert.AreEqual(0xBB, readBuffer[1], "Byte at position 1 should be 0xBB.");
|
|
|
|
// Verify data at position 7
|
|
Assert.AreEqual(0xCC, readBuffer[7], "Byte at position 7 should be 0xCC.");
|
|
Assert.AreEqual(0xDD, readBuffer[8], "Byte at position 8 should be 0xDD.");
|
|
Assert.AreEqual(0xEE, readBuffer[9], "Byte at position 9 should be 0xEE.");
|
|
|
|
// Verify data at position 20
|
|
Assert.AreEqual(0x11, readBuffer[20], "Byte at position 20 should be 0x11.");
|
|
Assert.AreEqual(0x22, readBuffer[21], "Byte at position 21 should be 0x22.");
|
|
Assert.AreEqual(0x33, readBuffer[22], "Byte at position 22 should be 0x33.");
|
|
Assert.AreEqual(0x44, readBuffer[23], "Byte at position 23 should be 0x44.");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Test_PartitionWriteStream_WriteMultipleTimes_DataMatches()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionOffset = 20 * sectorSize;
|
|
var partitionSize = 3 * sectorSize;
|
|
|
|
// Write data in multiple operations
|
|
using (var stream = new PartitionWriteStream(_writableRawDisk, partitionOffset, partitionSize))
|
|
{
|
|
// First write
|
|
stream.Write([0x01, 0x02, 0x03], 0, 3);
|
|
|
|
// Second write (should append)
|
|
stream.Write([0x04, 0x05], 0, 2);
|
|
|
|
// Third write
|
|
stream.Write([0x06, 0x07, 0x08, 0x09], 0, 4);
|
|
}
|
|
|
|
// Read back and verify all data was written
|
|
using var readStream = await _writableRawDisk.ReadBytesAsync(partitionOffset, 9, CancellationToken.None);
|
|
var readBuffer = new byte[9];
|
|
var bytesRead = await readStream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None);
|
|
|
|
Assert.AreEqual(9, bytesRead, "Should read exactly 9 bytes.");
|
|
|
|
var expected = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
|
|
Assert.AreEqual(expected, readBuffer, "All written data should match.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_SetLengthBeyondMaxSize_ThrowsIOException()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionSize = 2 * sectorSize;
|
|
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, partitionSize);
|
|
|
|
// Try to set length beyond partition size
|
|
var ex = Assert.Throws<IOException>(() =>
|
|
{
|
|
stream.SetLength(partitionSize + 100);
|
|
}, "Setting length beyond partition size should throw IOException.");
|
|
|
|
StringAssert.Contains("partition size", ex!.Message, "Exception message should mention partition size.");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Test_PartitionWriteStream_SetLengthWithinMaxSize_SetsLength()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionOffset = 10 * sectorSize;
|
|
var partitionSize = 2 * sectorSize;
|
|
|
|
using (var stream = new PartitionWriteStream(_writableRawDisk, partitionOffset, partitionSize))
|
|
{
|
|
// Set length to a specific value within bounds
|
|
stream.SetLength(100);
|
|
Assert.AreEqual(100, stream.Length, "Length should be set to 100.");
|
|
|
|
// Write some data at position 0
|
|
stream.Write([0xAA, 0xBB, 0xCC], 0, 3);
|
|
// Length should be max(SetLength(100), written position) = 100
|
|
Assert.AreEqual(100, stream.Length, "Length should remain 100 (SetLength value).");
|
|
}
|
|
|
|
// Verify the 3 bytes were written at position 0 and length 100 bytes are flushed
|
|
using var readStream = await _writableRawDisk.ReadBytesAsync(partitionOffset, 100, CancellationToken.None);
|
|
var readBuffer = new byte[100];
|
|
var bytesRead = await readStream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None);
|
|
|
|
Assert.AreEqual(100, bytesRead, "Should read exactly 100 bytes.");
|
|
|
|
Assert.AreEqual(0xAA, readBuffer[0], "Byte at position 0 should be 0xAA.");
|
|
Assert.AreEqual(0xBB, readBuffer[1], "Byte at position 1 should be 0xBB.");
|
|
Assert.AreEqual(0xCC, readBuffer[2], "Byte at position 2 should be 0xCC.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_CanRead_ReturnsFalse()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, sectorSize);
|
|
|
|
Assert.IsFalse(stream.CanRead, "CanRead should be false.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_CanWrite_ReturnsTrue()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, sectorSize);
|
|
|
|
Assert.IsTrue(stream.CanWrite, "CanWrite should be true.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_CanSeek_ReturnsTrue()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, sectorSize);
|
|
|
|
Assert.IsTrue(stream.CanSeek, "CanSeek should be true.");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_PartitionWriteStream_Read_ThrowsNotSupported()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
using var stream = new PartitionWriteStream(_writableRawDisk, 10 * sectorSize, sectorSize);
|
|
|
|
Assert.Throws<NotSupportedException>(() =>
|
|
{
|
|
var _ = stream.Read(new byte[10], 0, 10);
|
|
}, "Read should throw NotSupportedException.");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Test_PartitionWriteStream_WriteEmptyData_NoFlush()
|
|
{
|
|
var sectorSize = _writableRawDisk.SectorSize;
|
|
var partitionOffset = 10 * sectorSize;
|
|
var partitionSize = sectorSize;
|
|
|
|
// Read initial data at this offset (may not be zero due to previous tests)
|
|
using var initialReadStream = await _writableRawDisk.ReadBytesAsync(partitionOffset, 10, CancellationToken.None);
|
|
var initialBuffer = new byte[10];
|
|
var initialBytesRead = await initialReadStream.ReadAsync(initialBuffer.AsMemory(), CancellationToken.None);
|
|
Assert.AreEqual(10, initialBytesRead, "Should read 10 bytes.");
|
|
|
|
// Create stream but write nothing
|
|
using (var stream = new PartitionWriteStream(_writableRawDisk, partitionOffset, partitionSize))
|
|
// Don't write anything
|
|
Assert.AreEqual(0, stream.Length, "Length should be 0 when nothing is written.");
|
|
|
|
// Read back and verify no data was flushed (should be unchanged)
|
|
using var readStream = await _writableRawDisk.ReadBytesAsync(partitionOffset, 10, CancellationToken.None);
|
|
var readBuffer = new byte[10];
|
|
var bytesRead = await readStream.ReadAsync(readBuffer.AsMemory(), CancellationToken.None);
|
|
|
|
Assert.AreEqual(10, bytesRead, "Should read 10 bytes.");
|
|
|
|
// All bytes should be unchanged from before the stream was created
|
|
for (int i = 0; i < readBuffer.Length; i++)
|
|
{
|
|
Assert.AreEqual(initialBuffer[i], readBuffer[i], $"Byte at position {i} should be unchanged (no data written).");
|
|
}
|
|
}
|
|
|
|
} |