using System.Text; using Serilog; using YZWater.Core.Utils; namespace YZWater.Core.Tests.Services; /// /// 文本文件编码的回归测试。 /// 背景:日志和 CSV 导出原先写成无 BOM 的 UTF-8,中文 Windows 上记事本、Excel、 /// PowerShell 5.1 的 Get-Content 都会按 GBK 解释,取证时看到的是乱码。 /// public class FileEncodingTests : IDisposable { private readonly string _dir = Path.Combine( Path.GetTempPath(), "yzwater-encoding-" + Guid.NewGuid().ToString("N")); public FileEncodingTests() => Directory.CreateDirectory(_dir); public void Dispose() { try { Directory.Delete(_dir, true); } catch (IOException) { } } [Fact] public void Utf8WithBom_Preamble_IsTheThreeUtf8SignatureBytes() { Assert.Equal(new byte[] { 0xEF, 0xBB, 0xBF }, FileTextEncoding.Utf8WithBom.Preamble.ToArray()); } [Fact] public void SerilogFileSink_NewFile_StartsWithBomAndKeepsChineseReadable() { var previous = Serilog.Log.Logger; var path = Path.Combine(_dir, "log-encoding.txt"); try { Log.Logger = new LoggerConfiguration() .WriteTo.File(path, encoding: FileTextEncoding.Utf8WithBom) .CreateLogger(); Log.Information("集水池 {Level:F2} 米,泵已停止", 3.5f); Log.CloseAndFlush(); var bytes = File.ReadAllBytes(path); Assert.Equal(new byte[] { 0xEF, 0xBB, 0xBF }, bytes.Take(3).ToArray()); // 去 BOM 后必须是合法 UTF-8 且中文原样可读(乱码时这两条都会失败) var text = File.ReadAllText(path); Assert.Contains("集水池 3.50 米,泵已停止", text); Assert.DoesNotContain("", text); } finally { Log.Logger = previous; } } [Fact] public void AppendAllText_WritesBomOnlyOnce() { var path = Path.Combine(_dir, "crash-sim.log"); File.AppendAllText(path, "第一段:集水池溢出\n", FileTextEncoding.Utf8WithBom); File.AppendAllText(path, "第二段:泵故障\n", FileTextEncoding.Utf8WithBom); var bytes = File.ReadAllBytes(path); Assert.Equal(1, CountOccurrences(bytes, new byte[] { 0xEF, 0xBB, 0xBF })); Assert.Contains("第二段:泵故障", File.ReadAllText(path)); } private static int CountOccurrences(byte[] haystack, byte[] needle) { var count = 0; for (var i = 0; i <= haystack.Length - needle.Length; i++) { var match = true; for (var j = 0; j < needle.Length; j++) if (haystack[i + j] != needle[j]) { match = false; break; } if (match) count++; } return count; } }