From 66ce263237282f1f74ecb7b76edae2e1f634471d Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:15:35 +0200 Subject: [PATCH] [mt] Add signalR WBT on WASM browser app (#100723) * WIP, fixing net::ERR_CONNECTION_REFUSED. * Change of setup - aspnet project serving WASM static files. * Enable SharedArrayBuffer. * WIP, investigating `JsonSerializerIsReflectionDisabled` * Fix serialization issue. * Fixed * Cleanup. * Trying to fix integrity check issue. * Async interop + more logging. * Move test preparation code to a method. * Remove buttons, match other TestAppScenarios style. * Moved the test out of TestAppScenarios. Standalone version works. * Working version of test. * Cleanup. * Cleanup. * @maraf's feedback - simplify the prebuild steps. * WIP - merging blazor and wasmapp into one proj. * Missing change in a non-related test. * Do not pack blazor into a subfolder. * Remove old app. * Connect test logic, simplify. * Not intentional. * AspServer serves only "standard content types" * Prevent loosing `InnerException` on exceptions thrown during `TaskCompletionSource` completion. * Revert. * Cleanup. * @maraf's cleanup * SignalR hub is located in a url with path. * Fix "GET http://localhost:5000/wasmclient, Response status code: 404" --- .../scenarios/BuildWasmAppsJobsList.txt | 3 +- .../AspNetCore/SignalRClientTests.cs | 27 ++++ .../Blazor/BlazorWasmTestBase.cs | 2 +- .../Blazor/SignalRClientTests.cs | 30 +++++ .../wasm/Wasm.Build.Tests/SignalRTestsBase.cs | 49 ++++++++ .../TestAppScenarios/AppSettingsTests.cs | 2 +- .../TestAppScenarios/AppTestBase.cs | 37 +++--- .../TestAppScenarios/DebugLevelTests.cs | 14 +-- .../TestAppScenarios/LazyLoadingTests.cs | 4 +- .../LibraryInitializerTests.cs | 4 +- .../TestAppScenarios/ModuleConfigTests.cs | 4 +- .../TestAppScenarios/SatelliteLoadingTests.cs | 2 +- .../TestAppScenarios/SignalRClientTests.cs | 101 --------------- .../BlazorHosted.Client/Helper.cs | 42 ------- .../BlazorHosted.Client/Pages/Chat.razor | 116 ------------------ .../BlazorHosted.Client/wwwroot/favicon.ico | Bin 5430 -> 0 bytes .../BlazorHosted.Server/Program.cs | 52 -------- .../BlazorHosted.Server/appsettings.json | 8 -- .../AspNetCoreServer/AspNetCoreServer.csproj} | 5 +- .../AspNetCoreServer}/ChatHub.cs | 3 +- .../AspNetCoreServer/Program.cs | 62 ++++++++++ .../BlazorClient}/App.razor | 0 .../BlazorClient/BlazorClient.csproj} | 11 +- .../BlazorClient}/Layout/MainLayout.razor | 0 .../BlazorClient/Pages/Chat.razor | 34 +++++ .../BlazorClient}/Program.cs | 2 +- .../BlazorClient}/_Imports.razor | 5 +- .../BlazorClient}/wwwroot/index.html | 11 +- .../WasmOnAspNetCore/Shared/QueryParser.cs | 24 ++++ .../WasmOnAspNetCore/Shared/Shared.csproj | 19 +++ .../WasmOnAspNetCore/Shared/SignalRTest.cs | 114 +++++++++++++++++ .../WasmOnAspNetCore/Shared/TestOutput.cs | 20 +++ .../WasmBrowserClient/Program.cs | 21 ++++ .../WasmBrowserClient/TestOutput.cs | 18 +++ .../WasmBrowserClient.csproj | 17 +++ .../WasmBrowserClient/wwwroot/index.html | 17 +++ .../WasmBrowserClient/wwwroot/main.js | 19 +++ 37 files changed, 528 insertions(+), 371 deletions(-) create mode 100644 src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SignalRClientTests.cs delete mode 100644 src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Helper.cs delete mode 100644 src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Pages/Chat.razor delete mode 100644 src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/wwwroot/favicon.ico delete mode 100644 src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/Program.cs delete mode 100644 src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/appsettings.json rename src/mono/wasm/testassets/{BlazorHostedApp/BlazorHosted.Server/BlazorHosted.Server.csproj => WasmOnAspNetCore/AspNetCoreServer/AspNetCoreServer.csproj} (63%) rename src/mono/wasm/testassets/{BlazorHostedApp/BlazorHosted.Server => WasmOnAspNetCore/AspNetCoreServer}/ChatHub.cs (93%) create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/Program.cs rename src/mono/wasm/testassets/{BlazorHostedApp/BlazorHosted.Client => WasmOnAspNetCore/BlazorClient}/App.razor (100%) rename src/mono/wasm/testassets/{BlazorHostedApp/BlazorHosted.Client/BlazorHosted.Client.csproj => WasmOnAspNetCore/BlazorClient/BlazorClient.csproj} (67%) rename src/mono/wasm/testassets/{BlazorHostedApp/BlazorHosted.Client => WasmOnAspNetCore/BlazorClient}/Layout/MainLayout.razor (100%) create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/Pages/Chat.razor rename src/mono/wasm/testassets/{BlazorHostedApp/BlazorHosted.Client => WasmOnAspNetCore/BlazorClient}/Program.cs (95%) rename src/mono/wasm/testassets/{BlazorHostedApp/BlazorHosted.Client => WasmOnAspNetCore/BlazorClient}/_Imports.razor (72%) rename src/mono/wasm/testassets/{BlazorHostedApp/BlazorHosted.Client => WasmOnAspNetCore/BlazorClient}/wwwroot/index.html (62%) create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/Shared/QueryParser.cs create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/Shared/Shared.csproj create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/Shared/SignalRTest.cs create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/Shared/TestOutput.cs create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/Program.cs create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/TestOutput.cs create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/WasmBrowserClient.csproj create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/wwwroot/index.html create mode 100644 src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/wwwroot/main.js diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index 63c4453b4ff66..6afea6cb3e223 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -17,6 +17,7 @@ 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.HybridGlobalizationTests @@ -37,7 +38,7 @@ Wasm.Build.Tests.TestAppScenarios.LazyLoadingTests Wasm.Build.Tests.TestAppScenarios.LibraryInitializerTests Wasm.Build.Tests.TestAppScenarios.SatelliteLoadingTests Wasm.Build.Tests.TestAppScenarios.ModuleConfigTests -Wasm.Build.Tests.TestAppScenarios.SignalRClientTests +Wasm.Build.Tests.AspNetCore.SignalRClientTests Wasm.Build.Tests.WasmBuildAppTest Wasm.Build.Tests.WasmNativeDefaultsTests Wasm.Build.Tests.WasmRunOutOfAppBundleTests diff --git a/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs b/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs new file mode 100644 index 0000000000000..85f538fac1c20 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/AspNetCore/SignalRClientTests.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit; + +#nullable enable + +namespace Wasm.Build.Tests.AspNetCore; + +public class SignalRClientTests : SignalRTestsBase +{ + public SignalRClientTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [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) => + await SignalRPassMessage("wasmclient", config, transport); +} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs index da9c7764f2d1f..46c8f2ce13287 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWasmTestBase.cs @@ -205,7 +205,7 @@ public async Task BlazorRunTest(string runArgs, onConsoleMessage: OnConsoleMessage, onServerMessage: runOptions.OnServerMessage, onError: OnErrorMessage, - modifyBrowserUrl: browserUrl => browserUrl + runOptions.BrowserPath + runOptions.QueryString); + 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 }); diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs new file mode 100644 index 0000000000000..cf4f938bc1885 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/SignalRClientTests.cs @@ -0,0 +1,30 @@ +// 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.Threading.Tasks; +using Xunit.Abstractions; +using Xunit; + +#nullable enable + +namespace Wasm.Build.Tests.Blazor; + +public class SignalRClientTests : SignalRTestsBase +{ + public SignalRClientTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [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) => + await SignalRPassMessage("blazorclient", config, transport); +} + diff --git a/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.cs new file mode 100644 index 0000000000000..183d984b39b48 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/SignalRTestsBase.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.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Collections.Generic; +using Wasm.Build.Tests.TestAppScenarios; +using Xunit.Abstractions; +using Xunit; +#nullable enable + +namespace Wasm.Build.Tests; + +public class SignalRTestsBase : AppTestBase +{ + public SignalRTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + protected async Task SignalRPassMessage(string staticWebAssetBasePath, string config, string transport) + { + CopyTestAsset("WasmOnAspNetCore", "SignalRClientTests", "AspNetCoreServer"); + PublishProject(config, runtimeType: RuntimeVariant.MultiThreaded, assertAppBundle: false); + + var result = await RunSdkStyleAppForBuild(new( + Configuration: config, + ServerEnvironment: new Dictionary { ["ASPNETCORE_ENVIRONMENT"] = "Development" }, + BrowserPath: staticWebAssetBasePath, + BrowserQueryString: new Dictionary { ["transport"] = transport, ["message"] = "ping" } )); + + string testOutput = string.Join("\n", result.TestOutput) ?? ""; + Assert.NotEmpty(testOutput); + // check sending and receiving threadId + string threadIdUsedForSending = GetThreadOfAction(testOutput, @"SignalRPassMessages was sent by CurrentManagedThreadId=(\d+)", "signalR message was sent"); + string threadIdUsedForReceiving = GetThreadOfAction(testOutput, @"ReceiveMessage from server on CurrentManagedThreadId=(\d+)", "signalR message was received"); + string consoleOutput = string.Join("\n", result.ConsoleOutput); + Assert.True("1" != threadIdUsedForSending || "1" != threadIdUsedForReceiving, + $"Expected to send/receive with signalR in non-UI threads, instead only CurrentManagedThreadId=1 was used. ConsoleOutput: {consoleOutput}."); + } + + private string GetThreadOfAction(string testOutput, string pattern, string actionDescription) + { + Match match = Regex.Match(testOutput, pattern); + Assert.True(match.Success, $"Expected to find a log that {actionDescription}. TestOutput: {testOutput}."); + return match.Groups[1].Value ?? ""; + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs index 5d028cc238909..4ce4134291777 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs @@ -25,7 +25,7 @@ public AppSettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [InlineData("Production")] public async Task LoadAppSettingsBasedOnApplicationEnvironment(string applicationEnvironment) { - CopyTestAsset("WasmBasicTestApp", "AppSettingsTests"); + CopyTestAsset("WasmBasicTestApp", "AppSettingsTests", "App"); PublishProject("Debug"); var result = await RunSdkStyleAppForPublish(new( diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs index 01a1afe96c0bb..9b228c65faaa4 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs @@ -23,7 +23,7 @@ protected AppTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture b protected string Id { get; set; } protected string LogPath { get; set; } - protected void CopyTestAsset(string assetName, string generatedProjectNamePrefix = null) + protected void CopyTestAsset(string assetName, string generatedProjectNamePrefix = null, string? projectDirSuffix = null) { Id = $"{generatedProjectNamePrefix ?? assetName}_{GetRandomId()}"; InitBlazorWasmProjectDir(Id); @@ -31,27 +31,21 @@ protected void CopyTestAsset(string assetName, string generatedProjectNamePrefix LogPath = Path.Combine(s_buildEnv.LogRootPath, Id); Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, assetName), Path.Combine(_projectDir!)); - switch(assetName) + if (!string.IsNullOrEmpty(projectDirSuffix)) { - case "WasmBasicTestApp": - // WasmBasicTestApp consists of App + Library projects - _projectDir = Path.Combine(_projectDir!, "App"); - break; - case "BlazorHostedApp": - // BlazorHostedApp consists of BlazorHosted.Client and BlazorHosted.Server projects - _projectDir = Path.Combine(_projectDir!, "BlazorHosted.Server"); - break; + _projectDir = Path.Combine(_projectDir, projectDirSuffix); } } protected void BlazorHostedBuild( string config, string assetName, + string projectDirSuffix, string clientDirRelativeToProjectDir = "", string? generatedProjectNamePrefix = null, RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded) { - CopyTestAsset(assetName, generatedProjectNamePrefix); + CopyTestAsset(assetName, generatedProjectNamePrefix, projectDirSuffix); string frameworkDir = FindBlazorHostedBinFrameworkDir(config, forPublish: false, clientDirRelativeToProjectDir: clientDirRelativeToProjectDir); @@ -76,9 +70,17 @@ protected void BuildProject( result.EnsureSuccessful(); } - protected void PublishProject(string configuration, params string[] extraArgs) + protected void PublishProject( + string configuration, + RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded, + bool assertAppBundle = true, + params string[] extraArgs) { - (CommandResult result, _) = BlazorPublish(new BlazorBuildOptions(Id, configuration), extraArgs); + (CommandResult result, _) = BlazorPublish(new BlazorBuildOptions( + Id: Id, + Config: configuration, + RuntimeType: runtimeType, + AssertAppBundle: assertAppBundle), extraArgs); result.EnsureSuccessful(); } @@ -99,12 +101,11 @@ private async Task RunSdkStyleApp(RunOptions options, BlazorRunHost h 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]+)$)|(Program terminated with exit\\((?[0-9]+)\\))"); + Regex exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); BlazorRunOptions blazorRunOptions = new( CheckCounter: false, @@ -114,7 +115,8 @@ private async Task RunSdkStyleApp(RunOptions options, BlazorRunHost h OnServerMessage: OnServerMessage, BrowserPath: options.BrowserPath, QueryString: queryString, - Host: host); + Host: host, + ExtraArgs: options.ExtraArgs); await BlazorRunTest(blazorRunOptions); @@ -171,7 +173,8 @@ protected record RunOptions( Dictionary ServerEnvironment = null, Action OnConsoleMessage = null, Action OnServerMessage = null, - int? ExpectedExitCode = 0 + int? ExpectedExitCode = 0, + string? ExtraArgs = null ); protected record RunResult( diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTests.cs index 1bbe8691d80db..ee69b34819bbd 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTests.cs @@ -33,7 +33,7 @@ private void AssertDebugLevel(RunResult result, int value) [InlineData("Release")] public async Task BuildWithDefaultLevel(string configuration) { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_BuildWithDefaultLevel_{configuration}"); + CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_BuildWithDefaultLevel_{configuration}", "App"); BuildProject(configuration); var result = await RunSdkStyleAppForBuild(new( @@ -50,7 +50,7 @@ public async Task BuildWithDefaultLevel(string configuration) [InlineData("Release", 0)] public async Task BuildWithExplicitValue(string configuration, int debugLevel) { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_BuildWithExplicitValue_{configuration}"); + CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_BuildWithExplicitValue_{configuration}", "App"); BuildProject(configuration: configuration, extraArgs: $"-p:WasmDebugLevel={debugLevel}"); var result = await RunSdkStyleAppForBuild(new( @@ -65,7 +65,7 @@ public async Task BuildWithExplicitValue(string configuration, int debugLevel) [InlineData("Release")] public async Task PublishWithDefaultLevel(string configuration) { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_PublishWithDefaultLevel_{configuration}"); + CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_PublishWithDefaultLevel_{configuration}", "App"); PublishProject(configuration); var result = await RunSdkStyleAppForPublish(new( @@ -82,8 +82,8 @@ public async Task PublishWithDefaultLevel(string configuration) [InlineData("Release", -1)] public async Task PublishWithExplicitValue(string configuration, int debugLevel) { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_PublishWithExplicitValue_{configuration}"); - PublishProject(configuration, $"-p:WasmDebugLevel={debugLevel}"); + CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_PublishWithExplicitValue_{configuration}", "App"); + PublishProject(configuration, RuntimeVariant.SingleThreaded, assertAppBundle: true, $"-p:WasmDebugLevel={debugLevel}"); var result = await RunSdkStyleAppForPublish(new( Configuration: configuration, @@ -97,8 +97,8 @@ public async Task PublishWithExplicitValue(string configuration, int debugLevel) [InlineData("Release")] public async Task PublishWithDefaultLevelAndPdbs(string configuration) { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_PublishWithDefaultLevelAndPdbs_{configuration}"); - PublishProject(configuration, $"-p:CopyOutputSymbolsToPublishDirectory=true"); + CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_PublishWithDefaultLevelAndPdbs_{configuration}", "App"); + PublishProject(configuration, RuntimeVariant.SingleThreaded, assertAppBundle: true, $"-p:CopyOutputSymbolsToPublishDirectory=true"); var result = await RunSdkStyleAppForPublish(new( Configuration: configuration, diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs index cf16a0536a38d..038951e1822e6 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs @@ -23,7 +23,7 @@ public LazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [Fact] public async Task LoadLazyAssemblyBeforeItIsNeeded() { - CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests"); + CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests", "App"); PublishProject("Debug"); var result = await RunSdkStyleAppForPublish(new(Configuration: "Debug", TestScenario: "LazyLoadingTest")); @@ -33,7 +33,7 @@ public async Task LoadLazyAssemblyBeforeItIsNeeded() [Fact] public async Task FailOnMissingLazyAssembly() { - CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests"); + CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests", "App"); PublishProject("Debug"); var result = await RunSdkStyleAppForPublish(new( diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs index e985ad23d89a0..c4380ed755a59 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs @@ -26,7 +26,7 @@ public LibraryInitializerTests(ITestOutputHelper output, SharedBuildPerTestClass [Fact] public async Task LoadLibraryInitializer() { - CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_LoadLibraryInitializer"); + CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_LoadLibraryInitializer", "App"); PublishProject("Debug"); var result = await RunSdkStyleAppForPublish(new(Configuration: "Debug", TestScenario: "LibraryInitializerTest")); @@ -39,7 +39,7 @@ public async Task LoadLibraryInitializer() [Fact] public async Task AbortStartupOnError() { - CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_AbortStartupOnError"); + CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_AbortStartupOnError", "App"); PublishProject("Debug"); var result = await RunSdkStyleAppForPublish(new( diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs index 4dc20e7358aa5..532751a4ac534 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/ModuleConfigTests.cs @@ -25,7 +25,7 @@ public ModuleConfigTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur [InlineData(true)] public async Task DownloadProgressFinishes(bool failAssemblyDownload) { - CopyTestAsset("WasmBasicTestApp", $"ModuleConfigTests_DownloadProgressFinishes_{failAssemblyDownload}"); + CopyTestAsset("WasmBasicTestApp", $"ModuleConfigTests_DownloadProgressFinishes_{failAssemblyDownload}", "App"); PublishProject("Debug"); var result = await RunSdkStyleAppForPublish(new( @@ -58,7 +58,7 @@ public async Task DownloadProgressFinishes(bool failAssemblyDownload) [Fact] public async Task OutErrOverrideWorks() { - CopyTestAsset("WasmBasicTestApp", $"ModuleConfigTests_OutErrOverrideWorks"); + CopyTestAsset("WasmBasicTestApp", $"ModuleConfigTests_OutErrOverrideWorks", "App"); PublishProject("Debug"); var result = await RunSdkStyleAppForPublish(new( diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs index 2088e1522ad73..517f34255f996 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs @@ -26,7 +26,7 @@ public SatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFi [Fact] public async Task LoadSatelliteAssembly() { - CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTests"); + CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTests", "App"); BuildProject("Debug"); var result = await RunSdkStyleAppForBuild(new(Configuration: "Debug", TestScenario: "SatelliteAssembliesTest")); diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SignalRClientTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SignalRClientTests.cs deleted file mode 100644 index 1b09272b48793..0000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SignalRClientTests.cs +++ /dev/null @@ -1,101 +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; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Microsoft.Playwright; -using Xunit.Abstractions; -using Xunit; - -#nullable enable - -namespace Wasm.Build.Tests.TestAppScenarios; - -public class SignalRClientTests : AppTestBase -{ - public SignalRClientTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - [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 SignalRPassMessages(string config, string transport) - { - BlazorHostedBuild(config, - assetName: "BlazorHostedApp", - clientDirRelativeToProjectDir: "../BlazorHosted.Client", - generatedProjectNamePrefix: "SignalRClientTests", - runtimeType: RuntimeVariant.MultiThreaded); - - List consoleOutput = new(); - List serverOutput = new(); - - var result = await RunSdkStyleAppForBuild(new( - Configuration: config, - // We are using build (not publish), - // we need to instruct static web assets to use manifest file, - // because wwwroot in bin doesn't contain all files (for build) - ServerEnvironment: new Dictionary { ["ASPNETCORE_ENVIRONMENT"] = "Development" }, - BrowserPath: "/chat", - BrowserQueryString: new Dictionary { ["transport"] = transport, ["message"] = "ping" }, - OnServerMessage: (msg) => serverOutput.Add(msg), - OnConsoleMessage: async (page, msg) => - { - consoleOutput.Add(msg.Text); - if (msg.Text.Contains("TestOutput ->")) - _testOutput.WriteLine(msg.Text); - - // prevent timeouts with [Long Running Test] on error - if (msg.Text.ToLowerInvariant().Contains("error")) - { - Console.WriteLine(msg.Text); - Console.WriteLine(_testOutput); - throw new Exception(msg.Text); - } - - if (msg.Text.Contains("Finished GetQueryParameters")) - await SaveClickButtonAsync(page, "button#connectButton"); - - if (msg.Text.Contains("SignalR connected")) - await SaveClickButtonAsync(page, "button#subscribeButton"); - - if (msg.Text.Contains("Subscribed to ReceiveMessage")) - await SaveClickButtonAsync(page, "button#sendMessageButton"); - - if (msg.Text.Contains("ReceiveMessage from server")) - await SaveClickButtonAsync(page, "button#exitProgramButton"); - } - )); - - string output = _testOutput.ToString() ?? ""; - Assert.NotEmpty(output); - // check sending and receiving threadId - string threadIdUsedForSending = GetThreadOfAction(output, @"SignalRPassMessages was sent by CurrentManagedThreadId=(\d+)", "signalR message was sent"); - string threadIdUsedForReceiving = GetThreadOfAction(output, @"ReceiveMessage from server on CurrentManagedThreadId=(\d+)", "signalR message was received"); - Assert.True("1" != threadIdUsedForSending || "1" != threadIdUsedForReceiving, - $"Expected to send/receive with signalR in non-UI threads, instead only CurrentManagedThreadId=1 was used. TestOutput: {output}."); - } - - private string GetThreadOfAction(string testOutput, string pattern, string actionDescription) - { - Match match = Regex.Match(testOutput, pattern); - Assert.True(match.Success, $"Expected to find a log that {actionDescription}. TestOutput: {testOutput}."); - return match.Groups[1].Value ?? ""; - } - - private async Task SaveClickButtonAsync(IPage page, string selector) - { - await page.WaitForSelectorAsync(selector); - await page.ClickAsync(selector); - } -} diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Helper.cs b/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Helper.cs deleted file mode 100644 index 38ead1438099f..0000000000000 --- a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Helper.cs +++ /dev/null @@ -1,42 +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.Collections.Specialized; -using Microsoft.AspNetCore.Http.Connections; - -namespace BlazorHosted.Client; - -public static class Helper -{ - public static string GetValue(NameValueCollection parameters, string key) - { - var values = parameters.GetValues(key); - if (values == null || values.Length == 0) - { - throw new Exception($"Parameter '{key}' is required in the query string"); - } - if (values.Length > 1) - { - throw new Exception($"Parameter '{key}' should be unique in the query string"); - } - return values[0]; - } - - public static HttpTransportType StringToTransportType(string transport) - { - switch (transport.ToLowerInvariant()) - { - case "longpolling": - return HttpTransportType.LongPolling; - case "websockets": - return HttpTransportType.WebSockets; - default: - throw new Exception($"{transport} is invalid transport type"); - } - } - - public static void TestOutputWriteLine(string message) - { - Console.WriteLine("TestOutput -> " + message); - } -} diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Pages/Chat.razor b/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Pages/Chat.razor deleted file mode 100644 index f90aa96c87b92..0000000000000 --- a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Pages/Chat.razor +++ /dev/null @@ -1,116 +0,0 @@ -@page "/chat" -@using Microsoft.AspNetCore.SignalR -@using Microsoft.AspNetCore.SignalR.Client -@using Microsoft.AspNetCore.Http.Connections; -@using System.Web; -@inject NavigationManager NavigationManager -@inject IJSRuntime JSRuntime - -

Chat Room

- - - - - -
- @foreach (var chatMessage in chatMessages) - { -

@chatMessage

- } -
- -@code { - private string _hubUrl = string.Empty; - private HubConnection? _hubConnection; - private string message = string.Empty; - private string transport = string.Empty; - private List chatMessages = new List(); - private string wrongQueryError = "Query string with parameters 'message' and 'transport' are required"; - - // remove when https://github.com/dotnet/runtime/issues/96546 is fixed - // log that rendering is about to start in case we hit the issue before OnAfterRender is called - protected override bool ShouldRender() - { - bool shouldRender = base.ShouldRender(); - Helper.TestOutputWriteLine($"ShouldRender = {shouldRender}"); - return shouldRender; - } - - protected override void OnAfterRender(bool firstRender) - { - if (firstRender) - { - Helper.TestOutputWriteLine($"OnAfterRender on CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); - GetQueryParameters(); - } - base.OnAfterRender(firstRender); - } - - private void GetQueryParameters() - { - var uri = new Uri(NavigationManager.Uri); - if (string.IsNullOrEmpty(uri.Query)) - { - throw new Exception(wrongQueryError); - } - var parameters = HttpUtility.ParseQueryString(uri.Query); - if (parameters == null) - { - throw new Exception(wrongQueryError); - } - transport = Helper.GetValue(parameters, "transport"); - message = $"{transport} {Helper.GetValue(parameters, "message")}" ; - Helper.TestOutputWriteLine($"Finished GetQueryParameters on CurrentManagedThreadId={Environment.CurrentManagedThreadId}."); - } - - private async Task Connect() - { - _hubUrl = NavigationManager.BaseUri + "chathub"; - HttpTransportType httpTransportType = Helper.StringToTransportType(transport); - _hubConnection = new HubConnectionBuilder() - .WithUrl(_hubUrl, options => - { - options.Transports = httpTransportType; - }) - .Build(); - - await _hubConnection.StartAsync(); - Helper.TestOutputWriteLine($"SignalR connected by CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); - } - - private void Subscribe() - { - _hubConnection.On("ReceiveMessage", (message) => - { - Helper.TestOutputWriteLine($"Message = [{message}]. ReceiveMessage from server on CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); - chatMessages.Add(message); - }); - Helper.TestOutputWriteLine($"Subscribed to ReceiveMessage by CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); - } - - private async Task SignalRPassMessages() => - await Task.Run(async () => - { - await _hubConnection.SendAsync( "SendMessage", message, Environment.CurrentManagedThreadId); - Helper.TestOutputWriteLine($"SignalRPassMessages was sent by CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); - }); - - private async Task SendExitSignal() - { - await DisposeHubConnection(); - // exit the client - Helper.TestOutputWriteLine($"SendExitSignal by CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); - await JSRuntime.InvokeVoidAsync("eval", "setTimeout(() => { getDotnetRuntime(0).exit(0); }, 50);"); - } - - private async Task DisposeHubConnection() - { - if (_hubConnection != null) - { - _hubConnection.Remove("ReceiveMessage"); - await _hubConnection.DisposeAsync(); - _hubConnection = null; - } - Helper.TestOutputWriteLine($"SignalR disconnected by CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); - } -} diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/wwwroot/favicon.ico b/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/wwwroot/favicon.ico deleted file mode 100644 index 63e859b476eff5055e0e557aaa151ca8223fbeef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5430 zcmc&&Yj2xp8Fqnv;>&(QB_ve7>^E#o2mu=cO~A%R>DU-_hfbSRv1t;m7zJ_AMrntN zy0+^f&8be>q&YYzH%(88lQ?#KwiCzaCO*ZEo%j&v;<}&Lj_stKTKK>#U3nin@AF>w zb3ONSAFR{u(S1d?cdw53y}Gt1b-Hirbh;;bm(Rcbnoc*%@jiaXM|4jU^1WO~`TYZ~ zC-~jh9~b-f?fX`DmwvcguQzn*uV}c^Vd&~?H|RUs4Epv~gTAfR(B0lT&?RWQOtduM z^1vUD9{HQsW!{a9|0crA34m7Z6lpG^}f6f?={zD+ zXAzk^i^aKN_}s2$eX81wjSMONE#WVdzf|MT)Ap*}Vsn!XbvsI#6o&ij{87^d%$|A{ z=F{KB%)g%@z76yBzbb7seW**Ju8r4e*Z3PWNX3_tTDgzZatz7)Q6ytwB%@&@A|XT; zecM`Snxx5po$C)%yCP!KEtos~eOS)@2=kX-RIm)4glMCoagTEFxrBeSX%Euz734Fk z%7)x(k~T!@Hbg_37NSQL!vlTBXoURSzt~I**Zw`&F24fH*&kx=%nvZv|49SC*daD( zIw<~%#=lk8{2-l(BcIjy^Q$Q&m#KlWL9?UG{b8@qhlD z;umc+6p%|NsAT~0@DgV4-NKgQuWPWrmPIK&&XhV&n%`{l zOl^bbWYjQNuVXTXESO)@|iUKVmErPUDfz2Wh`4dF@OFiaCW|d`3paV^@|r^8T_ZxM)Z+$p5qx# z#K=z@%;aBPO=C4JNNGqVv6@UGolIz;KZsAro``Rz8X%vq_gpi^qEV&evgHb_=Y9-l z`)imdx0UC>GWZYj)3+3aKh?zVb}=@%oNzg7a8%kfVl)SV-Amp1Okw&+hEZ3|v(k8vRjXW9?ih`&FFM zV$~{j3IzhtcXk?Mu_!12;=+I7XK-IR2>Yd%VB^?oI9c^E&Chb&&je$NV0P-R;ujkP z;cbLCCPEF6|22NDj=S`F^2e~XwT1ZnRX8ra0#DaFa9-X|8(xNW_+JhD75WnSd7cxo z2>I_J5{c|WPfrgl7E2R)^c}F7ry()Z>$Jhk9CzZxiPKL#_0%`&{MX>P_%b~Dx0D^S z7xP1(DQ!d_Icpk!RN3I1w@~|O1ru#CO==h#9M~S4Chx*@?=EKUPGBv$tmU+7Zs_al z`!jR?6T&Z7(%uVq>#yLu`abWk!FBlnY{RFNHlj~6zh*;@u}+}viRKsD`IIxN#R-X3 z@vxu#EA_m}I503U(8Qmx^}u;)KfGP`O9E1H1Q|xeeksX8jC%@!{YT1)!lWgO=+Y3*jr=iSxvOW1}^HSy=y){tOMQJ@an>sOl4FYniE z;GOxd7AqxZNbYFNqobpv&HVO$c-w!Y*6r;$2oJ~h(a#(Bp<-)dg*mNigX~9rPqcHv z^;c*|Md?tD)$y?6FO$DWl$jUGV`F1G_^E&E>sY*YnA~ruv3=z9F8&&~Xpm<<75?N3 z>x~`I&M9q)O1=zWZHN9hZWx>RQ}zLP+iL57Q)%&_^$Sme^^G7;e-P~CR?kqU#Io#( z(nH1Wn*Ig)|M>WLGrxoU?FZrS`4GO&w;+39A3f8w{{Q7eg|$+dIlNFPAe+tN=FOYU z{A&Fg|H73+w1IK(W=j*L>JQgz$g0 z7JpKXLHIh}#$wm|N`s}o-@|L_`>*(gTQ~)wr3Eap7g%PVNisKw82im;Gdv#85x#s+ zoqqtnwu4ycd>cOQgRh-=aEJbnvVK`}ja%+FZx}&ehtX)n(9nVfe4{mn0bgijUbNr7Tf5X^$*{qh2%`?--%+sbSrjE^;1e3>% zqa%jdY16{Y)a1hSy*mr0JGU05Z%=qlx5vGvTjSpTt6k%nR06q}1DU`SQh_ZAeJ}A@`hL~xvv05U?0%=spP`R>dk?cOWM9^KNb7B?xjex>OZo%JMQQ1Q zB|q@}8RiP@DWn-(fB;phPaIOP2Yp)XN3-Fsn)S3w($4&+p8f5W_f%gac}QvmkHfCj$2=!t`boCvQ zCW;&Dto=f8v##}dy^wg3VNaBy&kCe3N;1|@n@pUaMPT?(aJ9b*(gJ28$}(2qFt$H~u5z94xcIQkcOI++)*exzbrk?WOOOf*|%k5#KV zL=&ky3)Eirv$wbRJ2F2s_ILQY--D~~7>^f}W|Aw^e7inXr#WLI{@h`0|jHud2Y~cI~Yn{r_kU^Vo{1gja -{ - options.KeepAliveInterval = TimeSpan.Zero; // minimize keep-alive messages -}); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseWebAssemblyDebugging(); -} -else -{ - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); -} - -// Add headers to enable SharedArrayBuffer -app.Use(async (context, next) => -{ - var response = context.Response; - response.Headers.Append("Cross-Origin-Opener-Policy", "same-origin"); - response.Headers.Append("Cross-Origin-Embedder-Policy", "require-corp"); - - await next(); -}); -app.UseBlazorFrameworkFiles(); -app.UseStaticFiles(); - -app.UseRouting(); - -app.MapRazorPages(); -app.MapControllers(); -app.MapFallbackToFile("index.html"); - -app.MapHub("/chathub"); - -app.Run(); diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/appsettings.json b/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/appsettings.json deleted file mode 100644 index 75b7c2aa1eced..0000000000000 --- a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/appsettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} \ No newline at end of file diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/BlazorHosted.Server.csproj b/src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/AspNetCoreServer.csproj similarity index 63% rename from src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/BlazorHosted.Server.csproj rename to src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/AspNetCoreServer.csproj index db0b51cd37008..9b556e8a965da 100644 --- a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/BlazorHosted.Server.csproj +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/AspNetCoreServer.csproj @@ -4,6 +4,7 @@ net9.0 enable enable + true CA2007 @@ -13,7 +14,9 @@ - + + + diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/ChatHub.cs b/src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/ChatHub.cs similarity index 93% rename from src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/ChatHub.cs rename to src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/ChatHub.cs index 8b2e77807c6fb..2a1267d102c2f 100644 --- a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Server/ChatHub.cs +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/ChatHub.cs @@ -3,7 +3,8 @@ using Microsoft.AspNetCore.SignalR; -namespace BlazorHosted.Server.Hubs; +namespace Server; + public class ChatHub : Hub { public async Task SendMessage(string message, int sendingThreadId) diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/Program.cs b/src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/Program.cs new file mode 100644 index 0000000000000..b22b8ded516a0 --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/AspNetCoreServer/Program.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http.Connections; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.FileProviders; +using Microsoft.AspNetCore.StaticFiles; +using Server; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddSignalR(); +var app = builder.Build(); + +// Add headers to enable SharedArrayBuffer +app.Use(async (context, next) => +{ + var response = context.Response; + response.Headers.Append("Cross-Origin-Opener-Policy", "same-origin"); + response.Headers.Append("Cross-Origin-Embedder-Policy", "require-corp"); + + await next(); +}); + +app.UseDefaultFiles(); + +var provider = new FileExtensionContentTypeProvider(); +provider.Mappings[".dll"] = "application/octet-stream"; +provider.Mappings[".pdb"] = "application/octet-stream"; +provider.Mappings[".dat"] = "application/octet-stream"; +app.UseStaticFiles(new StaticFileOptions +{ + ContentTypeProvider = provider, +}); + +ConfigureClientApp(app, "wasmclient"); +ConfigureClientApp(app, "blazorclient"); + +app.Run(); + + +static void ConfigureClientApp(WebApplication app, string clientAppPath) +{ + app.MapWhen( + ctx => ctx.Request.Path.StartsWithSegments($"/{clientAppPath}", out var rest), + clientApp => + { + clientApp + .UseBlazorFrameworkFiles($"/{clientAppPath}") + .UsePathBase($"/{clientAppPath}") + .UseRouting() + .UseEndpoints(endpoints => + { + endpoints.MapHub("/chathub"); + endpoints.MapFallbackToFile($"{clientAppPath}/index.html"); + }); + } + ); +} diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/App.razor b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/App.razor similarity index 100% rename from src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/App.razor rename to src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/App.razor diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/BlazorHosted.Client.csproj b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/BlazorClient.csproj similarity index 67% rename from src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/BlazorHosted.Client.csproj rename to src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/BlazorClient.csproj index 237c5cf2d75ac..5c974a459386c 100644 --- a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/BlazorHosted.Client.csproj +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/BlazorClient.csproj @@ -7,13 +7,16 @@ true CS8604;CS4014 + blazorclient - - - - + + + + + + diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Layout/MainLayout.razor b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/Layout/MainLayout.razor similarity index 100% rename from src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Layout/MainLayout.razor rename to src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/Layout/MainLayout.razor diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/Pages/Chat.razor b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/Pages/Chat.razor new file mode 100644 index 0000000000000..6009840f50221 --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/Pages/Chat.razor @@ -0,0 +1,34 @@ +@page "/" +@inject NavigationManager NavigationManager + +

Chat Room

+@code { + private SignalRTest signalRTest = new(); + + // remove when https://github.com/dotnet/runtime/issues/96546 is fixed + // log that rendering is about to start in case we hit the issue before OnAfterRender is called + protected override bool ShouldRender() + { + bool shouldRender = base.ShouldRender(); + TestOutput.WriteLine($"ShouldRender = {shouldRender}"); + return shouldRender; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + TestOutput.WriteLine($"SignalRTest is started on CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); + try + { + int result = await signalRTest.Run(NavigationManager.BaseUri, NavigationManager.Uri); + TestOutput.WriteLine($"SignalRTest finished with code {result}. WASM EXIT {result}"); + } + catch (Exception ex) + { + TestOutput.WriteLine($"SignalRTest failed with exception {ex}. WASM EXIT -1"); + } + } + base.OnAfterRenderAsync(firstRender); + } +} diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Program.cs b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/Program.cs similarity index 95% rename from src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Program.cs rename to src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/Program.cs index 67a2fb06d6a1e..bcc0dff43d986 100644 --- a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/Program.cs +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/Program.cs @@ -1,7 +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 BlazorHosted.Client; +using BlazorClient; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/_Imports.razor b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/_Imports.razor similarity index 72% rename from src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/_Imports.razor rename to src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/_Imports.razor index d39afd384f899..1c193c143df2c 100644 --- a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/_Imports.razor +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/_Imports.razor @@ -2,5 +2,6 @@ @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web @using Microsoft.JSInterop -@using BlazorHosted.Client -@using BlazorHosted.Client.Layout \ No newline at end of file +@using BlazorClient +@using BlazorClient.Layout +@using Shared \ No newline at end of file diff --git a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/wwwroot/index.html b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/wwwroot/index.html similarity index 62% rename from src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/wwwroot/index.html rename to src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/wwwroot/index.html index 56dd2027fdf83..f911682fdf48b 100644 --- a/src/mono/wasm/testassets/BlazorHostedApp/BlazorHosted.Client/wwwroot/index.html +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/BlazorClient/wwwroot/index.html @@ -5,7 +5,7 @@ BlazorHosted - + @@ -16,14 +16,7 @@ Reload 🗙 - - + diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/QueryParser.cs b/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/QueryParser.cs new file mode 100644 index 0000000000000..b863fa4ed5491 --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/QueryParser.cs @@ -0,0 +1,24 @@ +// 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.Specialized; + +namespace Shared; + +public static class QueryParser +{ + public static string GetValue(NameValueCollection parameters, string key) + { + var values = parameters.GetValues(key); + if (values == null || values.Length == 0) + { + throw new Exception($"Parameter '{key}' is required in the query string"); + } + if (values.Length > 1) + { + throw new Exception($"Parameter '{key}' should be unique in the query string"); + } + return values[0]; + } +} diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/Shared.csproj b/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/Shared.csproj new file mode 100644 index 0000000000000..5980a8781983a --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/Shared.csproj @@ -0,0 +1,19 @@ + + + + net9.0 + Library + true + enable + CA2007 + + + + + + + + + + + diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/SignalRTest.cs b/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/SignalRTest.cs new file mode 100644 index 0000000000000..5b4253646413b --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/SignalRTest.cs @@ -0,0 +1,114 @@ +// 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.Specialized; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Connections; +using Microsoft.AspNetCore.SignalR.Client; +using System.Web; + +namespace Shared; + +public class SignalRTest +{ + private TaskCompletionSource? tcs; + private HubConnection? _hubConnection; + private string transport = string.Empty; + private string message = string.Empty; + private string wrongQueryError = "Query string with parameters 'message' and 'transport' is required"; + + public async Task Run(string origin, string fullUrl) + { + tcs = new TaskCompletionSource(); + GetQueryParameters(fullUrl); + await Connect(origin); + await SignalRPassMessages(); + + int delayInMin = 2; + await Task.WhenAny( + tcs!.Task, + Task.Delay(TimeSpan.FromMinutes(delayInMin))); + + if (!tcs!.Task.IsCompleted) + throw new TimeoutException($"Test timed out after waiting {delayInMin} minutes for process to exit."); + return tcs.Task.Result; + + } + + private void SetResult(int value) => tcs?.SetResult(value); + + private void GetQueryParameters(string url) + { + var uri = new Uri(url); + if (string.IsNullOrEmpty(uri.Query)) + { + throw new Exception(wrongQueryError); + } + var parameters = HttpUtility.ParseQueryString(uri.Query); + if (parameters == null) + { + throw new Exception(wrongQueryError); + } + transport = QueryParser.GetValue(parameters, "transport"); + message = $"{transport} {QueryParser.GetValue(parameters, "message")}" ; + TestOutput.WriteLine($"Finished GetQueryParameters on CurrentManagedThreadId={Environment.CurrentManagedThreadId}."); + } + + private async Task Connect(string baseUri) + { + string hubUrl = new Uri(new Uri(baseUri), "chathub").ToString(); + Console.WriteLine($"hubUrl: {hubUrl}"); + HttpTransportType httpTransportType = StringToTransportType(transport); + _hubConnection = new HubConnectionBuilder() + .WithUrl(hubUrl, options => + { + options.Transports = httpTransportType; + }) + .Build(); + + _hubConnection.On("ReceiveMessage", async (message) => + { + TestOutput.WriteLine($"Message = [{message}]. ReceiveMessage from server on CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); + await DisposeHubConnection(); + SetResult(0); + }); + TestOutput.WriteLine($"Subscribed to ReceiveMessage by CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); + + await _hubConnection.StartAsync(); + TestOutput.WriteLine($"SignalR connected by CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); + } + + private static HttpTransportType StringToTransportType(string transport) + { + switch (transport.ToLowerInvariant()) + { + case "longpolling": + return HttpTransportType.LongPolling; + case "websockets": + return HttpTransportType.WebSockets; + default: + throw new Exception($"{transport} is invalid transport type"); + } + } + + private async Task SignalRPassMessages() => + await Task.Run(async () => + { + if (_hubConnection == null) + throw new Exception("Cannot send messages before establishing hub connection"); + await _hubConnection!.SendAsync("SendMessage", message, Environment.CurrentManagedThreadId); + TestOutput.WriteLine($"SignalRPassMessages was sent by CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); + }); + + private async Task DisposeHubConnection() + { + if (_hubConnection != null) + { + _hubConnection.Remove("ReceiveMessage"); + await _hubConnection.DisposeAsync(); + _hubConnection = null; + } + TestOutput.WriteLine($"SignalR disconnected by CurrentManagedThreadId={Environment.CurrentManagedThreadId}"); + } +} diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/TestOutput.cs b/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/TestOutput.cs new file mode 100644 index 0000000000000..3cb92a24277c2 --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/Shared/TestOutput.cs @@ -0,0 +1,20 @@ +// 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 Shared; + +public static class TestOutput +{ + public static void WriteLine(string message) + { + Console.WriteLine("TestOutput -> " + message); + } + + public static void WriteLine(object message) + { + Console.Write("TestOutput -> "); + Console.WriteLine(message); + } +} diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/Program.cs b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/Program.cs new file mode 100644 index 0000000000000..88bed62c36086 --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/Program.cs @@ -0,0 +1,21 @@ +// 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.Threading.Tasks; +using Shared; + +public partial class Program +{ + public static async Task Main(string[] args) + { + if (args.Length < 2) + throw new Exception("Expected url origin and href passed as arguments"); + + SignalRTest test = new(); + Console.WriteLine($"arg0: {args[0]}, arg1: {args[1]}"); + int result = await test.Run(origin: args[0], fullUrl: args[1]); + if (result != 0) + throw new Exception($"WasmBrowser finished with non-success code: {result}"); + } +} diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/TestOutput.cs b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/TestOutput.cs new file mode 100644 index 0000000000000..5755b753b5e49 --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/TestOutput.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +public static class TestOutput +{ + public static void WriteLine(string message) + { + Console.WriteLine("TestOutput -> " + message); + } + + public static void WriteLine(object message) + { + Console.Write("TestOutput -> "); + Console.WriteLine(message); + } +} diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/WasmBrowserClient.csproj b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/WasmBrowserClient.csproj new file mode 100644 index 0000000000000..a74e1246d8bdd --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/WasmBrowserClient.csproj @@ -0,0 +1,17 @@ + + + net9.0 + browser-wasm + Exe + true + enable + + CA1050;CA2007;CA1861;IL2104 + true + wasmclient + + + + + + diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/wwwroot/index.html b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/wwwroot/index.html new file mode 100644 index 0000000000000..86bc345d2e23a --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/wwwroot/index.html @@ -0,0 +1,17 @@ + + + + + + + WasmBrowser + + + + + + + + + + \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/wwwroot/main.js b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/wwwroot/main.js new file mode 100644 index 0000000000000..f182fd9eff991 --- /dev/null +++ b/src/mono/wasm/testassets/WasmOnAspNetCore/WasmBrowserClient/wwwroot/main.js @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { dotnet, exit } from './_framework/dotnet.js' + +try { + const dotnetRuntime = await dotnet + .withElementOnExit() + .withExitCodeLogging() + .withExitOnUnhandledError() + .create(); + const config = dotnetRuntime.getConfig(); + var url = window.location.origin + window.location.pathname; + await dotnetRuntime.runMainAndExit(config.mainAssemblyName, [url, window.location.href]); + +} +catch (err) { + exit(2, err); +} \ No newline at end of file