diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DeploymentParameters.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DeploymentParameters.cs
index a687a542b3..968647edba 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DeploymentParameters.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DeploymentParameters.cs
@@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
public string ServerConfigLocation { get; set; }
- public string SiteName { get; set; }
+ public string SiteName { get; set; } = "HttpTestSite";
public string ApplicationPath { get; set; }
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs
index 38f84d5044..d9f5577056 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs
@@ -5,7 +5,7 @@ using System;
using System.IO;
using System.Runtime.InteropServices;
-namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
internal static class DotNetCommands
{
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployer.cs
index ddaac89711..a9212b0d54 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployer.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployer.cs
@@ -161,6 +161,22 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
}
+ protected string GetDotNetExeForArchitecture()
+ {
+ var executableName = DotnetCommandName;
+ // We expect x64 dotnet.exe to be on the path but we have to go searching for the x86 version.
+ if (DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture))
+ {
+ executableName = DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture);
+ if (!File.Exists(executableName))
+ {
+ throw new Exception($"Unable to find '{executableName}'.'");
+ }
+ }
+
+ return executableName;
+ }
+
protected void ShutDownIfAnyHostProcess(Process hostProcess)
{
if (hostProcess != null && !hostProcess.HasExited)
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs
index 91c9c7eae3..1bdc58ec8a 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs
@@ -3,9 +3,9 @@
using System;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -42,15 +42,52 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
// Start timer
StartTimer();
- // For now we always auto-publish. Otherwise we'll have to write our own local web.config for the HttpPlatformHandler
- DotnetPublish();
+ // For an unpublished application the dllroot points pre-built dlls like projectdir/bin/debug/net461/
+ // and contentRoot points to the project directory so you get things like static assets.
+ // For a published app both point to the publish directory.
+ var dllRoot = CheckIfPublishIsRequired();
+ var contentRoot = string.Empty;
+ if (DeploymentParameters.PublishApplicationBeforeDeployment)
+ {
+ DotnetPublish();
+ contentRoot = DeploymentParameters.PublishedApplicationRootPath;
+ dllRoot = contentRoot;
+ }
+ else
+ {
+ // Core+Standalone always publishes. This must be Clr+Standalone or Core+Portable.
+ // Update processPath and arguments for our current scenario
+ contentRoot = DeploymentParameters.ApplicationPath;
- var contentRoot = DeploymentParameters.PublishedApplicationRootPath;
+ var executableExtension = DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" : ".exe";
+ var entryPoint = Path.Combine(dllRoot, DeploymentParameters.ApplicationName + executableExtension);
+
+ var executableName = string.Empty;
+ var executableArgs = string.Empty;
+
+ if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable)
+ {
+ executableName = GetDotNetExeForArchitecture();
+ executableArgs = entryPoint;
+ }
+ else
+ {
+ executableName = entryPoint;
+ }
+
+ Logger.LogInformation("Executing: {exe} {args}", executableName, executableArgs);
+ DeploymentParameters.EnvironmentVariables["LAUNCHER_PATH"] = executableName;
+ DeploymentParameters.EnvironmentVariables["LAUNCHER_ARGS"] = executableArgs;
+
+ // CurrentDirectory will point to bin/{config}/{tfm}, but the config and static files aren't copied, point to the app base instead.
+ Logger.LogInformation("ContentRoot: {path}", DeploymentParameters.ApplicationPath);
+ DeploymentParameters.EnvironmentVariables["ASPNETCORE_CONTENTROOT"] = DeploymentParameters.ApplicationPath;
+ }
var testUri = TestUriHelper.BuildTestUri(ServerType.IISExpress, DeploymentParameters.ApplicationBaseUriHint);
// Launch the host process.
- var (actualUri, hostExitToken) = await StartIISExpressAsync(testUri, contentRoot);
+ var (actualUri, hostExitToken) = await StartIISExpressAsync(testUri, contentRoot, dllRoot);
Logger.LogInformation("Application ready at URL: {appUrl}", actualUri);
@@ -64,7 +101,43 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
}
- private async Task<(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot)
+ private string CheckIfPublishIsRequired()
+ {
+ var targetFramework = DeploymentParameters.TargetFramework;
+
+ // IISIntegration uses this layout
+ var dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.RuntimeArchitecture.ToString(),
+ DeploymentParameters.Configuration, targetFramework);
+
+ if (!Directory.Exists(dllRoot))
+ {
+ // Most repos use this layout
+ dllRoot = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.Configuration, targetFramework);
+
+ if (!Directory.Exists(dllRoot))
+ {
+ // The bits we need weren't pre-compiled, compile on publish
+ DeploymentParameters.PublishApplicationBeforeDeployment = true;
+ }
+ else if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr
+ && DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86)
+ {
+ // x64 is the default. Publish to rebuild for the right bitness
+ DeploymentParameters.PublishApplicationBeforeDeployment = true;
+ }
+ }
+
+ if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr
+ && DeploymentParameters.ApplicationType == ApplicationType.Standalone)
+ {
+ // Publish is always required to get the correct standalone files in the output directory
+ DeploymentParameters.PublishApplicationBeforeDeployment = true;
+ }
+
+ return dllRoot;
+ }
+
+ private async Task<(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot, string dllRoot)
{
using (Logger.BeginScope("StartIISExpress"))
{
@@ -74,60 +147,17 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
port = (uri.Scheme == "https") ? TestPortHelper.GetNextSSLPort() : TestPortHelper.GetNextPort();
}
+ Logger.LogInformation("Attempting to start IIS Express on port: {port}", port);
+ PrepareConfig(contentRoot, dllRoot, port);
+
+ var parameters = string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) ?
+ string.Format("/port:{0} /path:\"{1}\" /trace:error", uri.Port, contentRoot) :
+ string.Format("/site:{0} /config:{1} /trace:error", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation);
+
+ var iisExpressPath = GetIISExpressPath();
+
for (var attempt = 0; attempt < MaximumAttempts; attempt++)
{
- Logger.LogInformation("Attempting to start IIS Express on port: {port}", port);
-
- if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigTemplateContent))
- {
- var serverConfig = DeploymentParameters.ServerConfigTemplateContent;
-
- // Pass on the applicationhost.config to iis express. With this don't need to pass in the /path /port switches as they are in the applicationHost.config
- // We take a copy of the original specified applicationHost.Config to prevent modifying the one in the repo.
- serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMPath]", dllName: "aspnetcore.dll", serverConfig, contentRoot);
-
- serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", dllName: "aspnetcorev2.dll", serverConfig, contentRoot);
-
- Logger.LogDebug("Writing ApplicationPhysicalPath '{applicationPhysicalPath}' to config", contentRoot);
- Logger.LogDebug("Writing Port '{port}' to config", port);
- serverConfig =
- serverConfig
- .Replace("[ApplicationPhysicalPath]", contentRoot)
- .Replace("[PORT]", port.ToString());
-
- DeploymentParameters.ServerConfigLocation = Path.GetTempFileName();
-
- if (serverConfig.Contains("[HostingModel]"))
- {
- var hostingModel = DeploymentParameters.HostingModel.ToString();
- serverConfig.Replace("[HostingModel]", hostingModel);
- Logger.LogDebug("Writing HostingModel '{hostingModel}' to config", hostingModel);
- }
-
- Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation);
-
- if (Logger.IsEnabled(LogLevel.Trace))
- {
- Logger.LogTrace($"Config File Content:{Environment.NewLine}===START CONFIG==={Environment.NewLine}{{configContent}}{Environment.NewLine}===END CONFIG===", serverConfig);
- }
-
- File.WriteAllText(DeploymentParameters.ServerConfigLocation, serverConfig);
- }
-
- if (DeploymentParameters.HostingModel == HostingModel.InProcess)
- {
- ModifyAspNetCoreSectionInWebConfig(key: "hostingModel", value: "inprocess");
- }
-
- ModifyHandlerSectionInWebConfig(key: "modules", value: DeploymentParameters.AncmVersion.ToString());
- ModifyDotNetExePathInWebConfig();
-
- var parameters = string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigLocation) ?
- string.Format("/port:{0} /path:\"{1}\" /trace:error", uri.Port, contentRoot) :
- string.Format("/site:{0} /config:{1} /trace:error", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation);
-
- var iisExpressPath = GetIISExpressPath();
-
Logger.LogInformation("Executing command : {iisExpress} {parameters}", iisExpressPath, parameters);
var startInfo = new ProcessStartInfo
@@ -197,7 +227,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
// just in case we missed one -anurse
if (!await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10)))
{
- Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", _hostProcess.Id, port);
+ Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", process.Id, port);
// Wait for the process to exit and try again
process.WaitForExit(30 * 1000);
@@ -217,15 +247,69 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
}
- private string ModifyANCMPathInConfig(string replaceFlag, string dllName, string serverConfig, string contentRoot)
+ private void PrepareConfig(string contentRoot, string dllRoot, int port)
+ {
+ // Config is required. If not present then fall back to one we carry with us.
+ if (string.IsNullOrEmpty(DeploymentParameters.ServerConfigTemplateContent))
+ {
+ using (var stream = GetType().Assembly.GetManifestResourceStream("Microsoft.AspNetCore.Server.IntegrationTesting.Http.config"))
+ using (var reader = new StreamReader(stream))
+ {
+ DeploymentParameters.ServerConfigTemplateContent = reader.ReadToEnd();
+ }
+ }
+
+ var serverConfig = DeploymentParameters.ServerConfigTemplateContent;
+
+ // Pass on the applicationhost.config to iis express. With this don't need to pass in the /path /port switches as they are in the applicationHost.config
+ // We take a copy of the original specified applicationHost.Config to prevent modifying the one in the repo.
+ serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMPath]", dllName: "aspnetcore.dll", serverConfig, dllRoot);
+ serverConfig = ModifyANCMPathInConfig(replaceFlag: "[ANCMV2Path]", dllName: "aspnetcorev2.dll", serverConfig, dllRoot);
+
+ serverConfig = ReplacePlaceholder(serverConfig, "[PORT]", port.ToString(CultureInfo.InvariantCulture));
+ serverConfig = ReplacePlaceholder(serverConfig, "[ApplicationPhysicalPath]", contentRoot);
+
+ if (DeploymentParameters.PublishApplicationBeforeDeployment)
+ {
+ // For published apps, prefer the content in the web.config, but update it.
+ ModifyAspNetCoreSectionInWebConfig(key: "hostingModel",
+ value: DeploymentParameters.HostingModel == HostingModel.InProcess ? "inprocess" : "");
+ ModifyHandlerSectionInWebConfig(key: "modules", value: DeploymentParameters.AncmVersion.ToString());
+ ModifyDotNetExePathInWebConfig();
+ serverConfig = RemoveRedundantElements(serverConfig);
+ }
+ else
+ {
+ // The elements normally in the web.config are in the applicationhost.config for unpublished apps.
+ serverConfig = ReplacePlaceholder(serverConfig, "[HostingModel]", DeploymentParameters.HostingModel.ToString());
+ serverConfig = ReplacePlaceholder(serverConfig, "[AspNetCoreModule]", DeploymentParameters.AncmVersion.ToString());
+ }
+
+ DeploymentParameters.ServerConfigLocation = Path.GetTempFileName();
+ Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation);
+
+ File.WriteAllText(DeploymentParameters.ServerConfigLocation, serverConfig);
+ }
+
+ private string ReplacePlaceholder(string content, string field, string value)
+ {
+ if (content.Contains(field))
+ {
+ content = content.Replace(field, value);
+ Logger.LogDebug("Writing {field} '{value}' to config", field, value);
+ }
+ return content;
+ }
+
+ private string ModifyANCMPathInConfig(string replaceFlag, string dllName, string serverConfig, string dllRoot)
{
if (serverConfig.Contains(replaceFlag))
{
var arch = DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{dllName}" : $@"x86\{dllName}";
- var ancmFile = Path.Combine(contentRoot, arch);
+ var ancmFile = Path.Combine(dllRoot, arch);
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
- ancmFile = Path.Combine(contentRoot, dllName);
+ ancmFile = Path.Combine(dllRoot, dllName);
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
{
throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
@@ -263,7 +347,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
ShutDownIfAnyHostProcess(_hostProcess);
- if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigLocation)
+ if (!string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation)
&& File.Exists(DeploymentParameters.ServerConfigLocation))
{
// Delete the temp applicationHostConfig that we created.
@@ -317,7 +401,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
// Transforms the web.config file to set attributes like hostingModel="inprocess" element
private void ModifyAspNetCoreSectionInWebConfig(string key, string value)
{
- var webConfigFile = $"{DeploymentParameters.PublishedApplicationRootPath}/web.config";
+ var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config");
var config = XDocument.Load(webConfigFile);
var element = config.Descendants("aspNetCore").FirstOrDefault();
element.SetAttributeValue(key, value);
@@ -326,11 +410,27 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
private void ModifyHandlerSectionInWebConfig(string key, string value)
{
- var webConfigFile = $"{DeploymentParameters.PublishedApplicationRootPath}/web.config";
+ var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config");
var config = XDocument.Load(webConfigFile);
var element = config.Descendants("handlers").FirstOrDefault().Descendants("add").FirstOrDefault();
element.SetAttributeValue(key, value);
- config.Save(webConfigFile);
+ config.Save(webConfigFile);
+ }
+
+ // These elements are duplicated in the web.config if you publish. Remove them from the host.config.
+ private string RemoveRedundantElements(string serverConfig)
+ {
+ var hostConfig = XDocument.Parse(serverConfig);
+
+ var coreElement = hostConfig.Descendants("aspNetCore").FirstOrDefault();
+ coreElement?.Remove();
+
+ var handlersElement = hostConfig.Descendants("handlers").First();
+ var handlerElement = handlersElement.Descendants("add")
+ .Where(x => x.Attribute("name").Value == "aspNetCore").FirstOrDefault();
+ handlerElement?.Remove();
+
+ return hostConfig.ToString();
}
}
}
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs
index dc764bf98a..f33b285d63 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs
@@ -39,37 +39,37 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
$" Supported server types are {nameof(ServerType.Kestrel)}, {nameof(ServerType.IIS)} and {nameof(ServerType.HttpSys)}");
}
- if (string.IsNullOrWhiteSpace(_deploymentParameters.ServerName))
+ if (string.IsNullOrEmpty(_deploymentParameters.ServerName))
{
throw new ArgumentException($"Invalid value '{_deploymentParameters.ServerName}' for {nameof(RemoteWindowsDeploymentParameters.ServerName)}");
}
- if (string.IsNullOrWhiteSpace(_deploymentParameters.ServerAccountName))
+ if (string.IsNullOrEmpty(_deploymentParameters.ServerAccountName))
{
throw new ArgumentException($"Invalid value '{_deploymentParameters.ServerAccountName}' for {nameof(RemoteWindowsDeploymentParameters.ServerAccountName)}." +
" Account credentials are required to enable creating a powershell session to the remote server.");
}
- if (string.IsNullOrWhiteSpace(_deploymentParameters.ServerAccountPassword))
+ if (string.IsNullOrEmpty(_deploymentParameters.ServerAccountPassword))
{
throw new ArgumentException($"Invalid value '{_deploymentParameters.ServerAccountPassword}' for {nameof(RemoteWindowsDeploymentParameters.ServerAccountPassword)}." +
" Account credentials are required to enable creating a powershell session to the remote server.");
}
if (_deploymentParameters.ApplicationType == ApplicationType.Portable
- && string.IsNullOrWhiteSpace(_deploymentParameters.DotnetRuntimePath))
+ && string.IsNullOrEmpty(_deploymentParameters.DotnetRuntimePath))
{
throw new ArgumentException($"Invalid value '{_deploymentParameters.DotnetRuntimePath}' for {nameof(RemoteWindowsDeploymentParameters.DotnetRuntimePath)}. " +
"It must be non-empty for portable apps.");
}
- if (string.IsNullOrWhiteSpace(_deploymentParameters.RemoteServerFileSharePath))
+ if (string.IsNullOrEmpty(_deploymentParameters.RemoteServerFileSharePath))
{
throw new ArgumentException($"Invalid value for {nameof(RemoteWindowsDeploymentParameters.RemoteServerFileSharePath)}." +
" . A file share is required to copy the application's published output.");
}
- if (string.IsNullOrWhiteSpace(_deploymentParameters.ApplicationBaseUriHint))
+ if (string.IsNullOrEmpty(_deploymentParameters.ApplicationBaseUriHint))
{
throw new ArgumentException($"Invalid value for {nameof(RemoteWindowsDeploymentParameters.ApplicationBaseUriHint)}.");
}
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/SelfHostDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/SelfHostDeployer.cs
index 8a9f6d6a4e..ae6a7a08af 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/SelfHostDeployer.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/SelfHostDeployer.cs
@@ -194,22 +194,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
}
- private string GetDotNetExeForArchitecture()
- {
- var executableName = DotnetCommandName;
- // We expect x64 dotnet.exe to be on the path but we have to go searching for the x86 version.
- if (DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture))
- {
- executableName = DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture);
- if (!File.Exists(executableName))
- {
- throw new Exception($"Unable to find '{executableName}'.'");
- }
- }
-
- return executableName;
- }
-
public override void Dispose()
{
using (Logger.BeginScope("SelfHost.Dispose"))
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Http.config b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Http.config
new file mode 100644
index 0000000000..4508dea843
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Http.config
@@ -0,0 +1,1034 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Microsoft.AspNetCore.Server.IntegrationTesting.csproj b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
index 76b5ed9fb2..9e304fde5e 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
@@ -13,6 +13,10 @@
false
+
+
+
+
diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestMatrix.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestMatrix.cs
index 76785d20c7..d577e25dc7 100644
--- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestMatrix.cs
+++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestMatrix.cs
@@ -305,7 +305,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
if (hostingModel == HostingModel.InProcess)
{
// Not supported
- if (Tfm.Matches(Tfm.Net461, tfm) || version == AncmVersion.AspNetCoreModule)
+ if (Tfm.Matches(Tfm.Net461, tfm) || Tfm.Matches(Tfm.NetCoreApp20, tfm) || version == AncmVersion.AspNetCoreModule)
{
continue;
}