diff --git a/src/c#/BowlTest/BowlTest.csproj b/src/c#/BowlTest/BowlTest.csproj
deleted file mode 100644
index 32b4662a..00000000
--- a/src/c#/BowlTest/BowlTest.csproj
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
- net10.0
- disable
- enable
- false
- true
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/c#/DifferentialTest/Abstractions/BZip2CompressionProviderTests.cs b/src/c#/DifferentialTest/Abstractions/BZip2CompressionProviderTests.cs
deleted file mode 100644
index 64495056..00000000
--- a/src/c#/DifferentialTest/Abstractions/BZip2CompressionProviderTests.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-using GeneralUpdate.Differential.Abstractions;
-using GeneralUpdate.Differential.Binary;
-
-namespace DifferentialTest.Abstractions
-{
- ///
- /// 分支覆盖点:
- /// 1. FormatVersion — 始终返回 0x00
- /// 2. CreateCompressStream — 返回 BZip2OutputStream
- /// 3. CreateCompressStream — IsStreamOwner = false
- /// 4. CreateDecompressStream — 返回 BZip2InputStream
- /// 5. 空流/正常MemoryStream — 行为验证
- ///
- /// 触发条件:正常MemoryStream
- /// 预期结果:正确版本号、正确的流类型、IsStreamOwner=false
- ///
- public class BZip2CompressionProviderTests
- {
- [Fact(DisplayName = "FormatVersion_始终返回0x00")]
- public void FormatVersion_Always_Returns00()
- {
- var provider = new BZip2CompressionProvider();
-
- Assert.Equal((byte)0x00, provider.FormatVersion);
- }
-
- [Fact(DisplayName = "CreateCompressStream_有效MemoryStream_返回BZip2OutputStream")]
- public void CreateCompressStream_ValidStream_ReturnsBZip2OutputStream()
- {
- var provider = new BZip2CompressionProvider();
-
- using var ms = new MemoryStream();
- using var stream = provider.CreateCompressStream(ms);
-
- Assert.NotNull(stream);
- Assert.IsType(stream);
- }
-
- [Fact(DisplayName = "CreateCompressStream_输出的BZip2OutputStream_IsStreamOwner为false")]
- public void CreateCompressStream_Result_IsStreamOwnerIsFalse()
- {
- var provider = new BZip2CompressionProvider();
-
- using var ms = new MemoryStream();
- using var stream = provider.CreateCompressStream(ms);
-
- var bz2Out = Assert.IsType(stream);
- Assert.False(bz2Out.IsStreamOwner);
- }
-
- [Fact(DisplayName = "CreateCompressStream_写入数据后_原始流包含数据")]
- public void CreateCompressStream_WriteData_UnderlyingStreamContainsData()
- {
- var provider = new BZip2CompressionProvider();
- var data = new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 };
-
- using var ms = new MemoryStream();
- using (var stream = provider.CreateCompressStream(ms))
- {
- stream.Write(data, 0, data.Length);
- }
-
- Assert.True(ms.Length > 0);
- }
-
- [Fact(DisplayName = "CreateCompressStream_释放包装流_不关闭原始流")]
- public void CreateCompressStream_DisposeWrapper_DoesNotCloseUnderlying()
- {
- var provider = new BZip2CompressionProvider();
-
- using var ms = new MemoryStream();
- using (var stream = provider.CreateCompressStream(ms))
- {
- stream.WriteByte(42);
- }
-
- Assert.True(ms.CanRead);
- }
-
- [Fact(DisplayName = "CreateDecompressStream_有效MemoryStream_返回BZip2InputStream")]
- public void CreateDecompressStream_ValidStream_ReturnsBZip2InputStream()
- {
- // 需要有效的BZip2数据来创建InputStream,但仅验证类型
- var provider = new BZip2CompressionProvider();
-
- // 构造最小的有效BZip2数据
- using var ms = new MemoryStream();
- var minimalBz2 = new byte[] {
- (byte)'B', (byte)'Z', (byte)'h', (byte)'1',
- 0x31, 0x41, 0x59, 0x26, 0x53, 0x59,
- 0x00, 0x00, 0x00, 0x00, 0x00,
- (byte)0x17, (byte)'r', (byte)'E', (byte)'8', (byte)'P', (byte)0x90,
- 0x00, 0x00, 0x00, 0x00
- };
- ms.Write(minimalBz2, 0, minimalBz2.Length);
- ms.Position = 0;
-
- // InputStream 的构造函数会从流中读取;即便"空"也能创建(只是streamEnd=true)
- var stream = provider.CreateDecompressStream(ms);
-
- Assert.NotNull(stream);
- Assert.IsType(stream);
- }
-
- [Fact(DisplayName = "CreateCompressStream_CancellationToken传递_不抛出异常")]
- public void CreateCompressStream_WithCancellationToken_DoesNotThrow()
- {
- var provider = new BZip2CompressionProvider();
- using var ms = new MemoryStream();
- using var cts = new CancellationTokenSource();
-
- using var stream = provider.CreateCompressStream(ms, cts.Token);
-
- Assert.NotNull(stream);
- }
-
- [Fact(DisplayName = "CreateDecompressStream_CancellationToken传递_不抛出异常")]
- public void CreateDecompressStream_WithCancellationToken_DoesNotThrow()
- {
- var provider = new BZip2CompressionProvider();
- using var ms = new MemoryStream();
-
- using var stream = provider.CreateDecompressStream(ms);
-
- Assert.NotNull(stream);
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Abstractions/BrotliCompressionProviderTests.cs b/src/c#/DifferentialTest/Abstractions/BrotliCompressionProviderTests.cs
deleted file mode 100644
index 8c223aa4..00000000
--- a/src/c#/DifferentialTest/Abstractions/BrotliCompressionProviderTests.cs
+++ /dev/null
@@ -1,154 +0,0 @@
-#if NET6_0_OR_GREATER
-using System.IO.Compression;
-using GeneralUpdate.Differential.Abstractions;
-
-namespace DifferentialTest.Abstractions
-{
- ///
- /// BrotliCompressionProvider 分支覆盖测试。
- /// 覆盖:构造函数(optimalLevel true/false)、FormatVersion、CreateCompressStream、
- /// CreateDecompressStream、leaveOpen 行为、压缩解压往返。
- /// 前置条件:GeneralUpdate.Differential 需以 net6.0+ 目标框架编译,
- /// 使 BrotliCompressionProvider 类型可用。
- ///
- public class BrotliCompressionProviderTests
- {
- [Fact(DisplayName = "构造函数_默认参数_使用Optimal压缩级别")]
- public void Constructor_Default_UsesOptimalCompression()
- {
- // Arrange & Act
- var provider = new BrotliCompressionProvider();
-
- // Assert
- Assert.NotNull(provider);
- }
-
- [Fact(DisplayName = "构造函数_optimalLevel为false_使用Fastest压缩级别")]
- public void Constructor_OptimalLevelFalse_UsesFastestCompression()
- {
- // Arrange & Act
- var provider = new BrotliCompressionProvider(optimalLevel: false);
-
- // Assert
- Assert.NotNull(provider);
- }
-
- [Fact(DisplayName = "FormatVersion_始终返回0x02")]
- public void FormatVersion_Always_Returns02()
- {
- // Arrange
- var provider = new BrotliCompressionProvider();
-
- // Act
- var version = provider.FormatVersion;
-
- // Assert
- Assert.Equal((byte)0x02, version);
- }
-
- [Fact(DisplayName = "CreateCompressStream_有效MemoryStream_返回BrotliStream")]
- public void CreateCompressStream_ValidStream_ReturnsBrotliStream()
- {
- // Arrange
- var provider = new BrotliCompressionProvider();
- using var ms = new MemoryStream();
-
- // Act
- using var stream = provider.CreateCompressStream(ms);
-
- // Assert
- Assert.NotNull(stream);
- var brotliStream = Assert.IsType(stream);
- Assert.True(brotliStream.CanWrite);
- }
-
- [Fact(DisplayName = "CreateCompressStream_写入数据_原始流包含压缩数据")]
- public void CreateCompressStream_WriteData_UnderlyingContainsCompressedData()
- {
- // Arrange
- var provider = new BrotliCompressionProvider();
- var data = new byte[1024];
- new Random(42).NextBytes(data);
-
- using var ms = new MemoryStream();
-
- // Act
- using (var stream = provider.CreateCompressStream(ms))
- {
- stream.Write(data, 0, data.Length);
- }
-
- // Assert
- Assert.True(ms.Length > 0);
- }
-
- [Fact(DisplayName = "CreateCompressStream_释放包装流_不关闭原始流")]
- public void CreateCompressStream_Dispose_DoesNotCloseUnderlying()
- {
- // Arrange
- var provider = new BrotliCompressionProvider();
-
- using var ms = new MemoryStream();
-
- // Act
- using (var stream = provider.CreateCompressStream(ms))
- {
- stream.WriteByte(42);
- }
-
- // Assert: leaveOpen=true → underlying stream still readable
- Assert.True(ms.CanRead);
- }
-
- [Fact(DisplayName = "CreateDecompressStream_有效压缩数据_返回BrotliStream")]
- public void CreateDecompressStream_ValidData_ReturnsBrotliStream()
- {
- // Arrange
- var provider = new BrotliCompressionProvider();
- var data = System.Text.Encoding.UTF8.GetBytes("Hello, Brotli World!");
-
- // Compress first
- using var compressedMs = new MemoryStream();
- using (var compressStream = provider.CreateCompressStream(compressedMs))
- {
- compressStream.Write(data, 0, data.Length);
- }
-
- // Act
- compressedMs.Position = 0;
- using var decompressStream = provider.CreateDecompressStream(compressedMs);
-
- // Assert
- Assert.NotNull(decompressStream);
- Assert.IsType(decompressStream);
- Assert.True(decompressStream.CanRead);
- }
-
- [Fact(DisplayName = "压缩解压往返_产生相同数据")]
- public void CompressDecompress_RoundTrip_ProducesIdenticalData()
- {
- // Arrange
- var provider = new BrotliCompressionProvider();
- var originalData = new byte[4096];
- new Random(42).NextBytes(originalData);
-
- // Act — compress
- using var compressedMs = new MemoryStream();
- using (var compressStream = provider.CreateCompressStream(compressedMs))
- {
- compressStream.Write(originalData, 0, originalData.Length);
- }
-
- // Act — decompress
- compressedMs.Position = 0;
- using var decompressStream = provider.CreateDecompressStream(compressedMs);
- using var resultMs = new MemoryStream();
- decompressStream.CopyTo(resultMs);
-
- // Assert
- var decompressedData = resultMs.ToArray();
- Assert.Equal(originalData, decompressedData);
- }
- }
-}
-#endif
diff --git a/src/c#/DifferentialTest/Abstractions/DeflateCompressionProviderTests.cs b/src/c#/DifferentialTest/Abstractions/DeflateCompressionProviderTests.cs
deleted file mode 100644
index 2a92530c..00000000
--- a/src/c#/DifferentialTest/Abstractions/DeflateCompressionProviderTests.cs
+++ /dev/null
@@ -1,208 +0,0 @@
-using System.IO.Compression;
-using GeneralUpdate.Differential.Abstractions;
-
-namespace DifferentialTest.Abstractions
-{
- ///
- /// 分支覆盖点:
- /// 1. 构造函数 — optimalLevel=true → CompressionLevel.Optimal
- /// 2. 构造函数 — optimalLevel=false → CompressionLevel.Fastest
- /// 3. FormatVersion — 始终返回 0x01
- /// 4. CreateCompressStream — 返回 DeflateStream 包装原始流出
- /// 5. CreateCompressStream — CancellationToken默认参数
- /// 6. CreateDecompressStream — 返回 DeflateStream 包装原始流入
- /// 7. 空流/已关闭流 — 异常分支
- ///
- /// 触发条件:不同构造函数参数 / 不同流状态
- /// 预期结果:正确版本号、正确流类型、leaveOpen行为正确
- ///
- public class DeflateCompressionProviderTests
- {
- [Fact(DisplayName = "构造函数_optimalLevel为true_使用Optimal压缩级别")]
- public void Constructor_OptimalLevelTrue_UsesOptimalCompression()
- {
- var provider = new DeflateCompressionProvider(optimalLevel: true);
-
- using var ms = new MemoryStream();
- using var compressStream = provider.CreateCompressStream(ms);
- Assert.NotNull(compressStream);
- var deflateStream = Assert.IsType(compressStream);
- // 默认情况下Optimal级别无法直接读取,但可以校验流不为空
- }
-
- [Fact(DisplayName = "构造函数_optimalLevel为false_使用Fastest压缩级别")]
- public void Constructor_OptimalLevelFalse_UsesFastestCompression()
- {
- var provider = new DeflateCompressionProvider(optimalLevel: false);
-
- using var ms = new MemoryStream();
- using var compressStream = provider.CreateCompressStream(ms);
- Assert.NotNull(compressStream);
- Assert.IsType(compressStream);
- }
-
- [Fact(DisplayName = "构造函数_默认参数_使用Optimal压缩级别")]
- public void Constructor_DefaultParameter_UsesOptimalCompression()
- {
- var provider = new DeflateCompressionProvider();
-
- Assert.NotNull(provider);
- }
-
- [Fact(DisplayName = "FormatVersion_始终返回0x01")]
- public void FormatVersion_Always_Returns01()
- {
- var provider = new DeflateCompressionProvider();
-
- Assert.Equal((byte)0x01, provider.FormatVersion);
- }
-
- [Fact(DisplayName = "CreateCompressStream_正常MemoryStream_返回DeflateStream包装")]
- public void CreateCompressStream_ValidStream_ReturnsDeflateStreamWrapper()
- {
- var provider = new DeflateCompressionProvider();
-
- using var ms = new MemoryStream();
- using var compressStream = provider.CreateCompressStream(ms);
-
- Assert.NotNull(compressStream);
- Assert.IsType(compressStream);
- Assert.True(compressStream.CanWrite);
- }
-
- [Fact(DisplayName = "CreateCompressStream_写入数据后_原始流包含压缩数据")]
- public void CreateCompressStream_WriteData_UnderlyingStreamContainsCompressedData()
- {
- var provider = new DeflateCompressionProvider();
- var data = new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 };
-
- using var ms = new MemoryStream();
- using (var compressStream = provider.CreateCompressStream(ms))
- {
- compressStream.Write(data, 0, data.Length);
- } // 释放压缩流以刷新数据
-
- Assert.True(ms.Length > 0);
- }
-
- [Fact(DisplayName = "CreateCompressStream_leaveOpen为true_释放包装流不关闭原始流")]
- public void CreateCompressStream_LeaveOpen_DisposingWrapperDoesNotCloseUnderlying()
- {
- var provider = new DeflateCompressionProvider();
-
- using var ms = new MemoryStream();
- using (var compressStream = provider.CreateCompressStream(ms))
- {
- compressStream.WriteByte(42);
- }
-
- // 原始流仍可读写
- Assert.True(ms.CanRead);
- }
-
- [Fact(DisplayName = "CreateDecompressStream_正常MemoryStream_返回DeflateStream包装")]
- public void CreateDecompressStream_ValidStream_ReturnsDeflateStreamWrapper()
- {
- var provider = new DeflateCompressionProvider();
-
- // 先压缩一些数据
- using var compressedMs = new MemoryStream();
- using (var compressStream = provider.CreateCompressStream(compressedMs))
- {
- var data = System.Text.Encoding.UTF8.GetBytes("hello world hello world hello world");
- compressStream.Write(data, 0, data.Length);
- }
-
- // 再解压
- compressedMs.Position = 0;
- using var decompressStream = provider.CreateDecompressStream(compressedMs);
-
- Assert.NotNull(decompressStream);
- Assert.IsType(decompressStream);
- Assert.True(decompressStream.CanRead);
- }
-
- [Fact(DisplayName = "CreateDecompressStream_leaveOpen为true_释放包装流不关闭原始流")]
- public void CreateDecompressStream_LeaveOpen_DisposingWrapperDoesNotCloseUnderlying()
- {
- var provider = new DeflateCompressionProvider();
-
- using var compressedMs = new MemoryStream();
- using (var compressStream = provider.CreateCompressStream(compressedMs))
- {
- var data = new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 };
- compressStream.Write(data, 0, data.Length);
- }
-
- compressedMs.Position = 0;
- using (var decompressStream = provider.CreateDecompressStream(compressedMs))
- {
- var buf = new byte[10];
- int totalRead = 0;
- while (totalRead < buf.Length)
- {
- int read = decompressStream.Read(buf, totalRead, buf.Length - totalRead);
- if (read == 0) break;
- totalRead += read;
- }
- }
-
- // 原始流仍可寻道
- Assert.True(compressedMs.CanSeek);
- }
-
- [Fact(DisplayName = "CreateDecompressStream_空压缩数据_读取返回0")]
- public void CreateDecompressStream_EmptyData_ReadReturnsZero()
- {
- var provider = new DeflateCompressionProvider();
-
- using var compressedMs = new MemoryStream();
- using (var compressStream = provider.CreateCompressStream(compressedMs))
- {
- // 不写入任何数据
- }
-
- compressedMs.Position = 0;
- using var decompressStream = provider.CreateDecompressStream(compressedMs);
- var buf = new byte[10];
-
- // 空DeflateStream可能抛出或返回0,取决于BaseStream状态
- // 这里验证不会NullReferenceException
- Assert.NotNull(decompressStream);
- }
-
- [Fact(DisplayName = "CreateCompressStream_CancellationToken已取消_压缩流仍可创建(tokeng未被使用)")]
- public void CreateCompressStream_CancellationTokenPassed_DoesNotThrow()
- {
- var provider = new DeflateCompressionProvider();
- using var ms = new MemoryStream();
- using var cts = new CancellationTokenSource();
-
- // ICompressionProvider 接口中有 CancellationToken,但 Deflate 实现可能忽略
- using var stream = provider.CreateCompressStream(ms, cts.Token);
-
- Assert.NotNull(stream);
- }
-
- [Fact(DisplayName = "最优级别和最快级别_压缩结果可能不同")]
- public void OptimalVsFastest_CompressionResultsMayDiffer()
- {
- var optimal = new DeflateCompressionProvider(optimalLevel: true);
- var fastest = new DeflateCompressionProvider(optimalLevel: false);
- var data = new byte[1024];
- new Random(42).NextBytes(data);
-
- using var msOpt = new MemoryStream();
- using (var cs = optimal.CreateCompressStream(msOpt))
- cs.Write(data, 0, data.Length);
-
- using var msFast = new MemoryStream();
- using (var cs = fastest.CreateCompressStream(msFast))
- cs.Write(data, 0, data.Length);
-
- // 两者都能产生压缩输出(长度不一定相同,但都不应为0)
- Assert.True(msOpt.Length > 0);
- Assert.True(msFast.Length > 0);
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Binary/BZip2InputStreamTests.cs b/src/c#/DifferentialTest/Binary/BZip2InputStreamTests.cs
deleted file mode 100644
index 9240f5f0..00000000
--- a/src/c#/DifferentialTest/Binary/BZip2InputStreamTests.cs
+++ /dev/null
@@ -1,218 +0,0 @@
-using GeneralUpdate.Differential.Binary;
-
-namespace DifferentialTest.Binary
-{
- ///
- /// 分支覆盖点:
- /// 1. CanRead/CanSeek/CanWrite — 暴露底层流属性
- /// 2. Length/Position get — 暴露底层流属性
- /// 3. Position set → NotSupportedException
- /// 4. Seek → NotSupportedException
- /// 5. SetLength → NotSupportedException
- /// 6. Write/WriteByte → NotSupportedException
- /// 7. Read — buffer为null → ArgumentNullException
- /// 8. Read — 正常读取行为
- /// 9. ReadByte — streamEnd → -1
- /// 10. IsStreamOwner — 默认true, 可设置false
- /// 11. Close — IsStreamOwner=false时不关闭底层流
- /// 12. Flush — 转发到底层流
- /// 13. 构造函数 — 初始化内部数组
- /// 14. 构造函数 — 空流(streamEnd立即为true)
- ///
- /// 触发条件:各种流状态、参数组合
- /// 预期结果:异常正确抛出、流属性正确、Close行为正确
- ///
- public class BZip2InputStreamTests
- {
- [Fact(DisplayName = "构造函数_传入空MemoryStream_streamEnd为true")]
- public void Constructor_EmptyStream_StreamEndIsTrue()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.False(stream.CanWrite);
- Assert.True(stream.CanRead);
- }
-
- [Fact(DisplayName = "构造函数_传入有效BZip2数据头_可正确初始化")]
- public void Constructor_ValidBz2Header_InitializesCorrectly()
- {
- var bz2Data = CreateMinimalBz2Stream();
-
- using var stream = new BZip2InputStream(bz2Data);
-
- Assert.True(stream.CanRead);
- }
-
- [Fact(DisplayName = "CanWrite_始终返回false")]
- public void CanWrite_Always_ReturnsFalse()
- {
- using var ms = CreateMinimalBz2Stream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.False(stream.CanWrite);
- }
-
- [Fact(DisplayName = "CanRead_返回底层流CanRead")]
- public void CanRead_ReturnsUnderlyingStreamCanRead()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.Equal(ms.CanRead, stream.CanRead);
- }
-
- [Fact(DisplayName = "CanSeek_返回底层流CanSeek")]
- public void CanSeek_ReturnsUnderlyingStreamCanSeek()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.Equal(ms.CanSeek, stream.CanSeek);
- }
-
- [Fact(DisplayName = "Length_返回底层流Length")]
- public void Length_ReturnsUnderlyingStreamLength()
- {
- using var ms = new MemoryStream(new byte[100]);
- using var stream = new BZip2InputStream(ms);
-
- Assert.Equal(ms.Length, stream.Length);
- }
-
- [Fact(DisplayName = "Position_get_返回底层流Position")]
- public void Position_Get_ReturnsUnderlyingStreamPosition()
- {
- using var ms = new MemoryStream(new byte[100]);
- ms.Position = 10;
- using var stream = new BZip2InputStream(ms);
-
- Assert.Equal(ms.Position, stream.Position);
- }
-
- [Fact(DisplayName = "Position_set_抛出NotSupportedException")]
- public void Position_Set_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.Throws(() => stream.Position = 0);
- }
-
- [Fact(DisplayName = "Seek_抛出NotSupportedException")]
- public void Seek_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.Throws(() => stream.Seek(0, SeekOrigin.Begin));
- }
-
- [Fact(DisplayName = "SetLength_抛出NotSupportedException")]
- public void SetLength_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.Throws(() => stream.SetLength(0));
- }
-
- [Fact(DisplayName = "Write_抛出NotSupportedException")]
- public void Write_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.Throws(() => stream.Write(new byte[1], 0, 1));
- }
-
- [Fact(DisplayName = "WriteByte_抛出NotSupportedException")]
- public void WriteByte_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.Throws(() => stream.WriteByte(0));
- }
-
- [Fact(DisplayName = "Read_buffer为null_抛出ArgumentNullException")]
- public void Read_NullBuffer_ThrowsArgumentNullException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
-
- Assert.Throws(() => stream.Read(null!, 0, 1));
- }
-
- [Fact(DisplayName = "Read_空流_streamEnd返回-1")]
- public void Read_EmptyStream_ReturnsMinusOne()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2InputStream(ms);
- var buffer = new byte[10];
-
- int read = stream.Read(buffer, 0, buffer.Length);
-
- // 空流可能返回-1或触发异常
- Assert.True(read == -1 || read == 0);
- }
-
- [Fact(DisplayName = "IsStreamOwner_默认为true_Close关闭底层流")]
- public void IsStreamOwner_DefaultTrue_CloseClosesUnderlyingStream()
- {
- var ms = new MemoryStream();
- var stream = new BZip2InputStream(ms);
-
- Assert.True(stream.IsStreamOwner);
- }
-
- [Fact(DisplayName = "IsStreamOwner_设为false_Close不关闭底层流")]
- public void IsStreamOwner_SetFalse_CloseDoesNotCloseUnderlying()
- {
- var ms = new MemoryStream();
- var stream = new BZip2InputStream(ms) { IsStreamOwner = false };
-
- stream.Close();
-
- // 底层流不应被关闭(无法直接验证,但不抛出异常即可)
- Assert.False(stream.IsStreamOwner);
- }
-
- [Fact(DisplayName = "Flush_底层流存在_转发到底层流")]
- public void Flush_UnderlyingStreamExists_ForwardsToUnderlying()
- {
- using var ms = new MemoryStream(new byte[10]);
- using var stream = new BZip2InputStream(ms);
-
- // Flush 不应抛出异常
- stream.Flush();
- }
-
- [Fact(DisplayName = "构造函数_传入非BZip2数据_安全初始化")]
- public void Constructor_NonBz2Data_HandlesGracefully()
- {
- using var ms = new MemoryStream(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
- using var stream = new BZip2InputStream(ms);
-
- // 构造函数应在无效数据下也不崩溃(streamEnd=true)
- Assert.NotNull(stream);
- }
-
- ///
- /// 创建最小有效BZip2流 (BZh1 header + stream end marker)
- ///
- private static MemoryStream CreateMinimalBz2Stream()
- {
- var ms = new MemoryStream();
- ms.Write(new byte[] {
- (byte)'B', (byte)'Z', (byte)'h', (byte)'1',
- 0x31, 0x41, 0x59, 0x26, 0x53, 0x59,
- 0x00, 0x00, 0x00, 0x00, 0x00,
- (byte)0x17, (byte)'r', (byte)'E', (byte)'8', (byte)'P', (byte)0x90,
- 0x00, 0x00, 0x00, 0x00
- }, 0, 24);
- ms.Position = 0;
- return ms;
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Binary/BZip2OutputStreamTests.cs b/src/c#/DifferentialTest/Binary/BZip2OutputStreamTests.cs
deleted file mode 100644
index 5109b66b..00000000
--- a/src/c#/DifferentialTest/Binary/BZip2OutputStreamTests.cs
+++ /dev/null
@@ -1,297 +0,0 @@
-using GeneralUpdate.Differential.Binary;
-
-namespace DifferentialTest.Binary
-{
- ///
- /// 分支覆盖点:
- /// 1. CanRead/CanSeek — 始终返回 false
- /// 2. CanWrite — 返回底层流 CanWrite
- /// 3. Length/Position get — 暴露底层流属性
- /// 4. Position set → NotSupportedException
- /// 5. Seek → NotSupportedException
- /// 6. SetLength → NotSupportedException
- /// 7. Read/ReadByte → NotSupportedException
- /// 8. Write — buffer为null → ArgumentNullException
- /// 9. Write — offset < 0 → ArgumentOutOfRangeException
- /// 10. Write — count < 0 → ArgumentOutOfRangeException
- /// 11. Write — offset+count > buffer.Length → ArgumentException
- /// 12. WriteByte — run-length encoding 逻辑
- /// - currentChar == -1 → 新字符开始
- /// - currentChar == num → runLength++ (runLength > 254 分支)
- /// - currentChar != num → WriteRun 重置
- /// 13. 构造函数 — blockSize clamp (blockSize > 9 / < 1)
- /// 14. IsStreamOwner — 默认为true
- /// 15. Close → Dispose
- /// 16. BytesWritten — 累计输出字节
- ///
- /// 触发条件:各种入参、流状态
- /// 预期结果:异常正确抛出、属性正确、压缩可写出
- ///
- public class BZip2OutputStreamTests
- {
- [Fact(DisplayName = "构造函数_默认blockSize_使用blockSize=9")]
- public void Constructor_DefaultBlocksize_UsesBlockSize9()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.False(stream.CanRead);
- Assert.True(stream.CanWrite);
- Assert.Equal(0, stream.BytesWritten);
- }
-
- [Theory(DisplayName = "构造函数_blockSize边界值_安全处理")]
- [InlineData(0, 1)] // clamp to 1
- [InlineData(1, 1)]
- [InlineData(9, 9)]
- [InlineData(10, 9)] // clamp to 9
- [InlineData(-1, 1)] // clamp to 1
- [InlineData(999, 9)] // clamp to 9
- public void Constructor_BlockSizeBoundaries_HandlesSafely(int input, int _)
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms, input);
-
- Assert.NotNull(stream);
- }
-
- [Fact(DisplayName = "CanRead_始终返回false")]
- public void CanRead_Always_ReturnsFalse()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.False(stream.CanRead);
- }
-
- [Fact(DisplayName = "CanSeek_始终返回false")]
- public void CanSeek_Always_ReturnsFalse()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.False(stream.CanSeek);
- }
-
- [Fact(DisplayName = "CanWrite_返回底层流CanWrite")]
- public void CanWrite_ReturnsUnderlyingStreamCanWrite()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.Equal(ms.CanWrite, stream.CanWrite);
- }
-
- [Fact(DisplayName = "Length_返回底层流Length")]
- public void Length_ReturnsUnderlyingStreamLength()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.Equal(ms.Length, stream.Length);
- }
-
- [Fact(DisplayName = "Position_get_返回底层流Position")]
- public void Position_Get_ReturnsUnderlyingStreamPosition()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.Equal(ms.Position, stream.Position);
- }
-
- [Fact(DisplayName = "Position_set_抛出NotSupportedException")]
- public void Position_Set_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.Throws(() => stream.Position = 0);
- }
-
- [Fact(DisplayName = "Seek_抛出NotSupportedException")]
- public void Seek_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.Throws(() => stream.Seek(0, SeekOrigin.Begin));
- }
-
- [Fact(DisplayName = "SetLength_抛出NotSupportedException")]
- public void SetLength_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.Throws(() => stream.SetLength(0));
- }
-
- [Fact(DisplayName = "Read_抛出NotSupportedException")]
- public void Read_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.Throws(() => stream.Read(new byte[1], 0, 1));
- }
-
- [Fact(DisplayName = "ReadByte_抛出NotSupportedException")]
- public void ReadByte_ThrowsNotSupportedException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.Throws(() => stream.ReadByte());
- }
-
- [Fact(DisplayName = "Write_buffer为null_抛出ArgumentNullException")]
- public void Write_NullBuffer_ThrowsArgumentNullException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- var ex = Assert.Throws(() => stream.Write(null!, 0, 1));
- Assert.Equal("buffer", ex.ParamName);
- }
-
- [Fact(DisplayName = "Write_offset为负数_抛出ArgumentOutOfRangeException")]
- public void Write_NegativeOffset_ThrowsArgumentOutOfRangeException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- var ex = Assert.Throws(() => stream.Write(new byte[10], -1, 1));
- Assert.Equal("offset", ex.ParamName);
- }
-
- [Fact(DisplayName = "Write_count为负数_抛出ArgumentOutOfRangeException")]
- public void Write_NegativeCount_ThrowsArgumentOutOfRangeException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- var ex = Assert.Throws(() => stream.Write(new byte[10], 0, -1));
- Assert.Equal("count", ex.ParamName);
- }
-
- [Fact(DisplayName = "Write_offset+count超出范围_抛出ArgumentException")]
- public void Write_OffsetPlusCountExceeds_ThrowsArgumentException()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- Assert.Throws(() => stream.Write(new byte[5], 3, 5));
- }
-
- [Fact(DisplayName = "WriteByte_写入单个字节_BytesWritten递增")]
- public void WriteByte_SingleByte_BytesWrittenIncrements()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- stream.WriteByte(65); // 'A'
- stream.WriteByte(66); // 'B'
-
- // BZip2有header开销,写出0字节数据时也可能有输出
- Assert.NotNull(stream);
- }
-
- [Fact(DisplayName = "Write和Close_写入数据后Close_原始流包含数据")]
- public void WriteAndClose_WriteDataThenClose_UnderlyingContainsData()
- {
- using var ms = new MemoryStream();
- using (var stream = new BZip2OutputStream(ms))
- {
- var data = new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 };
- stream.Write(data, 0, data.Length);
- }
-
- // Close后原始流应有BZip2压缩数据
- Assert.True(ms.Length > 0);
- }
-
- [Fact(DisplayName = "WriteByte_相同字节254次_runLength编码触发")]
- public void WriteByte_SameByte254Times_RunLengthEncodingTriggered()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- for (int i = 0; i < 255; i++)
- stream.WriteByte(65);
-
- // 应触发runLength > 254分支,WriteRun被调用
- stream.Close();
- Assert.True(ms.Length > 0);
- }
-
- [Fact(DisplayName = "Write和Close_大数据量_能正常压缩")]
- public void WriteAndClose_LargeData_CompressesNormally()
- {
- using var ms = new MemoryStream();
- using (var stream = new BZip2OutputStream(ms) { IsStreamOwner = false })
- {
- var data = new byte[10000];
- new Random(42).NextBytes(data);
- stream.Write(data, 0, data.Length);
- }
-
- Assert.True(ms.Length > 0);
- }
-
- [Fact(DisplayName = "Close_重复调用_不抛出异常")]
- public void Close_MultipleCalls_DoesNotThrow()
- {
- using var ms = new MemoryStream();
- var stream = new BZip2OutputStream(ms);
-
- stream.WriteByte(42);
- stream.Close();
- // 第二次Close/Dispose不应崩溃
- stream.Close();
- }
-
- [Fact(DisplayName = "IsStreamOwner_默认为true_Close关闭底层流")]
- public void IsStreamOwner_DefaultTrue_CloseClosesUnderlyingStream()
- {
- var ms = new MemoryStream();
- var stream = new BZip2OutputStream(ms);
-
- Assert.True(stream.IsStreamOwner);
- }
-
- [Fact(DisplayName = "IsStreamOwner_设为false_Close不关闭底层流")]
- public void IsStreamOwner_SetFalse_CloseDoesNotCloseUnderlying()
- {
- var ms = new MemoryStream();
- var stream = new BZip2OutputStream(ms) { IsStreamOwner = false };
-
- stream.Close();
-
- Assert.False(stream.IsStreamOwner);
- }
-
- [Fact(DisplayName = "Flush_转发到底层流")]
- public void Flush_ForwardsToUnderlying()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- stream.Flush();
- // 不抛出异常即可
- }
-
- [Fact(DisplayName = "Write_count为0_不应有任何输出")]
- public void Write_ZeroCount_DoesNotWrite()
- {
- using var ms = new MemoryStream();
- using var stream = new BZip2OutputStream(ms);
-
- var data = new byte[] { 1, 2, 3 };
- stream.Write(data, 0, 0);
-
- // 写count=0不应有数据输出
- Assert.True(ms.Length == 0);
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Binary/StrangeCRCTests.cs b/src/c#/DifferentialTest/Binary/StrangeCRCTests.cs
deleted file mode 100644
index ed70c828..00000000
--- a/src/c#/DifferentialTest/Binary/StrangeCRCTests.cs
+++ /dev/null
@@ -1,181 +0,0 @@
-using GeneralUpdate.Differential.Binary;
-
-namespace DifferentialTest.Binary
-{
- ///
- /// 分支覆盖点:
- /// 1. 构造函数 — globalCrc = -1 (Reset调用)
- /// 2. Value — 返回 ~globalCrc
- /// 3. Reset — 将 globalCrc 重置为 -1
- /// 4. Update(int) — num < 0 分支 (normalize)
- /// 5. Update(byte[]) — null buffer → ArgumentNullException
- /// 6. Update(byte[], int, int) — offset < 0 / count < 0 / offset+count > buffer.Length → 异常分支
- /// 7. Update 循环 — 遍历 count 次
- /// 8. CRC 一致性 — 相同数据产生相同CRC
- ///
- /// 触发条件:各种入参
- /// 预期结果:CRC值符合预期、异常正确抛出
- ///
- public class StrangeCRCTests
- {
- [Fact(DisplayName = "构造函数_创建实例_默认Value为0")]
- public void Constructor_NewInstance_DefaultValueIsZero()
- {
- var crc = new StrangeCRC();
-
- Assert.Equal(0L, crc.Value);
- }
-
- [Fact(DisplayName = "Reset_重置后_Value归零")]
- public void Reset_AfterUpdate_ValueReturnsToZero()
- {
- var crc = new StrangeCRC();
- crc.Update(42);
-
- crc.Reset();
-
- Assert.Equal(0L, crc.Value);
- }
-
- [Fact(DisplayName = "Update_int单值_Value变化")]
- public void Update_SingleInt_ValueChanges()
- {
- var crc = new StrangeCRC();
-
- crc.Update(65);
-
- Assert.NotEqual(0L, crc.Value);
- }
-
- [Theory(DisplayName = "Update_int多个值_相同输入产生相同CRC")]
- [InlineData(new int[] { 1, 2, 3, 4, 5 })]
- [InlineData(new int[] { 255 })]
- [InlineData(new int[] { 0 })]
- [InlineData(new int[] { })]
- public void Update_MultiInt_SameInputSameCrc(int[] values)
- {
- var crc1 = new StrangeCRC();
- foreach (var v in values) crc1.Update(v);
-
- var crc2 = new StrangeCRC();
- foreach (var v in values) crc2.Update(v);
-
- Assert.Equal(crc1.Value, crc2.Value);
- }
-
- [Fact(DisplayName = "Update_byte数组_正确计算CRC")]
- public void Update_ByteArray_ComputesCrc()
- {
- var crc = new StrangeCRC();
- var data = new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F };
-
- crc.Update(data);
-
- Assert.NotEqual(0L, crc.Value);
- }
-
- [Fact(DisplayName = "Update_byte数组null_抛出ArgumentNullException")]
- public void Update_NullByteArray_ThrowsArgumentNullException()
- {
- var crc = new StrangeCRC();
-
- var ex = Assert.Throws(() => crc.Update((byte[])null!));
-
- Assert.Equal("buffer", ex.ParamName);
- }
-
- [Fact(DisplayName = "Update_带offset和count的byte数组null_抛出ArgumentNullException")]
- public void Update_OffsetCountNullBuffer_ThrowsArgumentNullException()
- {
- var crc = new StrangeCRC();
-
- var ex = Assert.Throws(() => crc.Update(null!, 0, 1));
-
- Assert.Equal("buffer", ex.ParamName);
- }
-
- [Fact(DisplayName = "Update_offset为负数_抛出ArgumentOutOfRangeException")]
- public void Update_NegativeOffset_ThrowsArgumentOutOfRangeException()
- {
- var crc = new StrangeCRC();
- var buffer = new byte[10];
-
- var ex = Assert.Throws(() => crc.Update(buffer, -1, 1));
-
- Assert.Equal("offset", ex.ParamName);
- }
-
- [Fact(DisplayName = "Update_count为负数_抛出ArgumentOutOfRangeException")]
- public void Update_NegativeCount_ThrowsArgumentOutOfRangeException()
- {
- var crc = new StrangeCRC();
- var buffer = new byte[10];
-
- var ex = Assert.Throws(() => crc.Update(buffer, 0, -1));
-
- Assert.Equal("count", ex.ParamName);
- }
-
- [Fact(DisplayName = "Update_offset+count超出buffer长度_抛出ArgumentOutOfRangeException")]
- public void Update_OffsetPlusCountExceedsLength_ThrowsArgumentOutOfRangeException()
- {
- var crc = new StrangeCRC();
- var buffer = new byte[5];
-
- var ex = Assert.Throws(() => crc.Update(buffer, 3, 3));
-
- Assert.Equal("count", ex.ParamName);
- }
-
- [Fact(DisplayName = "Update_offset和count为0_不影响CRC值")]
- public void Update_ZeroCount_DoesNotChangeCrc()
- {
- var crc = new StrangeCRC();
- var original = crc.Value;
- var buffer = new byte[10];
-
- crc.Update(buffer, 0, 0);
-
- Assert.Equal(original, crc.Value);
- }
-
- [Fact(DisplayName = "Update_整个buffer与分段Update_结果一致")]
- public void Update_FullBufferVsSegmented_SameResult()
- {
- var data = new byte[16];
- new Random(99).NextBytes(data);
-
- var crcFull = new StrangeCRC();
- crcFull.Update(data);
-
- var crcSeg = new StrangeCRC();
- crcSeg.Update(data, 0, 8);
- crcSeg.Update(data, 8, 8);
-
- Assert.Equal(crcFull.Value, crcSeg.Value);
- }
-
- [Fact(DisplayName = "Value_获取后仍可继续Update")]
- public void Value_GetValueThenContinueUpdating_ChangesCrc()
- {
- var crc = new StrangeCRC();
- crc.Update(1);
- var val1 = crc.Value;
- crc.Update(2);
- var val2 = crc.Value;
-
- Assert.NotEqual(val1, val2);
- }
-
- [Fact(DisplayName = "Update_int_256边界值normalize分支覆盖")]
- public void Update_IntWith256Boundary_NormalizesCorrectly()
- {
- var crc = new StrangeCRC();
-
- for (int i = 0; i < 1000; i++)
- crc.Update(i % 256);
-
- Assert.NotEqual(0L, crc.Value);
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Differ/BsdiffDifferTests.cs b/src/c#/DifferentialTest/Differ/BsdiffDifferTests.cs
deleted file mode 100644
index a7cb5ac1..00000000
--- a/src/c#/DifferentialTest/Differ/BsdiffDifferTests.cs
+++ /dev/null
@@ -1,207 +0,0 @@
-using Moq;
-using GeneralUpdate.Differential.Abstractions;
-using GeneralUpdate.Differential.Differ;
-
-namespace DifferentialTest.Binary
-{
- ///
- /// 分支覆盖点:
- /// 1. 无参构造函数 — 使用 BZip2CompressionProvider 默认
- /// 2. 带ICompressionProvider的构造函数 — null → ArgumentNullException
- /// 3. CleanAsync — CancellationToken 已取消 → OperationCanceledException
- /// 4. CleanAsync — 委托到 Clean (文件系统依赖)
- /// 5. DirtyAsync — CancellationToken 已取消 → OperationCanceledException
- /// 6. DirtyAsync — 委托到 Dirty
- /// 7. Clean/Dirty (内部) — ValidationParameters 异常分支
- /// - oldFilePath/newFilePath/patchPath 为null/空白
- /// 8. ValidationParameters 三参数所有组合
- ///
- /// 触发条件:各种路径参数、CancellationToken状态
- /// 预期结果:正确异常抛出、正确委托
- ///
- public class BsdiffDifferTests : IDisposable
- {
- private readonly string _testDir;
-
- public BsdiffDifferTests()
- {
- _testDir = Path.Combine(Path.GetTempPath(), $"BsdiffDifferTests_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_testDir);
- }
-
- public void Dispose()
- {
- if (Directory.Exists(_testDir))
- Directory.Delete(_testDir, true);
- }
-
- [Fact(DisplayName = "构造函数_无参_使用BZip2CompressionProvider")]
- public void Constructor_NoArgs_UsesBZip2CompressionProvider()
- {
- var handler = new BsdiffDiffer();
-
- Assert.NotNull(handler);
- }
-
- [Fact(DisplayName = "构造函数_自定义压缩提供者_正确创建实例")]
- public void Constructor_CustomCompressionProvider_CreatesInstance()
- {
- var mockProvider = new Mock();
- mockProvider.Setup(p => p.FormatVersion).Returns(0x01);
-
- var handler = new BsdiffDiffer(mockProvider.Object);
-
- Assert.NotNull(handler);
- }
-
- [Fact(DisplayName = "构造函数_compressionProvider为null_抛出ArgumentNullException")]
- public void Constructor_NullProvider_ThrowsArgumentNullException()
- {
- var ex = Assert.Throws(() => new BsdiffDiffer(null!));
-
- Assert.Equal("compressionProvider", ex.ParamName);
- }
-
- [Fact(DisplayName = "CleanAsync_CancellationToken已取消_抛出OperationCanceledException")]
- public async Task CleanAsync_CancelledToken_ThrowsOperationCanceledException()
- {
- var handler = new BsdiffDiffer();
- using var cts = new CancellationTokenSource();
- cts.Cancel();
-
- await Assert.ThrowsAsync(() =>
- handler.CleanAsync("old", "new", "patch", cts.Token));
- }
-
- [Fact(DisplayName = "DirtyAsync_CancellationToken已取消_抛出OperationCanceledException")]
- public async Task DirtyAsync_CancelledToken_ThrowsOperationCanceledException()
- {
- var handler = new BsdiffDiffer();
- using var cts = new CancellationTokenSource();
- cts.Cancel();
-
- await Assert.ThrowsAsync(() =>
- handler.DirtyAsync("old", "new", "patch", cts.Token));
- }
-
- [Fact(DisplayName = "Clean_文件不存在_正常失败")]
- public async Task Clean_NonExistentFiles_ThrowsFileNotFound()
- {
- var handler = new BsdiffDiffer();
-
- await Assert.ThrowsAsync(() =>
- handler.Clean(
- Path.Combine(_testDir, "nonexistent_old.bin"),
- Path.Combine(_testDir, "nonexistent_new.bin"),
- Path.Combine(_testDir, "output.patch")));
- }
-
- [Fact(DisplayName = "Dirty_文件不存在_正常失败")]
- public async Task Dirty_NonExistentFiles_ThrowsFileNotFound()
- {
- var handler = new BsdiffDiffer();
-
- await Assert.ThrowsAsync(() =>
- handler.Dirty(
- Path.Combine(_testDir, "nonexistent_old.bin"),
- Path.Combine(_testDir, "nonexistent_new.bin"),
- Path.Combine(_testDir, "nonexistent_patch.bin")));
- }
-
- [Fact(DisplayName = "Clean_源和目标文件相同_生成最小补丁")]
- public async Task Clean_IdenticalFiles_GeneratesMinimalPatch()
- {
- var handler = new BsdiffDiffer();
- var oldFile = Path.Combine(_testDir, "old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- var data = new byte[1024];
- new Random(42).NextBytes(data);
- File.WriteAllBytes(oldFile, data);
- File.WriteAllBytes(newFile, data);
-
- await handler.Clean(oldFile, newFile, patchFile);
-
- Assert.True(File.Exists(patchFile));
- Assert.True(new FileInfo(patchFile).Length > 0);
- }
-
- [Fact(DisplayName = "Clean_生成补丁后Dirty还原_文件内容一致")]
- public async Task CleanThenDirty_RoundTrip_ProducesIdenticalFile()
- {
- var handler = new BsdiffDiffer();
- var oldFile = Path.Combine(_testDir, "old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchedFile = Path.Combine(_testDir, "patched.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- var oldData = new byte[4096];
- new Random(77).NextBytes(oldData);
- File.WriteAllBytes(oldFile, oldData);
-
- var newData = new byte[4096];
- Array.Copy(oldData, newData, 2048); // 前半相同
- var suffix = new byte[2048]; new Random(88).NextBytes(suffix); Array.Copy(suffix, 0, newData, 2048, 2048); // 后半不同
- File.WriteAllBytes(newFile, newData);
-
- // Clean: 生成补丁
- await handler.Clean(oldFile, newFile, patchFile);
- Assert.True(File.Exists(patchFile));
-
- // Dirty: 应用补丁
- await handler.Dirty(oldFile, patchedFile, patchFile);
- Assert.True(File.Exists(patchedFile));
-
- // 验证
- var resultData = File.ReadAllBytes(patchedFile);
- Assert.Equal(newData, resultData);
- }
-
- [Fact(DisplayName = "Clean_空文件_能正确生成补丁")]
- public async Task Clean_EmptyFiles_GeneratesPatch()
- {
- var handler = new BsdiffDiffer();
- var oldFile = Path.Combine(_testDir, "empty_old.bin");
- var newFile = Path.Combine(_testDir, "empty_new.bin");
- var patchFile = Path.Combine(_testDir, "empty_patch.bin");
-
- File.WriteAllBytes(oldFile, Array.Empty());
- File.WriteAllBytes(newFile, Array.Empty());
-
- await handler.Clean(oldFile, newFile, patchFile);
-
- Assert.True(File.Exists(patchFile));
- }
-
- [Fact(DisplayName = "Dirty_旧文件空_应用补丁生成新文件")]
- public async Task Dirty_EmptyOldFile_AppliesPatchToNewFile()
- {
- var handler = new BsdiffDiffer();
- var oldFile = Path.Combine(_testDir, "empty_old.bin");
- var newFile = Path.Combine(_testDir, "full_new.bin");
- var patchedFile = Path.Combine(_testDir, "patched.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- File.WriteAllBytes(oldFile, Array.Empty());
- var newData = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
- File.WriteAllBytes(newFile, newData);
-
- await handler.Clean(oldFile, newFile, patchFile);
- await handler.Dirty(oldFile, patchedFile, patchFile);
-
- Assert.Equal(newData, File.ReadAllBytes(patchedFile));
- }
-
- [Fact(DisplayName = "Dirty_不存在的补丁文件_正常抛出异常")]
- public async Task Dirty_NonExistentPatch_Throws()
- {
- var handler = new BsdiffDiffer();
- var oldFile = Path.Combine(_testDir, "old.bin");
- File.WriteAllBytes(oldFile, new byte[] { 1, 2, 3 });
-
- await Assert.ThrowsAsync(() =>
- handler.Dirty(oldFile, Path.Combine(_testDir, "new.bin"), Path.Combine(_testDir, "nonexistent.patch")));
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Differ/StreamingHdiffDifferTests.cs b/src/c#/DifferentialTest/Differ/StreamingHdiffDifferTests.cs
deleted file mode 100644
index d734d49d..00000000
--- a/src/c#/DifferentialTest/Differ/StreamingHdiffDifferTests.cs
+++ /dev/null
@@ -1,469 +0,0 @@
-using Moq;
-using GeneralUpdate.Differential.Abstractions;
-using GeneralUpdate.Differential.Differ;
-
-namespace DifferentialTest.Differ
-{
- ///
- /// StreamingHdiffDiffer 单元测试。
- ///
- /// 覆盖点:
- /// 1. 无参构造函数 — 使用 DeflateCompressionProvider 默认值
- /// 2. 有参构造函数 — 自定义压缩提供者和参数
- /// 3. 构造函数 — null provider → ArgumentNullException
- /// 4. 构造函数 — blockSize≤0 → ArgumentOutOfRangeException
- /// 5. 构造函数 — maxWindowSize≤0 → ArgumentOutOfRangeException
- /// 6. CleanAsync — null/空路径 → ArgumentNullException
- /// 7. CleanAsync — CancellationToken已取消 → OperationCanceledException
- /// 8. CleanAsync — 文件不存在 → FileNotFoundException
- /// 9. Clean — 相同文件生成最小补丁
- /// 10. Clean — 不同文件生成补丁
- /// 11. Clean — 空文件正确处理
- /// 12. CleanThenDirty — 相同文件往返验证
- /// 13. CleanThenDirty — 修改文件往返验证
- /// 14. CleanThenDirty — 完全不同文件往返验证
- /// 15. CleanThenDirty — 空旧文件生成新文件
- /// 16. DirtyAsync — null路径 → ArgumentNullException
- /// 17. DirtyAsync — CancellationToken已取消 → OperationCanceledException
- /// 18. Dirty — 不存在的补丁文件 → 抛出异常
- /// 19. 自定义BlockSize — 功能验证往返
- ///
- public class StreamingHdiffDifferTests : IDisposable
- {
- private readonly string _testDir;
-
- public StreamingHdiffDifferTests()
- {
- _testDir = Path.Combine(Path.GetTempPath(), $"StreamingHdiffDifferTests_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_testDir);
- }
-
- public void Dispose()
- {
- if (Directory.Exists(_testDir))
- Directory.Delete(_testDir, true);
- }
-
- #region Constructor Tests
-
- [Fact(DisplayName = "无参构造函数_使用默认DeflateCompressionProvider和默认参数")]
- public void Constructor_NoArgs_UsesDefaults()
- {
- // Arrange & Act
- var differ = new StreamingHdiffDiffer();
-
- // Assert
- Assert.NotNull(differ);
- Assert.Equal(64 * 1024, differ.BlockSize);
- Assert.Equal(128 * 1024 * 1024, differ.MaxWindowSize);
- }
-
- [Fact(DisplayName = "有参构造函数_自定义参数_正确创建实例")]
- public void Constructor_ValidParams_CreatesInstance()
- {
- // Arrange
- var mockProvider = new Mock();
- mockProvider.Setup(p => p.FormatVersion).Returns(0x01);
- const int blockSize = 32 * 1024;
- const int maxWindowSize = 64 * 1024 * 1024;
-
- // Act
- var differ = new StreamingHdiffDiffer(mockProvider.Object, blockSize, maxWindowSize);
-
- // Assert
- Assert.NotNull(differ);
- Assert.Equal(blockSize, differ.BlockSize);
- Assert.Equal(maxWindowSize, differ.MaxWindowSize);
- }
-
- [Fact(DisplayName = "构造函数_compressionProvider为null_抛出ArgumentNullException")]
- public void Constructor_NullProvider_ThrowsArgumentNullException()
- {
- // Arrange, Act & Assert
- var ex = Assert.Throws(() =>
- new StreamingHdiffDiffer(null!, 64 * 1024, 128 * 1024 * 1024));
-
- Assert.Equal("compressionProvider", ex.ParamName);
- }
-
- [Fact(DisplayName = "构造函数_blockSize为零_抛出ArgumentOutOfRangeException")]
- public void Constructor_ZeroBlockSize_ThrowsArgumentOutOfRangeException()
- {
- // Arrange
- var mockProvider = new Mock();
- mockProvider.Setup(p => p.FormatVersion).Returns(0x01);
-
- // Act & Assert
- var ex = Assert.Throws(() =>
- new StreamingHdiffDiffer(mockProvider.Object, 0, 128 * 1024 * 1024));
-
- Assert.Equal("blockSize", ex.ParamName);
- }
-
- [Fact(DisplayName = "构造函数_maxWindowSize为负数_抛出ArgumentOutOfRangeException")]
- public void Constructor_NegativeMaxWindowSize_ThrowsArgumentOutOfRangeException()
- {
- // Arrange
- var mockProvider = new Mock();
- mockProvider.Setup(p => p.FormatVersion).Returns(0x01);
-
- // Act & Assert
- var ex = Assert.Throws(() =>
- new StreamingHdiffDiffer(mockProvider.Object, 64 * 1024, -1));
-
- Assert.Equal("maxWindowSize", ex.ParamName);
- }
-
- #endregion Constructor Tests
-
- #region CleanAsync Tests
-
- [Fact(DisplayName = "CleanAsync_oldFilePath为null_抛出ArgumentNullException")]
- public async Task CleanAsync_NullOldPath_ThrowsArgumentNullException()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var newPath = Path.Combine(_testDir, "new.bin");
- var patchPath = Path.Combine(_testDir, "patch.bin");
-
- // Act & Assert
- await Assert.ThrowsAsync(() =>
- differ.CleanAsync(null!, newPath, patchPath));
- }
-
- [Fact(DisplayName = "CleanAsync_newFilePath为空白字符串_抛出ArgumentNullException")]
- public async Task CleanAsync_WhitespaceNewPath_ThrowsArgumentNullException()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var oldPath = Path.Combine(_testDir, "old.bin");
- var patchPath = Path.Combine(_testDir, "patch.bin");
-
- // Act & Assert
- await Assert.ThrowsAsync(() =>
- differ.CleanAsync(oldPath, " ", patchPath));
- }
-
- [Fact(DisplayName = "CleanAsync_CancellationToken已取消_抛出OperationCanceledException")]
- public async Task CleanAsync_CancelledToken_ThrowsOperationCanceledException()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- using var cts = new CancellationTokenSource();
- cts.Cancel();
-
- // Act & Assert
- await Assert.ThrowsAsync(() =>
- differ.CleanAsync("old", "new", "patch", cts.Token));
- }
-
- [Fact(DisplayName = "Clean_文件不存在_抛出FileNotFoundException")]
- public async Task Clean_NonExistentFiles_Throws()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
-
- // Act & Assert
- await Assert.ThrowsAsync(() =>
- differ.CleanAsync(
- Path.Combine(_testDir, "nonexistent_old.bin"),
- Path.Combine(_testDir, "nonexistent_new.bin"),
- Path.Combine(_testDir, "output.patch")));
- }
-
- [Fact(DisplayName = "Clean_相同文件_生成最小补丁")]
- public async Task Clean_IdenticalFiles_GeneratesMinimalPatch()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var oldFile = Path.Combine(_testDir, "old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- var data = new byte[2048];
- new Random(42).NextBytes(data);
- File.WriteAllBytes(oldFile, data);
- File.WriteAllBytes(newFile, data);
-
- // Act
- await differ.CleanAsync(oldFile, newFile, patchFile);
-
- // Assert
- Assert.True(File.Exists(patchFile));
- Assert.True(new FileInfo(patchFile).Length > 0, "补丁文件不应为空");
- }
-
- [Fact(DisplayName = "Clean_后半部分不同的文件_生成补丁")]
- public async Task Clean_DifferentFiles_GeneratesPatch()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var oldFile = Path.Combine(_testDir, "old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- // 4KB 文件,前半相同,后半不同
- var oldData = new byte[4096];
- new Random(100).NextBytes(oldData);
- File.WriteAllBytes(oldFile, oldData);
-
- var newData = new byte[4096];
- Array.Copy(oldData, 0, newData, 0, 2048); // 前半相同
- var suffix = new byte[2048];
- new Random(200).NextBytes(suffix);
- Array.Copy(suffix, 0, newData, 2048, 2048); // 后半不同
- File.WriteAllBytes(newFile, newData);
-
- // Act
- await differ.CleanAsync(oldFile, newFile, patchFile);
-
- // Assert
- Assert.True(File.Exists(patchFile));
- Assert.True(new FileInfo(patchFile).Length > 0, "补丁文件不应为空");
- }
-
- [Fact(DisplayName = "Clean_空文件_正常处理")]
- public async Task Clean_EmptyFiles_HandlesGracefully()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var oldFile = Path.Combine(_testDir, "empty_old.bin");
- var newFile = Path.Combine(_testDir, "empty_new.bin");
- var patchFile = Path.Combine(_testDir, "empty_patch.bin");
-
- File.WriteAllBytes(oldFile, Array.Empty());
- File.WriteAllBytes(newFile, Array.Empty());
-
- // Act
- await differ.CleanAsync(oldFile, newFile, patchFile);
-
- // Assert
- Assert.True(File.Exists(patchFile));
- Assert.True(new FileInfo(patchFile).Length > 0, "即使空文件也应生成有效的BSDIF补丁头");
- }
-
- #endregion CleanAsync Tests
-
- #region CleanThenDirty Round-Trip Tests
-
- [Fact(DisplayName = "CleanThenDirty_相同文件_生成与源文件一致的结果")]
- public async Task CleanThenDirty_IdenticalFiles_ProducesIdenticalOutput()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var oldFile = Path.Combine(_testDir, "old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchedFile = Path.Combine(_testDir, "patched.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- var data = new byte[4096];
- new Random(77).NextBytes(data);
- File.WriteAllBytes(oldFile, data);
- File.WriteAllBytes(newFile, data);
-
- // Act — Clean: 生成补丁
- await differ.CleanAsync(oldFile, newFile, patchFile);
- Assert.True(File.Exists(patchFile));
-
- // Act — Dirty: 应用补丁
- await differ.DirtyAsync(oldFile, patchedFile, patchFile);
- Assert.True(File.Exists(patchedFile));
-
- // Assert
- var resultData = File.ReadAllBytes(patchedFile);
- Assert.Equal(data, resultData);
- }
-
- [Fact(DisplayName = "CleanThenDirty_部分修改文件_生成正确结果")]
- public async Task CleanThenDirty_ModifiedFiles_ProducesExpectedOutput()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var oldFile = Path.Combine(_testDir, "old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchedFile = Path.Combine(_testDir, "patched.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- // 4KB 旧文件,新文件前半2KB相同、后半2KB不同
- var oldData = new byte[4096];
- new Random(88).NextBytes(oldData);
- File.WriteAllBytes(oldFile, oldData);
-
- var newData = new byte[4096];
- Array.Copy(oldData, 0, newData, 0, 2048); // 前半相同
- var suffix = new byte[2048];
- new Random(99).NextBytes(suffix);
- Array.Copy(suffix, 0, newData, 2048, 2048); // 后半不同
- File.WriteAllBytes(newFile, newData);
-
- // Act — Clean
- await differ.CleanAsync(oldFile, newFile, patchFile);
- Assert.True(File.Exists(patchFile));
-
- // Act — Dirty
- await differ.DirtyAsync(oldFile, patchedFile, patchFile);
- Assert.True(File.Exists(patchedFile));
-
- // Assert
- var resultData = File.ReadAllBytes(patchedFile);
- Assert.Equal(newData, resultData);
- }
-
- [Fact(DisplayName = "CleanThenDirty_完全不同文件_生成正确结果")]
- public async Task CleanThenDirty_CompletelyDifferentFiles_ProducesExpectedOutput()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var oldFile = Path.Combine(_testDir, "old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchedFile = Path.Combine(_testDir, "patched.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- var oldData = new byte[4096];
- new Random(111).NextBytes(oldData);
- File.WriteAllBytes(oldFile, oldData);
-
- var newData = new byte[4096];
- new Random(222).NextBytes(newData);
- File.WriteAllBytes(newFile, newData);
-
- // Act — Clean
- await differ.CleanAsync(oldFile, newFile, patchFile);
- Assert.True(File.Exists(patchFile));
-
- // Act — Dirty
- await differ.DirtyAsync(oldFile, patchedFile, patchFile);
- Assert.True(File.Exists(patchedFile));
-
- // Assert
- var resultData = File.ReadAllBytes(patchedFile);
- Assert.Equal(newData, resultData);
- }
-
- [Fact(DisplayName = "CleanThenDirty_空旧文件_正确生成新文件")]
- public async Task CleanThenDirty_EmptyOldFile_ProducesNewFile()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var oldFile = Path.Combine(_testDir, "empty_old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchedFile = Path.Combine(_testDir, "patched.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- File.WriteAllBytes(oldFile, Array.Empty());
- var newData = new byte[512];
- new Random(333).NextBytes(newData);
- File.WriteAllBytes(newFile, newData);
-
- // Act — Clean
- await differ.CleanAsync(oldFile, newFile, patchFile);
- Assert.True(File.Exists(patchFile));
-
- // Act — Dirty
- await differ.DirtyAsync(oldFile, patchedFile, patchFile);
- Assert.True(File.Exists(patchedFile));
-
- // Assert
- var resultData = File.ReadAllBytes(patchedFile);
- Assert.Equal(newData, resultData);
- }
-
- #endregion CleanThenDirty Round-Trip Tests
-
- #region DirtyAsync Tests
-
- [Fact(DisplayName = "DirtyAsync_oldFilePath为null_抛出ArgumentNullException")]
- public async Task DirtyAsync_NullOldPath_ThrowsArgumentNullException()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var newPath = Path.Combine(_testDir, "new.bin");
- var patchPath = Path.Combine(_testDir, "patch.bin");
- File.WriteAllBytes(patchPath, new byte[] { 1, 2, 3 });
-
- // Act & Assert
- await Assert.ThrowsAsync(() =>
- differ.DirtyAsync(null!, newPath, patchPath));
- }
-
- [Fact(DisplayName = "DirtyAsync_CancellationToken已取消_抛出OperationCanceledException")]
- public async Task DirtyAsync_CancelledToken_ThrowsOperationCanceledException()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- using var cts = new CancellationTokenSource();
- cts.Cancel();
-
- // Act & Assert
- await Assert.ThrowsAsync(() =>
- differ.DirtyAsync("old", "new", "patch", cts.Token));
- }
-
- [Fact(DisplayName = "Dirty_不存在的补丁文件_抛出异常")]
- public async Task Dirty_NonExistentPatch_Throws()
- {
- // Arrange
- var differ = new StreamingHdiffDiffer();
- var oldFile = Path.Combine(_testDir, "old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchFile = Path.Combine(_testDir, "nonexistent.patch");
-
- File.WriteAllBytes(oldFile, new byte[] { 1, 2, 3, 4, 5 });
-
- // Act & Assert
- await Assert.ThrowsAsync(() =>
- differ.DirtyAsync(oldFile, newFile, patchFile));
- }
-
- #endregion DirtyAsync Tests
-
- #region Custom Configuration Tests
-
- [Fact(DisplayName = "自定义BlockSize_16KB_往返功能正常")]
- public async Task Constructor_CustomBlockSize_Functional()
- {
- // Arrange
- var mockProvider = new Mock();
- mockProvider.Setup(p => p.FormatVersion).Returns(0x01);
- mockProvider
- .Setup(p => p.CreateCompressStream(It.IsAny(), It.IsAny()))
- .Returns((s, _) => new System.IO.Compression.DeflateStream(s, System.IO.Compression.CompressionLevel.Fastest, true));
- mockProvider
- .Setup(p => p.CreateDecompressStream(It.IsAny(), It.IsAny()))
- .Returns((s, _) => new System.IO.Compression.DeflateStream(s, System.IO.Compression.CompressionMode.Decompress, true));
-
- const int blockSize = 16 * 1024;
- var differ = new StreamingHdiffDiffer(mockProvider.Object, blockSize, 128 * 1024 * 1024);
- var oldFile = Path.Combine(_testDir, "old.bin");
- var newFile = Path.Combine(_testDir, "new.bin");
- var patchedFile = Path.Combine(_testDir, "patched.bin");
- var patchFile = Path.Combine(_testDir, "patch.bin");
-
- // 创建足够大的文件测试16KB分块哈希
- var oldData = new byte[32768];
- new Random(444).NextBytes(oldData);
- File.WriteAllBytes(oldFile, oldData);
-
- var newData = new byte[32768];
- Array.Copy(oldData, 0, newData, 0, 16384); // 前半相同
- var suffix = new byte[16384];
- new Random(555).NextBytes(suffix);
- Array.Copy(suffix, 0, newData, 16384, 16384); // 后半不同
- File.WriteAllBytes(newFile, newData);
-
- // Act — Clean
- await differ.CleanAsync(oldFile, newFile, patchFile);
- Assert.True(File.Exists(patchFile));
-
- // Act — Dirty
- await differ.DirtyAsync(oldFile, patchedFile, patchFile);
- Assert.True(File.Exists(patchedFile));
-
- // Assert
- var resultData = File.ReadAllBytes(patchedFile);
- Assert.Equal(newData, resultData);
- }
-
- #endregion Custom Configuration Tests
- }
-}
diff --git a/src/c#/DifferentialTest/DifferentialTest.csproj b/src/c#/DifferentialTest/DifferentialTest.csproj
deleted file mode 100644
index 1011e8a9..00000000
--- a/src/c#/DifferentialTest/DifferentialTest.csproj
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- net10.0
- enable
- enable
- false
- true
- default
-
-
-
-
-
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
diff --git a/src/c#/DifferentialTest/GlobalUsings.cs b/src/c#/DifferentialTest/GlobalUsings.cs
deleted file mode 100644
index 66c75e8d..00000000
--- a/src/c#/DifferentialTest/GlobalUsings.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-global using System;
-global using System.Collections.Generic;
-global using System.IO;
-global using System.Linq;
-global using System.Net.Http;
-global using System.Threading;
-global using System.Threading.Tasks;
-global using Xunit;
diff --git a/src/c#/DifferentialTest/Matchers/DefaultCleanMatcherTests.cs b/src/c#/DifferentialTest/Matchers/DefaultCleanMatcherTests.cs
deleted file mode 100644
index c321e347..00000000
--- a/src/c#/DifferentialTest/Matchers/DefaultCleanMatcherTests.cs
+++ /dev/null
@@ -1,192 +0,0 @@
-using GeneralUpdate.Core.Differential;
-
-namespace DifferentialTest.Matchers
-{
- ///
- /// 分支覆盖点:
- /// 1. Match — oldFile匹配成功 (名称+相对路径匹配, 且文件存在)
- /// 2. Match — oldFile名称不匹配 → null
- /// 3. Match — oldFile相对路径不匹配 → null
- /// 4. Match — oldFile匹配但文件不存在(File.Exists=false) → null
- /// 5. Match — newFile匹配但文件不存在(File.Exists=false) → null
- /// 6. Match — leftNodes为空 → null
- /// 7. Match — leftNodes有多个匹配 → 取FirstOrDefault
- /// 8. Match — 大小写不敏感匹配
- ///
- /// 触发条件:各种 FileNode 组合
- /// 预期结果:正确匹配或返回null
- ///
- public class DefaultCleanMatcherTests : IDisposable
- {
- private readonly string _testDir;
-
- public DefaultCleanMatcherTests()
- {
- _testDir = Path.Combine(Path.GetTempPath(), $"CleanMatcherTests_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_testDir);
- }
-
- public void Dispose()
- {
- if (Directory.Exists(_testDir))
- Directory.Delete(_testDir, true);
- }
-
- [Fact(DisplayName = "Match_名称和相对路径均匹配且文件存在_返回oldFile")]
- public void Match_NameAndPathMatchFileExists_ReturnsOldFile()
- {
- var matcher = new DefaultCleanMatcher();
-
- var oldFile = CreateFileNode("app.dll", "sub");
- var newFile = CreateFileNode("app.dll", "sub");
- var leftNodes = new List { oldFile };
-
- var result = matcher.Match(newFile, leftNodes);
-
- Assert.NotNull(result);
- Assert.Equal(oldFile.FullName, result.FullName);
- }
-
- [Fact(DisplayName = "Match_名称匹配但相对路径不匹配_返回null")]
- public void Match_NameMatchButPathMismatch_ReturnsNull()
- {
- var matcher = new DefaultCleanMatcher();
-
- var oldFile = CreateFileNode("app.dll", "sub1");
- var newFile = CreateFileNode("app.dll", "sub2");
- var leftNodes = new List { oldFile };
-
- var result = matcher.Match(newFile, leftNodes);
-
- Assert.Null(result);
- }
-
- [Fact(DisplayName = "Match_相对路径匹配但名称不匹配_返回null")]
- public void Match_PathMatchButNameMismatch_ReturnsNull()
- {
- var matcher = new DefaultCleanMatcher();
-
- var oldFile = CreateFileNode("old.dll", "sub");
- var newFile = CreateFileNode("new.dll", "sub");
- var leftNodes = new List { oldFile };
-
- var result = matcher.Match(newFile, leftNodes);
-
- Assert.Null(result);
- }
-
- [Fact(DisplayName = "Match_匹配但oldFile文件不存在_返回null")]
- public void Match_FoundButOldFileNotExist_ReturnsNull()
- {
- var matcher = new DefaultCleanMatcher();
- var nonExistent = Path.Combine(_testDir, "nonexistent.dll");
-
- var oldFile = new GeneralUpdate.Core.FileSystem.FileNode
- {
- Name = "app.dll",
- RelativePath = "sub",
- FullName = nonExistent
- };
- var newFile = CreateFileNode("app.dll", "sub");
- var leftNodes = new List { oldFile };
-
- var result = matcher.Match(newFile, leftNodes);
-
- Assert.Null(result);
- }
-
- [Fact(DisplayName = "Match_匹配但newFile文件不存在_返回null")]
- public void Match_FoundButNewFileNotExist_ReturnsNull()
- {
- var matcher = new DefaultCleanMatcher();
- var nonExistent = Path.Combine(_testDir, "nonexistent2.dll");
-
- var oldFile = CreateFileNode("app.dll", "sub");
- var newFile = new GeneralUpdate.Core.FileSystem.FileNode
- {
- Name = "app.dll",
- RelativePath = "sub",
- FullName = nonExistent
- };
- var leftNodes = new List { oldFile };
-
- var result = matcher.Match(newFile, leftNodes);
-
- Assert.Null(result);
- }
-
- [Fact(DisplayName = "Match_leftNodes为空_返回null")]
- public void Match_EmptyLeftNodes_ReturnsNull()
- {
- var matcher = new DefaultCleanMatcher();
- var newFile = CreateFileNode("app.dll", "sub");
- var leftNodes = Enumerable.Empty();
-
- var result = matcher.Match(newFile, leftNodes);
-
- Assert.Null(result);
- }
-
- [Fact(DisplayName = "Match_名称大小写不同_仍然匹配(OrdinalIgnoreCase)")]
- public void Match_DifferentCaseName_StillMatches()
- {
- var matcher = new DefaultCleanMatcher();
-
- var oldFile = CreateFileNode("APP.DLL", "sub");
- var newFile = CreateFileNode("app.dll", "sub");
- var leftNodes = new List { oldFile };
-
- var result = matcher.Match(newFile, leftNodes);
-
- Assert.NotNull(result);
- }
-
- [Fact(DisplayName = "Match_相对路径大小写不同_仍然匹配")]
- public void Match_DifferentCasePath_StillMatches()
- {
- var matcher = new DefaultCleanMatcher();
-
- var oldFile = CreateFileNode("app.dll", "SUB");
- var newFile = CreateFileNode("app.dll", "sub");
- var leftNodes = new List { oldFile };
-
- var result = matcher.Match(newFile, leftNodes);
-
- Assert.NotNull(result);
- }
-
- [Fact(DisplayName = "Match_leftNodes有多个条目_匹配第一个")]
- public void Match_MultipleLeftNodes_MatchesFirst()
- {
- var matcher = new DefaultCleanMatcher();
-
- var oldFile1 = CreateFileNode("app.dll", "sub");
- var oldFile2 = CreateFileNode("app_v2.dll", "sub");
- var newFile = CreateFileNode("app.dll", "sub");
- var leftNodes = new List { oldFile1, oldFile2 };
-
- var result = matcher.Match(newFile, leftNodes);
-
- Assert.NotNull(result);
- Assert.Equal(oldFile1.FullName, result.FullName);
- }
-
- ///
- /// 创建真实存在的测试文件及其FileNode
- ///
- private GeneralUpdate.Core.FileSystem.FileNode CreateFileNode(string name, string relativeDir)
- {
- var dir = Path.Combine(_testDir, relativeDir);
- Directory.CreateDirectory(dir);
- var filePath = Path.Combine(dir, name);
- File.WriteAllText(filePath, "test content");
-
- return new GeneralUpdate.Core.FileSystem.FileNode
- {
- Name = name,
- RelativePath = relativeDir,
- FullName = filePath
- };
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Matchers/DefaultDirtyMatcherTests.cs b/src/c#/DifferentialTest/Matchers/DefaultDirtyMatcherTests.cs
deleted file mode 100644
index c075e7ca..00000000
--- a/src/c#/DifferentialTest/Matchers/DefaultDirtyMatcherTests.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-using GeneralUpdate.Core.Differential;
-
-namespace DifferentialTest.Matchers
-{
- ///
- /// 分支覆盖点:
- /// 1. Match — 通过文件名(去掉.patch后缀后)匹配 (case-insensitive)
- /// 2. Match — 文件有双重后缀 (如 app.dll.patch) → 只去掉最后一个.patch
- /// 3. Match — 文件名没有.patch后缀 → 直接按oldFile.Name比较
- /// 4. Match — 匹配到但扩展名不是.patch → 返回null
- /// 5. Match — patchFiles为空 → null
- /// 6. Match — 大小写不敏感匹配
- /// 7. Match — 名字包含.patch但不以.patch结尾 (如 patchfile.txt)
- ///
- /// 触发条件:各种文件名组合
- /// 预期结果:正确匹配或返回null
- ///
- public class DefaultDirtyMatcherTests : IDisposable
- {
- private readonly string _testDir;
-
- public DefaultDirtyMatcherTests()
- {
- _testDir = Path.Combine(Path.GetTempPath(), $"DirtyMatcherTests_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_testDir);
- }
-
- public void Dispose()
- {
- if (Directory.Exists(_testDir))
- Directory.Delete(_testDir, true);
- }
-
- [Fact(DisplayName = "Match_文件名去掉patch后匹配_返回patchFile")]
- public void Match_NameWithoutPatchMatches_ReturnsPatchFile()
- {
- var matcher = new DefaultDirtyMatcher();
- var oldFile = CreateFileInfo("app.dll");
- var patchFile = CreateFileInfo("app.dll.patch");
- var patchFiles = new List { patchFile };
-
- var result = matcher.Match(oldFile, patchFiles);
-
- Assert.NotNull(result);
- Assert.Equal(patchFile.FullName, result!.FullName);
- }
-
- [Fact(DisplayName = "Match_双重后缀.patch文件_去掉最后一个patch后缀后匹配")]
- public void Match_DoubleExtensionWithPatch_StripsOnlyLastPatch()
- {
- var matcher = new DefaultDirtyMatcher();
- var oldFile = CreateFileInfo("app.dll.patch"); // 原始文件名含.patch
- var patchFile1 = CreateFileInfo("app.dll.patch.patch");
- var patchFiles = new List { patchFile1 };
-
- var result = matcher.Match(oldFile, patchFiles);
-
- Assert.NotNull(result);
- Assert.Equal(patchFile1.FullName, result!.FullName);
- }
-
- [Fact(DisplayName = "Match_文件名不匹配_返回null")]
- public void Match_NameMismatch_ReturnsNull()
- {
- var matcher = new DefaultDirtyMatcher();
- var oldFile = CreateFileInfo("app.dll");
- var patchFile = CreateFileInfo("other.dll.patch");
- var patchFiles = new List { patchFile };
-
- var result = matcher.Match(oldFile, patchFiles);
-
- Assert.Null(result);
- }
-
- [Fact(DisplayName = "Match_匹配但扩展名不是.patch_返回null")]
- public void Match_MatchedButNotPatchExtension_ReturnsNull()
- {
- var matcher = new DefaultDirtyMatcher();
- var oldFile = CreateFileInfo("app.dll");
- var patchFile = CreateFileInfo("app.dll.txt"); // 非.patch扩展名
- var patchFiles = new List { patchFile };
-
- var result = matcher.Match(oldFile, patchFiles);
-
- Assert.Null(result);
- }
-
- [Fact(DisplayName = "Match_patchFiles为空_返回null")]
- public void Match_EmptyPatchFiles_ReturnsNull()
- {
- var matcher = new DefaultDirtyMatcher();
- var oldFile = CreateFileInfo("app.dll");
- var patchFiles = Enumerable.Empty();
-
- var result = matcher.Match(oldFile, patchFiles);
-
- Assert.Null(result);
- }
-
- [Fact(DisplayName = "Match_文件名大小写不同_仍然匹配")]
- public void Match_DifferentCaseName_StillMatches()
- {
- var matcher = new DefaultDirtyMatcher();
- var oldFile = CreateFileInfo("APP.DLL");
- var patchFile = CreateFileInfo("app.dll.patch");
- var patchFiles = new List { patchFile };
-
- var result = matcher.Match(oldFile, patchFiles);
-
- Assert.NotNull(result);
- }
-
- [Fact(DisplayName = "Match_patch后缀大小写不同_仍能匹配")]
- public void Match_DifferentCasePatchExtension_StillMatches()
- {
- var matcher = new DefaultDirtyMatcher();
- var oldFile = CreateFileInfo("app.dll");
- var patchFile = CreateFileInfo("app.dll.PATCH");
- var patchFiles = new List { patchFile };
-
- var result = matcher.Match(oldFile, patchFiles);
-
- Assert.NotNull(result);
- }
-
- [Fact(DisplayName = "Match_多个patch文件_匹配第一个")]
- public void Match_MultiplePatchFiles_MatchesFirst()
- {
- var matcher = new DefaultDirtyMatcher();
- var oldFile = CreateFileInfo("app.dll");
- var patchFile1 = CreateFileInfo("app.dll.patch");
- var patchFile2 = CreateFileInfo("app_v2.dll.patch");
- var patchFiles = new List { patchFile1, patchFile2 };
-
- var result = matcher.Match(oldFile, patchFiles);
-
- Assert.NotNull(result);
- Assert.Equal(patchFile1.FullName, result!.FullName);
- }
-
- [Fact(DisplayName = "Match_patch文件名没有.patch后缀_直接按名称比较")]
- public void Match_NoPatchSuffixInName_ComparesByNameDirectly()
- {
- var matcher = new DefaultDirtyMatcher();
- var oldFile = CreateFileInfo("readme.txt");
- var patchFile = CreateFileInfo("readme.txt.patch");
- var patchFiles = new List { patchFile };
-
- var result = matcher.Match(oldFile, patchFiles);
-
- Assert.NotNull(result);
- }
-
- private FileInfo CreateFileInfo(string name)
- {
- var path = Path.Combine(_testDir, name);
- File.WriteAllText(path, "test");
- return new FileInfo(path);
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Models/DiffProgressTests.cs b/src/c#/DifferentialTest/Models/DiffProgressTests.cs
deleted file mode 100644
index 409e0083..00000000
--- a/src/c#/DifferentialTest/Models/DiffProgressTests.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using GeneralUpdate.Core.Models;
-
-namespace DifferentialTest.Models
-{
- ///
- /// 分支覆盖点:
- /// 1. 构造函数正常分支 (completed/total/currentFile/error 不同组合)
- /// 2. Percentage — Total == 0 返回 100;Total > 0 返回比率
- /// 3. IsComplete — Completed >= Total (等于/大于)
- /// 4. Complete 静态工厂方法
- /// 5. ToString — IsComplete 为 true / false / Error 有值/无值
- ///
- /// 触发条件:各属性组合
- /// 预期结果:属性值、百分比、完成标志、字符串格式符合预期
- ///
- public class DiffProgressTests
- {
- [Fact(DisplayName = "构造函数_正常赋值_属性正确返回")]
- public void Constructor_ValidValues_PropertiesReturnCorrectly()
- {
- var progress = new DiffProgress(5, 10, "file.dll");
-
- Assert.Equal(5, progress.Completed);
- Assert.Equal(10, progress.Total);
- Assert.Equal("file.dll", progress.CurrentFile);
- Assert.Null(progress.Error);
- }
-
- [Fact(DisplayName = "构造函数_带Error参数_Error属性正确返回")]
- public void Constructor_WithError_ErrorPropertyReturnsCorrectly()
- {
- var progress = new DiffProgress(3, 10, "bad.dll", "IO error");
-
- Assert.Equal("IO error", progress.Error);
- }
-
- [Theory(DisplayName = "Percentage_不同Total值_返回正确百分比")]
- [InlineData(5, 10, 50.0)]
- [InlineData(0, 10, 0.0)]
- [InlineData(10, 10, 100.0)]
- [InlineData(1, 3, 100.0 / 3.0)]
- [InlineData(0, 0, 100.0)]
- [InlineData(7, 1, 700.0)]
- public void Percentage_VariousTotals_ReturnsCorrectPercentage(int completed, int total, double expected)
- {
- var progress = new DiffProgress(completed, total, null);
-
- Assert.Equal(expected, progress.Percentage);
- }
-
- [Theory(DisplayName = "IsComplete_Completed与Total的关系_正确返回完成标志")]
- [InlineData(5, 10, false)]
- [InlineData(10, 10, true)]
- [InlineData(0, 0, true)]
- [InlineData(11, 10, true)]
- public void IsComplete_CompletedVsTotal_ReturnsCorrectFlag(int completed, int total, bool expected)
- {
- var progress = new DiffProgress(completed, total, null);
-
- Assert.Equal(expected, progress.IsComplete);
- }
-
- [Fact(DisplayName = "Complete_静态工厂_返回已完成标记")]
- public void Complete_StaticFactory_ReturnsCompleteMarker()
- {
- var progress = DiffProgress.Complete(42);
-
- Assert.Equal(42, progress.Completed);
- Assert.Equal(42, progress.Total);
- Assert.Null(progress.CurrentFile);
- Assert.Null(progress.Error);
- Assert.True(progress.IsComplete);
- }
-
- [Fact(DisplayName = "ToString_已完成状态_返回格式化的完成字符串")]
- public void ToString_CompleteState_ReturnsFormattedCompleteString()
- {
- var progress = DiffProgress.Complete(10);
-
- var result = progress.ToString();
-
- Assert.Contains("Complete", result);
- Assert.Contains("10/10", result);
- }
-
- [Fact(DisplayName = "ToString_进行中状态_返回百分比和处理文件")]
- public void ToString_InProgressState_ReturnsPercentageAndFile()
- {
- var progress = new DiffProgress(5, 10, "app.exe");
-
- var result = progress.ToString();
-
- Assert.Contains("5/10", result);
- Assert.Contains("app.exe", result);
- Assert.DoesNotContain("failed", result);
- }
-
- [Fact(DisplayName = "ToString_带Error的进行中状态_包含失败信息")]
- public void ToString_InProgressWithError_ContainsFailureInfo()
- {
- var progress = new DiffProgress(3, 10, "bad.dll", "access denied");
-
- var result = progress.ToString();
-
- Assert.Contains("failed: access denied", result);
- }
-
- [Fact(DisplayName = "ToString_CurrentFile为null_显示省略号")]
- public void ToString_NullCurrentFile_ShowsEllipsis()
- {
- var progress = new DiffProgress(1, 10, null);
-
- var result = progress.ToString();
-
- Assert.Contains("...", result);
- }
-
- [Fact(DisplayName = "DiffProgress_值类型语义_相等比较")]
- public void ValueTypeSemantics_EqualityComparison()
- {
- var a = new DiffProgress(1, 10, "f");
- var b = new DiffProgress(1, 10, "f");
-
- Assert.True(a.Equals(b));
- Assert.Equal(a.GetHashCode(), b.GetHashCode());
- }
-
- [Fact(DisplayName = "DiffProgress_CurrentFile为null_属性返回null")]
- public void NullCurrentFile_ReturnsNull()
- {
- var progress = new DiffProgress(0, 10, null);
-
- Assert.Null(progress.CurrentFile);
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Pipeline/DiffPipelineBuilderTests.cs b/src/c#/DifferentialTest/Pipeline/DiffPipelineBuilderTests.cs
deleted file mode 100644
index 3862508d..00000000
--- a/src/c#/DifferentialTest/Pipeline/DiffPipelineBuilderTests.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-using Moq;
-using GeneralUpdate.Differential.Abstractions;
-using GeneralUpdate.Core.Differential;
-using GeneralUpdate.Core.Models;
-using GeneralUpdate.Core.Pipeline;
-
-namespace DifferentialTest.Pipeline
-{
- ///
- /// 分支覆盖点:
- /// 1. UseDiffer — differ为null → ArgumentNullException
- /// 2. UseDiffer — 正常设置
- /// 3. UseCleanMatcher — matcher为null → ArgumentNullException
- /// 4. UseCleanMatcher — 正常设置
- /// 5. UseDirtyMatcher — matcher为null → ArgumentNullException
- /// 6. UseDirtyMatcher — 正常设置
- /// 7. WithParallelism — maxDegreeOfParallelism < 1 → ArgumentOutOfRangeException
- /// 8. WithParallelism — 正常/边界值
- /// 9. WithStopOnFirstError — true/false
- /// 10. WithProgress — progress为null → ArgumentNullException
- /// 11. WithProgress — 正常设置
- /// 12. Build — 未设置任何选项 → 全部默认值
- /// 13. Build — 全部自定义 → 传给DiffPipeline
- /// 14. Build — differ未设置 → 默认StreamingHdiffDiffer
- ///
- /// 触发条件:各种构造组合
- /// 预期结果:正确构建DiffPipeline
- ///
- public class DiffPipelineBuilderTests
- {
- [Fact(DisplayName = "Build_未设置任何选项_使用所有默认值")]
- public void Build_NoOptions_UsesAllDefaults()
- {
- var pipeline = new DiffPipelineBuilder().Build();
-
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "UseDiffer_有效differ_正确设置")]
- public void UseDiffer_ValidDiffer_SetsCorrectly()
- {
- var mockDiffer = new Mock();
- var builder = new DiffPipelineBuilder();
-
- var result = builder.UseDiffer(mockDiffer.Object);
- var pipeline = result.Build();
-
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "UseDiffer_null_抛出ArgumentNullException")]
- public void UseDiffer_Null_ThrowsArgumentNullException()
- {
- var builder = new DiffPipelineBuilder();
-
- var ex = Assert.Throws(() => builder.UseDiffer(null!));
-
- Assert.Equal("differ", ex.ParamName);
- }
-
- [Fact(DisplayName = "UseCleanMatcher_有效matcher_正确设置")]
- public void UseCleanMatcher_ValidMatcher_SetsCorrectly()
- {
- var mockMatcher = new Mock();
-
- var pipeline = new DiffPipelineBuilder()
- .UseCleanMatcher(mockMatcher.Object)
- .Build();
-
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "UseCleanMatcher_null_抛出ArgumentNullException")]
- public void UseCleanMatcher_Null_ThrowsArgumentNullException()
- {
- var builder = new DiffPipelineBuilder();
-
- var ex = Assert.Throws(() => builder.UseCleanMatcher(null!));
-
- Assert.Equal("matcher", ex.ParamName);
- }
-
- [Fact(DisplayName = "UseDirtyMatcher_有效matcher_正确设置")]
- public void UseDirtyMatcher_ValidMatcher_SetsCorrectly()
- {
- var mockMatcher = new Mock();
-
- var pipeline = new DiffPipelineBuilder()
- .UseDirtyMatcher(mockMatcher.Object)
- .Build();
-
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "UseDirtyMatcher_null_抛出ArgumentNullException")]
- public void UseDirtyMatcher_Null_ThrowsArgumentNullException()
- {
- var builder = new DiffPipelineBuilder();
-
- var ex = Assert.Throws(() => builder.UseDirtyMatcher(null!));
-
- Assert.Equal("matcher", ex.ParamName);
- }
-
- [Theory(DisplayName = "WithParallelism_有效值_正确设置")]
- [InlineData(1)]
- [InlineData(4)]
- [InlineData(16)]
- [InlineData(32)]
- public void WithParallelism_ValidValues_SetsCorrectly(int parallelism)
- {
- var pipeline = new DiffPipelineBuilder()
- .WithParallelism(parallelism)
- .Build();
-
- Assert.NotNull(pipeline);
- }
-
- [Theory(DisplayName = "WithParallelism_无效值_抛出ArgumentOutOfRangeException")]
- [InlineData(0)]
- [InlineData(-1)]
- [InlineData(int.MinValue)]
- public void WithParallelism_InvalidValues_ThrowsArgumentOutOfRangeException(int invalidValue)
- {
- var builder = new DiffPipelineBuilder();
-
- Assert.Throws(() => builder.WithParallelism(invalidValue));
- }
-
- [Theory(DisplayName = "WithStopOnFirstError_不同值_正确设置")]
- [InlineData(true)]
- [InlineData(false)]
- public void WithStopOnFirstError_VariousValues_SetsCorrectly(bool stopOnFirstError)
- {
- var pipeline = new DiffPipelineBuilder()
- .WithStopOnFirstError(stopOnFirstError)
- .Build();
-
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "WithProgress_有效progress_正确设置")]
- public void WithProgress_ValidProgress_SetsCorrectly()
- {
- var mockProgress = new Mock>();
-
- var pipeline = new DiffPipelineBuilder()
- .WithProgress(mockProgress.Object)
- .Build();
-
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "WithProgress_null_抛出ArgumentNullException")]
- public void WithProgress_Null_ThrowsArgumentNullException()
- {
- var builder = new DiffPipelineBuilder();
-
- var ex = Assert.Throws(() => builder.WithProgress(null!));
-
- Assert.Equal("progress", ex.ParamName);
- }
-
- [Fact(DisplayName = "链式调用_全部自定义_构建成功")]
- public void FluentApi_AllCustom_BuildsSuccessfully()
- {
- var mockDiffer = new Mock();
- var mockCleanMatcher = new Mock();
- var mockDirtyMatcher = new Mock();
- var mockProgress = new Mock>();
-
- var pipeline = new DiffPipelineBuilder()
- .UseDiffer(mockDiffer.Object)
- .UseCleanMatcher(mockCleanMatcher.Object)
- .UseDirtyMatcher(mockDirtyMatcher.Object)
- .WithParallelism(4)
- .WithStopOnFirstError(true)
- .WithProgress(mockProgress.Object)
- .Build();
-
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "Build_重复调用_每次返回新实例")]
- public void Build_MultipleCalls_ReturnsDifferentInstances()
- {
- var builder = new DiffPipelineBuilder().WithParallelism(2);
-
- var p1 = builder.Build();
- var p2 = builder.Build();
-
- Assert.NotSame(p1, p2);
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Pipeline/DiffPipelineIntegrationTests.cs b/src/c#/DifferentialTest/Pipeline/DiffPipelineIntegrationTests.cs
deleted file mode 100644
index 03f8ee86..00000000
--- a/src/c#/DifferentialTest/Pipeline/DiffPipelineIntegrationTests.cs
+++ /dev/null
@@ -1,621 +0,0 @@
-using System.IO.Compression;
-using Moq;
-using GeneralUpdate.Differential.Abstractions;
-using GeneralUpdate.Core.Differential;
-using GeneralUpdate.Core.Models;
-using GeneralUpdate.Core.Pipeline;
-
-namespace DifferentialTest.Pipeline
-{
- ///
- /// DiffPipeline 集成测试:端到端验证 Clean/Dirty 管线的文件级 patch 生成与应用。
- /// 每个测试创建临时目录,在 Dispose 中清理。
- ///
- public class DiffPipelineIntegrationTests : IDisposable
- {
- private readonly string _testDir;
-
- public DiffPipelineIntegrationTests()
- {
- _testDir = Path.Combine(Path.GetTempPath(), $"DPI_IT_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_testDir);
- }
-
- public void Dispose()
- {
- if (Directory.Exists(_testDir))
- {
- try { Directory.Delete(_testDir, true); } catch { }
- }
- }
-
- // ---- helpers ----
-
- private string GetPath(string relative) => Path.Combine(_testDir, relative);
-
- private static void CopyDirectory(string sourceDir, string destDir)
- {
- Directory.CreateDirectory(destDir);
- foreach (var file in Directory.GetFiles(sourceDir))
- File.Copy(file, Path.Combine(destDir, Path.GetFileName(file)), true);
- foreach (var dir in Directory.GetDirectories(sourceDir))
- CopyDirectory(dir, Path.Combine(destDir, Path.GetFileName(dir)));
- }
-
- private static (IProgress Progress, List Captured) CreateProgressCapture()
- {
- var captured = new List();
- var progress = new Progress(p => captured.Add(p));
- return (progress, captured);
- }
-
- // ================================================================
- // CleanAsync 集成测试
- // ================================================================
-
- [Fact(DisplayName = "CleanAsync_单个修改文件_生成补丁文件")]
- public async Task CleanAsync_SingleModifiedFile_GeneratesPatch()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
-
- var oldData = new byte[200];
- new Random(1).NextBytes(oldData);
- var newData = new byte[200];
- oldData.CopyTo(newData, 0);
- newData[50] ^= 0xFF; // modify one byte
-
- File.WriteAllBytes(Path.Combine(sourceDir, "file.bin"), oldData);
- File.WriteAllBytes(Path.Combine(targetDir, "file.bin"), newData);
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
-
- // Act
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Assert
- var patchFile = Path.Combine(patchDir, "file.bin.patch");
- Assert.True(File.Exists(patchFile));
- Assert.True(new FileInfo(patchFile).Length > 0);
- }
-
- [Fact(DisplayName = "CleanAsync_多文件_生成所有补丁且不处理无变更文件")]
- public async Task CleanAsync_MultipleFiles_GeneratesAllPatches()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
-
- var rng = new Random(42);
- var shared = new byte[100];
- rng.NextBytes(shared);
-
- // 3 modified files
- byte[] mod1Old = new byte[100]; shared.CopyTo(mod1Old, 0);
- byte[] mod1New = new byte[100]; shared.CopyTo(mod1New, 0); mod1New[30] = 88;
-
- var mod2Old = new byte[200]; rng.NextBytes(mod2Old);
- var mod2New = new byte[200]; rng.NextBytes(mod2New);
-
- var mod3Old = new byte[50]; rng.NextBytes(mod3Old);
- var mod3New = new byte[50]; rng.NextBytes(mod3New);
-
- // 1 identical file
- var ident = new byte[150]; rng.NextBytes(ident);
-
- // 1 new file (only in target)
- var newFileData = new byte[80]; rng.NextBytes(newFileData);
-
- File.WriteAllBytes(Path.Combine(sourceDir, "mod1.bin"), mod1Old);
- File.WriteAllBytes(Path.Combine(sourceDir, "mod2.bin"), mod2Old);
- File.WriteAllBytes(Path.Combine(sourceDir, "mod3.bin"), mod3Old);
- File.WriteAllBytes(Path.Combine(sourceDir, "ident.bin"), ident);
- // "new1.bin" intentionally NOT written to source
-
- File.WriteAllBytes(Path.Combine(targetDir, "mod1.bin"), mod1New);
- File.WriteAllBytes(Path.Combine(targetDir, "mod2.bin"), mod2New);
- File.WriteAllBytes(Path.Combine(targetDir, "mod3.bin"), mod3New);
- File.WriteAllBytes(Path.Combine(targetDir, "ident.bin"), ident);
- File.WriteAllBytes(Path.Combine(targetDir, "new1.bin"), newFileData);
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
-
- // Act
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Assert
- // 3 modified → .patch files
- Assert.True(File.Exists(Path.Combine(patchDir, "mod1.bin.patch")));
- Assert.True(File.Exists(Path.Combine(patchDir, "mod2.bin.patch")));
- Assert.True(File.Exists(Path.Combine(patchDir, "mod3.bin.patch")));
- // 1 new → copied directly (not patched)
- Assert.True(File.Exists(Path.Combine(patchDir, "new1.bin")));
- // 1 identical → no output
- Assert.False(File.Exists(Path.Combine(patchDir, "ident.bin.patch")));
- Assert.False(File.Exists(Path.Combine(patchDir, "ident.bin")));
- }
-
- [Fact(DisplayName = "CleanAsync_目标有新文件_直接复制而非打补丁")]
- public async Task CleanAsync_NewFile_CopiedDirectly()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
-
- var data = new byte[120];
- new Random(7).NextBytes(data);
-
- // source has NO newfile.bin
- File.WriteAllBytes(Path.Combine(targetDir, "newfile.bin"), data);
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
-
- // Act
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Assert: copied, not patched
- var copied = Path.Combine(patchDir, "newfile.bin");
- var patched = Path.Combine(patchDir, "newfile.bin.patch");
- Assert.True(File.Exists(copied));
- Assert.False(File.Exists(patched));
- Assert.Equal(data, File.ReadAllBytes(copied));
- }
-
- [Fact(DisplayName = "CleanAsync_相同文件_不生成补丁也不复制")]
- public async Task CleanAsync_IdenticalFile_NoPatch()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
-
- var data = new byte[80];
- new Random(5).NextBytes(data);
- File.WriteAllBytes(Path.Combine(sourceDir, "ident.bin"), data);
- File.WriteAllBytes(Path.Combine(targetDir, "ident.bin"), data);
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
-
- // Act
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Assert: no .patch and no copy for unchanged file
- Assert.False(File.Exists(Path.Combine(patchDir, "ident.bin.patch")));
- Assert.False(File.Exists(Path.Combine(patchDir, "ident.bin")));
- }
-
- [Fact(DisplayName = "CleanAsync_源有目标无_写入删除列表JSON")]
- public async Task CleanAsync_DeletedFile_WritesDeleteJson()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
-
- var data = new byte[60];
- new Random(3).NextBytes(data);
- File.WriteAllBytes(Path.Combine(sourceDir, "delete_me.bin"), data);
- // target is intentionally empty (no delete_me.bin)
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
-
- // Act
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Assert
- var deleteJson = Path.Combine(patchDir, "generalupdate.delete.json");
- Assert.True(File.Exists(deleteJson));
- var content = File.ReadAllText(deleteJson);
- Assert.NotEmpty(content);
- }
-
- [Fact(DisplayName = "CleanAsync_进度回调_最终Completed等于Total")]
- public async Task CleanAsync_WithProgress_ReportsProgress()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
-
- var rng = new Random(12);
- for (int i = 1; i <= 5; i++)
- {
- var oldData = new byte[100]; rng.NextBytes(oldData);
- var newData = new byte[100]; rng.NextBytes(newData);
- File.WriteAllBytes(Path.Combine(sourceDir, $"f{i}.bin"), oldData);
- File.WriteAllBytes(Path.Combine(targetDir, $"f{i}.bin"), newData);
- }
-
- var (progress, captured) = CreateProgressCapture();
- var pipeline = new DiffPipeline(
- new DiffPipelineOptions { MaxDegreeOfParallelism = 1 },
- new GeneralUpdate.Differential.Differ.StreamingHdiffDiffer(),
- progress: progress);
-
- // Act
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Assert
- Assert.NotEmpty(captured);
- var last = captured[^1];
- Assert.True(last.IsComplete);
- Assert.Equal(5, last.Completed);
- Assert.Equal(5, last.Total);
- }
-
- [Fact(DisplayName = "CleanAsync_已取消令牌_抛出OperationCanceledException")]
- public async Task CleanAsync_Cancellation_ThrowsOperationCanceledException()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
-
- File.WriteAllBytes(Path.Combine(sourceDir, "a.bin"), new byte[] { 1, 2, 3 });
- File.WriteAllBytes(Path.Combine(targetDir, "a.bin"), new byte[] { 4, 5, 6 });
-
- var pipeline = new DiffPipeline();
- using var cts = new CancellationTokenSource();
- cts.Cancel();
-
- // Act & Assert
- await Assert.ThrowsAsync(() =>
- pipeline.CleanAsync(sourceDir, targetDir, patchDir, cancellationToken: cts.Token));
- }
-
- [Fact(DisplayName = "CleanAsync_StopOnFirstError为false_一个文件出错其他继续")]
- public async Task CleanAsync_StopOnFirstError_False_ContinuesOnError()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
-
- var rng = new Random(99);
- var aOld = new byte[100]; rng.NextBytes(aOld);
- var aNew = new byte[100]; rng.NextBytes(aNew);
- var bOld = new byte[80]; rng.NextBytes(bOld);
- var bNew = new byte[80]; rng.NextBytes(bNew);
-
- File.WriteAllBytes(Path.Combine(sourceDir, "a.bin"), aOld);
- File.WriteAllBytes(Path.Combine(sourceDir, "b.bin"), bOld);
- File.WriteAllBytes(Path.Combine(targetDir, "a.bin"), aNew);
- File.WriteAllBytes(Path.Combine(targetDir, "b.bin"), bNew);
-
- // Mock differ: a.bin fails, b.bin succeeds
- var mockDiffer = new Mock();
- mockDiffer
- .Setup(d => d.CleanAsync(
- It.IsAny(),
- It.IsAny(),
- It.IsAny(),
- It.IsAny()))
- .Returns((string old, string _, string patch, CancellationToken ct) =>
- {
- if (Path.GetFileName(old) == "a.bin")
- return Task.FromException(new IOException("Simulated error for a.bin"));
-
- if (!File.Exists(patch))
- File.WriteAllBytes(patch, new byte[10]);
- return Task.CompletedTask;
- });
-
- var (progress, captured) = CreateProgressCapture();
- var options = new DiffPipelineOptions { MaxDegreeOfParallelism = 1, StopOnFirstError = false };
- var pipeline = new DiffPipeline(options, mockDiffer.Object, progress: progress);
-
- // Act
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Assert
- // a.bin → error, no patch
- Assert.False(File.Exists(Path.Combine(patchDir, "a.bin.patch")));
- // b.bin → succeeded, patch exists
- Assert.True(File.Exists(Path.Combine(patchDir, "b.bin.patch")));
-
- // progress should include an error entry and complete
- var errors = captured.Where(p => p.Error != null).ToList();
- Assert.NotEmpty(errors);
- }
-
- // ================================================================
- // DirtyAsync 集成测试
- // ================================================================
-
- [Fact(DisplayName = "DirtyAsync_应用多个补丁_正确更新文件")]
- public async Task DirtyAsync_AppliesMultiplePatches_Correctly()
- {
- // Arrange — Phase 1: generate patches via Clean
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- var appDir = GetPath("app");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
- Directory.CreateDirectory(appDir);
-
- var rng = new Random(42);
- var aOld = new byte[200]; rng.NextBytes(aOld);
- var aNew = new byte[200]; rng.NextBytes(aNew);
- var bOld = new byte[300]; rng.NextBytes(bOld);
- var bNew = new byte[300]; rng.NextBytes(bNew);
-
- File.WriteAllBytes(Path.Combine(sourceDir, "a.bin"), aOld);
- File.WriteAllBytes(Path.Combine(sourceDir, "b.bin"), bOld);
- File.WriteAllBytes(Path.Combine(targetDir, "a.bin"), aNew);
- File.WriteAllBytes(Path.Combine(targetDir, "b.bin"), bNew);
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Verify patches were generated
- Assert.True(File.Exists(Path.Combine(patchDir, "a.bin.patch")));
- Assert.True(File.Exists(Path.Combine(patchDir, "b.bin.patch")));
-
- // Phase 2: set up app dir with old files, patch dir copy
- File.WriteAllBytes(Path.Combine(appDir, "a.bin"), aOld);
- File.WriteAllBytes(Path.Combine(appDir, "b.bin"), bOld);
-
- var patch2Dir = GetPath("patch2");
- CopyDirectory(patchDir, patch2Dir);
-
- // Act
- await pipeline.DirtyAsync(appDir, patch2Dir);
-
- // Assert: app files now match target versions
- Assert.Equal(aNew, File.ReadAllBytes(Path.Combine(appDir, "a.bin")));
- Assert.Equal(bNew, File.ReadAllBytes(Path.Combine(appDir, "b.bin")));
- }
-
- [Fact(DisplayName = "DirtyAsync_有删除列表_删除标记文件")]
- public async Task DirtyAsync_WithDeleteList_DeletesMarkedFiles()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- var appDir = GetPath("app");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
- Directory.CreateDirectory(appDir);
-
- var keepData = new byte[60];
- var deleteData = new byte[80];
- new Random(1).NextBytes(keepData);
- new Random(2).NextBytes(deleteData);
-
- // Source has delete_me.bin, target does NOT
- File.WriteAllBytes(Path.Combine(sourceDir, "keep.bin"), keepData);
- File.WriteAllBytes(Path.Combine(sourceDir, "delete_me.bin"), deleteData);
- File.WriteAllBytes(Path.Combine(targetDir, "keep.bin"), keepData);
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
-
- // Phase 1: Clean generates delete JSON
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
- Assert.True(File.Exists(Path.Combine(patchDir, "generalupdate.delete.json")));
-
- // Phase 2: app dir has both files
- File.WriteAllBytes(Path.Combine(appDir, "keep.bin"), keepData);
- File.WriteAllBytes(Path.Combine(appDir, "delete_me.bin"), deleteData);
-
- var patch2Dir = GetPath("patch2");
- CopyDirectory(patchDir, patch2Dir);
-
- // Act
- await pipeline.DirtyAsync(appDir, patch2Dir);
-
- // Assert: keep.bin stays, delete_me.bin is deleted
- Assert.True(File.Exists(Path.Combine(appDir, "keep.bin")));
- Assert.False(File.Exists(Path.Combine(appDir, "delete_me.bin")));
- }
-
- [Fact(DisplayName = "DirtyAsync_补丁目录有额外未知文件_复制到应用目录")]
- public async Task DirtyAsync_UnknownFiles_CopiedToAppDir()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- var appDir = GetPath("app");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
- Directory.CreateDirectory(appDir);
-
- var data = new byte[50];
- new Random(3).NextBytes(data);
- File.WriteAllBytes(Path.Combine(sourceDir, "a.bin"), data);
- File.WriteAllBytes(Path.Combine(targetDir, "a.bin"), data);
-
- // Phase 1: Clean (no differences → empty patch dir)
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Add an "unknown" file (not .patch) to patch dir
- var unknownData = new byte[] { 100, 101, 102, 103 };
- File.WriteAllBytes(Path.Combine(patchDir, "config.xml"), unknownData);
-
- // App dir has original (identical) file
- File.WriteAllBytes(Path.Combine(appDir, "a.bin"), data);
-
- // Act
- await pipeline.DirtyAsync(appDir, patchDir);
-
- // Assert: unknown file was copied to app dir
- var copied = Path.Combine(appDir, "config.xml");
- Assert.True(File.Exists(copied));
- Assert.Equal(unknownData, File.ReadAllBytes(copied));
- }
-
- [Fact(DisplayName = "DirtyAsync_进度回调_最终Completed等于Total")]
- public async Task DirtyAsync_WithProgress_ReportsProgress()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- var appDir = GetPath("app");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
- Directory.CreateDirectory(appDir);
-
- var rng = new Random(55);
- for (int i = 1; i <= 3; i++)
- {
- var oldData = new byte[100]; rng.NextBytes(oldData);
- var newData = new byte[100]; rng.NextBytes(newData);
- File.WriteAllBytes(Path.Combine(sourceDir, $"f{i}.bin"), oldData);
- File.WriteAllBytes(Path.Combine(targetDir, $"f{i}.bin"), newData);
- File.WriteAllBytes(Path.Combine(appDir, $"f{i}.bin"), oldData);
- }
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- var patch2Dir = GetPath("patch2");
- CopyDirectory(patchDir, patch2Dir);
-
- var (progress, captured) = CreateProgressCapture();
-
- // Act
- await pipeline.DirtyAsync(appDir, patch2Dir, progress: progress);
-
- // Assert
- Assert.NotEmpty(captured);
- var last = captured[^1];
- Assert.True(last.IsComplete);
- Assert.True(last.Completed >= 3);
- }
-
- [Fact(DisplayName = "DirtyAsync_已取消令牌_抛出OperationCanceledException")]
- public async Task DirtyAsync_Cancellation_ThrowsOperationCanceledException()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- var appDir = GetPath("app");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
- Directory.CreateDirectory(appDir);
-
- var data = new byte[50]; new Random(1).NextBytes(data);
- var newData = new byte[50]; new Random(2).NextBytes(newData);
- File.WriteAllBytes(Path.Combine(sourceDir, "a.bin"), data);
- File.WriteAllBytes(Path.Combine(targetDir, "a.bin"), newData);
- File.WriteAllBytes(Path.Combine(appDir, "a.bin"), data);
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- var patch2Dir = GetPath("patch2");
- CopyDirectory(patchDir, patch2Dir);
-
- using var cts = new CancellationTokenSource();
- cts.Cancel();
-
- // Act & Assert
- await Assert.ThrowsAsync(() =>
- pipeline.DirtyAsync(appDir, patch2Dir, cancellationToken: cts.Token));
- }
-
- [Fact(DisplayName = "端到端往返_多个文件Clean再Dirty_输出与目标一致")]
- public async Task FullPipeline_RoundTrip_ProducesIdenticalOutput()
- {
- // Arrange
- var sourceDir = GetPath("source");
- var targetDir = GetPath("target");
- var patchDir = GetPath("patch");
- var appDir = GetPath("app");
- Directory.CreateDirectory(sourceDir);
- Directory.CreateDirectory(targetDir);
- Directory.CreateDirectory(patchDir);
- Directory.CreateDirectory(appDir);
-
- var rng = new Random(1234);
-
- // Create 3 modified + 1 new + 1 identical
- var aOld = new byte[200]; rng.NextBytes(aOld);
- var aNew = new byte[200]; rng.NextBytes(aNew);
-
- var bOld = new byte[300]; rng.NextBytes(bOld);
- var bNew = new byte[300]; rng.NextBytes(bNew);
-
- var cOld = new byte[150]; rng.NextBytes(cOld);
- var cNew = new byte[150]; rng.NextBytes(cNew);
-
- var ident = new byte[100]; rng.NextBytes(ident);
- var onlyNew = new byte[80]; rng.NextBytes(onlyNew);
-
- // Source (old version)
- File.WriteAllBytes(Path.Combine(sourceDir, "a.bin"), aOld);
- File.WriteAllBytes(Path.Combine(sourceDir, "b.bin"), bOld);
- File.WriteAllBytes(Path.Combine(sourceDir, "c.bin"), cOld);
- File.WriteAllBytes(Path.Combine(sourceDir, "ident.bin"), ident);
-
- // Target (new version)
- File.WriteAllBytes(Path.Combine(targetDir, "a.bin"), aNew);
- File.WriteAllBytes(Path.Combine(targetDir, "b.bin"), bNew);
- File.WriteAllBytes(Path.Combine(targetDir, "c.bin"), cNew);
- File.WriteAllBytes(Path.Combine(targetDir, "ident.bin"), ident);
- File.WriteAllBytes(Path.Combine(targetDir, "newfile.bin"), onlyNew);
-
- var pipeline = new DiffPipeline(new DiffPipelineOptions { MaxDegreeOfParallelism = 1 });
-
- // Phase 1: Clean
- await pipeline.CleanAsync(sourceDir, targetDir, patchDir);
-
- // Phase 2: set up app dir as old version
- File.WriteAllBytes(Path.Combine(appDir, "a.bin"), aOld);
- File.WriteAllBytes(Path.Combine(appDir, "b.bin"), bOld);
- File.WriteAllBytes(Path.Combine(appDir, "c.bin"), cOld);
- File.WriteAllBytes(Path.Combine(appDir, "ident.bin"), ident);
-
- var patch2Dir = GetPath("patch2");
- CopyDirectory(patchDir, patch2Dir);
-
- // Phase 3: Dirty
- await pipeline.DirtyAsync(appDir, patch2Dir);
-
- // Assert: all target files present with correct content
- Assert.Equal(aNew, File.ReadAllBytes(Path.Combine(appDir, "a.bin")));
- Assert.Equal(bNew, File.ReadAllBytes(Path.Combine(appDir, "b.bin")));
- Assert.Equal(cNew, File.ReadAllBytes(Path.Combine(appDir, "c.bin")));
- Assert.Equal(ident, File.ReadAllBytes(Path.Combine(appDir, "ident.bin")));
- Assert.Equal(onlyNew, File.ReadAllBytes(Path.Combine(appDir, "newfile.bin")));
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Pipeline/DiffPipelineOptionsTests.cs b/src/c#/DifferentialTest/Pipeline/DiffPipelineOptionsTests.cs
deleted file mode 100644
index 4ab380f8..00000000
--- a/src/c#/DifferentialTest/Pipeline/DiffPipelineOptionsTests.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using GeneralUpdate.Core.Pipeline;
-
-namespace DifferentialTest.Pipeline
-{
- ///
- /// 分支覆盖点:
- /// 1. MaxDegreeOfParallelism — 默认 = 2
- /// 2. StopOnFirstError — 默认 = false
- /// 3. DeletePatchAfterApply — 默认 = true
- /// 4. 属性 set/get — 修改后正确返回
- /// 5. 边界值 — MaxDegreeOfParallelism=0 (合法,由调用者检查)
- ///
- /// 触发条件:属性get/set操作
- /// 预期结果:默认值符合规范,set后get返回新值
- ///
- public class DiffPipelineOptionsTests
- {
- [Fact(DisplayName = "默认构造_MaxDegreeOfParallelism为2")]
- public void DefaultConstructor_MaxDegreeOfParallelism_Equals2()
- {
- var options = new DiffPipelineOptions();
-
- Assert.Equal(2, options.MaxDegreeOfParallelism);
- }
-
- [Fact(DisplayName = "默认构造_StopOnFirstError为false")]
- public void DefaultConstructor_StopOnFirstError_IsFalse()
- {
- var options = new DiffPipelineOptions();
-
- Assert.False(options.StopOnFirstError);
- }
-
- [Fact(DisplayName = "默认构造_DeletePatchAfterApply为true")]
- public void DefaultConstructor_DeletePatchAfterApply_IsTrue()
- {
- var options = new DiffPipelineOptions();
-
- Assert.True(options.DeletePatchAfterApply);
- }
-
- [Fact(DisplayName = "MaxDegreeOfParallelism_set_设置后get返回新值")]
- public void MaxDegreeOfParallelism_Set_ReturnsNewValue()
- {
- var options = new DiffPipelineOptions { MaxDegreeOfParallelism = 8 };
-
- Assert.Equal(8, options.MaxDegreeOfParallelism);
- }
-
- [Fact(DisplayName = "StopOnFirstError_set_设置后get返回新值")]
- public void StopOnFirstError_Set_ReturnsNewValue()
- {
- var options = new DiffPipelineOptions { StopOnFirstError = true };
-
- Assert.True(options.StopOnFirstError);
- }
-
- [Fact(DisplayName = "DeletePatchAfterApply_set_设置后get返回新值")]
- public void DeletePatchAfterApply_Set_ReturnsNewValue()
- {
- var options = new DiffPipelineOptions { DeletePatchAfterApply = false };
-
- Assert.False(options.DeletePatchAfterApply);
- }
-
- [Theory(DisplayName = "MaxDegreeOfParallelism_边界值_正确设置")]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(64)]
- [InlineData(int.MaxValue)]
- public void MaxDegreeOfParallelism_BoundaryValues_SetsCorrectly(int value)
- {
- var options = new DiffPipelineOptions { MaxDegreeOfParallelism = value };
-
- Assert.Equal(value, options.MaxDegreeOfParallelism);
- }
- }
-}
diff --git a/src/c#/DifferentialTest/Pipeline/DiffPipelineTests.cs b/src/c#/DifferentialTest/Pipeline/DiffPipelineTests.cs
deleted file mode 100644
index 4dce4b05..00000000
--- a/src/c#/DifferentialTest/Pipeline/DiffPipelineTests.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-using Moq;
-using GeneralUpdate.Differential.Abstractions;
-using GeneralUpdate.Core.Differential;
-using GeneralUpdate.Core.Models;
-using GeneralUpdate.Core.Pipeline;
-
-namespace DifferentialTest.Pipeline
-{
- ///
- /// 分支覆盖点:构造函数、参数验证、异常分支。
- /// 触发条件:各种构造参数和运行时参数的组合。
- /// 预期结果:参数验证、异常分支正确。
- ///
- public class DiffPipelineTests
- {
- [Fact(DisplayName = "构造函数_无参_使用所有默认值")]
- public void Constructor_NoArgs_UsesAllDefaults()
- {
- var pipeline = new DiffPipeline();
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "构造函数_仅options_默认differ和matchers")]
- public void Constructor_OptionsOnly_UsesDefaults()
- {
- var options = new DiffPipelineOptions { MaxDegreeOfParallelism = 2 };
- var pipeline = new DiffPipeline(options);
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "构造函数_options为null_抛出ArgumentNullException")]
- public void Constructor_NullOptions_ThrowsArgumentNullException()
- {
- var mockDiffer = new Mock();
- var ex = Assert.Throws(() =>
- {
- DiffPipelineOptions? opt = null;
- _ = new DiffPipeline(options: opt!, binaryDiffer: mockDiffer.Object, cleanMatcher: (ICleanMatcher?)null);
- });
- Assert.Equal("options", ex.ParamName);
- }
-
- [Fact(DisplayName = "构造函数_binaryDiffer为null_抛出ArgumentNullException")]
- public void Constructor_NullDiffer_ThrowsArgumentNullException()
- {
- var options = new DiffPipelineOptions();
- var ex = Assert.Throws(() =>
- {
- IBinaryDiffer? diff = null;
- _ = new DiffPipeline(options: options, binaryDiffer: diff!, cleanMatcher: (ICleanMatcher?)null);
- });
- Assert.Equal("binaryDiffer", ex.ParamName);
- }
-
- [Fact(DisplayName = "构造函数_cleanMatcher为null_使用DefaultCleanMatcher")]
- public void Constructor_NullCleanMatcher_UsesDefault()
- {
- var options = new DiffPipelineOptions();
- var mockDiffer = new Mock();
- ICleanMatcher? cm = null;
- IDirtyMatcher? dm = null;
- IProgress? pr = null;
- var pipeline = new DiffPipeline(options, mockDiffer.Object, cm, dm, pr);
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "构造函数_dirtyMatcher为null_使用DefaultDirtyMatcher")]
- public void Constructor_NullDirtyMatcher_UsesDefault()
- {
- var options = new DiffPipelineOptions();
- var mockDiffer = new Mock();
- ICleanMatcher? cm = null;
- IDirtyMatcher? dm = null;
- IProgress? pr = null;
- var pipeline = new DiffPipeline(options, mockDiffer.Object, cm, dm, pr);
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "构造函数_progress为null_允许")]
- public void Constructor_NullProgress_Allowed()
- {
- var options = new DiffPipelineOptions();
- var mockDiffer = new Mock();
- ICleanMatcher? cm = null;
- IDirtyMatcher? dm = null;
- IProgress? pr = null;
- var pipeline = new DiffPipeline(options, mockDiffer.Object, cm, dm, pr);
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "构造函数_全参自定义_创建成功")]
- public void Constructor_AllCustom_CreatesSuccessfully()
- {
- var options = new DiffPipelineOptions { MaxDegreeOfParallelism = 4 };
- var mockDiffer = new Mock();
- var mockCleanMatcher = new Mock();
- var mockDirtyMatcher = new Mock();
- var mockProgress = new Mock>();
- var pipeline = new DiffPipeline(options, mockDiffer.Object,
- mockCleanMatcher.Object, mockDirtyMatcher.Object, mockProgress.Object);
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "构造函数_兼容3参重载_创建成功")]
- public void Constructor_CompatOverload_CreatesSuccessfully()
- {
- var options = new DiffPipelineOptions();
- var mockDiffer = new Mock();
- var mockProgress = new Mock>();
- var pipeline = new DiffPipeline(options, mockDiffer.Object, mockProgress.Object);
- Assert.NotNull(pipeline);
- }
-
- [Fact(DisplayName = "CleanAsync_sourcePath为null_抛出ArgumentNullException")]
- public async Task CleanAsync_NullSourcePath_ThrowsArgumentNullException()
- {
- var pipeline = new DiffPipeline();
- await Assert.ThrowsAsync(() =>
- pipeline.CleanAsync(null!, "target", "patch"));
- }
-
- [Fact(DisplayName = "CleanAsync_sourcePath为空白_抛出ArgumentNullException")]
- public async Task CleanAsync_WhitespaceSourcePath_ThrowsArgumentNullException()
- {
- var pipeline = new DiffPipeline();
- await Assert.ThrowsAsync(() =>
- pipeline.CleanAsync(" ", "target", "patch"));
- }
-
- [Fact(DisplayName = "CleanAsync_targetPath为null_抛出ArgumentNullException")]
- public async Task CleanAsync_NullTargetPath_ThrowsArgumentNullException()
- {
- var pipeline = new DiffPipeline();
- await Assert.ThrowsAsync(() =>
- pipeline.CleanAsync("source", null!, "patch"));
- }
-
- [Fact(DisplayName = "CleanAsync_patchPath为null_抛出ArgumentNullException")]
- public async Task CleanAsync_NullPatchPath_ThrowsArgumentNullException()
- {
- var pipeline = new DiffPipeline();
- await Assert.ThrowsAsync(() =>
- pipeline.CleanAsync("source", "target", null!));
- }
-
- [Fact(DisplayName = "CleanAsync_目录不存在_抛出DirectoryNotFoundException")]
- public async Task CleanAsync_NonExistentDir_ThrowsDirectoryNotFoundException()
- {
- var pipeline = new DiffPipeline();
- await Assert.ThrowsAsync(() =>
- pipeline.CleanAsync(
- Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")),
- Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")),
- Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"))));
- }
-
- [Fact(DisplayName = "CleanAsync_CancellationToken已取消_抛出OperationCanceledException")]
- public async Task CleanAsync_CancelledToken_ThrowsOperationCanceledException()
- {
- var pipeline = new DiffPipeline();
- using var cts = new CancellationTokenSource();
- cts.Cancel();
- await Assert.ThrowsAsync(() =>
- pipeline.CleanAsync("src", "tgt", "patch", cancellationToken: cts.Token));
- }
-
- [Fact(DisplayName = "DirtyAsync_CancellationToken已取消_抛出OperationCanceledException")]
- public async Task DirtyAsync_CancelledToken_ThrowsOperationCanceledException()
- {
- var pipeline = new DiffPipeline();
- using var cts = new CancellationTokenSource();
- cts.Cancel();
- await Assert.ThrowsAsync(() =>
- pipeline.DirtyAsync("app", "patch", cancellationToken: cts.Token));
- }
- }
-}
diff --git a/src/c#/DrivelutionTest/Core/DrivelutionFactoryTests.cs b/src/c#/DrivelutionTest/Core/DrivelutionFactoryTests.cs
deleted file mode 100644
index dc1ff6ff..00000000
--- a/src/c#/DrivelutionTest/Core/DrivelutionFactoryTests.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using GeneralUpdate.Drivelution.Core;
-using GeneralUpdate.Drivelution.Abstractions;
-using GeneralUpdate.Drivelution.Abstractions.Configuration;
-
-namespace DrivelutionTest.Core;
-
-///
-/// DrivelutionFactory 测试
-/// 分支覆盖点:
-/// - Create() 返回正确的平台实现
-/// - Create(DrivelutionOptions) 传递选项
-/// - CreateValidator() 返回正确平台验证器
-/// - CreateBackup() 返回正确平台备份实现
-/// - GetCurrentPlatform() 返回平台名称字符串
-/// - IsPlatformSupported() 返回布尔值
-/// - 不支持的平台抛出 PlatformNotSupportedException
-/// 触发条件:调用工厂方法
-/// 预期结果:根据当前平台返回正确实现
-///
-public class DrivelutionFactoryTests
-{
- [Fact(DisplayName = "DrivelutionFactory_Create_返回IGeneralDrivelution实例")]
- public void Create_ReturnsIGeneralDrivelution()
- {
- var updater = DrivelutionFactory.Create();
-
- Assert.NotNull(updater);
- Assert.IsAssignableFrom(updater);
- }
-
- [Fact(DisplayName = "DrivelutionFactory_Create_带Options参数返回实例")]
- public void Create_WithOptions_ReturnsInstance()
- {
- var options = new DrivelutionOptions
- {
- DefaultRetryCount = 5,
- DefaultTimeoutSeconds = 600
- };
-
- var updater = DrivelutionFactory.Create(options);
-
- Assert.NotNull(updater);
- Assert.IsAssignableFrom(updater);
- }
-
- [Fact(DisplayName = "DrivelutionFactory_Create_nullOptions不抛异常")]
- public void Create_NullOptions_DoesNotThrow()
- {
- var updater = DrivelutionFactory.Create(null);
-
- Assert.NotNull(updater);
- }
-
- [Fact(DisplayName = "DrivelutionFactory_CreateValidator_返回IDriverValidator实例")]
- public void CreateValidator_ReturnsIDriverValidator()
- {
- var validator = DrivelutionFactory.CreateValidator();
-
- Assert.NotNull(validator);
- Assert.IsAssignableFrom(validator);
- }
-
- [Fact(DisplayName = "DrivelutionFactory_CreateBackup_返回IDriverBackup实例")]
- public void CreateBackup_ReturnsIDriverBackup()
- {
- var backup = DrivelutionFactory.CreateBackup();
-
- Assert.NotNull(backup);
- Assert.IsAssignableFrom(backup);
- }
-
- [Fact(DisplayName = "DrivelutionFactory_GetCurrentPlatform_返回非空字符串")]
- public void GetCurrentPlatform_ReturnsNonNullString()
- {
- var platform = DrivelutionFactory.GetCurrentPlatform();
-
- Assert.NotNull(platform);
- Assert.NotEmpty(platform);
- }
-
- [Fact(DisplayName = "DrivelutionFactory_IsPlatformSupported_返回true或false")]
- public void IsPlatformSupported_ReturnsBoolean()
- {
- var supported = DrivelutionFactory.IsPlatformSupported();
-
- // On Windows, Linux, or macOS, should return true
- // This test is platform-dependent but always returns bool
- Assert.True(supported || !supported);
- }
-
- [Fact(DisplayName = "DrivelutionFactory_GetCurrentPlatform_返回Windows_Linux_MacOS或Unknown")]
- public void GetCurrentPlatform_ReturnsKnownValue()
- {
- var platform = DrivelutionFactory.GetCurrentPlatform();
-
- Assert.Contains(platform, new[] { "Windows", "Linux", "MacOS", "Unknown" });
- }
-
- [Fact(DisplayName = "DrivelutionFactory_Create_返回不同类型实例_验证非同一引用")]
- public void Create_TwoCalls_ReturnDifferentInstances()
- {
- var u1 = DrivelutionFactory.Create();
- var u2 = DrivelutionFactory.Create();
-
- Assert.NotSame(u1, u2);
- }
-
- [Fact(DisplayName = "DrivelutionFactory_CreateValidator_两次调用返回不同实例")]
- public void CreateValidator_TwoCalls_DifferentInstances()
- {
- var v1 = DrivelutionFactory.CreateValidator();
- var v2 = DrivelutionFactory.CreateValidator();
-
- Assert.NotSame(v1, v2);
- }
-}
diff --git a/src/c#/DrivelutionTest/DrivelutionTest.csproj b/src/c#/DrivelutionTest/DrivelutionTest.csproj
deleted file mode 100644
index 4d7194ef..00000000
--- a/src/c#/DrivelutionTest/DrivelutionTest.csproj
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
- net10.0
- enable
- enable
- false
- true
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/c#/DrivelutionTest/Execution/CommandResultTests.cs b/src/c#/DrivelutionTest/Execution/CommandResultTests.cs
deleted file mode 100644
index e4f8c7c2..00000000
--- a/src/c#/DrivelutionTest/Execution/CommandResultTests.cs
+++ /dev/null
@@ -1,134 +0,0 @@
-using GeneralUpdate.Drivelution.Core.Execution;
-
-namespace DrivelutionTest.Execution;
-
-///
-/// CommandResult 测试
-/// 分支覆盖点:
-/// - 默认构造函数属性默认值
-/// - Success 属性:ExitCode == 0 返回 true,ExitCode != 0 返回 false
-/// - ExitCode 正整数/负整数边界
-/// - StandardOutput/StandardError 字符串
-/// - ToString:成功时显示 Output,失败时显示 Error
-/// 触发条件:创建 CommandResult 并设置属性
-/// 预期结果:逻辑正确
-///
-public class CommandResultTests
-{
- [Fact(DisplayName = "CommandResult_默认构造函数_所有属性为默认值")]
- public void CommandResult_DefaultConstructor_AllPropertiesHaveDefaultValues()
- {
- var result = new CommandResult();
-
- Assert.Equal(0, result.ExitCode);
- Assert.Equal(string.Empty, result.StandardOutput);
- Assert.Equal(string.Empty, result.StandardError);
- }
-
- [Fact(DisplayName = "CommandResult_Success_ExitCode为0时返回true")]
- public void CommandResult_Success_ExitCodeZero_ReturnsTrue()
- {
- var result = new CommandResult { ExitCode = 0 };
- Assert.True(result.Success);
- }
-
- [Fact(DisplayName = "CommandResult_Success_ExitCode为1时返回false")]
- public void CommandResult_Success_ExitCodeOne_ReturnsFalse()
- {
- var result = new CommandResult { ExitCode = 1 };
- Assert.False(result.Success);
- }
-
- [Theory(DisplayName = "CommandResult_Success_ExitCode不为零时全返回false")]
- [InlineData(-1)]
- [InlineData(2)]
- [InlineData(255)]
- [InlineData(int.MaxValue)]
- [InlineData(int.MinValue)]
- public void CommandResult_Success_NonZeroExitCode_ReturnsFalse(int exitCode)
- {
- var result = new CommandResult { ExitCode = exitCode };
- Assert.False(result.Success);
- }
-
- [Fact(DisplayName = "CommandResult_ToString_成功时包含Output")]
- public void CommandResult_ToString_Success_ContainsOutput()
- {
- var result = new CommandResult
- {
- ExitCode = 0,
- StandardOutput = "Driver installed successfully\n"
- };
-
- var str = result.ToString();
- Assert.Contains("ExitCode=0", str);
- Assert.Contains("Driver installed successfully", str);
- Assert.DoesNotContain("Error=", str);
- }
-
- [Fact(DisplayName = "CommandResult_ToString_失败时包含Error")]
- public void CommandResult_ToString_Failure_ContainsError()
- {
- var result = new CommandResult
- {
- ExitCode = 1,
- StandardError = "Permission denied\n"
- };
-
- var str = result.ToString();
- Assert.Contains("ExitCode=1", str);
- Assert.Contains("Error=Permission denied", str);
- }
-
- [Fact(DisplayName = "CommandResult_ToString_StandardOutput有前后空白时被Trim")]
- public void CommandResult_ToString_StandardOutput_Trimmed()
- {
- var result = new CommandResult
- {
- ExitCode = 0,
- StandardOutput = " output \n"
- };
-
- var str = result.ToString();
- Assert.Contains("Output=output", str);
- }
-
- [Fact(DisplayName = "CommandResult_ToString_StandardError有前后空白时被Trim")]
- public void CommandResult_ToString_StandardError_Trimmed()
- {
- var result = new CommandResult
- {
- ExitCode = 2,
- StandardError = " error \n"
- };
-
- var str = result.ToString();
- Assert.Contains("Error=error", str);
- }
-
- [Fact(DisplayName = "CommandResult_ToString_Output为空字符串时正常处理")]
- public void CommandResult_ToString_EmptyOutput_Works()
- {
- var result = new CommandResult
- {
- ExitCode = 0,
- StandardOutput = ""
- };
-
- var str = result.ToString();
- Assert.Contains("Output=", str);
- }
-
- [Fact(DisplayName = "CommandResult_ToString_Error为空字符串时正常处理")]
- public void CommandResult_ToString_EmptyError_Works()
- {
- var result = new CommandResult
- {
- ExitCode = 1,
- StandardError = ""
- };
-
- var str = result.ToString();
- Assert.Contains("Error=", str);
- }
-}
diff --git a/src/c#/DrivelutionTest/LinuxImplementations/LinuxDriverBackupTests.cs b/src/c#/DrivelutionTest/LinuxImplementations/LinuxDriverBackupTests.cs
deleted file mode 100644
index 53350c37..00000000
--- a/src/c#/DrivelutionTest/LinuxImplementations/LinuxDriverBackupTests.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using GeneralUpdate.Drivelution.Linux.Implementation;
-using GeneralUpdate.Drivelution.Abstractions.Exceptions;
-
-namespace DrivelutionTest.LinuxImplementations;
-
-public class LinuxDriverBackupTests : IDisposable
-{
- private readonly string _tempDir;
- private readonly LinuxDriverBackup _backup;
-
- public LinuxDriverBackupTests()
- {
- _tempDir = Path.Combine(Path.GetTempPath(), $"linux_drv_test_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_tempDir);
- _backup = new LinuxDriverBackup();
- }
-
- public void Dispose()
- {
- try { if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, true); }
- catch { }
- }
-
- [Fact(DisplayName = "LinuxDriverBackup_BackupAsync_源文件不存在_抛出FileNotFoundException")]
- public async Task BackupAsync_SourceNotExists_ThrowsFileNotFoundException()
- {
- await Assert.ThrowsAsync(() => _backup.BackupAsync(Path.Combine(_tempDir, "nonexistent.ko"), Path.Combine(_tempDir, "backup")));
- }
-
- [Fact(DisplayName = "LinuxDriverBackup_BackupAsync_成功备份_返回true")]
- public async Task BackupAsync_SuccessfulBackup_ReturnsTrue()
- {
- var sourceFile = Path.Combine(_tempDir, "test.ko");
- await File.WriteAllTextAsync(sourceFile, "module data");
- var result = await _backup.BackupAsync(sourceFile, Path.Combine(_tempDir, "backups", "driver_backup"));
- Assert.True(result);
- }
-
- [Fact(DisplayName = "LinuxDriverBackup_RestoreAsync_备份文件不存在_抛出FileNotFoundException")]
- public async Task RestoreAsync_BackupNotExists_ThrowsFileNotFoundException()
- {
- await Assert.ThrowsAsync(() => _backup.RestoreAsync(Path.Combine(_tempDir, "nobackup.ko"), Path.Combine(_tempDir, "target.ko")));
- }
-
- [Fact(DisplayName = "LinuxDriverBackup_RestoreAsync_成功恢复_返回true")]
- public async Task RestoreAsync_SuccessfulRestore_ReturnsTrue()
- {
- var backupFile = Path.Combine(_tempDir, "backup_module.ko");
- await File.WriteAllTextAsync(backupFile, "backup data");
- var targetFile = Path.Combine(_tempDir, "restored_module.ko");
- var result = await _backup.RestoreAsync(backupFile, targetFile);
- Assert.True(result);
- }
-
- [Fact(DisplayName = "LinuxDriverBackup_DeleteBackupAsync_文件存在_返回true")]
- public async Task DeleteBackupAsync_FileExists_ReturnsTrue()
- {
- var file = Path.Combine(_tempDir, "to_delete.ko");
- await File.WriteAllTextAsync(file, "data");
- var result = await _backup.DeleteBackupAsync(file);
- Assert.True(result);
- }
-
- [Fact(DisplayName = "LinuxDriverBackup_DeleteBackupAsync_文件不存在_返回false")]
- public async Task DeleteBackupAsync_FileNotExists_ReturnsFalse()
- {
- var result = await _backup.DeleteBackupAsync(Path.Combine(_tempDir, "nonexistent.ko"));
- Assert.False(result);
- }
-}
diff --git a/src/c#/DrivelutionTest/Models/DriverInfoTests.cs b/src/c#/DrivelutionTest/Models/DriverInfoTests.cs
deleted file mode 100644
index 820e4b0c..00000000
--- a/src/c#/DrivelutionTest/Models/DriverInfoTests.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-using GeneralUpdate.Drivelution.Abstractions.Models;
-
-namespace DrivelutionTest.Models;
-
-///
-/// DriverInfo 测试
-/// 分支覆盖点:
-/// - 默认构造函数:所有属性应为其默认值
-/// - 属性设置/获取:所有可写属性
-/// - 空字符串、空集合、默认值边界
-/// 触发条件:创建 DriverInfo 实例
-/// 预期结果:属性值正确返回
-///
-public class DriverInfoTests
-{
- [Fact(DisplayName = "DriverInfo_默认构造函数_所有属性为默认值")]
- public void DriverInfo_DefaultConstructor_AllPropertiesHaveDefaultValues()
- {
- // Act
- var info = new DriverInfo();
-
- // Assert
- Assert.Equal(string.Empty, info.Name);
- Assert.Equal(string.Empty, info.Version);
- Assert.Equal(string.Empty, info.FilePath);
- Assert.Equal(string.Empty, info.TargetOS);
- Assert.Equal(string.Empty, info.Architecture);
- Assert.Equal(string.Empty, info.HardwareId);
- Assert.Equal(string.Empty, info.Hash);
- Assert.Equal("SHA256", info.HashAlgorithm);
- Assert.NotNull(info.TrustedPublishers);
- Assert.Empty(info.TrustedPublishers);
- Assert.Equal(string.Empty, info.Description);
- Assert.Equal(default, info.ReleaseDate);
- Assert.NotNull(info.Metadata);
- Assert.Empty(info.Metadata);
- }
-
- [Fact(DisplayName = "DriverInfo_设置Name属性_值正确返回")]
- public void DriverInfo_SetName_ReturnsCorrectValue()
- {
- var info = new DriverInfo { Name = "Test Driver" };
- Assert.Equal("Test Driver", info.Name);
- }
-
- [Fact(DisplayName = "DriverInfo_设置Version属性_值正确返回")]
- public void DriverInfo_SetVersion_ReturnsCorrectValue()
- {
- var info = new DriverInfo { Version = "2.1.0" };
- Assert.Equal("2.1.0", info.Version);
- }
-
- [Fact(DisplayName = "DriverInfo_设置FilePath属性_值正确返回")]
- public void DriverInfo_SetFilePath_ReturnsCorrectValue()
- {
- var info = new DriverInfo { FilePath = "C:\\drivers\\test.sys" };
- Assert.Equal("C:\\drivers\\test.sys", info.FilePath);
- }
-
- [Fact(DisplayName = "DriverInfo_设置TargetOS属性_值正确返回")]
- public void DriverInfo_SetTargetOS_ReturnsCorrectValue()
- {
- var info = new DriverInfo { TargetOS = "Windows" };
- Assert.Equal("Windows", info.TargetOS);
- }
-
- [Fact(DisplayName = "DriverInfo_设置Architecture属性_值正确返回")]
- public void DriverInfo_SetArchitecture_ReturnsCorrectValue()
- {
- var info = new DriverInfo { Architecture = "x64" };
- Assert.Equal("x64", info.Architecture);
- }
-
- [Fact(DisplayName = "DriverInfo_设置HardwareId属性_值正确返回")]
- public void DriverInfo_SetHardwareId_ReturnsCorrectValue()
- {
- var info = new DriverInfo { HardwareId = "PCI\\VEN_8086" };
- Assert.Equal("PCI\\VEN_8086", info.HardwareId);
- }
-
- [Fact(DisplayName = "DriverInfo_设置Hash属性_值正确返回")]
- public void DriverInfo_SetHash_ReturnsCorrectValue()
- {
- var info = new DriverInfo { Hash = "abc123" };
- Assert.Equal("abc123", info.Hash);
- }
-
- [Fact(DisplayName = "DriverInfo_设置HashAlgorithm属性_值正确返回")]
- public void DriverInfo_SetHashAlgorithm_ReturnsCorrectValue()
- {
- var info = new DriverInfo { HashAlgorithm = "MD5" };
- Assert.Equal("MD5", info.HashAlgorithm);
- }
-
- [Fact(DisplayName = "DriverInfo_TrustedPublishers_可以添加和检索发布者")]
- public void DriverInfo_TrustedPublishers_CanAddAndRetrievePublishers()
- {
- var info = new DriverInfo();
- info.TrustedPublishers.Add("Microsoft");
- info.TrustedPublishers.Add("Intel");
-
- Assert.Equal(2, info.TrustedPublishers.Count);
- Assert.Contains("Microsoft", info.TrustedPublishers);
- Assert.Contains("Intel", info.TrustedPublishers);
- }
-
- [Fact(DisplayName = "DriverInfo_设置Description属性_值正确返回")]
- public void DriverInfo_SetDescription_ReturnsCorrectValue()
- {
- var info = new DriverInfo { Description = "Network adapter driver" };
- Assert.Equal("Network adapter driver", info.Description);
- }
-
- [Fact(DisplayName = "DriverInfo_设置ReleaseDate属性_值正确返回")]
- public void DriverInfo_SetReleaseDate_ReturnsCorrectValue()
- {
- var date = new DateTime(2025, 6, 15);
- var info = new DriverInfo { ReleaseDate = date };
- Assert.Equal(date, info.ReleaseDate);
- }
-
- [Fact(DisplayName = "DriverInfo_Metadata_可以添加和检索键值对")]
- public void DriverInfo_Metadata_CanAddAndRetrieveKeyValuePairs()
- {
- var info = new DriverInfo();
- info.Metadata["Author"] = "JusterZhu";
- info.Metadata["Platform"] = "Windows";
-
- Assert.Equal(2, info.Metadata.Count);
- Assert.Equal("JusterZhu", info.Metadata["Author"]);
- Assert.Equal("Windows", info.Metadata["Platform"]);
- }
-
- [Fact(DisplayName = "DriverInfo_TrustedPublishers_空列表_Count为0")]
- public void DriverInfo_TrustedPublishers_EmptyList_CountIsZero()
- {
- var info = new DriverInfo();
- Assert.Empty(info.TrustedPublishers);
- Assert.Equal(0, info.TrustedPublishers.Count);
- }
-
- [Fact(DisplayName = "DriverInfo_Name为空字符串时_不会抛出异常")]
- public void DriverInfo_NameIsEmptyString_DoesNotThrow()
- {
- var info = new DriverInfo { Name = "" };
- Assert.Equal(string.Empty, info.Name);
- }
-}
diff --git a/src/c#/DrivelutionTest/Models/ErrorInfoTests.cs b/src/c#/DrivelutionTest/Models/ErrorInfoTests.cs
deleted file mode 100644
index b2c5a116..00000000
--- a/src/c#/DrivelutionTest/Models/ErrorInfoTests.cs
+++ /dev/null
@@ -1,149 +0,0 @@
-using GeneralUpdate.Drivelution.Abstractions.Models;
-
-namespace DrivelutionTest.Models;
-
-///
-/// ErrorInfo 测试
-/// 分支覆盖点:
-/// - 默认构造函数:所有属性默认值
-/// - 属性赋值:Code, Type, Message, Details, StackTrace, Timestamp, CanRetry, SuggestedResolution
-/// - 可空属性:StackTrace为null, InnerException为null
-/// - 枚举值遍历:ErrorType所有成员
-/// 触发条件:创建 ErrorInfo 实例并设置属性
-/// 预期结果:属性值正确返回
-///
-public class ErrorInfoTests
-{
- [Fact(DisplayName = "ErrorInfo_默认构造函数_所有属性为默认值")]
- public void ErrorInfo_DefaultConstructor_AllPropertiesHaveDefaultValues()
- {
- var error = new ErrorInfo();
-
- Assert.Equal(string.Empty, error.Code);
- Assert.Equal(default(ErrorType), error.Type);
- Assert.Equal(string.Empty, error.Message);
- Assert.Equal(string.Empty, error.Details);
- Assert.Null(error.StackTrace);
- Assert.Null(error.InnerException);
- Assert.Equal(default, error.CanRetry);
- Assert.Equal(string.Empty, error.SuggestedResolution);
- }
-
- [Fact(DisplayName = "ErrorInfo_Timestamp_默认值为UtcNow附近")]
- public void ErrorInfo_Timestamp_DefaultIsNearUtcNow()
- {
- var before = DateTime.UtcNow.AddSeconds(-1);
- var error = new ErrorInfo();
- var after = DateTime.UtcNow.AddSeconds(1);
-
- Assert.InRange(error.Timestamp, before, after);
- }
-
- [Fact(DisplayName = "ErrorInfo_设置Code属性_值正确返回")]
- public void ErrorInfo_SetCode_ReturnsCorrectValue()
- {
- var error = new ErrorInfo { Code = "ERR_TIMEOUT" };
- Assert.Equal("ERR_TIMEOUT", error.Code);
- }
-
- [Fact(DisplayName = "ErrorInfo_设置Type为PermissionDenied_值正确返回")]
- public void ErrorInfo_SetTypePermissionDenied_ReturnsCorrectValue()
- {
- var error = new ErrorInfo { Type = ErrorType.PermissionDenied };
- Assert.Equal(ErrorType.PermissionDenied, error.Type);
- }
-
- [Theory(DisplayName = "ErrorInfo_Type枚举_所有值均可设置")]
- [InlineData(ErrorType.PermissionDenied)]
- [InlineData(ErrorType.SignatureValidationFailed)]
- [InlineData(ErrorType.HashValidationFailed)]
- [InlineData(ErrorType.CompatibilityValidationFailed)]
- [InlineData(ErrorType.FileNotFound)]
- [InlineData(ErrorType.FileCorrupted)]
- [InlineData(ErrorType.BackupFailed)]
- [InlineData(ErrorType.InstallationFailed)]
- [InlineData(ErrorType.RollbackFailed)]
- [InlineData(ErrorType.NetworkError)]
- [InlineData(ErrorType.Timeout)]
- [InlineData(ErrorType.UserCancelled)]
- [InlineData(ErrorType.SystemNotSupported)]
- [InlineData(ErrorType.Unknown)]
- public void ErrorInfo_Type_AllEnumValuesCanBeSet(ErrorType type)
- {
- var error = new ErrorInfo { Type = type };
- Assert.Equal(type, error.Type);
- }
-
- [Fact(DisplayName = "ErrorInfo_设置Message属性_值正确返回")]
- public void ErrorInfo_SetMessage_ReturnsCorrectValue()
- {
- var error = new ErrorInfo { Message = "Operation timed out" };
- Assert.Equal("Operation timed out", error.Message);
- }
-
- [Fact(DisplayName = "ErrorInfo_设置Details属性_值正确返回")]
- public void ErrorInfo_SetDetails_ReturnsCorrectValue()
- {
- var error = new ErrorInfo { Details = "Detailed error information" };
- Assert.Equal("Detailed error information", error.Details);
- }
-
- [Fact(DisplayName = "ErrorInfo_StackTrace为null时_不抛出异常")]
- public void ErrorInfo_StackTraceIsNull_DoesNotThrow()
- {
- var error = new ErrorInfo { StackTrace = null };
- Assert.Null(error.StackTrace);
- }
-
- [Fact(DisplayName = "ErrorInfo_设置StackTrace属性_值正确返回")]
- public void ErrorInfo_SetStackTrace_ReturnsCorrectValue()
- {
- var error = new ErrorInfo { StackTrace = "at Test.Method()" };
- Assert.Equal("at Test.Method()", error.StackTrace);
- }
-
- [Fact(DisplayName = "ErrorInfo_InnerException为null时_不抛出异常")]
- public void ErrorInfo_InnerExceptionIsNull_DoesNotThrow()
- {
- var error = new ErrorInfo { InnerException = null };
- Assert.Null(error.InnerException);
- }
-
- [Fact(DisplayName = "ErrorInfo_设置InnerException属性_值正确返回")]
- public void ErrorInfo_SetInnerException_ReturnsCorrectValue()
- {
- var ex = new InvalidOperationException("test");
- var error = new ErrorInfo { InnerException = ex };
- Assert.Same(ex, error.InnerException);
- }
-
- [Theory(DisplayName = "ErrorInfo_CanRetry_两种值均可设置")]
- [InlineData(true)]
- [InlineData(false)]
- public void ErrorInfo_CanRetry_BothValuesCanBeSet(bool canRetry)
- {
- var error = new ErrorInfo { CanRetry = canRetry };
- Assert.Equal(canRetry, error.CanRetry);
- }
-
- [Fact(DisplayName = "ErrorInfo_设置SuggestedResolution属性_值正确返回")]
- public void ErrorInfo_SetSuggestedResolution_ReturnsCorrectValue()
- {
- var error = new ErrorInfo { SuggestedResolution = "Restart the application" };
- Assert.Equal("Restart the application", error.SuggestedResolution);
- }
-
- [Fact(DisplayName = "ErrorInfo_空字符串Code_不抛出异常")]
- public void ErrorInfo_EmptyStringCode_DoesNotThrow()
- {
- var error = new ErrorInfo { Code = "" };
- Assert.Equal(string.Empty, error.Code);
- }
-
- [Fact(DisplayName = "ErrorInfo_空字符串Message_不抛出异常")]
- public void ErrorInfo_EmptyStringMessage_DoesNotThrow()
- {
- var error = new ErrorInfo { Message = "" };
- Assert.Equal(string.Empty, error.Message);
- }
-}
diff --git a/src/c#/DrivelutionTest/Models/UpdateResultTests.cs b/src/c#/DrivelutionTest/Models/UpdateResultTests.cs
deleted file mode 100644
index 58f00cb4..00000000
--- a/src/c#/DrivelutionTest/Models/UpdateResultTests.cs
+++ /dev/null
@@ -1,146 +0,0 @@
-using GeneralUpdate.Drivelution.Abstractions.Models;
-
-namespace DrivelutionTest.Models;
-
-///
-/// UpdateResult 测试
-/// 分支覆盖点:
-/// - 默认构造函数属性默认值
-/// - DurationMs 计算:EndTime > StartTime 返回正值,EndTime == StartTime 返回0
-/// - StepLogs 列表为空,可添加
-/// - Error 为 null 和 非 null
-/// - BackupPath 为 null
-/// - RolledBack 默认为 false
-/// - Status 枚举所有值
-/// 触发条件:创建 UpdateResult 并设置属性
-/// 预期结果:属性正确返回
-///
-public class UpdateResultTests
-{
- [Fact(DisplayName = "UpdateResult_默认构造函数_所有属性为默认值")]
- public void UpdateResult_DefaultConstructor_AllPropertiesHaveDefaultValues()
- {
- var result = new UpdateResult();
-
- Assert.False(result.Success);
- Assert.Equal(default(UpdateStatus), result.Status);
- Assert.Null(result.Error);
- Assert.Equal(default, result.StartTime);
- Assert.Equal(default, result.EndTime);
- Assert.Null(result.BackupPath);
- Assert.False(result.RolledBack);
- Assert.Equal(string.Empty, result.Message);
- Assert.NotNull(result.StepLogs);
- Assert.Empty(result.StepLogs);
- }
-
- [Fact(DisplayName = "UpdateResult_DurationMs_EndTime大于StartTime_返回正毫秒数")]
- public void UpdateResult_DurationMs_EndTimeAfterStartTime_ReturnsPositiveMs()
- {
- var result = new UpdateResult
- {
- StartTime = new DateTime(2025, 1, 1, 12, 0, 0),
- EndTime = new DateTime(2025, 1, 1, 12, 0, 5)
- };
-
- Assert.Equal(5000, result.DurationMs);
- }
-
- [Fact(DisplayName = "UpdateResult_DurationMs_EndTime等于StartTime_返回0")]
- public void UpdateResult_DurationMs_EndTimeEqualsStartTime_ReturnsZero()
- {
- var time = DateTime.UtcNow;
- var result = new UpdateResult
- {
- StartTime = time,
- EndTime = time
- };
-
- Assert.Equal(0, result.DurationMs);
- }
-
- [Fact(DisplayName = "UpdateResult_Success为true_值正确返回")]
- public void UpdateResult_SuccessIsTrue_ReturnsTrue()
- {
- var result = new UpdateResult { Success = true };
- Assert.True(result.Success);
- }
-
- [Fact(DisplayName = "UpdateResult_Status为Succeeded_值正确返回")]
- public void UpdateResult_StatusIsSucceeded_ReturnsSucceeded()
- {
- var result = new UpdateResult { Status = UpdateStatus.Succeeded };
- Assert.Equal(UpdateStatus.Succeeded, result.Status);
- }
-
- [Fact(DisplayName = "UpdateResult_Error不为null_值正确返回")]
- public void UpdateResult_ErrorNotNull_ReturnsErrorInfo()
- {
- var error = new ErrorInfo { Code = "ERR_TEST" };
- var result = new UpdateResult { Error = error };
-
- Assert.NotNull(result.Error);
- Assert.Equal("ERR_TEST", result.Error.Code);
- }
-
- [Fact(DisplayName = "UpdateResult_Error为null_不抛出异常")]
- public void UpdateResult_ErrorIsNull_DoesNotThrow()
- {
- var result = new UpdateResult { Error = null };
- Assert.Null(result.Error);
- }
-
- [Fact(DisplayName = "UpdateResult_BackupPath为null_不抛出异常")]
- public void UpdateResult_BackupPathIsNull_DoesNotThrow()
- {
- var result = new UpdateResult { BackupPath = null };
- Assert.Null(result.BackupPath);
- }
-
- [Fact(DisplayName = "UpdateResult_BackupPath有值_返回正确路径")]
- public void UpdateResult_BackupPathHasValue_ReturnsCorrectPath()
- {
- var result = new UpdateResult { BackupPath = "C:\\backups\\driver" };
- Assert.Equal("C:\\backups\\driver", result.BackupPath);
- }
-
- [Fact(DisplayName = "UpdateResult_RolledBack为true_值正确返回")]
- public void UpdateResult_RolledBackIsTrue_ReturnsTrue()
- {
- var result = new UpdateResult { RolledBack = true };
- Assert.True(result.RolledBack);
- }
-
- [Fact(DisplayName = "UpdateResult_StepLogs_可以添加日志条目")]
- public void UpdateResult_StepLogs_CanAddLogEntries()
- {
- var result = new UpdateResult();
- result.StepLogs.Add("[12:00:00] Step 1 completed");
- result.StepLogs.Add("[12:00:05] Step 2 completed");
-
- Assert.Equal(2, result.StepLogs.Count);
- Assert.Contains("[12:00:00] Step 1 completed", result.StepLogs);
- }
-
- [Fact(DisplayName = "UpdateResult_Message_可设置信息消息")]
- public void UpdateResult_Message_CanSetInfoMessage()
- {
- var result = new UpdateResult { Message = "Update completed successfully" };
- Assert.Equal("Update completed successfully", result.Message);
- }
-
- [Theory(DisplayName = "UpdateResult_Status_所有枚举值均可设置")]
- [InlineData(UpdateStatus.NotStarted)]
- [InlineData(UpdateStatus.Validating)]
- [InlineData(UpdateStatus.BackingUp)]
- [InlineData(UpdateStatus.Updating)]
- [InlineData(UpdateStatus.Verifying)]
- [InlineData(UpdateStatus.Succeeded)]
- [InlineData(UpdateStatus.Failed)]
- [InlineData(UpdateStatus.RolledBack)]
- public void UpdateResult_Status_AllEnumValuesCanBeSet(UpdateStatus status)
- {
- var result = new UpdateResult { Status = status };
- Assert.Equal(status, result.Status);
- }
-}
diff --git a/src/c#/DrivelutionTest/Models/UpdateStrategyTests.cs b/src/c#/DrivelutionTest/Models/UpdateStrategyTests.cs
deleted file mode 100644
index 78920d29..00000000
--- a/src/c#/DrivelutionTest/Models/UpdateStrategyTests.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-using GeneralUpdate.Drivelution.Abstractions.Models;
-
-namespace DrivelutionTest.Models;
-
-///
-/// UpdateStrategy 测试
-/// 分支覆盖点:
-/// - 默认构造函数:所有属性默认值
-/// - Mode 枚举:Full, Incremental
-/// - ForceUpdate 布尔值
-/// - RequireBackup 布尔值(默认true)
-/// - BackupPath 字符串
-/// - RetryCount 整数值(包括0, 负数, 极值)
-/// - RetryIntervalSeconds 整数值
-/// - Priority 整数值
-/// - RestartMode 枚举:None, Prompt, Delayed, Immediate
-/// - SkipSignatureValidation 布尔值(默认false)
-/// - SkipHashValidation 布尔值(默认false)
-/// - TimeoutSeconds 整数值(默认300)
-/// 触发条件:创建 UpdateStrategy 并设置各属性
-/// 预期结果:属性值正确
-///
-public class UpdateStrategyTests
-{
- [Fact(DisplayName = "UpdateStrategy_默认构造函数_所有属性为默认值")]
- public void UpdateStrategy_DefaultConstructor_AllPropertiesHaveDefaultValues()
- {
- var strategy = new UpdateStrategy();
-
- Assert.Equal(UpdateMode.Full, strategy.Mode);
- Assert.False(strategy.ForceUpdate);
- Assert.True(strategy.RequireBackup);
- Assert.Equal(string.Empty, strategy.BackupPath);
- Assert.Equal(3, strategy.RetryCount);
- Assert.Equal(5, strategy.RetryIntervalSeconds);
- Assert.Equal(0, strategy.Priority);
- Assert.Equal(RestartMode.Prompt, strategy.RestartMode);
- Assert.False(strategy.SkipSignatureValidation);
- Assert.False(strategy.SkipHashValidation);
- Assert.Equal(300, strategy.TimeoutSeconds);
- }
-
- [Theory(DisplayName = "UpdateStrategy_Mode_两种枚举值均可设置")]
- [InlineData(UpdateMode.Full)]
- [InlineData(UpdateMode.Incremental)]
- public void UpdateStrategy_Mode_BothEnumValuesCanBeSet(UpdateMode mode)
- {
- var strategy = new UpdateStrategy { Mode = mode };
- Assert.Equal(mode, strategy.Mode);
- }
-
- [Theory(DisplayName = "UpdateStrategy_ForceUpdate_两种值均可设置")]
- [InlineData(true)]
- [InlineData(false)]
- public void UpdateStrategy_ForceUpdate_BothValuesCanBeSet(bool force)
- {
- var strategy = new UpdateStrategy { ForceUpdate = force };
- Assert.Equal(force, strategy.ForceUpdate);
- }
-
- [Theory(DisplayName = "UpdateStrategy_RequireBackup_两种值均可设置")]
- [InlineData(true)]
- [InlineData(false)]
- public void UpdateStrategy_RequireBackup_BothValuesCanBeSet(bool requireBackup)
- {
- var strategy = new UpdateStrategy { RequireBackup = requireBackup };
- Assert.Equal(requireBackup, strategy.RequireBackup);
- }
-
- [Fact(DisplayName = "UpdateStrategy_BackupPath_空字符串默认值")]
- public void UpdateStrategy_BackupPath_DefaultIsEmptyString()
- {
- var strategy = new UpdateStrategy();
- Assert.Equal(string.Empty, strategy.BackupPath);
- }
-
- [Fact(DisplayName = "UpdateStrategy_BackupPath_可设置路径")]
- public void UpdateStrategy_BackupPath_CanSetPath()
- {
- var strategy = new UpdateStrategy { BackupPath = "C:\\backups" };
- Assert.Equal("C:\\backups", strategy.BackupPath);
- }
-
- [Theory(DisplayName = "UpdateStrategy_RetryCount_各种整数值均可设置")]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(5)]
- [InlineData(100)]
- [InlineData(-1)]
- public void UpdateStrategy_RetryCount_VariousValuesCanBeSet(int count)
- {
- var strategy = new UpdateStrategy { RetryCount = count };
- Assert.Equal(count, strategy.RetryCount);
- }
-
- [Theory(DisplayName = "UpdateStrategy_RetryIntervalSeconds_各种值均可设置")]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(10)]
- [InlineData(60)]
- public void UpdateStrategy_RetryIntervalSeconds_VariousValuesCanBeSet(int seconds)
- {
- var strategy = new UpdateStrategy { RetryIntervalSeconds = seconds };
- Assert.Equal(seconds, strategy.RetryIntervalSeconds);
- }
-
- [Theory(DisplayName = "UpdateStrategy_Priority_各种值均可设置")]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(-1)]
- [InlineData(int.MaxValue)]
- [InlineData(int.MinValue)]
- public void UpdateStrategy_Priority_VariousValuesCanBeSet(int priority)
- {
- var strategy = new UpdateStrategy { Priority = priority };
- Assert.Equal(priority, strategy.Priority);
- }
-
- [Theory(DisplayName = "UpdateStrategy_RestartMode_所有枚举值均可设置")]
- [InlineData(RestartMode.None)]
- [InlineData(RestartMode.Prompt)]
- [InlineData(RestartMode.Delayed)]
- [InlineData(RestartMode.Immediate)]
- public void UpdateStrategy_RestartMode_AllEnumValuesCanBeSet(RestartMode mode)
- {
- var strategy = new UpdateStrategy { RestartMode = mode };
- Assert.Equal(mode, strategy.RestartMode);
- }
-
- [Theory(DisplayName = "UpdateStrategy_SkipSignatureValidation_两种值均可设置")]
- [InlineData(true)]
- [InlineData(false)]
- public void UpdateStrategy_SkipSignatureValidation_BothValuesCanBeSet(bool skip)
- {
- var strategy = new UpdateStrategy { SkipSignatureValidation = skip };
- Assert.Equal(skip, strategy.SkipSignatureValidation);
- }
-
- [Theory(DisplayName = "UpdateStrategy_SkipHashValidation_两种值均可设置")]
- [InlineData(true)]
- [InlineData(false)]
- public void UpdateStrategy_SkipHashValidation_BothValuesCanBeSet(bool skip)
- {
- var strategy = new UpdateStrategy { SkipHashValidation = skip };
- Assert.Equal(skip, strategy.SkipHashValidation);
- }
-
- [Theory(DisplayName = "UpdateStrategy_TimeoutSeconds_各种值均可设置")]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(300)]
- [InlineData(3600)]
- [InlineData(int.MaxValue)]
- public void UpdateStrategy_TimeoutSeconds_VariousValuesCanBeSet(int timeout)
- {
- var strategy = new UpdateStrategy { TimeoutSeconds = timeout };
- Assert.Equal(timeout, strategy.TimeoutSeconds);
- }
-
- [Fact(DisplayName = "UpdateStrategy_TimeoutSeconds为0_表示无超时")]
- public void UpdateStrategy_TimeoutSecondsIsZero_RepresentsNoTimeout()
- {
- var strategy = new UpdateStrategy { TimeoutSeconds = 0 };
- Assert.Equal(0, strategy.TimeoutSeconds);
- }
-}
diff --git a/src/c#/DrivelutionTest/Pipeline/PipelineResultTests.cs b/src/c#/DrivelutionTest/Pipeline/PipelineResultTests.cs
deleted file mode 100644
index c8d45b1a..00000000
--- a/src/c#/DrivelutionTest/Pipeline/PipelineResultTests.cs
+++ /dev/null
@@ -1,205 +0,0 @@
-using GeneralUpdate.Drivelution.Abstractions.Models;
-using GeneralUpdate.Drivelution.Core.Pipeline;
-
-namespace DrivelutionTest.Pipeline;
-
-///
-/// PipelineResult 测试
-/// 分支覆盖点:
-/// - Ok() 静态方法:Success=true, ErrorMessage=null, Exception=null
-/// - Fail(string) 静态方法:Success=false, ErrorMessage已设置, Exception=null
-/// - Fail(string, Exception) 静态方法:Success=false, 已设置 ErrorMessage 和 Exception
-/// - 空字符串 ErrorMessage
-/// - Exception 为 null
-/// 触发条件:调用 Ok() / Fail()
-/// 预期结果:属性正确反映成功/失败状态
-///
-public class PipelineResultTests
-{
- [Fact(DisplayName = "PipelineResult_Ok_返回Success为true")]
- public void PipelineResult_Ok_ReturnsSuccessTrue()
- {
- var result = PipelineResult.Ok();
-
- Assert.True(result.Success);
- Assert.Null(result.ErrorMessage);
- Assert.Null(result.Exception);
- }
-
- [Fact(DisplayName = "PipelineResult_Fail_仅含消息_返回Success为false")]
- public void PipelineResult_Fail_MessageOnly_ReturnsSuccessFalse()
- {
- var result = PipelineResult.Fail("Something went wrong");
-
- Assert.False(result.Success);
- Assert.Equal("Something went wrong", result.ErrorMessage);
- Assert.Null(result.Exception);
- }
-
- [Fact(DisplayName = "PipelineResult_Fail_含消息和异常_返回Success为false")]
- public void PipelineResult_Fail_MessageAndException_ReturnsSuccessFalse()
- {
- var ex = new InvalidOperationException("inner error");
- var result = PipelineResult.Fail("Failed", ex);
-
- Assert.False(result.Success);
- Assert.Equal("Failed", result.ErrorMessage);
- Assert.Same(ex, result.Exception);
- }
-
- [Fact(DisplayName = "PipelineResult_Fail_空字符串消息_不抛出异常")]
- public void PipelineResult_Fail_EmptyMessage_DoesNotThrow()
- {
- var result = PipelineResult.Fail("");
-
- Assert.False(result.Success);
- Assert.Equal("", result.ErrorMessage);
- }
-
- [Fact(DisplayName = "PipelineResult_Fail_消息为null_允许null")]
- public void PipelineResult_Fail_NullMessage_AllowsNull()
- {
- var result = PipelineResult.Fail(null!);
-
- Assert.False(result.Success);
- Assert.Null(result.ErrorMessage);
- }
-
- [Fact(DisplayName = "PipelineResult_Fail_Exception为null_不会包装")]
- public void PipelineResult_Fail_NullException_DoesNotWrap()
- {
- var result = PipelineResult.Fail("Error", null);
-
- Assert.False(result.Success);
- Assert.Equal("Error", result.ErrorMessage);
- Assert.Null(result.Exception);
- }
-
- [Fact(DisplayName = "PipelineResult_Ok_多次调用返回不同实例")]
- public void PipelineResult_Ok_MultipleCallsReturnDifferentInstances()
- {
- var r1 = PipelineResult.Ok();
- var r2 = PipelineResult.Ok();
-
- Assert.NotSame(r1, r2);
- Assert.True(r1.Success);
- Assert.True(r2.Success);
- }
-}
-
-///
-/// PipelineContext 测试
-/// 分支覆盖点:
-/// - 构造函数正确初始化 DriverInfo, Strategy, Result
-/// - 对 null 参数抛出 ArgumentNullException
-/// - Bag 字典可读写
-/// - Bag 初始为空
-/// 触发条件:创建 PipelineContext
-/// 预期结果:构造正确,null 检测生效
-///
-public class PipelineContextTests
-{
- private static DriverInfo CreateDriver() => new()
- {
- Name = "TestDriver",
- Version = "1.0.0",
- FilePath = "/test/path"
- };
-
- private static UpdateStrategy CreateStrategy() => new()
- {
- RequireBackup = true,
- BackupPath = "/backups"
- };
-
- private static UpdateResult CreateResult() => new()
- {
- Status = UpdateStatus.NotStarted
- };
-
- [Fact(DisplayName = "PipelineContext_构造函数_正确初始化所有属性")]
- public void PipelineContext_Constructor_InitializesAllProperties()
- {
- var driver = CreateDriver();
- var strategy = CreateStrategy();
- var result = CreateResult();
-
- var context = new PipelineContext(driver, strategy, result);
-
- Assert.Same(driver, context.DriverInfo);
- Assert.Same(strategy, context.Strategy);
- Assert.Same(result, context.Result);
- Assert.NotNull(context.Bag);
- Assert.Empty(context.Bag);
- }
-
- [Fact(DisplayName = "PipelineContext_DriverInfo为null_抛出ArgumentNullException")]
- public void PipelineContext_DriverInfoNull_ThrowsArgumentNullException()
- {
- Assert.Throws(() =>
- new PipelineContext(null!, CreateStrategy(), CreateResult()));
- }
-
- [Fact(DisplayName = "PipelineContext_Strategy为null_抛出ArgumentNullException")]
- public void PipelineContext_StrategyNull_ThrowsArgumentNullException()
- {
- Assert.Throws(() =>
- new PipelineContext(CreateDriver(), null!, CreateResult()));
- }
-
- [Fact(DisplayName = "PipelineContext_Result为null_抛出ArgumentNullException")]
- public void PipelineContext_ResultNull_ThrowsArgumentNullException()
- {
- Assert.Throws(() =>
- new PipelineContext(CreateDriver(), CreateStrategy(), null!));
- }
-
- [Fact(DisplayName = "PipelineContext_Bag_可存储和检索值")]
- public void PipelineContext_Bag_CanStoreAndRetrieveValues()
- {
- var context = new PipelineContext(CreateDriver(), CreateStrategy(), CreateResult());
-
- context.Bag["key1"] = "value1";
- context.Bag["key2"] = 42;
- context.Bag["BackupPath"] = "/backups/driver";
-
- Assert.Equal(3, context.Bag.Count);
- Assert.Equal("value1", context.Bag["key1"]);
- Assert.Equal(42, context.Bag["key2"]);
- Assert.Equal("/backups/driver", context.Bag["BackupPath"]);
- }
-
- [Fact(DisplayName = "PipelineContext_Bag_可存储null值")]
- public void PipelineContext_Bag_CanStoreNullValues()
- {
- var context = new PipelineContext(CreateDriver(), CreateStrategy(), CreateResult());
-
- context.Bag["nullKey"] = null;
-
- Assert.True(context.Bag.ContainsKey("nullKey"));
- Assert.Null(context.Bag["nullKey"]);
- }
-
- [Fact(DisplayName = "PipelineContext_Bag_TryGetValue_获取不存在的键返回false")]
- public void PipelineContext_Bag_TryGetValue_MissingKey_ReturnsFalse()
- {
- var context = new PipelineContext(CreateDriver(), CreateStrategy(), CreateResult());
-
- var found = context.Bag.TryGetValue("nonexistent", out var value);
-
- Assert.False(found);
- Assert.Null(value);
- }
-
- [Fact(DisplayName = "PipelineContext_Bag_TryGetValue_获取存在的键返回true")]
- public void PipelineContext_Bag_TryGetValue_ExistingKey_ReturnsTrue()
- {
- var context = new PipelineContext(CreateDriver(), CreateStrategy(), CreateResult());
- context.Bag["exists"] = "hello";
-
- var found = context.Bag.TryGetValue("exists", out var value);
-
- Assert.True(found);
- Assert.Equal("hello", value);
- }
-}
diff --git a/src/c#/DrivelutionTest/Pipeline/RetryPolicyTests.cs b/src/c#/DrivelutionTest/Pipeline/RetryPolicyTests.cs
deleted file mode 100644
index 9d602c3f..00000000
--- a/src/c#/DrivelutionTest/Pipeline/RetryPolicyTests.cs
+++ /dev/null
@@ -1,337 +0,0 @@
-using GeneralUpdate.Drivelution.Core.Pipeline;
-using GeneralUpdate.Drivelution.Abstractions.Configuration;
-
-namespace DrivelutionTest.Pipeline;
-
-///
-/// RetryPolicy 测试
-/// 分支覆盖点:
-/// - 构造函数:正确设置 MaxRetries, Delay, UseExponentialBackoff
-/// - Default 静态属性:3次重试, 5秒间隔, 无回退
-/// - NoRetry 静态属性:0次重试, 零延迟
-/// - FromOptions: options为null返回Default, options值有效则构建, 值为0/负使用默认值
-/// - ExecuteAsync: 成功操作直接返回, 失败操作重试, OperationCanceledException不重试直接抛出
-/// - ExecuteWithRetryAsync: 成功返回true, 失败重试, 耗尽重试返回false
-/// - 指数退避:Delay按2^(attempt-1)翻倍
-/// - 边界:MaxRetries=0时无重试
-/// 触发条件:构造和执行操作
-/// 预期结果:重试行为正确
-///
-public class RetryPolicyTests
-{
- [Fact(DisplayName = "RetryPolicy_构造函数_正确设置属性")]
- public void RetryPolicy_Constructor_SetsPropertiesCorrectly()
- {
- var policy = new RetryPolicy(5, TimeSpan.FromSeconds(10));
-
- Assert.Equal(5, policy.MaxRetries);
- Assert.Equal(TimeSpan.FromSeconds(10), policy.Delay);
- Assert.False(policy.UseExponentialBackoff);
- }
-
- [Fact(DisplayName = "RetryPolicy_构造函数_UseExponentialBackoff为true")]
- public void RetryPolicy_Constructor_ExponentialBackoffTrue()
- {
- var policy = new RetryPolicy(3, TimeSpan.FromSeconds(1), true);
-
- Assert.Equal(3, policy.MaxRetries);
- Assert.True(policy.UseExponentialBackoff);
- }
-
- [Fact(DisplayName = "RetryPolicy_Default_返回3次重试5秒间隔")]
- public void RetryPolicy_Default_Returns3Retries5Seconds()
- {
- var policy = RetryPolicy.Default;
-
- Assert.Equal(3, policy.MaxRetries);
- Assert.Equal(TimeSpan.FromSeconds(5), policy.Delay);
- Assert.False(policy.UseExponentialBackoff);
- }
-
- [Fact(DisplayName = "RetryPolicy_NoRetry_返回0次重试零延迟")]
- public void RetryPolicy_NoRetry_ReturnsZeroRetries()
- {
- var policy = RetryPolicy.NoRetry;
-
- Assert.Equal(0, policy.MaxRetries);
- Assert.Equal(TimeSpan.Zero, policy.Delay);
- }
-
- [Fact(DisplayName = "RetryPolicy_FromOptions_null参数返回Default")]
- public void RetryPolicy_FromOptions_NullOptions_ReturnsDefault()
- {
- var policy = RetryPolicy.FromOptions(null);
-
- Assert.Equal(3, policy.MaxRetries);
- Assert.Equal(TimeSpan.FromSeconds(5), policy.Delay);
- }
-
- [Fact(DisplayName = "RetryPolicy_FromOptions_有效值返回对应策略")]
- public void RetryPolicy_FromOptions_ValidOptions_ReturnsCorrespondingPolicy()
- {
- var options = new DrivelutionOptions
- {
- DefaultRetryCount = 10,
- DefaultRetryIntervalSeconds = 15,
- UseExponentialBackoff = true
- };
-
- var policy = RetryPolicy.FromOptions(options);
-
- Assert.Equal(10, policy.MaxRetries);
- Assert.Equal(TimeSpan.FromSeconds(15), policy.Delay);
- Assert.True(policy.UseExponentialBackoff);
- }
-
- [Fact(DisplayName = "RetryPolicy_FromOptions_RetryCount为0使用默认3")]
- public void RetryPolicy_FromOptions_RetryCountZero_UsesDefault3()
- {
- var options = new DrivelutionOptions
- {
- DefaultRetryCount = 0,
- DefaultRetryIntervalSeconds = 5
- };
-
- var policy = RetryPolicy.FromOptions(options);
-
- Assert.Equal(3, policy.MaxRetries);
- }
-
- [Fact(DisplayName = "RetryPolicy_FromOptions_RetryCount为负数使用默认3")]
- public void RetryPolicy_FromOptions_RetryCountNegative_UsesDefault3()
- {
- var options = new DrivelutionOptions
- {
- DefaultRetryCount = -5,
- DefaultRetryIntervalSeconds = 5
- };
-
- var policy = RetryPolicy.FromOptions(options);
-
- Assert.Equal(3, policy.MaxRetries);
- }
-
- [Fact(DisplayName = "RetryPolicy_FromOptions_RetryInterval为0使用默认5")]
- public void RetryPolicy_FromOptions_RetryIntervalZero_UsesDefault5()
- {
- var options = new DrivelutionOptions
- {
- DefaultRetryCount = 3,
- DefaultRetryIntervalSeconds = 0
- };
-
- var policy = RetryPolicy.FromOptions(options);
-
- Assert.Equal(TimeSpan.FromSeconds(5), policy.Delay);
- }
-
- [Fact(DisplayName = "RetryPolicy_FromOptions_RetryInterval为负数使用默认5")]
- public void RetryPolicy_FromOptions_RetryIntervalNegative_UsesDefault5()
- {
- var options = new DrivelutionOptions
- {
- DefaultRetryCount = 3,
- DefaultRetryIntervalSeconds = -2
- };
-
- var policy = RetryPolicy.FromOptions(options);
-
- Assert.Equal(TimeSpan.FromSeconds(5), policy.Delay);
- }
-
- [Fact(DisplayName = "RetryPolicy_FromOptions_所有值为0使用默认值")]
- public void RetryPolicy_FromOptions_AllZero_UsesDefaults()
- {
- var options = new DrivelutionOptions
- {
- DefaultRetryCount = 0,
- DefaultRetryIntervalSeconds = 0
- };
-
- var policy = RetryPolicy.FromOptions(options);
-
- Assert.Equal(3, policy.MaxRetries);
- Assert.Equal(TimeSpan.FromSeconds(5), policy.Delay);
- Assert.False(policy.UseExponentialBackoff);
- }
-
- [Fact(DisplayName = "RetryPolicy_FromOptions_使用指数退避")]
- public void RetryPolicy_FromOptions_UseExponentialBackoff()
- {
- var options = new DrivelutionOptions
- {
- DefaultRetryCount = 5,
- DefaultRetryIntervalSeconds = 2,
- UseExponentialBackoff = true
- };
-
- var policy = RetryPolicy.FromOptions(options);
-
- Assert.Equal(5, policy.MaxRetries);
- Assert.Equal(TimeSpan.FromSeconds(2), policy.Delay);
- Assert.True(policy.UseExponentialBackoff);
- }
-
- [Fact(DisplayName = "RetryPolicy_ExecuteAsync_成功操作立即返回结果")]
- public async Task RetryPolicy_ExecuteAsync_SuccessfulOperation_ReturnsImmediately()
- {
- var policy = new RetryPolicy(3, TimeSpan.FromMilliseconds(10));
- int callCount = 0;
-
- var result = await policy.ExecuteAsync(_ =>
- {
- callCount++;
- return Task.FromResult(42);
- });
-
- Assert.Equal(42, result);
- Assert.Equal(1, callCount);
- }
-
- [Fact(DisplayName = "RetryPolicy_ExecuteAsync_失败后重试成功")]
- public async Task RetryPolicy_ExecuteAsync_RetriesAfterFailure()
- {
- var policy = new RetryPolicy(3, TimeSpan.FromMilliseconds(10));
- int callCount = 0;
-
- var result = await policy.ExecuteAsync(_ =>
- {
- callCount++;
- if (callCount < 3)
- throw new InvalidOperationException("transient");
- return Task.FromResult("success");
- });
-
- Assert.Equal("success", result);
- Assert.Equal(3, callCount);
- }
-
- [Fact(DisplayName = "RetryPolicy_ExecuteAsync_超过重试次数仍抛出最后异常")]
- public async Task RetryPolicy_ExecuteAsync_ExceedsMaxRetries_Rethrows()
- {
- var policy = new RetryPolicy(2, TimeSpan.FromMilliseconds(5));
- int callCount = 0;
-
- var ex = await Assert.ThrowsAsync(() =>
- policy.ExecuteAsync(_ =>
- {
- callCount++;
- throw new InvalidOperationException($"fail {callCount}");
- })
- );
-
- Assert.Contains("fail 3", ex.Message); // 1st call + 2 retries = 3 total
- Assert.Equal(3, callCount);
- }
-
- [Fact(DisplayName = "RetryPolicy_ExecuteAsync_OperationCanceledException不重试直接抛出")]
- public async Task RetryPolicy_ExecuteAsync_Cancellation_ThrowsImmediately()
- {
- var policy = new RetryPolicy(5, TimeSpan.FromMilliseconds(10));
- int callCount = 0;
-
- await Assert.ThrowsAsync(() =>
- policy.ExecuteAsync(_ =>
- {
- callCount++;
- throw new OperationCanceledException("cancelled");
- })
- );
-
- Assert.Equal(1, callCount);
- }
-
- [Fact(DisplayName = "RetryPolicy_ExecuteAsync_MaxRetries为0不重试")]
- public async Task RetryPolicy_ExecuteAsync_ZeroMaxRetries_NoRetry()
- {
- var policy = RetryPolicy.NoRetry;
- int callCount = 0;
-
- await Assert.ThrowsAsync(() =>
- policy.ExecuteAsync(_ =>
- {
- callCount++;
- throw new InvalidOperationException("fail");
- })
- );
-
- Assert.Equal(1, callCount);
- }
-
- [Fact(DisplayName = "RetryPolicy_ExecuteWithRetryAsync_成功返回true")]
- public async Task RetryPolicy_ExecuteWithRetryAsync_Success_ReturnsTrue()
- {
- var policy = new RetryPolicy(3, TimeSpan.FromMilliseconds(5));
- var result = await policy.ExecuteWithRetryAsync(_ => Task.FromResult(true));
- Assert.True(result);
- }
-
- [Fact(DisplayName = "RetryPolicy_ExecuteWithRetryAsync_最终失败返回false")]
- public async Task RetryPolicy_ExecuteWithRetryAsync_AlwaysFalse_ReturnsFalse()
- {
- var policy = new RetryPolicy(2, TimeSpan.FromMilliseconds(5));
- int callCount = 0;
-
- var result = await policy.ExecuteWithRetryAsync(_ =>
- {
- callCount++;
- return Task.FromResult(false);
- });
-
- Assert.False(result);
- Assert.Equal(3, callCount); // 1 + 2 retries
- }
-
- [Fact(DisplayName = "RetryPolicy_ExecuteWithRetryAsync_重试后成功返回true")]
- public async Task RetryPolicy_ExecuteWithRetryAsync_RetrySuccess_ReturnsTrue()
- {
- var policy = new RetryPolicy(3, TimeSpan.FromMilliseconds(5));
- int callCount = 0;
-
- var result = await policy.ExecuteWithRetryAsync(_ =>
- {
- callCount++;
- if (callCount < 3)
- throw new Exception("transient");
- return Task.FromResult(true);
- });
-
- Assert.True(result);
- Assert.Equal(3, callCount);
- }
-
- [Fact(DisplayName = "RetryPolicy_ExecuteWithRetryAsync_OperationCanceledException不重试")]
- public async Task RetryPolicy_ExecuteWithRetryAsync_Cancellation_Rethrows()
- {
- var policy = new RetryPolicy(5, TimeSpan.FromMilliseconds(5));
- int callCount = 0;
-
- await Assert.ThrowsAsync(() =>
- policy.ExecuteWithRetryAsync(_ =>
- {
- callCount++;
- throw new OperationCanceledException();
- })
- );
-
- Assert.Equal(1, callCount);
- }
-
- [Fact(DisplayName = "RetryPolicy_使用CancellationToken取消时立即停止")]
- public async Task RetryPolicy_WithCancellationToken_CancelsImmediately()
- {
- var policy = new RetryPolicy(10, TimeSpan.FromMilliseconds(100));
- using var cts = new CancellationTokenSource();
-
- int callCount = 0;
- var task = policy.ExecuteAsync(ct =>
- {
- callCount++;
- cts.Cancel();
- throw new InvalidOperationException("fail");
- }, cts.Token);
-
- await Assert.ThrowsAsync(() => task);
- Assert.Equal(1, callCount);
- }
-}
diff --git a/src/c#/DrivelutionTest/Utilities/CompatibilityCheckerTests.cs b/src/c#/DrivelutionTest/Utilities/CompatibilityCheckerTests.cs
deleted file mode 100644
index a760f44f..00000000
--- a/src/c#/DrivelutionTest/Utilities/CompatibilityCheckerTests.cs
+++ /dev/null
@@ -1,230 +0,0 @@
-using GeneralUpdate.Drivelution.Abstractions.Models;
-using GeneralUpdate.Drivelution.Core.Utilities;
-
-namespace DrivelutionTest.Utilities;
-
-///
-/// CompatibilityChecker 测试
-/// 分支覆盖点:
-/// - CheckCompatibility: null DriverInfo -> ArgumentNullException
-/// - OS兼容检查: 匹配->true, 不匹配->false
-/// - 架构兼容检查: 匹配->true, 不匹配->false
-/// - TargetOS为空/null -> 假设兼容 (true)
-/// - Architecture为空/null -> 假设兼容 (true)
-/// - GetCurrentOS: Windows/Linux/MacOS/Unknown
-/// - GetCurrentArchitecture
-/// - GetSystemVersion
-/// - GetCompatibilityReport: 完整报告
-/// - NormalizeArchitecture: X64/AMD64/X86_64 -> X64, X86/I386/I686 -> X86, ARM64/AARCH64 -> ARM64, ARM/ARMV7 -> ARM
-/// - CheckCompatibilityAsync
-/// 触发条件:创建 DriverInfo 实例
-/// 预期结果:兼容性检查正确
-///
-public class CompatibilityCheckerTests
-{
- [Fact(DisplayName = "CompatibilityChecker_CheckCompatibility_null参数抛出ArgumentNullException")]
- public async Task CheckCompatibility_NullDriverInfo_ThrowsArgumentNullException()
- {
- await Assert.ThrowsAsync(() =>
- CompatibilityChecker.CheckCompatibilityAsync(null!));
- }
-
- [Fact(DisplayName = "CompatibilityChecker_GetCurrentOS_返回非空字符串")]
- public void GetCurrentOS_ReturnsNonNullString()
- {
- var os = CompatibilityChecker.GetCurrentOS();
- Assert.NotNull(os);
- Assert.NotEmpty(os);
- }
-
- [Fact(DisplayName = "CompatibilityChecker_GetCurrentArchitecture_返回非空字符串")]
- public void GetCurrentArchitecture_ReturnsNonNullString()
- {
- var arch = CompatibilityChecker.GetCurrentArchitecture();
- Assert.NotNull(arch);
- Assert.NotEmpty(arch);
- }
-
- [Fact(DisplayName = "CompatibilityChecker_GetSystemVersion_返回非空字符串")]
- public void GetSystemVersion_ReturnsNonNullString()
- {
- var version = CompatibilityChecker.GetSystemVersion();
- Assert.NotNull(version);
- Assert.NotEmpty(version);
- }
-
- [Fact(DisplayName = "CompatibilityChecker_GetCompatibilityReport_包含所有字段")]
- public void GetCompatibilityReport_ContainsAllFields()
- {
- var driverInfo = new DriverInfo
- {
- TargetOS = "Windows",
- Architecture = "x64"
- };
-
- var report = CompatibilityChecker.GetCompatibilityReport(driverInfo);
-
- Assert.Equal("Windows", report.TargetOS);
- Assert.Equal("x64", report.TargetArchitecture);
- Assert.NotNull(report.CurrentOS);
- Assert.NotNull(report.CurrentArchitecture);
- Assert.NotNull(report.SystemVersion);
- Assert.Equal(report.OSCompatible && report.ArchitectureCompatible, report.OverallCompatible);
- }
-
- [Fact(DisplayName = "CompatibilityChecker_CheckCompatibility_TargetOS为空假定兼容")]
- public void CheckCompatibility_EmptyTargetOS_AssumesCompatible()
- {
- var driverInfo = new DriverInfo
- {
- TargetOS = "",
- Architecture = ""
- };
-
- var result = CompatibilityChecker.CheckCompatibility(driverInfo);
-
- Assert.True(result);
- }
-
- [Fact(DisplayName = "CompatibilityChecker_CheckCompatibility_TargetOS为空格假定兼容")]
- public void CheckCompatibility_WhitespaceTargetOS_AssumesCompatible()
- {
- var driverInfo = new DriverInfo
- {
- TargetOS = " ",
- Architecture = ""
- };
-
- var result = CompatibilityChecker.CheckCompatibility(driverInfo);
-
- Assert.True(result);
- }
-
- [Fact(DisplayName = "CompatibilityChecker_GetCompatibilityReport_TargetOS为空时OverallCompatible为true")]
- public void GetCompatibilityReport_EmptyTargetOS_OverallCompatibleTrue()
- {
- var driverInfo = new DriverInfo
- {
- TargetOS = "",
- Architecture = ""
- };
-
- var report = CompatibilityChecker.GetCompatibilityReport(driverInfo);
-
- Assert.True(report.OverallCompatible);
- Assert.True(report.OSCompatible);
- Assert.True(report.ArchitectureCompatible);
- }
-
- [Fact(DisplayName = "CompatibilityChecker_CheckCompatibility_不匹配OS返回false")]
- public void CheckCompatibility_MismatchedOS_ReturnsFalse()
- {
- var driverInfo = new DriverInfo
- {
- TargetOS = "FreeBSD",
- Architecture = ""
- };
-
- var result = CompatibilityChecker.CheckCompatibility(driverInfo);
-
- Assert.False(result);
- }
-
- [Fact(DisplayName = "CompatibilityChecker_NormalizeArchitecture_X64别名正常化")]
- public void GetCompatibilityReport_NormalizeArchitecture_X64Aliases()
- {
- // Architecture normalization is handled internally in IsArchitectureCompatible
- // We verify via the CompatibilityReport that normalization works
- var currentArch = CompatibilityChecker.GetCurrentArchitecture();
- Assert.NotNull(currentArch);
- }
-
- [Fact(DisplayName = "CompatibilityChecker_CheckCompatibilityAsync_异步返回结果")]
- public async Task CheckCompatibilityAsync_ReturnsCompatibilityResult()
- {
- var driverInfo = new DriverInfo
- {
- TargetOS = "",
- Architecture = ""
- };
-
- var result = await CompatibilityChecker.CheckCompatibilityAsync(driverInfo);
-
- Assert.True(result);
- }
-}
-
-///
-/// CompatibilityReport 测试
-/// 分支覆盖点:
-/// - 默认构造函数属性默认值
-/// - 属性可读写
-/// - OverallCompatible = OSCompatible && ArchitectureCompatible
-/// 触发条件:创建 CompatibilityReport
-/// 预期结果:属性正确链接
-///
-public class CompatibilityReportTests
-{
- [Fact(DisplayName = "CompatibilityReport_默认构造函数_所有属性为默认值")]
- public void CompatibilityReport_DefaultConstructor_AllPropertiesHaveDefaultValues()
- {
- var report = new CompatibilityReport();
-
- Assert.Equal(string.Empty, report.CurrentOS);
- Assert.Equal(string.Empty, report.CurrentArchitecture);
- Assert.Equal(string.Empty, report.SystemVersion);
- Assert.Equal(string.Empty, report.TargetOS);
- Assert.Equal(string.Empty, report.TargetArchitecture);
- Assert.False(report.OSCompatible);
- Assert.False(report.ArchitectureCompatible);
- Assert.False(report.OverallCompatible);
- }
-
- [Fact(DisplayName = "CompatibilityReport_OverallCompatible_OS和架构都兼容时返回true")]
- public void CompatibilityReport_OverallCompatible_BothCompatible_ReturnsTrue()
- {
- var report = new CompatibilityReport
- {
- OSCompatible = true,
- ArchitectureCompatible = true
- };
-
- Assert.True(report.OverallCompatible);
- }
-
- [Fact(DisplayName = "CompatibilityReport_OverallCompatible_仅OS兼容时返回false")]
- public void CompatibilityReport_OverallCompatible_OnlyOSCompatible_ReturnsFalse()
- {
- var report = new CompatibilityReport
- {
- OSCompatible = true,
- ArchitectureCompatible = false
- };
-
- Assert.False(report.OverallCompatible);
- }
-
- [Fact(DisplayName = "CompatibilityReport_OverallCompatible_仅架构兼容时返回false")]
- public void CompatibilityReport_OverallCompatible_OnlyArchCompatible_ReturnsFalse()
- {
- var report = new CompatibilityReport
- {
- OSCompatible = false,
- ArchitectureCompatible = true
- };
-
- Assert.False(report.OverallCompatible);
- }
-
- [Fact(DisplayName = "CompatibilityReport_OverallCompatible_都false时返回false")]
- public void CompatibilityReport_OverallCompatible_NeitherCompatible_ReturnsFalse()
- {
- var report = new CompatibilityReport
- {
- OSCompatible = false,
- ArchitectureCompatible = false
- };
-
- Assert.False(report.OverallCompatible);
- }
-}
diff --git a/src/c#/DrivelutionTest/Utilities/HashValidatorTests.cs b/src/c#/DrivelutionTest/Utilities/HashValidatorTests.cs
deleted file mode 100644
index 197371f6..00000000
--- a/src/c#/DrivelutionTest/Utilities/HashValidatorTests.cs
+++ /dev/null
@@ -1,210 +0,0 @@
-using GeneralUpdate.Drivelution.Core.Utilities;
-
-namespace DrivelutionTest.Utilities;
-
-///
-/// HashValidator 测试
-/// 分支覆盖点:
-/// - ComputeHashAsync: 文件存在且有效 -> 返回hash; 文件不存在 -> FileNotFoundException
-/// - 算法: SHA256 (默认), MD5
-/// - 不支持的算法 -> ArgumentException
-/// - ValidateHashAsync: null/空 expectedHash -> ArgumentException
-/// - ValidateHashAsync: 匹配 -> true, 不匹配 -> false
-/// - ComputeStringHash: 空/空字符串 input -> ArgumentException
-/// - ComputeStringHash: SHA256, MD5, 不支持的算法
-/// - ByteArrayToHexString 内部实现
-/// 触发条件:创建临时文件或字符串来测试
-/// 预期结果:哈希正确,异常正确
-///
-public class HashValidatorTests : IDisposable
-{
- private string _tempFilePath;
-
- public HashValidatorTests()
- {
- _tempFilePath = Path.GetTempFileName();
- }
-
- public void Dispose()
- {
- if (File.Exists(_tempFilePath))
- File.Delete(_tempFilePath);
- }
-
- [Fact(DisplayName = "HashValidator_ComputeHashAsync_SHA256返回64字符hex")]
- public async Task ComputeHashAsync_SHA256_Returns64CharHex()
- {
- await File.WriteAllTextAsync(_tempFilePath, "test data");
-
- var hash = await HashValidator.ComputeHashAsync(_tempFilePath);
-
- Assert.NotNull(hash);
- Assert.Equal(64, hash.Length);
- }
-
- [Fact(DisplayName = "HashValidator_ComputeHashAsync_MD5返回32字符hex")]
- public async Task ComputeHashAsync_MD5_Returns32CharHex()
- {
- await File.WriteAllTextAsync(_tempFilePath, "test data");
-
- var hash = await HashValidator.ComputeHashAsync(_tempFilePath, "MD5");
-
- Assert.NotNull(hash);
- Assert.Equal(32, hash.Length);
- }
-
- [Fact(DisplayName = "HashValidator_ComputeHashAsync_文件不存在抛出FileNotFoundException")]
- public async Task ComputeHashAsync_FileNotFound_ThrowsFileNotFoundException()
- {
- await Assert.ThrowsAsync(() =>
- HashValidator.ComputeHashAsync("nonexistent_file.xyz"));
- }
-
- [Fact(DisplayName = "HashValidator_ComputeHashAsync_不支持的算法抛出ArgumentException")]
- public async Task ComputeHashAsync_UnsupportedAlgorithm_ThrowsArgumentException()
- {
- await File.WriteAllTextAsync(_tempFilePath, "data");
-
- await Assert.ThrowsAsync(() =>
- HashValidator.ComputeHashAsync(_tempFilePath, "SHA1"));
- }
-
- [Fact(DisplayName = "HashValidator_ComputeHashAsync_大小写不敏感算法名")]
- public async Task ComputeHashAsync_CaseInsensitiveAlgorithm_Works()
- {
- await File.WriteAllTextAsync(_tempFilePath, "data");
-
- var hash = await HashValidator.ComputeHashAsync(_tempFilePath, "sha256");
-
- Assert.NotNull(hash);
- Assert.Equal(64, hash.Length);
- }
-
- [Fact(DisplayName = "HashValidator_ComputeHashAsync_相同内容生成相同哈希")]
- public async Task ComputeHashAsync_SameContent_SameHash()
- {
- await File.WriteAllTextAsync(_tempFilePath, "identical content");
-
- var hash1 = await HashValidator.ComputeHashAsync(_tempFilePath);
- var hash2 = await HashValidator.ComputeHashAsync(_tempFilePath);
-
- Assert.Equal(hash1, hash2);
- }
-
- [Fact(DisplayName = "HashValidator_ComputeHashAsync_不同内容生成不同哈希")]
- public async Task ComputeHashAsync_DifferentContent_DifferentHash()
- {
- await File.WriteAllTextAsync(_tempFilePath, "content A");
- var hash1 = await HashValidator.ComputeHashAsync(_tempFilePath);
-
- await File.WriteAllTextAsync(_tempFilePath, "content B");
- var hash2 = await HashValidator.ComputeHashAsync(_tempFilePath);
-
- Assert.NotEqual(hash1, hash2);
- }
-
- [Fact(DisplayName = "HashValidator_ValidateHashAsync_匹配返回true")]
- public async Task ValidateHashAsync_MatchingHash_ReturnsTrue()
- {
- await File.WriteAllTextAsync(_tempFilePath, "hello world");
-
- var hash = await HashValidator.ComputeHashAsync(_tempFilePath);
-
- Assert.True(await HashValidator.ValidateHashAsync(_tempFilePath, hash));
- }
-
- [Fact(DisplayName = "HashValidator_ValidateHashAsync_不匹配返回false")]
- public async Task ValidateHashAsync_MismatchingHash_ReturnsFalse()
- {
- await File.WriteAllTextAsync(_tempFilePath, "hello world");
-
- Assert.False(await HashValidator.ValidateHashAsync(_tempFilePath,
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"));
- }
-
- [Theory(DisplayName = "HashValidator_ValidateHashAsync_null或空expectedHash抛出ArgumentException")]
- [InlineData(null)]
- [InlineData("")]
- [InlineData(" ")]
- public async Task ValidateHashAsync_NullOrEmptyExpectedHash_ThrowsArgumentException(string? expectedHash)
- {
- await File.WriteAllTextAsync(_tempFilePath, "data");
-
- await Assert.ThrowsAsync(() =>
- HashValidator.ValidateHashAsync(_tempFilePath, expectedHash!));
- }
-
- [Fact(DisplayName = "HashValidator_ComputeStringHash_SHA256返回结果")]
- public void ComputeStringHash_SHA256_ReturnsHash()
- {
- var hash = HashValidator.ComputeStringHash("test input");
-
- Assert.NotNull(hash);
- Assert.Equal(64, hash.Length);
- }
-
- [Fact(DisplayName = "HashValidator_ComputeStringHash_MD5返回结果")]
- public void ComputeStringHash_MD5_ReturnsHash()
- {
- var hash = HashValidator.ComputeStringHash("test input", "MD5");
-
- Assert.NotNull(hash);
- Assert.Equal(32, hash.Length);
- }
-
- [Theory(DisplayName = "HashValidator_ComputeStringHash_null或空输入抛出ArgumentException")]
- [InlineData(null)]
- [InlineData("")]
- public void ComputeStringHash_NullOrEmptyInput_ThrowsArgumentException(string? input)
- {
- Assert.Throws(() => HashValidator.ComputeStringHash(input!));
- }
-
- [Fact(DisplayName = "HashValidator_ComputeStringHash_不支持的算法抛出ArgumentException")]
- public void ComputeStringHash_UnsupportedAlgorithm_ThrowsArgumentException()
- {
- Assert.Throws(() => HashValidator.ComputeStringHash("data", "SHA1"));
- }
-
- [Fact(DisplayName = "HashValidator_ComputeStringHash_相同输入产生相同哈希")]
- public void ComputeStringHash_SameInput_SameHash()
- {
- var h1 = HashValidator.ComputeStringHash("hello");
- var h2 = HashValidator.ComputeStringHash("hello");
-
- Assert.Equal(h1, h2);
- }
-
- [Fact(DisplayName = "HashValidator_ComputeStringHash_不同输入产生不同哈希")]
- public void ComputeStringHash_DifferentInput_DifferentHash()
- {
- var h1 = HashValidator.ComputeStringHash("hello");
- var h2 = HashValidator.ComputeStringHash("world");
-
- Assert.NotEqual(h1, h2);
- }
-
- [Fact(DisplayName = "HashValidator_ComputeHashAsync_空文件仍返回有效哈希")]
- public async Task ComputeHashAsync_EmptyFile_ReturnsValidHash()
- {
- await File.WriteAllTextAsync(_tempFilePath, "");
-
- var hash = await HashValidator.ComputeHashAsync(_tempFilePath);
-
- Assert.NotNull(hash);
- Assert.Equal(64, hash.Length);
- }
-
- [Fact(DisplayName = "HashValidator_ValidateHashAsync_大小写不敏感比较")]
- public async Task ValidateHashAsync_CaseInsensitiveComparison_ReturnsTrue()
- {
- await File.WriteAllTextAsync(_tempFilePath, "data");
-
- var hash = await HashValidator.ComputeHashAsync(_tempFilePath);
- var upperHash = hash.ToUpper();
- var lowerHash = hash.ToLower();
-
- Assert.True(await HashValidator.ValidateHashAsync(_tempFilePath, upperHash));
- Assert.True(await HashValidator.ValidateHashAsync(_tempFilePath, lowerHash));
- }
-}
diff --git a/src/c#/DrivelutionTest/Utilities/RestartHelperTests.cs b/src/c#/DrivelutionTest/Utilities/RestartHelperTests.cs
deleted file mode 100644
index aa6baf8c..00000000
--- a/src/c#/DrivelutionTest/Utilities/RestartHelperTests.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-using GeneralUpdate.Drivelution.Core.Utilities;
-using GeneralUpdate.Drivelution.Abstractions.Models;
-
-namespace DrivelutionTest.Utilities;
-
-///
-/// RestartHelper 测试
-/// 分支覆盖点:
-/// - HandleRestartAsync: RestartMode.None -> true
-/// - HandleRestartAsync: RestartMode.Prompt -> PromptUserForRestart (返回false)
-/// - HandleRestartAsync: RestartMode.Delayed -> 延迟后调用重启
-/// - HandleRestartAsync: RestartMode.Immediate -> 立即重启
-/// - HandleRestartAsync: 未知RestartMode -> false
-/// - PromptUserForRestart: 始终返回false (简化实现)
-/// - IsRestartRequired: None -> false, 其他 -> true
-/// - RestartCurrentProcess: 会导致进程退出(无法直接测试),但可验证调用不抛异常
-/// - RestartSystemAsync: 端到端测试(会尝试真实重启, 此处只测试异常处理路径)
-/// 触发条件:调用各辅助方法
-/// 预期结果:模式分发正确
-///
-public class RestartHelperTests
-{
- [Fact(DisplayName = "RestartHelper_HandleRestartAsync_RestartModeNone返回true")]
- public async Task HandleRestartAsync_ModeNone_ReturnsTrue()
- {
- var result = await RestartHelper.HandleRestartAsync(RestartMode.None);
-
- Assert.True(result);
- }
-
- [Fact(DisplayName = "RestartHelper_HandleRestartAsync_RestartModePrompt返回false")]
- public async Task HandleRestartAsync_ModePrompt_ReturnsFalse()
- {
- // PromptUserForRestart always returns false in simplified implementation
- var result = await RestartHelper.HandleRestartAsync(RestartMode.Prompt);
-
- Assert.False(result);
- }
-
- [Fact(DisplayName = "RestartHelper_PromptUserForRestart_返回false")]
- public void PromptUserForRestart_ReturnsFalse()
- {
- var result = RestartHelper.PromptUserForRestart();
-
- Assert.False(result);
- }
-
- [Fact(DisplayName = "RestartHelper_PromptUserForRestart_带消息参数返回false")]
- public void PromptUserForRestart_WithMessage_ReturnsFalse()
- {
- var result = RestartHelper.PromptUserForRestart("Custom message");
-
- Assert.False(result);
- }
-
- [Fact(DisplayName = "RestartHelper_PromptUserForRestart_空消息使用默认消息")]
- public void PromptUserForRestart_EmptyMessage_UsesDefault()
- {
- var result = RestartHelper.PromptUserForRestart("");
-
- Assert.False(result);
- }
-
- [Fact(DisplayName = "RestartHelper_IsRestartRequired_None模式返回false")]
- public void IsRestartRequired_ModeNone_ReturnsFalse()
- {
- Assert.False(RestartHelper.IsRestartRequired(RestartMode.None));
- }
-
- [Theory(DisplayName = "RestartHelper_IsRestartRequired_非None模式返回true")]
- [InlineData(RestartMode.Prompt)]
- [InlineData(RestartMode.Delayed)]
- [InlineData(RestartMode.Immediate)]
- public void IsRestartRequired_NonNoneMode_ReturnsTrue(RestartMode mode)
- {
- Assert.True(RestartHelper.IsRestartRequired(mode));
- }
-
- [Fact(DisplayName = "RestartHelper_HandleRestartAsync_Delayed模式_等待后尝试重启")]
- public async Task HandleRestartAsync_ModeDelayed_WaitsAndReturns()
- {
- using var cts = new CancellationTokenSource(2000);
-
- var task = RestartHelper.HandleRestartAsync(RestartMode.Delayed, 1, "test");
-
- // Should complete quickly (1 second delay) or fail fast on non-elevated
- try { await task.WaitAsync(cts.Token); } catch (OperationCanceledException) { }
- }
-
- [Fact(DisplayName = "RestartHelper_HandleRestartAsync_Immediate模式返回结果")]
- public async Task HandleRestartAsync_ModeImmediate_ReturnsResult()
- {
- using var cts = new CancellationTokenSource(2000);
-
- var task = RestartHelper.HandleRestartAsync(RestartMode.Immediate);
-
- try { await task.WaitAsync(cts.Token); } catch (OperationCanceledException) { }
- }
-
- [Fact(DisplayName = "RestartHelper_HandleRestartAsync_未知模式返回false")]
- public async Task HandleRestartAsync_UnknownMode_ReturnsFalse()
- {
- var result = await RestartHelper.HandleRestartAsync((RestartMode)999);
-
- Assert.False(result);
- }
-}
diff --git a/src/c#/DrivelutionTest/Utilities/VersionComparerTests.cs b/src/c#/DrivelutionTest/Utilities/VersionComparerTests.cs
deleted file mode 100644
index 4bcbc6dd..00000000
--- a/src/c#/DrivelutionTest/Utilities/VersionComparerTests.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-using GeneralUpdate.Drivelution.Core.Utilities;
-
-namespace DrivelutionTest.Utilities;
-
-///
-/// VersionComparer 测试
-/// 分支覆盖点:
-/// - Compare: 正常版本比较, null/空字符串抛出 ArgumentException
-/// - Major/Minor/Patch 不同时的比较
-/// - 预发布版本比较: 正式版 > 预发布版
-/// - 预发布标识符比较: 数字 vs 数字, 字母 vs 字母, 数字 vs 字母
-/// - 较长预发布 > 较短预发布
-/// - IsGreaterThan / IsLessThan / IsEqual 辅助方法
-/// - IsValidSemVer: 有效版本返回 true, null/空/无效返回 false
-/// - 非SemVer格式抛出 FormatException
-/// 触发条件:调用各比较和验证方法
-/// 预期结果:正确比较和验证
-///
-public class VersionComparerTests
-{
- [Theory(DisplayName = "VersionComparer_Compare_相同版本返回0")]
- [InlineData("1.0.0", "1.0.0")]
- [InlineData("2.1.3", "2.1.3")]
- [InlineData("0.0.0", "0.0.0")]
- [InlineData("10.20.30", "10.20.30")]
- public void Compare_SameVersions_ReturnsZero(string v1, string v2)
- {
- Assert.Equal(0, VersionComparer.Compare(v1, v2));
- }
-
- [Theory(DisplayName = "VersionComparer_Compare_v1大于v2返回1")]
- [InlineData("2.0.0", "1.0.0")]
- [InlineData("1.2.0", "1.1.0")]
- [InlineData("1.0.3", "1.0.2")]
- [InlineData("10.0.0", "9.99.99")]
- public void Compare_V1Greater_ReturnsOne(string v1, string v2)
- {
- Assert.Equal(1, VersionComparer.Compare(v1, v2));
- }
-
- [Theory(DisplayName = "VersionComparer_Compare_v1小于v2返回-1")]
- [InlineData("1.0.0", "2.0.0")]
- [InlineData("1.1.0", "1.2.0")]
- [InlineData("1.0.2", "1.0.3")]
- public void Compare_V1Less_ReturnsMinusOne(string v1, string v2)
- {
- Assert.Equal(-1, VersionComparer.Compare(v1, v2));
- }
-
- [Theory(DisplayName = "VersionComparer_Compare_正式版大于预发布版")]
- [InlineData("1.0.0", "1.0.0-alpha")]
- [InlineData("1.0.0", "1.0.0-beta.1")]
- public void Compare_ReleaseGreaterThanPreRelease_ReturnsOne(string v1, string v2)
- {
- Assert.True(VersionComparer.Compare(v1, v2) > 0);
- }
-
- [Theory(DisplayName = "VersionComparer_Compare_预发布版小于正式版")]
- [InlineData("1.0.0-alpha", "1.0.0")]
- [InlineData("1.0.0-rc.1", "1.0.0")]
- public void Compare_PreReleaseLessThanRelease_ReturnsMinusOne(string v1, string v2)
- {
- Assert.True(VersionComparer.Compare(v1, v2) < 0);
- }
-
- [Theory(DisplayName = "VersionComparer_Compare_预发布版本号比较")]
- [InlineData("1.0.0-1", "1.0.0-2")]
- [InlineData("1.0.0-alpha", "1.0.0-beta")]
- [InlineData("1.0.0-alpha.1", "1.0.0-alpha.2")]
- public void Compare_PreReleaseComparison_OrderedCorrectly(string v1, string v2)
- {
- Assert.True(VersionComparer.Compare(v1, v2) < 0);
- }
-
- [Fact(DisplayName = "VersionComparer_Compare_数字预发布标识小于字母标识")]
- public void Compare_NumericPreReleaseLessThanAlpha()
- {
- Assert.True(VersionComparer.Compare("1.0.0-1", "1.0.0-alpha") < 0);
- }
-
- [Fact(DisplayName = "VersionComparer_Compare_较长预发布标识更大")]
- public void Compare_LongerPreReleaseIsGreater()
- {
- Assert.True(VersionComparer.Compare("1.0.0-alpha.1.2", "1.0.0-alpha.1") > 0);
- }
-
- [Theory(DisplayName = "VersionComparer_Compare_null或空字符串抛出ArgumentException")]
- [InlineData(null, "1.0.0")]
- [InlineData("1.0.0", null)]
- [InlineData("", "1.0.0")]
- [InlineData("1.0.0", "")]
- [InlineData(" ", "1.0.0")]
- public void Compare_NullOrEmpty_ThrowsArgumentException(string? v1, string? v2)
- {
- Assert.Throws(() => VersionComparer.Compare(v1!, v2!));
- }
-
- [Fact(DisplayName = "VersionComparer_Compare_无效版本格式抛出FormatException")]
- public void Compare_InvalidFormat_ThrowsFormatException()
- {
- Assert.Throws(() => VersionComparer.Compare("not.a.version", "1.0.0"));
- }
-
- [Theory(DisplayName = "VersionComparer_IsGreaterThan_正确判断")]
- [InlineData("2.0.0", "1.0.0", true)]
- [InlineData("1.0.0", "2.0.0", false)]
- [InlineData("1.0.0", "1.0.0", false)]
- public void IsGreaterThan_CorrectlyDetermined(string v1, string v2, bool expected)
- {
- Assert.Equal(expected, VersionComparer.IsGreaterThan(v1, v2));
- }
-
- [Theory(DisplayName = "VersionComparer_IsLessThan_正确判断")]
- [InlineData("1.0.0", "2.0.0", true)]
- [InlineData("2.0.0", "1.0.0", false)]
- [InlineData("1.0.0", "1.0.0", false)]
- public void IsLessThan_CorrectlyDetermined(string v1, string v2, bool expected)
- {
- Assert.Equal(expected, VersionComparer.IsLessThan(v1, v2));
- }
-
- [Theory(DisplayName = "VersionComparer_IsEqual_正确判断")]
- [InlineData("1.0.0", "1.0.0", true)]
- [InlineData("1.0.0", "1.0.1", false)]
- public void IsEqual_CorrectlyDetermined(string v1, string v2, bool expected)
- {
- Assert.Equal(expected, VersionComparer.IsEqual(v1, v2));
- }
-
- [Theory(DisplayName = "VersionComparer_IsValidSemVer_有效版本格式")]
- [InlineData("1.0.0")]
- [InlineData("0.0.0")]
- [InlineData("10.20.30")]
- [InlineData("1.0.0-alpha")]
- [InlineData("1.0.0-alpha.1")]
- [InlineData("1.0.0-alpha.beta")]
- [InlineData("1.0.0+build")]
- [InlineData("1.0.0-alpha+build")]
- [InlineData("1.0.0+build.123")]
- public void IsValidSemVer_ValidVersions_ReturnsTrue(string version)
- {
- Assert.True(VersionComparer.IsValidSemVer(version));
- }
-
- [Theory(DisplayName = "VersionComparer_IsValidSemVer_无效版本格式")]
- [InlineData(null)]
- [InlineData("")]
- [InlineData(" ")]
- [InlineData("1")]
- [InlineData("1.0")]
- [InlineData("v1.0.0")]
- [InlineData("1.0.0.0")]
- [InlineData("not.a.version")]
- public void IsValidSemVer_InvalidVersions_ReturnsFalse(string? version)
- {
- Assert.False(VersionComparer.IsValidSemVer(version));
- }
-
- [Theory(DisplayName = "VersionComparer_Compare_带BuildMetadata版本正确比较")]
- [InlineData("1.0.0+build1", "1.0.0+build2")]
- public void Compare_WithBuildMetadata_ComparedCorrectly(string v1, string v2)
- {
- Assert.Equal(0, VersionComparer.Compare(v1, v2));
- }
-
- [Theory(DisplayName = "VersionComparer_Compare_包含预发布和构建元数据")]
- [InlineData("1.0.0-alpha.1+build", "1.0.0-alpha.2+build")]
- public void Compare_WithBuildAndPreRelease_CorrectOrder(string v1, string v2)
- {
- Assert.True(VersionComparer.Compare(v1, v2) < 0);
- }
-
- [Fact(DisplayName = "VersionComparer_Compare_Major值极大版本比较")]
- public void Compare_VeryLargeMajorVersion_Works()
- {
- Assert.Equal(1, VersionComparer.Compare("999999.0.0", "999998.0.0"));
- }
-}
diff --git a/src/c#/DrivelutionTest/WindowsImplementations/WindowsDriverBackupTests.cs b/src/c#/DrivelutionTest/WindowsImplementations/WindowsDriverBackupTests.cs
deleted file mode 100644
index e79179e1..00000000
--- a/src/c#/DrivelutionTest/WindowsImplementations/WindowsDriverBackupTests.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using GeneralUpdate.Drivelution.Windows.Implementation;
-using GeneralUpdate.Drivelution.Abstractions.Exceptions;
-
-namespace DrivelutionTest.WindowsImplementations;
-
-///
-/// WindowsDriverBackup 测试
-/// 分支覆盖点:
-/// - BackupAsync: 源文件不存在 -> FileNotFoundException
-/// - BackupAsync: 备份目录不存在 -> 自动创建
-/// - BackupAsync: 成功备份 -> true
-/// - BackupAsync: 异常 -> DriverBackupException
-/// - RestoreAsync: 备份文件不存在 -> FileNotFoundException
-/// - RestoreAsync: 目标文件已存在 -> 重命名为.old
-/// - RestoreAsync: 成功恢复 -> true
-/// - RestoreAsync: 异常 -> DriverRollbackException
-/// - DeleteBackupAsync: 文件存在 -> 删除返回true
-/// - DeleteBackupAsync: 文件不存在 -> 返回false
-/// - DeleteBackupAsync: 异常 -> 返回false
-/// 触发条件:创建临时文件测试备份/恢复/删除
-/// 预期结果:I/O操作正确执行
-///
-public class WindowsDriverBackupTests : IDisposable
-{
- private readonly string _tempDir;
- private readonly WindowsDriverBackup _backup;
-
- public WindowsDriverBackupTests()
- {
- _tempDir = Path.Combine(Path.GetTempPath(), $"drivelution_test_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_tempDir);
- _backup = new WindowsDriverBackup();
- }
-
- public void Dispose()
- {
- try { if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, true); }
- catch { /* cleanup best-effort */ }
- }
-
- [Fact(DisplayName = "WindowsDriverBackup_BackupAsync_源文件不存在_抛出FileNotFoundException")]
- public async Task BackupAsync_SourceNotExists_ThrowsFileNotFoundException()
- {
- await Assert.ThrowsAsync(() =>
- _backup.BackupAsync(Path.Combine(_tempDir, "nonexistent.sys"),
- Path.Combine(_tempDir, "backup")));
- }
-
- [Fact(DisplayName = "WindowsDriverBackup_BackupAsync_成功备份_返回true")]
- public async Task BackupAsync_SuccessfulBackup_ReturnsTrue()
- {
- var sourceFile = Path.Combine(_tempDir, "test.sys");
- await File.WriteAllTextAsync(sourceFile, "driver data");
- var backupPath = Path.Combine(_tempDir, "backups", "driver_backup");
-
- var result = await _backup.BackupAsync(sourceFile, backupPath);
-
- Assert.True(result);
- // Directory should be created
- Assert.True(Directory.Exists(Path.Combine(_tempDir, "backups")));
- }
-
- [Fact(DisplayName = "WindowsDriverBackup_RestoreAsync_备份文件不存在_抛出FileNotFoundException")]
- public async Task RestoreAsync_BackupNotExists_ThrowsFileNotFoundException()
- {
- await Assert.ThrowsAsync(() =>
- _backup.RestoreAsync(Path.Combine(_tempDir, "nobackup.sys"),
- Path.Combine(_tempDir, "target.sys")));
- }
-
- [Fact(DisplayName = "WindowsDriverBackup_RestoreAsync_成功恢复_返回true")]
- public async Task RestoreAsync_SuccessfulRestore_ReturnsTrue()
- {
- // Create backup
- var backupFile = Path.Combine(_tempDir, "backup_driver.sys");
- await File.WriteAllTextAsync(backupFile, "backup data");
- var targetFile = Path.Combine(_tempDir, "restored_driver.sys");
-
- var result = await _backup.RestoreAsync(backupFile, targetFile);
-
- Assert.True(result);
- Assert.True(File.Exists(targetFile));
- }
-
- [Fact(DisplayName = "WindowsDriverBackup_RestoreAsync_目标文件已存在_先重命名")]
- public async Task RestoreAsync_TargetExists_RenamesFirst()
- {
- var backupFile = Path.Combine(_tempDir, "backup_driver2.sys");
- await File.WriteAllTextAsync(backupFile, "backup data");
- var targetFile = Path.Combine(_tempDir, "existing_target.sys");
- await File.WriteAllTextAsync(targetFile, "existing data");
-
- var result = await _backup.RestoreAsync(backupFile, targetFile);
-
- Assert.True(result);
- Assert.True(File.Exists(targetFile + ".old"));
- }
-
- [Fact(DisplayName = "WindowsDriverBackup_DeleteBackupAsync_文件存在_返回true")]
- public async Task DeleteBackupAsync_FileExists_ReturnsTrue()
- {
- var file = Path.Combine(_tempDir, "to_delete.sys");
- await File.WriteAllTextAsync(file, "data");
-
- var result = await _backup.DeleteBackupAsync(file);
-
- Assert.True(result);
- Assert.False(File.Exists(file));
- }
-
- [Fact(DisplayName = "WindowsDriverBackup_DeleteBackupAsync_文件不存在_返回false")]
- public async Task DeleteBackupAsync_FileNotExists_ReturnsFalse()
- {
- var result = await _backup.DeleteBackupAsync(Path.Combine(_tempDir, "nonexistent.sys"));
- Assert.False(result);
- }
-}
diff --git a/src/c#/ExtensionTest/Communication/ExtensionHttpClientTests.cs b/src/c#/ExtensionTest/Communication/ExtensionHttpClientTests.cs
deleted file mode 100644
index 19839a73..00000000
--- a/src/c#/ExtensionTest/Communication/ExtensionHttpClientTests.cs
+++ /dev/null
@@ -1,447 +0,0 @@
-///
-/// 测试覆盖点:
-/// - 构造函数
-/// - (serverUrl, scheme, token) 便利构造函数
-/// - (serverUrl, scheme, token, httpClient, ownsHttpClient) 完整构造函数
-/// - serverUrl 为 null => ArgumentNullException
-/// - httpClient 为 null => ArgumentNullException
-/// - serverUrl 以 '/' 结尾 => 被 TrimEnd
-/// - scheme/token 为空 => Authorization header 不设置
-/// - scheme/token 非空 => Authorization header 正确设置
-/// - QueryExtensionsAsync(query, ct)
-/// - 成功响应 => 反序列化返回 DTO
-/// - 非成功状态码 => 返回 Message 含 HTTP 状态码
-/// - 网络异常 => 返回失败 DTO
-/// - JSON 反序列化为 null => 返回默认 DTO
-/// - 取消 Token => 抛出 OperationCanceledException
-/// - DownloadExtensionAsync(extensionId, savePath, progress, ct)
-/// - 委托给 DownloadExtensionWithResultAsync
-/// - 返回 result.Success
-/// - DownloadExtensionWithResultAsync(extensionId, savePath, progress, ct)
-/// - 成功下载
-/// - 断点续传_文件已存在时追加
-/// - HTTP 416 RangeNotSatisfiable => Ok()
-/// - 客户端错误 4xx => Fail ClientError
-/// - 服务器错误 5xx => Fail ServerError
-/// - OperationCanceledException => Fail Cancelled
-/// - HttpRequestException => Fail NetworkError
-/// - IOException => Fail IoError
-/// - 其他异常 => Fail Unknown
-/// - progress 报告进度
-/// - Dispose()
-/// - ownsHttpClient=true => 释放 HttpClient
-/// - ownsHttpClient=false => 不释放 HttpClient
-///
-using System.Net;
-using System.Net.Http;
-using Moq;
-using Moq.Protected;
-using Newtonsoft.Json;
-using GeneralUpdate.Extension.Communication;
-using GeneralUpdate.Extension.Common.DTOs;
-using GeneralUpdate.Extension.Common.Models;
-
-namespace GeneralUpdate.Extension.Communication.Tests;
-
-public class ExtensionHttpClientTests
-{
- private static Mock CreateHandlerMock(HttpResponseMessage response)
- {
- var handler = new Mock();
- handler.Protected()
- .Setup>("SendAsync",
- ItExpr.IsAny(),
- ItExpr.IsAny())
- .ReturnsAsync(response);
- return handler;
- }
-
- // ===== 构造函数测试 =====
-
- [Fact]
- public void 构造函数_ServerUrl为null_抛出ArgumentNullException()
- {
- Assert.Throws(() =>
- new ExtensionHttpClient(null!, "Bearer", "token"));
- }
-
- [Fact]
- public void 构造函数_HttpClient为null_抛出ArgumentNullException()
- {
- Assert.Throws(() =>
- new ExtensionHttpClient("http://test", "Bearer", "token", null!));
- }
-
- [Fact]
- public void 构造函数_ServerUrl末尾斜杠被Trim()
- {
- using var httpClient = new HttpClient();
- var client = new ExtensionHttpClient("http://test.com/", "Bearer", "token", httpClient);
- Assert.NotNull(client);
- }
-
- [Fact]
- public async Task 构造函数_Scheme和Token非空_设置AuthorizationHeader()
- {
- var handler = new Mock();
- HttpRequestMessage? capturedRequest = null;
- handler.Protected()
- .Setup>("SendAsync",
- ItExpr.IsAny(),
- ItExpr.IsAny())
- .Callback((req, _) => capturedRequest = req)
- .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new StringContent(JsonConvert.SerializeObject(new HttpResponseDTO>()))
- });
-
- using var httpClient = new HttpClient(handler.Object);
- using var client = new ExtensionHttpClient("http://test", "Bearer", "my-token", httpClient);
-
- await client.QueryExtensionsAsync(new ExtensionQueryDTO());
-
- Assert.NotNull(capturedRequest);
- Assert.NotNull(capturedRequest!.Headers.Authorization);
- Assert.Equal("Bearer", capturedRequest.Headers.Authorization.Scheme);
- Assert.Equal("my-token", capturedRequest.Headers.Authorization.Parameter);
- }
-
- [Fact]
- public async Task 构造函数_Scheme为空_不设置Authorization()
- {
- var handler = new Mock();
- HttpRequestMessage? capturedRequest = null;
- handler.Protected()
- .Setup>("SendAsync",
- ItExpr.IsAny(),
- ItExpr.IsAny())
- .Callback((req, _) => capturedRequest = req)
- .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new StringContent(JsonConvert.SerializeObject(new HttpResponseDTO>()))
- });
-
- using var httpClient = new HttpClient(handler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- await client.QueryExtensionsAsync(new ExtensionQueryDTO());
-
- Assert.Null(capturedRequest!.Headers.Authorization);
- }
-
- // ===== QueryExtensionsAsync 测试 =====
-
- [Fact]
- public async Task QueryExtensionsAsync_成功响应_返回解析后的DTO()
- {
- var expectedDto = new HttpResponseDTO>
- {
- Code = "200",
- Message = "OK",
- Body = new PagedResultDTO
- {
- PageNumber = 1,
- TotalCount = 1,
- Items = new[] { new ExtensionDTO { Id = "ext-1", Name = "test" } }
- }
- };
- var response = new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new StringContent(JsonConvert.SerializeObject(expectedDto))
- };
- var mockHandler = CreateHandlerMock(response);
- using var httpClient = new HttpClient(mockHandler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
-
- var result = await client.QueryExtensionsAsync(new ExtensionQueryDTO { Id = "ext-1" });
-
- Assert.NotNull(result);
- Assert.Equal("OK", result.Message);
- Assert.NotNull(result.Body);
- Assert.Single(result.Body!.Items);
- Assert.Equal("ext-1", result.Body.Items.First().Id);
- }
-
- [Fact]
- public async Task QueryExtensionsAsync_非成功状态码_返回错误DTO()
- {
- var response = new HttpResponseMessage(HttpStatusCode.NotFound)
- {
- Content = new StringContent("Not Found")
- };
- var mockHandler = CreateHandlerMock(response);
- using var httpClient = new HttpClient(mockHandler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
-
- var result = await client.QueryExtensionsAsync(new ExtensionQueryDTO());
-
- Assert.NotNull(result);
- Assert.Contains("404", result.Message);
- }
-
- [Fact]
- public async Task QueryExtensionsAsync_网络异常_返回错误DTO()
- {
- var handler = new Mock();
- handler.Protected()
- .Setup>("SendAsync",
- ItExpr.IsAny(),
- ItExpr.IsAny())
- .ThrowsAsync(new HttpRequestException("Network error"));
- using var httpClient = new HttpClient(handler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
-
- var result = await client.QueryExtensionsAsync(new ExtensionQueryDTO());
-
- Assert.NotNull(result);
- Assert.Equal("QUERY_ERROR", result.Code);
- Assert.Contains("Network error", result.Message);
- }
-
- [Fact]
- public async Task QueryExtensionsAsync_响应为nullJSON_返回默认DTO()
- {
- var response = new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new StringContent("null")
- };
- var mockHandler = CreateHandlerMock(response);
- using var httpClient = new HttpClient(mockHandler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
-
- var result = await client.QueryExtensionsAsync(new ExtensionQueryDTO());
- Assert.NotNull(result);
- }
-
- // ===== DownloadExtensionAsync 测试 =====
-
- [Fact]
- public async Task DownloadExtensionAsync_成功下载_返回true()
- {
- var response = new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new ByteArrayContent(new byte[] { 1, 2, 3 })
- };
- response.Content.Headers.ContentLength = 3;
- var mockHandler = CreateHandlerMock(response);
- using var httpClient = new HttpClient(mockHandler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dl-{Guid.NewGuid()}.zip");
-
- var result = await client.DownloadExtensionAsync("ext-1", savePath);
-
- Assert.True(result);
- Assert.True(File.Exists(savePath));
- // 清理
- try { File.Delete(savePath); } catch { }
- }
-
- // ===== DownloadExtensionWithResultAsync 测试 =====
-
- [Fact]
- public async Task DownloadExtensionWithResultAsync_成功下载_返回Ok()
- {
- var response = new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new ByteArrayContent(new byte[] { 10, 20, 30 })
- };
- response.Content.Headers.ContentLength = 3;
- var mockHandler = CreateHandlerMock(response);
- using var httpClient = new HttpClient(mockHandler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dlr-{Guid.NewGuid()}.zip");
-
- var result = await client.DownloadExtensionWithResultAsync("ext-1", savePath);
-
- Assert.True(result.Success);
- Assert.Equal(DownloadErrorType.None, result.ErrorType);
- try { File.Delete(savePath); } catch { }
- }
-
- [Fact]
- public async Task DownloadExtensionWithResultAsync_HTTP416_返回Ok()
- {
- var response = new HttpResponseMessage(HttpStatusCode.RequestedRangeNotSatisfiable);
- var mockHandler = CreateHandlerMock(response);
- using var httpClient = new HttpClient(mockHandler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dlr416-{Guid.NewGuid()}.zip");
- // 创建文件以模拟已有下载
- File.WriteAllText(savePath, "existing");
-
- var result = await client.DownloadExtensionWithResultAsync("ext-1", savePath);
-
- Assert.True(result.Success);
- try { File.Delete(savePath); } catch { }
- }
-
- [Fact]
- public async Task DownloadExtensionWithResultAsync_服务端500_返回Fail()
- {
- var response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
- {
- Content = new StringContent("Server error"),
- ReasonPhrase = "Internal Server Error"
- };
- var mockHandler = CreateHandlerMock(response);
- using var httpClient = new HttpClient(mockHandler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dlr500-{Guid.NewGuid()}.zip");
-
- var result = await client.DownloadExtensionWithResultAsync("ext-1", savePath);
-
- Assert.False(result.Success);
- Assert.Equal(DownloadErrorType.ServerError, result.ErrorType);
- Assert.Equal(500, result.HttpStatusCode);
- }
-
- [Fact]
- public async Task DownloadExtensionWithResultAsync_客户端404_返回Fail()
- {
- var response = new HttpResponseMessage(HttpStatusCode.NotFound)
- {
- ReasonPhrase = "Not Found"
- };
- var mockHandler = CreateHandlerMock(response);
- using var httpClient = new HttpClient(mockHandler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dlr404-{Guid.NewGuid()}.zip");
-
- var result = await client.DownloadExtensionWithResultAsync("ext-1", savePath);
-
- Assert.False(result.Success);
- Assert.Equal(DownloadErrorType.ClientError, result.ErrorType);
- }
-
- [Fact]
- public async Task DownloadExtensionWithResultAsync_HttpRequestException_返回NetworkError()
- {
- var handler = new Mock();
- handler.Protected()
- .Setup>("SendAsync",
- ItExpr.IsAny(),
- ItExpr.IsAny())
- .ThrowsAsync(new HttpRequestException("Connection refused"));
- using var httpClient = new HttpClient(handler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dlr-net-{Guid.NewGuid()}.zip");
-
- var result = await client.DownloadExtensionWithResultAsync("ext-1", savePath);
-
- Assert.False(result.Success);
- Assert.Equal(DownloadErrorType.NetworkError, result.ErrorType);
- }
-
- [Fact]
- public async Task DownloadExtensionWithResultAsync_OperationCanceledException_返回Cancelled()
- {
- var handler = new Mock();
- handler.Protected()
- .Setup>("SendAsync",
- ItExpr.IsAny(),
- ItExpr.IsAny())
- .ThrowsAsync(new OperationCanceledException());
- using var httpClient = new HttpClient(handler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dlr-cancel-{Guid.NewGuid()}.zip");
-
- var result = await client.DownloadExtensionWithResultAsync("ext-1", savePath);
-
- Assert.False(result.Success);
- Assert.Equal(DownloadErrorType.Cancelled, result.ErrorType);
- }
-
- [Fact]
- public async Task DownloadExtensionWithResultAsync_IOException_返回IoError()
- {
- var handler = new Mock();
- handler.Protected()
- .Setup>("SendAsync",
- ItExpr.IsAny(),
- ItExpr.IsAny())
- .ThrowsAsync(new IOException("Disk full"));
- using var httpClient = new HttpClient(handler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dlr-io-{Guid.NewGuid()}.zip");
-
- var result = await client.DownloadExtensionWithResultAsync("ext-1", savePath);
-
- Assert.False(result.Success);
- Assert.Equal(DownloadErrorType.IoError, result.ErrorType);
- }
-
- [Fact]
- public async Task DownloadExtensionWithResultAsync_一般Exception_返回Unknown()
- {
- var handler = new Mock();
- handler.Protected()
- .Setup>("SendAsync",
- ItExpr.IsAny(),
- ItExpr.IsAny())
- .ThrowsAsync(new Exception("Unexpected error"));
- using var httpClient = new HttpClient(handler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dlr-unk-{Guid.NewGuid()}.zip");
-
- var result = await client.DownloadExtensionWithResultAsync("ext-1", savePath);
-
- Assert.False(result.Success);
- Assert.Equal(DownloadErrorType.Unknown, result.ErrorType);
- }
-
- [Fact]
- public async Task DownloadExtensionWithResultAsync_进度报告正确()
- {
- var bytes = new byte[8192 * 3]; // 3 buffers worth
- new Random(42).NextBytes(bytes);
- var response = new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new ByteArrayContent(bytes)
- };
- response.Content.Headers.ContentLength = bytes.Length;
- var mockHandler = CreateHandlerMock(response);
- using var httpClient = new HttpClient(mockHandler.Object);
- using var client = new ExtensionHttpClient("http://test", "", "", httpClient);
- var savePath = Path.Combine(Path.GetTempPath(), $"dlr-prog-{Guid.NewGuid()}.zip");
-
- var progressValues = new List();
- var progress = new Progress(p => progressValues.Add(p));
-
- var result = await client.DownloadExtensionWithResultAsync("ext-1", savePath, progress);
-
- Assert.True(result.Success);
- Assert.NotEmpty(progressValues);
- Assert.Contains(100, progressValues);
- try { File.Delete(savePath); } catch { }
- }
-
- // ===== Dispose 测试 =====
-
- [Fact]
- public void Dispose_ownsHttpClient为true_释放HttpClient()
- {
- var handler = new Mock();
- var httpClient = new HttpClient(handler.Object);
- var client = new ExtensionHttpClient("http://test", "", "", httpClient, ownsHttpClient: true);
- client.Dispose();
- // HttpClient 被释放后,再发送请求会抛异常
- Assert.Throws(() => httpClient.Timeout = TimeSpan.FromSeconds(1));
- }
-
- [Fact]
- public void Dispose_ownsHttpClient为false_不释放HttpClient()
- {
- var handler = new Mock();
- var httpClient = new HttpClient(handler.Object);
- var client = new ExtensionHttpClient("http://test", "", "", httpClient, ownsHttpClient: false);
- client.Dispose();
- // HttpClient 仍可用
- httpClient.Timeout = TimeSpan.FromSeconds(5);
- }
-
- [Fact]
- public void 便利构造函数_ownsHttpClient为true()
- {
- var client = new ExtensionHttpClient("http://test", "Bearer", "token");
- client.Dispose(); // 应释放内部创建的 HttpClient
- }
-}
diff --git a/src/c#/ExtensionTest/Core/GeneralExtensionHostTests.cs b/src/c#/ExtensionTest/Core/GeneralExtensionHostTests.cs
deleted file mode 100644
index 4f8203b6..00000000
--- a/src/c#/ExtensionTest/Core/GeneralExtensionHostTests.cs
+++ /dev/null
@@ -1,589 +0,0 @@
-///
-/// 测试覆盖点:
-/// - DI构造函数
-/// - options=null => ArgumentNullException
-/// - httpClient=null => ArgumentNullException
-/// - catalog=null => ArgumentNullException
-/// - compatibilityChecker=null => ArgumentNullException
-/// - downloadQueue=null => ArgumentNullException
-/// - platformMatcher=null => ArgumentNullException
-/// - dependencyResolver=null => 不抛异常_注释掉的检查
-/// - lifecycleHooks/metadataMapper 可选参数可为 null
-/// - 订阅 DownloadStatusChanged 事件
-/// - 创建目录
-/// - Legacy构造函数
-/// - options=null => ArgumentNullException
-/// - ExtensionCatalog 属性
-/// - QueryExtensionsAsync(query)
-/// - 正常查询返回结果
-/// - 查询抛异常_异常继续向上传播
-/// - DownloadExtensionAsync(extensionId, savePath)
-/// - 成功下载返回 true
-/// - 失败下载返回 false
-/// - UpdateExtensionAsync(extensionId)
-/// - 成功更新完整流程_查询 + 兼容性检查 + 平台检查 + 依赖解析 + 下载 + 安装 + 编目更新
-/// - 服务器返回 null items => 抛异常
-/// - 服务器无此扩展 => InvalidOperationException
-/// - 不兼容 => InvalidOperationException
-/// - 平台不支持 => InvalidOperationException
-/// - 有未安装依赖 => 递归安装
-/// - 下载失败 => 抛异常
-/// - 安装失败 => 抛异常
-/// - 事件触发: Queued -> Updating(progress) -> UpdateSuccessful(progress=100)
-/// - 异常时: Queued -> UpdateFailed
-/// - 返回 false 但事件通知失败
-/// - InstallExtensionAsync(extensionPath, rollbackOnFailure)
-/// - 文件不存在 => FileNotFoundException
-/// - 非 .zip 文件 => InvalidOperationException
-/// - lifecycleHooks.OnBeforeInstallAsync 返回 false => 取消安装
-/// - 正常安装流程
-/// - 已存在同名扩展且有rollback => 备份->删除旧->解压新->删除备份
-/// - rollbackOnFailure=false => 跳过备份
-/// - 安装失败且rollback => 恢复备份
-/// - UpdateExtensionsAsync(extensionIds, ct)
-/// - CancellationToken 取消
-/// - 单个失败不影响其他
-/// - IsExtensionCompatible(extension)
-/// - SetAutoUpdate / IsAutoUpdateEnabled
-/// - 单个扩展设置
-/// - 全局设置兜底
-/// - SetGlobalAutoUpdate
-/// - ExtensionUpdateStatusChanged 事件
-/// - SafeExtractZipAsync_内部方法难以直接测试
-/// - ComputeFileSha256Async_内部方法难以直接测试
-/// - SafeDeleteFile_内部方法难以直接测试
-/// - ToMetadata 静态方法_DTO -> Metadata 映射
-///
-using Moq;
-using GeneralUpdate.Extension.Core;
-using GeneralUpdate.Extension.Catalog;
-using GeneralUpdate.Extension.Communication;
-using GeneralUpdate.Extension.Compatibility;
-using GeneralUpdate.Extension.Dependencies;
-using GeneralUpdate.Extension.Download;
-using GeneralUpdate.Extension.Common.DTOs;
-using GeneralUpdate.Extension.Common.Enums;
-using GeneralUpdate.Extension.Common.Models;
-
-namespace GeneralUpdate.Extension.Core.Tests;
-
-public class GeneralExtensionHostTests
-{
- private static ExtensionHostOptions CreateOptions(string? extDir = null)
- {
- return new ExtensionHostOptions
- {
- ServerUrl = "http://test-server",
- Scheme = "Bearer",
- Token = "test-token",
- HostVersion = "2.0.0",
- ExtensionsDirectory = extDir ?? Path.Combine(Path.GetTempPath(), $"host-test-{Guid.NewGuid()}")
- };
- }
-
- private static Mock CreateHttpClientMock()
- {
- return new Mock();
- }
-
- private static Mock CreateCatalogMock()
- {
- var mock = new Mock();
- mock.Setup(m => m.GetInstalledExtensions()).Returns(new List());
- mock.Setup(m => m.GetInstalledExtensionById(It.IsAny())).Returns((ExtensionMetadata?)null);
- return mock;
- }
-
- // ===== DI构造函数测试 =====
-
- [Fact]
- public void DI构造函数_options为null_抛出ArgumentNullException()
- {
- Assert.Throws(() =>
- new GeneralExtensionHost(null!, CreateHttpClientMock().Object, CreateCatalogMock().Object,
- Mock.Of(), Mock.Of(),
- Mock.Of(), Mock.Of()));
- }
-
- [Fact]
- public void DI构造函数_httpClient为null_抛出ArgumentNullException()
- {
- var opts = CreateOptions();
- Assert.Throws(() =>
- new GeneralExtensionHost(opts, null!, CreateCatalogMock().Object,
- Mock.Of(), Mock.Of(),
- Mock.Of(), Mock.Of()));
- }
-
- [Fact]
- public void DI构造函数_catalog为null_抛出ArgumentNullException()
- {
- var opts = CreateOptions();
- Assert.Throws(() =>
- new GeneralExtensionHost(opts, CreateHttpClientMock().Object, null!,
- Mock.Of(), Mock.Of(),
- Mock.Of(), Mock.Of()));
- }
-
- [Fact]
- public void DI构造函数_compatibilityChecker为null_抛出ArgumentNullException()
- {
- var opts = CreateOptions();
- Assert.Throws(() =>
- new GeneralExtensionHost(opts, CreateHttpClientMock().Object, CreateCatalogMock().Object,
- null!, Mock.Of(),
- Mock.Of(), Mock.Of()));
- }
-
- [Fact]
- public void DI构造函数_downloadQueue为null_抛出ArgumentNullException()
- {
- var opts = CreateOptions();
- Assert.Throws(() =>
- new GeneralExtensionHost(opts, CreateHttpClientMock().Object, CreateCatalogMock().Object,
- Mock.Of(), null!,
- Mock.Of(), Mock.Of()));
- }
-
- [Fact]
- public void DI构造函数_platformMatcher为null_抛出ArgumentNullException()
- {
- var opts = CreateOptions();
- Assert.Throws(() =>
- new GeneralExtensionHost(opts, CreateHttpClientMock().Object, CreateCatalogMock().Object,
- Mock.Of(), Mock.Of(),
- Mock.Of(), null!));
- }
-
- [Fact]
- public void DI构造函数_dependencyResolver为null_不抛异常()
- {
- var opts = CreateOptions();
- // dependencyResolver 的 null 检查被注释掉了,所以不应抛异常
- var host = new GeneralExtensionHost(opts, CreateHttpClientMock().Object,
- CreateCatalogMock().Object, Mock.Of(),
- Mock.Of(), null!, Mock.Of());
- Assert.NotNull(host);
- }
-
- [Fact]
- public void DI构造函数_可选参数为null_可正常构建()
- {
- var opts = CreateOptions();
- var host = new GeneralExtensionHost(opts, CreateHttpClientMock().Object,
- CreateCatalogMock().Object, Mock.Of(),
- Mock.Of(), Mock.Of(),
- Mock.Of(), lifecycleHooks: null, metadataMapper: null);
- Assert.NotNull(host);
- }
-
- [Fact]
- public void DI构造函数_正常构建_ExtensionCatalog属性可用()
- {
- var opts = CreateOptions();
- var catalogMock = CreateCatalogMock();
- var host = new GeneralExtensionHost(opts, CreateHttpClientMock().Object,
- catalogMock.Object, Mock.Of(),
- Mock.Of(), Mock.Of(),
- Mock.Of());
- Assert.NotNull(host.ExtensionCatalog);
- Assert.Same(catalogMock.Object, host.ExtensionCatalog);
- }
-
- // ===== Legacy构造函数测试 =====
-
- [Fact]
- public void Legacy构造函数_options为null_抛出ArgumentNullException()
- {
- Assert.Throws(() => new GeneralExtensionHost(null!));
- }
-
- [Fact]
- public void Legacy构造函数_正常构建()
- {
- var opts = CreateOptions();
- var host = new GeneralExtensionHost(opts);
- Assert.NotNull(host);
- Assert.NotNull(host.ExtensionCatalog);
- }
-
- // ===== QueryExtensionsAsync 测试 =====
-
- [Fact]
- public async Task QueryExtensionsAsync_正常查询_返回结果()
- {
- var expectedResponse = new HttpResponseDTO>
- {
- Code = "200",
- Body = new PagedResultDTO
- {
- Items = new[] { new ExtensionDTO { Id = "ext-1" } }
- }
- };
- var httpMock = CreateHttpClientMock();
- httpMock.Setup(m => m.QueryExtensionsAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(expectedResponse);
-
- var host = new GeneralExtensionHost(CreateOptions(), httpMock.Object,
- CreateCatalogMock().Object, Mock.Of(),
- Mock.Of(), Mock.Of(),
- Mock.Of());
-
- var result = await host.QueryExtensionsAsync(new ExtensionQueryDTO { Id = "ext-1" });
- Assert.NotNull(result);
- Assert.Equal("200", result.Code);
- }
-
- [Fact]
- public async Task QueryExtensionsAsync_查询抛异常_异常向上传播()
- {
- var httpMock = CreateHttpClientMock();
- httpMock.Setup(m => m.QueryExtensionsAsync(It.IsAny(), It.IsAny