diff --git a/build/CodeSign.props b/build/CodeSign.props
index 415c54d527..0d356b8ec6 100644
--- a/build/CodeSign.props
+++ b/build/CodeSign.props
@@ -44,6 +44,8 @@
+
+
diff --git a/build/CodeSign.targets b/build/CodeSign.targets
index 13d89fc64f..ddc1e51d6e 100644
--- a/build/CodeSign.targets
+++ b/build/CodeSign.targets
@@ -1,6 +1,8 @@
+
+ true
$(CodeSignDependsOn);CollectFileSignInfo
diff --git a/build/artifacts.props b/build/artifacts.props
index 4203c4cd99..c9c2348299 100644
--- a/build/artifacts.props
+++ b/build/artifacts.props
@@ -20,7 +20,6 @@
-
@@ -134,7 +133,6 @@
-
diff --git a/build/dependencies.props b/build/dependencies.props
index 48d2a749f4..c69d15bb69 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -97,6 +97,7 @@
2.2.0
2.2.0
+ 2.2.0
0.6.0-rtm-final
diff --git a/build/external-dependencies.props b/build/external-dependencies.props
index e99d58281f..2ea0972af2 100644
--- a/build/external-dependencies.props
+++ b/build/external-dependencies.props
@@ -79,7 +79,7 @@
-
+
diff --git a/eng/dependencies.temp.props b/eng/dependencies.temp.props
index c7e0c9bc50..3ebcecb640 100644
--- a/eng/dependencies.temp.props
+++ b/eng/dependencies.temp.props
@@ -6,6 +6,7 @@ This is required to provide dependencies for samples and tests.
+
diff --git a/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs
index a0579880a0..ce333f232c 100644
--- a/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs
+++ b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs
@@ -94,7 +94,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
private class HostingLogScope : IReadOnlyList>
{
- private readonly HttpContext _httpContext;
+ private readonly string _path;
+ private readonly string _traceIdentifier;
private readonly string _correlationId;
private string _cachedToString;
@@ -113,11 +114,11 @@ namespace Microsoft.AspNetCore.Hosting.Internal
{
if (index == 0)
{
- return new KeyValuePair("RequestId", _httpContext.TraceIdentifier);
+ return new KeyValuePair("RequestId", _traceIdentifier);
}
else if (index == 1)
{
- return new KeyValuePair("RequestPath", _httpContext.Request.Path.ToString());
+ return new KeyValuePair("RequestPath", _path);
}
else if (index == 2)
{
@@ -130,7 +131,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
public HostingLogScope(HttpContext httpContext, string correlationId)
{
- _httpContext = httpContext;
+ _traceIdentifier = httpContext.TraceIdentifier;
+ _path = httpContext.Request.Path.ToString();
_correlationId = correlationId;
}
@@ -141,8 +143,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
_cachedToString = string.Format(
CultureInfo.InvariantCulture,
"RequestId:{0} RequestPath:{1}",
- _httpContext.TraceIdentifier,
- _httpContext.Request.Path);
+ _traceIdentifier,
+ _path);
}
return _cachedToString;
diff --git a/src/Hosting/Hosting/src/WebHostBuilder.cs b/src/Hosting/Hosting/src/WebHostBuilder.cs
index 423b898cec..2238bd3b9b 100644
--- a/src/Hosting/Hosting/src/WebHostBuilder.cs
+++ b/src/Hosting/Hosting/src/WebHostBuilder.cs
@@ -174,13 +174,6 @@ namespace Microsoft.AspNetCore.Hosting
}
}
- var logger = hostingServiceProvider.GetRequiredService>();
- // Warn about duplicate HostingStartupAssemblies
- foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().GroupBy(a => a, StringComparer.OrdinalIgnoreCase).Where(g => g.Count() > 1))
- {
- logger.LogWarning($"The assembly {assemblyName} was specified multiple times. Hosting startup assemblies should only be specified once.");
- }
-
AddApplicationServices(applicationServices, hostingServiceProvider);
var host = new WebHost(
@@ -193,6 +186,14 @@ namespace Microsoft.AspNetCore.Hosting
{
host.Initialize();
+ var logger = host.Services.GetRequiredService>();
+
+ // Warn about duplicate HostingStartupAssemblies
+ foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().GroupBy(a => a, StringComparer.OrdinalIgnoreCase).Where(g => g.Count() > 1))
+ {
+ logger.LogWarning($"The assembly {assemblyName} was specified multiple times. Hosting startup assemblies should only be specified once.");
+ }
+
return host;
}
catch
@@ -208,7 +209,7 @@ namespace Microsoft.AspNetCore.Hosting
var provider = collection.BuildServiceProvider();
var factory = provider.GetService>();
- if (factory != null)
+ if (factory != null && !(factory is DefaultServiceProviderFactory))
{
using (provider)
{
diff --git a/src/Hosting/Hosting/src/WebHostExtensions.cs b/src/Hosting/Hosting/src/WebHostExtensions.cs
index 06a3e00cf8..e73399c419 100644
--- a/src/Hosting/Hosting/src/WebHostExtensions.cs
+++ b/src/Hosting/Hosting/src/WebHostExtensions.cs
@@ -45,8 +45,14 @@ namespace Microsoft.AspNetCore.Hosting
{
AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: string.Empty);
- await host.WaitForTokenShutdownAsync(cts.Token);
- done.Set();
+ try
+ {
+ await host.WaitForTokenShutdownAsync(cts.Token);
+ }
+ finally
+ {
+ done.Set();
+ }
}
}
@@ -80,8 +86,14 @@ namespace Microsoft.AspNetCore.Hosting
var shutdownMessage = host.Services.GetRequiredService().SuppressStatusMessages ? string.Empty : "Application is shutting down...";
AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: shutdownMessage);
- await host.RunAsync(cts.Token, "Application started. Press Ctrl+C to shut down.");
- done.Set();
+ try
+ {
+ await host.RunAsync(cts.Token, "Application started. Press Ctrl+C to shut down.");
+ }
+ finally
+ {
+ done.Set();
+ }
}
}
@@ -92,7 +104,6 @@ namespace Microsoft.AspNetCore.Hosting
await host.StartAsync(token);
var hostingEnvironment = host.Services.GetService();
- var applicationLifetime = host.Services.GetService();
var options = host.Services.GetRequiredService();
if (!options.SuppressStatusMessages)
diff --git a/src/Hosting/Server.IntegrationTesting/src/ApplicationPublisher.cs b/src/Hosting/Server.IntegrationTesting/src/ApplicationPublisher.cs
new file mode 100644
index 0000000000..4d576db725
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/ApplicationPublisher.cs
@@ -0,0 +1,125 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+ public class ApplicationPublisher
+ {
+ public string ApplicationPath { get; }
+
+ public ApplicationPublisher(string applicationPath)
+ {
+ ApplicationPath = applicationPath;
+ }
+
+ public static readonly string DotnetCommandName = "dotnet";
+
+ public virtual Task Publish(DeploymentParameters deploymentParameters, ILogger logger)
+ {
+ var publishDirectory = CreateTempDirectory();
+ using (logger.BeginScope("dotnet-publish"))
+ {
+ if (string.IsNullOrEmpty(deploymentParameters.TargetFramework))
+ {
+ throw new Exception($"A target framework must be specified in the deployment parameters for applications that require publishing before deployment");
+ }
+
+ var parameters = $"publish "
+ + $" --output \"{publishDirectory.FullName}\""
+ + $" --framework {deploymentParameters.TargetFramework}"
+ + $" --configuration {deploymentParameters.Configuration}"
+ + " --no-restore -p:VerifyMatchingImplicitPackageVersion=false";
+ // Set VerifyMatchingImplicitPackageVersion to disable errors when Microsoft.NETCore.App's version is overridden externally
+ // This verification doesn't matter if we are skipping restore during tests.
+
+ if (deploymentParameters.ApplicationType == ApplicationType.Standalone)
+ {
+ parameters += $" --runtime {GetRuntimeIdentifier(deploymentParameters)}";
+ }
+
+ parameters += $" {deploymentParameters.AdditionalPublishParameters}";
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = DotnetCommandName,
+ Arguments = parameters,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ WorkingDirectory = deploymentParameters.ApplicationPath,
+ };
+
+ ProcessHelpers.AddEnvironmentVariablesToProcess(startInfo, deploymentParameters.PublishEnvironmentVariables, logger);
+
+ var hostProcess = new Process() { StartInfo = startInfo };
+
+ logger.LogInformation($"Executing command {DotnetCommandName} {parameters}");
+
+ hostProcess.StartAndCaptureOutAndErrToLogger("dotnet-publish", logger);
+
+ // A timeout is passed to Process.WaitForExit() for two reasons:
+ //
+ // 1. When process output is read asynchronously, WaitForExit() without a timeout blocks until child processes
+ // are killed, which can cause hangs due to MSBuild NodeReuse child processes started by dotnet.exe.
+ // With a timeout, WaitForExit() returns when the parent process is killed and ignores child processes.
+ // https://stackoverflow.com/a/37983587/102052
+ //
+ // 2. If "dotnet publish" does hang indefinitely for some reason, tests should fail fast with an error message.
+ const int timeoutMinutes = 5;
+ if (hostProcess.WaitForExit(milliseconds: timeoutMinutes * 60 * 1000))
+ {
+ if (hostProcess.ExitCode != 0)
+ {
+ var message = $"{DotnetCommandName} publish exited with exit code : {hostProcess.ExitCode}";
+ logger.LogError(message);
+ throw new Exception(message);
+ }
+ }
+ else
+ {
+ var message = $"{DotnetCommandName} publish failed to exit after {timeoutMinutes} minutes";
+ logger.LogError(message);
+ throw new Exception(message);
+ }
+
+ logger.LogInformation($"{DotnetCommandName} publish finished with exit code : {hostProcess.ExitCode}");
+ }
+
+ return Task.FromResult(new PublishedApplication(publishDirectory.FullName, logger));
+ }
+
+ private static string GetRuntimeIdentifier(DeploymentParameters deploymentParameters)
+ {
+ var architecture = deploymentParameters.RuntimeArchitecture;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return "win7-" + architecture;
+ }
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return "linux-" + architecture;
+ }
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return "osx-" + architecture;
+ }
+ throw new InvalidOperationException("Unrecognized operation system platform");
+ }
+
+ protected static DirectoryInfo CreateTempDirectory()
+ {
+ var tempPath = Path.GetTempPath() + Guid.NewGuid().ToString("N");
+ var target = new DirectoryInfo(tempPath);
+ target.Create();
+ return target;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/CachingApplicationPublisher.cs b/src/Hosting/Server.IntegrationTesting/src/CachingApplicationPublisher.cs
new file mode 100644
index 0000000000..e32d8e22db
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/CachingApplicationPublisher.cs
@@ -0,0 +1,96 @@
+// 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.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+ public class CachingApplicationPublisher: ApplicationPublisher, IDisposable
+ {
+ private readonly Dictionary _publishCache = new Dictionary();
+
+ public CachingApplicationPublisher(string applicationPath) : base(applicationPath)
+ {
+ }
+
+ public override async Task Publish(DeploymentParameters deploymentParameters, ILogger logger)
+ {
+ if (ApplicationPath != deploymentParameters.ApplicationPath)
+ {
+ throw new InvalidOperationException("ApplicationPath mismatch");
+ }
+
+ if (deploymentParameters.PublishEnvironmentVariables.Any())
+ {
+ throw new InvalidOperationException("DeploymentParameters.PublishEnvironmentVariables not supported");
+ }
+
+ if (!string.IsNullOrEmpty(deploymentParameters.PublishedApplicationRootPath))
+ {
+ throw new InvalidOperationException("DeploymentParameters.PublishedApplicationRootPath not supported");
+ }
+
+ var dotnetPublishParameters = new DotnetPublishParameters
+ {
+ TargetFramework = deploymentParameters.TargetFramework,
+ Configuration = deploymentParameters.Configuration,
+ ApplicationType = deploymentParameters.ApplicationType,
+ RuntimeArchitecture = deploymentParameters.RuntimeArchitecture
+ };
+
+ if (!_publishCache.TryGetValue(dotnetPublishParameters, out var publishedApplication))
+ {
+ publishedApplication = await base.Publish(deploymentParameters, logger);
+ _publishCache.Add(dotnetPublishParameters, publishedApplication);
+ }
+
+ return new PublishedApplication(CopyPublishedOutput(publishedApplication, logger), logger);
+ }
+
+ private string CopyPublishedOutput(PublishedApplication application, ILogger logger)
+ {
+ var target = CreateTempDirectory();
+
+ var source = new DirectoryInfo(application.Path);
+ CopyFiles(source, target, logger);
+ return target.FullName;
+ }
+
+ public static void CopyFiles(DirectoryInfo source, DirectoryInfo target, ILogger logger)
+ {
+ foreach (DirectoryInfo directoryInfo in source.GetDirectories())
+ {
+ CopyFiles(directoryInfo, target.CreateSubdirectory(directoryInfo.Name), logger);
+ }
+
+ logger.LogDebug($"Processing {target.FullName}");
+ foreach (FileInfo fileInfo in source.GetFiles())
+ {
+ logger.LogDebug($" Copying {fileInfo.Name}");
+ var destFileName = Path.Combine(target.FullName, fileInfo.Name);
+ fileInfo.CopyTo(destFileName);
+ }
+ }
+
+ public void Dispose()
+ {
+ foreach (var publishedApp in _publishCache.Values)
+ {
+ publishedApp.Dispose();
+ }
+ }
+
+ private struct DotnetPublishParameters
+ {
+ public string TargetFramework { get; set; }
+ public string Configuration { get; set; }
+ public ApplicationType ApplicationType { get; set; }
+ public RuntimeArchitecture RuntimeArchitecture { get; set; }
+ }
+ }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/ANCMVersion.cs b/src/Hosting/Server.IntegrationTesting/src/Common/ANCMVersion.cs
new file mode 100644
index 0000000000..38cf93ffcd
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/ANCMVersion.cs
@@ -0,0 +1,12 @@
+// 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.AspNetCore.Server.IntegrationTesting
+{
+ public enum AncmVersion
+ {
+ None,
+ AspNetCoreModule,
+ AspNetCoreModuleV2
+ }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/ApplicationType.cs b/src/Hosting/Server.IntegrationTesting/src/Common/ApplicationType.cs
index 02d8715984..3a8afeb94d 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Common/ApplicationType.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/ApplicationType.cs
@@ -5,7 +5,14 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
public enum ApplicationType
{
+ ///
+ /// Does not target a specific platform. Requires the matching runtime to be installed.
+ ///
Portable,
+
+ ///
+ /// All dlls are published with the app for x-copy deploy. Net461 requires this because ASP.NET Core is not in the GAC.
+ ///
Standalone
}
}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs b/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs
index 3d824741c3..5e347e4933 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs
@@ -13,6 +13,35 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
///
public class DeploymentParameters
{
+ public DeploymentParameters()
+ {
+ EnvironmentVariables["ASPNETCORE_DETAILEDERRORS"] = "true";
+
+ var configAttribute = Assembly.GetCallingAssembly().GetCustomAttribute();
+ if (configAttribute != null && !string.IsNullOrEmpty(configAttribute.Configuration))
+ {
+ Configuration = configAttribute.Configuration;
+ }
+ }
+
+ public DeploymentParameters(TestVariant variant)
+ {
+ EnvironmentVariables["ASPNETCORE_DETAILEDERRORS"] = "true";
+
+ var configAttribute = Assembly.GetCallingAssembly().GetCustomAttribute();
+ if (configAttribute != null && !string.IsNullOrEmpty(configAttribute.Configuration))
+ {
+ Configuration = configAttribute.Configuration;
+ }
+
+ ServerType = variant.Server;
+ TargetFramework = variant.Tfm;
+ ApplicationType = variant.ApplicationType;
+ RuntimeArchitecture = variant.Architecture;
+ HostingModel = variant.HostingModel;
+ AncmVersion = variant.AncmVersion;
+ }
+
///
/// Creates an instance of .
///
@@ -36,11 +65,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
throw new DirectoryNotFoundException(string.Format("Application path {0} does not exist.", applicationPath));
}
- if (runtimeArchitecture == RuntimeArchitecture.x86 && runtimeFlavor == RuntimeFlavor.CoreClr)
- {
- throw new NotSupportedException("32 bit deployment is not yet supported for CoreCLR. Don't remove the tests, just disable them for now.");
- }
-
ApplicationPath = applicationPath;
ApplicationName = new DirectoryInfo(ApplicationPath).Name;
ServerType = serverType;
@@ -54,11 +78,34 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
}
- public ServerType ServerType { get; }
+ public DeploymentParameters(DeploymentParameters parameters)
+ {
+ foreach (var propertyInfo in typeof(DeploymentParameters).GetProperties())
+ {
+ if (propertyInfo.CanWrite)
+ {
+ propertyInfo.SetValue(this, propertyInfo.GetValue(parameters));
+ }
+ }
- public RuntimeFlavor RuntimeFlavor { get; }
+ foreach (var kvp in parameters.EnvironmentVariables)
+ {
+ EnvironmentVariables.Add(kvp);
+ }
- public RuntimeArchitecture RuntimeArchitecture { get; } = RuntimeArchitecture.x64;
+ foreach (var kvp in parameters.PublishEnvironmentVariables)
+ {
+ PublishEnvironmentVariables.Add(kvp);
+ }
+ }
+
+ public ApplicationPublisher ApplicationPublisher { get; set; }
+
+ public ServerType ServerType { get; set; }
+
+ public RuntimeFlavor RuntimeFlavor { get; set; }
+
+ public RuntimeArchitecture RuntimeArchitecture { get; set; } = RuntimeArchitecture.x64;
///
/// Suggested base url for the deployed application. The final deployed url could be
@@ -67,15 +114,20 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
///
public string ApplicationBaseUriHint { get; set; }
+ ///
+ /// Scheme used by the deployed application if is empty.
+ ///
+ public string Scheme { get; set; } = Uri.UriSchemeHttp;
+
public string EnvironmentName { get; set; }
public string ServerConfigTemplateContent { get; set; }
public string ServerConfigLocation { get; set; }
- public string SiteName { get; set; }
+ public string SiteName { get; set; } = "HttpTestSite";
- public string ApplicationPath { get; }
+ public string ApplicationPath { get; set; }
///
/// Gets or sets the name of the application. This is used to execute the application when deployed.
@@ -95,11 +147,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
///
public string AdditionalPublishParameters { get; set; }
- ///
- /// Publish restores by default, this property opts out by default.
- ///
- public bool RestoreOnPublish { get; set; }
-
///
/// To publish the application before deployment.
///
@@ -115,6 +162,12 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
public HostingModel HostingModel { get; set; }
+ ///
+ /// When using the IISExpressDeployer, determines whether to use the older or newer version
+ /// of ANCM.
+ ///
+ public AncmVersion AncmVersion { get; set; } = AncmVersion.AspNetCoreModule;
+
///
/// Environment variables to be set before starting the host.
/// Not applicable for IIS Scenarios.
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/DotNetCommands.cs b/src/Hosting/Server.IntegrationTesting/src/Common/DotNetCommands.cs
new file mode 100644
index 0000000000..e30e9defc5
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/DotNetCommands.cs
@@ -0,0 +1,71 @@
+// 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.IO;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+ public static class DotNetCommands
+ {
+ private const string _dotnetFolderName = ".dotnet";
+
+ internal static string DotNetHome { get; } = GetDotNetHome();
+
+ // Compare to https://github.com/aspnet/BuildTools/blob/314c98e4533217a841ff9767bb38e144eb6c93e4/tools/KoreBuild.Console/Commands/CommandContext.cs#L76
+ public static string GetDotNetHome()
+ {
+ var dotnetHome = Environment.GetEnvironmentVariable("DOTNET_HOME");
+ var userProfile = Environment.GetEnvironmentVariable("USERPROFILE");
+ var home = Environment.GetEnvironmentVariable("HOME");
+
+ var result = Path.Combine(Directory.GetCurrentDirectory(), _dotnetFolderName);
+ if (!string.IsNullOrEmpty(dotnetHome))
+ {
+ result = dotnetHome;
+ }
+ else if (!string.IsNullOrEmpty(userProfile))
+ {
+ result = Path.Combine(userProfile, _dotnetFolderName);
+ }
+ else if (!string.IsNullOrEmpty(home))
+ {
+ result = home;
+ }
+
+ return result;
+ }
+
+ public static string GetDotNetInstallDir(RuntimeArchitecture arch)
+ {
+ var dotnetDir = DotNetHome;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ dotnetDir = Path.Combine(dotnetDir, arch.ToString());
+ }
+
+ return dotnetDir;
+ }
+
+ public static string GetDotNetExecutable(RuntimeArchitecture arch)
+ {
+ var dotnetDir = GetDotNetInstallDir(arch);
+
+ var dotnetFile = "dotnet";
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ dotnetFile += ".exe";
+ }
+
+ return Path.Combine(dotnetDir, dotnetFile);
+ }
+
+ public static bool IsRunningX86OnX64(RuntimeArchitecture arch)
+ {
+ return (RuntimeInformation.OSArchitecture == Architecture.X64 || RuntimeInformation.OSArchitecture == Architecture.Arm64)
+ && arch == RuntimeArchitecture.x86;
+ }
+ }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs b/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs
index 1eb1aea8c0..5eea2b8ce3 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs
@@ -3,8 +3,12 @@
namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
+ ///
+ /// For ANCM
+ ///
public enum HostingModel
{
+ None,
OutOfProcess,
InProcess
}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/IWebHostExtensions.cs b/src/Hosting/Server.IntegrationTesting/src/Common/IWebHostExtensions.cs
new file mode 100644
index 0000000000..732a598ab8
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/IWebHostExtensions.cs
@@ -0,0 +1,13 @@
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+ public static class IWebHostExtensions
+ {
+ public static string GetAddress(this IWebHost host)
+ {
+ return host.ServerFeatures.Get().Addresses.First();
+ }
+ }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeFlavor.cs b/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeFlavor.cs
index 510c713f59..3d65f0eaca 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeFlavor.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeFlavor.cs
@@ -5,7 +5,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
public enum RuntimeFlavor
{
- Clr,
- CoreClr
+ None,
+ CoreClr,
+ Clr
}
}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/ServerType.cs b/src/Hosting/Server.IntegrationTesting/src/Common/ServerType.cs
index 060c8ed0ca..ac84d0225f 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Common/ServerType.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/ServerType.cs
@@ -5,9 +5,10 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
public enum ServerType
{
+ None,
IISExpress,
IIS,
- WebListener,
+ HttpSys,
Kestrel,
Nginx
}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs b/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs
new file mode 100644
index 0000000000..05c6080cd5
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/TestPortHelper.cs
@@ -0,0 +1,60 @@
+// 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.Net;
+using System.Net.Sockets;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
+{
+ public static class TestPortHelper
+ {
+ // Copied from https://github.com/aspnet/KestrelHttpServer/blob/47f1db20e063c2da75d9d89653fad4eafe24446c/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs#L508
+ //
+ // This method is an attempt to safely get a free port from the OS. Most of the time,
+ // when binding to dynamic port "0" the OS increments the assigned port, so it's safe
+ // to re-use the assigned port in another process. However, occasionally the OS will reuse
+ // a recently assigned port instead of incrementing, which causes flaky tests with AddressInUse
+ // exceptions. This method should only be used when the application itself cannot use
+ // dynamic port "0" (e.g. IISExpress). Most functional tests using raw Kestrel
+ // (with status messages enabled) should directly bind to dynamic port "0" and scrape
+ // the assigned port from the status message, which should be 100% reliable since the port
+ // is bound once and never released.
+ public static int GetNextPort()
+ {
+ using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+ {
+ socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ return ((IPEndPoint)socket.LocalEndPoint).Port;
+ }
+ }
+
+ // IIS Express preregisteres 44300-44399 ports with SSL bindings.
+ // So some tests always have to use ports in this range, and we can't rely on OS-allocated ports without a whole lot of ceremony around
+ // creating self-signed certificates and registering SSL bindings with HTTP.sys
+ public static int GetNextSSLPort()
+ {
+ var next = 44300;
+ using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+ {
+ while (true)
+ {
+ try
+ {
+ var port = next++;
+ socket.Bind(new IPEndPoint(IPAddress.Loopback, port));
+ return port;
+ }
+ catch (SocketException)
+ {
+ // Retry unless exhausted
+ if (next > 44399)
+ {
+ throw;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs b/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs
index 7ebcb30367..5a79a31cef 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs
@@ -2,27 +2,24 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Net;
-using System.Net.Sockets;
-
namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
{
public static class TestUriHelper
{
- public static Uri BuildTestUri()
+ public static Uri BuildTestUri(ServerType serverType)
{
- return BuildTestUri(null);
+ return BuildTestUri(serverType, hint: null);
}
- public static Uri BuildTestUri(string hint)
+ public static Uri BuildTestUri(ServerType serverType, string hint)
{
- // If this method is called directly, there is no way to know the server type or whether status messages
- // are enabled. It's safest to assume the server is WebListener (which doesn't support binding to dynamic
- // port "0") and status messages are not enabled (so the assigned port cannot be scraped from console output).
- return BuildTestUri(hint, serverType: ServerType.WebListener, statusMessagesEnabled: false);
+ // Assume status messages are enabled for Kestrel and disabled for all other servers.
+ var statusMessagesEnabled = (serverType == ServerType.Kestrel);
+
+ return BuildTestUri(serverType, Uri.UriSchemeHttp, hint, statusMessagesEnabled);
}
- internal static Uri BuildTestUri(string hint, ServerType serverType, bool statusMessagesEnabled)
+ internal static Uri BuildTestUri(ServerType serverType, string scheme, string hint, bool statusMessagesEnabled)
{
if (string.IsNullOrEmpty(hint))
{
@@ -33,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
// once and never released. Binding to dynamic port "0" on "localhost" (both IPv4 and IPv6) is not
// supported, so the port is only bound on "127.0.0.1" (IPv4). If a test explicitly requires IPv6,
// it should provide a hint URL with "localhost" (IPv4 and IPv6) or "[::1]" (IPv6-only).
- return new UriBuilder("http", "127.0.0.1", 0).Uri;
+ return new UriBuilder(scheme, "127.0.0.1", 0).Uri;
}
else
{
@@ -41,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
// from which to scrape the assigned port, so the less reliable GetNextPort() must be used. The
// port is bound on "localhost" (both IPv4 and IPv6), since this is supported when using a specific
// (non-zero) port.
- return new UriBuilder("http", "localhost", GetNextPort()).Uri;
+ return new UriBuilder(scheme, "localhost", TestPortHelper.GetNextPort()).Uri;
}
}
else
@@ -52,7 +49,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
// Only a few tests use this codepath, so it's fine to use the less reliable GetNextPort() for simplicity.
// The tests using this codepath will be reviewed to see if they can be changed to directly bind to dynamic
// port "0" on "127.0.0.1" and scrape the assigned port from the status message (the default codepath).
- return new UriBuilder(uriHint) { Port = GetNextPort() }.Uri;
+ return new UriBuilder(uriHint) { Port = TestPortHelper.GetNextPort() }.Uri;
}
else
{
@@ -61,25 +58,5 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
}
}
}
-
- // Copied from https://github.com/aspnet/KestrelHttpServer/blob/47f1db20e063c2da75d9d89653fad4eafe24446c/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs#L508
- //
- // This method is an attempt to safely get a free port from the OS. Most of the time,
- // when binding to dynamic port "0" the OS increments the assigned port, so it's safe
- // to re-use the assigned port in another process. However, occasionally the OS will reuse
- // a recently assigned port instead of incrementing, which causes flaky tests with AddressInUse
- // exceptions. This method should only be used when the application itself cannot use
- // dynamic port "0" (e.g. IISExpress). Most functional tests using raw Kestrel
- // (with status messages enabled) should directly bind to dynamic port "0" and scrape
- // the assigned port from the status message, which should be 100% reliable since the port
- // is bound once and never released.
- public static int GetNextPort()
- {
- using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
- {
- socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
- return ((IPEndPoint)socket.LocalEndPoint).Port;
- }
- }
}
}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/TestUrlHelper.cs b/src/Hosting/Server.IntegrationTesting/src/Common/TestUrlHelper.cs
new file mode 100644
index 0000000000..f03321eba6
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/TestUrlHelper.cs
@@ -0,0 +1,11 @@
+namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
+{
+ // Public for use in other test projects
+ public static class TestUrlHelper
+ {
+ public static string GetTestUrl(ServerType serverType)
+ {
+ return TestUriHelper.BuildTestUri(serverType).ToString();
+ }
+ }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/Tfm.cs b/src/Hosting/Server.IntegrationTesting/src/Common/Tfm.cs
new file mode 100644
index 0000000000..56715d3c08
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/Tfm.cs
@@ -0,0 +1,20 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+ public static class Tfm
+ {
+ public const string Net461 = "net461";
+ public const string NetCoreApp20 = "netcoreapp2.0";
+ public const string NetCoreApp21 = "netcoreapp2.1";
+ public const string NetCoreApp22 = "netcoreapp2.2";
+
+ public static bool Matches(string tfm1, string tfm2)
+ {
+ return string.Equals(tfm1, tfm2, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployer.cs
index 3c81f32902..5e1457e70f 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployer.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployer.cs
@@ -16,88 +16,73 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
///
/// Abstract base class of all deployers with implementation of some of the common helpers.
///
- public abstract class ApplicationDeployer : IApplicationDeployer
+ public abstract class ApplicationDeployer : IDisposable
{
public static readonly string DotnetCommandName = "dotnet";
- // This is the argument that separates the dotnet arguments for the args being passed to the
- // app being run when running dotnet run
- public static readonly string DotnetArgumentSeparator = "--";
-
private readonly Stopwatch _stopwatch = new Stopwatch();
+ private PublishedApplication _publishedApplication;
+
public ApplicationDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
{
DeploymentParameters = deploymentParameters;
LoggerFactory = loggerFactory;
Logger = LoggerFactory.CreateLogger(GetType().FullName);
+
+ ValidateParameters();
+ }
+
+ private void ValidateParameters()
+ {
+ if (DeploymentParameters.ServerType == ServerType.None)
+ {
+ throw new ArgumentException($"Invalid ServerType '{DeploymentParameters.ServerType}'.");
+ }
+
+ if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.None && !string.IsNullOrEmpty(DeploymentParameters.TargetFramework))
+ {
+ DeploymentParameters.RuntimeFlavor = GetRuntimeFlavor(DeploymentParameters.TargetFramework);
+ }
+
+ if (string.IsNullOrEmpty(DeploymentParameters.ApplicationPath))
+ {
+ throw new ArgumentException("ApplicationPath cannot be null.");
+ }
+
+ if (!Directory.Exists(DeploymentParameters.ApplicationPath))
+ {
+ throw new DirectoryNotFoundException(string.Format("Application path {0} does not exist.", DeploymentParameters.ApplicationPath));
+ }
+
+ if (string.IsNullOrEmpty(DeploymentParameters.ApplicationName))
+ {
+ DeploymentParameters.ApplicationName = new DirectoryInfo(DeploymentParameters.ApplicationPath).Name;
+ }
+ }
+
+ private RuntimeFlavor GetRuntimeFlavor(string tfm)
+ {
+ if (Tfm.Matches(Tfm.Net461, tfm))
+ {
+ return RuntimeFlavor.Clr;
+ }
+ return RuntimeFlavor.CoreClr;
}
protected DeploymentParameters DeploymentParameters { get; }
protected ILoggerFactory LoggerFactory { get; }
+
protected ILogger Logger { get; }
public abstract Task DeployAsync();
protected void DotnetPublish(string publishRoot = null)
{
- using (Logger.BeginScope("dotnet-publish"))
- {
- if (string.IsNullOrEmpty(DeploymentParameters.TargetFramework))
- {
- throw new Exception($"A target framework must be specified in the deployment parameters for applications that require publishing before deployment");
- }
-
- DeploymentParameters.PublishedApplicationRootPath = publishRoot ?? Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
-
- var parameters = $"publish "
- + $" --output \"{DeploymentParameters.PublishedApplicationRootPath}\""
- + $" --framework {DeploymentParameters.TargetFramework}"
- + $" --configuration {DeploymentParameters.Configuration}"
- + (DeploymentParameters.RestoreOnPublish
- ? string.Empty
- : " --no-restore -p:VerifyMatchingImplicitPackageVersion=false");
- // Set VerifyMatchingImplicitPackageVersion to disable errors when Microsoft.NETCore.App's version is overridden externally
- // This verification doesn't matter if we are skipping restore during tests.
-
- if (DeploymentParameters.ApplicationType == ApplicationType.Standalone)
- {
- parameters += $" --runtime {GetRuntimeIdentifier()}";
- }
-
- parameters += $" {DeploymentParameters.AdditionalPublishParameters}";
-
- var startInfo = new ProcessStartInfo
- {
- FileName = DotnetCommandName,
- Arguments = parameters,
- UseShellExecute = false,
- CreateNoWindow = true,
- RedirectStandardError = true,
- RedirectStandardOutput = true,
- WorkingDirectory = DeploymentParameters.ApplicationPath,
- };
-
- AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.PublishEnvironmentVariables);
-
- var hostProcess = new Process() { StartInfo = startInfo };
-
- Logger.LogInformation($"Executing command {DotnetCommandName} {parameters}");
-
- hostProcess.StartAndCaptureOutAndErrToLogger("dotnet-publish", Logger);
-
- hostProcess.WaitForExit();
-
- if (hostProcess.ExitCode != 0)
- {
- var message = $"{DotnetCommandName} publish exited with exit code : {hostProcess.ExitCode}";
- Logger.LogError(message);
- throw new Exception(message);
- }
-
- Logger.LogInformation($"{DotnetCommandName} publish finished with exit code : {hostProcess.ExitCode}");
- }
+ var publisher = DeploymentParameters.ApplicationPublisher ?? new ApplicationPublisher(DeploymentParameters.ApplicationPath);
+ _publishedApplication = publisher.Publish(DeploymentParameters, Logger).GetAwaiter().GetResult();
+ DeploymentParameters.PublishedApplicationRootPath = _publishedApplication.Path;
}
protected void CleanPublishedOutput()
@@ -112,15 +97,27 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
else
{
- RetryHelper.RetryOperation(
- () => Directory.Delete(DeploymentParameters.PublishedApplicationRootPath, true),
- e => Logger.LogWarning($"Failed to delete directory : {e.Message}"),
- retryCount: 3,
- retryDelayMilliseconds: 100);
+ _publishedApplication.Dispose();
}
}
}
+ 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)
@@ -147,26 +144,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
protected void AddEnvironmentVariablesToProcess(ProcessStartInfo startInfo, IDictionary environmentVariables)
{
var environment = startInfo.Environment;
- SetEnvironmentVariable(environment, "ASPNETCORE_ENVIRONMENT", DeploymentParameters.EnvironmentName);
-
- foreach (var environmentVariable in environmentVariables)
- {
- SetEnvironmentVariable(environment, environmentVariable.Key, environmentVariable.Value);
- }
- }
-
- protected void SetEnvironmentVariable(IDictionary environment, string name, string value)
- {
- if (value == null)
- {
- Logger.LogInformation("Removing environment variable {name}", name);
- environment.Remove(name);
- }
- else
- {
- Logger.LogInformation("SET {name}={value}", name, value);
- environment[name] = value;
- }
+ ProcessHelpers.SetEnvironmentVariable(environment, "ASPNETCORE_ENVIRONMENT", DeploymentParameters.EnvironmentName, Logger);
+ ProcessHelpers.AddEnvironmentVariablesToProcess(startInfo, environmentVariables, Logger);
}
protected void InvokeUserApplicationCleanup()
@@ -214,39 +193,5 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
public abstract void Dispose();
-
- private string GetRuntimeIdentifier()
- {
- var architecture = GetArchitecture();
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return "win7-" + architecture;
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return "linux-" + architecture;
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return "osx-" + architecture;
- }
- else
- {
- throw new InvalidOperationException("Unrecognized operation system platform");
- }
- }
-
- private string GetArchitecture()
- {
- switch (RuntimeInformation.OSArchitecture)
- {
- case Architecture.X86:
- return "x86";
- case Architecture.X64:
- return "x64";
- default:
- throw new NotSupportedException($"Unsupported architecture: {RuntimeInformation.OSArchitecture}");
- }
- }
}
}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployerFactory.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployerFactory.cs
index eb66761807..959f4ffeed 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployerFactory.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployerFactory.cs
@@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
///
///
///
- public static IApplicationDeployer Create(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
+ public static ApplicationDeployer Create(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
{
if (deploymentParameters == null)
{
@@ -32,10 +32,9 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
switch (deploymentParameters.ServerType)
{
case ServerType.IISExpress:
- return new IISExpressDeployer(deploymentParameters, loggerFactory);
case ServerType.IIS:
- throw new NotSupportedException("The IIS deployer is no longer supported");
- case ServerType.WebListener:
+ throw new NotSupportedException("Use Microsoft.AspNetCore.Server.IntegrationTesting.IIS package and IISApplicationDeployerFactory for IIS support.");
+ case ServerType.HttpSys:
case ServerType.Kestrel:
return new SelfHostDeployer(deploymentParameters, loggerFactory);
case ServerType.Nginx:
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/IApplicationDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/IApplicationDeployer.cs
deleted file mode 100644
index 400ae978ed..0000000000
--- a/src/Hosting/Server.IntegrationTesting/src/Deployers/IApplicationDeployer.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// 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.Threading.Tasks;
-
-namespace Microsoft.AspNetCore.Server.IntegrationTesting
-{
- ///
- /// Common operations on an application deployer.
- ///
- public interface IApplicationDeployer : IDisposable
- {
- ///
- /// Deploys the application to the target with specified .
- ///
- ///
- Task DeployAsync();
- }
-}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/IISExpressDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/IISExpressDeployer.cs
deleted file mode 100644
index bc7aecb700..0000000000
--- a/src/Hosting/Server.IntegrationTesting/src/Deployers/IISExpressDeployer.cs
+++ /dev/null
@@ -1,317 +0,0 @@
-// 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.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
-using Microsoft.AspNetCore.Testing;
-using Microsoft.Extensions.Logging;
-
-namespace Microsoft.AspNetCore.Server.IntegrationTesting
-{
- ///
- /// Deployment helper for IISExpress.
- ///
- public class IISExpressDeployer : ApplicationDeployer
- {
- private const string IISExpressRunningMessage = "IIS Express is running.";
- private const string FailedToInitializeBindingsMessage = "Failed to initialize site bindings";
- private const string UnableToStartIISExpressMessage = "Unable to start iisexpress.";
- private const int MaximumAttempts = 5;
-
- private static readonly Regex UrlDetectorRegex = new Regex(@"^\s*Successfully registered URL ""(?[^""]+)"" for site.*$");
-
- private Process _hostProcess;
-
- public IISExpressDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
- : base(deploymentParameters, loggerFactory)
- {
- }
-
- public bool IsWin8OrLater
- {
- get
- {
- var win8Version = new Version(6, 2);
-
- return (Environment.OSVersion.Version >= win8Version);
- }
- }
-
- public bool Is64BitHost
- {
- get
- {
- return Environment.Is64BitOperatingSystem;
- }
- }
-
- public override async Task DeployAsync()
- {
- using (Logger.BeginScope("Deployment"))
- {
- // Start timer
- StartTimer();
-
- // For now we always auto-publish. Otherwise we'll have to write our own local web.config for the HttpPlatformHandler
- DeploymentParameters.PublishApplicationBeforeDeployment = true;
- if (DeploymentParameters.PublishApplicationBeforeDeployment)
- {
- DotnetPublish();
- }
-
- var contentRoot = DeploymentParameters.PublishApplicationBeforeDeployment ? DeploymentParameters.PublishedApplicationRootPath : DeploymentParameters.ApplicationPath;
-
- var testUri = TestUriHelper.BuildTestUri(DeploymentParameters.ApplicationBaseUriHint);
-
- // Launch the host process.
- var (actualUri, hostExitToken) = await StartIISExpressAsync(testUri, contentRoot);
-
- Logger.LogInformation("Application ready at URL: {appUrl}", actualUri);
-
- // Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath.
- return new DeploymentResult(
- LoggerFactory,
- DeploymentParameters,
- applicationBaseUri: actualUri.ToString(),
- contentRoot: contentRoot,
- hostShutdownToken: hostExitToken);
- }
- }
-
- private async Task<(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot)
- {
- using (Logger.BeginScope("StartIISExpress"))
- {
- var port = uri.Port;
- if (port == 0)
- {
- port = TestUriHelper.GetNextPort();
- }
-
- 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.
-
- if (serverConfig.Contains("[ANCMPath]"))
- {
- // We need to pick the bitness based the OS / IIS Express, not the application.
- // We'll eventually add support for choosing which IIS Express bitness to run: https://github.com/aspnet/Hosting/issues/880
- var ancmFile = Path.Combine(contentRoot, Is64BitHost ? @"x64\aspnetcore.dll" : @"x86\aspnetcore.dll");
- // Bin deployed by Microsoft.AspNetCore.AspNetCoreModule.nupkg
-
- if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
- {
- throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
- }
-
- Logger.LogDebug("Writing ANCMPath '{ancmPath}' to config", ancmFile);
- serverConfig =
- serverConfig.Replace("[ANCMPath]", ancmFile);
- }
-
- 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)
- {
- ModifyWebConfigToInProcess();
- }
-
- 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
- {
- FileName = iisExpressPath,
- Arguments = parameters,
- UseShellExecute = false,
- CreateNoWindow = true,
- RedirectStandardError = true,
- RedirectStandardOutput = true
- };
-
- AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);
-
- Uri url = null;
- var started = new TaskCompletionSource();
-
- var process = new Process() { StartInfo = startInfo };
- process.OutputDataReceived += (sender, dataArgs) =>
- {
- if (string.Equals(dataArgs.Data, UnableToStartIISExpressMessage))
- {
- // We completely failed to start and we don't really know why
- started.TrySetException(new InvalidOperationException("Failed to start IIS Express"));
- }
- else if (string.Equals(dataArgs.Data, FailedToInitializeBindingsMessage))
- {
- started.TrySetResult(false);
- }
- else if (string.Equals(dataArgs.Data, IISExpressRunningMessage))
- {
- started.TrySetResult(true);
- }
- else if (!string.IsNullOrEmpty(dataArgs.Data))
- {
- var m = UrlDetectorRegex.Match(dataArgs.Data);
- if (m.Success)
- {
- url = new Uri(m.Groups["url"].Value);
- }
- }
- };
-
- process.EnableRaisingEvents = true;
- var hostExitTokenSource = new CancellationTokenSource();
- process.Exited += (sender, e) =>
- {
- Logger.LogInformation("iisexpress Process {pid} shut down", process.Id);
-
- // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want
- started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {process.ExitCode}"));
-
- TriggerHostShutdown(hostExitTokenSource);
- };
- process.StartAndCaptureOutAndErrToLogger("iisexpress", Logger);
- Logger.LogInformation("iisexpress Process {pid} started", process.Id);
-
- if (process.HasExited)
- {
- Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, process.Id, process.ExitCode);
- throw new Exception("Failed to start host");
- }
-
- // Wait for the app to start
- // The timeout here is large, because we don't know how long the test could need
- // We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build
- // 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);
-
- // Wait for the process to exit and try again
- process.WaitForExit(30 * 1000);
- await Task.Delay(1000); // Wait a second to make sure the socket is completely cleaned up
- }
- else
- {
- _hostProcess = process;
- Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port);
- return (url: url, hostExitToken: hostExitTokenSource.Token);
- }
- }
-
- var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port";
- Logger.LogError(message);
- throw new TimeoutException(message);
- }
- }
-
- private string GetIISExpressPath()
- {
- // Get path to program files
- var iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("SystemDrive") + "\\", "Program Files", "IIS Express", "iisexpress.exe");
-
- if (!File.Exists(iisExpressPath))
- {
- throw new Exception("Unable to find IISExpress on the machine: " + iisExpressPath);
- }
-
- return iisExpressPath;
- }
-
- public override void Dispose()
- {
- using (Logger.BeginScope("Dispose"))
- {
- ShutDownIfAnyHostProcess(_hostProcess);
-
- if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigLocation)
- && File.Exists(DeploymentParameters.ServerConfigLocation))
- {
- // Delete the temp applicationHostConfig that we created.
- Logger.LogDebug("Deleting applicationHost.config file from {configLocation}", DeploymentParameters.ServerConfigLocation);
- try
- {
- File.Delete(DeploymentParameters.ServerConfigLocation);
- }
- catch (Exception exception)
- {
- // Ignore delete failures - just write a log.
- Logger.LogWarning("Failed to delete '{config}'. Exception : {exception}", DeploymentParameters.ServerConfigLocation, exception.Message);
- }
- }
-
- if (DeploymentParameters.PublishApplicationBeforeDeployment)
- {
- CleanPublishedOutput();
- }
-
- InvokeUserApplicationCleanup();
-
- StopTimer();
- }
-
- // If by this point, the host process is still running (somehow), throw an error.
- // A test failure is better than a silent hang and unknown failure later on
- if (_hostProcess != null && !_hostProcess.HasExited)
- {
- throw new Exception($"iisexpress Process {_hostProcess.Id} failed to shutdown");
- }
- }
-
- // Transforms the web.config file to include the hostingModel="inprocess" element
- // and adds the server type = Microsoft.AspNetServer.IIS such that Kestrel isn't added again in ServerTests
- private void ModifyWebConfigToInProcess()
- {
- var webConfigFile = $"{DeploymentParameters.PublishedApplicationRootPath}/web.config";
- var config = XDocument.Load(webConfigFile);
- var element = config.Descendants("aspNetCore").FirstOrDefault();
- element.SetAttributeValue("hostingModel", "inprocess");
- config.Save(webConfigFile);
- }
- }
-}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs
index 1bc6c4b766..262ff80ce8 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs
@@ -4,7 +4,10 @@
using System;
using System.Diagnostics;
using System.IO;
+using System.Net;
using System.Net.Http;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
using Microsoft.Extensions.Logging;
@@ -18,6 +21,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
private string _configFile;
private readonly int _waitTime = (int)TimeSpan.FromSeconds(30).TotalMilliseconds;
+ private Socket _portSelector;
public NginxDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
: base(deploymentParameters, loggerFactory)
@@ -29,11 +33,37 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
using (Logger.BeginScope("Deploy"))
{
_configFile = Path.GetTempFileName();
+
var uri = string.IsNullOrEmpty(DeploymentParameters.ApplicationBaseUriHint) ?
- TestUriHelper.BuildTestUri() :
+ new Uri("http://localhost:0") :
new Uri(DeploymentParameters.ApplicationBaseUriHint);
- var redirectUri = TestUriHelper.BuildTestUri();
+ if (uri.Port == 0)
+ {
+ var builder = new UriBuilder(uri);
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ // This works with nginx 1.9.1 and later using the reuseport flag, available on Ubuntu 16.04.
+ // Keep it open so nobody else claims the port
+ _portSelector = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ _portSelector.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ builder.Port = ((IPEndPoint)_portSelector.LocalEndPoint).Port;
+ }
+ else
+ {
+ builder.Port = TestPortHelper.GetNextPort();
+ }
+ uri = builder.Uri;
+ }
+
+ var redirectUri = TestUriHelper.BuildTestUri(ServerType.Nginx);
+
+ if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr
+ && DeploymentParameters.ApplicationType == ApplicationType.Standalone)
+ {
+ // Publish is required to get the correct files in the output directory
+ DeploymentParameters.PublishApplicationBeforeDeployment = true;
+ }
if (DeploymentParameters.PublishApplicationBeforeDeployment)
{
@@ -70,19 +100,51 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
}
}
+ private string GetUserName()
+ {
+ var retVal = Environment.GetEnvironmentVariable("LOGNAME")
+ ?? Environment.GetEnvironmentVariable("USER")
+ ?? Environment.GetEnvironmentVariable("USERNAME");
+
+ if (!string.IsNullOrEmpty(retVal))
+ {
+ return retVal;
+ }
+
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ using (var process = new Process
+ {
+ StartInfo =
+ {
+ FileName = "whoami",
+ RedirectStandardOutput = true,
+ }
+ })
+ {
+ process.Start();
+ process.WaitForExit(10_000);
+ return process.StandardOutput.ReadToEnd();
+ }
+ }
+
+ return null;
+ }
+
private void SetupNginx(string redirectUri, Uri originalUri)
{
using (Logger.BeginScope("SetupNginx"))
{
+ var userName = GetUserName() ?? throw new InvalidOperationException("Could not identify the current username");
// copy nginx.conf template and replace pertinent information
var pidFile = Path.Combine(DeploymentParameters.ApplicationPath, $"{Guid.NewGuid()}.nginx.pid");
var errorLog = Path.Combine(DeploymentParameters.ApplicationPath, "nginx.error.log");
var accessLog = Path.Combine(DeploymentParameters.ApplicationPath, "nginx.access.log");
DeploymentParameters.ServerConfigTemplateContent = DeploymentParameters.ServerConfigTemplateContent
- .Replace("[user]", Environment.GetEnvironmentVariable("LOGNAME"))
+ .Replace("[user]", userName)
.Replace("[errorlog]", errorLog)
.Replace("[accesslog]", accessLog)
- .Replace("[listenPort]", originalUri.Port.ToString())
+ .Replace("[listenPort]", originalUri.Port.ToString() + (_portSelector != null ? " reuseport" : ""))
.Replace("[redirectUri]", redirectUri)
.Replace("[pidFile]", pidFile);
Logger.LogDebug("Using PID file: {pidFile}", pidFile);
@@ -110,13 +172,14 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
runNginx.StartAndCaptureOutAndErrToLogger("nginx start", Logger);
runNginx.WaitForExit(_waitTime);
+
if (runNginx.ExitCode != 0)
{
- throw new Exception("Failed to start nginx");
+ throw new InvalidOperationException("Failed to start nginx");
}
// Read the PID file
- if(!File.Exists(pidFile))
+ if (!File.Exists(pidFile))
{
Logger.LogWarning("Unable to find nginx PID file: {pidFile}", pidFile);
}
@@ -158,6 +221,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
File.Delete(_configFile);
}
+ _portSelector?.Dispose();
+
base.Dispose();
}
}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs
index a102cd02da..f33b285d63 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs
@@ -33,43 +33,43 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
if (_deploymentParameters.ServerType != ServerType.IIS
&& _deploymentParameters.ServerType != ServerType.Kestrel
- && _deploymentParameters.ServerType != ServerType.WebListener)
+ && _deploymentParameters.ServerType != ServerType.HttpSys)
{
throw new InvalidOperationException($"Server type {_deploymentParameters.ServerType} is not supported for remote deployment." +
- $" Supported server types are {nameof(ServerType.Kestrel)}, {nameof(ServerType.IIS)} and {nameof(ServerType.WebListener)}");
+ $" 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/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs
index 12f2b83de1..ae6a7a08af 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs
@@ -36,14 +36,29 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
// Start timer
StartTimer();
+ if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr
+ && DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86)
+ {
+ // Publish is required to rebuild for the right bitness
+ DeploymentParameters.PublishApplicationBeforeDeployment = true;
+ }
+
+ if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr
+ && DeploymentParameters.ApplicationType == ApplicationType.Standalone)
+ {
+ // Publish is required to get the correct files in the output directory
+ DeploymentParameters.PublishApplicationBeforeDeployment = true;
+ }
+
if (DeploymentParameters.PublishApplicationBeforeDeployment)
{
DotnetPublish();
}
var hintUrl = TestUriHelper.BuildTestUri(
- DeploymentParameters.ApplicationBaseUriHint,
DeploymentParameters.ServerType,
+ DeploymentParameters.Scheme,
+ DeploymentParameters.ApplicationBaseUriHint,
DeploymentParameters.StatusMessagesEnabled);
// Launch the host process.
@@ -64,43 +79,42 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
using (Logger.BeginScope("StartSelfHost"))
{
- string executableName;
- string executableArgs = string.Empty;
- string workingDirectory = string.Empty;
+ var executableName = string.Empty;
+ var executableArgs = string.Empty;
+ var workingDirectory = string.Empty;
+ var executableExtension = DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll"
+ : (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : "");
+
if (DeploymentParameters.PublishApplicationBeforeDeployment)
{
workingDirectory = DeploymentParameters.PublishedApplicationRootPath;
- var executableExtension =
- DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? ".exe" :
- DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" : "";
- var executable = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, DeploymentParameters.ApplicationName + executableExtension);
-
- if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- executableName = "mono";
- executableArgs = executable;
- }
- else if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable)
- {
- executableName = "dotnet";
- executableArgs = executable;
- }
- else
- {
- executableName = executable;
- }
}
else
{
- workingDirectory = DeploymentParameters.ApplicationPath;
- var targetFramework = DeploymentParameters.TargetFramework ?? (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0");
-
- executableName = DotnetCommandName;
- executableArgs = $"run --no-build -c {DeploymentParameters.Configuration} --framework {targetFramework} {DotnetArgumentSeparator}";
+ // Core+Standalone always publishes. This must be Clr+Standalone or Core+Portable.
+ // Run from the pre-built bin/{config}/{tfm} directory.
+ var targetFramework = DeploymentParameters.TargetFramework
+ ?? (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? Tfm.Net461 : Tfm.NetCoreApp22);
+ workingDirectory = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.Configuration, targetFramework);
+ // CurrentDirectory will point to bin/{config}/{tfm}, but the config and static files aren't copied, point to the app base instead.
+ DeploymentParameters.EnvironmentVariables["ASPNETCORE_CONTENTROOT"] = DeploymentParameters.ApplicationPath;
}
- executableArgs += $" --server.urls {hintUrl} "
- + $" --server {(DeploymentParameters.ServerType == ServerType.WebListener ? "Microsoft.AspNetCore.Server.HttpSys" : "Microsoft.AspNetCore.Server.Kestrel")}";
+ var executable = Path.Combine(workingDirectory, DeploymentParameters.ApplicationName + executableExtension);
+
+ if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable)
+ {
+ executableName = GetDotNetExeForArchitecture();
+ executableArgs = executable;
+ }
+ else
+ {
+ executableName = executable;
+ }
+
+ var server = DeploymentParameters.ServerType == ServerType.HttpSys
+ ? "Microsoft.AspNetCore.Server.HttpSys" : "Microsoft.AspNetCore.Server.Kestrel";
+ executableArgs += $" --urls {hintUrl} --server {server}";
Logger.LogInformation($"Executing {executableName} {executableArgs}");
diff --git a/src/Hosting/Server.IntegrationTesting/src/Http.config b/src/Hosting/Server.IntegrationTesting/src/Http.config
new file mode 100644
index 0000000000..4508dea843
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Http.config
@@ -0,0 +1,1034 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
index e693a22278..a469dc113a 100644
--- a/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
+++ b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
@@ -15,11 +15,16 @@
false
+
+
+
+
+
diff --git a/src/Hosting/Server.IntegrationTesting/src/ProcessHelpers.cs b/src/Hosting/Server.IntegrationTesting/src/ProcessHelpers.cs
new file mode 100644
index 0000000000..c00b6c2b4c
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/ProcessHelpers.cs
@@ -0,0 +1,36 @@
+// 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.Collections.Generic;
+using System.Diagnostics;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+ internal class ProcessHelpers
+ {
+ public static void AddEnvironmentVariablesToProcess(ProcessStartInfo startInfo, IDictionary environmentVariables, ILogger logger)
+ {
+ var environment = startInfo.Environment;
+
+ foreach (var environmentVariable in environmentVariables)
+ {
+ SetEnvironmentVariable(environment, environmentVariable.Key, environmentVariable.Value, logger);
+ }
+ }
+
+ public static void SetEnvironmentVariable(IDictionary environment, string name, string value, ILogger logger)
+ {
+ if (value == null)
+ {
+ logger.LogInformation("Removing environment variable {name}", name);
+ environment.Remove(name);
+ }
+ else
+ {
+ logger.LogInformation("SET {name}={value}", name, value);
+ environment[name] = value;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/PublishedApplication.cs b/src/Hosting/Server.IntegrationTesting/src/PublishedApplication.cs
new file mode 100644
index 0000000000..3913a7e908
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/PublishedApplication.cs
@@ -0,0 +1,31 @@
+// 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.IO;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+ public class PublishedApplication: IDisposable
+ {
+ private readonly ILogger _logger;
+
+ public string Path { get; }
+
+ public PublishedApplication(string path, ILogger logger)
+ {
+ _logger = logger;
+ Path = path;
+ }
+
+ public void Dispose()
+ {
+ RetryHelper.RetryOperation(
+ () => Directory.Delete(Path, true),
+ e => _logger.LogWarning($"Failed to delete directory : {e.Message}"),
+ retryCount: 3,
+ retryDelayMilliseconds: 100);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/TestMatrix.cs b/src/Hosting/Server.IntegrationTesting/src/TestMatrix.cs
new file mode 100644
index 0000000000..4353627fc5
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/TestMatrix.cs
@@ -0,0 +1,361 @@
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+ public class TestMatrix : IEnumerable