From 486083729c1bb92d7d40d139d4aa0de557774923 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 14 Dec 2022 13:34:59 +0100 Subject: [PATCH 01/68] User can set `GlobalizationShard`. --- src/mono/wasm/build/WasmApp.targets | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 8cfc736c4c2525..5ae60aacd3fd61 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -305,9 +305,18 @@ - - icudt.dat + + icudt.dat + + icudt_EFIGS.dat + icudt_CJK.dat + icudt_no_CJK.dat + + <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true From 234ee7396846f88d23c8b0fd500fa56367eedb12 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 14 Dec 2022 14:13:44 +0100 Subject: [PATCH 02/68] Sample for sharding, might be transformed into a test. --- .../browser-globalization-shards/Makefile | 11 ++++++ .../browser-globalization-shards/Program.cs | 34 +++++++++++++++++++ .../Wasm.Browser.Globalization.Shards.csproj | 7 ++++ .../browser-globalization-shards/index.html | 20 +++++++++++ .../wasm/browser-globalization-shards/main.js | 25 ++++++++++++++ .../wasm/browser/Wasm.Browser.Sample.csproj | 1 + 6 files changed, 98 insertions(+) create mode 100644 src/mono/sample/wasm/browser-globalization-shards/Makefile create mode 100644 src/mono/sample/wasm/browser-globalization-shards/Program.cs create mode 100644 src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj create mode 100644 src/mono/sample/wasm/browser-globalization-shards/index.html create mode 100644 src/mono/sample/wasm/browser-globalization-shards/main.js diff --git a/src/mono/sample/wasm/browser-globalization-shards/Makefile b/src/mono/sample/wasm/browser-globalization-shards/Makefile new file mode 100644 index 00000000000000..7ae388cce865d3 --- /dev/null +++ b/src/mono/sample/wasm/browser-globalization-shards/Makefile @@ -0,0 +1,11 @@ +TOP=../../../../.. + +include ../wasm.mk + +ifneq ($(AOT),) +override MSBUILD_ARGS+=/p:RunAOTCompilation=true +endif + +PROJECT_NAME=Wasm.Browser.Sample.csproj + +run: run-browser diff --git a/src/mono/sample/wasm/browser-globalization-shards/Program.cs b/src/mono/sample/wasm/browser-globalization-shards/Program.cs new file mode 100644 index 00000000000000..e81be408036ca2 --- /dev/null +++ b/src/mono/sample/wasm/browser-globalization-shards/Program.cs @@ -0,0 +1,34 @@ +// 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.Runtime.InteropServices.JavaScript; +using System.Globalization; +using System.Text; + +Console.WriteLine("Hello, Browser Shard Sample!"); + +public partial class Sample +{ + [JSExport] + internal static string Greeting() + { + var existingCulture = new CultureInfo("en-GB"); + Console.WriteLine(existingCulture.NumberFormat.CurrencySymbol); + try + { + var nonExistingCulture = new CultureInfo("pl-PL"); + Console.WriteLine(nonExistingCulture.NumberFormat.CurrencySymbol); + } + catch(Exception ex) + { + Console.WriteLine($"Culture pl-PL does not exist on this shard: {ex}"); + } + var text = $"Hello, World! Greetings from {GetHRef()}"; + Console.WriteLine(text); + return text; + } + + [JSImport("window.location.href", "main.js")] + internal static partial string GetHRef(); +} diff --git a/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj b/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj new file mode 100644 index 00000000000000..15f6a3bee12a7f --- /dev/null +++ b/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj @@ -0,0 +1,7 @@ + + + + EFIGS + <_SampleProject>Wasm.Browser.Globalization.Shards.csproj + + diff --git a/src/mono/sample/wasm/browser-globalization-shards/index.html b/src/mono/sample/wasm/browser-globalization-shards/index.html new file mode 100644 index 00000000000000..4805439dd65d69 --- /dev/null +++ b/src/mono/sample/wasm/browser-globalization-shards/index.html @@ -0,0 +1,20 @@ + + + + + + + Wasm Browser Globalization Shards Sample + + + + + + + + + Answer to the Ultimate Question of Life, the Universe, and Everything is : + + + + diff --git a/src/mono/sample/wasm/browser-globalization-shards/main.js b/src/mono/sample/wasm/browser-globalization-shards/main.js new file mode 100644 index 00000000000000..3696dd9f6e1c91 --- /dev/null +++ b/src/mono/sample/wasm/browser-globalization-shards/main.js @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { dotnet } from './dotnet.js' + +const { setModuleImports, getAssemblyExports, getConfig } = await dotnet + .withDiagnosticTracing(false) + .withApplicationArgumentsFromQuery() + .create(); + +setModuleImports('main.js', { + window: { + location: { + href: () => globalThis.window.location.href + } + } +}); + +const config = getConfig(); +const exports = await getAssemblyExports(config.mainAssemblyName); +const text = exports.Sample.Greeting(); +console.log(text); + +document.getElementById('out').innerHTML = text; +await dotnet.run(); diff --git a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj index 76ec8950104ab5..9c37b19140e836 100644 --- a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj +++ b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj @@ -1,6 +1,7 @@ + EFIGS <_SampleProject>Wasm.Browser.Sample.csproj From 6790c0f9ec3865fd6e634e11852062bf639cb9d9 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 16 Dec 2022 07:15:04 +0100 Subject: [PATCH 03/68] Option of custom shard file upload. --- .../Wasm.Browser.Globalization.Shards.csproj | 2 +- src/mono/wasm/build/WasmApp.targets | 22 ++++------ src/mono/wasm/runtime/assets.ts | 43 +++++++++++++++++-- src/mono/wasm/runtime/dotnet.d.ts | 8 ++++ src/mono/wasm/runtime/types.ts | 12 +++++- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 16 +++++-- 6 files changed, 78 insertions(+), 25 deletions(-) diff --git a/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj b/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj index 15f6a3bee12a7f..a7153ee34ff92a 100644 --- a/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj +++ b/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj @@ -1,7 +1,7 @@ - EFIGS + icudt_EFIGS.dat <_SampleProject>Wasm.Browser.Globalization.Shards.csproj diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 5ae60aacd3fd61..5cecc71dbe8843 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -305,18 +305,8 @@ - - icudt.dat - - icudt_EFIGS.dat - icudt_CJK.dat - icudt_no_CJK.dat - - + true <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true @@ -324,6 +314,9 @@ + + + @@ -333,7 +326,7 @@ '$(_HasDotnetJsSymbols)' != 'true' and Exists('$(MicrosoftNetCoreAppRuntimePackRidNativeDir)dotnet.js.symbols')" /> - + @@ -341,7 +334,7 @@ @@ -366,7 +359,8 @@ InvariantGlobalization="$(InvariantGlobalization)" SatelliteAssemblies="@(_WasmSatelliteAssemblies)" FilesToIncludeInFileSystem="@(WasmFilesToIncludeInFileSystem)" - IcuDataFileName="$(WasmIcuDataFileName)" + IcuDataFileNames="@(WasmIcuDataFileNames)" + IsIcuDataCustom="$(IsIcuDataCustom)" RemoteSources="@(WasmRemoteSources)" ExtraFilesToDeploy="@(WasmExtraFilesToDeploy)" ExtraConfig="@(WasmExtraConfig)" diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 2f6ab851f1bb12..8f287fc1dce889 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -47,6 +47,40 @@ const skipInstantiateByAssetTypes: { "dotnetwasm": true, }; +export function get_skipped_icu_assets() : { [name: string]: boolean; } +{ + const preferredCulture = (navigator.languages && navigator.languages[0]); + const prefix = preferredCulture.split("-")[0]; + if (["en", "fr", "it", "de", "es"].includes(prefix)) + { + return { + "icudt.dat": true, + "icudt_EFIGS.dat": false, + "icudt_CJK.dat": true, + "icudt_no_CJK.dat": true + }; + } + if (["zh", "ko", "ja",].includes(prefix)) { + return { + "icudt.dat": true, + "icudt_EFIGS.dat": true, + "icudt_CJK.dat": false, + "icudt_no_CJK.dat": true + }; + } + return { + "icudt.dat": true, + "icudt_EFIGS.dat": true, + "icudt_CJK.dat": true, + "icudt_no_CJK.dat": false + }; +} + +export function skipIcuAssets(asset : AssetEntryInternal, skipIcuByAssetNames: { [name: string]: boolean; }) : boolean{ + // if custom then there is only one icu file in assets and it should not be skipped + return !(asset.behavior == "icu" && !runtimeHelpers.config.isIcuDataCustom && skipIcuByAssetNames[asset.name]); +} + export function resolve_asset_path(behavior: AssetBehaviours) { const asset: AssetEntry | undefined = runtimeHelpers.config.assets?.find(a => a.behavior == behavior); mono_assert(asset, () => `Can't find asset for ${behavior}`); @@ -64,13 +98,14 @@ export async function mono_download_assets(): Promise { runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; try { const promises_of_assets_with_buffer: Promise[] = []; + const skipIcuByAssetNames = get_skipped_icu_assets(); // start fetching and instantiating all assets in parallel for (const a of runtimeHelpers.config.assets!) { const asset: AssetEntryInternal = a; - if (!skipInstantiateByAssetTypes[asset.behavior]) { + if (!skipInstantiateByAssetTypes[asset.behavior] && skipIcuAssets(asset, skipIcuByAssetNames)) { expected_instantiated_assets_count++; } - if (!skipDownloadsByAssetTypes[asset.behavior]) { + if (!skipDownloadsByAssetTypes[asset.behavior] && skipIcuAssets(asset, skipIcuByAssetNames)) { const headersOnly = skipBufferByAssetTypes[asset.behavior];// `response.arrayBuffer()` can't be called twice. Some usecases are calling it on response in the instantiation. expected_downloaded_assets_count++; if (asset.pendingDownload) { @@ -117,10 +152,10 @@ export async function mono_download_assets(): Promise { const headersOnly = skipBufferByAssetTypes[asset.behavior]; if (!headersOnly) { mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); - if (!skipDownloadsByAssetTypes[asset.behavior]) { + if (!skipDownloadsByAssetTypes[asset.behavior] && skipIcuAssets(asset, skipIcuByAssetNames)) { expected_downloaded_assets_count--; } - if (!skipInstantiateByAssetTypes[asset.behavior]) { + if (!skipInstantiateByAssetTypes[asset.behavior] && skipIcuAssets(asset, skipIcuByAssetNames)) { expected_instantiated_assets_count--; } } diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index c51064343d22df..d58fbbe6d20fab 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -108,6 +108,14 @@ type MonoConfig = { * Configures the runtime's globalization mode */ globalizationMode?: GlobalizationMode; + /** + * User can choose the option to load only partial globalization data: + * "icudt_EFIGS.dat" for "en-US" "fr-FR" "es-ES" "it-IT" "de-DE" + * "icudt_CJK.dat" for "ja" "ko" "zh" "en" + * "icudt_no_CJK.dat" for all cultures, excluding "ja" "ko" "zh" + * "customName" + */ + isIcuDataCustom?: boolean; /** * debugLevel > 0 enables debugging and sets the debug log level to debugLevel * debugLevel == 0 disables debugging and enables interpreter optimizations diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 81739f9a0b3a90..c5ed0f26a39ab4 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -95,6 +95,14 @@ export type MonoConfig = { * Configures the runtime's globalization mode */ globalizationMode?: GlobalizationMode, + /** + * User can choose the option to load only partial globalization data: + * "icudt_EFIGS.dat" for "en-*" "fr-FR" "es-ES" "it-IT" "de-DE" + * "icudt_CJK.dat" for "ja" "ko" "zh" "en" + * "icudt_no_CJK.dat" for all cultures, excluding "ja" "ko" "zh" + * "customName" + */ + isIcuDataCustom?: boolean, /** * debugLevel > 0 enables debugging and sets the debug log level to debugLevel * debugLevel == 0 disables debugging and enables interpreter optimizations @@ -168,13 +176,13 @@ export interface AssetEntry extends ResourceRequest { /** * If true, an attempt will be made to load the asset from each location in MonoConfig.remoteSources. */ - loadRemote?: boolean, // + loadRemote?: boolean, // /** * If true, the runtime startup would not fail if the asset download was not successful. */ isOptional?: boolean /** - * If provided, runtime doesn't have to fetch the data. + * If provided, runtime doesn't have to fetch the data. * Runtime would set the buffer to null after instantiation to free the memory. */ buffer?: ArrayBuffer diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 2fead7b9860bfd..b947087f34938d 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -37,7 +37,8 @@ public class WasmAppBuilder : Task // full list of ICU data files we produce can be found here: // https://github.com/dotnet/icu/tree/maint/maint-67/icu-filters - public string? IcuDataFileName { get; set; } + public ITaskItem[]? IcuDataFileNames { get; set; } + public bool IsIcuDataCustom {get; set;} public int DebugLevel { get; set; } public ITaskItem[]? SatelliteAssemblies { get; set; } @@ -87,6 +88,8 @@ private sealed class WasmAppConfig public List RemoteSources { get; set; } = new List(); [JsonExtensionData] public Dictionary Extra { get; set; } = new(); + [JsonPropertyName("isIcuDataCustom")] + public bool IsIcuDataCustom { get; set; } } private class AssetEntry @@ -161,8 +164,8 @@ private bool ExecuteInternal () { if (!File.Exists(MainJS)) throw new LogAsErrorException($"File MainJS='{MainJS}' doesn't exist."); - if (!InvariantGlobalization && string.IsNullOrEmpty(IcuDataFileName)) - throw new LogAsErrorException("IcuDataFileName property shouldn't be empty if InvariantGlobalization=false"); + if (!InvariantGlobalization && (IcuDataFileNames == null || IcuDataFileNames.Length == 0)) + throw new LogAsErrorException("IcuDataFileNames property shouldn't be empty if InvariantGlobalization=false"); if (Assemblies.Length == 0) { @@ -244,6 +247,7 @@ private bool ExecuteInternal () } config.DebugLevel = DebugLevel; + config.IsIcuDataCustom = IsIcuDataCustom; if (SatelliteAssemblies != null) { @@ -311,7 +315,11 @@ private bool ExecuteInternal () } if (!InvariantGlobalization) - config.Assets.Add(new IcuData(IcuDataFileName!) { LoadRemote = RemoteSources?.Length > 0 }); + { + foreach (var idfn in IcuDataFileNames!) + config.Assets.Add(new IcuData(idfn.GetMetadata("Identity")) { LoadRemote = RemoteSources?.Length > 0 }); + } + config.Assets.Add(new VfsEntry ("dotnet.timezones.blat") { VirtualPath = "/usr/share/zoneinfo/"}); config.Assets.Add(new WasmEntry ("dotnet.wasm") ); From 8b293c588ccb8bfdc02f02d6c36c7fa564932d5e Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 Jan 2023 11:24:21 +0100 Subject: [PATCH 04/68] Replaced sample with WBT. --- .../browser-globalization-shards/Makefile | 11 --- .../browser-globalization-shards/Program.cs | 34 -------- .../Wasm.Browser.Globalization.Shards.csproj | 7 -- .../browser-globalization-shards/index.html | 20 ----- .../wasm/browser-globalization-shards/main.js | 25 ------ .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 83 +++++++++++++++++++ src/mono/wasm/runtime/dotnet.d.ts | 2 +- 7 files changed, 84 insertions(+), 98 deletions(-) delete mode 100644 src/mono/sample/wasm/browser-globalization-shards/Makefile delete mode 100644 src/mono/sample/wasm/browser-globalization-shards/Program.cs delete mode 100644 src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj delete mode 100644 src/mono/sample/wasm/browser-globalization-shards/index.html delete mode 100644 src/mono/sample/wasm/browser-globalization-shards/main.js create mode 100644 src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs diff --git a/src/mono/sample/wasm/browser-globalization-shards/Makefile b/src/mono/sample/wasm/browser-globalization-shards/Makefile deleted file mode 100644 index 7ae388cce865d3..00000000000000 --- a/src/mono/sample/wasm/browser-globalization-shards/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -TOP=../../../../.. - -include ../wasm.mk - -ifneq ($(AOT),) -override MSBUILD_ARGS+=/p:RunAOTCompilation=true -endif - -PROJECT_NAME=Wasm.Browser.Sample.csproj - -run: run-browser diff --git a/src/mono/sample/wasm/browser-globalization-shards/Program.cs b/src/mono/sample/wasm/browser-globalization-shards/Program.cs deleted file mode 100644 index e81be408036ca2..00000000000000 --- a/src/mono/sample/wasm/browser-globalization-shards/Program.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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.Runtime.InteropServices.JavaScript; -using System.Globalization; -using System.Text; - -Console.WriteLine("Hello, Browser Shard Sample!"); - -public partial class Sample -{ - [JSExport] - internal static string Greeting() - { - var existingCulture = new CultureInfo("en-GB"); - Console.WriteLine(existingCulture.NumberFormat.CurrencySymbol); - try - { - var nonExistingCulture = new CultureInfo("pl-PL"); - Console.WriteLine(nonExistingCulture.NumberFormat.CurrencySymbol); - } - catch(Exception ex) - { - Console.WriteLine($"Culture pl-PL does not exist on this shard: {ex}"); - } - var text = $"Hello, World! Greetings from {GetHRef()}"; - Console.WriteLine(text); - return text; - } - - [JSImport("window.location.href", "main.js")] - internal static partial string GetHRef(); -} diff --git a/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj b/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj deleted file mode 100644 index a7153ee34ff92a..00000000000000 --- a/src/mono/sample/wasm/browser-globalization-shards/Wasm.Browser.Globalization.Shards.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - icudt_EFIGS.dat - <_SampleProject>Wasm.Browser.Globalization.Shards.csproj - - diff --git a/src/mono/sample/wasm/browser-globalization-shards/index.html b/src/mono/sample/wasm/browser-globalization-shards/index.html deleted file mode 100644 index 4805439dd65d69..00000000000000 --- a/src/mono/sample/wasm/browser-globalization-shards/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - Wasm Browser Globalization Shards Sample - - - - - - - - - Answer to the Ultimate Question of Life, the Universe, and Everything is : - - - - diff --git a/src/mono/sample/wasm/browser-globalization-shards/main.js b/src/mono/sample/wasm/browser-globalization-shards/main.js deleted file mode 100644 index 3696dd9f6e1c91..00000000000000 --- a/src/mono/sample/wasm/browser-globalization-shards/main.js +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { dotnet } from './dotnet.js' - -const { setModuleImports, getAssemblyExports, getConfig } = await dotnet - .withDiagnosticTracing(false) - .withApplicationArgumentsFromQuery() - .create(); - -setModuleImports('main.js', { - window: { - location: { - href: () => globalThis.window.location.href - } - } -}); - -const config = getConfig(); -const exports = await getAssemblyExports(config.mainAssemblyName); -const text = exports.Sample.Greeting(); -console.log(text); - -document.getElementById('out').innerHTML = text; -await dotnet.run(); diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs new file mode 100644 index 00000000000000..31f07192d9bfb5 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -0,0 +1,83 @@ +// 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; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +#nullable enable + +namespace Wasm.Build.Tests +{ + public class IcuShardingTests : BuildTestBase + { + public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + public static IEnumerable BuildAndRun_ShardData(bool aot, RunHost host) + => ConfigWithAOTData(aot) + .Multiply(ShardData) + .WithRunHosts(host) + .UnwrapItemsAsArrays(); + + public static IEnumerable ShardData() + { + yield return new object[] { "icudt_EFIGS.dat", "new string[] { \"en-US\", \"fr-FR\" }", "new string[] { \"pl-PL\", \"ko-KR\" \"cs-CZ\" }" }; + yield return new object[] { "icudt_CJK.dat", "new string[] { \"en-GB\", \"zh-CN\" }", "new string[] { \"fr-FR\", \"hr-HR\", \"it-IT\" }" }; + yield return new object[] { "icudt_no_CJK.dat", "new string[] { \"en-AU\", \"fr-FR\", \"sk-SK\" }", "new string[] { \"ja-JP\", \"ko-KR\", \"zh-CN\"}" }; + } + + protected static string GetProgramText(string expectedLocales, string missingLocales) = $@" + using System; + using System.Globalization; + + string[] expectedLocales = {expectedLocales}; + string[] missingLocales = {missingLocales}; + foreach (var loc in expectedLocales) + {{ + var culture = new CultureInfo(loc); + }} + foreach (var loc in missingLocales) + {{ + try + {{ + var culture = new CultureInfo(loc); + }} + catch() + {{ + Console.WriteLine("failed"); + }} + }} + return 42; + "; + + [Theory] + [MemberData(nameof(BuildAndRun_ShardData), parameters: new object[] { false, RunHost.All })] + [MemberData(nameof(BuildAndRun_ShardData), parameters: new object[] { true, RunHost.All })] + public void TestIcuShard(BuildArgs buildArgs, string shardName, string expectedLocales, string missingLocales, RunHost host, string id) + { + string projectName = $"shard_{shardName}_{buildArgs.Config}_{buildArgs.AOT}"; + + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); + + string programTest = GetProgramText(expectedLocales, missingLocales); + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programTest), + DotnetWasmFromRuntimePack: true, + CustomIcudt: shardName)); + System.Console.WriteLine($"Build output: {output}"); + + string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, + test: output => {}, + host: host, id: id); + Assert.Equals("failed\nfailed\nfailed\n", runOutput); + System.Console.WriteLine($"Run output: {runOutput}"); + } + } +} diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index d58fbbe6d20fab..53429a62aa1f67 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -110,7 +110,7 @@ type MonoConfig = { globalizationMode?: GlobalizationMode; /** * User can choose the option to load only partial globalization data: - * "icudt_EFIGS.dat" for "en-US" "fr-FR" "es-ES" "it-IT" "de-DE" + * "icudt_EFIGS.dat" for "en-*" "fr-FR" "es-ES" "it-IT" "de-DE" * "icudt_CJK.dat" for "ja" "ko" "zh" "en" * "icudt_no_CJK.dat" for all cultures, excluding "ja" "ko" "zh" * "customName" From da5c9bcd986b0f91acb4a8a72364c89074cbf579 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 Jan 2023 11:26:37 +0100 Subject: [PATCH 05/68] Remove unnecessary change. --- src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj index 9c37b19140e836..76ec8950104ab5 100644 --- a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj +++ b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj @@ -1,7 +1,6 @@ - EFIGS <_SampleProject>Wasm.Browser.Sample.csproj From 5ad7252af9506a551d91ff06332036d2aaef6b81 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 Jan 2023 14:35:33 +0100 Subject: [PATCH 06/68] Fixed invariantMode + full custom path + nit. --- src/mono/wasm/build/WasmApp.targets | 7 ++++--- src/mono/wasm/runtime/types.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index ac0384520003fa..51444a1945a1f6 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -316,8 +316,8 @@ - - + + @@ -328,7 +328,8 @@ '$(_HasDotnetJsSymbols)' != 'true' and Exists('$(MicrosoftNetCoreAppRuntimePackRidNativeDir)dotnet.js.symbols')" /> - + + diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index c5ed0f26a39ab4..70e5a437e57ffa 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -176,7 +176,7 @@ export interface AssetEntry extends ResourceRequest { /** * If true, an attempt will be made to load the asset from each location in MonoConfig.remoteSources. */ - loadRemote?: boolean, // + loadRemote?: boolean, /** * If true, the runtime startup would not fail if the asset download was not successful. */ From a30970e42483d5c39c2c848c8325d861ad2edb40 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 Jan 2023 14:35:51 +0100 Subject: [PATCH 07/68] Draft of documentation. --- docs/README.md | 1 + .../design/features/globalization-icu-wasm.md | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 docs/design/features/globalization-icu-wasm.md diff --git a/docs/README.md b/docs/README.md index 023cb7b5686099..6989879155dcc5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,6 +25,7 @@ Design Docs ================= - [.NET Globalization Invariant Mode](design/features/globalization-invariant-mode.md) +- [WASM Globalization Icu](design/features/globalization-icu-wasm.md) - [Cross-Platform Cryptography](design/features/cross-platform-cryptography.md) - Many more under [design/features](design/features/) diff --git a/docs/design/features/globalization-icu-wasm.md b/docs/design/features/globalization-icu-wasm.md new file mode 100644 index 00000000000000..241e43219fc2f4 --- /dev/null +++ b/docs/design/features/globalization-icu-wasm.md @@ -0,0 +1,60 @@ +# WASM Globalization Icu + +In WASM applications when [globalization invariant mode(design/features/globalization-invariant-mode.md)) is switched off we loading internalization data files. There are four basic types of these files: +- `icudt.dat` - full data +- `icudt_EFIGS.dat` - data for locales: "en-*" "fr-FR" "es-ES" "it-IT" "de-DE" +- `icudt_CJK.dat` - for locales: "en" "ja" "ko" "zh" +- `icudt_no_CJK.dat` - all locales from `icudt.dat`, excluding "ja" "ko" "zh". + +## Wasm Console, Wasm Browser + +We can specify the file we want to load, e.g. `icudt_no_CJK.dat` by adding to .csproj: +``` +icudt_no_CJK.dat +``` +IcuFileName can also be a custom file, created by the developer. To create a custom ICU file, see `Custom ICU` section below. +If no IcuFileName was specified, the application's culture will be checked and corresponding file will be loaded, e.g. for `en-US` file `icudt_EFIGS.dat` and for `zh-CN` - `icudt_CJK.dat`. + +## Custom ICU + +Clone https://github.com/dotnet/icu. See files in `./icu-filters`, read https://unicode-org.github.io/icu/userguide/icu_data/buildtool.html#locale-slicing. Build your own filter or edit the existing file. Choose what filters to build in `eng/icu.mk`. Choose the platform: + +- Building for Browser: + For prerequisites run `.devcontainer/postCreateCommand.sh`. + Building: + ``` + ./build.sh /p:TargetOS=Browser /p:TargetArchitecture=wasm /p:IcuTracing=true + Output is located in `artifacts/bin/icu-browser-wasm`. + + ``` +- Building for Mobiles: + + For prerequisites run: + > ```bash + > export ANDROID_NDK_ROOT=$PWD/artifacts/ndk/ + > mkdir $ANDROID_NDK_ROOT + > wget https://dl.google.com/android/repository/android-ndk-r25b-linux.zip + > unzip android-ndk-r25b-linux.zip -d $ANDROID_NDK_ROOT + > rm android-ndk-r25b-linux.zip + > mv $ANDROID_NDK_ROOT/*/* $ANDROID_NDK_ROOT + > rmdir $ANDROID_NDK_ROOT/android-ndk-r25b + > ``` + + Building: + > ```bash + > ./build.sh /p:TargetOS=Android /p:TargetArchitecture=x64 /p:IcuTracing=true + > ``` + +Output from both builds will be located in subdirectories of `artifacts/bin`. Copy the result `.dat` files to a suitable location and provide the full path to it in .csproj, e.g.: +``` +C:\Users\wasmUser\icuSources\customIcu.dat +``` + +## Blazor + +In Blazor we are loading the file based on the applications's culture. +To force full data to be loaded add to your .csproj: +``` +true +``` +Custom files loading for Blazor is not possible. From 147d6041e0d943f5bb2c99fa074d61106b0c224c Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 Jan 2023 15:11:46 +0100 Subject: [PATCH 08/68] Rename to reflect usage better. --- src/mono/wasm/build/WasmApp.targets | 8 ++++---- src/mono/wasm/runtime/assets.ts | 7 ++++--- src/mono/wasm/runtime/dotnet.d.ts | 2 +- src/mono/wasm/runtime/types.ts | 2 +- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 8 ++++---- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 51444a1945a1f6..afac9ca801de28 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -308,7 +308,7 @@ - true + true <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true @@ -316,8 +316,8 @@ - - + + @@ -363,7 +363,7 @@ SatelliteAssemblies="@(_WasmSatelliteAssemblies)" FilesToIncludeInFileSystem="@(WasmFilesToIncludeInFileSystem)" IcuDataFileNames="@(WasmIcuDataFileNames)" - IsIcuDataCustom="$(IsIcuDataCustom)" + IsIcuFilePredefined="$(IsIcuFilePredefined)" RemoteSources="@(WasmRemoteSources)" ExtraFilesToDeploy="@(WasmExtraFilesToDeploy)" ExtraConfig="@(WasmExtraConfig)" diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 7c795dfaa17116..5d4f14630407ea 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -77,8 +77,8 @@ export function get_skipped_icu_assets() : { [name: string]: boolean; } } export function skipIcuAssets(asset : AssetEntryInternal, skipIcuByAssetNames: { [name: string]: boolean; }) : boolean{ - // if custom then there is only one icu file in assets and it should not be skipped - return !(asset.behavior == "icu" && !runtimeHelpers.config.isIcuDataCustom && skipIcuByAssetNames[asset.name]); + // if icu predefined then there is only one icu file in assets and it should not be skipped + return !(asset.behavior == "icu" && !runtimeHelpers.config.isIcuFilePredefined && skipIcuByAssetNames[asset.name]); } export function resolve_asset_path(behavior: AssetBehaviours) { @@ -93,12 +93,13 @@ type AssetWithBuffer = { asset: AssetEntryInternal, buffer?: ArrayBuffer } +const skipIcuByAssetNames = get_skipped_icu_assets(); + export async function mono_download_assets(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; try { const promises_of_assets_with_buffer: Promise[] = []; - const skipIcuByAssetNames = get_skipped_icu_assets(); // start fetching and instantiating all assets in parallel for (const a of runtimeHelpers.config.assets!) { const asset: AssetEntryInternal = a; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 53429a62aa1f67..79c9026a6fb938 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -115,7 +115,7 @@ type MonoConfig = { * "icudt_no_CJK.dat" for all cultures, excluding "ja" "ko" "zh" * "customName" */ - isIcuDataCustom?: boolean; + isIcuFilePredefined?: boolean; /** * debugLevel > 0 enables debugging and sets the debug log level to debugLevel * debugLevel == 0 disables debugging and enables interpreter optimizations diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 70e5a437e57ffa..d31583c35c2183 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -102,7 +102,7 @@ export type MonoConfig = { * "icudt_no_CJK.dat" for all cultures, excluding "ja" "ko" "zh" * "customName" */ - isIcuDataCustom?: boolean, + isIcuFilePredefined?: boolean, /** * debugLevel > 0 enables debugging and sets the debug log level to debugLevel * debugLevel == 0 disables debugging and enables interpreter optimizations diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index b947087f34938d..223c024adc647d 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -38,7 +38,7 @@ public class WasmAppBuilder : Task // full list of ICU data files we produce can be found here: // https://github.com/dotnet/icu/tree/maint/maint-67/icu-filters public ITaskItem[]? IcuDataFileNames { get; set; } - public bool IsIcuDataCustom {get; set;} + public bool IsIcuFilePredefined {get; set;} public int DebugLevel { get; set; } public ITaskItem[]? SatelliteAssemblies { get; set; } @@ -88,8 +88,8 @@ private sealed class WasmAppConfig public List RemoteSources { get; set; } = new List(); [JsonExtensionData] public Dictionary Extra { get; set; } = new(); - [JsonPropertyName("isIcuDataCustom")] - public bool IsIcuDataCustom { get; set; } + [JsonPropertyName("isIcuFilePredefined")] + public bool IsIcuFilePredefined { get; set; } } private class AssetEntry @@ -247,7 +247,7 @@ private bool ExecuteInternal () } config.DebugLevel = DebugLevel; - config.IsIcuDataCustom = IsIcuDataCustom; + config.IsIcuFilePredefined = IsIcuFilePredefined; if (SatelliteAssemblies != null) { From 5d2aa14d442989862aeebbb6e0c248b61174f599 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 Jan 2023 15:12:27 +0100 Subject: [PATCH 09/68] Fix full path file loading to the Browser. --- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 223c024adc647d..0407ae8f7261b6 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -317,7 +317,7 @@ private bool ExecuteInternal () if (!InvariantGlobalization) { foreach (var idfn in IcuDataFileNames!) - config.Assets.Add(new IcuData(idfn.GetMetadata("Identity")) { LoadRemote = RemoteSources?.Length > 0 }); + config.Assets.Add(new IcuData(Path.GetFileName(idfn.GetMetadata("Identity"))) { LoadRemote = RemoteSources?.Length > 0 }); } From ca4c9fe316419c62c2e787578d412a24bceed512 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 Jan 2023 15:13:50 +0100 Subject: [PATCH 10/68] Fix. --- src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 31f07192d9bfb5..4b105ce27c1733 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -5,6 +5,7 @@ using Xunit; using Xunit.Abstractions; using Xunit.Sdk; +using System.Collections.Generic; #nullable enable @@ -30,7 +31,7 @@ public static IEnumerable ShardData() yield return new object[] { "icudt_no_CJK.dat", "new string[] { \"en-AU\", \"fr-FR\", \"sk-SK\" }", "new string[] { \"ja-JP\", \"ko-KR\", \"zh-CN\"}" }; } - protected static string GetProgramText(string expectedLocales, string missingLocales) = $@" + protected static string GetProgramText(string expectedLocales, string missingLocales) => $@" using System; using System.Globalization; @@ -48,7 +49,7 @@ protected static string GetProgramText(string expectedLocales, string missingLoc }} catch() {{ - Console.WriteLine("failed"); + Console.WriteLine(""failed""); }} }} return 42; From ef0782ccc8929e6fedd31c1d5fef40caffb6f776 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 Jan 2023 15:53:42 +0100 Subject: [PATCH 11/68] Fixed WBT build errors. --- src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs | 12 +++++++++--- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 16 ++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index da06061a47aa35..96b2e557129fdd 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -423,7 +423,8 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp options.HasV8Script, options.TargetFramework ?? DefaultTargetFramework, options.HasIcudt, - options.DotnetWasmFromRuntimePack ?? !buildArgs.AOT); + options.DotnetWasmFromRuntimePack ?? !buildArgs.AOT, + options.PredefinedIcudt ?? ""); } if (options.UseCache) @@ -616,7 +617,8 @@ protected static void AssertBasicAppBundle(string bundleDir, bool hasV8Script, string targetFramework, bool hasIcudt = true, - bool dotnetWasmFromRuntimePack = true) + bool dotnetWasmFromRuntimePack = true, + string predefinedIcudt = "") { AssertFilesExist(bundleDir, new [] { @@ -629,7 +631,10 @@ protected static void AssertBasicAppBundle(string bundleDir, }); AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script); - AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: hasIcudt); + if (!string.IsNullOrEmpty(predefinedIcudt)) + AssertFilesExist(bundleDir, new[] { predefinedIcudt }, expectToExist: true); + else + AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: hasIcudt); string managedDir = Path.Combine(bundleDir, "managed"); AssertFilesExist(managedDir, new[] { $"{projectName}.dll" }); @@ -1097,6 +1102,7 @@ public record BuildProjectOptions string? Label = null, string? TargetFramework = null, string? MainJS = null, + string? PredefinedIcudt = null, IDictionary? ExtraBuildEnvironmentVariables = null ); diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 4b105ce27c1733..3b5a8552e5c2a8 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -20,17 +20,13 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture public static IEnumerable BuildAndRun_ShardData(bool aot, RunHost host) => ConfigWithAOTData(aot) - .Multiply(ShardData) + .Multiply( + new object[] { "icudt_EFIGS.dat", "new string[] { \"en-US\", \"fr-FR\" }", "new string[] { \"pl-PL\", \"ko-KR\" \"cs-CZ\" }" }, + new object[] { "icudt_CJK.dat", "new string[] { \"en-GB\", \"zh-CN\" }", "new string[] { \"fr-FR\", \"hr-HR\", \"it-IT\" }" }, + new object[] { "icudt_no_CJK.dat", "new string[] { \"en-AU\", \"fr-FR\", \"sk-SK\" }", "new string[] { \"ja-JP\", \"ko-KR\", \"zh-CN\"}" }) .WithRunHosts(host) .UnwrapItemsAsArrays(); - public static IEnumerable ShardData() - { - yield return new object[] { "icudt_EFIGS.dat", "new string[] { \"en-US\", \"fr-FR\" }", "new string[] { \"pl-PL\", \"ko-KR\" \"cs-CZ\" }" }; - yield return new object[] { "icudt_CJK.dat", "new string[] { \"en-GB\", \"zh-CN\" }", "new string[] { \"fr-FR\", \"hr-HR\", \"it-IT\" }" }; - yield return new object[] { "icudt_no_CJK.dat", "new string[] { \"en-AU\", \"fr-FR\", \"sk-SK\" }", "new string[] { \"ja-JP\", \"ko-KR\", \"zh-CN\"}" }; - } - protected static string GetProgramText(string expectedLocales, string missingLocales) => $@" using System; using System.Globalization; @@ -71,13 +67,13 @@ public void TestIcuShard(BuildArgs buildArgs, string shardName, string expectedL new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programTest), DotnetWasmFromRuntimePack: true, - CustomIcudt: shardName)); + PredefinedIcudt: shardName)); System.Console.WriteLine($"Build output: {output}"); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, test: output => {}, host: host, id: id); - Assert.Equals("failed\nfailed\nfailed\n", runOutput); + Assert.Equal("failed\nfailed\nfailed\n", runOutput); System.Console.WriteLine($"Run output: {runOutput}"); } } From 002cbc5012c6a5d4337cc1678ad1eb3afacd9827 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 Jan 2023 15:56:08 +0100 Subject: [PATCH 12/68] Docs: only one ICU file can be used. --- docs/design/features/globalization-icu-wasm.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/design/features/globalization-icu-wasm.md b/docs/design/features/globalization-icu-wasm.md index 241e43219fc2f4..2547f62930164a 100644 --- a/docs/design/features/globalization-icu-wasm.md +++ b/docs/design/features/globalization-icu-wasm.md @@ -12,8 +12,7 @@ We can specify the file we want to load, e.g. `icudt_no_CJK.dat` by adding to .c ``` icudt_no_CJK.dat ``` -IcuFileName can also be a custom file, created by the developer. To create a custom ICU file, see `Custom ICU` section below. -If no IcuFileName was specified, the application's culture will be checked and corresponding file will be loaded, e.g. for `en-US` file `icudt_EFIGS.dat` and for `zh-CN` - `icudt_CJK.dat`. +Only one value for ICU file can be set. IcuFileName can also be a custom file, created by the developer. To create a custom ICU file, see `Custom ICU` section below. If no IcuFileName was specified, the application's culture will be checked and corresponding file will be loaded, e.g. for `en-US` file `icudt_EFIGS.dat` and for `zh-CN` - `icudt_CJK.dat`. ## Custom ICU From 235451423cbcea2a646b7eace02d4c1103c21c2c Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 11 Jan 2023 10:13:41 +0100 Subject: [PATCH 13/68] Fixed NodeJs: `navigator` is not defined. --- src/mono/wasm/runtime/assets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 5d4f14630407ea..2f56de8c192dce 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -49,7 +49,7 @@ const skipInstantiateByAssetTypes: { export function get_skipped_icu_assets() : { [name: string]: boolean; } { - const preferredCulture = (navigator.languages && navigator.languages[0]); + const preferredCulture = Intl.DateTimeFormat().resolvedOptions().locale; const prefix = preferredCulture.split("-")[0]; if (["en", "fr", "it", "de", "es"].includes(prefix)) { From e8b1af585655c8ed16d85ac4164521490e7e1a6e Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 11 Jan 2023 11:15:12 +0100 Subject: [PATCH 14/68] Paritial fix of sharding tests. --- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 3b5a8552e5c2a8..445e2931dae794 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -21,8 +21,8 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture public static IEnumerable BuildAndRun_ShardData(bool aot, RunHost host) => ConfigWithAOTData(aot) .Multiply( - new object[] { "icudt_EFIGS.dat", "new string[] { \"en-US\", \"fr-FR\" }", "new string[] { \"pl-PL\", \"ko-KR\" \"cs-CZ\" }" }, - new object[] { "icudt_CJK.dat", "new string[] { \"en-GB\", \"zh-CN\" }", "new string[] { \"fr-FR\", \"hr-HR\", \"it-IT\" }" }, + new object[] { "icudt_EFIGS.dat", "new string[] { \"en-US\", \"fr-FR\", \"es-ES\" }", "new string[] { \"pl-PL\", \"ko-KR\", \"cs-CZ\" }" }, + new object[] { "icudt_CJK.dat", "new string[] { \"en-GB\", \"zh-CN\", \"ja-JP\" }", "new string[] { \"fr-FR\", \"hr-HR\", \"it-IT\" }" }, new object[] { "icudt_no_CJK.dat", "new string[] { \"en-AU\", \"fr-FR\", \"sk-SK\" }", "new string[] { \"ja-JP\", \"ko-KR\", \"zh-CN\"}" }) .WithRunHosts(host) .UnwrapItemsAsArrays(); @@ -33,9 +33,12 @@ protected static string GetProgramText(string expectedLocales, string missingLoc string[] expectedLocales = {expectedLocales}; string[] missingLocales = {missingLocales}; + int missingLocalesCnt = 0; + int expectedLocalesCnt = 0; foreach (var loc in expectedLocales) {{ var culture = new CultureInfo(loc); + expectedLocalesCnt++; }} foreach (var loc in missingLocales) {{ @@ -43,14 +46,18 @@ protected static string GetProgramText(string expectedLocales, string missingLoc {{ var culture = new CultureInfo(loc); }} - catch() + catch(Exception) {{ - Console.WriteLine(""failed""); + missingLocalesCnt++; }} }} + Console.WriteLine($""Missing locales count: {{missingLocalesCnt}}, expected locales count: {{expectedLocalesCnt}}.""); return 42; "; + + // on Chrome: when loading only EFIGS or only CJK, CoreLib's failure on culture not found cannot be easily caught: + // Encountered infinite recursion while looking up resource 'Argument_CultureNotSupported' in System.Private.CoreLib. [Theory] [MemberData(nameof(BuildAndRun_ShardData), parameters: new object[] { false, RunHost.All })] [MemberData(nameof(BuildAndRun_ShardData), parameters: new object[] { true, RunHost.All })] @@ -68,13 +75,11 @@ public void TestIcuShard(BuildArgs buildArgs, string shardName, string expectedL InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programTest), DotnetWasmFromRuntimePack: true, PredefinedIcudt: shardName)); - System.Console.WriteLine($"Build output: {output}"); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, test: output => {}, host: host, id: id); - Assert.Equal("failed\nfailed\nfailed\n", runOutput); - System.Console.WriteLine($"Run output: {runOutput}"); + Assert.Contains("Missing locales count: 3, expected locales count: 3.", runOutput); } } } From b3ff24105d78112ab689cccc5e40b63be13b07d1 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 11 Jan 2023 12:09:48 +0100 Subject: [PATCH 15/68] RuntimePack can differ for AOT and Release. --- src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 445e2931dae794..e47a551fb539ab 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -64,6 +64,7 @@ protected static string GetProgramText(string expectedLocales, string missingLoc public void TestIcuShard(BuildArgs buildArgs, string shardName, string expectedLocales, string missingLocales, RunHost host, string id) { string projectName = $"shard_{shardName}_{buildArgs.Config}_{buildArgs.AOT}"; + bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); @@ -73,7 +74,7 @@ public void TestIcuShard(BuildArgs buildArgs, string shardName, string expectedL id: id, new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programTest), - DotnetWasmFromRuntimePack: true, + DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, PredefinedIcudt: shardName)); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, From 348d4bf1aa6168c61e18330e94a058fa8c188a20 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 11 Jan 2023 13:55:35 +0100 Subject: [PATCH 16/68] Fix `ja-JP` culture not found in CheckDateTimeLocale. --- .../wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj index 8789247541c8da..670c176e137323 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj @@ -14,6 +14,9 @@ $(OutputPath).runsettings $(ArtifactsObjDir)vscode\.runsettings + + + icudt.dat From ef2dc3c8863a19e5e81f64e9503e711c64d8c53a Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 11 Jan 2023 14:22:09 +0100 Subject: [PATCH 17/68] Fixedculture not found in SatelliteAssemblyTests. --- .../wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs index b7c95aaae91e36..f5063b5d39974d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs @@ -45,7 +45,9 @@ public void ResourcesFromMainAssembly(BuildArgs buildArgs, string extraProperties = (nativeRelink ? $"true" : string.Empty) // make ASSERTIONS=1 so that we test with it + $"-O0 -sASSERTIONS=1" - + $"-O1"; + + $"-O1" + // to access simultaneously locales: es-ES and ja-JP: + + $"icudt.dat"; buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, @@ -86,7 +88,9 @@ public void ResourcesFromProjectReference(BuildArgs buildArgs, string extraProperties = $"{(nativeRelink ? "true" : "false")}" // make ASSERTIONS=1 so that we test with it + $"-O0 -sASSERTIONS=1" - + $"-O1"; + + $"-O1" + // to access simultaneously locales: es-ES and ja-JP: + + $"icudt.dat"; buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, From b1b2b52acd4d6b7f9e8b454f3ff3509bbea169d7 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 11 Jan 2023 15:49:03 +0100 Subject: [PATCH 18/68] Fixed culture not found in System.Runtime tests. --- src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 32c2137af5a21c..172474d2cec686 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -7,9 +7,10 @@ true $(Features.Replace('nullablePublicOnly', '') + icudt.dat - From 5df3c9ccc851c45fbdbc3acfd96162c3237973c1 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 10:23:35 +0100 Subject: [PATCH 19/68] `icudt.dat` should not be loaded when empty + EFIGS contains only some specific locales + `IsIcuFilePredefined` is not necessary. --- src/mono/wasm/build/WasmApp.targets | 6 ++--- src/mono/wasm/runtime/assets.ts | 28 ++++++++++++---------- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 8 ++----- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index afac9ca801de28..5d733fdb87764a 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -308,7 +308,6 @@ - true <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true @@ -316,8 +315,8 @@ - - + + @@ -363,7 +362,6 @@ SatelliteAssemblies="@(_WasmSatelliteAssemblies)" FilesToIncludeInFileSystem="@(WasmFilesToIncludeInFileSystem)" IcuDataFileNames="@(WasmIcuDataFileNames)" - IsIcuFilePredefined="$(IsIcuFilePredefined)" RemoteSources="@(WasmRemoteSources)" ExtraFilesToDeploy="@(WasmExtraFilesToDeploy)" ExtraConfig="@(WasmExtraConfig)" diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 2f56de8c192dce..36da79b2c82429 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -51,34 +51,30 @@ export function get_skipped_icu_assets() : { [name: string]: boolean; } { const preferredCulture = Intl.DateTimeFormat().resolvedOptions().locale; const prefix = preferredCulture.split("-")[0]; - if (["en", "fr", "it", "de", "es"].includes(prefix)) + if (["en", "fr-FR", "it-IT", "de-DE", "es-ES"].includes(prefix)) { return { - "icudt.dat": true, "icudt_EFIGS.dat": false, "icudt_CJK.dat": true, "icudt_no_CJK.dat": true }; } - if (["zh", "ko", "ja",].includes(prefix)) { + if (["zh", "ko", "ja"].includes(prefix)) { return { - "icudt.dat": true, "icudt_EFIGS.dat": true, "icudt_CJK.dat": false, "icudt_no_CJK.dat": true }; } return { - "icudt.dat": true, "icudt_EFIGS.dat": true, "icudt_CJK.dat": true, "icudt_no_CJK.dat": false }; } -export function skipIcuAssets(asset : AssetEntryInternal, skipIcuByAssetNames: { [name: string]: boolean; }) : boolean{ - // if icu predefined then there is only one icu file in assets and it should not be skipped - return !(asset.behavior == "icu" && !runtimeHelpers.config.isIcuFilePredefined && skipIcuByAssetNames[asset.name]); +export function shouldLoadIcuAsset(asset : AssetEntryInternal, isIcuAssetPredefined: boolean) : boolean{ + return !(asset.behavior == "icu" && !isIcuAssetPredefined && skipIcuByAssetNames[asset.name]); } export function resolve_asset_path(behavior: AssetBehaviours) { @@ -93,12 +89,19 @@ type AssetWithBuffer = { asset: AssetEntryInternal, buffer?: ArrayBuffer } + const skipIcuByAssetNames = get_skipped_icu_assets(); export async function mono_download_assets(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; try { + // By setting user can define what ICU source file they want to load. + // There is no need to check application's culture when is set. + // If it was not set, then we have 3 "icu" assets in config and we should choose + // only one for loading, this one that matches the application's locale. + const isIcuAssetPredefined : boolean = runtimeHelpers.config.assets?.filter(a => a["behavior"] == "icu").length === 1; + console.log(`ILONA, isIcuAssetPredefined ${isIcuAssetPredefined}; ${runtimeHelpers.config.assets?.filter(a => a["behavior"] == "icu").length}`); const promises_of_assets_with_buffer: Promise[] = []; // start fetching and instantiating all assets in parallel for (const a of runtimeHelpers.config.assets!) { @@ -109,10 +112,11 @@ export async function mono_download_assets(): Promise { mono_assert(!asset.resolvedUrl || typeof asset.resolvedUrl === "string", "asset resolvedUrl could be string"); mono_assert(!asset.hash || typeof asset.hash === "string", "asset resolvedUrl could be string"); mono_assert(!asset.pendingDownload || typeof asset.pendingDownload === "object", "asset pendingDownload could be object"); - if (!skipInstantiateByAssetTypes[asset.behavior] && skipIcuAssets(asset, skipIcuByAssetNames)) { + if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, isIcuAssetPredefined)) { expected_instantiated_assets_count++; } - if (!skipDownloadsByAssetTypes[asset.behavior] && skipIcuAssets(asset, skipIcuByAssetNames)) { + if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, isIcuAssetPredefined)) { + console.log(`ILONA, loading ${asset["behavior"]}, ${asset["name"]}`); expected_downloaded_assets_count++; promises_of_assets_with_buffer.push(start_asset_download(asset)); } @@ -142,10 +146,10 @@ export async function mono_download_assets(): Promise { const headersOnly = skipBufferByAssetTypes[asset.behavior]; if (!headersOnly) { mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); - if (!skipDownloadsByAssetTypes[asset.behavior] && skipIcuAssets(asset, skipIcuByAssetNames)) { + if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, isIcuAssetPredefined)) { expected_downloaded_assets_count--; } - if (!skipInstantiateByAssetTypes[asset.behavior] && skipIcuAssets(asset, skipIcuByAssetNames)) { + if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, isIcuAssetPredefined)) { expected_instantiated_assets_count--; } } diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 0407ae8f7261b6..125eb8efe730b1 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -37,8 +37,7 @@ public class WasmAppBuilder : Task // full list of ICU data files we produce can be found here: // https://github.com/dotnet/icu/tree/maint/maint-67/icu-filters - public ITaskItem[]? IcuDataFileNames { get; set; } - public bool IsIcuFilePredefined {get; set;} + public string[]? IcuDataFileNames { get; set; } public int DebugLevel { get; set; } public ITaskItem[]? SatelliteAssemblies { get; set; } @@ -88,8 +87,6 @@ private sealed class WasmAppConfig public List RemoteSources { get; set; } = new List(); [JsonExtensionData] public Dictionary Extra { get; set; } = new(); - [JsonPropertyName("isIcuFilePredefined")] - public bool IsIcuFilePredefined { get; set; } } private class AssetEntry @@ -247,7 +244,6 @@ private bool ExecuteInternal () } config.DebugLevel = DebugLevel; - config.IsIcuFilePredefined = IsIcuFilePredefined; if (SatelliteAssemblies != null) { @@ -317,7 +313,7 @@ private bool ExecuteInternal () if (!InvariantGlobalization) { foreach (var idfn in IcuDataFileNames!) - config.Assets.Add(new IcuData(Path.GetFileName(idfn.GetMetadata("Identity"))) { LoadRemote = RemoteSources?.Length > 0 }); + config.Assets.Add(new IcuData(Path.GetFileName(idfn)) { LoadRemote = RemoteSources?.Length > 0 }); } From d55aeb38bbac0a4162d89b936975c2e2dd734262 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 10:38:31 +0100 Subject: [PATCH 20/68] Improved doc readability using @radical's suggestions. --- .../design/features/globalization-icu-wasm.md | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/docs/design/features/globalization-icu-wasm.md b/docs/design/features/globalization-icu-wasm.md index 2547f62930164a..89def053f4b1f8 100644 --- a/docs/design/features/globalization-icu-wasm.md +++ b/docs/design/features/globalization-icu-wasm.md @@ -1,10 +1,10 @@ # WASM Globalization Icu -In WASM applications when [globalization invariant mode(design/features/globalization-invariant-mode.md)) is switched off we loading internalization data files. There are four basic types of these files: +In WASM applications when [globalization invariant mode](globalization-invariant-mode.md) is switched off, internalization data file is loaded. There are four basic types of these files: - `icudt.dat` - full data -- `icudt_EFIGS.dat` - data for locales: "en-*" "fr-FR" "es-ES" "it-IT" "de-DE" -- `icudt_CJK.dat` - for locales: "en" "ja" "ko" "zh" -- `icudt_no_CJK.dat` - all locales from `icudt.dat`, excluding "ja" "ko" "zh". +- `icudt_EFIGS.dat` - data for locales: "en-*", "fr-FR", "es-ES", "it-IT", and "de-DE". +- `icudt_CJK.dat` - for locales: "en" "ja", "ko", and "zh". +- `icudt_no_CJK.dat` - all locales from `icudt.dat`, excluding "ja", "ko", and "zh". ## Wasm Console, Wasm Browser @@ -12,48 +12,46 @@ We can specify the file we want to load, e.g. `icudt_no_CJK.dat` by adding to .c ``` icudt_no_CJK.dat ``` -Only one value for ICU file can be set. IcuFileName can also be a custom file, created by the developer. To create a custom ICU file, see `Custom ICU` section below. If no IcuFileName was specified, the application's culture will be checked and corresponding file will be loaded, e.g. for `en-US` file `icudt_EFIGS.dat` and for `zh-CN` - `icudt_CJK.dat`. +Only one value for `IcuFileName` can be set. It can also be a custom file, created by the developer. To create a custom ICU file, see `Custom ICU` section below. If no `IcuFileName` was specified, the application's culture will be checked and the corresponding file will be loaded, e.g. for `en-US` file `icudt_EFIGS.dat`, and for `zh-CN` - `icudt_CJK.dat`. ## Custom ICU -Clone https://github.com/dotnet/icu. See files in `./icu-filters`, read https://unicode-org.github.io/icu/userguide/icu_data/buildtool.html#locale-slicing. Build your own filter or edit the existing file. Choose what filters to build in `eng/icu.mk`. Choose the platform: +Clone https://github.com/dotnet/icu . See files in https://github.com/dotnet/icu/tree/dotnet/main/icu-filters, and read https://unicode-org.github.io/icu/userguide/icu_data/buildtool.html#locale-slicing. Build your own filter or edit the existing file. Choose what filters to build in `eng/icu.mk`. Choose the platform: -- Building for Browser: - For prerequisites run `.devcontainer/postCreateCommand.sh`. - Building: +### Building for Browser: +* For prerequisites run `.devcontainer/postCreateCommand.sh`. +* Building: ``` ./build.sh /p:TargetOS=Browser /p:TargetArchitecture=wasm /p:IcuTracing=true + ``` Output is located in `artifacts/bin/icu-browser-wasm`. +### Building for Mobiles: +* For prerequisites run: + ```bash + export ANDROID_NDK_ROOT=$PWD/artifacts/ndk/ + mkdir $ANDROID_NDK_ROOT + wget https://dl.google.com/android/repository/android-ndk-r25b-linux.zip + unzip android-ndk-r25b-linux.zip -d $ANDROID_NDK_ROOT + rm android-ndk-r25b-linux.zip + mv $ANDROID_NDK_ROOT/*/* $ANDROID_NDK_ROOT + rmdir $ANDROID_NDK_ROOT/android-ndk-r25b + ``` +* Building: + ```bash + ./build.sh /p:TargetOS=Android /p:TargetArchitecture=x64 /p:IcuTracing=true ``` -- Building for Mobiles: - - For prerequisites run: - > ```bash - > export ANDROID_NDK_ROOT=$PWD/artifacts/ndk/ - > mkdir $ANDROID_NDK_ROOT - > wget https://dl.google.com/android/repository/android-ndk-r25b-linux.zip - > unzip android-ndk-r25b-linux.zip -d $ANDROID_NDK_ROOT - > rm android-ndk-r25b-linux.zip - > mv $ANDROID_NDK_ROOT/*/* $ANDROID_NDK_ROOT - > rmdir $ANDROID_NDK_ROOT/android-ndk-r25b - > ``` - - Building: - > ```bash - > ./build.sh /p:TargetOS=Android /p:TargetArchitecture=x64 /p:IcuTracing=true - > ``` - -Output from both builds will be located in subdirectories of `artifacts/bin`. Copy the result `.dat` files to a suitable location and provide the full path to it in .csproj, e.g.: -``` + +Output from both builds will be located in subdirectories of `artifacts/bin`. Copy the generated `.dat` files to a suitable location and provide the full path to it in the `.csproj`, e.g.: +```xml C:\Users\wasmUser\icuSources\customIcu.dat ``` ## Blazor In Blazor we are loading the file based on the applications's culture. -To force full data to be loaded add to your .csproj: -``` +To force the full data to be loaded, add this to your `.csproj`: +```xml true ``` Custom files loading for Blazor is not possible. From d2297367abfe6e19a182c160f8800dc3c9bdfd0e Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 11:30:28 +0100 Subject: [PATCH 21/68] Forgot to remove comments. --- src/mono/wasm/runtime/assets.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 36da79b2c82429..ca27195676444b 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -101,7 +101,6 @@ export async function mono_download_assets(): Promise { // If it was not set, then we have 3 "icu" assets in config and we should choose // only one for loading, this one that matches the application's locale. const isIcuAssetPredefined : boolean = runtimeHelpers.config.assets?.filter(a => a["behavior"] == "icu").length === 1; - console.log(`ILONA, isIcuAssetPredefined ${isIcuAssetPredefined}; ${runtimeHelpers.config.assets?.filter(a => a["behavior"] == "icu").length}`); const promises_of_assets_with_buffer: Promise[] = []; // start fetching and instantiating all assets in parallel for (const a of runtimeHelpers.config.assets!) { @@ -116,7 +115,6 @@ export async function mono_download_assets(): Promise { expected_instantiated_assets_count++; } if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, isIcuAssetPredefined)) { - console.log(`ILONA, loading ${asset["behavior"]}, ${asset["name"]}`); expected_downloaded_assets_count++; promises_of_assets_with_buffer.push(start_asset_download(asset)); } From f06aba47d4dcc91a1c9fbdd8594e09b1b023b25a Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 12:14:14 +0100 Subject: [PATCH 22/68] Missing change for 5df3c9ccc851c45fbdbc3acfd96162c3237973c1. --- src/mono/wasm/runtime/dotnet.d.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 79c9026a6fb938..516475c639a518 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -3,7 +3,7 @@ //! //! This is generated file, see src/mono/wasm/runtime/rollup.config.js -//! This is not considered public API with backward compatibility guarantees. +//! This is not considered public API with backward compatibility guarantees. interface DotnetHostBuilder { withConfig(config: MonoConfig): DotnetHostBuilder; @@ -108,14 +108,6 @@ type MonoConfig = { * Configures the runtime's globalization mode */ globalizationMode?: GlobalizationMode; - /** - * User can choose the option to load only partial globalization data: - * "icudt_EFIGS.dat" for "en-*" "fr-FR" "es-ES" "it-IT" "de-DE" - * "icudt_CJK.dat" for "ja" "ko" "zh" "en" - * "icudt_no_CJK.dat" for all cultures, excluding "ja" "ko" "zh" - * "customName" - */ - isIcuFilePredefined?: boolean; /** * debugLevel > 0 enables debugging and sets the debug log level to debugLevel * debugLevel == 0 disables debugging and enables interpreter optimizations From 2d2ef6042983685f849fcb3305a490864109148d Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 12:43:45 +0100 Subject: [PATCH 23/68] WBT: Applied review. --- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 120 +++++++++--------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index e47a551fb539ab..8e3367367f69f9 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -9,78 +9,82 @@ #nullable enable -namespace Wasm.Build.Tests +namespace Wasm.Build.Tests; + +public class IcuShardingTests : BuildTestBase { - public class IcuShardingTests : BuildTestBase + public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) { - public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } + } - public static IEnumerable BuildAndRun_ShardData(bool aot, RunHost host) - => ConfigWithAOTData(aot) - .Multiply( - new object[] { "icudt_EFIGS.dat", "new string[] { \"en-US\", \"fr-FR\", \"es-ES\" }", "new string[] { \"pl-PL\", \"ko-KR\", \"cs-CZ\" }" }, - new object[] { "icudt_CJK.dat", "new string[] { \"en-GB\", \"zh-CN\", \"ja-JP\" }", "new string[] { \"fr-FR\", \"hr-HR\", \"it-IT\" }" }, - new object[] { "icudt_no_CJK.dat", "new string[] { \"en-AU\", \"fr-FR\", \"sk-SK\" }", "new string[] { \"ja-JP\", \"ko-KR\", \"zh-CN\"}" }) - .WithRunHosts(host) - .UnwrapItemsAsArrays(); + public static IEnumerable IcuExpectedAndMissingShardTestData(bool aot, RunHost host) + => ConfigWithAOTData(aot) + .Multiply( + new object[] { "icudt.dat", new string[] { "en-GB", "zh-CN", "hr-HR" }, new string[] { "xx-yy" } }, + new object[] { "icudt_EFIGS.dat", new string[] { "en-US", "fr-FR", "es-ES" }, new string[] { "pl-PL", "ko-KR", "cs-CZ" } }, + new object[] { "icudt_CJK.dat", new string[] { "en-GB", "zh-CN", "ja-JP" }, new string[] { "fr-FR", "hr-HR", "it-IT" } }, + new object[] { "icudt_no_CJK.dat", new string[] { "en-AU", "fr-FR", "sk-SK" }, new string[] { "ja-JP", "ko-KR", "zh-CN"} }) + .WithRunHosts(host) + .UnwrapItemsAsArrays(); - protected static string GetProgramText(string expectedLocales, string missingLocales) => $@" - using System; - using System.Globalization; + private static string GetProgramText(string expectedLocalesTxt, string missingLocalesTxt) => $@" + using System; + using System.Globalization; - string[] expectedLocales = {expectedLocales}; - string[] missingLocales = {missingLocales}; - int missingLocalesCnt = 0; - int expectedLocalesCnt = 0; - foreach (var loc in expectedLocales) + string[] expectedLocales = new string[] {{ "" {expectedLocalesTxt} "" }}; + string[] missingLocales = new string[] {{ "" {missingLocalesTxt} "" }}; + foreach (var loc in expectedLocales) + {{ + var culture = new CultureInfo(loc); + Console.WriteLine($""Found expected locale: {{loc}} - {{culture.Name}}""); + }} + foreach (var loc in missingLocales) + {{ + try {{ var culture = new CultureInfo(loc); - expectedLocalesCnt++; }} - foreach (var loc in missingLocales) + catch(Exception) {{ - try - {{ - var culture = new CultureInfo(loc); - }} - catch(Exception) - {{ - missingLocalesCnt++; - }} + Console.WriteLine($""Missing locale as planned: {{loc}}""); }} - Console.WriteLine($""Missing locales count: {{missingLocalesCnt}}, expected locales count: {{expectedLocalesCnt}}.""); - return 42; - "; + }} + return 42; + "; + + // on Chrome: when loading only EFIGS or only CJK, CoreLib's failure on culture not found cannot be easily caught: + // Encountered infinite recursion while looking up resource 'Argument_CultureNotSupported' in System.Private.CoreLib. + [Theory] + [MemberData(nameof(IcuExpectedAndMissingShardTestData), parameters: new object[] { false, RunHost.All })] + [MemberData(nameof(IcuExpectedAndMissingShardTestData), parameters: new object[] { true, RunHost.All })] + public void TestIcuShard(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) + { + string projectName = $"shard_{shardName}_{buildArgs.Config}_{buildArgs.AOT}"; + bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); + + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); + string expectedLocalesTxt = $"{string.Join("\", \"", expectedLocales)}"; + string missingLocalesTxt = $"{string.Join("\", \"", missingLocales)}"; - // on Chrome: when loading only EFIGS or only CJK, CoreLib's failure on culture not found cannot be easily caught: - // Encountered infinite recursion while looking up resource 'Argument_CultureNotSupported' in System.Private.CoreLib. - [Theory] - [MemberData(nameof(BuildAndRun_ShardData), parameters: new object[] { false, RunHost.All })] - [MemberData(nameof(BuildAndRun_ShardData), parameters: new object[] { true, RunHost.All })] - public void TestIcuShard(BuildArgs buildArgs, string shardName, string expectedLocales, string missingLocales, RunHost host, string id) - { - string projectName = $"shard_{shardName}_{buildArgs.Config}_{buildArgs.AOT}"; - bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); + string programText = GetProgramText(expectedLocalesTxt, missingLocalesTxt); + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), + DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, + PredefinedIcudt: shardName)); - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); + string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, + test: output => {}, + host: host, id: id); - string programTest = GetProgramText(expectedLocales, missingLocales); - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programTest), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - PredefinedIcudt: shardName)); + foreach (var loc in expectedLocales) + Assert.Contains($"Found expected locale: {loc} - {loc}", runOutput); - string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, - test: output => {}, - host: host, id: id); - Assert.Contains("Missing locales count: 3, expected locales count: 3.", runOutput); - } + foreach (var loc in missingLocales) + Assert.Contains($"Missing locale as planned: {loc}", runOutput); } } From 983e62bacbfd88ccbb5db4424402f68d2da04bfe Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 14:49:50 +0100 Subject: [PATCH 24/68] Applied @pavelsavara's suggestion. --- src/mono/wasm/runtime/assets.ts | 63 +++++++++++++++------------------ 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index ca27195676444b..efa63cacdce251 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -47,34 +47,34 @@ const skipInstantiateByAssetTypes: { "dotnetwasm": true, }; -export function get_skipped_icu_assets() : { [name: string]: boolean; } -{ +export function get_preferred_icu_asset(): string | null { + if (!runtimeHelpers.config.assets) + return null; + + // By setting user can define what ICU source file they want to load. + // There is no need to check application's culture when is set. + // If it was not set, then we have 3 "icu" assets in config and we should choose + // only one for loading, this one that matches the application's locale. + const icuAssets = runtimeHelpers.config.assets.filter(a => a["behavior"] == "icu"); + if (icuAssets.length === 1) + return icuAssets[0].name; + const preferredCulture = Intl.DateTimeFormat().resolvedOptions().locale; const prefix = preferredCulture.split("-")[0]; - if (["en", "fr-FR", "it-IT", "de-DE", "es-ES"].includes(prefix)) - { - return { - "icudt_EFIGS.dat": false, - "icudt_CJK.dat": true, - "icudt_no_CJK.dat": true - }; - } - if (["zh", "ko", "ja"].includes(prefix)) { - return { - "icudt_EFIGS.dat": true, - "icudt_CJK.dat": false, - "icudt_no_CJK.dat": true - }; - } - return { - "icudt_EFIGS.dat": true, - "icudt_CJK.dat": true, - "icudt_no_CJK.dat": false - }; + const CJK = "icudt_CJK.dat"; + const EFIGS = "icudt_EFIGS.dat"; + const OTHERS = "icudt_no_CJK.dat"; + + // not all "fr", "it", "de", "es" are in EFIGS, only the one that is mostly used + if (prefix == "en" || ["fr-FR", "it-IT", "de-DE", "es-ES"].includes(preferredCulture)) + return EFIGS; + if (["zh", "ko", "ja"].includes(prefix)) + return CJK; + return OTHERS; } -export function shouldLoadIcuAsset(asset : AssetEntryInternal, isIcuAssetPredefined: boolean) : boolean{ - return !(asset.behavior == "icu" && !isIcuAssetPredefined && skipIcuByAssetNames[asset.name]); +export function shouldLoadIcuAsset(asset : AssetEntryInternal) : boolean{ + return !(asset.behavior == "icu" && asset.name === preferredIcuAsset); } export function resolve_asset_path(behavior: AssetBehaviours) { @@ -90,17 +90,12 @@ type AssetWithBuffer = { buffer?: ArrayBuffer } -const skipIcuByAssetNames = get_skipped_icu_assets(); +const preferredIcuAsset = get_preferred_icu_asset(); export async function mono_download_assets(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; try { - // By setting user can define what ICU source file they want to load. - // There is no need to check application's culture when is set. - // If it was not set, then we have 3 "icu" assets in config and we should choose - // only one for loading, this one that matches the application's locale. - const isIcuAssetPredefined : boolean = runtimeHelpers.config.assets?.filter(a => a["behavior"] == "icu").length === 1; const promises_of_assets_with_buffer: Promise[] = []; // start fetching and instantiating all assets in parallel for (const a of runtimeHelpers.config.assets!) { @@ -111,10 +106,10 @@ export async function mono_download_assets(): Promise { mono_assert(!asset.resolvedUrl || typeof asset.resolvedUrl === "string", "asset resolvedUrl could be string"); mono_assert(!asset.hash || typeof asset.hash === "string", "asset resolvedUrl could be string"); mono_assert(!asset.pendingDownload || typeof asset.pendingDownload === "object", "asset pendingDownload could be object"); - if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, isIcuAssetPredefined)) { + if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { expected_instantiated_assets_count++; } - if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, isIcuAssetPredefined)) { + if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { expected_downloaded_assets_count++; promises_of_assets_with_buffer.push(start_asset_download(asset)); } @@ -144,10 +139,10 @@ export async function mono_download_assets(): Promise { const headersOnly = skipBufferByAssetTypes[asset.behavior]; if (!headersOnly) { mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); - if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, isIcuAssetPredefined)) { + if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { expected_downloaded_assets_count--; } - if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, isIcuAssetPredefined)) { + if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { expected_instantiated_assets_count--; } } From b5a1308372ed00f1dea08dadd647e97dffe57e27 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 14:52:27 +0100 Subject: [PATCH 25/68] Added `WasmIncludeFullIcuData` to work like Blazor. --- src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs | 5 ++++- src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs | 11 ++++------- src/mono/wasm/build/WasmApp.targets | 9 ++++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 96b2e557129fdd..05b312f4b51471 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -347,6 +347,9 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp string id, BuildProjectOptions options) { + if (string.IsNullOrEmpty(options.PredefinedIcudt)) + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"true"); + string msgPrefix = options.Label != null ? $"[{options.Label}] " : string.Empty; if (options.UseCache && _buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) { @@ -1102,7 +1105,7 @@ public record BuildProjectOptions string? Label = null, string? TargetFramework = null, string? MainJS = null, - string? PredefinedIcudt = null, + string? PredefinedIcudt = null, IDictionary? ExtraBuildEnvironmentVariables = null ); diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 8e3367367f69f9..855b7375624c58 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -28,12 +28,12 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture .WithRunHosts(host) .UnwrapItemsAsArrays(); - private static string GetProgramText(string expectedLocalesTxt, string missingLocalesTxt) => $@" + private static string GetProgramText(string[] expectedLocales, string[] missingLocales) => $@" using System; using System.Globalization; - string[] expectedLocales = new string[] {{ "" {expectedLocalesTxt} "" }}; - string[] missingLocales = new string[] {{ "" {missingLocalesTxt} "" }}; + string[] expectedLocales = new string[] {{ "" {string.Join("\", \"", expectedLocales)} "" }}; + string[] missingLocales = new string[] {{ "" {string.Join("\", \"", missingLocales)} "" }}; foreach (var loc in expectedLocales) {{ var culture = new CultureInfo(loc); @@ -66,10 +66,7 @@ public void TestIcuShard(BuildArgs buildArgs, string shardName, string[] expecte buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); - string expectedLocalesTxt = $"{string.Join("\", \"", expectedLocales)}"; - string missingLocalesTxt = $"{string.Join("\", \"", missingLocales)}"; - - string programText = GetProgramText(expectedLocalesTxt, missingLocalesTxt); + string programText = GetProgramText(expectedLocales, missingLocales); (_, string output) = BuildProject(buildArgs, id: id, new BuildProjectOptions( diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 5d733fdb87764a..445fd7c651990c 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -314,10 +314,13 @@ <_HasDotnetJs Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">true - - - + + + + + + From 512792971fe3555975fac68d538c271938e57304 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 15:03:52 +0100 Subject: [PATCH 26/68] Another missing change for 5df3c9ccc851c45fbdbc3acfd96162c3237973c1. --- src/mono/wasm/runtime/types.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index d31583c35c2183..e22a9ffb8a9182 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -95,14 +95,6 @@ export type MonoConfig = { * Configures the runtime's globalization mode */ globalizationMode?: GlobalizationMode, - /** - * User can choose the option to load only partial globalization data: - * "icudt_EFIGS.dat" for "en-*" "fr-FR" "es-ES" "it-IT" "de-DE" - * "icudt_CJK.dat" for "ja" "ko" "zh" "en" - * "icudt_no_CJK.dat" for all cultures, excluding "ja" "ko" "zh" - * "customName" - */ - isIcuFilePredefined?: boolean, /** * debugLevel > 0 enables debugging and sets the debug log level to debugLevel * debugLevel == 0 disables debugging and enables interpreter optimizations From e86f13208b81cd3ff8bbb9995fef5e1528c169e5 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 15:05:02 +0100 Subject: [PATCH 27/68] Missing change for b5a1308372ed00f1dea08dadd647e97dffe57e27. --- .../wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs index f5063b5d39974d..b7c95aaae91e36 100644 --- a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs @@ -45,9 +45,7 @@ public void ResourcesFromMainAssembly(BuildArgs buildArgs, string extraProperties = (nativeRelink ? $"true" : string.Empty) // make ASSERTIONS=1 so that we test with it + $"-O0 -sASSERTIONS=1" - + $"-O1" - // to access simultaneously locales: es-ES and ja-JP: - + $"icudt.dat"; + + $"-O1"; buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, @@ -88,9 +86,7 @@ public void ResourcesFromProjectReference(BuildArgs buildArgs, string extraProperties = $"{(nativeRelink ? "true" : "false")}" // make ASSERTIONS=1 so that we test with it + $"-O0 -sASSERTIONS=1" - + $"-O1" - // to access simultaneously locales: es-ES and ja-JP: - + $"icudt.dat"; + + $"-O1"; buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, From 0d2b732536611fd8aa2378077a28b8cc8cdbc977 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Jan 2023 15:37:07 +0100 Subject: [PATCH 28/68] Fix debugger failures for real, previous flag was in the wrong place. --- .../wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj | 3 --- .../wasm/debugger/tests/debugger-test/debugger-test.csproj | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj index 670c176e137323..8789247541c8da 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj @@ -14,9 +14,6 @@ $(OutputPath).runsettings $(ArtifactsObjDir)vscode\.runsettings - - - icudt.dat diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj index f312b67ebcac4b..e84909d62a2a21 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -8,6 +8,8 @@ true library true + + true From 87caebd782a97f4c592f6a2dd6fd23660275d816 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 13 Jan 2023 13:06:10 +0100 Subject: [PATCH 29/68] Fixed most libraries that require full ICU. --- .../tests/System.Collections.NonGeneric.Tests.csproj | 2 ++ .../tests/System.ComponentModel.TypeConverter.Tests.csproj | 2 ++ .../System.Data.Common/tests/System.Data.Common.Tests.csproj | 2 ++ .../tests/System.Globalization.Calendars.Tests.csproj | 4 +++- .../tests/System.Globalization.Extensions.Tests.csproj | 2 ++ src/libraries/System.Linq/tests/System.Linq.Tests.csproj | 2 ++ .../tests/Functional/System.Net.Mail.Functional.Tests.csproj | 2 ++ .../System.Private.Uri.Functional.Tests.csproj | 2 ++ .../tests/System.Reflection.MetadataLoadContext.Tests.csproj | 2 ++ .../tests/System.Runtime.Numerics.Tests.csproj | 2 ++ .../System.Runtime/tests/System.Runtime.Tests.csproj | 2 +- 11 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj b/src/libraries/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj index 9527a6f551a5c4..78432de9d49813 100644 --- a/src/libraries/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj +++ b/src/libraries/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj @@ -3,6 +3,8 @@ true $(NetCoreAppCurrent) true + + true diff --git a/src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj b/src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj index b5e7125f309110..fb425f3efd22b1 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj +++ b/src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj @@ -5,6 +5,8 @@ 9.9.9.9 $(NetCoreAppCurrent) + + true diff --git a/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj b/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj index 9cc67ab8f40476..3c35859348f860 100644 --- a/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj +++ b/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj @@ -3,6 +3,8 @@ $(NoWarn),0168,0169,0414,0219,0649 true $(NetCoreAppCurrent) + + true diff --git a/src/libraries/System.Globalization.Calendars/tests/System.Globalization.Calendars.Tests.csproj b/src/libraries/System.Globalization.Calendars/tests/System.Globalization.Calendars.Tests.csproj index 2a64681a8b5678..4848c2bfe2b116 100644 --- a/src/libraries/System.Globalization.Calendars/tests/System.Globalization.Calendars.Tests.csproj +++ b/src/libraries/System.Globalization.Calendars/tests/System.Globalization.Calendars.Tests.csproj @@ -2,6 +2,8 @@ $(NetCoreAppCurrent) true + + true @@ -105,4 +107,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Globalization.Extensions/tests/System.Globalization.Extensions.Tests.csproj b/src/libraries/System.Globalization.Extensions/tests/System.Globalization.Extensions.Tests.csproj index 58d424e4778cda..abfcfb67728fdf 100644 --- a/src/libraries/System.Globalization.Extensions/tests/System.Globalization.Extensions.Tests.csproj +++ b/src/libraries/System.Globalization.Extensions/tests/System.Globalization.Extensions.Tests.csproj @@ -2,6 +2,8 @@ $(NetCoreAppCurrent) true + + true diff --git a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj index 1ea6a48d7382b2..fc5a49b6b9b627 100644 --- a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj +++ b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj @@ -2,6 +2,8 @@ $(NetCoreAppCurrent) true + + true diff --git a/src/libraries/System.Net.Mail/tests/Functional/System.Net.Mail.Functional.Tests.csproj b/src/libraries/System.Net.Mail/tests/Functional/System.Net.Mail.Functional.Tests.csproj index b2ce00275613a5..67538d66352086 100644 --- a/src/libraries/System.Net.Mail/tests/Functional/System.Net.Mail.Functional.Tests.csproj +++ b/src/libraries/System.Net.Mail/tests/Functional/System.Net.Mail.Functional.Tests.csproj @@ -6,6 +6,8 @@ true true true + + true diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj b/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj index 151547aba2ce7f..95cba2caa61e0c 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj @@ -2,6 +2,8 @@ $(NetCoreAppCurrent) $(NoWarn),SYSLIB0013 + + true diff --git a/src/libraries/System.Reflection.MetadataLoadContext/tests/System.Reflection.MetadataLoadContext.Tests.csproj b/src/libraries/System.Reflection.MetadataLoadContext/tests/System.Reflection.MetadataLoadContext.Tests.csproj index 32e2dcef87db85..36c138e0561a06 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/tests/System.Reflection.MetadataLoadContext.Tests.csproj +++ b/src/libraries/System.Reflection.MetadataLoadContext/tests/System.Reflection.MetadataLoadContext.Tests.csproj @@ -5,6 +5,8 @@ $(NoWarn);SYSLIB0005;SYSLIB0037 + + true true false + + true diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 172474d2cec686..ee90193a3e6be7 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -7,7 +7,7 @@ true $(Features.Replace('nullablePublicOnly', '') - icudt.dat + true @@ -329,13 +327,17 @@ Condition="'$(WasmEmitSymbolMap)' == 'true' and '$(_HasDotnetJsSymbols)' != 'true' and Exists('$(MicrosoftNetCoreAppRuntimePackRidNativeDir)dotnet.js.symbols')" /> - - - - + + + + + + + + + <_WasmAppIncludeThreadsWorker Condition="'$(WasmEnableThreads)' == 'true' or '$(WasmEnablePerfTracing)' == 'true'">true diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index efa63cacdce251..2dd9c8ff8b8fe2 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -51,8 +51,8 @@ export function get_preferred_icu_asset(): string | null { if (!runtimeHelpers.config.assets) return null; - // By setting user can define what ICU source file they want to load. - // There is no need to check application's culture when is set. + // By setting user can define what ICU source file they want to load. + // There is no need to check application's culture when is set. // If it was not set, then we have 3 "icu" assets in config and we should choose // only one for loading, this one that matches the application's locale. const icuAssets = runtimeHelpers.config.assets.filter(a => a["behavior"] == "icu"); From ea5785cdcef8543d0c735c542e629bb56084bc52 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 16 Jan 2023 18:26:30 +0100 Subject: [PATCH 32/68] Missing change for the last commit. --- src/mono/wasm/build/WasmApp.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index c99acef6abbd48..698bec64995657 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -333,7 +333,7 @@ - + From 04e97e4f4cc701147ef9e65ab04789de5610ed6c Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 17 Jan 2023 15:39:22 +0100 Subject: [PATCH 33/68] Build args should not be edited inside of `BuildProject()` + Identity on a property does not exist. --- src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs | 8 ++++---- src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs | 2 +- .../wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs | 5 +++-- .../NativeRebuildTests/NativeRebuildTestsBase.cs | 7 +++++-- src/mono/wasm/build/WasmApp.targets | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index bc24a59f5ed384..990d696796725e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -335,6 +335,8 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp extraProperties = $"{extraProperties}\ntrue"; extraProperties += $"\n{RuntimeInformation.IsOSPlatform(OSPlatform.Windows)}\n"; } + if (buildArgs.WasmIncludeFullIcuData) + extraProperties += $"\ntrue"; string projectContents = projectTemplate .Replace("##EXTRA_PROPERTIES##", extraProperties) @@ -347,9 +349,6 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp string id, BuildProjectOptions options) { - if (options.HasIcudt && string.IsNullOrEmpty(options.PredefinedIcudt)) - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"true"); - string msgPrefix = options.Label != null ? $"[{options.Label}] " : string.Empty; if (options.UseCache && _buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) { @@ -1084,7 +1083,8 @@ public record BuildArgs(string ProjectName, string Config, bool AOT, string ProjectFileContents, - string? ExtraBuildArgs); + string? ExtraBuildArgs, + bool WasmIncludeFullIcuData = true); public record BuildProduct(string ProjectDir, string LogFile, bool Result); internal record FileStat (bool Exists, DateTime LastWriteTimeUtc, long Length, string FullPath); internal record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BundleDir); diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 38216f35ddbce6..abc375c5eb2055 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -63,7 +63,7 @@ public void TestIcuShard(BuildArgs buildArgs, string shardName, string[] expecte string projectName = $"shard_{shardName}_{buildArgs.Config}_{buildArgs.AOT}"; bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); - buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = false }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); string programText = GetProgramText(expectedLocales, missingLocales); diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index f34cb9cde7d655..48c80ac1096808 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -48,7 +48,8 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob if (invariantGlobalization != null) extraProperties = $"{extraProperties}{invariantGlobalization}"; - buildArgs = buildArgs with { ProjectName = projectName }; + bool hasIcudt = invariantGlobalization == null || invariantGlobalization.Value == false; + buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = hasIcudt }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties); if (dotnetWasmFromRuntimePack == null) @@ -78,7 +79,7 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - HasIcudt: invariantGlobalization == null || invariantGlobalization.Value == false)); + HasIcudt: hasIcudt)); if (invariantGlobalization == true) { diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index 361f9bfd270ce2..015994359bdaf6 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -66,7 +66,8 @@ protected string Rebuild(bool nativeRelink, bool invariant, BuildArgs buildArgs, File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); - buildArgs = buildArgs with { ExtraBuildArgs = $"{buildArgs.ExtraBuildArgs} {extraBuildArgs}" }; + bool hasIcudt = !invariant; + buildArgs = buildArgs with { ExtraBuildArgs = $"{buildArgs.ExtraBuildArgs} {extraBuildArgs}", WasmIncludeFullIcuData = hasIcudt }; var newBuildArgs = GenerateProjectContents(buildArgs, nativeRelink, invariant, extraProperties); // key(buildArgs) being changed @@ -82,7 +83,7 @@ protected string Rebuild(bool nativeRelink, bool invariant, BuildArgs buildArgs, id: id, new BuildProjectOptions( DotnetWasmFromRuntimePack: false, - HasIcudt: !invariant, + HasIcudt: hasIcudt, CreateProject: false, UseCache: false, Verbosity: verbosity)); @@ -98,6 +99,8 @@ protected BuildArgs GenerateProjectContents(BuildArgs buildArgs, bool nativeReli propertiesBuilder.Append($"true"); if (invariant) propertiesBuilder.Append($"true"); + else + propertiesBuilder.Append($"true"); propertiesBuilder.Append(extraProperties); return ExpandBuildArgs(buildArgs, propertiesBuilder.ToString()); diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 698bec64995657..662c46a55eb088 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -315,7 +315,7 @@ <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true <_HasDotnetJs Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">true <_WasmIcuFileName Condition="'$(WasmIcuFileName)' != '' and Exists('$(WasmIcuFileName)')">$(WasmIcuFileName) - <_WasmIcuFileName Condition="'$(WasmIcuFileName)' != '' and !Exists('$(WasmIcuFileName)')">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)%(WasmIcuFileName.Identity) + <_WasmIcuFileName Condition="'$(WasmIcuFileName)' != '' and !Exists('$(WasmIcuFileName)')">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(WasmIcuFileName) From 2d8ec10ed6c1820fcb31ed00b24e5ecfbc8a7b20 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Wed, 18 Jan 2023 12:32:05 +0000 Subject: [PATCH 34/68] One flag for all lib tests instead of multiple changes. --- src/libraries/Directory.Build.targets | 2 ++ .../tests/System.Collections.NonGeneric.Tests.csproj | 2 -- .../System.ComponentModel.TypeConverter.Tests.csproj | 2 -- .../tests/System.Data.Common.Tests.csproj | 2 -- .../System.Globalization.Calendars.Tests.csproj | 2 -- .../System.Globalization.Extensions.Tests.csproj | 2 -- .../tests/System.Globalization.Tests.csproj | 12 ++++++------ .../System.Linq/tests/System.Linq.Tests.csproj | 2 -- .../System.Net.Mail.Functional.Tests.csproj | 2 -- .../System.Private.Uri.Functional.Tests.csproj | 2 -- ...ystem.Reflection.MetadataLoadContext.Tests.csproj | 2 -- .../tests/System.Runtime.Numerics.Tests.csproj | 2 -- .../System.Runtime/tests/System.Runtime.Tests.csproj | 1 - 13 files changed, 8 insertions(+), 27 deletions(-) diff --git a/src/libraries/Directory.Build.targets b/src/libraries/Directory.Build.targets index 75732bff0f7171..a3a8f091b6601a 100644 --- a/src/libraries/Directory.Build.targets +++ b/src/libraries/Directory.Build.targets @@ -5,6 +5,8 @@ MicrosoftAspNetCore Open $(TestStrongNameKeyId) + + true diff --git a/src/libraries/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj b/src/libraries/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj index b80763df95b5d2..60b01faed6ed1c 100644 --- a/src/libraries/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj +++ b/src/libraries/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj @@ -3,8 +3,6 @@ true $(NetCoreAppCurrent) true - - true diff --git a/src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj b/src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj index fb425f3efd22b1..b5e7125f309110 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj +++ b/src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj @@ -5,8 +5,6 @@ 9.9.9.9 $(NetCoreAppCurrent) - - true diff --git a/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj b/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj index 3c35859348f860..9cc67ab8f40476 100644 --- a/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj +++ b/src/libraries/System.Data.Common/tests/System.Data.Common.Tests.csproj @@ -3,8 +3,6 @@ $(NoWarn),0168,0169,0414,0219,0649 true $(NetCoreAppCurrent) - - true diff --git a/src/libraries/System.Globalization.Calendars/tests/System.Globalization.Calendars.Tests.csproj b/src/libraries/System.Globalization.Calendars/tests/System.Globalization.Calendars.Tests.csproj index 4848c2bfe2b116..565c9325487d3a 100644 --- a/src/libraries/System.Globalization.Calendars/tests/System.Globalization.Calendars.Tests.csproj +++ b/src/libraries/System.Globalization.Calendars/tests/System.Globalization.Calendars.Tests.csproj @@ -2,8 +2,6 @@ $(NetCoreAppCurrent) true - - true diff --git a/src/libraries/System.Globalization.Extensions/tests/System.Globalization.Extensions.Tests.csproj b/src/libraries/System.Globalization.Extensions/tests/System.Globalization.Extensions.Tests.csproj index abfcfb67728fdf..58d424e4778cda 100644 --- a/src/libraries/System.Globalization.Extensions/tests/System.Globalization.Extensions.Tests.csproj +++ b/src/libraries/System.Globalization.Extensions/tests/System.Globalization.Extensions.Tests.csproj @@ -2,8 +2,6 @@ $(NetCoreAppCurrent) true - - true diff --git a/src/libraries/System.Globalization/tests/System.Globalization.Tests.csproj b/src/libraries/System.Globalization/tests/System.Globalization.Tests.csproj index f570f26b4e65dc..4178cef2d1b755 100644 --- a/src/libraries/System.Globalization/tests/System.Globalization.Tests.csproj +++ b/src/libraries/System.Globalization/tests/System.Globalization.Tests.csproj @@ -10,7 +10,7 @@ - + - + @@ -95,7 +95,7 @@ - + diff --git a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj index d8b79b3688117c..22fb00cf9cc86c 100644 --- a/src/libraries/System.Linq/tests/System.Linq.Tests.csproj +++ b/src/libraries/System.Linq/tests/System.Linq.Tests.csproj @@ -2,8 +2,6 @@ $(NetCoreAppCurrent) true - - true diff --git a/src/libraries/System.Net.Mail/tests/Functional/System.Net.Mail.Functional.Tests.csproj b/src/libraries/System.Net.Mail/tests/Functional/System.Net.Mail.Functional.Tests.csproj index 67538d66352086..b2ce00275613a5 100644 --- a/src/libraries/System.Net.Mail/tests/Functional/System.Net.Mail.Functional.Tests.csproj +++ b/src/libraries/System.Net.Mail/tests/Functional/System.Net.Mail.Functional.Tests.csproj @@ -6,8 +6,6 @@ true true true - - true diff --git a/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj b/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj index 95cba2caa61e0c..151547aba2ce7f 100644 --- a/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj +++ b/src/libraries/System.Private.Uri/tests/FunctionalTests/System.Private.Uri.Functional.Tests.csproj @@ -2,8 +2,6 @@ $(NetCoreAppCurrent) $(NoWarn),SYSLIB0013 - - true diff --git a/src/libraries/System.Reflection.MetadataLoadContext/tests/System.Reflection.MetadataLoadContext.Tests.csproj b/src/libraries/System.Reflection.MetadataLoadContext/tests/System.Reflection.MetadataLoadContext.Tests.csproj index d423e3cfa17a48..ecafc778a16af9 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/tests/System.Reflection.MetadataLoadContext.Tests.csproj +++ b/src/libraries/System.Reflection.MetadataLoadContext/tests/System.Reflection.MetadataLoadContext.Tests.csproj @@ -5,8 +5,6 @@ $(NoWarn);SYSLIB0005;SYSLIB0037 - - true true false - - true diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index ee90193a3e6be7..5f41dad4489529 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -7,7 +7,6 @@ true $(Features.Replace('nullablePublicOnly', '') - true + - + @@ -95,7 +95,7 @@ - + From cc9750e14dd1cda5021c6ea6879332d281e0173a Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Wed, 18 Jan 2023 14:09:36 +0000 Subject: [PATCH 36/68] Fix for trimming tests. --- eng/testing/linker/SupportFiles/Directory.Build.props | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eng/testing/linker/SupportFiles/Directory.Build.props b/eng/testing/linker/SupportFiles/Directory.Build.props index 1e08117fd3171c..651818566146c5 100644 --- a/eng/testing/linker/SupportFiles/Directory.Build.props +++ b/eng/testing/linker/SupportFiles/Directory.Build.props @@ -11,6 +11,8 @@ false true - $(NoWarn);IL2121 + $(NoWarn);IL2121 + + true From 821afdc0cb8cfff191d18546766648cb88f6f33b Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Wed, 18 Jan 2023 14:23:03 +0000 Subject: [PATCH 37/68] Rename + throwing when file not found. --- docs/design/features/globalization-icu-wasm.md | 6 +++--- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 2 +- src/mono/wasm/build/WasmApp.targets | 15 ++++++++------- src/mono/wasm/runtime/assets.ts | 4 ++-- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 10 +++++++--- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/docs/design/features/globalization-icu-wasm.md b/docs/design/features/globalization-icu-wasm.md index 17eeabec51cee6..a4bbd0657d2a6e 100644 --- a/docs/design/features/globalization-icu-wasm.md +++ b/docs/design/features/globalization-icu-wasm.md @@ -10,9 +10,9 @@ In WASM applications when [globalization invariant mode](globalization-invariant We can specify the file we want to load, e.g. `icudt_no_CJK.dat` by adding to .csproj: ``` -icudt_no_CJK.dat +icudt_no_CJK.dat ``` -Only one value for `WasmIcuFileName` can be set. It can also be a custom file, created by the developer. To create a custom ICU file, see `Custom ICU` section below. If no `WasmIcuFileName` was specified, the application's culture will be checked and the corresponding file will be loaded, e.g. for `en-US` file `icudt_EFIGS.dat`, and for `zh-CN` - `icudt_CJK.dat`. +Only one value for `WasmIcuDataFileName` can be set. It can also be a custom file, created by the developer. To create a custom ICU file, see `Custom ICU` section below. If no `WasmIcuDataFileName` was specified, the application's culture will be checked and the corresponding file will be loaded, e.g. for `en-US` file `icudt_EFIGS.dat`, and for `zh-CN` - `icudt_CJK.dat`. ## Custom ICU @@ -44,7 +44,7 @@ Clone https://github.com/dotnet/icu . See files in https://github.com/dotnet/icu Output from both builds will be located in subdirectories of `artifacts/bin`. Copy the generated `.dat` files to a suitable location and provide the full path to it in the `.csproj`, e.g.: ```xml -C:\Users\wasmUser\icuSources\customIcu.dat +C:\Users\wasmUser\icuSources\customIcu.dat ``` ## Blazor diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index abc375c5eb2055..04d8d651da02a1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -64,7 +64,7 @@ public void TestIcuShard(BuildArgs buildArgs, string shardName, string[] expecte bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = false }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); string programText = GetProgramText(expectedLocales, missingLocales); (_, string output) = BuildProject(buildArgs, diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 662c46a55eb088..87183c07ab4092 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -68,7 +68,7 @@ - $(WasmEnableExceptionHandling) - Enable support for the WASM Exception Handling feature. - $(WasmEnableSIMD) - Enable support for the WASM SIMD feature. - $(WasmBundleFullIcuData) - Loads full ICU data (icudt.dat). Only when InvariantGlobalization false. Defaults to false. - - $(WasmIcuFileName) - Name/path of ICU globalization file loaded to app. Only when InvariantGloblization false and WasmBundleFullIcuData false. + - $(WasmIcuDataFileName) - Name/path of ICU globalization file loaded to app. Only when InvariantGloblization=false and WasmBundleFullIcuData=false. Public items: - @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir). @@ -314,8 +314,8 @@ <_HasDotnetJsWorker Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.worker.js'">true <_HasDotnetJsSymbols Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js.symbols'">true <_HasDotnetJs Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">true - <_WasmIcuFileName Condition="'$(WasmIcuFileName)' != '' and Exists('$(WasmIcuFileName)')">$(WasmIcuFileName) - <_WasmIcuFileName Condition="'$(WasmIcuFileName)' != '' and !Exists('$(WasmIcuFileName)')">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(WasmIcuFileName) + <_WasmIcuDataFileName Condition="'$(WasmIcuDataFileName)' != '' and Exists('$(WasmIcuDataFileName)')">$(WasmIcuDataFileName) + <_WasmIcuDataFileName Condition="'$(WasmIcuDataFileName)' != '' and !Exists('$(WasmIcuDataFileName)')">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(WasmIcuDataFileName) @@ -332,10 +332,10 @@ - + <_IcuAvailableDataFiles Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)icudt_*" /> - - + + @@ -346,7 +346,8 @@ Condition="'$(WasmGenerateAppBundle)' == 'true'" DependsOnTargets="_WasmGenerateRuntimeConfig;_GetWasmGenerateAppBundleDependencies"> - + + <_WasmAppIncludeThreadsWorker Condition="'$(WasmEnableThreads)' == 'true' or '$(WasmEnablePerfTracing)' == 'true'">true diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 2dd9c8ff8b8fe2..687152dc989e9e 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -51,8 +51,8 @@ export function get_preferred_icu_asset(): string | null { if (!runtimeHelpers.config.assets) return null; - // By setting user can define what ICU source file they want to load. - // There is no need to check application's culture when is set. + // By setting user can define what ICU source file they want to load. + // There is no need to check application's culture when is set. // If it was not set, then we have 3 "icu" assets in config and we should choose // only one for loading, this one that matches the application's locale. const icuAssets = runtimeHelpers.config.assets.filter(a => a["behavior"] == "icu"); diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 125eb8efe730b1..7320289847a21b 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -37,7 +37,7 @@ public class WasmAppBuilder : Task // full list of ICU data files we produce can be found here: // https://github.com/dotnet/icu/tree/maint/maint-67/icu-filters - public string[]? IcuDataFileNames { get; set; } + public string[] IcuDataFileNames { get; set; } = Array.Empty(); public int DebugLevel { get; set; } public ITaskItem[]? SatelliteAssemblies { get; set; } @@ -162,7 +162,7 @@ private bool ExecuteInternal () if (!File.Exists(MainJS)) throw new LogAsErrorException($"File MainJS='{MainJS}' doesn't exist."); if (!InvariantGlobalization && (IcuDataFileNames == null || IcuDataFileNames.Length == 0)) - throw new LogAsErrorException("IcuDataFileNames property shouldn't be empty if InvariantGlobalization=false"); + throw new LogAsErrorException($"{nameof(IcuDataFileNames)} property shouldn't be empty when {nameof(InvariantGlobalization)}=false"); if (Assemblies.Length == 0) { @@ -312,8 +312,12 @@ private bool ExecuteInternal () if (!InvariantGlobalization) { + bool loadRemote = RemoteSources?.Length > 0; foreach (var idfn in IcuDataFileNames!) - config.Assets.Add(new IcuData(Path.GetFileName(idfn)) { LoadRemote = RemoteSources?.Length > 0 }); + { + if (File.Exists(idfn)) + config.Assets.Add(new IcuData(Path.GetFileName(idfn)) { LoadRemote = loadRemote }); + } } From 9abdb6c12691ff139b1d53a260a181e1d7943c71 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Wed, 18 Jan 2023 17:08:09 +0000 Subject: [PATCH 38/68] Fixed NoopNativeRebuildTest WBT. --- .../NativeRebuildTests/NoopNativeRebuildTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs index e5f17d261811a8..916ccaab485819 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs @@ -22,7 +22,8 @@ public NoopNativeRebuildTest(ITestOutputHelper output, SharedBuildPerTestClassFi [MemberData(nameof(NativeBuildData))] public void NoOpRebuildForNativeBuilds(BuildArgs buildArgs, bool nativeRelink, bool invariant, RunHost host, string id) { - buildArgs = buildArgs with { ProjectName = $"rebuild_noop_{buildArgs.Config}" }; + bool hasIcudt = !invariant; + buildArgs = buildArgs with { ProjectName = $"rebuild_noop_{buildArgs.Config}", WasmIncludeFullIcuData = hasIcudt }; (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink: nativeRelink, invariant: invariant, buildArgs, id); var pathsDict = GetFilesTable(buildArgs, paths, unchanged: true); From beaa6e0ca712ef2bcb1c50388d900c77be21bff5 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 19 Jan 2023 09:28:33 +0100 Subject: [PATCH 39/68] Forgotten logging. --- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 7320289847a21b..bfe32cfe2e53cb 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -315,8 +315,12 @@ private bool ExecuteInternal () bool loadRemote = RemoteSources?.Length > 0; foreach (var idfn in IcuDataFileNames!) { - if (File.Exists(idfn)) - config.Assets.Add(new IcuData(Path.GetFileName(idfn)) { LoadRemote = loadRemote }); + if (!File.Exists(idfn)) + { + Log.LogError($"Expected the file defined as ICU resource: {idfn} to exist but it does not."); + return false; + } + config.Assets.Add(new IcuData(Path.GetFileName(idfn)) { LoadRemote = loadRemote }); } } From d332ed9278f1e98894617eb71c9702d713565bd8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 19 Jan 2023 13:40:38 +0100 Subject: [PATCH 40/68] Tests with custom shard. --- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 6 +++++- src/mono/wasm/testassets/icudt_custom.dat | Bin 0 -> 530704 bytes 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 src/mono/wasm/testassets/icudt_custom.dat diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 04d8d651da02a1..5b61bc0d42bd98 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -18,9 +18,13 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture { } + private static string customIcuPath = Path.Combine(Directory.GetCurrentDirectory().Split("artifacts")[0], "src", "mono", "wasm", "testassets", "icudt_custom.dat"); + public static IEnumerable IcuExpectedAndMissingShardTestData(bool aot, RunHost host) => ConfigWithAOTData(aot) .Multiply( + // custom file contains only 4 locales, nothing else: + new object[] { customIcuPath, new string[] { "cy-GB", "is-IS", "bs-BA" , "lb-LU"}, new string[] { "fr-FR", "hr-HR", "ko-KR" } }, new object[] { "icudt.dat", new string[] { "en-GB", "zh-CN", "hr-HR" }, new string[] { "xx-yy" } }, new object[] { "icudt_EFIGS.dat", new string[] { "en-US", "fr-FR", "es-ES" }, new string[] { "pl-PL", "ko-KR", "cs-CZ" } }, new object[] { "icudt_CJK.dat", new string[] { "en-GB", "zh-CN", "ja-JP" }, new string[] { "fr-FR", "hr-HR", "it-IT" } }, @@ -60,7 +64,7 @@ private static string GetProgramText(string[] expectedLocales, string[] missingL [MemberData(nameof(IcuExpectedAndMissingShardTestData), parameters: new object[] { true, RunHost.All })] public void TestIcuShard(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) { - string projectName = $"shard_{shardName}_{buildArgs.Config}_{buildArgs.AOT}"; + string projectName = $"shard_{Path.GetFileName(shardName)}_{buildArgs.Config}_{buildArgs.AOT}"; bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = false }; diff --git a/src/mono/wasm/testassets/icudt_custom.dat b/src/mono/wasm/testassets/icudt_custom.dat new file mode 100644 index 0000000000000000000000000000000000000000..7b8041930042ede2f0ad2effb66b898b59039479 GIT binary patch literal 530704 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ºU0gYy$or5f-1YHR4x$ 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

w&-WZ_sz$QTJv6GN7?@W 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%0Db}$ znWzBvKxHPA05c$JlX-yOf#)DwKz=u<)?^9LWGKsI7T^pBgjfamGDu>A2mBX6I+InP zp4UJIlXbv;3bJFe3D6w0ZL$q;9<*k%3-GU?F_V3OUxPXPZ7?wYpg8^kpi!VXI1C8| zeE}Bm2|&J1$dm~I(6%6<83y2G&>n&Y*!y9VCOAOzVcUo`puV?36DD-P-UFG3Yy4zT|KrJ2q@$pg`tECKu)gg0FVxD8^Mt^)iP#5P?6*Z}33 z?gP9J5||<_f&M|IX4`-cgtAO`0KN;wnCt@l0mL;00sd4N$MgWudmyGM0?#h!~*($N{MlMu5@aF|#p%vEXsDaey(P39|`+Jn*F1B*0GCl-U%(MDVoPG{9u= zjM)spRPe0XEWkL>@5ccVc+PAN&{^Plvw46y;03bn1Jp?v~*acV&#+mE`{U`-5A_PEOIBXYj0NB5P z!pyjUE(b3ob^yNz)QH#xr~t1a!vrQ0KwCmrO^`r5ABr=R0@@pjF_Qso1*6Oq0NcTMGc~|xp|cPS zkS`p{G(!P2gVD|C06ky?GacYBgv>&4fEIulW@CUp0wI~v0Nn{@n~ein2Ns)50*r;o z%#=X=$01xZ6~Jd995V(G_Zygvm;?Mt5V;u(&{0s8**u{8z-qG49pH;lm^l;Rc`(9U3;3O(Zmldp zzXC>@>i|6mMwyQR^}Y_qm~#O8OVCRsF2GA*oH-BRN8q5 z77PoPg}_2&u>w(BbS3C53>GsM3l@tOtB@`5mccI z7p<49m#x>WH>`K8B`_&$I01&zLG%zLihC<~ee(_$IIvk+Q{ zEhH9l3x$Q&!e}vVF=w%Cv0||a-muuQ*s}mG@Q^4Z9!WydkPPH5ScsG$6-X6Qi)2A3 zkTb|xBnYyI&Pu646=uWS&E=|OM)c=LbqgCq98IT4uXg9EQOXLOR=TIQf{fR zR6}WyamyLYdCN`6k|hJOYPn&#ZMkQ8U^xW^Ss|@vp^MNZD9LICO1ENKv8=dOLMySA z#7b(Vw=!6*S#4NtS_vULR+~@}WZw#Ay$wZKv)~XRX;VHDuMA2ivmVvlhb+tQ9aCN)6M)*1!`mFj#}qqpngXPz$JK)JK#Z z(hh1Dg|ul*!r83ElWb@S~u zQ#J=S2=v2v2KpeLiDsdNXa!n}oY%#WuJfD;ec@&Yrdy`#Z#%yH6j+HuZt-Z7cB z=(y~-;<)Cx?x-u>cHD8?b=-5D;vt-nPB+MEGTVvc)JDEQ<2k)d);P^LA@MZ45U;{d z;HUAk_$B-<9_Ea2MmnRMG0u2rk~71Z>&$Z&ILn+B&J)h4<208E62qm4x)nb_U2xfO z*>w4lw&OBJMYyubi*)?Q?^JJB->=@Qegr%~G^s(>plfh7IPj}UMLErQd671SzfO0b4HCs;?#3N}!is4dhs>JWcHa7A!kV4HADuqn7Jcp%sp zz>>_9Xi4ORJ=8ubo8ptiObSinBpsmQk_1U0n|un)CM^kJ^BYW@l%FI^sz_2Mp=>_o zpH0HpFp}{$1REu3J^51nz2t|F@=&+3#X?Tc&rp{N}3Q1R|*@28ex}E zFMJm^EF2R~3eO5>gcpPyn|a}7;gay0@HA~zD6r}0-xO{LA5-rL?+Z;*g*IXvWGXrp zmugRQNhPJ$(mYccsS=x@RCa1~DlauTRg{{ODorg-RixIXDs9xMYMb^{U8>e*AhiKL zk$NU|I#q9TK6NhjQmWBrG4*QdxXnuHjnqk-_0-#`TdDU_cT?f;hpE#x2dOhQvo>=! z^EL}Mi#AI(os?CZHJf#tW;iCzF^!Nm%A=&w(>822ZT!==Y_@H7Y<6w-Y67Va z)8*(!+D!U|^!fBvnhLE(UryJd^=Jdyi?)<*M314z(bv)^(BAM#^lJLe^vAqA>D%cC zE%(#+(jTRRL=fH#dKNv0HWAIEks`DRCt^?!lUzh3k*5g3TR<~JK_a%uiWe>7i2|w1 z=qXCFNF>S;Nkye1g{V%nhE|K(MLN-dXal{8{wKMSx`j51w$VH2UG!1Wgy@WDT6A8t zhn^Gdqc4dTMWNKIq674b=!R%rbX&9~x+j8Bc10lD7bytaha#lyfe6FH+2U;>83bEs zE6Em-L9?~Wpxa_H95WcUgbYdsJ;OiaZ5YdzZOgG`W$qv(?+4<2~RRY?bjwTR-@i?YQlP?WFCL?TqcL?Zu2~>YQ!F z#JufG)T3lG%A)O(?Lx+tjAdI}N+5j2b~yt_ai*-=g2?N(*E2S3H*L3U*D`i&cWw7< zZ)LQ>H!}#lecQVkI~fl$zNQ}7f-o1T`x)lsJEX@M6kaRsyFpl{c_t2{h<}@k%6x;5 z%`}klnZ!&2hJ<+@W=*DH=okiuiD6;b7!HPu;bD*-v`hiUCsT+KVI&x4rW7N?(0T8G ztZcOi-3d7BUN+wTIcq9AH3pFT(CoU9w17 zIa$5pxg108gItsy#_n?p&dw6PpZho$ZwHf@O9*!N;!zTk9nB6a!Alr+M2U}tWp^f? zZO5_W+A$@e5{@KJB9Np>#FBi;CPiQ;lL+lZc49m8)(VNl?peN4BDHIj$n6w%N;{RE z+D;?slISIk@xzia$xr+fl1a%~$&BO&(u~~&$-HFNZq9DrZozIBvShbxw_(k0hWx5Y{9QnTO6p zU~zdac_=InOUlDzJ@W`y5|+rPVd>cS`HZ}vJa%4m9s|q7valR17dt=`V0n2$tOzT{ zO0ZI_4BH7$&b!4EELnY`(|^Lb;~ajY9{0y~MF!cJpnuyc8r@)q;vu=Cgj>>_pvyNo@_FCedA zSFvl@b?i5=t9dJV8`w?k7IquEgWbjMVfV3b@ei;!@~(oz;UIhTDTMuc-tD}tynA^r zWRyL|{vHKqkGCh-Z_#%1NcIo&a*YRhkbJg1#~#L?hIR9~_B?xmJtALdFR~ZgOYEig zGJ6}i++Ja?v{%`y?XB`P_NEE$WK6zeJ|Ulyud}D;%gE+@gZ&=FKOeVtMfmA zVfiij+Wa;9-u!iYL;iQL(fkej@%&Bu)A>{R;Djyva`LwQj{UCvp8dZ4fjtNZ!y#}; z914fSRg`*q zg4KeX1setLf$tP-7u+xChwl~WNFeDU8t~vvs-U5zIH`-2B>k1obnuiiq(M@)G+N4& zCQC)q9O*DgDlL^Nq;=AdU}~vOIv^b<8Kp<1W3&nB8EHRhT6$hOClxtdk}gWGO8M{= z>8_MZwJM|(eoti;MieR>xP|Z25(kEkTaiXvqZ&5`bSpLRaJu}D+Y zRrC^{?bt)*IC33<3n0h&qRT}>$E6~Xqu5d6D0TdvB6E~GDje?=p^HVubH(e$jxwdA zTvjbp$y#JuS+C3>8ad%p-fmNDJv>#Ahncf%X-UR zry0sd%f`!4PN&PJ$}mniCp-ALve~kWWec<`W!KBr$|lKpCxX-SWRlYi=~mfh8O`Z# z*-qJmvi-8hWppQ)93{uf@p7V^CTBRkM59xfPMf@Bs*n5}ndQWkhsuK!IPy5TK%OS& zI*H}^a+$nB-YD0|1x{UZp_9l-FBdxv%Oy@yr%KY8{DfTQG%1%mDV)yAl};+B8TkeI zyxfm+S*~_kk_S^c ztkVy$M{<+$Ij4E21*b))1IQdKr+nFI#R*Maby{*cr0x5}A#7XJNX_sZFL4xWpDk93{H z!@tV^l_bDd@OR4}mLHUh@EPztsu(Z9Ln;szQoL0Krb32ytRPfSD(Dq*yaKPptMMAV z7O%tW@dmsRKZZ}Iu__`exD^Q%!ivlaNyQ|73SU$)gP+6C;}`IYczH#2#dyVR#ZJXC z9$UGBU&XKC_bBW54g4lvP`QQQ#_!KBTkYP1UHJYGd`K3z3cb*_r$On08Gx>&_@ zW;wH+InIwM3sqODmaDE;37wDApwwGccdH&(iJZmGTAIY!rip`iYNfNvSzoPo);MdO_0A`%4bDdA$?7rZZ)h$xq#Dl}Mom!7r1O;Xv@^RVx@N|C z)|pomMwxS-cV2K_bWW~Wa$a_xhpjk^YF3@soY$RmYNRzA&ZRY*&Rfn!JVnig(QW6t z8g)&3&5rY~^PY1tW#3s>bKneefw>@DkS@j=l*`c?j0?^M?=n$yriS3MP9nKHN1LuW zUo%%jcQG+AU05z`7mf?ph36u0`5JbqX0hgKjo3xvB6X3u$X(%F?&I-aY*^LQbJac#rNXO- zDuM@1<)dP%LRB193T4?fP9;#Ksa9OYs(jU|>lZMYDw0;AQmPtNrXF3YCDnb^p6U_s zQtyVVNdvlp-N0)|ZaCVo)o`!jVZ)~DK?9@_(P-6J1>bVrcEvRAxbC{{xjHuPyB@e+ zCW8nt0-+H>pfn;0_S7ZVdCIF4JQYQ7qkc-p5d0fijT_`D$}-FW9?=MbavQ%QlL$1z z3XD!*5G)gXTN4^>T7`|Pu*^m#LDE>%$Rfxa>nLnOb>kZBE?L#s(#R$72w#xCC2Jci zNJ4^$ASSHCqy%SpZ=<1co-*1^6N+~8Llmm9A)-sMdZrU?|4K%?>5d5 zW(hlu4;uFy=LqwJ$BnQi^QHyDB4LSei`K|rCae&)V5p{5!W!XO(mDa(v_aS;oPvio zahl?q1Wjp8;-+`VTZC=G4k5ou#oHz95oApjP0FUmrhUQzLDK{*mAE0?kZu^a_o+BH zyxTUct4ZHPa2sx-xrr!rx3MOMTQ`a6c8sJavE0~h95=4piKfY>DDv5+nWhU(^G%nV zmYS|Ltv2!8-lSZi+-&+AC62rcBf~eEmdP!=J5Ae70ym+X$nAcU*ln-rQ4>gQqF$j$ z+@x-MFr*r-mbu~7ayNyW(#=J!a#OpJ)EYNWHA5Yw*1GB3^lk<>qZ?bj4;ynEcbjmF zR!_R|)CaI+H3%+J=cuRMX58l7q-q$vRIN}W;C1SGw*|LFw7tQw+%P7 zdeg04z2&A;BjE$;Z8xKO$8Fbb&+QG^zS~jtf!l-{Mnu68L?rQy8bibp@k9b~T76zU zr@o}75$VK5HG{|`vWQpJE9x6+418V9Cf-(Wsqd+G)f^(1$Rj>f3y4DEff~{*CQ68i zW~*i?QAWfxD~OKGN+O||(oApmZ`Kf5%@NJqW-T$HSx>~l4Mbrx9-i4ufE$UD<}u

RG3(Y+Em1d^90M2q>Zf3i4+_~=8o7b9eHS^pB?n3v?W|8~d=AGsy zio{*&{yA0VE_YYBKWJ9E?>9eghP9~N&0Ey&s1}VowngVIhU?w&EfTmCPHdsI7~Id2 zWN@Rq9Ik--v@lyjTRtFjTE^Wc+%=R*_bK;j_qY~8%Z&T1`<#1P%e?!7`=a}j`?C9r z`>Ol1aB)k1OGQf!Y0X^;Uw7Yd-*i{O)$lF%ZFgmh2Hx1R7 z(j;6;G69{fo~gc2JzssfdZ`*%nQ_eI@kmJEjSo}Mci>YHJg{~d2dsk?LOdYNkU|g+ z#7UTf&%kHlaS4KiMfeJQ6`q!`z!N7d^RDyq6L==Fgo*^ciN>@CvIL3&dBY%}&*-=5 zjUcuM3>4+z28r`X^st1eJ=#3FJo-Eg9wQ!AkT*Ood%WY30a^Ai2YGvZ4YmZ{Q1}Ae z39$uPc~U*Gz zH$c2VH$4$v-jJWbmR>%<8g!^vq?aAU57gtu1XgMXyx@>vUaf-0=9+W5eSskGmcpkiPZU_4vu-k;gwhV4h~4 zww^doXHTLh$1~P5!865E{Ky|`X{uVk-u zFR@piSCLnl*HqF`q^^n(?*Kx1YUe9?=d%fg!!RvLe1+TZgu6o__ z`qFF5>l?58UO#&6d;R7Gq2DEu>E)yd`nRMQ`Ywq_Pok&Mv*;3fA^j)PBhqKoQu=Lb zCB1{LqxaK?>4)j#^i%YIl3?UB^cUze^jGK?>5KGt>DTBV(*I8VJAIq}9UWMWY;~e% zwwi%t9$5MV`p@(OI@sI9+rk^=y$%ZSW_gEuM|;P63%pakGre=YrQRjp72fl$HQp-k zX76_IZg0K!khd*))cctCg!fym&w8Kr{)_jl_iNslyx;O(@}_AVd|Z5>40}c(Bc1Va z+pRWOyG1*?-JzY(PHq3p&!@fC&}`^5^cy}id}Xi>zyw6o45WCPfaVa8O3S3V1mx1( z184!>0f@lHOv}JE3_9>crhOnja3<3&kP^6w@d{kcWCR8VZfAxD-p`B-+{@$!@&kEU z$yuVTqgfMK(^&^uR$`2pAWje?c`7lQH!8j;_T+7fPh?MKpUu9Oy_ap0L&}NHQRJv| z9_Fxedvh0Z2Lf*e76$!OYtm`iiRpCeBzAgqGCG4hBRVUCR_(8Zd>?`jEechHwuZhR z`bp?Fp)Z6n9GQ+RM{3VR@tIyb3z45hdU85AI?j8vevT=1n1iGq=8SVran5l5*7pKu zhVu%iAo_&rvT9qUYglYpX}H}G5nCEN9{XN!}jbW@I04 zE3%CNm*|p6O7u(&N@OQa#z!Y6CyEl!#^)qT6H5~niFJwU#P&p8;y|J?@o3_i#OcKI ziF1jU5*HI&_$!Gw64w)lTaUD!Ykj5lJ@5Yb>)s!GuX}&teaHJB-aFnuct7+`5C{dC z0*RnV;9-ypss$>6R?sV$i8lyF1>*uO|3dsl!LndYup@XZKqdX^jZFgi;FE|+IIT8oGePtNtPy;CY$>xlIxPy$x(*( zWL#gSxYH2+)CL@xtp?+@*rhD<#7s32rPyRu|m9% zD5ME}giK+mkRyx}3WQ=|zHl{OCae%XqdBX2N%L2YyARFhJ&iNj+sEH0*oR1F`$YLr z$#Fi3K6J9sC&MSlr@-e^jUQR&Q|?pkQ}3hpY4hpw>GLu8jQAY&Iq4Hb-WKlj*5mht zpwt8Eq|fs{=X?-tFZ;~-yy0`%=N+GApAUR)_*l1L+dlQ#@cGK;u8(sYsqI^zU7w$P z9{K#!2j*+$YvpU}i}Q8%CHhi*>ArrxLB8GbVZI#SSli@QZSif%Z5eI7zJtC--y^;zv=hG1_*&4We7i_}Bo6sS-&it=R?&9e z*N*n8uOrQsHt+kUFNt==*MnwE{hO~3?P%(X??=8jeLwfz^zCi?+V`IC_r80+zxY1( zMKCNGC)?1B1TvoC#-K1#$X*NvBa{)z;4;p%@fpbsF(Z#r#3*AZ7`2Q>Mk@o7re*Xn z1{jAJV~pdB(~RdBBJwojB}O*+0^@bY>ut+6D>jHUtF#5i+l;G>_Zh2lPxa6AA59xiJDoO_b~Ao9?PA(O+Lg5BwCibWX}8ig({laqrftOU zr0u6YPJ^YJr=!xb>G*VFx=;F@cxHNNdR)37JuO{?7Na%k60{U8^)KZ|ET{l{|WzR{m=UU#b1MF(PsU-+F$d(``G9i)F9*y8 zyb*9YK%P;Zp~`qCU^$>AW7_tEfExkYj86kL0=^2k8}MyFZ^mxGPXUht3>p6n7|j^Z zIGr(-aV}#v1I9FCS}|>zIHoi6bt;ibWzv~F9ezw0CWslvzGYU4Rf5=$$Yj$O6z3~GL1|k

jlIl+8}ImLXDsfa(% ze3d!Re3N;F*+-u4_!|?CSz&&}yvaOC{+zkVETMhPyvO{WxySs4`IvdJ0~)y0@qWi= z9h)6`i~(cBjA6zxg3Po`ab|v|jTWbM)l#(HTBbHk8?EJQh1x8wGE`2npe%M)be1%$G|OX1kyV$a&T7xnWesE* zv(99l&zj4+l(m?3HOp^kCF@4kde-f%t*m=lyIBvjAYz1gFWym15!1zw;{C-eafFyF z7K$^)Af7~ABsSs6#UVr0;ubNErxhE-zc zH*(fnpq+mfrzJwt&%nK|EJVGuDR0P%r>a{1xje)I!+Q6Q`LxE#~#{*9XJ{LG0 z_)_47z}Ev80^bh28u)(TYTzeFo4&i`x5v2D={vjduHizXd{q zOoPVko*^THrpPuy*r0K{ziF>)KhZh`P1q5F$U!fXC+#-0f7edgc?O*)`vx(CLV_ZK zVuE-B&alKNumsD2x_*qK%NYgPLjI1w9CA zr~MqXLOuur2ak4sL^cVw2u20l1v>`229tt4f_;Jmf?2`g!O_8!oiB8r?>rPAA1nx7 z=u8dH49*RHr&AhS5?m2n6RZku4(_JiB!5nB5AF`u2M+~rl1GE}v}3^&!OsQ{(MD-s zlkbtw2LB~^HuxCrwcty^Zv}r({-|@kb1C?};OoI32k(*BgTDyA6a0_hUt4y9e+d4C z{9Wh6;9rA5tjFZNPB`n=PIHzu3&V0?xv(HzW?fKb?V#TrSDR@dEi)All1$1Swa#(JZ0@f5v#wurpcIoXYlxo%18anJly#Cd$$Fl3j`cEYj`arXGRuqd4r`h90qX|qQ`QFSE7o1s zx2#>(Ppn5Q2IZfuKng6xEW|3rHUt+^*X0~S455b5L;OO5Lc&5gA+aF|At@oEknE69 zingn-Yq;x3mnuI$q&P$#QWa7c(iEZz=?v)&84NLo90@rQ@=VB7$crK8y3U8Z8ZsZ^ z(E4V`D_wsJSqX`xd=zpsDE4ebei z+%*t-D0D3JchHi!45B)JT zs5`uSKlHayNSJ9DGR!6n8|D;72qTAihWUo2b!T@=yUV((yEWb2-2>et-Q(RS3YcLb zVSBg>1^c)I9LV9NZkWSd_k01;VU~w-z&YR@2o9^=pLR!tF&y?GUw6lZ?R4|P9(99t z2whTGT3A+?BrKhBlvEgYL%J?)=iQcWN$*J?N)M!vLPQ~^(6NwExWK0uUg7%}F7r#n zymXae%CLs8mavX6U08qEaM{?j7E?JkM zE7n!$W*pSIS%(hYoI|f}-eE|$;4r3JbU3M7ayX+~c6d>@;xMcGFl>Uh7WP@#?XV)s z-@~@Uz6*O0c9QgS*g;qs1G;5PGf^trp!Z+9(>}eKWLObiZh?=n?S8oK1?2JY+Gh*roU=k5ueg%qR{j z{$Yq+99_&SPA;C{pQuNJQq z-zdIayj6U!c(?dr@j)>}hLC}Wtz-llMMjtT%UH5CeuRuGOOOd=nKFs2NOp<+7JG@U zrSwn+D2FIxl;f1sl=s-z*&nk5d)C=su1|i@ThP|Yg~9@xG+2;JSV)Mr?jW4NA0L_)H>=Mr(`z6 zv$Bh_Y2Fptb=jKiJnxpw=(s7nE8CGhknPJJ%U~t9_~s?3l5t0T$tIszLMw3?b{qB> zPAl;n4jGOb<`2*DQirpL3x~^xYlo*C3&Le3%91a7wtB`&PLy2YO_rQ3X&#;_S>#

A@yIiuu>l|JxxmL1TauMby; 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%0APdr+ONlaW;K37yOjTc2a7-iP{U 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 Date: Thu, 19 Jan 2023 15:21:27 +0100 Subject: [PATCH 41/68] Added more complex tests. --- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 150 ++++++++++++++++-- 1 file changed, 134 insertions(+), 16 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 5b61bc0d42bd98..715e5337ffb53e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.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.IO; using Xunit; using Xunit.Abstractions; @@ -14,17 +15,23 @@ namespace Wasm.Build.Tests; public class IcuShardingTests : BuildTestBase { public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } + : base(output, buildContext) { } private static string customIcuPath = Path.Combine(Directory.GetCurrentDirectory().Split("artifacts")[0], "src", "mono", "wasm", "testassets", "icudt_custom.dat"); + private static string[] customIcuExpectedLocales = new string[] { "cy-GB", "is-IS", "bs-BA" , "lb-LU"}; + private static string[] customIcuMissingLocales = new string[] { "fr-FR", "hr-HR", "ko-KR"}; - public static IEnumerable IcuExpectedAndMissingShardTestData(bool aot, RunHost host) + public static IEnumerable IcuExpectedAndMissingCustomShardTestData(bool aot, RunHost host) => ConfigWithAOTData(aot) .Multiply( // custom file contains only 4 locales, nothing else: - new object[] { customIcuPath, new string[] { "cy-GB", "is-IS", "bs-BA" , "lb-LU"}, new string[] { "fr-FR", "hr-HR", "ko-KR" } }, + new object[] { customIcuPath, customIcuExpectedLocales, customIcuMissingLocales }) + .WithRunHosts(host) + .UnwrapItemsAsArrays(); + + public static IEnumerable IcuExpectedAndMissingShardFromRuntimePackTestData(bool aot, RunHost host) + => ConfigWithAOTData(aot) + .Multiply( new object[] { "icudt.dat", new string[] { "en-GB", "zh-CN", "hr-HR" }, new string[] { "xx-yy" } }, new object[] { "icudt_EFIGS.dat", new string[] { "en-US", "fr-FR", "es-ES" }, new string[] { "pl-PL", "ko-KR", "cs-CZ" } }, new object[] { "icudt_CJK.dat", new string[] { "en-GB", "zh-CN", "ja-JP" }, new string[] { "fr-FR", "hr-HR", "it-IT" } }, @@ -32,6 +39,24 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture .WithRunHosts(host) .UnwrapItemsAsArrays(); + public static IEnumerable FullIcuWithInvariantTestData(bool aot, RunHost host) + => ConfigWithAOTData(aot) + .Multiply( + new object[] { true, true, new string[] { "en-GB", "pl-PL", "ko-KR" } }, + new object[] { true, false, new string[] { "en-GB", "pl-PL", "ko-KR" } }, + new object[] { false, false, new string[] { "en-GB", "pl-PL", "ko-KR" } }, + new object[] { false, true, new string[] { "en-GB", "pl-PL", "ko-KR" } }) + .WithRunHosts(host) + .UnwrapItemsAsArrays(); + + public static IEnumerable FullIcuWithICustomIcuTestData(bool aot, RunHost host) + => ConfigWithAOTData(aot) + .Multiply( + new object[] { true }, + new object[] { false }) + .WithRunHosts(host) + .UnwrapItemsAsArrays(); + private static string GetProgramText(string[] expectedLocales, string[] missingLocales) => $@" using System; using System.Globalization; @@ -57,12 +82,33 @@ private static string GetProgramText(string[] expectedLocales, string[] missingL return 42; "; + private void CheckExpectedLocales(string[] expectedLocales, string runOutput) + { + foreach (var loc in expectedLocales) + Assert.Contains($"Found expected locale: {loc} - {loc}", runOutput); + } + + private void CheckMissingLocales(string[] missingLocales, string runOutput) + { + foreach (var loc in missingLocales) + Assert.Contains($"Missing locale as planned: {loc}", runOutput); + } + + [Theory] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.All })] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.All })] + public void DefaultAvailableIcuShardsFromRuntimePack(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => + TestIcuShards(buildArgs, shardName, expectedLocales, missingLocales, host, id); + + [Theory] + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { false, RunHost.All })] + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { true, RunHost.All })] + public void CustomIcuShard(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => + TestIcuShards(buildArgs, shardName, expectedLocales, missingLocales, host, id); + // on Chrome: when loading only EFIGS or only CJK, CoreLib's failure on culture not found cannot be easily caught: // Encountered infinite recursion while looking up resource 'Argument_CultureNotSupported' in System.Private.CoreLib. - [Theory] - [MemberData(nameof(IcuExpectedAndMissingShardTestData), parameters: new object[] { false, RunHost.All })] - [MemberData(nameof(IcuExpectedAndMissingShardTestData), parameters: new object[] { true, RunHost.All })] - public void TestIcuShard(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) + private void TestIcuShards(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) { string projectName = $"shard_{Path.GetFileName(shardName)}_{buildArgs.Config}_{buildArgs.AOT}"; bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); @@ -78,14 +124,86 @@ public void TestIcuShard(BuildArgs buildArgs, string shardName, string[] expecte DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, PredefinedIcudt: shardName)); - string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, - test: output => {}, - host: host, id: id); + string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - foreach (var loc in expectedLocales) - Assert.Contains($"Found expected locale: {loc} - {loc}", runOutput); + CheckExpectedLocales(expectedLocales, runOutput); + CheckMissingLocales(missingLocales, runOutput); + } - foreach (var loc in missingLocales) - Assert.Contains($"Missing locale as planned: {loc}", runOutput); + [Theory] + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { false, RunHost.All })] + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { true, RunHost.All })] + public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invariant, bool fullIcu, string[] testedLocales, RunHost host, string id) + { + string projectName = $"fullIcuInvariant_{fullIcu}_{invariant}_{buildArgs.Config}_{buildArgs.AOT}"; + bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); + + buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = fullIcu }; + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{invariant}"); + + // in invariant mode, all locales should be missing + string[] expectedLocales = invariant ? Array.Empty() : testedLocales; + string[] missingLocales = invariant ? testedLocales : Array.Empty(); + string programText = GetProgramText(expectedLocales, missingLocales); + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), + DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, + HasIcudt: !invariant)); + + string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + + if (invariant) + Assert.Contains("Only the invariant culture is supported in globalization-invariant mode", runOutput); + CheckExpectedLocales(expectedLocales, runOutput); + CheckMissingLocales(missingLocales, runOutput); + } + + [Theory] + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { false, RunHost.All })] + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { true, RunHost.All })] + public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool hasCustomIcu, RunHost host, string id) + { + string projectName = $"fullIcuCustom_{hasCustomIcu}_{buildArgs.Config}_{buildArgs.AOT}"; + bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); + + buildArgs = buildArgs with { ProjectName = projectName }; + if (hasCustomIcu) + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{customIcuPath}"); + + // custom icu has locales that are not present in full icu data and the other way around + string[] expectedLocales = hasCustomIcu ? customIcuExpectedLocales : customIcuMissingLocales; + string[] missingLocales = hasCustomIcu ? customIcuMissingLocales : customIcuExpectedLocales; + string programText = GetProgramText(expectedLocales, missingLocales); + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), + DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, + PredefinedIcudt: hasCustomIcu ? customIcuPath : "")); + + string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + + if (hasCustomIcu) + Assert.Contains("$(WasmIcuDataFileName) has no effect when $(WasmIncludeFullIcuData) is set to true.", runOutput); + CheckExpectedLocales(expectedLocales, runOutput); + CheckMissingLocales(missingLocales, runOutput); + } + + [Theory] + [BuildAndRun(host: RunHost.None)] + public void NonExistingCustomFileAssertError(BuildArgs buildArgs, string id) + { + string projectName = $"invalidCustomIcu_{buildArgs.Config}_{buildArgs.AOT}"; + buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = false }; + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"nonexisting.dat"); + + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), + ExpectSuccess: false)); + Assert.Contains("File in location $(WasmIcuDataFileName)=nonexisting.dat cannot be found neither when used as absolute path nor a relative runtime pack path.", output); } } From ca0e1b836d6a32f9226605aa3b5dd64d6ed344a8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 20 Jan 2023 11:07:50 +0100 Subject: [PATCH 42/68] Should fix wasm tests on helix. --- eng/testing/tests.wasm.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index 2d23e4fcf91e18..5ba9854f551876 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -215,6 +215,7 @@ <_WasmPropertyNames Include="_WasmDevel" /> <_WasmPropertyNames Include="_WasmStrictVersionMatch" /> <_WasmPropertyNames Include="WasmEmitSymbolMap" /> + <_WasmPropertyNames Include="WasmIncludeFullIcuData" /> <_WasmPropertiesToPass Include="$(%(_WasmPropertyNames.Identity))" From 48f146f21171ce28d54106e7c9aa89ac7d7eda9a Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 20 Jan 2023 12:24:53 +0100 Subject: [PATCH 43/68] Fix WBT. --- .../wasm/Wasm.Build.Tests/BuildTestBase.cs | 62 ++++++++++++------- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 4 +- .../InvariantGlobalizationTests.cs | 2 +- .../NativeRebuildTestsBase.cs | 4 +- .../Wasm.Build.Tests/WasmTemplateTests.cs | 4 ++ 5 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 990d696796725e..c5508142dc66d3 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -424,7 +424,7 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp options.MainJS ?? "test-main.js", options.HasV8Script, options.TargetFramework ?? DefaultTargetFramework, - options.HasIcudt, + options.GlobalizationMode, options.DotnetWasmFromRuntimePack ?? !buildArgs.AOT, options.PredefinedIcudt ?? ""); } @@ -618,7 +618,7 @@ protected static void AssertBasicAppBundle(string bundleDir, string mainJS, bool hasV8Script, string targetFramework, - bool hasIcudt = true, + GlobalizationMode globalizationMode = GlobalizationMode.FullIcu, bool dotnetWasmFromRuntimePack = true, string predefinedIcudt = "") { @@ -633,10 +633,22 @@ protected static void AssertBasicAppBundle(string bundleDir, }); AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script); - if (!string.IsNullOrEmpty(predefinedIcudt)) - AssertFilesExist(bundleDir, new[] { predefinedIcudt }, expectToExist: true); - else - AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: hasIcudt); + switch (globalizationMode) + { + case GlobalizationMode.Invariant: + AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: false); + break; + case GlobalizationMode.FullIcu: + AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: true); + break; + case GlobalizationMode.PredefinedIcu: + if (string.IsNullOrEmpty(predefinedIcudt)) + throw new ArgumentException("WasmBuildTest is invalid, value for predefinedIcudt is required when GlobalizationMode=PredefinedIcu."); + AssertFilesExist(bundleDir, new[] { predefinedIcudt }, expectToExist: true); + break; + case GlobalizationMode.AutomaticIcu: + throw new ArgumentException($"Running WBT in GlobalizationMode=AutomaticIcu is not allowed. In this mode icu loading depends on the locale of environment where the test is running and no check if globalization data was loaded is performed."); + } string managedDir = Path.Combine(bundleDir, "managed"); AssertFilesExist(managedDir, new[] { $"{projectName}.dll" }); @@ -1091,24 +1103,32 @@ internal record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, stri public record BuildProjectOptions ( - Action? InitProject = null, - bool? DotnetWasmFromRuntimePack = null, - bool HasIcudt = true, - bool UseCache = true, - bool ExpectSuccess = true, - bool AssertAppBundle = true, - bool CreateProject = true, - bool Publish = true, - bool BuildOnlyAfterPublish = true, - bool HasV8Script = true, - string? Verbosity = null, - string? Label = null, - string? TargetFramework = null, - string? MainJS = null, - string? PredefinedIcudt = null, + Action? InitProject = null, + bool? DotnetWasmFromRuntimePack = null, + GlobalizationMode GlobalizationMode = GlobalizationMode.FullIcu, + bool UseCache = true, + bool ExpectSuccess = true, + bool AssertAppBundle = true, + bool CreateProject = true, + bool Publish = true, + bool BuildOnlyAfterPublish = true, + bool HasV8Script = true, + string? Verbosity = null, + string? Label = null, + string? TargetFramework = null, + string? MainJS = null, + string? PredefinedIcudt = null, IDictionary? ExtraBuildEnvironmentVariables = null ); + public enum GlobalizationMode + { + Invariant, // no icu + FullIcu, // full icu data: icudt.dat is loaded + AutomaticIcu, // icu loaded based on the current locale + PredefinedIcu // user set WasmIcuDataFileName value and we are loading that file + }; + public record BlazorBuildOptions ( string Id, diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 715e5337ffb53e..bf2101f12a4947 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -122,6 +122,7 @@ private void TestIcuShards(BuildArgs buildArgs, string shardName, string[] expec new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, + GlobalizationMode: GlobalizationMode.PredefinedIcu, PredefinedIcudt: shardName)); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); @@ -150,7 +151,7 @@ public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invari new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - HasIcudt: !invariant)); + GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.FullIcu)); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); @@ -181,6 +182,7 @@ public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool hasCus new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, + GlobalizationMode: hasCustomIcu ? GlobalizationMode.PredefinedIcu : GlobalizationMode.FullIcu, PredefinedIcudt: hasCustomIcu ? customIcuPath : "")); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index 48c80ac1096808..db979c610e348a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -79,7 +79,7 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - HasIcudt: hasIcudt)); + GlobalizationMode: hasIcudt ? GlobalizationMode.FullIcu : GlobalizationMode.Invariant)); if (invariantGlobalization == true) { diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index 015994359bdaf6..8c0fef58eed525 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -52,7 +52,7 @@ public NativeRebuildTestsBase(ITestOutputHelper output, SharedBuildPerTestClassF new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: false, - HasIcudt: !invariant, + GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.FullIcu, CreateProject: true)); RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: RunHost.Chrome, id: id); @@ -83,7 +83,7 @@ protected string Rebuild(bool nativeRelink, bool invariant, BuildArgs buildArgs, id: id, new BuildProjectOptions( DotnetWasmFromRuntimePack: false, - HasIcudt: hasIcudt, + GlobalizationMode: hasIcudt ? GlobalizationMode.FullIcu : GlobalizationMode.Invariant, CreateProject: false, UseCache: false, Verbosity: verbosity)); diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs index 4507dddd1ff511..46ae203d8135ec 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs @@ -87,6 +87,7 @@ public void BrowserBuildThenPublish(string config) string projectName = Path.GetFileNameWithoutExtension(projectFile); UpdateBrowserMainJs(DefaultTargetFramework); + AddItemsPropertiesToProject(projectFile, "true"); var buildArgs = new BuildArgs(projectName, config, false, id, null); buildArgs = ExpandBuildArgs(buildArgs); @@ -135,6 +136,7 @@ public void ConsoleBuildThenPublish(string config) string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); string projectName = Path.GetFileNameWithoutExtension(projectFile); + AddItemsPropertiesToProject(projectFile, "true"); UpdateConsoleMainJs(); var buildArgs = new BuildArgs(projectName, config, false, id, null); @@ -200,6 +202,7 @@ private void ConsoleBuildAndRun(string config, bool relinking, string extraNewAr string projectFile = CreateWasmTemplateProject(id, "wasmconsole", extraNewArgs); string projectName = Path.GetFileNameWithoutExtension(projectFile); + AddItemsPropertiesToProject(projectFile, "true"); UpdateProgramCS(); UpdateConsoleMainJs(); if (relinking) @@ -362,6 +365,7 @@ public void ConsolePublishAndRun(string config, bool aot, bool relinking) string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); string projectName = Path.GetFileNameWithoutExtension(projectFile); + AddItemsPropertiesToProject(projectFile, "true"); UpdateProgramCS(); UpdateConsoleMainJs(); From fd07a3cc24e276529b8a3eb7a25aa9ade5fd3223 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 25 Jan 2023 05:27:35 +0000 Subject: [PATCH 44/68] [wasm] Update eng/testing/scenarios/BuildWasmAppsJobsList.txt --- eng/testing/scenarios/BuildWasmAppsJobsList.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index 6bafc00c1ad435..38e288f81cfa14 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -4,16 +4,18 @@ Wasm.Build.NativeRebuild.Tests.OptimizationFlagChangeTests Wasm.Build.NativeRebuild.Tests.ReferenceNewAssemblyRebuildTest Wasm.Build.NativeRebuild.Tests.SimpleSourceChangeRebuildTest Wasm.Build.Tests.Blazor.BuildPublishTests -Wasm.Build.Tests.Blazor.MiscTests2 Wasm.Build.Tests.Blazor.MiscTests +Wasm.Build.Tests.Blazor.MiscTests2 Wasm.Build.Tests.Blazor.NativeRefTests Wasm.Build.Tests.BuildPublishTests Wasm.Build.Tests.CleanTests Wasm.Build.Tests.ConfigSrcTests +Wasm.Build.Tests.IcuShardingTests Wasm.Build.Tests.InvariantGlobalizationTests Wasm.Build.Tests.MainWithArgsTests Wasm.Build.Tests.NativeBuildTests Wasm.Build.Tests.NativeLibraryTests +Wasm.Build.Tests.NonWasmTemplateBuildTests Wasm.Build.Tests.PInvokeTableGeneratorTests Wasm.Build.Tests.RebuildTests Wasm.Build.Tests.SatelliteAssembliesTests From 0969201ed084bdb47b5970618b8758b98691a2ba Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 25 Jan 2023 05:27:54 +0000 Subject: [PATCH 45/68] [wasm] fix testassets path --- src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index bf2101f12a4947..b570d4c7f87d52 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -17,7 +17,7 @@ public class IcuShardingTests : BuildTestBase public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - private static string customIcuPath = Path.Combine(Directory.GetCurrentDirectory().Split("artifacts")[0], "src", "mono", "wasm", "testassets", "icudt_custom.dat"); + private static string customIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat"); private static string[] customIcuExpectedLocales = new string[] { "cy-GB", "is-IS", "bs-BA" , "lb-LU"}; private static string[] customIcuMissingLocales = new string[] { "fr-FR", "hr-HR", "ko-KR"}; From 125ede2833e6a4a43ca4d85e48fb185c497ba970 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 27 Jan 2023 13:55:18 +0100 Subject: [PATCH 46/68] Default mode for tests should be the same as default mode for template apps. --- .../wasm/Wasm.Build.Tests/BuildTestBase.cs | 92 ++++++++++++------- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 8 +- .../InvariantGlobalizationTests.cs | 5 +- .../NativeRebuildTestsBase.cs | 9 +- .../NoopNativeRebuildTest.cs | 3 +- .../Wasm.Build.Tests/WasmTemplateTests.cs | 4 - src/mono/wasm/build/WasmApp.targets | 4 +- 7 files changed, 73 insertions(+), 52 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index da33c3fc9f1647..0567ff0d67860f 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -139,7 +139,8 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, string targetFramework = DefaultTargetFramework, string? extraXHarnessMonoArgs = null, string? extraXHarnessArgs = null, - string jsRelativePath = "test-main.js") + string jsRelativePath = "test-main.js", + string environmentLocale = "en-US") { buildDir ??= _projectDir; envVars ??= new(); @@ -169,6 +170,7 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, }; extraXHarnessArgs += " " + xharnessArgs; + args = $"{args} --lang={environmentLocale}"; string testLogPath = Path.Combine(_logPath, host.ToString()); string output = RunWithXHarness( @@ -342,8 +344,6 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp if (UseWebcil) { extraProperties += "true\n"; } - if (buildArgs.WasmIncludeFullIcuData) - extraProperties += $"\ntrue"; string projectContents = projectTemplate .Replace("##EXTRA_PROPERTIES##", extraProperties) @@ -632,7 +632,7 @@ protected static void AssertBasicAppBundle(string bundleDir, string mainJS, bool hasV8Script, string targetFramework, - GlobalizationMode globalizationMode = GlobalizationMode.FullIcu, + GlobalizationMode? globalizationMode, string predefinedIcudt = "", bool dotnetWasmFromRuntimePack = true, bool useWebcil = true) @@ -648,22 +648,7 @@ protected static void AssertBasicAppBundle(string bundleDir, }); AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script); - switch (globalizationMode) - { - case GlobalizationMode.Invariant: - AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: false); - break; - case GlobalizationMode.FullIcu: - AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: true); - break; - case GlobalizationMode.PredefinedIcu: - if (string.IsNullOrEmpty(predefinedIcudt)) - throw new ArgumentException("WasmBuildTest is invalid, value for predefinedIcudt is required when GlobalizationMode=PredefinedIcu."); - AssertFilesExist(bundleDir, new[] { predefinedIcudt }, expectToExist: true); - break; - case GlobalizationMode.AutomaticIcu: - throw new ArgumentException($"Running WBT in GlobalizationMode=AutomaticIcu is not allowed. In this mode icu loading depends on the locale of environment where the test is running and no check if globalization data was loaded is performed."); - } + AssertIcuAssets(); string managedDir = Path.Combine(bundleDir, "managed"); string bundledMainAppAssembly = @@ -685,6 +670,53 @@ protected static void AssertBasicAppBundle(string bundleDir, } AssertDotNetWasmJs(bundleDir, fromRuntimePack: dotnetWasmFromRuntimePack, targetFramework); + + void AssertIcuAssets() + { + bool expectEFIGS = false; + bool expectCJK = false; + bool expectNOCJK = false; + bool expectFULL = false; + switch (globalizationMode) + { + case GlobalizationMode.Invariant: + break; + case GlobalizationMode.FullIcu: + expectFULL = true; + break; + case GlobalizationMode.PredefinedIcu: + if (string.IsNullOrEmpty(predefinedIcudt)) + throw new ArgumentException("WasmBuildTest is invalid, value for predefinedIcudt is required when GlobalizationMode=PredefinedIcu."); + AssertFilesExist(bundleDir, new[] { predefinedIcudt }, expectToExist: true); + // predefined ICU name can be identical with the icu files from runtime pack + switch (predefinedIcudt) + { + case "icudt.dat": + expectFULL = true; + break; + case "icudt_EFIGS.dat": + expectEFIGS = true; + break; + case "icudt_CJK.dat": + expectCJK = true; + break; + case "icudt_no_CJK.dat": + expectNOCJK = true; + break; + } + break; + default: + // icu shard chosen based on the locale + expectCJK = true; + expectEFIGS = true; + expectNOCJK = true; + break; + } + AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: expectFULL); + AssertFilesExist(bundleDir, new[] { "icudt_EFIGS.dat" }, expectToExist: expectEFIGS); + AssertFilesExist(bundleDir, new[] { "icudt_CJK.dat" }, expectToExist: expectCJK); + AssertFilesExist(bundleDir, new[] { "icudt_no_CJK.dat" }, expectToExist: expectNOCJK); + } } protected static void AssertDotNetWasmJs(string bundleDir, bool fromRuntimePack, string targetFramework) @@ -1114,8 +1146,7 @@ public record BuildArgs(string ProjectName, string Config, bool AOT, string ProjectFileContents, - string? ExtraBuildArgs, - bool WasmIncludeFullIcuData = true); + string? ExtraBuildArgs); public record BuildProduct(string ProjectDir, string LogFile, bool Result); internal record FileStat (bool Exists, DateTime LastWriteTimeUtc, long Length, string FullPath); internal record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BundleDir); @@ -1124,7 +1155,7 @@ public record BuildProjectOptions ( Action? InitProject = null, bool? DotnetWasmFromRuntimePack = null, - GlobalizationMode GlobalizationMode = GlobalizationMode.FullIcu, + GlobalizationMode? GlobalizationMode = null, string? PredefinedIcudt = null, bool UseCache = true, bool ExpectSuccess = true, @@ -1140,14 +1171,6 @@ public record BuildProjectOptions IDictionary? ExtraBuildEnvironmentVariables = null ); - public enum GlobalizationMode - { - Invariant, // no icu - FullIcu, // full icu data: icudt.dat is loaded - AutomaticIcu, // icu loaded based on the current locale - PredefinedIcu // user set WasmIcuDataFileName value and we are loading that file - }; - public record BlazorBuildOptions ( string Id, @@ -1157,5 +1180,12 @@ public record BlazorBuildOptions bool WarnAsError = true ); + public enum GlobalizationMode + { + Invariant, // no icu + FullIcu, // full icu data: icudt.dat is loaded + PredefinedIcu // user set WasmIcuDataFileName value and we are loading that file + }; + public enum NativeFilesType { FromRuntimePack, Relinked, AOT }; } diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index b570d4c7f87d52..7b17511e8c0b3b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -113,7 +113,7 @@ private void TestIcuShards(BuildArgs buildArgs, string shardName, string[] expec string projectName = $"shard_{Path.GetFileName(shardName)}_{buildArgs.Config}_{buildArgs.AOT}"; bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); - buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = false }; + buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); string programText = GetProgramText(expectedLocales, missingLocales); @@ -139,8 +139,8 @@ public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invari string projectName = $"fullIcuInvariant_{fullIcu}_{invariant}_{buildArgs.Config}_{buildArgs.AOT}"; bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); - buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = fullIcu }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{invariant}"); + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{invariant}{fullIcu}"); // in invariant mode, all locales should be missing string[] expectedLocales = invariant ? Array.Empty() : testedLocales; @@ -198,7 +198,7 @@ public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool hasCus public void NonExistingCustomFileAssertError(BuildArgs buildArgs, string id) { string projectName = $"invalidCustomIcu_{buildArgs.Config}_{buildArgs.AOT}"; - buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = false }; + buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"nonexisting.dat"); (_, string output) = BuildProject(buildArgs, diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index db979c610e348a..41dac50141cf7c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -48,8 +48,7 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob if (invariantGlobalization != null) extraProperties = $"{extraProperties}{invariantGlobalization}"; - bool hasIcudt = invariantGlobalization == null || invariantGlobalization.Value == false; - buildArgs = buildArgs with { ProjectName = projectName, WasmIncludeFullIcuData = hasIcudt }; + buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties); if (dotnetWasmFromRuntimePack == null) @@ -79,7 +78,7 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - GlobalizationMode: hasIcudt ? GlobalizationMode.FullIcu : GlobalizationMode.Invariant)); + GlobalizationMode: invariantGlobalization == true ? GlobalizationMode.Invariant : null)); if (invariantGlobalization == true) { diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index 8c0fef58eed525..c9b4e2bc666020 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -52,7 +52,7 @@ public NativeRebuildTestsBase(ITestOutputHelper output, SharedBuildPerTestClassF new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: false, - GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.FullIcu, + GlobalizationMode: invariant ? GlobalizationMode.Invariant : null, CreateProject: true)); RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: RunHost.Chrome, id: id); @@ -66,8 +66,7 @@ protected string Rebuild(bool nativeRelink, bool invariant, BuildArgs buildArgs, File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); - bool hasIcudt = !invariant; - buildArgs = buildArgs with { ExtraBuildArgs = $"{buildArgs.ExtraBuildArgs} {extraBuildArgs}", WasmIncludeFullIcuData = hasIcudt }; + buildArgs = buildArgs with { ExtraBuildArgs = $"{buildArgs.ExtraBuildArgs} {extraBuildArgs}" }; var newBuildArgs = GenerateProjectContents(buildArgs, nativeRelink, invariant, extraProperties); // key(buildArgs) being changed @@ -83,7 +82,7 @@ protected string Rebuild(bool nativeRelink, bool invariant, BuildArgs buildArgs, id: id, new BuildProjectOptions( DotnetWasmFromRuntimePack: false, - GlobalizationMode: hasIcudt ? GlobalizationMode.FullIcu : GlobalizationMode.Invariant, + GlobalizationMode: invariant ? GlobalizationMode.Invariant : null, CreateProject: false, UseCache: false, Verbosity: verbosity)); @@ -99,8 +98,6 @@ protected BuildArgs GenerateProjectContents(BuildArgs buildArgs, bool nativeReli propertiesBuilder.Append($"true"); if (invariant) propertiesBuilder.Append($"true"); - else - propertiesBuilder.Append($"true"); propertiesBuilder.Append(extraProperties); return ExpandBuildArgs(buildArgs, propertiesBuilder.ToString()); diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs index 916ccaab485819..e5f17d261811a8 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs @@ -22,8 +22,7 @@ public NoopNativeRebuildTest(ITestOutputHelper output, SharedBuildPerTestClassFi [MemberData(nameof(NativeBuildData))] public void NoOpRebuildForNativeBuilds(BuildArgs buildArgs, bool nativeRelink, bool invariant, RunHost host, string id) { - bool hasIcudt = !invariant; - buildArgs = buildArgs with { ProjectName = $"rebuild_noop_{buildArgs.Config}", WasmIncludeFullIcuData = hasIcudt }; + buildArgs = buildArgs with { ProjectName = $"rebuild_noop_{buildArgs.Config}" }; (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink: nativeRelink, invariant: invariant, buildArgs, id); var pathsDict = GetFilesTable(buildArgs, paths, unchanged: true); diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs index ed1f21d7466aeb..706ddc9b0b58ec 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs @@ -87,7 +87,6 @@ public void BrowserBuildThenPublish(string config) string projectName = Path.GetFileNameWithoutExtension(projectFile); UpdateBrowserMainJs(DefaultTargetFramework); - AddItemsPropertiesToProject(projectFile, "true"); var buildArgs = new BuildArgs(projectName, config, false, id, null); buildArgs = ExpandBuildArgs(buildArgs); @@ -136,7 +135,6 @@ public void ConsoleBuildThenPublish(string config) string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); string projectName = Path.GetFileNameWithoutExtension(projectFile); - AddItemsPropertiesToProject(projectFile, "true"); UpdateConsoleMainJs(); var buildArgs = new BuildArgs(projectName, config, false, id, null); @@ -202,7 +200,6 @@ private void ConsoleBuildAndRun(string config, bool relinking, string extraNewAr string projectFile = CreateWasmTemplateProject(id, "wasmconsole", extraNewArgs); string projectName = Path.GetFileNameWithoutExtension(projectFile); - AddItemsPropertiesToProject(projectFile, "true"); UpdateProgramCS(); UpdateConsoleMainJs(); if (relinking) @@ -364,7 +361,6 @@ public void ConsolePublishAndRun(string config, bool aot, bool relinking) string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); string projectName = Path.GetFileNameWithoutExtension(projectFile); - AddItemsPropertiesToProject(projectFile, "true"); UpdateProgramCS(); UpdateConsoleMainJs(); diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 93bf65ede2a30f..16502fc506c4fe 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -69,8 +69,8 @@ - $(WasmEnableExceptionHandling) - Enable support for the WASM Exception Handling feature. - $(WasmEnableSIMD) - Enable support for the WASM SIMD feature. - $(WasmEnableWebcil) - Enable conversion of assembly .dlls to .webcil - - $(WasmBundleFullIcuData) - Loads full ICU data (icudt.dat). Only when InvariantGlobalization false. Defaults to false. - - $(WasmIcuDataFileName) - Name/path of ICU globalization file loaded to app. Only when InvariantGloblization=false and WasmBundleFullIcuData=false. + - $(WasmIncludeFullIcuData ) - Loads full ICU data (icudt.dat). Only when InvariantGlobalization false. Defaults to false. + - $(WasmIcuDataFileName) - Name/path of ICU globalization file loaded to app. Only when InvariantGloblization=false and WasmIncludeFullIcuData=false. Public items: - @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir). From 984716fad238549f6f5e5748ac2d5839cd277522 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 27 Jan 2023 16:00:09 +0100 Subject: [PATCH 47/68] Checking preferred icu when assets are still not populated was incorrect. --- src/mono/wasm/runtime/assets.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 687152dc989e9e..74aa7aaa0abe8c 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -65,6 +65,7 @@ export function get_preferred_icu_asset(): string | null { const EFIGS = "icudt_EFIGS.dat"; const OTHERS = "icudt_no_CJK.dat"; + // not all "fr", "it", "de", "es" are in EFIGS, only the one that is mostly used if (prefix == "en" || ["fr-FR", "it-IT", "de-DE", "es-ES"].includes(preferredCulture)) return EFIGS; @@ -73,7 +74,7 @@ export function get_preferred_icu_asset(): string | null { return OTHERS; } -export function shouldLoadIcuAsset(asset : AssetEntryInternal) : boolean{ +export function shouldLoadIcuAsset(asset : AssetEntryInternal, preferredIcuAsset: string | null) : boolean{ return !(asset.behavior == "icu" && asset.name === preferredIcuAsset); } @@ -90,9 +91,8 @@ type AssetWithBuffer = { buffer?: ArrayBuffer } -const preferredIcuAsset = get_preferred_icu_asset(); - export async function mono_download_assets(): Promise { + const preferredIcuAsset = get_preferred_icu_asset(); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; try { @@ -106,10 +106,10 @@ export async function mono_download_assets(): Promise { mono_assert(!asset.resolvedUrl || typeof asset.resolvedUrl === "string", "asset resolvedUrl could be string"); mono_assert(!asset.hash || typeof asset.hash === "string", "asset resolvedUrl could be string"); mono_assert(!asset.pendingDownload || typeof asset.pendingDownload === "object", "asset pendingDownload could be object"); - if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, preferredIcuAsset)) { expected_instantiated_assets_count++; } - if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, preferredIcuAsset)) { expected_downloaded_assets_count++; promises_of_assets_with_buffer.push(start_asset_download(asset)); } @@ -139,10 +139,10 @@ export async function mono_download_assets(): Promise { const headersOnly = skipBufferByAssetTypes[asset.behavior]; if (!headersOnly) { mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); - if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + if (!skipDownloadsByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, preferredIcuAsset)) { expected_downloaded_assets_count--; } - if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset)) { + if (!skipInstantiateByAssetTypes[asset.behavior] && shouldLoadIcuAsset(asset, preferredIcuAsset)) { expected_instantiated_assets_count--; } } From ad5791e9f55d37d797797c56a103d73ffbc9f802 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 27 Jan 2023 17:24:52 +0100 Subject: [PATCH 48/68] DataTimeFormat does not work well for browser + wrong condition. --- src/mono/wasm/runtime/assets.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 74aa7aaa0abe8c..041dc2ff5e8b62 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -59,7 +59,7 @@ export function get_preferred_icu_asset(): string | null { if (icuAssets.length === 1) return icuAssets[0].name; - const preferredCulture = Intl.DateTimeFormat().resolvedOptions().locale; + const preferredCulture = ENVIRONMENT_IS_WEB ? navigator.language : Intl.DateTimeFormat().resolvedOptions().locale; const prefix = preferredCulture.split("-")[0]; const CJK = "icudt_CJK.dat"; const EFIGS = "icudt_EFIGS.dat"; @@ -75,7 +75,7 @@ export function get_preferred_icu_asset(): string | null { } export function shouldLoadIcuAsset(asset : AssetEntryInternal, preferredIcuAsset: string | null) : boolean{ - return !(asset.behavior == "icu" && asset.name === preferredIcuAsset); + return !(asset.behavior == "icu" && asset.name != preferredIcuAsset); } export function resolve_asset_path(behavior: AssetBehaviours) { From 3c36db4e3ad021929979ef11961538c66b9fb78d Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 27 Jan 2023 17:30:19 +0100 Subject: [PATCH 49/68] Removed empty space changes. --- src/mono/wasm/runtime/dotnet.d.ts | 2 +- src/mono/wasm/runtime/types.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 516475c639a518..c51064343d22df 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -3,7 +3,7 @@ //! //! This is generated file, see src/mono/wasm/runtime/rollup.config.js -//! This is not considered public API with backward compatibility guarantees. +//! This is not considered public API with backward compatibility guarantees. interface DotnetHostBuilder { withConfig(config: MonoConfig): DotnetHostBuilder; diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index e22a9ffb8a9182..81739f9a0b3a90 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -168,13 +168,13 @@ export interface AssetEntry extends ResourceRequest { /** * If true, an attempt will be made to load the asset from each location in MonoConfig.remoteSources. */ - loadRemote?: boolean, + loadRemote?: boolean, // /** * If true, the runtime startup would not fail if the asset download was not successful. */ isOptional?: boolean /** - * If provided, runtime doesn't have to fetch the data. + * If provided, runtime doesn't have to fetch the data. * Runtime would set the buffer to null after instantiation to free the memory. */ buffer?: ArrayBuffer From 98b17e6732bacd4122765fd781c3fc3c8e12f341 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 27 Jan 2023 17:49:59 +0100 Subject: [PATCH 50/68] WBT for automatic icu selection based on locale. --- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 7b17511e8c0b3b..273a4655ef2bd5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -21,6 +21,15 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture private static string[] customIcuExpectedLocales = new string[] { "cy-GB", "is-IS", "bs-BA" , "lb-LU"}; private static string[] customIcuMissingLocales = new string[] { "fr-FR", "hr-HR", "ko-KR"}; + public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(bool aot, RunHost host) + => ConfigWithAOTData(aot) + .Multiply( + new object[] { "fr-FR", new string[] { "en-US", "fr-FR", "es-ES" }, new string[] { "pl-PL", "ko-KR", "cs-CZ" } }, // "icudt_EFIGS.dat" + new object[] { "ja-JP", new string[] { "en-GB", "zh-CN", "ja-JP" }, new string[] { "fr-FR", "hr-HR", "it-IT" } }, // icudt_CJK.dat + new object[] { "fr-CA", new string[] { "en-AU", "fr-FR", "sk-SK" }, new string[] { "ja-JP", "ko-KR", "zh-CN"} }) // "icudt_no_CJK.dat" + .WithRunHosts(host) + .UnwrapItemsAsArrays(); + public static IEnumerable IcuExpectedAndMissingCustomShardTestData(bool aot, RunHost host) => ConfigWithAOTData(aot) .Multiply( @@ -106,6 +115,30 @@ public void DefaultAvailableIcuShardsFromRuntimePack(BuildArgs buildArgs, string public void CustomIcuShard(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => TestIcuShards(buildArgs, shardName, expectedLocales, missingLocales, host, id); + [Theory] + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { false, RunHost.All })] + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { true, RunHost.All })] + public void AutomaticShardSelectionDependingOnEnvLocale(BuildArgs buildArgs, string environmentLocale, string[] expectedLocales, string[] missingLocales, RunHost host, string id) + { + string projectName = $"automatic_shard_{environmentLocale}_{buildArgs.Config}_{buildArgs.AOT}"; + bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); + + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs); + + string programText = GetProgramText(expectedLocales, missingLocales); + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), + DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); + + string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id, environmentLocale: environmentLocale); + + CheckExpectedLocales(expectedLocales, runOutput); + CheckMissingLocales(missingLocales, runOutput); + } + // on Chrome: when loading only EFIGS or only CJK, CoreLib's failure on culture not found cannot be easily caught: // Encountered infinite recursion while looking up resource 'Argument_CultureNotSupported' in System.Private.CoreLib. private void TestIcuShards(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) From 8a39ff55fe00ba5c715fde3eec04c210b5cfec9a Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 30 Jan 2023 11:42:16 +0100 Subject: [PATCH 51/68] Moved the full icu flag from libs to lib tests. --- eng/testing/tests.wasm.targets | 3 +++ src/libraries/Directory.Build.targets | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index cf9ff99f3df5be..f0a0a68dc091db 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -8,6 +8,9 @@ true full + + true + $([MSBuild]::NormalizeDirectory($(MonoProjectRoot), 'wasm', 'emsdk')) diff --git a/src/libraries/Directory.Build.targets b/src/libraries/Directory.Build.targets index a3a8f091b6601a..75732bff0f7171 100644 --- a/src/libraries/Directory.Build.targets +++ b/src/libraries/Directory.Build.targets @@ -5,8 +5,6 @@ MicrosoftAspNetCore Open $(TestStrongNameKeyId) - - true From f47d57092746381d309b11f418d201745d6ba492 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 30 Jan 2023 11:46:27 +0100 Subject: [PATCH 52/68] Flag in tests.wasm.targets should be enough for trimming tests. --- eng/testing/linker/SupportFiles/Directory.Build.props | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eng/testing/linker/SupportFiles/Directory.Build.props b/eng/testing/linker/SupportFiles/Directory.Build.props index 651818566146c5..1e08117fd3171c 100644 --- a/eng/testing/linker/SupportFiles/Directory.Build.props +++ b/eng/testing/linker/SupportFiles/Directory.Build.props @@ -11,8 +11,6 @@ false true - $(NoWarn);IL2121 - - true + $(NoWarn);IL2121 From 358ecd467db0ae0bc6f41066fa22f931114b5ae3 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 30 Jan 2023 15:32:40 +0100 Subject: [PATCH 53/68] Debugger tests in default globalization mode unless CJK needed. Firefox does not respect UILocale. --- .../DebuggerTestSuite/ChromeProvider.cs | 9 ++-- .../DebuggerTestSuite/DateTimeTests.cs | 46 +++++++++++++++++-- .../DebuggerTestSuite/DebuggerTestBase.cs | 10 ++-- .../DebuggerTestSuite/DebuggerTestFirefox.cs | 3 +- .../DebuggerTestSuite/FirefoxProvider.cs | 5 +- .../DebuggerTestSuite/TestHarnessOptions.cs | 1 + .../DebuggerTestSuite/TestHarnessProxy.cs | 3 +- .../DebuggerTestSuite/TestHarnessStartup.cs | 13 ++++-- .../tests/debugger-test/debugger-test.csproj | 2 - 9 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs index fc7878af3bf689..d65f4c7d2de7c3 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs @@ -47,12 +47,13 @@ public async Task StartBrowserAndProxyAsync(HttpContext context, string messagePrefix, ILoggerFactory loggerFactory, CancellationTokenSource cts, - int browserReadyTimeoutMs = 20000) + int browserReadyTimeoutMs = 20000, + string locale = "en-US") { string? line; try { - ProcessStartInfo psi = GetProcessStartInfo(s_browserPath.Value, GetInitParms(remoteDebuggingPort), targetUrl); + ProcessStartInfo psi = GetProcessStartInfo(s_browserPath.Value, GetInitParms(remoteDebuggingPort, locale), targetUrl); line = await LaunchHostAsync( psi, context, @@ -161,9 +162,9 @@ private async Task ExtractConnUrl (string str, ILogger logger) return wsURl; } - private static string GetInitParms(int port) + private static string GetInitParms(int port, string lang="en-US") { - string str = $"--headless --disable-gpu --lang=en-US --incognito --remote-debugging-port={port}"; + string str = $"--headless --disable-gpu --lang={lang} --incognito --remote-debugging-port={port}"; if (File.Exists("/.dockerenv")) { Console.WriteLine ("Detected a container, disabling sandboxing for debugger tests."); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs index 31cb68cf4f62fa..714b70e0ec6d62 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs @@ -9,16 +9,52 @@ namespace DebuggerTests { - public class DateTimeTests : DebuggerTests + public class DateTimeTestsCJK : DebuggerTests { - public DateTimeTests(ITestOutputHelper testOutput) : base(testOutput) + public DateTimeTestsCJK(ITestOutputHelper testOutput) : base(testOutput, locale: "ja-JA") + {} + + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("ja-JP", "yyyy\u5E74M\u6708d\u65E5dddd H:mm:ss", "yyyy\u5E74M\u6708d\u65E5dddd", "H:mm:ss", "yyyy/MM/dd", "H:mm")] + public async Task CheckDateTimeLocale(string locale, string fdtp, string ldp, string ltp, string sdp, string stp) + { + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs"; + + await SetBreakpointInMethod("debugger-test", "DebuggerTests.DateTimeTest", "LocaleTest", 15); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DateTimeTest:LocaleTest'," + + $"'{locale}'); }}, 1);", + debugger_test_loc, 25, 12, "DebuggerTests.DateTimeTest.LocaleTest", + locals_fn: async (locals) => + { + DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat; + CultureInfo.CurrentCulture = new CultureInfo(locale, false); + + await CheckProps(locals, new + { + fdtp = TString(fdtp), + ldp = TString(ldp), + ltp = TString(ltp), + sdp = TString(sdp), + stp = TString(stp), + dt = TDateTime(new DateTime(2020, 1, 2, 3, 4, 5)) + }, "locals", num_fields: 8); + } + ); + } + + } + + public class DateTimeTestsEFIGS : DebuggerTests + { + public DateTimeTestsEFIGS(ITestOutputHelper testOutput) : base(testOutput) {} [Theory] [InlineData("en-US", "dddd, MMMM d, yyyy h:mm:ss tt", "dddd, MMMM d, yyyy", "h:mm:ss tt", "M/d/yyyy", "h:mm tt")] - [InlineData("ja-JP", "yyyy\u5E74M\u6708d\u65E5dddd H:mm:ss", "yyyy\u5E74M\u6708d\u65E5dddd", "H:mm:ss", "yyyy/MM/dd", "H:mm")] - [InlineData("es-ES", "dddd, d 'de' MMMM 'de' yyyy H:mm:ss", "dddd, d 'de' MMMM 'de' yyyy", "H:mm:ss", "d/M/yyyy", "H:mm")] - [InlineData("de-DE", "dddd, d. MMMM yyyy HH:mm:ss", "dddd, d. MMMM yyyy", "HH:mm:ss", "dd.MM.yyyy", "HH:mm")] + // [InlineData("es-ES", "dddd, d 'de' MMMM 'de' yyyy H:mm:ss", "dddd, d 'de' MMMM 'de' yyyy", "H:mm:ss", "d/M/yyyy", "H:mm")] + // [InlineData("de-DE", "dddd, d. MMMM yyyy HH:mm:ss", "dddd, d. MMMM yyyy", "HH:mm:ss", "dd.MM.yyyy", "HH:mm")] public async Task CheckDateTimeLocale(string locale, string fdtp, string ldp, string ltp, string sdp, string stp) { var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs"; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 673217c64fa9bb..527b7fd8302eb7 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -24,8 +24,8 @@ public class DebuggerTests : DebuggerTestFirefox #endif { - public DebuggerTests(ITestOutputHelper testOutput, string driver = "debugger-driver.html") - : base(testOutput, driver) + public DebuggerTests(ITestOutputHelper testOutput, string driver = "debugger-driver.html", string locale = "en-US") + : base(testOutput, driver, locale) {} } @@ -125,19 +125,19 @@ static DebuggerTestBase() Directory.Delete(TempPath, recursive: true); } - public DebuggerTestBase(ITestOutputHelper testOutput, string driver = "debugger-driver.html") + public DebuggerTestBase(ITestOutputHelper testOutput, string driver = "debugger-driver.html", string locale = "en-US") // here pass locale { _env = new TestEnvironment(testOutput); _testOutput = testOutput; Id = Interlocked.Increment(ref s_idCounter); // the debugger is working in locale of the debugged application. For example Datetime.ToString() // we want the test to mach it. We are also starting chrome with --lang=en-US - System.Globalization.CultureInfo.CurrentCulture = new System.Globalization.CultureInfo("en-US"); + System.Globalization.CultureInfo.CurrentCulture = new System.Globalization.CultureInfo(locale); insp = new Inspector(Id, _testOutput); cli = insp.Client; scripts = SubscribeToScripts(insp); - startTask = TestHarnessProxy.Start(DebuggerTestAppPath, driver, UrlToRemoteDebugging(), testOutput); + startTask = TestHarnessProxy.Start(DebuggerTestAppPath, driver, UrlToRemoteDebugging(), testOutput, locale); } public virtual async Task InitializeAsync() diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs index 5e8e15fb826fc6..282ecbf9a56645 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs @@ -16,7 +16,8 @@ public class DebuggerTestFirefox : DebuggerTestBase { private new TimeSpan TestTimeout => base.TestTimeout * 5; internal FirefoxInspectorClient _client; - public DebuggerTestFirefox(ITestOutputHelper testOutput, string driver = "debugger-driver.html"):base(testOutput, driver) + public DebuggerTestFirefox(ITestOutputHelper testOutput, string driver = "debugger-driver.html", string locale = "en-US") + : base(testOutput, driver, locale) { if (insp.Client is not FirefoxInspectorClient) throw new Exception($"Bug: client should be {nameof(FirefoxInspectorClient)} for use with {nameof(DebuggerTestFirefox)}"); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/FirefoxProvider.cs b/src/mono/wasm/debugger/DebuggerTestSuite/FirefoxProvider.cs index 1744336c5b594b..b64fe84ca3d92f 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/FirefoxProvider.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/FirefoxProvider.cs @@ -38,14 +38,15 @@ public async Task StartBrowserAndProxyAsync(HttpContext context, string messagePrefix, ILoggerFactory loggerFactory, CancellationTokenSource cts, - int browserReadyTimeoutMs = 20000) + int browserReadyTimeoutMs = 20000, + string locale = "en-US") { if (_isDisposed) throw new ObjectDisposedException(nameof(FirefoxProvider)); try { - string args = $"-profile {GetProfilePath(Id)} -headless -new-instance -private -start-debugger-server {remoteDebuggingPort}"; + string args = $"-profile {GetProfilePath(Id)} -headless -new-instance -private -start-debugger-server {remoteDebuggingPort} -UILocale {locale}"; ProcessStartInfo? psi = GetProcessStartInfo(s_browserPath.Value, args, targetUrl); string? line = await LaunchHostAsync( psi, diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessOptions.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessOptions.cs index c3b9bc6c588801..0ab39d91fa0712 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessOptions.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessOptions.cs @@ -15,5 +15,6 @@ public class TestHarnessOptions : ProxyOptions public string NodeApp { get; set; } public string BrowserParms { get; set; } public Func, Task> ExtractConnUrl { get; set; } + public string Locale { get; set; } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs index 5124e349980fcf..038829aa60a5c5 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs @@ -34,7 +34,7 @@ public class TestHarnessProxy private static readonly ConcurrentDictionary>> s_exitHandlers = new(); private static readonly ConcurrentDictionary s_statusTable = new(); - public static Task Start(string appPath, string pagePath, string url, ITestOutputHelper testOutput) + public static Task Start(string appPath, string pagePath, string url, ITestOutputHelper testOutput, string locale = "en-US") // here pass locale { lock (proxyLock) { @@ -82,6 +82,7 @@ public static Task Start(string appPath, string pagePath, string url, ITestOutpu options.AppPath = appPath; options.PagePath = pagePath; options.DevToolsUrl = new Uri(url); + options.Locale = locale; }); }) .UseStartup() diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs index e71411cfb8cb24..605de92031279e 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs @@ -75,7 +75,12 @@ async Task SendNodeList(HttpContext context) } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor, IWebHostEnvironment env, ILogger logger, ILoggerFactory loggerFactory) + public void Configure( + IApplicationBuilder app, + IOptionsMonitor optionsAccessor, + IWebHostEnvironment env, + ILogger logger, + ILoggerFactory loggerFactory) { this.Logger = logger; this._loggerFactory = loggerFactory; @@ -135,7 +140,8 @@ await provider.StartBrowserAndProxyAsync(context, browserPort, message_prefix, _loggerFactory, - cts).ConfigureAwait(false); + cts, + locale: options.Locale).ConfigureAwait(false); } else if (host == WasmHost.Firefox) { @@ -147,7 +153,8 @@ await provider.StartBrowserAndProxyAsync(context, firefox_proxy_port, message_prefix, _loggerFactory, - cts).ConfigureAwait(false); + cts, + locale: options.Locale).ConfigureAwait(false); } Logger.LogDebug($"{message_prefix} TestHarnessStartup done"); } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj index 5ba07f672b7ff5..4d01709f69382d 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -8,8 +8,6 @@ true library true - - true true From 90c5f4fa1ce38a1a7aab47f78ffc3e7ef0828a87 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 30 Jan 2023 15:47:22 +0100 Subject: [PATCH 54/68] Removing forgotten comment. --- src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs | 4 ++-- src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs index 714b70e0ec6d62..b6a7e45e0091be 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs @@ -53,8 +53,8 @@ public DateTimeTestsEFIGS(ITestOutputHelper testOutput) : base(testOutput) [Theory] [InlineData("en-US", "dddd, MMMM d, yyyy h:mm:ss tt", "dddd, MMMM d, yyyy", "h:mm:ss tt", "M/d/yyyy", "h:mm tt")] - // [InlineData("es-ES", "dddd, d 'de' MMMM 'de' yyyy H:mm:ss", "dddd, d 'de' MMMM 'de' yyyy", "H:mm:ss", "d/M/yyyy", "H:mm")] - // [InlineData("de-DE", "dddd, d. MMMM yyyy HH:mm:ss", "dddd, d. MMMM yyyy", "HH:mm:ss", "dd.MM.yyyy", "HH:mm")] + [InlineData("es-ES", "dddd, d 'de' MMMM 'de' yyyy H:mm:ss", "dddd, d 'de' MMMM 'de' yyyy", "H:mm:ss", "d/M/yyyy", "H:mm")] + [InlineData("de-DE", "dddd, d. MMMM yyyy HH:mm:ss", "dddd, d. MMMM yyyy", "HH:mm:ss", "dd.MM.yyyy", "HH:mm")] public async Task CheckDateTimeLocale(string locale, string fdtp, string ldp, string ltp, string sdp, string stp) { var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs"; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs index 038829aa60a5c5..efe6eea035e56f 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs @@ -34,7 +34,7 @@ public class TestHarnessProxy private static readonly ConcurrentDictionary>> s_exitHandlers = new(); private static readonly ConcurrentDictionary s_statusTable = new(); - public static Task Start(string appPath, string pagePath, string url, ITestOutputHelper testOutput, string locale = "en-US") // here pass locale + public static Task Start(string appPath, string pagePath, string url, ITestOutputHelper testOutput, string locale = "en-US") { lock (proxyLock) { From 5188563ac578641cea5d7810b4eabd720db28e36 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 31 Jan 2023 13:12:35 +0100 Subject: [PATCH 55/68] Fixed sharding wbt. --- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 273a4655ef2bd5..c6190f085a38e7 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -18,15 +18,15 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture : base(output, buildContext) { } private static string customIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat"); - private static string[] customIcuExpectedLocales = new string[] { "cy-GB", "is-IS", "bs-BA" , "lb-LU"}; - private static string[] customIcuMissingLocales = new string[] { "fr-FR", "hr-HR", "ko-KR"}; + private static string[] customIcuExpectedLocales = new string[] { "cy-GB", "is-IS", "bs-BA", "lb-LU" }; + private static string[] customIcuMissingLocales = new string[] { "fr-FR", "hr-HR", "ko-KR" }; public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(bool aot, RunHost host) => ConfigWithAOTData(aot) .Multiply( new object[] { "fr-FR", new string[] { "en-US", "fr-FR", "es-ES" }, new string[] { "pl-PL", "ko-KR", "cs-CZ" } }, // "icudt_EFIGS.dat" new object[] { "ja-JP", new string[] { "en-GB", "zh-CN", "ja-JP" }, new string[] { "fr-FR", "hr-HR", "it-IT" } }, // icudt_CJK.dat - new object[] { "fr-CA", new string[] { "en-AU", "fr-FR", "sk-SK" }, new string[] { "ja-JP", "ko-KR", "zh-CN"} }) // "icudt_no_CJK.dat" + new object[] { "fr-CA", new string[] { "en-AU", "fr-FR", "sk-SK" }, new string[] { "ja-JP", "ko-KR", "zh-CN" } }) // "icudt_no_CJK.dat" .WithRunHosts(host) .UnwrapItemsAsArrays(); @@ -53,7 +53,7 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture .Multiply( new object[] { true, true, new string[] { "en-GB", "pl-PL", "ko-KR" } }, new object[] { true, false, new string[] { "en-GB", "pl-PL", "ko-KR" } }, - new object[] { false, false, new string[] { "en-GB", "pl-PL", "ko-KR" } }, + new object[] { false, false, new string[] { "en-GB", "fr-FR", "it-IT" } }, // default mode, only "icudt_EFIGS.dat" loaded new object[] { false, true, new string[] { "en-GB", "pl-PL", "ko-KR" } }) .WithRunHosts(host) .UnwrapItemsAsArrays(); @@ -66,12 +66,14 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture .WithRunHosts(host) .UnwrapItemsAsArrays(); + private static string GetLocalesStrArr(string[] locales) => locales.Length == 0 ? "Array.Empty()" : $@"new string[] {{ ""{string.Join("\", \"", locales)}"" }}"; + private static string GetProgramText(string[] expectedLocales, string[] missingLocales) => $@" using System; using System.Globalization; - string[] expectedLocales = new string[] {{ "" {string.Join("\", \"", expectedLocales)} "" }}; - string[] missingLocales = new string[] {{ "" {string.Join("\", \"", missingLocales)} "" }}; + string[] expectedLocales = {GetLocalesStrArr(expectedLocales)}; + string[] missingLocales = {GetLocalesStrArr(missingLocales)}; foreach (var loc in expectedLocales) {{ var culture = new CultureInfo(loc); @@ -85,7 +87,7 @@ private static string GetProgramText(string[] expectedLocales, string[] missingL }} catch(Exception) {{ - Console.WriteLine($""Missing locale as planned: {{loc}}""); + Console.WriteLine($""Missing locale as planned: {{loc}}""); }} }} return 42; @@ -103,7 +105,7 @@ private void CheckMissingLocales(string[] missingLocales, string runOutput) Assert.Contains($"Missing locale as planned: {loc}", runOutput); } - [Theory] + // [Theory] [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.All })] [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.All })] public void DefaultAvailableIcuShardsFromRuntimePack(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => @@ -184,12 +186,10 @@ public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invari new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.FullIcu)); + GlobalizationMode: invariant ? GlobalizationMode.Invariant : fullIcu ? GlobalizationMode.FullIcu : null)); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - if (invariant) - Assert.Contains("Only the invariant culture is supported in globalization-invariant mode", runOutput); CheckExpectedLocales(expectedLocales, runOutput); CheckMissingLocales(missingLocales, runOutput); } From 988b037c97d43728498a375c227f17249069c49b Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 31 Jan 2023 13:24:46 +0100 Subject: [PATCH 56/68] This should not be commented out. --- src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index c6190f085a38e7..7995fcaace7076 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -105,7 +105,7 @@ private void CheckMissingLocales(string[] missingLocales, string runOutput) Assert.Contains($"Missing locale as planned: {loc}", runOutput); } - // [Theory] + [Theory] [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.All })] [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.All })] public void DefaultAvailableIcuShardsFromRuntimePack(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => From dde05ee00bf0aecacfbfe6e302f34f65e272e2d2 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 3 Feb 2023 12:20:48 +0100 Subject: [PATCH 57/68] Sharding WBT do not make sense on v8 + fixed debugger tests on Linux + fixed WBT on Chrome on Windows. --- .../wasm/Wasm.Build.Tests/BuildTestBase.cs | 3 +- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 32 ++++++++++++------- .../DebuggerTestSuite/ChromeProvider.cs | 3 ++ src/mono/wasm/runtime/assets.ts | 6 ++-- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 1b0a09b8456b95..1c6931b7849cb4 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -166,11 +166,10 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, { RunHost.V8 => ("wasm test", $"--js-file={jsRelativePath} --engine=V8 -v trace", true), RunHost.NodeJS => ("wasm test", $"--js-file={jsRelativePath} --engine=NodeJS -v trace", true), - _ => ("wasm test-browser", $"-v trace -b {host} --web-server-use-cop", false) + _ => ("wasm test-browser", $"-v trace -b {host} --browser-arg=--lang={environmentLocale} --web-server-use-cop", false) }; extraXHarnessArgs += " " + xharnessArgs; - args = $"{args} --lang={environmentLocale}"; string testLogPath = Path.Combine(_logPath, host.ToString()); string output = RunWithXHarness( diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 7995fcaace7076..f8039ec680de2c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -26,7 +26,7 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture .Multiply( new object[] { "fr-FR", new string[] { "en-US", "fr-FR", "es-ES" }, new string[] { "pl-PL", "ko-KR", "cs-CZ" } }, // "icudt_EFIGS.dat" new object[] { "ja-JP", new string[] { "en-GB", "zh-CN", "ja-JP" }, new string[] { "fr-FR", "hr-HR", "it-IT" } }, // icudt_CJK.dat - new object[] { "fr-CA", new string[] { "en-AU", "fr-FR", "sk-SK" }, new string[] { "ja-JP", "ko-KR", "zh-CN" } }) // "icudt_no_CJK.dat" + new object[] { "cz-CS", new string[] { "en-AU", "fr-FR", "sk-SK" }, new string[] { "ja-JP", "ko-KR", "zh-CN" } }) // "icudt_no_CJK.dat" .WithRunHosts(host) .UnwrapItemsAsArrays(); @@ -106,20 +106,26 @@ private void CheckMissingLocales(string[] missingLocales, string runOutput) } [Theory] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.All })] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.All })] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.NodeJS })] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.Chrome })] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.NodeJS })] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.Chrome })] public void DefaultAvailableIcuShardsFromRuntimePack(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => TestIcuShards(buildArgs, shardName, expectedLocales, missingLocales, host, id); [Theory] - [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { false, RunHost.All })] - [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { true, RunHost.All })] + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { false, RunHost.NodeJS })] + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { false, RunHost.Chrome })] + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { true, RunHost.NodeJS })] + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { true, RunHost.Chrome })] public void CustomIcuShard(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => TestIcuShards(buildArgs, shardName, expectedLocales, missingLocales, host, id); [Theory] - [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { false, RunHost.All })] - [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { true, RunHost.All })] + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { false, RunHost.NodeJS })] + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { false, RunHost.Chrome })] + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { true, RunHost.NodeJS })] + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { true, RunHost.Chrome })] public void AutomaticShardSelectionDependingOnEnvLocale(BuildArgs buildArgs, string environmentLocale, string[] expectedLocales, string[] missingLocales, RunHost host, string id) { string projectName = $"automatic_shard_{environmentLocale}_{buildArgs.Config}_{buildArgs.AOT}"; @@ -167,8 +173,10 @@ private void TestIcuShards(BuildArgs buildArgs, string shardName, string[] expec } [Theory] - [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { false, RunHost.All })] - [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { true, RunHost.All })] + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { false, RunHost.NodeJS })] + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { false, RunHost.Chrome })] + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { true, RunHost.NodeJS })] + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { true, RunHost.Chrome })] public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invariant, bool fullIcu, string[] testedLocales, RunHost host, string id) { string projectName = $"fullIcuInvariant_{fullIcu}_{invariant}_{buildArgs.Config}_{buildArgs.AOT}"; @@ -195,8 +203,10 @@ public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invari } [Theory] - [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { false, RunHost.All })] - [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { true, RunHost.All })] + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { false, RunHost.NodeJS })] + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { false, RunHost.Chrome })] + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { true, RunHost.NodeJS })] + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { true, RunHost.Chrome })] public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool hasCustomIcu, RunHost host, string id) { string projectName = $"fullIcuCustom_{hasCustomIcu}_{buildArgs.Config}_{buildArgs.AOT}"; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs index d65f4c7d2de7c3..7d95f72e675185 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ChromeProvider.cs @@ -53,6 +53,9 @@ public async Task StartBrowserAndProxyAsync(HttpContext context, string? line; try { + // for WIndows setting --lang arg is enough + if (!OperatingSystem.IsWindows()) + Environment.SetEnvironmentVariable("LANGUAGE", locale); ProcessStartInfo psi = GetProcessStartInfo(s_browserPath.Value, GetInitParms(remoteDebuggingPort, locale), targetUrl); line = await LaunchHostAsync( psi, diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index a1077e82dd5743..4e25d46762ee37 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -59,15 +59,15 @@ export function get_preferred_icu_asset(): string | null { if (icuAssets.length === 1) return icuAssets[0].name; + // reads the browsers locale / the OS's locale const preferredCulture = ENVIRONMENT_IS_WEB ? navigator.language : Intl.DateTimeFormat().resolvedOptions().locale; const prefix = preferredCulture.split("-")[0]; const CJK = "icudt_CJK.dat"; const EFIGS = "icudt_EFIGS.dat"; const OTHERS = "icudt_no_CJK.dat"; - - // not all "fr", "it", "de", "es" are in EFIGS, only the one that is mostly used - if (prefix == "en" || ["fr-FR", "it-IT", "de-DE", "es-ES"].includes(preferredCulture)) + // not all "fr-*", "it-*", "de-*", "es-*" are in EFIGS, only the one that is mostly used + if (prefix == "en" || ["fr", "fr-FR", "it", "it-IT", "de", "de-DE", "es", "es-ES"].includes(preferredCulture)) return EFIGS; if (["zh", "ko", "ja"].includes(prefix)) return CJK; From 6d6f3bb5e68801d865c4391684d655a7868fe5e6 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:36:51 +0000 Subject: [PATCH 58/68] Cleanup. --- .../wasm/Wasm.Build.Tests/BuildTestBase.cs | 28 +++++++++----- .../HostRunner/IHostRunner.cs | 16 ++++++++ .../HostRunner/NodeJSHostRunner.cs | 14 +++++++ .../HostRunner/OtherHostRunner.cs | 14 +++++++ .../HostRunner/V8HostRunner.cs | 18 +++++++++ .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 37 +++++++------------ .../SatelliteAssembliesTests.cs | 18 +++++---- 7 files changed, 104 insertions(+), 41 deletions(-) create mode 100644 src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/HostRunner/OtherHostRunner.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 1c6931b7849cb4..00dfc73d6673aa 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -39,8 +39,12 @@ public abstract class BuildTestBase : IClassFixture skip automatic icu testing with Node + protected static RunHost hostsForOSLocaleSensitiveTests = isWindows ? RunHost.Chrome : RunHost.NodeJS | RunHost.Chrome; // FIXME: use an envvar to override this - protected static int s_defaultPerTestTimeoutMs = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 30*60*1000 : 15*60*1000; + protected static int s_defaultPerTestTimeoutMs = isWindows ? 30*60*1000 : 15*60*1000; protected static BuildEnvironment s_buildEnv; private const string s_runtimePackPathPattern = "\\*\\* MicrosoftNetCoreAppRuntimePackDir : '([^ ']*)'"; private const string s_nugetInsertionTag = ""; @@ -158,16 +162,15 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, } bundleDir ??= Path.Combine(GetBinDir(baseDir: buildDir, config: buildArgs.Config, targetFramework: targetFramework), "AppBundle"); - if (host is RunHost.V8 && OperatingSystem.IsWindows()) + IHostRunner hostRunner = GetHostRunnerFromRunHost(host); + if (!hostRunner.CanRunWBT()) throw new InvalidOperationException("Running tests with V8 on windows isn't supported"); // Use wasm-console.log to get the xharness output for non-browser cases - (string testCommand, string xharnessArgs, bool useWasmConsoleOutput) = host switch - { - RunHost.V8 => ("wasm test", $"--js-file={jsRelativePath} --engine=V8 -v trace", true), - RunHost.NodeJS => ("wasm test", $"--js-file={jsRelativePath} --engine=NodeJS -v trace", true), - _ => ("wasm test-browser", $"-v trace -b {host} --browser-arg=--lang={environmentLocale} --web-server-use-cop", false) - }; + string testCommand = hostRunner.GetTestCommand(); + XHarnessArgsOptions options = new XHarnessArgsOptions(jsRelativePath, environmentLocale, host); + string xharnessArgs = isWindows ? hostRunner.GetXharnessArgsWindowsOS(options) : hostRunner.GetXharnessArgsOtherOS(options); + bool useWasmConsoleOutput = hostRunner.UseWasmConsoleOutput(); extraXHarnessArgs += " " + xharnessArgs; @@ -337,7 +340,7 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp if (buildArgs.AOT) { extraProperties = $"{extraProperties}\ntrue"; - extraProperties += $"\n{RuntimeInformation.IsOSPlatform(OSPlatform.Windows)}\n"; + extraProperties += $"\n{isWindows}\n"; } if (UseWebcil) { @@ -1143,6 +1146,13 @@ public static int Main() return 42; } }"; + + private IHostRunner GetHostRunnerFromRunHost(RunHost host) => host switch + { + RunHost.V8 => new V8HostRunner(), + RunHost.NodeJS => new NodeJSHostRunner(), + _ => new OtherHostRunner(), + }; } public record BuildArgs(string ProjectName, diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs new file mode 100644 index 00000000000000..6179ea9d4df0a0 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Wasm.Build.Tests +{ + public record XHarnessArgsOptions(string jsRelativePath, string environmentLocale, RunHost host); + + interface IHostRunner + { + string GetTestCommand(); + string GetXharnessArgsWindowsOS(XHarnessArgsOptions options); + string GetXharnessArgsOtherOS(XHarnessArgsOptions options); + bool UseWasmConsoleOutput(); + bool CanRunWBT(); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs new file mode 100644 index 00000000000000..3f156bda05f660 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Wasm.Build.Tests +{ + public class NodeJSHostRunner : IHostRunner + { + public string GetTestCommand() => "wasm test"; + public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => $"--js-file={options.jsRelativePath} --engine=NodeJS -v trace"; + public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"--js-file={options.jsRelativePath} --engine=NodeJS -v trace --locale={options.environmentLocale}"; + public bool UseWasmConsoleOutput() => true; + public bool CanRunWBT() => true; + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/OtherHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/OtherHostRunner.cs new file mode 100644 index 00000000000000..5a2128c8d3190a --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/OtherHostRunner.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Wasm.Build.Tests +{ + public class OtherHostRunner : IHostRunner + { + public string GetTestCommand() => "wasm test-browser"; + public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --browser-arg=--lang={options.environmentLocale} --web-server-use-cop"; // Windows: chrome.exe --lang=locale + public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --locale={options.environmentLocale} --web-server-use-cop"; // Linux: LANGUAGE=locale ./chrome + public bool UseWasmConsoleOutput() => false; + public bool CanRunWBT() => true; + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs new file mode 100644 index 00000000000000..398bee325a3d2e --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace Wasm.Build.Tests +{ + public class V8HostRunner : IHostRunner + { + private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace"; + + public string GetTestCommand() => "wasm test"; + public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath); + public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath); + public bool UseWasmConsoleOutput() => true; + public bool CanRunWBT() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index f8039ec680de2c..fc35f8de4d3850 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -18,16 +18,16 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture : base(output, buildContext) { } private static string customIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat"); - private static string[] customIcuExpectedLocales = new string[] { "cy-GB", "is-IS", "bs-BA", "lb-LU" }; + private static string[] customIcuExpectedLocales = new string[] { "cy-GB", "is-IS", "bs-BA", "lb-LU" }; // there is no en-US inside! private static string[] customIcuMissingLocales = new string[] { "fr-FR", "hr-HR", "ko-KR" }; - public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(bool aot, RunHost host) + public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(bool aot) => ConfigWithAOTData(aot) .Multiply( new object[] { "fr-FR", new string[] { "en-US", "fr-FR", "es-ES" }, new string[] { "pl-PL", "ko-KR", "cs-CZ" } }, // "icudt_EFIGS.dat" new object[] { "ja-JP", new string[] { "en-GB", "zh-CN", "ja-JP" }, new string[] { "fr-FR", "hr-HR", "it-IT" } }, // icudt_CJK.dat new object[] { "cz-CS", new string[] { "en-AU", "fr-FR", "sk-SK" }, new string[] { "ja-JP", "ko-KR", "zh-CN" } }) // "icudt_no_CJK.dat" - .WithRunHosts(host) + .WithRunHosts(BuildTestBase.hostsForOSLocaleSensitiveTests) .UnwrapItemsAsArrays(); public static IEnumerable IcuExpectedAndMissingCustomShardTestData(bool aot, RunHost host) @@ -106,26 +106,20 @@ private void CheckMissingLocales(string[] missingLocales, string runOutput) } [Theory] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.NodeJS })] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.Chrome })] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.NodeJS })] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.Chrome })] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.NodeJS | RunHost.Chrome })] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.NodeJS | RunHost.Chrome })] public void DefaultAvailableIcuShardsFromRuntimePack(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => TestIcuShards(buildArgs, shardName, expectedLocales, missingLocales, host, id); [Theory] - [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { false, RunHost.NodeJS })] - [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { false, RunHost.Chrome })] - [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { true, RunHost.NodeJS })] - [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { true, RunHost.Chrome })] + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { false, RunHost.NodeJS | RunHost.Chrome })] + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { true, RunHost.NodeJS | RunHost.Chrome })] public void CustomIcuShard(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => TestIcuShards(buildArgs, shardName, expectedLocales, missingLocales, host, id); [Theory] - [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { false, RunHost.NodeJS })] - [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { false, RunHost.Chrome })] - [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { true, RunHost.NodeJS })] - [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { true, RunHost.Chrome })] + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { false })] + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { true })] public void AutomaticShardSelectionDependingOnEnvLocale(BuildArgs buildArgs, string environmentLocale, string[] expectedLocales, string[] missingLocales, RunHost host, string id) { string projectName = $"automatic_shard_{environmentLocale}_{buildArgs.Config}_{buildArgs.AOT}"; @@ -140,7 +134,6 @@ public void AutomaticShardSelectionDependingOnEnvLocale(BuildArgs buildArgs, str new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); - string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id, environmentLocale: environmentLocale); CheckExpectedLocales(expectedLocales, runOutput); @@ -173,10 +166,8 @@ private void TestIcuShards(BuildArgs buildArgs, string shardName, string[] expec } [Theory] - [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { false, RunHost.NodeJS })] - [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { false, RunHost.Chrome })] - [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { true, RunHost.NodeJS })] - [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { true, RunHost.Chrome })] + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { false, RunHost.NodeJS | RunHost.Chrome })] + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { true, RunHost.NodeJS | RunHost.Chrome })] public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invariant, bool fullIcu, string[] testedLocales, RunHost host, string id) { string projectName = $"fullIcuInvariant_{fullIcu}_{invariant}_{buildArgs.Config}_{buildArgs.AOT}"; @@ -203,10 +194,8 @@ public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invari } [Theory] - [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { false, RunHost.NodeJS })] - [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { false, RunHost.Chrome })] - [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { true, RunHost.NodeJS })] - [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { true, RunHost.Chrome })] + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { false, RunHost.NodeJS | RunHost.Chrome })] + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { true, RunHost.NodeJS | RunHost.Chrome })] public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool hasCustomIcu, RunHost host, string id) { string projectName = $"fullIcuCustom_{hasCustomIcu}_{buildArgs.Config}_{buildArgs.AOT}"; diff --git a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs index b7c95aaae91e36..31048375100cec 100644 --- a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs @@ -19,19 +19,19 @@ public SatelliteAssembliesTests(ITestOutputHelper output, SharedBuildPerTestClas { } - public static IEnumerable SatelliteAssemblyTestData(bool aot, bool relinking, RunHost host) + public static IEnumerable SatelliteAssemblyTestData(bool aot, bool relinking) => ConfigWithAOTData(aot) .Multiply( new object?[] { relinking, "es-ES" }, new object?[] { relinking, null }, new object?[] { relinking, "ja-JP" }) - .WithRunHosts(host) + .WithRunHosts(BuildTestBase.hostsForOSLocaleSensitiveTests) .UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false, RunHost.All })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true, RunHost.All })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false, RunHost.All })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false })] public void ResourcesFromMainAssembly(BuildArgs buildArgs, bool nativeRelink, string? argCulture, @@ -66,14 +66,15 @@ public void ResourcesFromMainAssembly(BuildArgs buildArgs, buildArgs, expectedExitCode: 42, args: argCulture, host: host, id: id, + environmentLocale: argCulture ?? "en-US", // check that downloading assets doesn't have timing race conditions extraXHarnessMonoArgs: host is RunHost.Chrome ? "--fetch-random-delay=200" : string.Empty); } [Theory] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false, RunHost.All })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true, RunHost.All })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false, RunHost.All })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false })] public void ResourcesFromProjectReference(BuildArgs buildArgs, bool nativeRelink, string? argCulture, @@ -121,6 +122,7 @@ public void ResourcesFromProjectReference(BuildArgs buildArgs, RunAndTestWasmApp(buildArgs, expectedExitCode: 42, args: argCulture, + environmentLocale: argCulture ?? "en-US", host: host, id: id); } From b414f33c51803dc490e3de5f0eb43603ac23267a Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 10 Feb 2023 10:03:34 +0100 Subject: [PATCH 59/68] Fix test to have sense. --- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index fc35f8de4d3850..9f157c6da755e0 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -196,31 +196,29 @@ public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invari [Theory] [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { false, RunHost.NodeJS | RunHost.Chrome })] [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { true, RunHost.NodeJS | RunHost.Chrome })] - public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool hasCustomIcu, RunHost host, string id) + public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool fullIcu, RunHost host, string id) { - string projectName = $"fullIcuCustom_{hasCustomIcu}_{buildArgs.Config}_{buildArgs.AOT}"; + string projectName = $"fullIcuCustom_{fullIcu}_{buildArgs.Config}_{buildArgs.AOT}"; bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); buildArgs = buildArgs with { ProjectName = projectName }; - if (hasCustomIcu) - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{customIcuPath}"); + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{customIcuPath}{fullIcu}"); // custom icu has locales that are not present in full icu data and the other way around - string[] expectedLocales = hasCustomIcu ? customIcuExpectedLocales : customIcuMissingLocales; - string[] missingLocales = hasCustomIcu ? customIcuMissingLocales : customIcuExpectedLocales; + string[] expectedLocales = fullIcu ? customIcuMissingLocales : customIcuExpectedLocales; + string[] missingLocales = fullIcu ? customIcuExpectedLocales : customIcuMissingLocales; string programText = GetProgramText(expectedLocales, missingLocales); (_, string output) = BuildProject(buildArgs, id: id, new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - GlobalizationMode: hasCustomIcu ? GlobalizationMode.PredefinedIcu : GlobalizationMode.FullIcu, - PredefinedIcudt: hasCustomIcu ? customIcuPath : "")); + GlobalizationMode: fullIcu ? GlobalizationMode.FullIcu : GlobalizationMode.PredefinedIcu, + PredefinedIcudt: fullIcu ? "" : customIcuPath)); + if (fullIcu) + Assert.Contains("$(WasmIcuDataFileName) has no effect when $(WasmIncludeFullIcuData) is set to true.", output); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - - if (hasCustomIcu) - Assert.Contains("$(WasmIcuDataFileName) has no effect when $(WasmIncludeFullIcuData) is set to true.", runOutput); CheckExpectedLocales(expectedLocales, runOutput); CheckMissingLocales(missingLocales, runOutput); } From e66edb0b3c1796eedb87db75f16141b44b34333f Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 16 Feb 2023 12:46:28 +0100 Subject: [PATCH 60/68] Fixed tests to reflect expected behavior after removing predefinedCulturesOnly=true from WASM + added en-US to custom ICU. --- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 200 ++++++++++-------- src/mono/wasm/testassets/icudt_custom.dat | Bin 530704 -> 538240 bytes 2 files changed, 106 insertions(+), 94 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 9f157c6da755e0..a13633f3045d1c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -17,44 +17,73 @@ public class IcuShardingTests : BuildTestBase public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } + // custom file contains only locales "cy-GB", "is-IS", "bs-BA", "lb-LU" and fallback locale: "en-US": private static string customIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat"); - private static string[] customIcuExpectedLocales = new string[] { "cy-GB", "is-IS", "bs-BA", "lb-LU" }; // there is no en-US inside! - private static string[] customIcuMissingLocales = new string[] { "fr-FR", "hr-HR", "ko-KR" }; + public record SundayNames { + public static string French = "dimanche"; + public static string Spanish = "domingo"; + public static string Chinese = "星期日"; + public static string Japanese = "日曜日"; + public static string Slovak = "nedeľa"; + } + private static string customIcuTestedLocales = $@"new Locale[] {{ + new Locale(""cy-GB"", ""Dydd Sul""), new Locale(""is-IS"", ""sunnudagur""), new Locale(""bs-BA"", ""nedjelja""), new Locale(""lb-LU"", ""Sonndeg""), + new Locale(""fr-FR""), new Locale(""hr-HR""), new Locale(""ko-KR"") + }}"; + private static string GetEfigsTestedLocales(string fallbackSundayName="Sunday") => $@"new Locale[] {{ + new Locale(""en-US""), new Locale(""fr-FR"", ""{SundayNames.French}""), new Locale(""es-ES"", ""{SundayNames.Spanish}""), + new Locale(""pl-PL"", ""{fallbackSundayName}""), new Locale(""ko-KR"", ""{fallbackSundayName}""), new Locale(""cs-CZ"", ""{fallbackSundayName}"") + }}"; + private static string GetCjkTestedLocales(string fallbackSundayName="Sunday") => $@"new Locale[] {{ + new Locale(""en-GB""), new Locale(""zh-CN"", ""{SundayNames.Chinese}""), new Locale(""ja-JP"", ""{SundayNames.Japanese}""), + new Locale(""fr-FR"", ""{fallbackSundayName}""), new Locale(""hr-HR"", ""{fallbackSundayName}""), new Locale(""it-IT"", ""{fallbackSundayName}"") + }}"; + private static string GetNocjkTestedLocales(string fallbackSundayName="Sunday") => $@"new Locale[] {{ + new Locale(""en-AU""), new Locale(""fr-FR"", ""{SundayNames.French}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), + new Locale(""ja-JP"", ""{fallbackSundayName}""), new Locale(""ko-KR"", ""{fallbackSundayName}""), new Locale(""zh-CN"", ""{fallbackSundayName}"") + }}"; + private static string fullIcuTestedLocales = $@"new Locale[] {{ + new Locale(""en-GB""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), new Locale(""zh-CN"", ""{SundayNames.Chinese}"") + }}"; - public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(bool aot) + public static IEnumerable IcuExpectedAndMissingCustomShardTestData(bool aot, RunHost host) => ConfigWithAOTData(aot) .Multiply( - new object[] { "fr-FR", new string[] { "en-US", "fr-FR", "es-ES" }, new string[] { "pl-PL", "ko-KR", "cs-CZ" } }, // "icudt_EFIGS.dat" - new object[] { "ja-JP", new string[] { "en-GB", "zh-CN", "ja-JP" }, new string[] { "fr-FR", "hr-HR", "it-IT" } }, // icudt_CJK.dat - new object[] { "cz-CS", new string[] { "en-AU", "fr-FR", "sk-SK" }, new string[] { "ja-JP", "ko-KR", "zh-CN" } }) // "icudt_no_CJK.dat" - .WithRunHosts(BuildTestBase.hostsForOSLocaleSensitiveTests) + new object[] { customIcuPath, customIcuTestedLocales, false }, + new object[] { customIcuPath, customIcuTestedLocales, true }) + .WithRunHosts(host) .UnwrapItemsAsArrays(); - public static IEnumerable IcuExpectedAndMissingCustomShardTestData(bool aot, RunHost host) + public static IEnumerable IcuExpectedAndMissingShardFromRuntimePackTestData(bool aot, RunHost host) => ConfigWithAOTData(aot) .Multiply( - // custom file contains only 4 locales, nothing else: - new object[] { customIcuPath, customIcuExpectedLocales, customIcuMissingLocales }) + new object[] { "icudt.dat", + $@"new Locale[] {{ + new Locale(""en-GB""), new Locale(""zh-CN"", ""{SundayNames.Chinese}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), + new Locale(""xx-yy"") }}" }, + new object[] { "icudt_EFIGS.dat", GetEfigsTestedLocales() }, + new object[] { "icudt_CJK.dat", GetCjkTestedLocales() }, + new object[] { "icudt_no_CJK.dat", GetNocjkTestedLocales() }) .WithRunHosts(host) .UnwrapItemsAsArrays(); - public static IEnumerable IcuExpectedAndMissingShardFromRuntimePackTestData(bool aot, RunHost host) + public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(bool aot) => ConfigWithAOTData(aot) .Multiply( - new object[] { "icudt.dat", new string[] { "en-GB", "zh-CN", "hr-HR" }, new string[] { "xx-yy" } }, - new object[] { "icudt_EFIGS.dat", new string[] { "en-US", "fr-FR", "es-ES" }, new string[] { "pl-PL", "ko-KR", "cs-CZ" } }, - new object[] { "icudt_CJK.dat", new string[] { "en-GB", "zh-CN", "ja-JP" }, new string[] { "fr-FR", "hr-HR", "it-IT" } }, - new object[] { "icudt_no_CJK.dat", new string[] { "en-AU", "fr-FR", "sk-SK" }, new string[] { "ja-JP", "ko-KR", "zh-CN"} }) - .WithRunHosts(host) + new object[] { "fr-FR", GetEfigsTestedLocales(SundayNames.French)}, + new object[] { "ja-JP", GetCjkTestedLocales(SundayNames.Japanese) }, + new object[] { "sk-SK", GetNocjkTestedLocales(SundayNames.Slovak) }) + .WithRunHosts(BuildTestBase.hostsForOSLocaleSensitiveTests) .UnwrapItemsAsArrays(); public static IEnumerable FullIcuWithInvariantTestData(bool aot, RunHost host) => ConfigWithAOTData(aot) .Multiply( - new object[] { true, true, new string[] { "en-GB", "pl-PL", "ko-KR" } }, - new object[] { true, false, new string[] { "en-GB", "pl-PL", "ko-KR" } }, - new object[] { false, false, new string[] { "en-GB", "fr-FR", "it-IT" } }, // default mode, only "icudt_EFIGS.dat" loaded - new object[] { false, true, new string[] { "en-GB", "pl-PL", "ko-KR" } }) + // in invariant mode, all locales should be missing + new object[] { true, true, "Array.Empty()" }, + new object[] { true, false, "Array.Empty()" }, + new object[] { false, false, GetEfigsTestedLocales() }, + new object[] { false, true, fullIcuTestedLocales}) .WithRunHosts(host) .UnwrapItemsAsArrays(); @@ -66,61 +95,82 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture .WithRunHosts(host) .UnwrapItemsAsArrays(); - private static string GetLocalesStrArr(string[] locales) => locales.Length == 0 ? "Array.Empty()" : $@"new string[] {{ ""{string.Join("\", \"", locales)}"" }}"; - - private static string GetProgramText(string[] expectedLocales, string[] missingLocales) => $@" + private static string GetProgramText(string testedLocales, bool onlyPredefinedCultures=false) => $@" using System; using System.Globalization; - string[] expectedLocales = {GetLocalesStrArr(expectedLocales)}; - string[] missingLocales = {GetLocalesStrArr(missingLocales)}; - foreach (var loc in expectedLocales) - {{ - var culture = new CultureInfo(loc); - Console.WriteLine($""Found expected locale: {{loc}} - {{culture.Name}}""); - }} - foreach (var loc in missingLocales) + bool onlyPredefinedCultures = {onlyPredefinedCultures.ToString().ToLower()}; + Locale[] locales = {testedLocales}; + foreach (var loc in locales) {{ try {{ - var culture = new CultureInfo(loc); + var culture = new CultureInfo(loc.Code); + var localizedSundayName = culture.DateTimeFormat.GetDayName(new DateTime(2000,01,02).DayOfWeek); + if (loc.SundayName != localizedSundayName) + {{ + Console.WriteLine($""Error: incorrect localized value for Sunday in locale {{loc.Code}}. Expected {{loc.SundayName}} but got {{localizedSundayName}}.""); + return -1; + }} }} - catch(Exception) + catch(Exception ex) {{ - Console.WriteLine($""Missing locale as planned: {{loc}}""); + if (!onlyPredefinedCultures) + {{ + Console.WriteLine($""Error: for locale {{loc.Code}} encountered an exception. This is expected only when OnlyPredefinedCultures=true. Ex: {{ex}}.""); + return -1; + }} + if (!ex.Message.Contains(""Culture is not supported"")) + {{ + Console.WriteLine($""Error: for locale {{loc.Code}} encountered an exception from different reason than expected. Ex: {{ex}}.""); + return -1; + }} }} }} return 42; + + public record Locale(string Code, string SundayName=""Sunday""); "; - private void CheckExpectedLocales(string[] expectedLocales, string runOutput) + private void TestIcuShards(BuildArgs buildArgs, string shardName, string testedLocales, RunHost host, string id, bool onlyPredefinedCultures=false) { - foreach (var loc in expectedLocales) - Assert.Contains($"Found expected locale: {loc} - {loc}", runOutput); - } + string projectName = $"shard_{Path.GetFileName(shardName)}_{buildArgs.Config}_{buildArgs.AOT}"; + bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); - private void CheckMissingLocales(string[] missingLocales, string runOutput) - { - foreach (var loc in missingLocales) - Assert.Contains($"Missing locale as planned: {loc}", runOutput); - } + buildArgs = buildArgs with { ProjectName = projectName }; + string extraProperties = onlyPredefinedCultures ? + $"{shardName}true" : + $"{shardName}"; + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: extraProperties); - [Theory] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false, RunHost.NodeJS | RunHost.Chrome })] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.NodeJS | RunHost.Chrome })] - public void DefaultAvailableIcuShardsFromRuntimePack(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => - TestIcuShards(buildArgs, shardName, expectedLocales, missingLocales, host, id); + string programText = GetProgramText(testedLocales, onlyPredefinedCultures); + (_, string output) = BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), + DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, + GlobalizationMode: GlobalizationMode.PredefinedIcu, + PredefinedIcudt: shardName)); + + string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + } [Theory] [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { false, RunHost.NodeJS | RunHost.Chrome })] [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { true, RunHost.NodeJS | RunHost.Chrome })] - public void CustomIcuShard(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) => - TestIcuShards(buildArgs, shardName, expectedLocales, missingLocales, host, id); + public void CustomIcuShard(BuildArgs buildArgs, string shardName, string testedLocales, bool onlyPredefinedCultures, RunHost host, string id) => + TestIcuShards(buildArgs, shardName, testedLocales, host, id, onlyPredefinedCultures); + + [Theory] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { false,RunHost.NodeJS | RunHost.Chrome })] + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { true, RunHost.NodeJS | RunHost.Chrome })] + public void DefaultAvailableIcuShardsFromRuntimePack(BuildArgs buildArgs, string shardName, string testedLocales, RunHost host, string id) => + TestIcuShards(buildArgs, shardName, testedLocales, host, id); [Theory] [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { false })] [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { true })] - public void AutomaticShardSelectionDependingOnEnvLocale(BuildArgs buildArgs, string environmentLocale, string[] expectedLocales, string[] missingLocales, RunHost host, string id) + public void AutomaticShardSelectionDependingOnEnvLocale(BuildArgs buildArgs, string environmentLocale, string testedLocales, RunHost host, string id) { string projectName = $"automatic_shard_{environmentLocale}_{buildArgs.Config}_{buildArgs.AOT}"; bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); @@ -128,47 +178,19 @@ public void AutomaticShardSelectionDependingOnEnvLocale(BuildArgs buildArgs, str buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs); - string programText = GetProgramText(expectedLocales, missingLocales); + string programText = GetProgramText(testedLocales); (_, string output) = BuildProject(buildArgs, id: id, new BuildProjectOptions( InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id, environmentLocale: environmentLocale); - - CheckExpectedLocales(expectedLocales, runOutput); - CheckMissingLocales(missingLocales, runOutput); - } - - // on Chrome: when loading only EFIGS or only CJK, CoreLib's failure on culture not found cannot be easily caught: - // Encountered infinite recursion while looking up resource 'Argument_CultureNotSupported' in System.Private.CoreLib. - private void TestIcuShards(BuildArgs buildArgs, string shardName, string[] expectedLocales, string[] missingLocales, RunHost host, string id) - { - string projectName = $"shard_{Path.GetFileName(shardName)}_{buildArgs.Config}_{buildArgs.AOT}"; - bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{shardName}"); - - string programText = GetProgramText(expectedLocales, missingLocales); - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - GlobalizationMode: GlobalizationMode.PredefinedIcu, - PredefinedIcudt: shardName)); - - string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - - CheckExpectedLocales(expectedLocales, runOutput); - CheckMissingLocales(missingLocales, runOutput); } [Theory] [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { false, RunHost.NodeJS | RunHost.Chrome })] [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { true, RunHost.NodeJS | RunHost.Chrome })] - public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invariant, bool fullIcu, string[] testedLocales, RunHost host, string id) + public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invariant, bool fullIcu, string testedLocales, RunHost host, string id) { string projectName = $"fullIcuInvariant_{fullIcu}_{invariant}_{buildArgs.Config}_{buildArgs.AOT}"; bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); @@ -176,10 +198,7 @@ public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invari buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{invariant}{fullIcu}"); - // in invariant mode, all locales should be missing - string[] expectedLocales = invariant ? Array.Empty() : testedLocales; - string[] missingLocales = invariant ? testedLocales : Array.Empty(); - string programText = GetProgramText(expectedLocales, missingLocales); + string programText = GetProgramText(testedLocales); (_, string output) = BuildProject(buildArgs, id: id, new BuildProjectOptions( @@ -188,9 +207,6 @@ public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invari GlobalizationMode: invariant ? GlobalizationMode.Invariant : fullIcu ? GlobalizationMode.FullIcu : null)); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - - CheckExpectedLocales(expectedLocales, runOutput); - CheckMissingLocales(missingLocales, runOutput); } [Theory] @@ -204,10 +220,8 @@ public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool fullIc buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{customIcuPath}{fullIcu}"); - // custom icu has locales that are not present in full icu data and the other way around - string[] expectedLocales = fullIcu ? customIcuMissingLocales : customIcuExpectedLocales; - string[] missingLocales = fullIcu ? customIcuExpectedLocales : customIcuMissingLocales; - string programText = GetProgramText(expectedLocales, missingLocales); + string testedLocales = fullIcu ? fullIcuTestedLocales : customIcuTestedLocales; + string programText = GetProgramText(testedLocales); (_, string output) = BuildProject(buildArgs, id: id, new BuildProjectOptions( @@ -219,8 +233,6 @@ public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool fullIc Assert.Contains("$(WasmIcuDataFileName) has no effect when $(WasmIncludeFullIcuData) is set to true.", output); string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - CheckExpectedLocales(expectedLocales, runOutput); - CheckMissingLocales(missingLocales, runOutput); } [Theory] diff --git a/src/mono/wasm/testassets/icudt_custom.dat b/src/mono/wasm/testassets/icudt_custom.dat index 7b8041930042ede2f0ad2effb66b898b59039479..703d9704375e0835cf84161fce435f4b2be6dc92 100644 GIT binary patch delta 16901 zcmd5@Xcp6Nw2A8xM{qP1`j!QBZr+?!}W+~YSBeE>H! zlIS$t^>8f%a&YUxJrpHs3pe~EQE#~0;obt5o+6qCcflWtmKYSJL-1@wz|^BeyHvst zh>ob|r%n1A?wKtnF%t#tFzITz;VU`yg*&wcr{QpuyK~A>;c#ce4GreB4DNck>*2Nw zaoP@-vN^q~;^BS*cWRW=H)cmfEaP-uMZj%h1YeS+;(jKq@2S1wvpU1H)dWcrs#}Wr)VpCpx%Xo_MtR%!^oub-MEw*X>7RJy)mePjlSo0bcq6Ub+FK^Z%WfKAj5PbQu&K z>UnfV8J=JU<$K{;FvAlVfEiv*Rr~w8B7Jc0=lWDu8Rfpgu1{4zz1%m<^=0C&q{!`l-Q{W^w5;-}6e@Ns zMRog9Xq?x=6!gGEFFKoMc)rOrx!k9_E`_GkcsERq?QqwZMp<4n($G#Vp^Dd9pnU$Y zMo^xrLhmR==vLROYOFKtgFeuO^nt4r_C-29M19d?Ij%&16sOZEHPr1?zK9z;5O-BI zBIVPu%BNLtAY`ZD3Evgv)#@}5wbq3wpP$WiFIEX^ZBZSr+oXJYgwv5;_f|Tpr*lyq zt|gRD4^9S3)BUS_`C9)o&~mLf84%W$Q7WhhXAp#S2P@yr0MQ^+P!~4{1EE?%G#C=P z2UV4{LRVondO0*$V4YpZ?smG;i|5#NEYt8I6c}rrSj){ z?~WS#DdlPM&>Y<$6`t$m+}kY;vgm(|O%8Ox*MeDy(qpbVGS_?2(p_QQI=ADzj8uOW zcrb0mbNY2jKAtfdIs!?KLqXdmlLH4I7D z8{O$obPLs|D)csuplete&1Qp*Z`cbg%~;E#W~q(<~1>RLI(}x^$ZEVpp?+Y=ZF?C4(@mrmHbowWP-kieVn3EzWz5c*COP zgW>4qr02Ej7>BwZF#o%A8{%j{Zb4zzX|R6`>LCGU3GVDuVlQP93sPyi9bEsgf@bGlW4 zV9?7S92?Y=rBdbvo&2e6t$ZL0+ zE*Omy^8x`(-j|c4bux)8nk1@FDhCR$4V^6ak>}B>kJH(E%V{DS#W2{8yPox!WAf>P* zSwIi5Hg@NPTN0BKmx^5|Don|IG@am6*i`m5<=dxO3z{iA*nN?%js1arEJ39%4J0Ri z9C$>Ylm?xRvIos0G+vr?7QQ5NwE4X8ykXGKY%Tkgt|Ak@jqs;g-FbJ;-KR2WY((^P zFF(Eo-^3m^E8?q&0;|9}YE^_V>#yc#Y)xDq*NA(p=sngwpqNWpl3Bt^nD%uwxN2Q-Js8zrB$f#6uh54_0*?n8C;TXfLLZ`I?4kIEL4}M|)On^c!I*1w zN>rEsl#-w>Nvp+L5fkghli~sKn2=PP#)~P!q-kP2?ia_uDQDU#@=p1&tRtJqi{rP7 zXGLCA3}glH3}hE4sGNbIKN=^DuZ1U3j1WkEvq@nQwaUN6e17@50}qdg?9MKm(xM{`;`N zSYY^1FGtN>TsqkzRy&|ximTmh*0Mdc2P&yvxV3Ckg1WTsm)a}J---H(^AaX`??h_g z$MW!c2}1%emA@f*0d*x&nK&dq5&Oh4@wE6rydk!W=t*&0ye*PwxyTX6jTZ8_v0N+{ zE#yG9N^BLUSrT=XdGdQvT`m)+#h0RkJTIg2u-uE!?8Vvr>fEB`*>Tz-o)<5Qy@JzB zF-Oc33*cL4AG1%}(ZIitEiN`Nx94M0y1}B*K!9U5sEv897Ar1pK<+{|_?)1y@;9Uq zCgptgcSc6h8yWT4gGCENwWF^=>;P~WI12m;_yYI~@Do6SLII#EPy?t1Tmf7OGzOXj zt%1!bq8;20Kvy6I=neD((t%811TY531||U}wW-Q(U|+Iv7R9Eauzp2%4@!&bDQSsO z$57MK2DXXaV{Buk>=pJFJHkE!Az5Kek`&%!PleBhrD zZh_~SD4+wLn*=C43~pbfyAA%I5MP4A&cb~P&&^Tb0Jx12HVbYHaw~u$^6#v9~2bZ2o#Gb#h!&HKJo=T{{eUlI1Cu{kg*p+`#A;Q2JpThqus_w$n*=~uYezh zPi&|<&=~OLjzZ=Wz=B*2Q%N>6J4%XO4|KJ0>jCsC8WL*jX-OJ{`vZf4VZbOL8^ADx z3W0gR65wuNIZy=L53B(m1~vds0GomBKx_0?3Eby_J-}CvaW)uYvh7?NHuRqkfL4D9pwN<0& zYtVy$3#v%|^<+ROI;(TgUpcc33cltXV>( z-6R{?UBo=P4dcC(jl!htP1?~5X6-MKU?aQMo3+2UzjtTxbKVR+GB6TT?MC*U_x!qj z)JyrQd=(6~%;=0~J$DyKf3~h_R+_sjP2JJDE}?DX&D~qgttQ?qckZs2pe{XkFE_3% z5C1ywb)cgco^Ko~5APIcXx|_3rEd?Y%b=~|8PJJs;EELk@|pdWeOhh@lN}Ysh*U6H z0VW$U6_=feS{4T?uF5fu0~ME~mGX1BM|>vkl8xjSLXt^X*Wj8XPS}d!J}H)pHDJN_ ziz4wj;+M%2vZX!UUL>!#qiOhBR%Gy#__Z0N*;YBjHO*CarraeD$v~yh74}?&zH>pS zn>OkumL9EDoYgcF&DHk3LS=LqB9EZPn)JrUstk4PrHeP{8?hN3e@fJbMm+5#zA+9vRSHjI$W~t}@tc0hK@?s?%Y{wBuUkRh&nTwcK!b=gz z+<>~Y5`N42gMn)0EDMwc3{MHy8g0sz& z!3?W_3r6Q)akNW}a~0-U%-A>2R1^$F$o z@c#o4lxdDID@Y|i{lO7{B9tTHChP~zQ4e*=Io^-g^XS4%PN6vf3?rdCfIESuzzSd$ z-~bN-j{uJWiac(D`!rAjya4P0{s6oV90V}UhCTw00iOdPk1ltt#VK|*o+qF>ZC#E# zs;FpCOJ`|&o{$tzC7t!{d7bEoDC$zgaXG?|f|Z2Ppq=nH1i5Sn7pshLD!5xCaJ(>h zn@=3aB5g0kg^}hQ;y@h35%4<>8hab!K1SL%0a&zLzIX`kItFsN2cA(7RD`i9gf>BV zHU#ljg)c)v_>_iEL-28g_e6Ym0CQ@%8ZwUOgja$yHwD@N*8%ZyJ@MQZ7zAMNshrt- zWQM_2&{0198I4SHfI`3zi^9JgSO@r&=o`p<2gDP`Cz0`HU@Pzpumh;92Bpa11z-=b z4|pAT3wRgs*Bc+fe+)PYC^b-Pk8gk-2& zd~!F!7>0)|AQ$kP(j3gDHQ);>Dz^Yw4ESM7;a>r)27I~eAkz&70IE-vcdz4_>E z2(>Y^YWHvlxR-+3R_4^kLP;L~QoIW*9$1Ss|MI!lO?VE$=A)RIx+{AJ%#Q9o1zXup zD8lPSQz;Fh%BBK?EjFsjYEMBb_-Emm5XQHeAQVMPZPW>m75x01fKihxj!ylG36svLp?=x@pQMMi3Ry_d9H06irp7Fa{^^)phbp_$q;VMiIl^U7~*$G%WC|gliDoRgH zh|WQ>_HOqFVKCJ-EA(1@t=q*Hhj(x*cX4<}R9toKh54i)pIi)I5Zb5=Rm#Ssbb2ne zkJ3B@H*?$E48FOb-}5fnnpTKaieI4>@$FYyqFxeZ+RQ`sB1oNv%2cd+RG~q)f+|kg z;35b~QNiOOS~+@(n~AEWj=i{Zb#czx+q75SDo z&q-;@YdGnvc~Yawb;?51^vI~bR4)TLRd(_#SSzP}b6%sH&P%I6dF?LIDcV%1 zA6>8WX&f)~FM8SeeNSH7;qUU~K7O-&t*hL0H6D6={B-`i9IvUZobOhlu?xz$xJ-|& zpIcl^qRL6V#i)}rrU%Lw&3Sbvw>#g|Zd=;?q4PqUPT3cj^ z+#$b_i|H%zl~7i(M4DmFF_)N2%@yYT=34VHbDXiz+-#mPQt=v}G5kyUQ!)2@Hnjh= z^iLL*m;Q4tWjRwze;(K&8+n$qY&n`t`!+U@-Hl!6_@)02v&2?uXBw)yYUvNVfonEP z60X@SdV$@nF{_CPZ8sZetCe_#6|7z0Nn2fh{2;DYKGS|r z_K+RpLjGW%gz@eqwh5N9`dHgVFKsu_i@*0^T~is2lA|E7k@-6iXXROM$KkAbueU&4 zBo<)@rp#E0XT9vZ8V$Lm-N0Y=sa0RpTlUp-vsugH_a1a>^|CL1??JaxhtHK2dk?y; zV1I7gbvq5}W%WQa(yYF?$NS%v3#}>?^;K$;oF?<-Pu6c$DTCCGMLy5x+Be^vZ%SHZ zZMODUuUfk;$11fXWm}`IiPjWryfw|FpUb2ip{9tZDAHMNg}f)eY!inb@_D zx0~G~8_M3Ijm!`Zc=UxaQ!N#<*t$5zm$}&FwMtg$Wi>^non${`+QV6x%4(V#M~y*PsZM6#Bo{=V-b5{uYJ)@AWyVmHsy>w5QLXp^ubp zINlDHWUEVu$8TeV?Xdlc7mjU=sOhWlTKS_0;&s;wZDP;D#KyxzR*|*Iy3?9zJ!(Ay z3wYEjv9?(&tR!kB#)wa0IG<^~ZM|-NWnIQPiV0#kwlUg6oqAerf%drCLbbYBwP_$s z?CI8EYp8W2d|R;Xu^;;mS8>X)LRO|V9Bt5+DFlVE+QJ4yZBi?wqG6#{-X_6|2z~{4 z19%5013m>#0$5yyegw_~1{PNmNCGYcsspuwdO$-1b0c;0B;K&<{ul zGJz4m7$DoCt3s3D!Fr0_WGEX{aWPhAv7sn12Uvr;w!yc)y}OTa36s=?2C3rWWb7PV zYG=VWNzJt?Oj7>N0>{FC2NwH{AbY8u1=v`$Ny@}-z!Rvi+FFz!VTMHNMEbPZ} zx;Et0&cfGlF@uEjkzpd>?wb+M$b4`BTsy9#I`iiU>Tcq-u=6FVZ4 zE{lAD}iA9)P$lE5;P`s6du1de?sj}0jev`!u4A<5iQCE{B-yj$8b=>O?MewKV2cx%mS*y zjI`bCWb8AYU`JjvT3nouj-3w70-zPK`M^S81^VwW6t5wqS^@2V&cIXv+i56KTa*qU z&3yM+8-N%HlmRIBKl;@kfl6f2U(A1)g4f~=`2(DeKLk7sYychuu*m>()hDKd!d9a& z?9T;@rVj1pTBw|T9r=qbmBq`3F;{I;e1#1P1#KkC7H>md?`WvaiGj|xPQ12IwxVuS z`bj5#t64g{3xCikt=XMFY1FL>8Wzv%^ogf(BaJ%ibU*}XdL<)GfoBtV*xtqJ@GJQl z+b|c?LHnTn8&5l3Xs&rj_?+#$wZ+JQx#nRzO#K5Dr=3=n8*Db%%3#yl{xOhfoVR8g zTZ}WdW^F%5@ilME+K|t_>LwHhLa^=oTCI~$+ylJ%1H9X_>fzZ);w) z*PpCjvQF{uyg4x5KJ*G#E%sBrVrSZ4W9Hi>UIv@c_az{b+DfD}V!656tYw^pE<}Bm z(C3{r%NiRPi^3K2Y8Pl1kklUMm9CRjN|}EwKGOBi4y~ z#Y$L%B=xs1w`bTp?9p})dm@ZL|3*Ig3o@w8z4l6Gm&!!@O}Pc%(|Er2l(8KB`8XPO#iE#{AU`hSj9n5M$KhOwpmyx%9_eV@QIbqXh&xI8TG z7O#pkINQ`pHj`JuJe7oFdJ(8^4iFuWvwC@0Q%w=IaYPhnNaZ6kAcxDl#n0kzVuEaF zZ^L=L!(fQNB)ealTls;ei7-gb5_83Tu@Jtqwg}V=T!rsn#d)WTW3_2&oT7golbis5 z0bs!-66l+`2`P{)>L0P}#`fxCg_zmw)7m#6#!Hu)h+yA+pPj&NnAdIycY&`Nb4{BjE@UIyr$;*t-8@;wNsQ%YgT z^#|8fR;m+G?|9R5sNqy3RL7fELFixc$qmRrS*p4rO{r(8g2Bh-leJLbNyPhnayRmf zw?ru}sdyylV(bGz#YO!yPsb7PC2$5f3;4|GCh&^U0A3aMSQA#Wp(=o~`}kqa;O`75 zdg#mLKt?e$;T>yg$JN26;^L8DZ@&k^z&Jr7b}KwN&O80$bY(K~nFc_OLJNVrfR(_5 zz@xwuKsk7Bm^=ZsveCy z-BcG8vMJC3@Eh9;ehh1DB;YspSv0gXu9L(3x>I5e74$(BJcH*K%A(k1`(ImKdJg#Y9H`V*mqDYXI>w{#f5!QPijy*$1AT>)GXAA0P-Dtu z&b2r2VD#dj0jTqql^3^lX*nHbX>unuH2f|b1BR8xF6=me1G6cW*vDmn;VWU(GCq^t z!TKfjltC$RDBO_MWJeqgH|d!u_?6;VlQ26;S_5{q82oA>rnCF-a;%lhB)9j=-u4}` zDmc~&oNw5I6An*_o#I2pBZFTdk8>>YF?7^9`91E+4|Dudb_;qio-J39<(cjf|F$9j zg)}+pa%hhyGoL1e?2y(GsK^OhUB8tkJZ`HiiE7Ctd4>D|$BX}hZSS+9i)<@f$bX2a z;t7*Ho}ec79FHbMKoh3R+vH$5UTUFSc^O+R56Tzue(eGk-VZW7PrM>t0w4WUypQdl z$6eO(t8$AXJC5HH6beNKU=bG6g(3r->e!9(>ya+lfc|%}IJ?M)@YjqooU`$ zWVbCik`THc=n3=z1_Bv?+W5ku&k*Kad}=9#U5?N+z@JL6lM!1CtOES7N5CXvII|Pm z1^D&;40Zm}tG7B?{2Jm80EYly@O}7kmM3-$@a4XP%#Q%hS9qsx)xO*+qpJBOyWUJS zN1^RFFA>WEvVnYbeD~?N?Sv41?j3v?I1QWw{HbXp{O1A8G}z3zf!_{)GNvK5$5=5v zl}+O(Yt^5GU6JkB!B9|tN=EtBv{YJl7oTA|Pb}kIqbXBDQ!=vhCq?oLn#{<`&&kRy zq`0t@v19Yg!^h>}XVbF^hvjGG7TlVbKLtNm#1GDLv$OJB-D} z^eJN_`K=44MkY_rn_fuyk;z$wIWra0V>nzjux=u!47YZePJ`I#;dY)obYU38|!Qf6Abq>b&<9uU)P3F@_+`a3-zbb)DW# zc`}5QeJ=w=Y8NI@XWay3KC}t)P3^*KR@1iiAhkx0Ful_}=aD8zkiD7La&~M*s?S%T zn($R;Gn{HMp3H0e1h}!7M|>UQ-rg?FRbKMor5t%%-IZIyDdDyNo@-0 zuU8_oHY@oZ&cT(uzVocQ-L@66_ZLC-%au4n+;bJezFCD_mNw(xQy2?-F`nGZ4~Y85^T_c^igp| ztvY*1gbVei4o=1zeuM3w6?g7g!<%1&U?gmVfl!I=z&(+AP%o%PCVokL3yr}6g$c0I bPb*5XYGa>f=AGZJ;eZ8*(r?$uM$!KR+gEYH delta 10426 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 Date: Thu, 16 Feb 2023 12:35:29 +0000 Subject: [PATCH 61/68] Fixed merge errors, removed redundant comment. --- eng/testing/tests.browser.targets | 1 + eng/testing/tests.wasm.targets | 3 --- src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets index aca8c2e27818f0..cd5ba59ef93df5 100644 --- a/eng/testing/tests.browser.targets +++ b/eng/testing/tests.browser.targets @@ -187,6 +187,7 @@ <_WasmPropertyNames Include="DisableParallelEmccCompile" /> <_WasmPropertyNames Include="EmccCompileOptimizationFlag" /> <_WasmPropertyNames Include="EmccLinkOptimizationFlag" /> + <_WasmPropertyNames Include="WasmIncludeFullIcuData" /> diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index ce367ef6937d3a..8a66c083a9f21c 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -8,9 +8,6 @@ true full - - $([MSBuild]::NormalizeDirectory($(MonoProjectRoot), 'wasm', 'emsdk')) - true diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 0e41e1542cc648..532e3db86282f2 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -130,7 +130,7 @@ static DebuggerTestBase() Directory.Delete(TempPath, recursive: true); } - public DebuggerTestBase(ITestOutputHelper testOutput, string driver = "debugger-driver.html", string locale = "en-US") // here pass locale + public DebuggerTestBase(ITestOutputHelper testOutput, string driver = "debugger-driver.html", string locale = "en-US") { _env = new TestEnvironment(testOutput); _testOutput = testOutput; From ca1bd113fa85a6b911a93349982ac897e44b78fe Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 22 Feb 2023 15:55:33 +0100 Subject: [PATCH 62/68] Update docs, disable NodeJS in tests. --- docs/design/features/globalization-icu-wasm.md | 10 ++++++++-- src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs | 7 ++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/design/features/globalization-icu-wasm.md b/docs/design/features/globalization-icu-wasm.md index a4bbd0657d2a6e..dc83b154feff44 100644 --- a/docs/design/features/globalization-icu-wasm.md +++ b/docs/design/features/globalization-icu-wasm.md @@ -16,10 +16,16 @@ Only one value for `WasmIcuDataFileName` can be set. It can also be a custom fil ## Custom ICU -Clone https://github.com/dotnet/icu . See files in https://github.com/dotnet/icu/tree/dotnet/main/icu-filters, and read https://unicode-org.github.io/icu/userguide/icu_data/buildtool.html#locale-slicing. Build your own filter or edit the existing file. Choose what filters to build in `eng/icu.mk`. Choose the platform: +The easiest way to build ICU is to open https://github.com/dotnet/icu/ it in [Codespaces](docs\workflow\Codespaces.md). See files in https://github.com/dotnet/icu/tree/dotnet/main/icu-filters, and read https://unicode-org.github.io/icu/userguide/icu_data/buildtool.html#locale-slicing. Build your own filter or edit the existing file. +We advise to edit the filters **only by adding/removing locales** from the `localeFilter/whitelist` to avoid removing important data. We recommend not to remove "en-US" locale from the localeFilter/whitelist because it is used as a fallback. Removing it for when +- `true`: results in `Encountered infinite recursion while looking for resource in System.Private.Corelib.` exception +- when predefined cultures only is not set: results in resolving data from ICU's `root.txt` files, e.g. `CultureInfo.DateTimeFormat.GetDayName(DateTime.Today.DayOfWeek)` will return an abbreviated form: `Mon` instead of `Monday`. +Removing specific feature data might result in an exception that starts with `[CultureData.IcuGetLocaleInfo(LocaleStringData)] Failed`. It means you removed data necessary to extract basic information about the locale. + + In the file `eng/icu.mk`, you can choose what filters to build. Choose the platform: ### Building for Browser: -* For prerequisites run `.devcontainer/postCreateCommand.sh`. +* For prerequisites run `.devcontainer/postCreateCommand.sh` (it is run automatically on creation if using Codespaces) * Building: ``` ./build.sh /p:TargetOS=Browser /p:TargetArchitecture=wasm /p:IcuTracing=true diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index e5bbf30bfc3bbb..e3a8e571a6b882 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -40,9 +40,10 @@ public abstract class BuildTestBase : IClassFixture skip automatic icu testing with Node - protected static RunHost hostsForOSLocaleSensitiveTests = isWindows ? RunHost.Chrome : RunHost.NodeJS | RunHost.Chrome; + // on Linux sharding does not work because we rely on LANG env var to check locale and emcc is overwriting it + protected static RunHost hostsForOSLocaleSensitiveTests = RunHost.Chrome; // FIXME: use an envvar to override this protected static int s_defaultPerTestTimeoutMs = isWindows ? 30*60*1000 : 15*60*1000; protected static BuildEnvironment s_buildEnv; @@ -1146,7 +1147,7 @@ public static int Main() return 42; } }"; - + private IHostRunner GetHostRunnerFromRunHost(RunHost host) => host switch { RunHost.V8 => new V8HostRunner(), From 30d709655c5237c3d4e70c5a4ff38db427292b71 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 23 Feb 2023 11:22:33 +0000 Subject: [PATCH 63/68] @radical's review part 1: embedded comments. --- eng/testing/tests.browser.targets | 3 ++- .../wasm/Wasm.Build.Tests/BuildTestBase.cs | 15 ++++++------ .../HostRunner/BrowserHostRunner.cs | 15 ++++++++++++ .../HostRunner/IHostRunner.cs | 23 ++++++++++--------- .../HostRunner/NodeJSHostRunner.cs | 19 +++++++-------- .../HostRunner/OtherHostRunner.cs | 14 ----------- .../HostRunner/V8HostRunner.cs | 21 +++++++++-------- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 20 ++++++++-------- .../SatelliteAssembliesTests.cs | 18 +++++++-------- src/mono/wasm/build/WasmApp.targets | 2 +- .../DebuggerTestSuite/DebuggerTestBase.cs | 2 +- src/mono/wasm/runtime/assets.ts | 2 +- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 2 +- 13 files changed, 80 insertions(+), 76 deletions(-) create mode 100644 src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/HostRunner/OtherHostRunner.cs diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets index cd5ba59ef93df5..849a12e96a83b4 100644 --- a/eng/testing/tests.browser.targets +++ b/eng/testing/tests.browser.targets @@ -8,7 +8,7 @@ true - + true @@ -188,6 +188,7 @@ <_WasmPropertyNames Include="EmccCompileOptimizationFlag" /> <_WasmPropertyNames Include="EmccLinkOptimizationFlag" /> <_WasmPropertyNames Include="WasmIncludeFullIcuData" /> + <_WasmPropertyNames Include="WasmIcuDataFileName" /> diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index e3a8e571a6b882..39e02f4c799690 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -30,6 +30,7 @@ public abstract class BuildTestBase : IClassFixture skip automatic icu testing with Node // on Linux sharding does not work because we rely on LANG env var to check locale and emcc is overwriting it - protected static RunHost hostsForOSLocaleSensitiveTests = RunHost.Chrome; + protected static RunHost s_hostsForOSLocaleSensitiveTests = RunHost.Chrome; // FIXME: use an envvar to override this - protected static int s_defaultPerTestTimeoutMs = isWindows ? 30*60*1000 : 15*60*1000; + protected static int s_defaultPerTestTimeoutMs = s_isWindows ? 30*60*1000 : 15*60*1000; protected static BuildEnvironment s_buildEnv; private const string s_runtimePackPathPattern = "\\*\\* MicrosoftNetCoreAppRuntimePackDir : '([^ ']*)'"; private const string s_nugetInsertionTag = ""; @@ -145,7 +146,7 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, string? extraXHarnessMonoArgs = null, string? extraXHarnessArgs = null, string jsRelativePath = "test-main.js", - string environmentLocale = "en-US") + string environmentLocale = DefaultEnvironmentLocale) { buildDir ??= _projectDir; envVars ??= new(); @@ -170,7 +171,7 @@ protected string RunAndTestWasmApp(BuildArgs buildArgs, // Use wasm-console.log to get the xharness output for non-browser cases string testCommand = hostRunner.GetTestCommand(); XHarnessArgsOptions options = new XHarnessArgsOptions(jsRelativePath, environmentLocale, host); - string xharnessArgs = isWindows ? hostRunner.GetXharnessArgsWindowsOS(options) : hostRunner.GetXharnessArgsOtherOS(options); + string xharnessArgs = s_isWindows ? hostRunner.GetXharnessArgsWindowsOS(options) : hostRunner.GetXharnessArgsOtherOS(options); bool useWasmConsoleOutput = hostRunner.UseWasmConsoleOutput(); extraXHarnessArgs += " " + xharnessArgs; @@ -341,7 +342,7 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp if (buildArgs.AOT) { extraProperties = $"{extraProperties}\ntrue"; - extraProperties += $"\n{isWindows}\n"; + extraProperties += $"\n{s_isWindows}\n"; } if (UseWebcil) { @@ -1152,7 +1153,7 @@ public static int Main() { RunHost.V8 => new V8HostRunner(), RunHost.NodeJS => new NodeJSHostRunner(), - _ => new OtherHostRunner(), + _ => new BrowserHostRunner(), }; } diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs new file mode 100644 index 00000000000000..288048616f6d7a --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace Wasm.Build.Tests; + +public class BrowserHostRunner : IHostRunner +{ + public string GetTestCommand() => "wasm test-browser"; + public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --browser-arg=--lang={options.environmentLocale} --web-server-use-cop"; // Windows: chrome.exe --lang=locale + public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --locale={options.environmentLocale} --web-server-use-cop"; // Linux: LANGUAGE=locale ./chrome + public bool UseWasmConsoleOutput() => false; + public bool CanRunWBT() => true; +} diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs index 6179ea9d4df0a0..0c93a7310ded4a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs +++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs @@ -1,16 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Wasm.Build.Tests -{ - public record XHarnessArgsOptions(string jsRelativePath, string environmentLocale, RunHost host); +#nullable enable + +namespace Wasm.Build.Tests; - interface IHostRunner - { - string GetTestCommand(); - string GetXharnessArgsWindowsOS(XHarnessArgsOptions options); - string GetXharnessArgsOtherOS(XHarnessArgsOptions options); - bool UseWasmConsoleOutput(); - bool CanRunWBT(); - } +public record XHarnessArgsOptions(string jsRelativePath, string environmentLocale, RunHost host); + +interface IHostRunner +{ + string GetTestCommand(); + string GetXharnessArgsWindowsOS(XHarnessArgsOptions options); + string GetXharnessArgsOtherOS(XHarnessArgsOptions options); + bool UseWasmConsoleOutput(); + bool CanRunWBT(); } diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs index 3f156bda05f660..cf311557c27f2e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs +++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/NodeJSHostRunner.cs @@ -1,14 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Wasm.Build.Tests +#nullable enable + +namespace Wasm.Build.Tests; + +public class NodeJSHostRunner : IHostRunner { - public class NodeJSHostRunner : IHostRunner - { - public string GetTestCommand() => "wasm test"; - public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => $"--js-file={options.jsRelativePath} --engine=NodeJS -v trace"; - public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"--js-file={options.jsRelativePath} --engine=NodeJS -v trace --locale={options.environmentLocale}"; - public bool UseWasmConsoleOutput() => true; - public bool CanRunWBT() => true; - } + public string GetTestCommand() => "wasm test"; + public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => $"--js-file={options.jsRelativePath} --engine=NodeJS -v trace"; + public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"--js-file={options.jsRelativePath} --engine=NodeJS -v trace --locale={options.environmentLocale}"; + public bool UseWasmConsoleOutput() => true; + public bool CanRunWBT() => true; } diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/OtherHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/OtherHostRunner.cs deleted file mode 100644 index 5a2128c8d3190a..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/OtherHostRunner.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Wasm.Build.Tests -{ - public class OtherHostRunner : IHostRunner - { - public string GetTestCommand() => "wasm test-browser"; - public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --browser-arg=--lang={options.environmentLocale} --web-server-use-cop"; // Windows: chrome.exe --lang=locale - public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --locale={options.environmentLocale} --web-server-use-cop"; // Linux: LANGUAGE=locale ./chrome - public bool UseWasmConsoleOutput() => false; - public bool CanRunWBT() => true; - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs index 398bee325a3d2e..885aba29ad4634 100644 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs +++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs @@ -1,18 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + +namespace Wasm.Build.Tests; + using System.Runtime.InteropServices; -namespace Wasm.Build.Tests +public class V8HostRunner : IHostRunner { - public class V8HostRunner : IHostRunner - { - private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace"; + private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace"; - public string GetTestCommand() => "wasm test"; - public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath); - public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath); - public bool UseWasmConsoleOutput() => true; - public bool CanRunWBT() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - } + public string GetTestCommand() => "wasm test"; + public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath); + public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath); + public bool UseWasmConsoleOutput() => true; + public bool CanRunWBT() => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); } diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index a13633f3045d1c..162d64e63e2c6a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -18,7 +18,7 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture : base(output, buildContext) { } // custom file contains only locales "cy-GB", "is-IS", "bs-BA", "lb-LU" and fallback locale: "en-US": - private static string customIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat"); + private static string s_customIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat"); public record SundayNames { public static string French = "dimanche"; public static string Spanish = "domingo"; @@ -26,7 +26,7 @@ public record SundayNames { public static string Japanese = "日曜日"; public static string Slovak = "nedeľa"; } - private static string customIcuTestedLocales = $@"new Locale[] {{ + private static readonly string s_customIcuTestedLocales = $@"new Locale[] {{ new Locale(""cy-GB"", ""Dydd Sul""), new Locale(""is-IS"", ""sunnudagur""), new Locale(""bs-BA"", ""nedjelja""), new Locale(""lb-LU"", ""Sonndeg""), new Locale(""fr-FR""), new Locale(""hr-HR""), new Locale(""ko-KR"") }}"; @@ -42,15 +42,15 @@ private static string GetNocjkTestedLocales(string fallbackSundayName="Sunday") new Locale(""en-AU""), new Locale(""fr-FR"", ""{SundayNames.French}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), new Locale(""ja-JP"", ""{fallbackSundayName}""), new Locale(""ko-KR"", ""{fallbackSundayName}""), new Locale(""zh-CN"", ""{fallbackSundayName}"") }}"; - private static string fullIcuTestedLocales = $@"new Locale[] {{ + private static readonly string s_fullIcuTestedLocales = $@"new Locale[] {{ new Locale(""en-GB""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), new Locale(""zh-CN"", ""{SundayNames.Chinese}"") }}"; public static IEnumerable IcuExpectedAndMissingCustomShardTestData(bool aot, RunHost host) => ConfigWithAOTData(aot) .Multiply( - new object[] { customIcuPath, customIcuTestedLocales, false }, - new object[] { customIcuPath, customIcuTestedLocales, true }) + new object[] { s_customIcuPath, s_customIcuTestedLocales, false }, + new object[] { s_customIcuPath, s_customIcuTestedLocales, true }) .WithRunHosts(host) .UnwrapItemsAsArrays(); @@ -73,7 +73,7 @@ private static string GetNocjkTestedLocales(string fallbackSundayName="Sunday") new object[] { "fr-FR", GetEfigsTestedLocales(SundayNames.French)}, new object[] { "ja-JP", GetCjkTestedLocales(SundayNames.Japanese) }, new object[] { "sk-SK", GetNocjkTestedLocales(SundayNames.Slovak) }) - .WithRunHosts(BuildTestBase.hostsForOSLocaleSensitiveTests) + .WithRunHosts(BuildTestBase.s_hostsForOSLocaleSensitiveTests) .UnwrapItemsAsArrays(); public static IEnumerable FullIcuWithInvariantTestData(bool aot, RunHost host) @@ -83,7 +83,7 @@ private static string GetNocjkTestedLocales(string fallbackSundayName="Sunday") new object[] { true, true, "Array.Empty()" }, new object[] { true, false, "Array.Empty()" }, new object[] { false, false, GetEfigsTestedLocales() }, - new object[] { false, true, fullIcuTestedLocales}) + new object[] { false, true, s_fullIcuTestedLocales}) .WithRunHosts(host) .UnwrapItemsAsArrays(); @@ -218,9 +218,9 @@ public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool fullIc bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release"); buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{customIcuPath}{fullIcu}"); + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{s_customIcuPath}{fullIcu}"); - string testedLocales = fullIcu ? fullIcuTestedLocales : customIcuTestedLocales; + string testedLocales = fullIcu ? s_fullIcuTestedLocales : s_customIcuTestedLocales; string programText = GetProgramText(testedLocales); (_, string output) = BuildProject(buildArgs, id: id, @@ -228,7 +228,7 @@ public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool fullIc InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, GlobalizationMode: fullIcu ? GlobalizationMode.FullIcu : GlobalizationMode.PredefinedIcu, - PredefinedIcudt: fullIcu ? "" : customIcuPath)); + PredefinedIcudt: fullIcu ? "" : s_customIcuPath)); if (fullIcu) Assert.Contains("$(WasmIcuDataFileName) has no effect when $(WasmIncludeFullIcuData) is set to true.", output); diff --git a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs index 31048375100cec..b7c95aaae91e36 100644 --- a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs @@ -19,19 +19,19 @@ public SatelliteAssembliesTests(ITestOutputHelper output, SharedBuildPerTestClas { } - public static IEnumerable SatelliteAssemblyTestData(bool aot, bool relinking) + public static IEnumerable SatelliteAssemblyTestData(bool aot, bool relinking, RunHost host) => ConfigWithAOTData(aot) .Multiply( new object?[] { relinking, "es-ES" }, new object?[] { relinking, null }, new object?[] { relinking, "ja-JP" }) - .WithRunHosts(BuildTestBase.hostsForOSLocaleSensitiveTests) + .WithRunHosts(host) .UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false, RunHost.All })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true, RunHost.All })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false, RunHost.All })] public void ResourcesFromMainAssembly(BuildArgs buildArgs, bool nativeRelink, string? argCulture, @@ -66,15 +66,14 @@ public void ResourcesFromMainAssembly(BuildArgs buildArgs, buildArgs, expectedExitCode: 42, args: argCulture, host: host, id: id, - environmentLocale: argCulture ?? "en-US", // check that downloading assets doesn't have timing race conditions extraXHarnessMonoArgs: host is RunHost.Chrome ? "--fetch-random-delay=200" : string.Empty); } [Theory] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true })] - [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false, RunHost.All })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true, RunHost.All })] + [MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false, RunHost.All })] public void ResourcesFromProjectReference(BuildArgs buildArgs, bool nativeRelink, string? argCulture, @@ -122,7 +121,6 @@ public void ResourcesFromProjectReference(BuildArgs buildArgs, RunAndTestWasmApp(buildArgs, expectedExitCode: 42, args: argCulture, - environmentLocale: argCulture ?? "en-US", host: host, id: id); } diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 5df06450a44787..d59b265dd91818 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -69,7 +69,7 @@ - $(WasmEnableExceptionHandling) - Enable support for the WASM Exception Handling feature. - $(WasmEnableSIMD) - Enable support for the WASM SIMD feature. - $(WasmEnableWebcil) - Enable conversion of assembly .dlls to .webcil - - $(WasmIncludeFullIcuData ) - Loads full ICU data (icudt.dat). Only when InvariantGlobalization false. Defaults to false. + - $(WasmIncludeFullIcuData) - Loads full ICU data (icudt.dat). Defaults to false. Only applicable when InvariantGlobalization=false. - $(WasmIcuDataFileName) - Name/path of ICU globalization file loaded to app. Only when InvariantGloblization=false and WasmIncludeFullIcuData=false. - $(WasmAllowUndefinedSymbols) - Controls whether undefined symbols are allowed or not, if true, appends 'allow-undefined' and sets 'ERROR_ON_UNDEFINED_SYMBOLS=0' as arguments for wasm-ld, diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 532e3db86282f2..0c9686d200aece 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -130,7 +130,7 @@ static DebuggerTestBase() Directory.Delete(TempPath, recursive: true); } - public DebuggerTestBase(ITestOutputHelper testOutput, string driver = "debugger-driver.html", string locale = "en-US") + public DebuggerTestBase(ITestOutputHelper testOutput, string driver = "debugger-driver.html", string locale) { _env = new TestEnvironment(testOutput); _testOutput = testOutput; diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index ffb2969c2790bd..c485d0d8d94ce0 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -54,7 +54,7 @@ export function get_preferred_icu_asset(): string | null { // By setting user can define what ICU source file they want to load. // There is no need to check application's culture when is set. // If it was not set, then we have 3 "icu" assets in config and we should choose - // only one for loading, this one that matches the application's locale. + // only one for loading, the one that matches the application's locale. const icuAssets = runtimeHelpers.config.assets.filter(a => a["behavior"] == "icu"); if (icuAssets.length === 1) return icuAssets[0].name; diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 9a63322d519a93..0492959c4090ae 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -311,7 +311,7 @@ protected override bool ExecuteInternal() if (!InvariantGlobalization) { bool loadRemote = RemoteSources?.Length > 0; - foreach (var idfn in IcuDataFileNames!) + foreach (var idfn in IcuDataFileNames) { if (!File.Exists(idfn)) { From 8a27d8e6bb61dad2fa9f230e4e6832256795d341 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:13:49 +0000 Subject: [PATCH 64/68] Merge with main and fix strange CI behavior. --- src/tasks/WasmAppBuilder/wasi/WasiAppBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasks/WasmAppBuilder/wasi/WasiAppBuilder.cs b/src/tasks/WasmAppBuilder/wasi/WasiAppBuilder.cs index 0269e6721cf2a5..8ad5ee7960de9f 100644 --- a/src/tasks/WasmAppBuilder/wasi/WasiAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/wasi/WasiAppBuilder.cs @@ -17,8 +17,8 @@ protected override bool ValidateArguments() if (!base.ValidateArguments()) return false; - if (!InvariantGlobalization && string.IsNullOrEmpty(IcuDataFileName)) - throw new LogAsErrorException("IcuDataFileName property shouldn't be empty if InvariantGlobalization=false"); + if (!InvariantGlobalization && (IcuDataFileNames == null || IcuDataFileNames.Length == 0)) + throw new LogAsErrorException($"{nameof(IcuDataFileNames)} property shouldn't be empty when {nameof(InvariantGlobalization)}=false"); if (Assemblies.Length == 0 && !IsSingleFileBundle) { From f0484dba04aa7a246b976ef0df2f48d100d43992 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:05:50 +0000 Subject: [PATCH 65/68] Test clean-up by @radical. --- .../wasm/Wasm.Build.Tests/IcuShardingTests.cs | 87 ++++++++++++------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 162d64e63e2c6a..19a4ac63434683 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -20,30 +20,34 @@ public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture // custom file contains only locales "cy-GB", "is-IS", "bs-BA", "lb-LU" and fallback locale: "en-US": private static string s_customIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat"); public record SundayNames { + public static string English = "Sunday"; public static string French = "dimanche"; public static string Spanish = "domingo"; public static string Chinese = "星期日"; public static string Japanese = "日曜日"; public static string Slovak = "nedeľa"; } + + private const string FallbackSundayNameEnUS = "Sunday"; + private static readonly string s_customIcuTestedLocales = $@"new Locale[] {{ new Locale(""cy-GB"", ""Dydd Sul""), new Locale(""is-IS"", ""sunnudagur""), new Locale(""bs-BA"", ""nedjelja""), new Locale(""lb-LU"", ""Sonndeg""), - new Locale(""fr-FR""), new Locale(""hr-HR""), new Locale(""ko-KR"") + new Locale(""fr-FR"", null), new Locale(""hr-HR"", null), new Locale(""ko-KR"", null) }}"; - private static string GetEfigsTestedLocales(string fallbackSundayName="Sunday") => $@"new Locale[] {{ - new Locale(""en-US""), new Locale(""fr-FR"", ""{SundayNames.French}""), new Locale(""es-ES"", ""{SundayNames.Spanish}""), + private static string GetEfigsTestedLocales(string fallbackSundayName=FallbackSundayNameEnUS) => $@"new Locale[] {{ + new Locale(""en-US"", ""{SundayNames.English}""), new Locale(""fr-FR"", ""{SundayNames.French}""), new Locale(""es-ES"", ""{SundayNames.Spanish}""), new Locale(""pl-PL"", ""{fallbackSundayName}""), new Locale(""ko-KR"", ""{fallbackSundayName}""), new Locale(""cs-CZ"", ""{fallbackSundayName}"") }}"; - private static string GetCjkTestedLocales(string fallbackSundayName="Sunday") => $@"new Locale[] {{ - new Locale(""en-GB""), new Locale(""zh-CN"", ""{SundayNames.Chinese}""), new Locale(""ja-JP"", ""{SundayNames.Japanese}""), + private static string GetCjkTestedLocales(string fallbackSundayName=FallbackSundayNameEnUS) => $@"new Locale[] {{ + new Locale(""en-GB"", ""{SundayNames.English}""), new Locale(""zh-CN"", ""{SundayNames.Chinese}""), new Locale(""ja-JP"", ""{SundayNames.Japanese}""), new Locale(""fr-FR"", ""{fallbackSundayName}""), new Locale(""hr-HR"", ""{fallbackSundayName}""), new Locale(""it-IT"", ""{fallbackSundayName}"") }}"; - private static string GetNocjkTestedLocales(string fallbackSundayName="Sunday") => $@"new Locale[] {{ - new Locale(""en-AU""), new Locale(""fr-FR"", ""{SundayNames.French}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), + private static string GetNocjkTestedLocales(string fallbackSundayName=FallbackSundayNameEnUS) => $@"new Locale[] {{ + new Locale(""en-AU"", ""{SundayNames.English}""), new Locale(""fr-FR"", ""{SundayNames.French}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), new Locale(""ja-JP"", ""{fallbackSundayName}""), new Locale(""ko-KR"", ""{fallbackSundayName}""), new Locale(""zh-CN"", ""{fallbackSundayName}"") }}"; private static readonly string s_fullIcuTestedLocales = $@"new Locale[] {{ - new Locale(""en-GB""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), new Locale(""zh-CN"", ""{SundayNames.Chinese}"") + new Locale(""en-GB"", ""{SundayNames.English}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), new Locale(""zh-CN"", ""{SundayNames.Chinese}"") }}"; public static IEnumerable IcuExpectedAndMissingCustomShardTestData(bool aot, RunHost host) @@ -59,8 +63,8 @@ private static string GetNocjkTestedLocales(string fallbackSundayName="Sunday") .Multiply( new object[] { "icudt.dat", $@"new Locale[] {{ - new Locale(""en-GB""), new Locale(""zh-CN"", ""{SundayNames.Chinese}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), - new Locale(""xx-yy"") }}" }, + new Locale(""en-GB"", ""{SundayNames.English}""), new Locale(""zh-CN"", ""{SundayNames.Chinese}""), new Locale(""sk-SK"", ""{SundayNames.Slovak}""), + new Locale(""xx-yy"", null) }}" }, new object[] { "icudt_EFIGS.dat", GetEfigsTestedLocales() }, new object[] { "icudt_CJK.dat", GetCjkTestedLocales() }, new object[] { "icudt_no_CJK.dat", GetNocjkTestedLocales() }) @@ -95,41 +99,56 @@ private static string GetNocjkTestedLocales(string fallbackSundayName="Sunday") .WithRunHosts(host) .UnwrapItemsAsArrays(); - private static string GetProgramText(string testedLocales, bool onlyPredefinedCultures=false) => $@" + private static string GetProgramText(string testedLocales, bool onlyPredefinedCultures=false, string fallbackSundayName=FallbackSundayNameEnUS) => $@" + #nullable enable + using System; using System.Globalization; - bool onlyPredefinedCultures = {onlyPredefinedCultures.ToString().ToLower()}; - Locale[] locales = {testedLocales}; - foreach (var loc in locales) + Console.WriteLine($""Current culture: '{{CultureInfo.CurrentCulture.Name}}'""); + + string fallbackSundayName = ""{fallbackSundayName}""; + bool onlyPredefinedCultures = {(onlyPredefinedCultures ? "true" : "false")}; + Locale[] localesToTest = {testedLocales}; + + bool fail = false; + foreach (var testLocale in localesToTest) {{ + bool expectMissing = string.IsNullOrEmpty(testLocale.SundayName); + bool ctorShouldFail = expectMissing && onlyPredefinedCultures; + CultureInfo culture; + try {{ - var culture = new CultureInfo(loc.Code); - var localizedSundayName = culture.DateTimeFormat.GetDayName(new DateTime(2000,01,02).DayOfWeek); - if (loc.SundayName != localizedSundayName) + culture = new CultureInfo(testLocale.Code); + if (ctorShouldFail) {{ - Console.WriteLine($""Error: incorrect localized value for Sunday in locale {{loc.Code}}. Expected {{loc.SundayName}} but got {{localizedSundayName}}.""); - return -1; + Console.WriteLine($""CultureInfo..ctor did not throw an exception for {{testLocale.Code}} as was expected.""); + fail = true; + continue; }} }} - catch(Exception ex) + catch(CultureNotFoundException cnfe) when (ctorShouldFail && cnfe.Message.Contains($""{{testLocale.Code}} is an invalid culture identifier."")) {{ - if (!onlyPredefinedCultures) - {{ - Console.WriteLine($""Error: for locale {{loc.Code}} encountered an exception. This is expected only when OnlyPredefinedCultures=true. Ex: {{ex}}.""); - return -1; - }} - if (!ex.Message.Contains(""Culture is not supported"")) - {{ - Console.WriteLine($""Error: for locale {{loc.Code}} encountered an exception from different reason than expected. Ex: {{ex}}.""); - return -1; - }} + Console.WriteLine($""{{testLocale.Code}}: Success. .ctor failed as expected.""); + continue; + }} + + string expectedSundayName = (expectMissing && !onlyPredefinedCultures) + ? fallbackSundayName + : testLocale.SundayName; + var actualLocalizedSundayName = culture.DateTimeFormat.GetDayName(new DateTime(2000,01,02).DayOfWeek); + if (expectedSundayName != actualLocalizedSundayName) + {{ + Console.WriteLine($""Error: incorrect localized value for Sunday in locale {{testLocale.Code}}. Expected '{{expectedSundayName}}' but got '{{actualLocalizedSundayName}}'.""); + fail = true; + continue; }} + Console.WriteLine($""{{testLocale.Code}}: Success. Sunday name: {{actualLocalizedSundayName}}""); }} - return 42; + return fail ? -1 : 42; - public record Locale(string Code, string SundayName=""Sunday""); + public record Locale(string Code, string? SundayName); "; private void TestIcuShards(BuildArgs buildArgs, string shardName, string testedLocales, RunHost host, string id, bool onlyPredefinedCultures=false) @@ -144,6 +163,7 @@ private void TestIcuShards(BuildArgs buildArgs, string shardName, string testedL buildArgs = ExpandBuildArgs(buildArgs, extraProperties: extraProperties); string programText = GetProgramText(testedLocales, onlyPredefinedCultures); + _testOutput.WriteLine($"----- Program: -----{Environment.NewLine}{programText}{Environment.NewLine}-------"); (_, string output) = BuildProject(buildArgs, id: id, new BuildProjectOptions( @@ -179,6 +199,7 @@ public void AutomaticShardSelectionDependingOnEnvLocale(BuildArgs buildArgs, str buildArgs = ExpandBuildArgs(buildArgs); string programText = GetProgramText(testedLocales); + _testOutput.WriteLine($"----- Program: -----{Environment.NewLine}{programText}{Environment.NewLine}-------"); (_, string output) = BuildProject(buildArgs, id: id, new BuildProjectOptions( @@ -199,6 +220,7 @@ public void FullIcuFromRuntimePackWithInvariant(BuildArgs buildArgs, bool invari buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"{invariant}{fullIcu}"); string programText = GetProgramText(testedLocales); + _testOutput.WriteLine($"----- Program: -----{Environment.NewLine}{programText}{Environment.NewLine}-------"); (_, string output) = BuildProject(buildArgs, id: id, new BuildProjectOptions( @@ -222,6 +244,7 @@ public void FullIcuFromRuntimePackWithCustomIcu(BuildArgs buildArgs, bool fullIc string testedLocales = fullIcu ? s_fullIcuTestedLocales : s_customIcuTestedLocales; string programText = GetProgramText(testedLocales); + _testOutput.WriteLine($"----- Program: -----{Environment.NewLine}{programText}{Environment.NewLine}-------"); (_, string output) = BuildProject(buildArgs, id: id, new BuildProjectOptions( From a9a01676b5eda5d8a494dacb4f76003181f5e65c Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:13:15 +0000 Subject: [PATCH 66/68] Fix WASI to treat ICU like WASM. --- src/mono/wasi/build/WasiApp.targets | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets index 0688e932d0e164..4bde8cd1570fd2 100644 --- a/src/mono/wasi/build/WasiApp.targets +++ b/src/mono/wasi/build/WasiApp.targets @@ -60,6 +60,8 @@ - $(WasmAotProfilePath) - Path to an AOT profile file. - $(WasmEnableExceptionHandling) - Enable support for the WASM Exception Handling feature. - $(WasmEnableSIMD) - Enable support for the WASM SIMD feature. + - $(WasmIncludeFullIcuData) - Loads full ICU data (icudt.dat). Defaults to false. Only applicable when InvariantGlobalization=false. + - $(WasmIcuDataFileName) - Name/path of ICU globalization file loaded to app. Only when InvariantGloblization=false and WasmIncludeFullIcuData=false. Public items: - @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir). @@ -319,27 +321,36 @@ - icudt.dat - <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true + <_WasmIcuDataFileName Condition="'$(WasmIcuDataFileName)' != '' and Exists('$(WasmIcuDataFileName)')">$(WasmIcuDataFileName) + <_WasmIcuDataFileName Condition="'$(WasmIcuDataFileName)' != '' and !Exists('$(WasmIcuDataFileName)')">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(WasmIcuDataFileName) - - - + + + <_IcuAvailableDataFiles Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)icudt_*" /> + + + + + + DependsOnTargets="_WasmGenerateRuntimeConfig;_GetWasiGenerateAppBundleDependencies;_WasiDefaultGenerateAppBundle;_GenerateRunWasmtimeScript"> + + + + @@ -353,7 +364,7 @@ DefaultHostConfig="$(DefaultWasmHostConfig)" InvariantGlobalization="$(InvariantGlobalization)" SatelliteAssemblies="@(_WasmSatelliteAssemblies)" - IcuDataFileName="$(WasmIcuDataFileName)" + IcuDataFileNames="$(WasmIcuDataFileNames)" ExtraFilesToDeploy="@(WasmExtraFilesToDeploy)" NativeAssets="@(WasmNativeAsset)" DebugLevel="$(WasmDebugLevel)" From 839bb773a58ac2732233b6c18305c1908f7c48e7 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:03:37 +0000 Subject: [PATCH 67/68] Fix arg order in debugger + revert wasi sharding. --- src/mono/wasi/build/WasiApp.targets | 11 +---------- .../debugger/DebuggerTestSuite/DebuggerTestBase.cs | 6 +++--- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets index 4bde8cd1570fd2..1c98ee06201d27 100644 --- a/src/mono/wasi/build/WasiApp.targets +++ b/src/mono/wasi/build/WasiApp.targets @@ -60,8 +60,6 @@ - $(WasmAotProfilePath) - Path to an AOT profile file. - $(WasmEnableExceptionHandling) - Enable support for the WASM Exception Handling feature. - $(WasmEnableSIMD) - Enable support for the WASM SIMD feature. - - $(WasmIncludeFullIcuData) - Loads full ICU data (icudt.dat). Defaults to false. Only applicable when InvariantGlobalization=false. - - $(WasmIcuDataFileName) - Name/path of ICU globalization file loaded to app. Only when InvariantGloblization=false and WasmIncludeFullIcuData=false. Public items: - @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir). @@ -322,8 +320,6 @@ <_HasDotnetWasm Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.wasm'">true - <_WasmIcuDataFileName Condition="'$(WasmIcuDataFileName)' != '' and Exists('$(WasmIcuDataFileName)')">$(WasmIcuDataFileName) - <_WasmIcuDataFileName Condition="'$(WasmIcuDataFileName)' != '' and !Exists('$(WasmIcuDataFileName)')">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(WasmIcuDataFileName) @@ -334,10 +330,7 @@ - <_IcuAvailableDataFiles Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)icudt_*" /> - - - + @@ -347,8 +340,6 @@ Outputs="$(WasmAppDir)\.stamp" Condition="'$(WasmGenerateAppBundle)' == 'true'" DependsOnTargets="_WasmGenerateRuntimeConfig;_GetWasiGenerateAppBundleDependencies;_WasiDefaultGenerateAppBundle;_GenerateRunWasmtimeScript"> - - diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 0c9686d200aece..225fcabff37f6c 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -24,8 +24,8 @@ public class DebuggerTests : DebuggerTestFirefox #endif { - public DebuggerTests(ITestOutputHelper testOutput, string driver = "debugger-driver.html", string locale = "en-US") - : base(testOutput, driver, locale) + public DebuggerTests(ITestOutputHelper testOutput, string locale = "en-US", string driver = "debugger-driver.html") + : base(testOutput, locale, driver) {} } @@ -130,7 +130,7 @@ static DebuggerTestBase() Directory.Delete(TempPath, recursive: true); } - public DebuggerTestBase(ITestOutputHelper testOutput, string driver = "debugger-driver.html", string locale) + public DebuggerTestBase(ITestOutputHelper testOutput, string locale, string driver = "debugger-driver.html") { _env = new TestEnvironment(testOutput); _testOutput = testOutput; From f0802221c582e5f54ab6257b0a3a63f6d0e410d2 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:44:38 +0000 Subject: [PATCH 68/68] It's a list, not a prop. --- src/mono/wasi/build/WasiApp.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets index 1c98ee06201d27..56a05ab2506778 100644 --- a/src/mono/wasi/build/WasiApp.targets +++ b/src/mono/wasi/build/WasiApp.targets @@ -355,7 +355,7 @@ DefaultHostConfig="$(DefaultWasmHostConfig)" InvariantGlobalization="$(InvariantGlobalization)" SatelliteAssemblies="@(_WasmSatelliteAssemblies)" - IcuDataFileNames="$(WasmIcuDataFileNames)" + IcuDataFileNames="@(WasmIcuDataFileNames)" ExtraFilesToDeploy="@(WasmExtraFilesToDeploy)" NativeAssets="@(WasmNativeAsset)" DebugLevel="$(WasmDebugLevel)"