Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ Wasm.Build.Tests.SatelliteLoadingTests
Wasm.Build.Tests.WasmBuildAppTest
Wasm.Build.Tests.WasmRunOutOfAppBundleTests
Wasm.Build.Tests.WasmTemplateTests
Wasm.Build.Tests.MaxParallelDownloadsTests
Wasm.Build.Tests.MaxParallelDownloadsTests
Wasm.Build.Tests.LibraryInitializerTests
1 change: 1 addition & 0 deletions src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public abstract class BuildTestBase : IClassFixture<SharedBuildPerTestClassFixtu
public static bool IsNotUsingWorkloads => !s_buildEnv.IsWorkload;
public static bool IsWorkloadWithMultiThreadingForDefaultFramework => s_buildEnv.IsWorkloadWithMultiThreadingForDefaultFramework;
public static bool IsMonoRuntime => s_buildEnv.IsMonoRuntime;
public static bool IsCoreClrRuntime => s_buildEnv.IsCoreClrRuntime;
public static bool UseWebcil => s_buildEnv.UseWebcil;
public static string GetNuGetConfigPath()
=> Path.Combine(BuildEnvironment.TestDataPath, "nuget.config");
Expand Down
4 changes: 3 additions & 1 deletion src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class BuildEnvironment
public bool UseWebcil { get; init; }
public bool IsWorkloadWithMultiThreadingForDefaultFramework { get; init; }
public bool IsMonoRuntime { get; init; }
public bool IsCoreClrRuntime { get; init; }
public bool IsRunningOnCI => EnvironmentVariables.IsRunningOnCI;

public static readonly string RelativeTestAssetsPath = @"..\testassets\";
Expand Down Expand Up @@ -114,6 +115,7 @@ public BuildEnvironment()

UseWebcil = EnvironmentVariables.UseWebcil && EnvironmentVariables.RuntimeFlavor != "CoreCLR"; // TODO-WASM: CoreCLR support for Webcil https://github.com/dotnet/runtime/issues/120248
IsMonoRuntime = EnvironmentVariables.RuntimeFlavor == "Mono";
IsCoreClrRuntime = EnvironmentVariables.RuntimeFlavor == "CoreCLR";

if (EnvironmentVariables.BuiltNuGetsPath is null || !Directory.Exists(EnvironmentVariables.BuiltNuGetsPath))
throw new Exception($"Cannot find 'BUILT_NUGETS_PATH={EnvironmentVariables.BuiltNuGetsPath}'");
Expand Down Expand Up @@ -141,7 +143,7 @@ public BuildEnvironment()
EnvVars["WasmFingerprintAssets"] = "false";
}

if (EnvironmentVariables.RuntimeFlavor == "CoreCLR")
if (IsCoreClrRuntime)
{
EnvVars["WasmTestSupport"] = "true";
EnvVars["WasmTestExitOnUnhandledError"] = "true";
Expand Down
13 changes: 11 additions & 2 deletions src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,28 @@ public async Task LoadLibraryInitializer()
[GeneratedRegex("MONO_WASM: Failed to invoke 'onRuntimeConfigLoaded' on library initializer '../WasmBasicTestApp.[a-z0-9]+.lib.module.js': Error: Error thrown from library initializer")]
private static partial Regex AbortStartupOnErrorRegex { get; }

[GeneratedRegex("Aborting startup, reason: Error: Failed to invoke 'onRuntimeReady' on library initializer '(\\.\\./)?WasmBasicTestApp(\\.[a-z0-9]+)?\\.lib\\.module\\.js': Error thrown from library initializer")]
private static partial Regex AbortStartupOnErrorCoreClrRegex { get; }

[Fact, TestCategory("bundler-friendly")]
public async Task AbortStartupOnError()
{
bool isCoreClr = IsCoreClrRuntime;
string queryParam = isCoreClr ? "throwErrorOnReady" : "throwError";
Regex expectedRegex = isCoreClr ? AbortStartupOnErrorCoreClrRegex : AbortStartupOnErrorRegex;

Configuration config = Configuration.Debug;
ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LibraryInitializerTests_AbortStartupOnError");
PublishProject(info, config);

BrowserRunOptions options = new(
config,
TestScenario: "LibraryInitializerTest",
BrowserQueryString: new NameValueCollection { {"throwError", "true" } },
BrowserQueryString: new NameValueCollection { { queryParam, "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");
Assert.True(
result.ConsoleOutput.Any(m => expectedRegex.IsMatch(m)),
$"The library initializer test didn't emit expected error message.\nConsole output:\n{string.Join("\n", result.ConsoleOutput)}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ protected ProjectInfo CopyTestAsset(
""";
}

if (EnvironmentVariables.RuntimeFlavor == "CoreCLR")
if (s_buildEnv.IsCoreClrRuntime)
{
// TODO-WASM: https://github.com/dotnet/sdk/issues/51213
string versionSuffix = s_buildEnv.IsRunningOnCI ? "ci" : "dev";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export function onRuntimeConfigLoaded(config) {
}

export async function onRuntimeReady({ getAssemblyExports, getConfig }) {
if (params.get("throwErrorOnReady") === "true") {
throw new Error("Error thrown from library initializer");
}

const testCase = params.get("test");
if (testCase == "LibraryInitializerTest") {
const config = getConfig();
Expand Down
2 changes: 1 addition & 1 deletion src/native/libs/Common/JavaScript/loader/exit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export function exit(exitCode: number, reason: any): void {
}
}
}
if (!runtimeState.dotnetReady) {
if (runtimeState.creatingRuntime) {
dotnetLogger.info(`Aborting startup, reason: ${reason}`);
dotnetLoaderExports.abortStartup(reason);
}
Expand Down
38 changes: 26 additions & 12 deletions src/native/libs/Common/JavaScript/loader/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import type { JsModuleExports, EmscriptenModuleInternal } from "./types";

import { dotnetAssert, dotnetInternals, dotnetBrowserHostExports, Module } from "./cross-module";
import { dotnetAssert, dotnetInternals, dotnetBrowserHostExports, dotnetApi, Module } from "./cross-module";
import { exit, runtimeState } from "./exit";
import { createPromiseCompletionSource } from "./promise-completion-source";
import { getIcuResourceName } from "./icu";
Expand All @@ -14,6 +14,18 @@ import { validateEngineFeatures } from "./bootstrap";

const runMainPromiseController = createPromiseCompletionSource<number>();

async function callLibraryInitializers(modules: JsModuleExports[], resources: any[], methodName: string, args: any): Promise<void> {
await Promise.all(modules.map(async (module, i) => {
try {
await (module as any)[methodName]?.(args);
} catch (err) {
const name = (resources[i] as any).name || "unknown";
const message = err instanceof Error ? err.message : String(err);
throw new Error(`Failed to invoke '${methodName}' on library initializer '${name}': ${message}`, { cause: err });
}
}));
}

// WASM-TODO: downloadOnly - Blazor render mode auto pre-download. Really no start.
// WASM-TODO: debugLevel

Expand All @@ -31,10 +43,9 @@ export async function createRuntime(downloadOnly: boolean): Promise<any> {
}
validateLoaderConfig();

const modulesAfterConfigLoaded = await Promise.all((loaderConfig.resources.modulesAfterConfigLoaded || []).map(loadJSModule));
for (const afterConfigLoadedModule of modulesAfterConfigLoaded) {
await afterConfigLoadedModule.onRuntimeConfigLoaded?.(loaderConfig);
}
const afterConfigLoadedResources = loaderConfig.resources.modulesAfterConfigLoaded || [];
const modulesAfterConfigLoaded = await Promise.all(afterConfigLoadedResources.map(loadJSModule));
await callLibraryInitializers(modulesAfterConfigLoaded, afterConfigLoadedResources, "onRuntimeConfigLoaded", loaderConfig);

// after onConfigLoaded hooks, polyfills can be initialized
await initPolyfills();
Expand Down Expand Up @@ -66,7 +77,8 @@ export async function createRuntime(downloadOnly: boolean): Promise<any> {
const isDebuggingSupported = loaderConfig.debugLevel != 0;
const corePDBsPromise = isDebuggingSupported ? Promise.all((loaderConfig.resources.corePdb || []).map(fetchPdb)) : Promise.resolve([]);
const pdbsPromise = isDebuggingSupported ? Promise.all((loaderConfig.resources.pdb || []).map(fetchPdb)) : Promise.resolve([]);
const modulesAfterRuntimeReadyPromise = Promise.all((loaderConfig.resources.modulesAfterRuntimeReady || []).map(loadJSModule));
const afterRuntimeReadyResources = loaderConfig.resources.modulesAfterRuntimeReady || [];
const modulesAfterRuntimeReadyPromise = Promise.all(afterRuntimeReadyResources.map(loadJSModule));

const nativeModule = await nativeModulePromise;
const modulePromise = nativeModule.dotnetInitializeModule<EmscriptenModuleInternal>(dotnetInternals);
Expand Down Expand Up @@ -95,12 +107,14 @@ export async function createRuntime(downloadOnly: boolean): Promise<any> {

verifyAllAssetsDownloaded();

if (typeof Module.onDotnetReady === "function") {
await Module.onDotnetReady();
}
const modulesAfterRuntimeReady = await modulesAfterRuntimeReadyPromise;
for (const afterRuntimeReadyModule of modulesAfterRuntimeReady) {
await afterRuntimeReadyModule.onRuntimeReady?.(loaderConfig);
if (!downloadOnly) {
if (typeof Module.onDotnetReady === "function") {
await Module.onDotnetReady();
}
const modulesAfterRuntimeReady = await modulesAfterRuntimeReadyPromise;
const allRuntimeReadyModules = [...modulesAfterConfigLoaded, ...modulesAfterRuntimeReady];
const allRuntimeReadyResources = [...afterConfigLoadedResources, ...afterRuntimeReadyResources];
await callLibraryInitializers(allRuntimeReadyModules, allRuntimeReadyResources, "onRuntimeReady", dotnetApi);
}
runtimeState.creatingRuntime = false;
} catch (err) {
Expand Down
Loading