Merge pull request #18361 from dotnet-maestro-bot/merge/blazor-wasm-to-master

[automated] Merge branch 'blazor-wasm' => 'master'
This commit is contained in:
Justin Kotalik 2020-01-15 13:36:29 -08:00 committed by GitHub
commit 337b951c15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1100 additions and 268 deletions

View File

@ -66,7 +66,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Tasks
protected override string GenerateFullPathToTool() => DotNetPath;
protected override string GenerateCommandLineCommands() => ILLinkPath;
protected override string GenerateCommandLineCommands()
{
var args = new StringBuilder();
args.Append(Quote(ILLinkPath));
return args.ToString();
}
private static string Quote(string path)
{

View File

@ -1,6 +1,7 @@
// 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.IO;
using System.Linq;
using System.Reflection;
@ -27,12 +28,23 @@ namespace Microsoft.AspNetCore.Blazor.Build
public override bool Execute()
{
var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name;
var assemblies = References.Select(c => Path.GetFileName(c.ItemSpec)).ToArray();
var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray();
using var fileStream = File.Create(OutputPath);
WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled);
return true;
static string GetUriPath(ITaskItem item)
{
var outputPath = item.GetMetadata("RelativeOutputPath");
if (string.IsNullOrEmpty(outputPath))
{
outputPath = Path.GetFileName(item.ItemSpec);
}
return outputPath.Replace('\\', '/');
}
}
internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled)

View File

@ -74,7 +74,7 @@
</ItemGroup>
</Target>
<Target Name="_ResolveBlazorInputs">
<Target Name="_ResolveBlazorInputs" DependsOnTargets="ResolveReferences;ResolveRuntimePackAssets">
<PropertyGroup>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor -->
<BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</BlazorIntermediateOutputPath>
@ -82,6 +82,8 @@
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml -->
<GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor>
<_TypeGranularityLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml</_TypeGranularityLinkerDescriptor>
<!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ -->
<BlazorIntermediateLinkerOutputPath>$(BlazorIntermediateOutputPath)linker/</BlazorIntermediateLinkerOutputPath>
@ -94,8 +96,6 @@
</PropertyGroup>
<ItemGroup>
<_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" />
<_WebAssemblyBCLFolder Include="
$(DotNetWebAssemblyBCLPath);
$(DotNetWebAssemblyBCLFacadesPath);
@ -104,6 +104,22 @@
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
</ItemGroup>
<!--
Calculate the assemblies that act as inputs to calculate assembly closure. Based on _ComputeAssembliesToPostprocessOnPublish which is used as input to SDK's linker
https://github.com/dotnet/sdk/blob/d597e7b09d7657ba4e326d6734e14fcbf8473564/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets#L864-L873
-->
<ItemGroup>
<!-- Assemblies from packages -->
<_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" />
<!-- Assemblies from other references -->
<_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" />
<_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" />
<_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" />
<_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" />
</ItemGroup>
<MakeDir Directories="$(BlazorIntermediateOutputPath)" />
</Target>
@ -111,6 +127,27 @@
<Error
Message="Unrecongnized value for BlazorLinkOnBuild: '$(BlazorLinkOnBuild)'. Valid values are 'true' or 'false'."
Condition="'$(BlazorLinkOnBuild)' != 'true' AND '$(BlazorLinkOnBuild)' != 'false'" />
<ItemGroup>
<!--
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set.
-->
<_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" />
<_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
<BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
<BlazorRuntimeFile>true</BlazorRuntimeFile>
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath>
<RelativeOutputPath>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</RelativeOutputPath>
</BlazorOutputWithTargetPath>
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)">
<BlazorRuntimeFile>true</BlazorRuntimeFile>
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
<RelativeOutputPath>%(FileName)%(Extension)</RelativeOutputPath>
</BlazorOutputWithTargetPath>
</ItemGroup>
</Target>
<!--
@ -124,18 +161,34 @@
<Target
Name="_ResolveBlazorOutputsWhenLinked"
Condition="'$(BlazorLinkOnBuild)' == 'true'"
DependsOnTargets="_GenerateBlazorLinkerDescriptor;_LinkBlazorApplication">
DependsOnTargets="_PrepareBlazorLinkerInputs;_GenerateBlazorLinkerDescriptor;_GenerateTypeGranularLinkerDescriptor;_LinkBlazorApplication">
<!-- _BlazorLinkerOutputCache records files linked during the last incremental build of the target. Read the contents and assign linked files to be copied to the output. -->
<ReadLinesFromFile File="$(_BlazorLinkerOutputCache)">
<Output TaskParameter="Lines" ItemName="_BlazorLinkedFile"/>
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
</ReadLinesFromFile>
</Target>
<Target Name="_PrepareBlazorLinkerInputs">
<ItemGroup>
<BlazorOutputWithTargetPath Include="%(_BlazorLinkedFile.Identity)">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</BlazorOutputWithTargetPath>
<_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" />
<!--
Any assembly from a package reference that starts with System. file name is allowed to be linked.
Assemblies from Microsoft.AspNetCore and Microsoft.Extensions, are also linked but with TypeGranularity.
-->
<_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
<_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
<_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" />
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
<_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" />
<_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" />
</ItemGroup>
</Target>
<UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" />
@ -157,31 +210,30 @@
</ItemGroup>
</Target>
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" />
<UsingTask TaskName="GenerateTypeGranularityLinkingConfig" AssemblyFile="$(BlazorTasksPath)" />
<Target Name="_GenerateTypeGranularLinkerDescriptor"
Inputs="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
Outputs="$(_TypeGranularityLinkerDescriptor)">
<GenerateTypeGranularityLinkingConfig
Assemblies="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))"
OutputPath="$(_TypeGranularityLinkerDescriptor)" />
<ItemGroup>
<BlazorLinkerDescriptor Include="$(_TypeGranularityLinkerDescriptor)" />
<FileWrites Include="$(_TypeGranularityLinkerDescriptor)" />
</ItemGroup>
</Target>
<UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_LinkBlazorApplication"
Inputs="$(ProjectAssetsFile);
@(IntermediateAssembly);
@(_BlazorDependencyInput);
@(_BlazorManagedRuntimeAssemby);
@(BlazorLinkerDescriptor);
$(MSBuildAllProjects)"
Outputs="$(_BlazorLinkerOutputCache)">
<ItemGroup>
<_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)" />
<_BlazorDependencyAssembly IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
<_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
<_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
<_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
<_BlazorAssemblyToLink Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" />
<_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
<_BlazorLinkerRoot Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" />
</ItemGroup>
<PropertyGroup>
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions>
</PropertyGroup>
@ -203,15 +255,6 @@
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName>
</PropertyGroup>
<PropertyGroup>
<_TypeGranularityLinkingConfig>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml</_TypeGranularityLinkingConfig>
</PropertyGroup>
<GenerateTypeGranularityLinkingConfig Assemblies="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))" OutputPath="$(_TypeGranularityLinkingConfig)" />
<ItemGroup>
<BlazorLinkerDescriptor Include="$(_TypeGranularityLinkingConfig)" />
<FileWrites Include="$(_TypeGranularityLinkingConfig)" />
</ItemGroup>
<BlazorILLink
ILLinkPath="$(MonoLinkerPath)"
AssemblyPaths="@(_BlazorAssemblyToLink)"
@ -230,29 +273,22 @@
<WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" />
</Target>
<UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" />
<Target
Name="_ResolveBlazorOutputsWhenNotLinked"
DependsOnTargets="_ResolveBlazorRuntimeDependencies"
Condition="'$(BlazorLinkOnBuild)' != 'true'">
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedRuntimeDependencies->Count())' == '0'">
<Output TaskParameter="Lines" ItemName="_BlazorResolvedRuntimeDependencies"/>
<ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedAssembly->Count())' == '0'">
<Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/>
</ReadLinesFromFile>
<ItemGroup>
<BlazorOutputWithTargetPath Include="@(_BlazorResolvedRuntimeDependencies)">
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
</BlazorOutputWithTargetPath>
</ItemGroup>
</Target>
<Target
Name="_ResolveBlazorRuntimeDependencies"
Inputs="$(ProjectAssetsFile);
@(IntermediateAssembly);
@(_BlazorDependencyInput)"
@(_BlazorManagedRuntimeAssemby)"
Outputs="$(_BlazorApplicationAssembliesCacheFile)">
<!--
@ -262,10 +298,10 @@
-->
<ResolveBlazorRuntimeDependencies
EntryPoint="@(IntermediateAssembly)"
ApplicationDependencies="@(_BlazorDependencyInput)"
ApplicationDependencies="@(_BlazorManagedRuntimeAssemby)"
WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)">
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedRuntimeDependencies" />
<Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssembly" />
</ResolveBlazorRuntimeDependencies>
<WriteLinesToFile File="$(_BlazorApplicationAssembliesCacheFile)" Lines="@(_BlazorResolvedRuntimeDependencies)" Overwrite="true" />
@ -282,13 +318,12 @@
Inputs="@(BlazorOutputWithTargetPath)"
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
<ItemGroup>
<_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.dll'))" />
<_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.pdb'))" Condition="'$(BlazorEnableDebugging)' == 'true'" />
<_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" />
</ItemGroup>
<GenerateBlazorBootJson
AssemblyPath="@(IntermediateAssembly)"
References="@(_AppReferences)"
References="@(_BlazorRuntimeFile)"
LinkerEnabled="$(BlazorLinkOnBuild)"
OutputPath="$(BlazorBootJsonIntermediateOutputPath)" />

View File

@ -70,5 +70,66 @@ namespace Microsoft.AspNetCore.Blazor.Build
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
}
[Fact]
public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
[Fact]
public async Task Build_WithBlazorLinkOnBuildFalse_SatelliteAssembliesAreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
Assert.BuildPassed(result);
var buildOutputDirectory = project.BuildOutputDirectory;
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
}
}

View File

@ -112,6 +112,35 @@ namespace Microsoft.AspNetCore.Blazor.Build
Assert.FileExists(result, publishDirectory, "web.config");
}
[Fact]
public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput()
{
// Arrange
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
project.AddProjectFileContent(
@"
<PropertyGroup>
<DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" />
</ItemGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore");
Assert.BuildPassed(result);
var publishDirectory = project.PublishOutputDirectory;
var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
var bootJsonPath = Path.Combine(blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
}
[Fact]
public async Task Publish_HostedApp_Works()
{

View File

@ -0,0 +1,12 @@
using System;
namespace classlibrarywithsatelliteassemblies
{
public class Class1
{
public static void Test()
{
GC.KeepAlive(typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation));
}
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RazorLangVersion>3.0</RazorLangVersion>
</PropertyGroup>
<ItemGroup>
<!-- The compiler package contains quite a few satellite assemblies -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
</ItemGroup>
</Project>

View File

@ -1,10 +1,14 @@

using System;
namespace standalone
{
public class Program
{
public static void Main(string[] args)
{
#if REFERENCE_classlibrarywithsatelliteassemblies
GC.KeepAlive(typeof(classlibrarywithsatelliteassemblies.Class1));
#endif
}
}
}

View File

@ -1,6 +0,0 @@
import { HtmlUI } from './lib/minibench/minibench.js';
import './appStartup.js';
import './renderList.js';
import './jsonHandling.js';
new HtmlUI('E2E Performance', '#display');

View File

@ -21,8 +21,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DevServer", "Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.E2EPerformance", "Blazor\testassets\Microsoft.AspNetCore.Blazor.E2EPerformance\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj", "{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Server", "Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{A7ABAC29-F73F-456D-AE54-46842CFC2E10}"
@ -238,8 +236,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor", "Ignitor\src\Igni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Validation", "Validation", "{FD9BD646-9D50-42ED-A3E1-90558BA0C6B2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation", "Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", "{B70F90C7-2696-4050-B24E-BF0308F4E059}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests", "Blazor\Validation\test\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", "{A5617A9D-C71E-44DE-936C-27611EB40A02}"
@ -250,6 +246,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -344,18 +348,6 @@ Global
{A6C8050D-7C18-4585-ADCF-833AC1765847}.Release|x64.Build.0 = Release|Any CPU
{A6C8050D-7C18-4585-ADCF-833AC1765847}.Release|x86.ActiveCfg = Release|Any CPU
{A6C8050D-7C18-4585-ADCF-833AC1765847}.Release|x86.Build.0 = Release|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x64.ActiveCfg = Debug|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x64.Build.0 = Debug|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x86.ActiveCfg = Debug|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x86.Build.0 = Debug|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|Any CPU.Build.0 = Release|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x64.ActiveCfg = Release|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x64.Build.0 = Release|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x86.ActiveCfg = Release|Any CPU
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x86.Build.0 = Release|Any CPU
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -1532,6 +1524,30 @@ Global
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x64.Build.0 = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x86.ActiveCfg = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x86.Build.0 = Release|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x64.ActiveCfg = Debug|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x64.Build.0 = Debug|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x86.ActiveCfg = Debug|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x86.Build.0 = Debug|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|Any CPU.Build.0 = Release|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x64.ActiveCfg = Release|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x64.Build.0 = Release|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x86.ActiveCfg = Release|Any CPU
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x86.Build.0 = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x64.ActiveCfg = Debug|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x64.Build.0 = Debug|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x86.ActiveCfg = Debug|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x86.Build.0 = Debug|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|Any CPU.Build.0 = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.ActiveCfg = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.Build.0 = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.ActiveCfg = Release|Any CPU
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1544,7 +1560,6 @@ Global
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A7ABAC29-F73F-456D-AE54-46842CFC2E10} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{FD37F740-A654-4117-BFB6-9112CE4C1D3B} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
@ -1641,12 +1656,14 @@ Global
{BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED}
{CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
{FD9BD646-9D50-42ED-A3E1-90558BA0C6B2} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{B70F90C7-2696-4050-B24E-BF0308F4E059} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{D141CFEE-D10A-406B-8963-F86FA13732E3} = {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}
{F2E27E1C-2E47-42C1-9AC7-36265A381717} = {44E0D4F3-4430-4175-B482-0D1AEE4BB699}
{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} = {CCC82E97-7B58-43E2-BBBD-23D82F926367}
{CA9948CA-B3FA-4C2E-A726-5E47BAD19457} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}

View File

@ -13,14 +13,12 @@
"Blazor\\DevServer\\src\\Microsoft.AspNetCore.Blazor.DevServer.csproj",
"Blazor\\Http\\src\\Microsoft.AspNetCore.Blazor.HttpClient.csproj",
"Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj",
"Blazor\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj",
"Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj",
"Blazor\\Templates\\src\\Microsoft.AspNetCore.Blazor.Templates.csproj",
"Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj",
"Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj",
"Blazor\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj",
"Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
"Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
"Blazor\\testassets\\Microsoft.AspNetCore.Blazor.E2EPerformance\\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj",
"Blazor\\testassets\\MonoSanityClient\\MonoSanityClient.csproj",
"Blazor\\testassets\\MonoSanity\\MonoSanity.csproj",
"Blazor\\testassets\\StandaloneApp\\StandaloneApp.csproj",
@ -36,6 +34,8 @@
"Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj",
"Web\\src\\Microsoft.AspNetCore.Components.Web.csproj",
"Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj",
"benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
"benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
"test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
"test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
"test\\testassets\\TestContentPackage\\TestContentPackage.csproj",

View File

@ -0,0 +1,14 @@
// 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;
namespace Wasm.Performance.Driver
{
internal class BenchmarkMeasurement
{
public DateTime Timestamp { get; internal set; }
public string Name { get; internal set; }
public double Value { get; internal set; }
}
}

View File

@ -0,0 +1,14 @@
// 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.
namespace Wasm.Performance.Driver
{
internal class BenchmarkMetadata
{
public string Source { get; set; }
public string Name { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
public string Format { get; set; }
}
}

View File

@ -0,0 +1,14 @@
// 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.Collections.Generic;
namespace Wasm.Performance.Driver
{
internal class BenchmarkOutput
{
public List<BenchmarkMetadata> Metadata { get; } = new List<BenchmarkMetadata>();
public List<BenchmarkMeasurement> Measurements { get; } = new List<BenchmarkMeasurement>();
}
}

View File

@ -0,0 +1,13 @@
namespace Wasm.Performance.Driver
{
class BenchmarkResult
{
public string Name { get; set; }
public bool Success { get; set; }
public int NumExecutions { get; set; }
public double Duration { get; set; }
}
}

View File

@ -0,0 +1,37 @@
// 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.Collections.Generic;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Wasm.Performance.Driver
{
public class BenchmarkDriverStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(c => c.AddDefaultPolicy(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
}
public void Configure(IApplicationBuilder app)
{
app.UseCors();
app.Run(async context =>
{
var result = await JsonSerializer.DeserializeAsync<List<BenchmarkResult>>(context.Request.Body, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
await context.Response.WriteAsync("OK");
Program.SetBenchmarkResult(result);
});
}
}
}

View File

@ -0,0 +1,232 @@
// 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.Runtime.ExceptionServices;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using DevHostServerProgram = Microsoft.AspNetCore.Blazor.DevServer.Server.Program;
namespace Wasm.Performance.Driver
{
public class Program
{
static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3);
static TaskCompletionSource<List<BenchmarkResult>> benchmarkResult = new TaskCompletionSource<List<BenchmarkResult>>();
public static async Task<int> Main(string[] args)
{
var seleniumPort = 4444;
if (args.Length > 0)
{
if (!int.TryParse(args[0], out seleniumPort))
{
Console.Error.WriteLine("Usage Driver <selenium-port>");
return 1;
}
}
// This write is required for the benchmarking infrastructure.
Console.WriteLine("Application started.");
var cancellationToken = new CancellationTokenSource(Timeout);
cancellationToken.Token.Register(() => benchmarkResult.TrySetException(new TimeoutException($"Timed out after {Timeout}")));
using var browser = await Selenium.CreateBrowser(seleniumPort, cancellationToken.Token);
using var testApp = StartTestApp();
using var benchmarkReceiver = StartBenchmarkResultReceiver();
var testAppUrl = GetListeningUrl(testApp);
var receiverUrl = GetListeningUrl(benchmarkReceiver);
Console.WriteLine($"Test app listening at {testAppUrl}.");
var launchUrl = $"{testAppUrl}?resultsUrl={UrlEncoder.Default.Encode(receiverUrl)}#automated";
browser.Url = launchUrl;
browser.Navigate();
var results = await benchmarkResult.Task;
FormatAsBenchmarksOutput(results);
Console.WriteLine("Done executing benchmark");
return 0;
}
internal static void SetBenchmarkResult(List<BenchmarkResult> result)
{
benchmarkResult.TrySetResult(result);
}
private static void FormatAsBenchmarksOutput(List<BenchmarkResult> results)
{
// Sample of the the format: https://github.com/aspnet/Benchmarks/blob/e55f9e0312a7dd019d1268c1a547d1863f0c7237/src/Benchmarks/Program.cs#L51-L67
var output = new BenchmarkOutput();
foreach (var result in results)
{
output.Metadata.Add(new BenchmarkMetadata
{
Source = "BlazorWasm",
Name = result.Name,
ShortDescription = $"{result.Name} Duration",
LongDescription = $"{result.Name} Duration",
Format = "n2"
});
output.Measurements.Add(new BenchmarkMeasurement
{
Timestamp = DateTime.UtcNow,
Name = result.Name,
Value = result.Duration,
});
}
// Statistics about publish sizes
output.Metadata.Add(new BenchmarkMetadata
{
Source = "BlazorWasm",
Name = "Publish size",
ShortDescription = "Publish size (KB)",
LongDescription = "Publish size (KB)",
Format = "n2",
});
var testAssembly = typeof(TestApp.Startup).Assembly;
var testAssemblyLocation = new FileInfo(testAssembly.Location);
var testApp = new DirectoryInfo(Path.Combine(
testAssemblyLocation.Directory.FullName,
testAssembly.GetName().Name));
output.Measurements.Add(new BenchmarkMeasurement
{
Timestamp = DateTime.UtcNow,
Name = "Publish size",
Value = GetDirectorySize(testApp) / 1024,
});
output.Metadata.Add(new BenchmarkMetadata
{
Source = "BlazorWasm",
Name = "Publish size (compressed)",
ShortDescription = "Publish size compressed app (KB)",
LongDescription = "Publish size - compressed app (KB)",
Format = "n2",
});
var gzip = new FileInfo(Path.Combine(
testAssemblyLocation.Directory.FullName,
$"{testAssembly.GetName().Name}.gzip"));
output.Measurements.Add(new BenchmarkMeasurement
{
Timestamp = DateTime.UtcNow,
Name = "Publish size (compressed)",
Value = (gzip.Exists ? gzip.Length : 0) / 1024,
});
Console.WriteLine("#StartJobStatistics");
Console.WriteLine(JsonSerializer.Serialize(output));
Console.WriteLine("#EndJobStatistics");
}
static IHost StartTestApp()
{
var args = new[]
{
"--urls", "http://127.0.0.1:0",
"--applicationpath", typeof(TestApp.Startup).Assembly.Location,
};
var host = DevHostServerProgram.BuildWebHost(args);
RunInBackgroundThread(host.Start);
return host;
}
static IHost StartBenchmarkResultReceiver()
{
var args = new[]
{
"--urls", "http://127.0.0.1:0",
};
var host = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder => builder.UseStartup<BenchmarkDriverStartup>())
.Build();
RunInBackgroundThread(host.Start);
return host;
}
static void RunInBackgroundThread(Action action)
{
var isDone = new ManualResetEvent(false);
ExceptionDispatchInfo edi = null;
Task.Run(() =>
{
try
{
action();
}
catch (Exception ex)
{
edi = ExceptionDispatchInfo.Capture(ex);
}
isDone.Set();
});
if (!isDone.WaitOne(Timeout))
{
throw new TimeoutException("Timed out waiting for: " + action);
}
if (edi != null)
{
throw edi.SourceException;
}
}
static string GetListeningUrl(IHost testApp)
{
return testApp.Services.GetRequiredService<IServer>()
.Features
.Get<IServerAddressesFeature>()
.Addresses
.First();
}
static long GetDirectorySize(DirectoryInfo directory)
{
// This can happen if you run the app without publishing it.
if (!directory.Exists)
{
return 0;
}
long size = 0;
foreach (var item in directory.EnumerateFileSystemInfos())
{
if (item is FileInfo fileInfo)
{
size += fileInfo.Length;
}
else if (item is DirectoryInfo directoryInfo)
{
size += GetDirectorySize(directoryInfo);
}
}
return size;
}
}
}

View File

@ -0,0 +1,121 @@
// 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.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
namespace Wasm.Performance.Driver
{
class Selenium
{
static bool RunHeadlessBrowser = true;
static bool PoolForBrowserLogs = true;
private static async ValueTask<Uri> WaitForServerAsync(int port, CancellationToken cancellationToken)
{
var uri = new UriBuilder("http", "localhost", port, "/wd/hub/").Uri;
var httpClient = new HttpClient
{
BaseAddress = uri,
Timeout = TimeSpan.FromSeconds(1),
};
Console.WriteLine($"Attempting to connect to Selenium Server running at {uri}");
const int MaxRetries = 30;
var retries = 0;
while (retries < MaxRetries)
{
retries++;
try
{
var response = (await httpClient.GetAsync("status", cancellationToken)).EnsureSuccessStatusCode();
Console.WriteLine("Connected to Selenium");
return uri;
}
catch
{
if (retries == 1)
{
Console.WriteLine("Could not connect to selenium-server. Has it been started as yet?");
}
}
await Task.Delay(1000);
}
throw new Exception($"Unable to connect to selenium-server at {uri}");
}
public static async Task<RemoteWebDriver> CreateBrowser(int port, CancellationToken cancellationToken)
{
var uri = await WaitForServerAsync(port, cancellationToken);
var options = new ChromeOptions();
if (RunHeadlessBrowser)
{
options.AddArgument("--headless");
}
options.SetLoggingPreference(LogType.Browser, LogLevel.All);
var attempt = 0;
const int MaxAttempts = 3;
do
{
try
{
// The driver opens the browser window and tries to connect to it on the constructor.
// Under heavy load, this can cause issues
// To prevent this we let the client attempt several times to connect to the server, increasing
// the max allowed timeout for a command on each attempt linearly.
var driver = new RemoteWebDriver(
uri,
options.ToCapabilities(),
TimeSpan.FromSeconds(60).Add(TimeSpan.FromSeconds(attempt * 60)));
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1);
if (PoolForBrowserLogs)
{
// Run in background.
var logs = new RemoteLogs(driver);
_ = Task.Run(async () =>
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(3));
var consoleLogs = logs.GetLog(LogType.Browser);
foreach (var entry in consoleLogs)
{
Console.WriteLine($"[Browser Log]: {entry.Timestamp}: {entry.Message}");
}
}
});
}
return driver;
}
catch (Exception ex)
{
Console.WriteLine($"Error initializing RemoteWebDriver: {ex.Message}");
}
attempt++;
} while (attempt < MaxAttempts);
throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive");
}
}
}

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Intentionally pinned this to .NET Core 3.1 since that's the supported version in the docker image -->
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference>
<OutputType>exe</OutputType>
<!-- WebDriver is not strong-named, so this test project cannot be strong named either. -->
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>
<Reference Include="Selenium.Support" />
<Reference Include="Selenium.WebDriver" />
<ProjectReference Include="..\..\..\Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj" />
<ProjectReference Include="..\TestApp\Wasm.Performance.TestApp.csproj" />
<Content Include="appsettings.json" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}

View File

@ -0,0 +1,20 @@
## Blazor WASM benchmarks
These projects assist in Benchmarking Components.
See https://github.com/aspnet/Benchmarks#benchmarks for usage guidance on using the Benchmarking tool with your application
### Running the benchmarks
The TestApp is a regular BlazorWASM project and can be run using `dotnet run`. The Driver is an app that connects against an existing Selenium server, and speaks the Benchmark protocol. You generally do not need to run the Driver locally, but if you were to do so, you can either start a selenium-server instance and run using `dotnet run [<selenium-server-port>]` or run it inside a Linux-based docker container.
Here are the commands you would need to run it locally inside docker:
1. `dotnet publish -c Release -r linux-x64 Driver/Wasm.Performance.Driver.csproj`
2. `docker build -t blazor-local -f ./local.dockerfile . `
3. `docker run -it blazor-local`
To run the benchmark app in the Benchmark server, run
```
dotnet run -- --config aspnetcore/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json application.endpoints <BenchmarkServerUri> --scenario blazorwasmbenchmark
```

View File

@ -3,7 +3,7 @@
using Microsoft.JSInterop;
namespace Microsoft.AspNetCore.Blazor.E2EPerformance
namespace Wasm.Performance.TestApp
{
public static class BenchmarkEvent
{

View File

@ -3,7 +3,7 @@
using Microsoft.AspNetCore.Blazor.Hosting;
namespace Microsoft.AspNetCore.Blazor.E2EPerformance
namespace Wasm.Performance.TestApp
{
public class Program
{

View File

@ -4,7 +4,7 @@
using Microsoft.AspNetCore.Components.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Blazor.E2EPerformance
namespace Wasm.Performance.TestApp
{
public class Startup
{

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>

View File

@ -2,5 +2,5 @@
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Blazor.E2EPerformance
@using Microsoft.AspNetCore.Blazor.E2EPerformance.Shared
@using Wasm.Performance.TestApp
@using Wasm.Performance.TestApp.Shared

View File

@ -0,0 +1,39 @@
import { groups, BenchmarkEvent, onBenchmarkEvent } from './lib/minibench/minibench.js';
import { HtmlUI } from './lib/minibench/minibench.ui.js';
import './appStartup.js';
import './renderList.js';
import './jsonHandling.js';
new HtmlUI('E2E Performance', '#display');
if (location.href.indexOf('#automated') !== -1) {
const query = new URLSearchParams(window.location.search);
const group = query.get('group');
const resultsUrl = query.get('resultsUrl');
groups.filter(g => !group || g.name === group).forEach(g => g.runAll());
const benchmarksResults = [];
onBenchmarkEvent(async (status, args) => {
switch (status) {
case BenchmarkEvent.runStarted:
benchmarksResults.length = 0;
break;
case BenchmarkEvent.benchmarkCompleted:
case BenchmarkEvent.benchmarkError:
console.log(`Completed benchmark ${args.name}`);
benchmarksResults.push(args);
break;
case BenchmarkEvent.runCompleted:
if (resultsUrl) {
await fetch(resultsUrl, {
method: 'post',
body: JSON.stringify(benchmarksResults)
});
}
break;
default:
throw new Error(`Unknown status: ${status}`);
}
})
}

View File

@ -66,7 +66,7 @@ window.addEventListener('message', evt => {
To work around browsers' current nonsupport for high-resolution timers
(since Spectre etc.), the approach used here is to group executions into
blocks of roughly fixed duration.
- In each block, we execute the test code as many times as we can until
the end of the block duration, without even yielding the thread if
it's a synchronous call. We count how many executions completed. It
@ -82,7 +82,7 @@ window.addEventListener('message', evt => {
during which there was no unrelated GC cycle or other background contention.
- We keep running blocks until some larger timeout occurs *and* we've done
at least some minimum number of executions.
Note that this approach does *not* allow for per-execution setup/teardown
logic whose timing is separated from the code under test. Because of the
low timer precision, there would be no way to separate the setup duration
@ -174,10 +174,23 @@ class Benchmark extends EventEmitter {
}
run(runOptions) {
if (reportBenchmarkEvent) {
const areAllIdle = groups.reduce(
(prev, next) => prev && next.status === BenchmarkStatus.idle,
true
);
if (areAllIdle) {
// This is the first test being run from the idle state
reportBenchmarkEvent(BenchmarkEvent.runStarted);
}
}
this._currentRunWasAborted = false;
if (this._state.status === BenchmarkStatus.idle) {
this._updateState({ status: BenchmarkStatus.queued });
this.workQueueCancelHandle = addToWorkQueue(async () => {
try {
if (!(runOptions && runOptions.skipGroupSetup)) {
await this._group.runSetup();
@ -192,10 +205,13 @@ class Benchmark extends EventEmitter {
await this._group.runTeardown();
}
reportBenchmarkEvent(BenchmarkEvent.benchmarkCompleted, { 'name': this.name, success: true, numExecutions: this._state.numExecutions, duration: this._state.estimatedExecutionDurationMs });
this._updateState({ status: BenchmarkStatus.idle });
} catch (ex) {
this._updateState({ status: BenchmarkStatus.error });
console.error(ex);
reportBenchmarkEvent(BenchmarkEvent.benchmarkError, { 'name': this.name, success: false });
}
});
}
@ -237,6 +253,13 @@ const BenchmarkStatus = {
error: 3,
};
const BenchmarkEvent = {
runStarted: 0,
benchmarkCompleted : 1,
benchmarkError: 2,
runCompleted: 3,
}
class Group extends EventEmitter {
constructor(name) {
super();
@ -279,6 +302,7 @@ class Group extends EventEmitter {
}
const groups = [];
let reportBenchmarkEvent = () => {};
function group(name, configure) {
groups.push(new Group(name));
@ -298,184 +322,21 @@ function teardown(fn) {
groups[groups.length - 1].teardown = fn;
}
class BenchmarkDisplay {
constructor(htmlUi, benchmark) {
this.benchmark = benchmark;
this.elem = document.createElement('tr');
const headerCol = this.elem.appendChild(document.createElement('th'));
headerCol.className = 'pl-4';
headerCol.textContent = benchmark.name;
headerCol.setAttribute('scope', 'row');
function onBenchmarkEvent(fn) {
reportBenchmarkEvent = fn;
const progressCol = this.elem.appendChild(document.createElement('td'));
this.numExecutionsText = progressCol.appendChild(document.createTextNode(''));
groups.forEach(group$$1 => {
group$$1.on('changed', () => {
const areAllIdle = groups.reduce(
(prev, next) => prev && next.status === BenchmarkStatus.idle,
true
);
const timingCol = this.elem.appendChild(document.createElement('td'));
this.executionDurationText = timingCol.appendChild(document.createElement('span'));
const runCol = this.elem.appendChild(document.createElement('td'));
runCol.className = 'pr-4';
runCol.setAttribute('align', 'right');
this.runButton = document.createElement('a');
this.runButton.className = 'run-button';
runCol.appendChild(this.runButton);
this.runButton.textContent = 'Run';
this.runButton.onclick = evt => {
evt.preventDefault();
this.benchmark.run(htmlUi.globalRunOptions);
};
benchmark.on('changed', state => this.updateDisplay(state));
this.updateDisplay(this.benchmark.state);
}
updateDisplay(state) {
const benchmark = this.benchmark;
this.elem.className = rowClass(state.status);
this.runButton.textContent = runButtonText(state.status);
this.numExecutionsText.textContent = state.numExecutions
? `Executions: ${state.numExecutions}` : '';
this.executionDurationText.innerHTML = state.estimatedExecutionDurationMs
? `Duration: <b>${parseFloat(state.estimatedExecutionDurationMs.toPrecision(3))}ms</b>` : '';
if (state.status === BenchmarkStatus.idle) {
this.runButton.setAttribute('href', '');
} else {
this.runButton.removeAttribute('href');
if (state.status === BenchmarkStatus.error) {
this.numExecutionsText.textContent = 'Error - see console';
if (areAllIdle) {
fn(BenchmarkEvent.runCompleted);
}
}
}
}
function runButtonText(status) {
switch (status) {
case BenchmarkStatus.idle:
case BenchmarkStatus.error:
return 'Run';
case BenchmarkStatus.queued:
return 'Waiting...';
case BenchmarkStatus.running:
return 'Running...';
default:
throw new Error(`Unknown status: ${status}`);
}
}
function rowClass(status) {
switch (status) {
case BenchmarkStatus.idle:
return 'benchmark-idle';
case BenchmarkStatus.queued:
return 'benchmark-waiting';
case BenchmarkStatus.running:
return 'benchmark-running';
case BenchmarkStatus.error:
return 'benchmark-error';
default:
throw new Error(`Unknown status: ${status}`);
}
}
class GroupDisplay {
constructor(htmlUi, group) {
this.group = group;
this.elem = document.createElement('div');
this.elem.className = 'my-3 py-2 bg-white rounded shadow-sm';
const headerContainer = this.elem.appendChild(document.createElement('div'));
headerContainer.className = 'd-flex align-items-baseline px-4';
const header = headerContainer.appendChild(document.createElement('h5'));
header.className = 'py-2';
header.textContent = group.name;
this.runButton = document.createElement('a');
this.runButton.className = 'ml-auto run-button';
this.runButton.setAttribute('href', '');
headerContainer.appendChild(this.runButton);
this.runButton.textContent = 'Run all';
this.runButton.onclick = evt => {
evt.preventDefault();
group.runAll(htmlUi.globalRunOptions);
};
const table = this.elem.appendChild(document.createElement('table'));
table.className = 'table mb-0 benchmarks';
const tbody = table.appendChild(document.createElement('tbody'));
group.benchmarks.forEach(benchmark => {
const benchmarkDisplay = new BenchmarkDisplay(htmlUi, benchmark);
tbody.appendChild(benchmarkDisplay.elem);
});
group.on('changed', () => this.updateDisplay());
this.updateDisplay();
}
updateDisplay() {
const canRun = this.group.status === BenchmarkStatus.idle;
this.runButton.style.display = canRun ? 'block' : 'none';
}
}
class HtmlUI {
constructor(title, selector) {
this.containerElement = document.querySelector(selector);
const headerDiv = this.containerElement.appendChild(document.createElement('div'));
headerDiv.className = 'd-flex align-items-center';
const header = headerDiv.appendChild(document.createElement('h2'));
header.className = 'mx-3 flex-grow-1';
header.textContent = title;
const verifyCheckboxLabel = document.createElement('label');
verifyCheckboxLabel.className = 'ml-auto mr-5';
headerDiv.appendChild(verifyCheckboxLabel);
this.verifyCheckbox = verifyCheckboxLabel.appendChild(document.createElement('input'));
this.verifyCheckbox.type = 'checkbox';
this.verifyCheckbox.className = 'mr-2';
verifyCheckboxLabel.appendChild(document.createTextNode('Verify only'));
this.runButton = document.createElement('button');
this.runButton.className = 'btn btn-success ml-auto px-4 run-button';
headerDiv.appendChild(this.runButton);
this.runButton.textContent = 'Run all';
this.runButton.onclick = () => {
groups.forEach(g => g.runAll(this.globalRunOptions));
};
this.stopButton = document.createElement('button');
this.stopButton.className = 'btn btn-danger ml-auto px-4 stop-button';
headerDiv.appendChild(this.stopButton);
this.stopButton.textContent = 'Stop';
this.stopButton.onclick = () => {
groups.forEach(g => g.stopAll());
};
groups.forEach(group$$1 => {
const groupDisplay = new GroupDisplay(this, group$$1);
this.containerElement.appendChild(groupDisplay.elem);
group$$1.on('changed', () => this.updateDisplay());
});
this.updateDisplay();
}
updateDisplay() {
const areAllIdle = groups.reduce(
(prev, next) => prev && next.status === BenchmarkStatus.idle,
true
);
this.runButton.style.display = areAllIdle ? 'block' : 'none';
this.stopButton.style.display = areAllIdle ? 'none' : 'block';
}
get globalRunOptions() {
return { verifyOnly: this.verifyCheckbox.checked };
}
});
}
/**
@ -483,4 +344,4 @@ class HtmlUI {
* https://github.com/SteveSanderson/minibench
*/
export { group, benchmark, setup, teardown, HtmlUI };
export { groups, group, benchmark, setup, teardown, onBenchmarkEvent, BenchmarkEvent, BenchmarkStatus };

View File

@ -0,0 +1,191 @@
/** minibench - https://github.com/SteveSanderson/minibench */
import { groups, BenchmarkStatus } from './minibench.js';
class BenchmarkDisplay {
constructor(htmlUi, benchmark) {
this.benchmark = benchmark;
this.elem = document.createElement('tr');
const headerCol = this.elem.appendChild(document.createElement('th'));
headerCol.className = 'pl-4';
headerCol.textContent = benchmark.name;
headerCol.setAttribute('scope', 'row');
const progressCol = this.elem.appendChild(document.createElement('td'));
this.numExecutionsText = progressCol.appendChild(document.createTextNode(''));
const timingCol = this.elem.appendChild(document.createElement('td'));
this.executionDurationText = timingCol.appendChild(document.createElement('span'));
const runCol = this.elem.appendChild(document.createElement('td'));
runCol.className = 'pr-4';
runCol.setAttribute('align', 'right');
this.runButton = document.createElement('a');
this.runButton.className = 'run-button';
runCol.appendChild(this.runButton);
this.runButton.textContent = 'Run';
this.runButton.onclick = evt => {
evt.preventDefault();
this.benchmark.run(htmlUi.globalRunOptions);
};
benchmark.on('changed', state => this.updateDisplay(state));
this.updateDisplay(this.benchmark.state);
}
updateDisplay(state) {
const benchmark = this.benchmark;
this.elem.className = rowClass(state.status);
this.runButton.textContent = runButtonText(state.status);
this.numExecutionsText.textContent = state.numExecutions
? `Executions: ${state.numExecutions}` : '';
this.executionDurationText.innerHTML = state.estimatedExecutionDurationMs
? `Duration: <b>${parseFloat(state.estimatedExecutionDurationMs.toPrecision(3))}ms</b>` : '';
if (state.status === BenchmarkStatus.idle) {
this.runButton.setAttribute('href', '');
} else {
this.runButton.removeAttribute('href');
if (state.status === BenchmarkStatus.error) {
this.numExecutionsText.textContent = 'Error - see console';
}
}
}
}
function runButtonText(status) {
switch (status) {
case BenchmarkStatus.idle:
case BenchmarkStatus.error:
return 'Run';
case BenchmarkStatus.queued:
return 'Waiting...';
case BenchmarkStatus.running:
return 'Running...';
default:
throw new Error(`Unknown status: ${status}`);
}
}
function rowClass(status) {
switch (status) {
case BenchmarkStatus.idle:
return 'benchmark-idle';
case BenchmarkStatus.queued:
return 'benchmark-waiting';
case BenchmarkStatus.running:
return 'benchmark-running';
case BenchmarkStatus.error:
return 'benchmark-error';
default:
throw new Error(`Unknown status: ${status}`);
}
}
class GroupDisplay {
constructor(htmlUi, group) {
this.group = group;
this.elem = document.createElement('div');
this.elem.className = 'my-3 py-2 bg-white rounded shadow-sm';
const headerContainer = this.elem.appendChild(document.createElement('div'));
headerContainer.className = 'd-flex align-items-baseline px-4';
const header = headerContainer.appendChild(document.createElement('h5'));
header.className = 'py-2';
header.textContent = group.name;
this.runButton = document.createElement('a');
this.runButton.className = 'ml-auto run-button';
this.runButton.setAttribute('href', '');
headerContainer.appendChild(this.runButton);
this.runButton.textContent = 'Run all';
this.runButton.onclick = evt => {
evt.preventDefault();
group.runAll(htmlUi.globalRunOptions);
};
const table = this.elem.appendChild(document.createElement('table'));
table.className = 'table mb-0 benchmarks';
const tbody = table.appendChild(document.createElement('tbody'));
group.benchmarks.forEach(benchmark => {
const benchmarkDisplay = new BenchmarkDisplay(htmlUi, benchmark);
tbody.appendChild(benchmarkDisplay.elem);
});
group.on('changed', () => this.updateDisplay());
this.updateDisplay();
}
updateDisplay() {
const canRun = this.group.status === BenchmarkStatus.idle;
this.runButton.style.display = canRun ? 'block' : 'none';
}
}
class HtmlUI {
constructor(title, selector) {
this.containerElement = document.querySelector(selector);
const headerDiv = this.containerElement.appendChild(document.createElement('div'));
headerDiv.className = 'd-flex align-items-center';
const header = headerDiv.appendChild(document.createElement('h2'));
header.className = 'mx-3 flex-grow-1';
header.textContent = title;
const verifyCheckboxLabel = document.createElement('label');
verifyCheckboxLabel.className = 'ml-auto mr-5';
headerDiv.appendChild(verifyCheckboxLabel);
this.verifyCheckbox = verifyCheckboxLabel.appendChild(document.createElement('input'));
this.verifyCheckbox.type = 'checkbox';
this.verifyCheckbox.className = 'mr-2';
verifyCheckboxLabel.appendChild(document.createTextNode('Verify only'));
this.runButton = document.createElement('button');
this.runButton.className = 'btn btn-success ml-auto px-4 run-button';
headerDiv.appendChild(this.runButton);
this.runButton.textContent = 'Run all';
this.runButton.setAttribute('id', 'runAll');
this.runButton.onclick = () => {
groups.forEach(g => g.runAll(this.globalRunOptions));
};
this.stopButton = document.createElement('button');
this.stopButton.className = 'btn btn-danger ml-auto px-4 stop-button';
headerDiv.appendChild(this.stopButton);
this.stopButton.textContent = 'Stop';
this.stopButton.onclick = () => {
groups.forEach(g => g.stopAll());
};
groups.forEach(group$$1 => {
const groupDisplay = new GroupDisplay(this, group$$1);
this.containerElement.appendChild(groupDisplay.elem);
group$$1.on('changed', () => this.updateDisplay());
});
this.updateDisplay();
}
updateDisplay() {
const areAllIdle = groups.reduce(
(prev, next) => prev && next.status === BenchmarkStatus.idle,
true
);
this.runButton.style.display = areAllIdle ? 'block' : 'none';
this.stopButton.style.display = areAllIdle ? 'none' : 'block';;
}
get globalRunOptions() {
return { verifyOnly: this.verifyCheckbox.checked };
}
}
/**
* minibench
* https://github.com/SteveSanderson/minibench
*/
export { HtmlUI };

View File

@ -0,0 +1,21 @@
{
"$schema": "https://raw.githubusercontent.com/aspnet/Benchmarks/master/src/BenchmarksDriver2/benchmarks.schema.json",
"scenarios": {
"blazorwasmbenchmark": {
"application": {
"job": "blazorwasmbenchmark"
}
}
},
"jobs": {
"blazorwasmbenchmark": {
"source": {
"repository": "https://github.com/dotnet/AspNetCore.git",
"branchOrCommit": "blazor-wasm",
"dockerfile": "src/Components/benchmarkapps/Wasm.Performance/dockerfile"
},
"waitForExit": true,
"readyStateText": "Application started."
}
}
}

View File

@ -0,0 +1,32 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
ARG DEBIAN_FRONTEND=noninteractive
# Setup for nodejs
RUN curl -sL https://deb.nodesource.com/setup_13.x | bash -
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libunwind-dev \
nodejs \
git
ARG gitBranch=blazor-wasm
WORKDIR /src
ADD https://api.github.com/repos/dotnet/aspnetcore/git/ref/heads/${gitBranch} /aspnetcore.commit
RUN git init \
&& git fetch https://github.com/aspnet/aspnetcore ${gitBranch} \
&& git reset --hard FETCH_HEAD \
&& git submodule update --init
RUN dotnet publish -c Release -r linux-x64 -o /app ./src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj
RUN chmod +x /app/Wasm.Performance.Driver
WORKDIR /app
FROM selenium/standalone-chrome:3.141.59-mercury as final
COPY --from=build ./app ./
COPY ./exec.sh ./
ENTRYPOINT [ "bash", "./exec.sh" ]

View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
/opt/bin/start-selenium-standalone.sh&
./Wasm.Performance.Driver

View File

@ -0,0 +1,7 @@
FROM selenium/standalone-chrome:3.141.59-mercury as final
WORKDIR /app
COPY ./Driver/bin/Release/netcoreapp3.1/linux-x64/publish ./
COPY ./exec.sh ./
ENTRYPOINT [ "bash", "./exec.sh" ]

View File

@ -35,7 +35,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Blazor\testassets\Microsoft.AspNetCore.Blazor.E2EPerformance\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj" />
<ProjectReference Include="..\..\benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj" />
<ProjectReference Include="..\..\Blazor\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj" />
<ProjectReference Include="..\..\Blazor\testassets\HostedInAspNet.Server\HostedInAspNet.Server.csproj" />
<ProjectReference Include="..\..\Blazor\testassets\MonoSanityClient\MonoSanityClient.csproj" />

View File

@ -13,11 +13,11 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
public class PerformanceTest
: ServerTestBase<DevHostServerFixture<Blazor.E2EPerformance.Program>>
: ServerTestBase<DevHostServerFixture<Wasm.Performance.TestApp.Program>>
{
public PerformanceTest(
BrowserFixture browserFixture,
DevHostServerFixture<Blazor.E2EPerformance.Program> serverFixture,
DevHostServerFixture<Wasm.Performance.TestApp.Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
@ -52,10 +52,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
() => runAllButton.Displayed || Browser.FindElements(By.CssSelector(".benchmark-error")).Any(),
TimeSpan.FromSeconds(60));
var finishedBenchmarks = Browser.FindElements(By.CssSelector(".benchmark-idle"));
var failedBenchmarks = Browser.FindElements(By.CssSelector(".benchmark-error"));
Assert.NotEmpty(finishedBenchmarks);
Assert.Empty(failedBenchmarks);
Browser.DoesNotExist(By.CssSelector(".benchmark-error")); // no failures
Browser.Exists(By.CssSelector(".benchmark-idle")); // everything's done
}
}
}