File: WasmBuildIncrementalismTest.cs
Web Access
Project: ..\..\..\test\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj (Microsoft.NET.Sdk.BlazorWebAssembly.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Text.Json;
using Microsoft.NET.Sdk.WebAssembly;
 
namespace Microsoft.NET.Sdk.BlazorWebAssembly.Tests
{
    public class WasmBuildIncrementalismTest(ITestOutputHelper log) : AspNetSdkTest(log)
    {
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Build_IsIncremental()
        {
            // Arrange
            var testAsset = "BlazorWasmWithLibrary";
            var projectDirectory = CreateAspNetSdkTestAsset(testAsset);
 
            var build = CreateBuildCommand(projectDirectory, "blazorwasm");
            build.Execute("/bl")
                .Should()
                .Pass();
 
            var buildOutputDirectory = build.GetOutputDirectory(DefaultTfm).ToString();
 
            // ActiveIssue https://github.com/dotnet/runtime/issues/115646
            var filesToIgnore = new[]
            {
                Path.Combine(buildOutputDirectory, "blazorwasm.runtimeconfig.json"),
                Path.Combine(buildOutputDirectory, "RazorClassLibrary.staticwebassets.endpoints.json"),
                Path.Combine(buildOutputDirectory, "blazorwasm.staticwebassets.endpoints.json")
            };
 
            // Act
            var thumbPrint = FileThumbPrint.CreateFolderThumbprint(projectDirectory, buildOutputDirectory, filesToIgnore);
 
            // Assert
            for (var i = 0; i < 3; i++)
            {
                build = CreateBuildCommand(projectDirectory, "blazorwasm");
                build.Execute($"/bl:msbuild{i}.binlog").Should().Pass();
 
                var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(projectDirectory, buildOutputDirectory, filesToIgnore);
                newThumbPrint.Count.Should().Be(thumbPrint.Count);
                for (var j = 0; j < thumbPrint.Count; j++)
                {
                    var first = thumbPrint[j];
                    var actual = newThumbPrint[j];
                    actual.Path.Equals(first.Path).Should().BeTrue($"because {actual.Path} should match {first.Path} on build {i}");
                    actual.Hash.Equals(first.Hash).Should().BeTrue($"because {actual.Hash} should match {first.Hash} for {first.Path} on build {i}");
                    actual.LastWriteTimeUtc.Equals(first.LastWriteTimeUtc).Should().BeTrue($"because {actual.LastWriteTimeUtc} should match {first.LastWriteTimeUtc} for {first.Path} on build {i}");
                }
            }
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Build_GzipCompression_IsIncremental()
        {
            // Arrange
            var testAsset = "BlazorWasmWithLibrary";
            var projectDirectory = CreateAspNetSdkTestAsset(testAsset).WithProjectChanges((path, document) =>
            {
                if (Path.GetFileNameWithoutExtension(path) == "blazorwasm")
                {
                    // Since boot config gets modified on each build, we explicitly exclude it from compression so
                    // its compressed asset doesn't fail the thumb print check.
                    document.Root.Add(XElement.Parse($"<PropertyGroup><CompressionExcludePatterns>$(CompressionExcludePatterns);_framework\\{WasmBootConfigFileName}</CompressionExcludePatterns></PropertyGroup>"));
                }
            });
 
            var build = CreateBuildCommand(projectDirectory, "blazorwasm");
            build.Execute("/bl")
                .Should()
                .Pass();
 
            var gzipCompressionDirectory = Path.Combine(projectDirectory.TestRoot, "blazorwasm", "obj", "Debug", DefaultTfm, "compressed");
            new DirectoryInfo(gzipCompressionDirectory).Should().Exist();
 
            // Act
            var thumbPrint = FileThumbPrint.CreateFolderThumbprint(projectDirectory, gzipCompressionDirectory);
 
            // Assert
            for (var i = 0; i < 3; i++)
            {
                build = CreateBuildCommand(projectDirectory, "blazorwasm");
                build.Execute($"/bl:msbuild{i}.binlog")
                    .Should()
                    .Pass();
 
                var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(projectDirectory, gzipCompressionDirectory);
                Assert.Equal(thumbPrint.Count, newThumbPrint.Count);
                for (var j = 0; j < thumbPrint.Count; j++)
                {
                    thumbPrint[j].Equals(newThumbPrint[j]).Should().BeTrue($"because {thumbPrint[j].Hash} should be the same as {newThumbPrint[j].Hash} for file {thumbPrint[j].Path}");
                }
            }
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Build_SatelliteAssembliesFileIsPreserved()
        {
            // Arrange
            var testAsset = "BlazorWasmWithLibrary";
            var projectDirectory = CreateAspNetSdkTestAsset(testAsset)
                .WithProjectChanges((p, doc) =>
                {
                    var itemGroup = new XElement("PropertyGroup");
                    var fingerprintAssets = new XElement("WasmFingerprintAssets", false);
                    itemGroup.Add(fingerprintAssets);
                    doc.Root.Add(itemGroup);
                });
 
            File.Move(Path.Combine(projectDirectory.TestRoot, "blazorwasm", "Resources.ja.resx.txt"), Path.Combine(projectDirectory.TestRoot, "blazorwasm", "Resource.ja.resx"));
 
            var build = CreateBuildCommand(projectDirectory, "blazorwasm");
            build.Execute()
                .Should()
                .Pass();
 
            var satelliteAssemblyFile = Path.Combine(build.GetOutputDirectory(DefaultTfm).ToString(), "wwwroot", "_framework", "ja", "blazorwasm.resources.wasm");
            var bootJson = Path.Combine(build.GetOutputDirectory(DefaultTfm).ToString(), "wwwroot", "_framework", WasmBootConfigFileName);
 
            // Assert
            for (var i = 0; i < 3; i++)
            {
                build = CreateBuildCommand(projectDirectory, "blazorwasm");
                build.Execute()
                    .Should()
                    .Pass();
 
                Verify();
            }
 
            // Assert - incremental builds with BuildingProject=false
            for (var i = 0; i < 3; i++)
            {
                build = CreateBuildCommand(projectDirectory, "blazorwasm");
                build.Execute("/p:BuildingProject=false")
                    .Should()
                    .Pass();
 
                Verify();
            }
 
            void Verify()
            {
                new FileInfo(satelliteAssemblyFile).Should().Exist();
 
                var bootJsonFile = BootJsonDataLoader.ParseBootData(bootJson);
                var satelliteResources = bootJsonFile.resources.satelliteResources;
 
 
                satelliteResources.Should().HaveCount(1);
 
                var kvp = satelliteResources.SingleOrDefault(p => p.Key == "ja");
                kvp.Should().NotBeNull();
 
                kvp.Value.Should().ContainKey("blazorwasm.resources.wasm");
            }
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Build_SatelliteAssembliesFileIsCreated_IfNewFileIsAdded()
        {
            // Arrange
            var testAsset = "BlazorWasmWithLibrary";
            var projectDirectory = CreateAspNetSdkTestAsset(testAsset)
                .WithProjectChanges((p, doc) =>
                {
                    var itemGroup = new XElement("PropertyGroup");
                    var fingerprintAssets = new XElement("WasmFingerprintAssets", false);
                    itemGroup.Add(fingerprintAssets);
                    doc.Root.Add(itemGroup);
                });
 
            var build = CreateBuildCommand(projectDirectory, "blazorwasm");
            build.Execute("/bl:build1-msbuild.binlog")
                .Should()
                .Pass();
 
            var satelliteAssemblyFile = Path.Combine(build.GetOutputDirectory(DefaultTfm).ToString(), "wwwroot", "_framework", "ja", "blazorwasm.resources.wasm");
            var bootJson = Path.Combine(build.GetOutputDirectory(DefaultTfm).ToString(), "wwwroot", "_framework", WasmBootConfigFileName);
 
            build = CreateBuildCommand(projectDirectory, "blazorwasm");
            build.Execute("/bl:build2-msbuild.binlog")
                .Should()
                .Pass();
 
            new FileInfo(satelliteAssemblyFile).Should().NotExist();
 
            var bootJsonFile = BootJsonDataLoader.ParseBootData(bootJson);
            var satelliteResources = bootJsonFile.resources.satelliteResources;
            satelliteResources.Should().BeNull();
 
            File.Move(Path.Combine(projectDirectory.TestRoot, "blazorwasm", "Resources.ja.resx.txt"), Path.Combine(projectDirectory.TestRoot, "blazorwasm", "Resource.ja.resx"));
            build = CreateBuildCommand(projectDirectory, "blazorwasm");
            build.Execute("/bl:build3-msbuild.binlog")
                .Should()
                .Pass();
 
            new FileInfo(satelliteAssemblyFile).Should().Exist();
            bootJsonFile = BootJsonDataLoader.ParseBootData(bootJson);
            satelliteResources = bootJsonFile.resources.satelliteResources;
            satelliteResources.Should().HaveCount(1);
 
            var kvp = satelliteResources.SingleOrDefault(p => p.Key == "ja");
            kvp.Should().NotBeNull();
 
            kvp.Value.Should().ContainKey("blazorwasm.resources.wasm");
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Build_SatelliteAssembliesFileIsDeleted_IfAllSatelliteFilesAreRemoved()
        {
            // Arrange
            var testAsset = "BlazorWasmWithLibrary";
            var projectDirectory = CreateAspNetSdkTestAsset(testAsset)
                .WithProjectChanges((p, doc) =>
                {
                    var itemGroup = new XElement("PropertyGroup");
                    var fingerprintAssets = new XElement("WasmFingerprintAssets", false);
                    itemGroup.Add(fingerprintAssets);
                    doc.Root.Add(itemGroup);
                });
            File.Move(Path.Combine(projectDirectory.TestRoot, "blazorwasm", "Resources.ja.resx.txt"), Path.Combine(projectDirectory.TestRoot, "blazorwasm", "Resource.ja.resx"));
 
            var build = CreateBuildCommand(projectDirectory, "blazorwasm");
            build.Execute()
                .Should()
                .Pass();
 
            var satelliteAssemblyFile = Path.Combine(build.GetOutputDirectory(DefaultTfm).ToString(), "wwwroot", "_framework", "ja", "blazorwasm.resources.wasm");
            var bootJson = Path.Combine(build.GetOutputDirectory(DefaultTfm).ToString(), "wwwroot", "_framework", WasmBootConfigFileName);
 
            build = CreateBuildCommand(projectDirectory, "blazorwasm");
            build.Execute()
                .Should()
                .Pass();
 
            new FileInfo(satelliteAssemblyFile).Should().Exist();
 
            var bootJsonFile = BootJsonDataLoader.ParseBootData(bootJson);
            var satelliteResources = bootJsonFile.resources.satelliteResources;
            satelliteResources.Should().HaveCount(1);
 
            var kvp = satelliteResources.SingleOrDefault(p => p.Key == "ja");
            kvp.Should().NotBeNull();
 
            kvp.Value.Should().ContainKey("blazorwasm.resources.wasm");
 
 
            File.Delete(Path.Combine(projectDirectory.TestRoot, "blazorwasm", "Resource.ja.resx"));
            build = CreateBuildCommand(projectDirectory, "blazorwasm");
            build.Execute()
                .Should()
                .Pass();
 
            bootJsonFile = BootJsonDataLoader.ParseBootData(bootJson);
            satelliteResources = bootJsonFile.resources.satelliteResources;
            satelliteResources.Should().BeNull();
        }
    }
}