From e34b4e8335468299a75eb0b9e2b0f2296e81dc7a Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 11 Apr 2018 15:57:11 -0700 Subject: [PATCH] Use DependencyContext.CompileLibraries to determine dependency graph (#7626) * Use DependencyContext.CompileLibraries to determine dependency graph Fixes https://github.com/aspnet/Mvc/issues/7617 --- .../ApplicationAssembliesProvider.cs | 62 ++-- .../WebApplicationFactory.cs | 49 +-- .../ApplicationAssembliesProviderTest.cs | 345 +++++++++++++----- 3 files changed, 307 insertions(+), 149 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs index 836d8e2e7d..08149805ff 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs @@ -51,8 +51,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts IEnumerable assemblyItems; - if (dependencyContext == null) + if (dependencyContext == null || dependencyContext.CompileLibraries.Count == 0) { + // If an application was built with PreserveCompilationContext = false, CompileLibraries will be empty and we + // can no longer reliably infer the dependency closure. In this case, treat it the same as a missing + // deps file. assemblyItems = new[] { GetAssemblyItem(entryAssembly) }; } else @@ -151,30 +154,46 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts // Internal for unit testing internal static IEnumerable GetCandidateLibraries(DependencyContext dependencyContext) { - var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies); - return candidatesResolver.GetCandidates(); + // When using Microsoft.AspNetCore.App \ Microsoft.AspNetCore.All shared runtimes, entries in the RuntimeLibraries + // get "erased" and it is no longer accurate to query to determine a library's dependency closure. + // We'll use CompileLibraries to calculate the dependency graph and runtime library to resolve assemblies to inspect. + var candidatesResolver = new CandidateResolver(dependencyContext.CompileLibraries, ReferenceAssemblies); + foreach (var library in dependencyContext.RuntimeLibraries) + { + if (candidatesResolver.IsCandidate(library)) + { + yield return library; + } + } } private class CandidateResolver { - private readonly IDictionary _runtimeDependencies; + private readonly IDictionary _compileLibraries; - public CandidateResolver(IReadOnlyList runtimeDependencies, ISet referenceAssemblies) + public CandidateResolver(IReadOnlyList compileLibraries, ISet referenceAssemblies) { - var dependenciesWithNoDuplicates = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var dependency in runtimeDependencies) + var compileLibrariesWithNoDuplicates = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var library in compileLibraries) { - if (dependenciesWithNoDuplicates.ContainsKey(dependency.Name)) + if (compileLibrariesWithNoDuplicates.ContainsKey(library.Name)) { - throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(dependency.Name)); + throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(library.Name)); } - dependenciesWithNoDuplicates.Add(dependency.Name, CreateDependency(dependency, referenceAssemblies)); + + compileLibrariesWithNoDuplicates.Add(library.Name, CreateDependency(library, referenceAssemblies)); } - _runtimeDependencies = dependenciesWithNoDuplicates; + _compileLibraries = compileLibrariesWithNoDuplicates; } - private Dependency CreateDependency(RuntimeLibrary library, ISet referenceAssemblies) + public bool IsCandidate(Library library) + { + var classification = ComputeClassification(library.Name); + return classification == DependencyClassification.ReferencesMvc; + } + + private Dependency CreateDependency(Library library, ISet referenceAssemblies) { var classification = DependencyClassification.Unknown; if (referenceAssemblies.Contains(library.Name)) @@ -187,14 +206,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts private DependencyClassification ComputeClassification(string dependency) { - if (!_runtimeDependencies.ContainsKey(dependency)) + if (!_compileLibraries.ContainsKey(dependency)) { // Library does not have runtime dependency. Since we can't infer // anything about it's references, we'll assume it does not have a reference to Mvc. return DependencyClassification.DoesNotReferenceMvc; } - var candidateEntry = _runtimeDependencies[dependency]; + var candidateEntry = _compileLibraries[dependency]; if (candidateEntry.Classification != DependencyClassification.Unknown) { return candidateEntry.Classification; @@ -219,26 +238,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts } } - public IEnumerable GetCandidates() - { - foreach (var dependency in _runtimeDependencies) - { - if (ComputeClassification(dependency.Key) == DependencyClassification.ReferencesMvc) - { - yield return dependency.Value.Library; - } - } - } - private class Dependency { - public Dependency(RuntimeLibrary library, DependencyClassification classification) + public Dependency(Library library, DependencyClassification classification) { Library = library; Classification = classification; } - public RuntimeLibrary Library { get; } + public Library Library { get; } public DependencyClassification Classification { get; set; } diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs index f1780878f9..3ae20c1b60 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs @@ -176,34 +176,35 @@ namespace Microsoft.AspNetCore.Mvc.Testing { // The default dependency context will be populated in .net core applications. var context = DependencyContext.Default; - if (context != null) - { - // Find the list of projects - var projects = context.CompileLibraries.Where(l => l.Type == "project"); - - // Find the list of projects runtime information and their assembly names. - var runtimeProjectLibraries = context.RuntimeLibraries - .Where(r => projects.Any(p => p.Name == r.Name)) - .ToDictionary(r => r, r => r.GetDefaultAssemblyNames(context).ToArray()); - - var entryPointAssemblyName = typeof(TEntryPoint).Assembly.GetName().Name; - - // Find the project containing TEntryPoint - var entryPointRuntimeLibrary = runtimeProjectLibraries - .Single(rpl => rpl.Value.Any(a => string.Equals(a.Name, entryPointAssemblyName, StringComparison.Ordinal))); - - // Find the list of projects referencing TEntryPoint. - var candidates = runtimeProjectLibraries - .Where(rpl => rpl.Key.Dependencies - .Any(d => string.Equals(d.Name, entryPointRuntimeLibrary.Key.Name, StringComparison.Ordinal))); - - return candidates.SelectMany(rl => rl.Value).Select(Assembly.Load); - } - else + if (context == null || context.CompileLibraries.Count == 0) { // The app domain friendly name will be populated in full framework. return new[] { Assembly.Load(AppDomain.CurrentDomain.FriendlyName) }; } + + var runtimeProjectLibraries = context.RuntimeLibraries + .ToDictionary(r => r.Name, r => r, StringComparer.Ordinal); + + // Find the list of projects + var projects = context.CompileLibraries.Where(l => l.Type == "project"); + + var entryPointAssemblyName = typeof(TEntryPoint).Assembly.GetName().Name; + + // Find the list of projects referencing TEntryPoint. + var candidates = context.CompileLibraries + .Where(library => library.Dependencies.Any(d => string.Equals(d.Name, entryPointAssemblyName, StringComparison.Ordinal))); + + var testAssemblies = new List(); + foreach (var candidate in candidates) + { + if (runtimeProjectLibraries.TryGetValue(candidate.Name, out var runtimeLibrary)) + { + var runtimeAssemblies = runtimeLibrary.GetDefaultAssemblyNames(context); + testAssemblies.AddRange(runtimeAssemblies.Select(Assembly.Load)); + } + } + + return testAssemblies; } catch (Exception) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs index 5746a39ac7..612b60e56b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyModel; +using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNetCore.Mvc.ApplicationParts @@ -28,6 +29,30 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts Assert.Equal(new[] { ThisAssembly }, result); } + [Fact] + public void ResolveAssemblies_ReturnsCurrentAssembly_IfDepsFileDoesNotHaveAnyCompileLibraries() + { + // Arrange + var runtimeLibraries = new[] + { + GetRuntimeLibrary("MyApp", "Microsoft.AspNetCore.All"), + GetRuntimeLibrary("Microsoft.AspNetCore.All", "Microsoft.NETCore.App"), + GetRuntimeLibrary("Microsoft.NETCore.App"), + GetRuntimeLibrary("ClassLibrary"), + }; + var dependencyContext = GetDependencyContext(compileLibraries: Array.Empty(), runtimeLibraries); + var provider = new TestApplicationAssembliesProvider + { + DependencyContext = dependencyContext, + }; + + // Act + var result = provider.ResolveAssemblies(ThisAssembly); + + // Assert + Assert.Equal(new[] { ThisAssembly }, result); + } + [Fact] public void ResolveAssemblies_ReturnsRelatedAssembliesOrderedByName() { @@ -56,12 +81,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts var mvcAssembly = typeof(IActionResult).Assembly; var classLibrary = typeof(FactAttribute).Assembly; - var dependencyContext = GetDependencyContext(new[] + var libraries = new Dictionary { - GetLibrary(ThisAssembly.GetName().Name, new[] { mvcAssembly.GetName().Name, classLibrary.GetName().Name }), - GetLibrary(mvcAssembly.GetName().Name), - GetLibrary(classLibrary.GetName().Name, new[] { mvcAssembly.GetName().Name }), - }); + [ThisAssembly.GetName().Name] = new[] { mvcAssembly.GetName().Name, classLibrary.GetName().Name }, + [mvcAssembly.GetName().Name] = Array.Empty(), + [classLibrary.GetName().Name] = new[] { mvcAssembly.GetName().Name }, + }; + + var dependencyContext = GetDependencyContext(libraries); var provider = new TestApplicationAssembliesProvider { @@ -83,13 +110,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts var classLibrary = typeof(object).Assembly; var relatedPart = typeof(FactAttribute).Assembly; - var dependencyContext = GetDependencyContext(new[] + var libraries = new Dictionary { - GetLibrary(ThisAssembly.GetName().Name, new[] { relatedPart.GetName().Name, classLibrary.GetName().Name }), - GetLibrary(classLibrary.GetName().Name, new[] { mvcAssembly.GetName().Name }), - GetLibrary(relatedPart.GetName().Name, new[] { mvcAssembly.GetName().Name }), - GetLibrary(mvcAssembly.GetName().Name), - }); + [ThisAssembly.GetName().Name] = new[] { relatedPart.GetName().Name, classLibrary.GetName().Name }, + [classLibrary.GetName().Name] = new[] { mvcAssembly.GetName().Name }, + [relatedPart.GetName().Name] = new[] { mvcAssembly.GetName().Name }, + [mvcAssembly.GetName().Name] = Array.Empty(), + }; + + var dependencyContext = GetDependencyContext(libraries); var provider = new TestApplicationAssembliesProvider { @@ -136,8 +165,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts { // Arrange var mvcAssembly = typeof(IActionResult).Assembly; - var libraryAssembly1 = typeof(object).Assembly; - var libraryAssembly2 = typeof(HttpContext).Assembly; + var libraryAssembly1 = typeof(HttpContext).Assembly; + var libraryAssembly2 = typeof(JsonConverter).Assembly; var relatedPart = typeof(FactAttribute).Assembly; var expected = string.Join( Environment.NewLine, @@ -145,13 +174,15 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts libraryAssembly1.FullName, libraryAssembly2.FullName); - var dependencyContext = GetDependencyContext(new[] + var libraries = new Dictionary { - GetLibrary(ThisAssembly.GetName().Name, new[] { relatedPart.GetName().Name, libraryAssembly1.GetName().Name }), - GetLibrary(libraryAssembly1.GetName().Name, new[] { mvcAssembly.GetName().Name }), - GetLibrary(libraryAssembly2.GetName().Name, new[] { mvcAssembly.GetName().Name }), - GetLibrary(mvcAssembly.GetName().Name), - }); + [ThisAssembly.GetName().Name] = new[] { relatedPart.GetName().Name, libraryAssembly1.GetName().Name }, + [libraryAssembly1.GetName().Name] = new[] { mvcAssembly.GetName().Name }, + [libraryAssembly2.GetName().Name] = new[] { mvcAssembly.GetName().Name }, + [mvcAssembly.GetName().Name] = Array.Empty(), + }; + + var dependencyContext = GetDependencyContext(libraries); var provider = new TestApplicationAssembliesProvider { @@ -179,31 +210,41 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts var upperCaseLibrary = "Microsoft.AspNetCore.Mvc"; var mixedCaseLibrary = "microsoft.aspNetCore.mvc"; - var dependencyContext = GetDependencyContext(new[] + var libraries = new Dictionary { - GetLibrary(mixedCaseLibrary), - GetLibrary(upperCaseLibrary), - }); + [upperCaseLibrary] = Array.Empty(), + [mixedCaseLibrary] = Array.Empty(), + }; + var dependencyContext = GetDependencyContext(libraries); // Act - var exception = Assert.Throws(() => ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext)); + var exception = Assert.Throws(() => ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToArray()); // Assert - Assert.Equal($"A duplicate entry for library reference {upperCaseLibrary} was found. Please check that all package references in all projects use the same casing for the same package references.", exception.Message); + Assert.Equal($"A duplicate entry for library reference {mixedCaseLibrary} was found. Please check that all package references in all projects use the same casing for the same package references.", exception.Message); } [Fact] public void GetCandidateLibraries_IgnoresMvcAssemblies() { // Arrange - var expected = GetLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions"); - var dependencyContext = GetDependencyContext(new[] + var expected = GetRuntimeLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions"); + var runtimeLibraries = new[] { - GetLibrary("Microsoft.AspNetCore.Mvc.Core"), - GetLibrary("Microsoft.AspNetCore.Mvc"), - GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), + GetRuntimeLibrary("Microsoft.AspNetCore.Mvc.Core"), + GetRuntimeLibrary("Microsoft.AspNetCore.Mvc"), + GetRuntimeLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), expected, - }); + }; + + var compileLibraries = new[] + { + GetCompileLibrary("Microsoft.AspNetCore.Mvc.Core"), + GetCompileLibrary("Microsoft.AspNetCore.Mvc"), + GetCompileLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), + GetCompileLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions"), + }; + var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries); // Act var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); @@ -213,16 +254,86 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts } [Fact] - public void GetCandidateLibraries_DoesNotThrow_IfLibraryDoesNotHaveRuntimeComponent() + public void GetCandidateLibraries_ReturnsRuntimeLibraries_IfCompileLibraryDependencyToMvcIsPresent() { // Arrange - var expected = GetLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"); - var dependencyContext = GetDependencyContext(new[] + // When an app is running against Microsoft.AspNetCore.All shared runtime or if the DependencyContext is queried + // from an app that's running on Microsoft.NETCore.App (e.g. in a unit testing scenario), the + // runtime library does not state that the app references Mvc whereas the compile library does. This test validates + // that we correctly recognize this scenario. + var expected = GetRuntimeLibrary("MyApp", "Microsoft.AspNetCore.All"); + var runtimeLibraries = new[] { expected, - GetLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"), - GetLibrary("Microsoft.AspNetCore.Mvc"), - }); + GetRuntimeLibrary("Microsoft.AspNetCore.All", "Microsoft.NETCore.App"), + GetRuntimeLibrary("Microsoft.NETCore.App"), + }; + + var compileLibraries = new[] + { + GetCompileLibrary("MyApp", "Microsoft.AspNetCore.All"), + GetCompileLibrary("Microsoft.AspNetCore.All", "Microsoft.AspNetCore.Mvc", "Microsoft.NETCore.App"), + GetCompileLibrary("Microsoft.AspNetCore.Mvc"), + GetCompileLibrary("Microsoft.NETCore.App"), + }; + var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries); + + // Act + var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); + + // Assert + Assert.Equal(new[] { expected }, candidates); + } + + [Fact] + public void GetCandidateLibraries_DoesNotThrow_IfLibraryDoesNotAppearAsCompileOrRuntimeLibrary() + { + // Arrange + var expected = GetRuntimeLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"); + var compileLibraries = new[] + { + GetCompileLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"), + GetCompileLibrary("Microsoft.AspNetCore.Mvc"), + GetCompileLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"), + }; + + var runtimeLibraries = new[] + { + expected, + GetRuntimeLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"), + GetRuntimeLibrary("Microsoft.AspNetCore.Mvc"), + }; + + var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries); + + // Act + var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToList(); + + // Assert + Assert.Equal(new[] { expected }, candidates); + } + + [Fact] + public void GetCandidateLibraries_DoesNotThrow_IfCompileLibraryIsPresentButNotRuntimeLibrary() + { + // Arrange + var expected = GetRuntimeLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"); + var compileLibraries = new[] + { + GetCompileLibrary("MyApplication", "Microsoft.AspNetCore.Server.Kestrel", "Microsoft.AspNetCore.Mvc"), + GetCompileLibrary("Microsoft.AspNetCore.Mvc"), + GetCompileLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"), + GetCompileLibrary("Libuv"), + }; + + var runtimeLibraries = new[] + { + expected, + GetRuntimeLibrary("Microsoft.AspNetCore.Server.Kestrel", "Libuv"), + GetRuntimeLibrary("Microsoft.AspNetCore.Mvc"), + }; + + var dependencyContext = GetDependencyContext(compileLibraries, runtimeLibraries); // Act var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext).ToList(); @@ -235,78 +346,81 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts public void GetCandidateLibraries_ReturnsLibrariesReferencingAnyMvcAssembly() { // Arrange - var dependencyContext = GetDependencyContext(new[] + var libraries = new Dictionary { - GetLibrary("Foo", "Microsoft.AspNetCore.Mvc.Core"), - GetLibrary("Bar", "Microsoft.AspNetCore.Mvc"), - GetLibrary("Qux", "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc"), - GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), - GetLibrary("Microsoft.AspNetCore.Mvc.Core"), - GetLibrary("Microsoft.AspNetCore.Mvc"), - GetLibrary("Not.Mvc.Assembly"), - GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"), - GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), - }); + ["Foo"] = new[] { "Microsoft.AspNetCore.Mvc.Core" }, + ["Bar"] = new[] { "Microsoft.AspNetCore.Mvc" }, + ["Qux"] = new[] { "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc" }, + ["Baz"] = new[] { "Microsoft.AspNetCore.Mvc.Abstractions" }, + ["Microsoft.AspNetCore.Mvc.Core"] = Array.Empty(), + ["Microsoft.AspNetCore.Mvc"] = Array.Empty(), + ["Not.Mvc.Assembly"] = Array.Empty(), + ["Unofficial.Microsoft.AspNetCore.Mvc"] = Array.Empty(), + ["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty(), + }; + var dependencyContext = GetDependencyContext(libraries); // Act var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); // Assert - Assert.Equal(new[] { "Foo", "Bar", "Baz" }, candidates.Select(a => a.Name)); + Assert.Equal(new[] { "Bar", "Baz", "Foo", }, candidates.Select(a => a.Name)); } [Fact] public void GetCandidateLibraries_LibraryNameComparisonsAreCaseInsensitive() { // Arrange - var dependencyContext = GetDependencyContext(new[] + var libraries = new Dictionary { - GetLibrary("Foo", "MICROSOFT.ASPNETCORE.MVC.CORE"), - GetLibrary("Bar", "microsoft.aspnetcore.mvc"), - GetLibrary("Qux", "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc"), - GetLibrary("Baz", "mIcRoSoFt.AsPnEtCoRe.MvC.aBsTrAcTiOnS"), - GetLibrary("Microsoft.AspNetCore.Mvc.Core"), - GetLibrary("LibraryA", "LIBRARYB"), - GetLibrary("LibraryB", "microsoft.aspnetcore.mvc"), - GetLibrary("Microsoft.AspNetCore.Mvc"), - GetLibrary("Not.Mvc.Assembly"), - GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"), - GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), - }); + ["Foo"] = new[] { "MICROSOFT.ASPNETCORE.MVC.CORE" }, + ["Bar"] = new[] { "microsoft.aspnetcore.mvc" }, + ["Qux"] = new[] { "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc" }, + ["Baz"] = new[] { "mIcRoSoFt.AsPnEtCoRe.MvC.aBsTrAcTiOnS" }, + ["Microsoft.AspNetCore.Mvc.Core"] = Array.Empty(), + ["LibraryA"] = new[] { "LIBRARYB" }, + ["LibraryB"] = new[] { "microsoft.aspnetcore.mvc" }, + ["Microsoft.AspNetCore.Mvc"] = Array.Empty(), + ["Not.Mvc.Assembly"] = Array.Empty(), + ["Unofficial.Microsoft.AspNetCore.Mvc"] = Array.Empty(), + ["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty(), + }; + var dependencyContext = GetDependencyContext(libraries); // Act var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); // Assert - Assert.Equal(new[] { "Foo", "Bar", "Baz", "LibraryA", "LibraryB" }, candidates.Select(a => a.Name)); + Assert.Equal(new[] { "Bar", "Baz", "Foo", "LibraryA", "LibraryB" }, candidates.Select(a => a.Name)); } [Fact] public void GetCandidateLibraries_ReturnsLibrariesWithTransitiveReferencesToAnyMvcAssembly() { // Arrange - var expectedLibraries = new[] { "Foo", "Bar", "Baz", "LibraryA", "LibraryB", "LibraryC", "LibraryE", "LibraryG", "LibraryH" }; + var expectedLibraries = new[] { "Bar", "Baz", "Foo", "LibraryA", "LibraryB", "LibraryC", "LibraryE", "LibraryG", "LibraryH" }; - var dependencyContext = GetDependencyContext(new[] + var libraries = new Dictionary { - GetLibrary("Foo", "Bar"), - GetLibrary("Bar", "Microsoft.AspNetCore.Mvc"), - GetLibrary("Qux", "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc"), - GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), - GetLibrary("Microsoft.AspNetCore.Mvc"), - GetLibrary("Not.Mvc.Assembly"), - GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), - GetLibrary("Unofficial.Microsoft.AspNetCore.Mvc"), - GetLibrary("LibraryA", "LibraryB"), - GetLibrary("LibraryB","LibraryC"), - GetLibrary("LibraryC", "LibraryD", "Microsoft.AspNetCore.Mvc.Abstractions"), - GetLibrary("LibraryD"), - GetLibrary("LibraryE","LibraryF","LibraryG"), - GetLibrary("LibraryF"), - GetLibrary("LibraryG", "LibraryH"), - GetLibrary("LibraryH", "LibraryI", "Microsoft.AspNetCore.Mvc"), - GetLibrary("LibraryI"), - }); + ["Foo"] = new[] { "Bar" }, + ["Bar"] = new[] { "Microsoft.AspNetCore.Mvc" }, + ["Qux"] = new[] { "Not.Mvc.Assembly", "Unofficial.Microsoft.AspNetCore.Mvc" }, + ["Baz"] = new[] { "Microsoft.AspNetCore.Mvc.Abstractions" }, + ["Microsoft.AspNetCore.Mvc"] = Array.Empty(), + ["Not.Mvc.Assembly"] = Array.Empty(), + ["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty(), + ["Unofficial.Microsoft.AspNetCore.Mvc"] = Array.Empty(), + ["LibraryA"] = new[] { "LibraryB" }, + ["LibraryB"] = new[] { "LibraryC" }, + ["LibraryC"] = new[] { "LibraryD", "Microsoft.AspNetCore.Mvc.Abstractions" }, + ["LibraryD"] = Array.Empty(), + ["LibraryE"] = new[] { "LibraryF", "LibraryG" }, + ["LibraryF"] = Array.Empty(), + ["LibraryG"] = new[] { "LibraryH" }, + ["LibraryH"] = new[] { "LibraryI", "Microsoft.AspNetCore.Mvc" }, + ["LibraryI"] = Array.Empty(), + }; + var dependencyContext = GetDependencyContext(libraries); // Act var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); @@ -319,23 +433,25 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts public void GetCandidateLibraries_SkipsMvcAssemblies() { // Arrange - var dependencyContext = GetDependencyContext(new[] + var libraries = new Dictionary { - GetLibrary("MvcSandbox", "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc"), - GetLibrary("Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.HttpAbstractions"), - GetLibrary("Microsoft.AspNetCore.HttpAbstractions"), - GetLibrary("Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.Core"), - GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), - GetLibrary("Microsoft.AspNetCore.Mvc.TagHelpers", "Microsoft.AspNetCore.Mvc.Razor"), - GetLibrary("Microsoft.AspNetCore.Mvc.Razor"), - GetLibrary("ControllersAssembly", "Microsoft.AspNetCore.Mvc"), - }); + ["MvcSandbox"] = new[] { "Microsoft.AspNetCore.Mvc.Core", "Microsoft.AspNetCore.Mvc" }, + ["Microsoft.AspNetCore.Mvc.Core"] = new[] { "Microsoft.AspNetCore.HttpAbstractions" }, + ["Microsoft.AspNetCore.HttpAbstractions"] = Array.Empty(), + ["Microsoft.AspNetCore.Mvc"] = new[] { "Microsoft.AspNetCore.Mvc.Abstractions", "Microsoft.AspNetCore.Mvc.Core" }, + ["Microsoft.AspNetCore.Mvc.Abstractions"] = Array.Empty(), + ["Microsoft.AspNetCore.Mvc.TagHelpers"] = new[] { "Microsoft.AspNetCore.Mvc.Razor" }, + ["Microsoft.AspNetCore.Mvc.Razor"] = Array.Empty(), + ["ControllersAssembly"] = new[] { "Microsoft.AspNetCore.Mvc" }, + }; + + var dependencyContext = GetDependencyContext(libraries); // Act var candidates = ApplicationAssembliesProvider.GetCandidateLibraries(dependencyContext); // Assert - Assert.Equal(new[] { "MvcSandbox", "ControllersAssembly" }, candidates.Select(a => a.Name)); + Assert.Equal(new[] { "ControllersAssembly", "MvcSandbox" }, candidates.Select(a => a.Name)); } // This test verifies DefaultAssemblyPartDiscoveryProvider.ReferenceAssemblies reflects the actual loadable assemblies @@ -397,18 +513,37 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts } } - private static DependencyContext GetDependencyContext(RuntimeLibrary[] libraries) + private static DependencyContext GetDependencyContext(IDictionary libraries) + { + var compileLibraries = new List(); + var runtimeLibraries = new List(); + + foreach (var kvp in libraries.OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) + { + var compileLibrary = GetCompileLibrary(kvp.Key, kvp.Value); + compileLibraries.Add(compileLibrary); + + var runtimeLibrary = GetRuntimeLibrary(kvp.Key, kvp.Value); + runtimeLibraries.Add(runtimeLibrary); + } + + return GetDependencyContext(compileLibraries, runtimeLibraries); + } + + private static DependencyContext GetDependencyContext( + IReadOnlyList compileLibraries, + IReadOnlyList runtimeLibraries) { var dependencyContext = new DependencyContext( new TargetInfo("framework", "runtime", "signature", isPortable: true), CompilationOptions.Default, - new CompilationLibrary[0], - libraries, + compileLibraries, + runtimeLibraries, Enumerable.Empty()); return dependencyContext; } - private static RuntimeLibrary GetLibrary(string name, params string[] dependencyNames) + private static RuntimeLibrary GetRuntimeLibrary(string name, params string[] dependencyNames) { var dependencies = dependencyNames?.Select(d => new Dependency(d, "42.0.0")) ?? new Dependency[0]; @@ -424,6 +559,20 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts serviceable: true); } + private static CompilationLibrary GetCompileLibrary(string name, params string[] dependencyNames) + { + var dependencies = dependencyNames?.Select(d => new Dependency(d, "42.0.0")) ?? new Dependency[0]; + + return new CompilationLibrary( + "package", + name, + "23.0.0", + "hash", + Enumerable.Empty(), + dependencies: dependencies.ToArray(), + serviceable: true); + } + private class TestAssembly : Assembly { public override string FullName => "TestRelatedAssembly";