diff --git a/src/mono/wasm/Wasm.Build.Tests/AssertTestMainJsAppBundleOptions.cs b/src/mono/wasm/Wasm.Build.Tests/AssertTestMainJsAppBundleOptions.cs
new file mode 100644
index 0000000000000..125c5c6a709bd
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/AssertTestMainJsAppBundleOptions.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.
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public record AssertTestMainJsAppBundleOptions
+(
+ string BundleDir,
+ string ProjectName,
+ string Config,
+ string MainJS,
+ bool HasV8Script,
+ GlobalizationMode? GlobalizationMode,
+ string PredefinedIcudt = "",
+ bool UseWebcil = true,
+ bool IsBrowserProject = true,
+ bool IsPublish = false
+);
diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs
index 89ecb5b3c6a32..767099b21a2cb 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/AppsettingsTests.cs
@@ -54,4 +54,4 @@ await BlazorRunForBuildWithDotnetRun("debug", onConsoleMessage: msg =>
Assert.True(existsChecked, "File '/appsettings.json' wasn't found");
Assert.True(contentChecked, "Content of '/appsettings.json' is not matched");
}
-}
\ No newline at end of file
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs
index c94d1dda9fdf9..7ccb136d1dc29 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs
@@ -34,11 +34,11 @@ public void DefaultTemplate_WithoutWorkload(string config)
// Build
BlazorBuildInternal(id, config, publish: false);
- AssertBlazorBootJson(config, isPublish: false, isNet7AndBelow: false);
+ AssertBlazorBootJson(config, isPublish: false);
// Publish
BlazorBuildInternal(id, config, publish: true);
- AssertBlazorBootJson(config, isPublish: true, isNet7AndBelow: false);
+ AssertBlazorBootJson(config, isPublish: true);
}
[Theory]
diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs
index 9a094d9d10265..fd7a96b1e8cd3 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests.cs
@@ -39,8 +39,12 @@ public void NativeBuild_WithDeployOnBuild_UsedByVS(string config, bool nativeRel
var expectedFileType = nativeRelink ? NativeFilesType.Relinked : NativeFilesType.AOT;
- AssertDotNetNativeFiles(expectedFileType, config, forPublish: true, targetFramework: DefaultTargetFrameworkForBlazor);
- AssertBlazorBundle(config, isPublish: true, dotnetWasmFromRuntimePack: false);
+ AssertBlazorBundle(new BlazorBuildOptions
+ (
+ Id: id,
+ Config: config,
+ ExpectedFileType: expectedFileType
+ ), isPublish: true);
if (expectedFileType == NativeFilesType.AOT)
{
diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs
index 467a23597f777..d23fa9e0d7447 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/MiscTests2.cs
@@ -66,70 +66,4 @@ private CommandResult PublishForRequiresWorkloadTest(string config, string extra
$"-bl:{publishLogPath}",
$"-p:Configuration={config}");
}
-
- [Theory]
- [InlineData("Debug")]
- [InlineData("Release")]
- public void Net50Projects_NativeReference(string config)
- => BuildNet50Project(config, aot: false, expectError: true, @"");
-
- public static TheoryData Net50TestData = new()
- {
- { "Debug", /*aot*/ true, /*expectError*/ true },
- { "Debug", /*aot*/ false, /*expectError*/ false },
- { "Release", /*aot*/ true, /*expectError*/ true },
- { "Release", /*aot*/ false, /*expectError*/ false }
- };
-
- // FIXME: test for WasmBuildNative=true?
- [Theory]
- [MemberData(nameof(Net50TestData))]
- public void Net50Projects_AOT(string config, bool aot, bool expectError)
- => BuildNet50Project(config, aot: aot, expectError: expectError);
-
- private void BuildNet50Project(string config, bool aot, bool expectError, string? extraItems=null)
- {
- string id = $"Blazor_net50_{config}_{aot}_{Path.GetRandomFileName()}";
- InitBlazorWasmProjectDir(id);
-
- string directoryBuildTargets = @"
-
-
-
- ";
-
- File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.props"), "");
- File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.targets"), directoryBuildTargets);
-
- string logPath = Path.Combine(s_buildEnv.LogRootPath, id);
- Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "Blazor_net50"), Path.Combine(_projectDir!));
-
- string projectFile = Path.Combine(_projectDir!, "Blazor_net50.csproj");
- AddItemsPropertiesToProject(projectFile, extraItems: extraItems);
-
- string publishLogPath = Path.Combine(logPath, $"{id}.binlog");
- CommandResult result = new DotNetCommand(s_buildEnv, _testOutput)
- .WithWorkingDirectory(_projectDir!)
- .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir)
- .ExecuteWithCapturedOutput("publish",
- $"-bl:{publishLogPath}",
- (aot ? "-p:RunAOTCompilation=true" : ""),
- $"-p:Configuration={config}");
-
- if (expectError)
- {
- result.EnsureExitCode(1);
- Assert.Contains("are only supported for projects targeting net6.0+", result.Output);
- }
- else
- {
- result.EnsureSuccessful();
- Assert.Contains("** UsingBrowserRuntimeWorkload: 'false'", result.Output);
-
- string binFrameworkDir = FindBlazorBinFrameworkDir(config, forPublish: true, framework: "net5.0");
- AssertBlazorBootJson(config, isPublish: true, isNet7AndBelow: true, binFrameworkDir: binFrameworkDir);
- // dotnet.wasm here would be from 5.0 nuget like:
- // /Users/radical/.nuget/packages/microsoft.netcore.app.runtime.browser-wasm/5.0.9/runtimes/browser-wasm/native/dotnet.wasm
- }
- }
}
diff --git a/src/mono/wasm/Wasm.Build.Tests/BlazorWasmProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/BlazorWasmProjectProvider.cs
new file mode 100644
index 0000000000000..7432a78eed603
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/BlazorWasmProjectProvider.cs
@@ -0,0 +1,88 @@
+// 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.Data;
+using System.IO;
+using System.Linq;
+using Xunit;
+using Xunit.Abstractions;
+using System.Runtime.Serialization.Json;
+using Microsoft.NET.Sdk.WebAssembly;
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public class BlazorWasmProjectProvider(string projectDir, ITestOutputHelper testOutput)
+ : WasmSdkBasedProjectProvider(projectDir, testOutput)
+{
+ public void AssertBlazorBootJson(
+ string binFrameworkDir,
+ bool expectFingerprintOnDotnetJs = false,
+ bool isPublish = false,
+ RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded)
+ {
+ string bootJsonPath = Path.Combine(binFrameworkDir, "blazor.boot.json");
+ Assert.True(File.Exists(bootJsonPath), $"Expected to find {bootJsonPath}");
+
+ BootJsonData bootJson = ParseBootData(bootJsonPath);
+ var bootJsonEntries = bootJson.resources.runtime.Keys.Where(k => k.StartsWith("dotnet.", StringComparison.Ordinal)).ToArray();
+
+ var expectedEntries = new SortedDictionary>();
+ IReadOnlySet expected = GetDotNetFilesExpectedSet(runtimeType, isPublish);
+
+ var knownSet = GetAllKnownDotnetFilesToFingerprintMap(runtimeType);
+ foreach (string expectedFilename in expected)
+ {
+ if (Path.GetExtension(expectedFilename) == ".map")
+ continue;
+
+ bool expectFingerprint = knownSet[expectedFilename];
+ expectedEntries[expectedFilename] = item =>
+ {
+ string prefix = Path.GetFileNameWithoutExtension(expectedFilename);
+ string extension = Path.GetExtension(expectedFilename).Substring(1);
+
+ if (ShouldCheckFingerprint(expectedFilename: expectedFilename,
+ expectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs,
+ expectFingerprintForThisFile: expectFingerprint))
+ {
+ Assert.Matches($"{prefix}{s_dotnetVersionHashRegex}{extension}", item);
+ }
+ else
+ {
+ Assert.Equal(expectedFilename, item);
+ }
+
+ string absolutePath = Path.Combine(binFrameworkDir, item);
+ Assert.True(File.Exists(absolutePath), $"Expected to find '{absolutePath}'");
+ };
+ }
+ // FIXME: maybe use custom code so the details can show up in the log
+ Assert.Collection(bootJsonEntries.Order(), expectedEntries.Values.ToArray());
+ }
+
+ public static BootJsonData ParseBootData(string bootJsonPath)
+ {
+ using FileStream stream = File.OpenRead(bootJsonPath);
+ stream.Position = 0;
+ var serializer = new DataContractJsonSerializer(
+ typeof(BootJsonData),
+ new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true });
+
+ var config = (BootJsonData?)serializer.ReadObject(stream);
+ Assert.NotNull(config);
+ return config;
+ }
+
+ public string FindBlazorBinFrameworkDir(string config, bool forPublish, string framework)
+ {
+ string basePath = Path.Combine(ProjectDir, "bin", config, framework);
+ if (forPublish)
+ basePath = FindSubDirIgnoringCase(basePath, "publish");
+
+ return Path.Combine(basePath, "wwwroot", "_framework");
+ }
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.cs
new file mode 100644
index 0000000000000..77a4b2bee8ae6
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/BuildProjectOptions.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.Collections.Generic;
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public record BuildProjectOptions
+(
+ Action? InitProject = null,
+ bool? DotnetWasmFromRuntimePack = null,
+ GlobalizationMode? GlobalizationMode = null,
+ string? PredefinedIcudt = 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 = null,
+ string? MainJS = null,
+ bool IsBrowserProject = true,
+ IDictionary? ExtraBuildEnvironmentVariables = null
+);
diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs
index 433af91ec0f16..e9d15fe279483 100644
--- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs
@@ -10,7 +10,6 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
-using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Threading;
@@ -19,8 +18,6 @@
using Xunit.Abstractions;
using Xunit.Sdk;
using Microsoft.Playwright;
-using System.Runtime.Serialization.Json;
-using Microsoft.NET.Sdk.WebAssembly;
#nullable enable
@@ -101,21 +98,10 @@ public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture bu
{
_testIdx = Interlocked.Increment(ref s_testCounter);
_buildContext = buildContext;
- _testOutput = output;
+ _testOutput = new TestOutputWrapper(output);
_logPath = s_buildEnv.LogRootPath; // FIXME:
}
- /*
- * TODO:
- - AOT modes
- - llvmonly
- - aotinterp
- - skipped assemblies should get have their pinvoke/icall stuff scanned
-
- - only buildNative
- - aot but no wrapper - check that AppBundle wasn't generated
- */
-
public static IEnumerable> ConfigWithAOTData(bool aot, string? config = null, string? extraArgs = null)
{
if (extraArgs == null)
@@ -142,7 +128,6 @@ public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture bu
}
}
-
protected string RunAndTestWasmApp(BuildArgs buildArgs,
RunHost host,
string id,
@@ -361,6 +346,53 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp
return buildArgs with { ProjectFileContents = projectContents };
}
+ public (string projectDir, string buildOutput) BuildTemplateProject(BuildArgs buildArgs,
+ string id,
+ BuildProjectOptions buildProjectOptions,
+ AssertTestMainJsAppBundleOptions? assertAppBundleOptions = null)
+ {
+ StringBuilder buildCmdLine = new();
+ buildCmdLine.Append(buildProjectOptions.Publish ? "publish" : "build");
+
+ string logFilePath = Path.Combine(s_buildEnv.LogRootPath, $"{id}.binlog");
+ _testOutput.WriteLine($"-------- Building ---------");
+ _testOutput.WriteLine($"Binlog path: {logFilePath}");
+ buildCmdLine.Append($" -c {buildArgs.Config} -bl:{logFilePath} {buildArgs.ExtraBuildArgs}");
+
+ if (buildProjectOptions.Publish && buildProjectOptions.BuildOnlyAfterPublish)
+ buildCmdLine.Append(" -p:WasmBuildOnlyAfterPublish=true");
+
+ CommandResult res = new DotNetCommand(s_buildEnv, _testOutput)
+ .WithWorkingDirectory(_projectDir!)
+ .WithEnvironmentVariables(buildProjectOptions.ExtraBuildEnvironmentVariables)
+ .ExecuteWithCapturedOutput(buildCmdLine.ToString());
+ if (buildProjectOptions.ExpectSuccess)
+ res.EnsureSuccessful();
+ else
+ Assert.NotEqual(0, res.ExitCode);
+
+ if (buildProjectOptions.UseCache)
+ _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir!, logFilePath, true, res.Output));
+
+ AssertRuntimePackPath(res.Output, buildProjectOptions.TargetFramework ?? DefaultTargetFramework);
+ string bundleDir = Path.Combine(GetBinDir(config: buildArgs.Config, targetFramework: buildProjectOptions.TargetFramework ?? DefaultTargetFramework), "AppBundle");
+
+ assertAppBundleOptions ??= new AssertTestMainJsAppBundleOptions(
+ BundleDir: bundleDir,
+ ProjectName: buildArgs.ProjectName,
+ Config: buildArgs.Config,
+ MainJS: buildProjectOptions.MainJS ?? "test-main.js",
+ HasV8Script: buildProjectOptions.HasV8Script,
+ GlobalizationMode: buildProjectOptions.GlobalizationMode,
+ PredefinedIcudt: buildProjectOptions.PredefinedIcudt ?? "",
+ UseWebcil: UseWebcil,
+ IsBrowserProject: buildProjectOptions.IsBrowserProject,
+ IsPublish: buildProjectOptions.Publish);
+ AssertBasicAppBundle(assertAppBundleOptions);
+
+ return (_projectDir!, res.Output);
+ }
+
public (string projectDir, string buildOutput) BuildProject(BuildArgs buildArgs,
string id,
BuildProjectOptions options)
@@ -442,17 +474,17 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp
AssertRuntimePackPath(result.buildOutput, options.TargetFramework ?? DefaultTargetFramework);
string bundleDir = Path.Combine(GetBinDir(config: buildArgs.Config, targetFramework: options.TargetFramework ?? DefaultTargetFramework), "AppBundle");
- AssertBasicAppBundle(bundleDir,
- buildArgs.ProjectName,
- buildArgs.Config,
- options.MainJS ?? "test-main.js",
- options.HasV8Script,
- options.TargetFramework ?? DefaultTargetFramework,
- options.GlobalizationMode,
- options.PredefinedIcudt ?? "",
- options.DotnetWasmFromRuntimePack ?? !buildArgs.AOT,
- UseWebcil,
- options.IsBrowserProject);
+ AssertBasicAppBundle(new AssertTestMainJsAppBundleOptions(
+ BundleDir: bundleDir,
+ ProjectName: buildArgs.ProjectName,
+ Config: buildArgs.Config,
+ MainJS: options.MainJS ?? "test-main.js",
+ HasV8Script: options.HasV8Script,
+ GlobalizationMode: options.GlobalizationMode,
+ PredefinedIcudt: options.PredefinedIcudt ?? "",
+ UseWebcil: UseWebcil,
+ IsBrowserProject: options.IsBrowserProject,
+ IsPublish: options.Publish));
}
if (options.UseCache)
@@ -554,13 +586,7 @@ public string CreateBlazorWasmTemplateProject(string id)
extraArgs = extraArgs.Append("/warnaserror").ToArray();
var res = BlazorBuildInternal(options.Id, options.Config, publish: false, setWasmDevel: false, extraArgs);
- _testOutput.WriteLine($"BlazorBuild, options.tfm: {options.TargetFramework}");
- AssertDotNetNativeFiles(options.ExpectedFileType, options.Config, forPublish: false, targetFramework: options.TargetFramework);
- AssertBlazorBundle(options.Config,
- isPublish: false,
- dotnetWasmFromRuntimePack: options.ExpectedFileType == NativeFilesType.FromRuntimePack,
- targetFramework: options.TargetFramework,
- expectFingerprintOnDotnetJs: options.ExpectFingerprintOnDotnetJs);
+ AssertBlazorBundle(options, isPublish: false);
return res;
}
@@ -568,12 +594,7 @@ public string CreateBlazorWasmTemplateProject(string id)
protected (CommandResult, string) BlazorPublish(BlazorBuildOptions options, params string[] extraArgs)
{
var res = BlazorBuildInternal(options.Id, options.Config, publish: true, setWasmDevel: false, extraArgs);
- AssertDotNetNativeFiles(options.ExpectedFileType, options.Config, forPublish: true, targetFramework: options.TargetFramework);
- AssertBlazorBundle(options.Config,
- isPublish: true,
- dotnetWasmFromRuntimePack: options.ExpectedFileType == NativeFilesType.FromRuntimePack,
- targetFramework: options.TargetFramework,
- expectFingerprintOnDotnetJs: options.ExpectFingerprintOnDotnetJs);
+ AssertBlazorBundle(options, isPublish: true);
if (options.ExpectedFileType == NativeFilesType.AOT)
{
@@ -583,7 +604,6 @@ public string CreateBlazorWasmTemplateProject(string id)
// make sure this assembly gets skipped
Assert.DoesNotContain("Microsoft.JSInterop.WebAssembly.dll -> Microsoft.JSInterop.WebAssembly.dll.bc", res.Item1.Output);
-
}
string objBuildDir = Path.Combine(_projectDir!, "obj", options.Config, options.TargetFramework, "wasm", "for-build");
@@ -622,36 +642,48 @@ public string CreateBlazorWasmTemplateProject(string id)
return (res, logPath);
}
- protected void AssertDotNetNativeFiles(NativeFilesType type, string config, bool forPublish, string targetFramework)
+ private void AssertBlazorDotNetNativeFiles(
+ NativeFilesType type,
+ string config,
+ bool forPublish,
+ string targetFramework,
+ bool expectFingerprintOnDotnetJs,
+ RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded)
{
string label = forPublish ? "publish" : "build";
string objBuildDir = Path.Combine(_projectDir!, "obj", config, targetFramework, "wasm", forPublish ? "for-publish" : "for-build");
string binFrameworkDir = FindBlazorBinFrameworkDir(config, forPublish, framework: targetFramework);
- string srcDir = type switch
+ var dotnetFiles = new WasmSdkBasedProjectProvider(_projectDir!, _testOutput)
+ .FindAndAssertDotnetFiles(
+ dir: binFrameworkDir,
+ isPublish: forPublish,
+ expectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs,
+ runtimeType: runtimeType);
+
+ string runtimeNativeDir = s_buildEnv.GetRuntimeNativeDir(targetFramework, runtimeType);
+
+ string srcDirForNativeFileToCompareAgainst = type switch
{
- NativeFilesType.FromRuntimePack => s_buildEnv.GetRuntimeNativeDir(targetFramework),
+ NativeFilesType.FromRuntimePack => runtimeNativeDir,
NativeFilesType.Relinked => objBuildDir,
NativeFilesType.AOT => objBuildDir,
_ => throw new ArgumentOutOfRangeException(nameof(type))
};
-
- AssertSameFile(Path.Combine(srcDir, "dotnet.native.wasm"), Path.Combine(binFrameworkDir, "dotnet.native.wasm"), label);
-
- // find dotnet*js
- string? dotnetJsPath = Directory.EnumerateFiles(binFrameworkDir)
- .Where(p => Path.GetFileName(p).StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) &&
- Path.GetFileName(p).EndsWith(".js", StringComparison.OrdinalIgnoreCase))
- .SingleOrDefault();
-
- Assert.True(!string.IsNullOrEmpty(dotnetJsPath), $"[{label}] Expected to find dotnet.native*js in {binFrameworkDir}");
- AssertSameFile(Path.Combine(srcDir, "dotnet.native.js"), dotnetJsPath!, label);
-
- if (type != NativeFilesType.FromRuntimePack)
+ foreach (string nativeFilename in new[] { "dotnet.native.wasm", "dotnet.native.js" })
{
- // check that the files are *not* from runtime pack
- AssertNotSameFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.wasm"), Path.Combine(binFrameworkDir, "dotnet.native.wasm"), label);
- AssertNotSameFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.js"), dotnetJsPath!, label);
+ // For any *type*, check against the expected path
+ AssertSameFile(Path.Combine(srcDirForNativeFileToCompareAgainst, nativeFilename),
+ dotnetFiles[nativeFilename].ActualPath,
+ label);
+
+ if (type != NativeFilesType.FromRuntimePack)
+ {
+ // Confirm that it doesn't match the file from the runtime pack
+ AssertNotSameFile(Path.Combine(runtimeNativeDir, nativeFilename),
+ dotnetFiles[nativeFilename].ActualPath,
+ label);
+ }
}
}
@@ -667,44 +699,37 @@ static void AssertRuntimePackPath(string buildOutput, string targetFramework)
throw new XunitException($"Runtime pack path doesn't match.{Environment.NewLine}Expected: '{expectedRuntimePackDir}'{Environment.NewLine}Actual: '{actualPath}'");
}
- protected static void AssertBasicAppBundle(string bundleDir,
- string projectName,
- string config,
- string mainJS,
- bool hasV8Script,
- string targetFramework,
- GlobalizationMode? globalizationMode,
- string predefinedIcudt = "",
- bool dotnetWasmFromRuntimePack = true,
- bool useWebcil = true,
- bool isBrowserProject = true)
+ private void AssertBasicAppBundle(AssertTestMainJsAppBundleOptions options)
{
+ new TestMainJsProjectProvider(_projectDir!, _testOutput)
+ .FindAndAssertDotnetFiles(
+ Path.Combine(options.BundleDir, "_framework"),
+ isPublish: options.IsPublish,
+ expectFingerprintOnDotnetJs: false,
+ runtimeType: RuntimeVariant.SingleThreaded);
+
var filesToExist = new List()
{
- mainJS,
- "_framework/dotnet.native.wasm",
+ options.MainJS,
"_framework/blazor.boot.json",
- "_framework/dotnet.js",
"_framework/dotnet.js.map",
- "_framework/dotnet.native.js",
- "_framework/dotnet.runtime.js",
"_framework/dotnet.runtime.js.map",
};
- if (isBrowserProject)
+ if (options.IsBrowserProject)
filesToExist.Add("index.html");
- AssertFilesExist(bundleDir, filesToExist);
+ AssertFilesExist(options.BundleDir, filesToExist);
- AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script);
+ AssertFilesExist(options.BundleDir, new[] { "run-v8.sh" }, expectToExist: options.HasV8Script);
AssertIcuAssets();
- string managedDir = Path.Combine(bundleDir, "_framework");
+ string managedDir = Path.Combine(options.BundleDir, "_framework");
string bundledMainAppAssembly =
- useWebcil ? $"{projectName}{WebcilInWasmExtension}" : $"{projectName}.dll";
+ options.UseWebcil ? $"{options.ProjectName}{WebcilInWasmExtension}" : $"{options.ProjectName}.dll";
AssertFilesExist(managedDir, new[] { bundledMainAppAssembly });
- bool is_debug = config == "Debug";
+ bool is_debug = options.Config == "Debug";
if (is_debug)
{
// Use cecil to check embedded pdb?
@@ -718,8 +743,6 @@ protected static void AssertBasicAppBundle(string bundleDir,
//}
}
- AssertDotNetWasmJs(bundleDir, fromRuntimePack: dotnetWasmFromRuntimePack, targetFramework);
-
void AssertIcuAssets()
{
bool expectEFIGS = false;
@@ -727,7 +750,7 @@ void AssertIcuAssets()
bool expectNOCJK = false;
bool expectFULL = false;
bool expectHYBRID = false;
- switch (globalizationMode)
+ switch (options.GlobalizationMode)
{
case GlobalizationMode.Invariant:
break;
@@ -738,11 +761,11 @@ void AssertIcuAssets()
expectHYBRID = true;
break;
case GlobalizationMode.PredefinedIcu:
- if (string.IsNullOrEmpty(predefinedIcudt))
+ if (string.IsNullOrEmpty(options.PredefinedIcudt))
throw new ArgumentException("WasmBuildTest is invalid, value for predefinedIcudt is required when GlobalizationMode=PredefinedIcu.");
- AssertFilesExist(bundleDir, new[] { Path.Combine("_framework", predefinedIcudt) }, expectToExist: true);
+ AssertFilesExist(options.BundleDir, new[] { Path.Combine("_framework", options.PredefinedIcudt) }, expectToExist: true);
// predefined ICU name can be identical with the icu files from runtime pack
- switch (predefinedIcudt)
+ switch (options.PredefinedIcudt)
{
case "icudt.dat":
expectFULL = true;
@@ -766,7 +789,7 @@ void AssertIcuAssets()
break;
}
- var frameworkDir = Path.Combine(bundleDir, "_framework");
+ var frameworkDir = Path.Combine(options.BundleDir, "_framework");
AssertFilesExist(frameworkDir, new[] { "icudt.dat" }, expectToExist: expectFULL);
AssertFilesExist(frameworkDir, new[] { "icudt_EFIGS.dat" }, expectToExist: expectEFIGS);
AssertFilesExist(frameworkDir, new[] { "icudt_CJK.dat" }, expectToExist: expectCJK);
@@ -775,19 +798,6 @@ void AssertIcuAssets()
}
}
- protected static void AssertDotNetWasmJs(string bundleDir, bool fromRuntimePack, string targetFramework)
- {
- AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.wasm"),
- Path.Combine(bundleDir, "_framework/dotnet.native.wasm"),
- "Expected dotnet.native.wasm to be same as the runtime pack",
- same: fromRuntimePack);
-
- AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.js"),
- Path.Combine(bundleDir, "_framework/dotnet.native.js"),
- "Expected dotnet.native.js to be same as the runtime pack",
- same: fromRuntimePack);
- }
-
protected static void AssertDotNetJsSymbols(string bundleDir, bool fromRuntimePack, string targetFramework)
=> AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.js.symbols"),
Path.Combine(bundleDir, "_framework/dotnet.native.js.symbols"),
@@ -841,109 +851,45 @@ protected static void AssertFile(string file0, string file1, string? label = nul
return result;
}
- protected void AssertBlazorBundle(string config, bool isPublish, bool dotnetWasmFromRuntimePack, string targetFramework = DefaultTargetFrameworkForBlazor, string? binFrameworkDir = null, bool expectFingerprintOnDotnetJs = false)
- {
- binFrameworkDir ??= FindBlazorBinFrameworkDir(config, isPublish, targetFramework);
-
- AssertBlazorBootJson(config, isPublish, targetFramework != DefaultTargetFrameworkForBlazor, targetFramework, binFrameworkDir: binFrameworkDir);
- AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.wasm"),
- Path.Combine(binFrameworkDir, "dotnet.native.wasm"),
- "Expected dotnet.native.wasm to be same as the runtime pack",
- same: dotnetWasmFromRuntimePack);
-
- string? dotnetJsPath = Directory.EnumerateFiles(binFrameworkDir, "dotnet.native.*.js").FirstOrDefault();
- Assert.True(dotnetJsPath != null, $"Could not find blazor's dotnet*js in {binFrameworkDir}");
-
- AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.js"),
- dotnetJsPath!,
- "Expected dotnet.native.js to be same as the runtime pack",
- same: dotnetWasmFromRuntimePack);
-
- string bootConfigPath = Path.Combine(binFrameworkDir, "blazor.boot.json");
- Assert.True(File.Exists(bootConfigPath), $"Expected to find '{bootConfigPath}'");
-
- using (var bootConfigContent = File.OpenRead(bootConfigPath))
- {
- var bootConfig = ParseBootData(bootConfigContent);
- var dotnetJsEntries = bootConfig.resources.runtime.Keys.Where(k => k.StartsWith("dotnet.") && k.EndsWith(".js")).ToArray();
-
- void AssertFileExists(string fileName)
- {
- string absolutePath = Path.Combine(binFrameworkDir, fileName);
- Assert.True(File.Exists(absolutePath), $"Expected to find '{absolutePath}'");
- }
-
- string versionHashRegex = @"\.(?.+)\.(?[a-zA-Z0-9]+)\.";
-
- Assert.Collection(
- dotnetJsEntries.OrderBy(f => f),
- item =>
- {
- if (expectFingerprintOnDotnetJs)
- Assert.Matches($"dotnet{versionHashRegex}js", item);
- else
- Assert.Equal("dotnet.js", item);
-
- AssertFileExists(item);
- },
- item => { Assert.Matches($"dotnet\\.native{versionHashRegex}js", item); AssertFileExists(item); },
- item => { Assert.Matches($"dotnet\\.runtime{versionHashRegex}js", item); AssertFileExists(item); }
- );
- }
- }
-
- private static BootJsonData ParseBootData(Stream stream)
- {
- stream.Position = 0;
- var serializer = new DataContractJsonSerializer(
- typeof(BootJsonData),
- new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true });
-
- var config = (BootJsonData?)serializer.ReadObject(stream);
- Assert.NotNull(config);
- return config;
- }
-
- protected void AssertBlazorBootJson(string config, bool isPublish, bool isNet7AndBelow, string targetFramework = DefaultTargetFrameworkForBlazor, string? binFrameworkDir = null)
+ protected void AssertBlazorBundle(
+ BlazorBuildOptions options,
+ bool isPublish,
+ string? binFrameworkDir = null)
{
- binFrameworkDir ??= FindBlazorBinFrameworkDir(config, isPublish, targetFramework);
-
- string bootJsonPath = Path.Combine(binFrameworkDir, "blazor.boot.json");
- Assert.True(File.Exists(bootJsonPath), $"Expected to find {bootJsonPath}");
-
- string bootJson = File.ReadAllText(bootJsonPath);
- var bootJsonNode = JsonNode.Parse(bootJson);
- var runtimeObj = bootJsonNode?["resources"]?["runtime"]?.AsObject();
- Assert.NotNull(runtimeObj);
-
- string msgPrefix = $"[{(isPublish ? "publish" : "build")}]";
- Assert.True(runtimeObj!.Where(kvp => kvp.Key == (isNet7AndBelow ? "dotnet.wasm" : "dotnet.native.wasm")).Any(), $"{msgPrefix} Could not find dotnet.native.wasm entry in blazor.boot.json");
- Assert.True(runtimeObj!.Where(kvp => kvp.Key.StartsWith("dotnet.", StringComparison.OrdinalIgnoreCase) &&
- kvp.Key.EndsWith(".js", StringComparison.OrdinalIgnoreCase)).Any(),
- $"{msgPrefix} Could not find dotnet.*js in {bootJson}");
+ if (options.TargetFramework is null)
+ options = options with { TargetFramework = DefaultTargetFrameworkForBlazor };
+
+ AssertBlazorDotNetNativeFiles(options.ExpectedFileType,
+ options.Config,
+ forPublish: isPublish,
+ targetFramework: options.TargetFramework,
+ expectFingerprintOnDotnetJs: options.ExpectFingerprintOnDotnetJs,
+ runtimeType: options.RuntimeType);
+
+ AssertBlazorBootJson(config: options.Config,
+ isPublish: isPublish,
+ targetFramework: options.TargetFramework,
+ expectFingerprintOnDotnetJs: options.ExpectFingerprintOnDotnetJs,
+ runtimeType: options.RuntimeType);
}
- protected string FindBlazorBinFrameworkDir(string config, bool forPublish, string framework = DefaultTargetFrameworkForBlazor)
+ protected void AssertBlazorBootJson(
+ string config,
+ bool isPublish,
+ string targetFramework = DefaultTargetFrameworkForBlazor,
+ bool expectFingerprintOnDotnetJs = false,
+ RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded)
{
- string basePath = Path.Combine(_projectDir!, "bin", config, framework);
- if (forPublish)
- basePath = FindSubDirIgnoringCase(basePath, "publish");
-
- return Path.Combine(basePath, "wwwroot", "_framework");
+ new BlazorWasmProjectProvider(_projectDir!, _testOutput)
+ .AssertBlazorBootJson(binFrameworkDir: FindBlazorBinFrameworkDir(config, isPublish, targetFramework),
+ isPublish: isPublish,
+ expectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs,
+ runtimeType: runtimeType);
}
- private string FindSubDirIgnoringCase(string parentDir, string dirName)
- {
- IEnumerable matchingDirs = Directory.EnumerateDirectories(parentDir,
- dirName,
- new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive });
-
- 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())}");
-
- return first ?? Path.Combine(parentDir, dirName);
- }
+ public string FindBlazorBinFrameworkDir(string config, bool forPublish, string framework = DefaultTargetFrameworkForBlazor)
+ => new BlazorWasmProjectProvider(_projectDir!, _testOutput)
+ .FindBlazorBinFrameworkDir(config, forPublish, framework);
protected string GetBinDir(string config, string targetFramework = DefaultTargetFramework, string? baseDir = null)
{
@@ -1032,8 +978,6 @@ public async Task BlazorRunTest(string runArgs,
void OnConsoleMessage(IConsoleMessage msg)
{
- if (EnvironmentVariables.ShowBuildOutput)
- Console.WriteLine($"[{msg.Type}] {msg.Text}");
_testOutput.WriteLine($"[{msg.Type}] {msg.Text}");
onConsoleMessage?.Invoke(msg);
@@ -1052,10 +996,9 @@ public static (int exitCode, string buildOutput) RunProcess(string path,
IDictionary? envVars = null,
string? workingDir = null,
string? label = null,
- bool logToXUnit = true,
int? timeoutMs = null)
{
- var t = RunProcessAsync(path, _testOutput, args, envVars, workingDir, label, logToXUnit, timeoutMs);
+ var t = RunProcessAsync(path, _testOutput, args, envVars, workingDir, label, timeoutMs);
t.Wait();
return t.Result;
}
@@ -1066,7 +1009,6 @@ public static (int exitCode, string buildOutput) RunProcess(string path,
IDictionary? envVars = null,
string? workingDir = null,
string? label = null,
- bool logToXUnit = true,
int? timeoutMs = null)
{
_testOutput.WriteLine($"Running {path} {args}");
@@ -1167,14 +1109,12 @@ void LogData(string label, string? message)
{
lock (syncObj)
{
- if (logToXUnit && message != null)
+ if (message != null)
{
_testOutput.WriteLine($"{label} {message}");
}
outputBuilder.AppendLine($"{label} {message}");
}
- if (EnvironmentVariables.ShowBuildOutput)
- Console.WriteLine($"{label} {message}");
}
}
@@ -1277,6 +1217,16 @@ protected void AssertSubstring(string substring, string full, bool contains)
else
Assert.DoesNotContain(substring, full);
}
+
+ public static void AssertEqual(object expected, object actual, string label)
+ {
+ if (expected?.Equals(actual) == true)
+ return;
+
+ throw new AssertActualExpectedException(
+ expected, actual,
+ $"[{label}]\n");
+ }
}
public record BuildArgs(string ProjectName,
@@ -1288,27 +1238,6 @@ public record BuildProduct(string ProjectDir, string LogFile, bool Result, strin
internal record FileStat(bool Exists, DateTime LastWriteTimeUtc, long Length, string FullPath);
internal record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BundleDir);
- public record BuildProjectOptions
- (
- Action? InitProject = null,
- bool? DotnetWasmFromRuntimePack = null,
- GlobalizationMode? GlobalizationMode = null,
- string? PredefinedIcudt = 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 = null,
- string? MainJS = null,
- bool IsBrowserProject = true,
- IDictionary? ExtraBuildEnvironmentVariables = null
- );
-
public record BlazorBuildOptions
(
string Id,
@@ -1317,7 +1246,8 @@ public record BlazorBuildOptions
string TargetFramework = BuildTestBase.DefaultTargetFrameworkForBlazor,
bool WarnAsError = true,
bool ExpectRelinkDirWhenPublishing = false,
- bool ExpectFingerprintOnDotnetJs = false
+ bool ExpectFingerprintOnDotnetJs = false,
+ RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded
);
public enum GlobalizationMode
diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
index cb24f659c96c9..4dddc0c2ed2d4 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
@@ -139,10 +139,14 @@ public BuildEnvironment()
// FIXME: error checks
public string GetRuntimePackVersion(string tfm = BuildTestBase.DefaultTargetFramework) => s_runtimePackVersions[tfm];
- public string GetRuntimePackDir(string tfm = BuildTestBase.DefaultTargetFramework)
- => Path.Combine(WorkloadPacksDir, $"Microsoft.NETCore.App.Runtime.Mono.{DefaultRuntimeIdentifier}", GetRuntimePackVersion(tfm));
- public string GetRuntimeNativeDir(string tfm = BuildTestBase.DefaultTargetFramework)
- => Path.Combine(GetRuntimePackDir(tfm), "runtimes", DefaultRuntimeIdentifier, "native");
+ public string GetRuntimePackDir(string tfm = BuildTestBase.DefaultTargetFramework, RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded)
+ => Path.Combine(WorkloadPacksDir,
+ runtimeType is RuntimeVariant.SingleThreaded
+ ? $"Microsoft.NETCore.App.Runtime.Mono.{DefaultRuntimeIdentifier}"
+ : $"Microsoft.NETCore.App.Runtime.Mono.multithread.{DefaultRuntimeIdentifier}",
+ GetRuntimePackVersion(tfm));
+ public string GetRuntimeNativeDir(string tfm = BuildTestBase.DefaultTargetFramework, RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded)
+ => Path.Combine(GetRuntimePackDir(tfm, runtimeType), "runtimes", DefaultRuntimeIdentifier, "native");
protected static string s_directoryBuildPropsForWorkloads = File.ReadAllText(Path.Combine(TestDataPath, "Workloads.Directory.Build.props"));
protected static string s_directoryBuildTargetsForWorkloads = File.ReadAllText(Path.Combine(TestDataPath, "Workloads.Directory.Build.targets"));
diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/RuntimeVariant.cs b/src/mono/wasm/Wasm.Build.Tests/Common/RuntimeVariant.cs
new file mode 100644
index 0000000000000..c060f42520588
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/Common/RuntimeVariant.cs
@@ -0,0 +1,8 @@
+// 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 RuntimeVariant { SingleThreaded, MultiThreaded };
diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/TestOutputWrapper.cs b/src/mono/wasm/Wasm.Build.Tests/Common/TestOutputWrapper.cs
new file mode 100644
index 0000000000000..a28657fa7bf0e
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/Common/TestOutputWrapper.cs
@@ -0,0 +1,26 @@
+// 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 Xunit.Abstractions;
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public class TestOutputWrapper(ITestOutputHelper baseOutput) : ITestOutputHelper
+{
+ public void WriteLine(string message)
+ {
+ baseOutput.WriteLine(message);
+ if (EnvironmentVariables.ShowBuildOutput)
+ Console.WriteLine(message);
+ }
+
+ public void WriteLine(string format, params object[] args)
+ {
+ baseOutput.WriteLine(format, args);
+ if (EnvironmentVariables.ShowBuildOutput)
+ Console.WriteLine(format, args);
+ }
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/ToolCommand.cs b/src/mono/wasm/Wasm.Build.Tests/Common/ToolCommand.cs
index 2fae80aa4bdd3..a308c7ba668cb 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Common/ToolCommand.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Common/ToolCommand.cs
@@ -115,8 +115,6 @@ private async Task ExecuteAsyncInternal(string executable, string
string msg = $"[{_label}] {e.Data}";
output.Add(msg);
_testOutput.WriteLine(msg);
- if (EnvironmentVariables.ShowBuildOutput)
- Console.WriteLine(msg);
ErrorDataReceived?.Invoke(s, e);
};
@@ -128,8 +126,6 @@ private async Task ExecuteAsyncInternal(string executable, string
string msg = $"[{_label}] {e.Data}";
output.Add(msg);
_testOutput.WriteLine(msg);
- if (EnvironmentVariables.ShowBuildOutput)
- Console.WriteLine(msg);
OutputDataReceived?.Invoke(s, e);
};
diff --git a/src/mono/wasm/Wasm.Build.Tests/DotNetFileName.cs b/src/mono/wasm/Wasm.Build.Tests/DotNetFileName.cs
new file mode 100644
index 0000000000000..42fd8873e78f5
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/DotNetFileName.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public sealed record DotNetFileName
+(
+ string ExpectedFilename,
+ string? Version,
+ string? Hash,
+ string ActualPath
+);
diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs
new file mode 100644
index 0000000000000..5dd4240c00435
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs
@@ -0,0 +1,170 @@
+// 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 System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Wasm.Build.Tests;
+
+public abstract class ProjectProviderBase(string projectDir, ITestOutputHelper _testOutput)
+{
+ protected const string s_dotnetVersionHashRegex = @"\.(?.+)\.(?[a-zA-Z0-9]+)\.";
+ private static string[] s_dotnetExtensionsToIgnore = new[]
+ {
+ ".gz",
+ ".br",
+ ".symbols"
+ };
+
+ public string ProjectDir { get; } = projectDir;
+
+ public IReadOnlyDictionary FindAndAssertDotnetFiles(
+ string dir,
+ bool isPublish,
+ bool expectFingerprintOnDotnetJs,
+ RuntimeVariant runtimeType)
+ {
+ return FindAndAssertDotnetFiles(dir: dir,
+ expectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs,
+ superSet: GetAllKnownDotnetFilesToFingerprintMap(runtimeType),
+ expected: GetDotNetFilesExpectedSet(runtimeType, isPublish));
+ }
+
+ protected abstract IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(RuntimeVariant runtimeType);
+ protected abstract IReadOnlySet GetDotNetFilesExpectedSet(RuntimeVariant runtimeType, bool isPublish);
+
+ public IReadOnlyDictionary FindAndAssertDotnetFiles(
+ string dir,
+ bool expectFingerprintOnDotnetJs,
+ IReadOnlyDictionary superSet,
+ IReadOnlySet? expected)
+ {
+ var actual = new SortedDictionary();
+
+ IList dotnetFiles = Directory.EnumerateFiles(dir,
+ "dotnet.*",
+ SearchOption.TopDirectoryOnly)
+ .Order()
+ .ToList();
+ foreach ((string expectedFilename, bool expectFingerprint) in superSet.OrderByDescending(kvp => kvp.Key))
+ {
+ string prefix = Path.GetFileNameWithoutExtension(expectedFilename);
+ string extension = Path.GetExtension(expectedFilename).Substring(1);
+
+ dotnetFiles = dotnetFiles
+ .Where(actualFile =>
+ {
+ if (s_dotnetExtensionsToIgnore.Contains(Path.GetExtension(actualFile)))
+ return false;
+
+ string actualFilename = Path.GetFileName(actualFile);
+ _testOutput.WriteLine($"Comparing {expectedFilename} with {actualFile}, expectFingerprintOnDotnetJs: {expectFingerprintOnDotnetJs}, expectFingerprint: {expectFingerprint}");
+ if (ShouldCheckFingerprint(expectedFilename: expectedFilename,
+ expectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs,
+ expectFingerprintForThisFile: expectFingerprint))
+ {
+ string pattern = $"^{prefix}{s_dotnetVersionHashRegex}{extension}$";
+ var match = Regex.Match(actualFilename, pattern);
+ if (!match.Success)
+ return true;
+
+ actual[expectedFilename] = new(ExpectedFilename: expectedFilename,
+ Version: match.Groups[1].Value,
+ Hash: match.Groups[2].Value,
+ ActualPath: actualFile);
+ }
+ else
+ {
+ if (actualFilename != expectedFilename)
+ return true;
+
+ actual[expectedFilename] = new(ExpectedFilename: expectedFilename,
+ Version: null,
+ Hash: null,
+ ActualPath: actualFile);
+ }
+
+ return false;
+ }).ToList();
+ }
+
+ _testOutput.WriteLine($"Accepted count: {actual.Count}");
+ foreach (var kvp in actual)
+ _testOutput.WriteLine($"Accepted: \t[{kvp.Key}] = {kvp.Value}");
+
+ if (dotnetFiles.Any())
+ {
+ throw new XunitException($"Found unknown files in {dir}:{Environment.NewLine} {string.Join($"{Environment.NewLine} ", dotnetFiles)}");
+ }
+
+ if (expected is not null)
+ AssertDotNetFilesSet(expected, superSet, actual, expectFingerprintOnDotnetJs);
+ return actual;
+ }
+
+ public void AssertDotNetFilesSet(
+ IReadOnlySet expected,
+ IReadOnlyDictionary superSet,
+ IDictionary actual,
+ bool expectFingerprintOnDotnetJs)
+ {
+ foreach (string expectedFilename in expected)
+ {
+ bool expectFingerprint = superSet[expectedFilename];
+
+ Assert.True(actual.ContainsKey(expectedFilename), $"Could not find {expectedFilename} in {string.Join(", ", actual.Keys)}");
+
+ // Check that the version and hash are present or not present as expected
+ if (ShouldCheckFingerprint(expectedFilename: expectedFilename,
+ expectFingerprintOnDotnetJs: expectFingerprintOnDotnetJs,
+ expectFingerprintForThisFile: expectFingerprint))
+ {
+ if (string.IsNullOrEmpty(actual[expectedFilename].Version))
+ throw new XunitException($"Expected version in filename: {actual[expectedFilename].ActualPath}");
+ if (string.IsNullOrEmpty(actual[expectedFilename].Hash))
+ throw new XunitException($"Expected hash in filename: {actual[expectedFilename].ActualPath}");
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(actual[expectedFilename].Version))
+ throw new XunitException($"Expected no version in filename: {actual[expectedFilename].ActualPath}");
+ if (!string.IsNullOrEmpty(actual[expectedFilename].Hash))
+ throw new XunitException($"Expected no hash in filename: {actual[expectedFilename].ActualPath}");
+ }
+ }
+
+ if (expected.Count < actual.Count)
+ {
+ StringBuilder sb = new();
+ sb.AppendLine($"Expected: {string.Join(", ", expected)}");
+ // FIXME: show the difference in a better way
+ sb.AppendLine($"Actual: {string.Join(", ", actual.Values.Select(a => a.ActualPath).Order())}");
+ throw new XunitException($"Expected and actual file sets do not match.{Environment.NewLine}{sb}");
+ }
+ }
+
+ public static string FindSubDirIgnoringCase(string parentDir, string dirName)
+ {
+ IEnumerable matchingDirs = Directory.EnumerateDirectories(parentDir,
+ dirName,
+ new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive });
+
+ 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())}");
+
+ return first ?? Path.Combine(parentDir, dirName);
+ }
+
+ public static bool ShouldCheckFingerprint(string expectedFilename, bool expectFingerprintOnDotnetJs, bool expectFingerprintForThisFile) =>
+ (expectedFilename == "dotnet.js" && expectFingerprintOnDotnetJs) || expectFingerprintForThisFile;
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs
new file mode 100644
index 0000000000000..ac24e587f2662
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/TestMainJsProjectProvider.cs
@@ -0,0 +1,65 @@
+// 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 Xunit.Abstractions;
+
+namespace Wasm.Build.Tests;
+
+public class TestMainJsProjectProvider(string projectDir, ITestOutputHelper testOutput)
+ : ProjectProviderBase(projectDir, testOutput)
+{
+ // no fingerprinting
+ protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(RuntimeVariant runtimeType)
+ => new SortedDictionary()
+ {
+ { "dotnet.js", false },
+ { "dotnet.js.map", false },
+ { "dotnet.native.js", false },
+ { "dotnet.native.wasm", false },
+ { "dotnet.native.worker.js", false },
+ { "dotnet.runtime.js", false },
+ { "dotnet.runtime.js.map", false }
+ };
+
+ protected override IReadOnlySet GetDotNetFilesExpectedSet(RuntimeVariant runtimeType, bool isPublish)
+ {
+ SortedSet? res = null;
+ if (runtimeType is RuntimeVariant.SingleThreaded)
+ {
+ res = new SortedSet()
+ {
+ "dotnet.js",
+ "dotnet.native.wasm",
+ "dotnet.native.js",
+ "dotnet.runtime.js",
+ };
+
+ res.Add("dotnet.js.map");
+ res.Add("dotnet.runtime.js.map");
+ }
+
+ if (runtimeType is RuntimeVariant.MultiThreaded)
+ {
+ res = new SortedSet()
+ {
+ "dotnet.js",
+ "dotnet.native.js",
+ "dotnet.native.wasm",
+ "dotnet.native.worker.js",
+ "dotnet.runtime.js",
+ };
+ if (!isPublish)
+ {
+ res.Add("dotnet.js.map");
+ res.Add("dotnet.runtime.js.map");
+ res.Add("dotnet.native.worker.js.map");
+ }
+ }
+
+ return res ?? throw new ArgumentException($"Unknown runtime type: {runtimeType}");
+ }
+}
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 0ff15466c29ef..fb09aa484eea2 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj
+++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj
@@ -45,11 +45,6 @@
-
- <_SdkWithWorkloadForTestingDirName>$([System.IO.Path]::GetDirectoryName($(SdkWithWorkloadForTestingPath)))
- <_SdkWithWorkloadForTestingDirName>$([System.IO.Path]::GetFilename($(_SdkWithWorkloadForTestingDirName)))
-
-
@@ -72,12 +67,18 @@
<_XUnitTraitArg Condition="'$(TestUsingWorkloads)' != 'true'">-trait category=no-workload
+
+ <_SdkPathForLocalTesting Condition="'$(TestUsingWorkloads)' == 'true'">$([System.IO.Path]::GetDirectoryName($(SdkWithWorkloadForTestingPath)))
+ <_SdkPathForLocalTesting Condition="'$(TestUsingWorkloads)' != 'true'">$([System.IO.Path]::GetDirectoryName($(SdkWithNoWorkloadForTestingPath)))
+
+ <_SdkPathForLocalTesting>$([System.IO.Path]::GetFilename($(_SdkPathForLocalTesting)))
+
-
-
+
+
diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs
new file mode 100644
index 0000000000000..6ee5a6ca253f5
--- /dev/null
+++ b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs
@@ -0,0 +1,48 @@
+// 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 Xunit.Abstractions;
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public class WasmSdkBasedProjectProvider(string projectDir, ITestOutputHelper _testOutput)
+ : ProjectProviderBase(projectDir, _testOutput)
+{
+ protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(RuntimeVariant runtimeType)
+ => new SortedDictionary()
+ {
+ { "dotnet.js", false },
+ { "dotnet.js.map", false },
+ { "dotnet.native.js", true },
+ { "dotnet.native.wasm", false },
+ { "dotnet.native.worker.js", true },
+ { "dotnet.runtime.js", true },
+ { "dotnet.runtime.js.map", false }
+ };
+
+ protected override IReadOnlySet GetDotNetFilesExpectedSet(RuntimeVariant runtimeType, bool isPublish)
+ {
+ SortedSet res = new()
+ {
+ "dotnet.js",
+ "dotnet.native.wasm",
+ "dotnet.native.js",
+ "dotnet.runtime.js",
+ };
+ if (runtimeType is RuntimeVariant.MultiThreaded)
+ {
+ res.Add("dotnet.native.worker.js");
+ }
+
+ if (!isPublish)
+ {
+ res.Add("dotnet.js.map");
+ res.Add("dotnet.runtime.js.map");
+ }
+
+ return res;
+ }
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs
index 88af535d0895d..088b1134a8540 100644
--- a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs
@@ -96,7 +96,7 @@ public void BrowserBuildThenPublish(string config)
var buildArgs = new BuildArgs(projectName, config, false, id, null);
buildArgs = ExpandBuildArgs(buildArgs);
- BuildProject(buildArgs,
+ BuildTemplateProject(buildArgs,
id: id,
new BuildProjectOptions(
DotnetWasmFromRuntimePack: true,
@@ -117,7 +117,7 @@ public void BrowserBuildThenPublish(string config)
_testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
bool expectRelinking = config == "Release";
- BuildProject(buildArgs,
+ BuildTemplateProject(buildArgs,
id: id,
new BuildProjectOptions(
DotnetWasmFromRuntimePack: !expectRelinking,
@@ -145,7 +145,7 @@ public void ConsoleBuildThenPublish(string config)
var buildArgs = new BuildArgs(projectName, config, false, id, null);
buildArgs = ExpandBuildArgs(buildArgs);
- BuildProject(buildArgs,
+ BuildTemplateProject(buildArgs,
id: id,
new BuildProjectOptions(
DotnetWasmFromRuntimePack: true,
@@ -173,7 +173,7 @@ public void ConsoleBuildThenPublish(string config)
_testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
bool expectRelinking = config == "Release";
- BuildProject(buildArgs,
+ BuildTemplateProject(buildArgs,
id: id,
new BuildProjectOptions(
DotnetWasmFromRuntimePack: !expectRelinking,
@@ -217,7 +217,7 @@ private void ConsoleBuildAndRun(string config, bool relinking, string extraNewAr
var buildArgs = new BuildArgs(projectName, config, false, id, null);
buildArgs = ExpandBuildArgs(buildArgs);
- BuildProject(buildArgs,
+ BuildTemplateProject(buildArgs,
id: id,
new BuildProjectOptions(
DotnetWasmFromRuntimePack: !relinking,
@@ -391,7 +391,7 @@ public void ConsolePublishAndRun(string config, bool aot, bool relinking)
buildArgs = ExpandBuildArgs(buildArgs);
bool expectRelinking = config == "Release" || aot || relinking;
- BuildProject(buildArgs,
+ BuildTemplateProject(buildArgs,
id: id,
new BuildProjectOptions(
DotnetWasmFromRuntimePack: !expectRelinking,
diff --git a/src/mono/wasm/wasm.code-workspace b/src/mono/wasm/wasm.code-workspace
index 62834e391e2fb..80a990415ad60 100644
--- a/src/mono/wasm/wasm.code-workspace
+++ b/src/mono/wasm/wasm.code-workspace
@@ -13,6 +13,7 @@
"settings": {
"omnisharp.enableMsBuildLoadProjectsOnDemand": true,
"omnisharp.defaultLaunchSolution": "${workspaceFolder}sln/WasmBuild.sln",
- "omnisharp.enableRoslynAnalyzers": true
+ "omnisharp.enableRoslynAnalyzers": true,
+ "cSpell.enabled": false
}
}