diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index 1f8b21d6eca66..8df77ea901da4 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -3,23 +3,21 @@ Wasm.Build.NativeRebuild.Tests.NoopNativeRebuildTest Wasm.Build.NativeRebuild.Tests.OptimizationFlagChangeTests Wasm.Build.NativeRebuild.Tests.ReferenceNewAssemblyRebuildTest Wasm.Build.NativeRebuild.Tests.SimpleSourceChangeRebuildTest -Wasm.Build.Tests.TestAppScenarios.InterpPgoTests +Wasm.Build.Tests.InterpPgoTests Wasm.Build.Templates.Tests.NativeBuildTests Wasm.Build.Tests.Blazor.AppsettingsTests Wasm.Build.Tests.Blazor.BuildPublishTests Wasm.Build.Tests.Blazor.SimpleRunTests Wasm.Build.Tests.Blazor.CleanTests Wasm.Build.Tests.Blazor.MiscTests -Wasm.Build.Tests.Blazor.MiscTests2 -Wasm.Build.Tests.Blazor.MiscTests3 +Wasm.Build.Tests.Blazor.DllImportTests Wasm.Build.Tests.Blazor.NativeTests Wasm.Build.Tests.Blazor.NoopNativeRebuildTest Wasm.Build.Tests.Blazor.WorkloadRequiredTests -Wasm.Build.Tests.Blazor.IcuTests -Wasm.Build.Tests.Blazor.IcuShardingTests Wasm.Build.Tests.Blazor.SignalRClientTests Wasm.Build.Tests.BuildPublishTests Wasm.Build.Tests.ConfigSrcTests +Wasm.Build.Tests.DllImportTests Wasm.Build.Tests.IcuShardingTests Wasm.Build.Tests.IcuShardingTests2 Wasm.Build.Tests.IcuTests @@ -32,13 +30,13 @@ Wasm.Build.Tests.NonWasmTemplateBuildTests Wasm.Build.Tests.PInvokeTableGeneratorTests Wasm.Build.Tests.RebuildTests Wasm.Build.Tests.SatelliteAssembliesTests -Wasm.Build.Tests.TestAppScenarios.AppSettingsTests -Wasm.Build.Tests.TestAppScenarios.DownloadThenInitTests -Wasm.Build.Tests.TestAppScenarios.LazyLoadingTests -Wasm.Build.Tests.TestAppScenarios.LibraryInitializerTests -Wasm.Build.Tests.TestAppScenarios.SatelliteLoadingTests -Wasm.Build.Tests.TestAppScenarios.ModuleConfigTests -Wasm.Build.Tests.TestAppScenarios.MemoryTests +Wasm.Build.Tests.AppSettingsTests +Wasm.Build.Tests.DownloadThenInitTests +Wasm.Build.Tests.LazyLoadingTests +Wasm.Build.Tests.LibraryInitializerTests +Wasm.Build.Tests.SatelliteLoadingTests +Wasm.Build.Tests.ModuleConfigTests +Wasm.Build.Tests.MemoryTests Wasm.Build.Tests.AspNetCore.SignalRClientTests Wasm.Build.Tests.WasmBuildAppTest Wasm.Build.Tests.WasmNativeDefaultsTests diff --git a/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs b/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs index 76bc6fed455be..c05c99d72c8c9 100644 --- a/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs @@ -758,6 +758,13 @@ public record BuildProjectOptions string? TargetFramework = null, IDictionary? ExtraBuildEnvironmentVariables = null ); + + public record AssertBundleOptions( + BuildProjectOptions BuildOptions, + bool ExpectSymbolsFile = true, + bool AssertIcuAssets = true, + bool AssertSymbolsFile = true + ); public enum NativeFilesType { FromRuntimePack, Relinked, AOT }; } diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/SharedBuildPerTestClassFixture.cs b/src/mono/wasi/Wasi.Build.Tests/SharedBuildPerTestClassFixture.cs similarity index 95% rename from src/mono/wasm/Wasm.Build.Tests/Common/SharedBuildPerTestClassFixture.cs rename to src/mono/wasi/Wasi.Build.Tests/SharedBuildPerTestClassFixture.cs index 019391f3efaad..a89fc21946b52 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/SharedBuildPerTestClassFixture.cs +++ b/src/mono/wasi/Wasi.Build.Tests/SharedBuildPerTestClassFixture.cs @@ -9,6 +9,7 @@ #nullable enable +// ToDo: should be common with Wasm.Build.Tests, copied here after Wasm.Build.Tests refactoring namespace Wasm.Build.Tests { public class SharedBuildPerTestClassFixture : IDisposable diff --git a/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs new file mode 100644 index 0000000000000..49eff5165bf0f --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests; + +public class AppSettingsTests : WasmTemplateTestsBase +{ + public AppSettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Theory] + [InlineData("Development")] + [InlineData("Production")] + public async Task LoadAppSettingsBasedOnApplicationEnvironment(string applicationEnvironment) + { + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.WasmBasicTestApp, "AppSettingsTest"); + PublishProject(info, config); + BrowserRunOptions options = new( + config, + TestScenario: "AppSettingsTest", + BrowserQueryString: new NameValueCollection { { "applicationEnvironment", applicationEnvironment } } + ); + RunResult result = await RunForPublishWithWebServer(options); + Assert.Contains(result.TestOutput, m => m.Contains("'/appsettings.json' exists 'True'")); + Assert.Contains(result.TestOutput, m => m.Contains($"'/appsettings.Development.json' exists '{applicationEnvironment == "Development"}'")); + Assert.Contains(result.TestOutput, m => m.Contains($"'/appsettings.Production.json' exists '{applicationEnvironment == "Production"}'")); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs b/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs index 2675e253bcd54..30f3073de9af2 100644 --- a/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs @@ -19,10 +19,10 @@ public SignalRClientTests(ITestOutputHelper output, SharedBuildPerTestClassFixtu [ActiveIssue("https://github.com/dotnet/runtime/issues/106807")] [ConditionalTheory(typeof(BuildTestBase), nameof(IsWorkloadWithMultiThreadingForDefaultFramework))] - [InlineData("Debug", "LongPolling")] - [InlineData("Release", "LongPolling")] - [InlineData("Debug", "WebSockets")] - [InlineData("Release", "WebSockets")] - public async Task SignalRPassMessageWasmBrowser(string config, string transport) => + [InlineData(Configuration.Debug, "LongPolling")] + [InlineData(Configuration.Release, "LongPolling")] + [InlineData(Configuration.Debug, "WebSockets")] + [InlineData(Configuration.Release, "WebSockets")] + public async Task SignalRPassMessageWasmBrowser(Configuration config, string transport) => await SignalRPassMessage("wasmclient", config, transport); } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs index db0607d226a8c..fa47fc4b45871 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.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.Collections.Generic; using System.IO; using System.Threading.Tasks; using Xunit; @@ -21,39 +22,34 @@ public AppsettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [Fact] public async Task FileInVfs() { - string id = $"blazor_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - - string projectDirectory = Path.GetDirectoryName(projectFile)!; - - File.WriteAllText(Path.Combine(projectDirectory, "wwwroot", "appsettings.json"), $"{{ \"Id\": \"{id}\" }}"); - - string programPath = Path.Combine(projectDirectory, "Program.cs"); - string programContent = File.ReadAllText(programPath); - programContent = programContent.Replace("var builder", - """ - System.Console.WriteLine($"appSettings Exists '{File.Exists("/appsettings.json")}'"); - System.Console.WriteLine($"appSettings Content '{File.ReadAllText("/appsettings.json")}'"); - var builder - """); - File.WriteAllText(programPath, programContent); - - BlazorBuild(new BlazorBuildOptions(id, "debug", NativeFilesType.FromRuntimePack)); + Configuration config = Configuration.Debug; + ProjectInfo info = CreateWasmTemplateProject(Template.BlazorWasm, config, aot: false, "blazor"); + UpdateHomePage(); + string projectDirectory = Path.GetDirectoryName(info.ProjectFilePath)!; + File.WriteAllText(Path.Combine(projectDirectory, "wwwroot", "appsettings.json"), $"{{ \"Id\": \"{info.ProjectName}\" }}"); + UpdateFile("Program.cs", new Dictionary + { + { + "var builder", + """ + System.Console.WriteLine($"appSettings Exists '{File.Exists("/appsettings.json")}'"); + System.Console.WriteLine($"appSettings Content '{File.ReadAllText("/appsettings.json")}'"); + var builder + """ + } + }); + (string _, string buildOutput) = BlazorBuild(info, config); bool existsChecked = false; bool contentChecked = false; - - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() - { - Config = "debug", - OnConsoleMessage = (_, msg) => - { - if (msg.Text.Contains("appSettings Exists 'True'")) + await RunForBuildWithDotnetRun(new BlazorRunOptions( + config, + OnConsoleMessage: (_, msg) => { + if (msg.Contains("appSettings Exists 'True'")) existsChecked = true; - else if (msg.Text.Contains($"appSettings Content '{{ \"Id\": \"{id}\" }}'")) + else if (msg.Contains($"appSettings Content '{{ \"Id\": \"{info.ProjectName}\" }}'")) contentChecked = true; - } - }); + })); Assert.True(existsChecked, "File '/appsettings.json' wasn't found"); Assert.True(contentChecked, "Content of '/appsettings.json' is not matched"); diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorBuildOptions.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorBuildOptions.cs deleted file mode 100644 index cebbcae891f38..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorBuildOptions.cs +++ /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. - -#nullable enable - -namespace Wasm.Build.Tests; - -public record BlazorBuildOptions -( - string Id, - string Config, - NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, - string TargetFramework = BuildTestBase.DefaultTargetFrameworkForBlazor, - string BootConfigFileName = "blazor.boot.json", - bool IsPublish = false, - bool WarnAsError = true, - bool ExpectSuccess = true, - bool ExpectRelinkDirWhenPublishing = false, - bool ExpectFingerprintOnDotnetJs = false, - RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string CustomIcuFile = "", - bool AssertAppBundle = true, - string? BinFrameworkDir = null -); diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorRunOptions.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorRunOptions.cs index c0e2a2e60cce0..628227edc7c85 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorRunOptions.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorRunOptions.cs @@ -3,26 +3,56 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Threading.Tasks; using Microsoft.Playwright; #nullable enable -namespace Wasm.Build.Tests.Blazor; -public record BlazorRunOptions -( - BlazorRunHost Host = BlazorRunHost.DotnetRun, - bool DetectRuntimeFailures = true, - bool CheckCounter = true, - Dictionary? ServerEnvironment = null, - Func? Test = null, - Action? OnConsoleMessage = null, - Action? OnServerMessage = null, - Action? OnErrorMessage = null, - string Config = "Debug", - string? ExtraArgs = null, - string BrowserPath = "", - string QueryString = "" -); +namespace Wasm.Build.Tests; +public record BlazorRunOptions : RunOptions +{ + public bool CheckCounter { get; init; } + public Func? Test { get; init; } + + public BlazorRunOptions( + Configuration Configuration, + bool AOT = false, + RunHost Host = RunHost.DotnetRun, + bool DetectRuntimeFailures = true, + Dictionary? ServerEnvironment = null, + NameValueCollection? BrowserQueryString = null, + Action? OnConsoleMessage = null, + Action? OnServerMessage = null, + Action? OnErrorMessage = null, + string ExtraArgs = "", + string BrowserPath = "", + string Locale = "en-US", + int? ExpectedExitCode = 0, + string CustomBundleDir = "", + bool CheckCounter = true, + Func? Test = null, + Func? ExecuteAfterLoaded = null + ) : base( + Configuration, + AOT, + Host, + DetectRuntimeFailures, + ServerEnvironment, + BrowserQueryString, + OnConsoleMessage, + OnServerMessage, + OnErrorMessage, + ExtraArgs, + BrowserPath, + Locale, + ExpectedExitCode, + CustomBundleDir, + ExecuteAfterLoaded + ) + { + this.CheckCounter = CheckCounter; + this.Test = Test; + } +} -public enum BlazorRunHost { DotnetRun, WebServer }; diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmProjectProvider.cs deleted file mode 100644 index f715ece03bd55..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmProjectProvider.cs +++ /dev/null @@ -1,32 +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.IO; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests; - -public class BlazorWasmProjectProvider : WasmSdkBasedProjectProvider -{ - public BlazorWasmProjectProvider(ITestOutputHelper _testOutput, string defaultTargetFramework, string? _projectDir = null) - : base(_testOutput, defaultTargetFramework, _projectDir) - { } - - public void AssertBundle(BlazorBuildOptions options) - => AssertBundle(new AssertWasmSdkBundleOptions( - Config: options.Config, - BootConfigFileName: options.BootConfigFileName, - IsPublish: options.IsPublish, - TargetFramework: options.TargetFramework, - BinFrameworkDir: options.BinFrameworkDir ?? FindBinFrameworkDir(options.Config, options.IsPublish, options.TargetFramework), - GlobalizationMode: options.GlobalizationMode, - CustomIcuFile: options.CustomIcuFile, - ExpectFingerprintOnDotnetJs: options.ExpectFingerprintOnDotnetJs, - ExpectedFileType: options.ExpectedFileType, - RuntimeType: options.RuntimeType, - AssertIcuAssets: true, - AssertSymbolsFile: false // FIXME: not supported yet - )); -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs index 75769e5da3cbc..060b58639cb34 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -17,13 +18,55 @@ namespace Wasm.Build.Tests; public abstract class BlazorWasmTestBase : WasmTemplateTestsBase { - protected readonly BlazorWasmProjectProvider _provider; + protected readonly WasmSdkBasedProjectProvider _provider; + private readonly string _blazorExtraBuildArgs = "-p:BlazorEnableCompression=false /warnaserror"; + protected readonly PublishOptions _defaultBlazorPublishOptions; + private readonly BuildOptions _defaultBlazorBuildOptions; + protected BlazorWasmTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext, new BlazorWasmProjectProvider(output, DefaultTargetFrameworkForBlazor)) + : base(output, buildContext, new WasmSdkBasedProjectProvider(output, DefaultTargetFrameworkForBlazor)) { - _provider = GetProvider(); + _provider = GetProvider(); + _defaultBlazorPublishOptions = _defaultPublishOptions with { ExtraMSBuildArgs = _blazorExtraBuildArgs }; + _defaultBlazorBuildOptions = _defaultBuildOptions with { ExtraMSBuildArgs = _blazorExtraBuildArgs }; } + private Dictionary blazorHomePageReplacements = new Dictionary + { + { + "Welcome to your new app.", + """ + Welcome to your new app. + @code { + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { + Console.WriteLine("WASM EXIT 0"); + } + } + } + """ } + }; + + private Func? _executeAfterLoaded = async (runOptions, page) => + { + if (runOptions is BlazorRunOptions bro && bro.CheckCounter) + { + await page.Locator("text=Counter").ClickAsync(); + var txt = await page.Locator("p[role='status']").InnerHTMLAsync(); + Assert.Equal("Current count: 0", txt); + + await page.Locator("text=\"Click me\"").ClickAsync(); + await Task.Delay(300); + txt = await page.Locator("p[role='status']").InnerHTMLAsync(); + Assert.Equal("Current count: 1", txt); + } + }; + + protected void UpdateHomePage() => + UpdateFile(Path.Combine("Pages", "Home.razor"), blazorHomePageReplacements); + public void InitBlazorWasmProjectDir(string id, string targetFramework = DefaultTargetFrameworkForBlazor) { InitPaths(id); @@ -46,92 +89,82 @@ public string CreateBlazorWasmTemplateProject(string id) { InitBlazorWasmProjectDir(id); using DotNetCommand dotnetCommand = new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false); - CommandResult result = dotnetCommand.WithWorkingDirectory(_projectDir!) + CommandResult result = dotnetCommand.WithWorkingDirectory(_projectDir) .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) .ExecuteWithCapturedOutput("new blazorwasm") .EnsureSuccessful(); - return Path.Combine(_projectDir!, $"{id}.csproj"); + return Path.Combine(_projectDir, $"{id}.csproj"); } - protected (CommandResult, string) BlazorBuild(BlazorBuildOptions options, params string[] extraArgs) - { - if (options.WarnAsError) - extraArgs = extraArgs.Append("/warnaserror").ToArray(); - - (CommandResult res, string logPath) = BlazorBuildInternal(options.Id, options.Config, publish: false, setWasmDevel: false, expectSuccess: options.ExpectSuccess, extraArgs); + protected (string projectDir, string buildOutput) BlazorBuild(ProjectInfo info, Configuration config, bool? isNativeBuild = null) => + BlazorBuild(info, config, _defaultBlazorBuildOptions, isNativeBuild); - if (options.ExpectSuccess && options.AssertAppBundle) + protected (string projectDir, string buildOutput) BlazorBuild( + ProjectInfo info, Configuration config, MSBuildOptions buildOptions, bool? isNativeBuild = null) + { + try { - AssertBundle(res.Output, options with { IsPublish = false }); + if (buildOptions != _defaultBlazorPublishOptions) + buildOptions = buildOptions with { ExtraMSBuildArgs = $"{buildOptions.ExtraMSBuildArgs} {_blazorExtraBuildArgs}" }; + (string projectDir, string buildOutput) = BuildProject( + info, + config, + buildOptions, + isNativeBuild); + if (buildOptions.ExpectSuccess && buildOptions.AssertAppBundle) + { + // additional blazor-only assert, basic assert is done in BuildProject + AssertBundle(config, buildOutput, buildOptions, isNativeBuild); + } + return (projectDir, buildOutput); } - - return (res, logPath); - } - - protected (CommandResult, string) BlazorPublish(BlazorBuildOptions options, params string[] extraArgs) - { - if (options.WarnAsError) - extraArgs = extraArgs.Append("/warnaserror").ToArray(); - - (CommandResult res, string logPath) = BlazorBuildInternal(options.Id, options.Config, publish: true, setWasmDevel: false, expectSuccess: options.ExpectSuccess, extraArgs); - - if (options.ExpectSuccess && options.AssertAppBundle) + catch (XunitException xe) { - // Because we do relink in Release publish by default - if (options.Config == "Release") - options = options with { ExpectedFileType = NativeFilesType.Relinked }; - - AssertBundle(res.Output, options with { IsPublish = true }); + if (xe.Message.Contains("error CS1001: Identifier expected")) + Utils.DirectoryCopy(_projectDir, _logPath, testOutput: _testOutput); + throw; } - - return (res, logPath); } + + protected (string projectDir, string buildOutput) BlazorPublish(ProjectInfo info, Configuration config, bool? isNativeBuild = null) => + BlazorPublish(info, config, _defaultBlazorPublishOptions, isNativeBuild); - protected (CommandResult res, string logPath) BlazorBuildInternal( - string id, - string config, - bool publish = false, - bool setWasmDevel = true, - bool expectSuccess = true, - params string[] extraArgs) + protected (string projectDir, string buildOutput) BlazorPublish( + ProjectInfo info, Configuration config, PublishOptions publishOptions, bool? isNativeBuild = null) { try { - return BuildProjectWithoutAssert( - id, - config, - new BuildProjectOptions(CreateProject: false, UseCache: false, Publish: publish, ExpectSuccess: expectSuccess), - extraArgs.Concat(new[] - { - "-p:BlazorEnableCompression=false", - setWasmDevel ? "-p:_WasmDevel=true" : string.Empty - }).ToArray()); + if (publishOptions != _defaultBlazorPublishOptions) + publishOptions = publishOptions with { ExtraMSBuildArgs = $"{publishOptions.ExtraMSBuildArgs} {_blazorExtraBuildArgs}" }; + (string projectDir, string buildOutput) = PublishProject( + info, + config, + publishOptions, + isNativeBuild); + if (publishOptions.ExpectSuccess && publishOptions.AssertAppBundle) + { + // additional blazor-only assert, basic assert is done in PublishProject + AssertBundle(config, buildOutput, publishOptions, isNativeBuild); + } + return (projectDir, buildOutput); } catch (XunitException xe) { if (xe.Message.Contains("error CS1001: Identifier expected")) - Utils.DirectoryCopy(_projectDir!, Path.Combine(s_buildEnv.LogRootPath, id), testOutput: _testOutput); + Utils.DirectoryCopy(_projectDir, _logPath, testOutput: _testOutput); throw; } } - public void AssertBundle(string buildOutput, BlazorBuildOptions blazorBuildOptions) + public void AssertBundle(Configuration config, string buildOutput, MSBuildOptions buildOptions, bool? isNativeBuild = null) { - if (IsUsingWorkloads) - { - // In no-workload case, the path would be from a restored nuget - ProjectProviderBase.AssertRuntimePackPath(buildOutput, blazorBuildOptions.TargetFramework ?? DefaultTargetFramework, blazorBuildOptions.RuntimeType); - } - - _provider.AssertBundle(blazorBuildOptions); - - if (!blazorBuildOptions.IsPublish) + if (!buildOptions.IsPublish) return; + var expectedFileType = _provider.GetExpectedFileType(config, buildOptions.AOT, buildOptions.IsPublish, IsUsingWorkloads, isNativeBuild); // Publish specific checks - - if (blazorBuildOptions.ExpectedFileType == NativeFilesType.AOT) + if (expectedFileType == NativeFilesType.AOT) { // check for this too, so we know the format is correct for the negative // test for jsinterop.webassembly.dll @@ -141,112 +174,52 @@ public void AssertBundle(string buildOutput, BlazorBuildOptions blazorBuildOptio Assert.DoesNotContain("Microsoft.JSInterop.WebAssembly.dll -> Microsoft.JSInterop.WebAssembly.dll.bc", buildOutput); } - string objBuildDir = Path.Combine(_projectDir!, "obj", blazorBuildOptions.Config, blazorBuildOptions.TargetFramework!, "wasm", "for-build"); + string objBuildDir = Path.Combine(_projectDir, "obj", config.ToString(), buildOptions.TargetFramework!, "wasm", "for-build"); // Check that we linked only for publish - if (blazorBuildOptions.ExpectRelinkDirWhenPublishing) + if (buildOptions is PublishOptions publishOptions && publishOptions.ExpectRelinkDirWhenPublishing) Assert.True(Directory.Exists(objBuildDir), $"Could not find expected {objBuildDir}, which gets created when relinking during Build. This is likely a test authoring error"); else Assert.False(File.Exists(Path.Combine(objBuildDir, "emcc-link.rsp")), $"Found unexpected `emcc-link.rsp` in {objBuildDir}, which gets created when relinking during Build."); } - protected string CreateProjectWithNativeReference(string id) + protected ProjectInfo CreateProjectWithNativeReference(Configuration config, bool aot, string extraProperties) { - CreateBlazorWasmTemplateProject(id); - string extraItems = @$" {GetSkiaSharpReferenceItems()} "; - string projectFile = Path.Combine(_projectDir!, $"{id}.csproj"); - AddItemsPropertiesToProject(projectFile, extraItems: extraItems); - - return projectFile; + return CopyTestAsset( + config, aot, TestAsset.BlazorBasicTestApp, "blz_nativeref_aot", extraItems: extraItems, extraProperties: extraProperties); } // Keeping these methods with explicit Build/Publish in the name // so in the test code it is evident which is being run! - public Task BlazorRunForBuildWithDotnetRun(BlazorRunOptions runOptions) - => BlazorRunTest(runOptions with { Host = BlazorRunHost.DotnetRun }); - - public Task BlazorRunForPublishWithWebServer(BlazorRunOptions runOptions) - => BlazorRunTest(runOptions with { Host = BlazorRunHost.WebServer }); - - public Task BlazorRunTest(BlazorRunOptions runOptions) => runOptions.Host switch + public override async Task RunForBuildWithDotnetRun(RunOptions runOptions) => + await base.RunForBuildWithDotnetRun(runOptions with { + ExecuteAfterLoaded = runOptions.ExecuteAfterLoaded ?? _executeAfterLoaded, + ServerEnvironment = GetServerEnvironmentForBuild(runOptions.ServerEnvironment) + }); + + public override async Task RunForPublishWithWebServer(RunOptions runOptions) + => await base.RunForPublishWithWebServer(runOptions with { + ExecuteAfterLoaded = runOptions.ExecuteAfterLoaded ?? _executeAfterLoaded + }); + + private Dictionary? GetServerEnvironmentForBuild(Dictionary? originalServerEnv) { - BlazorRunHost.DotnetRun => - BlazorRunTest($"run -c {runOptions.Config} --no-build", _projectDir!, runOptions), - - BlazorRunHost.WebServer => - BlazorRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files", - Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(runOptions.Config, forPublish: true), "..")), - runOptions), - - _ => throw new NotImplementedException(runOptions.Host.ToString()) - }; - - public async Task BlazorRunTest(string runArgs, - string workingDirectory, - BlazorRunOptions runOptions) - { - if (!string.IsNullOrEmpty(runOptions.ExtraArgs)) - runArgs += $" {runOptions.ExtraArgs}"; - - runOptions.ServerEnvironment?.ToList().ForEach( - kv => s_buildEnv.EnvVars[kv.Key] = kv.Value); - - using RunCommand runCommand = new RunCommand(s_buildEnv, _testOutput); - ToolCommand cmd = runCommand.WithWorkingDirectory(workingDirectory); - - await using var runner = new BrowserRunner(_testOutput); - var page = await runner.RunAsync( - cmd, - runArgs, - onConsoleMessage: OnConsoleMessage, - onServerMessage: runOptions.OnServerMessage, - onError: OnErrorMessage, - modifyBrowserUrl: browserUrl => new Uri(new Uri(browserUrl), runOptions.BrowserPath + runOptions.QueryString).ToString()); - - _testOutput.WriteLine("Waiting for page to load"); - await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded, new () { Timeout = 1 * 60 * 1000 }); - - if (runOptions.CheckCounter) + var serverEnvironment = new Dictionary(); + if (originalServerEnv != null) { - await page.Locator("text=Counter").ClickAsync(); - var txt = await page.Locator("p[role='status']").InnerHTMLAsync(); - Assert.Equal("Current count: 0", txt); - - await page.Locator("text=\"Click me\"").ClickAsync(); - await Task.Delay(300); - txt = await page.Locator("p[role='status']").InnerHTMLAsync(); - Assert.Equal("Current count: 1", txt); - } - - if (runOptions.Test is not null) - await runOptions.Test(page); - - _testOutput.WriteLine($"Waiting for additional 10secs to see if any errors are reported"); - await Task.Delay(10_000); - - void OnConsoleMessage(IPage page, IConsoleMessage msg) - { - _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); - - runOptions.OnConsoleMessage?.Invoke(page, msg); - - if (runOptions.DetectRuntimeFailures) + foreach (var kvp in originalServerEnv) { - if (msg.Text.Contains("[MONO] * Assertion") || msg.Text.Contains("Error: [MONO] ")) - throw new XunitException($"Detected a runtime failure at line: {msg.Text}"); + serverEnvironment.Add(kvp.Key, kvp.Value); } } - - void OnErrorMessage(string msg) - { - _testOutput.WriteLine($"[ERROR] {msg}"); - runOptions.OnErrorMessage?.Invoke(msg); - } + // avoid "System.IO.IOException: address already in use" + serverEnvironment.Add("ASPNETCORE_URLS", "http://127.0.0.1:0"); + return serverEnvironment; } - public string FindBlazorBinFrameworkDir(string config, bool forPublish, string framework = DefaultTargetFrameworkForBlazor, string? projectDir = null) - => _provider.FindBinFrameworkDir(config: config, forPublish: forPublish, framework: framework, projectDir: projectDir); + public string GetBlazorBinFrameworkDir(Configuration config, bool forPublish, string framework = DefaultTargetFrameworkForBlazor, string? projectDir = null) + => _provider.GetBinFrameworkDir(config: config, forPublish: forPublish, framework: framework, projectDir: projectDir); } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs index 5877b0f6a06dd..56364a1a09c39 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs @@ -23,150 +23,107 @@ public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur _enablePerTestCleanup = true; } - [Theory, TestCategory("no-workload")] - [InlineData("Debug")] - [InlineData("Release")] - public async Task DefaultTemplate_WithoutWorkload(string config) + public static TheoryData TestDataForDefaultTemplate_WithWorkload(bool isAot) { - string id = $"blz_no_workload_{config}_{GetRandomId()}_{s_unicodeChars}"; - CreateBlazorWasmTemplateProject(id); - - BlazorBuild(new BlazorBuildOptions(id, config)); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - - BlazorPublish(new BlazorBuildOptions(id, config)); - await BlazorRunForPublishWithWebServer(new BlazorRunOptions() { Config = config }); - } - - - public static TheoryData TestDataForDefaultTemplate_WithWorkload(bool isAot) - { - var data = new TheoryData(); + var data = new TheoryData(); if (!isAot) { // AOT does not support managed debugging, is disabled by design - data.Add("Debug", false); - data.Add("Debug", true); + data.Add(Configuration.Debug, false); + data.Add(Configuration.Debug, true); } // [ActiveIssue("https://github.com/dotnet/runtime/issues/103625", TestPlatforms.Windows)] // when running locally the path might be longer than 260 chars and these tests can fail with AOT - data.Add("Release", false); // Release relinks by default - data.Add("Release", true); + data.Add(Configuration.Release, false); // Release relinks by default + data.Add(Configuration.Release, true); return data; } [Theory] [MemberData(nameof(TestDataForDefaultTemplate_WithWorkload), parameters: new object[] { false })] - public void DefaultTemplate_NoAOT_WithWorkload(string config, bool testUnicode) + public void DefaultTemplate_NoAOT_WithWorkload(Configuration config, bool testUnicode) { - string id = testUnicode ? - $"blz_no_aot_{config}_{GetRandomId()}_{s_unicodeChars}" : - $"blz_no_aot_{config}_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - if (config == "Release") - { - // relinking in publish for Release config - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true)); - } - else - { - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack, ExpectRelinkDirWhenPublishing: true)); - } + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_no_aot", appendUnicodeToPath: testUnicode); + BlazorPublish(info, config); } [Theory] [MemberData(nameof(TestDataForDefaultTemplate_WithWorkload), parameters: new object[] { true })] - public void DefaultTemplate_AOT_WithWorkload(string config, bool testUnicode) + public void DefaultTemplate_AOT_WithWorkload(Configuration config, bool testUnicode) { - string id = testUnicode ? - $"blz_aot_{config}_{GetRandomId()}_{s_unicodeChars}" : - $"blz_aot_{config}_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_aot", appendUnicodeToPath: testUnicode); + BlazorBuild(info, config); - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT), "-p:RunAOTCompilation=true"); + PublishProject(info, config, new PublishOptions(AOT: true, UseCache: false)); } [Theory] - [InlineData("Debug", false)] - [InlineData("Release", false)] - [InlineData("Debug", true)] - [InlineData("Release", true)] - public void DefaultTemplate_CheckFingerprinting(string config, bool expectFingerprintOnDotnetJs) + [InlineData(Configuration.Debug, false)] + [InlineData(Configuration.Release, false)] + [InlineData(Configuration.Debug, true)] + [InlineData(Configuration.Release, true)] + public void DefaultTemplate_CheckFingerprinting(Configuration config, bool expectFingerprintOnDotnetJs) { - string id = $"blz_checkfingerprinting_{config}_{GetRandomId()}"; - - CreateBlazorWasmTemplateProject(id); - - var options = new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true, ExpectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs); - var finterprintingArg = expectFingerprintOnDotnetJs ? "/p:WasmFingerprintDotnetJs=true" : string.Empty; - - BlazorBuild(options, "/p:WasmBuildNative=true", finterprintingArg); - BlazorPublish(options, "/p:WasmBuildNative=true", finterprintingArg); + var extraProperty = expectFingerprintOnDotnetJs ? + "truetrue" : + "true"; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_checkfingerprinting", extraProperties: extraProperty); + BlazorBuild(info, config, isNativeBuild: true); + BlazorPublish(info, config, new PublishOptions(UseCache: false), isNativeBuild: true); } // Disabling for now - publish folder can have more than one dotnet*hash*js, and not sure // how to pick which one to check, for the test //[Theory] - //[InlineData("Debug")] - //[InlineData("Release")] - //public void DefaultTemplate_AOT_OnlyWithPublishCommandLine_Then_PublishNoAOT(string config) + //[InlineData(Configuration.Debug)] + //[InlineData(Configuration.Release)] + //public void DefaultTemplate_AOT_OnlyWithPublishCommandLine_Then_PublishNoAOT(Configuration config) //{ //string id = $"blz_aot_pub_{config}"; //CreateBlazorWasmTemplateProject(id); //// No relinking, no AOT - //BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack); + //BlazorBuild(new BuildOptions(id, config, NativeFilesType.FromRuntimePack); //// AOT=true only for the publish command line, similar to what //// would happen when setting it in Publish dialog for VS - //BlazorPublish(new BlazorBuildOptions(id, config, expectedFileType: NativeFilesType.AOT, "-p:RunAOTCompilation=true"); + //BlazorPublish(new BuildOptions(id, config, expectedFileType: NativeFilesType.AOT, "-p:RunAOTCompilation=true"); //// publish again, no AOT - //BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked); + //BlazorPublish(new BuildOptions(id, config, NativeFilesType.Relinked); //} [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void DefaultTemplate_WithResources_Publish(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void DefaultTemplate_WithResources_Publish(Configuration config) { string[] cultures = ["ja-JP", "es-ES"]; - string id = $"blz_resources_{config}_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_resources"); // Ensure we have the source data we rely on string resxSourcePath = Path.Combine(BuildEnvironment.TestAssetsPath, "resx"); foreach (string culture in cultures) Assert.True(File.Exists(Path.Combine(resxSourcePath, $"words.{culture}.resx"))); - Utils.DirectoryCopy(resxSourcePath, Path.Combine(_projectDir!, "resx")); + Utils.DirectoryCopy(resxSourcePath, Path.Combine(_projectDir, "resx")); // Build and assert resource dlls - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - AssertResourcesDlls(FindBlazorBinFrameworkDir(config, false)); + BlazorBuild(info, config); + AssertResourcesDlls(GetBlazorBinFrameworkDir(config, forPublish: false)); // Publish and assert resource dlls - if (config == "Release") - { - // relinking in publish for Release config - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true, IsPublish: true)); - } - else - { - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack, ExpectRelinkDirWhenPublishing: true, IsPublish: true)); - } - - AssertResourcesDlls(FindBlazorBinFrameworkDir(config, true)); + BlazorPublish(info, config, new PublishOptions(UseCache: false)); + AssertResourcesDlls(GetBlazorBinFrameworkDir(config, forPublish: true)); void AssertResourcesDlls(string basePath) { foreach (string culture in cultures) { - string? resourceAssemblyPath = Directory.EnumerateFiles(Path.Combine(basePath, culture), $"*{ProjectProviderBase.WasmAssemblyExtension}").SingleOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith($"{id}.resources")); + string? resourceAssemblyPath = Directory.EnumerateFiles( + Path.Combine(basePath, culture), + $"*{ProjectProviderBase.WasmAssemblyExtension}").SingleOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith($"{info.ProjectName}.resources")); Assert.True(resourceAssemblyPath != null && File.Exists(resourceAssemblyPath), $"Expects to have a resource assembly at {resourceAssemblyPath}"); } } @@ -177,36 +134,29 @@ void AssertResourcesDlls(string basePath) [InlineData("false", false)] // the other case public async Task Test_WasmStripILAfterAOT(string stripILAfterAOT, bool expectILStripping) { - string config = "Release"; - string id = $"blz_WasmStripILAfterAOT_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - string projectDirectory = Path.GetDirectoryName(projectFile)!; - + Configuration config = Configuration.Release; string extraProperties = "true"; if (!string.IsNullOrEmpty(stripILAfterAOT)) extraProperties += $"{stripILAfterAOT}"; - AddItemsPropertiesToProject(projectFile, extraProperties); + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blz_WasmStripILAfterAOT", extraProperties: extraProperties); - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT, AssertAppBundle : false)); - await BlazorRunForPublishWithWebServer(new BlazorRunOptions() { Config = config }); + BlazorPublish(info, config); + await RunForPublishWithWebServer(new BlazorRunOptions(config)); - string frameworkDir = Path.Combine(projectDirectory, "bin", config, BuildTestBase.DefaultTargetFrameworkForBlazor, "publish", "wwwroot", "_framework"); - string objBuildDir = Path.Combine(projectDirectory, "obj", config, BuildTestBase.DefaultTargetFrameworkForBlazor, "wasm", "for-publish"); + string frameworkDir = Path.Combine(_projectDir, "bin", config.ToString(), BuildTestBase.DefaultTargetFrameworkForBlazor, "publish", "wwwroot", "_framework"); + string objBuildDir = Path.Combine(_projectDir, "obj", config.ToString(), BuildTestBase.DefaultTargetFrameworkForBlazor, "wasm", "for-publish"); WasmTemplateTests.TestWasmStripILAfterAOTOutput(objBuildDir, frameworkDir, expectILStripping, _testOutput); } [Theory] - [InlineData("Debug")] - public void BlazorWasm_CannotAOT_InDebug(string config) + [InlineData(Configuration.Debug)] + public void BlazorWasm_CannotAOT_InDebug(Configuration config) { - string id = $"blazorwasm_{config}_aot_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"), - extraItems: null, - extraProperties: "true"); - - (CommandResult res, _) = BlazorPublish(new BlazorBuildOptions(id, config, ExpectSuccess: false)); - Assert.Contains("AOT is not supported in debug configuration", res.Output); + ProjectInfo info = CopyTestAsset( + config, aot: true, TestAsset.BlazorBasicTestApp, "blazorwasm", extraProperties: "true"); + + (string _, string output) = PublishProject(info, config, new PublishOptions(ExpectSuccess: false)); + Assert.Contains("AOT is not supported in debug configuration", output); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/CleanTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/CleanTests.cs index 122aa27496237..2d9a8baa260f7 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/CleanTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/CleanTests.cs @@ -21,27 +21,20 @@ public CleanTests(ITestOutputHelper output, SharedBuildPerTestClassFixture build } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void Blazor_BuildThenClean_NativeRelinking(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void Blazor_BuildThenClean_NativeRelinking(Configuration config) { - string id = GetRandomId(); + string extraProperties = @"<_WasmDevel>truetrue"; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "clean", extraProperties: extraProperties); + BlazorBuild(info, config, isNativeBuild: true); - InitBlazorWasmProjectDir(id); - string projectFile = CreateBlazorWasmTemplateProject(id); - - string extraProperties = @"<_WasmDevel>true - true"; - - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - - string relinkDir = Path.Combine(_projectDir!, "obj", config, DefaultTargetFrameworkForBlazor, "wasm", "for-build"); + string relinkDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFrameworkForBlazor, "wasm", "for-build"); Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}"); - string logPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-clean.binlog"); + string logPath = Path.Combine(s_buildEnv.LogRootPath, info.ProjectName, $"{info.ProjectName}-clean.binlog"); using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); + .WithWorkingDirectory(_projectDir); cmd.WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) .ExecuteWithCapturedOutput("build", "-t:Clean", $"-p:Configuration={config}", $"-bl:{logPath}") .EnsureSuccessful(); @@ -50,52 +43,51 @@ public void Blazor_BuildThenClean_NativeRelinking(string config) } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void Blazor_BuildNoNative_ThenBuildNative_ThenClean(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void Blazor_BuildNoNative_ThenBuildNative_ThenClean(Configuration config) => Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: false); [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void Blazor_BuildNative_ThenBuildNonNative_ThenClean(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void Blazor_BuildNative_ThenBuildNonNative_ThenClean(Configuration config) => Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: true); - private void Blazor_BuildNativeNonNative_ThenCleanTest(string config, bool firstBuildNative) + private void Blazor_BuildNativeNonNative_ThenCleanTest(Configuration config, bool firstBuildNative) { - string id = GetRandomId(); - - InitBlazorWasmProjectDir(id); - string projectFile = CreateBlazorWasmTemplateProject(id); - string extraProperties = @"<_WasmDevel>true"; - - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "clean_native", extraProperties: extraProperties); bool relink = firstBuildNative; - BlazorBuildInternal(id, config, publish: false, - extraArgs: relink ? "-p:WasmBuildNative=true" : string.Empty); + BlazorBuild(info, + config, + new BuildOptions(ExtraMSBuildArgs: relink ? "-p:WasmBuildNative=true" : string.Empty), + isNativeBuild: relink); - string relinkDir = Path.Combine(_projectDir!, "obj", config, DefaultTargetFrameworkForBlazor, "wasm", "for-build"); + string relinkDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFrameworkForBlazor, "wasm", "for-build"); if (relink) Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}"); relink = !firstBuildNative; - BlazorBuildInternal(id, config, publish: false, - extraArgs: relink ? "-p:WasmBuildNative=true" : string.Empty); + BlazorBuild(info, + config, + new BuildOptions(UseCache: false, ExtraMSBuildArgs: relink ? "-p:WasmBuildNative=true" : string.Empty), + isNativeBuild: relink ? true : null); if (relink) Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}"); - string logPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-clean.binlog"); + string logPath = Path.Combine(s_buildEnv.LogRootPath, info.ProjectName, $"{info.ProjectName}-clean.binlog"); using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); - cmd.WithEnvironmentVariable("NUGET_PACKAGES", _projectDir!) + .WithWorkingDirectory(_projectDir); + cmd.WithEnvironmentVariable("NUGET_PACKAGES", _projectDir) .ExecuteWithCapturedOutput("build", "-t:Clean", $"-p:Configuration={config}", $"-bl:{logPath}") .EnsureSuccessful(); AssertEmptyOrNonExistentDirectory(relinkDir); } + private void AssertEmptyOrNonExistentDirectory(string dirPath) { _testOutput.WriteLine($"dirPath: {dirPath}"); diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/DllImportTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/DllImportTests.cs new file mode 100644 index 0000000000000..48cb35d332c9a --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/DllImportTests.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; +using Microsoft.Playwright; + +#nullable enable + +namespace Wasm.Build.Tests.Blazor; + +public class DllImportTests : BlazorWasmTestBase +{ + public DllImportTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + _enablePerTestCleanup = true; + } + + public static TheoryData DllImportTheoryData() + { + var data = new TheoryData(); + data.Add(Configuration.Debug, /*build*/true, /*publish*/false); + data.Add(Configuration.Release, /*build*/true, /*publish*/false); + data.Add(Configuration.Release, /*build*/false, /*publish*/true); + + // ActiveIssue("https://github.com/dotnet/runtime/issues/110482") + if (!s_isWindows) + { + data.Add(Configuration.Release, /*build*/true, /*publish*/true); + } + return data; + } + + [Theory] + [MemberData(nameof(DllImportTheoryData))] + public async Task WithDllImportInMainAssembly(Configuration config, bool build, bool publish) + { + // Based on https://github.com/dotnet/runtime/issues/59255 + string prefix = $"blz_dllimp_{config}_{s_unicodeChars}"; + if (build && publish) + prefix += "build_then_publish"; + else if (build) + prefix += "build"; + else + prefix += "publish"; + string extraItems = @""; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, prefix, extraItems: extraItems); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "MyDllImport.cs"), Path.Combine(_projectDir, "Pages", "MyDllImport.cs")); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "mylib.cpp"), Path.Combine(_projectDir, "mylib.cpp")); + UpdateFile(Path.Combine("Pages", "MyDllImport.cs"), new Dictionary { { "##NAMESPACE##", info.ProjectName } }); + + BlazorAddRazorButton("cpp_add", """ + var result = MyDllImports.cpp_add(10, 12); + outputText = $"{result}"; + """); + + if (build) + BlazorBuild(info, config, isNativeBuild: true); + + if (publish) + BlazorPublish(info, config, new PublishOptions(UseCache: false), isNativeBuild: true); + + BlazorRunOptions runOptions = new(config, Test: TestDllImport); + if (publish) + await RunForPublishWithWebServer(runOptions); + else + await RunForBuildWithDotnetRun(runOptions); + + async Task TestDllImport(IPage page) + { + await page.Locator("text=\"cpp_add\"").ClickAsync(); + var txt = await page.Locator("p[role='test']").InnerHTMLAsync(); + Assert.Equal("Output: 22", txt); + } + } + + private void BlazorAddRazorButton(string buttonText, string customCode, string methodName = "test") => + UpdateFile(Path.Combine("Pages", "Counter.razor"), new Dictionary { + { + @"", + $@" + +

Output: @outputText

+ + " + }, + { + "private int currentCount = 0;", + $@" + private int currentCount = 0; + private string outputText = string.Empty; + public void {methodName}() + {{ + {customCode} + }} + " + } + }); +} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs deleted file mode 100644 index 9a6ba4e09ae8d..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs +++ /dev/null @@ -1,112 +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.IO; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; -using System.Collections.Generic; -using System.Threading.Tasks; - -#nullable enable - -namespace Wasm.Build.Tests.Blazor; - -// these tests only check if correct ICU files got copied -public class IcuShardingTests : BlazorWasmTestBase -{ - public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) {} - - [Theory] - [InlineData("Debug", "icudt.dat")] - [InlineData("Release", "icudt.dat")] - [InlineData("Debug", "icudt_CJK.dat")] - [InlineData("Release", "icudt_CJK.dat")] - public async Task CustomIcuFileFromRuntimePack(string config, string fileName) - { - string id = $"blz_customFromRuntimePack_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - var buildOptions = new BlazorBuildOptions( - id, - config, - WarnAsError: true, - GlobalizationMode: GlobalizationMode.Custom, - CustomIcuFile: fileName - ); - AddItemsPropertiesToProject( - projectFile, - extraProperties: - $"{fileName}"); - - (CommandResult res, string logPath) = BlazorBuild(buildOptions); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } - - [Theory] - [InlineData("Debug", "incorrectName.dat", false)] - [InlineData("Release", "incorrectName.dat", false)] - [InlineData("Debug", "icudtNonExisting.dat", true)] - [InlineData("Release", "icudtNonExisting.dat", true)] - public void NonExistingCustomFileAssertError(string config, string fileName, bool isFilenameCorrect) - { - string id = $"blz_invalidCustomIcu_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject( - projectFile, - extraProperties: - $"{fileName}"); - - try - { - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: GlobalizationMode.Custom, - CustomIcuFile: fileName - )); - } - catch (XunitException ex) - { - if (isFilenameCorrect) - { - Assert.Contains($"Could not find $(BlazorIcuDataFileName)={fileName}, or when used as a path relative to the runtime pack", ex.Message); - } - else - { - Assert.Contains("File name in $(BlazorIcuDataFileName) has to start with 'icudt'", ex.Message); - } - } - catch (Exception) - { - throw new Exception("Unexpected exception in test scenario."); - } - // we expect build error, so there is not point in running the app - } - - [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task CustomFileNotFromRuntimePackAbsolutePath(string config) - { - string id = $"blz_invalidCustomIcu_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject( - projectFile, - extraProperties: - $"{IcuTestsBase.CustomIcuPath}"); - - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: GlobalizationMode.Custom, - CustomIcuFile: IcuTestsBase.CustomIcuPath - )); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuTests.cs deleted file mode 100644 index 8d8914ee195f9..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuTests.cs +++ /dev/null @@ -1,134 +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.IO; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; -using System.Collections.Generic; -using System.Threading.Tasks; - -#nullable enable - -namespace Wasm.Build.Tests.Blazor; - -// these tests only check if correct ICU files got copied -public class IcuTests : BlazorWasmTestBase -{ - public IcuTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) {} - - [Theory] - [InlineData("Debug", false)] - [InlineData("Debug", true)] - [InlineData("Debug", null)] - [InlineData("Release", false)] - [InlineData("Release", true)] - [InlineData("Release", null)] - public async Task HybridWithInvariant(string config, bool? invariant) - { - string id = $"blz_hybrid_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - string extraProperties = "true"; - if (invariant != null) - extraProperties += $"{invariant}"; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: invariant == true ? GlobalizationMode.Invariant : GlobalizationMode.Hybrid, - ExpectedFileType: invariant == true ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack - )); - - string warning = "$(HybridGlobalization) has no effect when $(InvariantGlobalization) is set to true."; - if (invariant == true) - { - Assert.Contains(warning, res.Output); - } - else - { - Assert.DoesNotContain(warning, res.Output); - } - - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } - - [Theory] - [InlineData("Debug", false)] - [InlineData("Debug", true)] - [InlineData("Debug", null)] - [InlineData("Release", false)] - [InlineData("Release", true)] - [InlineData("Release", null)] - public async Task HybridWithFullIcuFromRuntimePack(string config, bool? fullIcu) - { - string id = $"blz_hybrid_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - string extraProperties = "true"; - if (fullIcu != null) - extraProperties += $"{fullIcu}"; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: GlobalizationMode.Hybrid - )); - - string warning = "$(BlazorWebAssemblyLoadAllGlobalizationData) has no effect when $(HybridGlobalization) is set to true."; - if (fullIcu == true) - { - Assert.Contains(warning, res.Output); - } - else - { - Assert.DoesNotContain(warning, res.Output); - } - - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } - - [Theory] - [InlineData("Debug", false)] - [InlineData("Debug", true)] - [InlineData("Debug", null)] - [InlineData("Release", false)] - [InlineData("Release", true)] - [InlineData("Release", null)] - public async Task FullIcuFromRuntimePackWithInvariant(string config, bool? invariant) - { - string id = $"blz_hybrid_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - string extraProperties = "true"; - if (invariant != null) - extraProperties += $"{invariant}"; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - (CommandResult res, string logPath) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - WarnAsError: false, - GlobalizationMode: invariant == true ? GlobalizationMode.Invariant : GlobalizationMode.FullIcu, - ExpectedFileType: invariant == true ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack - )); - - string warning = "$(BlazorWebAssemblyLoadAllGlobalizationData) has no effect when $(InvariantGlobalization) is set to true."; - if (invariant == true) - { - Assert.Contains(warning, res.Output); - } - else - { - Assert.DoesNotContain(warning, res.Output); - } - - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs index c93ec2c6af452..9ef748b867808 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs @@ -1,9 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.IO; +using System.Linq; +using System.Text.Json; using Xunit; using Xunit.Abstractions; +using Xunit.Sdk; #nullable enable @@ -18,60 +22,85 @@ public MiscTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildC } [Theory] - [InlineData("Debug", true)] - [InlineData("Debug", false)] - [InlineData("Release", true)] - [InlineData("Release", false)] - public void NativeBuild_WithDeployOnBuild_UsedByVS(string config, bool nativeRelink) + [InlineData(Configuration.Debug, true)] + [InlineData(Configuration.Debug, false)] + [InlineData(Configuration.Release, true)] + [InlineData(Configuration.Release, false)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/103566")] + public void NativeBuild_WithDeployOnBuild_UsedByVS(Configuration config, bool nativeRelink) { - string id = $"blz_deploy_on_build_{config}_{nativeRelink}_{GetRandomId()}"; - string projectFile = CreateProjectWithNativeReference(id); - string extraProperties = config == "Debug" + string extraProperties = config == Configuration.Debug ? ("-O1" + "-O1") : string.Empty; if (!nativeRelink) extraProperties += "true"; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blz_deploy_on_build", extraProperties: extraProperties); // build with -p:DeployOnBuild=true, and that will trigger a publish - (CommandResult res, _) = BlazorBuild(new BlazorBuildOptions( - Id: id, - Config: config, - ExpectedFileType: nativeRelink ? NativeFilesType.Relinked : NativeFilesType.AOT, - ExpectRelinkDirWhenPublishing: false, - IsPublish: false), - "-p:DeployBuild=true"); + (string _, string buildOutput) = BlazorBuild(info, + config, + new BuildOptions(ExtraMSBuildArgs: "-p:DeployBuild=true"), + isNativeBuild: true); // double check relinking! - int index = res.Output.IndexOf("pinvoke.c -> pinvoke.o"); - Assert.NotEqual(-1, index); + string substring = "pinvoke.c -> pinvoke.o"; + Assert.Contains(substring, buildOutput); // there should be only one instance of this string! - index = res.Output.IndexOf("pinvoke.c -> pinvoke.o", index + 1); - Assert.Equal(-1, index); + int occurrences = buildOutput.Split(new[] { substring }, StringSplitOptions.None).Length - 1; + Assert.Equal(2, occurrences); } [Theory] - [InlineData("Release")] - public void DefaultTemplate_AOT_InProjectFile(string config) + [InlineData(Configuration.Release)] + public void DefaultTemplate_AOT_InProjectFile(Configuration config) { - string id = $"blz_aot_prj_file_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - - string extraProperties = config == "Debug" - ? ("-O1" + + string extraProperties = config == Configuration.Debug + ? ("true" + + "-O1" + "-O1") - : string.Empty; - AddItemsPropertiesToProject(projectFile, extraProperties: "true" + extraProperties); + : "true"; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blz_aot_prj_file", extraProperties: extraProperties); // No relinking, no AOT - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); + BlazorBuild(info, config); // will aot - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT, ExpectRelinkDirWhenPublishing: true)); + BlazorPublish(info, config, new PublishOptions(UseCache: false, AOT: true)); // build again - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); + BlazorBuild(info, config, new BuildOptions(UseCache: false)); + } + + [Fact] + public void BugRegression_60479_WithRazorClassLib() + { + Configuration config = Configuration.Release; + string razorClassLibraryName = "RazorClassLibrary"; + string extraItems = @$" + + "; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blz_razor_lib_top", extraItems: extraItems); + + // No relinking, no AOT + BlazorBuild(info, config); + + // will relink + BlazorPublish(info, config, new PublishOptions(UseCache: false)); + + // publish/wwwroot/_framework/blazor.boot.json + string frameworkDir = GetBlazorBinFrameworkDir(config, forPublish: true); + string bootJson = Path.Combine(frameworkDir, "blazor.boot.json"); + + Assert.True(File.Exists(bootJson), $"Could not find {bootJson}"); + var jdoc = JsonDocument.Parse(File.ReadAllText(bootJson)); + if (!jdoc.RootElement.TryGetProperty("resources", out JsonElement resValue) || + !resValue.TryGetProperty("lazyAssembly", out JsonElement lazyVal)) + { + throw new XunitException($"Could not find resources.lazyAssembly object in {bootJson}"); + } + + Assert.True(lazyVal.EnumerateObject().Select(jp => jp.Name).FirstOrDefault(f => f.StartsWith(razorClassLibraryName)) != null); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs deleted file mode 100644 index 00a47837523ce..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs +++ /dev/null @@ -1,69 +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.IO; -using Xunit; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests.Blazor; - -public class MiscTests2 : BlazorWasmTestBase -{ - public MiscTests2(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - [Theory, TestCategory("no-workload")] - [InlineData("Debug")] - [InlineData("Release")] - public void NativeRef_EmitsWarningBecauseItRequiresWorkload(string config) - { - CommandResult res = PublishForRequiresWorkloadTest(config, extraItems: ""); - res.EnsureSuccessful(); - Assert.Matches("warning : .*but the native references won't be linked in", res.Output); - } - - [Theory, TestCategory("no-workload")] - [InlineData("Debug")] - [InlineData("Release")] - public void AOT_FailsBecauseItRequiresWorkload(string config) - { - CommandResult res = PublishForRequiresWorkloadTest(config, extraProperties: "true"); - Assert.NotEqual(0, res.ExitCode); - Assert.Contains("following workloads must be installed: wasm-tools", res.Output); - } - - [Theory, TestCategory("no-workload")] - [InlineData("Debug")] - [InlineData("Release")] - public void AOT_And_NativeRef_FailBecauseTheyRequireWorkload(string config) - { - CommandResult res = PublishForRequiresWorkloadTest(config, - extraProperties: "true", - extraItems: ""); - - Assert.NotEqual(0, res.ExitCode); - Assert.Contains("following workloads must be installed: wasm-tools", res.Output); - } - - private CommandResult PublishForRequiresWorkloadTest(string config, string extraItems="", string extraProperties="") - { - string id = $"needs_workload_{config}_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); - - AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"), - extraProperties: extraProperties, - extraItems: extraItems); - - string publishLogPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}.binlog"); - using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput); - return cmd.WithWorkingDirectory(_projectDir!) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("publish", - $"-bl:{publishLogPath}", - $"-p:Configuration={config}"); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs deleted file mode 100644 index cdd0143cd2f08..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests3.cs +++ /dev/null @@ -1,167 +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.IO; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; -using Microsoft.Playwright; - -#nullable enable - -namespace Wasm.Build.Tests.Blazor; - -public class MiscTests3 : BlazorWasmTestBase -{ - public MiscTests3(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - _enablePerTestCleanup = true; - } - - [Theory] - [InlineData("Debug", /*build*/true, /*publish*/false)] - [InlineData("Debug", /*build*/false, /*publish*/true)] - [InlineData("Debug", /*build*/true, /*publish*/true)] - [InlineData("Release", /*build*/true, /*publish*/false)] - [InlineData("Release", /*build*/false, /*publish*/true)] - [InlineData("Release", /*build*/true, /*publish*/true)] - public async Task WithDllImportInMainAssembly(string config, bool build, bool publish) - { - // Based on https://github.com/dotnet/runtime/issues/59255 - string id = $"blz_dllimp_{config}_{s_unicodeChars}"; - if (build && publish) - id += "build_then_publish"; - else if (build) - id += "build"; - else - id += "publish"; - - string projectFile = CreateProjectWithNativeReference(id); - string nativeSource = @" - #include - - extern ""C"" { - int cpp_add(int a, int b) { - return a + b; - } - }"; - - File.WriteAllText(Path.Combine(_projectDir!, "mylib.cpp"), nativeSource); - - string myDllImportCs = @$" - using System.Runtime.InteropServices; - namespace {id}; - - public static class MyDllImports - {{ - [DllImport(""mylib"")] - public static extern int cpp_add(int a, int b); - }}"; - - File.WriteAllText(Path.Combine(_projectDir!, "Pages", "MyDllImport.cs"), myDllImportCs); - - AddItemsPropertiesToProject(projectFile, extraItems: @""); - BlazorAddRazorButton("cpp_add", """ - var result = MyDllImports.cpp_add(10, 12); - outputText = $"{result}"; - """); - - if (build) - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - - if (publish) - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: build)); - - BlazorRunOptions runOptions = new() { Config = config, Test = TestDllImport }; - if (publish) - await BlazorRunForPublishWithWebServer(runOptions); - else - await BlazorRunForBuildWithDotnetRun(runOptions); - - async Task TestDllImport(IPage page) - { - await page.Locator("text=\"cpp_add\"").ClickAsync(); - var txt = await page.Locator("p[role='test']").InnerHTMLAsync(); - Assert.Equal("Output: 22", txt); - } - } - - [Fact] - public void BugRegression_60479_WithRazorClassLib() - { - string id = $"blz_razor_lib_top_{GetRandomId()}"; - InitBlazorWasmProjectDir(id); - - string wasmProjectDir = Path.Combine(_projectDir!, "wasm"); - string wasmProjectFile = Path.Combine(wasmProjectDir, "wasm.csproj"); - Directory.CreateDirectory(wasmProjectDir); - using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false); - cmd.WithWorkingDirectory(wasmProjectDir) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("new blazorwasm") - .EnsureSuccessful(); - - string razorProjectDir = Path.Combine(_projectDir!, "RazorClassLibrary"); - Directory.CreateDirectory(razorProjectDir); - cmd.WithWorkingDirectory(razorProjectDir) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("new razorclasslib") - .EnsureSuccessful(); - - string razorClassLibraryFileNameWithoutExtension = "RazorClassLibrary"; - AddItemsPropertiesToProject(wasmProjectFile, extraItems: @$" - - - "); - - _projectDir = wasmProjectDir; - string config = "Release"; - // No relinking, no AOT - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - - // will relink - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true)); - - // publish/wwwroot/_framework/blazor.boot.json - string frameworkDir = FindBlazorBinFrameworkDir(config, forPublish: true); - string bootJson = Path.Combine(frameworkDir, "blazor.boot.json"); - - Assert.True(File.Exists(bootJson), $"Could not find {bootJson}"); - var jdoc = JsonDocument.Parse(File.ReadAllText(bootJson)); - if (!jdoc.RootElement.TryGetProperty("resources", out JsonElement resValue) || - !resValue.TryGetProperty("lazyAssembly", out JsonElement lazyVal)) - { - throw new XunitException($"Could not find resources.lazyAssembly object in {bootJson}"); - } - - Assert.True(lazyVal.EnumerateObject().Select(jp => jp.Name).FirstOrDefault(f => f.StartsWith(razorClassLibraryFileNameWithoutExtension)) != null); - } - - private void BlazorAddRazorButton(string buttonText, string customCode, string methodName = "test", string razorPage = "Pages/Counter.razor") - { - string additionalCode = $$""" -

Output: @outputText

- - - @code { - private string outputText = string.Empty; - public void {{methodName}}() - { - {{customCode}} - } - } - """; - - // find blazor's Counter.razor - string counterRazorPath = Path.Combine(_projectDir!, razorPage); - if (!File.Exists(counterRazorPath)) - throw new FileNotFoundException($"Could not find {counterRazorPath}"); - - string oldContent = File.ReadAllText(counterRazorPath); - File.WriteAllText(counterRazorPath, oldContent + additionalCode); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/NativeRefTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/NativeRefTests.cs index fac71883e3f2a..180c1e249c52b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/NativeRefTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/NativeRefTests.cs @@ -18,60 +18,48 @@ public NativeTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buil } [Theory] - [InlineData("Debug")] - [InlineData("Release")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] [ActiveIssue("https://github.com/dotnet/runtime/issues/82725")] - public void WithNativeReference_AOTInProjectFile(string config) + public void WithNativeReference_AOTInProjectFile(Configuration config) { - string id = $"blz_nativeref_aot_{config}_{GetRandomId()}"; - string projectFile = CreateProjectWithNativeReference(id); - string extraProperties = config == "Debug" + string extraProperties = config == Configuration.Debug ? ("-O1" + - "-O1") - : string.Empty; - AddItemsPropertiesToProject(projectFile, extraProperties: "true" + extraProperties); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT, ExpectRelinkDirWhenPublishing: true)); - + "-O1" + + "true") + : "true"; + ProjectInfo info = CreateProjectWithNativeReference(config, aot: true, extraProperties: extraProperties); + BlazorBuild(info, config, isNativeBuild: true); + BlazorPublish(info, config, new PublishOptions(UseCache: false), isNativeBuild: true); // will relink - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); + BlazorBuild(info, config, new BuildOptions(UseCache: false), isNativeBuild: true); } [Theory] - [InlineData("Debug")] - [InlineData("Release")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] [ActiveIssue("https://github.com/dotnet/runtime/issues/82725")] - public void WithNativeReference_AOTOnCommandLine(string config) + public void WithNativeReference_AOTOnCommandLine(Configuration config) { - string id = $"blz_nativeref_aot_{config}_{GetRandomId()}"; - string projectFile = CreateProjectWithNativeReference(id); - string extraProperties = config == "Debug" + string extraProperties = config == Configuration.Debug ? ("-O1" + "-O1") : string.Empty; - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT, ExpectRelinkDirWhenPublishing: true), "-p:RunAOTCompilation=true"); - + ProjectInfo info = CreateProjectWithNativeReference(config, aot: false, extraProperties: extraProperties); + BlazorBuild(info, config, isNativeBuild: true); + BlazorPublish(info, config, new PublishOptions(AOT: true), isNativeBuild: true); // no aot! - BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.Relinked, ExpectRelinkDirWhenPublishing: true)); + BlazorPublish(info, config, isNativeBuild: true); } [Theory] - [InlineData("Release")] - public void BlazorWasm_CannotAOT_WithNoTrimming(string config) + [InlineData(Configuration.Release)] + public void BlazorWasm_CannotAOT_WithNoTrimming(Configuration config) { - string id = $"blazorwasm_{config}_aot_{GetRandomId()}"; - CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"), - extraItems: null, - extraProperties: "falsetrue"); + string extraProperties = "falsetrue"; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.BlazorBasicTestApp, "blazorwasm_aot", extraProperties: extraProperties); - (CommandResult res, _) = BlazorPublish(new BlazorBuildOptions(id, config, ExpectSuccess: false)); - Assert.Contains("AOT is not supported without IL trimming", res.Output); + (string _, string output) = BlazorPublish(info, config, new PublishOptions(ExpectSuccess: false)); + Assert.Contains("AOT is not supported without IL trimming", output); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/NoopNativeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/NoopNativeRebuildTest.cs index 942d88643df47..526a4737a29be 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/NoopNativeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/NoopNativeRebuildTest.cs @@ -19,58 +19,54 @@ public NoopNativeRebuildTest(ITestOutputHelper output, SharedBuildPerTestClassFi } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void BlazorNoopRebuild(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void BlazorNoopRebuild(Configuration config) { - string id = $"blz_rebuild_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject(projectFile, extraProperties: "true"); - - string objDir = Path.Combine(_projectDir!, "obj", config, DefaultTargetFrameworkForBlazor, "wasm"); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - File.Move(Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build.binlog"), - Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build-first.binlog")); - + string extraProperties = "true"; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_rebuild", extraProperties: extraProperties); + BlazorBuild(info, config, isNativeBuild: true); + string projectDir = Path.GetFileName(Path.GetDirectoryName(Path.GetDirectoryName(info.ProjectFilePath)))!; + File.Move(Path.Combine(s_buildEnv.LogRootPath, projectDir, $"{info.ProjectName}-build.binlog"), + Path.Combine(s_buildEnv.LogRootPath, projectDir, $"{info.ProjectName}-build-first.binlog")); + + string objDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFrameworkForBlazor, "wasm"); var pathsDict = _provider.GetFilesTable(true, objDir); pathsDict.Remove("runtime-icall-table.h"); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var originalStat = _provider.StatFiles(pathsDict); // build again - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked)); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + BlazorBuild(info, config, new BuildOptions(UseCache: false), isNativeBuild: true); + var newStat = _provider.StatFiles(pathsDict); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); + _provider.CompareStat(originalStat, newStat, pathsDict); } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public void BlazorOnlyLinkRebuild(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void BlazorOnlyLinkRebuild(Configuration config) { - string id = $"blz_relink_{config}_{GetRandomId()}"; - string projectFile = CreateBlazorWasmTemplateProject(id); - AddItemsPropertiesToProject(projectFile, extraProperties: "true"); - - string objDir = Path.Combine(_projectDir!, "obj", config, DefaultTargetFrameworkForBlazor, "wasm"); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked), "-p:EmccLinkOptimizationFlag=-O2"); - File.Move(Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build.binlog"), - Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build-first.binlog")); - + string extraProperties = "true"; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_relink", extraProperties: extraProperties); + var buildOptions = new BuildOptions(ExtraMSBuildArgs: "-p:EmccLinkOptimizationFlag=-O2"); + BlazorBuild(info, config, buildOptions, isNativeBuild: true); + string projectDir = Path.GetFileName(Path.GetDirectoryName(Path.GetDirectoryName(info.ProjectFilePath)))!; + File.Move(Path.Combine(s_buildEnv.LogRootPath, projectDir, $"{info.ProjectName}-build.binlog"), + Path.Combine(s_buildEnv.LogRootPath, projectDir, $"{info.ProjectName}-build-first.binlog")); + + string objDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFrameworkForBlazor, "wasm"); var pathsDict = _provider.GetFilesTable(true, objDir); pathsDict.Remove("runtime-icall-table.h"); pathsDict.UpdateTo(unchanged: false, "dotnet.native.wasm", "dotnet.native.js", "emcc-link.rsp"); - - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var originalStat = _provider.StatFiles(pathsDict); // build again - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.Relinked), "-p:EmccLinkOptimizationFlag=-O1"); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + BlazorBuild(info, config, new BuildOptions(ExtraMSBuildArgs: "-p:EmccLinkOptimizationFlag=-O1", UseCache: false), isNativeBuild: true); + var newStat = _provider.StatFiles(pathsDict); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); + _provider.CompareStat(originalStat, newStat, pathsDict); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs index cf4f938bc1885..f48472f9b9646 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs @@ -20,11 +20,11 @@ public SignalRClientTests(ITestOutputHelper output, SharedBuildPerTestClassFixtu [ConditionalTheory(typeof(BuildTestBase), nameof(IsWorkloadWithMultiThreadingForDefaultFramework))] [ActiveIssue("https://github.com/dotnet/runtime/issues/100445")] // to be fixed by: "https://github.com/dotnet/aspnetcore/issues/54365" - [InlineData("Debug", "LongPolling")] - [InlineData("Release", "LongPolling")] - [InlineData("Debug", "WebSockets")] - [InlineData("Release", "WebSockets")] - public async Task SignalRPassMessageBlazor(string config, string transport) => + [InlineData(Configuration.Debug, "LongPolling")] + [InlineData(Configuration.Release, "LongPolling")] + [InlineData(Configuration.Debug, "WebSockets")] + [InlineData(Configuration.Release, "WebSockets")] + public async Task SignalRPassMessageBlazor(Configuration config, string transport) => await SignalRPassMessage("blazorclient", config, transport); } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleMultiThreadedTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleMultiThreadedTests.cs index 556d40d42a40e..ce948e9a4a2dc 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleMultiThreadedTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleMultiThreadedTests.cs @@ -21,38 +21,37 @@ public SimpleMultiThreadedTests(ITestOutputHelper output, SharedBuildPerTestClas } // dotnet-run needed for running with *build* so wwwroot has the index.html etc - // [Theory] - // [InlineData("Debug")] - // [InlineData("Release")] - // public async Task BlazorBuildRunTest(string config) - // { - // string id = $"blazor_mt_{config}_{GetRandomId()}"; - // string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - - // AddItemsPropertiesToProject(projectFile, "true"); - // BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack, RuntimeType: RuntimeType.MultiThreaded)); - // // await BlazorRunForBuildWithDotnetRun(config); - - // await BlazorRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files --web-server-use-cors --web-server-use-cop --web-server-use-https --timeout=15:00:00", - // Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(config, forPublish: false), ".."))); - // } + [Theory] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/100373")] // to be fixed by: "https://github.com/dotnet/aspnetcore/issues/54365" + public async Task BlazorBuildRunTest(Configuration config) + { + string extraProperties = "true"; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blazorwasm", extraProperties: extraProperties); + bool isPublish = false; + string frameworkDir = GetBlazorBinFrameworkDir(config, isPublish); + BuildProject(info, config, new BuildOptions(RuntimeType: RuntimeVariant.MultiThreaded)); + // we wan to use "xharness wasm webserver" but from non-publish location + string extraArgs = " --web-server-use-cors --web-server-use-cop --web-server-use-https --timeout=15:00:00"; + await RunForPublishWithWebServer(new BlazorRunOptions(config, ExtraArgs: extraArgs, CustomBundleDir: Path.Combine(frameworkDir, ".."))); + } [ConditionalTheory(typeof(BuildTestBase), nameof(IsWorkloadWithMultiThreadingForDefaultFramework))] [ActiveIssue("https://github.com/dotnet/runtime/issues/100373")] // to be fixed by: "https://github.com/dotnet/aspnetcore/issues/54365" - // [InlineData("Debug", false)] // ActiveIssue https://github.com/dotnet/runtime/issues/98758 - // [InlineData("Debug", true)] - [InlineData("Release", false)] - // [InlineData("Release", true)] - public async Task BlazorPublishRunTest(string config, bool aot) + // [InlineData(Configuration.Debug, false)] // ActiveIssue https://github.com/dotnet/runtime/issues/98758 + // [InlineData(Configuration.Debug, true)] + [InlineData(Configuration.Release, false)] + // [InlineData(Configuration.Release, true)] + public async Task BlazorPublishRunTest(Configuration config, bool aot) { - string id = $"blazor_mt_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - AddItemsPropertiesToProject(projectFile, "true"); + string extraProperties = "true"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.BlazorBasicTestApp, "blazor_mt", extraProperties: extraProperties); // if (aot) // AddItemsPropertiesToProject(projectFile, "true"); File.WriteAllText( - Path.Combine(Path.GetDirectoryName(projectFile)!, "wwwroot", id + ".lib.module.js"), + Path.Combine(Path.GetDirectoryName(info.ProjectFilePath)!, "wwwroot", info.ProjectName + ".lib.module.js"), """ export function onRuntimeReady({ runtimeBuildInfo }) { console.log('Runtime is ready: ' + JSON.stringify(runtimeBuildInfo)); @@ -61,26 +60,21 @@ export function onRuntimeReady({ runtimeBuildInfo }) { """ ); - BlazorPublish(new BlazorBuildOptions( - id, - config, - aot ? NativeFilesType.AOT - : (config == "Release" ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack), - RuntimeType: RuntimeVariant.MultiThreaded)); + BuildProject(info, config, new PublishOptions(RuntimeType: RuntimeVariant.MultiThreaded, AOT: aot)); bool hasEmittedWasmEnableThreads = false; StringBuilder errorOutput = new(); - await BlazorRunForPublishWithWebServer( + await RunForPublishWithWebServer( runOptions: new BlazorRunOptions( - Config: config, + Configuration: config, ExtraArgs: "--web-server-use-cors --web-server-use-cop", - OnConsoleMessage: (_, message) => + OnConsoleMessage: (type, message) => { - if (message.Text.Contains("WasmEnableThreads=true")) + if (message.Contains("WasmEnableThreads=true")) hasEmittedWasmEnableThreads = true; - if (message.Type == "error") - errorOutput.AppendLine(message.Text); + if (type == "error") + errorOutput.AppendLine(message); }, OnErrorMessage: (message) => { diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleRunTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleRunTests.cs index 433d77699c1c7..e623c6d4efc7e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleRunTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/SimpleRunTests.cs @@ -23,75 +23,51 @@ public SimpleRunTests(ITestOutputHelper output, SharedBuildPerTestClassFixture b } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task BlazorBuildRunTest(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task BlazorBuildRunTest(Configuration config) { - string id = $"blazor_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - - BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blazor"); + BlazorBuild(info, config); + await RunForBuildWithDotnetRun(new BlazorRunOptions(config)); } [Theory] - [InlineData("Debug", /*appendRID*/ true, /*useArtifacts*/ false)] - [InlineData("Debug", /*appendRID*/ true, /*useArtifacts*/ true)] - [InlineData("Debug", /*appendRID*/ false, /*useArtifacts*/ true)] - [InlineData("Debug", /*appendRID*/ false, /*useArtifacts*/ false)] - public async Task BlazorBuildAndRunForDifferentOutputPaths(string config, bool appendRID, bool useArtifacts) + [InlineData(Configuration.Debug, /*appendRID*/ true, /*useArtifacts*/ false)] + [InlineData(Configuration.Debug, /*appendRID*/ true, /*useArtifacts*/ true)] + [InlineData(Configuration.Debug, /*appendRID*/ false, /*useArtifacts*/ true)] + [InlineData(Configuration.Debug, /*appendRID*/ false, /*useArtifacts*/ false)] + public async Task BlazorBuildAndRunForDifferentOutputPaths(Configuration config, bool appendRID, bool useArtifacts) { - string id = $"{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blazor"); string extraPropertiesForDBP = ""; if (appendRID) extraPropertiesForDBP += "true"; if (useArtifacts) extraPropertiesForDBP += "true."; - - string projectDirectory = Path.GetDirectoryName(projectFile)!; + string projectDir = Path.GetDirectoryName(info.ProjectFilePath) ?? ""; + string rootDir = Path.GetDirectoryName(projectDir) ?? ""; if (!string.IsNullOrEmpty(extraPropertiesForDBP)) - AddItemsPropertiesToProject(Path.Combine(projectDirectory, "Directory.Build.props"), + AddItemsPropertiesToProject(Path.Combine(rootDir, "Directory.Build.props"), extraPropertiesForDBP); - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - - BlazorBuildOptions buildOptions = new(id, config, NativeFilesType.FromRuntimePack); - if (useArtifacts) - { - buildOptions = buildOptions with - { - BinFrameworkDir = Path.Combine(projectDirectory, - "bin", - id, - config.ToLower(), - "wwwroot", - "_framework") - }; - } - BlazorBuild(buildOptions); - await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config }); + bool isPublish = false; + string frameworkDir = useArtifacts ? + Path.Combine( + projectDir, "bin", info.ProjectName, config.ToString().ToLower(), "wwwroot", "_framework") : + GetBinFrameworkDir(config, isPublish); + BuildProject(info, config, new BuildOptions(NonDefaultFrameworkDir: frameworkDir)); + await RunForBuildWithDotnetRun(new BlazorRunOptions(config)); } [Theory] - [InlineData("Debug", false)] - [InlineData("Release", false)] - [InlineData("Release", true)] - public async Task BlazorPublishRunTest(string config, bool aot) + [InlineData(Configuration.Debug, false)] + [InlineData(Configuration.Release, false)] + [InlineData(Configuration.Release, true)] + public async Task BlazorPublishRunTest(Configuration config, bool aot) { - string id = $"blazor_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - if (aot) - AddItemsPropertiesToProject(projectFile, "true"); - - BlazorPublish(new BlazorBuildOptions( - id, - config, - aot ? NativeFilesType.AOT - : (config == "Release" ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack))); - await BlazorRunForPublishWithWebServer(new BlazorRunOptions() { Config = config }); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.BlazorBasicTestApp, "blazor_publish"); + BlazorPublish(info, config); + await RunForPublishWithWebServer(new BlazorRunOptions(config)); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs index a735e2af15e44..9dbf10ac2a365 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs @@ -34,14 +34,14 @@ public WorkloadRequiredTests(ITestOutputHelper output, SharedBuildPerTestClassFi { } - public static TheoryData SettingDifferentFromValuesInRuntimePack() + public static TheoryData SettingDifferentFromValuesInRuntimePack() { - TheoryData data = new(); + TheoryData data = new(); - string[] configs = new[] { "Debug", "Release" }; + var configs = new[] { Configuration.Debug, Configuration.Release }; foreach (var defaultPair in PropertiesWithTriggerValues) { - foreach (string config in configs) + foreach (Configuration config in configs) { data.Add(config, $"<{defaultPair.propertyName}>{defaultPair.triggerValue}", true); data.Add(config, $"<{defaultPair.propertyName}>{!defaultPair.triggerValue}", false); @@ -53,18 +53,18 @@ public static TheoryData SettingDifferentFromValuesInRunti [Theory, TestCategory("no-workload")] [MemberData(nameof(SettingDifferentFromValuesInRuntimePack))] - public void WorkloadRequiredForBuild(string config, string extraProperties, bool workloadNeeded) + public void WorkloadRequiredForBuild(Configuration config, string extraProperties, bool workloadNeeded) => CheckWorkloadRequired(config, extraProperties, workloadNeeded, publish: false); [Theory, TestCategory("no-workload")] [MemberData(nameof(SettingDifferentFromValuesInRuntimePack))] - public void WorkloadRequiredForPublish(string config, string extraProperties, bool workloadNeeded) + public void WorkloadRequiredForPublish(Configuration config, string extraProperties, bool workloadNeeded) => CheckWorkloadRequired(config, extraProperties, workloadNeeded, publish: true); - public static TheoryData InvariantGlobalizationTestData(bool publish) + public static TheoryData InvariantGlobalizationTestData(bool publish) { - TheoryData data = new(); - foreach (string config in new[] { "Debug", "Release" }) + TheoryData data = new(); + foreach (Configuration config in new[] { Configuration.Debug, Configuration.Release }) { data.Add(config, /*invariant*/ true, /*publish*/ publish); data.Add(config, /*invariant*/ false, /*publish*/ publish); @@ -73,129 +73,156 @@ public static TheoryData InvariantGlobalizationTestData(bool } [Theory, TestCategory("no-workload")] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: /*publish*/ false)] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: /*publish*/ true)] - public async Task WorkloadNotRequiredForInvariantGlobalization(string config, bool invariant, bool publish) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task DefaultTemplate_WithoutWorkload(Configuration config) { - string id = $"props_req_workload_{(publish ? "publish" : "build")}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, "blz_no_workload"); + BlazorBuild(info, config); + await RunForBuildWithDotnetRun(new BlazorRunOptions(config)); - if (invariant) - AddItemsPropertiesToProject(projectFile, extraProperties: "true"); + BlazorPublish(info, config, new PublishOptions(UseCache: false)); + await RunForPublishWithWebServer(new BlazorRunOptions(config)); + } - string counterPath = Path.Combine(Path.GetDirectoryName(projectFile)!, "Pages", "Counter.razor"); - string allText = File.ReadAllText(counterPath); - string ccText = "currentCount++;"; - if (allText.IndexOf(ccText) < 0) - throw new Exception("Counter.razor does not have the expected content. Test needs to be updated."); + [Theory, TestCategory("no-workload")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void NativeRef_EmitsWarningBecauseItRequiresWorkload(Configuration config) + { + CommandResult res = PublishForRequiresWorkloadTest(config, extraItems: ""); + res.EnsureSuccessful(); + Assert.Matches("warning : .*but the native references won't be linked in", res.Output); + } - allText = allText.Replace(ccText, $"{ccText}{Environment.NewLine}TestInvariantCulture();"); - allText += s_invariantCultureMethodForBlazor; - File.WriteAllText(counterPath, allText); - _testOutput.WriteLine($"Updated counter.razor: {allText}"); + [Theory, TestCategory("no-workload")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void AOT_FailsBecauseItRequiresWorkload(Configuration config) + { + CommandResult res = PublishForRequiresWorkloadTest(config, extraProperties: "true"); + Assert.NotEqual(0, res.ExitCode); + Assert.Contains("following workloads must be installed: wasm-tools", res.Output); + } - CommandResult result; - GlobalizationMode mode = invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded; - if (publish) - { - (result, _) = BlazorPublish( - new BlazorBuildOptions( - id, - config, - ExpectSuccess: true, - GlobalizationMode: mode)); - } - else - { - (result, _) = BlazorBuild( - new BlazorBuildOptions( - id, - config, - ExpectSuccess: true, - GlobalizationMode: mode)); - } + [Theory, TestCategory("no-workload")] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void AOT_And_NativeRef_FailBecauseTheyRequireWorkload(Configuration config) + { + CommandResult res = PublishForRequiresWorkloadTest(config, + extraProperties: "true", + extraItems: ""); - StringBuilder sbOutput = new(); - await BlazorRunTest(new BlazorRunOptions() + Assert.NotEqual(0, res.ExitCode); + Assert.Contains("following workloads must be installed: wasm-tools", res.Output); + } + + + [Theory, TestCategory("no-workload")] + [MemberData(nameof(InvariantGlobalizationTestData), parameters: /*publish*/ false)] + [MemberData(nameof(InvariantGlobalizationTestData), parameters: /*publish*/ true)] + public async Task WorkloadNotRequiredForInvariantGlobalization(Configuration config, bool invariant, bool publish) + { + string prefix = $"props_req_workload_{(publish ? "publish" : "build")}"; + string extraProperties = invariant ? $"true" : ""; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, prefix, extraProperties: extraProperties); + string ccText = "currentCount++;"; + // UpdateFile throws if code that is to be replaced does not exist + UpdateFile(Path.Combine("Pages", "Counter.razor"), new Dictionary { - Config = config, - Host = publish ? BlazorRunHost.WebServer : BlazorRunHost.DotnetRun, - OnConsoleMessage = (_, msg) => - { - sbOutput.AppendLine(msg.Text); - } + { ccText, $"{ccText}\nTestInvariantCulture();" }, + { "private int currentCount = 0;", $"{s_invariantCultureMethodForBlazor}" } }); + string counterPath = Path.Combine(_projectDir, "Pages", "Counter.razor"); + string allText = File.ReadAllText(counterPath); + _testOutput.WriteLine($"Updated counter.razor: {allText}"); + + var globalizationMode = invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded; + _ = publish ? + PublishProject(info, config, new PublishOptions(GlobalizationMode: globalizationMode)) : + BuildProject(info, config, new BuildOptions(GlobalizationMode: globalizationMode)); + + BlazorRunOptions runOptions = new(config); + RunResult result = publish ? await RunForPublishWithWebServer(runOptions) : await RunForBuildWithDotnetRun(runOptions); - string output = sbOutput.ToString(); if (invariant) { - Assert.Contains("Could not create es-ES culture", output); + Assert.Contains(result.TestOutput, m => m.Contains("Could not create es-ES culture")); // For invariant, we get: // Could not create es-ES culture: Argument_CultureNotSupportedInInvariantMode Arg_ParamName_Name, name // Argument_CultureInvalidIdentifier, es-ES // .. which is expected. // // Assert.Contains("es-ES is an invalid culture identifier.", output); - Assert.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); - Assert.DoesNotContain($"es-ES: Is-LCID-InvariantCulture:", output); + Assert.Contains(result.TestOutput, m => m.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)")); + Assert.All(result.TestOutput, m => Assert.DoesNotContain("es-ES: Is-LCID-InvariantCulture", m)); } else { - Assert.DoesNotContain("Could not create es-ES culture", output); - Assert.DoesNotContain("invalid culture", output); - Assert.DoesNotContain("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); - Assert.Contains("es-ES: Is-LCID-InvariantCulture: False", output); - Assert.Contains("NativeName: espa\u00F1ol (Espa\u00F1a)", output); - + Assert.All(result.TestOutput, m => Assert.DoesNotContain("Could not create es-ES culture", m)); + Assert.All(result.TestOutput, m => Assert.DoesNotContain("invalid culture", m)); + Assert.All(result.TestOutput, m => Assert.DoesNotContain("CurrentCulture.NativeName: Invariant Language (Invariant Country)", m)); + Assert.Contains(result.TestOutput, m => m.Contains("es-ES: Is-LCID-InvariantCulture: False")); + Assert.Contains(result.TestOutput, m => m.Contains("NativeName: espa\u00F1ol (Espa\u00F1a)")); // ignoring the last line of the output which prints the current culture } } - private void CheckWorkloadRequired(string config, string extraProperties, bool workloadNeeded, bool publish) + private CommandResult PublishForRequiresWorkloadTest(Configuration config, string extraItems="", string extraProperties="") { - string id = $"props_req_workload_{(publish ? "publish" : "build")}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); - AddItemsPropertiesToProject(projectFile, extraProperties, - atTheEnd: @" - - "); + ProjectInfo info = CopyTestAsset( + config, aot: false, TestAsset.BlazorBasicTestApp, "needs_workload", extraProperties: extraProperties, extraItems: extraItems); + + string publishLogPath = Path.Combine(s_buildEnv.LogRootPath, info.ProjectName, $"{info.ProjectName}.binlog"); + using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput); + return cmd.WithWorkingDirectory(_projectDir) + .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) + .ExecuteWithCapturedOutput("publish", + $"-bl:{publishLogPath}", + $"-p:Configuration={config}"); + } - CommandResult result; - if (publish) - (result, _) = BlazorPublish(new BlazorBuildOptions(id, config, ExpectSuccess: false)); - else - (result, _) = BlazorBuild(new BlazorBuildOptions(id, config, ExpectSuccess: false)); + private void CheckWorkloadRequired(Configuration config, string extraProperties, bool workloadNeeded, bool publish) + { + string prefix = $"props_req_workload_{(publish ? "publish" : "build")}"; + string insertAtEnd = @" + + "; + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.BlazorBasicTestApp, prefix, extraProperties: extraProperties, insertAtEnd: insertAtEnd); + (string _, string output) = publish ? + PublishProject(info, config, new PublishOptions(ExpectSuccess: false)) : + BuildProject(info, config, new BuildOptions(ExpectSuccess: false)); if (workloadNeeded) { - Assert.Contains("following workloads must be installed: wasm-tools", result.Output); - Assert.DoesNotContain("error : Stopping the build", result.Output); + Assert.Contains("following workloads must be installed: wasm-tools", output); + Assert.DoesNotContain("error : Stopping the build", output); } else { - Assert.DoesNotContain("following workloads must be installed: wasm-tools", result.Output); - Assert.Contains("error : Stopping the build", result.Output); + Assert.DoesNotContain("following workloads must be installed: wasm-tools", output); + Assert.Contains("error : Stopping the build", output); } } private static string s_invariantCultureMethodForBlazor = """ - @code { + private int currentCount = 0; public int TestInvariantCulture() { // https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data try { System.Globalization.CultureInfo culture = new ("es-ES", false); - System.Console.WriteLine($"es-ES: Is-LCID-InvariantCulture: {culture.LCID == System.Globalization.CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); + System.Console.WriteLine($"TestOutput -> es-ES: Is-LCID-InvariantCulture: {culture.LCID == System.Globalization.CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); } catch (System.Globalization.CultureNotFoundException cnfe) { - System.Console.WriteLine($"Could not create es-ES culture: {cnfe.Message}"); + System.Console.WriteLine($"TestOutput -> Could not create es-ES culture: {cnfe.Message}"); } - System.Console.WriteLine($"CurrentCulture.NativeName: {System.Globalization.CultureInfo.CurrentCulture.NativeName}"); + System.Console.WriteLine($"TestOutput -> CurrentCulture.NativeName: {System.Globalization.CultureInfo.CurrentCulture.NativeName}"); return 42; } - } """; } diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs index 0b6b7d3dda413..be2e2dd13a29b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs @@ -113,12 +113,12 @@ public async Task SpawnBrowserAsync( bool headless = true, int? timeout = null, int maxRetries = 3, - string language = "en-US" + string locale = "en-US" ) { var url = new Uri(browserUrl); Playwright = await Microsoft.Playwright.Playwright.CreateAsync(); // codespaces: ignore certificate error -> Microsoft.Playwright.PlaywrightException : net::ERR_CERT_AUTHORITY_INVALID - string[] chromeArgs = new[] { $"--explicitly-allowed-ports={url.Port}", "--ignore-certificate-errors", $"--lang={language}" }; + string[] chromeArgs = new[] { $"--explicitly-allowed-ports={url.Port}", "--ignore-certificate-errors", $"--lang={locale}" }; _testOutput.WriteLine($"Launching chrome ('{s_chromePath.Value}') via playwright with args = {string.Join(',', chromeArgs)}"); int attempt = 0; @@ -155,15 +155,15 @@ public async Task RunAsync( ToolCommand cmd, string args, bool headless = true, - string language = "en-US", - Action? onConsoleMessage = null, + string locale = "en-US", + Action? onConsoleMessage = null, Action? onServerMessage = null, Action? onError = null, Func? modifyBrowserUrl = null) { var urlString = await StartServerAndGetUrlAsync(cmd, args, onServerMessage); - var browser = await SpawnBrowserAsync(urlString, headless, language: language); - var context = await browser.NewContextAsync(new BrowserNewContextOptions { Locale = language }); + var browser = await SpawnBrowserAsync(urlString, headless, locale: locale); + var context = await browser.NewContextAsync(new BrowserNewContextOptions { Locale = locale }); return await RunAsync(context, urlString, headless, onConsoleMessage, onError, modifyBrowserUrl); } @@ -171,7 +171,7 @@ public async Task RunAsync( IBrowserContext context, string browserUrl, bool headless = true, - Action? onConsoleMessage = null, + Action? onConsoleMessage = null, Action? onError = null, Func? modifyBrowserUrl = null, bool resetExitedState = false @@ -192,26 +192,15 @@ public async Task RunAsync( { message = payloadMatch.Groups["payload"].Value; } - if (message.StartsWith("TestOutput -> ")) - { - lock (OutputLines) - { - OutputLines.Add(message); - } - } Match exitMatch = s_exitRegex.Match(message); if (exitMatch.Success) { - lock (OutputLines) - { - OutputLines.Add(message); - } int exitCode = int.Parse(exitMatch.Groups["exitCode"].Value); _exited.TrySetResult(exitCode); } if (onConsoleMessage is not null) { - onConsoleMessage(page, msg); + onConsoleMessage(msg.Type, message); } }; @@ -227,7 +216,7 @@ public async Task RunAsync( return page; } - public async Task WaitForExitMessageAsync(TimeSpan timeout) + public async Task WaitForExitMessageAsync(TimeSpan timeout) { if (RunTask is null || RunTask.IsCompleted) throw new Exception($"No run task, or already completed"); @@ -235,8 +224,9 @@ public async Task WaitForExitMessageAsync(TimeSpan timeout) await Task.WhenAny(RunTask!, _exited.Task, Task.Delay(timeout)); if (_exited.Task.IsCompleted) { - _testOutput.WriteLine ($"Exited with {await _exited.Task}"); - return; + int code = await _exited.Task; + _testOutput.WriteLine ($"Exited with {code}"); + return code; } throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for 'WASM EXIT' message"); diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs new file mode 100644 index 0000000000000..c2fd74e11bf6d --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.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. + +#nullable enable + +using System.IO; + +namespace Wasm.Build.Tests; + +public record AssertBundleOptions( + Configuration Configuration, + MSBuildOptions BuildOptions, + NativeFilesType ExpectedFileType, + string BinFrameworkDir, + bool ExpectSymbolsFile = true, + bool AssertIcuAssets = true, + bool AssertSymbolsFile = true +); diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BrowserRunOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BrowserRunOptions.cs new file mode 100644 index 0000000000000..98b8e9d0aef42 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BrowserRunOptions.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Threading.Tasks; +using Microsoft.Playwright; + +#nullable enable + +namespace Wasm.Build.Tests; + +public record BrowserRunOptions : RunOptions +{ + public string? TestScenario { get; init; } + + public BrowserRunOptions( + Configuration Configuration, + bool AOT = false, + RunHost Host = RunHost.DotnetRun, + bool DetectRuntimeFailures = true, + Dictionary? ServerEnvironment = null, + NameValueCollection? BrowserQueryString = null, + Action? OnConsoleMessage = null, + Action? OnServerMessage = null, + Action? OnErrorMessage = null, + string ExtraArgs = "", + string BrowserPath = "", + string Locale = "en-US", + int? ExpectedExitCode = 0, + string CustomBundleDir = "", + string? TestScenario = null + ) : base( + Configuration, + AOT, + Host, + DetectRuntimeFailures, + ServerEnvironment, + BrowserQueryString, + OnConsoleMessage, + OnServerMessage, + OnErrorMessage, + ExtraArgs, + BrowserPath, + Locale, + ExpectedExitCode, + CustomBundleDir + ) + { + this.TestScenario = TestScenario; + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs new file mode 100644 index 0000000000000..d573405986a9c --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +#nullable enable + +namespace Wasm.Build.Tests; + +public record BuildOptions : MSBuildOptions +{ + public BuildOptions( + bool IsPublish = false, + bool AOT = false, + NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, + string TargetFramework = BuildTestBase.DefaultTargetFramework, + GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, + string CustomIcuFile = "", + bool UseCache = true, + bool ExpectSuccess = true, + bool AssertAppBundle = true, + string Label = "", + bool WarnAsError = true, + RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, + IDictionary? ExtraBuildEnvironmentVariables = null, + string BootConfigFileName = "blazor.boot.json", + string NonDefaultFrameworkDir = "", + string ExtraMSBuildArgs = "" + ) : base( + IsPublish, + AOT, + ExpectedFileType, + TargetFramework, + GlobalizationMode, + CustomIcuFile, + UseCache, + ExpectSuccess, + AssertAppBundle, + Label, + WarnAsError, + RuntimeType, + ExtraBuildEnvironmentVariables, + BootConfigFileName, + NonDefaultFrameworkDir, + ExtraMSBuildArgs + ) + { + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildProduct.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildProduct.cs new file mode 100644 index 0000000000000..ba17af588d847 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildProduct.cs @@ -0,0 +1,13 @@ +// 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 record BuildResult( + string ProjectDir, + string LogFile, + bool Success, + string BuildOutput +); diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs new file mode 100644 index 0000000000000..24ae96b074581 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +#nullable enable + +namespace Wasm.Build.Tests; + +public abstract record MSBuildOptions +( + bool IsPublish, + bool AOT = false, + NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, + string TargetFramework = BuildTestBase.DefaultTargetFramework, + GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, + string CustomIcuFile = "", + bool UseCache = true, + bool ExpectSuccess = true, + bool AssertAppBundle = true, + string Label = "", + bool WarnAsError = true, + RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, + IDictionary? ExtraBuildEnvironmentVariables = null, + string BootConfigFileName = "blazor.boot.json", + string NonDefaultFrameworkDir = "", + string ExtraMSBuildArgs = "" +); diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/NativeFilesType.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/NativeFilesType.cs new file mode 100644 index 0000000000000..9709504974199 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/NativeFilesType.cs @@ -0,0 +1,9 @@ +// 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 enum NativeFilesType { FromRuntimePack, Relinked, AOT }; + diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs new file mode 100644 index 0000000000000..92516a9b3e7e2 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +#nullable enable + +namespace Wasm.Build.Tests; + +public record PublishOptions : MSBuildOptions +{ + public bool BuildOnlyAfterPublish { get; init; } + public bool ExpectRelinkDirWhenPublishing { get; init; } + + public PublishOptions( + bool IsPublish = true, + bool AOT = false, + NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, + string TargetFramework = BuildTestBase.DefaultTargetFramework, + GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, + string CustomIcuFile = "", + bool UseCache = true, + bool ExpectSuccess = true, + bool AssertAppBundle = true, + string Label = "", + bool WarnAsError = true, + RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, + IDictionary? ExtraBuildEnvironmentVariables = null, + string BootConfigFileName = "blazor.boot.json", + string NonDefaultFrameworkDir = "", + string ExtraMSBuildArgs = "", + bool BuildOnlyAfterPublish = true, + bool ExpectRelinkDirWhenPublishing = false + ) : base( + IsPublish, + AOT, + ExpectedFileType, + TargetFramework, + GlobalizationMode, + CustomIcuFile, + UseCache, + ExpectSuccess, + AssertAppBundle, + Label, + WarnAsError, + RuntimeType, + ExtraBuildEnvironmentVariables, + BootConfigFileName, + NonDefaultFrameworkDir, + ExtraMSBuildArgs + ) + { + this.IsPublish = IsPublish; + this.BuildOnlyAfterPublish = BuildOnlyAfterPublish; + this.ExpectRelinkDirWhenPublishing = ExpectRelinkDirWhenPublishing; + } +} \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/RunOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/RunOptions.cs new file mode 100644 index 0000000000000..df379e86db30c --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/RunOptions.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Threading.Tasks; +using Microsoft.Playwright; + +#nullable enable + +namespace Wasm.Build.Tests; +public abstract record RunOptions +( + Configuration Configuration, + bool AOT = false, + RunHost Host = RunHost.DotnetRun, + bool DetectRuntimeFailures = true, + + Dictionary? ServerEnvironment = null, + NameValueCollection? BrowserQueryString = null, + Action? OnConsoleMessage = null, + Action? OnServerMessage = null, + Action? OnErrorMessage = null, + string ExtraArgs = "", + string BrowserPath = "", + string Locale = "en-US", + int? ExpectedExitCode = 0, + string CustomBundleDir = "", + Func? ExecuteAfterLoaded = null +); + +public enum RunHost { DotnetRun, WebServer }; diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/SharedBuildPerTestClassFixture.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/SharedBuildPerTestClassFixture.cs new file mode 100644 index 0000000000000..c764ab6e419a8 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/SharedBuildPerTestClassFixture.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; + +#nullable enable + +// ToDo: should be common with Wasi.Build.Tests, copied here after Wasm.Build.Tests refactoring +namespace Wasm.Build.Tests +{ + public class SharedBuildPerTestClassFixture : IDisposable + { + public Dictionary _buildPaths = new(); + + public void CacheBuild(ProjectInfo buildArgs, BuildResult result) + { + if (result == null) + throw new ArgumentNullException(nameof(result)); + if (buildArgs == null) + throw new ArgumentNullException(nameof(buildArgs)); + _buildPaths.Add(buildArgs, result); + } + + public void RemoveFromCache(string buildPath, bool keepDir=true) + { + ProjectInfo? foundBuildArgs = _buildPaths.Where(kvp => kvp.Value.ProjectDir == buildPath).Select(kvp => kvp.Key).SingleOrDefault(); + if (foundBuildArgs is not null) + _buildPaths.Remove(foundBuildArgs); + + if (!keepDir) + RemoveDirectory(buildPath); + } + + public bool TryGetBuildFor(ProjectInfo buildArgs, [NotNullWhen(true)] out BuildResult? product) + => _buildPaths.TryGetValue(buildArgs, out product); + + public void Dispose() + { + Console.WriteLine ($"============== DELETING THE BUILDS ============="); + foreach (var kvp in _buildPaths.Values) + { + RemoveDirectory(kvp.ProjectDir); + } + } + + private void RemoveDirectory(string path) + { + if (EnvironmentVariables.SkipProjectCleanup == "1") + return; + + try + { + Directory.Delete(path, recursive: true); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Failed to delete '{path}' during test cleanup: {ex}"); + throw; + } + } + + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/TestAsset.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/TestAsset.cs new file mode 100644 index 0000000000000..4062b307cbc62 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/TestAsset.cs @@ -0,0 +1,7 @@ +public class TestAsset +{ + public string Name { get; init; } + public string RunnableProjectSubPath { get; init; } + public static readonly TestAsset WasmBasicTestApp = new() { Name = "WasmBasicTestApp", RunnableProjectSubPath = "App" }; + public static readonly TestAsset BlazorBasicTestApp = new() { Name = "BlazorBasicTestApp", RunnableProjectSubPath = "App" }; +} \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.cs deleted file mode 100644 index d0bd02a2289a5..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.cs +++ /dev/null @@ -1,31 +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.Collections.Generic; - -#nullable enable - -namespace Wasm.Build.Tests; - -public record BuildProjectOptions -( - Action? InitProject = null, - bool? DotnetWasmFromRuntimePack = null, - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string? CustomIcuFile = null, - 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 = BuildTestBase.DefaultTargetFramework, - string? MainJS = null, - bool IsBrowserProject = true, - IDictionary? ExtraBuildEnvironmentVariables = null, - string? BinFrameworkDir = null -); diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs b/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs index 31807b6d54bb7..375b77d350170 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildPublishTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Linq; -using Wasm.Build.NativeRebuild.Tests; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -14,7 +14,7 @@ namespace Wasm.Build.Tests { - public class BuildPublishTests : NativeRebuildTestsBase + public class BuildPublishTests : WasmTemplateTestsBase { public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -22,172 +22,97 @@ public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: true, config: "Debug")] - public void Wasm_CannotAOT_InDebug(BuildArgs buildArgs, RunHost _, string id) + [BuildAndRun(config: Configuration.Debug, aot: true)] + public void Wasm_CannotAOT_InDebug(Configuration config, bool aot) { - string projectName = GetTestProjectPath(prefix: "no_aot_in_debug", config: buildArgs.Config); - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - (string projectDir, string buildOutput) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: true, - CreateProject: true, - Publish: true, - ExpectSuccess: false - )); - - Console.WriteLine($"buildOutput={buildOutput}"); - + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "no_aot_in_debug"); + (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot, ExpectSuccess: false)); Assert.Contains("AOT is not supported in debug configuration", buildOutput); } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: false, config: "Release")] - [BuildAndRun(host: RunHost.Chrome, aot: false, config: "Debug")] - public void BuildThenPublishNoAOT(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release)] + [BuildAndRun(config: Configuration.Debug)] + public async Task BuildThenPublishNoAOT(Configuration config, bool aot) { - string projectName = GetTestProjectPath(prefix: "build_publish", config: buildArgs.Config); - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - // no relinking for build - bool relinked = false; - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: !relinked, - CreateProject: true, - Publish: false - )); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "build_publish"); + BuildProject(info, config); - Run(); + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); - - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); - - _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}"); + BrowserRunOptions runOptions = new(config, TestScenario: "DotnetRun"); + await RunForBuildWithDotnetRun(runOptions); - // relink by default for Release+publish - relinked = buildArgs.Config == "Release"; - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: !relinked, - CreateProject: false, - Publish: true, - UseCache: false)); - - Run(); - - void Run() => RunAndTestWasmApp( - buildArgs, buildDir: _projectDir, expectedExitCode: 42, - test: output => {}, - host: host, id: id); + PublishProject(info, config, new PublishOptions(UseCache: false)); + await RunForPublishWithWebServer(runOptions); } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: true, config: "Release")] - public void BuildThenPublishWithAOT(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public async Task BuildThenPublishWithAOT(Configuration config, bool aot) { - bool testUnicode = true; - string projectName = GetTestProjectPath( - prefix: "build_publish", config: buildArgs.Config, appendUnicode: testUnicode); - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - // no relinking for build - bool relinked = false; - (_, string output) = BuildProject(buildArgs, - id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: !relinked, - CreateProject: true, - Publish: false, - Label: "first_build")); - - BuildPaths paths = GetBuildPaths(buildArgs); - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: false); - - string mainDll = $"{buildArgs.ProjectName}.dll"; - var firstBuildStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "build_publish"); + + bool isPublish = false; + (_, string output) = BuildProject(info, config, new BuildOptions(Label: "first_build", AOT: aot)); + + BuildPaths paths = GetBuildPaths(config, forPublish: isPublish); + IDictionary pathsDict = + GetFilesTable(info.ProjectName, aot, paths, unchanged: false); + + string mainDll = $"{info.ProjectName}.dll"; + var firstBuildStat = StatFiles(pathsDict); Assert.False(firstBuildStat["pinvoke.o"].Exists); Assert.False(firstBuildStat[$"{mainDll}.bc"].Exists); + + CheckOutputForNativeBuild(expectAOT: false, expectRelinking: isPublish, info.ProjectName, output); - CheckOutputForNativeBuild(expectAOT: false, expectRelinking: relinked, buildArgs, output, testUnicode); - - Run(expectAOT: false); - - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); + BrowserRunOptions runOptions = new(config, TestScenario: "DotnetRun"); + await RunForBuildWithDotnetRun(runOptions); + File.Move(result!.LogFile, Path.ChangeExtension(result.LogFile!, ".first.binlog")); + _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}"); - Dictionary publishStat = new(); // relink by default for Release+publish - (_, output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: false, - CreateProject: false, - Publish: true, - UseCache: false, - Label: "first_publish")); - - publishStat = (Dictionary)_provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + isPublish = true; + (_, output) = PublishProject(info, config, new PublishOptions(Label: "first_publish", UseCache: false, AOT: aot)); + + // publish has different paths ("for-publish", not "for-build") + paths = GetBuildPaths(config, forPublish: isPublish); + pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: false); + IDictionary publishStat = StatFiles(pathsDict); Assert.True(publishStat["pinvoke.o"].Exists); Assert.True(publishStat[$"{mainDll}.bc"].Exists); - CheckOutputForNativeBuild(expectAOT: true, expectRelinking: false, buildArgs, output, testUnicode); - _provider.CompareStat(firstBuildStat, publishStat, pathsDict.Values); - - Run(expectAOT: true); + CheckOutputForNativeBuild(expectAOT: true, expectRelinking: isPublish, info.ProjectName, output); + + // source maps are created for build but not for publish, make sure CompareStat won't expect them in publish: + pathsDict["dotnet.js.map"] = (pathsDict["dotnet.js.map"].fullPath, unchanged: false); + pathsDict["dotnet.runtime.js.map"] = (pathsDict["dotnet.runtime.js.map"].fullPath, unchanged: false); + CompareStat(firstBuildStat, publishStat, pathsDict); + await RunForPublishWithWebServer(runOptions); // second build - (_, output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: !relinked, - CreateProject: true, - Publish: false, - Label: "second_build")); - var secondBuildStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - + isPublish = false; + (_, output) = BuildProject(info, config, new BuildOptions(Label: "second_build", UseCache: false, AOT: aot)); + var secondBuildStat = StatFiles(pathsDict); + // no relinking, or AOT - CheckOutputForNativeBuild(expectAOT: false, expectRelinking: false, buildArgs, output, testUnicode); + CheckOutputForNativeBuild(expectAOT: false, expectRelinking: isPublish, info.ProjectName, output); // no native files changed pathsDict.UpdateTo(unchanged: true); - _provider.CompareStat(publishStat, secondBuildStat, pathsDict.Values); - - void Run(bool expectAOT) => RunAndTestWasmApp( - buildArgs with { AOT = expectAOT }, - buildDir: _projectDir, expectedExitCode: 42, - host: host, id: id); + CompareStat(publishStat, secondBuildStat, pathsDict); } - void CheckOutputForNativeBuild(bool expectAOT, bool expectRelinking, BuildArgs buildArgs, string buildOutput, bool testUnicode) + void CheckOutputForNativeBuild(bool expectAOT, bool expectRelinking, string projectName, string buildOutput) { - if (testUnicode) - { - string projectNameCore = buildArgs.ProjectName.Replace(s_unicodeChars, ""); - TestUtils.AssertMatches(@$"{projectNameCore}\S+.dll -> {projectNameCore}\S+.dll.bc", buildOutput, contains: expectAOT); - TestUtils.AssertMatches(@$"{projectNameCore}\S+.dll.bc -> {projectNameCore}\S+.dll.o", buildOutput, contains: expectAOT); - } - else - { - TestUtils.AssertSubstring($"{buildArgs.ProjectName}.dll -> {buildArgs.ProjectName}.dll.bc", buildOutput, contains: expectAOT); - TestUtils.AssertSubstring($"{buildArgs.ProjectName}.dll.bc -> {buildArgs.ProjectName}.dll.o", buildOutput, contains: expectAOT); - } + TestUtils.AssertMatches(@$"{projectName}.dll -> {projectName}.dll.bc", buildOutput, contains: expectAOT); + TestUtils.AssertMatches(@$"{projectName}.dll.bc -> {projectName}.dll.o", buildOutput, contains: expectAOT); TestUtils.AssertMatches("pinvoke.c -> pinvoke.o", buildOutput, contains: expectRelinking || expectAOT); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 4efbbe711e39e..a8f36495f9032 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -48,11 +48,10 @@ 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 s_hostsForOSLocaleSensitiveTests = RunHost.Chrome; // FIXME: use an envvar to override this protected static int s_defaultPerTestTimeoutMs = s_isWindows ? 30 * 60 * 1000 : 15 * 60 * 1000; public static BuildEnvironment s_buildEnv; @@ -68,9 +67,9 @@ public static string GetNuGetConfigPathFor(string targetFramework) public TProvider GetProvider() where TProvider : ProjectProviderBase => (TProvider)_providerOfBaseType; - protected string? _projectDir + protected string _projectDir { - get => _providerOfBaseType.ProjectDir; + get => _providerOfBaseType.ProjectDir!; set => _providerOfBaseType.ProjectDir = value; } @@ -113,41 +112,37 @@ public BuildTestBase(ProjectProviderBase providerBase, ITestOutputHelper output, _providerOfBaseType = providerBase; } - public static IEnumerable> ConfigWithAOTData(bool aot, string? config = null, string? extraArgs = null) + public static IEnumerable> ConfigWithAOTData(bool aot, Configuration config = Configuration.Undefined) { - if (extraArgs == null) - extraArgs = string.Empty; - - if (config == null) + if (config == Configuration.Undefined) { return new IEnumerable[] { #if TEST_DEBUG_CONFIG_ALSO // list of each member data - for Debug+@aot - new object?[] { new BuildArgs("placeholder", "Debug", aot, "placeholder", extraArgs) }.AsEnumerable(), + new object?[] { Configuration.Debug, aot }.AsEnumerable(), #endif // list of each member data - for Release+@aot - new object?[] { new BuildArgs("placeholder", "Release", aot, "placeholder", extraArgs) }.AsEnumerable() + new object?[] { Configuration.Release, aot }.AsEnumerable() }.AsEnumerable(); } else { return new IEnumerable[] { - new object?[] { new BuildArgs("placeholder", config, aot, "placeholder", extraArgs) }.AsEnumerable() + new object?[] { config, aot }.AsEnumerable() }; } } public (CommandResult res, string logPath) BuildProjectWithoutAssert( - string id, - string config, - BuildProjectOptions buildProjectOptions, - params string[] extraArgs) + Configuration configuration, + string projectName, + MSBuildOptions buildOptions) { - string buildType = buildProjectOptions.Publish ? "publish" : "build"; - string logFileSuffix = buildProjectOptions.Label == null ? string.Empty : buildProjectOptions.Label.Replace(' ', '_') + "-"; - string logFilePath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-{logFileSuffix}{buildType}.binlog"); + string buildType = buildOptions.IsPublish ? "publish" : "build"; + string logFileSuffix = string.IsNullOrEmpty(buildOptions.Label) ? string.Empty : buildOptions.Label.Replace(' ', '_') + "-"; + string logFilePath = Path.Combine(_logPath, $"{projectName}-{logFileSuffix}{buildType}.binlog"); _testOutput.WriteLine($"{Environment.NewLine}** -------- {buildType} -------- **{Environment.NewLine}"); _testOutput.WriteLine($"Binlog path: {logFilePath}"); @@ -156,23 +151,23 @@ public BuildTestBase(ProjectProviderBase providerBase, ITestOutputHelper output, { buildType, $"-bl:{logFilePath}", - $"-p:Configuration={config}", + $"-p:Configuration={configuration}", "-nr:false" }; - commandLineArgs.AddRange(extraArgs); + commandLineArgs.AddRange(buildOptions.ExtraMSBuildArgs); - if (buildProjectOptions.Publish && buildProjectOptions.BuildOnlyAfterPublish) + if (buildOptions.IsPublish && buildOptions is PublishOptions po && po.BuildOnlyAfterPublish) commandLineArgs.Append("-p:WasmBuildOnlyAfterPublish=true"); using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); + .WithWorkingDirectory(_projectDir); cmd.WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .WithEnvironmentVariables(buildProjectOptions.ExtraBuildEnvironmentVariables); + .WithEnvironmentVariables(buildOptions.ExtraBuildEnvironmentVariables); if (UseWBTOverridePackTargets && s_buildEnv.IsWorkload) cmd.WithEnvironmentVariable("WBTOverrideRuntimePack", "true"); CommandResult res = cmd.ExecuteWithCapturedOutput(commandLineArgs.ToArray()); - if (buildProjectOptions.ExpectSuccess) + if (buildOptions.ExpectSuccess) res.EnsureSuccessful(); else if (res.ExitCode == 0) throw new XunitException($"Build should have failed, but it didn't. Process exited with exitCode : {res.ExitCode}"); @@ -219,163 +214,8 @@ private string GetBinlogMessageContext(TextNode node) return string.Empty; } - protected bool IsDotnetWasmFromRuntimePack(BuildArgs buildArgs) => - !(buildArgs.AOT || (buildArgs.Config == "Release" && IsUsingWorkloads)); - - protected string RunAndTestWasmApp(BuildArgs buildArgs, - RunHost host, - string id, - Action? test = null, - string? buildDir = null, - string? bundleDir = null, - int expectedExitCode = 0, - string? args = null, - Dictionary? envVars = null, - string targetFramework = DefaultTargetFramework, - string? extraXHarnessMonoArgs = null, - string? extraXHarnessArgs = null, - string jsRelativePath = "test-main.js", - string environmentLocale = DefaultEnvironmentLocale) - { - buildDir ??= _projectDir; - envVars ??= new(); - envVars["XHARNESS_DISABLE_COLORED_OUTPUT"] = "true"; - if (buildArgs.AOT) - { - envVars["MONO_LOG_LEVEL"] = "debug"; - envVars["MONO_LOG_MASK"] = "aot"; - } - - if (s_buildEnv.EnvVars != null) - { - foreach (var kvp in s_buildEnv.EnvVars) - envVars[kvp.Key] = kvp.Value; - } - - bundleDir ??= Path.Combine(GetBinDir(baseDir: buildDir, config: buildArgs.Config, targetFramework: targetFramework), "AppBundle"); - 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 = hostRunner.GetTestCommand(); - XHarnessArgsOptions options = new XHarnessArgsOptions(jsRelativePath, environmentLocale, host); - string xharnessArgs = s_isWindows ? hostRunner.GetXharnessArgsWindowsOS(options) : hostRunner.GetXharnessArgsOtherOS(options); - bool useWasmConsoleOutput = hostRunner.UseWasmConsoleOutput(); - - extraXHarnessArgs += " " + xharnessArgs; - - string testLogPath = Path.Combine(_logPath, host.ToString()); - string output = RunWithXHarness( - testCommand, - testLogPath, - buildArgs.ProjectName, - bundleDir, - _testOutput, - envVars: envVars, - expectedAppExitCode: expectedExitCode, - extraXHarnessArgs: extraXHarnessArgs, - appArgs: args, - extraXHarnessMonoArgs: extraXHarnessMonoArgs, - useWasmConsoleOutput: useWasmConsoleOutput - ); - - TestUtils.AssertSubstring("AOT: image 'System.Private.CoreLib' found.", output, contains: buildArgs.AOT); - - if (s_isWindows && buildArgs.ProjectName.Contains(s_unicodeChars)) - { - // unicode chars in output on Windows are decoded in unknown way, so finding utf8 string is more complicated - string projectNameCore = buildArgs.ProjectName.Replace(s_unicodeChars, ""); - TestUtils.AssertMatches(@$"AOT: image '{projectNameCore}\S+' found.", output, contains: buildArgs.AOT); - } - else - { - TestUtils.AssertSubstring($"AOT: image '{buildArgs.ProjectName}' found.", output, contains: buildArgs.AOT); - } - - if (test != null) - test(output); - - return output; - } - - protected static string RunWithXHarness(string testCommand, string testLogPath, string projectName, string bundleDir, - ITestOutputHelper _testOutput, IDictionary? envVars = null, - int expectedAppExitCode = 0, int xharnessExitCode = 0, string? extraXHarnessArgs = null, - string? appArgs = null, string? extraXHarnessMonoArgs = null, bool useWasmConsoleOutput = false) - { - _testOutput.WriteLine($"============== {testCommand} ============="); - Directory.CreateDirectory(testLogPath); - - StringBuilder args = new(); - args.Append(s_xharnessRunnerCommand); - args.Append($" {testCommand}"); - args.Append($" --app=."); - args.Append($" --output-directory={testLogPath}"); - args.Append($" --expected-exit-code={expectedAppExitCode}"); - args.Append($" {extraXHarnessArgs ?? string.Empty}"); - args.Append(" --browser-arg=--disable-gpu"); - args.Append(" --pageLoadStrategy=none"); - - // `/.dockerenv` - is to check if this is running in a codespace - if (File.Exists("/.dockerenv")) - args.Append(" --browser-arg=--no-sandbox"); - - args.Append(" -- "); - if (extraXHarnessMonoArgs != null) - { - args.Append($" {extraXHarnessMonoArgs}"); - } - // App arguments - if (envVars != null) - { - var setenv = string.Join(' ', envVars - .Where(ev => ev.Key != "PATH") - .Select(kvp => $"\"--setenv={kvp.Key}={kvp.Value}\"").ToArray()); - args.Append($" {setenv}"); - } - - args.Append($" --run {projectName}.dll"); - args.Append($" {appArgs ?? string.Empty}"); - - _testOutput.WriteLine(string.Empty); - _testOutput.WriteLine($"---------- Running with {testCommand} ---------"); - var (exitCode, output) = RunProcess(s_buildEnv.DotNet, _testOutput, - args: args.ToString(), - workingDir: bundleDir, - envVars: envVars, - label: testCommand, - timeoutMs: s_defaultPerTestTimeoutMs); - - File.WriteAllText(Path.Combine(testLogPath, $"xharness.log"), output); - if (useWasmConsoleOutput) - { - string wasmConsolePath = Path.Combine(testLogPath, "wasm-console.log"); - try - { - if (File.Exists(wasmConsolePath)) - output = File.ReadAllText(wasmConsolePath); - else - _testOutput.WriteLine($"Warning: Could not find {wasmConsolePath}. Ignoring."); - } - catch (IOException ioex) - { - _testOutput.WriteLine($"Warning: Could not read {wasmConsolePath}: {ioex}"); - } - } - - if (exitCode != xharnessExitCode) - { - _testOutput.WriteLine($"Exit code: {exitCode}"); - if (exitCode != expectedAppExitCode) - throw new XunitException($"[{testCommand}] Exit code, expected {expectedAppExitCode} but got {exitCode} for command: {args}"); - } - - return output; - } - [MemberNotNull(nameof(_projectDir), nameof(_logPath))] - protected void InitPaths(string id) + protected (string, string) InitPaths(string id) { if (_projectDir == null) _projectDir = Path.Combine(BuildEnvironment.TmpPath, id); @@ -387,10 +227,13 @@ protected void InitPaths(string id) Directory.CreateDirectory(_nugetPackagesDir!); Directory.CreateDirectory(_logPath); + return (_logPath, _nugetPackagesDir); } protected void InitProjectDir(string dir, bool addNuGetSourceForLocalPackages = true, string targetFramework = DefaultTargetFramework) { + if (Directory.Exists(dir)) + Directory.Delete(dir, recursive: true); Directory.CreateDirectory(dir); File.WriteAllText(Path.Combine(dir, "Directory.Build.props"), s_buildEnv.DirectoryBuildPropsContents); File.WriteAllText(Path.Combine(dir, "Directory.Build.targets"), s_buildEnv.DirectoryBuildTargetsContents); @@ -411,38 +254,6 @@ protected void InitProjectDir(string dir, bool addNuGetSourceForLocalPackages = } } - protected const string SimpleProjectTemplate = - @$" - - {DefaultTargetFramework} - browser-wasm - Exe - true - test-main.js - ##EXTRA_PROPERTIES## - - - ##EXTRA_ITEMS## - - ##INSERT_AT_END## - "; - - protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProperties = "", string extraItems = "", string insertAtEnd = "", string projectTemplate = SimpleProjectTemplate) - { - if (buildArgs.AOT) - { - extraProperties = $"{extraProperties}\ntrue"; - extraProperties += $"\n{s_isWindows}\n"; - } - - extraItems += ""; - - string projectContents = projectTemplate - .Replace("##EXTRA_PROPERTIES##", extraProperties) - .Replace("##EXTRA_ITEMS##", extraItems) - .Replace("##INSERT_AT_END##", insertAtEnd); - return buildArgs with { ProjectFileContents = projectContents }; - } protected static string GetNuGetConfigWithLocalPackagesPath(string templatePath, string localNuGetsPath) { @@ -453,153 +264,11 @@ protected static string GetNuGetConfigWithLocalPackagesPath(string templatePath, return contents.Replace(s_nugetInsertionTag, $@""); } - protected string GetBinDir(string config, string targetFramework = DefaultTargetFramework, string? baseDir = null) - { - var dir = baseDir ?? _projectDir; - Assert.NotNull(dir); - return Path.Combine(dir!, "bin", config, targetFramework, "browser-wasm"); - } - - protected string GetObjDir(string config, string targetFramework = DefaultTargetFramework, string? baseDir = null) - { - var dir = baseDir ?? _projectDir; - Assert.NotNull(dir); - return Path.Combine(dir!, "obj", config, targetFramework, "browser-wasm"); - } - - public static (int exitCode, string buildOutput) RunProcess(string path, - ITestOutputHelper _testOutput, - string args = "", - IDictionary? envVars = null, - string? workingDir = null, - string? label = null, - int? timeoutMs = null) - { - var t = RunProcessAsync(path, _testOutput, args, envVars, workingDir, label, timeoutMs); - t.Wait(); - return t.Result; - } - - public static async Task<(int exitCode, string buildOutput)> RunProcessAsync(string path, - ITestOutputHelper _testOutput, - string args = "", - IDictionary? envVars = null, - string? workingDir = null, - string? label = null, - int? timeoutMs = null) - { - _testOutput.WriteLine($"Running {path} {args}"); - _testOutput.WriteLine($"WorkingDirectory: {workingDir}"); - StringBuilder outputBuilder = new(); - object syncObj = new(); - - var processStartInfo = new ProcessStartInfo - { - FileName = path, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardError = true, - RedirectStandardOutput = true, - Arguments = args, - }; - - if (workingDir == null || !Directory.Exists(workingDir)) - throw new Exception($"Working directory {workingDir} not found"); - - if (workingDir != null) - processStartInfo.WorkingDirectory = workingDir; - - if (envVars != null) - { - if (envVars.Count > 0) - _testOutput.WriteLine("Setting environment variables for execution:"); - - foreach (KeyValuePair envVar in envVars) - { - processStartInfo.EnvironmentVariables[envVar.Key] = envVar.Value; - _testOutput.WriteLine($"\t{envVar.Key} = {envVar.Value}"); - } - - // runtime repo sets this, which interferes with the tests - processStartInfo.RemoveEnvironmentVariables("MSBuildSDKsPath"); - } - - Process process = new(); - process.StartInfo = processStartInfo; - process.EnableRaisingEvents = true; - - // AutoResetEvent resetEvent = new (false); - // process.Exited += (_, _) => { _testOutput.WriteLine ($"- exited called"); resetEvent.Set(); }; - - if (!process.Start()) - throw new ArgumentException("No process was started: process.Start() return false."); - - try - { - DataReceivedEventHandler logStdErr = (sender, e) => LogData($"[{label}-stderr]", e.Data); - DataReceivedEventHandler logStdOut = (sender, e) => LogData($"[{label}]", e.Data); - - process.ErrorDataReceived += logStdErr; - process.OutputDataReceived += logStdOut; - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - using CancellationTokenSource cts = new(); - cts.CancelAfter(timeoutMs ?? s_defaultPerTestTimeoutMs); - - await process.WaitForExitAsync(cts.Token); - - if (cts.IsCancellationRequested) - { - // process didn't exit - process.Kill(entireProcessTree: true); - lock (syncObj) - { - var lastLines = outputBuilder.ToString().Split('\r', '\n').TakeLast(20); - throw new XunitException($"Process timed out. Last 20 lines of output:{Environment.NewLine}{string.Join(Environment.NewLine, lastLines)}"); - } - } - - // this will ensure that all the async event handling has completed - // and should be called after process.WaitForExit(int) - // https://learn.microsoft.com/dotnet/api/system.diagnostics.process.waitforexit?view=net-5.0#System_Diagnostics_Process_WaitForExit_System_Int32_ - process.WaitForExit(); - - process.ErrorDataReceived -= logStdErr; - process.OutputDataReceived -= logStdOut; - process.CancelErrorRead(); - process.CancelOutputRead(); - - lock (syncObj) - { - var exitCode = process.ExitCode; - return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); - } - } - catch (Exception ex) - { - _testOutput.WriteLine($"-- exception -- {ex}"); - throw; - } - - void LogData(string label, string? message) - { - lock (syncObj) - { - if (message != null) - { - _testOutput.WriteLine($"{label} {message}"); - } - outputBuilder.AppendLine($"{label} {message}"); - } - } - } - - public static string AddItemsPropertiesToProject(string projectFile, string? extraProperties = null, string? extraItems = null, string? atTheEnd = null) + public static string AddItemsPropertiesToProject(string projectFile, string? extraProperties = null, string? extraItems = null, string? insertAtEnd = null) { if (!File.Exists(projectFile)) throw new Exception($"{projectFile} does not exist"); - if (extraProperties == null && extraItems == null && atTheEnd == null) + if (extraProperties == null && extraItems == null && insertAtEnd == null) return projectFile; XmlDocument doc = new(); @@ -620,10 +289,10 @@ public static string AddItemsPropertiesToProject(string projectFile, string? ext root.AppendChild(node); } - if (atTheEnd != null) + if (insertAtEnd != null) { XmlNode node = doc.CreateNode(XmlNodeType.DocumentFragment, "foo", null); - node.InnerXml = atTheEnd; + node.InnerXml = insertAtEnd; root.InsertAfter(node, root.LastChild); } @@ -640,15 +309,6 @@ public void Dispose() public static string GetRandomId() => TestUtils.FixupSymbolName(Path.GetRandomFileName()); - internal BuildPaths GetBuildPaths(BuildArgs buildArgs, bool forPublish = true) - { - string objDir = GetObjDir(buildArgs.Config); - string bundleDir = Path.Combine(GetBinDir(baseDir: _projectDir, config: buildArgs.Config), "AppBundle"); - string wasmDir = Path.Combine(objDir, "wasm", forPublish ? "for-publish" : "for-build"); - - return new BuildPaths(wasmDir, objDir, GetBinDir(buildArgs.Config), bundleDir); - } - protected static string GetSkiaSharpReferenceItems() => @" @@ -661,21 +321,5 @@ public static int Main() return 42; } }"; - - private static IHostRunner GetHostRunnerFromRunHost(RunHost host) => host switch - { - RunHost.V8 => new V8HostRunner(), - _ => new BrowserHostRunner(), - }; } - - public record BuildArgs(string ProjectName, - string Config, - bool AOT, - string Id, - string? ExtraBuildArgs, - string? ProjectFileContents=null); - public record BuildProduct(string ProjectDir, string LogFile, bool Result, string BuildOutput); - - public enum NativeFilesType { FromRuntimePack, Relinked, AOT }; } diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/AssertBundleOptionsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Common/AssertBundleOptionsBase.cs deleted file mode 100644 index bf5301a53c762..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Common/AssertBundleOptionsBase.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System.IO; - -namespace Wasm.Build.Tests; - -public abstract record AssertBundleOptionsBase( - string Config, - bool IsPublish, - string TargetFramework, - string BinFrameworkDir, - string? CustomIcuFile, - string BundleDirName = "wwwroot", - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string BootJsonFileName = "blazor.boot.json", - NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, - RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, - bool ExpectFingerprintOnDotnetJs = false, - bool ExpectSymbolsFile = true, - bool AssertIcuAssets = true, - bool AssertSymbolsFile = true) -{ - public bool DotnetWasmFromRuntimePack => ExpectedFileType == NativeFilesType.FromRuntimePack; - public bool AOT => ExpectedFileType == NativeFilesType.AOT; - public string BundleDir => Path.Combine(BinFrameworkDir, ".."); -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/AssertTestMainJsAppBundleOptions.cs b/src/mono/wasm/Wasm.Build.Tests/Common/AssertTestMainJsAppBundleOptions.cs deleted file mode 100644 index 2924281dbdc43..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Common/AssertTestMainJsAppBundleOptions.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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 record AssertTestMainJsAppBundleOptions( - string Config, - bool IsPublish, - string TargetFramework, - string BinFrameworkDir, - string? CustomIcuFile, - string ProjectName, - string MainJS, - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string BootJsonFileName = "blazor.boot.json", - NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, - RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, - bool ExpectFingerprintOnDotnetJs = false, - bool ExpectSymbolsFile = true, - bool AssertIcuAssets = true, - bool AssertSymbolsFile = true, - bool HasV8Script = false, - bool IsBrowserProject = true) - : AssertWasmSdkBundleOptions( - Config: Config, - IsPublish: IsPublish, - TargetFramework: TargetFramework, - BinFrameworkDir: BinFrameworkDir, - CustomIcuFile: CustomIcuFile, - GlobalizationMode: GlobalizationMode, - ExpectedFileType: ExpectedFileType, - RuntimeType: RuntimeType, - BootConfigFileName: BootJsonFileName, - ExpectFingerprintOnDotnetJs: ExpectFingerprintOnDotnetJs, - ExpectSymbolsFile: ExpectSymbolsFile, - AssertIcuAssets: AssertIcuAssets, - AssertSymbolsFile: AssertSymbolsFile) -{ -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/AssertWasmSdkBundleOptions.cs b/src/mono/wasm/Wasm.Build.Tests/Common/AssertWasmSdkBundleOptions.cs deleted file mode 100644 index e489c41f7b15d..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Common/AssertWasmSdkBundleOptions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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; - -// Identical to AssertBundleOptionsBase currently -public record AssertWasmSdkBundleOptions( - string Config, - bool IsPublish, - string TargetFramework, - string BinFrameworkDir, - string? CustomIcuFile, - GlobalizationMode GlobalizationMode = GlobalizationMode.Sharded, - string BootConfigFileName = "blazor.boot.json", - NativeFilesType ExpectedFileType = NativeFilesType.FromRuntimePack, - RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded, - bool ExpectFingerprintOnDotnetJs = false, - bool ExpectSymbolsFile = true, - bool AssertIcuAssets = true, - bool AssertSymbolsFile = true) - : AssertBundleOptionsBase( - Config: Config, - IsPublish: IsPublish, - TargetFramework: TargetFramework, - BinFrameworkDir: BinFrameworkDir, - CustomIcuFile: CustomIcuFile, - GlobalizationMode: GlobalizationMode, - ExpectedFileType: ExpectedFileType, - RuntimeType: RuntimeType, - BootJsonFileName: BootConfigFileName, - ExpectFingerprintOnDotnetJs: ExpectFingerprintOnDotnetJs, - ExpectSymbolsFile: ExpectSymbolsFile, - AssertIcuAssets: AssertIcuAssets, - AssertSymbolsFile: AssertSymbolsFile) -{} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildAndRunAttribute.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildAndRunAttribute.cs index 3ba21e075ce4a..609834bc6bf6a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildAndRunAttribute.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildAndRunAttribute.cs @@ -14,7 +14,7 @@ namespace Wasm.Build.Tests /// /// Example usage: /// [BuildAndRun(aot: true, parameters: new object[] { arg1, arg2 })] - /// public void Test(BuildArgs, arg1, arg2, RunHost, id) + /// public void Test(ProjectInfo, arg1, arg2, RunHost, id) /// [DataDiscoverer("Xunit.Sdk.DataDiscoverer", "xunit.core")] [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] @@ -22,26 +22,23 @@ public class BuildAndRunAttribute : DataAttribute { private readonly IEnumerable _data; - public BuildAndRunAttribute(BuildArgs buildArgs, RunHost host = RunHost.All, params object?[] parameters) + +#if TARGET_WASI + // remove when wasi is refectored and use Configuration + public BuildAndRunAttribute(bool aot=false, string? config=null, params object?[] parameters) { - _data = new IEnumerable[] - { - new object?[] { buildArgs }.AsEnumerable(), - } - .AsEnumerable() + _data = BuildTestBase.ConfigWithAOTData(aot, config) .Multiply(parameters) - .WithRunHosts(host) .UnwrapItemsAsArrays().ToList(); } - - public BuildAndRunAttribute(bool aot=false, RunHost host = RunHost.All, string? config=null, params object?[] parameters) +#else + public BuildAndRunAttribute(bool aot=false, Configuration config=Configuration.Undefined, params object?[] parameters) { _data = BuildTestBase.ConfigWithAOTData(aot, config) .Multiply(parameters) - .WithRunHosts(host) .UnwrapItemsAsArrays().ToList(); } - +#endif public override IEnumerable GetData(MethodInfo testMethod) => _data; } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildPaths.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildPaths.cs index 1affeed9a37bb..3f08f890eb383 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildPaths.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildPaths.cs @@ -4,4 +4,4 @@ #nullable enable namespace Wasm.Build.Tests; -public record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BundleDir); +public record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BinFrameworkDir); diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/Configuration.cs b/src/mono/wasm/Wasm.Build.Tests/Common/Configuration.cs new file mode 100644 index 0000000000000..151d21bbdcc6b --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Common/Configuration.cs @@ -0,0 +1,13 @@ +// 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 enum Configuration +{ + Release, + Debug, + Undefined +} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs index d928fc1e1b406..1e4df1407b692 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs @@ -23,6 +23,7 @@ internal static class EnvironmentVariables internal static readonly bool ShowBuildOutput = IsRunningOnCI || Environment.GetEnvironmentVariable("SHOW_BUILD_OUTPUT") is not null; internal static readonly bool UseWebcil = Environment.GetEnvironmentVariable("USE_WEBCIL_FOR_TESTS") is "true"; internal static readonly bool UseFingerprinting = Environment.GetEnvironmentVariable("USE_FINGERPRINTING_FOR_TESTS") is "true"; + internal static readonly bool UseFingerprintingDotnetJS = Environment.GetEnvironmentVariable("WASM_FINGERPRINT_DOTNET_JS") is "true"; internal static readonly string? SdkDirName = Environment.GetEnvironmentVariable("SDK_DIR_NAME"); internal static readonly string? WasiSdkPath = Environment.GetEnvironmentVariable("WASI_SDK_PATH"); internal static readonly bool WorkloadsTestPreviousVersions = Environment.GetEnvironmentVariable("WORKLOADS_TEST_PREVIOUS_VERSIONS") is "true"; diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/HelperExtensions.cs b/src/mono/wasm/Wasm.Build.Tests/Common/HelperExtensions.cs index 35023c6174c2f..130ddd5e81c4e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/HelperExtensions.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/HelperExtensions.cs @@ -61,49 +61,6 @@ public static class HelperExtensions public static IEnumerable> MultiplyWithSingleArgs(this IEnumerable> data, params object?[] arrayOfArgs) => data.SelectMany(row => arrayOfArgs.Select(argCol => row.Concat(new[] { argCol }))); - public static object?[] Enumerate(this RunHost host) - { - if (host == RunHost.None) - return Array.Empty(); - - var list = new List(); - foreach (var value in Enum.GetValues()) - { - if (value == RunHost.None) - continue; - - if (value == RunHost.V8 && OperatingSystem.IsWindows()) - { - // Don't run tests with V8 on windows - continue; - } - - // Ignore any combos like RunHost.All from Enum.GetValues - // by ignoring any @value that has more than 1 bit set - if (((int)value & ((int)value - 1)) != 0) - continue; - - if ((host & value) == value) - list.Add(value); - } - return list.ToArray(); - } - - public static IEnumerable> WithRunHosts(this IEnumerable> data, RunHost hosts) - { - IEnumerable hostsEnumerable = hosts.Enumerate(); - if (hosts == RunHost.None) - return data.Select(d => d.Append((object?) BuildTestBase.GetRandomId())); - - return data.SelectMany(d => - { - string runId = BuildTestBase.GetRandomId(); - return hostsEnumerable.Select(o => - d.Append((object?)o) - .Append((object?)runId)); - }); - } - public static void UpdateTo(this IDictionary dict, bool unchanged, params string[] filenames) { IEnumerable keys = filenames.Length == 0 ? dict.Keys.ToList() : filenames; diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/ProjectInfo.cs b/src/mono/wasm/Wasm.Build.Tests/Common/ProjectInfo.cs new file mode 100644 index 0000000000000..70fc3cbe426cf --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Common/ProjectInfo.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 record ProjectInfo( + // string Configuration, + // bool AOT, + string ProjectName, + string ProjectFilePath, + string LogPath, + string NugetDir +); diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/RunHost.cs b/src/mono/wasm/Wasm.Build.Tests/Common/RunHost.cs deleted file mode 100644 index 0aa1ae14ea812..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/Common/RunHost.cs +++ /dev/null @@ -1,19 +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; - -namespace Wasm.Build.Tests -{ - [Flags] - public enum RunHost - { - None = 0, - V8 = 1, - Chrome = 2, - Safari = 4, - Firefox = 8, - - All = V8 | Chrome//| Firefox//Safari - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/RunResult.cs b/src/mono/wasm/Wasm.Build.Tests/Common/RunResult.cs new file mode 100644 index 0000000000000..b947d92143836 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Common/RunResult.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Wasm.Build.Tests; + +public record RunResult( + int ExitCode, + IReadOnlyCollection TestOutput, + IReadOnlyCollection ConsoleOutput, + IReadOnlyCollection ServerOutput +); \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/Template.cs b/src/mono/wasm/Wasm.Build.Tests/Common/Template.cs new file mode 100644 index 0000000000000..b840205de0070 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Common/Template.cs @@ -0,0 +1,13 @@ +// 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 enum Template +{ + BlazorWasm, + WasmBrowser, + Wasi +} diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/TestUtils.cs b/src/mono/wasm/Wasm.Build.Tests/Common/TestUtils.cs index fdf88fecc9ee0..aed3fbc235c8b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/TestUtils.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/TestUtils.cs @@ -65,6 +65,14 @@ public static string FindSubDirIgnoringCase(string parentDir, string dirName) return first ?? Path.Combine(parentDir, dirName); } + public static void AssertSubstring(string substring, IReadOnlyCollection full, bool contains) + { + if (contains) + Assert.Contains(full, m => m.Contains(substring)); + else + Assert.All(full, m => Assert.DoesNotContain(substring, m)); + } + public static void AssertSubstring(string substring, string full, bool contains) { if (contains) diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs b/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs index b6d087a013575..54152700de989 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs @@ -13,7 +13,7 @@ internal static class Utils { - public static void DirectoryCopy(string sourceDirName, string destDirName, Func? predicate=null, bool copySubDirs=true, bool silent=false, ITestOutputHelper? testOutput = null) + public static void DirectoryCopy(string sourceDirName, string destDirName, Func? predicate=null, bool copySubDirs=true, bool silent=false, ITestOutputHelper? testOutput = null, bool overwrite = false) { // Get the subdirectories for the specified directory. DirectoryInfo dir = new DirectoryInfo(sourceDirName); @@ -45,7 +45,7 @@ public static void DirectoryCopy(string sourceDirName, string destDirName, Func< string tempPath = Path.Combine(destDirName, file.Name); if (!silent) testOutput?.WriteLine($"Copying {fullPath} to {tempPath}"); - file.CopyTo(tempPath, false); + file.CopyTo(tempPath, overwrite); } // If copying subdirectories, copy them and their contents to new location. diff --git a/src/mono/wasm/Wasm.Build.Tests/ConfigSrcTests.cs b/src/mono/wasm/Wasm.Build.Tests/ConfigSrcTests.cs deleted file mode 100644 index e971485b91ef4..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/ConfigSrcTests.cs +++ /dev/null @@ -1,37 +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.IO; -using Xunit; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests; - -public class ConfigSrcTests : TestMainJsTestBase -{ - public ConfigSrcTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) - { } - - // NOTE: port number determinizes dynamically, so could not generate absolute URI - [Theory] - [BuildAndRun(host: RunHost.V8)] - public void ConfigSrcAbsolutePath(BuildArgs buildArgs, RunHost host, string id) - { - buildArgs = buildArgs with { ProjectName = $"configsrcabsolute_{buildArgs.Config}_{buildArgs.AOT}" }; - buildArgs = ExpandBuildArgs(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: IsDotnetWasmFromRuntimePack(buildArgs))); - - string binDir = GetBinDir(baseDir: _projectDir!, config: buildArgs.Config); - string bundleDir = Path.Combine(binDir, "AppBundle"); - string configSrc = Path.GetFullPath(Path.Combine(bundleDir, "_framework", "blazor.boot.json")); - - RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id, extraXHarnessMonoArgs: $"--config-src=\"{configSrc}\""); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/DebugLevelTests.cs b/src/mono/wasm/Wasm.Build.Tests/DebugLevelTests.cs index 8fc90b9376ec5..47d1bc3c0426b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/DebugLevelTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/DebugLevelTests.cs @@ -8,118 +8,108 @@ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; -using Wasm.Build.Tests.TestAppScenarios; #nullable enable namespace Wasm.Build.Tests; -// ToDo: fix to be based on WasmTemplateTestBase -public class DebugLevelTests : AppTestBase +public class DebugLevelTests : WasmTemplateTestsBase { public DebugLevelTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - private void AssertDebugLevel(string result, int value) - => Assert.Contains($"WasmDebugLevel: {value}", result); + private void AssertDebugLevel(IReadOnlyCollection result, int value) + => Assert.Contains(result, m => m.Contains($"WasmDebugLevel: {value}")); - private BuildProjectOptions GetProjectOptions(bool isPublish = false) => - new BuildProjectOptions( - DotnetWasmFromRuntimePack: !isPublish, - CreateProject: false, - MainJS: "main.js", - HasV8Script: false, - Publish: isPublish, - AssertAppBundle: false, - UseCache: false - ); - - private string BuildPublishProject(string projectName, string config, bool isPublish = false, params string[] extraArgs) + [Theory] + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task BuildWithDefaultLevel(Configuration configuration) { - var buildArgs = new BuildArgs(projectName, config, false, projectName, null); - buildArgs = ExpandBuildArgs(buildArgs); - (string _, string output) = BuildTemplateProject(buildArgs, - buildArgs.Id, - GetProjectOptions(isPublish), - extraArgs: extraArgs + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_BuildWithDefaultLevel" ); - return output; + BuildProject(info, configuration); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForBuildWithDotnetRun(options); + AssertDebugLevel(result.TestOutput, -1); } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task BuildWithDefaultLevel(string configuration) + [InlineData(Configuration.Debug, 1)] + [InlineData(Configuration.Release, 1)] + [InlineData(Configuration.Debug, 0)] + [InlineData(Configuration.Release, 0)] + public async Task BuildWithExplicitValue(Configuration configuration, int debugLevel) { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_BuildWithDefaultLevel_{configuration}", "App"); - BuildPublishProject(projectFile, configuration); - - string result = await RunBuiltBrowserApp(configuration, projectFile, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, -1); + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_BuildWithExplicitValue" + ); + BuildProject(info, configuration, new BuildOptions(ExtraMSBuildArgs: $"-p:WasmDebugLevel={debugLevel}")); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForBuildWithDotnetRun(options); + AssertDebugLevel(result.TestOutput, debugLevel); } [Theory] - [InlineData("Debug", 1)] - [InlineData("Release", 1)] - [InlineData("Debug", 0)] - [InlineData("Release", 0)] - public async Task BuildWithExplicitValue(string configuration, int debugLevel) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task PublishWithDefaultLevel(Configuration configuration) { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_BuildWithExplicitValue_{configuration}", "App"); - BuildPublishProject(projectFile, configuration, extraArgs: $"-p:WasmDebugLevel={debugLevel}"); - - string result = await RunBuiltBrowserApp(configuration, projectFile, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, debugLevel); + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_PublishWithDefaultLevel" + ); + PublishProject(info, configuration); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForPublishWithWebServer(options); + AssertDebugLevel(result.TestOutput, 0); } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task PublishWithDefaultLevel(string configuration) + [InlineData(Configuration.Debug, 1)] + [InlineData(Configuration.Release, 1)] + [InlineData(Configuration.Debug, -1)] + [InlineData(Configuration.Release, -1)] + public async Task PublishWithExplicitValue(Configuration configuration, int debugLevel) { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_PublishWithDefaultLevel_{configuration}", "App"); - BuildPublishProject(projectFile, configuration, isPublish: true); - - string result = await RunPublishedBrowserApp(configuration, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, 0); + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_PublishWithExplicitValue" + ); + PublishProject(info, configuration, new PublishOptions(ExtraMSBuildArgs: $"-p:WasmDebugLevel={debugLevel}")); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForPublishWithWebServer(options); + AssertDebugLevel(result.TestOutput, debugLevel); } - [Theory] - [InlineData("Debug", 1)] - [InlineData("Release", 1)] - [InlineData("Debug", -1)] - [InlineData("Release", -1)] - public async Task PublishWithExplicitValue(string configuration, int debugLevel) - { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_PublishWithExplicitValue_{configuration}", "App"); - BuildPublishProject(projectFile, configuration, isPublish: true, extraArgs: $"-p:WasmDebugLevel={debugLevel}"); - - string result = await RunBuiltBrowserApp(configuration, projectFile, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, debugLevel); - } - [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task PublishWithDefaultLevelAndPdbs(string configuration) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task PublishWithDefaultLevelAndPdbs(Configuration configuration) { - string testAssetName = "WasmBasicTestApp"; - string projectFile = $"{testAssetName}.csproj"; - CopyTestAsset(testAssetName, $"DebugLevelTests_PublishWithDefaultLevelAndPdbs_{configuration}", "App"); - BuildPublishProject(projectFile, configuration, isPublish: true, extraArgs: $"-p:CopyOutputSymbolsToPublishDirectory=true"); - - var result = await RunPublishedBrowserApp(configuration, testScenario: "DebugLevelTest"); - AssertDebugLevel(result, -1); + ProjectInfo info = CopyTestAsset( + configuration, + aot: false, + asset: TestAsset.WasmBasicTestApp, + idPrefix: "DebugLevelTests_PublishWithDefaultLevelAndPdbs" + ); + PublishProject(info, configuration, new PublishOptions(ExtraMSBuildArgs: $"-p:CopyOutputSymbolsToPublishDirectory=true")); + BrowserRunOptions options = new(configuration, TestScenario: "DebugLevelTest", ExpectedExitCode: 42); + RunResult result = await RunForPublishWithWebServer(options); + AssertDebugLevel(result.TestOutput, -1); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs b/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs new file mode 100644 index 0000000000000..8f5dfcf0d9b7e --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests +{ + public class DllImportTests : PInvokeTableGeneratorTestsBase + { + public DllImportTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Theory] + [BuildAndRun(aot: false)] + public async void NativeLibraryWithVariadicFunctions(Configuration config, bool aot) + { + ProjectInfo info = PrepareProjectForVariadicFunction(config, aot, "variadic"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "VariadicFunctions.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); + Assert.Matches("warning.*native function.*sum.*varargs", output); + Assert.Contains("System.Int32 sum_one(System.Int32)", output); + Assert.Contains("System.Int32 sum_two(System.Int32, System.Int32)", output); + Assert.Contains("System.Int32 sum_three(System.Int32, System.Int32, System.Int32)", output); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); + } + + [Theory] + [BuildAndRun()] + public async void DllImportWithFunctionPointersCompilesWithoutWarning(Configuration config, bool aot) + { + ProjectInfo info = PrepareProjectForVariadicFunction(config, aot, "fnptr"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "DllImportNoWarning.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); + + Assert.DoesNotMatch("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); + Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); + } + + [Theory] + [BuildAndRun()] + public async void DllImportWithFunctionPointers_ForVariadicFunction_CompilesWithWarning(Configuration config, bool aot) + { + ProjectInfo info = PrepareProjectForVariadicFunction(config, aot, "fnptr_variadic"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "DllImportWarning.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); + + Assert.DoesNotMatch("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); + Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); + } + + [Theory] + [BuildAndRun()] + public async void DllImportWithFunctionPointers_WarningsAsMessages(Configuration config, bool aot) + { + string extraProperties = "$(MSBuildWarningsAsMessage);WASM0001"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "fnptr", extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "FunctionPointers.cs")); + + string output = PublishForVariadicFunctionTests(info, config, aot); + Assert.DoesNotContain("warning WASM0001", output); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); + } + + [Theory] + [BuildAndRun()] + public void UnmanagedCallback_WithFunctionPointers_CompilesWithWarnings(Configuration config, bool aot) + { + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "cb_fnptr"); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "FunctionPointers.cs")); + UpdateFile(programRelativePath, new Dictionary { { "[DllImport(\"someting\")]", "[UnmanagedCallersOnly]" } }); + string output = PublishForVariadicFunctionTests(info, config, aot); + Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*Test::SomeFunction1.*because.*function\\spointer", output); + } + + [Theory] + [BuildAndRun(parameters: new object[] { new object[] { + "with-hyphen", + "with#hash-and-hyphen", + "with.per.iod", + "with🚀unicode#" + } })] + public async void CallIntoLibrariesWithNonAlphanumericCharactersInTheirNames(Configuration config, bool aot, string[] libraryNames) + { + var extraItems = @""; + string extraProperties = aot ? string.Empty : "true"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "abi", extraItems: extraItems, extraProperties: extraProperties); + + int baseArg = 10; + GenerateSourceFiles(_projectDir, baseArg); + bool isPublish = aot; + (_, string output) = isPublish ? + PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true): + BuildProject(info, config, new BuildOptions(AOT: aot), isNativeBuild: true); + + var runOptions = new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 42); + RunResult result = isPublish ? await RunForPublishWithWebServer(runOptions) : await RunForBuildWithDotnetRun(runOptions); + + for (int i = 0; i < libraryNames.Length; i ++) + { + Assert.Contains($"square_{i}: {(i + baseArg) * (i + baseArg)}", result.TestOutput); + } + + void GenerateSourceFiles(string outputPath, int baseArg) + { + StringBuilder csBuilder = new($@" + using System; + using System.Runtime.InteropServices; + "); + + StringBuilder dllImportsBuilder = new(); + for (int i = 0; i < libraryNames.Length; i ++) + { + dllImportsBuilder.AppendLine($"[DllImport(\"{libraryNames[i]}\")] static extern int square_{i}(int x);"); + csBuilder.AppendLine($@"Console.WriteLine($""TestOutput -> square_{i}: {{square_{i}({i + baseArg})}}"");"); + + string nativeCode = $@" + #include + + int square_{i}(int x) + {{ + return x * x; + }}"; + File.WriteAllText(Path.Combine(outputPath, $"{libraryNames[i]}.c"), nativeCode); + } + + csBuilder.AppendLine("return 42;"); + csBuilder.Append(dllImportsBuilder); + + UpdateFile(Path.Combine("Common", "Program.cs"), csBuilder.ToString()); + } + } + + private ProjectInfo PrepareProjectForVariadicFunction(Configuration config, bool aot, string prefix, string extraProperties = "") + { + string objectFilename = "variadic.o"; + extraProperties += "true<_WasmDevel>true"; + string extraItems = $""; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraItems: extraItems, extraProperties: extraProperties); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", objectFilename), Path.Combine(_projectDir, objectFilename)); + return info; + } + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DownloadThenInitTests.cs b/src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs similarity index 67% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DownloadThenInitTests.cs rename to src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs index aaa2df9b59655..6d8f0a6167602 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DownloadThenInitTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/DownloadThenInitTests.cs @@ -10,9 +10,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class DownloadThenInitTests : AppTestBase +public class DownloadThenInitTests : WasmTemplateTestsBase { public DownloadThenInitTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -20,16 +20,16 @@ public DownloadThenInitTests(ITestOutputHelper output, SharedBuildPerTestClassFi } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task NoResourcesReFetchedAfterDownloadFinished(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task NoResourcesReFetchedAfterDownloadFinished(Configuration config) { - CopyTestAsset("WasmBasicTestApp", "DownloadThenInitTests", "App"); - BuildProject(config); - - var result = await RunSdkStyleAppForBuild(new(Configuration: config, TestScenario: "DownloadThenInit")); + ProjectInfo info = CopyTestAsset(config, aot: false, TestAsset.WasmBasicTestApp, "DownloadThenInitTests"); + BuildProject(info, config); + BrowserRunOptions options = new(config, TestScenario: "DownloadThenInit"); + RunResult result = await RunForBuildWithDotnetRun(options); var resultTestOutput = result.TestOutput.ToList(); - int index = resultTestOutput.FindIndex(s => s == "download finished"); + int index = resultTestOutput.FindIndex(s => s.Contains("download finished")); Assert.True(index > 0); // number of fetched resources cannot be 0 var afterDownload = resultTestOutput.Skip(index + 1).Where(s => s.StartsWith("fetching")).ToList(); if (afterDownload.Count > 0) diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs deleted file mode 100644 index a27e9be892908..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/BrowserHostRunner.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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; -using System.IO; - -public class BrowserHostRunner : IHostRunner -{ - private static string? s_binaryPathArg; - private static string BinaryPathArg - { - get - { - if (s_binaryPathArg is null) - { - if (!string.IsNullOrEmpty(EnvironmentVariables.ChromePathForTests)) - { - if (!File.Exists(EnvironmentVariables.ChromePathForTests)) - throw new Exception($"Cannot find CHROME_PATH_FOR_TESTS={EnvironmentVariables.ChromePathForTests}"); - s_binaryPathArg = $" --browser-path=\"{EnvironmentVariables.ChromePathForTests}\""; - } - else - { - s_binaryPathArg = ""; - } - } - return s_binaryPathArg; - } - } - - - 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 {BinaryPathArg}"; // Windows: chrome.exe --lang=locale - public string GetXharnessArgsOtherOS(XHarnessArgsOptions options) => $"-v trace -b {options.host} --locale={options.environmentLocale} --web-server-use-cop {BinaryPathArg}"; // 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 deleted file mode 100644 index 9e41c5fa76616..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/IHostRunner.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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 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/V8HostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs deleted file mode 100644 index 4153a7326c441..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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; -using System.IO; -using System.Runtime.InteropServices; - -public class V8HostRunner : IHostRunner -{ - private static string? s_binaryPathArg; - private static string BinaryPathArg - { - get - { - if (s_binaryPathArg is null) - { - if (!string.IsNullOrEmpty(EnvironmentVariables.V8PathForTests)) - { - if (!File.Exists(EnvironmentVariables.V8PathForTests)) - throw new Exception($"Cannot find V8_PATH_FOR_TESTS={EnvironmentVariables.V8PathForTests}"); - s_binaryPathArg += $" --js-engine-path=\"{EnvironmentVariables.V8PathForTests}\""; - } - else - { - s_binaryPathArg = ""; - } - } - return s_binaryPathArg; - } - } - - private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace --engine-arg=--module {BinaryPathArg}"; - - 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 02abd96db46c2..e3ecb5a48717d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -19,15 +19,14 @@ public class IcuShardingTests : IcuTestsBase public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable IcuExpectedAndMissingCustomShardTestData(string config) => - from templateType in templateTypes - from aot in boolOptions + public static IEnumerable IcuExpectedAndMissingCustomShardTestData(Configuration config) => + from aot in boolOptions from onlyPredefinedCultures in boolOptions // isOnlyPredefinedCultures = true fails with wasmbrowser: https://github.com/dotnet/runtime/issues/108272 - where !(onlyPredefinedCultures && templateType == "wasmbrowser") - select new object[] { config, templateType, aot, CustomIcuPath, s_customIcuTestedLocales, onlyPredefinedCultures }; + where !(onlyPredefinedCultures) + select new object[] { config, aot, CustomIcuPath, s_customIcuTestedLocales, onlyPredefinedCultures }; - public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(string config) + public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData(Configuration config) { var locales = new Dictionary { @@ -37,16 +36,16 @@ public static IEnumerable IcuExpectedAndMissingAutomaticShardTestData( }; return from aot in boolOptions from locale in locales - select new object[] { config, "wasmbrowser", aot, locale.Key, locale.Value }; + select new object[] { config, aot, locale.Key, locale.Value }; } [Theory] - [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { "Release" })] - public async Task CustomIcuShard(string config, string templateType, bool aot, string customIcuPath, string customLocales, bool onlyPredefinedCultures) => - await TestIcuShards(config, templateType, aot, customIcuPath, customLocales, GlobalizationMode.Custom, onlyPredefinedCultures); + [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { Configuration.Release })] + public async Task CustomIcuShard(Configuration config, bool aot, string customIcuPath, string customLocales, bool onlyPredefinedCultures) => + await TestIcuShards(config, Template.WasmBrowser, aot, customIcuPath, customLocales, GlobalizationMode.Custom, onlyPredefinedCultures); [Theory] - [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { "Release" })] - public async Task AutomaticShardSelectionDependingOnEnvLocale(string config, string templateType, bool aot, string environmentLocale, string testedLocales) => - await BuildAndRunIcuTest(config, templateType, aot, testedLocales, GlobalizationMode.Sharded, language: environmentLocale); + [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { Configuration.Release })] + public async Task AutomaticShardSelectionDependingOnEnvLocale(Configuration config, bool aot, string environmentLocale, string testedLocales) => + await PublishAndRunIcuTest(config, Template.WasmBrowser, aot, testedLocales, GlobalizationMode.Sharded, locale: environmentLocale); } diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs index 8f7e8ad459332..5e28d2c0627a1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs @@ -19,7 +19,7 @@ public class IcuShardingTests2 : IcuTestsBase public IcuShardingTests2(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable IcuExpectedAndMissingShardFromRuntimePackTestData(string config) + public static IEnumerable IcuExpectedAndMissingShardFromRuntimePackTestData(Configuration config) { var locales = new Dictionary { @@ -31,14 +31,13 @@ public static IEnumerable IcuExpectedAndMissingShardFromRuntimePackTes { "icudt_no_CJK.dat", GetNocjkTestedLocales() } }; return - from templateType in templateTypes from aot in boolOptions from locale in locales - select new object[] { config, templateType, aot, locale.Key, locale.Value }; + select new object[] { config, aot, locale.Key, locale.Value }; } [Theory] - [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { "Release" })] - public async Task DefaultAvailableIcuShardsFromRuntimePack(string config, string templateType, bool aot, string shardName, string testedLocales) => - await TestIcuShards(config, templateType, aot, shardName, testedLocales, GlobalizationMode.Custom); + [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { Configuration.Release })] + public async Task DefaultAvailableIcuShardsFromRuntimePack(Configuration config, bool aot, string shardName, string testedLocales) => + await TestIcuShards(config, Template.WasmBrowser, aot, shardName, testedLocales, GlobalizationMode.Custom); } \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs index d3f808c87a514..825f00e3b9b6f 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs @@ -19,13 +19,12 @@ public class IcuTests : IcuTestsBase public IcuTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable FullIcuWithICustomIcuTestData(string config) => - from templateType in templateTypes - from aot in boolOptions + public static IEnumerable FullIcuWithICustomIcuTestData(Configuration config) => + from aot in boolOptions from fullIcu in boolOptions - select new object[] { config, templateType, aot, fullIcu }; + select new object[] { config, aot, fullIcu }; - public static IEnumerable FullIcuWithInvariantTestData(string config) + public static IEnumerable FullIcuWithInvariantTestData(Configuration config) { var locales = new object[][] { @@ -35,31 +34,29 @@ public static IEnumerable FullIcuWithInvariantTestData(string config) new object[] { false, false, GetEfigsTestedLocales() }, new object[] { false, true, s_fullIcuTestedLocales } }; - return from templateType in templateTypes - from aot in boolOptions + return from aot in boolOptions from locale in locales - select new object[] { config, templateType, aot, locale[0], locale[1], locale[2] }; + select new object[] { config, aot, locale[0], locale[1], locale[2] }; } - public static IEnumerable IncorrectIcuTestData(string config) + public static IEnumerable IncorrectIcuTestData(Configuration config) { var customFiles = new Dictionary { { "icudtNonExisting.dat", true }, { "incorrectName.dat", false } }; - return from templateType in templateTypes - from customFile in customFiles - select new object[] { config, templateType, customFile.Key, customFile.Value }; + return from customFile in customFiles + select new object[] { config, customFile.Key, customFile.Value }; } [Theory] - [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { "Release" })] - public async Task FullIcuFromRuntimePackWithInvariant(string config, string templateType, bool aot, bool invariant, bool fullIcu, string testedLocales) => - await BuildAndRunIcuTest( + [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { Configuration.Release })] + public async Task FullIcuFromRuntimePackWithInvariant(Configuration config=Configuration.Release, bool aot=false, bool invariant=true, bool fullIcu=true, string testedLocales="Array.Empty()") => + await PublishAndRunIcuTest( config, - templateType, + Template.WasmBrowser, aot, testedLocales, globalizationMode: invariant ? GlobalizationMode.Invariant : fullIcu ? GlobalizationMode.FullIcu : GlobalizationMode.Sharded, @@ -68,38 +65,35 @@ await BuildAndRunIcuTest( $"{invariant}{fullIcu}{aot}"); [Theory] - [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { "Release" })] - public async Task FullIcuFromRuntimePackWithCustomIcu(string config, string templateType, bool aot, bool fullIcu) + [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { Configuration.Release })] + public async Task FullIcuFromRuntimePackWithCustomIcu(Configuration config, bool aot, bool fullIcu) { - bool isBrowser = templateType == "wasmbrowser"; - string customIcuProperty = isBrowser ? "BlazorIcuDataFileName" : "WasmIcuDataFileName"; - string fullIcuProperty = isBrowser ? "BlazorWebAssemblyLoadAllGlobalizationData" : "WasmIncludeFullIcuData"; + string customIcuProperty = "BlazorIcuDataFileName"; + string fullIcuProperty = "BlazorWebAssemblyLoadAllGlobalizationData"; string extraProperties = $"<{customIcuProperty}>{CustomIcuPath}<{fullIcuProperty}>{fullIcu}{aot}"; string testedLocales = fullIcu ? s_fullIcuTestedLocales : s_customIcuTestedLocales; GlobalizationMode globalizationMode = fullIcu ? GlobalizationMode.FullIcu : GlobalizationMode.Custom; string customIcuFile = fullIcu ? "" : CustomIcuPath; - string output = await BuildAndRunIcuTest(config, templateType, aot, testedLocales, globalizationMode, extraProperties, icuFileName: customIcuFile); + string output = await PublishAndRunIcuTest(config, Template.WasmBrowser, aot, testedLocales, globalizationMode, extraProperties, icuFileName: customIcuFile); if (fullIcu) Assert.Contains($"$({customIcuProperty}) has no effect when $({fullIcuProperty}) is set to true.", output); } [Theory] - [MemberData(nameof(IncorrectIcuTestData), parameters: new object[] { "Release" })] - public void NonExistingCustomFileAssertError(string config, string templateType, string customIcu, bool isFilenameFormCorrect) + [MemberData(nameof(IncorrectIcuTestData), parameters: new object[] { Configuration.Release })] + public void NonExistingCustomFileAssertError(Configuration config, string customIcu, bool isFilenameFormCorrect) { string customIcuProperty = "BlazorIcuDataFileName"; string extraProperties = $"<{customIcuProperty}>{customIcu}"; - (BuildArgs buildArgs, string projectFile) = CreateIcuProject( - config, templateType, aot: false, "Array.Empty()", extraProperties); - string output = BuildIcuTest( - buildArgs, - GlobalizationMode.Custom, - customIcu, - expectSuccess: false, - assertAppBundle: false); - + ProjectInfo info = CreateIcuProject(config, Template.WasmBrowser, aot: false, "Array.Empty()", extraProperties); + (string _, string output) = BuildProject(info, config, new BuildOptions( + GlobalizationMode: GlobalizationMode.Custom, + CustomIcuFile: customIcu, + ExpectSuccess: false, + AssertAppBundle: false + )); if (isFilenameFormCorrect) { Assert.Contains($"Could not find $({customIcuProperty})={customIcu}, or when used as a path relative to the runtime pack", output); diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs index 25a03ad781dce..1bf7e100314e5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs @@ -106,92 +106,59 @@ protected string GetProgramText(string testedLocales, bool onlyPredefinedCulture public record Locale(string Code, string? SundayName); "; - protected async Task TestIcuShards(string config, string templateType, bool aot, string shardName, string testedLocales, GlobalizationMode globalizationMode, bool onlyPredefinedCultures=false) + protected async Task TestIcuShards(Configuration config, Template templateType, bool aot, string shardName, string testedLocales, GlobalizationMode globalizationMode, bool onlyPredefinedCultures=false) { string icuProperty = "BlazorIcuDataFileName"; // https://github.com/dotnet/runtime/issues/94133 // by default, we remove resource strings from an app. ICU tests are checking exception messages contents -> resource string keys are not enough string extraProperties = $"<{icuProperty}>{shardName}false{aot}"; if (onlyPredefinedCultures) extraProperties = $"{extraProperties}true"; - await BuildAndRunIcuTest(config, templateType, aot, testedLocales, globalizationMode, extraProperties, onlyPredefinedCultures, icuFileName: shardName); + await PublishAndRunIcuTest(config, templateType, aot, testedLocales, globalizationMode, extraProperties, onlyPredefinedCultures, icuFileName: shardName); } - protected (BuildArgs buildArgs, string projectFile) CreateIcuProject( - string config, - string templateType, + protected ProjectInfo CreateIcuProject( + Configuration config, + Template templateType, bool aot, string testedLocales, string extraProperties = "", bool onlyPredefinedCultures=false) { - string id = $"icu_{config}_{aot}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, templateType); - string projectDirectory = Path.GetDirectoryName(projectFile)!; - string projectName = Path.GetFileNameWithoutExtension(projectFile); - var buildArgs = new BuildArgs(projectName, config, aot, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - + ProjectInfo info = CreateWasmTemplateProject(templateType, config, aot, "icu", extraProperties: extraProperties); + string projectDirectory = Path.GetDirectoryName(info.ProjectFilePath)!; string programPath = Path.Combine(projectDirectory, "Program.cs"); string programText = GetProgramText(testedLocales, onlyPredefinedCultures); File.WriteAllText(programPath, programText); _testOutput.WriteLine($"----- Program: -----{Environment.NewLine}{programText}{Environment.NewLine}-------"); - - string mainPath = Path.Combine("wwwroot", "main.js"); - var replacements = new Dictionary { - { "runMain", "runMainAndExit" }, - { ".create()", ".withConsoleForwarding().withElementOnExit().withExitCodeLogging().create()" } - }; - UpdateFile(mainPath, replacements); - RemoveContentsFromProjectFile(mainPath, ".create();", "await runMainAndExit();"); - return (buildArgs, projectFile); - } - - protected string BuildIcuTest( - BuildArgs buildArgs, - GlobalizationMode globalizationMode, - string icuFileName = "", - bool expectSuccess = true, - bool assertAppBundle = true) - { - bool dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); - (string _, string buildOutput) = BuildTemplateProject(buildArgs, - id: buildArgs.Id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - CreateProject: false, - HasV8Script: false, - MainJS: "main.js", - Publish: true, - TargetFramework: BuildTestBase.DefaultTargetFramework, - UseCache: false, - IsBrowserProject: true, - GlobalizationMode: globalizationMode, - CustomIcuFile: icuFileName, - ExpectSuccess: expectSuccess, - AssertAppBundle: assertAppBundle - )); - return buildOutput; + + UpdateBrowserMainJs(); + return info; } - protected async Task BuildAndRunIcuTest( - string config, - string templateType, + protected async Task PublishAndRunIcuTest( + Configuration config, + Template templateType, bool aot, string testedLocales, GlobalizationMode globalizationMode, string extraProperties = "", bool onlyPredefinedCultures=false, - string language = "en-US", + string locale = "en-US", string icuFileName = "") { try { - (BuildArgs buildArgs, string projectFile) = CreateIcuProject( + ProjectInfo info = CreateIcuProject( config, templateType, aot, testedLocales, extraProperties, onlyPredefinedCultures); - string buildOutput = BuildIcuTest(buildArgs, globalizationMode, icuFileName); - string runOutput = await RunBuiltBrowserApp(buildArgs.Config, projectFile, language); - return $"{buildOutput}\n{runOutput}"; + bool triggersNativeBuild = globalizationMode == GlobalizationMode.Invariant; + (string _, string buildOutput) = PublishProject(info, + config, + new PublishOptions(GlobalizationMode: globalizationMode, CustomIcuFile: icuFileName), + isNativeBuild: triggersNativeBuild ? true : null); + + BrowserRunOptions runOptions = new(config, Locale: locale, ExpectedExitCode: 42); + RunResult runOutput = await RunForPublishWithWebServer(runOptions); + return $"{buildOutput}\n{runOutput.TestOutput}"; } catch(Exception ex) { diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/InterpPgoTests.cs b/src/mono/wasm/Wasm.Build.Tests/InterpPgoTests.cs similarity index 92% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/InterpPgoTests.cs rename to src/mono/wasm/Wasm.Build.Tests/InterpPgoTests.cs index 9399ee93ec87c..4af6f409aeedb 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/InterpPgoTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InterpPgoTests.cs @@ -13,9 +13,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class InterpPgoTests : AppTestBase +public class InterpPgoTests : WasmTemplateTestsBase { public InterpPgoTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -25,28 +25,28 @@ public InterpPgoTests(ITestOutputHelper output, SharedBuildPerTestClassFixture b [Theory] // Interpreter PGO is not meaningful to enable in debug builds - tiering is inactive there so all methods // would get added to the PGO table instead of just hot ones. - [InlineData("Release")] + [InlineData(Configuration.Release)] [ActiveIssue("https://github.com/dotnet/runtime/issues/105733")] - public async Task FirstRunGeneratesTableAndSecondRunLoadsIt(string config) + public async Task FirstRunGeneratesTableAndSecondRunLoadsIt(Configuration config) { // We need to invoke Greeting enough times to cause BCL code to tier so we can exercise interpreter PGO // Invoking it too many times makes the test meaningfully slower. const int iterationCount = 70; _testOutput.WriteLine("/// Creating project"); - CopyTestAsset("WasmBasicTestApp", "InterpPgoTest", "App"); + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "InterpPgoTest"); _testOutput.WriteLine("/// Building"); - BuildProject(config, extraArgs: "-p:WasmDebugLevel=0"); + BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: "-p:WasmDebugLevel=0")); _testOutput.WriteLine("/// Starting server"); // Create a single browser instance and single context to host all our pages. // If we don't do this, each page will have its own unique cache and the table won't be loaded. using var runCommand = new RunCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); + .WithWorkingDirectory(_projectDir); await using var runner = new BrowserRunner(_testOutput); - var url = await runner.StartServerAndGetUrlAsync(runCommand, $"run --no-silent -c {config} --no-build --project \"{_projectDir!}\" --forward-console"); + var url = await runner.StartServerAndGetUrlAsync(runCommand, $"run --no-silent -c {config} --no-build --project \"{_projectDir}\" --forward-console"); url = $"{url}?test=InterpPgoTest&iterationCount={iterationCount}"; _testOutput.WriteLine($"/// Spawning browser at URL {url}"); diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index 212d2205987dd..28253df6cea2c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -10,68 +12,66 @@ namespace Wasm.Build.Tests { - public class InvariantGlobalizationTests : TestMainJsTestBase + public class InvariantGlobalizationTests : WasmTemplateTestsBase { public InvariantGlobalizationTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable InvariantGlobalizationTestData(bool aot, RunHost host) + public static IEnumerable InvariantGlobalizationTestData(bool aot) => ConfigWithAOTData(aot) .Multiply( new object?[] { null }, new object?[] { false }, new object?[] { true }) - .WithRunHosts(host) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) .UnwrapItemsAsArrays(); // TODO: check that icu bits have been linked out [Theory] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void AOT_InvariantGlobalization(BuildArgs buildArgs, bool? invariantGlobalization, RunHost host, string id) - => TestInvariantGlobalization(buildArgs, invariantGlobalization, host, id); + [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ false })] + [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ true })] + public async Task AOT_InvariantGlobalization(Configuration config, bool aot, bool? invariantGlobalization) + => await TestInvariantGlobalization(config, aot, invariantGlobalization); // TODO: What else should we use to verify a relinked build? [Theory] - [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void RelinkingWithoutAOT(BuildArgs buildArgs, bool? invariantGlobalization, RunHost host, string id) - => TestInvariantGlobalization(buildArgs, invariantGlobalization, host, id, - extraProperties: "true", - dotnetWasmFromRuntimePack: false); + [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ false })] + public async Task RelinkingWithoutAOT(Configuration config, bool aot, bool? invariantGlobalization) + => await TestInvariantGlobalization(config, aot, invariantGlobalization, isNativeBuild: true); - private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlobalization, - RunHost host, string id, string extraProperties="", bool? dotnetWasmFromRuntimePack=null) + private async Task TestInvariantGlobalization(Configuration config, bool aot, bool? invariantGlobalization, bool? isNativeBuild = null) { - string projectName = $"invariant_{invariantGlobalization?.ToString() ?? "unset"}"; + string extraProperties = isNativeBuild == true ? "true" : ""; if (invariantGlobalization != null) + { extraProperties = $"{extraProperties}{invariantGlobalization}"; + } + if (invariantGlobalization == true) + { + if (isNativeBuild == false) + throw new System.ArgumentException("InvariantGlobalization=true requires a native build"); + // -p:InvariantGlobalization=true triggers native build, isNativeBuild is not undefined anymore + isNativeBuild = true; + } - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties); - - if (dotnetWasmFromRuntimePack == null) - dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); + string prefix = $"invariant_{invariantGlobalization?.ToString() ?? "unset"}"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "InvariantGlobalization.cs")); - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "Wasm.Buid.Tests.Programs", "InvariantGlobalization.cs"), Path.Combine(_projectDir!, "Program.cs")), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - GlobalizationMode: invariantGlobalization == true ? GlobalizationMode.Invariant : GlobalizationMode.Sharded)); + var globalizationMode = invariantGlobalization == true ? GlobalizationMode.Invariant : GlobalizationMode.Sharded; + PublishProject(info, config, new PublishOptions(GlobalizationMode: globalizationMode, AOT: aot), isNativeBuild: isNativeBuild); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 42)); if (invariantGlobalization == true) { - string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Could not create es-ES culture", output); - Assert.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); + Assert.Contains(output.TestOutput, m => m.Contains("Could not create es-ES culture")); + Assert.Contains(output.TestOutput, m => m.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)")); } else { - string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id, args: "nativename=\"espa\u00F1ol (Espa\u00F1a)\""); - Assert.Contains("es-ES: Is Invariant LCID: False", output); - + Assert.Contains(output.TestOutput, m => m.Contains("es-ES: Is Invariant LCID: False")); // ignoring the last line of the output which prints the current culture } } diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantTimezoneTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantTimezoneTests.cs index 75a1af9cd3caf..d515dfea8312a 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantTimezoneTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantTimezoneTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -10,63 +11,61 @@ namespace Wasm.Build.Tests { - public class InvariantTimezoneTests : TestMainJsTestBase + public class InvariantTimezoneTests : WasmTemplateTestsBase { public InvariantTimezoneTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable InvariantTimezoneTestData(bool aot, RunHost host) + public static IEnumerable InvariantTimezoneTestData(bool aot) => ConfigWithAOTData(aot) .Multiply( new object?[] { null }, new object?[] { false }, new object?[] { true }) - .WithRunHosts(host) .UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void AOT_InvariantTimezone(BuildArgs buildArgs, bool? invariantTimezone, RunHost host, string id) - => TestInvariantTimezone(buildArgs, invariantTimezone, host, id); + [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false, })] + [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ true })] + public async Task AOT_InvariantTimezone(Configuration config, bool aot, bool? invariantTimezone) + => await TestInvariantTimezone(config, aot, invariantTimezone); [Theory] - [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void RelinkingWithoutAOT(BuildArgs buildArgs, bool? invariantTimezone, RunHost host, string id) - => TestInvariantTimezone(buildArgs, invariantTimezone, host, id, - extraProperties: "true", - dotnetWasmFromRuntimePack: false); + [MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false })] + public async Task RelinkingWithoutAOT(Configuration config, bool aot, bool? invariantTimezone) + => await TestInvariantTimezone(config, aot, invariantTimezone, isNativeBuild: true); - private void TestInvariantTimezone(BuildArgs buildArgs, bool? invariantTimezone, - RunHost host, string id, string extraProperties="", bool? dotnetWasmFromRuntimePack=null) + private async Task TestInvariantTimezone(Configuration config, bool aot, bool? invariantTimezone, bool? isNativeBuild = null) { - string projectName = $"invariant_{invariantTimezone?.ToString() ?? "unset"}"; + string extraProperties = isNativeBuild == true ? "true" : ""; if (invariantTimezone != null) + { extraProperties = $"{extraProperties}{invariantTimezone}"; + } + if (invariantTimezone == true) + { + if (isNativeBuild == false) + throw new System.ArgumentException("InvariantTimezone=true requires a native build"); + // -p:InvariantTimezone=true triggers native build, isNativeBuild is not undefined anymore + isNativeBuild = true; + } - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties); - - if (dotnetWasmFromRuntimePack == null) - dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "Wasm.Buid.Tests.Programs", "InvariantTimezone.cs"), Path.Combine(_projectDir!, "Program.cs")), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); + string prefix = $"invariant_{invariantTimezone?.ToString() ?? "unset"}"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "InvariantTimezone.cs")); + PublishProject(info, config, isNativeBuild: isNativeBuild); - string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains("UTC BaseUtcOffset is 0", output); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 42)); + Assert.Contains(output.TestOutput, m => m.Contains("UTC BaseUtcOffset is 0")); if (invariantTimezone == true) { - Assert.Contains("Could not find Asia/Tokyo", output); + Assert.Contains(output.TestOutput, m => m.Contains("Could not find Asia/Tokyo")); } else { - Assert.Contains("Asia/Tokyo BaseUtcOffset is 09:00:00", output); + Assert.Contains(output.TestOutput, m => m.Contains("Asia/Tokyo BaseUtcOffset is 09:00:00")); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs similarity index 60% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs rename to src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs index eb98b3d6f6e0a..6501196237422 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -11,9 +12,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class LazyLoadingTests : AppTestBase +public class LazyLoadingTests : WasmTemplateTestsBase { public LazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -30,17 +31,18 @@ public LazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [MemberData(nameof(LoadLazyAssemblyBeforeItIsNeededData))] public async Task LoadLazyAssemblyBeforeItIsNeeded(string lazyLoadingTestExtension, string[] allLazyLoadingTestExtensions) { - CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests", "App"); - BuildProject("Debug", extraArgs: $"-p:LazyLoadingTestExtension={lazyLoadingTestExtension}"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LazyLoadingTests"); + BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: $"-p:LazyLoadingTestExtension={lazyLoadingTestExtension} -p:TestLazyLoading=true")); // We are running the app and passing all possible lazy extensions to test matrix of all possibilities. // We don't need to rebuild the application to test how client is trying to load the assembly. foreach (var clientLazyLoadingTestExtension in allLazyLoadingTestExtensions) { - var result = await RunSdkStyleAppForBuild(new( - Configuration: "Debug", - TestScenario: "LazyLoadingTest", - BrowserQueryString: new Dictionary { ["lazyLoadingTestExtension"] = clientLazyLoadingTestExtension } + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "LazyLoadingTest", + BrowserQueryString: new NameValueCollection { {"lazyLoadingTestExtension", clientLazyLoadingTestExtension } } )); Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON"); @@ -51,15 +53,16 @@ public async Task LoadLazyAssemblyBeforeItIsNeeded(string lazyLoadingTestExtensi [Fact] public async Task FailOnMissingLazyAssembly() { - CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests", "App"); - PublishProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LazyLoadingTests"); - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", + PublishProject(info, config, new PublishOptions(ExtraMSBuildArgs: "-p:TestLazyLoading=true")); + BrowserRunOptions options = new( + config, TestScenario: "LazyLoadingTest", - BrowserQueryString: new Dictionary { ["loadRequiredAssembly"] = "false" }, - ExpectedExitCode: 1 - )); + BrowserQueryString: new NameValueCollection { {"loadRequiredAssembly", "false" } }, + ExpectedExitCode: 1); + RunResult result = await RunForPublishWithWebServer(options); Assert.True(result.ConsoleOutput.Any(m => m.Contains("Could not load file or assembly") && m.Contains("Json")), "The lazy loading test didn't emit expected error message"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs similarity index 57% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs rename to src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs index 5e351203596d4..b60bf5c858f31 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; @@ -14,9 +15,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public partial class LibraryInitializerTests : AppTestBase +public partial class LibraryInitializerTests : WasmTemplateTestsBase { public LibraryInitializerTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -26,10 +27,10 @@ public LibraryInitializerTests(ITestOutputHelper output, SharedBuildPerTestClass [Fact] public async Task LoadLibraryInitializer() { - CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_LoadLibraryInitializer", "App"); - PublishProject("Debug"); - - var result = await RunSdkStyleAppForPublish(new(Configuration: "Debug", TestScenario: "LibraryInitializerTest")); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LibraryInitializerTests_LoadLibraryInitializer"); + PublishProject(info, config); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "LibraryInitializerTest")); Assert.Collection( result.TestOutput, m => Assert.Equal("LIBRARY_INITIALIZER_TEST = 1", m) @@ -42,15 +43,16 @@ public async Task LoadLibraryInitializer() [Fact] public async Task AbortStartupOnError() { - CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_AbortStartupOnError", "App"); - PublishProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LibraryInitializerTests_AbortStartupOnError"); + PublishProject(info, config); - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", + BrowserRunOptions options = new( + config, TestScenario: "LibraryInitializerTest", - BrowserQueryString: new Dictionary { ["throwError"] = "true" }, - ExpectedExitCode: 1 - )); + BrowserQueryString: new NameValueCollection { {"throwError", "true" } }, + ExpectedExitCode: 1); + RunResult result = await RunForPublishWithWebServer(options); Assert.True(result.ConsoleOutput.Any(m => AbortStartupOnErrorRegex().IsMatch(m)), "The library initializer test didn't emit expected error message"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/MainWithArgsTests.cs b/src/mono/wasm/Wasm.Build.Tests/MainWithArgsTests.cs index 5fb7887e962cf..83e79e6955ddd 100644 --- a/src/mono/wasm/Wasm.Build.Tests/MainWithArgsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/MainWithArgsTests.cs @@ -3,7 +3,10 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; +using System.Linq; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -11,95 +14,53 @@ namespace Wasm.Build.Tests { - public class MainWithArgsTests : TestMainJsTestBase + public class MainWithArgsTests : WasmTemplateTestsBase { public MainWithArgsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable MainWithArgsTestData(bool aot, RunHost host) + public static IEnumerable MainWithArgsTestData(bool aot) => ConfigWithAOTData(aot).Multiply( new object?[] { new object?[] { "abc", "foobar"} }, - new object?[] { new object?[0] } - ).WithRunHosts(host).UnwrapItemsAsArrays(); + new object?[] { new object?[0] }) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) + .UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void AsyncMainWithArgs(BuildArgs buildArgs, string[] args, RunHost host, string id) - => TestMainWithArgs("async_main_with_args", @" - public class TestClass { - public static async System.Threading.Tasks.Task Main(string[] args) - { - ##CODE## - return await System.Threading.Tasks.Task.FromResult(42 + count); - } - }", - buildArgs, args, host, id); + [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ false })] + [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ true })] + public async Task AsyncMainWithArgs(Configuration config, bool aot, string[] args) + => await TestMainWithArgs(config, aot, "async_main_with_args", "AsyncMainWithArgs.cs", args); [Theory] - [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void NonAsyncMainWithArgs(BuildArgs buildArgs, string[] args, RunHost host, string id) - => TestMainWithArgs("non_async_main_args", @" - public class TestClass { - public static int Main(string[] args) - { - ##CODE## - return 42 + count; - } - }", buildArgs, args, host, id); + [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ false })] + [MemberData(nameof(MainWithArgsTestData), parameters: new object[] { /*aot*/ true })] + public async Task NonAsyncMainWithArgs(Configuration config, bool aot, string[] args) + => await TestMainWithArgs(config, aot, "non_async_main_args", "SyncMainWithArgs.cs", args); - void TestMainWithArgs(string projectNamePrefix, - string projectContents, - BuildArgs buildArgs, - string[] args, - RunHost host, - string id, - bool? dotnetWasmFromRuntimePack=null) + async Task TestMainWithArgs(Configuration config, + bool aot, + string projectNamePrefix, + string projectContentsName, + string[] args) { - string projectName = $"{projectNamePrefix}_{buildArgs.Config}_{buildArgs.AOT}"; - string code = @" - int count = args == null ? 0 : args.Length; - System.Console.WriteLine($""args#: {args?.Length}""); - foreach (var arg in args ?? System.Array.Empty()) - System.Console.WriteLine($""arg: {arg}""); - "; - string programText = projectContents.Replace("##CODE##", code); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, projectNamePrefix); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", projectContentsName)); - buildArgs = buildArgs with { ProjectName = projectName, ProjectFileContents = programText }; - buildArgs = ExpandBuildArgs(buildArgs); - if (dotnetWasmFromRuntimePack == null) - dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); + var queryArgs = new NameValueCollection(); + foreach (var arg in args) + queryArgs.Add("arg", arg); + PublishProject(info, config, new PublishOptions(AOT: aot)); - _testOutput.WriteLine ($"-- args: {buildArgs}, name: {projectName}"); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); - - // Because we get extra "-verbosity", "Debug" from XHarness int argsCount = args.Length; - bool isBrowser = host == RunHost.Chrome || host == RunHost.Firefox || host == RunHost.Safari; - if (isBrowser) - argsCount += 2; - - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42 + argsCount, args: string.Join(' ', args), - test: output => - { - Assert.Contains($"args#: {argsCount}", output); - foreach (var arg in args) - Assert.Contains($"arg: {arg}", output); - - if (isBrowser) - { - Assert.Contains($"arg: -verbosity", output); - Assert.Contains($"arg: Debug", output); - } - }, host: host, id: id); + int expectedCode = 42 + argsCount; + RunResult output = await RunForPublishWithWebServer( + new BrowserRunOptions(config, TestScenario: "MainWithArgs", BrowserQueryString: queryArgs, ExpectedExitCode: expectedCode)); + Assert.Contains(output.TestOutput, m => m.Contains($"args#: {argsCount}")); + foreach (var arg in args) + Assert.Contains(output.TestOutput, m => m.Contains($"arg: {arg}")); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MaxParallelDownloadsTests.cs b/src/mono/wasm/Wasm.Build.Tests/MaxParallelDownloadsTests.cs similarity index 61% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MaxParallelDownloadsTests.cs rename to src/mono/wasm/Wasm.Build.Tests/MaxParallelDownloadsTests.cs index d9ae89ef0c6e5..5c7c13c9b7db2 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MaxParallelDownloadsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/MaxParallelDownloadsTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Threading.Tasks; using Xunit.Abstractions; @@ -11,9 +12,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class MaxParallelDownloadsTests : AppTestBase +public class MaxParallelDownloadsTests : WasmTemplateTestsBase { public MaxParallelDownloadsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -21,18 +22,18 @@ public MaxParallelDownloadsTests(ITestOutputHelper output, SharedBuildPerTestCla } [Theory] - [InlineData("Release", "1")] - [InlineData("Release", "4")] - public async Task NeverFetchMoreThanMaxAllowed(string config, string maxParallelDownloads) + [InlineData(Configuration.Release, "1")] + [InlineData(Configuration.Release, "4")] + public async Task NeverFetchMoreThanMaxAllowed(Configuration config, string maxParallelDownloads) { - CopyTestAsset("WasmBasicTestApp", "MaxParallelDownloadsTests", "App"); - BuildProject(config); - - var result = await RunSdkStyleAppForBuild(new( - Configuration: config, + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "MaxParallelDownloadsTests"); + BuildProject(info, config); + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, TestScenario: "MaxParallelDownloads", - BrowserQueryString: new Dictionary { ["maxParallelDownloads"] = maxParallelDownloads } + BrowserQueryString: new NameValueCollection { {"maxParallelDownloads", maxParallelDownloads } } )); + var resultTestOutput = result.TestOutput.ToList(); var regex = new Regex(@"Active downloads: (\d+)"); foreach (var line in resultTestOutput) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs b/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs similarity index 62% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs rename to src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs index d0589c04f1ddd..39d7f5be61c5d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs @@ -11,9 +11,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class MemoryTests : AppTestBase +public class MemoryTests : WasmTemplateTestsBase { public MemoryTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -28,27 +28,34 @@ public async Task AllocateLargeHeapThenRepeatedlyInterop_NoWorkload() => [Fact] public async Task AllocateLargeHeapThenRepeatedlyInterop() { - string config = "Release"; - CopyTestAsset("WasmBasicTestApp", "MemoryTests", "App"); + Configuration config = Configuration.Release; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "MemoryTests"); string extraArgs = "-p:EmccMaximumHeapSize=4294901760"; - BuildProject(config, assertAppBundle: false, extraArgs: extraArgs, expectSuccess: BuildTestBase.IsUsingWorkloads); + BuildProject(info, + config, + new BuildOptions(ExtraMSBuildArgs: extraArgs, ExpectSuccess: BuildTestBase.IsUsingWorkloads), + // using EmccMaximumHeapSize forces native rebuild + isNativeBuild: true); if (BuildTestBase.IsUsingWorkloads) { - await RunSdkStyleAppForBuild(new (Configuration: config, TestScenario: "AllocateLargeHeapThenInterop")); + await RunForBuildWithDotnetRun(new BrowserRunOptions( + Configuration: config, + TestScenario: "AllocateLargeHeapThenInterop" + )); } } [Fact] public async Task RunSimpleAppWithProfiler() { - string config = "Release"; - CopyTestAsset("WasmBasicTestApp", "ProfilerTest", "App"); + Configuration config = Configuration.Release; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ProfilerTest"); // are are linking all 3 profilers, but below we only initialize log profiler and test it string extraArgs = $"-p:WasmProfilers=\"aot+browser+log\" -p:WasmBuildNative=true"; - BuildProject(config, assertAppBundle: false, extraArgs: extraArgs); + BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: extraArgs, AssertAppBundle: false), isNativeBuild: true); - var result = await RunSdkStyleAppForBuild(new (Configuration: config, TestScenario: "ProfilerTest")); + var result = await RunForBuildWithDotnetRun(new BrowserRunOptions(Configuration: config, TestScenario: "ProfilerTest")); Regex regex = new Regex(@"Profile data of size (\d+) bytes"); var match = result.TestOutput .Select(line => regex.Match(line)) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs b/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs similarity index 58% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs rename to src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs index e5abf407dbd9d..a825a1dadbac9 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ModuleConfigTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -11,9 +12,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class ModuleConfigTests : AppTestBase +public class ModuleConfigTests : WasmTemplateTestsBase { public ModuleConfigTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -25,13 +26,14 @@ public ModuleConfigTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur [InlineData(true)] public async Task DownloadProgressFinishes(bool failAssemblyDownload) { - CopyTestAsset("WasmBasicTestApp", $"ModuleConfigTests_DownloadProgressFinishes_{failAssemblyDownload}", "App"); - PublishProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, $"ModuleConfigTests_DownloadProgressFinishes_{failAssemblyDownload}"); + PublishProject(info, config); - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", + var result = await RunForPublishWithWebServer(new BrowserRunOptions( + Configuration: config, TestScenario: "DownloadResourceProgressTest", - BrowserQueryString: new Dictionary { ["failAssemblyDownload"] = failAssemblyDownload.ToString().ToLowerInvariant() } + BrowserQueryString: new NameValueCollection { {"failAssemblyDownload", failAssemblyDownload.ToString().ToLowerInvariant() } } )); Assert.True( result.TestOutput.Any(m => m.Contains("DownloadResourceProgress: Finished")), @@ -58,11 +60,12 @@ public async Task DownloadProgressFinishes(bool failAssemblyDownload) [Fact] public async Task OutErrOverrideWorks() { - CopyTestAsset("WasmBasicTestApp", $"ModuleConfigTests_OutErrOverrideWorks", "App"); - PublishProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ModuleConfigTests_OutErrOverrideWorks"); + PublishProject(info, config); - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", + var result = await RunForPublishWithWebServer(new BrowserRunOptions( + Configuration: Configuration.Debug, TestScenario: "OutErrOverrideWorks" )); Assert.True( @@ -76,25 +79,27 @@ public async Task OutErrOverrideWorks() } [Theory] - [InlineData("Release", true)] - [InlineData("Release", false)] - public async Task OverrideBootConfigName(string config, bool isPublish) + [InlineData(Configuration.Release, true)] + [InlineData(Configuration.Release, false)] + public async Task OverrideBootConfigName(Configuration config, bool isPublish) { - CopyTestAsset("WasmBasicTestApp", $"OverrideBootConfigName", "App"); + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "OverrideBootConfigName"); + (string _, string _) = isPublish ? + PublishProject(info, config) : + BuildProject(info, config); - string[] extraArgs = ["-p:WasmBootConfigFileName=boot.json"]; - if (isPublish) - PublishProject(config, bootConfigFileName: "boot.json", extraArgs: extraArgs); - else - BuildProject(config, bootConfigFileName: "boot.json", extraArgs: extraArgs); + string extraArgs = "-p:WasmBootConfigFileName=boot.json"; + (string _, string _) = isPublish ? + PublishProject(info, config, new PublishOptions(BootConfigFileName: "boot.json", UseCache: false, ExtraMSBuildArgs: extraArgs)) : + BuildProject(info, config, new BuildOptions(BootConfigFileName: "boot.json", UseCache: false, ExtraMSBuildArgs: extraArgs)); - var runOptions = new RunOptions( + var runOptions = new BrowserRunOptions( Configuration: config, TestScenario: "OverrideBootConfigName" ); var result = await (isPublish - ? RunSdkStyleAppForPublish(runOptions) - : RunSdkStyleAppForBuild(runOptions) + ? RunForPublishWithWebServer(runOptions) + : RunForBuildWithDotnetRun(runOptions) ); Assert.Collection( diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs index f7c7483a949d3..87b6c0cf0929e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Data; using System.IO; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -12,7 +13,7 @@ namespace Wasm.Build.Tests { - public class NativeBuildTests : TestMainJsTestBase + public class NativeBuildTests : WasmTemplateTestsBase { public NativeBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -20,48 +21,44 @@ public NativeBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture } [Theory] - [BuildAndRun] - public void SimpleNativeBuild(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(aot: false)] + public async Task SimpleNativeBuild(Configuration config, bool aot) { - string projectName = $"simple_native_build_{buildArgs.Config}_{buildArgs.AOT}"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "true"); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: false)); - - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, - test: output => { }, - host: host, id: id); + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot, + "simple_native_build", + extraProperties: "true"); + + UpdateBrowserProgramFile(); + UpdateBrowserMainJs(); + + (string _, string buildOutput) = PublishProject(info, config, isNativeBuild: true); + await RunForPublishWithWebServer(new BrowserRunOptions(config, ExpectedExitCode: 42)); } [Theory] - [BuildAndRun(aot: true, host: RunHost.None)] - public void AOTNotSupportedWithNoTrimming(BuildArgs buildArgs, string id) + [BuildAndRun(aot: true)] + public void AOTNotSupportedWithNoTrimming(Configuration config, bool aot) { - string projectName = $"mono_aot_cross_{buildArgs.Config}_{buildArgs.AOT}"; - - buildArgs = buildArgs with { ProjectName = projectName, ExtraBuildArgs = "-p:PublishTrimmed=false" }; - buildArgs = ExpandBuildArgs(buildArgs); - - (_, string output) = BuildProject( - buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: false, - ExpectSuccess: false)); - + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot, + "mono_aot_cross", + extraProperties: "false"); + + UpdateBrowserProgramFile(); + UpdateBrowserMainJs(); + + (string _, string output) = PublishProject(info, config, new PublishOptions(ExpectSuccess: false, AOT: aot)); Assert.Contains("AOT is not supported without IL trimming", output); } [Theory] - [BuildAndRun(host: RunHost.None, aot: true)] - public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(BuildArgs buildArgs, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(Configuration config, bool aot) { string printFileTypeTarget = @" @@ -77,39 +74,32 @@ public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(BuildArgs buildArgs, st "" Importance=""High"" /> "; - string projectName = $"bc_to_o_{buildArgs.Config}"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, insertAtEnd: printFileTypeTarget); - - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: false)); - + + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot, + "bc_to_o", + insertAtEnd: printFileTypeTarget); + + (string _, string output) = PublishProject(info, config, new PublishOptions(AOT: aot)); if (!output.Contains("** wasm-dis exit code: 0")) throw new XunitException($"Expected to successfully run wasm-dis on System.Private.CoreLib.dll.o ." + " It might fail if it was incorrectly compiled to a bitcode file, instead of wasm."); } [Theory] - [BuildAndRun(host: RunHost.None, aot: true)] - public void NativeBuildIsRequired(BuildArgs buildArgs, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public void NativeBuildIsRequired(Configuration config, bool aot) { - string projectName = $"native_build_{buildArgs.Config}_{buildArgs.AOT}"; - - buildArgs = buildArgs with { ProjectName = projectName, ExtraBuildArgs = "-p:WasmBuildNative=false -p:WasmSingleFileBundle=true" }; - buildArgs = ExpandBuildArgs(buildArgs); - - (_, string output) = BuildProject( - buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: false, - ExpectSuccess: false)); - + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot, + "native_build", + extraProperties: "falsetrue"); + + (string _, string output) = PublishProject(info, config, new PublishOptions(ExpectSuccess: false, AOT: aot)); Assert.Contains("WasmBuildNative is required", output); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs index 990d331f0c580..f01fb37c5fe99 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -10,7 +11,7 @@ namespace Wasm.Build.Tests { - public class NativeLibraryTests : TestMainJsTestBase + public class NativeLibraryTests : WasmTemplateTestsBase { public NativeLibraryTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -19,153 +20,79 @@ public NativeLibraryTests(ITestOutputHelper output, SharedBuildPerTestClassFixtu [Theory] [BuildAndRun(aot: false)] - [BuildAndRun(aot: true)] - public void ProjectWithNativeReference(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public async Task ProjectWithNativeReference(Configuration config, bool aot) { - string projectName = $"AppUsingNativeLib-a"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraItems: ""); - - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? _)) - { - InitPaths(id); - if (Directory.Exists(_projectDir)) - Directory.Delete(_projectDir, recursive: true); - - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"), Path.Combine(_projectDir, "native-lib.o")); - } - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions(DotnetWasmFromRuntimePack: false)); - - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, - test: output => {}, - host: host, id: id); - - Assert.Contains("print_line: 100", output); - Assert.Contains("from pinvoke: 142", output); + string objectFilename = "native-lib.o"; + string extraItems = $""; + string extraProperties = "true"; + + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "AppUsingNativeLib-a", extraItems: extraItems, extraProperties: extraProperties); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", objectFilename), Path.Combine(_projectDir, objectFilename)); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir, overwrite: true); + DeleteFile(Path.Combine(_projectDir, "Common", "Program.cs")); + + (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun")); + + Assert.Contains(output.TestOutput, m => m.Contains("print_line: 100")); + Assert.Contains(output.TestOutput, m => m.Contains("from pinvoke: 142")); } [Theory] [BuildAndRun(aot: false)] - [BuildAndRun(aot: true, config: "Release")] - public void ProjectUsingSkiaSharp(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/103566")] + public async Task ProjectUsingSkiaSharp(Configuration config, bool aot) { - string projectName = $"AppUsingSkiaSharp"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: @$" + string prefix = $"AppUsingSkiaSharp"; + string extraItems = @$" {GetSkiaSharpReferenceItems()} - "); - - string programText = @" -using System; -using SkiaSharp; - -public class Test -{ - public static int Main() - { - using SKFileStream skfs = new SKFileStream(""mono.png""); - using SKImage img = SKImage.FromEncodedData(skfs); - - Console.WriteLine ($""Size: {skfs.Length} Height: {img.Height}, Width: {img.Width}""); - return 0; - } -}"; - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: false)); - - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, - test: output => {}, - host: host, id: id, - args: "mono.png"); - - Assert.Contains("Size: 26462 Height: 599, Width: 499", output); + "; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraItems: extraItems); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "SkiaSharp.cs")); + + PublishProject(info, config, new PublishOptions(AOT: aot)); + BrowserRunOptions runOptions = new(config, ExtraArgs: "mono.png"); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 0)); + Assert.Contains(output.TestOutput, m => m.Contains("Size: 26462 Height: 599, Width: 499")); } [Theory] - [BuildAndRun(aot: false, host: RunHost.Chrome)] - [BuildAndRun(aot: true, host: RunHost.Chrome)] - public void ProjectUsingBrowserNativeCrypto(BuildArgs buildArgs, RunHost host, string id) - { - string projectName = $"AppUsingBrowserNativeCrypto"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - string programText = @" -using System; -using System.Security.Cryptography; - -public class Test -{ - public static int Main() - { - using (SHA256 mySHA256 = SHA256.Create()) + [BuildAndRun(aot: false)] + [BuildAndRun(config: Configuration.Release, aot: true)] + public async Task ProjectUsingBrowserNativeCrypto(Configuration config, bool aot) { - byte[] data = { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' }; - byte[] hashed = mySHA256.ComputeHash(data); - string asStr = string.Join(' ', hashed); - Console.WriteLine(""Hashed: "" + asStr); - return 0; - } - } -}"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "AppUsingBrowserNativeCrypto"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "NativeCrypto.cs")); - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: !buildArgs.AOT && buildArgs.Config != "Release")); + (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot)); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 0)); - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, - test: output => {}, - host: host, id: id); - - Assert.Contains( - "Hashed: 24 95 141 179 34 113 254 37 245 97 166 252 147 139 46 38 67 6 236 48 78 218 81 128 7 209 118 72 38 56 25 105", - output); + string hash = "Hashed: 24 95 141 179 34 113 254 37 245 97 166 252 147 139 46 38 67 6 236 48 78 218 81 128 7 209 118 72 38 56 25 105"; + Assert.Contains(output.TestOutput, m => m.Contains(hash)); string cryptoInitMsg = "MONO_WASM: Initializing Crypto WebWorker"; - Assert.DoesNotContain(cryptoInitMsg, output); + Assert.All(output.TestOutput, m => Assert.DoesNotContain(cryptoInitMsg, m)); } [Theory] [BuildAndRun(aot: false)] - [BuildAndRun(aot: true)] - public void ProjectWithNativeLibrary(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(config: Configuration.Release, aot: true)] + public async Task ProjectWithNativeLibrary(Configuration config, bool aot) { - string projectName = $"AppUsingNativeLibrary-a"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraItems: "\n"); - - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? _)) - { - InitPaths(id); - if (Directory.Exists(_projectDir)) - Directory.Delete(_projectDir, recursive: true); - - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"), Path.Combine(_projectDir, "native-lib.o")); - } - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions(DotnetWasmFromRuntimePack: false)); - - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, - test: output => {}, - host: host, id: id); - - Assert.Contains("print_line: 100", output); - Assert.Contains("from pinvoke: 142", output); + string extraItems = "\n"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "AppUsingNativeLib-a", extraItems: extraItems); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir, overwrite: true); + DeleteFile(Path.Combine(_projectDir, "Common", "Program.cs")); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"), Path.Combine(_projectDir, "native-lib.o")); + + (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true); + RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 0)); + + Assert.Contains(output.TestOutput, m => m.Contains("print_line: 100")); + Assert.Contains(output.TestOutput, m => m.Contains("from pinvoke: 142")); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs index 9f18293b3c848..76c55cf22287d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs @@ -20,50 +20,50 @@ public FlagsChangeRebuildTests(ITestOutputHelper output, SharedBuildPerTestClass } public static IEnumerable FlagsChangesForNativeRelinkingData(bool aot) - => ConfigWithAOTData(aot, config: "Release").Multiply( + => ConfigWithAOTData(aot, config: Configuration.Release).Multiply( new object[] { /*cflags*/ "/p:EmccExtraCFlags=-g", /*ldflags*/ "" }, new object[] { /*cflags*/ "", /*ldflags*/ "/p:EmccExtraLDFlags=-g" }, new object[] { /*cflags*/ "/p:EmccExtraCFlags=-g", /*ldflags*/ "/p:EmccExtraLDFlags=-g" } - ).WithRunHosts(RunHost.Chrome).UnwrapItemsAsArrays(); + ).UnwrapItemsAsArrays(); [Theory] [MemberData(nameof(FlagsChangesForNativeRelinkingData), parameters: /*aot*/ false)] [MemberData(nameof(FlagsChangesForNativeRelinkingData), parameters: /*aot*/ true)] - public void ExtraEmccFlagsSetButNoRealChange(BuildArgs buildArgs, string extraCFlags, string extraLDFlags, RunHost host, string id) + public async void ExtraEmccFlagsSetButNoRealChange(Configuration config, bool aot, string extraCFlags, string extraLDFlags) { - buildArgs = buildArgs with { ProjectName = $"rebuild_flags_{buildArgs.Config}" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink: true, invariant: false, buildArgs, id); - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: true); - if (extraLDFlags.Length > 0) + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_flags"); + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, requestNativeRelink: true, invariant: false); + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: true); + bool dotnetNativeFilesUnchanged = extraLDFlags.Length == 0; + if (!dotnetNativeFilesUnchanged) pathsDict.UpdateTo(unchanged: false, "dotnet.native.wasm", "dotnet.native.js"); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var originalStat = StatFiles(pathsDict); // Rebuild - - string mainAssembly = $"{buildArgs.ProjectName}.dll"; + string mainAssembly = $"{info.ProjectName}.dll"; string extraBuildArgs = $" {extraCFlags} {extraLDFlags}"; - string output = Rebuild(nativeRelink: true, invariant: false, buildArgs, id, extraBuildArgs: extraBuildArgs, verbosity: "normal"); - - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); + string output = Rebuild(info, config, aot, requestNativeRelink: true, invariant: false, extraBuildArgs: extraBuildArgs, assertAppBundle: dotnetNativeFilesUnchanged); + var newStat = StatFilesAfterRebuild(pathsDict); + CompareStat(originalStat, newStat, pathsDict); + // cflags: pinvoke get's compiled, but doesn't overwrite pinvoke.o // and thus doesn't cause relinking TestUtils.AssertSubstring("pinvoke.c -> pinvoke.o", output, contains: extraCFlags.Length > 0); - + // ldflags: link step args change, so it should trigger relink TestUtils.AssertSubstring("Linking with emcc", output, contains: extraLDFlags.Length > 0); - - if (buildArgs.AOT) + + if (aot) { // ExtraEmccLDFlags does not affect .bc files Assert.DoesNotContain("Compiling assembly bitcode files", output); } - - string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - TestUtils.AssertSubstring($"Found statically linked AOT module '{Path.GetFileNameWithoutExtension(mainAssembly)}'", runOutput, - contains: buildArgs.AOT); + + RunResult runOutput = await RunForPublishWithWebServer(new BrowserRunOptions(config, aot, TestScenario: "DotnetRun")); + TestUtils.AssertSubstring($"Found statically linked AOT module '{Path.GetFileNameWithoutExtension(mainAssembly)}'", runOutput.ConsoleOutput, + contains: aot); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index 09fafe0df6939..2b90d03136bb0 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -18,7 +18,7 @@ namespace Wasm.Build.NativeRebuild.Tests { // TODO: test for runtime components - public class NativeRebuildTestsBase : TestMainJsTestBase + public class NativeRebuildTestsBase : WasmTemplateTestsBase { public NativeRebuildTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -35,82 +35,56 @@ public NativeRebuildTestsBase(ITestOutputHelper output, SharedBuildPerTestClassF // aot data.AddRange(GetData(aot: true, nativeRelinking: false, invariant: false)); - data.AddRange(GetData(aot: true, nativeRelinking: false, invariant: true)); return data; IEnumerable GetData(bool aot, bool nativeRelinking, bool invariant) => ConfigWithAOTData(aot) .Multiply(new object[] { nativeRelinking, invariant }) - .WithRunHosts(RunHost.Chrome) + // AOT in Debug is switched off + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) .UnwrapItemsAsArrays().ToList(); } - internal (BuildArgs BuildArgs, BuildPaths paths) FirstNativeBuild(string programText, bool nativeRelink, bool invariant, BuildArgs buildArgs, string id, string extraProperties="") + internal async Task FirstNativeBuildAndRun(ProjectInfo info, Configuration config, bool aot, bool requestNativeRelink, bool invariant, string extraBuildArgs="") { - buildArgs = GenerateProjectContents(buildArgs, nativeRelink, invariant, extraProperties); - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: false, - GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded, - CreateProject: true)); - - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: RunHost.Chrome, id: id); - return (buildArgs, GetBuildPaths(buildArgs)); + var extraArgs = $"-p:_WasmDevel=true {extraBuildArgs}"; + if (requestNativeRelink) + extraArgs += $" -p:WasmBuildNative={requestNativeRelink}"; + if (invariant) + extraArgs += $" -p:InvariantGlobalization={invariant}"; + bool? nativeBuildValue = (requestNativeRelink || invariant) ? true : null; + PublishProject(info, + config, + new PublishOptions(AOT: aot, GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded, ExtraMSBuildArgs: extraArgs), + isNativeBuild: nativeBuildValue); + await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun")); + return GetBuildPaths(config, forPublish: true); } - protected string Rebuild(bool nativeRelink, bool invariant, BuildArgs buildArgs, string id, string extraProperties="", string extraBuildArgs="", string? verbosity=null) + protected string Rebuild( + ProjectInfo info, Configuration config, bool aot, bool requestNativeRelink, bool invariant, string extraBuildArgs="", string verbosity="normal", bool assertAppBundle=true) { - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); - - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); - - buildArgs = buildArgs with { ExtraBuildArgs = $"{buildArgs.ExtraBuildArgs} {extraBuildArgs}" }; - var newBuildArgs = GenerateProjectContents(buildArgs, nativeRelink, invariant, extraProperties); - - // key(buildArgs) being changed - _buildContext.RemoveFromCache(product.ProjectDir); - _buildContext.CacheBuild(newBuildArgs, product); - - if (buildArgs.ProjectFileContents != newBuildArgs.ProjectFileContents) - File.WriteAllText(Path.Combine(_projectDir!, $"{buildArgs.ProjectName}.csproj"), buildArgs.ProjectFileContents); - buildArgs = newBuildArgs; + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); + + File.Move(result!.LogFile, Path.ChangeExtension(result.LogFile!, ".first.binlog")); + + var extraArgs = $"-p:_WasmDevel=true -v:{verbosity} {extraBuildArgs}"; + if (requestNativeRelink) + extraArgs += $" -p:WasmBuildNative={requestNativeRelink}"; + if (invariant) + extraArgs += $" -p:InvariantGlobalization={invariant}"; // artificial delay to have new enough timestamps Thread.Sleep(5000); - _testOutput.WriteLine($"{Environment.NewLine}Rebuilding with no changes ..{Environment.NewLine}"); - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: false, - GlobalizationMode: invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded, - CreateProject: false, - UseCache: false, - Verbosity: verbosity)); - + bool? nativeBuildValue = (requestNativeRelink || invariant) ? true : null; + var globalizationMode = invariant ? GlobalizationMode.Invariant : GlobalizationMode.Sharded; + var options = new PublishOptions(AOT: aot, GlobalizationMode: globalizationMode, ExtraMSBuildArgs: extraArgs, UseCache: false, AssertAppBundle: assertAppBundle); + (string _, string output) = PublishProject(info, config, options, isNativeBuild: nativeBuildValue); return output; } - protected BuildArgs GenerateProjectContents(BuildArgs buildArgs, bool nativeRelink, bool invariant, string extraProperties) - { - StringBuilder propertiesBuilder = new(); - propertiesBuilder.Append("<_WasmDevel>true"); - if (nativeRelink) - propertiesBuilder.Append($"true"); - if (invariant) - propertiesBuilder.Append($"true"); - propertiesBuilder.Append(extraProperties); - - return ExpandBuildArgs(buildArgs, propertiesBuilder.ToString()); - } - - // appending UTF-8 char makes sure project build&publish under all types of paths is supported - protected string GetTestProjectPath(string prefix, string config, bool appendUnicode=true) => - appendUnicode ? $"{prefix}_{config}_{s_unicodeChars}" : $"{prefix}_{config}"; - } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs index 2a378573a7a2a..dada93b41b070 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs @@ -19,19 +19,30 @@ public NoopNativeRebuildTest(ITestOutputHelper output, SharedBuildPerTestClassFi [Theory] [MemberData(nameof(NativeBuildData))] - public void NoOpRebuildForNativeBuilds(BuildArgs buildArgs, bool nativeRelink, bool invariant, RunHost host, string id) + public async void NoOpRebuildForNativeBuilds(Configuration config, bool aot, bool nativeRelink, bool invariant) { - buildArgs = buildArgs with { ProjectName = $"rebuild_noop_{buildArgs.Config}" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink: nativeRelink, invariant: invariant, buildArgs, id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_noop"); + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, nativeRelink, invariant); - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: true); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: true); + var originalStat = StatFiles(pathsDict); - Rebuild(nativeRelink, invariant, buildArgs, id); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + Rebuild(info, config, aot, nativeRelink, invariant); + var newStat = StatFiles(pathsDict); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + CompareStat(originalStat, newStat, pathsDict); + await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun")); + } + + [Fact] + public void NativeRelinkFailsWithInvariant() + { + Configuration config = Configuration.Release; + string extraArgs = "-p:_WasmDevel=true -p:WasmBuildNative=false -p:InvariantGlobalization=true"; + ProjectInfo info = CopyTestAsset(config, aot: true, TestAsset.WasmBasicTestApp, "relink_fails"); + var options = new PublishOptions(ExpectSuccess: false, AOT: true, ExtraMSBuildArgs: extraArgs); + PublishProject(info, config, options); + Assert.Contains("WasmBuildNative is required because InvariantGlobalization=true, but WasmBuildNative is already set to 'false'", _testOutput.ToString()); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/OptimizationFlagChangeTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/OptimizationFlagChangeTests.cs index 5286664e4cb63..069b37cd62748 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/OptimizationFlagChangeTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/OptimizationFlagChangeTests.cs @@ -21,28 +21,29 @@ public OptimizationFlagChangeTests(ITestOutputHelper output, SharedBuildPerTestC } public static IEnumerable FlagsOnlyChangeData(bool aot) - => ConfigWithAOTData(aot, config: "Release").Multiply( + => ConfigWithAOTData(aot, config: Configuration.Release).Multiply( new object[] { /*cflags*/ "/p:EmccCompileOptimizationFlag=-O1", /*ldflags*/ "" }, new object[] { /*cflags*/ "", /*ldflags*/ "/p:EmccLinkOptimizationFlag=-O1" } - ).WithRunHosts(RunHost.Chrome).UnwrapItemsAsArrays(); + ).UnwrapItemsAsArrays(); [Theory] [MemberData(nameof(FlagsOnlyChangeData), parameters: /*aot*/ false)] [MemberData(nameof(FlagsOnlyChangeData), parameters: /*aot*/ true)] - public void OptimizationFlagChange(BuildArgs buildArgs, string cflags, string ldflags, RunHost host, string id) + public async void OptimizationFlagChange(Configuration config, bool aot, string cflags, string ldflags) { - // force _WasmDevel=false, so we don't get -O0 - buildArgs = buildArgs with { ProjectName = $"rebuild_flags_{buildArgs.Config}", ExtraBuildArgs = "/p:_WasmDevel=false" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink: true, invariant: false, buildArgs, id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_flags"); + // force _WasmDevel=false, so we don't get -O0 but -O2 + string optElevationArg = "/p:_WasmDevel=false"; + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, requestNativeRelink: true, invariant: false, extraBuildArgs: optElevationArg); - string mainAssembly = $"{buildArgs.ProjectName}.dll"; - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: false); + string mainAssembly = $"{info.ProjectName}{ProjectProviderBase.WasmAssemblyExtension}"; + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: false); pathsDict.UpdateTo(unchanged: true, mainAssembly, "icall-table.h", "pinvoke-table.h", "driver-gen.c"); if (cflags.Length == 0) pathsDict.UpdateTo(unchanged: true, "pinvoke.o", "corebindings.o", "driver.o", "runtime.o"); pathsDict.Remove(mainAssembly); - if (buildArgs.AOT) + if (aot) { // link optimization flag change affects .bc->.o files too, but // it might result in only *some* files being *changed, @@ -55,17 +56,21 @@ public void OptimizationFlagChange(BuildArgs buildArgs, string cflags, string ld pathsDict.Remove(key); } } - - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + var originalStat = StatFiles(pathsDict); // Rebuild + string output = Rebuild(info, + config, + aot, + requestNativeRelink: true, + invariant: false, + extraBuildArgs: $" {cflags} {ldflags} {optElevationArg}", + assertAppBundle: false); // optimization flags change changes the size of dotnet.native.wasm + var newStat = StatFilesAfterRebuild(pathsDict); + CompareStat(originalStat, newStat, pathsDict); - string output = Rebuild(nativeRelink: true, invariant: false, buildArgs, id, extraBuildArgs: $" {cflags} {ldflags}", verbosity: "normal"); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); - - string runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - TestUtils.AssertSubstring($"Found statically linked AOT module '{Path.GetFileNameWithoutExtension(mainAssembly)}'", runOutput, - contains: buildArgs.AOT); + RunResult runOutput = await RunForPublishWithWebServer(new BrowserRunOptions(config, aot, TestScenario: "DotnetRun")); + TestUtils.AssertSubstring($"Found statically linked AOT module '{Path.GetFileNameWithoutExtension(mainAssembly)}'", runOutput.ConsoleOutput, + contains: aot); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs index e9b3198519fed..7f39761591054 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs @@ -21,40 +21,26 @@ public ReferenceNewAssemblyRebuildTest(ITestOutputHelper output, SharedBuildPerT [Theory] [MemberData(nameof(NativeBuildData))] - public void ReferenceNewAssembly(BuildArgs buildArgs, bool nativeRelink, bool invariant, RunHost host, string id) + public async void ReferenceNewAssembly(Configuration config, bool aot, bool nativeRelink, bool invariant) { - buildArgs = buildArgs with { ProjectName = $"rebuild_tasks_{buildArgs.Config}" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink, invariant: invariant, buildArgs, id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_tasks"); + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, nativeRelink, invariant); - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: false); + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: false); pathsDict.UpdateTo(unchanged: true, "corebindings.o"); pathsDict.UpdateTo(unchanged: true, "driver.o"); - if (!buildArgs.AOT) // relinking + if (!aot) // relinking pathsDict.UpdateTo(unchanged: true, "driver-gen.c"); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - - string programText = - @$" - using System; - using System.Text.Json; - public class Test - {{ - public static int Main() - {{" + - @" string json = ""{ \""name\"": \""value\"" }"";" + - @" var jdoc = JsonDocument.Parse($""{json}"", new JsonDocumentOptions());" + - @$" Console.WriteLine($""json: {{jdoc}}""); - return 42; - }} - }}"; - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - - Rebuild(nativeRelink, invariant, buildArgs, id); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - - _provider.CompareStat(originalStat, newStat, pathsDict.Values); - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + var originalStat = StatFiles(pathsDict); + + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "NativeRebuildNewAssembly.cs")); + + Rebuild(info, config, aot, nativeRelink, invariant, assertAppBundle: !aot); + var newStat = StatFilesAfterRebuild(pathsDict); + + CompareStat(originalStat, newStat, pathsDict); + await RunForPublishWithWebServer(new BrowserRunOptions(config, ExpectedExitCode: 42, TestScenario: "DotnetRun")); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs index 6e2c320cb3242..e7207e2660066 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs @@ -20,37 +20,30 @@ public SimpleSourceChangeRebuildTest(ITestOutputHelper output, SharedBuildPerTes [Theory] [MemberData(nameof(NativeBuildData))] - public void SimpleStringChangeInSource(BuildArgs buildArgs, bool nativeRelink, bool invariant, RunHost host, string id) + public async void SimpleStringChangeInSource(Configuration config, bool aot, bool nativeRelink, bool invariant) { - buildArgs = buildArgs with { ProjectName = $"rebuild_simple_{buildArgs.Config}" }; - (buildArgs, BuildPaths paths) = FirstNativeBuild(s_mainReturns42, nativeRelink, invariant: invariant, buildArgs, id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild_simple"); + BuildPaths paths = await FirstNativeBuildAndRun(info, config, aot, nativeRelink, invariant); - string mainAssembly = $"{buildArgs.ProjectName}.dll"; - var pathsDict = _provider.GetFilesTable(buildArgs, paths, unchanged: true); + string mainAssembly = $"{info.ProjectName}{ProjectProviderBase.WasmAssemblyExtension}"; + var pathsDict = GetFilesTable(info.ProjectName, aot, paths, unchanged: true); pathsDict.UpdateTo(unchanged: false, mainAssembly); - pathsDict.UpdateTo(unchanged: !buildArgs.AOT, "dotnet.native.wasm", "dotnet.native.js"); + bool dotnetFilesSizeUnchanged = !aot; + pathsDict.UpdateTo(unchanged: dotnetFilesSizeUnchanged, "dotnet.native.wasm", "dotnet.native.js"); + + if (aot) + pathsDict.UpdateTo(unchanged: false, $"{info.ProjectName}.dll.bc", $"{info.ProjectName}.dll.o"); - if (buildArgs.AOT) - pathsDict.UpdateTo(unchanged: false, $"{mainAssembly}.bc", $"{mainAssembly}.o"); + var originalStat = StatFiles(pathsDict); - var originalStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); - - // Changes - string mainResults55 = @" - public class TestClass { - public static int Main() - { - return 55; - } - }"; - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), mainResults55); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "SimpleSourceChange.cs")); // Rebuild - Rebuild(nativeRelink, invariant, buildArgs, id); - var newStat = _provider.StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + Rebuild(info, config, aot, nativeRelink, invariant, assertAppBundle: dotnetFilesSizeUnchanged); + var newStat = StatFilesAfterRebuild(pathsDict); - _provider.CompareStat(originalStat, newStat, pathsDict.Values); - RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 55, host: host, id: id); + CompareStat(originalStat, newStat, pathsDict); + await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 55)); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NonWasmTemplateBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NonWasmTemplateBuildTests.cs index e85a212f83d49..f383864a4b4de 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NonWasmTemplateBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NonWasmTemplateBuildTests.cs @@ -11,7 +11,7 @@ namespace Wasm.Build.Tests; -public class NonWasmTemplateBuildTests : TestMainJsTestBase +public class NonWasmTemplateBuildTests : WasmTemplateTestsBase { public NonWasmTemplateBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -23,8 +23,8 @@ public NonWasmTemplateBuildTests(ITestOutputHelper output, SharedBuildPerTestCla // So, copy the reference for latest TFM, and add that back with the // TFM=DefaultTargetFramework // - // This is useful for the case when we are on tfm=net7.0, but sdk, and packages - // are really 8.0 . + // This is useful for the case when we are on tfm=net8.0, but sdk, and packages + // are really 9.0 . private const string s_latestTargetFramework = "net9.0"; private const string s_previousTargetFramework = "net8.0"; private static string s_directoryBuildTargetsForPreviousTFM = @@ -55,8 +55,8 @@ public NonWasmTemplateBuildTests(ITestOutputHelper output, SharedBuildPerTestCla public static IEnumerable GetTestData() => new IEnumerable[] { - new object?[] { "Debug" }, - new object?[] { "Release" } + new object?[] { Configuration.Debug }, + new object?[] { Configuration.Release } } .AsEnumerable() .MultiplyWithSingleArgs @@ -79,7 +79,7 @@ public NonWasmTemplateBuildTests(ITestOutputHelper output, SharedBuildPerTestCla [Theory, TestCategory("no-workload")] [MemberData(nameof(GetTestData))] - public void NonWasmConsoleBuild_WithoutWorkload(string config, string extraBuildArgs, string targetFramework) + public void NonWasmConsoleBuild_WithoutWorkload(Configuration config, string extraBuildArgs, string targetFramework) => NonWasmConsoleBuild(config, extraBuildArgs, targetFramework, @@ -88,14 +88,14 @@ public void NonWasmConsoleBuild_WithoutWorkload(string config, string extraBuild [Theory] [MemberData(nameof(GetTestData))] - public void NonWasmConsoleBuild_WithWorkload(string config, string extraBuildArgs, string targetFramework) + public void NonWasmConsoleBuild_WithWorkload(Configuration config, string extraBuildArgs, string targetFramework) => NonWasmConsoleBuild(config, extraBuildArgs, targetFramework, // net6 is sdk would be needed to run the app shouldRun: targetFramework == s_latestTargetFramework); - private void NonWasmConsoleBuild(string config, + private void NonWasmConsoleBuild(Configuration config, string extraBuildArgs, string targetFramework, string? directoryBuildTargets = null, @@ -113,7 +113,7 @@ private void NonWasmConsoleBuild(string config, File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.targets"), directoryBuildTargets); using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false) - .WithWorkingDirectory(_projectDir!); + .WithWorkingDirectory(_projectDir); cmd.ExecuteWithCapturedOutput("new console --no-restore") .EnsureSuccessful(); diff --git a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs index 0c99d38835e2c..bd1b23f3a8876 100644 --- a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -13,7 +14,7 @@ namespace Wasm.Build.Tests { - public class PInvokeTableGeneratorTests : TestMainJsTestBase + public class PInvokeTableGeneratorTests : PInvokeTableGeneratorTestsBase { public PInvokeTableGeneratorTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -21,314 +22,107 @@ public PInvokeTableGeneratorTests(ITestOutputHelper output, SharedBuildPerTestCl } [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void NativeLibraryWithVariadicFunctions(BuildArgs buildArgs, RunHost host, string id) - { - string code = @" - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main(string[] args) - { - Console.WriteLine($""Main running""); - if (args.Length > 2) - { - // We don't want to run this, because we can't call variadic functions - Console.WriteLine($""sum_three: {sum_three(7, 14, 21)}""); - Console.WriteLine($""sum_two: {sum_two(3, 6)}""); - Console.WriteLine($""sum_one: {sum_one(5)}""); - } - return 42; - } - - [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_one(int a); - [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_two(int a, int b); - [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_three(int a, int b, int c); - }"; - - (buildArgs, string output) = BuildForVariadicFunctionTests(code, - buildArgs with { ProjectName = $"variadic_{buildArgs.Config}_{id}" }, - id); - Assert.Matches("warning.*native function.*sum.*varargs", output); - Assert.Contains("System.Int32 sum_one(System.Int32)", output); - Assert.Contains("System.Int32 sum_two(System.Int32, System.Int32)", output); - Assert.Contains("System.Int32 sum_three(System.Int32, System.Int32, System.Int32)", output); - - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); - } - - [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void DllImportWithFunctionPointersCompilesWithoutWarning(BuildArgs buildArgs, RunHost host, string id) - { - string code = - """ - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine("Main running"); - return 42; - } - - [DllImport("variadic", EntryPoint="sum")] - public unsafe static extern int using_sum_one(delegate* unmanaged callback); - - [DllImport("variadic", EntryPoint="sum")] - public static extern int sum_one(int a, int b); - } - """; - - (buildArgs, string output) = BuildForVariadicFunctionTests(code, - buildArgs with { ProjectName = $"fnptr_{buildArgs.Config}_{id}" }, - id); - - Assert.DoesNotMatch("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); - Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); - - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); - } - - [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void DllImportWithFunctionPointers_ForVariadicFunction_CompilesWithWarning(BuildArgs buildArgs, RunHost host, string id) - { - string code = @" - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine($""Main running""); - return 42; - } - - [DllImport(""variadic"", EntryPoint=""sum"")] - public unsafe static extern int using_sum_one(delegate* unmanaged callback); - }"; - - (buildArgs, string output) = BuildForVariadicFunctionTests(code, - buildArgs with { ProjectName = $"fnptr_variadic_{buildArgs.Config}_{id}" }, - id); - - Assert.DoesNotMatch("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); - Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); - - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); - } - - [Theory] - [BuildAndRun(host: RunHost.None)] + [BuildAndRun()] public void UnmanagedStructAndMethodIn_SameAssembly_WithoutDisableRuntimeMarshallingAttribute_NotConsideredBlittable - (BuildArgs buildArgs, string id) + (Configuration config, bool aot) { - (_, string output) = SingleProjectForDisabledRuntimeMarshallingTest( - withDisabledRuntimeMarshallingAttribute: false, - withAutoLayout: true, - expectSuccess: false, - buildArgs, - id - ); - + ProjectInfo info = PrepreProjectForBlittableTests( + config, aot, "not_blittable", disableRuntimeMarshalling: false, useAutoLayout: true); + (_, string output) = BuildProject(info, config, new BuildOptions(ExpectSuccess: false, AOT: aot)); Assert.Matches("error.*Parameter.*types.*pinvoke.*.*blittable", output); } [Theory] - [BuildAndRun(host: RunHost.None)] + [BuildAndRun()] public void UnmanagedStructAndMethodIn_SameAssembly_WithoutDisableRuntimeMarshallingAttribute_WithStructLayout_ConsideredBlittable - (BuildArgs buildArgs, string id) + (Configuration config, bool aot) { - (_, string output) = SingleProjectForDisabledRuntimeMarshallingTest( - withDisabledRuntimeMarshallingAttribute: false, - withAutoLayout: false, - expectSuccess: true, - buildArgs, - id - ); - + ProjectInfo info = PrepreProjectForBlittableTests( + config, aot, "blittable", disableRuntimeMarshalling: false, useAutoLayout: false); + (_, string output) = BuildProject(info, config, new BuildOptions(AOT: aot), isNativeBuild: true); Assert.DoesNotMatch("error.*Parameter.*types.*pinvoke.*.*blittable", output); } [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void UnmanagedStructAndMethodIn_SameAssembly_WithDisableRuntimeMarshallingAttribute_ConsideredBlittable - (BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun()] + public async void UnmanagedStructAndMethodIn_SameAssembly_WithDisableRuntimeMarshallingAttribute_ConsideredBlittable + (Configuration config, bool aot) { - (buildArgs, _) = SingleProjectForDisabledRuntimeMarshallingTest( - withDisabledRuntimeMarshallingAttribute: true, - withAutoLayout: true, - expectSuccess: true, - buildArgs, - id - ); - - string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running 5", output); + ProjectInfo info = PrepreProjectForBlittableTests( + config, aot, "blittable", disableRuntimeMarshalling: true, useAutoLayout: true); + (_, string output) = BuildProject(info, config, new BuildOptions(AOT: aot), isNativeBuild: true); + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains(result.TestOutput, m => m.Contains("Main running")); } - private (BuildArgs buildArgs ,string output) SingleProjectForDisabledRuntimeMarshallingTest( - bool withDisabledRuntimeMarshallingAttribute, bool withAutoLayout, - bool expectSuccess, BuildArgs buildArgs, string id - ) { - string code = - """ - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - """ - + (withDisabledRuntimeMarshallingAttribute ? "[assembly: DisableRuntimeMarshalling]" : "") - + """ - public class Test - { - public static int Main() - { - var x = new S { Value = 5 }; - - Console.WriteLine("Main running " + x.Value); - return 42; - } - """ - + (withAutoLayout ? "\n[StructLayout(LayoutKind.Auto)]\n" : "") - + """ - public struct S { public int Value; public float Value2; } + private ProjectInfo PrepreProjectForBlittableTests(Configuration config, bool aot, string prefix, bool disableRuntimeMarshalling, bool useAutoLayout = false) + { + string extraProperties = aot ? string.Empty : "true"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "BittableSameAssembly.cs")); - [UnmanagedCallersOnly] - public static void M(S myStruct) { } + var replacements = new Dictionary { }; + if (!disableRuntimeMarshalling) + { + replacements.Add("[assembly: DisableRuntimeMarshalling]", ""); } - """; - - buildArgs = ExpandBuildArgs( - buildArgs with { ProjectName = $"not_blittable_{buildArgs.Config}_{id}" }, - extraProperties: buildArgs.AOT - ? string.Empty - : "true" - ); - - (_, string output) = BuildProject( - buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - ExpectSuccess: expectSuccess - ) - ); - - return (buildArgs, output); + if (!useAutoLayout) + { + replacements.Add("[StructLayout(LayoutKind.Auto)]", ""); + } + if (replacements.Count > 0) + { + UpdateFile(programRelativePath, replacements); + } + return info; } - public static IEnumerable SeparateAssemblyWithDisableMarshallingAttributeTestData(string config) + public static IEnumerable SeparateAssemblyWithDisableMarshallingAttributeTestData(Configuration config) => ConfigWithAOTData(aot: false, config: config).Multiply( new object[] { /*libraryHasAttribute*/ false, /*appHasAttribute*/ false, /*expectSuccess*/ false }, new object[] { /*libraryHasAttribute*/ true, /*appHasAttribute*/ false, /*expectSuccess*/ false }, new object[] { /*libraryHasAttribute*/ false, /*appHasAttribute*/ true, /*expectSuccess*/ true }, new object[] { /*libraryHasAttribute*/ true, /*appHasAttribute*/ true, /*expectSuccess*/ true } - ).WithRunHosts(RunHost.Chrome).UnwrapItemsAsArrays(); + ).UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(SeparateAssemblyWithDisableMarshallingAttributeTestData), parameters: "Debug")] - [MemberData(nameof(SeparateAssemblyWithDisableMarshallingAttributeTestData), parameters: "Release")] - public void UnmanagedStructsAreConsideredBlittableFromDifferentAssembly - (BuildArgs buildArgs, bool libraryHasAttribute, bool appHasAttribute, bool expectSuccess, RunHost host, string id) - => SeparateAssembliesForDisableRuntimeMarshallingTest( - libraryHasAttribute: libraryHasAttribute, - appHasAttribute: appHasAttribute, - expectSuccess: expectSuccess, - buildArgs, - host, - id - ); - - private void SeparateAssembliesForDisableRuntimeMarshallingTest - (bool libraryHasAttribute, bool appHasAttribute, bool expectSuccess, BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(SeparateAssemblyWithDisableMarshallingAttributeTestData), parameters: Configuration.Debug)] + [MemberData(nameof(SeparateAssemblyWithDisableMarshallingAttributeTestData), parameters: Configuration.Release)] + public async void UnmanagedStructsAreConsideredBlittableFromDifferentAssembly + (Configuration config, bool aot, bool libraryHasAttribute, bool appHasAttribute, bool expectSuccess) { - string code = - (libraryHasAttribute ? "[assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]" : "") - + "public struct __NonBlittableTypeForAutomatedTests__ { } public struct S { public int Value; public __NonBlittableTypeForAutomatedTests__ NonBlittable; }"; - - var libraryBuildArgs = ExpandBuildArgs( - buildArgs with { ProjectName = $"blittable_different_library_{buildArgs.Config}_{id}" }, - extraProperties: "Library" - ); - - (string libraryDir, string output) = BuildProject( - libraryBuildArgs, - id: id + "_library", - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "S.cs"), code); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - AssertAppBundle: false - ) - ); - - code = - """ - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - """ - + (appHasAttribute ? "[assembly: DisableRuntimeMarshalling]" : "") - + """ - - public class Test + string extraProperties = aot ? string.Empty : "true"; + string extraItems = @$""; + string libRelativePath = Path.Combine("..", "Library", "Library.cs"); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "blittable_different_library", extraProperties: extraProperties, extraItems: extraItems); + ReplaceFile(libRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "BittableDifferentAssembly_Lib.cs")); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "BittableDifferentAssembly.cs")); + if (!libraryHasAttribute) { - public static int Main() - { - var x = new S { Value = 5 }; - - Console.WriteLine("Main running " + x.Value); - return 42; - } - - [UnmanagedCallersOnly] - public static void M(S myStruct) { } + UpdateFile(libRelativePath, new Dictionary { { "[assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]", "" } }); } - """; - - buildArgs = ExpandBuildArgs( - buildArgs with { ProjectName = $"blittable_different_app_{buildArgs.Config}_{id}" }, - extraItems: $@"", - extraProperties: buildArgs.AOT - ? string.Empty - : "true" - ); - - _projectDir = null; - - (_, output) = BuildProject( - buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - ExpectSuccess: expectSuccess - ) - ); - + if (!appHasAttribute) + { + UpdateFile(programRelativePath, new Dictionary { { "[assembly: DisableRuntimeMarshalling]", "" } }); + } + (_, string output) = BuildProject(info, + config, + new BuildOptions(ExpectSuccess: expectSuccess, AOT: aot), + isNativeBuild: true); if (expectSuccess) { - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running 5", output); + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running 5", result.TestOutput); } else { @@ -337,228 +131,58 @@ public static void M(S myStruct) { } } [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void DllImportWithFunctionPointers_WarningsAsMessages(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun()] + public async void UnmanagedCallback_InFileType(Configuration config, bool aot) { - string code = - """ - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine("Main running"); - return 42; - } - - [DllImport("someting")] - public unsafe static extern void SomeFunction1(delegate* unmanaged callback); - } - """; - - (buildArgs, string output) = BuildForVariadicFunctionTests( - code, - buildArgs with { ProjectName = $"fnptr_{buildArgs.Config}_{id}" }, - id, - verbosity: "normal", - extraProperties: "$(MSBuildWarningsAsMessage);WASM0001" - ); - - Assert.DoesNotContain("warning WASM0001", output); - - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); - } - - [Theory] - [BuildAndRun(host: RunHost.None)] - public void UnmanagedCallback_WithFunctionPointers_CompilesWithWarnings(BuildArgs buildArgs, string id) - { - string code = - """ - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine("Main running"); - return 42; - } - - [UnmanagedCallersOnly] - public unsafe static extern void SomeFunction1(delegate* unmanaged callback); - } - """; - - (_, string output) = BuildForVariadicFunctionTests( - code, - buildArgs with { ProjectName = $"cb_fnptr_{buildArgs.Config}" }, - id - ); - - Assert.DoesNotMatch("warning\\sWASM0001.*Skipping.*Test::SomeFunction1.*because.*function\\spointer", output); - } - - [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void UnmanagedCallback_InFileType(BuildArgs buildArgs, RunHost host, string id) - { - string code = - """ - using System; - using System.Runtime.InteropServices; - public class Test - { - public static int Main() - { - Console.WriteLine("Main running"); - return 42; - } - } - - file class Foo - { - [UnmanagedCallersOnly] - public unsafe static extern void SomeFunction1(int i); - } - """; - - (buildArgs, string output) = BuildForVariadicFunctionTests( - code, - buildArgs with { ProjectName = $"cb_filetype_{buildArgs.Config}" }, - id - ); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "cb_filetype"); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "UnmanagedCallbackInFile.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); Assert.DoesNotMatch(".*(warning|error).*>[A-Z0-9]+__Foo", output); - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Main running", output); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("Main running", result.TestOutput); } [Theory] - [BuildAndRun(host: RunHost.Chrome)] - public void UnmanagedCallersOnly_Namespaced(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun()] + public async void UnmanagedCallersOnly_Namespaced(Configuration config, bool aot) { - string code = - """ - using System; - using System.Runtime.InteropServices; - - public class Test - { - public unsafe static int Main() - { - ((delegate* unmanaged)&A.Conflict.C)(); - ((delegate* unmanaged)&B.Conflict.C)(); - ((delegate* unmanaged)&A.Conflict.C\u733f)(); - ((delegate* unmanaged)&B.Conflict.C\u733f)(); - return 42; - } - } - - namespace A { - public class Conflict { - [UnmanagedCallersOnly(EntryPoint = "A_Conflict_C")] - public static void C() { - Console.WriteLine("A.Conflict.C"); - } - - [UnmanagedCallersOnly(EntryPoint = "A_Conflict_C\u733f")] - public static void C\u733f() { - Console.WriteLine("A.Conflict.C_\U0001F412"); - } - } - } - - namespace B { - public class Conflict { - [UnmanagedCallersOnly(EntryPoint = "B_Conflict_C")] - public static void C() { - Console.WriteLine("B.Conflict.C"); - } - - [UnmanagedCallersOnly(EntryPoint = "B_Conflict_C\u733f")] - public static void C\u733f() { - Console.WriteLine("B.Conflict.C_\U0001F412"); - } - } - } - """; - - (buildArgs, string output) = BuildForVariadicFunctionTests( - code, - buildArgs with { ProjectName = $"cb_namespace_{buildArgs.Config}" }, - id - ); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "cb_namespace"); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "UnmanagedCallbackNamespaced.cs")); + string output = PublishForVariadicFunctionTests(info, config, aot); Assert.DoesNotMatch(".*(warning|error).*>[A-Z0-9]+__Foo", output); - output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.Contains("A.Conflict.C", output); - Assert.Contains("B.Conflict.C", output); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.Contains("A.Conflict.C", result.TestOutput); + Assert.Contains("B.Conflict.C", result.TestOutput); if (OperatingSystem.IsWindows()) { // Windows console unicode support is not great - Assert.Contains("A.Conflict.C_", output); - Assert.Contains("B.Conflict.C_", output); + Assert.Contains(result.TestOutput, m => m.Contains("A.Conflict.C_")); + Assert.Contains(result.TestOutput, m => m.Contains("B.Conflict.C_")); } else { - Assert.Contains("A.Conflict.C_\U0001F412", output); - Assert.Contains("B.Conflict.C_\U0001F412", output); + Assert.Contains("A.Conflict.C_\U0001F412", result.TestOutput); + Assert.Contains("B.Conflict.C_\U0001F412", result.TestOutput); } } [Theory] - [BuildAndRun(host: RunHost.None)] - public void IcallWithOverloadedParametersAndEnum(BuildArgs buildArgs, string id) + [BuildAndRun()] + public void IcallWithOverloadedParametersAndEnum(Configuration config, bool aot) { - // Build a library containing icalls with overloaded parameters. - - string code = + string appendToTheEnd = """ - using System; - using System.Runtime.CompilerServices; - - public static class Interop - { - public enum Numbers { A, B, C, D } - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void Square(Numbers x); - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern void Square(Numbers x, Numbers y); - - public static void Main() - { - // Noop - } - } - """; - - var libraryBuildArgs = ExpandBuildArgs( - buildArgs with { ProjectName = $"icall_enum_library_{buildArgs.Config}_{id}" } - ); - - (string libraryDir, string output) = BuildProject( - libraryBuildArgs, - id: id + "library", - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - }, - Publish: false, - DotnetWasmFromRuntimePack: false, - AssertAppBundle: false - ) - ); - - // Build a project with ManagedToNativeGenerator task reading icalls from the above library and runtime-icall-table.h bellow. - - string projectCode = - """ - @@ -581,10 +205,20 @@ public static void Main() - """; - string AddAssembly(string name) => $""; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "icall_enum", insertAtEnd: appendToTheEnd); + // build a library containing icalls with overloaded parameters. + ReplaceFile(Path.Combine("..", "Library", "Library.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "ICall_Lib.cs")); + // temporarily change the project directory to build the library + _projectDir = Path.Combine(_projectDir, "..", "Library"); + bool isPublish = false; + // libraries do not have framework dirs + string hypotheticalFrameworkDir = Path.Combine(GetBinFrameworkDir(config, isPublish)); + string libAssemblyPath = Path.Combine(hypotheticalFrameworkDir, "..", ".."); + BuildProject(info, config, new BuildOptions(AssertAppBundle: false, AOT: aot)); + // restore the project directory + _projectDir = Path.Combine(_projectDir, "..", "App"); string icallTable = """ @@ -595,6 +229,7 @@ public static void Main() ] """; + UpdateFile(Path.Combine(_projectDir, "runtime-icall-table.h"), icallTable); string tasksDir = Path.Combine(s_buildEnv.WorkloadPacksDir, "Microsoft.NET.Runtime.WebAssembly.Sdk", @@ -622,343 +257,72 @@ public static void Main() _testOutput.WriteLine ("Using WasmAppBuilder.dll from {0}", taskPath); - projectCode = projectCode - .Replace("###WasmPInvokeModule###", AddAssembly("System.Private.CoreLib") + AddAssembly("System.Runtime") + AddAssembly(libraryBuildArgs.ProjectName)) - .Replace("###WasmAppBuilder###", taskPath); - - buildArgs = buildArgs with { ProjectName = $"icall_enum_{buildArgs.Config}_{id}", ProjectFileContents = projectCode }; - - _projectDir = null; - - (_, output) = BuildProject( - buildArgs, - id: id + "tasks", - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "runtime-icall-table.h"), icallTable); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - UseCache: false, - AssertAppBundle: false - ) - ); - + string AddAssembly(string assemblyLocation, string name) => $""; + string frameworkDir = Path.Combine(GetBinFrameworkDir(config, isPublish)); + string appAssemblyPath = Path.Combine(frameworkDir, "..", ".."); + string pinvokeReplacement = + AddAssembly(appAssemblyPath, "System.Private.CoreLib") + + AddAssembly(appAssemblyPath, "System.Runtime") + + AddAssembly(libAssemblyPath, "Library"); + UpdateFile("WasmBasicTestApp.csproj", new Dictionary { + { "###WasmPInvokeModule###", pinvokeReplacement }, + { "###WasmAppBuilder###", taskPath } + }); + + // Build a project with ManagedToNativeGenerator task reading icalls from the above library and runtime-icall-table.h + (_, string output) = BuildProject(info, config, new BuildOptions(UseCache: false, AOT: aot)); Assert.DoesNotMatch(".*warning.*Numbers", output); } [Theory] - [BuildAndRun(host: RunHost.Chrome, parameters: new object[] { "tr_TR.UTF-8" })] - public void BuildNativeInNonEnglishCulture(BuildArgs buildArgs, string culture, RunHost host, string id) + [BuildAndRun(parameters: new object[] { "tr_TR.UTF-8" })] + public async void BuildNativeInNonEnglishCulture(Configuration config, bool aot, string culture) { // Check that we can generate interp tables in non-english cultures // Prompted by https://github.com/dotnet/runtime/issues/71149 - string code = @" - using System; - using System.Runtime.InteropServices; - - Console.WriteLine($""square: {square(5)}""); - return 42; - - [DllImport(""simple"")] static extern int square(int x); - "; - - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: @$"", - extraProperties: buildArgs.AOT - ? string.Empty - : "true"); + string extraItems = @$""; + string extraProperties = aot ? string.Empty : "true"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "buildNativeNonEng", extraItems: extraItems); + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "BuildNative.cs")); + string cCodeFilename = "simple.c"; + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", cCodeFilename), Path.Combine(_projectDir, cCodeFilename)); var extraEnvVars = new Dictionary { { "LANG", culture }, { "LC_ALL", culture }, }; - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "simple.c"), - Path.Combine(_projectDir!, "simple.c")); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false, - ExtraBuildEnvironmentVariables: extraEnvVars - )); - - output = RunAndTestWasmApp(buildArgs, - buildDir: _projectDir, - expectedExitCode: 42, - host: host, - id: id, - envVars: extraEnvVars); - Assert.Contains("square: 25", output); - } - - [Theory] - [BuildAndRun(host: RunHost.Chrome, parameters: new object[] { new object[] { - "with-hyphen", - "with#hash-and-hyphen", - "with.per.iod", - "with🚀unicode#" - } })] - - public void CallIntoLibrariesWithNonAlphanumericCharactersInTheirNames(BuildArgs buildArgs, string[] libraryNames, RunHost host, string id) - { - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: @$"", - extraProperties: buildArgs.AOT - ? string.Empty - : "true"); - - int baseArg = 10; - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => GenerateSourceFiles(_projectDir!, baseArg), - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: false - )); - - output = RunAndTestWasmApp(buildArgs, - buildDir: _projectDir, - expectedExitCode: 42, - host: host, - id: id); - - for (int i = 0; i < libraryNames.Length; i ++) - { - Assert.Contains($"square_{i}: {(i + baseArg) * (i + baseArg)}", output); - } - - void GenerateSourceFiles(string outputPath, int baseArg) - { - StringBuilder csBuilder = new($@" - using System; - using System.Runtime.InteropServices; - "); - - StringBuilder dllImportsBuilder = new(); - for (int i = 0; i < libraryNames.Length; i ++) - { - dllImportsBuilder.AppendLine($"[DllImport(\"{libraryNames[i]}\")] static extern int square_{i}(int x);"); - csBuilder.AppendLine($@"Console.WriteLine($""square_{i}: {{square_{i}({i + baseArg})}}"");"); - - string nativeCode = $@" - #include - - int square_{i}(int x) - {{ - return x * x; - }}"; - File.WriteAllText(Path.Combine(outputPath, $"{libraryNames[i]}.c"), nativeCode); - } - - csBuilder.AppendLine("return 42;"); - csBuilder.Append(dllImportsBuilder); - - File.WriteAllText(Path.Combine(outputPath, "Program.cs"), csBuilder.ToString()); - } - } - - private (BuildArgs, string) BuildForVariadicFunctionTests(string programText, BuildArgs buildArgs, string id, string? verbosity = null, string extraProperties = "") - { - extraProperties += "true<_WasmDevel>true"; - - string filename = "variadic.o"; - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: $"", - extraProperties: extraProperties); - - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", filename), - Path.Combine(_projectDir!, filename)); - }, - Publish: buildArgs.AOT, - Verbosity: verbosity, - DotnetWasmFromRuntimePack: false)); - - return (buildArgs, output); - } - - private void EnsureComInteropCompiles(BuildArgs buildArgs, RunHost host, string id) - { - string programText = @" - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - using System.Runtime.InteropServices.ComTypes; - - public class Test - { - public static int Main(string[] args) - { - var s = new STGMEDIUM(); - ReleaseStgMedium(ref s); - return 42; - } - - [DllImport(""ole32.dll"")] - internal static extern void ReleaseStgMedium(ref STGMEDIUM medium); - } - - "; - - buildArgs = ExpandBuildArgs(buildArgs); - - (string libraryDir, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - }, - Publish: buildArgs.AOT, - DotnetWasmFromRuntimePack: true)); - - Assert.Contains("Generated app bundle at " + libraryDir, output); + (_, string output) = PublishProject(info, + config, + new PublishOptions(ExtraBuildEnvironmentVariables: extraEnvVars, AOT: aot), + isNativeBuild: true); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42, + Locale: culture + )); + Assert.Contains("square: 25", result.TestOutput); } - private void EnsureWasmAbiRulesAreFollowed(BuildArgs buildArgs, RunHost host, string id) + private async Task EnsureWasmAbiRulesAreFollowed(Configuration config, bool aot) { - string programText = @" - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - public struct SingleFloatStruct { - public float Value; - } - public struct SingleDoubleStruct { - public struct Nested1 { - // This field is private on purpose to ensure we treat visibility correctly - double Value; - } - public Nested1 Value; - } - public struct SingleI64Struct { - public Int64 Value; - } - public struct PairStruct { - public int A, B; - } - public unsafe struct MyFixedArray { - public fixed int elements[2]; - } - [System.Runtime.CompilerServices.InlineArray(2)] - public struct MyInlineArray { - public int element0; - } - - public class Test - { - public static unsafe int Main(string[] argv) - { - var i64_a = 0xFF00FF00FF00FF0L; - var i64_b = ~i64_a; - var resI = direct64(i64_a); - Console.WriteLine(""l (l)="" + resI); - - var sis = new SingleI64Struct { Value = i64_a }; - var resSI = indirect64(sis); - Console.WriteLine(""s (s)="" + resSI.Value); - - var resF = direct(3.14); - Console.WriteLine(""f (d)="" + resF); - - SingleDoubleStruct sds = default; - Unsafe.As(ref sds) = 3.14; - - resF = indirect_arg(sds); - Console.WriteLine(""f (s)="" + resF); - - var res = indirect(sds); - Console.WriteLine(""s (s)="" + res.Value); - - var pair = new PairStruct { A = 1, B = 2 }; - var paires = accept_and_return_pair(pair); - Console.WriteLine(""paires.B="" + paires.B); - - // This test is split into methods to simplify debugging issues with it - var ia = InlineArrayTest1(); - var iares = InlineArrayTest2(ia); - Console.WriteLine($""iares[0]={iares[0]} iares[1]={iares[1]}""); - - MyFixedArray fa = new (); - for (int i = 0; i < 2; i++) - fa.elements[i] = i; - var fares = accept_and_return_fixedarray(fa); - Console.WriteLine(""fares.elements[1]="" + fares.elements[1]); - - return (int)res.Value; - } - - public static unsafe MyInlineArray InlineArrayTest1 () { - MyInlineArray ia = new (); - for (int i = 0; i < 2; i++) - ia[i] = i; - return ia; - } - - public static unsafe MyInlineArray InlineArrayTest2 (MyInlineArray ia) { - return accept_and_return_inlinearray(ia); - } - - [DllImport(""wasm-abi"", EntryPoint=""accept_double_struct_and_return_float_struct"")] - public static extern SingleFloatStruct indirect(SingleDoubleStruct arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_double_struct_and_return_float_struct"")] - public static extern float indirect_arg(SingleDoubleStruct arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_double_struct_and_return_float_struct"")] - public static extern float direct(double arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_i64_struct"")] - public static extern SingleI64Struct indirect64(SingleI64Struct arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_i64_struct"")] - public static extern Int64 direct64(Int64 arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_pair"")] - public static extern PairStruct accept_and_return_pair(PairStruct arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_fixedarray"")] - public static extern MyFixedArray accept_and_return_fixedarray(MyFixedArray arg); - - [DllImport(""wasm-abi"", EntryPoint=""accept_and_return_inlinearray"")] - public static extern MyInlineArray accept_and_return_inlinearray(MyInlineArray arg); - }"; - - var extraProperties = "true<_WasmDevel>falsefalse"; var extraItems = @""; + var extraProperties = "true<_WasmDevel>falsefalse"; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "abi", extraItems: extraItems, extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "AbiRules.cs")); + string cCodeFilename = "wasm-abi.c"; + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", cCodeFilename), Path.Combine(_projectDir, cCodeFilename)); - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: extraItems, - extraProperties: extraProperties); - - (string libraryDir, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "wasm-abi.c"), - Path.Combine(_projectDir!, "wasm-abi.c")); - }, - Publish: buildArgs.AOT, - // Verbosity: "diagnostic", - DotnetWasmFromRuntimePack: false)); + bool isPublish = aot; + (string _, string _) = isPublish ? + PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true) : + BuildProject(info, config, new BuildOptions(AOT: aot), isNativeBuild: true); - string objDir = Path.Combine(_projectDir!, "obj", buildArgs.Config!, "net9.0", "browser-wasm", "wasm", buildArgs.AOT ? "for-publish" : "for-build"); + string objDir = Path.Combine(_projectDir, "obj", config.ToString(), DefaultTargetFramework, "wasm", isPublish ? "for-publish" : "for-build"); // Verify that the right signature was added for the pinvoke. We can't determine this by examining the m2n file // FIXME: Not possible in in-process mode for some reason, even with verbosity at "diagnostic" @@ -972,58 +336,60 @@ public static unsafe MyInlineArray InlineArrayTest2 (MyInlineArray ia) { Assert.Contains("float accept_double_struct_and_return_float_struct (double);", pinvokeTable); Assert.Contains("int64_t accept_and_return_i64_struct (int64_t);", pinvokeTable); - var runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 3, host: host, id: id); - Assert.Contains("l (l)=-1148435428713435121", runOutput); - Assert.Contains("s (s)=-1148435428713435121", runOutput); - Assert.Contains("f (d)=3.14", runOutput); - Assert.Contains("f (s)=3.14", runOutput); - Assert.Contains("s (s)=3.14", runOutput); - Assert.Contains("paires.B=4", runOutput); - Assert.Contains("iares[0]=32", runOutput); - Assert.Contains("iares[1]=2", runOutput); - Assert.Contains("fares.elements[1]=2", runOutput); + var runOptions = new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 3); + RunResult result = isPublish ? await RunForPublishWithWebServer(runOptions) : await RunForBuildWithDotnetRun(runOptions); + Assert.Contains("l (l)=-1148435428713435121", result.TestOutput); + Assert.Contains("s (s)=-1148435428713435121", result.TestOutput); + Assert.Contains("f (d)=3.14", result.TestOutput); + Assert.Contains("f (s)=3.14", result.TestOutput); + Assert.Contains("s (s)=3.14", result.TestOutput); + Assert.Contains("paires.B=4", result.TestOutput); + Assert.Contains(result.TestOutput, m => m.Contains("iares[0]=32")); + Assert.Contains(result.TestOutput, m => m.Contains("iares[1]=2")); + Assert.Contains("fares.elements[1]=2", result.TestOutput); } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: true)] - public void EnsureWasmAbiRulesAreFollowedInAOT(BuildArgs buildArgs, RunHost host, string id) => - EnsureWasmAbiRulesAreFollowed(buildArgs, host, id); + [BuildAndRun(aot: true, config: Configuration.Release)] + public async void EnsureWasmAbiRulesAreFollowedInAOT(Configuration config, bool aot) => + await EnsureWasmAbiRulesAreFollowed(config, aot); [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: false)] - public void EnsureWasmAbiRulesAreFollowedInInterpreter(BuildArgs buildArgs, RunHost host, string id) => - EnsureWasmAbiRulesAreFollowed(buildArgs, host, id); + [BuildAndRun(aot: false)] + public async void EnsureWasmAbiRulesAreFollowedInInterpreter(Configuration config, bool aot) => + await EnsureWasmAbiRulesAreFollowed(config, aot); [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: true)] - public void EnsureComInteropCompilesInAOT(BuildArgs buildArgs, RunHost host, string id) => - EnsureComInteropCompiles(buildArgs, host, id); + [BuildAndRun(aot: true, config: Configuration.Release)] + public void EnsureComInteropCompilesInAOT(Configuration config, bool aot) + { + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "com"); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "ComInterop.cs")); + bool isPublish = aot; + (string libraryDir, string output) = isPublish ? + PublishProject(info, config, new PublishOptions(AOT: aot)) : + BuildProject(info, config, new BuildOptions(AOT: aot)); + } [Theory] - [BuildAndRun(host: RunHost.Chrome, aot: false)] - public void UCOWithSpecialCharacters(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(aot: false)] + public async void UCOWithSpecialCharacters(Configuration config, bool aot) { var extraProperties = "true"; var extraItems = @""; - - buildArgs = ExpandBuildArgs(buildArgs, - extraItems: extraItems, - extraProperties: extraProperties); - - (string libraryDir, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "Wasm.Buid.Tests.Programs", "UnmanagedCallback.cs"), Path.Combine(_projectDir!, "Program.cs")); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "local.c"), Path.Combine(_projectDir!, "local.c")); - }, - Publish: true, - DotnetWasmFromRuntimePack: false)); - - var runOutput = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); - Assert.DoesNotContain("Conflict.A.Managed8\u4F60Func(123) -> 123", runOutput); - Assert.Contains("ManagedFunc returned 42", runOutput); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "uoc", extraItems: extraItems, extraProperties: extraProperties); + ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "UnmanagedCallback.cs")); + string cCodeFilename = "local.c"; + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", cCodeFilename), Path.Combine(_projectDir, cCodeFilename)); + + PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42 + )); + Assert.DoesNotContain("Conflict.A.Managed8\u4F60Func(123) -> 123", result.TestOutput); + Assert.Contains("ManagedFunc returned 42", result.TestOutput); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTestsBase.cs new file mode 100644 index 0000000000000..983cd4742c8bd --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTestsBase.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests +{ + public class PInvokeTableGeneratorTestsBase : WasmTemplateTestsBase + { + public PInvokeTableGeneratorTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + protected string PublishForVariadicFunctionTests(ProjectInfo info, Configuration config, bool aot, string? verbosity = null, bool isNativeBuild = true) + { + string verbosityArg = verbosity == null ? string.Empty : $" -v:{verbosity}"; + // NativeFileReference forces native build + (_, string output) = PublishProject(info, + config, + new PublishOptions(ExtraMSBuildArgs: verbosityArg, AOT: aot), + isNativeBuild: isNativeBuild); + return output; + } + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index 417bf4b8d2a51..236d8f1901c0d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -20,6 +20,7 @@ namespace Wasm.Build.Tests; // For projects using WasmAppBuilder +// ToDo: REMOVE, use WasmSdkBasedProjectProvider only public abstract class ProjectProviderBase(ITestOutputHelper _testOutput, string? _projectDir) { public static string WasmAssemblyExtension = BuildTestBase.s_buildEnv.UseWebcil ? ".wasm" : ".dll"; @@ -38,12 +39,12 @@ public abstract class ProjectProviderBase(ITestOutputHelper _testOutput, string? protected BuildEnvironment _buildEnv = BuildTestBase.s_buildEnv; protected abstract string BundleDirName { get; } - public bool IsFingerprintingSupported { get; protected set; } + public bool IsFingerprintingEnabled => EnvironmentVariables.UseFingerprinting; - public bool IsFingerprintingEnabled => IsFingerprintingSupported && EnvironmentVariables.UseFingerprinting; + public bool IsFingerprintingOnDotnetJsEnabled => EnvironmentVariables.UseFingerprintingDotnetJS; // Returns the actual files on disk - public IReadOnlyDictionary AssertBasicBundle(AssertBundleOptionsBase assertOptions) + public IReadOnlyDictionary AssertBasicBundle(AssertBundleOptions assertOptions) { EnsureProjectDirIsSet(); var dotnetFiles = FindAndAssertDotnetFiles(assertOptions); @@ -77,17 +78,17 @@ public IReadOnlyDictionary AssertBasicBundle(AssertBundl return dotnetFiles; } - public IReadOnlyDictionary FindAndAssertDotnetFiles(AssertBundleOptionsBase assertOptions) + public IReadOnlyDictionary FindAndAssertDotnetFiles(AssertBundleOptions assertOptions) { EnsureProjectDirIsSet(); return FindAndAssertDotnetFiles(binFrameworkDir: assertOptions.BinFrameworkDir, - expectFingerprintOnDotnetJs: assertOptions.ExpectFingerprintOnDotnetJs, + expectFingerprintOnDotnetJs: IsFingerprintingOnDotnetJsEnabled, superSet: GetAllKnownDotnetFilesToFingerprintMap(assertOptions), expected: GetDotNetFilesExpectedSet(assertOptions)); } - protected abstract IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions); - protected abstract IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptionsBase assertOptions); + protected abstract IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptions assertOptions); + protected abstract IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptions assertOptions); public IReadOnlyDictionary FindAndAssertDotnetFiles( string binFrameworkDir, @@ -201,34 +202,38 @@ private void AssertDotNetFilesSet( } } - public void CompareStat(IDictionary oldStat, IDictionary newStat, IEnumerable<(string fullpath, bool unchanged)> expected) + public void CompareStat(IDictionary oldStat, IDictionary newStat, IDictionary expected) { StringBuilder msg = new(); foreach (var expect in expected) { - string expectFilename = Path.GetFileName(expect.fullpath); - if (!oldStat.TryGetValue(expectFilename, out FileStat? oldFs)) + if (!oldStat.TryGetValue(expect.Key, out FileStat? oldFs)) { - msg.AppendLine($"Could not find an entry for {expectFilename} in old files"); + msg.AppendLine($"Could not find an entry for {expect.Key} in old files"); continue; } - if (!newStat.TryGetValue(expectFilename, out FileStat? newFs)) + if (!newStat.TryGetValue(expect.Key, out FileStat? newFs)) { - msg.AppendLine($"Could not find an entry for {expectFilename} in new files"); + msg.AppendLine($"Could not find an entry for {expect.Key} in new files"); continue; } - bool actualUnchanged = oldFs == newFs; - if (expect.unchanged && !actualUnchanged) + // files never existed existed => no change + // fingerprinting is enabled => can't compare paths + bool actualUnchanged = (!oldFs.Exists && !newFs.Exists) || + IsFingerprintingEnabled && (oldFs.Length == newFs.Length && oldFs.LastWriteTimeUtc == newFs.LastWriteTimeUtc) || + !IsFingerprintingEnabled && oldFs == newFs; + + if (expect.Value.unchanged && !actualUnchanged) { - msg.AppendLine($"[Expected unchanged file: {expectFilename}]{Environment.NewLine}" + + msg.AppendLine($"[Expected unchanged file: {expect.Key}]{Environment.NewLine}" + $" old: {oldFs}{Environment.NewLine}" + $" new: {newFs}"); } - else if (!expect.unchanged && actualUnchanged) + else if (!expect.Value.unchanged && actualUnchanged) { - msg.AppendLine($"[Expected changed file: {expectFilename}]{Environment.NewLine}" + + msg.AppendLine($"[Expected changed file: {expect.Key}]{Environment.NewLine}" + $" {newFs}"); } } @@ -237,31 +242,75 @@ public void CompareStat(IDictionary oldStat, IDictionary StatFiles(IEnumerable fullpaths) + public IDictionary StatFiles(IDictionary pathsDict) { Dictionary table = new(); - foreach (string file in fullpaths) + foreach (var fileInfo in pathsDict) { - if (File.Exists(file)) - table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length)); + string file = fileInfo.Value.fullPath; + string nameNoFingerprinting = fileInfo.Key; + bool exists = File.Exists(file); + if (exists) + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length)); + } else - table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0)); + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0)); + } } return table; } - public static string FindSubDirIgnoringCase(string parentDir, string dirName) + public IDictionary StatFilesAfterRebuild(IDictionary pathsDict) + { + if (!IsFingerprintingEnabled) + return StatFiles(pathsDict); + + // files are expected to be fingerprinted, so we cannot rely on the paths that come with pathsDict, an update is needed + Dictionary table = new(); + foreach (var fileInfo in pathsDict) + { + string file = fileInfo.Value.fullPath; + string nameNoFingerprinting = fileInfo.Key; + string[] filesMatchingName = GetFilesMatchingNameConsideringFingerprinting(file, nameNoFingerprinting); + if (filesMatchingName.Length > 1) + { + string? fileMatch = filesMatchingName.FirstOrDefault(f => f != file); + if (fileMatch != null) + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: fileMatch, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(fileMatch), Length: new FileInfo(fileMatch).Length)); + } + } + if (filesMatchingName.Length == 0 || (filesMatchingName.Length == 1 && !File.Exists(file))) + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0)); + } + if (filesMatchingName.Length == 1 && File.Exists(file)) + { + table.Add(nameNoFingerprinting, new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length)); + } + } + return table; + } + + private string[] GetFilesMatchingNameConsideringFingerprinting(string filePath, string nameNoFingerprinting) { - IEnumerable matchingDirs = Directory.EnumerateDirectories(parentDir, - dirName, - new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }); + var directory = Path.GetDirectoryName(filePath); + if (directory == null) + return Array.Empty(); - string? first = matchingDirs.FirstOrDefault(); - if (matchingDirs.Count() > 1) - throw new Exception($"Found multiple directories with names that differ only in case. {string.Join(", ", matchingDirs.ToArray())}"); + string fileNameWithoutExtensionAndFingerprinting = Path.GetFileNameWithoutExtension(nameNoFingerprinting); + string fileExtension = Path.GetExtension(filePath); - return first ?? Path.Combine(parentDir, dirName); + // search for files that match the name in the directory, skipping fingerprinting + string[] files = Directory.GetFiles(directory, $"{fileNameWithoutExtensionAndFingerprinting}*{fileExtension}"); + + // filter files with a single fingerprint segment, e.g. "dotnet*.js" should not catch "dotnet.native.d1au9i.js" but should catch "dotnet.js" + string pattern = $@"^{Regex.Escape(fileNameWithoutExtensionAndFingerprinting)}(\.[^.]+)?{Regex.Escape(fileExtension)}$"; + var tmp = files.Where(f => Regex.IsMatch(Path.GetFileName(f), pattern)).ToArray(); + return tmp; } public IDictionary GetFilesTable(bool unchanged, params string[] baseDirs) @@ -276,11 +325,11 @@ public static string FindSubDirIgnoringCase(string parentDir, string dirName) return dict; } - public IDictionary GetFilesTable(BuildArgs buildArgs, BuildPaths paths, bool unchanged) + public IDictionary GetFilesTable(string projectName, bool isAOT, BuildPaths paths, bool unchanged) { List files = new() { - Path.Combine(paths.BinDir, "publish", $"{buildArgs.ProjectName}.dll"), + Path.Combine(paths.BinDir, "publish", BundleDirName, "_framework", $"{projectName}{WasmAssemblyExtension}"), Path.Combine(paths.ObjWasmDir, "driver.o"), Path.Combine(paths.ObjWasmDir, "runtime.o"), Path.Combine(paths.ObjWasmDir, "corebindings.o"), @@ -290,20 +339,20 @@ public static string FindSubDirIgnoringCase(string parentDir, string dirName) Path.Combine(paths.ObjWasmDir, "pinvoke-table.h"), Path.Combine(paths.ObjWasmDir, "driver-gen.c"), - Path.Combine(paths.BundleDir, "_framework", "dotnet.native.wasm"), - Path.Combine(paths.BundleDir, "_framework", "dotnet.native.js"), - Path.Combine(paths.BundleDir, "_framework", "dotnet.globalization.js"), + Path.Combine(paths.BinFrameworkDir, "dotnet.native.wasm"), + Path.Combine(paths.BinFrameworkDir, "dotnet.native.js"), + Path.Combine(paths.BinFrameworkDir, "dotnet.globalization.js"), }; - if (buildArgs.AOT) + if (isAOT) { files.AddRange(new[] { - Path.Combine(paths.ObjWasmDir, $"{buildArgs.ProjectName}.dll.bc"), - Path.Combine(paths.ObjWasmDir, $"{buildArgs.ProjectName}.dll.o"), + Path.Combine(paths.ObjWasmDir, $"{projectName}.dll.bc"), + Path.Combine(paths.ObjWasmDir, $"{projectName}.dll.o"), - Path.Combine(paths.ObjWasmDir, "System.Private.CoreLib.dll.bc"), - Path.Combine(paths.ObjWasmDir, "System.Private.CoreLib.dll.o"), + Path.Combine(paths.ObjWasmDir, $"System.Private.CoreLib.dll.bc"), + Path.Combine(paths.ObjWasmDir, $"System.Private.CoreLib.dll.o"), }); } @@ -312,12 +361,38 @@ public static string FindSubDirIgnoringCase(string parentDir, string dirName) dict[Path.GetFileName(file)] = (file, unchanged); // those files do not change on re-link - dict["dotnet.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.js"), true); - dict["dotnet.js.map"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.js.map"), true); - dict["dotnet.runtime.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.runtime.js"), true); - dict["dotnet.runtime.js.map"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.runtime.js.map"), true); - dict["dotnet.globalization.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.globalization.js"), true); + dict["dotnet.js"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.js"), true); + dict["dotnet.js.map"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.js.map"), true); + dict["dotnet.runtime.js"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.runtime.js"), true); + dict["dotnet.runtime.js.map"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.runtime.js.map"), true); + dict["dotnet.globalization.js"]=(Path.Combine(paths.BinFrameworkDir, "dotnet.globalization.js"), true); + + if (IsFingerprintingEnabled) + { + string bootJsonPath = Path.Combine(paths.BinFrameworkDir, "blazor.boot.json"); + BootJsonData bootJson = GetBootJson(bootJsonPath); + var keysToUpdate = new List(); + var updates = new List<(string oldKey, string newKey, (string fullPath, bool unchanged) value)>(); + foreach (var expectedItem in dict) + { + string filename = Path.GetFileName(expectedItem.Value.fullPath); + var expectedFingerprintedItem = bootJson.resources.fingerprinting + .Where(kv => kv.Value == filename) + .SingleOrDefault().Key; + + if (string.IsNullOrEmpty(expectedFingerprintedItem)) + continue; + if (filename != expectedFingerprintedItem) + { + string newKey = Path.Combine( + Path.GetDirectoryName(expectedItem.Value.fullPath) ?? "", + expectedFingerprintedItem + ); + dict[filename] = (newKey, expectedItem.Value.unchanged); + } + } + } return dict; } @@ -337,23 +412,23 @@ public static void AssertRuntimePackPath(string buildOutput, string targetFramew throw new XunitException($"Runtime pack path doesn't match.{Environment.NewLine}Expected: '{expectedRuntimePackDir}'{Environment.NewLine}Actual: '{actualPath}'"); } - public static void AssertDotNetJsSymbols(AssertBundleOptionsBase assertOptions) + public static void AssertDotNetJsSymbols(AssertBundleOptions assertOptions) { TestUtils.AssertFilesExist(assertOptions.BinFrameworkDir, new[] { "dotnet.native.js.symbols" }, expectToExist: assertOptions.ExpectSymbolsFile); - if (assertOptions.ExpectedFileType == NativeFilesType.FromRuntimePack) + if (assertOptions.BuildOptions.ExpectedFileType == NativeFilesType.FromRuntimePack) { TestUtils.AssertFile( - Path.Combine(BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.TargetFramework, assertOptions.RuntimeType), "dotnet.native.js.symbols"), + Path.Combine(BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.BuildOptions.TargetFramework, assertOptions.BuildOptions.RuntimeType), "dotnet.native.js.symbols"), Path.Combine(assertOptions.BinFrameworkDir, "dotnet.native.js.symbols"), same: true); } } - public void AssertIcuAssets(AssertBundleOptionsBase assertOptions, BootJsonData bootJson) + public void AssertIcuAssets(AssertBundleOptions assertOptions, BootJsonData bootJson) { List expected = new(); - switch (assertOptions.GlobalizationMode) + switch (assertOptions.BuildOptions.GlobalizationMode) { case GlobalizationMode.Invariant: break; @@ -365,11 +440,11 @@ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions, BootJsonData expected.Add("segmentation-rules.json"); break; case GlobalizationMode.Custom: - if (string.IsNullOrEmpty(assertOptions.CustomIcuFile)) + if (string.IsNullOrEmpty(assertOptions.BuildOptions.CustomIcuFile)) throw new ArgumentException("WasmBuildTest is invalid, value for Custom globalization mode is required when GlobalizationMode=Custom."); // predefined ICU name can be identical with the icu files from runtime pack - expected.Add(Path.GetFileName(assertOptions.CustomIcuFile)); + expected.Add(Path.GetFileName(assertOptions.BuildOptions.CustomIcuFile)); break; case GlobalizationMode.Sharded: // icu shard chosen based on the locale @@ -378,11 +453,11 @@ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions, BootJsonData expected.Add("icudt_no_CJK.dat"); break; default: - throw new NotImplementedException($"Unknown {nameof(assertOptions.GlobalizationMode)} = {assertOptions.GlobalizationMode}"); + throw new NotImplementedException($"Unknown {nameof(assertOptions.BuildOptions.GlobalizationMode)} = {assertOptions.BuildOptions.GlobalizationMode}"); } IEnumerable actual = Directory.EnumerateFiles(assertOptions.BinFrameworkDir, "icudt*dat"); - if (assertOptions.GlobalizationMode == GlobalizationMode.Hybrid) + if (assertOptions.BuildOptions.GlobalizationMode == GlobalizationMode.Hybrid) actual = actual.Union(Directory.EnumerateFiles(assertOptions.BinFrameworkDir, "segmentation-rules*json")); if (IsFingerprintingEnabled) @@ -401,24 +476,27 @@ public void AssertIcuAssets(AssertBundleOptionsBase assertOptions, BootJsonData } AssertFileNames(expected, actual); - if (assertOptions.GlobalizationMode is GlobalizationMode.Custom) + if (assertOptions.BuildOptions.GlobalizationMode is GlobalizationMode.Custom) { - string srcPath = assertOptions.CustomIcuFile!; - string runtimePackDir = BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.TargetFramework, assertOptions.RuntimeType); + string srcPath = assertOptions.BuildOptions.CustomIcuFile!; + string runtimePackDir = BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.BuildOptions.TargetFramework, assertOptions.BuildOptions.RuntimeType); if (!Path.IsPathRooted(srcPath)) - srcPath = Path.Combine(runtimePackDir, assertOptions.CustomIcuFile!); + srcPath = Path.Combine(runtimePackDir, assertOptions.BuildOptions.CustomIcuFile!); TestUtils.AssertSameFile(srcPath, actual.Single()); } } - public BootJsonData AssertBootJson(AssertBundleOptionsBase options) + private BootJsonData GetBootJson(string bootJsonPath) { - EnsureProjectDirIsSet(); - string binFrameworkDir = options.BinFrameworkDir; - string bootJsonPath = Path.Combine(binFrameworkDir, options.BootJsonFileName); Assert.True(File.Exists(bootJsonPath), $"Expected to find {bootJsonPath}"); + return ParseBootData(bootJsonPath); + } - BootJsonData bootJson = ParseBootData(bootJsonPath); + public BootJsonData AssertBootJson(AssertBundleOptions options) + { + EnsureProjectDirIsSet(); + string bootJsonPath = Path.Combine(options.BinFrameworkDir, options.BuildOptions.BootConfigFileName); + BootJsonData bootJson = GetBootJson(bootJsonPath); string spcExpectedFilename = $"System.Private.CoreLib{WasmAssemblyExtension}"; if (IsFingerprintingEnabled) @@ -459,7 +537,7 @@ public BootJsonData AssertBootJson(AssertBundleOptionsBase options) string extension = Path.GetExtension(expectedFilename).Substring(1); if (ShouldCheckFingerprint(expectedFilename: expectedFilename, - expectFingerprintOnDotnetJs: options.ExpectFingerprintOnDotnetJs, + expectFingerprintOnDotnetJs: IsFingerprintingOnDotnetJsEnabled, expectFingerprintForThisFile: expectFingerprint)) { return Regex.Match(item, $"{prefix}{s_dotnetVersionHashRegex}{extension}").Success; @@ -519,7 +597,7 @@ private void AssertFileNames(IEnumerable expected, IEnumerable a Assert.Equal(expected, actualFileNames); } - public virtual string FindBinFrameworkDir(string config, bool forPublish, string framework, string? projectDir = null) + public virtual string GetBinFrameworkDir(Configuration config, bool forPublish, string framework, string? projectDir = null) { throw new NotImplementedException(); } diff --git a/src/mono/wasm/Wasm.Build.Tests/README.md b/src/mono/wasm/Wasm.Build.Tests/README.md index 19fcaa84cf3c3..be629ccae4314 100644 --- a/src/mono/wasm/Wasm.Build.Tests/README.md +++ b/src/mono/wasm/Wasm.Build.Tests/README.md @@ -18,7 +18,8 @@ Linux/macOS: `$ make -C src/mono/(browser|wasi) run-build-tests` Windows: `.\dotnet.cmd build .\src\mono\wasm\Wasm.Build.Tests\Wasm.Build.Tests.csproj -c Release -t:Test -p:TargetOS=browser -p:TargetArchitecture=wasm` - Specific tests can be run via `XUnitClassName`, and `XUnitMethodName` - - eg. `XUnitClassName=Wasm.Build.Tests.BlazorWasmTests` + - e.g. `XUnitClassName=Wasm.Build.Tests.BlazorWasmTests` for running class of tests + - e.g. `XUnitMethodName=Wasm.Build.Tests.Blazor.MiscTests3.WithDllImportInMainAssembly` for running a specific test. ## Running on helix @@ -32,9 +33,9 @@ Most of the tests are structured on the idea that for a given case (or combination of options), we want to: 1. build once -2. run the same build with different hosts, eg. V8, Chrome, Firefox etc. +2. run the same build with different hosts, eg. Chrome, Firefox etc. -For this, the builds get cached using `BuildArgs` as the key. +For this, the builds get cached using `ProjectInfo` as the key. ## notes: @@ -53,10 +54,15 @@ For this, the builds get cached using `BuildArgs` as the key. use the build bits from the usual locations in artifacts, without requiring regenerating the nugets, and workload re-install. -- Each test gets a randomly generated "id". This `id` can be used to find the - binlogs, or the test directories. +- Each test is saved in directory with randomly generated name and unique `ProjectInfo`. `ProjectInfo` can be used to find the binlogs, or cached builds. ## Useful environment variables - `SHOW_BUILD_OUTPUT` - will show the build output to the console - `SKIP_PROJECT_CLEANUP` - won't remove the temporary project directories generated for the tests + +## How to add tests + +Blazor specific tests should be located in `Blazor` directory. If you are adding a new class with tests, list it in `eng/testing/scenarios/BuildWasmAppsJobsList.txt`. If you are adding a new test to existing class, make sure it does not prolong the execution time significantly. Tests run on parallel on CI and having one class running much longer than the average prolongs the total execution time. + +If you want to test templating mechanism, use `CreateWasmTemplateProject`. Otherwise, use `CopyTestAsset` with either `WasmBasicTestApp` or `BlazorBasicTestApp`, adding your custom `TestScenario` or using a generic `DotnetRun` scenario in case of WASM app and adding a page with test in case of Blazor app. Bigger snippets of code should be saved in `src/mono/wasm/testassets` and placed in the application using methods: `ReplaceFile` or `File.Move`. Replacing existing small parts of code with custom lines is done with `UpdateFile`. \ No newline at end of file diff --git a/src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs index 0a316a4b601c7..a3b8c189f2653 100644 --- a/src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs @@ -14,40 +14,28 @@ namespace Wasm.Build.Tests { - public class RebuildTests : TestMainJsTestBase + public class RebuildTests : WasmTemplateTestsBase { public RebuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable NonNativeDebugRebuildData() - => ConfigWithAOTData(aot: false, config: "Debug") - .WithRunHosts(RunHost.Chrome) - .UnwrapItemsAsArrays().ToList(); - [Theory] - [MemberData(nameof(NonNativeDebugRebuildData))] - public async Task NoOpRebuild(BuildArgs buildArgs, RunHost host, string id) + [BuildAndRun(aot: false, config: Configuration.Debug)] + public async Task NoOpRebuild(Configuration config, bool aot) { - string projectName = $"rebuild_{buildArgs.Config}_{buildArgs.AOT}"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: true, - CreateProject: true)); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "rebuild"); + UpdateFile(Path.Combine("Common", "Program.cs"), s_mainReturns42); + PublishProject(info, config); - Run(); + BrowserRunOptions runOptions = new(config, TestScenario: "DotnetRun", ExpectedExitCode: 42); + await RunForPublishWithWebServer(runOptions); - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); + File.Move(result!.LogFile, Path.ChangeExtension(result.LogFile!, ".first.binlog")); // artificial delay to have new enough timestamps await Task.Delay(5000); @@ -55,19 +43,8 @@ public async Task NoOpRebuild(BuildArgs buildArgs, RunHost host, string id) _testOutput.WriteLine($"{Environment.NewLine}Rebuilding with no changes ..{Environment.NewLine}"); // no-op Rebuild - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: true, - CreateProject: false, - UseCache: false)); - - Run(); - - void Run() => RunAndTestWasmApp( - buildArgs, buildDir: _projectDir, expectedExitCode: 42, - test: output => {}, - host: host, id: id); + PublishProject(info, config, new PublishOptions(UseCache: false)); + await RunForPublishWithWebServer(runOptions); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs index 0f2a4dc136552..c74a21b82d86b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SatelliteAssembliesTests.cs @@ -12,214 +12,117 @@ namespace Wasm.Build.Tests { - public class SatelliteAssembliesTests : TestMainJsTestBase + public class SatelliteAssembliesTests : WasmTemplateTestsBase { public SatelliteAssembliesTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - 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) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) .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 })] - public void ResourcesFromMainAssembly(BuildArgs buildArgs, - bool nativeRelink, - string? argCulture, - RunHost host, - string id) + [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 async void ResourcesFromMainAssembly(Configuration config, bool aot, bool nativeRelink, string? argCulture) { - string projectName = $"sat_asm_from_main_asm"; - // Release+publish defaults to native relinking - bool dotnetWasmFromRuntimePack = !nativeRelink && !buildArgs.AOT && buildArgs.Config != "Release"; - + string prefix = $"sat_asm_from_main_asm"; string extraProperties = (nativeRelink ? $"true" : string.Empty) // make ASSERTIONS=1 so that we test with it + $"-O0 -sASSERTIONS=1" + $"-O1"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, - projectTemplate: s_resourcesProjectTemplate, - extraProperties: extraProperties); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "resx"), Path.Combine(_projectDir!, "resx")); - CreateProgramForCultureTest(_projectDir!, $"{projectName}.resx.words", "TestClass"); - }, - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); - - RunAndTestWasmApp( - buildArgs, expectedExitCode: 42, - args: argCulture, - host: host, id: id, - // check that downloading assets doesn't have timing race conditions - extraXHarnessMonoArgs: host is RunHost.Chrome ? "--fetch-random-delay=200" : string.Empty); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "resx"), Path.Combine(_projectDir, "resx")); + CreateProgramForCultureTest($"{info.ProjectName}.resx.words", "TestClass"); + + (_, string output) = PublishProject(info, config, new PublishOptions(UseCache: false, AOT: aot), isNativeBuild: nativeRelink ? true : null); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42, + Locale: argCulture ?? "en-US", + // check that downloading assets doesn't have timing race conditions + ExtraArgs: "--fetch-random-delay=200" + )); } [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 })] - public void ResourcesFromProjectReference(BuildArgs buildArgs, - bool nativeRelink, - string? argCulture, - RunHost host, - string id) + [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 async void ResourcesFromProjectReference(Configuration config, bool aot, bool nativeRelink, string? argCulture) { - string projectName = $"SatelliteAssemblyFromProjectRef"; - bool dotnetWasmFromRuntimePack = !nativeRelink && !buildArgs.AOT; - + string prefix = $"SatelliteAssemblyFromProjectRef"; string extraProperties = $"{(nativeRelink ? "true" : "false")}" // make ASSERTIONS=1 so that we test with it + $"-O0 -sASSERTIONS=1" + $"-O1"; - - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, - projectTemplate: s_resourcesProjectTemplate, - extraProperties: extraProperties, - extraItems: $""); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - InitProject: () => - { - string rootDir = _projectDir!; - _projectDir = Path.Combine(rootDir, projectName); - - Directory.CreateDirectory(_projectDir); - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, projectName), rootDir); - - // D.B.* used for wasm projects should be moved next to the wasm project, so it doesn't - // affect the non-wasm library project - File.Move(Path.Combine(rootDir, "Directory.Build.props"), Path.Combine(_projectDir, "Directory.Build.props")); - File.Move(Path.Combine(rootDir, "Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets")); - if (UseWBTOverridePackTargets) - File.Move(Path.Combine(rootDir, "WasmOverridePacks.targets"), Path.Combine(_projectDir, "WasmOverridePacks.targets")); - - CreateProgramForCultureTest(_projectDir, "LibraryWithResources.resx.words", "LibraryWithResources.Class1"); - - // The root D.B* should be empty - File.WriteAllText(Path.Combine(rootDir, "Directory.Build.props"), ""); - File.WriteAllText(Path.Combine(rootDir, "Directory.Build.targets"), ""); - })); - - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - args: argCulture, - host: host, id: id); + string extraItems = $""; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties, extraItems: extraItems); + // D.B.* used for wasm projects should be moved next to the wasm project, so it doesn't + // affect the non-wasm library project + File.Move(Path.Combine(_projectDir, "..", "Directory.Build.props"), Path.Combine(_projectDir, "Directory.Build.props")); + File.Move(Path.Combine(_projectDir, "..", "Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets")); + if (UseWBTOverridePackTargets) + File.Move(Path.Combine(BuildEnvironment.TestDataPath, "WasmOverridePacks.targets"), Path.Combine(_projectDir, "WasmOverridePacks.targets")); + Utils.DirectoryCopy( + Path.Combine(BuildEnvironment.TestAssetsPath, "SatelliteAssemblyFromProjectRef/LibraryWithResources"), + Path.Combine(_projectDir, "..", "LibraryWithResources")); + CreateProgramForCultureTest("LibraryWithResources.resx.words", "LibraryWithResources.Class1"); + // move src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources to the test project + // The root D.B* should be empty + File.WriteAllText(Path.Combine(_projectDir, "..", "Directory.Build.props"), ""); + File.WriteAllText(Path.Combine(_projectDir, "..", "Directory.Build.targets"), ""); + // NativeFilesType dotnetWasmFileType = nativeRelink ? NativeFilesType.Relinked : aot ? NativeFilesType.AOT : NativeFilesType.FromRuntimePack; + + PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: nativeRelink); + + await RunForPublishWithWebServer( + new BrowserRunOptions(Configuration: config, TestScenario: "DotnetRun", ExpectedExitCode: 42, Locale: argCulture ?? "en-US")); } #pragma warning disable xUnit1026 [Theory] - [BuildAndRun(host: RunHost.None, aot: true)] - public void CheckThatSatelliteAssembliesAreNotAOTed(BuildArgs buildArgs, string id) + [BuildAndRun(aot: true, config: Configuration.Release)] + public void CheckThatSatelliteAssembliesAreNotAOTed(Configuration config, bool aot) { - string projectName = $"check_sat_asm_not_aot"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, - projectTemplate: s_resourcesProjectTemplate, - extraProperties: $@" - -O1 - -O1 - false", // -O0 can cause aot-instances.dll to blow up, and fail to compile, and it is not really needed here - extraItems: $""); + string extraProperties = $@"-O1 + -O1 + false"; // -O0 can cause aot-instances.dll to blow up, and fail to compile, and it is not really needed here + string extraItems = $""; + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "check_sat_asm_not_aot", extraProperties: extraProperties, extraItems: extraItems); + CreateProgramForCultureTest($"{info.ProjectName}.words", "TestClass"); - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => CreateProgramForCultureTest(_projectDir!, $"{projectName}.words", "TestClass"), - DotnetWasmFromRuntimePack: false)); + PublishProject(info, config, new PublishOptions(AOT: aot)); - var bitCodeFileNames = Directory.GetFileSystemEntries(Path.Combine(_projectDir!, "obj"), "*.dll.bc", SearchOption.AllDirectories) + var bitCodeFileNames = Directory.GetFileSystemEntries(Path.Combine(_projectDir, "obj"), "*.dll.bc", SearchOption.AllDirectories) .Select(path => Path.GetFileName(path)) .ToArray(); // sanity check, in case we change file extensions - Assert.Contains($"{projectName}.dll.bc", bitCodeFileNames); + Assert.Contains($"{info.ProjectName}.dll.bc", bitCodeFileNames); Assert.Empty(bitCodeFileNames.Where(file => file.EndsWith(".resources.dll.bc"))); } #pragma warning restore xUnit1026 - private void CreateProgramForCultureTest(string dir, string resourceName, string typeName) - => File.WriteAllText(Path.Combine(dir, "Program.cs"), - s_cultureResourceTestProgram - .Replace("##RESOURCE_NAME##", resourceName) - .Replace("##TYPE_NAME##", typeName)); - - private const string s_resourcesProjectTemplate = - @$" - - {DefaultTargetFramework} - browser-wasm - Exe - true - test-main.js - ##EXTRA_PROPERTIES## - - - ##EXTRA_ITEMS## - - ##INSERT_AT_END## - "; - - private static string s_cultureResourceTestProgram = @" -using System; -using System.Runtime.CompilerServices; -using System.Globalization; -using System.Resources; -using System.Threading; - -namespace ResourcesTest -{ - public class TestClass - { - public static int Main(string[] args) + private void CreateProgramForCultureTest(string resourceName, string typeName) { - string expected; - if (args.Length == 1) - { - string cultureToTest = args[0]; - var newCulture = new CultureInfo(cultureToTest); - Thread.CurrentThread.CurrentCulture = newCulture; - Thread.CurrentThread.CurrentUICulture = newCulture; - - if (cultureToTest == ""es-ES"") - expected = ""hola""; - else if (cultureToTest == ""ja-JP"") - expected = ""\u3053\u3093\u306B\u3061\u306F""; - else - throw new Exception(""Cannot determine the expected output for {cultureToTest}""); - - } else { - expected = ""hello""; - } - - var currentCultureName = Thread.CurrentThread.CurrentCulture.Name; - - var rm = new ResourceManager(""##RESOURCE_NAME##"", typeof(##TYPE_NAME##).Assembly); - Console.WriteLine($""For '{currentCultureName}' got: {rm.GetString(""hello"")}""); - - return rm.GetString(""hello"") == expected ? 42 : -1; + string programRelativePath = Path.Combine("Common", "Program.cs"); + ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "CultureResource.cs")); + var replacements = new Dictionary { + {"##RESOURCE_NAME##", resourceName}, + {"##TYPE_NAME##", typeName} + }; + UpdateFile(programRelativePath, replacements); } } -}"; - } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs similarity index 73% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs rename to src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs index ea1037e27872a..1dcd957811738 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SatelliteLoadingTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; @@ -15,9 +16,9 @@ #nullable enable -namespace Wasm.Build.Tests.TestAppScenarios; +namespace Wasm.Build.Tests; -public class SatelliteLoadingTests : AppTestBase +public class SatelliteLoadingTests : WasmTemplateTestsBase { public SatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) @@ -29,13 +30,14 @@ public SatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFi [InlineData(true)] public async Task LoadSatelliteAssembly(bool loadAllSatelliteResources) { - CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTests", "App"); - BuildProject("Debug"); + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "SatelliteLoadingTests"); + BuildProject(info, config); - var result = await RunSdkStyleAppForBuild(new( - Configuration: "Debug", + var result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + Configuration: config, TestScenario: "SatelliteAssembliesTest", - BrowserQueryString: new Dictionary { ["loadAllSatelliteResources"] = loadAllSatelliteResources.ToString().ToLowerInvariant() } + BrowserQueryString: new NameValueCollection { {"loadAllSatelliteResources", loadAllSatelliteResources.ToString().ToLowerInvariant() } } )); var expectedOutput = new List>(); @@ -59,10 +61,11 @@ public async Task LoadSatelliteAssembly(bool loadAllSatelliteResources) [Fact] public async Task LoadSatelliteAssemblyFromReference() { - CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTestsFromReference", "App"); + Configuration config = Configuration.Release; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "SatelliteLoadingTestsFromReference"); // Replace ProjectReference with Reference - var appCsprojPath = Path.Combine(_projectDir!, "WasmBasicTestApp.csproj"); + var appCsprojPath = Path.Combine(_projectDir, "WasmBasicTestApp.csproj"); var appCsproj = XDocument.Load(appCsprojPath); var projectReference = appCsproj.Descendants("ProjectReference").Where(pr => pr.Attribute("Include")?.Value?.Contains("ResourceLibrary") ?? false).Single(); @@ -76,7 +79,7 @@ public async Task LoadSatelliteAssemblyFromReference() appCsproj.Save(appCsprojPath); // Build the library - var libraryCsprojPath = Path.GetFullPath(Path.Combine(_projectDir!, "..", "ResourceLibrary")); + var libraryCsprojPath = Path.GetFullPath(Path.Combine(_projectDir, "..", "ResourceLibrary")); using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput); CommandResult res = cmd.WithWorkingDirectory(libraryCsprojPath) .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) @@ -84,9 +87,9 @@ public async Task LoadSatelliteAssemblyFromReference() .EnsureSuccessful(); // Publish the app and assert - PublishProject("Release"); + PublishProject(info, config); - var result = await RunSdkStyleAppForPublish(new(Configuration: "Release", TestScenario: "SatelliteAssembliesTest")); + var result = await RunForPublishWithWebServer(new BrowserRunOptions(Configuration: Configuration.Release, TestScenario: "SatelliteAssembliesTest")); Assert.Collection( result.TestOutput, m => Assert.Equal("default: hello", m), diff --git a/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs index 183d984b39b48..5987db9992f1b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs @@ -5,30 +5,35 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Collections.Generic; -using Wasm.Build.Tests.TestAppScenarios; +using System.Collections.Specialized; +using Wasm.Build.Tests; using Xunit.Abstractions; using Xunit; #nullable enable namespace Wasm.Build.Tests; -public class SignalRTestsBase : AppTestBase +public class SignalRTestsBase : WasmTemplateTestsBase { public SignalRTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - protected async Task SignalRPassMessage(string staticWebAssetBasePath, string config, string transport) + protected async Task SignalRPassMessage(string staticWebAssetBasePath, Configuration config, string transport) { - CopyTestAsset("WasmOnAspNetCore", "SignalRClientTests", "AspNetCoreServer"); - PublishProject(config, runtimeType: RuntimeVariant.MultiThreaded, assertAppBundle: false); + TestAsset asset = new() { Name = "WasmBasicTestApp", RunnableProjectSubPath = "AspNetCoreServer" }; + ProjectInfo info = CopyTestAsset(config, false, asset, "SignalRClientTests"); + PublishProject(info, config, new PublishOptions(RuntimeType: RuntimeVariant.MultiThreaded, AssertAppBundle: false)); - var result = await RunSdkStyleAppForBuild(new( + var result = await RunForPublishWithWebServer(new BrowserRunOptions( Configuration: config, ServerEnvironment: new Dictionary { ["ASPNETCORE_ENVIRONMENT"] = "Development" }, BrowserPath: staticWebAssetBasePath, - BrowserQueryString: new Dictionary { ["transport"] = transport, ["message"] = "ping" } )); + BrowserQueryString: new NameValueCollection { + { "transport", transport}, + { "message", "ping" } + })); string testOutput = string.Join("\n", result.TestOutput) ?? ""; Assert.NotEmpty(testOutput); diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/NativeBuildTests.cs index e76e1112e6b63..b1f892901d13d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/NativeBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/NativeBuildTests.cs @@ -23,8 +23,7 @@ public NativeBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [InlineData(false)] public void BuildWithUndefinedNativeSymbol(bool allowUndefined) { - string id = $"UndefinedNativeSymbol_{(allowUndefined ? "allowed" : "disabled")}_{GetRandomId()}"; - + Configuration config = Configuration.Release; string code = @" using System; using System.Runtime.InteropServices; @@ -35,71 +34,50 @@ public void BuildWithUndefinedNativeSymbol(bool allowUndefined) [DllImport(""undefined_xyz"")] static extern void call(); "; - string projectPath = CreateWasmTemplateProject(id); - - AddItemsPropertiesToProject( - projectPath, - extraItems: @$"", - extraProperties: allowUndefined ? $"true" : null + string extraItems = @$""; + string extraProperties = allowUndefined ? $"true" : ""; + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot: false, + $"UndefinedNativeSymbol_{(allowUndefined ? "allowed" : "disabled")}", + extraItems: extraItems, + extraProperties: extraProperties ); + UpdateFile("Program.cs", code); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "undefined-symbol.c"), Path.Combine(_projectDir, "undefined_xyz.c")); + var buildOptions = new BuildOptions(ExpectSuccess: allowUndefined, AssertAppBundle: false); + (string _, string buildOutput) = BuildProject(info, config, buildOptions, isNativeBuild: true); - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "undefined-symbol.c"), Path.Combine(_projectDir!, "undefined_xyz.c")); - - using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput); - CommandResult result = cmd.WithWorkingDirectory(_projectDir!) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("build", "-c Release"); - - if (allowUndefined) - { - Assert.True(result.ExitCode == 0, "Expected build to succeed"); - } - else + if (!allowUndefined) { - Assert.False(result.ExitCode == 0, "Expected build to fail"); - Assert.Contains("undefined symbol: sgfg", result.Output); - Assert.Contains("Use '-p:WasmAllowUndefinedSymbols=true' to allow undefined symbols", result.Output); + Assert.Contains("undefined symbol: sgfg", buildOutput); + Assert.Contains("Use '-p:WasmAllowUndefinedSymbols=true' to allow undefined symbols", buildOutput); } } [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task ProjectWithDllImportsRequiringMarshalIlGen_ArrayTypeParameter(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public async Task ProjectWithDllImportsRequiringMarshalIlGen_ArrayTypeParameter(Configuration config) { - string id = $"dllimport_incompatible_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, template: "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - string nativeSourceFilename = "incompatible_type.c"; - string nativeCode = "void call_needing_marhsal_ilgen(void *x) {}"; - File.WriteAllText(path: Path.Combine(_projectDir!, nativeSourceFilename), nativeCode); - - AddItemsPropertiesToProject( - projectFile, - extraItems: "" + string extraItems = ""; + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot: false, + "dllimport_incompatible", + extraItems: extraItems ); - + string nativeCode = "void call_needing_marhsal_ilgen(void *x) {}"; + File.WriteAllText(path: Path.Combine(_projectDir, nativeSourceFilename), nativeCode); UpdateBrowserMainJs(); - File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "marshal_ilgen_test.cs"), - Path.Combine(_projectDir!, "Program.cs"), - overwrite: true); - - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, id: id, new BuildProjectOptions( - AssertAppBundle: false, - CreateProject: false, - HasV8Script: false, - MainJS: "main.mjs", - Publish: false, - TargetFramework: DefaultTargetFramework, - IsBrowserProject: true) - ); - string runOutput = await RunBuiltBrowserApp(config, projectFile); + ReplaceFile("Program.cs", Path.Combine(BuildEnvironment.TestAssetsPath, "marshal_ilgen_test.cs")); - Assert.Contains("call_needing_marhsal_ilgen got called", runOutput); + (string _, string buildOutput) = BuildProject(info, config, new BuildOptions(AssertAppBundle: false), isNativeBuild: true); + var runOutput = await RunForBuildWithDotnetRun(new BrowserRunOptions(config, ExpectedExitCode: 42)); + Assert.Contains("call_needing_marhsal_ilgen got called", runOutput.TestOutput); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs index 89107e11d1868..3716b8d80006b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs @@ -22,66 +22,33 @@ public WasmTemplateTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur { } - private BuildProjectOptions _basePublishProjectOptions = new BuildProjectOptions( - DotnetWasmFromRuntimePack: false, - CreateProject: false, - HasV8Script: false, - MainJS: "main.js", - Publish: true - ); - private BuildProjectOptions _baseBuildProjectOptions = new BuildProjectOptions( - DotnetWasmFromRuntimePack: true, - CreateProject: false, - HasV8Script: false, - MainJS: "main.js", - Publish: false - ); - [Theory, TestCategory("no-fingerprinting")] - [InlineData("Debug")] - [InlineData("Release")] - public void BrowserBuildThenPublish(string config) + [InlineData(Configuration.Debug)] + [InlineData(Configuration.Release)] + public void BrowserBuildThenPublish(Configuration config) { - string id = $"browser_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - - UpdateBrowserProgramFile(); - UpdateBrowserMainJs(); - - var buildArgs = new BuildArgs(projectName, config, false, id, null); - - AddItemsPropertiesToProject(projectFile, - atTheEnd: - """ + string atEnd = """ <_LinkedOutFile Include="$(IntermediateOutputPath)\linked\*.dll" /> - """ - ); + """; + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot: false, "browser", insertAtEnd: atEnd); + UpdateBrowserProgramFile(); + UpdateBrowserMainJs(); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, id: id, _baseBuildProjectOptions); + BuildProject(info, config); - if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - throw new XunitException($"Test bug: could not get the build product in the cache"); + if (!_buildContext.TryGetBuildFor(info, out BuildResult? result)) + throw new XunitException($"Test bug: could not get the build result in the cache"); - File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); + File.Move(result!.LogFile, Path.ChangeExtension(result.LogFile!, ".first.binlog")); _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}"); - bool expectRelinking = config == "Release"; - BuildTemplateProject(buildArgs, - id: id, - _basePublishProjectOptions with - { - UseCache = false, - DotnetWasmFromRuntimePack = !expectRelinking, - } - ); + PublishProject(info, config, new PublishOptions(UseCache: false)); } public static TheoryData TestDataForAppBundleDir() @@ -107,27 +74,22 @@ void AddTestData(bool runOutsideProjectDirectory) [MemberData(nameof(TestDataForAppBundleDir))] [ActiveIssue("https://github.com/dotnet/runtime/issues/108107")] public async Task RunWithDifferentAppBundleLocations(bool runOutsideProjectDirectory, string extraProperties) - => await BrowserRunTwiceWithAndThenWithoutBuildAsync("Release", extraProperties, runOutsideProjectDirectory); + => await BrowserRunTwiceWithAndThenWithoutBuildAsync(Configuration.Release, extraProperties, runOutsideProjectDirectory); - private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(string config, string extraProperties = "", bool runOutsideProjectDirectory = false) + private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(Configuration config, string extraProperties = "", bool runOutsideProjectDirectory = false) { - string id = $"browser_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot: false, "browser", extraProperties: extraProperties); UpdateBrowserProgramFile(); UpdateBrowserMainJs(); - if (!string.IsNullOrEmpty(extraProperties)) - AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - - string workingDir = runOutsideProjectDirectory ? BuildEnvironment.TmpPath : _projectDir!; + string workingDir = runOutsideProjectDirectory ? BuildEnvironment.TmpPath : _projectDir; { using var runCommand = new RunCommand(s_buildEnv, _testOutput) .WithWorkingDirectory(workingDir); await using var runner = new BrowserRunner(_testOutput); - var page = await runner.RunAsync(runCommand, $"run --no-silent -c {config} --project \"{projectFile}\" --forward-console"); + var page = await runner.RunAsync(runCommand, $"run --no-silent -c {config} --project \"{info.ProjectName}.csproj\" --forward-console"); await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); } @@ -137,7 +99,7 @@ private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(string config, st .WithWorkingDirectory(workingDir); await using var runner = new BrowserRunner(_testOutput); - var page = await runner.RunAsync(runCommand, $"run --no-silent -c {config} --no-build --project \"{projectFile}\" --forward-console"); + var page = await runner.RunAsync(runCommand, $"run --no-silent -c {config} --no-build --project \"{info.ProjectName}.csproj\" --forward-console"); await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); } @@ -160,67 +122,52 @@ private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(string config, st [MemberData(nameof(BrowserBuildAndRunTestData))] public async Task BrowserBuildAndRun(string extraNewArgs, string targetFramework, string runtimeAssetsRelativePath) { - string config = "Debug"; - string id = $"browser_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser", extraNewArgs, addFrameworkArg: extraNewArgs.Length == 0); - string projectName = Path.GetFileNameWithoutExtension(projectFile); + Configuration config = Configuration.Debug; string extraProperties = runtimeAssetsRelativePath == DefaultRuntimeAssetsRelativePath ? "" : $"{runtimeAssetsRelativePath}"; - AddItemsPropertiesToProject(projectFile, extraProperties); + ProjectInfo info = CreateWasmTemplateProject( + Template.WasmBrowser, + config, + aot: false, + "browser", + extraProperties: extraProperties, + extraArgs: extraNewArgs, + addFrameworkArg: extraNewArgs.Length == 0 + ); if (targetFramework != "net8.0") UpdateBrowserProgramFile(); UpdateBrowserMainJs(targetFramework, runtimeAssetsRelativePath); - using ToolCommand cmd = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!); - cmd.Execute($"build -c {config} -bl:{Path.Combine(s_buildEnv.LogRootPath, $"{id}.binlog")} {(runtimeAssetsRelativePath != DefaultRuntimeAssetsRelativePath ? "-p:WasmRuntimeAssetsLocation=" + runtimeAssetsRelativePath : "")}") - .EnsureSuccessful(); - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, id: id, _baseBuildProjectOptions); + PublishProject(info, config, new PublishOptions(UseCache: false)); - string runOutput = await RunBuiltBrowserApp(config, projectFile); - Assert.Contains("Hello, Browser!", runOutput); + var runOutput = await RunForPublishWithWebServer(new BrowserRunOptions(config, ExpectedExitCode: 42)); + Assert.Contains("Hello, Browser!", runOutput.TestOutput); } [Theory] - [InlineData("Debug", /*appendRID*/ true, /*useArtifacts*/ false)] - [InlineData("Debug", /*appendRID*/ true, /*useArtifacts*/ true)] - [InlineData("Debug", /*appendRID*/ false, /*useArtifacts*/ true)] - [InlineData("Debug", /*appendRID*/ false, /*useArtifacts*/ false)] - public async Task BuildAndRunForDifferentOutputPaths(string config, bool appendRID, bool useArtifacts) + [InlineData(Configuration.Debug, /*appendRID*/ true, /*useArtifacts*/ false)] + [InlineData(Configuration.Debug, /*appendRID*/ true, /*useArtifacts*/ true)] + [InlineData(Configuration.Debug, /*appendRID*/ false, /*useArtifacts*/ true)] + [InlineData(Configuration.Debug, /*appendRID*/ false, /*useArtifacts*/ false)] + public async Task BuildAndRunForDifferentOutputPaths(Configuration config, bool appendRID, bool useArtifacts) { - string id = $"{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - string projectDirectory = Path.GetDirectoryName(projectFile)!; - + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot: false); UpdateBrowserProgramFile(); UpdateBrowserMainJs(); - string extraPropertiesForDBP = string.Empty; - string frameworkDir = FindBinFrameworkDir(config, forPublish: false); - - var buildOptions = _baseBuildProjectOptions with - { - BinFrameworkDir = frameworkDir - }; + bool isPublish = false; + string projectDirectory = Path.GetDirectoryName(info.ProjectFilePath) ?? ""; + // browser app does not allow appending RID + string frameworkDir = useArtifacts ? + Path.Combine(projectDirectory, "bin", info.ProjectName, config.ToString().ToLower(), "wwwroot", "_framework") : + GetBinFrameworkDir(config, isPublish); + + string extraPropertiesForDBP = string.Empty; if (useArtifacts) { extraPropertiesForDBP += "true."; - buildOptions = buildOptions with - { - // browser app does not allow appending RID - BinFrameworkDir = Path.Combine( - projectDirectory, - "bin", - id, - config.ToLower(), - "wwwroot", - "_framework") - }; } if (appendRID) { @@ -230,11 +177,8 @@ public async Task BuildAndRunForDifferentOutputPaths(string config, bool appendR string propsPath = Path.Combine(projectDirectory, "Directory.Build.props"); AddItemsPropertiesToProject(propsPath, extraPropertiesForDBP); - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, id: id, buildOptions); - - await RunBuiltBrowserApp(config, projectFile, extraArgs: "x y z"); + BuildProject(info, config, new BuildOptions(NonDefaultFrameworkDir: frameworkDir)); + await RunForBuildWithDotnetRun(new BrowserRunOptions(config, ExpectedExitCode: 42, ExtraArgs: "x y z")); } [Theory] @@ -242,33 +186,23 @@ public async Task BuildAndRunForDifferentOutputPaths(string config, bool appendR [InlineData("false", false)] // the other case public async Task Test_WasmStripILAfterAOT(string stripILAfterAOT, bool expectILStripping) { - string config = "Release"; - string id = $"strip_{config}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - string projectDirectory = Path.GetDirectoryName(projectFile)!; + Configuration config = Configuration.Release; bool aot = true; + string extraProperties = "true"; + if (!string.IsNullOrEmpty(stripILAfterAOT)) + extraProperties += $"{stripILAfterAOT}"; + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot, "strip", extraProperties: extraProperties); UpdateBrowserProgramFile(); UpdateBrowserMainJs(); - string extraProperties = "true"; - if (!string.IsNullOrEmpty(stripILAfterAOT)) - extraProperties += $"{stripILAfterAOT}"; - AddItemsPropertiesToProject(projectFile, extraProperties); - - var buildArgs = new BuildArgs(projectName, config, aot, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - BuildTemplateProject(buildArgs, - id: id, - _basePublishProjectOptions with { - UseCache = false, - AssertAppBundle = false - }); - - await RunBuiltBrowserApp(config, projectFile); - string frameworkDir = FindBinFrameworkDir(config, forPublish: true); - string objBuildDir = Path.Combine(projectDirectory, "obj", config, BuildTestBase.DefaultTargetFramework, "wasm", "for-publish"); + PublishProject(info, config, new PublishOptions(UseCache: false, AssertAppBundle: false)); + await RunForBuildWithDotnetRun(new BrowserRunOptions(config, ExpectedExitCode: 42)); + + string projectDirectory = Path.GetDirectoryName(info.ProjectFilePath)!; + string objBuildDir = Path.Combine(projectDirectory, "obj", config.ToString(), BuildTestBase.DefaultTargetFramework, "wasm", "for-publish"); + + string frameworkDir = GetBinFrameworkDir(config, forPublish: true); TestWasmStripILAfterAOTOutput(objBuildDir, frameworkDir, expectILStripping, _testOutput); } @@ -330,25 +264,20 @@ internal static void TestWasmStripILAfterAOTOutput(string objBuildDir, string fr [InlineData(true)] public void PublishPdb(bool copyOutputSymbolsToPublishDirectory) { - string config = "Release"; + Configuration config = Configuration.Release; string shouldCopy = copyOutputSymbolsToPublishDirectory.ToString().ToLower(); - string id = $"publishpdb_{shouldCopy}_{GetRandomId()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmbrowser"); - string projectName = Path.GetFileNameWithoutExtension(projectFile); - var buildArgs = new BuildArgs(projectName, config, false, id, null); - buildArgs = ExpandBuildArgs(buildArgs); - AddItemsPropertiesToProject(projectFile, - extraProperties: $"{shouldCopy}"); - - BuildTemplateProject(buildArgs, buildArgs.Id, _basePublishProjectOptions); - string publishPath = FindBinFrameworkDir(config, forPublish: true); + string extraProperties = $"{shouldCopy}"; + ProjectInfo info = CreateWasmTemplateProject(Template.WasmBrowser, config, aot: false, "publishpdb", extraProperties: extraProperties); + + PublishProject(info, config); + string publishPath = GetBinFrameworkDir(config, forPublish: true); AssertFile(".pdb"); AssertFile(".pdb.gz"); AssertFile(".pdb.br"); void AssertFile(string suffix) { - var fileName = Directory.EnumerateFiles(publishPath, $"*{suffix}").FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith(id)); + var fileName = Directory.EnumerateFiles(publishPath, $"*{suffix}").FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).StartsWith(info.ProjectName)); Assert.True(copyOutputSymbolsToPublishDirectory == (fileName != null && File.Exists(fileName)), $"The {fileName} file {(copyOutputSymbolsToPublishDirectory ? "should" : "shouldn't")} exist in publish folder"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs index 6b668222cc2f7..f5d499533139d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.Playwright; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -18,7 +20,10 @@ namespace Wasm.Build.Tests; public class WasmTemplateTestsBase : BuildTestBase { private readonly WasmSdkBasedProjectProvider _provider; + protected readonly PublishOptions _defaultPublishOptions = new PublishOptions(); + protected readonly BuildOptions _defaultBuildOptions = new BuildOptions(); protected const string DefaultRuntimeAssetsRelativePath = "./_framework/"; + public WasmTemplateTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext, ProjectProviderBase? provider = null) : base(provider ?? new WasmSdkBasedProjectProvider(output, DefaultTargetFramework), output, buildContext) { @@ -27,78 +32,139 @@ public WasmTemplateTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFi private Dictionary browserProgramReplacements = new Dictionary { - { "while(true)", $"int i = 0;{Environment.NewLine}while(i++ < 10)" }, - { "partial class StopwatchSample", $"return 42;{Environment.NewLine}partial class StopwatchSample" } + { "while(true)", $"int i = 0;{Environment.NewLine}while(i++ < 0)" }, // the test has to be fast, skip the loop + { "partial class StopwatchSample", $"return 42;{Environment.NewLine}partial class StopwatchSample" }, + { "Hello, Browser!", "TestOutput -> Hello, Browser!" } }; - public string CreateWasmTemplateProject(string id, string template = "wasmbrowser", string extraArgs = "", bool runAnalyzers = true, bool addFrameworkArg = false, string? extraProperties = null) + private string GetProjectName(string idPrefix, Configuration config, bool aot, bool appendUnicodeToPath, bool avoidAotLongPathIssue = false) => + avoidAotLongPathIssue ? // https://github.com/dotnet/runtime/issues/103625 + $"{GetRandomId()}" : + appendUnicodeToPath ? + $"{idPrefix}_{config}_{aot}_{GetRandomId()}_{s_unicodeChars}" : + $"{idPrefix}_{config}_{aot}_{GetRandomId()}"; + + private (string projectName, string logPath, string nugetDir) InitProjectLocation(string idPrefix, Configuration config, bool aot, bool appendUnicodeToPath, bool avoidAotLongPathIssue = false) { - InitPaths(id); + string projectName = GetProjectName(idPrefix, config, aot, appendUnicodeToPath, avoidAotLongPathIssue); + (string logPath, string nugetDir) = InitPaths(projectName); InitProjectDir(_projectDir, addNuGetSourceForLocalPackages: true); + return (projectName, logPath, nugetDir); + } - File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.props"), ""); - File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.targets"), - """ - - - - - - - - """); - if (UseWBTOverridePackTargets) - File.Copy(BuildEnvironment.WasmOverridePacksTargetsPath, Path.Combine(_projectDir, Path.GetFileName(BuildEnvironment.WasmOverridePacksTargetsPath)), overwrite: true); + public ProjectInfo CreateWasmTemplateProject( + Template template, + Configuration config, + bool aot, + string idPrefix = "wbt", + bool appendUnicodeToPath = true, + string extraArgs = "", + bool runAnalyzers = true, + bool addFrameworkArg = false, + string extraProperties = "", + string extraItems = "", + string insertAtEnd = "") + { + (string projectName, string logPath, string nugetDir) = + InitProjectLocation(idPrefix, config, aot, appendUnicodeToPath); if (addFrameworkArg) extraArgs += $" -f {DefaultTargetFramework}"; + using DotNetCommand cmd = new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false); - CommandResult result = cmd.WithWorkingDirectory(_projectDir!) - .ExecuteWithCapturedOutput($"new {template} {extraArgs}") + CommandResult result = cmd.WithWorkingDirectory(_projectDir) + .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) + .ExecuteWithCapturedOutput($"new {template.ToString().ToLower()} {extraArgs}") .EnsureSuccessful(); - string projectfile = Path.Combine(_projectDir!, $"{id}.csproj"); + string projectFilePath = Path.Combine(_projectDir, $"{projectName}.csproj"); + UpdateProjectFile(projectFilePath, runAnalyzers, extraProperties, extraItems, insertAtEnd); + return new ProjectInfo(projectName, projectFilePath, logPath, nugetDir); + } - if (extraProperties == null) - extraProperties = string.Empty; + protected ProjectInfo CopyTestAsset( + Configuration config, + bool aot, + TestAsset asset, + string idPrefix, + bool appendUnicodeToPath = true, + bool runAnalyzers = true, + string extraProperties = "", + string extraItems = "", + string insertAtEnd = "") + { + (string projectName, string logPath, string nugetDir) = + InitProjectLocation(idPrefix, config, aot, appendUnicodeToPath, avoidAotLongPathIssue: s_isWindows && aot); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, asset.Name), Path.Combine(_projectDir)); + if (!string.IsNullOrEmpty(asset.RunnableProjectSubPath)) + { + _projectDir = Path.Combine(_projectDir, asset.RunnableProjectSubPath); + } + string projectFilePath = Path.Combine(_projectDir, $"{asset.Name}.csproj"); + UpdateProjectFile(projectFilePath, runAnalyzers, extraProperties, extraItems, insertAtEnd); + return new ProjectInfo(asset.Name, projectFilePath, logPath, nugetDir); + } + private void UpdateProjectFile(string projectFilePath, bool runAnalyzers, string extraProperties, string extraItems, string insertAtEnd) + { extraProperties += "true"; if (runAnalyzers) extraProperties += "true"; - - AddItemsPropertiesToProject(projectfile, extraProperties); - - return projectfile; + AddItemsPropertiesToProject(projectFilePath, extraProperties, extraItems, insertAtEnd); } - public (string projectDir, string buildOutput) BuildTemplateProject( - BuildArgs buildArgs, - string id, - BuildProjectOptions buildProjectOptions, - params string[] extraArgs) + public virtual (string projectDir, string buildOutput) PublishProject( + ProjectInfo info, + Configuration configuration, + bool? isNativeBuild = null) => // null for WasmBuildNative unset + BuildProject(info, configuration, _defaultPublishOptions, isNativeBuild); + + public virtual (string projectDir, string buildOutput) PublishProject( + ProjectInfo info, + Configuration configuration, + PublishOptions publishOptions, + bool? isNativeBuild = null) => + BuildProject(info, configuration, publishOptions, isNativeBuild); + + public virtual (string projectDir, string buildOutput) BuildProject( + ProjectInfo info, + Configuration configuration, + bool? isNativeBuild = null) => // null for WasmBuildNative unset + BuildProject(info, configuration, _defaultBuildOptions, isNativeBuild); + + public virtual (string projectDir, string buildOutput) BuildProject( + ProjectInfo info, + Configuration configuration, + MSBuildOptions buildOptions, + bool? isNativeBuild = null) { - if (buildProjectOptions.ExtraBuildEnvironmentVariables is null) - buildProjectOptions = buildProjectOptions with { ExtraBuildEnvironmentVariables = new Dictionary() }; + if (buildOptions.AOT) + { + buildOptions = buildOptions with { ExtraMSBuildArgs = $"{buildOptions.ExtraMSBuildArgs} -p:RunAOTCompilation=true -p:EmccVerbose=true" }; + } + + if (buildOptions.ExtraBuildEnvironmentVariables is null) + buildOptions = buildOptions with { ExtraBuildEnvironmentVariables = new Dictionary() }; // TODO: reenable this when the SDK supports targetting net10.0 - //buildProjectOptions.ExtraBuildEnvironmentVariables["TreatPreviousAsCurrent"] = "false"; + //buildOptions.ExtraBuildEnvironmentVariables["TreatPreviousAsCurrent"] = "false"; + + (CommandResult res, string logFilePath) = BuildProjectWithoutAssert(configuration, info.ProjectName, buildOptions); - (CommandResult res, string logFilePath) = BuildProjectWithoutAssert(id, buildArgs.Config, buildProjectOptions, extraArgs); - if (buildProjectOptions.UseCache) - _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir!, logFilePath, true, res.Output)); + if (buildOptions.UseCache) + _buildContext.CacheBuild(info, new BuildResult(_projectDir, logFilePath, true, res.Output)); - if (buildProjectOptions.AssertAppBundle) + if (!buildOptions.ExpectSuccess) { - if (buildProjectOptions.IsBrowserProject) - { - _provider.AssertWasmSdkBundle(buildArgs, buildProjectOptions, res.Output); - } - else - { - _provider.AssertTestMainJsBundle(buildArgs, buildProjectOptions, res.Output); - } + res.EnsureFailed(); + return (_projectDir, res.Output); } - return (_projectDir!, res.Output); + + if (buildOptions.AssertAppBundle) + { + _provider.AssertWasmSdkBundle(configuration, buildOptions, IsUsingWorkloads, isNativeBuild, res.Output); + } + return (_projectDir, res.Output); } private string StringReplaceWithAssert(string oldContent, string oldValue, string newValue) @@ -115,7 +181,7 @@ protected void UpdateBrowserProgramFile() => protected void UpdateFile(string pathRelativeToProjectDir, Dictionary replacements) { - var path = Path.Combine(_projectDir!, pathRelativeToProjectDir); + var path = Path.Combine(_projectDir, pathRelativeToProjectDir); string text = File.ReadAllText(path); foreach (var replacement in replacements) { @@ -124,24 +190,30 @@ protected void UpdateFile(string pathRelativeToProjectDir, Dictionary RunForBuildWithDotnetRun(RunOptions runOptions) + => await BrowserRun(runOptions with { Host = RunHost.DotnetRun }); + + public virtual async Task RunForPublishWithWebServer(RunOptions runOptions) + => await BrowserRun(runOptions with { Host = RunHost.WebServer }); + + private async Task BrowserRun(RunOptions runOptions) => runOptions.Host switch { - string mainJsPath = Path.Combine(_projectDir!, "main.mjs"); - string mainJsContent = File.ReadAllText(mainJsPath); + RunHost.DotnetRun => + await BrowserRunTest($"run -c {runOptions.Configuration} --no-build", _projectDir, runOptions), + + RunHost.WebServer => + await BrowserRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files", + string.IsNullOrEmpty(runOptions.CustomBundleDir) ? + Path.GetFullPath(Path.Combine(GetBinFrameworkDir(runOptions.Configuration, forPublish: true), "..")) : + runOptions.CustomBundleDir, + runOptions), + + _ => throw new NotImplementedException(runOptions.Host.ToString()) + }; + + private async Task BrowserRunTest(string runArgs, + string workingDirectory, + RunOptions runOptions) + { + if (!string.IsNullOrEmpty(runOptions.ExtraArgs)) + runArgs += $" {runOptions.ExtraArgs}"; - StringBuilder js = new(); - foreach (var variable in variables) + runOptions.ServerEnvironment?.ToList().ForEach( + kv => s_buildEnv.EnvVars[kv.Key] = kv.Value); + + using RunCommand runCommand = new RunCommand(s_buildEnv, _testOutput); + ToolCommand cmd = runCommand.WithWorkingDirectory(workingDirectory); + + var query = runOptions.BrowserQueryString ?? new NameValueCollection(); + if (runOptions.AOT) + { + query.Add("MONO_LOG_LEVEL", "debug"); + query.Add("MONO_LOG_MASK", "aot"); + } + if (runOptions is BrowserRunOptions browserOp && !string.IsNullOrEmpty(browserOp.TestScenario)) + query.Add("test", browserOp.TestScenario); + var queryString = query.Count > 0 && query.AllKeys != null + ? "?" + string.Join("&", query.AllKeys.SelectMany(key => query.GetValues(key)?.Select(value => $"{key}={value}") ?? Enumerable.Empty())) + : ""; + + List testOutput = new(); + List consoleOutput = new(); + List serverOutput = new(); + await using var runner = new BrowserRunner(_testOutput); + var page = await runner.RunAsync( + cmd, + runArgs, + locale: runOptions.Locale, + onConsoleMessage: OnConsoleMessage, + onServerMessage: OnServerMessage, + onError: OnErrorMessage, + modifyBrowserUrl: browserUrl => new Uri(new Uri(browserUrl), runOptions.BrowserPath + queryString).ToString()); + + _testOutput.WriteLine("Waiting for page to load"); + await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded, new () { Timeout = 1 * 60 * 1000 }); + + if (runOptions.ExecuteAfterLoaded is not null) { - js.Append($".withEnvironmentVariable(\"{variable.key}\", \"{variable.value}\")"); + await runOptions.ExecuteAfterLoaded(runOptions, page); } - mainJsContent = StringReplaceWithAssert(mainJsContent, ".create()", js.ToString() + ".create()"); + if (runOptions is BlazorRunOptions blazorOp && blazorOp.Test is not null) + await blazorOp.Test(page); - File.WriteAllText(mainJsPath, mainJsContent); - } + _testOutput.WriteLine($"Waiting for additional 10secs to see if any errors are reported"); + int exitCode = await runner.WaitForExitMessageAsync(TimeSpan.FromSeconds(10)); + if (runOptions.ExpectedExitCode is not null && exitCode != runOptions.ExpectedExitCode) + throw new Exception($"Expected exit code {runOptions.ExpectedExitCode} but got {exitCode}.\nconsoleOutput={string.Join("\n", consoleOutput)}"); - // ToDo: consolidate with BlazorRunTest - protected async Task RunBuiltBrowserApp(string config, string projectFile, string language = "en-US", string extraArgs = "", string testScenario = "") - => await RunBrowser( - $"run --no-silent -c {config} --no-build --project \"{projectFile}\" --forward-console {extraArgs}", - _projectDir!, - language, - testScenario: testScenario); - - protected async Task RunPublishedBrowserApp(string config, string language = "en-US", string extraArgs = "", string testScenario = "") - => await RunBrowser( - command: $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files", - workingDirectory: Path.Combine(FindBinFrameworkDir(config, forPublish: true), ".."), - language: language, - testScenario: testScenario); - - private async Task RunBrowser(string command, string workingDirectory, string language = "en-US", string testScenario = "") - { - using var runCommand = new RunCommand(s_buildEnv, _testOutput).WithWorkingDirectory(workingDirectory); - await using var runner = new BrowserRunner(_testOutput); - Func? modifyBrowserUrl = string.IsNullOrEmpty(testScenario) ? - null : - browserUrl => new Uri(new Uri(browserUrl), $"?test={testScenario}").ToString(); - var page = await runner.RunAsync(runCommand, command, language: language, modifyBrowserUrl: modifyBrowserUrl); - await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); - Assert.Contains("WASM EXIT 42", string.Join(Environment.NewLine, runner.OutputLines)); - return string.Join("\n", runner.OutputLines); + return new(exitCode, testOutput, consoleOutput, serverOutput); + + void OnConsoleMessage(string type, string msg) + { + _testOutput.WriteLine($"[{type}] {msg}"); + consoleOutput.Add(msg); + OnTestOutput(msg); + + runOptions.OnConsoleMessage?.Invoke(type, msg); + + if (runOptions.DetectRuntimeFailures) + { + if (msg.Contains("[MONO] * Assertion") || msg.Contains("Error: [MONO] ")) + throw new XunitException($"Detected a runtime failure at line: {msg}"); + } + } + + void OnServerMessage(string msg) + { + serverOutput.Add(msg); + OnTestOutput(msg); + + if (runOptions.OnServerMessage != null) + runOptions.OnServerMessage(msg); + } + + void OnTestOutput(string msg) + { + const string testOutputPrefix = "TestOutput -> "; + if (msg.StartsWith(testOutputPrefix)) + testOutput.Add(msg.Substring(testOutputPrefix.Length)); + } + + void OnErrorMessage(string msg) + { + _testOutput.WriteLine($"[ERROR] {msg}"); + runOptions.OnErrorMessage?.Invoke(msg); + } } - public string FindBinFrameworkDir(string config, bool forPublish, string framework = DefaultTargetFramework, string? projectDir = null) => - _provider.FindBinFrameworkDir(config: config, forPublish: forPublish, framework: framework, projectDir: projectDir); + public string GetBinFrameworkDir(Configuration config, bool forPublish, string framework = DefaultTargetFramework, string? projectDir = null) => + _provider.GetBinFrameworkDir(config, forPublish, framework, projectDir); + + public BuildPaths GetBuildPaths(Configuration config, bool forPublish) => + _provider.GetBuildPaths(config, forPublish); + + public IDictionary GetFilesTable(string projectName, bool isAOT, BuildPaths paths, bool unchanged) => + _provider.GetFilesTable(projectName, isAOT, paths, unchanged); + + public IDictionary StatFiles(IDictionary fullpaths) => + _provider.StatFiles(fullpaths); + + // 2nd and next stats with fingerprinting require updated statistics + public IDictionary StatFilesAfterRebuild(IDictionary fullpaths) => + _provider.StatFilesAfterRebuild(fullpaths); + + public void CompareStat(IDictionary oldStat, IDictionary newStat, IDictionary expected) => + _provider.CompareStat(oldStat, newStat, expected); } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs deleted file mode 100644 index 4ce4134291777..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs +++ /dev/null @@ -1,46 +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.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests.TestAppScenarios; - -public class AppSettingsTests : AppTestBase -{ - public AppSettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - [Theory] - [InlineData("Development")] - [InlineData("Production")] - public async Task LoadAppSettingsBasedOnApplicationEnvironment(string applicationEnvironment) - { - CopyTestAsset("WasmBasicTestApp", "AppSettingsTests", "App"); - PublishProject("Debug"); - - var result = await RunSdkStyleAppForPublish(new( - Configuration: "Debug", - TestScenario: "AppSettingsTest", - BrowserQueryString: new Dictionary { ["applicationEnvironment"] = applicationEnvironment } - )); - Assert.Collection( - result.TestOutput, - m => Assert.Equal(GetFileExistenceMessage("/appsettings.json", true), m), - m => Assert.Equal(GetFileExistenceMessage("/appsettings.Development.json", applicationEnvironment == "Development"), m), - m => Assert.Equal(GetFileExistenceMessage("/appsettings.Production.json", applicationEnvironment == "Production"), m) - ); - } - - // Synchronize with AppSettingsTest - private static string GetFileExistenceMessage(string path, bool expected) => $"'{path}' exists '{expected}'"; -} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs deleted file mode 100644 index 71411e7c4dadf..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs +++ /dev/null @@ -1,178 +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.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Microsoft.Playwright; -using Xunit.Abstractions; -using Wasm.Build.Tests.Blazor; - -namespace Wasm.Build.Tests.TestAppScenarios; - -public abstract class AppTestBase : BlazorWasmTestBase -{ - protected AppTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - protected string Id { get; set; } - protected string LogPath { get; set; } - - protected void CopyTestAsset(string assetName, string generatedProjectNamePrefix = null, string? projectDirSuffix = null) - { - Id = $"{generatedProjectNamePrefix ?? assetName}_{GetRandomId()}"; - InitBlazorWasmProjectDir(Id); - - LogPath = Path.Combine(s_buildEnv.LogRootPath, Id); - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, assetName), Path.Combine(_projectDir!)); - - if (!string.IsNullOrEmpty(projectDirSuffix)) - { - _projectDir = Path.Combine(_projectDir, projectDirSuffix); - } - } - - protected void BuildProject( - string configuration, - string? binFrameworkDir = null, - RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded, - bool assertAppBundle = true, - bool expectSuccess = true, - string bootConfigFileName = "blazor.boot.json", - params string[] extraArgs) - { - (CommandResult result, _) = BlazorBuild(new BlazorBuildOptions( - Id: Id, - Config: configuration, - BinFrameworkDir: binFrameworkDir, - RuntimeType: runtimeType, - AssertAppBundle: assertAppBundle, - BootConfigFileName: bootConfigFileName, - ExpectSuccess: expectSuccess), extraArgs); - if (expectSuccess) - { - result.EnsureSuccessful(); - } - else - { - result.EnsureFailed(); - } - } - - protected void PublishProject( - string configuration, - RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded, - bool assertAppBundle = true, - string bootConfigFileName = "blazor.boot.json", - params string[] extraArgs) - { - (CommandResult result, _) = BlazorPublish(new BlazorBuildOptions( - Id: Id, - Config: configuration, - RuntimeType: runtimeType, - BootConfigFileName: bootConfigFileName, - AssertAppBundle: assertAppBundle), extraArgs); - result.EnsureSuccessful(); - } - - protected Task RunSdkStyleAppForBuild(RunOptions options) - => RunSdkStyleApp(options, BlazorRunHost.DotnetRun); - - protected Task RunSdkStyleAppForPublish(RunOptions options) - => RunSdkStyleApp(options, BlazorRunHost.WebServer); - - private async Task RunSdkStyleApp(RunOptions options, BlazorRunHost host = BlazorRunHost.DotnetRun) - { - var query = options.BrowserQueryString ?? new Dictionary(); - if (!string.IsNullOrEmpty(options.TestScenario)) - query.Add("test", options.TestScenario); - - var queryString = query.Any() ? "?" + string.Join("&", query.Select(kvp => $"{kvp.Key}={kvp.Value}")) : ""; - var tcs = new TaskCompletionSource(); - List testOutput = new(); - List consoleOutput = new(); - List serverOutput = new(); - Regex exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); - - BlazorRunOptions blazorRunOptions = new( - CheckCounter: false, - Config: options.Configuration, - ServerEnvironment: options.ServerEnvironment, - OnConsoleMessage: OnConsoleMessage, - OnServerMessage: OnServerMessage, - BrowserPath: options.BrowserPath, - QueryString: queryString, - Host: host, - ExtraArgs: options.ExtraArgs); - - await BlazorRunTest(blazorRunOptions); - - void OnConsoleMessage(IPage page, IConsoleMessage msg) - { - consoleOutput.Add(msg.Text); - - OnTestOutput(msg.Text); - - var exitMatch = exitRegex.Match(msg.Text); - if (exitMatch.Success) - tcs.TrySetResult(int.Parse(exitMatch.Groups["exitCode"].Value)); - - if (msg.Text.StartsWith("Error: Missing test scenario")) - throw new Exception(msg.Text); - - if (options.OnConsoleMessage != null) - options.OnConsoleMessage(page, msg); - } - - void OnServerMessage(string msg) - { - serverOutput.Add(msg); - OnTestOutput(msg); - - if (options.OnServerMessage != null) - options.OnServerMessage(msg); - } - - void OnTestOutput(string msg) - { - const string testOutputPrefix = "TestOutput -> "; - if (msg.StartsWith(testOutputPrefix)) - testOutput.Add(msg.Substring(testOutputPrefix.Length)); - } - - //TimeSpan timeout = TimeSpan.FromMinutes(2); - //await Task.WhenAny(tcs.Task, Task.Delay(timeout)); - //if (!tcs.Task.IsCompleted) - //throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); - - int wasmExitCode = tcs.Task.Result; - if (options.ExpectedExitCode != null && wasmExitCode != options.ExpectedExitCode) - throw new Exception($"Expected exit code {options.ExpectedExitCode} but got {wasmExitCode}"); - - return new(wasmExitCode, testOutput, consoleOutput, serverOutput); - } - - protected record RunOptions( - string Configuration, - string BrowserPath = "", - string? TestScenario = null, - Dictionary BrowserQueryString = null, - Dictionary ServerEnvironment = null, - Action OnConsoleMessage = null, - Action OnServerMessage = null, - int? ExpectedExitCode = 0, - string? ExtraArgs = null - ); - - protected record RunResult( - int ExitCode, - IReadOnlyCollection TestOutput, - IReadOnlyCollection ConsoleOutput, - IReadOnlyCollection ServerOutput - ); -} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs deleted file mode 100644 index b14d52ac4f182..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using Xunit.Abstractions; - -namespace Wasm.Build.Tests; - -public class TestMainJsProjectProvider : ProjectProviderBase -{ - public TestMainJsProjectProvider(ITestOutputHelper _testOutput, string? _projectDir = null) - : base(_testOutput, _projectDir) - { } - protected override string BundleDirName { get { return "AppBundle"; } } - - // no fingerprinting - protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions) - => new SortedDictionary() - { - { "dotnet.js", false }, - { "dotnet.js.map", false }, - { "dotnet.native.js", false }, - { "dotnet.native.js.symbols", false }, - { "dotnet.globalization.js", false }, - { "dotnet.native.wasm", false }, - { "dotnet.native.worker.mjs", false }, - { "dotnet.runtime.js", false }, - { "dotnet.runtime.js.map", false } - }; - - protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptionsBase assertOptions) - { - SortedSet? res = new(); - if (assertOptions.RuntimeType is RuntimeVariant.SingleThreaded) - { - res.Add("dotnet.js"); - res.Add("dotnet.native.wasm"); - res.Add("dotnet.native.js"); - res.Add("dotnet.runtime.js"); - res.Add("dotnet.js.map"); - res.Add("dotnet.runtime.js.map"); - } - - if (assertOptions.RuntimeType is RuntimeVariant.MultiThreaded) - { - res.Add("dotnet.js"); - res.Add("dotnet.native.wasm"); - res.Add("dotnet.native.js"); - res.Add("dotnet.runtime.js"); - res.Add("dotnet.native.worker.mjs"); - - if (!assertOptions.IsPublish) - { - res.Add("dotnet.js.map"); - res.Add("dotnet.runtime.js.map"); - res.Add("dotnet.native.worker.mjs.map"); - } - } - - if (assertOptions.GlobalizationMode is GlobalizationMode.Hybrid) - res.Add("dotnet.globalization.js"); - - if (assertOptions.AssertSymbolsFile && assertOptions.ExpectSymbolsFile) - res.Add("dotnet.native.js.symbols"); - - return res ?? throw new ArgumentException($"Unknown runtime type: {assertOptions.RuntimeType}"); - } - - public void AssertBundle(AssertTestMainJsAppBundleOptions assertOptions) - { - AssertBasicBundle(assertOptions); - - TestUtils.AssertFilesExist(assertOptions.BundleDir, new[] { assertOptions.MainJS }); - if (assertOptions.IsBrowserProject) - TestUtils.AssertFilesExist(assertOptions.BundleDir, new[] { "index.html" }); - TestUtils.AssertFilesExist(assertOptions.BundleDir, new[] { "run-v8.sh" }, expectToExist: assertOptions.HasV8Script); - - string bundledMainAppAssembly = $"{assertOptions.ProjectName}{WasmAssemblyExtension}"; - TestUtils.AssertFilesExist(assertOptions.BinFrameworkDir, new[] { bundledMainAppAssembly }); - } - - public void AssertBundle(BuildArgs buildArgs, BuildProjectOptions buildProjectOptions) - { - string binFrameworkDir = buildProjectOptions.BinFrameworkDir - ?? FindBinFrameworkDir(buildArgs.Config, - buildProjectOptions.Publish, - buildProjectOptions.TargetFramework); - NativeFilesType expectedFileType = buildArgs.AOT - ? NativeFilesType.AOT - : buildProjectOptions.DotnetWasmFromRuntimePack == false - ? NativeFilesType.Relinked - : NativeFilesType.FromRuntimePack; - - var assertOptions = new AssertTestMainJsAppBundleOptions( - Config: buildArgs.Config, - IsPublish: buildProjectOptions.Publish, - TargetFramework: buildProjectOptions.TargetFramework!, - BinFrameworkDir: binFrameworkDir, - ProjectName: buildArgs.ProjectName, - MainJS: buildProjectOptions.MainJS ?? "test-main.js", - GlobalizationMode: buildProjectOptions.GlobalizationMode, - HasV8Script: buildProjectOptions.HasV8Script, - CustomIcuFile: buildProjectOptions.CustomIcuFile ?? string.Empty, - IsBrowserProject: buildProjectOptions.IsBrowserProject, - ExpectedFileType: expectedFileType, - ExpectSymbolsFile: !buildArgs.AOT); - AssertBundle(assertOptions); - } - - public override string FindBinFrameworkDir(string config, bool forPublish, string framework, string? projectDir = null) - { - EnsureProjectDirIsSet(); - return Path.Combine(projectDir ?? ProjectDir!, "bin", config, framework, "browser-wasm", BundleDirName, "_framework"); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs deleted file mode 100644 index e5940778c3b3e..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestMainJsTestBase.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Wasm.Build.Tests; - -public abstract class TestMainJsTestBase : BuildTestBase -{ - protected TestMainJsProjectProvider _provider; - protected TestMainJsTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(new TestMainJsProjectProvider(output), output, buildContext) - { - _provider = GetProvider(); - } - - public (string projectDir, string buildOutput) BuildProject(BuildArgs buildArgs, - string id, - BuildProjectOptions options) - { - string msgPrefix = options.Label != null ? $"[{options.Label}] " : string.Empty; - if (options.UseCache && _buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) - { - _testOutput.WriteLine($"Using existing build found at {product.ProjectDir}, with build log at {product.LogFile}"); - - if (!product.Result) - throw new XunitException($"Found existing build at {product.ProjectDir}, but it had failed. Check build log at {product.LogFile}"); - _projectDir = product.ProjectDir; - - // use this test's id for the run logs - _logPath = Path.Combine(s_buildEnv.LogRootPath, id); - return (_projectDir, product.BuildOutput); - } - - if (options.CreateProject) - { - InitPaths(id); - InitProjectDir(_projectDir); - options.InitProject?.Invoke(); - - File.WriteAllText(Path.Combine(_projectDir, $"{buildArgs.ProjectName}.csproj"), buildArgs.ProjectFileContents); - File.Copy( - Path.Combine( - AppContext.BaseDirectory, - options.TargetFramework == "net7.0" - ? "data/test-main-7.0.js" - : "test-main.js" - ), - Path.Combine(_projectDir, "test-main.js") - ); - - File.WriteAllText(Path.Combine(_projectDir!, "index.html"), @""); - } - else if (_projectDir is null) - { - throw new Exception("_projectDir should be set, to use options.createProject=false"); - } - - if (options.ExtraBuildEnvironmentVariables is null) - options = options with { ExtraBuildEnvironmentVariables = new Dictionary() }; - - // TODO: reenable this when the SDK supports targetting net10.0 - //options.ExtraBuildEnvironmentVariables["TreatPreviousAsCurrent"] = "false"; - - try - { - (CommandResult res, string logFilePath) = BuildProjectWithoutAssert(id, - buildArgs.Config, - options, - string.Join(" ", buildArgs.ExtraBuildArgs)); - - if (options.ExpectSuccess && options.AssertAppBundle) - { - ProjectProviderBase.AssertRuntimePackPath(res.Output, options.TargetFramework ?? DefaultTargetFramework); - _provider.AssertBundle(buildArgs, options); - } - - if (options.UseCache) - _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir, logFilePath, true, res.Output)); - - return (_projectDir, res.Output); - } - catch (Exception ex) - { - if (options.UseCache) - _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir, /*logFilePath*/"unset-log-path", false, $"The build attempt resulted in exception: {ex}.")); - throw; - } - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj index 034811490dcd1..c58bed440b6a1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -123,6 +123,9 @@ + + + diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppBase.cs b/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppBase.cs new file mode 100644 index 0000000000000..7836ff1a15b3b --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppBase.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests +{ + public class WasmBuildAppBase : WasmTemplateTestsBase + { + public static IEnumerable MainMethodTestData(bool aot) + => ConfigWithAOTData(aot) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) + .UnwrapItemsAsArrays(); + + public WasmBuildAppBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) + { + } + + protected async void TestMain(string projectName, + string programText, + Configuration config, + bool aot, + bool? isNativeBuild = null, + int expectedExitCode = 42, + string expectedOutput = "Hello, World!", + string runtimeConfigContents = "", + string extraArgs = "") + { + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "DotnetRun"); + UpdateFile(Path.Combine("Common", "Program.cs"), programText); + if (!string.IsNullOrEmpty(runtimeConfigContents)) + { + UpdateFile("runtimeconfig.template.json", new Dictionary { { "}\n}", runtimeConfigContents } }); + } + PublishProject(info, config, new PublishOptions(AOT: aot, ExtraMSBuildArgs: extraArgs), isNativeBuild: isNativeBuild); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: expectedExitCode) + ); + Assert.Contains(result.ConsoleOutput, m => m.Contains(expectedOutput)); + } + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppTest.cs b/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppTest.cs index cfff7b40ba0ba..996d3a490651b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmBuildAppTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using Xunit; using Xunit.Abstractions; @@ -12,21 +13,22 @@ namespace Wasm.Build.Tests { public class WasmBuildAppTest : WasmBuildAppBase { + // similar to MainWithArgsTests.cs, consider merging public WasmBuildAppTest(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) {} [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void TopLevelMain(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void TopLevelMain(Configuration config, bool aot) => TestMain("top_level", @"System.Console.WriteLine(""Hello, World!""); return await System.Threading.Tasks.Task.FromResult(42);", - buildArgs, host, id); + config, aot); [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void AsyncMain(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void AsyncMain(Configuration config, bool aot) => TestMain("async_main", @" using System; using System.Threading.Tasks; @@ -37,12 +39,12 @@ public static async Task Main() Console.WriteLine(""Hello, World!""); return await Task.FromResult(42); } - }", buildArgs, host, id); + }", config, aot); [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void NonAsyncMain(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true })] + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void NonAsyncMain(Configuration config, bool aot) => TestMain("non_async_main", @" using System; using System.Threading.Tasks; @@ -53,11 +55,11 @@ public static int Main() Console.WriteLine(""Hello, World!""); return 42; } - }", buildArgs, host, id); + }", config, aot); [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void ExceptionFromMain(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void ExceptionFromMain(Configuration config, bool aot) => TestMain("main_exception", """ using System; using System.Threading.Tasks; @@ -65,7 +67,7 @@ public void ExceptionFromMain(BuildArgs buildArgs, RunHost host, string id) public class TestClass { public static int Main() => throw new Exception("MessageFromMyException"); } - """, buildArgs, host, id, expectedExitCode: 71, expectedOutput: "Error: MessageFromMyException"); + """, config, aot, expectedExitCode: 1, expectedOutput: "Error: MessageFromMyException"); private static string s_bug49588_ProgramCS = @" using System; @@ -81,120 +83,57 @@ public static int Main() }"; [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void Bug49588_RegressionTest_AOT(BuildArgs buildArgs, RunHost host, string id) - => TestMain("bug49588_aot", s_bug49588_ProgramCS, buildArgs, host, id); + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true })] + public void Bug49588_RegressionTest_AOT(Configuration config, bool aot) + => TestMain("bug49588_aot", s_bug49588_ProgramCS, config, aot); [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void Bug49588_RegressionTest_NativeRelinking(BuildArgs buildArgs, RunHost host, string id) - => TestMain("bug49588_native_relinking", s_bug49588_ProgramCS, buildArgs, host, id, - extraProperties: "true", - dotnetWasmFromRuntimePack: false); + [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false })] + public void Bug49588_RegressionTest_NativeRelinking(Configuration config, bool aot) + => TestMain("bug49588_native_relinking", s_bug49588_ProgramCS, config, aot, + extraArgs: "-p:WasmBuildNative=true", + isNativeBuild: true); [Theory] [BuildAndRun] - public void PropertiesFromRuntimeConfigJson(BuildArgs buildArgs, RunHost host, string id) - { - buildArgs = buildArgs with { ProjectName = $"runtime_config_{buildArgs.Config}_{buildArgs.AOT}" }; - buildArgs = ExpandBuildArgs(buildArgs); - - string programText = @" - using System; - using System.Runtime.CompilerServices; - - var config = AppContext.GetData(""test_runtimeconfig_json""); - Console.WriteLine ($""test_runtimeconfig_json: {(string)config}""); - return 42; - "; - - string runtimeConfigTemplateJson = @" - { - ""configProperties"": { - ""abc"": ""4"", - ""test_runtimeconfig_json"": ""25"" - } - }"; - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - File.WriteAllText(Path.Combine(_projectDir!, "runtimeconfig.template.json"), runtimeConfigTemplateJson); - }, - DotnetWasmFromRuntimePack: IsDotnetWasmFromRuntimePack(buildArgs))); - - RunAndTestWasmApp(buildArgs, expectedExitCode: 42, - test: output => Assert.Contains("test_runtimeconfig_json: 25", output), host: host, id: id); - } + [ActiveIssue("https://github.com/dotnet/runtime/issues/97449")] + public void PropertiesFromRuntimeConfigJson(Configuration config, bool aot) + => TestMain("runtime_config_json", + @" + using System; + using System.Runtime.CompilerServices; + + var config = AppContext.GetData(""test_runtimeconfig_json""); + Console.WriteLine ($""test_runtimeconfig_json: {(string)config}""); + return 42; + ", + config, + aot, + runtimeConfigContents: @" + }, + ""configProperties"": { + ""abc"": ""4"", + ""test_runtimeconfig_json"": ""25"" + } + }", + expectedOutput: "test_runtimeconfig_json: 25"); [Theory] [BuildAndRun] - public void PropertiesFromCsproj(BuildArgs buildArgs, RunHost host, string id) - { - buildArgs = buildArgs with { ProjectName = $"runtime_config_csproj_{buildArgs.Config}_{buildArgs.AOT}" }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "20"); - - string programText = @" - using System; - using System.Runtime.CompilerServices; - - var config = AppContext.GetData(""System.Threading.ThreadPool.MaxThreads""); - Console.WriteLine ($""System.Threading.ThreadPool.MaxThreads: {(string)config}""); - return 42; - "; - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => - { - File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); - }, - DotnetWasmFromRuntimePack: IsDotnetWasmFromRuntimePack(buildArgs))); - - RunAndTestWasmApp(buildArgs, expectedExitCode: 42, - test: output => Assert.Contains("System.Threading.ThreadPool.MaxThreads: 20", output), host: host, id: id); - } - } - - public class WasmBuildAppBase : TestMainJsTestBase - { - public static IEnumerable MainMethodTestData(bool aot, RunHost host) - => ConfigWithAOTData(aot) - .WithRunHosts(host) - .UnwrapItemsAsArrays(); - - public WasmBuildAppBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) - { - } - - protected void TestMain(string projectName, - string programText, - BuildArgs buildArgs, - RunHost host, - string id, - string extraProperties = "", - bool? dotnetWasmFromRuntimePack = null, - int expectedExitCode = 42, - string expectedOutput = "Hello, World!") - { - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, extraProperties); - - if (dotnetWasmFromRuntimePack == null) - dotnetWasmFromRuntimePack = IsDotnetWasmFromRuntimePack(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack)); - - RunAndTestWasmApp(buildArgs, expectedExitCode: expectedExitCode, - test: output => Assert.Contains(expectedOutput, output), host: host, id: id); - } + [ActiveIssue("https://github.com/dotnet/runtime/issues/97449")] + public void PropertiesFromCsproj(Configuration config, bool aot) + => TestMain("csproj_properties", + @" + using System; + using System.Runtime.CompilerServices; + + var config = AppContext.GetData(""System.Threading.ThreadPool.MaxThreads""); + Console.WriteLine ($""System.Threading.ThreadPool.MaxThreads: {(string)config}""); + return 42; + ", + config, + aot, + extraArgs: "-p:ThreadPoolMaxThreads=20", + expectedOutput: "System.Threading.ThreadPool.MaxThreads: 20"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs index 0fd78da40da58..d2c7a16d314e0 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs @@ -11,7 +11,7 @@ namespace Wasm.Build.Tests { - public class WasmNativeDefaultsTests : TestMainJsTestBase + public class WasmNativeDefaultsTests : WasmTemplateTestsBase { private static Regex s_regex = new("\\*\\* WasmBuildNative:.*"); public WasmNativeDefaultsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) @@ -19,7 +19,7 @@ public WasmNativeDefaultsTests(ITestOutputHelper output, SharedBuildPerTestClass { } - public static TheoryData SettingDifferentFromValuesInRuntimePack(bool forPublish) + public static TheoryData SettingDifferentFromValuesInRuntimePack(bool forPublish) { List<(string propertyName, bool defaultValueInRuntimePack)> defaults = new() { @@ -30,15 +30,15 @@ public static TheoryData SettingDifferentFromV // ("WasmNativeStrip", true) -- tested separately because it has special handling in targets }; - TheoryData data = new(); + TheoryData data = new(); - string[] configs = new[] { "Debug", "Release" }; + var configs = new[] { Configuration.Debug, Configuration.Release }; foreach (var defaultPair in defaults) { - foreach (string config in configs) + foreach (Configuration config in configs) { - // Config=Release always causes relinking when publishing - bool publishValue = forPublish && config == "Release" ? true : false; + // Configuration=Release always causes relinking when publishing + bool publishValue = forPublish && config == Configuration.Release ? true : false; // Setting the default value from the runtime pack shouldn't trigger relinking data.Add(config, $"<{defaultPair.propertyName}>{defaultPair.defaultValueInRuntimePack.ToString().ToLower()}", /*aot*/ false, /*build*/ false, /*publish*/ publishValue); @@ -54,42 +54,42 @@ public static TheoryData SettingDifferentFromV return data; } - public static TheoryData DefaultsTestData(bool forPublish) + public static TheoryData DefaultsTestData(bool forPublish) { - TheoryData data = new() + TheoryData data = new() { /* relink by default for publish+Release */ - { "Release", "", /*aot*/ false, /*build*/ false, /*publish*/ true }, + { Configuration.Release, "", /*aot*/ false, /*build*/ false, /*publish*/ true }, /* NO relink by default for publish+Release, when not trimming */ - { "Release", "false", /*aot*/ false, /*build*/ false, /*publish*/ false }, + { Configuration.Release, "false", /*aot*/ false, /*build*/ false, /*publish*/ false }, /* When not trimming, and no-aot, we don't relink. But WasmNativeStrip=false should still trigger it*/ - // { "Release", "falsefalse", + // { Configuration.Release, "falsefalse", // /*aot*/ false, /*build*/ true, /*publish*/ true } }; if (!forPublish) { /* Debug config, when building does trigger relinking */ - data.Add("Debug", "", /*aot*/ false, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Debug, "", /*aot*/ false, /*build*/ false, /*publish*/ true); } if (forPublish) { /* NO relink by default for publish+Debug */ - data.Add("Debug", "", /*aot*/ false, /*build*/ false, /*publish*/ false); + data.Add(Configuration.Debug, "", /*aot*/ false, /*build*/ false, /*publish*/ false); /* AOT */ - data.Add("Release", "", /*aot*/ true, /*build*/ false, /*publish*/ true); - data.Add("Debug", "", /*aot*/ true, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Release, "", /*aot*/ true, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Debug, "", /*aot*/ true, /*build*/ false, /*publish*/ true); // FIXME: separate test - // { "Release", "true", + // { Configuration.Release, "true", // /*aot*/ true, /*build*/ true, /*publish*/ true }, /* AOT not affected by trimming */ - data.Add("Release", "false", /*aot*/ true, /*build*/ false, /*publish*/ true); - data.Add("Debug", "false", /*aot*/ true, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Release, "false", /*aot*/ true, /*build*/ false, /*publish*/ true); + data.Add(Configuration.Debug, "false", /*aot*/ true, /*build*/ false, /*publish*/ true); } return data; @@ -99,9 +99,9 @@ public static TheoryData DefaultsTestData(bool [Theory] [MemberData(nameof(DefaultsTestData), parameters: false)] [MemberData(nameof(SettingDifferentFromValuesInRuntimePack), parameters: false)] - public void DefaultsWithBuild(string config, string extraProperties, bool aot, bool expectWasmBuildNativeForBuild, bool expectWasmBuildNativeForPublish) + public void DefaultsWithBuild(Configuration config, string extraProperties, bool aot, bool expectWasmBuildNativeForBuild, bool expectWasmBuildNativeForPublish) { - (string output, string? line) = CheckWasmNativeDefaultValue("native_defaults_build", config, extraProperties, aot, dotnetWasmFromRuntimePack: !expectWasmBuildNativeForBuild, publish: false); + (string output, string? line) = CheckWasmNativeDefaultValue("native_defaults_build", config, extraProperties, aot, expectWasmBuildNativeForBuild, isPublish: false); InferAndCheckPropertyValues(line, isPublish: false, wasmBuildNative: expectWasmBuildNativeForBuild, config: config); } @@ -109,36 +109,36 @@ public void DefaultsWithBuild(string config, string extraProperties, bool aot, b [Theory] [MemberData(nameof(DefaultsTestData), parameters: true)] [MemberData(nameof(SettingDifferentFromValuesInRuntimePack), parameters: true)] - public void DefaultsWithPublish(string config, string extraProperties, bool aot, bool expectWasmBuildNativeForBuild, bool expectWasmBuildNativeForPublish) + public void DefaultsWithPublish(Configuration config, string extraProperties, bool aot, bool expectWasmBuildNativeForBuild, bool expectWasmBuildNativeForPublish) { - (string output, string? line) = CheckWasmNativeDefaultValue("native_defaults_publish", config, extraProperties, aot, dotnetWasmFromRuntimePack: !expectWasmBuildNativeForPublish, publish: true); + (string output, string? line) = CheckWasmNativeDefaultValue("native_defaults_publish", config, extraProperties, aot, expectWasmBuildNativeForPublish, isPublish: true); InferAndCheckPropertyValues(line, isPublish: true, wasmBuildNative: expectWasmBuildNativeForPublish, config: config); } #pragma warning restore xunit1026 - public static TheoryData SetWasmNativeStripExplicitlyTestData(bool publish) => new() + public static TheoryData SetWasmNativeStripExplicitlyTestData(bool publish) => new() { - {"Debug", "true", /*wasmBuildNative*/ false, /*wasmNativeStrip*/ true }, - {"Release", "true", /*wasmBuildNative*/ publish, /*wasmNativeStrip*/ true }, - {"Debug", "false", /*wasmBuildNative*/ true, /*wasmNativeStrip*/ false }, - {"Release", "false", /*wasmBuildNative*/ true, /*wasmNativeStrip*/ false } + {Configuration.Debug, "true", /*wasmBuildNative*/ false, /*wasmNativeStrip*/ true }, + {Configuration.Release, "true", /*wasmBuildNative*/ publish, /*wasmNativeStrip*/ true }, + {Configuration.Debug, "false", /*wasmBuildNative*/ true, /*wasmNativeStrip*/ false }, + {Configuration.Release, "false", /*wasmBuildNative*/ true, /*wasmNativeStrip*/ false } }; - public static TheoryData SetWasmNativeStripExplicitlyWithWasmBuildNativeTestData() => new() + public static TheoryData SetWasmNativeStripExplicitlyWithWasmBuildNativeTestData() => new() { - { "Debug", "falsetrue", true, false }, - { "Release", "falsetrue", true, false }, - { "Debug", "truetrue", true, true }, - { "Release", "truetrue", true, true } + { Configuration.Debug, "falsetrue", true, false }, + { Configuration.Release, "falsetrue", true, false }, + { Configuration.Debug, "truetrue", true, true }, + { Configuration.Release, "truetrue", true, true } }; [Theory] [MemberData(nameof(SetWasmNativeStripExplicitlyTestData), parameters: /*publish*/ false)] [MemberData(nameof(SetWasmNativeStripExplicitlyWithWasmBuildNativeTestData))] - public void WasmNativeStripDefaultWithBuild(string config, string extraProperties, bool expectedWasmBuildNativeValue, bool expectedWasmNativeStripValue) + public void WasmNativeStripDefaultWithBuild(Configuration config, string extraProperties, bool expectedWasmBuildNativeValue, bool expectedWasmNativeStripValue) { - (string output, string? line) = CheckWasmNativeDefaultValue("native_strip_defaults", config, extraProperties, aot: false, dotnetWasmFromRuntimePack: !expectedWasmBuildNativeValue, publish: false); + (string output, string? line) = CheckWasmNativeDefaultValue("native_strip_defaults", config, extraProperties, aot: false, expectedWasmBuildNativeValue, isPublish: false); CheckPropertyValues(line, wasmBuildNative: expectedWasmBuildNativeValue, @@ -150,9 +150,9 @@ public void WasmNativeStripDefaultWithBuild(string config, string extraPropertie [Theory] [MemberData(nameof(SetWasmNativeStripExplicitlyTestData), parameters: /*publish*/ true)] [MemberData(nameof(SetWasmNativeStripExplicitlyWithWasmBuildNativeTestData))] - public void WasmNativeStripDefaultWithPublish(string config, string extraProperties, bool expectedWasmBuildNativeValue, bool expectedWasmNativeStripValue) + public void WasmNativeStripDefaultWithPublish(Configuration config, string extraProperties, bool expectedWasmBuildNativeValue, bool expectedWasmNativeStripValue) { - (string output, string? line) = CheckWasmNativeDefaultValue("native_strip_defaults", config, extraProperties, aot: false, dotnetWasmFromRuntimePack: !expectedWasmBuildNativeValue, publish: true); + (string output, string? line) = CheckWasmNativeDefaultValue("native_strip_defaults", config, extraProperties, aot: false, expectedWasmBuildNativeValue, isPublish: true); CheckPropertyValues(line, wasmBuildNative: expectedWasmBuildNativeValue, @@ -163,12 +163,12 @@ public void WasmNativeStripDefaultWithPublish(string config, string extraPropert [Theory] /* always relink */ - [InlineData("Debug", "", /*publish*/ false)] - [InlineData("Debug", "", /*publish*/ true)] - [InlineData("Release", "", /*publish*/ false)] - [InlineData("Release", "", /*publish*/ true)] - [InlineData("Release", "false", /*publish*/ true)] - public void WithNativeReference(string config, string extraProperties, bool publish) + [InlineData(Configuration.Debug, "", /*publish*/ false)] + [InlineData(Configuration.Debug, "", /*publish*/ true)] + [InlineData(Configuration.Release, "", /*publish*/ false)] + [InlineData(Configuration.Release, "", /*publish*/ true)] + [InlineData(Configuration.Release, "false", /*publish*/ true)] + public void WithNativeReference(Configuration config, string extraProperties, bool publish) { string nativeLibPath = Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"); string nativeRefItem = @$""; @@ -176,19 +176,19 @@ public void WithNativeReference(string config, string extraProperties, bool publ config, extraProperties, aot: false, - dotnetWasmFromRuntimePack: !publish, - publish: publish, + nativeBuild: true, + isPublish: publish, extraItems: nativeRefItem); InferAndCheckPropertyValues(line, isPublish: publish, wasmBuildNative: true, config: config); } - private (string, string?) CheckWasmNativeDefaultValue(string projectName, - string config, + private (string, string?) CheckWasmNativeDefaultValue(string projectPrefix, + Configuration config, string extraProperties, bool aot, - bool dotnetWasmFromRuntimePack, - bool publish, + bool nativeBuild, + bool isPublish, string extraItems = "") { // builds with -O0 @@ -197,27 +197,23 @@ public void WithNativeReference(string config, string extraProperties, bool publ string printValueTarget = @" - " + (publish + " + (isPublish ? @"" : @"") + ""; - - BuildArgs buildArgs = new(ProjectName: projectName, Config: config, AOT: aot, string.Empty, null); - buildArgs = ExpandBuildArgs(buildArgs, - extraProperties: extraProperties, - extraItems: extraItems, - insertAtEnd: printValueTarget); - - (_, string output) = BuildProject(buildArgs, - id: GetRandomId(), - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack, - ExpectSuccess: false, - UseCache: false, - BuildOnlyAfterPublish: false, - Publish: publish)); - + ProjectInfo info = CopyTestAsset( + config, + aot, + TestAsset.WasmBasicTestApp, + projectPrefix, + extraProperties: extraProperties, + extraItems: extraItems, + insertAtEnd: printValueTarget); + UpdateFile(Path.Combine("Common", "Program.cs"), s_mainReturns42); + + (string _, string output) = isPublish ? + PublishProject(info, config, new PublishOptions(ExpectSuccess: false, AOT: aot), nativeBuild) : + BuildProject(info, config, new BuildOptions(ExpectSuccess: false, AOT: aot), nativeBuild); Assert.Contains("Stopping the build", output); Match m = s_regex.Match(output); @@ -225,10 +221,10 @@ public void WithNativeReference(string config, string extraProperties, bool publ return (output, m.Success ? m.Groups[0]?.ToString() : null); } - private void InferAndCheckPropertyValues(string? line, bool isPublish, bool wasmBuildNative, string config) + private void InferAndCheckPropertyValues(string? line, bool isPublish, bool wasmBuildNative, Configuration config) { bool expectedWasmNativeStripValue; - if (!isPublish && wasmBuildNative && config == "Debug") + if (!isPublish && wasmBuildNative && config == Configuration.Debug) expectedWasmNativeStripValue = false; else expectedWasmNativeStripValue = true; @@ -239,11 +235,11 @@ private void InferAndCheckPropertyValues(string? line, bool isPublish, bool wasm private void CheckPropertyValues(string? line, bool wasmBuildNative, bool wasmNativeStrip, bool wasmNativeDebugSymbols, bool? wasmBuildingForNestedPublish) { Assert.NotNull(line); - Assert.Contains($"** WasmBuildNative: '{wasmBuildNative.ToString().ToLower()}', " + + string expected = $"** WasmBuildNative: '{wasmBuildNative.ToString().ToLower()}', " + $"WasmNativeStrip: '{wasmNativeStrip.ToString().ToLower()}', " + $"WasmNativeDebugSymbols: '{wasmNativeDebugSymbols.ToString().ToLower()}', " + - $"WasmBuildingForNestedPublish: '{(wasmBuildingForNestedPublish.HasValue && wasmBuildingForNestedPublish == true ? "true" : "")}'", - line); + $"WasmBuildingForNestedPublish: '{(wasmBuildingForNestedPublish.HasValue && wasmBuildingForNestedPublish == true ? "true" : "")}'"; + Assert.Contains(expected, line); } } } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmRunOutOfAppBundleTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmRunOutOfAppBundleTests.cs index 775d9cb46582d..d5161990bce18 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmRunOutOfAppBundleTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmRunOutOfAppBundleTests.cs @@ -9,48 +9,40 @@ namespace Wasm.Build.Tests; -public class WasmRunOutOfAppBundleTests : TestMainJsTestBase +public class WasmRunOutOfAppBundleTests : WasmTemplateTestsBase { public WasmRunOutOfAppBundleTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) {} [Theory] [BuildAndRun] - public void RunOutOfAppBundle(BuildArgs buildArgs, RunHost host, string id) + public async void RunOutOfAppBundle(Configuration config, bool aot) { - buildArgs = buildArgs with { ProjectName = $"outofappbundle_{buildArgs.Config}_{buildArgs.AOT}" }; - buildArgs = ExpandBuildArgs(buildArgs); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), - DotnetWasmFromRuntimePack: !(buildArgs.AOT || buildArgs.Config == "Release"))); - - string binDir = GetBinDir(baseDir: _projectDir!, config: buildArgs.Config); - string appBundleDir = Path.Combine(binDir, "AppBundle"); - string outerDir = Path.GetFullPath(Path.Combine(appBundleDir, "..")); - - if (host is RunHost.Chrome) + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "outofappbundle"); + UpdateFile(Path.Combine("Common", "Program.cs"), s_mainReturns42); + (string _, string output) = PublishProject(info, config, new PublishOptions(AOT: aot)); + + string binFrameworkDir = GetBinFrameworkDir(config, forPublish: true); + string appBundleDir = Path.Combine(binFrameworkDir, ".."); + string outerDir = Path.GetFullPath(Path.Combine(appBundleDir, "..")); + string indexHtmlPath = Path.Combine(appBundleDir, "index.html"); + // Delete the original one, so we don't use that by accident + if (File.Exists(indexHtmlPath)) + File.Delete(indexHtmlPath); + + indexHtmlPath = Path.Combine(outerDir, "index.html"); + string relativeMainJsPath = "./wwwroot/main.js"; + if (!File.Exists(indexHtmlPath)) { - string indexHtmlPath = Path.Combine(appBundleDir, "index.html"); - // Delete the original one, so we don't use that by accident - if (File.Exists(indexHtmlPath)) - File.Delete(indexHtmlPath); - - indexHtmlPath = Path.Combine(outerDir, "index.html"); - if (!File.Exists(indexHtmlPath)) - { - var html = @""; - File.WriteAllText(indexHtmlPath, html); - } + var html = $@""; + File.WriteAllText(indexHtmlPath, html); } - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - host: host, - id: id, - bundleDir: outerDir, - jsRelativePath: "./AppBundle/test-main.js"); + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + CustomBundleDir: outerDir, + ExpectedExitCode: 42) + ); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs index 08ac8512665b7..d4008d6af8ecc 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using Xunit; using Xunit.Abstractions; @@ -10,90 +11,57 @@ namespace Wasm.Build.Tests { - public class WasmSIMDTests : WasmBuildAppBase + public class WasmSIMDTests : WasmTemplateTestsBase { public WasmSIMDTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } - public static IEnumerable MainMethodSimdTestData(bool aot, RunHost host, bool simd) - => ConfigWithAOTData(aot, extraArgs: $"-p:WasmEnableSIMD={simd}") - .WithRunHosts(host) + public static IEnumerable MainMethodSimdTestData(bool aot, bool simd) + => ConfigWithAOTData(aot) + .Multiply(new object[] { simd }) + .Where(item => !(item.ElementAt(0) is Configuration config && config == Configuration.Debug && item.ElementAt(1) is bool aotValue && aotValue)) .UnwrapItemsAsArrays(); [Theory] - [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ false, RunHost.All, true /* simd */ })] - public void Build_NoAOT_ShouldNotRelink(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ false, /* simd */ true })] + public async void Build_NoAOT_ShouldNotRelink(Configuration config, bool aot, bool simd) { - string projectName = $"build_with_workload_no_aot"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs); - - (_, string output) = BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), - Publish: false, - DotnetWasmFromRuntimePack: true)); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "build_with_workload_no_aot"); + UpdateFile(Path.Combine("Common", "Program.cs"), s_simdProgramText); + (string _, string output) = BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: $"-p:WasmEnableSIMD={simd}")); // Confirm that we didn't relink Assert.DoesNotContain("Compiling native assets with emcc", output); - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - test: output => - { - Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); - Assert.Contains("Hello, World!", output); - }, host: host, id: id); - } - - [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ false, RunHost.All })] - public void PublishWithSIMD_AOT(BuildArgs buildArgs, RunHost host, string id) - { - string projectName = $"simd_with_workload_aot"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, "true"); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), - DotnetWasmFromRuntimePack: false)); + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42) + ); - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - test: output => - { - Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); - Assert.Contains("Hello, World!", output); - }, host: host, id: id); + Assert.Contains(result.TestOutput, m => m.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>")); + Assert.Contains(result.TestOutput, m => m.Contains("Hello, World!")); } [Theory] - [MemberData(nameof(MainMethodTestData), parameters: new object[] { /*aot*/ true, RunHost.All })] - public void PublishWithoutSIMD_AOT(BuildArgs buildArgs, RunHost host, string id) + [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ true, /* simd */ true })] + [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ false, /* simd */ true })] + [MemberData(nameof(MainMethodSimdTestData), parameters: new object[] { /*aot*/ true, /* simd */ false })] + public async void PublishSIMD_AOT(Configuration config, bool aot, bool simd) { - string projectName = $"nosimd_with_workload_aot"; - buildArgs = buildArgs with { ProjectName = projectName }; - buildArgs = ExpandBuildArgs(buildArgs, "false"); - - BuildProject(buildArgs, - id: id, - new BuildProjectOptions( - InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_simdProgramText), - DotnetWasmFromRuntimePack: false)); - - RunAndTestWasmApp(buildArgs, - expectedExitCode: 42, - test: output => - { - Assert.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>", output); - Assert.Contains("Hello, World!", output); - }, host: host, id: id); + ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "simd_publish"); + UpdateFile(Path.Combine("Common", "Program.cs"), s_simdProgramText); + (string _, string output) = PublishProject(info, config, new PublishOptions(ExtraMSBuildArgs: $"-p:WasmEnableSIMD={simd}", AOT: aot)); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42) + ); + Assert.Contains(result.TestOutput, m => m.Contains("<-2094756296, -2094756296, -2094756296, -2094756296>")); + Assert.Contains(result.TestOutput, m => m.Contains("Hello, World!")); } private static string s_simdProgramText = @" @@ -106,8 +74,8 @@ public static int Main() var v1 = Vector128.Create(0x12345678); var v2 = Vector128.Create(0x23456789); var v3 = v1*v2; - Console.WriteLine(v3); - Console.WriteLine(""Hello, World!""); + Console.WriteLine($""TestOutput -> {v3}""); + Console.WriteLine(""TestOutput -> Hello, World!""); return 42; } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs index 06d4787771750..c04d0bf50c2f6 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs @@ -20,11 +20,11 @@ public WasmSdkBasedProjectProvider(ITestOutputHelper _testOutput, string default : base(_testOutput, _projectDir) { _defaultTargetFramework = defaultTargetFramework; - IsFingerprintingSupported = true; } + protected override string BundleDirName { get { return "wwwroot"; } } - protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptionsBase assertOptions) + protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptions assertOptions) => new SortedDictionary() { { "dotnet.js", false }, @@ -38,7 +38,7 @@ protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFin { "dotnet.runtime.js.map", false }, }; - protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptionsBase assertOptions) + protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptions assertOptions) { SortedSet res = new() { @@ -47,16 +47,16 @@ protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOp "dotnet.native.js", "dotnet.runtime.js", }; - if (assertOptions.RuntimeType is RuntimeVariant.MultiThreaded) + if (assertOptions.BuildOptions.RuntimeType is RuntimeVariant.MultiThreaded) { res.Add("dotnet.native.worker.mjs"); } - if (assertOptions.GlobalizationMode is GlobalizationMode.Hybrid) + if (assertOptions.BuildOptions.GlobalizationMode is GlobalizationMode.Hybrid) { res.Add("dotnet.globalization.js"); } - if (!assertOptions.IsPublish) + if (!assertOptions.BuildOptions.IsPublish) { res.Add("dotnet.js.map"); res.Add("dotnet.runtime.js.map"); @@ -68,28 +68,36 @@ protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOp return res; } + public NativeFilesType GetExpectedFileType(Configuration config, bool isAOT, bool isPublish, bool isUsingWorkloads, bool? isNativeBuild=null) => + isNativeBuild == true ? NativeFilesType.Relinked : // precedence over build/publish check: build with -p:WasmBuildNative=true should use relinked + !isPublish ? NativeFilesType.FromRuntimePack : // precedence over AOT check: build with AOT should use runtime pack + isAOT ? NativeFilesType.AOT : // precedence over -p:WasmBuildNative=false check: publish with AOT relinks regardless of WasmBuildNative value + isNativeBuild == false ? NativeFilesType.FromRuntimePack : + (config == Configuration.Release) ? NativeFilesType.Relinked : + NativeFilesType.FromRuntimePack; - protected void AssertBundle(BuildArgs buildArgs, BuildProjectOptions buildProjectOptions) + public void AssertBundle(Configuration config, MSBuildOptions buildOptions, bool isUsingWorkloads, bool? isNativeBuild = null) { - string frameworkDir = buildProjectOptions.BinFrameworkDir ?? - FindBinFrameworkDir(buildArgs.Config, buildProjectOptions.Publish, buildProjectOptions.TargetFramework); - AssertBundle(new( - Config: buildArgs.Config, - IsPublish: buildProjectOptions.Publish, - TargetFramework: buildProjectOptions.TargetFramework, + string frameworkDir = string.IsNullOrEmpty(buildOptions.NonDefaultFrameworkDir) ? + GetBinFrameworkDir(config, buildOptions.IsPublish, _defaultTargetFramework) : + buildOptions.NonDefaultFrameworkDir; + + AssertBundle(new AssertBundleOptions( + config, + BuildOptions: buildOptions, + ExpectedFileType: GetExpectedFileType(config, buildOptions.AOT, buildOptions.IsPublish, isUsingWorkloads, isNativeBuild), BinFrameworkDir: frameworkDir, - CustomIcuFile: buildProjectOptions.CustomIcuFile, - GlobalizationMode: buildProjectOptions.GlobalizationMode, - AssertSymbolsFile: false, - ExpectedFileType: buildProjectOptions.Publish && buildArgs.Config == "Release" ? NativeFilesType.Relinked : NativeFilesType.FromRuntimePack + ExpectSymbolsFile: true, + AssertIcuAssets: true, + AssertSymbolsFile: false )); } - protected void AssertBundle(AssertWasmSdkBundleOptions assertOptions) + private void AssertBundle(AssertBundleOptions assertOptions) { IReadOnlyDictionary actualDotnetFiles = AssertBasicBundle(assertOptions); - if (assertOptions.IsPublish) + if (assertOptions.BuildOptions.IsPublish) { string publishPath = Path.GetFullPath(Path.Combine(assertOptions.BinFrameworkDir, "..", "..")); Assert.Equal("publish", Path.GetFileName(publishPath)); @@ -105,24 +113,25 @@ protected void AssertBundle(AssertWasmSdkBundleOptions assertOptions) return; // Compare files with the runtime pack - string objBuildDir = Path.Combine(ProjectDir!, "obj", assertOptions.Config, assertOptions.TargetFramework, "wasm", assertOptions.IsPublish ? "for-publish" : "for-build"); + string objBuildDir = Path.Combine(ProjectDir!, "obj", assertOptions.Configuration.ToString(), assertOptions.BuildOptions.TargetFramework, "wasm", assertOptions.BuildOptions.IsPublish ? "for-publish" : "for-build"); - string runtimeNativeDir = BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.TargetFramework, assertOptions.RuntimeType); + string runtimeNativeDir = BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.BuildOptions.TargetFramework, assertOptions.BuildOptions.RuntimeType); string srcDirForNativeFileToCompareAgainst = assertOptions.ExpectedFileType switch { NativeFilesType.FromRuntimePack => runtimeNativeDir, NativeFilesType.Relinked => objBuildDir, NativeFilesType.AOT => objBuildDir, - _ => throw new ArgumentOutOfRangeException(nameof(assertOptions.ExpectedFileType)) + _ => throw new ArgumentOutOfRangeException(nameof(assertOptions.BuildOptions.ExpectedFileType)) }; - string buildType = assertOptions.IsPublish ? "publish" : "build"; + + string buildType = assertOptions.BuildOptions.IsPublish ? "publish" : "build"; var nativeFilesToCheck = new List() { "dotnet.native.wasm", "dotnet.native.js" }; - if (assertOptions.RuntimeType == RuntimeVariant.MultiThreaded) + if (assertOptions.BuildOptions.RuntimeType == RuntimeVariant.MultiThreaded) { nativeFilesToCheck.Add("dotnet.native.worker.mjs"); } - if (assertOptions.GlobalizationMode == GlobalizationMode.Hybrid) + if (assertOptions.BuildOptions.GlobalizationMode == GlobalizationMode.Hybrid) { nativeFilesToCheck.Add("dotnet.globalization.js"); } @@ -138,7 +147,7 @@ protected void AssertBundle(AssertWasmSdkBundleOptions assertOptions) actualDotnetFiles[nativeFilename].ActualPath, buildType); - if (assertOptions.ExpectedFileType != NativeFilesType.FromRuntimePack) + if (assertOptions.BuildOptions.ExpectedFileType != NativeFilesType.FromRuntimePack) { if (nativeFilename == "dotnet.native.worker.mjs") { @@ -152,41 +161,36 @@ protected void AssertBundle(AssertWasmSdkBundleOptions assertOptions) } } } - - public void AssertTestMainJsBundle(BuildArgs buildArgs, - BuildProjectOptions buildProjectOptions, - string? buildOutput = null, - AssertTestMainJsAppBundleOptions? assertAppBundleOptions = null) - { - if (buildOutput is not null) - ProjectProviderBase.AssertRuntimePackPath(buildOutput, buildProjectOptions.TargetFramework ?? _defaultTargetFramework); - if (assertAppBundleOptions is not null) - AssertBundle(assertAppBundleOptions); - else - AssertBundle(buildArgs, buildProjectOptions); + public void AssertWasmSdkBundle(Configuration config, MSBuildOptions buildOptions, bool isUsingWorkloads, bool? isNativeBuild = null, string? buildOutput = null) + { + if (isUsingWorkloads && buildOutput is not null) + { + // In no-workload case, the path would be from a restored nuget + ProjectProviderBase.AssertRuntimePackPath(buildOutput, buildOptions.TargetFramework ?? _defaultTargetFramework, buildOptions.RuntimeType); + } + AssertBundle(config, buildOptions, isUsingWorkloads, isNativeBuild); } - - public void AssertWasmSdkBundle(BuildArgs buildArgs, - BuildProjectOptions buildProjectOptions, - string? buildOutput = null, - AssertWasmSdkBundleOptions? assertAppBundleOptions = null) + + public BuildPaths GetBuildPaths(Configuration configuration, bool forPublish) { - if (buildOutput is not null) - ProjectProviderBase.AssertRuntimePackPath(buildOutput, buildProjectOptions.TargetFramework ?? _defaultTargetFramework); - - if (assertAppBundleOptions is not null) - AssertBundle(assertAppBundleOptions); - else - AssertBundle(buildArgs, buildProjectOptions); + Assert.NotNull(ProjectDir); + string configStr = configuration.ToString(); + string objDir = Path.Combine(ProjectDir, "obj", configStr, _defaultTargetFramework); + string binDir = Path.Combine(ProjectDir, "bin", configStr, _defaultTargetFramework); + string binFrameworkDir = GetBinFrameworkDir(configuration, forPublish, _defaultTargetFramework); + + string objWasmDir = Path.Combine(objDir, "wasm", forPublish ? "for-publish" : "for-build"); + // for build: we should take from runtime pack? + return new BuildPaths(objWasmDir, objDir, binDir, binFrameworkDir); } - public override string FindBinFrameworkDir(string config, bool forPublish, string framework, string? projectDir = null) + public override string GetBinFrameworkDir(Configuration config, bool forPublish, string framework, string? projectDir = null) { EnsureProjectDirIsSet(); - string basePath = Path.Combine(projectDir ?? ProjectDir!, "bin", config, framework); + string basePath = Path.Combine(projectDir ?? ProjectDir!, "bin", config.ToString(), framework); if (forPublish) - basePath = FindSubDirIgnoringCase(basePath, "publish"); + basePath = Path.Combine(basePath, "publish"); return Path.Combine(basePath, BundleDirName, "_framework"); } diff --git a/src/mono/wasm/Wasm.Build.Tests/WorkloadTests.cs b/src/mono/wasm/Wasm.Build.Tests/WorkloadTests.cs index 009da0c7bb1ae..0889d53091c02 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WorkloadTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WorkloadTests.cs @@ -16,7 +16,7 @@ namespace Wasm.Build.Tests { - public class WorkloadTests : TestMainJsTestBase + public class WorkloadTests : WasmTemplateTestsBase { public WorkloadTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) diff --git a/src/mono/wasm/testassets/AppUsingNativeLib/Program.cs b/src/mono/wasm/testassets/AppUsingNativeLib/Program.cs index 5134392c9d8ca..b56e43a2e8404 100644 --- a/src/mono/wasm/testassets/AppUsingNativeLib/Program.cs +++ b/src/mono/wasm/testassets/AppUsingNativeLib/Program.cs @@ -11,7 +11,7 @@ public class Test { public static int Main(string[] args) { - Console.WriteLine ($"from pinvoke: {SimpleConsole.Test.print_line(100)}"); + Console.WriteLine ($"TestOutput -> from pinvoke: {SimpleConsole.Test.print_line(100)}"); return 0; } diff --git a/src/mono/wasm/testassets/AppUsingNativeLib/native-lib.cpp b/src/mono/wasm/testassets/AppUsingNativeLib/native-lib.cpp index 329a593279fe2..9babda6dde8bc 100644 --- a/src/mono/wasm/testassets/AppUsingNativeLib/native-lib.cpp +++ b/src/mono/wasm/testassets/AppUsingNativeLib/native-lib.cpp @@ -6,6 +6,6 @@ int print_line(int x) { - printf("print_line: %d\n", x); + printf("TestOutput -> print_line: %d\n", x); return 42 + x; } diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/App.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/App.razor new file mode 100644 index 0000000000000..6fd3ed1b5a3b0 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/BlazorBasicTestApp.csproj b/src/mono/wasm/testassets/BlazorBasicTestApp/App/BlazorBasicTestApp.csproj new file mode 100644 index 0000000000000..5030d68c356ee --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/BlazorBasicTestApp.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + + + + + + + + diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/MainLayout.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/MainLayout.razor new file mode 100644 index 0000000000000..76eb72528390c --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/MainLayout.razor @@ -0,0 +1,16 @@ +@inherits LayoutComponentBase +
+ + +
+
+ About +
+ +
+ @Body +
+
+
diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/NavMenu.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/NavMenu.razor new file mode 100644 index 0000000000000..345ee43c02f80 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Layout/NavMenu.razor @@ -0,0 +1,39 @@ + + + + +@code { + private bool collapseNavMenu = true; + + private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } +} diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Counter.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Counter.razor new file mode 100644 index 0000000000000..ef23cb31607f8 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Counter.razor @@ -0,0 +1,18 @@ +@page "/counter" + +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Home.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Home.razor new file mode 100644 index 0000000000000..6c432724c86b8 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Pages/Home.razor @@ -0,0 +1,17 @@ +@page "/" + +Home + +

Hello, world!

+ +Welcome to your new app. + +@code { + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { + Console.WriteLine("WASM EXIT 0"); + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/Program.cs b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Program.cs new file mode 100644 index 0000000000000..b81a54015b025 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/Program.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using BlazorBasicTestApp; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +await builder.Build().RunAsync(); diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/_Imports.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/App/_Imports.razor new file mode 100644 index 0000000000000..99e8be8c170d9 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using BlazorBasicTestApp +@using BlazorBasicTestApp.Layout diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/favicon.png b/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/favicon.png new file mode 100644 index 0000000000000..8422b59695935 Binary files /dev/null and b/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/favicon.png differ diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/icon-192.png b/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/icon-192.png new file mode 100644 index 0000000000000..166f56da7612e Binary files /dev/null and b/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/icon-192.png differ diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/index.html b/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/index.html new file mode 100644 index 0000000000000..d9b7c48fe9435 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/App/wwwroot/index.html @@ -0,0 +1,32 @@ + + + + + + + BlazorBasicTestApp + + + + + + + + +
+ + + + +
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/Component1.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/Component1.razor new file mode 100644 index 0000000000000..748079eb2e228 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/Component1.razor @@ -0,0 +1,3 @@ +
+ This component is defined in the RazorClassLibrary library. +
diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/RazorClassLibrary.csproj b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/RazorClassLibrary.csproj new file mode 100644 index 0000000000000..a4991806ea06c --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/RazorClassLibrary.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + diff --git a/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/_Imports.razor b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/_Imports.razor new file mode 100644 index 0000000000000..77285129dabe4 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorBasicTestApp/RazorClassLibrary/_Imports.razor @@ -0,0 +1 @@ +@using Microsoft.AspNetCore.Components.Web diff --git a/src/mono/wasm/testassets/EntryPoints/AsyncMainWithArgs.cs b/src/mono/wasm/testassets/EntryPoints/AsyncMainWithArgs.cs new file mode 100644 index 0000000000000..594a03a50d906 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/AsyncMainWithArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; + +public class TestClass { + static void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); + + public static async Task Main(string[] args) + { + int count = args == null ? 0 : args.Length; + WriteTestOutput($"args#: {args?.Length}"); + foreach (var arg in args ?? Array.Empty()) + WriteTestOutput($"arg: {arg}"); + return await Task.FromResult(42 + count); + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/CultureResource.cs b/src/mono/wasm/testassets/EntryPoints/CultureResource.cs new file mode 100644 index 0000000000000..ca928e32200a4 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/CultureResource.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.CompilerServices; +using System.Globalization; +using System.Resources; +using System.Threading; + +namespace ResourcesTest +{ + public class TestClass + { + public static int Main(string[] args) + { + string expected; + if (args.Length == 1) + { + string cultureToTest = args[0]; + var newCulture = new CultureInfo(cultureToTest); + Thread.CurrentThread.CurrentCulture = newCulture; + Thread.CurrentThread.CurrentUICulture = newCulture; + + if (cultureToTest == "es-ES") + expected = "hola"; + else if (cultureToTest == "ja-JP") + expected = "\u3053\u3093\u306B\u3061\u306F"; + else + throw new Exception($"Cannot determine the expected output for {cultureToTest}"); + + } else { + expected = "hello"; + } + + var currentCultureName = Thread.CurrentThread.CurrentCulture.Name; + + var rm = new ResourceManager("##RESOURCE_NAME##", typeof(##TYPE_NAME##).Assembly); + Console.WriteLine($"For '{currentCultureName}' got: {rm.GetString("hello")}"); + + return rm.GetString("hello") == expected ? 42 : -1; + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/HybridGlobalization.cs b/src/mono/wasm/testassets/EntryPoints/HybridGlobalization.cs similarity index 50% rename from src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/HybridGlobalization.cs rename to src/mono/wasm/testassets/EntryPoints/HybridGlobalization.cs index 094a10d787179..d318530fb26ff 100644 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/HybridGlobalization.cs +++ b/src/mono/wasm/testassets/EntryPoints/HybridGlobalization.cs @@ -1,6 +1,8 @@ using System; using System.Globalization; +void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); + try { CompareInfo compareInfo = new CultureInfo("es-ES").CompareInfo; @@ -10,16 +12,16 @@ return 1; } int shouldThrow = compareInfo.Compare("A\u0300", "\u00C0", CompareOptions.IgnoreNonSpace); - Console.WriteLine($"Did not throw as expected but returned {shouldThrow} as a result. Using CompareOptions.IgnoreNonSpace option alone should be unavailable in HybridGlobalization mode."); + WriteTestOutput($"Did not throw as expected but returned {shouldThrow} as a result. Using CompareOptions.IgnoreNonSpace option alone should be unavailable in HybridGlobalization mode."); } catch (PlatformNotSupportedException pnse) { - Console.WriteLine($"HybridGlobalization works, thrown exception as expected: {pnse}."); + WriteTestOutput($"HybridGlobalization works, thrown exception as expected: {pnse}."); return 42; } catch (Exception ex) { - Console.WriteLine($"HybridGlobalization failed, unexpected exception was thrown: {ex}."); + WriteTestOutput($"HybridGlobalization failed, unexpected exception was thrown: {ex}."); return 2; } return 3; diff --git a/src/mono/wasm/testassets/EntryPoints/InvariantGlobalization.cs b/src/mono/wasm/testassets/EntryPoints/InvariantGlobalization.cs new file mode 100644 index 0000000000000..535209b197c5d --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/InvariantGlobalization.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Linq; + +// https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data + + +void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); +try +{ + CultureInfo culture = new ("es-ES", false); + WriteTestOutput($"es-ES: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}"); + + string expectedNativeName = "espa\u00F1ol (Espa\u00F1a)"; + string nativeName = culture.NativeName; + if (nativeName != expectedNativeName) + throw new ArgumentException($"Expected es-ES NativeName: {expectedNativeName}, but got: {nativeName}"); +} +catch (CultureNotFoundException cnfe) +{ + WriteTestOutput($"Could not create es-ES culture: {cnfe.Message}"); +} + +WriteTestOutput($"CurrentCulture.NativeName: {CultureInfo.CurrentCulture.NativeName}"); +return 42; diff --git a/src/mono/wasm/testassets/EntryPoints/InvariantTimezone.cs b/src/mono/wasm/testassets/EntryPoints/InvariantTimezone.cs new file mode 100644 index 0000000000000..1ef6c684b3a19 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/InvariantTimezone.cs @@ -0,0 +1,23 @@ +using System; + +// https://github.com/dotnet/runtime/blob/main/docs/design/features/timezone-invariant-mode.md + +void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); + +var timezonesCount = TimeZoneInfo.GetSystemTimeZones().Count; +WriteTestOutput($"Found {timezonesCount} timezones in the TZ database"); + +TimeZoneInfo utc = TimeZoneInfo.FindSystemTimeZoneById("UTC"); +WriteTestOutput($"{utc.DisplayName} BaseUtcOffset is {utc.BaseUtcOffset}"); + +try +{ + TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo"); + WriteTestOutput($"{tst.DisplayName} BaseUtcOffset is {tst.BaseUtcOffset}"); +} +catch (TimeZoneNotFoundException tznfe) +{ + WriteTestOutput($"Could not find Asia/Tokyo: {tznfe.Message}"); +} + +return 42; diff --git a/src/mono/wasm/testassets/EntryPoints/MyDllImport.cs b/src/mono/wasm/testassets/EntryPoints/MyDllImport.cs new file mode 100644 index 0000000000000..a98e9d0334ab2 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/MyDllImport.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; +namespace ##NAMESPACE##; + +public static class MyDllImports +{ + [DllImport("mylib")] + public static extern int cpp_add(int a, int b); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/NativeCrypto.cs b/src/mono/wasm/testassets/EntryPoints/NativeCrypto.cs new file mode 100644 index 0000000000000..5dadfd89d4152 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/NativeCrypto.cs @@ -0,0 +1,17 @@ +using System; +using System.Security.Cryptography; + +public class Test +{ + public static int Main() + { + using (SHA256 mySHA256 = SHA256.Create()) + { + byte[] data = { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' }; + byte[] hashed = mySHA256.ComputeHash(data); + string asStr = string.Join(' ', hashed); + Console.WriteLine("TestOutput -> Hashed: " + asStr); + return 0; + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/NativeRebuildNewAssembly.cs b/src/mono/wasm/testassets/EntryPoints/NativeRebuildNewAssembly.cs new file mode 100644 index 0000000000000..fe96960f5385d --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/NativeRebuildNewAssembly.cs @@ -0,0 +1,17 @@ +using System; +using System.Security.Cryptography; +using System.Text; +public class Test +{ + public static int Main() + { + string input = "Hello, world!"; + using (SHA256 sha256 = SHA256.Create()) + { + byte[] inputBytes = Encoding.UTF8.GetBytes(input); + byte[] hashBytes = sha256.ComputeHash(inputBytes); + Console.WriteLine($"Hash of {input}: {Convert.ToBase64String(hashBytes)}"); + } + return 42; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/AbiRules.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/AbiRules.cs new file mode 100644 index 0000000000000..3c92521527242 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/AbiRules.cs @@ -0,0 +1,107 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +public struct SingleFloatStruct { + public float Value; +} +public struct SingleDoubleStruct { + public struct Nested1 { + // This field is private on purpose to ensure we treat visibility correctly + double Value; + } + public Nested1 Value; +} +public struct SingleI64Struct { + public Int64 Value; +} +public struct PairStruct { + public int A, B; +} +public unsafe struct MyFixedArray { + public fixed int elements[2]; +} +[System.Runtime.CompilerServices.InlineArray(2)] +public struct MyInlineArray { + public int element0; +} + +public class Test +{ + public static unsafe int Main(string[] argv) + { + var i64_a = 0xFF00FF00FF00FF0L; + var i64_b = ~i64_a; + var resI = direct64(i64_a); + Console.WriteLine("TestOutput -> l (l)=" + resI); + + var sis = new SingleI64Struct { Value = i64_a }; + var resSI = indirect64(sis); + Console.WriteLine("TestOutput -> s (s)=" + resSI.Value); + + var resF = direct(3.14); + Console.WriteLine("TestOutput -> f (d)=" + resF); + + SingleDoubleStruct sds = default; + Unsafe.As(ref sds) = 3.14; + + resF = indirect_arg(sds); + Console.WriteLine("TestOutput -> f (s)=" + resF); + + var res = indirect(sds); + Console.WriteLine("TestOutput -> s (s)=" + res.Value); + + var pair = new PairStruct { A = 1, B = 2 }; + var paires = accept_and_return_pair(pair); + Console.WriteLine("TestOutput -> paires.B=" + paires.B); + + // This test is split into methods to simplify debugging issues with it + var ia = InlineArrayTest1(); + var iares = InlineArrayTest2(ia); + Console.WriteLine($"TestOutput -> iares[0]={iares[0]} iares[1]={iares[1]}"); + + MyFixedArray fa = new (); + for (int i = 0; i < 2; i++) + fa.elements[i] = i; + var fares = accept_and_return_fixedarray(fa); + Console.WriteLine("TestOutput -> fares.elements[1]=" + fares.elements[1]); + + int exitCode = (int)res.Value; + return exitCode; + } + + public static unsafe MyInlineArray InlineArrayTest1 () { + MyInlineArray ia = new (); + for (int i = 0; i < 2; i++) + ia[i] = i; + return ia; + } + + public static unsafe MyInlineArray InlineArrayTest2 (MyInlineArray ia) { + return accept_and_return_inlinearray(ia); + } + + [DllImport("wasm-abi", EntryPoint="accept_double_struct_and_return_float_struct")] + public static extern SingleFloatStruct indirect(SingleDoubleStruct arg); + + [DllImport("wasm-abi", EntryPoint="accept_double_struct_and_return_float_struct")] + public static extern float indirect_arg(SingleDoubleStruct arg); + + [DllImport("wasm-abi", EntryPoint="accept_double_struct_and_return_float_struct")] + public static extern float direct(double arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_i64_struct")] + public static extern SingleI64Struct indirect64(SingleI64Struct arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_i64_struct")] + public static extern Int64 direct64(Int64 arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_pair")] + public static extern PairStruct accept_and_return_pair(PairStruct arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_fixedarray")] + public static extern MyFixedArray accept_and_return_fixedarray(MyFixedArray arg); + + [DllImport("wasm-abi", EntryPoint="accept_and_return_inlinearray")] + public static extern MyInlineArray accept_and_return_inlinearray(MyInlineArray arg); +} diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly.cs new file mode 100644 index 0000000000000..562f2384f40c2 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: DisableRuntimeMarshalling] +public class Test +{ + public static int Main() + { + var x = new S { Value = 5 }; + + Console.WriteLine("TestOutput -> Main running " + x.Value); + return 42; + } + + [UnmanagedCallersOnly] + public static void M(S myStruct) { } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly_Lib.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly_Lib.cs new file mode 100644 index 0000000000000..597b51823e518 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableDifferentAssembly_Lib.cs @@ -0,0 +1,6 @@ +[assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling] +public struct __NonBlittableTypeForAutomatedTests__ { } +public struct S { + public int Value; + public __NonBlittableTypeForAutomatedTests__ NonBlittable; +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableSameAssembly.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableSameAssembly.cs new file mode 100644 index 0000000000000..f5103f279e2a1 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/BittableSameAssembly.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: DisableRuntimeMarshalling] +public class Test +{ + public static int Main() + { + var x = new S { Value = 5 }; + + Console.WriteLine("TestOutput -> Main running " + x.Value); + return 42; + } + + [StructLayout(LayoutKind.Auto)] + public struct S { public int Value; public float Value2; } + + [UnmanagedCallersOnly] + public static void M(S myStruct) { } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/BuildNative.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/BuildNative.cs new file mode 100644 index 0000000000000..088651f205c79 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/BuildNative.cs @@ -0,0 +1,8 @@ +using System; +using System.Runtime.InteropServices; + +Console.WriteLine($"TestOutput -> square: {square(5)}"); +return 42; + +[DllImport("simple")] +static extern int square(int x); \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/ComInterop.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/ComInterop.cs new file mode 100644 index 0000000000000..b347a9162c065 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/ComInterop.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +public class Test +{ + public static int Main(string[] args) + { + var s = new STGMEDIUM(); + ReleaseStgMedium(ref s); + return 42; + } + + [DllImport("ole32.dll")] + internal static extern void ReleaseStgMedium(ref STGMEDIUM medium); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportNoWarning.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportNoWarning.cs new file mode 100644 index 0000000000000..a77515ae54c1a --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportNoWarning.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main() + { + Console.WriteLine("TestOutput -> Main running"); + return 42; + } + + [DllImport("variadic", EntryPoint="sum")] + public unsafe static extern int using_sum_one(delegate* unmanaged callback); + + [DllImport("variadic", EntryPoint="sum")] + public static extern int sum_one(int a, int b); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportWarning.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportWarning.cs new file mode 100644 index 0000000000000..8e6faeef850d1 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/DllImportWarning.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main() + { + Console.WriteLine("TestOutput -> Main running"); + return 42; + } + + [DllImport("variadic", EntryPoint="sum")] + public unsafe static extern int using_sum_one(delegate* unmanaged callback); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/FunctionPointers.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/FunctionPointers.cs new file mode 100644 index 0000000000000..6c3d56a09e3a5 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/FunctionPointers.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main() + { + Console.WriteLine("TestOutput -> Main running"); + return 42; + } + + [DllImport("someting")] + public unsafe static extern void SomeFunction1(delegate* unmanaged callback); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/ICall_Lib.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/ICall_Lib.cs new file mode 100644 index 0000000000000..351578387cd8f --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/ICall_Lib.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.CompilerServices; + +public static class Interop +{ + public enum Numbers { A, B, C, D } + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void Square(Numbers x); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void Square(Numbers x, Numbers y); + + public static void Main() + { + // Noop + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/UnmanagedCallback.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallback.cs similarity index 83% rename from src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/UnmanagedCallback.cs rename to src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallback.cs index 623cd2c5cb707..0482b445a42f9 100644 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/UnmanagedCallback.cs +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallback.cs @@ -18,7 +18,7 @@ file class Interop { [UnmanagedCallersOnly(EntryPoint = "ConflictManagedFunc")] public static int Managed8\u4F60Func(int number) { - Console.WriteLine($"Conflict.A.Managed8\u4F60Func({number}) -> {number}"); + Console.WriteLine($"TestOutput -> Conflict.A.Managed8\u4F60Func({number}) -> {number}"); return number; } } @@ -30,7 +30,7 @@ file partial class Interop public static int Managed8\u4F60Func(int number) { // called from UnmanagedFunc - Console.WriteLine($"Managed8\u4F60Func({number}) -> 42"); + Console.WriteLine($"TestOutput -> Managed8\u4F60Func({number}) -> 42"); return 42; } diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackInFile.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackInFile.cs new file mode 100644 index 0000000000000..a56183c29247d --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackInFile.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main() + { + Console.WriteLine("TestOutput -> Main running"); + return 42; + } +} + +file class Foo +{ + [UnmanagedCallersOnly] + public unsafe static extern void SomeFunction1(int i); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackNamespaced.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackNamespaced.cs new file mode 100644 index 0000000000000..334e49ef432a2 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/UnmanagedCallbackNamespaced.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.InteropServices; + +public class Test +{ + public unsafe static int Main() + { + ((delegate* unmanaged)&A.Conflict.C)(); + ((delegate* unmanaged)&B.Conflict.C)(); + ((delegate* unmanaged)&A.Conflict.C\u733f)(); + ((delegate* unmanaged)&B.Conflict.C\u733f)(); + return 42; + } +} + +namespace A { + public class Conflict { + [UnmanagedCallersOnly(EntryPoint = "A_Conflict_C")] + public static void C() { + Console.WriteLine("TestOutput -> A.Conflict.C"); + } + + [UnmanagedCallersOnly(EntryPoint = "A_Conflict_C\u733f")] + public static void C\u733f() { + Console.WriteLine("TestOutput -> A.Conflict.C_\U0001F412"); + } + } +} + +namespace B { + public class Conflict { + [UnmanagedCallersOnly(EntryPoint = "B_Conflict_C")] + public static void C() { + Console.WriteLine("TestOutput -> B.Conflict.C"); + } + + [UnmanagedCallersOnly(EntryPoint = "B_Conflict_C\u733f")] + public static void C\u733f() { + Console.WriteLine("TestOutput -> B.Conflict.C_\U0001F412"); + } + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/PInvoke/VariadicFunctions.cs b/src/mono/wasm/testassets/EntryPoints/PInvoke/VariadicFunctions.cs new file mode 100644 index 0000000000000..5357065a9a599 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/PInvoke/VariadicFunctions.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.InteropServices; +public class Test +{ + public static int Main(string[] args) + { + Console.WriteLine("TestOutput -> Main running"); + if (args.Length > 2) + { + // We don't want to run this, because we can't call variadic functions + Console.WriteLine($"sum_three: {sum_three(7, 14, 21)}"); + Console.WriteLine($"sum_two: {sum_two(3, 6)}"); + Console.WriteLine($"sum_one: {sum_one(5)}"); + } + return 42; + } + + [DllImport("variadic", EntryPoint="sum")] public static extern int sum_one(int a); + [DllImport("variadic", EntryPoint="sum")] public static extern int sum_two(int a, int b); + [DllImport("variadic", EntryPoint="sum")] public static extern int sum_three(int a, int b, int c); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/SimpleSourceChange.cs b/src/mono/wasm/testassets/EntryPoints/SimpleSourceChange.cs new file mode 100644 index 0000000000000..e86104e6df8f5 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/SimpleSourceChange.cs @@ -0,0 +1,7 @@ +public class TestClass +{ + public static int Main() + { + return 55; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/SkiaSharp.cs b/src/mono/wasm/testassets/EntryPoints/SkiaSharp.cs new file mode 100644 index 0000000000000..97236c63e5a66 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/SkiaSharp.cs @@ -0,0 +1,14 @@ +using System; +using SkiaSharp; + +public class Test +{ + public static int Main() + { + using SKFileStream skfs = new SKFileStream("mono.png"); + using SKImage img = SKImage.FromEncodedData(skfs); + + Console.WriteLine ($"Size: {skfs.Length} Height: {img.Height}, Width: {img.Width}"); + return 0; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/EntryPoints/SyncMainWithArgs.cs b/src/mono/wasm/testassets/EntryPoints/SyncMainWithArgs.cs new file mode 100644 index 0000000000000..08b859d5173e9 --- /dev/null +++ b/src/mono/wasm/testassets/EntryPoints/SyncMainWithArgs.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; + +public class TestClass { + static void WriteTestOutput(string output) => Console.WriteLine($"TestOutput -> {output}"); + + public static int Main(string[] args) + { + int count = args == null ? 0 : args.Length; + WriteTestOutput($"args#: {args?.Length}"); + foreach (var arg in args ?? Array.Empty()) + WriteTestOutput($"arg: {arg}"); + return 42 + count; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources/LibraryWithResources.csproj b/src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources/LibraryWithResources.csproj index c6c44c6413000..3043227ce00b5 100644 --- a/src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources/LibraryWithResources.csproj +++ b/src/mono/wasm/testassets/SatelliteAssemblyFromProjectRef/LibraryWithResources/LibraryWithResources.csproj @@ -1,5 +1,5 @@ - net7.0 + net9.0 diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs deleted file mode 100644 index c7b1219b6aabd..0000000000000 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; - -// https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data -try -{ - CultureInfo culture = new ("es-ES", false); - Console.WriteLine($"es-ES: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}"); - - var nativeNameArg = args.FirstOrDefault(arg => arg.StartsWith("nativename=")); - if (nativeNameArg == null) - throw new ArgumentException($"When not in invariant mode, InvariantGlobalization.cs expects nativename argument with expected es-ES NativeName."); - string expectedNativeName = nativeNameArg.Substring(11).Trim('"'); // skip nativename= - string nativeName = culture.NativeName; - if (nativeName != expectedNativeName) - throw new ArgumentException($"Expected es-ES NativeName: {expectedNativeName}, but got: {nativeName}"); -} -catch (CultureNotFoundException cnfe) -{ - Console.WriteLine($"Could not create es-ES culture: {cnfe.Message}"); -} - -Console.WriteLine($"CurrentCulture.NativeName: {CultureInfo.CurrentCulture.NativeName}"); -return 42; diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantTimezone.cs b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantTimezone.cs deleted file mode 100644 index deba28c7be27e..0000000000000 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantTimezone.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -// https://github.com/dotnet/runtime/blob/main/docs/design/features/timezone-invariant-mode.md - -var timezonesCount = TimeZoneInfo.GetSystemTimeZones().Count; -Console.WriteLine($"Found {timezonesCount} timezones in the TZ database"); - -TimeZoneInfo utc = TimeZoneInfo.FindSystemTimeZoneById("UTC"); -Console.WriteLine($"{utc.DisplayName} BaseUtcOffset is {utc.BaseUtcOffset}"); - -try -{ - TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo"); - Console.WriteLine($"{tst.DisplayName} BaseUtcOffset is {tst.BaseUtcOffset}"); -} -catch (TimeZoneNotFoundException tznfe) -{ - Console.WriteLine($"Could not find Asia/Tokyo: {tznfe.Message}"); -} - -return 42; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj b/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj index 548b310286bed..d65c69a4ba4ab 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj @@ -4,6 +4,8 @@ browser-wasm Exe true + + $(NoWarn);CS0169 @@ -15,11 +17,11 @@ - + - + diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html index 5179693a495f6..f0429391614fb 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html @@ -8,7 +8,7 @@ - + diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index b2354f0672c3f..8a773e2756aaa 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -26,6 +26,16 @@ dotnet .withExitCodeLogging() .withExitOnUnhandledError(); +const logLevel = params.get("MONO_LOG_LEVEL"); +const logMask = params.get("MONO_LOG_MASK"); +if (logLevel !== null && logMask !== null) { + dotnet.withDiagnosticTracing(true); // enable JavaScript tracing + dotnet.withConfig({environmentVariables: { + "MONO_LOG_LEVEL": logLevel, + "MONO_LOG_MASK": logMask, + }}); +} + // Modify runtime start based on test case switch (testCase) { case "SatelliteAssembliesTest": @@ -151,6 +161,9 @@ switch (testCase) { case "OverrideBootConfigName": dotnet.withConfigSrc("boot.json"); break; + case "MainWithArgs": + dotnet.withApplicationArgumentsFromQuery(); + break; } const { setModuleImports, Module, getAssemblyExports, getConfig, INTERNAL } = await dotnet.create(); @@ -199,6 +212,8 @@ try { exit(0); break; case "OutErrOverrideWorks": + case "DotnetRun": + case "MainWithArgs": dotnet.run(); break; case "DebugLevelTest": diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Library/Json.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Json/Json.cs similarity index 100% rename from src/mono/wasm/testassets/WasmBasicTestApp/Library/Json.cs rename to src/mono/wasm/testassets/WasmBasicTestApp/Json/Json.cs diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Library/Json.csproj b/src/mono/wasm/testassets/WasmBasicTestApp/Json/Json.csproj similarity index 100% rename from src/mono/wasm/testassets/WasmBasicTestApp/Library/Json.csproj rename to src/mono/wasm/testassets/WasmBasicTestApp/Json/Json.csproj diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.cs new file mode 100644 index 0000000000000..a1ed50130d713 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.cs @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Empty external library that is not referenced by default by the app +// this file can be updated using ReplaceFile or UpdateFile if needed \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.csproj b/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.csproj new file mode 100644 index 0000000000000..3d5e0e2093c1a --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Library/Library.csproj @@ -0,0 +1,7 @@ + + + net9.0 + Library + true + + diff --git a/src/mono/wasm/testassets/marshal_ilgen_test.cs b/src/mono/wasm/testassets/marshal_ilgen_test.cs index 1c52a8b5ee349..685ae119ccc4a 100644 --- a/src/mono/wasm/testassets/marshal_ilgen_test.cs +++ b/src/mono/wasm/testassets/marshal_ilgen_test.cs @@ -4,7 +4,7 @@ int[] x = new int[0]; MyClass.call_needing_marhsal_ilgen(x); -Console.WriteLine("call_needing_marhsal_ilgen got called"); +Console.WriteLine("TestOutput -> call_needing_marhsal_ilgen got called"); return 42; diff --git a/src/mono/wasm/testassets/native-libs/local.c b/src/mono/wasm/testassets/native-libs/local.c index df7987bb8f38d..125ee7c4b704b 100644 --- a/src/mono/wasm/testassets/native-libs/local.c +++ b/src/mono/wasm/testassets/native-libs/local.c @@ -6,5 +6,5 @@ void UnmanagedFunc() int ret = 0; printf("UnmanagedFunc calling ManagedFunc\n"); ret = ManagedFunc(123); - printf("ManagedFunc returned %d\n", ret); + printf("TestOutput -> ManagedFunc returned %d\n", ret); } diff --git a/src/mono/wasm/testassets/native-libs/mylib.cpp b/src/mono/wasm/testassets/native-libs/mylib.cpp new file mode 100644 index 0000000000000..b781006fec596 --- /dev/null +++ b/src/mono/wasm/testassets/native-libs/mylib.cpp @@ -0,0 +1,7 @@ +#include + +extern "C" { + int cpp_add(int a, int b) { + return a + b; + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/native-libs/native-lib.o b/src/mono/wasm/testassets/native-libs/native-lib.o index 10ccf42c5ff23..5b5218ec686a8 100644 Binary files a/src/mono/wasm/testassets/native-libs/native-lib.o and b/src/mono/wasm/testassets/native-libs/native-lib.o differ