From 4b247e805085f7e9c50335c2946f09b7dccdc934 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 11 Dec 2017 16:11:13 +0000 Subject: [PATCH] Make ReferencedAssemblyFileProviderTest more useful --- .../Microsoft.Blazor.Server.Test.csproj | 2 +- .../ReferencedAssemblyFileProviderTest.cs | 87 +++++++++++++++++-- samples/StandaloneApp/Program.cs | 2 +- samples/StandaloneApp/StandaloneApp.csproj | 1 + .../ReferencedAssemblyFileProvider.cs | 23 ++--- src/Microsoft.Blazor/Test.cs | 10 +++ 6 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 src/Microsoft.Blazor/Test.cs diff --git a/Microsoft.Blazor.Server.Test/Microsoft.Blazor.Server.Test.csproj b/Microsoft.Blazor.Server.Test/Microsoft.Blazor.Server.Test.csproj index 87ad644bcd..ec5898c951 100644 --- a/Microsoft.Blazor.Server.Test/Microsoft.Blazor.Server.Test.csproj +++ b/Microsoft.Blazor.Server.Test/Microsoft.Blazor.Server.Test.csproj @@ -13,7 +13,7 @@ - + diff --git a/Microsoft.Blazor.Server.Test/ReferencedAssemblyFileProviderTest.cs b/Microsoft.Blazor.Server.Test/ReferencedAssemblyFileProviderTest.cs index 28faf2a8ec..36eb98c50c 100644 --- a/Microsoft.Blazor.Server.Test/ReferencedAssemblyFileProviderTest.cs +++ b/Microsoft.Blazor.Server.Test/ReferencedAssemblyFileProviderTest.cs @@ -2,6 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.Blazor.Mono; +using Mono.Cecil; +using System; +using System.IO; using System.Linq; using Xunit; @@ -12,8 +15,10 @@ namespace Microsoft.Blazor.Server.Test [Fact] public void RootDirContainsOnlyBinDir() { + var (entrypoint, entrypointData) = GetBclAssemblyForTest("mscorlib"); var provider = new ReferencedAssemblyFileProvider( - typeof (HostedInAspNet.Client.Program).Assembly, + entrypoint, + entrypointData, MonoStaticFileProvider.Instance); Assert.Collection(provider.GetDirectoryContents("/"), item => { @@ -23,19 +28,89 @@ namespace Microsoft.Blazor.Server.Test } [Fact] - public void FindsEntrypointAssemblyAndReferencedAssemblies() + public void FindsReferencedAssemblyGraphSimple() { + var (entrypoint, entrypointData) = GetBclAssemblyForTest("System.Linq.Expressions"); var provider = new ReferencedAssemblyFileProvider( - typeof(HostedInAspNet.Client.Program).Assembly, + entrypoint, + entrypointData, MonoStaticFileProvider.Instance); var contents = provider.GetDirectoryContents("/bin").OrderBy(i => i.Name).ToList(); Assert.Collection(contents, - item => { Assert.Equal("/bin/HostedInAspNet.Client.dll", item.PhysicalPath); }, item => { Assert.Equal("/bin/mscorlib.dll", item.PhysicalPath); }, - item => { Assert.Equal("/bin/System.Console.dll", item.PhysicalPath); }, item => { Assert.Equal("/bin/System.Core.dll", item.PhysicalPath); }, item => { Assert.Equal("/bin/System.dll", item.PhysicalPath); }, - item => { Assert.Equal("/bin/System.Runtime.dll", item.PhysicalPath); }); + item => { Assert.Equal("/bin/System.Linq.Expressions.dll", item.PhysicalPath); }); + } + + [Fact] + public void FindsReferencedAssemblyGraphRealistic() + { + // Arrange + var standaloneAppAssemblyLocation = typeof(StandaloneApp.Program).Assembly.Location; + var provider = new ReferencedAssemblyFileProvider( + AssemblyDefinition.ReadAssembly(standaloneAppAssemblyLocation), + File.ReadAllBytes(standaloneAppAssemblyLocation), + MonoStaticFileProvider.Instance); + var expectedContents = new[] + { + /* + The current Mono WASM BCL forwards from netstandard.dll to various facade assemblies + in which small bits of implementation live, such as System.Xml.XPath.XDocument. So + if you reference netstandard, then you also reference System.Xml.XPath.XDocument.dll, + even though you're very unlikely to be calling it at runtime. That's why the following + list (for a very basic Blazor app) is longer than you'd expect. + + These redundant references could be stripped out during publishing, but it's still + unfortunate that in development mode you'd see all these unexpected assemblies get + fetched from the server. We should try to get the Mono WASM BCL reorganized so that + all the implementation goes into mscorlib.dll, with the facade assemblies existing only + in case someone (or some 3rd party assembly) references them directly, but with their + implementations 100% forwarding to mscorlib.dll. Then in development you'd fetch far + fewer assemblies from the server, and during publishing, illink would remove all the + uncalled implementation code from mscorlib.dll anyway. + */ + "/bin/Microsoft.Blazor.dll", + "/bin/mscorlib.dll", + "/bin/netstandard.dll", + "/bin/StandaloneApp.dll", + "/bin/System.Console.dll", + "/bin/System.Core.dll", + "/bin/System.Diagnostics.StackTrace.dll", + "/bin/System.dll", + "/bin/System.Globalization.Extensions.dll", + "/bin/System.Runtime.dll", + "/bin/System.Runtime.InteropServices.RuntimeInformation.dll", + "/bin/System.Runtime.Serialization.Primitives.dll", + "/bin/System.Runtime.Serialization.Xml.dll", + "/bin/System.Security.Cryptography.Algorithms.dll", + "/bin/System.Security.SecureString.dll", + "/bin/System.Xml.XPath.XDocument.dll", + }; + + // Act + var contents = provider.GetDirectoryContents("/bin") + .OrderBy(i => i.Name, StringComparer.InvariantCulture).ToList(); + + // Assert + Assert.Equal(expectedContents.Length, contents.Count); + for (var i = 0; i < expectedContents.Length; i++) + { + Assert.Equal(expectedContents[i], contents[i].PhysicalPath); + } + } + + private static (AssemblyDefinition, byte[]) GetBclAssemblyForTest(string name) + { + var possibleFilenames = new[] { $"/bcl/{name}.dll", $"/bcl/Facades/{name}.dll" }; + var fileInfo = possibleFilenames + .Select(MonoStaticFileProvider.Instance.GetFileInfo) + .First(item => item.Exists); + using (var data = new MemoryStream()) + { + fileInfo.CreateReadStream().CopyTo(data); + return (AssemblyDefinition.ReadAssembly(fileInfo.CreateReadStream()), data.ToArray()); + } } } } diff --git a/samples/StandaloneApp/Program.cs b/samples/StandaloneApp/Program.cs index a439e0def1..2da2284002 100644 --- a/samples/StandaloneApp/Program.cs +++ b/samples/StandaloneApp/Program.cs @@ -9,7 +9,7 @@ namespace StandaloneApp { public static void Main(string[] args) { - Console.WriteLine("Hello, world!"); + Console.WriteLine(Microsoft.Blazor.Test.Message); } } } diff --git a/samples/StandaloneApp/StandaloneApp.csproj b/samples/StandaloneApp/StandaloneApp.csproj index d6cd99b14b..2af197f092 100644 --- a/samples/StandaloneApp/StandaloneApp.csproj +++ b/samples/StandaloneApp/StandaloneApp.csproj @@ -11,6 +11,7 @@ --> + run --project ..\..\src\Microsoft.Blazor.DevHost --no-build serve diff --git a/src/Microsoft.Blazor.Server/ReferencedAssemblyFileProvider.cs b/src/Microsoft.Blazor.Server/ReferencedAssemblyFileProvider.cs index 8ec5d26975..0c61342b4b 100644 --- a/src/Microsoft.Blazor.Server/ReferencedAssemblyFileProvider.cs +++ b/src/Microsoft.Blazor.Server/ReferencedAssemblyFileProvider.cs @@ -14,20 +14,21 @@ namespace Microsoft.Blazor.Server { internal class ReferencedAssemblyFileProvider : InMemoryFileProvider { - public ReferencedAssemblyFileProvider(Assembly entrypointAssembly, IFileProvider clientBcl) - : base(ComputeContents(entrypointAssembly, clientBcl)) + public ReferencedAssemblyFileProvider( + AssemblyDefinition entrypoint, + byte[] entrypointData, + IFileProvider clientBcl) + : base(ComputeContents(entrypoint, entrypointData, clientBcl)) { } private static IEnumerable<(string, Stream)> ComputeContents( - Assembly entrypointAssembly, + AssemblyDefinition entrypoint, + byte[] entrypointData, IFileProvider clientBcl) { var foundAssemblies = new Dictionary(); - AddWithReferencesRecursive( - new ReferencedAssembly(AssemblyDefinition.ReadAssembly(entrypointAssembly.Location)), - clientBcl, - foundAssemblies); + AddWithReferencesRecursive(new ReferencedAssembly(entrypoint, entrypointData), clientBcl, foundAssemblies); return foundAssemblies.Values.Select(assembly => ( $"/bin/{assembly.Name}.dll", @@ -72,7 +73,9 @@ namespace Microsoft.Blazor.Server // (e.g., if it's in the app's bin directory, or a NuGet package) var nativelyResolved = module.AssemblyResolver.Resolve(referenceName); return AllowServingAssembly(nativelyResolved) - ? new ReferencedAssembly(nativelyResolved) + ? new ReferencedAssembly( + nativelyResolved, + File.ReadAllBytes(nativelyResolved.MainModule.FileName)) : null; } catch (AssemblyResolutionException) @@ -128,10 +131,10 @@ namespace Microsoft.Blazor.Server public byte[] Data { get; } public AssemblyDefinition Definition { get; } - public ReferencedAssembly(AssemblyDefinition definition) + public ReferencedAssembly(AssemblyDefinition definition, byte[] rawData) { Name = definition.Name.Name; - Data = File.ReadAllBytes(definition.MainModule.FileName); + Data = rawData; Definition = definition; } diff --git a/src/Microsoft.Blazor/Test.cs b/src/Microsoft.Blazor/Test.cs new file mode 100644 index 0000000000..3aafc43123 --- /dev/null +++ b/src/Microsoft.Blazor/Test.cs @@ -0,0 +1,10 @@ +// 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 Microsoft.Blazor +{ + public static class Test + { + public readonly static string Message = "Hello, world!"; + } +}