This PR has a large blast radius because it takes the final step and bumps up the Controller to be fully async. We have historically done a piece-by-piece update, so all operations were already async but the controller interface was kept synchronous. With this update, the controller is now fully async and all tests are updated. Most places where the new C# compiler warns about function names not ending in `Async` were also adressed, giving a massive refactor change. Functionally, no changes are done.
264 lines
8.8 KiB
C#
264 lines
8.8 KiB
C#
// Copyright (c) 2026 Duplicati Inc. All rights reserved.
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using Duplicati.Proprietary.DiskImage.Filesystem.Fat32;
|
|
using NUnit.Framework;
|
|
using NUnit.Framework.Legacy;
|
|
using Assert = NUnit.Framework.Legacy.ClassicAssert;
|
|
|
|
#nullable enable
|
|
|
|
namespace Duplicati.UnitTest.DiskImage.Fat32;
|
|
|
|
/// <summary>
|
|
/// Unit tests for the <see cref="Fat32Filesystem.Fat32ZeroStream"/> class.
|
|
/// </summary>
|
|
[TestFixture]
|
|
[Category("DiskImageUnit")]
|
|
public class Fat32ZeroStreamTests
|
|
{
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Read_ReturnsZeros()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
var buffer = new byte[1024];
|
|
int bytesRead = stream.Read(buffer, 0, buffer.Length);
|
|
|
|
Assert.AreEqual(1024, bytesRead, "Should read requested number of bytes");
|
|
|
|
// Verify all bytes are zero
|
|
for (int i = 0; i < buffer.Length; i++)
|
|
{
|
|
Assert.AreEqual(0, buffer[i], $"Byte at position {i} should be zero");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task Test_Fat32ZeroStream_ReadAsync_ReturnsZeros_Async()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
var buffer = new byte[1024];
|
|
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
|
|
|
Assert.AreEqual(1024, bytesRead, "Should read requested number of bytes");
|
|
|
|
// Verify all bytes are zero
|
|
for (int i = 0; i < buffer.Length; i++)
|
|
{
|
|
Assert.AreEqual(0, buffer[i], $"Byte at position {i} should be zero");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Length_MatchesBlockSize()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
Assert.AreEqual(blockSize, stream.Length, "Stream length should match block size");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Seek_Works()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
// Seek to beginning
|
|
var position = stream.Seek(0, SeekOrigin.Begin);
|
|
Assert.AreEqual(0, position, "Should be at position 0");
|
|
|
|
// Seek to middle
|
|
position = stream.Seek(blockSize / 2, SeekOrigin.Begin);
|
|
Assert.AreEqual(blockSize / 2, position, "Should be at middle position");
|
|
|
|
// Seek relative to current
|
|
position = stream.Seek(1024, SeekOrigin.Current);
|
|
Assert.AreEqual(blockSize / 2 + 1024, position, "Should be at relative position");
|
|
|
|
// Seek from end
|
|
position = stream.Seek(-1024, SeekOrigin.End);
|
|
Assert.AreEqual(blockSize - 1024, position, "Should be at position from end");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Write_ThrowsNotSupported()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
var buffer = new byte[1024];
|
|
Assert.Throws<NotSupportedException>(() => stream.Write(buffer, 0, buffer.Length));
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_MultipleInstances_ShareBuffer()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
|
|
// Create two streams with the same block size
|
|
using var stream1 = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
using var stream2 = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
// Both should read zeros
|
|
var buffer1 = new byte[1024];
|
|
var buffer2 = new byte[1024];
|
|
|
|
stream1.ReadExactly(buffer1);
|
|
stream2.ReadExactly(buffer2);
|
|
|
|
// Verify both have zeros
|
|
for (int i = 0; i < buffer1.Length; i++)
|
|
{
|
|
Assert.AreEqual(0, buffer1[i], $"Buffer1 byte at position {i} should be zero");
|
|
Assert.AreEqual(0, buffer2[i], $"Buffer2 byte at position {i} should be zero");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_ReadPartial_ReturnsCorrectCount()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
// Seek to near the end
|
|
stream.Seek(blockSize - 100, SeekOrigin.Begin);
|
|
|
|
var buffer = new byte[1024];
|
|
int bytesRead = stream.Read(buffer, 0, buffer.Length);
|
|
|
|
Assert.AreEqual(100, bytesRead, "Should only read remaining bytes");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_ReadPastEnd_ReturnsZero()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
// Seek to end
|
|
stream.Seek(0, SeekOrigin.End);
|
|
|
|
var buffer = new byte[1024];
|
|
int bytesRead = stream.Read(buffer, 0, buffer.Length);
|
|
|
|
Assert.AreEqual(0, bytesRead, "Should return 0 when reading past end");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_CanRead_CanWrite_CanSeek()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
Assert.IsTrue(stream.CanRead, "CanRead should be true");
|
|
Assert.IsFalse(stream.CanWrite, "CanWrite should be false");
|
|
Assert.IsTrue(stream.CanSeek, "CanSeek should be true");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_InvalidBlockSize_Throws()
|
|
{
|
|
Assert.Throws<ArgumentException>(() => new Fat32Filesystem.Fat32ZeroStream(0));
|
|
Assert.Throws<ArgumentException>(() => new Fat32Filesystem.Fat32ZeroStream(-1));
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_SetLength_ThrowsNotSupported()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
Assert.Throws<NotSupportedException>(() => stream.SetLength(1024));
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Flush_DoesNotThrow()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
Assert.DoesNotThrow(() => stream.Flush());
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Position_Property()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
Assert.AreEqual(0, stream.Position, "Initial position should be 0");
|
|
|
|
stream.Position = 1024;
|
|
Assert.AreEqual(1024, stream.Position, "Position should be settable");
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Position_InvalidValue_Throws()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => stream.Position = -1);
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => stream.Position = blockSize + 1);
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Seek_InvalidOrigin_Throws()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
Assert.Throws<ArgumentException>(() => stream.Seek(0, (SeekOrigin)999));
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Seek_OutOfRange_Throws()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => stream.Seek(-1, SeekOrigin.Begin));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => stream.Seek(blockSize + 1, SeekOrigin.Begin));
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Read_NullBuffer_Throws()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
#pragma warning disable CS8600, CS8625
|
|
Assert.Throws<ArgumentNullException>(() => stream.ReadExactly(null, 0, 1024));
|
|
#pragma warning restore CS8600, CS8625
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Read_InvalidOffset_Throws()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
var buffer = new byte[1024];
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => stream.ReadExactly(buffer, -1, 1024));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => stream.ReadExactly(buffer, buffer.Length + 1, 1024));
|
|
}
|
|
|
|
[Test]
|
|
public void Test_Fat32ZeroStream_Read_InvalidCount_Throws()
|
|
{
|
|
const int blockSize = 1024 * 1024; // 1MB
|
|
using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize);
|
|
|
|
var buffer = new byte[1024];
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => stream.ReadExactly(buffer, 0, -1));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => stream.ReadExactly(buffer, 512, 1024)); // Exceeds buffer length
|
|
}
|
|
}
|