Skip to content

Commit

Permalink
Allow restricting cultures creation with any arbitrary names in Globa…
Browse files Browse the repository at this point in the history
…lization Invariant Mode (dotnet#54247)

Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
  • Loading branch information
tarekgh and eerhardt authored Jul 1, 2021
1 parent be80f67 commit 04dac7b
Show file tree
Hide file tree
Showing 15 changed files with 167 additions and 56 deletions.
1 change: 1 addition & 0 deletions docs/workflow/trimming/feature-switches.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ configurations but their defaults might vary as any SDK can set the defaults dif
| EnableUnsafeBinaryFormatterSerialization | System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization | BinaryFormatter serialization support is trimmed when set to false |
| EventSourceSupport | System.Diagnostics.Tracing.EventSource.IsSupported | Any EventSource related code or logic is trimmed when set to false |
| InvariantGlobalization | System.Globalization.Invariant | All globalization specific code and data is trimmed when set to true |
| PredefinedCulturesOnly | System.Globalization.PredefinedCulturesOnly | Don't allow creating a culture for which the platform does not have data |
| UseSystemResourceKeys | System.Resources.UseSystemResourceKeys | Any localizable resources for system assemblies is trimmed when set to true |
| HttpActivityPropagationSupport | System.Net.Http.EnableActivityPropagation | Any dependency related to diagnostics support for System.Net.Http is trimmed when set to false |
| UseNativeHttpHandler | System.Net.Http.UseNativeHttpHandler | HttpClient uses by default platform native implementation of HttpMessageHandler if set to true. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using Microsoft.DotNet.RemoteExecutor;
using System.Diagnostics;
using System.Collections.Concurrent;
using Xunit;

namespace System.Globalization.Tests
Expand Down Expand Up @@ -139,5 +140,80 @@ public void PredefinedCulturesOnlyEnvVarTest(string predefinedCulturesOnlyEnvVar
}
}, cultureName, predefinedCulturesOnlyEnvVar, new RemoteInvokeOptions { StartInfo = psi }).Dispose();
}

[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData(true, true, false)]
[InlineData(true, true, true)]
[InlineData(true, false, true)]
[InlineData(false, true, true)]
[InlineData(false, true, false)]
[InlineData(false, false, true)]
public void TestAllowInvariantCultureOnly(bool enableInvariant, bool predefinedCulturesOnly, bool declarePredefinedCulturesOnly)
{
var psi = new ProcessStartInfo();
psi.Environment.Clear();

if (enableInvariant)
{
psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "true");
}

if (declarePredefinedCulturesOnly)
{
psi.Environment.Add("DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY", predefinedCulturesOnly ? "true" : "false");
}

bool restricted = enableInvariant && (declarePredefinedCulturesOnly ? predefinedCulturesOnly : true);

RemoteExecutor.Invoke((invariantEnabled, isRestricted) =>
{
bool restrictedMode = bool.Parse(isRestricted);

// First ensure we can create the current cultures regardless of the mode we are in
Assert.NotNull(CultureInfo.CurrentCulture);
Assert.NotNull(CultureInfo.CurrentUICulture);

// Invariant culture should be valid all the time
Assert.Equal("", new CultureInfo("").Name);
Assert.Equal("", CultureInfo.InvariantCulture.Name);

if (restrictedMode)
{
Assert.Equal("", CultureInfo.CurrentCulture.Name);
Assert.Equal("", CultureInfo.CurrentUICulture.Name);

// Throwing exception is testing accessing the resources in this restricted mode.
// We should retrieve the resources from the neutral resources in the main assemblies.
AssertExtensions.Throws<CultureNotFoundException>(() => new CultureInfo("en-US"));
AssertExtensions.Throws<CultureNotFoundException>(() => new CultureInfo("en"));

AssertExtensions.Throws<CultureNotFoundException>(() => new CultureInfo("ja-JP"));
AssertExtensions.Throws<CultureNotFoundException>(() => new CultureInfo("es"));

// Test throwing exceptions from non-core assemblies.
Exception exception = Record.Exception(() => new ConcurrentBag<string>(null));
Assert.NotNull(exception);
Assert.IsType<ArgumentNullException>(exception);
Assert.Equal("collection", (exception as ArgumentNullException).ParamName);
Assert.Equal("The collection argument is null. (Parameter 'collection')", exception.Message);
}
else
{
Assert.Equal("en-US", new CultureInfo("en-US").Name);
Assert.Equal("ja-JP", new CultureInfo("ja-JP").Name);
Assert.Equal("en", new CultureInfo("en").Name);
Assert.Equal("es", new CultureInfo("es").Name);
}

// Ensure the Invariant Mode functionality still work
if (bool.Parse(invariantEnabled))
{
Assert.True(CultureInfo.CurrentCulture.Calendar is GregorianCalendar);
Assert.True("abcd".Equals("ABCD", StringComparison.CurrentCultureIgnoreCase));
Assert.Equal("Invariant Language (Invariant Country)", CultureInfo.CurrentCulture.NativeName);
}

}, enableInvariant.ToString(), restricted.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose();
}
}
}
66 changes: 42 additions & 24 deletions src/libraries/System.Globalization/tests/Invariant/InvariantMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Reflection;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
Expand All @@ -13,6 +14,23 @@ namespace System.Globalization.Tests
{
public class InvariantModeTests
{
private static bool PredefinedCulturesOnlyIsDisabled { get; } = !PredefinedCulturesOnly();
private static bool PredefinedCulturesOnly()
{
bool ret;

try
{
ret = (bool) typeof(object).Assembly.GetType("System.Globalization.GlobalizationMode").GetProperty("PredefinedCulturesOnly", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
}
catch
{
ret = false;
}

return ret;
}

public static IEnumerable<object[]> Cultures_TestData()
{
yield return new object[] { "en-US" };
Expand Down Expand Up @@ -490,13 +508,13 @@ public static IEnumerable<object[]> GetUnicode_TestData()
yield return new object[] { "xn--de-jg4avhby1noc0d", 0, 21, "\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0" };
}

[Fact]
[ConditionalFact(nameof(PredefinedCulturesOnlyIsDisabled))]
public static void IcuShouldNotBeLoaded()
{
Assert.False(PlatformDetection.IsIcuGlobalization);
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(Cultures_TestData))]
public void TestCultureData(string cultureName)
{
Expand Down Expand Up @@ -636,7 +654,7 @@ public void TestCultureData(string cultureName)
Assert.True(cultureName.Equals(ci.CompareInfo.Name, StringComparison.OrdinalIgnoreCase));
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(Cultures_TestData))]
public void SetCultureData(string cultureName)
{
Expand All @@ -652,13 +670,13 @@ public void SetCultureData(string cultureName)
Assert.Throws<ArgumentOutOfRangeException>(() => ci.DateTimeFormat.Calendar = new TaiwanCalendar());
}

[Fact]
[ConditionalFact(nameof(PredefinedCulturesOnlyIsDisabled))]
public void TestEnum()
{
Assert.Equal(new CultureInfo[1] { CultureInfo.InvariantCulture }, CultureInfo.GetCultures(CultureTypes.AllCultures));
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(Cultures_TestData))]
public void TestSortVersion(string cultureName)
{
Expand All @@ -670,7 +688,7 @@ public void TestSortVersion(string cultureName)
Assert.Equal(version, new CultureInfo(cultureName).CompareInfo.Version);
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[InlineData(0, 0)]
[InlineData(1, 2)]
[InlineData(100_000, 200_000)]
Expand All @@ -683,7 +701,7 @@ public void TestGetSortKeyLength_Valid(int inputLength, int expectedSortKeyLengt
Assert.Equal(expectedSortKeyLength, CultureInfo.InvariantCulture.CompareInfo.GetSortKeyLength(dummySpan));
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[InlineData(0x4000_0000)]
[InlineData(int.MaxValue)]
public unsafe void TestGetSortKeyLength_OverlongArgument(int inputLength)
Expand All @@ -698,7 +716,7 @@ public unsafe void TestGetSortKeyLength_OverlongArgument(int inputLength)
});
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[InlineData("Hello", CompareOptions.None, "Hello")]
[InlineData("Hello", CompareOptions.IgnoreWidth, "Hello")]
[InlineData("Hello", CompareOptions.IgnoreCase, "HELLO")]
Expand Down Expand Up @@ -741,7 +759,7 @@ public unsafe void TestSortKey_FromSpan(string input, CompareOptions options, st
}
}

[Fact]
[ConditionalFact(nameof(PredefinedCulturesOnlyIsDisabled))]
public void TestSortKey_ZeroWeightCodePoints()
{
// In the invariant globalization mode, there's no such thing as a zero-weight code point,
Expand All @@ -753,7 +771,7 @@ public void TestSortKey_ZeroWeightCodePoints()
Assert.NotEqual(0, SortKey.Compare(sortKeyForEmptyString, sortKeyForZeroWidthJoiner));
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[InlineData("", "", 0)]
[InlineData("", "not-empty", -1)]
[InlineData("not-empty", "", 1)]
Expand Down Expand Up @@ -794,7 +812,7 @@ private static StringComparison GetStringComparison(CompareOptions options)
return sc;
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(IndexOf_TestData))]
public void TestIndexOf(string source, string value, int startIndex, int count, CompareOptions options, int result)
{
Expand Down Expand Up @@ -841,7 +859,7 @@ static void TestCore(CompareInfo compareInfo, string source, string value, int s
}
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(LastIndexOf_TestData))]
public void TestLastIndexOf(string source, string value, int startIndex, int count, CompareOptions options, int result)
{
Expand Down Expand Up @@ -901,7 +919,7 @@ static void TestCore(CompareInfo compareInfo, string source, string value, int s
}
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(IsPrefix_TestData))]
public void TestIsPrefix(string source, string value, CompareOptions options, bool result)
{
Expand Down Expand Up @@ -936,7 +954,7 @@ public void TestIsPrefix(string source, string value, CompareOptions options, bo
}
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(IsSuffix_TestData))]
public void TestIsSuffix(string source, string value, CompareOptions options, bool result)
{
Expand Down Expand Up @@ -971,7 +989,7 @@ public void TestIsSuffix(string source, string value, CompareOptions options, bo
}
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[InlineData("", false)]
[InlineData('x', true)]
[InlineData('\ud800', true)] // standalone high surrogate
Expand All @@ -988,7 +1006,7 @@ public void TestIsSortable(object sourceObj, bool expectedResult)
}
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(Compare_TestData))]
public void TestCompare(string source, string value, CompareOptions options, int result)
{
Expand Down Expand Up @@ -1019,7 +1037,7 @@ public void TestCompare(string source, string value, CompareOptions options, int
}


[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(ToLower_TestData))]
public void TestToLower(string upper, string lower, bool result)
{
Expand All @@ -1030,7 +1048,7 @@ public void TestToLower(string upper, string lower, bool result)
}
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(ToUpper_TestData))]
public void TestToUpper(string lower, string upper, bool result)
{
Expand All @@ -1041,7 +1059,7 @@ public void TestToUpper(string lower, string upper, bool result)
}
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[InlineData("", NormalizationForm.FormC)]
[InlineData("\uFB01", NormalizationForm.FormC)]
[InlineData("\uFB01", NormalizationForm.FormD)]
Expand All @@ -1063,7 +1081,7 @@ public void TestNormalization(string s, NormalizationForm form)
Assert.Equal(s, s.Normalize(form));
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(GetAscii_TestData))]
public void GetAscii(string unicode, int index, int count, string expected)
{
Expand All @@ -1078,7 +1096,7 @@ public void GetAscii(string unicode, int index, int count, string expected)
Assert.Equal(expected, new IdnMapping().GetAscii(unicode, index, count));
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[MemberData(nameof(GetUnicode_TestData))]
public void GetUnicode(string ascii, int index, int count, string expected)
{
Expand All @@ -1093,7 +1111,7 @@ public void GetUnicode(string ascii, int index, int count, string expected)
Assert.Equal(expected, new IdnMapping().GetUnicode(ascii, index, count));
}

[Fact]
[ConditionalFact(nameof(PredefinedCulturesOnlyIsDisabled))]
public void TestHashing()
{
StringComparer cultureComparer = StringComparer.Create(CultureInfo.GetCultureInfo("tr-TR"), true);
Expand All @@ -1102,7 +1120,7 @@ public void TestHashing()
Assert.Equal(ordinalComparer.GetHashCode(turkishString), cultureComparer.GetHashCode(turkishString));
}

[Theory]
[ConditionalTheory(nameof(PredefinedCulturesOnlyIsDisabled))]
[InlineData('a', 'A', 'a')]
[InlineData('A', 'A', 'a')]
[InlineData('i', 'I', 'i')] // to verify that we don't special-case the Turkish I in the invariant globalization mode
Expand All @@ -1121,7 +1139,7 @@ public void TestRune(int original, int expectedToUpper, int expectedToLower)
Assert.Equal(expectedToLower, Rune.ToLower(originalRune, CultureInfo.GetCultureInfo("tr-TR")).Value);
}

[Fact]
[ConditionalFact(nameof(PredefinedCulturesOnlyIsDisabled))]
public void TestGetCultureInfo_PredefinedOnly_ReturnsSame()
{
Assert.Equal(CultureInfo.GetCultureInfo("en-US"), CultureInfo.GetCultureInfo("en-US", predefinedOnly: true));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"configProperties": {
"System.Globalization.Invariant": true
"System.Globalization.Invariant": true,
"System.Globalization.PredefinedCulturesOnly": false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
to be trimmed when Invariant=true, and allows for the Settings static cctor (on Unix) to be preserved when Invariant=false. -->
<type fullname="System.Globalization.GlobalizationMode">
<method signature="System.Boolean get_Invariant()" body="stub" value="true" feature="System.Globalization.Invariant" featurevalue="true" />
<method signature="System.Boolean get_PredefinedCulturesOnly()" body="stub" value="true" feature="System.Globalization.PredefinedCulturesOnly" featurevalue="true" />
</type>
<type fullname="System.Globalization.GlobalizationMode/Settings">
<method signature="System.Boolean get_Invariant()" body="stub" value="false" feature="System.Globalization.Invariant" featurevalue="false" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,9 @@
<data name="Argument_CultureNotSupported" xml:space="preserve">
<value>Culture is not supported.</value>
</data>
<data name="Argument_CultureNotSupportedInInvariantMode" xml:space="preserve">
<value>Only the invariant culture is supported in globalization-invariant mode. See https://aka.ms/GlobalizationInvariantMode for more information.</value>
</data>
<data name="Argument_CustomAssemblyLoadContextRequestedNameMismatch" xml:space="preserve">
<value>Resolved assembly's simple name should be the same as of the requested assembly.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@ internal static class AppContextConfigHelper
internal static bool GetBooleanConfig(string configName, bool defaultValue) =>
AppContext.TryGetSwitch(configName, out bool value) ? value : defaultValue;

internal static bool GetBooleanConfig(string switchName, string envVariable)
internal static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false)
{
if (!AppContext.TryGetSwitch(switchName, out bool ret))
{
string? switchValue = Environment.GetEnvironmentVariable(envVariable);
if (switchValue != null)
{
ret = bool.IsTrueStringIgnoreCase(switchValue) || switchValue.Equals("1");
}
ret = switchValue != null ? (bool.IsTrueStringIgnoreCase(switchValue) || switchValue.Equals("1")) : defaultValue;
}

return ret;
Expand Down
Loading

0 comments on commit 04dac7b

Please sign in to comment.