Skip to content

Commit c315bac

Browse files
authored
[iOS] Enable and disable tests for hybrid globalization on Apple (dotnet#108187)
Enable and disable tests for hybrid globalization on Apple
1 parent 01aa3d9 commit c315bac

File tree

8 files changed

+61
-48
lines changed

8 files changed

+61
-48
lines changed

docs/design/features/globalization-hybrid-mode.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,12 @@ Affected public APIs:
398398
- String.Compare,
399399
- String.Equals.
400400

401+
Mapped to Apple Native API `compare:options:range:locale:`(https://developer.apple.com/documentation/foundation/nsstring/1414561-compare?language=objc)
402+
This implementation uses normalization techniques such as `precomposedStringWithCanonicalMapping`,
403+
which can result in behavior differences compared to other platforms.
404+
Specifically, the use of precomposed strings and additional locale-based string folding can affect the results of comparisons.
405+
Due to these differences, the exact result of string compariso on Apple platforms may differ.
406+
401407
The number of `CompareOptions` and `NSStringCompareOptions` combinations are limited. Originally supported combinations can be found [here for CompareOptions](https://learn.microsoft.com/dotnet/api/system.globalization.compareoptions) and [here for NSStringCompareOptions](https://developer.apple.com/documentation/foundation/nsstringcompareoptions).
402408

403409
- `IgnoreSymbols` is not supported because there is no equivalent in native api. Throws `PlatformNotSupportedException`.

src/libraries/Common/tests/Tests/System/StringTests.cs

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,7 +1011,6 @@ public static void MakeSureNoCompareToChecksGoOutOfRange_StringComparison()
10111011
}
10121012

10131013
[Fact]
1014-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
10151014
public static void CompareToNoMatch_StringComparison()
10161015
{
10171016
for (int length = 1; length < 150; length++)
@@ -1035,24 +1034,29 @@ public static void CompareToNoMatch_StringComparison()
10351034
var secondSpan = new ReadOnlySpan<char>(second);
10361035
Assert.True(0 > firstSpan.CompareTo(secondSpan, StringComparison.Ordinal));
10371036

1038-
// Due to differences in the implementation, the exact result of CompareTo will not necessarily match with string.Compare.
1039-
// However, the sign will match, which is what defines correctness.
1040-
Assert.Equal(
1041-
Math.Sign(string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.OrdinalIgnoreCase)),
1042-
Math.Sign(firstSpan.CompareTo(secondSpan, StringComparison.OrdinalIgnoreCase)));
1043-
1044-
Assert.Equal(
1045-
string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.CurrentCulture),
1046-
firstSpan.CompareTo(secondSpan, StringComparison.CurrentCulture));
1047-
Assert.Equal(
1048-
string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.CurrentCultureIgnoreCase),
1049-
firstSpan.CompareTo(secondSpan, StringComparison.CurrentCultureIgnoreCase));
1050-
Assert.Equal(
1051-
string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.InvariantCulture),
1052-
firstSpan.CompareTo(secondSpan, StringComparison.InvariantCulture));
1053-
Assert.Equal(
1054-
string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.InvariantCultureIgnoreCase),
1055-
firstSpan.CompareTo(secondSpan, StringComparison.InvariantCultureIgnoreCase));
1037+
// On Apple platforms, string comparison is handled by native Apple functions, which apply normalization techniques
1038+
// like `precomposedStringWithCanonicalMapping`. This can lead to differences in behavior compared to other platforms.
1039+
if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform)
1040+
{
1041+
// Due to differences in the implementation, the exact result of CompareTo will not necessarily match with string.Compare.
1042+
// However, the sign will match, which is what defines correctness.
1043+
Assert.Equal(
1044+
Math.Sign(string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.OrdinalIgnoreCase)),
1045+
Math.Sign(firstSpan.CompareTo(secondSpan, StringComparison.OrdinalIgnoreCase)));
1046+
1047+
Assert.Equal(
1048+
string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.CurrentCulture),
1049+
firstSpan.CompareTo(secondSpan, StringComparison.CurrentCulture));
1050+
Assert.Equal(
1051+
string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.CurrentCultureIgnoreCase),
1052+
firstSpan.CompareTo(secondSpan, StringComparison.CurrentCultureIgnoreCase));
1053+
Assert.Equal(
1054+
string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.InvariantCulture),
1055+
firstSpan.CompareTo(secondSpan, StringComparison.InvariantCulture));
1056+
Assert.Equal(
1057+
string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.InvariantCultureIgnoreCase),
1058+
firstSpan.CompareTo(secondSpan, StringComparison.InvariantCultureIgnoreCase));
1059+
}
10561060
}
10571061
}
10581062
}
@@ -1286,7 +1290,6 @@ public static void ContainsMatchDifferentSpans_StringComparison()
12861290
}
12871291

12881292
[Fact]
1289-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
12901293
public static void ContainsNoMatch_StringComparison()
12911294
{
12921295
for (int length = 1; length < 150; length++)
@@ -1312,19 +1315,24 @@ public static void ContainsNoMatch_StringComparison()
13121315

13131316
Assert.False(firstSpan.Contains(secondSpan, StringComparison.OrdinalIgnoreCase));
13141317

1315-
// Different behavior depending on OS
1316-
Assert.Equal(
1317-
firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCulture),
1318-
firstSpan.Contains(secondSpan, StringComparison.CurrentCulture));
1319-
Assert.Equal(
1320-
firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCultureIgnoreCase),
1321-
firstSpan.Contains(secondSpan, StringComparison.CurrentCultureIgnoreCase));
1322-
Assert.Equal(
1323-
firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.InvariantCulture),
1324-
firstSpan.Contains(secondSpan, StringComparison.InvariantCulture));
1325-
Assert.Equal(
1326-
firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.InvariantCultureIgnoreCase),
1327-
firstSpan.Contains(secondSpan, StringComparison.InvariantCultureIgnoreCase));
1318+
// On Apple platforms, string comparison is handled by native Apple functions, which apply normalization techniques
1319+
// like `precomposedStringWithCanonicalMapping`. This can lead to differences in behavior compared to other platforms.
1320+
if (PlatformDetection.IsNotHybridGlobalizationOnApplePlatform)
1321+
{
1322+
// Different behavior depending on OS
1323+
Assert.Equal(
1324+
firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCulture),
1325+
firstSpan.Contains(secondSpan, StringComparison.CurrentCulture));
1326+
Assert.Equal(
1327+
firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCultureIgnoreCase),
1328+
firstSpan.Contains(secondSpan, StringComparison.CurrentCultureIgnoreCase));
1329+
Assert.Equal(
1330+
firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.InvariantCulture),
1331+
firstSpan.Contains(secondSpan, StringComparison.InvariantCulture));
1332+
Assert.Equal(
1333+
firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.InvariantCultureIgnoreCase),
1334+
firstSpan.Contains(secondSpan, StringComparison.InvariantCultureIgnoreCase));
1335+
}
13281336
}
13291337
}
13301338
}
@@ -2113,7 +2121,6 @@ public static void EndsWithMatchDifferentSpans_StringComparison()
21132121
}
21142122

21152123
[Fact]
2116-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
21172124
public static void EndsWithNoMatch_StringComparison()
21182125
{
21192126
for (int length = 1; length < 150; length++)
@@ -7379,7 +7386,6 @@ public static void StartsWithMatchDifferentSpans_StringComparison()
73797386
}
73807387

73817388
[Fact]
7382-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
73837389
public static void StartsWithNoMatch_StringComparison()
73847390
{
73857391
for (int length = 1; length < 150; length++)

src/libraries/System.Data.Common/tests/System/Data/SqlTypes/SqlStringSortingTest.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ public static class SqlStringSortingTest
3737

3838
private static readonly UnicodeEncoding s_unicodeEncoding = new UnicodeEncoding(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: true);
3939

40-
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))]
41-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
40+
// On Apple platforms, the string comparison implementation relies on native Apple functions which uses normalization techniques, which can result in behavior differences compared to other platforms.
41+
// Specifically, the use of precomposed strings and additional locale-based string folding can affect the results of comparisons with certain options like `IgnoreKanaType`.
42+
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))]
4243
[InlineData("ja-JP", 0x0411)] // Japanese - Japan
4344
[InlineData("ar-SA", 0x0401)] // Arabic - Saudi Arabia
4445
[InlineData("de-DE", 0x0407)] // German - Germany

src/libraries/System.Runtime/tests/System.Globalization.Extensions.Tests/GetStringComparerTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ public void GetStringComparer_Invalid()
1919
AssertExtensions.Throws<ArgumentException>("options", () => new CultureInfo("tr-TR").CompareInfo.GetStringComparer(CompareOptions.OrdinalIgnoreCase | CompareOptions.IgnoreCase));
2020
}
2121

22-
[Theory]
23-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
22+
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))]
2423
[InlineData("hello", "hello", "fr-FR", CompareOptions.IgnoreCase, 0, 0)]
2524
[InlineData("hello", "HELLo", "fr-FR", CompareOptions.IgnoreCase, 0, 0)]
2625
[InlineData("hello", null, "fr-FR", CompareOptions.IgnoreCase, 1, 1)]

src/libraries/System.Runtime/tests/System.Globalization.Tests/CompareInfo/CompareInfoTests.HashCode.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ public class CompareInfoHashCodeTests : CompareInfoTestsBase
1414
{
1515

1616
[OuterLoop]
17-
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization))]
18-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
17+
// On Apple platforms, string comparison is handled by native Apple functions, which apply normalization techniques
18+
// like `precomposedStringWithCanonicalMapping`. This can lead to differences in behavior compared to other platforms.
19+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalization), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))]
1920
public void CheckHashingInLineWithEqual()
2021
{
2122
int additionalCollisions = 0;

src/libraries/System.Runtime/tests/System.Reflection.Tests/AssemblyNameTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,7 @@ public void CultureName_Set(AssemblyName assemblyName, string originalCultureNam
221221
Assert.Equal(new AssemblyName(expectedEqualString).FullName, assemblyName.FullName);
222222
}
223223

224-
[Fact]
225-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
224+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))]
226225
public void CultureName_Set_Invalid_ThrowsCultureNotFoundException()
227226
{
228227
var assemblyName = new AssemblyName("Test");

src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateTimeTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,7 +1982,10 @@ public static IEnumerable<object[]> Parse_ValidInput_Succeeds_MemberData()
19821982
yield return new object[] { "#2020-5-7T09:37:00.0000000+00:00#\0", CultureInfo.InvariantCulture, TimeZoneInfo.ConvertTimeFromUtc(new DateTime(2020, 5, 7, 9, 37, 0, DateTimeKind.Utc), TimeZoneInfo.Local) };
19831983
yield return new object[] { "2020-5-7T09:37:00.0000000+00:00", CultureInfo.InvariantCulture, TimeZoneInfo.ConvertTimeFromUtc(new DateTime(2020, 5, 7, 9, 37, 0, DateTimeKind.Utc), TimeZoneInfo.Local) };
19841984

1985-
if (PlatformDetection.IsNotInvariantGlobalization)
1985+
// On Apple platforms, the handling calendars relies on native Apple APIs (NSCalendar).
1986+
// These APIs can cause differences in behavior when parsing or formatting dates compared to other platforms.
1987+
// Specifically, the way Apple handles calendar identifiers and date formats for cultures like "he-IL" may lead to variations in the output.
1988+
if (PlatformDetection.IsNotInvariantGlobalization && PlatformDetection.IsNotHybridGlobalizationOnApplePlatform)
19861989
{
19871990
DateTime today = DateTime.Today;
19881991
var hebrewCulture = new CultureInfo("he-IL");
@@ -2003,7 +2006,6 @@ public static IEnumerable<object[]> Parse_ValidInput_Succeeds_MemberData()
20032006
}
20042007

20052008
[Theory]
2006-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
20072009
[MemberData(nameof(Parse_ValidInput_Succeeds_MemberData))]
20082010
public static void Parse_ValidInput_Succeeds(string input, CultureInfo culture, DateTime? expected)
20092011
{
@@ -2464,7 +2466,6 @@ public static IEnumerable<object[]> ToString_MatchesExpected_MemberData()
24642466
}
24652467

24662468
[Theory]
2467-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
24682469
[MemberData(nameof(Parse_ValidInput_Succeeds_MemberData))]
24692470
public static void Parse_Span_ValidInput_Succeeds(string input, CultureInfo culture, DateTime? expected)
24702471
{

src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Text/RuneTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public static void Casing_Invariant(int original, int upper, int lower)
5757
Assert.Equal(new Rune(lower), Rune.ToLowerInvariant(rune));
5858
}
5959

60-
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalizationAndNotHybridOnBrowser))]
60+
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsIcuGlobalizationAndNotHybridOnBrowser), nameof(PlatformDetection.IsNotHybridGlobalizationOnApplePlatform))]
61+
// HybridGlobalization on Apple mobile platforms has issues with casing dotless I
6162
// HybridGlobalization on Browser uses Invariant HashCode and SortKey, so its effect does not match this of ICU
6263
[InlineData('0', '0', '0')]
6364
[InlineData('a', 'A', 'a')]
@@ -71,7 +72,6 @@ public static void Casing_Invariant(int original, int upper, int lower)
7172
[InlineData('\u0131', '\u0131', '\u0131')] // U+0131 LATIN SMALL LETTER DOTLESS I
7273
[InlineData(0x10400, 0x10400, 0x10428)] // U+10400 DESERET CAPITAL LETTER LONG I
7374
[InlineData(0x10428, 0x10400, 0x10428)] // U+10428 DESERET SMALL LETTER LONG I
74-
[ActiveIssue("https://github.com/dotnet/runtime/issues/95338", typeof(PlatformDetection), nameof(PlatformDetection.IsHybridGlobalizationOnApplePlatform))]
7575
public static void ICU_Casing_Invariant(int original, int upper, int lower)
7676
{
7777
var rune = new Rune(original);

0 commit comments

Comments
 (0)