diff --git a/src/ZLogger/Internal/Shims/Shims.NetStandard2.cs b/src/ZLogger/Internal/Shims/Shims.NetStandard2.cs index 67c75c8b..5f796339 100644 --- a/src/ZLogger/Internal/Shims/Shims.NetStandard2.cs +++ b/src/ZLogger/Internal/Shims/Shims.NetStandard2.cs @@ -7,6 +7,7 @@ internal static partial class Shims { public static int GetBytes(this Encoding encoding, ReadOnlySpan chars, ReadOnlySpan bytes) { + if (chars.Length == 0 || bytes.Length == 0) return 0; unsafe { fixed (char* charsPtr = &chars[0]) @@ -19,6 +20,7 @@ public static int GetBytes(this Encoding encoding, ReadOnlySpan chars, Rea public static string GetString(this Encoding encoding, ReadOnlySpan bytes) { + if (bytes.Length == 0) return string.Empty; unsafe { fixed (byte* bytesPtr = &bytes[0]) diff --git a/tests/ZLogger.Tests/EnumDictionaryTest.cs b/tests/ZLogger.Tests/EnumDictionaryTest.cs index 3dc297a3..d714e54f 100644 --- a/tests/ZLogger.Tests/EnumDictionaryTest.cs +++ b/tests/ZLogger.Tests/EnumDictionaryTest.cs @@ -42,8 +42,10 @@ public void HttpStatusCodeTest() { EnumDictionary.GetStringName(HttpStatusCode.InternalServerError).Should().Be("InternalServerError"); EnumDictionary.GetStringName(HttpStatusCode.RequestEntityTooLarge).Should().Be("RequestEntityTooLarge"); +#if NET EnumDictionary.GetUtf8Name(HttpStatusCode.NetworkAuthenticationRequired).ToArray().Should().Equal(Encoding.UTF8.GetBytes("NetworkAuthenticationRequired")); EnumDictionary.GetJsonEncodedName(HttpStatusCode.UnprocessableEntity).Should().Be(JsonEncodedText.Encode("UnprocessableEntity")); +#endif } } diff --git a/tests/ZLogger.Tests/RollingFileProviderTest.cs b/tests/ZLogger.Tests/RollingFileProviderTest.cs index 77e5aeea..cf76cf17 100644 --- a/tests/ZLogger.Tests/RollingFileProviderTest.cs +++ b/tests/ZLogger.Tests/RollingFileProviderTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Text; using System.Threading.Tasks; @@ -11,7 +11,7 @@ namespace ZLogger.Tests; public class RollingFileProviderTest { - readonly string directory = Path.Join(Path.GetTempPath(), "zlogger-test"); + readonly string directory = Path.Combine(Path.GetTempPath(), "zlogger-test"); public RollingFileProviderTest() { @@ -30,7 +30,7 @@ public async Task RollByInterval() { var timeProvider = new FakeTimeProvider(new DateTimeOffset(2000, 1, 2, 3, 4, 5, TimeSpan.Zero)); - var path1= Path.Join(directory, $"ZLoggerRollingTest_{timeProvider.GetUtcNow():yyyy-MM-dd}-0.log"); + var path1= Path.Combine(directory, $"ZLoggerRollingTest_{timeProvider.GetUtcNow():yyyy-MM-dd}-0.log"); if (File.Exists(path1)) File.Delete(path1); using var loggerFactory = LoggerFactory.Create(x => @@ -38,7 +38,7 @@ public async Task RollByInterval() x.SetMinimumLevel(LogLevel.Debug); x.AddZLoggerRollingFile(options => { - options.FilePathSelector = (dt, seq) => Path.Join(directory, $"ZLoggerRollingTest_{dt:yyyy-MM-dd}-{seq}.log"); + options.FilePathSelector = (dt, seq) => Path.Combine(directory, $"ZLoggerRollingTest_{dt:yyyy-MM-dd}-{seq}.log"); options.RollingInterval = RollingInterval.Day; options.RollingSizeKB = 5; options.TimeProvider = timeProvider; @@ -57,7 +57,7 @@ public async Task RollByInterval() // Next day timeProvider.Advance(TimeSpan.FromDays(1)); - var path2 = Path.Join(directory, $"ZLoggerRollingTest_{timeProvider.GetUtcNow():yyyy-MM-dd}-0.log"); + var path2 = Path.Combine(directory, $"ZLoggerRollingTest_{timeProvider.GetUtcNow():yyyy-MM-dd}-0.log"); logger.LogDebug("a"); logger.LogDebug("v"); logger.LogDebug("c"); @@ -89,15 +89,15 @@ public async Task RollBySize() { var timeProvider = new FakeTimeProvider(new DateTimeOffset(2000, 1, 2, 3, 4, 5, TimeSpan.Zero)); - var path1 = Path.Join(directory, $"ZLoggerRollingTest_{timeProvider.GetUtcNow():yyyy-MM-dd}-0.log"); - var path2 = Path.Join(directory, $"ZLoggerRollingTest_{timeProvider.GetUtcNow():yyyy-MM-dd}-1.log"); + var path1 = Path.Combine(directory, $"ZLoggerRollingTest_{timeProvider.GetUtcNow():yyyy-MM-dd}-0.log"); + var path2 = Path.Combine(directory, $"ZLoggerRollingTest_{timeProvider.GetUtcNow():yyyy-MM-dd}-1.log"); using var loggerFactory = LoggerFactory.Create(x => { x.SetMinimumLevel(LogLevel.Debug); x.AddZLoggerRollingFile(options => { - options.FilePathSelector = (dt, seq) => Path.Join(directory, $"ZLoggerRollingTest_{dt:yyyy-MM-dd}-{seq}.log"); + options.FilePathSelector = (dt, seq) => Path.Combine(directory, $"ZLoggerRollingTest_{dt:yyyy-MM-dd}-{seq}.log"); options.RollingInterval = RollingInterval.Day; options.RollingSizeKB = 5; options.TimeProvider = timeProvider; @@ -119,4 +119,4 @@ static StreamReader OpenFile(string path) { return new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8); } -} \ No newline at end of file +} diff --git a/tests/ZLogger.Tests/ShimTests.cs b/tests/ZLogger.Tests/ShimTests.cs new file mode 100644 index 00000000..c6b9cb40 --- /dev/null +++ b/tests/ZLogger.Tests/ShimTests.cs @@ -0,0 +1,134 @@ +#if NETFRAMEWORK +using System; +using System.Text; + +namespace ZLogger.Tests +{ + public class ShimNetStandardTests + { + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("hello")] + [InlineData("Живко")] // Cyrillic + [InlineData("mañana café")] // Latin-1 accents + [InlineData("汉字テスト")] // CJK/Kana mix + public void GetBytes_Matches_Framework_For_Exact_Buffer_Utf8(string text) + { + var enc = Encoding.UTF8; + + // What the framework would produce (reference) + var expected = enc.GetBytes(text); + + // Exact-size buffer + var buffer = new byte[expected.Length]; + + // IMPORTANT: call the shim explicitly (avoid BCL span overload) + int written = Shims.GetBytes(enc, text.AsSpan(), buffer.AsSpan()); + + written.Should().Be(expected.Length); + buffer.Should().Equal(expected); + } + + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("hello")] + [InlineData("Žužu")] // Latin-2 + [InlineData("Здрасти")] // Bulgarian + public void GetBytes_Matches_Framework_For_Exact_Buffer_Unicode(string text) + { + var enc = Encoding.Unicode; // UTF-16 LE + var expected = enc.GetBytes(text); + var buffer = new byte[expected.Length]; + + int written = Shims.GetBytes(enc, text.AsSpan(), buffer.AsSpan()); + + written.Should().Be(expected.Length); + buffer.Should().Equal(expected); + } + + [Fact] + public void GetBytes_Returns_Zero_When_Chars_Empty() + { + var enc = Encoding.UTF8; + var buf = new byte[16]; + + int written = Shims.GetBytes(enc, ReadOnlySpan.Empty, buf); + + written.Should().Be(0); + buf.Should().OnlyContain(b => b == 0); // untouched + } + + [Fact] + public void GetBytes_Returns_Zero_When_Bytes_Empty() + { + var enc = Encoding.UTF8; + var text = "whatever"; + + int written = Shims.GetBytes(enc, text.AsSpan(), ReadOnlySpan.Empty); + + written.Should().Be(0); + } + + [Theory] + [InlineData("hello")] + [InlineData("Здрасти")] + [InlineData("mañana café")] + [InlineData("漢字テスト")] + public void GetString_Matches_Framework(string text) + { + var enc = Encoding.UTF8; + var bytes = enc.GetBytes(text); + + // Slice in the middle too, to ensure length parameter is respected. + var span = new ReadOnlySpan(bytes); + + string shim = Shims.GetString(enc, span); + string bcl = enc.GetString(bytes); + + shim.Should().Be(bcl); + } + + [Fact] + public void GetString_Returns_Empty_On_Empty_Bytes() + { + var enc = Encoding.UTF8; + Shims.GetString(enc, ReadOnlySpan.Empty).Should().Be(string.Empty); + } + + [Theory] + [InlineData("x", 'x', true)] + [InlineData("xyz", 'x', true)] + [InlineData("xyz", 'y', false)] + [InlineData("", 'x', false)] + [InlineData("Жоро", 'Ж', true)] + [InlineData("жоро", 'Ж', false)] + public void StartsWith_Behavior(string input, char value, bool expected) + { + // Explicit call to the shim — do NOT rely on string.StartsWith(char) on newer TFMs + bool actual = Shims.StartsWith(input, value); + actual.Should().Be(expected); + } + + [Fact] + public void GetBytes_With_Partial_Slice_Writes_For_Slice_Length() + { + var enc = Encoding.UTF8; + const string text = "ABCDE"; + + var full = enc.GetBytes(text); + // Give the shim only the first 3 chars and a correctly sized byte buffer for those 3 + var chars = text.AsSpan(0, 3); + + var expected = enc.GetBytes(chars.ToArray()); // reference for 3 chars + var buffer = new byte[expected.Length]; + + int written = Shims.GetBytes(enc, chars, buffer); + + written.Should().Be(expected.Length); + buffer.Should().Equal(expected); + } + } +} +#endif diff --git a/tests/ZLogger.Tests/ZLogger.Tests.csproj b/tests/ZLogger.Tests/ZLogger.Tests.csproj index b1aef7b4..eb244923 100644 --- a/tests/ZLogger.Tests/ZLogger.Tests.csproj +++ b/tests/ZLogger.Tests/ZLogger.Tests.csproj @@ -1,8 +1,9 @@  - net8.0 + net8.0;net48 true + latest