Add some extra publish integration tests, plus fix publish-from-VS-with-RID (#20410)
* Add detailed integration tests for publishing service workers, assets manifests, and blazor.boot.json * Fix publishing from VS with non-portable RID
This commit is contained in:
parent
a8bd551d99
commit
ae569e2b48
|
|
@ -210,6 +210,10 @@
|
|||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Note that the VS-specific condition below is a workaround for https://github.com/dotnet/aspnetcore/issues/19822
|
||||
For more details, see https://github.com/dotnet/aspnetcore/issues/20413
|
||||
-->
|
||||
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(_BlazorTasksPath)" />
|
||||
<Target
|
||||
Name="_LinkBlazorApplication"
|
||||
|
|
@ -217,7 +221,8 @@
|
|||
@(_BlazorManagedRuntimeAssembly);
|
||||
@(BlazorLinkerDescriptor);
|
||||
$(MSBuildAllProjects)"
|
||||
Outputs="$(_BlazorLinkerOutputCache)">
|
||||
Outputs="$(_BlazorLinkerOutputCache)"
|
||||
Condition="'$(BuildingInsideVisualStudio)' != 'true' OR '$(DeployOnBuild)' != 'true'">
|
||||
|
||||
<PropertyGroup>
|
||||
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@
|
|||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_OmitServiceWorkerContent" BeforeTargets="AssignTargetPaths">
|
||||
<Target Name="_OmitServiceWorkerContent" BeforeTargets="AssignTargetPaths; ResolveCurrentProjectStaticWebAssetsInputs">
|
||||
<ItemGroup>
|
||||
<!-- Don't emit the service worker source files to the output -->
|
||||
<Content Remove="@(ServiceWorker)" />
|
||||
|
|
@ -113,6 +113,7 @@
|
|||
<ContentSourcePath Condition="'%(ServiceWorker.PublishedContent)' == ''">%(ServiceWorker.Identity)</ContentSourcePath>
|
||||
<TargetOutputPath>%(ServiceWorker.Identity)</TargetOutputPath>
|
||||
<TargetOutputPath Condition="$([System.String]::Copy('%(ServiceWorker.Identity)').StartsWith('wwwroot\'))">$([System.String]::Copy('%(ServiceWorker.Identity)').Substring(8))</TargetOutputPath>
|
||||
<TargetOutputPath Condition="$([System.String]::Copy('%(ServiceWorker.Identity)').StartsWith('wwwroot/'))">$([System.String]::Copy('%(ServiceWorker.Identity)').Substring(8))</TargetOutputPath>
|
||||
</_ServiceWorkerIntermediateFile>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.IO.Compression;
|
|||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
|
@ -238,6 +239,23 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
}
|
||||
}
|
||||
|
||||
public static void FileHashEquals(MSBuildResult result, string filePath, string expectedSha256Base64)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
filePath = Path.Combine(result.Project.DirectoryPath, filePath);
|
||||
FileExists(result, filePath);
|
||||
|
||||
var actual = File.ReadAllBytes(filePath);
|
||||
using var algorithm = SHA256.Create();
|
||||
var actualSha256 = algorithm.ComputeHash(actual);
|
||||
var actualSha256Base64 = Convert.ToBase64String(actualSha256);
|
||||
Assert.Equal(expectedSha256Base64, actualSha256Base64);
|
||||
}
|
||||
|
||||
public static void FileEquals(MSBuildResult result, string expected, string actual)
|
||||
{
|
||||
if (result == null)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Blazor.Build;
|
||||
using Xunit;
|
||||
using ResourceHashesByNameDictionary = System.Collections.Generic.Dictionary<string, string>;
|
||||
using static Microsoft.AspNetCore.Components.WebAssembly.Build.WebAssemblyRuntimePackage;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||
|
|
@ -44,6 +50,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory);
|
||||
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
|
||||
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
|
||||
serviceWorkerContent: "// This is the production service worker",
|
||||
assetsManifestPath: "custom-service-worker-assets.js");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -81,6 +93,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory);
|
||||
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
|
||||
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
|
||||
serviceWorkerContent: "// This is the production service worker",
|
||||
assetsManifestPath: "custom-service-worker-assets.js");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -116,6 +134,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory);
|
||||
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
|
||||
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
|
||||
serviceWorkerContent: "// This is the production service worker",
|
||||
assetsManifestPath: "custom-service-worker-assets.js");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -145,6 +169,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
var bootJsonPath = Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json");
|
||||
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
|
||||
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
|
||||
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory);
|
||||
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
|
||||
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
|
||||
serviceWorkerContent: "// This is the production service worker",
|
||||
assetsManifestPath: "custom-service-worker-assets.js");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -178,6 +208,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory);
|
||||
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
|
||||
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
|
||||
serviceWorkerContent: "// This is the production service worker",
|
||||
assetsManifestPath: "custom-service-worker-assets.js");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -226,6 +262,12 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory);
|
||||
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
|
||||
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
|
||||
serviceWorkerContent: "// This is the production service worker",
|
||||
assetsManifestPath: "custom-service-worker-assets.js");
|
||||
|
||||
void AddSiblingProjectFileContent(string content)
|
||||
{
|
||||
var path = Path.Combine(project.SolutionPath, "standalone", "standalone.csproj");
|
||||
|
|
@ -269,6 +311,109 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
|||
|
||||
// Verify web.config
|
||||
Assert.FileExists(result, publishDirectory, "web.config");
|
||||
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory);
|
||||
VerifyServiceWorkerFiles(result, blazorPublishDirectory,
|
||||
serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
|
||||
serviceWorkerContent: "// This is the production service worker",
|
||||
assetsManifestPath: "custom-service-worker-assets.js");
|
||||
}
|
||||
|
||||
private static void VerifyBootManifestHashes(MSBuildResult result, string blazorPublishDirectory)
|
||||
{
|
||||
var bootManifestResolvedPath = Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json");
|
||||
var bootManifestJson = File.ReadAllText(bootManifestResolvedPath);
|
||||
var bootManifest = JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>(bootManifestJson);
|
||||
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory, bootManifest.resources.assembly, r => $"_framework/_bin/{r}");
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory, bootManifest.resources.runtime, r => $"_framework/wasm/{r}");
|
||||
|
||||
if (bootManifest.resources.pdb != null)
|
||||
{
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory, bootManifest.resources.pdb, r => $"_framework/_bin/{r}");
|
||||
}
|
||||
|
||||
if (bootManifest.resources.satelliteResources != null)
|
||||
{
|
||||
foreach (var resourcesForCulture in bootManifest.resources.satelliteResources.Values)
|
||||
{
|
||||
VerifyBootManifestHashes(result, blazorPublishDirectory, resourcesForCulture, r => $"_framework/_bin/{r}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void VerifyBootManifestHashes(MSBuildResult result, string blazorPublishDirectory, ResourceHashesByNameDictionary resources, Func<string, string> relativePathFunc)
|
||||
{
|
||||
foreach (var (name, hash) in resources)
|
||||
{
|
||||
var relativePath = Path.Combine(blazorPublishDirectory, relativePathFunc(name));
|
||||
Assert.FileHashEquals(result, relativePath, ParseWebFormattedHash(hash));
|
||||
}
|
||||
}
|
||||
|
||||
private static void VerifyServiceWorkerFiles(MSBuildResult result, string blazorPublishDirectory, string serviceWorkerPath, string serviceWorkerContent, string assetsManifestPath)
|
||||
{
|
||||
// Check the expected files are there
|
||||
var serviceWorkerResolvedPath = Assert.FileExists(result, blazorPublishDirectory, serviceWorkerPath);
|
||||
var assetsManifestResolvedPath = Assert.FileExists(result, blazorPublishDirectory, assetsManifestPath);
|
||||
|
||||
// Check the service worker contains the expected content (which comes from the PublishedContent file)
|
||||
Assert.FileContains(result, serviceWorkerResolvedPath, serviceWorkerContent);
|
||||
|
||||
// Check the assets manifest version was added to the published service worker
|
||||
var assetsManifest = ReadServiceWorkerAssetsManifest(assetsManifestResolvedPath);
|
||||
Assert.FileContains(result, serviceWorkerResolvedPath, $"/* Manifest version: {assetsManifest.version} */");
|
||||
|
||||
// Check the assets manifest contains correct entries for all static content we're publishing
|
||||
var resolvedPublishDirectory = Path.Combine(result.Project.DirectoryPath, blazorPublishDirectory);
|
||||
var publishedStaticFiles = Directory.GetFiles(resolvedPublishDirectory, "*", new EnumerationOptions { RecurseSubdirectories = true });
|
||||
var assetsManifestHashesByUrl = (IReadOnlyDictionary<string, string>)assetsManifest.assets.ToDictionary(x => x.url, x => x.hash);
|
||||
foreach (var publishedFilePath in publishedStaticFiles)
|
||||
{
|
||||
var publishedFileRelativePath = Path.GetRelativePath(resolvedPublishDirectory, publishedFilePath);
|
||||
|
||||
// We don't list compressed files in the SWAM, as these are transparent to the client,
|
||||
// nor do we list the service worker itself or its assets manifest, as these don't need to be fetched in the same way
|
||||
if (IsCompressedFile(publishedFileRelativePath)
|
||||
|| string.Equals(publishedFileRelativePath, serviceWorkerPath, StringComparison.Ordinal)
|
||||
|| string.Equals(publishedFileRelativePath, assetsManifestPath, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify hash
|
||||
var publishedFileUrl = publishedFileRelativePath.Replace('\\', '/');
|
||||
var expectedHash = ParseWebFormattedHash(assetsManifestHashesByUrl[publishedFileUrl]);
|
||||
Assert.Contains(publishedFileUrl, assetsManifestHashesByUrl);
|
||||
Assert.FileHashEquals(result, publishedFilePath, expectedHash);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ParseWebFormattedHash(string webFormattedHash)
|
||||
{
|
||||
Assert.StartsWith("sha256-", webFormattedHash);
|
||||
return webFormattedHash.Substring(7);
|
||||
}
|
||||
|
||||
private static bool IsCompressedFile(string path)
|
||||
{
|
||||
switch (Path.GetExtension(path))
|
||||
{
|
||||
case ".br":
|
||||
case ".gz":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static GenerateServiceWorkerAssetsManifest.AssetsManifestFile ReadServiceWorkerAssetsManifest(string assetsManifestResolvedPath)
|
||||
{
|
||||
var jsContents = File.ReadAllText(assetsManifestResolvedPath);
|
||||
var jsonStart = jsContents.IndexOf("{");
|
||||
var jsonLength = jsContents.LastIndexOf("}") - jsonStart + 1;
|
||||
var json = jsContents.Substring(jsonStart, jsonLength);
|
||||
return JsonSerializer.Deserialize<GenerateServiceWorkerAssetsManifest.AssetsManifestFile>(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="$(ReferenceBlazorBuildFromSourceProps)" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<RazorLangVersion>3.0</RazorLangVersion>
|
||||
<ServiceWorkerAssetsManifest>custom-service-worker-assets.js</ServiceWorkerAssetsManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Test Placeholder -->
|
||||
|
|
@ -32,6 +33,8 @@
|
|||
<!-- This asset should not be treated as a static web asset as it is being linked out of the wwwroot folder. -->
|
||||
<Content Update="wwwroot\Fake-License.txt" Link="Excluded-Static-Web-Assets\Fake-License.txt" />
|
||||
|
||||
<!-- The content from my-prod-service-worker.js should be published under the name my-service-worker.js -->
|
||||
<ServiceWorker Include="wwwroot\serviceworkers\my-service-worker.js" PublishedContent="wwwroot\serviceworkers\my-prod-service-worker.js" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
// This is the production service worker
|
||||
|
|
@ -0,0 +1 @@
|
|||
// This is the development service worker
|
||||
Loading…
Reference in New Issue