From 5d3733bc056194c656f3ad3f06ab4d0eaee59b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Fri, 25 Jul 2025 17:36:29 -0500 Subject: [PATCH 01/10] Add Compliance tests for GB18030-2022 --- src/libraries/Common/Common.Tests.slnx | 1 + .../ComplianceTests/Compliance.Tests.csproj | 28 ++ ...Test_Data_for_Mid_to_High_Volume_cases.txt | 45 ++ .../ComplianceTests/GB18030/TestHelper.cs | 96 +++++ .../GB18030/Tests/ConsoleTests.cs | 84 ++++ .../GB18030/Tests/DirectoryInfoTests.cs | 50 +++ .../GB18030/Tests/DirectoryTestBase.cs | 76 ++++ .../GB18030/Tests/DirectoryTests.cs | 34 ++ .../GB18030/Tests/EncodingTests.cs | 15 + .../GB18030/Tests/FileInfoTests.cs | 11 + .../GB18030/Tests/FileTestBase.cs | 87 ++++ .../GB18030/Tests/FileTests.cs | 65 +++ .../GB18030/Tests/StringTests.cs | 405 ++++++++++++++++++ 13 files changed, 997 insertions(+) create mode 100644 src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Level3+Amendment_Test_Data_for_Mid_to_High_Volume_cases.txt create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileInfoTests.cs create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs diff --git a/src/libraries/Common/Common.Tests.slnx b/src/libraries/Common/Common.Tests.slnx index c6b9f2a7939d0f..154e0a5b1a48f0 100644 --- a/src/libraries/Common/Common.Tests.slnx +++ b/src/libraries/Common/Common.Tests.slnx @@ -5,6 +5,7 @@ + diff --git a/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj b/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj new file mode 100644 index 00000000000000..840c426ec005d8 --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj @@ -0,0 +1,28 @@ + + + $(NetCoreAppCurrent);$(NetFrameworkCurrent) + enable + true + true + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Level3+Amendment_Test_Data_for_Mid_to_High_Volume_cases.txt b/src/libraries/Common/tests/ComplianceTests/GB18030/Level3+Amendment_Test_Data_for_Mid_to_High_Volume_cases.txt new file mode 100644 index 00000000000000..7534f5969d0079 --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Level3+Amendment_Test_Data_for_Mid_to_High_Volume_cases.txt @@ -0,0 +1,45 @@ +Short sample strings for inputs with length limitation: +‚50°¦˜8á7G9ï0A˜9°4D•4Î6¢¨˜8•5C@™2è3˜5ó79‹2ªÚš7ø6š4„2‚5‡9©J™9ø69˜9ž7ÇW‚5–7™48 +××™8ð0Q‚5’2˜9Ü6d9ï6™1Ã4˜6Ë4CæF˜9´0——˜5ó8£¸™9ø9gš7ø99 5˜9ž8Cš4„9©O‚5ˆ0•2Œ2¡x‚5–8 +ûœ™3Ô8c™9ï0dˆÒ™2è3—9«0¦ÁaÜܘ9´1‚55‚3þ9˜9ž99•4ªÙ˜5ó9š4…9Gš7ù9©S‚5ˆ1A‚5–9™9ù9˜7Ø8 +‚39d˜9³6‚5‘9—8þ2ßo™9’0C˜9µ8˜9Ø7¨¯˜6¬5Ñј5ô0˜9Ÿ0ª¯©P‚5ˆ295––‚5—0sš8’0š4—0š09 +˜9‘1ýM‚5‡45™9÷0žž™2£9r˜9²6òò‚52˜5ó2øÈ˜5ô1©I˜9Ÿ19›4£Úš8¨9˜8¼4š0–9‚5ˆ3š4¯0a +A‚59™9Á0©–r˜8ö1G•8À1˜6¤1ëO˜9ª5§™3þ3£Ï‚5ˆ4˜9ž79–1˜5ô2‚0ƒ2š4¶9À¡¯þš8³0š0 0‚5—2 +D˜6À4þŸ•8Û2‚5’1Áׂ0§3N˜9±4D™8¿3™3ð8©K˜9ž8š0£9¢ç9•3þþ‚5ˆ5™3þ2‚5—3˜5ô32š4À0š8À7 +ÅùÑ ‚1ƒ4A™3ú6‚5’0˜9£6D’@™1÷76˜5ô4–1Ž4¨¯©R9’2§ ˜9ž9š4Ï9š8Å9š0ª4‚5—4™7‘0G‚5ˆ6˜7–2 +ÌS‚5‘8εPšþ‚1¾5O—0»8¦¸˜8”4v˜9­0™3¶0š4Ö6q˜9Ÿ09ž7¡@˜5ô5™1”8š8Ì5š0¯0™4Ñ3©–‚5—5‚5ˆ7 +‚56Ø·ª‚1Á3˜9’50 þ™0™2™3¬9˜9´9D˜0À8©M˜9Ÿ1ø¡£¶›1£7˜5ó8š7ø09˜1š0¸7‚5˜2‚5ˆ8™4‚0 + +Long sample strings for inputs with extended length limitation£º +Group0: +‚5‘9‚5’2°¡÷þ™1Ã4˜9‘1ª@þO9î9‚5‡88e•2‚6˜5ó6@ þ˜9¶2™3þ3©•©–™48™9÷0˜9Ÿ8˜9µ9rP˜5ó7˜5ô5™9ø6š0¸7ªÆªÇªÈU1øÇøÈøÙU2¡x¡y¡zU3š7ø6›1£7¢Ù¨é˜9ž7˜9Ÿ19‹29 5‚5–7‚5˜2‚5‡9‚5ˆ8š4„2š7ø0˜5÷8˜9ž66n + +Group1: +°¡°¦™1Ã4•4Î60u9î99ï6A˜9¡3˜9¡9ªHýˆDEG•2‚6•2ƒ1¢§¨¯˜7Ø8˜9‚7i˜5ó7˜5ó8‚53‚57™48™9ö3™9Ì28‚5–7‚5–8˜9¶2˜9¶5˜9¶7©J©O˜9ž7˜9ž8™9ø6™9ø7š0‰7š0‰5‚5‡9‚5ˆ0š4„2š4Œ4š4˜5š4˜6š7ø6š7ø7š7ý8š7ý99‹29–8ª¡ª¢U1ø¡ø¢U2¡@¡A¡BU3 + +Group2: +°©°¯÷ý˜9‘1˜8â5JŠ‹ˆW9ï7‚1 4‚2§1ª@«ž˜8á7˜88˜8â58A™3þ0™3í7™3â0˜8º9˜9˜6˜8ö1FOCš82š83š8Š7š8Ÿ7•2ƒ4•2ƒ5–7×0¢Ã£ë˜5ó9˜5ô0˜9ž9˜9Ÿ0š05š06š0™1š0™2š0™5©Q‚5–9‚5—09Œ09’9š4™6š4¡9š4¢7š4«2š4«3š4«4ªÆªÇªÈU1øÆøÇøÈU2¡x¡y¡zU3‚5ˆ1‚5ˆ2˜9³6˜9²3˜9ª3™9Ì6™9ž6™8Ä0 + +Group3: +˜8á7™3¦0½¢÷þDŒÅ‡Ö‡åˆŠ‡Û¬@¬Aþ“¬BêŠcz‚1—5‚1—9‚2§0˜9²6˜9­1˜9µ8ao‚5‘9‚59¨»£Ñ–7×4–7×8š0›9š0£2š0£3š0¨5™3ï0™3Õ8©P©R©•©–˜9Ÿ1˜9ž7˜5ô1˜5ô2™8ñ0™7Ý0š4«6š4«7š4«8š4«9«¡«³U1ùÐùÑùÒU2¢`¢a¢bU3š86š87š8•3š8—3š8¹4E9z˜8¼1˜8¼4˜8Ì2‚5—1‚5—29–09™8‚5ˆ3‚5ˆ4C + +Group4: +‚58‚50½ª½­½®½¯™3Ô8˜9ƒ6¬H¬J¬L¬O50R˜8õ6˜8ã2˜8ã5˜0ñ0˜5Ÿ9˜5ó2˜9´0˜9³6˜9²38U5‚1ö3‚3 0‚4ƒ0™2¿8™3Á0™3“9˜5ô3˜5ô4©S©ˆ˜9Ÿ0˜9Ÿ18U5š0©0š0©1š0©4š0©5™7Ü9™7¶3™7ñ3«ð«ñ«òU1úÀúÁúÂU2£@£A£BU3‚5—3‚5—49š09›9‚5ˆ5‚5ˆ6¢ø£´¦Îš4Á3š4Á9š4à3š4ð6š8Á5š8·0š8Ë8š8Ë1ƒò†¿‡ß + +Group5: +À€ÃQÀFô™˜9³6—8ê6‚55‚56‚4¡5‚4¡6‚4¡78bبأؤإ˜6Ç6˜6‰5b˜9³6˜9ª3˜9µ8BDFG™6Î0™6Ÿ0š8Ñ9š8Ö0š9¸3š8Ü4š0ª5š0ª6š0ª7š0ª8‚5—5‚5—6˜5ó3˜2Ð5¡Å¡È˜5ô5˜5ó7‚5ˆ7‚5ˆ89œ090˜9ž7˜9ž9¬¥¬¦¬§¬¸U1ú×úØúÙU2£”£•£–U3™3’5™3¥6©M©•©–š4ñ7š4ñ858b + +Group6: +‚5‡19õ9‚0é0‚5‘5‚51ثجå¢å£˜5ž6˜5ž9£Ç¦¸ÁjÀÁØX˜8±0˜8ö1˜9„0˜9Ž70wL™5ù2™6«8˜9­1˜9ª3˜9°4š5Ó4š5Ó5š5Ý6š5í8™2â3™2á9˜9Ÿ0˜9ž8š8à5š8à6š8à7š8à8š8à9š0«1š0«2š0©0š0 3A‚5—7‚5—8˜87˜69¬Ð¬á¬ò­£U1û°ûÁûÒU2¥P¥a¥rU3‚5‡9‚5ˆ2©T¨“˜5ó8˜5ô0g99ž19ž9HJLMŠ} + +Group7: +‚58‚52å¤å¥å¦å¨å©å«˜8é6˜8±0pW5‚2¦2‚1ˆ0‚0 3‚4¡1šð‹šôšõ‹¼˜6Ï4˜8ü6˜8å5˜8ƒ4@pU‚2§2–7×9–7×1–7×3¨§¦Ã£ô©J©N˜9ª3˜9°4˜9µ8™4°0™4¯9™4‚1ñRø™ýRéMé]˜5ô1˜5ô2š0¯3š0³3š0³4š0³8e˜9Ÿ0˜9ž9š8å3š8å4š8ø4š8ü1‚5–9‚5—7‚5ˆ0‚5ˆ59Ÿ09Ÿ6š6†2š6†3š6†4š60š6˜7š6˜8™2²0™2¡0™2—5®À®ÔU1üÔüÕüÖU2¦q¦r¦sU3 + +Group8: +¸@®f¯ŸÑYãÆàïâ¬à˜4¶1™3¬5‚5‘7‚5‘6˜8º9˜8Ô3yG‚0±3‚0Õ2‚0”8‚0‰7A•2–7•2–8¢ò£¹£Á£Ú˜9²3˜9²6˜9°4˜9µ8y0Rš0´2š0´3š0µ1š0µ2šøšúšûšý ð™2„0™2„1™2ˆ3™2‹1©J©S˜5ó8˜5ô5˜9Ÿ0˜9ž9‚5—9‚5˜0¯Á¯Â¯Ã¯ÄU1ý©ýªý«ý¬ý­U2¦‡¦ˆ¦‰¦Š¦‹U3‚5ˆ7‚5ˆ8™4‚7™4‚8™4‚9y0Rš6²2š6º2š6¾1š6¾2š6Ç6š8ü8š8þ9š9”3š9™2š9£39Ÿ59ž3 + +Group9: + ñ ò ÷ ø‚0Å0‚0Å4‚0Å5QaëOþLþMA3c‚5’2‚5‘8÷ò÷ó÷ô÷ö÷÷•3›5•3Ž6•3…8—1ø7˜8ƒ4˜9´0˜9ª3˜9°4˜9µ8V9™4ˆ3™4ˆ6™4ˆ7©R©ˆš0¯8š0µ6š0µ7š0µ8˜9Ÿ0˜9Ÿ1š6ñ3š6õ7š6ù5š74š7‡6£Ø¦Â¨§˜5ô5˜5ô1š9Õ0š9·9š9¾8š9¾9š9Ó4˜7Ø8˜69˜8Ë0˜9‚6m09›59™1‚5˜1‚5˜2™2ƒ9™1¦7¯é¯ê¯ëU1þªþ«þ¬U2¦›¦œ¦U3‚5‡9‚5ˆ0 + +Group10: +‚5’0‚5’1÷ø÷ù÷ú÷û™2Œ4•5þ4þZþwþ’ªNþž‚2§2‚1—2‚17a4™1£9™0ó9™0Ä9˜9²6˜9­1˜9°4˜9µ8–7×9–7Ø0–7×3–7Ø1£ç¢Ç¨±„1ƒ0˜9ž8˜8â5˜9ƒ6˜8Ì2‚5ˆ5‚5ˆ8‚5–8‚5˜0™4¦1™4¦2™4¦4©J©„˜5ô3˜5ô5š0¸3š0¸4š0¸5š0Ÿ4š0¸7 © ú û üF›1£2›1£3›1£4›1£5›1£6›1£7š7÷6š7÷7š7÷8š7÷9š7ø09“19“2®É®Ê®ËU1þÙþÚþÛþÜU2§u§v§wU3iv \ No newline at end of file diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs new file mode 100644 index 00000000000000..998682b643bcbe --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +namespace GB18030.Tests; + +public static class TestHelper +{ + internal static CultureInfo[] s_cultureInfos = [ + CultureInfo.CurrentCulture, + CultureInfo.InvariantCulture, + new CultureInfo("zh-CN")]; + + internal static CompareOptions[] s_compareOptions = [ + CompareOptions.None, + CompareOptions.IgnoreCase, + CompareOptions.Ordinal, + CompareOptions.OrdinalIgnoreCase]; + + internal static readonly StringComparison[] s_ordinalStringComparisons = [ + StringComparison.Ordinal, + StringComparison.OrdinalIgnoreCase]; + + internal static readonly StringComparison[] s_nonOrdinalStringComparisons = [ + StringComparison.CurrentCulture, + StringComparison.CurrentCultureIgnoreCase, + StringComparison.InvariantCulture, + StringComparison.InvariantCultureIgnoreCase]; + + internal static readonly StringComparison[] s_allStringComparisons = [ + .. s_ordinalStringComparisons, + .. s_nonOrdinalStringComparisons]; + + internal static string s_testDataFilePath = Path.Combine(AppContext.BaseDirectory, "GB18030", "Level3+Amendment_Test_Data_for_Mid_to_High_Volume_cases.txt"); + + private static Encoding? s_gb18030Encoding; + internal static Encoding GB18030Encoding + { + get + { +#if !NETFRAMEWORK + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); +#endif + return s_gb18030Encoding ??= Encoding.GetEncoding("gb18030"); + } + } + + private static readonly IEnumerable s_encodedTestData = GetTestData(); + internal static readonly IEnumerable s_decodedTestData = s_encodedTestData.Select(data => GB18030Encoding.GetString(data)); + internal static readonly IEnumerable s_splitNewLineDecodedTestData = s_decodedTestData.SelectMany( + data => data.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries)); + + public static IEnumerable EncodedTestData { get; } = s_encodedTestData.Select(data => new object[] { data }); + public static IEnumerable DecodedTestData { get; } = s_decodedTestData.Select(data => new object[] { data }); + public static IEnumerable SplitNewLineDecodedTestData { get; } = s_splitNewLineDecodedTestData.Select(data => new object[] { data }); + + private static IEnumerable GetTestData() + { + byte[] startDelimiter = GB18030Encoding.GetBytes($":{Environment.NewLine}"); + byte[] endDelimiter = GB18030Encoding.GetBytes($"{Environment.NewLine}{Environment.NewLine}"); + + // Instead of inlining the data in source, parse the test data from the file to prevent encoding issues. + ReadOnlyMemory testFileBytes = File.ReadAllBytes(s_testDataFilePath); + + while (testFileBytes.Length > 0) + { + int start = testFileBytes.Span.IndexOf(startDelimiter); + testFileBytes = testFileBytes.Slice(start + startDelimiter.Length); + + int end = testFileBytes.Span.IndexOf(endDelimiter); + if (end == -1) + end = testFileBytes.Length; + + yield return testFileBytes.Slice(0, end).ToArray(); + + testFileBytes = testFileBytes.Slice(end); + } + + // Add a few additional test cases to exercise test correctness. + yield return GB18030Encoding.GetBytes("aaa"); + yield return GB18030Encoding.GetBytes("abc"); + yield return GB18030Encoding.GetBytes("ð«“§ð«“§"); + } + + internal static IEnumerable GetTextElements(string input) + { + TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(input); + while (enumerator.MoveNext()) + { + yield return enumerator.GetTextElement(); + } + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs new file mode 100644 index 00000000000000..ea99e3dc7a309f --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs @@ -0,0 +1,84 @@ +using System; +using Microsoft.DotNet.RemoteExecutor; +using Xunit; + +namespace GB18030.Tests; + +public class ConsoleTests +{ + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void StandardOutput(string decodedText) + { + var remoteOptions = new RemoteInvokeOptions(); + remoteOptions.StartInfo.RedirectStandardOutput = true; + remoteOptions.StartInfo.StandardOutputEncoding = TestHelper.GB18030Encoding; + + using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(line => + { + Console.OutputEncoding = TestHelper.GB18030Encoding; + Console.Write(line); + + return 42; + }, decodedText, remoteOptions); + + + Assert.Equal(decodedText, remoteHandle.Process.StandardOutput.ReadToEnd()); + Assert.True(remoteHandle.Process.WaitForExit(5_000)); + } + + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void StandardInput(string decodedText) + { + var remoteOptions = new RemoteInvokeOptions(); + remoteOptions.StartInfo.RedirectStandardInput = true; +#if !NETFRAMEWORK + remoteOptions.StartInfo.StandardInputEncoding = TestHelper.GB18030Encoding; +#endif + + using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(line => + { + Console.InputEncoding = TestHelper.GB18030Encoding; + Assert.Equal(line, Console.In.ReadToEnd()); + + return 42; + }, decodedText, remoteOptions); + + if (PlatformDetection.IsNetFramework) + { + // there's no StandardInputEncoding in .NET Framework, re-encode and write. + byte[] encoded = TestHelper.GB18030Encoding.GetBytes(decodedText); + remoteHandle.Process.StandardInput.BaseStream.Write(encoded, 0, encoded.Length); + remoteHandle.Process.StandardInput.Close(); + } + else + { + remoteHandle.Process.StandardInput.Write(decodedText); + remoteHandle.Process.StandardInput.Close(); + } + + Assert.True(remoteHandle.Process.WaitForExit(5_000)); + } + + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void StandardError(string decodedText) + { + var remoteOptions = new RemoteInvokeOptions(); + remoteOptions.StartInfo.RedirectStandardError = true; + remoteOptions.StartInfo.StandardErrorEncoding = TestHelper.GB18030Encoding; + + using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(line => + { + Console.OutputEncoding = TestHelper.GB18030Encoding; + Console.Error.Write(line); + + return 42; + }, decodedText, remoteOptions); + + + Assert.Equal(decodedText, remoteHandle.Process.StandardError.ReadToEnd()); + Assert.True(remoteHandle.Process.WaitForExit(5_000)); + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs new file mode 100644 index 00000000000000..59c8073976a889 --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace GB18030.Tests; + +public class DirectoryInfoTests : DirectoryTestBase +{ + protected override void CreateDirectory(string path) => new DirectoryInfo(path).Create(); + protected override void DeleteDirectory(string path, bool recursive) => new DirectoryInfo(path).Delete(recursive); + protected override void MoveDirectory(string source, string destination) => new DirectoryInfo(source).MoveTo(destination); + + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void CreateSubdirectory(string decoded) + { + foreach (string gb18030Line in decoded.Split([Environment.NewLine], StringSplitOptions.None)) + { + var subDirInfo = TempDirectory.CreateSubdirectory(gb18030Line); + + Assert.True(subDirInfo.Exists); + Assert.Equal(gb18030Line, subDirInfo.Name); + Assert.Equal(Path.Combine(TempDirectory.FullName, gb18030Line), subDirInfo.FullName); + } + } + + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void EnumerateFileSystemInfos(string decoded) + { + string rootDir = TempDirectory.FullName; + List expected = []; + + foreach (string gb18030Line in decoded.Split([Environment.NewLine], StringSplitOptions.None)) + { + string gb18030Dir = Path.Combine(rootDir, gb18030Line); + var dirInfo = new DirectoryInfo(gb18030Dir); + dirInfo.Create(); + expected.Add(dirInfo); + + string gb18030File = Path.Combine(rootDir, gb18030Line + ".txt"); + var fileInfo = new FileInfo(gb18030File); + fileInfo.Create().Dispose(); + expected.Add(fileInfo); + } + + Assert.Equivalent(expected, new DirectoryInfo(rootDir).EnumerateFileSystemInfos()); + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs new file mode 100644 index 00000000000000..f227312aa87406 --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xunit; + +namespace GB18030.Tests; + +public abstract class DirectoryTestBase : IDisposable +{ + protected abstract void CreateDirectory(string path); + protected abstract void DeleteDirectory(string path, bool recursive); + protected abstract void MoveDirectory(string source, string destination); + + protected DirectoryInfo TempDirectory { get; } + + public DirectoryTestBase() + { + TempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())); + } + + [Theory] + [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + public void Create(string gb18030Line) + { + string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); + CreateDirectory(gb18030Path); + + var dirInfo = new DirectoryInfo(gb18030Path); + Assert.True(dirInfo.Exists); + Assert.Equal(gb18030Line, dirInfo.Name); + } + + public static IEnumerable Delete_TestData() => + new int[] { 0, 2, 8 }.SelectMany(recurseLevel => + TestHelper.s_splitNewLineDecodedTestData.Select(testData => new object[] { recurseLevel, testData })); + + [Theory] + [MemberData(nameof(Delete_TestData))] + public void Delete(int recurseLevel, string gb18030Line) + { + string firstPath = Path.Combine(TempDirectory.FullName, gb18030Line); + string nestedDirPath = Path.Combine(firstPath, Path.Combine(Enumerable.Repeat(gb18030Line, recurseLevel).ToArray())); + Assert.True(recurseLevel > 0 || firstPath.Equals(nestedDirPath)); + + Directory.CreateDirectory(nestedDirPath); + Assert.True(Directory.Exists(nestedDirPath)); + + DeleteDirectory(firstPath, recursive: recurseLevel > 0); + + Assert.False(Directory.Exists(firstPath)); + } + + [Theory] + [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + public void Move(string gb18030Line) + { + string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); + Directory.CreateDirectory(gb18030Path); + Assert.True(Directory.Exists(gb18030Path)); + + string newPath = Path.Combine(TempDirectory.FullName, Path.GetRandomFileName()); + MoveDirectory(gb18030Path, newPath); + Assert.True(Directory.Exists(newPath)); + Assert.False(Directory.Exists(gb18030Path)); + + MoveDirectory(newPath, gb18030Path); + Assert.True(Directory.Exists(gb18030Path)); + Assert.False(Directory.Exists(newPath)); + } + + public void Dispose() + { + TempDirectory.Delete(true); + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs new file mode 100644 index 00000000000000..e90d1ed889ca53 --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace GB18030.Tests; + +public class DirectoryTests : DirectoryTestBase +{ + protected override void CreateDirectory(string path) => Directory.CreateDirectory(path); + protected override void DeleteDirectory(string path, bool recursive) => Directory.Delete(path, recursive); + protected override void MoveDirectory(string source, string destination) => Directory.Move(source, destination); + + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void EnumerateFileSystemEntries(string decoded) + { + string rootDir = TempDirectory.FullName; + List expected = []; + + foreach (string gb18030Line in decoded.Split([Environment.NewLine], StringSplitOptions.None)) + { + string gb18030Dir = Path.Combine(rootDir, gb18030Line); + Directory.CreateDirectory(gb18030Dir); + expected.Add(gb18030Dir); + + string gb18030File = Path.Combine(rootDir, gb18030Line + ".txt"); + File.Create(gb18030File).Dispose(); + expected.Add(gb18030File); + } + + Assert.Equivalent(expected, Directory.EnumerateFileSystemEntries(rootDir)); + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs new file mode 100644 index 00000000000000..dfebb4485b0832 --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs @@ -0,0 +1,15 @@ +using System; +using Xunit; + +namespace GB18030.Tests; + +public class EncodingTests +{ + [Theory] + [MemberData(nameof(TestHelper.EncodedTestData), MemberType = typeof(TestHelper))] + public void Roundtrips(byte[] testData) + { + Assert.True(testData.AsSpan().SequenceEqual( + TestHelper.GB18030Encoding.GetBytes(TestHelper.GB18030Encoding.GetString(testData)))); + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileInfoTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileInfoTests.cs new file mode 100644 index 00000000000000..77f04c813c3a4f --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileInfoTests.cs @@ -0,0 +1,11 @@ +using System.IO; + +namespace GB18030.Tests; + +public class FileInfoTests : FileTestBase +{ + protected override void CreateFile(string path) => new FileInfo(path).Create().Dispose(); + protected override void DeleteFile(string path) => new FileInfo(path).Delete(); + protected override void MoveFile(string source, string destination) => new FileInfo(source).MoveTo(destination); + protected override void CopyFile(string source, string destination) => new FileInfo(source).CopyTo(destination); +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs new file mode 100644 index 00000000000000..e201186189d9be --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; +using Xunit; + +namespace GB18030.Tests; + +public abstract class FileTestBase : IDisposable +{ + protected abstract void CreateFile(string path); + protected abstract void DeleteFile(string path); + protected abstract void MoveFile(string source, string destination); + protected abstract void CopyFile(string source, string destination); + + protected DirectoryInfo TempDirectory { get; } + + public FileTestBase() + { + TempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())); + } + + public void Dispose() + { + TempDirectory.Delete(true); + } + + [Theory] + [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + public void Create(string gb18030Line) + { + string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); + CreateFile(gb18030Path); + + var fileInfo = new FileInfo(gb18030Path); + Assert.True(fileInfo.Exists); + Assert.Equal(fileInfo.Name, gb18030Line); + } + + [Theory] + [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + public void Delete(string gb18030Line) + { + string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); + File.Create(gb18030Path).Dispose(); + Assert.True(File.Exists(gb18030Path)); + + DeleteFile(gb18030Path); + + Assert.False(File.Exists(gb18030Path)); + } + + [Theory] + [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + public void Move(string gb18030Line) + { + string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); + File.Create(gb18030Path).Dispose(); + + string newPath = Path.Combine(TempDirectory.FullName, Path.GetRandomFileName()); + MoveFile(gb18030Path, newPath); + Assert.True(File.Exists(newPath)); + Assert.False(File.Exists(gb18030Path)); + + File.Move(newPath, gb18030Path); + Assert.True(File.Exists(gb18030Path)); + Assert.False(File.Exists(newPath)); + } + + [Theory] + [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + public void Copy(string gb18030Line) + { + ReadOnlySpan sampleContent = "File_Copy"u8; + string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); + File.WriteAllBytes(gb18030Path, sampleContent.ToArray()); + + string newPath = Path.Combine(TempDirectory.FullName, Path.GetRandomFileName()); + CopyFile(gb18030Path, newPath); + Assert.True(File.Exists(newPath)); + + File.Delete(gb18030Path); + Assert.False(File.Exists(gb18030Path)); + + CopyFile(newPath, gb18030Path); + Assert.True(File.Exists(gb18030Path)); + Assert.True(sampleContent.SequenceEqual(File.ReadAllBytes(gb18030Path))); + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs new file mode 100644 index 00000000000000..91cc7573e89324 --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using System.Linq; +using Xunit; + +namespace GB18030.Tests; + +public class FileTests : FileTestBase +{ + private static readonly byte[] s_expectedBytes = File.ReadAllBytes(TestHelper.s_testDataFilePath); + private static readonly string s_expectedText = TestHelper.GB18030Encoding.GetString(s_expectedBytes); + + protected override void CreateFile(string path) => File.Create(path).Dispose(); + protected override void DeleteFile(string path) => File.Delete(path); + protected override void MoveFile(string source, string destination) => File.Move(source, destination); + protected override void CopyFile(string source, string destination) => File.Copy(source, destination); + + [Fact] + public void ReadAllText() + { + Assert.Equal(s_expectedText, File.ReadAllText(TestHelper.s_testDataFilePath, TestHelper.GB18030Encoding)); + } + + [Fact] + public void ReadAllLines() + { + Assert.Equal( + s_expectedText.Split([Environment.NewLine], StringSplitOptions.None), + File.ReadAllLines(TestHelper.s_testDataFilePath, TestHelper.GB18030Encoding)); + } + + [Fact] + public void WriteAllText() + { + string tempFile = Path.Combine(TempDirectory.FullName, Path.GetRandomFileName()); + File.WriteAllText(tempFile, s_expectedText, TestHelper.GB18030Encoding); + + Assert.True(s_expectedBytes.AsSpan().SequenceEqual(File.ReadAllBytes(tempFile))); + } + + [Fact] + public void WriteAllLines() + { + string tempFile = Path.Combine(TempDirectory.FullName, Path.GetRandomFileName()); + string[] lines = s_expectedText.Split([Environment.NewLine], StringSplitOptions.None); + File.WriteAllLines(tempFile, lines, TestHelper.GB18030Encoding); + + // WriteAllLines uses TextWriter.WriteLine which concats a newline to each provided line, + // the result is the expected text with an additional newline at the end. + byte[] expected = TestHelper.GB18030Encoding.GetBytes(s_expectedText + Environment.NewLine); + Assert.True(expected.AsSpan().SequenceEqual(File.ReadAllBytes(tempFile))); + } + + [Fact] + public void AppendAllText() + { + string tempFile = Path.Combine(TempDirectory.FullName, Path.GetRandomFileName()); + const string initialContent = "Initial content: "; + File.WriteAllText(tempFile, initialContent, TestHelper.GB18030Encoding); + File.AppendAllText(tempFile, s_expectedText, TestHelper.GB18030Encoding); + + byte[] expected = TestHelper.GB18030Encoding.GetBytes(initialContent + s_expectedText); + Assert.True(expected.AsSpan().SequenceEqual(File.ReadAllBytes(tempFile))); + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs new file mode 100644 index 00000000000000..7d9a6029e38433 --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Xunit; +#pragma warning disable xUnit2009 // Do not use boolean check to check for substrings +#pragma warning disable xUnit2010 // Do not use boolean check to check for string equality + +namespace GB18030.Tests; + +public class StringTests +{ + private const string Dummy = "\uFFFF"; + + [Theory] + [MemberData(nameof(TestHelper.EncodedTestData), MemberType = typeof(TestHelper))] + public unsafe void Ctor(byte[] encoded) + { + fixed (sbyte* p = (sbyte[])(object)encoded) + { + string s = new string(p, 0, encoded.Length, TestHelper.GB18030Encoding); + Assert.True(encoded.AsSpan().SequenceEqual(TestHelper.GB18030Encoding.GetBytes(s))); + } + } + + public static IEnumerable Compare_TestData() => + TestHelper.s_cultureInfos.SelectMany(culture => + TestHelper.s_compareOptions.SelectMany(option => + TestHelper.s_decodedTestData.Select(testData => new object[] { culture, option, testData }))); + + [Theory] + [MemberData(nameof(Compare_TestData))] + public void Compare(CultureInfo culture, CompareOptions option, string decoded) + { +#pragma warning disable 0618 // suppress obsolete warning for String.Copy + string copy = string.Copy(decoded); +#pragma warning restore 0618 + Assert.True(string.Compare(decoded, copy, culture, option) == 0); + } + + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void Contains(string decoded) + { + for (int i = 0; i < decoded.Length; i++) + Assert.True(decoded.Contains(decoded.Substring(0, i))); + + for (int i = decoded.Length - 1; i >= 0; i--) + Assert.True(decoded.Contains(decoded.Substring(i))); + } + + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void String_Equals(string decoded) + { +#pragma warning disable 0618 // suppress obsolete warning for String.Copy + string copy = string.Copy(decoded); +#pragma warning restore 0618 + Assert.True(decoded.Equals(decoded)); + Assert.True(decoded.Equals(copy)); + Assert.True(decoded.Equals((object)copy)); + Assert.True(string.Equals(decoded, copy)); + + Assert.False(decoded.Equals(copy + Dummy)); + Assert.False(decoded.Equals(Dummy + copy)); + Assert.False(decoded.Equals(copy.Substring(0, copy.Length / 2) + Dummy + copy.Substring(copy.Length / 2))); + Assert.False(decoded.Equals(null)); + } + + public static IEnumerable StringComparison_TestData() => + TestHelper.s_allStringComparisons.SelectMany(comparison => + TestHelper.s_decodedTestData.Select(decoded => new object[] { comparison, decoded })); + + [Theory] + [MemberData(nameof(StringComparison_TestData))] + public void String_Equals_StringComparison(StringComparison comparison, string decoded) + { +#pragma warning disable 0618 // suppress obsolete warning for String.Copy + string copy = string.Copy(decoded); +#pragma warning restore 0618 + if ((int)comparison % 2 != 0) // Odd values are *IgnoreCase + { + Assert.True(decoded.ToLower().Equals(copy.ToUpper(), comparison)); + Assert.True(string.Equals(decoded.ToUpper(), copy.ToLower(), comparison)); + } + else + { + Assert.True(decoded.Equals(copy, comparison)); + Assert.True(string.Equals(decoded, copy, comparison)); + } + } + + public static IEnumerable EndsStartsWith_TestData() => + TestHelper.s_cultureInfos.SelectMany(culture => + new bool[] { true, false }.SelectMany(ignoreCase => + TestHelper.s_decodedTestData.Select(testData => new object[] { culture, ignoreCase, testData }))); + + [Theory] + [MemberData(nameof(EndsStartsWith_TestData))] + public void EndsWith(CultureInfo culture, bool ignoreCase, string decoded) + { + string suffix = string.Empty; + foreach (string textElement in TestHelper.GetTextElements(decoded).Reverse()) + { + suffix = textElement + suffix; + if (ignoreCase) + Assert.True(decoded.ToUpper().EndsWith(suffix.ToLower(), ignoreCase, culture)); + else + Assert.True(decoded.EndsWith(suffix, ignoreCase, culture)); + } + } + + public static IEnumerable EndsStartsWith_Ordinal_TestData() => + new StringComparison[] + { + StringComparison.Ordinal, + StringComparison.OrdinalIgnoreCase + } + .SelectMany(culture => + TestHelper.s_decodedTestData.Select(testData => new object[] { culture, testData })); + + [Theory] + [MemberData(nameof(EndsStartsWith_Ordinal_TestData))] + public void EndsWith_Ordinal(StringComparison comparison, string decoded) + { + for (int i = decoded.Length - 1; i >= 0; i--) + { + if (comparison == StringComparison.OrdinalIgnoreCase) + Assert.True(decoded.ToLower().EndsWith(decoded.Substring(i).ToUpper(), comparison)); + else + Assert.True(decoded.EndsWith(decoded.Substring(i), comparison)); + } + } + + [Theory] + [MemberData(nameof(EndsStartsWith_TestData))] + public void StartsWith(CultureInfo culture, bool ignoreCase, string decoded) + { + string prefix = string.Empty; + foreach (string textElement in TestHelper.GetTextElements(decoded)) + { + prefix += textElement; + if (ignoreCase) + Assert.True(decoded.ToUpper().StartsWith(prefix.ToLower(), ignoreCase, culture)); + else + Assert.True(decoded.StartsWith(prefix, ignoreCase, culture)); + } + } + + [Theory] + [MemberData(nameof(EndsStartsWith_Ordinal_TestData))] + public void StartsWith_Ordinal(StringComparison comparison, string decoded) + { + for (int i = 0; i < decoded.Length; i++) + { + if (comparison == StringComparison.OrdinalIgnoreCase) + Assert.True(decoded.ToLower().StartsWith(decoded.Substring(0, i).ToUpper(), comparison)); + else + Assert.True(decoded.StartsWith(decoded.Substring(0, i), comparison)); + } + } + + [Theory] + [MemberData(nameof(StringComparison_TestData))] + public void IndexOf_MultipleElements(StringComparison comparison, string decoded) + { + bool ignoreCase = (int)comparison % 2 != 0; + if (comparison is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) + { + for (int i = 0; i < decoded.Length; i++) + { + string left = decoded.Substring(0, i); + string right = decoded.Substring(i); + Assert.Equal(decoded.Length, left.Length + right.Length); + + Assert.Equal(0, decoded.IndexOf(left, comparison)); + // right substring starts at the end of left one. + Assert.Equal(left.Length, decoded.IndexOf(right, startIndex: left.Length, comparison)); + + if (ignoreCase) + { + Assert.Equal(0, decoded.ToLower().IndexOf(left.ToUpper(), comparison)); + Assert.Equal(left.Length, decoded.ToLower().IndexOf(right.ToUpper(), startIndex: left.Length, comparison)); + } + } + } + else + { + string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); + for (int i = 0; i < textElements.Length; i++) + { + string left = string.Concat(textElements.Take(i)); + string right = string.Concat(textElements.Skip(i)); + Assert.Equal(decoded.Length, left.Length + right.Length); + + Assert.Equal(0, decoded.IndexOf(left, comparison)); + Assert.Equal(left.Length, decoded.IndexOf(right, startIndex: left.Length, comparison)); + + if (ignoreCase) + { + Assert.Equal(0, decoded.ToLower().IndexOf(left.ToUpper(), comparison)); + Assert.Equal(left.Length, decoded.ToLower().IndexOf(right.ToUpper(), startIndex: left.Length, comparison)); + } + } + } + } + + [Theory] + [MemberData(nameof(StringComparison_TestData))] + public void IndexOf_SingleElement(StringComparison comparison, string decoded) + { + bool ignoreCase = (int)comparison % 2 != 0; + if (comparison is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) + { + for (int i = 0; i < decoded.Length; i++) + { + string current = decoded.Substring(i, 1); + // Fast-check the expected index. + Assert.Equal(i, decoded.IndexOf(current, startIndex: i, comparison)); + IndexOf_SingleElement_Core(current, expectedIndex: i, ignoreCase); + } + } + else + { + string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); + for (int i = 0; i < textElements.Length; i++) + { + string current = textElements[i]; + int expectedIndex = textElements.Take(i).Sum(e => e.Length); + // Fast-check the expected index. + Assert.Equal(expectedIndex, decoded.IndexOf(current, startIndex: expectedIndex, comparison)); + IndexOf_SingleElement_Core(current, expectedIndex, ignoreCase); + } + } + + void IndexOf_SingleElement_Core(string current, int expectedIndex, bool ignoreCase) + { + int startIndex = 0; + while (true) + { + int result = ignoreCase ? + decoded.ToLower().IndexOf(current.ToUpper(), startIndex, comparison) : + decoded.IndexOf(current, startIndex, comparison); + + if (result == -1 || result > expectedIndex) + Assert.Fail($"'{current}' not found or found too late in '{decoded}'"); + else if (result < expectedIndex) + startIndex = result + current.Length; + else + { + Assert.Equal(expectedIndex, result); + break; + } + } + } + } + + [Theory] + [MemberData(nameof(StringComparison_TestData))] + public void LastIndexOf_MultipleElements(StringComparison comparison, string decoded) + { + bool ignoreCase = (int)comparison % 2 != 0; + + // Don't deal with LastIndexOf(string.Empty) nuances, test against full length outside of the loop. + // see https://learn.microsoft.com/dotnet/core/compatibility/core-libraries/5.0/lastindexof-improved-handling-of-empty-values + Assert.Equal(0, decoded.LastIndexOf(decoded, comparison)); + if (ignoreCase) + Assert.Equal(0, decoded.ToLower().LastIndexOf(decoded.ToUpper(), comparison)); + + if (comparison is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) + { + + for (int i = 1; i < decoded.Length; i++) + { + string left = decoded.Substring(0, i); + string right = decoded.Substring(i); + Assert.Equal(decoded.Length, left.Length + right.Length); + + Assert.Equal(0, decoded.LastIndexOf(left, startIndex: left.Length - 1, comparison)); + // right substring starts at the end of left one. + Assert.Equal(left.Length, decoded.LastIndexOf(right, comparison)); + + if (ignoreCase) + { + Assert.Equal(0, decoded.ToLower().LastIndexOf(left.ToUpper(), startIndex: left.Length - 1, comparison)); + Assert.Equal(left.Length, decoded.ToLower().LastIndexOf(right.ToUpper(), comparison)); + } + } + } + else + { + string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); + for (int i = 1; i < textElements.Length; i++) + { + string left = string.Concat(textElements.Take(i)); + string right = string.Concat(textElements.Skip(i)); + Assert.Equal(decoded.Length, left.Length + right.Length); + + Assert.Equal(0, decoded.LastIndexOf(left, startIndex: left.Length - 1, comparison)); + Assert.Equal(left.Length, decoded.LastIndexOf(right, comparison)); + + if (ignoreCase) + { + Assert.Equal(0, decoded.ToLower().LastIndexOf(left.ToUpper(), startIndex: left.Length - 1, comparison)); + Assert.Equal(left.Length, decoded.ToLower().LastIndexOf(right.ToUpper(), comparison)); + } + } + } + } + + [Theory] + [MemberData(nameof(StringComparison_TestData))] + public void LastIndexOf_SingleElement(StringComparison comparison, string decoded) + { + bool ignoreCase = (int)comparison % 2 != 0; + if (comparison is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) + { + for (int i = 0; i < decoded.Length; i++) + { + string current = decoded.Substring(i, 1); + // Fast-check the expected index. + Assert.Equal(i, decoded.LastIndexOf(current, startIndex: i, comparison)); + LastIndexOf_SingleElement_Core(current, expectedIndex: i, ignoreCase); + } + } + else + { + string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); + for (int i = 0; i < textElements.Length; i++) + { + string current = textElements[i]; + int expectedIndex = textElements.Take(i).Sum(e => e.Length); + // Fast-check the expected index. + Assert.Equal(expectedIndex, decoded.LastIndexOf(current, startIndex: expectedIndex + current.Length - 1, comparison)); + LastIndexOf_SingleElement_Core(current, expectedIndex, ignoreCase); + } + } + + void LastIndexOf_SingleElement_Core(string current, int expectedIndex, bool ignoreCase) + { + int startIndex = decoded.Length - 1; + while (true) + { + int result = ignoreCase ? + decoded.ToLower().LastIndexOf(current.ToUpper(), startIndex, comparison) : + decoded.LastIndexOf(current, startIndex, comparison); + + if (result == -1 || result < expectedIndex) + Assert.Fail($"'{current}' not found or found too late in '{decoded}'"); + else if (result > expectedIndex) + startIndex = result - 1; + else + { + Assert.Equal(expectedIndex, result); + break; + } + } + } + } + + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void Replace(string decoded) + { + Assert.False(decoded.Contains(Dummy)); + + foreach (string textElement in TestHelper.GetTextElements(decoded)) + { + int occurrences = decoded.Split([textElement], StringSplitOptions.None).Length; + + string replaced = decoded.Replace(textElement, Dummy); + Assert.Equal(occurrences, replaced.Split([Dummy], StringSplitOptions.None).Length); + + if (textElement.Length == 1) + { + replaced = decoded.Replace(textElement[0], Dummy[0]); + Assert.Equal(occurrences, replaced.Split([Dummy], StringSplitOptions.None).Length); + } + } + } + + [Theory] + [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + public void Split(string decoded) + { + Assert.Single(Split(decoded, Dummy)); + Assert.Single(Split(decoded, Dummy[0])); + + string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); + + for (int i = 0; i < textElements.Length; i++) + { + var result = Split(decoded, textElements[i]); + Assert.True(result.Length > 1); + } + + static string[] Split(string str, params T[] separators) => + separators switch + { + char[] chars => str.Split(chars, StringSplitOptions.None), + string[] strings => str.Split(strings, StringSplitOptions.None), + _ => throw new ArgumentException() + }; + } +} From 2d68cfc77d2d37ac916f67194d64c380268b5322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Mon, 28 Jul 2025 12:14:13 -0500 Subject: [PATCH 02/10] Address feedback Fix PathTooLong CI issue --- .../ComplianceTests/GB18030/TestHelper.cs | 41 +++++++++++++++-- .../GB18030/Tests/ConsoleTests.cs | 17 ++++--- .../GB18030/Tests/DirectoryInfoTests.cs | 45 +++++++++---------- .../GB18030/Tests/DirectoryTestBase.cs | 9 ++-- .../GB18030/Tests/DirectoryTests.cs | 22 ++++----- .../GB18030/Tests/EncodingTests.cs | 3 ++ .../GB18030/Tests/FileInfoTests.cs | 3 ++ .../GB18030/Tests/FileTestBase.cs | 11 +++-- .../GB18030/Tests/FileTests.cs | 3 ++ .../GB18030/Tests/StringTests.cs | 5 ++- 10 files changed, 106 insertions(+), 53 deletions(-) diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs index 998682b643bcbe..3f224a932438cd 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Collections.Generic; using System.Globalization; @@ -41,21 +44,51 @@ internal static Encoding GB18030Encoding { get { + if (s_gb18030Encoding is null) + { #if !NETFRAMEWORK - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); #endif - return s_gb18030Encoding ??= Encoding.GetEncoding("gb18030"); + s_gb18030Encoding = Encoding.GetEncoding("gb18030"); + } + + return s_gb18030Encoding; } } private static readonly IEnumerable s_encodedTestData = GetTestData(); internal static readonly IEnumerable s_decodedTestData = s_encodedTestData.Select(data => GB18030Encoding.GetString(data)); - internal static readonly IEnumerable s_splitNewLineDecodedTestData = s_decodedTestData.SelectMany( + private static readonly IEnumerable s_splitNewLineDecodedTestData = s_decodedTestData.SelectMany( data => data.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries)); + internal static readonly IEnumerable s_nonExceedingPathNameMaxDecodedTestData = + s_splitNewLineDecodedTestData.SelectMany( + (data) => + { + const int MaxPathSegmentName = 255; + Encoding fileSystemEncoding = PlatformDetection.IsWindows ? Encoding.Unicode : Encoding.UTF8; + + if (fileSystemEncoding.GetByteCount(data) <= MaxPathSegmentName) + return [data]; + + List result = new(); + string current = string.Empty; + foreach (string element in GetTextElements(data)) + { + if (fileSystemEncoding.GetByteCount(current) > MaxPathSegmentName) + { + result.Add(current); + current = string.Empty; + } + current += element; + } + result.Add(current); + return result; + }); + public static IEnumerable EncodedTestData { get; } = s_encodedTestData.Select(data => new object[] { data }); public static IEnumerable DecodedTestData { get; } = s_decodedTestData.Select(data => new object[] { data }); - public static IEnumerable SplitNewLineDecodedTestData { get; } = s_splitNewLineDecodedTestData.Select(data => new object[] { data }); + public static IEnumerable NonExceedingPathNameMaxDecodedTestData { get; } = s_nonExceedingPathNameMaxDecodedTestData.Select(data => new object[] { data }); private static IEnumerable GetTestData() { diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs index ea99e3dc7a309f..d96e586a14ce92 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -6,7 +9,9 @@ namespace GB18030.Tests; public class ConsoleTests { - [Theory] + protected static readonly int WaitInMS = 30 * 1000 * PlatformDetection.SlowRuntimeTimeoutModifier; + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] public void StandardOutput(string decodedText) { @@ -24,10 +29,10 @@ public void StandardOutput(string decodedText) Assert.Equal(decodedText, remoteHandle.Process.StandardOutput.ReadToEnd()); - Assert.True(remoteHandle.Process.WaitForExit(5_000)); + Assert.True(remoteHandle.Process.WaitForExit(WaitInMS)); } - [Theory] + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] public void StandardInput(string decodedText) { @@ -58,10 +63,10 @@ public void StandardInput(string decodedText) remoteHandle.Process.StandardInput.Close(); } - Assert.True(remoteHandle.Process.WaitForExit(5_000)); + Assert.True(remoteHandle.Process.WaitForExit(WaitInMS)); } - [Theory] + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] public void StandardError(string decodedText) { @@ -79,6 +84,6 @@ public void StandardError(string decodedText) Assert.Equal(decodedText, remoteHandle.Process.StandardError.ReadToEnd()); - Assert.True(remoteHandle.Process.WaitForExit(5_000)); + Assert.True(remoteHandle.Process.WaitForExit(WaitInMS)); } } diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs index 59c8073976a889..bf35b8e96771bc 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Collections.Generic; using System.IO; @@ -12,38 +15,32 @@ public class DirectoryInfoTests : DirectoryTestBase protected override void MoveDirectory(string source, string destination) => new DirectoryInfo(source).MoveTo(destination); [Theory] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] - public void CreateSubdirectory(string decoded) + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + public void CreateSubdirectory(string gb18030Line) { - foreach (string gb18030Line in decoded.Split([Environment.NewLine], StringSplitOptions.None)) - { - var subDirInfo = TempDirectory.CreateSubdirectory(gb18030Line); - - Assert.True(subDirInfo.Exists); - Assert.Equal(gb18030Line, subDirInfo.Name); - Assert.Equal(Path.Combine(TempDirectory.FullName, gb18030Line), subDirInfo.FullName); - } + var subDirInfo = TempDirectory.CreateSubdirectory(gb18030Line); + + Assert.True(subDirInfo.Exists); + Assert.Equal(gb18030Line, subDirInfo.Name); + Assert.Equal(Path.Combine(TempDirectory.FullName, gb18030Line), subDirInfo.FullName); } [Theory] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] - public void EnumerateFileSystemInfos(string decoded) + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + public void EnumerateFileSystemInfos(string gb18030Line) { string rootDir = TempDirectory.FullName; List expected = []; - foreach (string gb18030Line in decoded.Split([Environment.NewLine], StringSplitOptions.None)) - { - string gb18030Dir = Path.Combine(rootDir, gb18030Line); - var dirInfo = new DirectoryInfo(gb18030Dir); - dirInfo.Create(); - expected.Add(dirInfo); - - string gb18030File = Path.Combine(rootDir, gb18030Line + ".txt"); - var fileInfo = new FileInfo(gb18030File); - fileInfo.Create().Dispose(); - expected.Add(fileInfo); - } + string gb18030Dir = Path.Combine(rootDir, gb18030Line); + var dirInfo = new DirectoryInfo(gb18030Dir); + dirInfo.Create(); + expected.Add(dirInfo); + + string gb18030File = Path.Combine(rootDir, gb18030Line + ".txt"); + var fileInfo = new FileInfo(gb18030File); + fileInfo.Create().Dispose(); + expected.Add(fileInfo); Assert.Equivalent(expected, new DirectoryInfo(rootDir).EnumerateFileSystemInfos()); } diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs index f227312aa87406..b03e58cf6c46a5 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Collections.Generic; using System.IO; @@ -20,7 +23,7 @@ public DirectoryTestBase() } [Theory] - [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] public void Create(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); @@ -33,7 +36,7 @@ public void Create(string gb18030Line) public static IEnumerable Delete_TestData() => new int[] { 0, 2, 8 }.SelectMany(recurseLevel => - TestHelper.s_splitNewLineDecodedTestData.Select(testData => new object[] { recurseLevel, testData })); + TestHelper.s_nonExceedingPathNameMaxDecodedTestData.Select(testData => new object[] { recurseLevel, testData })); [Theory] [MemberData(nameof(Delete_TestData))] @@ -52,7 +55,7 @@ public void Delete(int recurseLevel, string gb18030Line) } [Theory] - [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] public void Move(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs index e90d1ed889ca53..0132ba27bb1d62 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Collections.Generic; using System.IO; @@ -12,22 +15,19 @@ public class DirectoryTests : DirectoryTestBase protected override void MoveDirectory(string source, string destination) => Directory.Move(source, destination); [Theory] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] - public void EnumerateFileSystemEntries(string decoded) + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + public void EnumerateFileSystemEntries(string gb18030Line) { string rootDir = TempDirectory.FullName; List expected = []; - foreach (string gb18030Line in decoded.Split([Environment.NewLine], StringSplitOptions.None)) - { - string gb18030Dir = Path.Combine(rootDir, gb18030Line); - Directory.CreateDirectory(gb18030Dir); - expected.Add(gb18030Dir); + string gb18030Dir = Path.Combine(rootDir, gb18030Line); + Directory.CreateDirectory(gb18030Dir); + expected.Add(gb18030Dir); - string gb18030File = Path.Combine(rootDir, gb18030Line + ".txt"); - File.Create(gb18030File).Dispose(); - expected.Add(gb18030File); - } + string gb18030File = Path.Combine(rootDir, gb18030Line + ".txt"); + File.Create(gb18030File).Dispose(); + expected.Add(gb18030File); Assert.Equivalent(expected, Directory.EnumerateFileSystemEntries(rootDir)); } diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs index dfebb4485b0832..f12ac3292e48a9 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using Xunit; diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileInfoTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileInfoTests.cs index 77f04c813c3a4f..56684a200c8425 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileInfoTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileInfoTests.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System.IO; namespace GB18030.Tests; diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs index e201186189d9be..e1f45beef92e2c 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.IO; using Xunit; @@ -24,7 +27,7 @@ public void Dispose() } [Theory] - [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] public void Create(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); @@ -36,7 +39,7 @@ public void Create(string gb18030Line) } [Theory] - [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] public void Delete(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); @@ -49,7 +52,7 @@ public void Delete(string gb18030Line) } [Theory] - [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] public void Move(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); @@ -66,7 +69,7 @@ public void Move(string gb18030Line) } [Theory] - [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] public void Copy(string gb18030Line) { ReadOnlySpan sampleContent = "File_Copy"u8; diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs index 91cc7573e89324..38ae3b3b55649a 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.IO; using System.Linq; diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs index 7d9a6029e38433..b7b336cc5658da 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs @@ -1,4 +1,7 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; From 570c9b4292bc188705477a72a0dc00f18db9a89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Wed, 6 Aug 2025 11:12:29 -0500 Subject: [PATCH 03/10] Fix StringTests - remove ordinal comparison tests --- .../ComplianceTests/GB18030/TestHelper.cs | 14 +- .../GB18030/Tests/StringTests.cs | 338 ++++++------------ 2 files changed, 117 insertions(+), 235 deletions(-) diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs index 3f224a932438cd..7b261cf94f86ab 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs @@ -12,20 +12,14 @@ namespace GB18030.Tests; public static class TestHelper { - internal static CultureInfo[] s_cultureInfos = [ + internal static CultureInfo[] s_cultures = [ CultureInfo.CurrentCulture, CultureInfo.InvariantCulture, new CultureInfo("zh-CN")]; internal static CompareOptions[] s_compareOptions = [ CompareOptions.None, - CompareOptions.IgnoreCase, - CompareOptions.Ordinal, - CompareOptions.OrdinalIgnoreCase]; - - internal static readonly StringComparison[] s_ordinalStringComparisons = [ - StringComparison.Ordinal, - StringComparison.OrdinalIgnoreCase]; + CompareOptions.IgnoreCase]; internal static readonly StringComparison[] s_nonOrdinalStringComparisons = [ StringComparison.CurrentCulture, @@ -33,10 +27,6 @@ public static class TestHelper StringComparison.InvariantCulture, StringComparison.InvariantCultureIgnoreCase]; - internal static readonly StringComparison[] s_allStringComparisons = [ - .. s_ordinalStringComparisons, - .. s_nonOrdinalStringComparisons]; - internal static string s_testDataFilePath = Path.Combine(AppContext.BaseDirectory, "GB18030", "Level3+Amendment_Test_Data_for_Mid_to_High_Volume_cases.txt"); private static Encoding? s_gb18030Encoding; diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs index b7b336cc5658da..4e9633314abd06 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs @@ -27,7 +27,7 @@ public unsafe void Ctor(byte[] encoded) } public static IEnumerable Compare_TestData() => - TestHelper.s_cultureInfos.SelectMany(culture => + TestHelper.s_cultures.SelectMany(culture => TestHelper.s_compareOptions.SelectMany(option => TestHelper.s_decodedTestData.Select(testData => new object[] { culture, option, testData }))); @@ -41,125 +41,79 @@ public void Compare(CultureInfo culture, CompareOptions option, string decoded) Assert.True(string.Compare(decoded, copy, culture, option) == 0); } - [Theory] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] - public void Contains(string decoded) - { - for (int i = 0; i < decoded.Length; i++) - Assert.True(decoded.Contains(decoded.Substring(0, i))); - - for (int i = decoded.Length - 1; i >= 0; i--) - Assert.True(decoded.Contains(decoded.Substring(i))); - } + public static IEnumerable Contains_TestData() => + TestHelper.s_nonOrdinalStringComparisons.SelectMany(comparison => + TestHelper.s_decodedTestData.Select(testData => new object[] { comparison, testData })); [Theory] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] - public void String_Equals(string decoded) + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + [MemberData(nameof(Contains_TestData))] + public void Contains(StringComparison comparison, string decoded) { -#pragma warning disable 0618 // suppress obsolete warning for String.Copy - string copy = string.Copy(decoded); -#pragma warning restore 0618 - Assert.True(decoded.Equals(decoded)); - Assert.True(decoded.Equals(copy)); - Assert.True(decoded.Equals((object)copy)); - Assert.True(string.Equals(decoded, copy)); - - Assert.False(decoded.Equals(copy + Dummy)); - Assert.False(decoded.Equals(Dummy + copy)); - Assert.False(decoded.Equals(copy.Substring(0, copy.Length / 2) + Dummy + copy.Substring(copy.Length / 2))); - Assert.False(decoded.Equals(null)); + string current = string.Empty; + foreach (string element in TestHelper.GetTextElements(decoded)) + { + current += element; + Assert.True(decoded.Contains(current, comparison)); + } + + current = string.Empty; + foreach (string element in TestHelper.GetTextElements(decoded).Reverse()) + { + current = element + current; + Assert.True(decoded.Contains(current, comparison)); + } } public static IEnumerable StringComparison_TestData() => - TestHelper.s_allStringComparisons.SelectMany(comparison => + TestHelper.s_nonOrdinalStringComparisons.SelectMany(comparison => TestHelper.s_decodedTestData.Select(decoded => new object[] { comparison, decoded })); [Theory] [MemberData(nameof(StringComparison_TestData))] - public void String_Equals_StringComparison(StringComparison comparison, string decoded) + public void String_Equals(StringComparison comparison, string decoded) { #pragma warning disable 0618 // suppress obsolete warning for String.Copy string copy = string.Copy(decoded); #pragma warning restore 0618 - if ((int)comparison % 2 != 0) // Odd values are *IgnoreCase - { - Assert.True(decoded.ToLower().Equals(copy.ToUpper(), comparison)); - Assert.True(string.Equals(decoded.ToUpper(), copy.ToLower(), comparison)); - } - else + + Assert.True(decoded.Equals(copy, comparison)); + Assert.True(string.Equals(decoded, copy, comparison)); + + string[] elements = TestHelper.GetTextElements(decoded).ToArray(); + for (int i = 0; i < elements.Length; i++) { - Assert.True(decoded.Equals(copy, comparison)); - Assert.True(string.Equals(decoded, copy, comparison)); + string left = string.Concat(elements.Take(i)); + string right = string.Concat(elements.Skip(i)); + Assert.True(decoded.Equals(left + '\0' + right, comparison)); } } public static IEnumerable EndsStartsWith_TestData() => - TestHelper.s_cultureInfos.SelectMany(culture => - new bool[] { true, false }.SelectMany(ignoreCase => - TestHelper.s_decodedTestData.Select(testData => new object[] { culture, ignoreCase, testData }))); + TestHelper.s_cultures.SelectMany(culture => + TestHelper.s_decodedTestData.Select(testData => new object[] { culture, testData })); [Theory] [MemberData(nameof(EndsStartsWith_TestData))] - public void EndsWith(CultureInfo culture, bool ignoreCase, string decoded) + public void EndsWith(CultureInfo culture, string decoded) { string suffix = string.Empty; foreach (string textElement in TestHelper.GetTextElements(decoded).Reverse()) { suffix = textElement + suffix; - if (ignoreCase) - Assert.True(decoded.ToUpper().EndsWith(suffix.ToLower(), ignoreCase, culture)); - else - Assert.True(decoded.EndsWith(suffix, ignoreCase, culture)); - } - } - - public static IEnumerable EndsStartsWith_Ordinal_TestData() => - new StringComparison[] - { - StringComparison.Ordinal, - StringComparison.OrdinalIgnoreCase - } - .SelectMany(culture => - TestHelper.s_decodedTestData.Select(testData => new object[] { culture, testData })); - - [Theory] - [MemberData(nameof(EndsStartsWith_Ordinal_TestData))] - public void EndsWith_Ordinal(StringComparison comparison, string decoded) - { - for (int i = decoded.Length - 1; i >= 0; i--) - { - if (comparison == StringComparison.OrdinalIgnoreCase) - Assert.True(decoded.ToLower().EndsWith(decoded.Substring(i).ToUpper(), comparison)); - else - Assert.True(decoded.EndsWith(decoded.Substring(i), comparison)); + Assert.True(decoded.EndsWith(suffix, ignoreCase: false, culture)); } } [Theory] [MemberData(nameof(EndsStartsWith_TestData))] - public void StartsWith(CultureInfo culture, bool ignoreCase, string decoded) + public void StartsWith(CultureInfo culture, string decoded) { string prefix = string.Empty; foreach (string textElement in TestHelper.GetTextElements(decoded)) { prefix += textElement; - if (ignoreCase) - Assert.True(decoded.ToUpper().StartsWith(prefix.ToLower(), ignoreCase, culture)); - else - Assert.True(decoded.StartsWith(prefix, ignoreCase, culture)); - } - } - - [Theory] - [MemberData(nameof(EndsStartsWith_Ordinal_TestData))] - public void StartsWith_Ordinal(StringComparison comparison, string decoded) - { - for (int i = 0; i < decoded.Length; i++) - { - if (comparison == StringComparison.OrdinalIgnoreCase) - Assert.True(decoded.ToLower().StartsWith(decoded.Substring(0, i).ToUpper(), comparison)); - else - Assert.True(decoded.StartsWith(decoded.Substring(0, i), comparison)); + Assert.True(decoded.StartsWith(prefix, ignoreCase: false, culture)); } } @@ -167,44 +121,18 @@ public void StartsWith_Ordinal(StringComparison comparison, string decoded) [MemberData(nameof(StringComparison_TestData))] public void IndexOf_MultipleElements(StringComparison comparison, string decoded) { - bool ignoreCase = (int)comparison % 2 != 0; - if (comparison is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) - { - for (int i = 0; i < decoded.Length; i++) - { - string left = decoded.Substring(0, i); - string right = decoded.Substring(i); - Assert.Equal(decoded.Length, left.Length + right.Length); + Assert.NotEqual(StringComparison.Ordinal, comparison); + Assert.NotEqual(StringComparison.OrdinalIgnoreCase, comparison); - Assert.Equal(0, decoded.IndexOf(left, comparison)); - // right substring starts at the end of left one. - Assert.Equal(left.Length, decoded.IndexOf(right, startIndex: left.Length, comparison)); - - if (ignoreCase) - { - Assert.Equal(0, decoded.ToLower().IndexOf(left.ToUpper(), comparison)); - Assert.Equal(left.Length, decoded.ToLower().IndexOf(right.ToUpper(), startIndex: left.Length, comparison)); - } - } - } - else + string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); + for (int i = 0; i < textElements.Length; i++) { - string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); - for (int i = 0; i < textElements.Length; i++) - { - string left = string.Concat(textElements.Take(i)); - string right = string.Concat(textElements.Skip(i)); - Assert.Equal(decoded.Length, left.Length + right.Length); - - Assert.Equal(0, decoded.IndexOf(left, comparison)); - Assert.Equal(left.Length, decoded.IndexOf(right, startIndex: left.Length, comparison)); + string left = string.Concat(textElements.Take(i)); + string right = string.Concat(textElements.Skip(i)); + Assert.Equal(decoded.Length, left.Length + right.Length); - if (ignoreCase) - { - Assert.Equal(0, decoded.ToLower().IndexOf(left.ToUpper(), comparison)); - Assert.Equal(left.Length, decoded.ToLower().IndexOf(right.ToUpper(), startIndex: left.Length, comparison)); - } - } + Assert.Equal(0, decoded.IndexOf(left, comparison)); + Assert.Equal(left.Length, decoded.IndexOf(right, startIndex: left.Length, comparison)); } } @@ -212,38 +140,25 @@ public void IndexOf_MultipleElements(StringComparison comparison, string decoded [MemberData(nameof(StringComparison_TestData))] public void IndexOf_SingleElement(StringComparison comparison, string decoded) { - bool ignoreCase = (int)comparison % 2 != 0; - if (comparison is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) - { - for (int i = 0; i < decoded.Length; i++) - { - string current = decoded.Substring(i, 1); - // Fast-check the expected index. - Assert.Equal(i, decoded.IndexOf(current, startIndex: i, comparison)); - IndexOf_SingleElement_Core(current, expectedIndex: i, ignoreCase); - } - } - else + Assert.NotEqual(StringComparison.Ordinal, comparison); + Assert.NotEqual(StringComparison.OrdinalIgnoreCase, comparison); + + string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); + for (int i = 0; i < textElements.Length; i++) { - string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); - for (int i = 0; i < textElements.Length; i++) - { - string current = textElements[i]; - int expectedIndex = textElements.Take(i).Sum(e => e.Length); - // Fast-check the expected index. - Assert.Equal(expectedIndex, decoded.IndexOf(current, startIndex: expectedIndex, comparison)); - IndexOf_SingleElement_Core(current, expectedIndex, ignoreCase); - } + string current = textElements[i]; + int expectedIndex = textElements.Take(i).Sum(e => e.Length); + // Fast-check the expected index. + Assert.Equal(expectedIndex, decoded.IndexOf(current, startIndex: expectedIndex, comparison)); + IndexOf_SingleElement_Slow(current, expectedIndex); } - void IndexOf_SingleElement_Core(string current, int expectedIndex, bool ignoreCase) + void IndexOf_SingleElement_Slow(string current, int expectedIndex) { int startIndex = 0; while (true) { - int result = ignoreCase ? - decoded.ToLower().IndexOf(current.ToUpper(), startIndex, comparison) : - decoded.IndexOf(current, startIndex, comparison); + int result = decoded.IndexOf(current, startIndex, comparison); if (result == -1 || result > expectedIndex) Assert.Fail($"'{current}' not found or found too late in '{decoded}'"); @@ -262,52 +177,22 @@ void IndexOf_SingleElement_Core(string current, int expectedIndex, bool ignoreCa [MemberData(nameof(StringComparison_TestData))] public void LastIndexOf_MultipleElements(StringComparison comparison, string decoded) { - bool ignoreCase = (int)comparison % 2 != 0; + Assert.NotEqual(StringComparison.Ordinal, comparison); + Assert.NotEqual(StringComparison.OrdinalIgnoreCase, comparison); // Don't deal with LastIndexOf(string.Empty) nuances, test against full length outside of the loop. // see https://learn.microsoft.com/dotnet/core/compatibility/core-libraries/5.0/lastindexof-improved-handling-of-empty-values Assert.Equal(0, decoded.LastIndexOf(decoded, comparison)); - if (ignoreCase) - Assert.Equal(0, decoded.ToLower().LastIndexOf(decoded.ToUpper(), comparison)); - if (comparison is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) + string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); + for (int i = 1; i < textElements.Length; i++) { + string left = string.Concat(textElements.Take(i)); + string right = string.Concat(textElements.Skip(i)); + Assert.Equal(decoded.Length, left.Length + right.Length); - for (int i = 1; i < decoded.Length; i++) - { - string left = decoded.Substring(0, i); - string right = decoded.Substring(i); - Assert.Equal(decoded.Length, left.Length + right.Length); - - Assert.Equal(0, decoded.LastIndexOf(left, startIndex: left.Length - 1, comparison)); - // right substring starts at the end of left one. - Assert.Equal(left.Length, decoded.LastIndexOf(right, comparison)); - - if (ignoreCase) - { - Assert.Equal(0, decoded.ToLower().LastIndexOf(left.ToUpper(), startIndex: left.Length - 1, comparison)); - Assert.Equal(left.Length, decoded.ToLower().LastIndexOf(right.ToUpper(), comparison)); - } - } - } - else - { - string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); - for (int i = 1; i < textElements.Length; i++) - { - string left = string.Concat(textElements.Take(i)); - string right = string.Concat(textElements.Skip(i)); - Assert.Equal(decoded.Length, left.Length + right.Length); - - Assert.Equal(0, decoded.LastIndexOf(left, startIndex: left.Length - 1, comparison)); - Assert.Equal(left.Length, decoded.LastIndexOf(right, comparison)); - - if (ignoreCase) - { - Assert.Equal(0, decoded.ToLower().LastIndexOf(left.ToUpper(), startIndex: left.Length - 1, comparison)); - Assert.Equal(left.Length, decoded.ToLower().LastIndexOf(right.ToUpper(), comparison)); - } - } + Assert.Equal(0, decoded.LastIndexOf(left, startIndex: left.Length - 1, comparison)); + Assert.Equal(left.Length, decoded.LastIndexOf(right, comparison)); } } @@ -315,38 +200,25 @@ public void LastIndexOf_MultipleElements(StringComparison comparison, string dec [MemberData(nameof(StringComparison_TestData))] public void LastIndexOf_SingleElement(StringComparison comparison, string decoded) { - bool ignoreCase = (int)comparison % 2 != 0; - if (comparison is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) - { - for (int i = 0; i < decoded.Length; i++) - { - string current = decoded.Substring(i, 1); - // Fast-check the expected index. - Assert.Equal(i, decoded.LastIndexOf(current, startIndex: i, comparison)); - LastIndexOf_SingleElement_Core(current, expectedIndex: i, ignoreCase); - } - } - else + Assert.NotEqual(StringComparison.Ordinal, comparison); + Assert.NotEqual(StringComparison.OrdinalIgnoreCase, comparison); + + string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); + for (int i = 0; i < textElements.Length; i++) { - string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); - for (int i = 0; i < textElements.Length; i++) - { - string current = textElements[i]; - int expectedIndex = textElements.Take(i).Sum(e => e.Length); - // Fast-check the expected index. - Assert.Equal(expectedIndex, decoded.LastIndexOf(current, startIndex: expectedIndex + current.Length - 1, comparison)); - LastIndexOf_SingleElement_Core(current, expectedIndex, ignoreCase); - } + string current = textElements[i]; + int expectedIndex = textElements.Take(i).Sum(e => e.Length); + // Fast-check the expected index. + Assert.Equal(expectedIndex, decoded.LastIndexOf(current, startIndex: expectedIndex + current.Length - 1, comparison)); + LastIndexOf_SingleElement_Slow(current, expectedIndex); } - void LastIndexOf_SingleElement_Core(string current, int expectedIndex, bool ignoreCase) + void LastIndexOf_SingleElement_Slow(string current, int expectedIndex) { int startIndex = decoded.Length - 1; while (true) { - int result = ignoreCase ? - decoded.ToLower().LastIndexOf(current.ToUpper(), startIndex, comparison) : - decoded.LastIndexOf(current, startIndex, comparison); + int result = decoded.LastIndexOf(current, startIndex, comparison); if (result == -1 || result < expectedIndex) Assert.Fail($"'{current}' not found or found too late in '{decoded}'"); @@ -369,40 +241,60 @@ public void Replace(string decoded) foreach (string textElement in TestHelper.GetTextElements(decoded)) { - int occurrences = decoded.Split([textElement], StringSplitOptions.None).Length; - + int occurrences = SplitHelper(decoded, textElement).Length; string replaced = decoded.Replace(textElement, Dummy); - Assert.Equal(occurrences, replaced.Split([Dummy], StringSplitOptions.None).Length); + Assert.Equal(occurrences, SplitHelper(replaced, Dummy).Length); if (textElement.Length == 1) { replaced = decoded.Replace(textElement[0], Dummy[0]); - Assert.Equal(occurrences, replaced.Split([Dummy], StringSplitOptions.None).Length); + Assert.Equal(occurrences, SplitHelper(replaced, Dummy).Length); } } } + public static IEnumerable Replace_NetCore_TestData() => + TestHelper.s_cultures.SelectMany(culture => + TestHelper.s_decodedTestData.Select(testData => new object[] { culture, testData })); + +#if NETCOREAPP + [Theory] + [MemberData(nameof(Replace_NetCore_TestData))] + public void Replace_CultureInfo(CultureInfo culture, string decoded) + { + Assert.False(decoded.Contains(Dummy)); + + foreach (string textElement in TestHelper.GetTextElements(decoded)) + { + int expected = SplitHelper(decoded, textElement).Length; + string replaced = decoded.Replace(textElement, Dummy, ignoreCase: false, culture); + Assert.True(expected == SplitHelper(replaced, Dummy).Length || + // Exception for non zh-CN culture, where '0' and '〇' are considered equal. + (culture.Name != "zh-CN" && textElement == "\u3007" && decoded.Contains('0')) || + (culture.Name != "zh-CN" && textElement == "0" && decoded.Contains('\u3007')), + $"Values differ for text element {textElement}"); + } + } +#endif + [Theory] [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] public void Split(string decoded) { - Assert.Single(Split(decoded, Dummy)); - Assert.Single(Split(decoded, Dummy[0])); - string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); for (int i = 0; i < textElements.Length; i++) { - var result = Split(decoded, textElements[i]); + var result = SplitHelper(decoded, textElements[i]); Assert.True(result.Length > 1); } - - static string[] Split(string str, params T[] separators) => - separators switch - { - char[] chars => str.Split(chars, StringSplitOptions.None), - string[] strings => str.Split(strings, StringSplitOptions.None), - _ => throw new ArgumentException() - }; } + + private static string[] SplitHelper(string str, params T[] separators) => + separators switch + { + char[] chars => str.Split(chars, StringSplitOptions.None), + string[] strings => str.Split(strings, StringSplitOptions.None), + _ => throw new ArgumentException() + }; } From e281e13fcc7287f5c15c707f435ae323ed3b1111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Wed, 6 Aug 2025 12:38:10 -0500 Subject: [PATCH 04/10] Change parameter ordering --- .../GB18030/Tests/DirectoryTestBase.cs | 6 +-- .../GB18030/Tests/StringTests.cs | 40 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs index b03e58cf6c46a5..c62f85f53245a6 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs @@ -35,12 +35,12 @@ public void Create(string gb18030Line) } public static IEnumerable Delete_TestData() => - new int[] { 0, 2, 8 }.SelectMany(recurseLevel => - TestHelper.s_nonExceedingPathNameMaxDecodedTestData.Select(testData => new object[] { recurseLevel, testData })); + TestHelper.s_nonExceedingPathNameMaxDecodedTestData.SelectMany(testData => + new int[] { 0, 2, 8 }.Select(recurseLevel => new object[] { testData, recurseLevel })); [Theory] [MemberData(nameof(Delete_TestData))] - public void Delete(int recurseLevel, string gb18030Line) + public void Delete(string gb18030Line, int recurseLevel) { string firstPath = Path.Combine(TempDirectory.FullName, gb18030Line); string nestedDirPath = Path.Combine(firstPath, Path.Combine(Enumerable.Repeat(gb18030Line, recurseLevel).ToArray())); diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs index 4e9633314abd06..7bf1544218c371 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs @@ -27,13 +27,13 @@ public unsafe void Ctor(byte[] encoded) } public static IEnumerable Compare_TestData() => + TestHelper.s_decodedTestData.SelectMany(testData => TestHelper.s_cultures.SelectMany(culture => - TestHelper.s_compareOptions.SelectMany(option => - TestHelper.s_decodedTestData.Select(testData => new object[] { culture, option, testData }))); + TestHelper.s_compareOptions.Select(option => new object[] { testData, culture, option }))); [Theory] [MemberData(nameof(Compare_TestData))] - public void Compare(CultureInfo culture, CompareOptions option, string decoded) + public void Compare(string decoded, CultureInfo culture, CompareOptions option) { #pragma warning disable 0618 // suppress obsolete warning for String.Copy string copy = string.Copy(decoded); @@ -42,13 +42,13 @@ public void Compare(CultureInfo culture, CompareOptions option, string decoded) } public static IEnumerable Contains_TestData() => - TestHelper.s_nonOrdinalStringComparisons.SelectMany(comparison => - TestHelper.s_decodedTestData.Select(testData => new object[] { comparison, testData })); + TestHelper.s_decodedTestData.SelectMany(testData => + TestHelper.s_nonOrdinalStringComparisons.Select(comparison => new object[] { testData, comparison })); [Theory] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] [MemberData(nameof(Contains_TestData))] - public void Contains(StringComparison comparison, string decoded) + public void Contains(string decoded, StringComparison comparison) { string current = string.Empty; foreach (string element in TestHelper.GetTextElements(decoded)) @@ -66,12 +66,12 @@ public void Contains(StringComparison comparison, string decoded) } public static IEnumerable StringComparison_TestData() => - TestHelper.s_nonOrdinalStringComparisons.SelectMany(comparison => - TestHelper.s_decodedTestData.Select(decoded => new object[] { comparison, decoded })); + TestHelper.s_decodedTestData.SelectMany(decoded => + TestHelper.s_nonOrdinalStringComparisons.Select(comparison => new object[] { decoded, comparison })); [Theory] [MemberData(nameof(StringComparison_TestData))] - public void String_Equals(StringComparison comparison, string decoded) + public void String_Equals(string decoded, StringComparison comparison) { #pragma warning disable 0618 // suppress obsolete warning for String.Copy string copy = string.Copy(decoded); @@ -90,12 +90,12 @@ public void String_Equals(StringComparison comparison, string decoded) } public static IEnumerable EndsStartsWith_TestData() => - TestHelper.s_cultures.SelectMany(culture => - TestHelper.s_decodedTestData.Select(testData => new object[] { culture, testData })); + TestHelper.s_decodedTestData.SelectMany(testData => + TestHelper.s_cultures.Select(culture => new object[] { testData, culture })); [Theory] [MemberData(nameof(EndsStartsWith_TestData))] - public void EndsWith(CultureInfo culture, string decoded) + public void EndsWith(string decoded, CultureInfo culture) { string suffix = string.Empty; foreach (string textElement in TestHelper.GetTextElements(decoded).Reverse()) @@ -107,7 +107,7 @@ public void EndsWith(CultureInfo culture, string decoded) [Theory] [MemberData(nameof(EndsStartsWith_TestData))] - public void StartsWith(CultureInfo culture, string decoded) + public void StartsWith(string decoded, CultureInfo culture) { string prefix = string.Empty; foreach (string textElement in TestHelper.GetTextElements(decoded)) @@ -119,7 +119,7 @@ public void StartsWith(CultureInfo culture, string decoded) [Theory] [MemberData(nameof(StringComparison_TestData))] - public void IndexOf_MultipleElements(StringComparison comparison, string decoded) + public void IndexOf_MultipleElements(string decoded, StringComparison comparison) { Assert.NotEqual(StringComparison.Ordinal, comparison); Assert.NotEqual(StringComparison.OrdinalIgnoreCase, comparison); @@ -138,7 +138,7 @@ public void IndexOf_MultipleElements(StringComparison comparison, string decoded [Theory] [MemberData(nameof(StringComparison_TestData))] - public void IndexOf_SingleElement(StringComparison comparison, string decoded) + public void IndexOf_SingleElement(string decoded, StringComparison comparison) { Assert.NotEqual(StringComparison.Ordinal, comparison); Assert.NotEqual(StringComparison.OrdinalIgnoreCase, comparison); @@ -175,7 +175,7 @@ void IndexOf_SingleElement_Slow(string current, int expectedIndex) [Theory] [MemberData(nameof(StringComparison_TestData))] - public void LastIndexOf_MultipleElements(StringComparison comparison, string decoded) + public void LastIndexOf_MultipleElements(string decoded, StringComparison comparison) { Assert.NotEqual(StringComparison.Ordinal, comparison); Assert.NotEqual(StringComparison.OrdinalIgnoreCase, comparison); @@ -198,7 +198,7 @@ public void LastIndexOf_MultipleElements(StringComparison comparison, string dec [Theory] [MemberData(nameof(StringComparison_TestData))] - public void LastIndexOf_SingleElement(StringComparison comparison, string decoded) + public void LastIndexOf_SingleElement(string decoded, StringComparison comparison) { Assert.NotEqual(StringComparison.Ordinal, comparison); Assert.NotEqual(StringComparison.OrdinalIgnoreCase, comparison); @@ -254,13 +254,13 @@ public void Replace(string decoded) } public static IEnumerable Replace_NetCore_TestData() => - TestHelper.s_cultures.SelectMany(culture => - TestHelper.s_decodedTestData.Select(testData => new object[] { culture, testData })); + TestHelper.s_decodedTestData.SelectMany(testData => + TestHelper.s_cultures.Select(culture => new object[] { testData, culture })); #if NETCOREAPP [Theory] [MemberData(nameof(Replace_NetCore_TestData))] - public void Replace_CultureInfo(CultureInfo culture, string decoded) + public void Replace_CultureInfo(string decoded, CultureInfo culture) { Assert.False(decoded.Contains(Dummy)); From b7af2dc27881e6fefba09d50bf22f85220929eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Wed, 6 Aug 2025 17:29:39 -0500 Subject: [PATCH 05/10] Add RegexTests --- src/libraries/Common/Common.Tests.slnx | 1 + .../ComplianceTests/Compliance.Tests.csproj | 23 +++- .../GB18030/Tests/RegexTests.cs | 122 ++++++++++++++++++ .../FunctionalTests/Regex.Tests.Common.cs | 2 +- .../RegexGeneratorHelper.netcoreapp.cs | 10 +- 5 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs diff --git a/src/libraries/Common/Common.Tests.slnx b/src/libraries/Common/Common.Tests.slnx index 154e0a5b1a48f0..218fb5ae746858 100644 --- a/src/libraries/Common/Common.Tests.slnx +++ b/src/libraries/Common/Common.Tests.slnx @@ -2,6 +2,7 @@ + diff --git a/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj b/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj index 840c426ec005d8..2403110d454bfe 100644 --- a/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj +++ b/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj @@ -16,13 +16,30 @@ + + + + + + + + + + + + + + - - Always - + diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs new file mode 100644 index 00000000000000..37eca98d56d96e --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using System.Text.RegularExpressions.Tests; +using System.Text.Unicode; +using System.Threading.Tasks; +using Xunit; + +namespace GB18030.Tests; + +/// +/// Regex does not support surrogate pairs, which drastically reduces the number of characters in GB18030 that can be tested. +/// +public class RegexTests +{ + // Ranges added in GB18030-2020 + private static readonly UnicodeRange s_cjkNewRange = UnicodeRange.Create((char)0x9FF0, (char)0x9FFF); + private static readonly UnicodeRange s_cjkExtensionANewRange = UnicodeRange.Create((char)0x4DB6, (char)0x4DBF); + + private static readonly IEnumerable s_cjkNewCharacters = Enumerable.Range(s_cjkNewRange.FirstCodePoint, s_cjkNewRange.Length).Select(c => ((char)c).ToString()); + private static readonly IEnumerable s_cjkExtensionANewCharacters = Enumerable.Range(s_cjkExtensionANewRange.FirstCodePoint, s_cjkExtensionANewRange.Length).Select(c => ((char)c).ToString()); + private static readonly IEnumerable s_allNewCharacters = s_cjkNewCharacters.Union(s_cjkExtensionANewCharacters); + + // https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#supported-named-blocks + private static readonly Dictionary s_rangeToRegexMap = new() + { + { s_cjkNewRange, ("IsCJKUnifiedIdeographs", s_cjkNewCharacters.ToArray()) }, + { s_cjkExtensionANewRange, ("IsCJKUnifiedIdeographsExtensionA", s_cjkExtensionANewCharacters.ToArray()) } + }; + + public static IEnumerable UnicodeCategories_TestData() => + RegexHelpers.AvailableEngines.SelectMany(engine => + TestHelper.s_cultures.Select(culture => new object[] { engine, culture })); + + [Theory] + [MemberData(nameof(UnicodeCategories_TestData))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2522617")] + public async Task UnicodeCategory_InclusionAsync(RegexEngine engine, CultureInfo culture) + { + Regex r = await RegexHelpers.GetRegexAsync(engine, @"\p{Lo}", RegexOptions.None, culture); + foreach (string element in s_allNewCharacters) + Assert.Matches(r, element); + + r = await RegexHelpers.GetRegexAsync(engine, @"[\p{Lo}]", RegexOptions.None, culture); + foreach (string element in s_allNewCharacters) + Assert.Matches(r, element); + + r = await RegexHelpers.GetRegexAsync(engine, @"\p{L}", RegexOptions.None, culture); + foreach (string element in s_allNewCharacters) + Assert.Matches(r, element); + + r = await RegexHelpers.GetRegexAsync(engine, @"[\p{L}]", RegexOptions.None, culture); + foreach (string element in s_allNewCharacters) + Assert.Matches(r, element); + } + + [Theory] + [MemberData(nameof(UnicodeCategories_TestData))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2522617")] + public async Task UnicodeCategory_ExclusionAsync(RegexEngine engine, CultureInfo culture) + { + Regex r = await RegexHelpers.GetRegexAsync(engine, @"\P{Lo}", RegexOptions.None, culture); + foreach (string element in s_allNewCharacters) + Assert.DoesNotMatch(r, element); + + r = await RegexHelpers.GetRegexAsync(engine, @"[^\p{Lo}]", RegexOptions.None, culture); + foreach (string element in s_allNewCharacters) + Assert.DoesNotMatch(r, element); + + r = await RegexHelpers.GetRegexAsync(engine, @"\P{L}", RegexOptions.None, culture); + foreach (string element in s_allNewCharacters) + Assert.DoesNotMatch(r, element); + + r = await RegexHelpers.GetRegexAsync(engine, @"[^\p{L}]", RegexOptions.None, culture); + foreach (string element in s_allNewCharacters) + Assert.DoesNotMatch(r, element); + } + + public static IEnumerable NamedBlock_TestData() => + s_rangeToRegexMap.SelectMany(rangeKvp => + RegexHelpers.AvailableEngines.SelectMany(engine => + TestHelper.s_cultures.Select(culture => new object[] { rangeKvp.Key, engine, culture }))); + + [Theory] + [MemberData(nameof(NamedBlock_TestData))] + public async Task NamedBlock_InclusionAsync(UnicodeRange range, RegexEngine engine, CultureInfo culture) + { + (string namedBlock, string[] charactersInRange) = s_rangeToRegexMap[range]; + + Regex r = await RegexHelpers.GetRegexAsync(engine, $@"\p{{{namedBlock}}}", RegexOptions.None, culture); + foreach (string element in charactersInRange) + Assert.Matches(r, element); + + r = await RegexHelpers.GetRegexAsync(engine, $@"[\p{{{namedBlock}}}]", RegexOptions.None, culture); + foreach (string element in charactersInRange) + Assert.Matches(r, element); + } + + [Theory] + [MemberData(nameof(NamedBlock_TestData))] + public async Task NamedBlock_ExclusionAsync(UnicodeRange range, RegexEngine engine, CultureInfo culture) + { + (string namedBlock, string[] charactersInRange) = s_rangeToRegexMap[range]; + + Regex r = await RegexHelpers.GetRegexAsync(engine, $@"\P{{{namedBlock}}}", RegexOptions.None, culture); + foreach (string element in charactersInRange) + { + Assert.DoesNotMatch(r, element); + } + + r = await RegexHelpers.GetRegexAsync(engine, $@"[^\p{{{namedBlock}}}]", RegexOptions.None, culture); + foreach (string element in charactersInRange) + { + Assert.DoesNotMatch(r, element); + } + } +} diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs index b0a9b6549492bc..ff80171c30fa1d 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs @@ -236,6 +236,6 @@ public CaptureData(string value, int index, int length, CaptureData[] captures) public string Value { get; } public int Index { get; } public int Length { get; } - public CaptureData[] Captures { get; } + public CaptureData[]? Captures { get; } } } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs index 677bf16d32ff0a..21db5608cd2168 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs @@ -18,7 +18,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; -using Xunit; +using Xunit; namespace System.Text.RegularExpressions.Tests { @@ -46,7 +46,7 @@ private static MetadataReference[] CreateReferences() return new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(corelibPath), "System.Runtime.dll")), + MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(corelibPath)!, "System.Runtime.dll")), MetadataReference.CreateFromFile(typeof(Unsafe).Assembly.Location), MetadataReference.CreateFromFile(typeof(Regex).Assembly.Location), }; @@ -175,7 +175,7 @@ internal static async Task SourceGenRegexAsync( code.Append($" [GeneratedRegex({SymbolDisplay.FormatLiteral(regex.pattern, quote: true)}"); if (regex.options is not null) { - code.Append($", {string.Join(" | ", regex.options.ToString().Split(',').Select(o => $"RegexOptions.{o.Trim()}"))}"); + code.Append($", {string.Join(" | ", regex.options.ToString()!.Split(',').Select(o => $"RegexOptions.{o.Trim()}"))}"); if (regex.matchTimeout is not null) { code.Append(string.Create(CultureInfo.InvariantCulture, $", {(int)regex.matchTimeout.Value.TotalMilliseconds}")); @@ -215,7 +215,7 @@ internal static async Task SourceGenRegexAsync( .AddDocument("RegexGenerator.g.cs", SourceText.From("// Empty", Encoding.UTF8)).Project; Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); - s_compilation = comp = await proj!.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); + s_compilation = comp = (await proj!.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false))!; Debug.Assert(comp is not null); } @@ -265,7 +265,7 @@ internal static async Task SourceGenRegexAsync( for (int i = 0; i < instances.Length; i++) { string memberName = $"Get{i}"; - instances[i] = (Regex)(c.GetMethod(memberName) ?? c.GetProperty(memberName).GetGetMethod())!.Invoke(null, null)!; + instances[i] = (Regex)(c.GetMethod(memberName) ?? c.GetProperty(memberName)!.GetGetMethod())!.Invoke(null, null)!; } // Issue an unload on the ALC, so it'll be collected once the Regex instance is collected From 3fd1b0f007db42cfa52d808ceb4b704e58d18c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Thu, 7 Aug 2025 22:16:40 -0500 Subject: [PATCH 06/10] Add CharTests --- .../ComplianceTests/Compliance.Tests.csproj | 1 + .../ComplianceTests/GB18030/TestHelper.cs | 1 + .../GB18030/Tests/CharTests.cs | 52 +++++++++++++++++++ .../GB18030/Tests/RegexTests.cs | 1 - 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs diff --git a/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj b/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj index 2403110d454bfe..99898c8292d235 100644 --- a/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj +++ b/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs index 7b261cf94f86ab..94f2e95ba372c2 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs @@ -78,6 +78,7 @@ internal static Encoding GB18030Encoding public static IEnumerable EncodedTestData { get; } = s_encodedTestData.Select(data => new object[] { data }); public static IEnumerable DecodedTestData { get; } = s_decodedTestData.Select(data => new object[] { data }); + public static IEnumerable SplitNewLineDecodedTestData { get; } = s_splitNewLineDecodedTestData.Select(data => new object[] { data }); public static IEnumerable NonExceedingPathNameMaxDecodedTestData { get; } = s_nonExceedingPathNameMaxDecodedTestData.Select(data => new object[] { data }); private static IEnumerable GetTestData() diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs new file mode 100644 index 00000000000000..a41ffb2b29981d --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using Xunit; + +namespace GB18030.Tests; + +public class CharTests +{ + [Theory] + [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + public void ConvertRoundtrips(string decoded) + { + foreach (string element in TestHelper.GetTextElements(decoded)) + { + Assert.Equal(element, char.ConvertFromUtf32(char.ConvertToUtf32(element, 0))); + } + } + + [Theory] + [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + public void Surrogate(string decoded) + { + foreach (string element in TestHelper.GetTextElements(decoded).Where(e => e.Length > 1)) + { + Assert.Equal(2, element.Length); + char high = element[0]; + char low = element[1]; + Assert.True(char.IsSurrogate(low)); + Assert.True(char.IsSurrogate(high)); + Assert.True(char.IsLowSurrogate(low)); + Assert.True(char.IsHighSurrogate(high)); + Assert.True(char.IsSurrogatePair(high, low)); + } + } + + [Theory] + [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] + public void NonSurrogate(string decoded) + { + foreach (string element in TestHelper.GetTextElements(decoded).Where(e => e.Length <= 1)) + { + Assert.Equal(1, element.Length); + char c = element[0]; + Assert.False(char.IsSurrogate(c)); + Assert.False(char.IsLowSurrogate(c)); + Assert.False(char.IsHighSurrogate(c)); + Assert.False(char.IsSurrogatePair(c, c)); + } + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs index 37eca98d56d96e..725d8b38ff0392 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs @@ -26,7 +26,6 @@ public class RegexTests private static readonly IEnumerable s_cjkExtensionANewCharacters = Enumerable.Range(s_cjkExtensionANewRange.FirstCodePoint, s_cjkExtensionANewRange.Length).Select(c => ((char)c).ToString()); private static readonly IEnumerable s_allNewCharacters = s_cjkNewCharacters.Union(s_cjkExtensionANewCharacters); - // https://learn.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#supported-named-blocks private static readonly Dictionary s_rangeToRegexMap = new() { { s_cjkNewRange, ("IsCJKUnifiedIdeographs", s_cjkNewCharacters.ToArray()) }, From c5bd64cf726a92cf690e90729181d68e4e824a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Mon, 11 Aug 2025 11:43:54 -0500 Subject: [PATCH 07/10] Add CharUnicodeInfoTests, more CharTests, and cleanup RegexTests --- .../ComplianceTests/Compliance.Tests.csproj | 22 ++-- .../ComplianceTests/GB18030/TestHelper.cs | 50 ++++++-- .../GB18030/Tests/CharTests.cs | 117 +++++++++++++++--- .../GB18030/Tests/CharUnicodeInfoTests.cs | 27 ++++ .../GB18030/Tests/FileTests.cs | 6 +- .../GB18030/Tests/RegexTests.cs | 69 +++++------ .../GB18030/Tests/StringTests.cs | 12 +- .../Globalization/CharUnicodeInfoTestData.cs | 6 +- 8 files changed, 223 insertions(+), 86 deletions(-) create mode 100644 src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs diff --git a/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj b/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj index 99898c8292d235..386a669efe325c 100644 --- a/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj +++ b/src/libraries/Common/tests/ComplianceTests/Compliance.Tests.csproj @@ -4,11 +4,13 @@ enable true true + 16.0 + @@ -19,18 +21,14 @@ - + + - + - + @@ -43,4 +41,12 @@ + + + + CharUnicodeInfo\UnicodeData.$(UnicodeUcdVersion).txt + UnicodeData.txt + + + diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs index 94f2e95ba372c2..200ca4b58bbb21 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs @@ -4,30 +4,64 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Globalization.Tests; using System.IO; using System.Linq; using System.Text; +using Xunit; namespace GB18030.Tests; public static class TestHelper { - internal static CultureInfo[] s_cultures = [ + // New Code Points in existing ranges + internal static IEnumerable CjkNewCodePoints { get; } = CreateRange(0x9FF0, 0x9FFF); + internal static IEnumerable CjkExtensionANewCodePoints { get; } = CreateRange(0x4DB6, 0x4DBF); + internal static IEnumerable CjkExtensionBNewCodePoints { get; } = CreateRange(0x2A6D7, 0x2A6DF); + internal static IEnumerable CjkExtensionCNewCodePoints { get; } = CreateRange(0x2B735, 0x2B739); + + // New ranges + internal static IEnumerable CjkExtensionG { get; } = CreateRange(0x30000, 0x3134A); + internal static IEnumerable CjkExtensionH { get; } = CreateRange(0x31350, 0x323AF); + internal static IEnumerable CjkExtensionI { get; } = CreateRange(0x2EBF0, 0x2EE5D); + + private static IEnumerable CreateRange(int first, int last) => Enumerable.Range(first, last - first + 1); + + private static IEnumerable s_gb18030CharUnicodeInfo { get; } = GetGB18030CharUnicodeInfo(); + private static IEnumerable GetGB18030CharUnicodeInfo() + { + const int CodePointsTotal = 9793; // Make sure a Unicode version downgrade doesn't make us lose coverage. + + var ret = CharUnicodeInfoTestData.TestCases.Where(tc => IsInGB18030Range(tc.CodePoint)); + Assert.Equal(CodePointsTotal, ret.Count()); + return ret; + + static bool IsInGB18030Range(int codePoint) + => (codePoint >= 0x9FF0 && codePoint <= 0x9FFF) || + (codePoint >= 0x4DB6 && codePoint <= 0x4DBF) || + (codePoint >= 0x2A6D7 && codePoint <= 0x2A6DF) || + (codePoint >= 0x2B735 && codePoint <= 0x2B739) || + (codePoint >= 0x30000 && codePoint <= 0x3134A) || + (codePoint >= 0x31350 && codePoint <= 0x323AF) || + (codePoint >= 0x2EBF0 && codePoint <= 0x2EE5D); + } + + internal static CultureInfo[] Cultures { get; } = [ CultureInfo.CurrentCulture, CultureInfo.InvariantCulture, new CultureInfo("zh-CN")]; - internal static CompareOptions[] s_compareOptions = [ - CompareOptions.None, - CompareOptions.IgnoreCase]; + internal static CompareOptions[] CompareOptions { get; } = [ + System.Globalization.CompareOptions.None, + System.Globalization.CompareOptions.IgnoreCase]; - internal static readonly StringComparison[] s_nonOrdinalStringComparisons = [ + internal static StringComparison[] NonOrdinalStringComparisons { get; } = [ StringComparison.CurrentCulture, StringComparison.CurrentCultureIgnoreCase, StringComparison.InvariantCulture, StringComparison.InvariantCultureIgnoreCase]; - internal static string s_testDataFilePath = Path.Combine(AppContext.BaseDirectory, "GB18030", "Level3+Amendment_Test_Data_for_Mid_to_High_Volume_cases.txt"); + internal static string TestDataFilePath { get; } = Path.Combine(AppContext.BaseDirectory, "GB18030", "Level3+Amendment_Test_Data_for_Mid_to_High_Volume_cases.txt"); private static Encoding? s_gb18030Encoding; internal static Encoding GB18030Encoding @@ -78,8 +112,8 @@ internal static Encoding GB18030Encoding public static IEnumerable EncodedTestData { get; } = s_encodedTestData.Select(data => new object[] { data }); public static IEnumerable DecodedTestData { get; } = s_decodedTestData.Select(data => new object[] { data }); - public static IEnumerable SplitNewLineDecodedTestData { get; } = s_splitNewLineDecodedTestData.Select(data => new object[] { data }); public static IEnumerable NonExceedingPathNameMaxDecodedTestData { get; } = s_nonExceedingPathNameMaxDecodedTestData.Select(data => new object[] { data }); + public static IEnumerable GB18030CharUnicodeInfoTestData { get; } = s_gb18030CharUnicodeInfo.Select(data => new object[] { data }); private static IEnumerable GetTestData() { @@ -87,7 +121,7 @@ private static IEnumerable GetTestData() byte[] endDelimiter = GB18030Encoding.GetBytes($"{Environment.NewLine}{Environment.NewLine}"); // Instead of inlining the data in source, parse the test data from the file to prevent encoding issues. - ReadOnlyMemory testFileBytes = File.ReadAllBytes(s_testDataFilePath); + ReadOnlyMemory testFileBytes = File.ReadAllBytes(TestDataFilePath); while (testFileBytes.Length > 0) { diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs index a41ffb2b29981d..a8a1c1e4e77e0a 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs @@ -1,52 +1,131 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Globalization; +using System.Globalization.Tests; using System.Linq; +using System.Xml.Linq; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Xunit; +using Xunit.Sdk; namespace GB18030.Tests; public class CharTests { [Theory] - [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] - public void ConvertRoundtrips(string decoded) + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + public void Convert(CharUnicodeInfoTestCase testCase) { - foreach (string element in TestHelper.GetTextElements(decoded)) + Assert.Equal(testCase.CodePoint, char.ConvertToUtf32(char.ConvertFromUtf32(testCase.CodePoint), 0)); + + string utf32String = testCase.Utf32CodeValue; + if (char.IsSurrogate(utf32String[0])) + { + Assert.Equal(2, utf32String.Length); + Assert.Equal(testCase.CodePoint, char.ConvertToUtf32(utf32String[0], utf32String[1])); + } + } + + [Theory] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + public void Parse(CharUnicodeInfoTestCase testCase) + { + string utf32String = testCase.Utf32CodeValue; + if (utf32String.Length > 1) { - Assert.Equal(element, char.ConvertFromUtf32(char.ConvertToUtf32(element, 0))); + Assert.False(char.TryParse(utf32String, out _)); + return; } + + char c = char.Parse(utf32String); + Assert.Equal(testCase.CodePoint, c); + + bool succeed = char.TryParse(utf32String, out c); + Assert.True(succeed); + Assert.Equal(testCase.CodePoint, c); } [Theory] - [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] - public void Surrogate(string decoded) + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + public void IsSurrogate(CharUnicodeInfoTestCase testCase) { - foreach (string element in TestHelper.GetTextElements(decoded).Where(e => e.Length > 1)) + string utf32String = testCase.Utf32CodeValue; + if (utf32String.Length > 1) { - Assert.Equal(2, element.Length); - char high = element[0]; - char low = element[1]; + Assert.Equal(2, utf32String.Length); + char high = utf32String[0]; + char low = utf32String[1]; Assert.True(char.IsSurrogate(low)); Assert.True(char.IsSurrogate(high)); Assert.True(char.IsLowSurrogate(low)); Assert.True(char.IsHighSurrogate(high)); Assert.True(char.IsSurrogatePair(high, low)); } - } - - [Theory] - [MemberData(nameof(TestHelper.SplitNewLineDecodedTestData), MemberType = typeof(TestHelper))] - public void NonSurrogate(string decoded) - { - foreach (string element in TestHelper.GetTextElements(decoded).Where(e => e.Length <= 1)) + else { - Assert.Equal(1, element.Length); - char c = element[0]; + char c = utf32String[0]; Assert.False(char.IsSurrogate(c)); Assert.False(char.IsLowSurrogate(c)); Assert.False(char.IsHighSurrogate(c)); Assert.False(char.IsSurrogatePair(c, c)); } } + + [Theory] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + public void IsLetter(CharUnicodeInfoTestCase testCase) + { + string utf32String = testCase.Utf32CodeValue; + Assert.True(char.IsLetter(utf32String, 0)); + Assert.True(char.IsLetterOrDigit(utf32String, 0)); + + if (utf32String.Length < 2) + { + Assert.True(char.IsLetter(utf32String[0])); + Assert.True(char.IsLetterOrDigit(utf32String[0])); + } + } + + + [Theory] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + public void IsNonLetter_False(CharUnicodeInfoTestCase testCase) + { + string utf32String = testCase.Utf32CodeValue; + Assert.False(char.IsControl(utf32String, 0)); + Assert.False(char.IsDigit(utf32String, 0)); + Assert.False(char.IsLower(utf32String, 0)); + Assert.False(char.IsNumber(utf32String, 0)); + Assert.False(char.IsPunctuation(utf32String, 0)); + Assert.False(char.IsSeparator(utf32String, 0)); + Assert.False(char.IsSymbol(utf32String, 0)); + Assert.False(char.IsUpper(utf32String, 0)); + Assert.False(char.IsWhiteSpace(utf32String, 0)); + + if (utf32String.Length < 2) + { + char c = utf32String[0]; +#if !NETFRAMEWORK + Assert.False(char.IsAscii(c)); + Assert.False(char.IsAsciiDigit(c)); + Assert.False(char.IsAsciiHexDigit(c)); + Assert.False(char.IsAsciiHexDigitLower(c)); + Assert.False(char.IsAsciiHexDigitUpper(c)); + Assert.False(char.IsAsciiLetter(c)); + Assert.False(char.IsAsciiLetterOrDigit(c)); + Assert.False(char.IsAsciiLetterLower(c)); + Assert.False(char.IsAsciiLetterUpper(c)); +#endif + Assert.False(char.IsControl(c)); + Assert.False(char.IsDigit(c)); + Assert.False(char.IsLower(c)); + Assert.False(char.IsNumber(c)); + Assert.False(char.IsPunctuation(c)); + Assert.False(char.IsSeparator(c)); + Assert.False(char.IsSymbol(c)); + Assert.False(char.IsUpper(c)); + Assert.False(char.IsWhiteSpace(c)); + } + } } diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs new file mode 100644 index 00000000000000..90e9100731436e --- /dev/null +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Globalization.Tests; +using Xunit; + +namespace GB18030.Tests; + +public class CharUnicodeInfoTests +{ + [Theory] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + public void GetUnicodeCategory(CharUnicodeInfoTestCase testCase) + { + if (testCase.Utf32CodeValue.Length == 1) + { + Assert.Equal(UnicodeCategory.OtherLetter, CharUnicodeInfo.GetUnicodeCategory(testCase.Utf32CodeValue[0])); + } + + Assert.Equal(UnicodeCategory.OtherLetter, CharUnicodeInfo.GetUnicodeCategory(testCase.Utf32CodeValue, 0)); +#if !NETFRAMEWORK + Assert.Equal(UnicodeCategory.OtherLetter, CharUnicodeInfo.GetUnicodeCategory(testCase.CodePoint)); +#endif + Assert.Equal(UnicodeCategory.OtherLetter, testCase.GeneralCategory); + } +} diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs index 38ae3b3b55649a..80fd1bebc3a25b 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTests.cs @@ -10,7 +10,7 @@ namespace GB18030.Tests; public class FileTests : FileTestBase { - private static readonly byte[] s_expectedBytes = File.ReadAllBytes(TestHelper.s_testDataFilePath); + private static readonly byte[] s_expectedBytes = File.ReadAllBytes(TestHelper.TestDataFilePath); private static readonly string s_expectedText = TestHelper.GB18030Encoding.GetString(s_expectedBytes); protected override void CreateFile(string path) => File.Create(path).Dispose(); @@ -21,7 +21,7 @@ public class FileTests : FileTestBase [Fact] public void ReadAllText() { - Assert.Equal(s_expectedText, File.ReadAllText(TestHelper.s_testDataFilePath, TestHelper.GB18030Encoding)); + Assert.Equal(s_expectedText, File.ReadAllText(TestHelper.TestDataFilePath, TestHelper.GB18030Encoding)); } [Fact] @@ -29,7 +29,7 @@ public void ReadAllLines() { Assert.Equal( s_expectedText.Split([Environment.NewLine], StringSplitOptions.None), - File.ReadAllLines(TestHelper.s_testDataFilePath, TestHelper.GB18030Encoding)); + File.ReadAllLines(TestHelper.TestDataFilePath, TestHelper.GB18030Encoding)); } [Fact] diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs index 725d8b38ff0392..b2df87604c439a 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Text.RegularExpressions; using System.Text.RegularExpressions.Tests; -using System.Text.Unicode; using System.Threading.Tasks; using Xunit; @@ -18,23 +17,23 @@ namespace GB18030.Tests; /// public class RegexTests { - // Ranges added in GB18030-2020 - private static readonly UnicodeRange s_cjkNewRange = UnicodeRange.Create((char)0x9FF0, (char)0x9FFF); - private static readonly UnicodeRange s_cjkExtensionANewRange = UnicodeRange.Create((char)0x4DB6, (char)0x4DBF); + public enum RegexNamedBlock + { + IsCJKUnifiedIdeographs, + IsCJKUnifiedIdeographsExtensionA, + } - private static readonly IEnumerable s_cjkNewCharacters = Enumerable.Range(s_cjkNewRange.FirstCodePoint, s_cjkNewRange.Length).Select(c => ((char)c).ToString()); - private static readonly IEnumerable s_cjkExtensionANewCharacters = Enumerable.Range(s_cjkExtensionANewRange.FirstCodePoint, s_cjkExtensionANewRange.Length).Select(c => ((char)c).ToString()); - private static readonly IEnumerable s_allNewCharacters = s_cjkNewCharacters.Union(s_cjkExtensionANewCharacters); + private static readonly IEnumerable s_cjkAndCjkExtensionANewChars = TestHelper.CjkNewCodePoints.Union(TestHelper.CjkExtensionANewCodePoints).Select(c => ((char)c).ToString()); - private static readonly Dictionary s_rangeToRegexMap = new() + private static readonly List<(RegexNamedBlock, string[])> s_namedBlocks = new() { - { s_cjkNewRange, ("IsCJKUnifiedIdeographs", s_cjkNewCharacters.ToArray()) }, - { s_cjkExtensionANewRange, ("IsCJKUnifiedIdeographsExtensionA", s_cjkExtensionANewCharacters.ToArray()) } + (RegexNamedBlock.IsCJKUnifiedIdeographs, TestHelper.CjkNewCodePoints.Select(c => ((char)c).ToString()).ToArray()), + (RegexNamedBlock.IsCJKUnifiedIdeographsExtensionA, TestHelper.CjkExtensionANewCodePoints.Select(c => ((char)c).ToString()).ToArray()) }; public static IEnumerable UnicodeCategories_TestData() => RegexHelpers.AvailableEngines.SelectMany(engine => - TestHelper.s_cultures.Select(culture => new object[] { engine, culture })); + TestHelper.Cultures.Select(culture => new object[] { engine, culture })); [Theory] [MemberData(nameof(UnicodeCategories_TestData))] @@ -42,19 +41,19 @@ public static IEnumerable UnicodeCategories_TestData() => public async Task UnicodeCategory_InclusionAsync(RegexEngine engine, CultureInfo culture) { Regex r = await RegexHelpers.GetRegexAsync(engine, @"\p{Lo}", RegexOptions.None, culture); - foreach (string element in s_allNewCharacters) + foreach (string element in s_cjkAndCjkExtensionANewChars) Assert.Matches(r, element); r = await RegexHelpers.GetRegexAsync(engine, @"[\p{Lo}]", RegexOptions.None, culture); - foreach (string element in s_allNewCharacters) + foreach (string element in s_cjkAndCjkExtensionANewChars) Assert.Matches(r, element); r = await RegexHelpers.GetRegexAsync(engine, @"\p{L}", RegexOptions.None, culture); - foreach (string element in s_allNewCharacters) + foreach (string element in s_cjkAndCjkExtensionANewChars) Assert.Matches(r, element); r = await RegexHelpers.GetRegexAsync(engine, @"[\p{L}]", RegexOptions.None, culture); - foreach (string element in s_allNewCharacters) + foreach (string element in s_cjkAndCjkExtensionANewChars) Assert.Matches(r, element); } @@ -64,58 +63,50 @@ public async Task UnicodeCategory_InclusionAsync(RegexEngine engine, CultureInfo public async Task UnicodeCategory_ExclusionAsync(RegexEngine engine, CultureInfo culture) { Regex r = await RegexHelpers.GetRegexAsync(engine, @"\P{Lo}", RegexOptions.None, culture); - foreach (string element in s_allNewCharacters) + foreach (string element in s_cjkAndCjkExtensionANewChars) Assert.DoesNotMatch(r, element); r = await RegexHelpers.GetRegexAsync(engine, @"[^\p{Lo}]", RegexOptions.None, culture); - foreach (string element in s_allNewCharacters) + foreach (string element in s_cjkAndCjkExtensionANewChars) Assert.DoesNotMatch(r, element); r = await RegexHelpers.GetRegexAsync(engine, @"\P{L}", RegexOptions.None, culture); - foreach (string element in s_allNewCharacters) + foreach (string element in s_cjkAndCjkExtensionANewChars) Assert.DoesNotMatch(r, element); r = await RegexHelpers.GetRegexAsync(engine, @"[^\p{L}]", RegexOptions.None, culture); - foreach (string element in s_allNewCharacters) + foreach (string element in s_cjkAndCjkExtensionANewChars) Assert.DoesNotMatch(r, element); } public static IEnumerable NamedBlock_TestData() => - s_rangeToRegexMap.SelectMany(rangeKvp => + s_namedBlocks.SelectMany(namedBlock => RegexHelpers.AvailableEngines.SelectMany(engine => - TestHelper.s_cultures.Select(culture => new object[] { rangeKvp.Key, engine, culture }))); + TestHelper.Cultures.Select(culture => new object[] { namedBlock.Item2, namedBlock.Item1, engine, culture }))); [Theory] [MemberData(nameof(NamedBlock_TestData))] - public async Task NamedBlock_InclusionAsync(UnicodeRange range, RegexEngine engine, CultureInfo culture) + public async Task NamedBlock_InclusionAsync(string[]characters, RegexNamedBlock namedBlock, RegexEngine engine, CultureInfo culture) { - (string namedBlock, string[] charactersInRange) = s_rangeToRegexMap[range]; - Regex r = await RegexHelpers.GetRegexAsync(engine, $@"\p{{{namedBlock}}}", RegexOptions.None, culture); - foreach (string element in charactersInRange) - Assert.Matches(r, element); + foreach (string c in characters) + Assert.Matches(r, c); r = await RegexHelpers.GetRegexAsync(engine, $@"[\p{{{namedBlock}}}]", RegexOptions.None, culture); - foreach (string element in charactersInRange) - Assert.Matches(r, element); + foreach (string c in characters) + Assert.Matches(r, c); } [Theory] [MemberData(nameof(NamedBlock_TestData))] - public async Task NamedBlock_ExclusionAsync(UnicodeRange range, RegexEngine engine, CultureInfo culture) + public async Task NamedBlock_ExclusionAsync(string[] characters, RegexNamedBlock namedBlock, RegexEngine engine, CultureInfo culture) { - (string namedBlock, string[] charactersInRange) = s_rangeToRegexMap[range]; - Regex r = await RegexHelpers.GetRegexAsync(engine, $@"\P{{{namedBlock}}}", RegexOptions.None, culture); - foreach (string element in charactersInRange) - { - Assert.DoesNotMatch(r, element); - } + foreach (string c in characters) + Assert.DoesNotMatch(r, c); r = await RegexHelpers.GetRegexAsync(engine, $@"[^\p{{{namedBlock}}}]", RegexOptions.None, culture); - foreach (string element in charactersInRange) - { - Assert.DoesNotMatch(r, element); - } + foreach (string c in characters) + Assert.DoesNotMatch(r, c); } } diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs index 7bf1544218c371..d8d0c55b0b7a75 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs @@ -28,8 +28,8 @@ public unsafe void Ctor(byte[] encoded) public static IEnumerable Compare_TestData() => TestHelper.s_decodedTestData.SelectMany(testData => - TestHelper.s_cultures.SelectMany(culture => - TestHelper.s_compareOptions.Select(option => new object[] { testData, culture, option }))); + TestHelper.Cultures.SelectMany(culture => + TestHelper.CompareOptions.Select(option => new object[] { testData, culture, option }))); [Theory] [MemberData(nameof(Compare_TestData))] @@ -43,7 +43,7 @@ public void Compare(string decoded, CultureInfo culture, CompareOptions option) public static IEnumerable Contains_TestData() => TestHelper.s_decodedTestData.SelectMany(testData => - TestHelper.s_nonOrdinalStringComparisons.Select(comparison => new object[] { testData, comparison })); + TestHelper.NonOrdinalStringComparisons.Select(comparison => new object[] { testData, comparison })); [Theory] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] @@ -67,7 +67,7 @@ public void Contains(string decoded, StringComparison comparison) public static IEnumerable StringComparison_TestData() => TestHelper.s_decodedTestData.SelectMany(decoded => - TestHelper.s_nonOrdinalStringComparisons.Select(comparison => new object[] { decoded, comparison })); + TestHelper.NonOrdinalStringComparisons.Select(comparison => new object[] { decoded, comparison })); [Theory] [MemberData(nameof(StringComparison_TestData))] @@ -91,7 +91,7 @@ public void String_Equals(string decoded, StringComparison comparison) public static IEnumerable EndsStartsWith_TestData() => TestHelper.s_decodedTestData.SelectMany(testData => - TestHelper.s_cultures.Select(culture => new object[] { testData, culture })); + TestHelper.Cultures.Select(culture => new object[] { testData, culture })); [Theory] [MemberData(nameof(EndsStartsWith_TestData))] @@ -255,7 +255,7 @@ public void Replace(string decoded) public static IEnumerable Replace_NetCore_TestData() => TestHelper.s_decodedTestData.SelectMany(testData => - TestHelper.s_cultures.Select(culture => new object[] { testData, culture })); + TestHelper.Cultures.Select(culture => new object[] { testData, culture })); #if NETCOREAPP [Theory] diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/CharUnicodeInfoTestData.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/CharUnicodeInfoTestData.cs index 224d3c8df98ff2..2266e1145330cc 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/CharUnicodeInfoTestData.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/CharUnicodeInfoTestData.cs @@ -13,12 +13,12 @@ public static class CharUnicodeInfoTestData { List testCases = new List(); string fileName = "UnicodeData.txt"; - Stream stream = typeof(CharUnicodeInfoTestData).GetTypeInfo().Assembly.GetManifestResourceStream(fileName); + Stream stream = typeof(CharUnicodeInfoTestData).GetTypeInfo().Assembly.GetManifestResourceStream(fileName)!; using (StreamReader reader = new StreamReader(stream)) { while (!reader.EndOfStream) { - Parse(testCases, reader.ReadLine()); + Parse(testCases, reader.ReadLine()!); } } return testCases; @@ -153,7 +153,7 @@ public enum StrongBidiCategory public class CharUnicodeInfoTestCase { - public string Utf32CodeValue { get; set; } + public string Utf32CodeValue { get; set; } = null!; // Can't use required in net481. public int CodePoint { get; set; } public UnicodeCategory GeneralCategory { get; set; } public double NumericValue { get; set; } From 0b6baf1b60dba65d8254ab499a7fa5c01bb1b988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Mon, 11 Aug 2025 11:49:23 -0500 Subject: [PATCH 08/10] Rename fields and properties --- .../ComplianceTests/GB18030/TestHelper.cs | 16 ++++--- .../GB18030/Tests/CharTests.cs | 10 ++-- .../GB18030/Tests/CharUnicodeInfoTests.cs | 2 +- .../GB18030/Tests/ConsoleTests.cs | 6 +-- .../GB18030/Tests/DirectoryInfoTests.cs | 4 +- .../GB18030/Tests/DirectoryTestBase.cs | 10 ++-- .../GB18030/Tests/DirectoryTests.cs | 2 +- .../GB18030/Tests/EncodingTests.cs | 2 +- .../GB18030/Tests/FileTestBase.cs | 8 ++-- .../GB18030/Tests/RegexTests.cs | 12 ++--- .../GB18030/Tests/StringTests.cs | 46 +++++++++---------- 11 files changed, 60 insertions(+), 58 deletions(-) diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs index 200ca4b58bbb21..210d204453c303 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs @@ -81,11 +81,13 @@ internal static Encoding GB18030Encoding } private static readonly IEnumerable s_encodedTestData = GetTestData(); - internal static readonly IEnumerable s_decodedTestData = s_encodedTestData.Select(data => GB18030Encoding.GetString(data)); - private static readonly IEnumerable s_splitNewLineDecodedTestData = s_decodedTestData.SelectMany( + + internal static IEnumerable DecodedTestData { get; } = s_encodedTestData.Select(data => GB18030Encoding.GetString(data)); + + private static readonly IEnumerable s_splitNewLineDecodedTestData = DecodedTestData.SelectMany( data => data.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries)); - internal static readonly IEnumerable s_nonExceedingPathNameMaxDecodedTestData = + internal static IEnumerable NonExceedingPathNameMaxDecodedTestData { get; } = s_splitNewLineDecodedTestData.SelectMany( (data) => { @@ -110,10 +112,10 @@ internal static Encoding GB18030Encoding return result; }); - public static IEnumerable EncodedTestData { get; } = s_encodedTestData.Select(data => new object[] { data }); - public static IEnumerable DecodedTestData { get; } = s_decodedTestData.Select(data => new object[] { data }); - public static IEnumerable NonExceedingPathNameMaxDecodedTestData { get; } = s_nonExceedingPathNameMaxDecodedTestData.Select(data => new object[] { data }); - public static IEnumerable GB18030CharUnicodeInfoTestData { get; } = s_gb18030CharUnicodeInfo.Select(data => new object[] { data }); + public static IEnumerable EncodedMemberData { get; } = s_encodedTestData.Select(data => new object[] { data }); + public static IEnumerable DecodedMemberData { get; } = DecodedTestData.Select(data => new object[] { data }); + public static IEnumerable NonExceedingPathNameMaxDecodedMemberData { get; } = NonExceedingPathNameMaxDecodedTestData.Select(data => new object[] { data }); + public static IEnumerable GB18030CharUnicodeInfoMemberData { get; } = s_gb18030CharUnicodeInfo.Select(data => new object[] { data }); private static IEnumerable GetTestData() { diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs index a8a1c1e4e77e0a..364a49e273aa25 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs @@ -14,7 +14,7 @@ namespace GB18030.Tests; public class CharTests { [Theory] - [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] public void Convert(CharUnicodeInfoTestCase testCase) { Assert.Equal(testCase.CodePoint, char.ConvertToUtf32(char.ConvertFromUtf32(testCase.CodePoint), 0)); @@ -28,7 +28,7 @@ public void Convert(CharUnicodeInfoTestCase testCase) } [Theory] - [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] public void Parse(CharUnicodeInfoTestCase testCase) { string utf32String = testCase.Utf32CodeValue; @@ -47,7 +47,7 @@ public void Parse(CharUnicodeInfoTestCase testCase) } [Theory] - [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] public void IsSurrogate(CharUnicodeInfoTestCase testCase) { string utf32String = testCase.Utf32CodeValue; @@ -73,7 +73,7 @@ public void IsSurrogate(CharUnicodeInfoTestCase testCase) } [Theory] - [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] public void IsLetter(CharUnicodeInfoTestCase testCase) { string utf32String = testCase.Utf32CodeValue; @@ -89,7 +89,7 @@ public void IsLetter(CharUnicodeInfoTestCase testCase) [Theory] - [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] public void IsNonLetter_False(CharUnicodeInfoTestCase testCase) { string utf32String = testCase.Utf32CodeValue; diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs index 90e9100731436e..3aaf687c0be355 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs @@ -10,7 +10,7 @@ namespace GB18030.Tests; public class CharUnicodeInfoTests { [Theory] - [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] public void GetUnicodeCategory(CharUnicodeInfoTestCase testCase) { if (testCase.Utf32CodeValue.Length == 1) diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs index d96e586a14ce92..cd9a681ad47e9d 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/ConsoleTests.cs @@ -12,7 +12,7 @@ public class ConsoleTests protected static readonly int WaitInMS = 30 * 1000 * PlatformDetection.SlowRuntimeTimeoutModifier; [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.DecodedMemberData), MemberType = typeof(TestHelper))] public void StandardOutput(string decodedText) { var remoteOptions = new RemoteInvokeOptions(); @@ -33,7 +33,7 @@ public void StandardOutput(string decodedText) } [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.DecodedMemberData), MemberType = typeof(TestHelper))] public void StandardInput(string decodedText) { var remoteOptions = new RemoteInvokeOptions(); @@ -67,7 +67,7 @@ public void StandardInput(string decodedText) } [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.DecodedMemberData), MemberType = typeof(TestHelper))] public void StandardError(string decodedText) { var remoteOptions = new RemoteInvokeOptions(); diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs index bf35b8e96771bc..27fcfd69c677ee 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryInfoTests.cs @@ -15,7 +15,7 @@ public class DirectoryInfoTests : DirectoryTestBase protected override void MoveDirectory(string source, string destination) => new DirectoryInfo(source).MoveTo(destination); [Theory] - [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedMemberData), MemberType = typeof(TestHelper))] public void CreateSubdirectory(string gb18030Line) { var subDirInfo = TempDirectory.CreateSubdirectory(gb18030Line); @@ -26,7 +26,7 @@ public void CreateSubdirectory(string gb18030Line) } [Theory] - [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedMemberData), MemberType = typeof(TestHelper))] public void EnumerateFileSystemInfos(string gb18030Line) { string rootDir = TempDirectory.FullName; diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs index c62f85f53245a6..d922db43d988ad 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTestBase.cs @@ -23,7 +23,7 @@ public DirectoryTestBase() } [Theory] - [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedMemberData), MemberType = typeof(TestHelper))] public void Create(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); @@ -34,12 +34,12 @@ public void Create(string gb18030Line) Assert.Equal(gb18030Line, dirInfo.Name); } - public static IEnumerable Delete_TestData() => - TestHelper.s_nonExceedingPathNameMaxDecodedTestData.SelectMany(testData => + public static IEnumerable Delete_MemberData() => + TestHelper.NonExceedingPathNameMaxDecodedTestData.SelectMany(testData => new int[] { 0, 2, 8 }.Select(recurseLevel => new object[] { testData, recurseLevel })); [Theory] - [MemberData(nameof(Delete_TestData))] + [MemberData(nameof(Delete_MemberData))] public void Delete(string gb18030Line, int recurseLevel) { string firstPath = Path.Combine(TempDirectory.FullName, gb18030Line); @@ -55,7 +55,7 @@ public void Delete(string gb18030Line, int recurseLevel) } [Theory] - [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedMemberData), MemberType = typeof(TestHelper))] public void Move(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs index 0132ba27bb1d62..5f7a85186285b5 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/DirectoryTests.cs @@ -15,7 +15,7 @@ public class DirectoryTests : DirectoryTestBase protected override void MoveDirectory(string source, string destination) => Directory.Move(source, destination); [Theory] - [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedMemberData), MemberType = typeof(TestHelper))] public void EnumerateFileSystemEntries(string gb18030Line) { string rootDir = TempDirectory.FullName; diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs index f12ac3292e48a9..d285eadb9daa48 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/EncodingTests.cs @@ -9,7 +9,7 @@ namespace GB18030.Tests; public class EncodingTests { [Theory] - [MemberData(nameof(TestHelper.EncodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.EncodedMemberData), MemberType = typeof(TestHelper))] public void Roundtrips(byte[] testData) { Assert.True(testData.AsSpan().SequenceEqual( diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs index e1f45beef92e2c..981246bf7433d9 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/FileTestBase.cs @@ -27,7 +27,7 @@ public void Dispose() } [Theory] - [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedMemberData), MemberType = typeof(TestHelper))] public void Create(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); @@ -39,7 +39,7 @@ public void Create(string gb18030Line) } [Theory] - [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedMemberData), MemberType = typeof(TestHelper))] public void Delete(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); @@ -52,7 +52,7 @@ public void Delete(string gb18030Line) } [Theory] - [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedMemberData), MemberType = typeof(TestHelper))] public void Move(string gb18030Line) { string gb18030Path = Path.Combine(TempDirectory.FullName, gb18030Line); @@ -69,7 +69,7 @@ public void Move(string gb18030Line) } [Theory] - [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.NonExceedingPathNameMaxDecodedMemberData), MemberType = typeof(TestHelper))] public void Copy(string gb18030Line) { ReadOnlySpan sampleContent = "File_Copy"u8; diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs index b2df87604c439a..3f1602479944c0 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs @@ -31,12 +31,12 @@ public enum RegexNamedBlock (RegexNamedBlock.IsCJKUnifiedIdeographsExtensionA, TestHelper.CjkExtensionANewCodePoints.Select(c => ((char)c).ToString()).ToArray()) }; - public static IEnumerable UnicodeCategories_TestData() => + public static IEnumerable UnicodeCategories_MemberData() => RegexHelpers.AvailableEngines.SelectMany(engine => TestHelper.Cultures.Select(culture => new object[] { engine, culture })); [Theory] - [MemberData(nameof(UnicodeCategories_TestData))] + [MemberData(nameof(UnicodeCategories_MemberData))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2522617")] public async Task UnicodeCategory_InclusionAsync(RegexEngine engine, CultureInfo culture) { @@ -58,7 +58,7 @@ public async Task UnicodeCategory_InclusionAsync(RegexEngine engine, CultureInfo } [Theory] - [MemberData(nameof(UnicodeCategories_TestData))] + [MemberData(nameof(UnicodeCategories_MemberData))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2522617")] public async Task UnicodeCategory_ExclusionAsync(RegexEngine engine, CultureInfo culture) { @@ -79,13 +79,13 @@ public async Task UnicodeCategory_ExclusionAsync(RegexEngine engine, CultureInfo Assert.DoesNotMatch(r, element); } - public static IEnumerable NamedBlock_TestData() => + public static IEnumerable NamedBlock_MemberData() => s_namedBlocks.SelectMany(namedBlock => RegexHelpers.AvailableEngines.SelectMany(engine => TestHelper.Cultures.Select(culture => new object[] { namedBlock.Item2, namedBlock.Item1, engine, culture }))); [Theory] - [MemberData(nameof(NamedBlock_TestData))] + [MemberData(nameof(NamedBlock_MemberData))] public async Task NamedBlock_InclusionAsync(string[]characters, RegexNamedBlock namedBlock, RegexEngine engine, CultureInfo culture) { Regex r = await RegexHelpers.GetRegexAsync(engine, $@"\p{{{namedBlock}}}", RegexOptions.None, culture); @@ -98,7 +98,7 @@ public async Task NamedBlock_InclusionAsync(string[]characters, RegexNamedBlock } [Theory] - [MemberData(nameof(NamedBlock_TestData))] + [MemberData(nameof(NamedBlock_MemberData))] public async Task NamedBlock_ExclusionAsync(string[] characters, RegexNamedBlock namedBlock, RegexEngine engine, CultureInfo culture) { Regex r = await RegexHelpers.GetRegexAsync(engine, $@"\P{{{namedBlock}}}", RegexOptions.None, culture); diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs index d8d0c55b0b7a75..0e8280016637a7 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/StringTests.cs @@ -16,7 +16,7 @@ public class StringTests private const string Dummy = "\uFFFF"; [Theory] - [MemberData(nameof(TestHelper.EncodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.EncodedMemberData), MemberType = typeof(TestHelper))] public unsafe void Ctor(byte[] encoded) { fixed (sbyte* p = (sbyte[])(object)encoded) @@ -26,13 +26,13 @@ public unsafe void Ctor(byte[] encoded) } } - public static IEnumerable Compare_TestData() => - TestHelper.s_decodedTestData.SelectMany(testData => + public static IEnumerable Compare_MemberData() => + TestHelper.DecodedTestData.SelectMany(testData => TestHelper.Cultures.SelectMany(culture => TestHelper.CompareOptions.Select(option => new object[] { testData, culture, option }))); [Theory] - [MemberData(nameof(Compare_TestData))] + [MemberData(nameof(Compare_MemberData))] public void Compare(string decoded, CultureInfo culture, CompareOptions option) { #pragma warning disable 0618 // suppress obsolete warning for String.Copy @@ -41,13 +41,13 @@ public void Compare(string decoded, CultureInfo culture, CompareOptions option) Assert.True(string.Compare(decoded, copy, culture, option) == 0); } - public static IEnumerable Contains_TestData() => - TestHelper.s_decodedTestData.SelectMany(testData => + public static IEnumerable Contains_MemberData() => + TestHelper.DecodedTestData.SelectMany(testData => TestHelper.NonOrdinalStringComparisons.Select(comparison => new object[] { testData, comparison })); [Theory] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] - [MemberData(nameof(Contains_TestData))] + [MemberData(nameof(Contains_MemberData))] public void Contains(string decoded, StringComparison comparison) { string current = string.Empty; @@ -65,12 +65,12 @@ public void Contains(string decoded, StringComparison comparison) } } - public static IEnumerable StringComparison_TestData() => - TestHelper.s_decodedTestData.SelectMany(decoded => + public static IEnumerable StringComparison_MemberData() => + TestHelper.DecodedTestData.SelectMany(decoded => TestHelper.NonOrdinalStringComparisons.Select(comparison => new object[] { decoded, comparison })); [Theory] - [MemberData(nameof(StringComparison_TestData))] + [MemberData(nameof(StringComparison_MemberData))] public void String_Equals(string decoded, StringComparison comparison) { #pragma warning disable 0618 // suppress obsolete warning for String.Copy @@ -89,12 +89,12 @@ public void String_Equals(string decoded, StringComparison comparison) } } - public static IEnumerable EndsStartsWith_TestData() => - TestHelper.s_decodedTestData.SelectMany(testData => + public static IEnumerable EndsStartsWith_MemberData() => + TestHelper.DecodedTestData.SelectMany(testData => TestHelper.Cultures.Select(culture => new object[] { testData, culture })); [Theory] - [MemberData(nameof(EndsStartsWith_TestData))] + [MemberData(nameof(EndsStartsWith_MemberData))] public void EndsWith(string decoded, CultureInfo culture) { string suffix = string.Empty; @@ -106,7 +106,7 @@ public void EndsWith(string decoded, CultureInfo culture) } [Theory] - [MemberData(nameof(EndsStartsWith_TestData))] + [MemberData(nameof(EndsStartsWith_MemberData))] public void StartsWith(string decoded, CultureInfo culture) { string prefix = string.Empty; @@ -118,7 +118,7 @@ public void StartsWith(string decoded, CultureInfo culture) } [Theory] - [MemberData(nameof(StringComparison_TestData))] + [MemberData(nameof(StringComparison_MemberData))] public void IndexOf_MultipleElements(string decoded, StringComparison comparison) { Assert.NotEqual(StringComparison.Ordinal, comparison); @@ -137,7 +137,7 @@ public void IndexOf_MultipleElements(string decoded, StringComparison comparison } [Theory] - [MemberData(nameof(StringComparison_TestData))] + [MemberData(nameof(StringComparison_MemberData))] public void IndexOf_SingleElement(string decoded, StringComparison comparison) { Assert.NotEqual(StringComparison.Ordinal, comparison); @@ -174,7 +174,7 @@ void IndexOf_SingleElement_Slow(string current, int expectedIndex) } [Theory] - [MemberData(nameof(StringComparison_TestData))] + [MemberData(nameof(StringComparison_MemberData))] public void LastIndexOf_MultipleElements(string decoded, StringComparison comparison) { Assert.NotEqual(StringComparison.Ordinal, comparison); @@ -197,7 +197,7 @@ public void LastIndexOf_MultipleElements(string decoded, StringComparison compar } [Theory] - [MemberData(nameof(StringComparison_TestData))] + [MemberData(nameof(StringComparison_MemberData))] public void LastIndexOf_SingleElement(string decoded, StringComparison comparison) { Assert.NotEqual(StringComparison.Ordinal, comparison); @@ -234,7 +234,7 @@ void LastIndexOf_SingleElement_Slow(string current, int expectedIndex) } [Theory] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.DecodedMemberData), MemberType = typeof(TestHelper))] public void Replace(string decoded) { Assert.False(decoded.Contains(Dummy)); @@ -253,13 +253,13 @@ public void Replace(string decoded) } } - public static IEnumerable Replace_NetCore_TestData() => - TestHelper.s_decodedTestData.SelectMany(testData => + public static IEnumerable Replace_NetCore_MemberData() => + TestHelper.DecodedTestData.SelectMany(testData => TestHelper.Cultures.Select(culture => new object[] { testData, culture })); #if NETCOREAPP [Theory] - [MemberData(nameof(Replace_NetCore_TestData))] + [MemberData(nameof(Replace_NetCore_MemberData))] public void Replace_CultureInfo(string decoded, CultureInfo culture) { Assert.False(decoded.Contains(Dummy)); @@ -278,7 +278,7 @@ public void Replace_CultureInfo(string decoded, CultureInfo culture) #endif [Theory] - [MemberData(nameof(TestHelper.DecodedTestData), MemberType = typeof(TestHelper))] + [MemberData(nameof(TestHelper.DecodedMemberData), MemberType = typeof(TestHelper))] public void Split(string decoded) { string[] textElements = TestHelper.GetTextElements(decoded).ToArray(); From 126ff40bce7e894b5c760ab52248eb0cad790b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Mon, 11 Aug 2025 12:16:11 -0500 Subject: [PATCH 09/10] Make adjustments for linux and netfx --- .../ComplianceTests/GB18030/TestHelper.cs | 2 +- .../GB18030/Tests/CharTests.cs | 20 ++++++++++++++++++- .../GB18030/Tests/CharUnicodeInfoTests.cs | 11 ++++++---- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs index 210d204453c303..c1d04bfff98641 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/TestHelper.cs @@ -91,7 +91,7 @@ internal static Encoding GB18030Encoding s_splitNewLineDecodedTestData.SelectMany( (data) => { - const int MaxPathSegmentName = 255; + const int MaxPathSegmentName = 128; Encoding fileSystemEncoding = PlatformDetection.IsWindows ? Encoding.Unicode : Encoding.UTF8; if (fileSystemEncoding.GetByteCount(data) <= MaxPathSegmentName) diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs index 364a49e273aa25..fb237e300edc0c 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Globalization; using System.Globalization.Tests; using System.Linq; @@ -72,7 +73,8 @@ public void IsSurrogate(CharUnicodeInfoTestCase testCase) } } - [Theory] + [ConditionalTheory] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] public void IsLetter(CharUnicodeInfoTestCase testCase) { @@ -87,6 +89,22 @@ public void IsLetter(CharUnicodeInfoTestCase testCase) } } + [ConditionalTheory] + [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)] + [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] + public void IsLetter_NetFramework(CharUnicodeInfoTestCase testCase) + { + string utf32String = testCase.Utf32CodeValue; + Assert.False(char.IsLetter(utf32String, 0)); + Assert.False(char.IsLetterOrDigit(utf32String, 0)); + + if (utf32String.Length < 2) + { + Assert.False(char.IsLetter(utf32String[0])); + Assert.False(char.IsLetterOrDigit(utf32String[0])); + } + } + [Theory] [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs index 3aaf687c0be355..f8d3f10754f3e8 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Globalization; using System.Globalization.Tests; using Xunit; @@ -13,15 +14,17 @@ public class CharUnicodeInfoTests [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] public void GetUnicodeCategory(CharUnicodeInfoTestCase testCase) { + UnicodeCategory expected = PlatformDetection.IsNetFramework ? UnicodeCategory.OtherNotAssigned : UnicodeCategory.OtherLetter; + if (testCase.Utf32CodeValue.Length == 1) { - Assert.Equal(UnicodeCategory.OtherLetter, CharUnicodeInfo.GetUnicodeCategory(testCase.Utf32CodeValue[0])); + Assert.Equal(expected, CharUnicodeInfo.GetUnicodeCategory(testCase.Utf32CodeValue[0])); } - Assert.Equal(UnicodeCategory.OtherLetter, CharUnicodeInfo.GetUnicodeCategory(testCase.Utf32CodeValue, 0)); + Assert.Equal(expected, CharUnicodeInfo.GetUnicodeCategory(testCase.Utf32CodeValue, 0)); #if !NETFRAMEWORK - Assert.Equal(UnicodeCategory.OtherLetter, CharUnicodeInfo.GetUnicodeCategory(testCase.CodePoint)); + Assert.Equal(expected, CharUnicodeInfo.GetUnicodeCategory(testCase.CodePoint)); #endif - Assert.Equal(UnicodeCategory.OtherLetter, testCase.GeneralCategory); + Assert.True(PlatformDetection.IsNetFramework || testCase.GeneralCategory == expected); } } From ba384e52b752ff8632dbf8727ed34b182fc82b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Mon, 11 Aug 2025 13:34:54 -0500 Subject: [PATCH 10/10] Skip NetFramework at testclass level --- .../GB18030/Tests/CharTests.cs | 26 ++----------------- .../GB18030/Tests/CharUnicodeInfoTests.cs | 1 + .../GB18030/Tests/RegexTests.cs | 3 +-- .../RegexGeneratorHelper.netcoreapp.cs | 2 +- 4 files changed, 5 insertions(+), 27 deletions(-) diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs index fb237e300edc0c..502c94612b0def 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharTests.cs @@ -1,17 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Globalization; using System.Globalization.Tests; -using System.Linq; -using System.Xml.Linq; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Xunit; -using Xunit.Sdk; namespace GB18030.Tests; +[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] public class CharTests { [Theory] @@ -73,8 +68,7 @@ public void IsSurrogate(CharUnicodeInfoTestCase testCase) } } - [ConditionalTheory] - [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + [Theory] [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] public void IsLetter(CharUnicodeInfoTestCase testCase) { @@ -89,22 +83,6 @@ public void IsLetter(CharUnicodeInfoTestCase testCase) } } - [ConditionalTheory] - [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)] - [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] - public void IsLetter_NetFramework(CharUnicodeInfoTestCase testCase) - { - string utf32String = testCase.Utf32CodeValue; - Assert.False(char.IsLetter(utf32String, 0)); - Assert.False(char.IsLetterOrDigit(utf32String, 0)); - - if (utf32String.Length < 2) - { - Assert.False(char.IsLetter(utf32String[0])); - Assert.False(char.IsLetterOrDigit(utf32String[0])); - } - } - [Theory] [MemberData(nameof(TestHelper.GB18030CharUnicodeInfoMemberData), MemberType = typeof(TestHelper))] diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs index f8d3f10754f3e8..530879e48023ee 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/CharUnicodeInfoTests.cs @@ -8,6 +8,7 @@ namespace GB18030.Tests; +[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] public class CharUnicodeInfoTests { [Theory] diff --git a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs index 3f1602479944c0..620dfba0103bff 100644 --- a/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs +++ b/src/libraries/Common/tests/ComplianceTests/GB18030/Tests/RegexTests.cs @@ -15,6 +15,7 @@ namespace GB18030.Tests; /// /// Regex does not support surrogate pairs, which drastically reduces the number of characters in GB18030 that can be tested. /// +[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] public class RegexTests { public enum RegexNamedBlock @@ -37,7 +38,6 @@ public static IEnumerable UnicodeCategories_MemberData() => [Theory] [MemberData(nameof(UnicodeCategories_MemberData))] - [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2522617")] public async Task UnicodeCategory_InclusionAsync(RegexEngine engine, CultureInfo culture) { Regex r = await RegexHelpers.GetRegexAsync(engine, @"\p{Lo}", RegexOptions.None, culture); @@ -59,7 +59,6 @@ public async Task UnicodeCategory_InclusionAsync(RegexEngine engine, CultureInfo [Theory] [MemberData(nameof(UnicodeCategories_MemberData))] - [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2522617")] public async Task UnicodeCategory_ExclusionAsync(RegexEngine engine, CultureInfo culture) { Regex r = await RegexHelpers.GetRegexAsync(engine, @"\P{Lo}", RegexOptions.None, culture); diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs index 21db5608cd2168..cbf143eb3b3cb3 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs @@ -18,7 +18,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; -using Xunit; +using Xunit; namespace System.Text.RegularExpressions.Tests {