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;
using Xunit.Abstractions;
using Xunit.Sdk;
using Microsoft.Playwright;
-using System.Runtime.Serialization.Json;
-using Microsoft.NET.Sdk.WebAssembly;
#nullable enable
{
_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<IEnumerable<object?>> ConfigWithAOTData(bool aot, string? config = null, string? extraArgs = null)
{
if (extraArgs == null)
}
}
-
protected string RunAndTestWasmApp(BuildArgs buildArgs,
RunHost host,
string id,
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)
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)
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;
}
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)
{
// 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");
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);
+ }
}
}
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<string>()
{
- 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?
//}
}
- AssertDotNetWasmJs(bundleDir, fromRuntimePack: dotnetWasmFromRuntimePack, targetFramework);
-
void AssertIcuAssets()
{
bool expectEFIGS = false;
bool expectNOCJK = false;
bool expectFULL = false;
bool expectHYBRID = false;
- switch (globalizationMode)
+ switch (options.GlobalizationMode)
{
case GlobalizationMode.Invariant:
break;
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;
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);
}
}
- 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"),
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 = @"\.(?<version>.+)\.(?<hash>[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<string> 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)
{
void OnConsoleMessage(IConsoleMessage msg)
{
- if (EnvironmentVariables.ShowBuildOutput)
- Console.WriteLine($"[{msg.Type}] {msg.Text}");
_testOutput.WriteLine($"[{msg.Type}] {msg.Text}");
onConsoleMessage?.Invoke(msg);
IDictionary<string, string>? 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;
}
IDictionary<string, string>? envVars = null,
string? workingDir = null,
string? label = null,
- bool logToXUnit = true,
int? timeoutMs = null)
{
_testOutput.WriteLine($"Running {path} {args}");
{
lock (syncObj)
{
- if (logToXUnit && message != null)
+ if (message != null)
{
_testOutput.WriteLine($"{label} {message}");
}
outputBuilder.AppendLine($"{label} {message}");
}
- if (EnvironmentVariables.ShowBuildOutput)
- Console.WriteLine($"{label} {message}");
}
}
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,
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<string, string>? ExtraBuildEnvironmentVariables = null
- );
-
public record BlazorBuildOptions
(
string Id,
string TargetFramework = BuildTestBase.DefaultTargetFrameworkForBlazor,
bool WarnAsError = true,
bool ExpectRelinkDirWhenPublishing = false,
- bool ExpectFingerprintOnDotnetJs = false
+ bool ExpectFingerprintOnDotnetJs = false,
+ RuntimeVariant RuntimeType = RuntimeVariant.SingleThreaded
);
public enum GlobalizationMode
--- /dev/null
+// 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 = @"\.(?<version>.+)\.(?<hash>[a-zA-Z0-9]+)\.";
+ private static string[] s_dotnetExtensionsToIgnore = new[]
+ {
+ ".gz",
+ ".br",
+ ".symbols"
+ };
+
+ public string ProjectDir { get; } = projectDir;
+
+ public IReadOnlyDictionary<string, DotNetFileName> 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<string, bool> GetAllKnownDotnetFilesToFingerprintMap(RuntimeVariant runtimeType);
+ protected abstract IReadOnlySet<string> GetDotNetFilesExpectedSet(RuntimeVariant runtimeType, bool isPublish);
+
+ public IReadOnlyDictionary<string, DotNetFileName> FindAndAssertDotnetFiles(
+ string dir,
+ bool expectFingerprintOnDotnetJs,
+ IReadOnlyDictionary<string, bool> superSet,
+ IReadOnlySet<string>? expected)
+ {
+ var actual = new SortedDictionary<string, DotNetFileName>();
+
+ IList<string> 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<string> expected,
+ IReadOnlyDictionary<string, bool> superSet,
+ IDictionary<string, DotNetFileName> 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<string> 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;
+}