diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs index 6a9da434fb..3681e8ef2a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultAssemblyProvider.cs @@ -25,9 +25,17 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure /// /// The . public DefaultAssemblyProvider(IApplicationEnvironment environment) + : this( + Assembly.Load(new AssemblyName(environment.ApplicationName)), + DependencyContext.Load(Assembly.Load(new AssemblyName(environment.ApplicationName)))) { - _entryAssembly = Assembly.Load(new AssemblyName(environment.ApplicationName)); - _dependencyContext = DependencyContext.Load(_entryAssembly); + } + + // Internal for unit testing. + internal DefaultAssemblyProvider(Assembly entryAssembly, DependencyContext dependencyContext) + { + _entryAssembly = entryAssembly; + _dependencyContext = dependencyContext; } /// @@ -78,7 +86,8 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure /// while ignoring MVC assemblies. /// /// A set of . - protected virtual IEnumerable GetCandidateLibraries() + // Internal for unit testing + protected internal virtual IEnumerable GetCandidateLibraries() { if (ReferenceAssemblies == null) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultAssemblyProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultAssemblyProviderTests.cs new file mode 100644 index 0000000000..eaf7fc37de --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultAssemblyProviderTests.cs @@ -0,0 +1,220 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyModel; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class DefaultAssemblyProviderTests + { + private static readonly Assembly CurrentAssembly = typeof(DefaultAssemblyProviderTests).GetTypeInfo().Assembly; + + [Fact] + public void GetCandidateLibraries_IgnoresMvcAssemblies() + { + // Arrange + var expected = GetLibrary("SomeRandomAssembly", "Microsoft.AspNetCore.Mvc.Abstractions"); + var dependencyContext = new DependencyContext( + target: null, + runtime: null, + compilationOptions: CompilationOptions.Default, + compileLibraries: new CompilationLibrary[0], + runtimeLibraries: new[] + { + GetLibrary("Microsoft.AspNetCore.Mvc.Core"), + GetLibrary("Microsoft.AspNetCore.Mvc"), + GetLibrary("Microsoft.AspNetCore.Mvc.Abstractions"), + expected, + }); + var provider = new DefaultAssemblyProvider(CurrentAssembly, dependencyContext); + + // Act + var candidates = provider.GetCandidateLibraries(); + + // Assert + Assert.Equal(new[] { expected }, candidates); + } + + [Fact] + public void CandidateAssemblies_ReturnsEntryAssemblyIfDependencyContextIsNull() + { + // Arrange + var provider = new DefaultAssemblyProvider(CurrentAssembly, dependencyContext: null); + + // Act + var candidates = provider.CandidateAssemblies; + + // Assert + Assert.Equal(new[] { CurrentAssembly }, candidates); + } + + [Fact] + public void GetCandidateLibraries_ReturnsLibrariesReferencingAnyMvcAssembly() + { + // Arrange + var dependencyContext = new DependencyContext( + target: null, + runtime: null, + compilationOptions: CompilationOptions.Default, + compileLibraries: new CompilationLibrary[0], + runtimeLibraries: new[] + { + 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"), + }); + var provider = new DefaultAssemblyProvider(CurrentAssembly, dependencyContext); + + // Act + var candidates = provider.GetCandidateLibraries(); + + // Assert + Assert.Equal(new[] { "Foo", "Bar", "Baz" }, candidates.Select(a => a.PackageName)); + } + + [Fact] + public void GetCandidateLibraries_ReturnsLibrariesReferencingOverriddenAssemblies() + { + // Arrange + var dependencyContext = new DependencyContext( + target: null, + runtime: null, + compilationOptions: CompilationOptions.Default, + compileLibraries: new CompilationLibrary[0], + runtimeLibraries: new[] + { + GetLibrary("Foo", "CustomMvc.Modules"), + GetLibrary("Bar", "CustomMvc.Application.Loader"), + GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), + }); + var referenceAssemblies = new HashSet + { + "CustomMvc.Modules", + "CustomMvc.Application.Loader" + }; + var assemblyProvider = new OverridenAssemblyProvider( + CurrentAssembly, + dependencyContext, + referenceAssemblies); + + // Act + var candidates = assemblyProvider.GetCandidateLibraries(); + + // Assert + Assert.Equal(new[] { "Foo", "Bar" }, candidates.Select(a => a.PackageName)); + } + + [Fact] + public void GetCandidateLibraries_ReturnsEmptySequenceWhenReferenceAssembliesIsNull() + { + // Arrange + var dependencyContext = new DependencyContext( + target: null, + runtime: null, + compilationOptions: CompilationOptions.Default, + compileLibraries: new CompilationLibrary[0], + runtimeLibraries: new[] + { + GetLibrary("Foo", "CustomMvc.Modules"), + GetLibrary("Bar", "CustomMvc.Application.Loader"), + GetLibrary("Baz", "Microsoft.AspNetCore.Mvc.Abstractions"), + }); + var assemblyProvider = new OverridenAssemblyProvider( + CurrentAssembly, + dependencyContext, + referenceAssemblies: null); + + // Act + var candidates = assemblyProvider.GetCandidateLibraries(); + + // Assert + Assert.Empty(candidates); + } + + // This test verifies DefaultAssemblyProvider.ReferenceAssemblies reflects the actual loadable assemblies + // of the libraries that Microsoft.AspNetCore.Mvc dependes on. + // If we add or remove dependencies, this test should be changed together. + [Fact] + public void ReferenceAssemblies_ReturnsLoadableReferenceAssemblies() + { + // Arrange + var provider = new TestableAssemblyProvider(CurrentAssembly, dependencyContext: null); + var excludeAssemblies = new string[] + { + "Microsoft.AspNetCore.Mvc.WebApiCompatShim", + "Microsoft.AspNetCore.Mvc.TestCommon", + "Microsoft.AspNetCore.Mvc.Core.Test", + "Microsoft.AspNetCore.Mvc.TestDiagnosticListener.Sources", + }; + + var additionalAssemblies = new[] + { + // The following assemblies are not reachable from Microsoft.AspNetCore.Mvc + "Microsoft.AspNetCore.Mvc.TagHelpers", + "Microsoft.AspNetCore.Mvc.Formatters.Xml", + }; + + var expected = DependencyContext.Load(CurrentAssembly) + .RuntimeLibraries + .Where(r => r.PackageName.StartsWith("Microsoft.AspNetCore.Mvc", StringComparison.Ordinal) && + !excludeAssemblies.Contains(r.PackageName, StringComparer.OrdinalIgnoreCase)) + .Select(r => r.PackageName) + .Concat(additionalAssemblies) + .Distinct() + .OrderBy(p => p, StringComparer.Ordinal); + + // Act + var referenceAssemblies = provider.ReferenceAssemblies.OrderBy(p => p, StringComparer.Ordinal); + + // Assert + Assert.Equal(expected, referenceAssemblies); + } + + private static RuntimeLibrary GetLibrary(string name, params string[] dependencyNames) + { + var dependencies = dependencyNames?.Select(d => new Dependency(d, "42.0.0")) ?? new Dependency[0]; + + return new RuntimeLibrary( + "package", + name, + "23.0.0", + "hash", + new[] { $"{name}.dll" }, + dependencies: dependencies.ToArray(), + serviceable: true); + } + + private class OverridenAssemblyProvider : DefaultAssemblyProvider + { + public OverridenAssemblyProvider( + Assembly entryAssembly, + DependencyContext dependencyContext, + HashSet referenceAssemblies) + : base(entryAssembly, dependencyContext) + { + ReferenceAssemblies = referenceAssemblies; + } + + protected override HashSet ReferenceAssemblies { get; } + } + + private class TestableAssemblyProvider : DefaultAssemblyProvider + { + public TestableAssemblyProvider( + Assembly entryAssembly, + DependencyContext dependencyContext) + : base(entryAssembly, dependencyContext) + { + } + + public new HashSet ReferenceAssemblies => base.ReferenceAssemblies; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/project.json b/test/Microsoft.AspNetCore.Mvc.Core.Test/project.json index 2234554f99..79660823b8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/project.json +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/project.json @@ -1,7 +1,8 @@ { "compilationOptions": { "warningsAsErrors": true, - "keyFile": "../../tools/Key.snk" + "keyFile": "../../tools/Key.snk", + "preserveCompilationContext": true }, "dependencies": { "Microsoft.AspNetCore.Http": "1.0.0-*",