FileEncodingTests.cs 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. using System.Text;
  2. using Serilog;
  3. using YZWater.Core.Utils;
  4. namespace YZWater.Core.Tests.Services;
  5. /// <summary>
  6. /// 文本文件编码的回归测试。
  7. /// 背景:日志和 CSV 导出原先写成无 BOM 的 UTF-8,中文 Windows 上记事本、Excel、
  8. /// PowerShell 5.1 的 Get-Content 都会按 GBK 解释,取证时看到的是乱码。
  9. /// </summary>
  10. public class FileEncodingTests : IDisposable
  11. {
  12. private readonly string _dir = Path.Combine(
  13. Path.GetTempPath(), "yzwater-encoding-" + Guid.NewGuid().ToString("N"));
  14. public FileEncodingTests() => Directory.CreateDirectory(_dir);
  15. public void Dispose()
  16. {
  17. try { Directory.Delete(_dir, true); } catch (IOException) { }
  18. }
  19. [Fact]
  20. public void Utf8WithBom_Preamble_IsTheThreeUtf8SignatureBytes()
  21. {
  22. Assert.Equal(new byte[] { 0xEF, 0xBB, 0xBF }, FileTextEncoding.Utf8WithBom.Preamble.ToArray());
  23. }
  24. [Fact]
  25. public void SerilogFileSink_NewFile_StartsWithBomAndKeepsChineseReadable()
  26. {
  27. var previous = Serilog.Log.Logger;
  28. var path = Path.Combine(_dir, "log-encoding.txt");
  29. try
  30. {
  31. Log.Logger = new LoggerConfiguration()
  32. .WriteTo.File(path, encoding: FileTextEncoding.Utf8WithBom)
  33. .CreateLogger();
  34. Log.Information("集水池 {Level:F2} 米,泵已停止", 3.5f);
  35. Log.CloseAndFlush();
  36. var bytes = File.ReadAllBytes(path);
  37. Assert.Equal(new byte[] { 0xEF, 0xBB, 0xBF }, bytes.Take(3).ToArray());
  38. // 去 BOM 后必须是合法 UTF-8 且中文原样可读(乱码时这两条都会失败)
  39. var text = File.ReadAllText(path);
  40. Assert.Contains("集水池 3.50 米,泵已停止", text);
  41. Assert.DoesNotContain("", text);
  42. }
  43. finally
  44. {
  45. Log.Logger = previous;
  46. }
  47. }
  48. [Fact]
  49. public void AppendAllText_WritesBomOnlyOnce()
  50. {
  51. var path = Path.Combine(_dir, "crash-sim.log");
  52. File.AppendAllText(path, "第一段:集水池溢出\n", FileTextEncoding.Utf8WithBom);
  53. File.AppendAllText(path, "第二段:泵故障\n", FileTextEncoding.Utf8WithBom);
  54. var bytes = File.ReadAllBytes(path);
  55. Assert.Equal(1, CountOccurrences(bytes, new byte[] { 0xEF, 0xBB, 0xBF }));
  56. Assert.Contains("第二段:泵故障", File.ReadAllText(path));
  57. }
  58. private static int CountOccurrences(byte[] haystack, byte[] needle)
  59. {
  60. var count = 0;
  61. for (var i = 0; i <= haystack.Length - needle.Length; i++)
  62. {
  63. var match = true;
  64. for (var j = 0; j < needle.Length; j++)
  65. if (haystack[i + j] != needle[j]) { match = false; break; }
  66. if (match) count++;
  67. }
  68. return count;
  69. }
  70. }