more logging and more resiliant port selection (#996)
This commit is contained in:
parent
7890fdbf94
commit
f15c99c980
|
|
@ -0,0 +1,34 @@
|
||||||
|
// 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 Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace System.Diagnostics
|
||||||
|
{
|
||||||
|
public static class ProcessLoggingExtensions
|
||||||
|
{
|
||||||
|
public static void StartAndCaptureOutAndErrToLogger(this Process process, string prefix, ILogger logger)
|
||||||
|
{
|
||||||
|
process.EnableRaisingEvents = true;
|
||||||
|
process.OutputDataReceived += (_, dataArgs) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(dataArgs.Data))
|
||||||
|
{
|
||||||
|
logger.LogWarning($"{prefix} stdout: {{line}}", dataArgs.Data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.ErrorDataReceived += (_, dataArgs) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(dataArgs.Data))
|
||||||
|
{
|
||||||
|
logger.LogWarning($"{prefix} stderr: {{line}}", dataArgs.Data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
process.BeginErrorReadLine();
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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 System.Threading.Tasks
|
||||||
|
{
|
||||||
|
internal static class TaskTimeoutExtensions
|
||||||
|
{
|
||||||
|
public static async Task OrTimeout(this Task task, TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var completed = await Task.WhenAny(task, Task.Delay(timeout));
|
||||||
|
if (completed == task)
|
||||||
|
{
|
||||||
|
// Manifest any exception
|
||||||
|
task.GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<T> OrTimeout<T>(this Task<T> task, TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var completed = await Task.WhenAny(task, Task.Delay(timeout));
|
||||||
|
if (completed == task)
|
||||||
|
{
|
||||||
|
return await task;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
|
||||||
{
|
{
|
||||||
public static Uri BuildTestUri()
|
public static Uri BuildTestUri()
|
||||||
{
|
{
|
||||||
return new UriBuilder("http", "localhost", FindFreePort()).Uri;
|
return new UriBuilder("http", "localhost", GetNextPort()).Uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri BuildTestUri(string hint)
|
public static Uri BuildTestUri(string hint)
|
||||||
|
|
@ -23,28 +23,28 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var uriHint = new Uri(hint);
|
var uriHint = new Uri(hint);
|
||||||
return new UriBuilder(uriHint) { Port = FindFreePort(uriHint.Port) }.Uri;
|
if (uriHint.Port == 0)
|
||||||
|
{
|
||||||
|
return new UriBuilder(uriHint) { Port = GetNextPort() }.Uri;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return uriHint;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int FindFreePort()
|
// Copied from https://github.com/aspnet/KestrelHttpServer/blob/47f1db20e063c2da75d9d89653fad4eafe24446c/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs#L508
|
||||||
{
|
public static int GetNextPort()
|
||||||
return FindFreePort(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int FindFreePort(int initialPort)
|
|
||||||
{
|
{
|
||||||
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
||||||
{
|
{
|
||||||
try
|
// Let the OS assign the next available port. Unless we cycle through all ports
|
||||||
{
|
// on a test run, the OS will always increment the port number when making these calls.
|
||||||
socket.Bind(new IPEndPoint(IPAddress.Loopback, initialPort));
|
// This prevents races in parallel test runs where a test is already bound to
|
||||||
}
|
// a given port, and a new test is able to bind to the same port due to port
|
||||||
catch (SocketException)
|
// reuse being enabled by default by the OS.
|
||||||
{
|
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
|
||||||
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((IPEndPoint)socket.LocalEndPoint).Port;
|
return ((IPEndPoint)socket.LocalEndPoint).Port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -6,6 +6,8 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -34,69 +36,75 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
|
|
||||||
protected ILogger Logger { get; }
|
protected ILogger Logger { get; }
|
||||||
|
|
||||||
public abstract DeploymentResult Deploy();
|
public abstract Task<DeploymentResult> DeployAsync();
|
||||||
|
|
||||||
protected void DotnetPublish(string publishRoot = null)
|
protected void DotnetPublish(string publishRoot = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(DeploymentParameters.TargetFramework))
|
using (Logger.BeginScope("dotnet-publish"))
|
||||||
{
|
{
|
||||||
throw new Exception($"A target framework must be specified in the deployment parameters for applications that require publishing before deployment");
|
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.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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
DeploymentParameters.PublishedApplicationRootPath = publishRoot ?? Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
|
||||||
|
|
||||||
var parameters = $"publish "
|
|
||||||
+ $" --output \"{DeploymentParameters.PublishedApplicationRootPath}\""
|
|
||||||
+ $" --framework {DeploymentParameters.TargetFramework}"
|
|
||||||
+ $" --configuration {DeploymentParameters.Configuration}"
|
|
||||||
+ $" {DeploymentParameters.AdditionalPublishParameters}";
|
|
||||||
|
|
||||||
Logger.LogInformation($"Executing command {DotnetCommandName} {parameters}");
|
|
||||||
|
|
||||||
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 };
|
|
||||||
hostProcess.ErrorDataReceived += (sender, dataArgs) => { Logger.LogWarning(dataArgs.Data ?? string.Empty); };
|
|
||||||
hostProcess.OutputDataReceived += (sender, dataArgs) => { Logger.LogInformation(dataArgs.Data ?? string.Empty); };
|
|
||||||
hostProcess.Start();
|
|
||||||
hostProcess.BeginErrorReadLine();
|
|
||||||
hostProcess.BeginOutputReadLine();
|
|
||||||
hostProcess.WaitForExit();
|
|
||||||
|
|
||||||
if (hostProcess.ExitCode != 0)
|
|
||||||
{
|
|
||||||
throw new Exception($"{DotnetCommandName} publish exited with exit code : {hostProcess.ExitCode}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.LogInformation($"{DotnetCommandName} publish finished with exit code : {hostProcess.ExitCode}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void CleanPublishedOutput()
|
protected void CleanPublishedOutput()
|
||||||
{
|
{
|
||||||
if (DeploymentParameters.PreservePublishedApplicationForDebugging)
|
using (Logger.BeginScope("CleanPublishedOutput"))
|
||||||
{
|
{
|
||||||
Logger.LogWarning(
|
if (DeploymentParameters.PreservePublishedApplicationForDebugging)
|
||||||
"Skipping deleting the locally published folder as property " +
|
{
|
||||||
$"'{nameof(DeploymentParameters.PreservePublishedApplicationForDebugging)}' is set to 'true'.");
|
Logger.LogWarning(
|
||||||
}
|
"Skipping deleting the locally published folder as property " +
|
||||||
else
|
$"'{nameof(DeploymentParameters.PreservePublishedApplicationForDebugging)}' is set to 'true'.");
|
||||||
{
|
}
|
||||||
RetryHelper.RetryOperation(
|
else
|
||||||
() => Directory.Delete(DeploymentParameters.PublishedApplicationRootPath, true),
|
{
|
||||||
e => Logger.LogWarning($"Failed to delete directory : {e.Message}"),
|
RetryHelper.RetryOperation(
|
||||||
retryCount: 3,
|
() => Directory.Delete(DeploymentParameters.PublishedApplicationRootPath, true),
|
||||||
retryDelayMilliseconds: 100);
|
e => Logger.LogWarning($"Failed to delete directory : {e.Message}"),
|
||||||
|
retryCount: 3,
|
||||||
|
retryDelayMilliseconds: 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,16 +158,19 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
|
|
||||||
protected void InvokeUserApplicationCleanup()
|
protected void InvokeUserApplicationCleanup()
|
||||||
{
|
{
|
||||||
if (DeploymentParameters.UserAdditionalCleanup != null)
|
using (Logger.BeginScope("UserAdditionalCleanup"))
|
||||||
{
|
{
|
||||||
// User cleanup.
|
if (DeploymentParameters.UserAdditionalCleanup != null)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
DeploymentParameters.UserAdditionalCleanup(DeploymentParameters);
|
// User cleanup.
|
||||||
}
|
try
|
||||||
catch (Exception exception)
|
{
|
||||||
{
|
DeploymentParameters.UserAdditionalCleanup(DeploymentParameters);
|
||||||
Logger.LogWarning("User cleanup code failed with exception : {exception}", exception.Message);
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("User cleanup code failed with exception : {exception}", exception.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -15,36 +15,31 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
/// Creates a deployer instance based on settings in <see cref="DeploymentParameters"/>.
|
/// Creates a deployer instance based on settings in <see cref="DeploymentParameters"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deploymentParameters"></param>
|
/// <param name="deploymentParameters"></param>
|
||||||
/// <param name="logger"></param>
|
/// <param name="loggerFactory"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static IApplicationDeployer Create(DeploymentParameters deploymentParameters, ILogger logger)
|
public static IApplicationDeployer Create(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
if (deploymentParameters == null)
|
if (deploymentParameters == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(deploymentParameters));
|
throw new ArgumentNullException(nameof(deploymentParameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger == null)
|
if (loggerFactory == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(logger));
|
throw new ArgumentNullException(nameof(loggerFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (deploymentParameters.ServerType)
|
switch (deploymentParameters.ServerType)
|
||||||
{
|
{
|
||||||
case ServerType.IISExpress:
|
case ServerType.IISExpress:
|
||||||
return new IISExpressDeployer(deploymentParameters, logger);
|
return new IISExpressDeployer(deploymentParameters, loggerFactory.CreateLogger<IISExpressDeployer>());
|
||||||
#if NET46
|
|
||||||
case ServerType.IIS:
|
case ServerType.IIS:
|
||||||
return new IISDeployer(deploymentParameters, logger);
|
throw new NotSupportedException("The IIS deployer is no longer supported");
|
||||||
#elif NETSTANDARD1_3
|
|
||||||
#else
|
|
||||||
#error Target framework needs to be updated.
|
|
||||||
#endif
|
|
||||||
case ServerType.WebListener:
|
case ServerType.WebListener:
|
||||||
case ServerType.Kestrel:
|
case ServerType.Kestrel:
|
||||||
return new SelfHostDeployer(deploymentParameters, logger);
|
return new SelfHostDeployer(deploymentParameters, loggerFactory.CreateLogger<SelfHostDeployer>());
|
||||||
case ServerType.Nginx:
|
case ServerType.Nginx:
|
||||||
return new NginxDeployer(deploymentParameters, logger);
|
return new NginxDeployer(deploymentParameters, loggerFactory.CreateLogger<NginxDeployer>());
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException(
|
throw new NotSupportedException(
|
||||||
string.Format("Found no deployers suitable for server type '{0}' with the current runtime.",
|
string.Format("Found no deployers suitable for server type '{0}' with the current runtime.",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
{
|
{
|
||||||
|
|
@ -14,6 +15,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
/// Deploys the application to the target with specified <see cref="DeploymentParameters"/>.
|
/// Deploys the application to the target with specified <see cref="DeploymentParameters"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
DeploymentResult Deploy();
|
Task<DeploymentResult> DeployAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,154 +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.
|
|
||||||
|
|
||||||
#if NET46
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Web.Administration;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Deployer for IIS.
|
|
||||||
/// </summary>
|
|
||||||
public class IISDeployer : ApplicationDeployer
|
|
||||||
{
|
|
||||||
private IISApplication _application;
|
|
||||||
private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource();
|
|
||||||
private static object _syncObject = new object();
|
|
||||||
|
|
||||||
public IISDeployer(DeploymentParameters startParameters, ILogger logger)
|
|
||||||
: base(startParameters, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override DeploymentResult Deploy()
|
|
||||||
{
|
|
||||||
// Start timer
|
|
||||||
StartTimer();
|
|
||||||
|
|
||||||
// Only supports publish and run on IIS.
|
|
||||||
DeploymentParameters.PublishApplicationBeforeDeployment = true;
|
|
||||||
|
|
||||||
_application = new IISApplication(DeploymentParameters, Logger);
|
|
||||||
|
|
||||||
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
|
||||||
{
|
|
||||||
DotnetPublish(publishRoot: _application.WebSiteRootFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop a json file instead of setting environment variable.
|
|
||||||
SetAspEnvironmentWithJson();
|
|
||||||
|
|
||||||
var uri = TestUriHelper.BuildTestUri(DeploymentParameters.ApplicationBaseUriHint);
|
|
||||||
|
|
||||||
lock (_syncObject)
|
|
||||||
{
|
|
||||||
// To prevent modifying the IIS setup concurrently.
|
|
||||||
_application.Deploy(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warm up time for IIS setup.
|
|
||||||
Thread.Sleep(1 * 1000);
|
|
||||||
Logger.LogInformation("Successfully finished IIS application directory setup.");
|
|
||||||
|
|
||||||
return new DeploymentResult
|
|
||||||
{
|
|
||||||
ContentRoot = DeploymentParameters.PublishedApplicationRootPath,
|
|
||||||
DeploymentParameters = DeploymentParameters,
|
|
||||||
// Accomodate the vdir name.
|
|
||||||
ApplicationBaseUri = uri.ToString(),
|
|
||||||
HostShutdownToken = _hostShutdownToken.Token
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Dispose()
|
|
||||||
{
|
|
||||||
if (_application != null)
|
|
||||||
{
|
|
||||||
lock (_syncObject)
|
|
||||||
{
|
|
||||||
// Sequentialize IIS operations.
|
|
||||||
_application.StopAndDeleteAppPool();
|
|
||||||
}
|
|
||||||
|
|
||||||
TriggerHostShutdown(_hostShutdownToken);
|
|
||||||
|
|
||||||
Thread.Sleep(TimeSpan.FromSeconds(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
CleanPublishedOutput();
|
|
||||||
InvokeUserApplicationCleanup();
|
|
||||||
|
|
||||||
StopTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetAspEnvironmentWithJson()
|
|
||||||
{
|
|
||||||
////S Drop a hosting.json with environment information.
|
|
||||||
// Logger.LogInformation("Creating hosting.json file with environment information.");
|
|
||||||
// var jsonFile = Path.Combine(DeploymentParameters.ApplicationPath, "hosting.json");
|
|
||||||
// File.WriteAllText(jsonFile, string.Format("{ \"environment\":\"{0}\" }", DeploymentParameters.EnvironmentName));
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IISApplication
|
|
||||||
{
|
|
||||||
private readonly ServerManager _serverManager = new ServerManager();
|
|
||||||
private readonly DeploymentParameters _deploymentParameters;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public IISApplication(DeploymentParameters deploymentParameters, ILogger logger)
|
|
||||||
{
|
|
||||||
_deploymentParameters = deploymentParameters;
|
|
||||||
_logger = logger;
|
|
||||||
WebSiteName = CreateTestSiteName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string WebSiteName { get; }
|
|
||||||
|
|
||||||
public string WebSiteRootFolder => $"{Environment.GetEnvironmentVariable("SystemDrive")}\\inetpub\\{WebSiteName}";
|
|
||||||
|
|
||||||
public void Deploy(Uri uri)
|
|
||||||
{
|
|
||||||
var contentRoot = _deploymentParameters.PublishApplicationBeforeDeployment ? _deploymentParameters.PublishedApplicationRootPath : _deploymentParameters.ApplicationPath;
|
|
||||||
_serverManager.Sites.Add(WebSiteName, contentRoot, uri.Port);
|
|
||||||
_serverManager.CommitChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopAndDeleteAppPool()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(WebSiteName))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var siteToRemove = _serverManager.Sites.FirstOrDefault(site => site.Name == WebSiteName);
|
|
||||||
if (siteToRemove != null)
|
|
||||||
{
|
|
||||||
siteToRemove.Stop();
|
|
||||||
_serverManager.Sites.Remove(siteToRemove);
|
|
||||||
_serverManager.CommitChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private string CreateTestSiteName()
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(_deploymentParameters.SiteName))
|
|
||||||
{
|
|
||||||
return $"{_deploymentParameters.SiteName}{DateTime.Now.ToString("yyyyMMddHHmmss")}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $"testsite{DateTime.Now.ToString("yyyyMMddHHmmss")}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#elif NETSTANDARD1_3
|
|
||||||
#else
|
|
||||||
#error Target framework needs to be updated.
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -16,6 +18,13 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IISExpressDeployer : ApplicationDeployer
|
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 ""(?<url>[^""]+)"" for site.*$");
|
||||||
|
|
||||||
private Process _hostProcess;
|
private Process _hostProcess;
|
||||||
|
|
||||||
public IISExpressDeployer(DeploymentParameters deploymentParameters, ILogger logger)
|
public IISExpressDeployer(DeploymentParameters deploymentParameters, ILogger logger)
|
||||||
|
|
@ -42,126 +51,200 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override DeploymentResult Deploy()
|
public override async Task<DeploymentResult> DeployAsync()
|
||||||
{
|
{
|
||||||
// Start timer
|
using (Logger.BeginScope("Deployment"))
|
||||||
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();
|
// 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);
|
||||||
|
|
||||||
|
return new DeploymentResult
|
||||||
|
{
|
||||||
|
ContentRoot = contentRoot,
|
||||||
|
DeploymentParameters = DeploymentParameters,
|
||||||
|
// Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath.
|
||||||
|
ApplicationBaseUri = actualUri.ToString(),
|
||||||
|
HostShutdownToken = hostExitToken
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentRoot = DeploymentParameters.PublishApplicationBeforeDeployment ? DeploymentParameters.PublishedApplicationRootPath : DeploymentParameters.ApplicationPath;
|
|
||||||
|
|
||||||
var uri = TestUriHelper.BuildTestUri(DeploymentParameters.ApplicationBaseUriHint);
|
|
||||||
// Launch the host process.
|
|
||||||
var hostExitToken = StartIISExpress(uri, contentRoot);
|
|
||||||
|
|
||||||
return new DeploymentResult
|
|
||||||
{
|
|
||||||
ContentRoot = contentRoot,
|
|
||||||
DeploymentParameters = DeploymentParameters,
|
|
||||||
// Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath.
|
|
||||||
ApplicationBaseUri = uri.ToString(),
|
|
||||||
HostShutdownToken = hostExitToken
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CancellationToken StartIISExpress(Uri uri, string contentRoot)
|
private async Task<(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigTemplateContent))
|
using (Logger.BeginScope("StartIISExpress"))
|
||||||
{
|
{
|
||||||
// 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
|
var port = uri.Port;
|
||||||
// We take a copy of the original specified applicationHost.Config to prevent modifying the one in the repo.
|
if (port == 0)
|
||||||
|
|
||||||
if (DeploymentParameters.ServerConfigTemplateContent.Contains("[ANCMPath]"))
|
|
||||||
{
|
{
|
||||||
string ancmPath;
|
port = TestUriHelper.GetNextPort();
|
||||||
if (!IsWin8OrLater)
|
}
|
||||||
|
|
||||||
|
for (var attempt = 0; attempt < MaximumAttempts; attempt++)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Attempting to start IIS Express on port: {port}", port);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigTemplateContent))
|
||||||
{
|
{
|
||||||
// The nupkg build of ANCM does not support Win7. https://github.com/aspnet/AspNetCoreModule/issues/40.
|
var serverConfig = DeploymentParameters.ServerConfigTemplateContent;
|
||||||
ancmPath = @"%ProgramFiles%\IIS Express\aspnetcore.dll";
|
|
||||||
|
// 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]"))
|
||||||
|
{
|
||||||
|
string ancmPath;
|
||||||
|
if (!IsWin8OrLater)
|
||||||
|
{
|
||||||
|
// The nupkg build of ANCM does not support Win7. https://github.com/aspnet/AspNetCoreModule/issues/40.
|
||||||
|
ancmPath = @"%ProgramFiles%\IIS Express\aspnetcore.dll";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 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 = Is64BitHost ? "aspnetcore_x64.dll" : "aspnetcore_x86.dll";
|
||||||
|
// Bin deployed by Microsoft.AspNetCore.AspNetCoreModule.nupkg
|
||||||
|
if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr
|
||||||
|
&& DeploymentParameters.ApplicationType == ApplicationType.Portable)
|
||||||
|
{
|
||||||
|
ancmPath = Path.Combine(contentRoot, @"runtimes\win7\native\", ancmFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ancmPath = Path.Combine(contentRoot, ancmFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmPath)))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug("Writing ANCMPath '{ancmPath}' to config", ancmPath);
|
||||||
|
serverConfig =
|
||||||
|
serverConfig.Replace("[ANCMPath]", ancmPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<bool>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
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
|
||||||
|
if (!await started.Task)
|
||||||
|
{
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
// We need to pick the bitness based the OS / IIS Express, not the application.
|
_hostProcess = process;
|
||||||
// We'll eventually add support for choosing which IIS Express bitness to run: https://github.com/aspnet/Hosting/issues/880
|
Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port);
|
||||||
var ancmFile = Is64BitHost ? "aspnetcore_x64.dll" : "aspnetcore_x86.dll";
|
return (url: url, hostExitToken: hostExitTokenSource.Token);
|
||||||
// Bin deployed by Microsoft.AspNetCore.AspNetCoreModule.nupkg
|
|
||||||
if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr
|
|
||||||
&& DeploymentParameters.ApplicationType == ApplicationType.Portable)
|
|
||||||
{
|
|
||||||
ancmPath = Path.Combine(contentRoot, @"runtimes\win7\native\", ancmFile);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ancmPath = Path.Combine(contentRoot, ancmFile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmPath)))
|
|
||||||
{
|
|
||||||
throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeploymentParameters.ServerConfigTemplateContent =
|
|
||||||
DeploymentParameters.ServerConfigTemplateContent.Replace("[ANCMPath]", ancmPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DeploymentParameters.ServerConfigTemplateContent =
|
var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port";
|
||||||
DeploymentParameters.ServerConfigTemplateContent
|
Logger.LogError(message);
|
||||||
.Replace("[ApplicationPhysicalPath]", contentRoot)
|
throw new TimeoutException(message);
|
||||||
.Replace("[PORT]", uri.Port.ToString());
|
|
||||||
|
|
||||||
DeploymentParameters.ServerConfigLocation = Path.GetTempFileName();
|
|
||||||
|
|
||||||
File.WriteAllText(DeploymentParameters.ServerConfigLocation, DeploymentParameters.ServerConfigTemplateContent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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} {args}", iisExpressPath, parameters);
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = iisExpressPath,
|
|
||||||
Arguments = parameters,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
RedirectStandardOutput = true
|
|
||||||
};
|
|
||||||
|
|
||||||
AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);
|
|
||||||
|
|
||||||
_hostProcess = new Process() { StartInfo = startInfo };
|
|
||||||
_hostProcess.ErrorDataReceived += (sender, dataArgs) => { Logger.LogError(dataArgs.Data ?? string.Empty); };
|
|
||||||
_hostProcess.OutputDataReceived += (sender, dataArgs) => { Logger.LogInformation(dataArgs.Data ?? string.Empty); };
|
|
||||||
_hostProcess.EnableRaisingEvents = true;
|
|
||||||
var hostExitTokenSource = new CancellationTokenSource();
|
|
||||||
_hostProcess.Exited += (sender, e) =>
|
|
||||||
{
|
|
||||||
TriggerHostShutdown(hostExitTokenSource);
|
|
||||||
};
|
|
||||||
_hostProcess.Start();
|
|
||||||
_hostProcess.BeginErrorReadLine();
|
|
||||||
_hostProcess.BeginOutputReadLine();
|
|
||||||
|
|
||||||
if (_hostProcess.HasExited)
|
|
||||||
{
|
|
||||||
Logger.LogError("Host process {processName} exited with code {exitCode} or failed to start.", startInfo.FileName, _hostProcess.ExitCode);
|
|
||||||
throw new Exception("Failed to start host");
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.LogInformation("Started iisexpress. Process Id : {processId}", _hostProcess.Id);
|
|
||||||
return hostExitTokenSource.Token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetIISExpressPath()
|
private string GetIISExpressPath()
|
||||||
|
|
@ -179,31 +262,35 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
ShutDownIfAnyHostProcess(_hostProcess);
|
using (Logger.BeginScope("Dispose"))
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigLocation)
|
|
||||||
&& File.Exists(DeploymentParameters.ServerConfigLocation))
|
|
||||||
{
|
{
|
||||||
// Delete the temp applicationHostConfig that we created.
|
ShutDownIfAnyHostProcess(_hostProcess);
|
||||||
try
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigLocation)
|
||||||
|
&& File.Exists(DeploymentParameters.ServerConfigLocation))
|
||||||
{
|
{
|
||||||
File.Delete(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
|
||||||
|
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
||||||
{
|
{
|
||||||
// Ignore delete failures - just write a log.
|
CleanPublishedOutput();
|
||||||
Logger.LogWarning("Failed to delete '{config}'. Exception : {exception}", DeploymentParameters.ServerConfigLocation, exception.Message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InvokeUserApplicationCleanup();
|
||||||
|
|
||||||
|
StopTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
|
||||||
{
|
|
||||||
CleanPublishedOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
InvokeUserApplicationCleanup();
|
|
||||||
|
|
||||||
StopTimer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -23,105 +24,79 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override DeploymentResult Deploy()
|
public override async Task<DeploymentResult> DeployAsync()
|
||||||
{
|
{
|
||||||
_configFile = Path.GetTempFileName();
|
using (Logger.BeginScope("Deploy"))
|
||||||
var uri = new Uri(DeploymentParameters.ApplicationBaseUriHint);
|
|
||||||
|
|
||||||
var redirectUri = $"http://localhost:{TestUriHelper.FindFreePort()}";
|
|
||||||
|
|
||||||
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
|
||||||
{
|
{
|
||||||
DotnetPublish();
|
_configFile = Path.GetTempFileName();
|
||||||
}
|
var uri = new Uri(DeploymentParameters.ApplicationBaseUriHint);
|
||||||
|
|
||||||
var exitToken = StartSelfHost(new Uri(redirectUri));
|
var redirectUri = $"http://localhost:{TestUriHelper.GetNextPort()}";
|
||||||
|
|
||||||
SetupNginx(redirectUri, uri);
|
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
||||||
|
|
||||||
// Wait for App to be loaded since Nginx returns 502 instead of 503 when App isn't loaded
|
|
||||||
// Target actual address to avoid going through Nginx proxy
|
|
||||||
using (var httpClient = new HttpClient())
|
|
||||||
{
|
|
||||||
var response = RetryHelper.RetryRequest(() =>
|
|
||||||
{
|
{
|
||||||
return httpClient.GetAsync(redirectUri);
|
DotnetPublish();
|
||||||
}, Logger, exitToken).Result;
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Deploy failed");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return new DeploymentResult
|
var (appUri, exitToken) = await StartSelfHostAsync(new Uri(redirectUri));
|
||||||
{
|
|
||||||
ContentRoot = DeploymentParameters.ApplicationPath,
|
SetupNginx(appUri.ToString(), uri);
|
||||||
DeploymentParameters = DeploymentParameters,
|
|
||||||
ApplicationBaseUri = uri.ToString(),
|
Logger.LogInformation("Application ready at URL: {appUrl}", uri);
|
||||||
HostShutdownToken = exitToken
|
|
||||||
};
|
// Wait for App to be loaded since Nginx returns 502 instead of 503 when App isn't loaded
|
||||||
|
// Target actual address to avoid going through Nginx proxy
|
||||||
|
using (var httpClient = new HttpClient())
|
||||||
|
{
|
||||||
|
var response = await RetryHelper.RetryRequest(() =>
|
||||||
|
{
|
||||||
|
return httpClient.GetAsync(redirectUri);
|
||||||
|
}, Logger, exitToken);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Deploy failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeploymentResult
|
||||||
|
{
|
||||||
|
ContentRoot = DeploymentParameters.ApplicationPath,
|
||||||
|
DeploymentParameters = DeploymentParameters,
|
||||||
|
ApplicationBaseUri = uri.ToString(),
|
||||||
|
HostShutdownToken = exitToken
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupNginx(string redirectUri, Uri originalUri)
|
private void SetupNginx(string redirectUri, Uri originalUri)
|
||||||
{
|
{
|
||||||
// copy nginx.conf template and replace pertinent information
|
using (Logger.BeginScope("SetupNginx"))
|
||||||
DeploymentParameters.ServerConfigTemplateContent = DeploymentParameters.ServerConfigTemplateContent
|
|
||||||
.Replace("[user]", Environment.GetEnvironmentVariable("LOGNAME"))
|
|
||||||
.Replace("[errorlog]", Path.Combine(DeploymentParameters.ApplicationPath, "nginx.error.log"))
|
|
||||||
.Replace("[accesslog]", Path.Combine(DeploymentParameters.ApplicationPath, "nginx.access.log"))
|
|
||||||
.Replace("[listenPort]", originalUri.Port.ToString())
|
|
||||||
.Replace("[redirectUri]", redirectUri)
|
|
||||||
.Replace("[pidFile]", Path.Combine(DeploymentParameters.ApplicationPath, Guid.NewGuid().ToString()));
|
|
||||||
File.WriteAllText(_configFile, DeploymentParameters.ServerConfigTemplateContent);
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
{
|
||||||
FileName = "nginx",
|
// copy nginx.conf template and replace pertinent information
|
||||||
Arguments = $"-c {_configFile}",
|
var pidFile = Path.Combine(DeploymentParameters.ApplicationPath, $"{Guid.NewGuid()}.nginx.pid");
|
||||||
UseShellExecute = false,
|
var errorLog = Path.Combine(DeploymentParameters.ApplicationPath, "nginx.error.log");
|
||||||
CreateNoWindow = true,
|
var accessLog = Path.Combine(DeploymentParameters.ApplicationPath, "nginx.access.log");
|
||||||
RedirectStandardError = true,
|
DeploymentParameters.ServerConfigTemplateContent = DeploymentParameters.ServerConfigTemplateContent
|
||||||
RedirectStandardOutput = true,
|
.Replace("[user]", Environment.GetEnvironmentVariable("LOGNAME"))
|
||||||
// Trying a work around for https://github.com/aspnet/Hosting/issues/140.
|
.Replace("[errorlog]", errorLog)
|
||||||
RedirectStandardInput = true
|
.Replace("[accesslog]", accessLog)
|
||||||
};
|
.Replace("[listenPort]", originalUri.Port.ToString())
|
||||||
|
.Replace("[redirectUri]", redirectUri)
|
||||||
using (var runNginx = new Process() { StartInfo = startInfo })
|
.Replace("[pidFile]", pidFile);
|
||||||
{
|
Logger.LogDebug("Using PID file: {pidFile}", pidFile);
|
||||||
runNginx.ErrorDataReceived += (sender, dataArgs) =>
|
Logger.LogDebug("Using Error Log file: {errorLog}", pidFile);
|
||||||
|
Logger.LogDebug("Using Access Log file: {accessLog}", pidFile);
|
||||||
|
if (Logger.IsEnabled(LogLevel.Trace))
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(dataArgs.Data))
|
Logger.LogTrace($"Config File Content:{Environment.NewLine}===START CONFIG==={Environment.NewLine}{{configContent}}{Environment.NewLine}===END CONFIG===", DeploymentParameters.ServerConfigTemplateContent);
|
||||||
{
|
|
||||||
Logger.LogWarning("nginx: " + dataArgs.Data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
runNginx.OutputDataReceived += (sender, dataArgs) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(dataArgs.Data))
|
|
||||||
{
|
|
||||||
Logger.LogInformation("nginx: " + dataArgs.Data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
runNginx.Start();
|
|
||||||
runNginx.BeginErrorReadLine();
|
|
||||||
runNginx.BeginOutputReadLine();
|
|
||||||
runNginx.WaitForExit(_waitTime);
|
|
||||||
if (runNginx.ExitCode != 0)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to start Nginx");
|
|
||||||
}
|
}
|
||||||
}
|
File.WriteAllText(_configFile, DeploymentParameters.ServerConfigTemplateContent);
|
||||||
}
|
|
||||||
|
|
||||||
public override void Dispose()
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(_configFile))
|
|
||||||
{
|
|
||||||
var startInfo = new ProcessStartInfo
|
var startInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = "nginx",
|
FileName = "nginx",
|
||||||
Arguments = $"-s stop -c {_configFile}",
|
Arguments = $"-c {_configFile}",
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
|
|
@ -132,14 +107,58 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
|
|
||||||
using (var runNginx = new Process() { StartInfo = startInfo })
|
using (var runNginx = new Process() { StartInfo = startInfo })
|
||||||
{
|
{
|
||||||
runNginx.Start();
|
runNginx.StartAndCaptureOutAndErrToLogger("nginx start", Logger);
|
||||||
runNginx.WaitForExit(_waitTime);
|
runNginx.WaitForExit(_waitTime);
|
||||||
|
if (runNginx.ExitCode != 0)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to start nginx");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the PID file
|
||||||
|
if(!File.Exists(pidFile))
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Unable to find nginx PID file: {pidFile}", pidFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pid = File.ReadAllText(pidFile);
|
||||||
|
Logger.LogInformation("nginx process ID {pid} started", pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
using (Logger.BeginScope("Dispose"))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_configFile))
|
||||||
|
{
|
||||||
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "nginx",
|
||||||
|
Arguments = $"-s stop -c {_configFile}",
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
// Trying a work around for https://github.com/aspnet/Hosting/issues/140.
|
||||||
|
RedirectStandardInput = true
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var runNginx = new Process() { StartInfo = startInfo })
|
||||||
|
{
|
||||||
|
runNginx.StartAndCaptureOutAndErrToLogger("nginx stop", Logger);
|
||||||
|
runNginx.WaitForExit(_waitTime);
|
||||||
|
Logger.LogInformation("nginx stop command issued");
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug("Deleting config file: {configFile}", _configFile);
|
||||||
|
File.Delete(_configFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
File.Delete(_configFile);
|
base.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -73,76 +75,82 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override DeploymentResult Deploy()
|
public override async Task<DeploymentResult> DeployAsync()
|
||||||
{
|
{
|
||||||
if (_isDisposed)
|
using (Logger.BeginScope("Deploy"))
|
||||||
{
|
{
|
||||||
throw new ObjectDisposedException("This instance of deployer has already been disposed.");
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("This instance of deployer has already been disposed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish the app to a local temp folder on the machine where the test is running
|
||||||
|
DotnetPublish();
|
||||||
|
|
||||||
|
if (_deploymentParameters.ServerType == ServerType.IIS)
|
||||||
|
{
|
||||||
|
UpdateWebConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
var folderId = Guid.NewGuid().ToString();
|
||||||
|
_deployedFolderPathInFileShare = Path.Combine(_deploymentParameters.RemoteServerFileSharePath, folderId);
|
||||||
|
|
||||||
|
DirectoryCopy(
|
||||||
|
_deploymentParameters.PublishedApplicationRootPath,
|
||||||
|
_deployedFolderPathInFileShare,
|
||||||
|
copySubDirs: true);
|
||||||
|
Logger.LogInformation($"Copied the locally published folder to the file share path '{_deployedFolderPathInFileShare}'");
|
||||||
|
|
||||||
|
await RunScriptAsync("StartServer");
|
||||||
|
|
||||||
|
return new DeploymentResult
|
||||||
|
{
|
||||||
|
ApplicationBaseUri = DeploymentParameters.ApplicationBaseUriHint,
|
||||||
|
DeploymentParameters = DeploymentParameters
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish the app to a local temp folder on the machine where the test is running
|
|
||||||
DotnetPublish();
|
|
||||||
|
|
||||||
if (_deploymentParameters.ServerType == ServerType.IIS)
|
|
||||||
{
|
|
||||||
UpdateWebConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
var folderId = Guid.NewGuid().ToString();
|
|
||||||
_deployedFolderPathInFileShare = Path.Combine(_deploymentParameters.RemoteServerFileSharePath, folderId);
|
|
||||||
|
|
||||||
DirectoryCopy(
|
|
||||||
_deploymentParameters.PublishedApplicationRootPath,
|
|
||||||
_deployedFolderPathInFileShare,
|
|
||||||
copySubDirs: true);
|
|
||||||
Logger.LogInformation($"Copied the locally published folder to the file share path '{_deployedFolderPathInFileShare}'");
|
|
||||||
|
|
||||||
RunScript("StartServer");
|
|
||||||
|
|
||||||
return new DeploymentResult
|
|
||||||
{
|
|
||||||
ApplicationBaseUri = DeploymentParameters.ApplicationBaseUriHint,
|
|
||||||
DeploymentParameters = DeploymentParameters
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
if (_isDisposed)
|
using (Logger.BeginScope("Dispose"))
|
||||||
{
|
{
|
||||||
return;
|
if (_isDisposed)
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"Stopping the application on the server '{_deploymentParameters.ServerName}'");
|
Logger.LogInformation($"Stopping the application on the server '{_deploymentParameters.ServerName}'");
|
||||||
RunScript("StopServer");
|
RunScriptAsync("StopServer").Wait();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(0, "Failed to stop the server.", ex);
|
Logger.LogWarning(0, "Failed to stop the server.", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"Deleting the deployed folder '{_deployedFolderPathInFileShare}'");
|
Logger.LogInformation($"Deleting the deployed folder '{_deployedFolderPathInFileShare}'");
|
||||||
Directory.Delete(_deployedFolderPathInFileShare, recursive: true);
|
Directory.Delete(_deployedFolderPathInFileShare, recursive: true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(0, $"Failed to delete the deployed folder '{_deployedFolderPathInFileShare}'.", ex);
|
Logger.LogWarning(0, $"Failed to delete the deployed folder '{_deployedFolderPathInFileShare}'.", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"Deleting the locally published folder '{DeploymentParameters.PublishedApplicationRootPath}'");
|
Logger.LogInformation($"Deleting the locally published folder '{DeploymentParameters.PublishedApplicationRootPath}'");
|
||||||
Directory.Delete(DeploymentParameters.PublishedApplicationRootPath, recursive: true);
|
Directory.Delete(DeploymentParameters.PublishedApplicationRootPath, recursive: true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(0, $"Failed to delete the locally published folder '{DeploymentParameters.PublishedApplicationRootPath}'.", ex);
|
Logger.LogWarning(0, $"Failed to delete the locally published folder '{DeploymentParameters.PublishedApplicationRootPath}'.", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,92 +184,92 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
environmentVariablesSection.Add(environmentVariable);
|
environmentVariablesSection.Add(environmentVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Logger.IsEnabled(LogLevel.Trace))
|
||||||
|
{
|
||||||
|
Logger.LogTrace($"Config File Content:{Environment.NewLine}===START CONFIG==={Environment.NewLine}{{configContent}}{Environment.NewLine}===END CONFIG===", webConfig.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
using (var fileStream = File.Open(webConfigFilePath, FileMode.Open))
|
using (var fileStream = File.Open(webConfigFilePath, FileMode.Open))
|
||||||
{
|
{
|
||||||
webConfig.Save(fileStream);
|
webConfig.Save(fileStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunScript(string serverAction)
|
private async Task RunScriptAsync(string serverAction)
|
||||||
{
|
{
|
||||||
var remotePSSessionHelperScript = _scripts.Value.RemotePSSessionHelper;
|
using (Logger.BeginScope($"RunScript:{serverAction}"))
|
||||||
|
|
||||||
string executablePath = null;
|
|
||||||
string executableParameters = null;
|
|
||||||
var applicationName = new DirectoryInfo(DeploymentParameters.ApplicationPath).Name;
|
|
||||||
if (DeploymentParameters.ApplicationType == ApplicationType.Portable)
|
|
||||||
{
|
{
|
||||||
executablePath = "dotnet.exe";
|
var remotePSSessionHelperScript = _scripts.Value.RemotePSSessionHelper;
|
||||||
executableParameters = Path.Combine(_deployedFolderPathInFileShare, applicationName + ".dll");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
executablePath = Path.Combine(_deployedFolderPathInFileShare, applicationName + ".exe");
|
|
||||||
}
|
|
||||||
|
|
||||||
var parameterBuilder = new StringBuilder();
|
string executablePath = null;
|
||||||
parameterBuilder.Append($"\"{remotePSSessionHelperScript}\"");
|
string executableParameters = null;
|
||||||
parameterBuilder.Append($" -serverName {_deploymentParameters.ServerName}");
|
var applicationName = new DirectoryInfo(DeploymentParameters.ApplicationPath).Name;
|
||||||
parameterBuilder.Append($" -accountName {_deploymentParameters.ServerAccountName}");
|
if (DeploymentParameters.ApplicationType == ApplicationType.Portable)
|
||||||
parameterBuilder.Append($" -accountPassword {_deploymentParameters.ServerAccountPassword}");
|
|
||||||
parameterBuilder.Append($" -deployedFolderPath {_deployedFolderPathInFileShare}");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_deploymentParameters.DotnetRuntimePath))
|
|
||||||
{
|
|
||||||
parameterBuilder.Append($" -dotnetRuntimePath \"{_deploymentParameters.DotnetRuntimePath}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
parameterBuilder.Append($" -executablePath \"{executablePath}\"");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(executableParameters))
|
|
||||||
{
|
|
||||||
parameterBuilder.Append($" -executableParameters \"{executableParameters}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
parameterBuilder.Append($" -serverType {_deploymentParameters.ServerType}");
|
|
||||||
parameterBuilder.Append($" -serverAction {serverAction}");
|
|
||||||
parameterBuilder.Append($" -applicationBaseUrl {_deploymentParameters.ApplicationBaseUriHint}");
|
|
||||||
var environmentVariables = string.Join("`,", _deploymentParameters.EnvironmentVariables.Select(envVariable => $"{envVariable.Key}={envVariable.Value}"));
|
|
||||||
parameterBuilder.Append($" -environmentVariables \"{environmentVariables}\"");
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "powershell.exe",
|
|
||||||
Arguments = parameterBuilder.ToString(),
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardInput = true
|
|
||||||
};
|
|
||||||
|
|
||||||
using (var runScriptsOnRemoteServerProcess = new Process() { StartInfo = startInfo })
|
|
||||||
{
|
|
||||||
runScriptsOnRemoteServerProcess.EnableRaisingEvents = true;
|
|
||||||
runScriptsOnRemoteServerProcess.ErrorDataReceived += (sender, dataArgs) =>
|
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(dataArgs.Data))
|
executablePath = "dotnet.exe";
|
||||||
{
|
executableParameters = Path.Combine(_deployedFolderPathInFileShare, applicationName + ".dll");
|
||||||
Logger.LogWarning($"[{_deploymentParameters.ServerName}]: {dataArgs.Data}");
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
executablePath = Path.Combine(_deployedFolderPathInFileShare, applicationName + ".exe");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameterBuilder = new StringBuilder();
|
||||||
|
parameterBuilder.Append($"\"{remotePSSessionHelperScript}\"");
|
||||||
|
parameterBuilder.Append($" -serverName {_deploymentParameters.ServerName}");
|
||||||
|
parameterBuilder.Append($" -accountName {_deploymentParameters.ServerAccountName}");
|
||||||
|
parameterBuilder.Append($" -accountPassword {_deploymentParameters.ServerAccountPassword}");
|
||||||
|
parameterBuilder.Append($" -deployedFolderPath {_deployedFolderPathInFileShare}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_deploymentParameters.DotnetRuntimePath))
|
||||||
|
{
|
||||||
|
parameterBuilder.Append($" -dotnetRuntimePath \"{_deploymentParameters.DotnetRuntimePath}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterBuilder.Append($" -executablePath \"{executablePath}\"");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(executableParameters))
|
||||||
|
{
|
||||||
|
parameterBuilder.Append($" -executableParameters \"{executableParameters}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterBuilder.Append($" -serverType {_deploymentParameters.ServerType}");
|
||||||
|
parameterBuilder.Append($" -serverAction {serverAction}");
|
||||||
|
parameterBuilder.Append($" -applicationBaseUrl {_deploymentParameters.ApplicationBaseUriHint}");
|
||||||
|
var environmentVariables = string.Join("`,", _deploymentParameters.EnvironmentVariables.Select(envVariable => $"{envVariable.Key}={envVariable.Value}"));
|
||||||
|
parameterBuilder.Append($" -environmentVariables \"{environmentVariables}\"");
|
||||||
|
|
||||||
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "powershell.exe",
|
||||||
|
Arguments = parameterBuilder.ToString(),
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardInput = true
|
||||||
};
|
};
|
||||||
|
|
||||||
runScriptsOnRemoteServerProcess.OutputDataReceived += (sender, dataArgs) =>
|
using (var runScriptsOnRemoteServerProcess = new Process() { StartInfo = startInfo })
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(dataArgs.Data))
|
var processExited = new TaskCompletionSource<object>();
|
||||||
|
|
||||||
|
runScriptsOnRemoteServerProcess.EnableRaisingEvents = true;
|
||||||
|
runScriptsOnRemoteServerProcess.Exited += (sender, exitedArgs) =>
|
||||||
{
|
{
|
||||||
Logger.LogInformation($"[{_deploymentParameters.ServerName}]: {dataArgs.Data}");
|
Logger.LogInformation($"[{_deploymentParameters.ServerName} {serverAction} stdout]: script complete");
|
||||||
|
processExited.TrySetResult(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
runScriptsOnRemoteServerProcess.StartAndCaptureOutAndErrToLogger(serverAction, Logger);
|
||||||
|
|
||||||
|
await processExited.Task.OrTimeout(TimeSpan.FromMinutes(1));
|
||||||
|
runScriptsOnRemoteServerProcess.WaitForExit((int)TimeSpan.FromMinutes(1).TotalMilliseconds);
|
||||||
|
|
||||||
|
if (runScriptsOnRemoteServerProcess.HasExited && runScriptsOnRemoteServerProcess.ExitCode != 0)
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to execute the script on '{_deploymentParameters.ServerName}'.");
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
runScriptsOnRemoteServerProcess.Start();
|
|
||||||
runScriptsOnRemoteServerProcess.BeginErrorReadLine();
|
|
||||||
runScriptsOnRemoteServerProcess.BeginOutputReadLine();
|
|
||||||
runScriptsOnRemoteServerProcess.WaitForExit((int)TimeSpan.FromMinutes(1).TotalMilliseconds);
|
|
||||||
|
|
||||||
if (runScriptsOnRemoteServerProcess.HasExited && runScriptsOnRemoteServerProcess.ExitCode != 0)
|
|
||||||
{
|
|
||||||
throw new Exception($"Failed to execute the script on '{_deploymentParameters.ServerName}'.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -16,6 +18,9 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SelfHostDeployer : ApplicationDeployer
|
public class SelfHostDeployer : ApplicationDeployer
|
||||||
{
|
{
|
||||||
|
private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: (?<url>.*)$");
|
||||||
|
private const string ApplicationStartedMessage = "Application started. Press Ctrl+C to shut down.";
|
||||||
|
|
||||||
public Process HostProcess { get; private set; }
|
public Process HostProcess { get; private set; }
|
||||||
|
|
||||||
public SelfHostDeployer(DeploymentParameters deploymentParameters, ILogger logger)
|
public SelfHostDeployer(DeploymentParameters deploymentParameters, ILogger logger)
|
||||||
|
|
@ -23,129 +28,159 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override DeploymentResult Deploy()
|
public override async Task<DeploymentResult> DeployAsync()
|
||||||
{
|
{
|
||||||
// Start timer
|
using (Logger.BeginScope("SelfHost.Deploy"))
|
||||||
StartTimer();
|
|
||||||
|
|
||||||
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
|
||||||
{
|
{
|
||||||
DotnetPublish();
|
// Start timer
|
||||||
|
StartTimer();
|
||||||
|
|
||||||
|
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
||||||
|
{
|
||||||
|
DotnetPublish();
|
||||||
|
}
|
||||||
|
|
||||||
|
var hintUrl = TestUriHelper.BuildTestUri(DeploymentParameters.ApplicationBaseUriHint);
|
||||||
|
|
||||||
|
// Launch the host process.
|
||||||
|
var (actualUrl, hostExitToken) = await StartSelfHostAsync(hintUrl);
|
||||||
|
|
||||||
|
Logger.LogInformation("Application ready at URL: {appUrl}", actualUrl);
|
||||||
|
|
||||||
|
return new DeploymentResult
|
||||||
|
{
|
||||||
|
ContentRoot = DeploymentParameters.PublishApplicationBeforeDeployment ? DeploymentParameters.PublishedApplicationRootPath : DeploymentParameters.ApplicationPath,
|
||||||
|
DeploymentParameters = DeploymentParameters,
|
||||||
|
ApplicationBaseUri = actualUrl.ToString(),
|
||||||
|
HostShutdownToken = hostExitToken
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var uri = TestUriHelper.BuildTestUri(DeploymentParameters.ApplicationBaseUriHint);
|
|
||||||
// Launch the host process.
|
|
||||||
var hostExitToken = StartSelfHost(uri);
|
|
||||||
|
|
||||||
return new DeploymentResult
|
|
||||||
{
|
|
||||||
ContentRoot = DeploymentParameters.PublishApplicationBeforeDeployment ? DeploymentParameters.PublishedApplicationRootPath : DeploymentParameters.ApplicationPath,
|
|
||||||
DeploymentParameters = DeploymentParameters,
|
|
||||||
ApplicationBaseUri = uri.ToString(),
|
|
||||||
HostShutdownToken = hostExitToken
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CancellationToken StartSelfHost(Uri uri)
|
protected async Task<(Uri url, CancellationToken hostExitToken)> StartSelfHostAsync(Uri hintUrl)
|
||||||
{
|
{
|
||||||
string executableName;
|
using (Logger.BeginScope("StartSelfHost"))
|
||||||
string executableArgs = string.Empty;
|
|
||||||
string workingDirectory = string.Empty;
|
|
||||||
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
|
||||||
{
|
{
|
||||||
workingDirectory = DeploymentParameters.PublishedApplicationRootPath;
|
string executableName;
|
||||||
var executableExtension =
|
string executableArgs = string.Empty;
|
||||||
DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? ".exe" :
|
string workingDirectory = string.Empty;
|
||||||
DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" : "";
|
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
||||||
var executable = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, DeploymentParameters.ApplicationName + executableExtension);
|
{
|
||||||
|
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))
|
if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
executableName = "mono";
|
executableName = "mono";
|
||||||
executableArgs = executable;
|
executableArgs = executable;
|
||||||
}
|
}
|
||||||
else if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable)
|
else if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable)
|
||||||
{
|
{
|
||||||
executableName = "dotnet";
|
executableName = "dotnet";
|
||||||
executableArgs = executable;
|
executableArgs = executable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
executableName = executable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
executableName = executable;
|
workingDirectory = DeploymentParameters.ApplicationPath;
|
||||||
|
var targetFramework = DeploymentParameters.TargetFramework ?? (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? "net46" : "netcoreapp2.0");
|
||||||
|
|
||||||
|
executableName = DotnetCommandName;
|
||||||
|
executableArgs = $"run --framework {targetFramework} {DotnetArgumentSeparator}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executableArgs += $" --server.urls {hintUrl} "
|
||||||
|
+ $" --server {(DeploymentParameters.ServerType == ServerType.WebListener ? "Microsoft.AspNetCore.Server.HttpSys" : "Microsoft.AspNetCore.Server.Kestrel")}";
|
||||||
|
|
||||||
|
Logger.LogInformation($"Executing {executableName} {executableArgs}");
|
||||||
|
|
||||||
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = executableName,
|
||||||
|
Arguments = executableArgs,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
// Trying a work around for https://github.com/aspnet/Hosting/issues/140.
|
||||||
|
RedirectStandardInput = true,
|
||||||
|
WorkingDirectory = workingDirectory
|
||||||
|
};
|
||||||
|
|
||||||
|
AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);
|
||||||
|
|
||||||
|
Uri actualUrl = null;
|
||||||
|
var started = new TaskCompletionSource<object>();
|
||||||
|
|
||||||
|
HostProcess = new Process() { StartInfo = startInfo };
|
||||||
|
HostProcess.EnableRaisingEvents = true;
|
||||||
|
HostProcess.OutputDataReceived += (sender, dataArgs) =>
|
||||||
|
{
|
||||||
|
if (string.Equals(dataArgs.Data, ApplicationStartedMessage))
|
||||||
|
{
|
||||||
|
started.TrySetResult(null);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(dataArgs.Data))
|
||||||
|
{
|
||||||
|
var m = NowListeningRegex.Match(dataArgs.Data);
|
||||||
|
if (m.Success)
|
||||||
|
{
|
||||||
|
actualUrl = new Uri(m.Groups["url"].Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var hostExitTokenSource = new CancellationTokenSource();
|
||||||
|
HostProcess.Exited += (sender, e) =>
|
||||||
|
{
|
||||||
|
Logger.LogInformation("host process ID {pid} shut down", HostProcess.Id);
|
||||||
|
TriggerHostShutdown(hostExitTokenSource);
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HostProcess.StartAndCaptureOutAndErrToLogger(executableName, Logger);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error occurred while starting the process. Exception: {exception}", ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HostProcess.HasExited)
|
||||||
|
{
|
||||||
|
Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, HostProcess.Id, HostProcess.ExitCode);
|
||||||
|
throw new Exception("Failed to start host");
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Started {fileName}. Process Id : {processId}", startInfo.FileName, HostProcess.Id);
|
||||||
|
|
||||||
|
await started.Task;
|
||||||
|
|
||||||
|
return (url: actualUrl ?? hintUrl, hostExitToken: hostExitTokenSource.Token);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
workingDirectory = DeploymentParameters.ApplicationPath;
|
|
||||||
var targetFramework = DeploymentParameters.TargetFramework ?? (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? "net46" : "netcoreapp2.0");
|
|
||||||
|
|
||||||
executableName = DotnetCommandName;
|
|
||||||
executableArgs = $"run --framework {targetFramework} {DotnetArgumentSeparator}";
|
|
||||||
}
|
|
||||||
|
|
||||||
executableArgs += $" --server.urls {uri} "
|
|
||||||
+ $" --server {(DeploymentParameters.ServerType == ServerType.WebListener ? "Microsoft.AspNetCore.Server.HttpSys" : "Microsoft.AspNetCore.Server.Kestrel")}";
|
|
||||||
|
|
||||||
Logger.LogInformation($"Executing {executableName} {executableArgs}");
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = executableName,
|
|
||||||
Arguments = executableArgs,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
// Trying a work around for https://github.com/aspnet/Hosting/issues/140.
|
|
||||||
RedirectStandardInput = true,
|
|
||||||
WorkingDirectory = workingDirectory
|
|
||||||
};
|
|
||||||
|
|
||||||
AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);
|
|
||||||
|
|
||||||
HostProcess = new Process() { StartInfo = startInfo };
|
|
||||||
HostProcess.ErrorDataReceived += (sender, dataArgs) => { Logger.LogError(dataArgs.Data ?? string.Empty); };
|
|
||||||
HostProcess.OutputDataReceived += (sender, dataArgs) => { Logger.LogInformation(dataArgs.Data ?? string.Empty); };
|
|
||||||
HostProcess.EnableRaisingEvents = true;
|
|
||||||
var hostExitTokenSource = new CancellationTokenSource();
|
|
||||||
HostProcess.Exited += (sender, e) =>
|
|
||||||
{
|
|
||||||
TriggerHostShutdown(hostExitTokenSource);
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HostProcess.Start();
|
|
||||||
HostProcess.BeginErrorReadLine();
|
|
||||||
HostProcess.BeginOutputReadLine();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError("Error occurred while starting the process. Exception: {exception}", ex.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HostProcess.HasExited)
|
|
||||||
{
|
|
||||||
Logger.LogError("Host process {processName} exited with code {exitCode} or failed to start.", startInfo.FileName, HostProcess.ExitCode);
|
|
||||||
throw new Exception("Failed to start host");
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.LogInformation("Started {fileName}. Process Id : {processId}", startInfo.FileName, HostProcess.Id);
|
|
||||||
return hostExitTokenSource.Token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
ShutDownIfAnyHostProcess(HostProcess);
|
using (Logger.BeginScope("SelfHost.Dispose"))
|
||||||
|
|
||||||
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
|
||||||
{
|
{
|
||||||
CleanPublishedOutput();
|
ShutDownIfAnyHostProcess(HostProcess);
|
||||||
|
|
||||||
|
if (DeploymentParameters.PublishApplicationBeforeDeployment)
|
||||||
|
{
|
||||||
|
CleanPublishedOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
InvokeUserApplicationCleanup();
|
||||||
|
|
||||||
|
StopTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
InvokeUserApplicationCleanup();
|
|
||||||
|
|
||||||
StopTimer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>ASP.NET Core helpers to deploy applications to IIS Express, IIS, WebListener and Kestrel for testing.</Description>
|
<Description>ASP.NET Core helpers to deploy applications to IIS Express, IIS, WebListener and Kestrel for testing.</Description>
|
||||||
<VersionPrefix>0.3.0</VersionPrefix>
|
<VersionPrefix>0.4.0</VersionPrefix>
|
||||||
<TargetFrameworks>net46;netstandard1.3</TargetFrameworks>
|
<TargetFrameworks>net46;netstandard1.3</TargetFrameworks>
|
||||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.ValueTuple" Version="$(CoreFxVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(AspNetCoreVersion)" />
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||||
using Microsoft.AspNetCore.Server.IntegrationTesting.xunit;
|
using Microsoft.AspNetCore.Server.IntegrationTesting.xunit;
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
|
@ -16,11 +17,12 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
|
||||||
[ConditionalFact]
|
[ConditionalFact]
|
||||||
[OSSkipCondition(OperatingSystems.Windows)]
|
[OSSkipCondition(OperatingSystems.Windows)]
|
||||||
[OSSkipCondition(OperatingSystems.MacOSX)]
|
[OSSkipCondition(OperatingSystems.MacOSX)]
|
||||||
public void ShutdownTest()
|
public async Task ShutdownTest()
|
||||||
{
|
{
|
||||||
var logger = new LoggerFactory()
|
var logger = new LoggerFactory()
|
||||||
.AddConsole()
|
.AddConsole()
|
||||||
.CreateLogger(nameof(ShutdownTest));
|
.CreateLogger(nameof(ShutdownTest));
|
||||||
|
logger.LogInformation("Started test: {testName}", nameof(ShutdownTest));
|
||||||
|
|
||||||
var applicationPath = Path.Combine(TestProjectHelpers.GetSolutionRoot(), "test",
|
var applicationPath = Path.Combine(TestProjectHelpers.GetSolutionRoot(), "test",
|
||||||
"Microsoft.AspNetCore.Hosting.TestSites");
|
"Microsoft.AspNetCore.Hosting.TestSites");
|
||||||
|
|
@ -39,10 +41,10 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
|
||||||
|
|
||||||
using (var deployer = new SelfHostDeployer(deploymentParameters, logger))
|
using (var deployer = new SelfHostDeployer(deploymentParameters, logger))
|
||||||
{
|
{
|
||||||
deployer.Deploy();
|
await deployer.DeployAsync();
|
||||||
|
|
||||||
// Wait for application to start
|
// Wait for application to start
|
||||||
System.Threading.Thread.Sleep(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
string output = string.Empty;
|
string output = string.Empty;
|
||||||
deployer.HostProcess.OutputDataReceived += (sender, args) => output += args.Data + '\n';
|
deployer.HostProcess.OutputDataReceived += (sender, args) => output += args.Data + '\n';
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<Import Project="..\..\build\common.props" />
|
<Import Project="..\..\build\common.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
|
<TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:49570/",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.Hosting.TestSites": {
|
||||||
|
"commandName": "Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue