Bläddra i källkod

Add support for generating app manifest files for tests (#28575)

* Add initial support for reading content paths from file
* Add task for generating app manifest file
* Completed support for generating AppManifest.json
* Fix deserializing JSON and getting path by assembly name
* Address feedback from peer review
* Fix up target definition and formatting
* Undo typo fix
* Fix target name and JSON parsing
* Update test scenario when using app manifest
* Add new project to ProjectReferences
* Generate file before PrepareResources
* Address feedback from peer review
Safia Abdalla 5 år sedan
förälder
incheckning
f7e1712db8

+ 1 - 0
Directory.Build.props

@@ -201,6 +201,7 @@
     <DeterministicSourcePaths Condition="'$(IsSampleProject)' == 'true' OR '$(IsTestAssetProject)' == 'true'">false</DeterministicSourcePaths>
     <!-- Projects which reference Microsoft.AspNetCore.Mvc.Testing should import this targets file to ensure dependency .deps.json files are copied into test output. -->
     <MvcTestingTargets>$(MSBuildThisFileDirectory)src\Mvc\Mvc.Testing\src\Microsoft.AspNetCore.Mvc.Testing.targets</MvcTestingTargets>
+    <_MvcTestingTasksAssembly>$(ArtifactsBinDir)\Microsoft.AspNetCore.Mvc.Testing.Tasks\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.Mvc.Testing.Tasks.dll</_MvcTestingTasksAssembly>
     <!-- IIS native projects can only be built on Windows for x86 and x64. -->
     <BuildIisNativeProjects Condition=" '$(TargetOsName)' == 'win' AND ('$(TargetArchitecture)' == 'x86' OR '$(TargetArchitecture)' == 'x64') ">true</BuildIisNativeProjects>
     <!-- This property is shared by several projects to layout the AspNetCore.App targeting pack for installers -->

+ 1 - 0
eng/ProjectReferences.props

@@ -119,6 +119,7 @@
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Mvc.RazorPages" ProjectPath="$(RepoRoot)src\Mvc\Mvc.RazorPages\src\Microsoft.AspNetCore.Mvc.RazorPages.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Mvc.Razor" ProjectPath="$(RepoRoot)src\Mvc\Mvc.Razor\src\Microsoft.AspNetCore.Mvc.Razor.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Mvc.TagHelpers" ProjectPath="$(RepoRoot)src\Mvc\Mvc.TagHelpers\src\Microsoft.AspNetCore.Mvc.TagHelpers.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Mvc.Testing.Tasks" ProjectPath="$(RepoRoot)src\Mvc\Mvc.Testing.Tasks\src\Microsoft.AspNetCore.Mvc.Testing.Tasks.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Mvc.Testing" ProjectPath="$(RepoRoot)src\Mvc\Mvc.Testing\src\Microsoft.AspNetCore.Mvc.Testing.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Mvc.ViewFeatures" ProjectPath="$(RepoRoot)src\Mvc\Mvc.ViewFeatures\src\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Mvc" ProjectPath="$(RepoRoot)src\Mvc\Mvc\src\Microsoft.AspNetCore.Mvc.csproj" />

+ 56 - 0
src/Mvc/Mvc.Testing.Tasks/src/GenerateMvcTestManifestTask.cs

@@ -0,0 +1,56 @@
+// 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.Reflection;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.AspNetCore.Mvc.Testing.Tasks
+{
+    /// <summary>
+    /// Generate a JSON file mapping assemblies to content root paths.
+    /// </summary>
+    public class GenerateMvcTestManifestTask : Task
+    {
+        /// <summary>
+        /// The path to output the manifest file to.
+        /// </summary>
+        [Required]
+        public string ManifestPath { get; set; }
+
+        /// <summary>
+        /// A list of content root paths and assembly names to generate the
+        /// manifest from.
+        /// </summary>
+        [Required]
+        public ITaskItem[] Projects { get; set; }
+
+        /// <inheritdoc />
+        public override bool Execute()
+        {
+            using var fileStream = File.Create(ManifestPath);
+            var output = new Dictionary<string, string>();
+
+            foreach (var project in Projects)
+            {
+                var contentRoot = project.GetMetadata("ContentRoot");
+                var assemblyName = project.GetMetadata("Identity");
+                output[assemblyName] = contentRoot;
+            }
+
+            var serializer = new DataContractJsonSerializer(typeof(Dictionary<string, string>), new DataContractJsonSerializerSettings
+            {
+                UseSimpleDictionaryFormat = true
+            });
+            using var writer = JsonReaderWriterFactory.CreateJsonWriter(fileStream, Encoding.UTF8, ownsStream: false, indent: true);
+            serializer.WriteObject(writer, output);
+
+            return !Log.HasLoggedErrors;
+        }
+    }
+}

+ 16 - 0
src/Mvc/Mvc.Testing.Tasks/src/Microsoft.AspNetCore.Mvc.Testing.Tasks.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <Description>Build tasks for functional tests.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+
+    <GenerateDocumentationFile>false</GenerateDocumentationFile>
+    <AddPublicApiAnalyzers>false</AddPublicApiAnalyzers>
+    <IsPackable>false</IsPackable>
+    <IsShipping>false</IsShipping>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.Build.Framework" />
+    <Reference Include="Microsoft.Build.Utilities.Core" />
+  </ItemGroup>
+</Project>

+ 9 - 0
src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.csproj

@@ -16,6 +16,15 @@
     <Reference Include="Microsoft.Extensions.HostFactoryResolver.Sources" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Reference
+        Include="Microsoft.AspNetCore.Mvc.Testing.Tasks"
+        Targets="Build"
+        ReferenceOutputAssembly="false"
+        SkipGetTargetFrameworkProperties="true"
+        UndefineProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier;PublishDir" />
+  </ItemGroup>
+
   <ItemGroup>
     <Content Include="Microsoft.AspNetCore.Mvc.Testing.targets" Pack="true" PackagePath="build/$(TargetFramework)/" />
   </ItemGroup>

+ 18 - 18
src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.targets

@@ -1,5 +1,10 @@
 <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 
+  <PropertyGroup>
+    <_MvcTestingTasksAssembly Condition="$(_MvcTestingTasksAssembly) == ''">$(MSBuildThisFileDirectory)..\tasks\Microsoft.AspNetCore.Mvc.Testing.dll</_MvcTestingTasksAssembly>
+  </PropertyGroup>
+  <UsingTask TaskName="GenerateMvcTestManifestTask" AssemblyFile="$(_MvcTestingTasksAssembly)"/>
+
   <!--
     Work around https://github.com/NuGet/Home/issues/4412. MVC uses DependencyContext.Load() which looks next to a .dll
     for a .deps.json. Information isn't available elsewhere. Need the .deps.json file for all web site applications.
@@ -23,30 +28,25 @@
       </ItemGroup>
     </Target>
 
-  <Target Name="_AddContentRootForProjectReferences" BeforeTargets="GetAssemblyAttributes" DependsOnTargets="_ResolveMvcTestProjectReferences">
+  <Target Name="_AddContentRootForProjectReferences" BeforeTargets="PrepareResources" DependsOnTargets="_ResolveMvcTestProjectReferences">
     <ItemGroup>
-      <WebApplicationFactoryContentRootAttribute
-        Condition="'%(_ContentRootProjectReferences.Identity)' != ''"
-        Include="%(_ContentRootProjectReferences.Identity)"
-        AssemblyName="%(_ContentRootProjectReferences.FusionName)"
-        ContentRootPath="$([System.IO.Path]::GetDirectoryName(%(_ContentRootProjectReferences.MSBuildSourceProjectFile)))"
-        ContentRootTest="$([System.IO.Path]::GetFileName(%(_ContentRootProjectReferences.MSBuildSourceProjectFile)))"
-        Priority="0" />
+      <_ManifestProjects Include="%(_ContentRootProjectReferences.FusionName)">
+        <ContentRoot>$([System.IO.Path]::GetDirectoryName(%(_ContentRootProjectReferences.MSBuildSourceProjectFile)))</ContentRoot>
+      </_ManifestProjects>
     </ItemGroup>
 
+    <GenerateMvcTestManifestTask ManifestPath="$(IntermediateOutputPath)MvcTestingAppManifest.json" Projects="@(_ManifestProjects)"/>
+
     <ItemGroup>
-      <AssemblyAttribute
-        Condition=" '%(WebApplicationFactoryContentRootAttribute.Identity)' != '' "
-        Include="Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryContentRootAttribute">
-        <_Parameter1>%(WebApplicationFactoryContentRootAttribute.AssemblyName)</_Parameter1>
-        <_Parameter2>%(WebApplicationFactoryContentRootAttribute.ContentRootPath)</_Parameter2>
-        <_Parameter3>%(WebApplicationFactoryContentRootAttribute.ContentRootTest)</_Parameter3>
-        <_Parameter4>%(WebApplicationFactoryContentRootAttribute.Priority)</_Parameter4>
-      </AssemblyAttribute>
+      <ContentWithTargetPath Include="$(IntermediateOutputPath)MvcTestingAppManifest.json"
+        TargetPath="MvcTestingAppManifest.json"
+        CopyToOutputDirectory="PreserveNewest"
+        CopyToPublishDirectory="Never"/>
+      <FileWrites Include="$(IntermediateOutputPath)MvcTestingAppManifest.json" />
     </ItemGroup>
   </Target>
 
-  <Target Name="CopyAditionalFiles" AfterTargets="Build;_ResolveMvcTestProjectReferences" Condition="'$(TargetFramework)'!=''">
+  <Target Name="_MvcCopyDependencyFiles" AfterTargets="Build;_ResolveMvcTestProjectReferences" Condition="'$(TargetFramework)'!=''">
     <ItemGroup>
       <DepsFilePaths
         Condition="'%(_ContentRootProjectReferences.Identity)' != ''"
@@ -56,4 +56,4 @@
     <Copy SourceFiles="%(DepsFilePaths.FullPath)" DestinationFolder="$(OutDir)" Condition="Exists('%(DepsFilePaths.FullPath)')" />
   </Target>
 
-</Project>
+</Project>

+ 33 - 9
src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

@@ -7,6 +7,8 @@ using System.IO;
 using System.Linq;
 using System.Net.Http;
 using System.Reflection;
+using System.Text.Json;
+using System.Runtime.Serialization.Json;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Hosting.Server;
 using Microsoft.AspNetCore.TestHost;
@@ -93,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.Testing
 
         /// <summary>
         /// Gets the <see cref="IReadOnlyList{WebApplicationFactory}"/> of factories created from this factory
-        /// by further customizing the <see cref="IWebHostBuilder"/> when calling 
+        /// by further customizing the <see cref="IWebHostBuilder"/> when calling
         /// <see cref="WebApplicationFactory{TEntryPoint}.WithWebHostBuilder(Action{IWebHostBuilder})"/>.
         /// </summary>
         public IReadOnlyList<WebApplicationFactory<TEntryPoint>> Factories => _derivedFactories.AsReadOnly();
@@ -171,6 +173,35 @@ namespace Microsoft.AspNetCore.Mvc.Testing
                 return;
             }
 
+            var fromFile = File.Exists("MvcTestingAppManifest.json");
+            var contentRoot = fromFile ? GetContentRootFromFile("MvcTestingAppManifest.json") : GetContentRootFromAssembly();
+
+            if (contentRoot != null)
+            {
+                builder.UseContentRoot(contentRoot);
+            }
+            else
+            {
+                builder.UseSolutionRelativeContentRoot(typeof(TEntryPoint).Assembly.GetName().Name);
+            }
+        }
+
+        private string GetContentRootFromFile(string file)
+        {
+            var data = JsonSerializer.Deserialize<IDictionary<string, string>>(File.ReadAllBytes(file));
+            var key = typeof(TEntryPoint).Assembly.GetName().FullName;
+            try
+            {
+                return data[key];
+            } catch
+            {
+                throw new KeyNotFoundException($"Could not find content root for project '{key}' in test manifest file '{file}'");
+            }
+
+        }
+
+        private string GetContentRootFromAssembly()
+        {
             var metadataAttributes = GetContentRootMetadataAttributes(
                 typeof(TEntryPoint).Assembly.FullName,
                 typeof(TEntryPoint).Assembly.GetName().Name);
@@ -194,14 +225,7 @@ namespace Microsoft.AspNetCore.Mvc.Testing
                 }
             }
 
-            if (contentRoot != null)
-            {
-                builder.UseContentRoot(contentRoot);
-            }
-            else
-            {
-                builder.UseSolutionRelativeContentRoot(typeof(TEntryPoint).Assembly.GetName().Name);
-            }
+            return contentRoot;
         }
 
         private static bool SetContentRootFromSetting(IWebHostBuilder builder)

+ 3 - 2
src/Mvc/test/Mvc.FunctionalTests/TestingInfrastructureInheritanceTests.cs

@@ -29,7 +29,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
             Assert.Equal(new[] { "ConfigureWebHost", "Customization", "FurtherCustomization" }, factory.ConfigureWebHostCalled.ToArray());
             Assert.True(factory.CreateServerCalled);
             Assert.True(factory.CreateWebHostBuilderCalled);
-            Assert.True(factory.GetTestAssembliesCalled);
+            // GetTestAssemblies is not called when reading content roots from MvcAppManifest
+            Assert.False(factory.GetTestAssembliesCalled);
             Assert.True(factory.CreateHostBuilderCalled);
             Assert.False(factory.CreateHostCalled);
         }
@@ -46,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
 
             // Assert
             Assert.Equal(new[] { "ConfigureWebHost", "Customization", "FurtherCustomization" }, factory.ConfigureWebHostCalled.ToArray());
-            Assert.True(factory.GetTestAssembliesCalled);
+            Assert.False(factory.GetTestAssembliesCalled);
             Assert.True(factory.CreateHostBuilderCalled);
             Assert.True(factory.CreateHostCalled);
             Assert.False(factory.CreateServerCalled);