diff --git a/Hosting.sln b/Hosting.sln
index fd29a14db9..b7b4361fe4 100644
--- a/Hosting.sln
+++ b/Hosting.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.22710.0
+VisualStudioVersion = 14.0.22803.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E0497F39-AFFB-4819-A116-E39E361915AB}"
EndProject
@@ -24,6 +24,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Hosting.In
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Hosting.Server.Interfaces", "src\Microsoft.AspNet.Hosting.Server.Interfaces\Microsoft.AspNet.Hosting.Server.Interfaces.xproj", "{FDBBA081-5248-4FC0-9E08-B46BEF3FA438}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.Testing", "src\Microsoft.AspNet.Server.Testing\Microsoft.AspNet.Server.Testing.xproj", "{3DA89347-6731-4366-80C4-548F24E8607B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -98,6 +100,18 @@ Global
{FDBBA081-5248-4FC0-9E08-B46BEF3FA438}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{FDBBA081-5248-4FC0-9E08-B46BEF3FA438}.Release|x86.ActiveCfg = Release|Any CPU
{FDBBA081-5248-4FC0-9E08-B46BEF3FA438}.Release|x86.Build.0 = Release|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Debug|x86.Build.0 = Debug|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Release|x86.ActiveCfg = Release|Any CPU
+ {3DA89347-6731-4366-80C4-548F24E8607B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -109,5 +123,6 @@ Global
{D4F18D58-52B1-435D-A012-10F2CDF158C4} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
{BB780FBB-7842-4759-8DE7-96FA2E5571C1} = {E0497F39-AFFB-4819-A116-E39E361915AB}
{FDBBA081-5248-4FC0-9E08-B46BEF3FA438} = {E0497F39-AFFB-4819-A116-E39E361915AB}
+ {3DA89347-6731-4366-80C4-548F24E8607B} = {E0497F39-AFFB-4819-A116-E39E361915AB}
EndGlobalSection
EndGlobal
diff --git a/src/Microsoft.AspNet.Server.Testing/Common/DeploymentParameters.cs b/src/Microsoft.AspNet.Server.Testing/Common/DeploymentParameters.cs
new file mode 100644
index 0000000000..39278112b2
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Common/DeploymentParameters.cs
@@ -0,0 +1,104 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Parameters to control application deployment.
+ ///
+ public class DeploymentParameters
+ {
+ ///
+ /// Creates an instance of .
+ ///
+ /// Source code location of the target location to be deployed.
+ /// Where to be deployed on.
+ /// Flavor of the clr to run against.
+ /// Architecture of the DNX to be used.
+ public DeploymentParameters(
+ string applicationPath,
+ ServerType serverType,
+ RuntimeFlavor runtimeFlavor,
+ RuntimeArchitecture runtimeArchitecture)
+ {
+ if (string.IsNullOrEmpty(applicationPath))
+ {
+ throw new ArgumentException("Value cannot be null.", "applicationPath");
+ }
+
+ if (!Directory.Exists(applicationPath))
+ {
+ throw new DirectoryNotFoundException(string.Format("Application path {0} does not exist.", applicationPath));
+ }
+
+ ApplicationPath = applicationPath;
+ ServerType = serverType;
+ RuntimeFlavor = runtimeFlavor;
+ RuntimeArchitecture = runtimeArchitecture;
+ }
+
+ public ServerType ServerType { get; private set; }
+
+ public RuntimeFlavor RuntimeFlavor { get; private set; }
+
+ public RuntimeArchitecture RuntimeArchitecture { get; private set; }
+
+ ///
+ /// Suggested base url for the deployed application. The final deployed url could be
+ /// different than this. Use for the
+ /// deployed url.
+ ///
+ public string ApplicationBaseUriHint { get; set; }
+
+ public string EnvironmentName { get; set; }
+
+ public string ApplicationHostConfigTemplateContent { get; set; }
+
+ public string ApplicationHostConfigLocation { get; set; }
+
+ public string SiteName { get; set; }
+
+ public string ApplicationPath { get; set; }
+
+ ///
+ /// To publish the application before deployment.
+ ///
+ public bool PublishApplicationBeforeDeployment { get; set; }
+
+ public string PublishedApplicationRootPath { get; set; }
+
+ ///
+ /// Passes the --no-source option when publishing.
+ ///
+ public bool PublishWithNoSource { get; set; }
+
+ public string DnxRuntime { get; set; }
+
+ ///
+ /// Environment variables to be set before starting the host.
+ /// Not applicable for IIS Scenarios.
+ ///
+ public List> EnvironmentVariables { get; private set; } = new List>();
+
+ ///
+ /// For any application level cleanup to be invoked after performing host cleanup.
+ ///
+ public Action UserAdditionalCleanup { get; set; }
+
+ public override string ToString()
+ {
+ return string.Format(
+ "[Variation] :: ServerType={0}, Runtime={1}, Arch={2}, BaseUrlHint={3}, Publish={4}, NoSource={5}",
+ ServerType,
+ RuntimeFlavor,
+ RuntimeArchitecture,
+ ApplicationBaseUriHint,
+ PublishApplicationBeforeDeployment,
+ PublishWithNoSource);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Common/DeploymentResult.cs b/src/Microsoft.AspNet.Server.Testing/Common/DeploymentResult.cs
new file mode 100644
index 0000000000..6a2c1a69d4
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Common/DeploymentResult.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Result of a deployment.
+ ///
+ public class DeploymentResult
+ {
+ ///
+ /// Base Uri of the deployment application.
+ ///
+ public string ApplicationBaseUri { get; set; }
+
+ ///
+ /// The web root folder where the application is hosted. This path can be different from the
+ /// original application source location if published before deployment.
+ ///
+ public string WebRootLocation { get; set; }
+
+ ///
+ /// Original deployment parameters used for this deployment.
+ ///
+ public DeploymentParameters DeploymentParameters { get; set; }
+
+ ///
+ /// Triggered when the host process dies or pulled down.
+ ///
+ public CancellationToken HostShutdownToken { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Common/DotnetArchitecture.cs b/src/Microsoft.AspNet.Server.Testing/Common/DotnetArchitecture.cs
new file mode 100644
index 0000000000..11b1a3d5e3
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Common/DotnetArchitecture.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ public enum RuntimeArchitecture
+ {
+ x64,
+ x86
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Common/DotnetFlavor.cs b/src/Microsoft.AspNet.Server.Testing/Common/DotnetFlavor.cs
new file mode 100644
index 0000000000..99913581b7
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Common/DotnetFlavor.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ public enum RuntimeFlavor
+ {
+ Clr,
+ CoreClr,
+ Mono
+ }
+}
diff --git a/src/Microsoft.AspNet.Server.Testing/Common/RetryHelper.cs b/src/Microsoft.AspNet.Server.Testing/Common/RetryHelper.cs
new file mode 100644
index 0000000000..1c6e6949dd
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Common/RetryHelper.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Framework.Logging;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ public class RetryHelper
+ {
+ ///
+ /// Retries every 1 sec for 60 times by default.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task RetryRequest(
+ Func> retryBlock,
+ ILogger logger,
+ CancellationToken cancellationToken = default(CancellationToken),
+ int retryCount = 60)
+ {
+ for (int retry = 0; retry < retryCount; retry++)
+ {
+ try
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ logger.LogInformation("Stopping retry as cancellation token is triggered.");
+ break;
+ }
+
+ logger.LogWarning("Retry count {retryCount}..", retry + 1);
+ var response = await retryBlock();
+
+ if (response.StatusCode == HttpStatusCode.ServiceUnavailable)
+ {
+ // Automatically retry on 503. May be application is still booting.
+ logger.LogWarning("Retrying a service unavailable error.");
+ continue;
+ }
+
+ return response; //Went through successfully
+ }
+ catch (Exception exception)
+ {
+ if (retry == retryCount - 1)
+ {
+ throw;
+ }
+ else
+ {
+ if (exception is HttpRequestException
+#if DNX451
+ || exception is System.Net.WebException
+#endif
+ )
+ {
+ logger.LogWarning("Failed to complete the request : {0}.", exception.Message);
+ await Task.Delay(1 * 1000); //Wait for a while before retry.
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Common/ServerType.cs b/src/Microsoft.AspNet.Server.Testing/Common/ServerType.cs
new file mode 100644
index 0000000000..9756b1496b
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Common/ServerType.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ public enum ServerType
+ {
+ IISExpress,
+ IIS,
+ IISNativeModule,
+ WebListener,
+ Kestrel
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Deployers/ApplicationDeployer.cs b/src/Microsoft.AspNet.Server.Testing/Deployers/ApplicationDeployer.cs
new file mode 100644
index 0000000000..c974840a22
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Deployers/ApplicationDeployer.cs
@@ -0,0 +1,227 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using Microsoft.Framework.Logging;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Abstract base class of all deplolyers with a back bone implementation of some of the common helpers.
+ ///
+ public abstract class ApplicationDeployer : IApplicationDeployer
+ {
+ protected string ChosenRuntimePath { get; set; }
+
+ protected string ChosenRuntimeName { get; set; }
+
+ protected DeploymentParameters DeploymentParameters { get; private set; }
+
+ protected ILogger Logger { get; private set; }
+
+ protected Stopwatch StopWatch { get; private set; } = new Stopwatch();
+
+ public abstract DeploymentResult Deploy();
+
+ public ApplicationDeployer(
+ DeploymentParameters deploymentParameters,
+ ILogger logger)
+ {
+ DeploymentParameters = deploymentParameters;
+ Logger = logger;
+ }
+
+ protected string PopulateChosenRuntimeInformation()
+ {
+ var runtimePath = Process.GetCurrentProcess().MainModule.FileName;
+ Logger.LogInformation(string.Empty);
+ Logger.LogInformation("Current runtime path is : {0}", runtimePath);
+
+ var replaceStr = new StringBuilder().
+ Append("dnx").
+ Append((DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr) ? "-coreclr" : "-clr").
+ Append("-win").
+ Append((DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86) ? "-x86" : "-x64").
+ ToString();
+
+ runtimePath = Regex.Replace(runtimePath, "dnx-(clr|coreclr)-win-(x86|x64)", replaceStr, RegexOptions.IgnoreCase);
+ ChosenRuntimePath = Path.GetDirectoryName(runtimePath);
+
+ var runtimeDirectoryInfo = new DirectoryInfo(ChosenRuntimePath);
+ if (!runtimeDirectoryInfo.Exists)
+ {
+ throw new Exception(
+ string.Format("Requested runtime at location '{0}' does not exist. Please make sure it is installed before running test.",
+ runtimeDirectoryInfo.FullName));
+ }
+
+ ChosenRuntimeName = runtimeDirectoryInfo.Parent.Name;
+ Logger.LogInformation(string.Empty);
+ Logger.LogInformation("Changing to use runtime : {runtimeName}", ChosenRuntimeName);
+ return ChosenRuntimeName;
+ }
+
+ protected void DnuPublish(string publishRoot = null)
+ {
+ DeploymentParameters.PublishedApplicationRootPath = Path.Combine(publishRoot ?? Path.GetTempPath(), Guid.NewGuid().ToString());
+
+ var parameters =
+ string.Format(
+ "publish {0} -o {1} --runtime {2} {3}",
+ DeploymentParameters.ApplicationPath,
+ DeploymentParameters.PublishedApplicationRootPath,
+ DeploymentParameters.DnxRuntime,
+ DeploymentParameters.PublishWithNoSource ? "--no-source" : string.Empty);
+
+ Logger.LogInformation("Executing command dnu {args}", parameters);
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = Path.Combine(ChosenRuntimePath, "dnu.cmd"),
+ Arguments = parameters,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ var hostProcess = Process.Start(startInfo);
+ hostProcess.WaitForExit(60 * 1000);
+
+ DeploymentParameters.ApplicationPath =
+ (DeploymentParameters.ServerType == ServerType.IISExpress ||
+ DeploymentParameters.ServerType == ServerType.IISNativeModule ||
+ DeploymentParameters.ServerType == ServerType.IIS) ?
+ Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "wwwroot") :
+ Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "approot", "src",
+ new DirectoryInfo(DeploymentParameters.ApplicationPath).Name);
+
+ Logger.LogInformation("dnu publish finished with exit code : {exitCode}", hostProcess.ExitCode);
+ }
+
+ protected void CleanPublishedOutput()
+ {
+ try
+ {
+ // We've originally published the application in a temp folder. We need to delete it.
+ Directory.Delete(DeploymentParameters.PublishedApplicationRootPath, true);
+ }
+ catch (Exception exception)
+ {
+ Logger.LogWarning("Failed to delete directory : {error}", exception.Message);
+ }
+ }
+
+ protected void ShutDownIfAnyHostProcess(Process hostProcess)
+ {
+ if (hostProcess != null && !hostProcess.HasExited)
+ {
+ // Shutdown the host process.
+ hostProcess.Kill();
+ hostProcess.WaitForExit(5 * 1000);
+ if (!hostProcess.HasExited)
+ {
+ Logger.LogWarning("Unable to terminate the host process with process Id '{processId}", hostProcess.Id);
+ }
+ else
+ {
+ Logger.LogInformation("Successfully terminated host process with process Id '{processId}'", hostProcess.Id);
+ }
+ }
+ else
+ {
+ Logger.LogWarning("Host process already exited or never started successfully.");
+ }
+ }
+
+ protected void AddEnvironmentVariablesToProcess(ProcessStartInfo startInfo)
+ {
+ var environment =
+#if DNX451
+ startInfo.EnvironmentVariables;
+#elif DNXCORE50
+ startInfo.Environment;
+#endif
+
+ SetEnvironmentVariable(environment, "ASPNET_ENV", DeploymentParameters.EnvironmentName);
+
+ // Work around for https://github.com/aspnet/dnx/issues/1515
+ if (DeploymentParameters.PublishWithNoSource)
+ {
+ SetEnvironmentVariable(environment, "DNX_PACKAGES", null);
+ }
+
+ SetEnvironmentVariable(environment, "DNX_DEFAULT_LIB", null);
+
+ foreach (var environmentVariable in DeploymentParameters.EnvironmentVariables)
+ {
+ SetEnvironmentVariable(environment, environmentVariable.Key, environmentVariable.Value);
+ }
+ }
+
+#if DNX451
+ protected void SetEnvironmentVariable(System.Collections.Specialized.StringDictionary environment, string name, string value)
+ {
+#elif DNXCORE50
+ protected void SetEnvironmentVariable(System.Collections.Generic.IDictionary environment, string name, string value)
+ {
+#endif
+ if (value == null)
+ {
+ Logger.LogInformation("Removing environment variable {name}", name);
+ environment.Remove(name);
+ }
+ else
+ {
+ Logger.LogInformation("SET {name}={value}", name, value);
+ environment[name] = value;
+ }
+ }
+
+ protected void InvokeUserApplicationCleanup()
+ {
+ if (DeploymentParameters.UserAdditionalCleanup != null)
+ {
+ // User cleanup.
+ try
+ {
+ DeploymentParameters.UserAdditionalCleanup(DeploymentParameters);
+ }
+ catch (Exception exception)
+ {
+ Logger.LogWarning("User cleanup code failed with exception : {exception}", exception.Message);
+ }
+ }
+ }
+
+ protected void TriggerHostShutdown(CancellationTokenSource hostShutdownSource)
+ {
+ Logger.LogInformation("Host process shutting down.");
+ try
+ {
+ hostShutdownSource.Cancel();
+ }
+ catch (Exception)
+ {
+ // Suppress errors.
+ }
+ }
+
+ protected void StartTimer()
+ {
+ Logger.LogInformation("Deploying {VariationDetails}", DeploymentParameters.ToString());
+ StopWatch.Start();
+ }
+
+ protected void StopTimer()
+ {
+ StopWatch.Stop();
+ Logger.LogInformation("[Time]: Total time taken for this test variation '{t}' seconds", StopWatch.Elapsed.TotalSeconds);
+ }
+
+ public abstract void Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Deployers/ApplicationDeployerFactory.cs b/src/Microsoft.AspNet.Server.Testing/Deployers/ApplicationDeployerFactory.cs
new file mode 100644
index 0000000000..5308609815
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Deployers/ApplicationDeployerFactory.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Framework.Logging;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Factory to create an appropriate deployer based on .
+ ///
+ public class ApplicationDeployerFactory
+ {
+ ///
+ /// Creates a deployer instance based on settings in .
+ ///
+ ///
+ ///
+ ///
+ public static IApplicationDeployer Create(DeploymentParameters deploymentParameters, ILogger logger)
+ {
+ if (deploymentParameters == null)
+ {
+ throw new ArgumentNullException(nameof(deploymentParameters));
+ }
+
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
+
+ if (deploymentParameters.RuntimeFlavor == RuntimeFlavor.Mono)
+ {
+ return new MonoDeployer(deploymentParameters, logger);
+ }
+
+ switch (deploymentParameters.ServerType)
+ {
+ case ServerType.IISExpress:
+ return new IISExpressDeployer(deploymentParameters, logger);
+#if DNX451
+ case ServerType.IIS:
+ case ServerType.IISNativeModule:
+ return new IISDeployer(deploymentParameters, logger);
+#endif
+ case ServerType.WebListener:
+ case ServerType.Kestrel:
+ return new SelfHostDeployer(deploymentParameters, logger);
+ default:
+ throw new NotSupportedException(
+ string.Format("Found no deployers suitable for server type '{0}' with the current runtime.",
+ deploymentParameters.ServerType)
+ );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Deployers/IApplicationDeployer.cs b/src/Microsoft.AspNet.Server.Testing/Deployers/IApplicationDeployer.cs
new file mode 100644
index 0000000000..c027632ccd
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Deployers/IApplicationDeployer.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Server.Testing
+{
+ ///
+ /// Common operations on an application deployer.
+ ///
+ public interface IApplicationDeployer : IDisposable
+ {
+ ///
+ /// Deploys the application to the target with specified .
+ ///
+ ///
+ DeploymentResult Deploy();
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Deployers/IISDeployer.cs b/src/Microsoft.AspNet.Server.Testing/Deployers/IISDeployer.cs
new file mode 100644
index 0000000000..bdc5ac70c6
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Deployers/IISDeployer.cs
@@ -0,0 +1,213 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#if DNX451
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Xml;
+using Microsoft.Framework.Logging;
+using Microsoft.Web.Administration;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Deployer for IIS.
+ ///
+ 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);
+
+ DeploymentParameters.DnxRuntime = PopulateChosenRuntimeInformation();
+
+ // Publish to IIS root\application folder.
+ DnuPublish(publishRoot: _application.WebSiteRootFolder);
+
+ // Drop an ini file instead of setting environment variable.
+ SetAspEnvironmentWithIni();
+
+ // Setup the IIS Application.
+ if (DeploymentParameters.ServerType == ServerType.IISNativeModule)
+ {
+ TurnRammFarOnNativeModule();
+ }
+
+ lock (_syncObject)
+ {
+ // To prevent modifying the IIS setup concurrently.
+ _application.Deploy();
+ }
+
+ // Warm up time for IIS setup.
+ Thread.Sleep(1 * 1000);
+ Logger.LogInformation("Successfully finished IIS application directory setup.");
+
+ return new DeploymentResult
+ {
+ WebRootLocation = DeploymentParameters.ApplicationPath,
+ DeploymentParameters = DeploymentParameters,
+ // Accomodate the vdir name.
+ ApplicationBaseUri = new UriBuilder(Uri.UriSchemeHttp, "localhost", IISApplication.Port, _application.VirtualDirectoryName).Uri.AbsoluteUri + "/",
+ HostShutdownToken = _hostShutdownToken.Token
+ };
+ }
+
+ private void SetAspEnvironmentWithIni()
+ {
+ // Drop a Microsoft.AspNet.Hosting.ini with ASPNET_ENV information.
+ Logger.LogInformation("Creating Microsoft.AspNet.Hosting.ini file with ASPNET_ENV.");
+ var iniFile = Path.Combine(DeploymentParameters.ApplicationPath, "Microsoft.AspNet.Hosting.ini");
+ File.WriteAllText(iniFile, string.Format("ASPNET_ENV={0}", DeploymentParameters.EnvironmentName));
+ }
+
+ private void TurnRammFarOnNativeModule()
+ {
+ Logger.LogInformation("Turning runAllManagedModulesForAllRequests=true in web.config for native module.");
+ var webConfig = Path.Combine(DeploymentParameters.ApplicationPath, "web.config");
+ var configuration = new XmlDocument();
+ configuration.LoadXml(File.ReadAllText(webConfig));
+
+ // https://github.com/aspnet/Helios/issues/77
+ var rammfarAttribute = configuration.CreateAttribute("runAllManagedModulesForAllRequests");
+ rammfarAttribute.Value = "true";
+ var modulesNode = configuration.CreateElement("modules");
+ modulesNode.Attributes.Append(rammfarAttribute);
+ var systemWebServerNode = configuration.CreateElement("system.webServer");
+ systemWebServerNode.AppendChild(modulesNode);
+ configuration.SelectSingleNode("//configuration").AppendChild(systemWebServerNode);
+ configuration.Save(webConfig);
+ }
+
+ public override void Dispose()
+ {
+ if (_application != null)
+ {
+ lock (_syncObject)
+ {
+ // Sequentialize IIS operations.
+ _application.StopAndDeleteAppPool();
+ }
+
+ TriggerHostShutdown(_hostShutdownToken);
+ }
+
+ CleanPublishedOutput();
+ InvokeUserApplicationCleanup();
+
+ StopTimer();
+ }
+
+ private class IISApplication
+ {
+ private const string WEBSITE_NAME = "ASPNETTESTRUNS";
+ private const string NATIVE_MODULE_MANAGED_RUNTIME_VERSION = "vCoreFX";
+
+ private readonly ServerManager _serverManager = new ServerManager();
+ private readonly DeploymentParameters _deploymentParameters;
+ private readonly ILogger _logger;
+ private ApplicationPool _applicationPool;
+ private Application _application;
+ private Site _website;
+
+ public string VirtualDirectoryName { get; set; }
+
+ // Always create website with the same port.
+ public const int Port = 5100;
+
+ public string WebSiteRootFolder
+ {
+ get
+ {
+ return Path.Combine(
+ Environment.GetEnvironmentVariable("SystemDrive") + @"\",
+ "inetpub",
+ WEBSITE_NAME);
+ }
+ }
+
+ public IISApplication(DeploymentParameters deploymentParameters, ILogger logger)
+ {
+ _deploymentParameters = deploymentParameters;
+ _logger = logger;
+ }
+
+ public void Deploy()
+ {
+ VirtualDirectoryName = new DirectoryInfo(_deploymentParameters.ApplicationPath).Parent.Name;
+ _applicationPool = CreateAppPool(VirtualDirectoryName);
+ _application = Website.Applications.Add("/" + VirtualDirectoryName, _deploymentParameters.ApplicationPath);
+ _application.ApplicationPoolName = _applicationPool.Name;
+ _serverManager.CommitChanges();
+ }
+
+ private Site Website
+ {
+ get
+ {
+ _website = _serverManager.Sites.Where(s => s.Name == WEBSITE_NAME).FirstOrDefault();
+ if (_website == null)
+ {
+ _website = _serverManager.Sites.Add(WEBSITE_NAME, WebSiteRootFolder, Port);
+ }
+
+ return _website;
+ }
+ }
+
+ private ApplicationPool CreateAppPool(string appPoolName)
+ {
+ var applicationPool = _serverManager.ApplicationPools.Add(appPoolName);
+ if (_deploymentParameters.ServerType == ServerType.IISNativeModule)
+ {
+ // Not assigning a runtime version will choose v4.0 default.
+ applicationPool.ManagedRuntimeVersion = NATIVE_MODULE_MANAGED_RUNTIME_VERSION;
+ }
+
+ applicationPool.Enable32BitAppOnWin64 = (_deploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86);
+ _logger.LogInformation("Created {bit} application pool '{name}' with runtime version {runtime}.",
+ _deploymentParameters.RuntimeArchitecture, applicationPool.Name,
+ string.IsNullOrEmpty(applicationPool.ManagedRuntimeVersion) ? "that is default" : applicationPool.ManagedRuntimeVersion);
+ return applicationPool;
+ }
+
+ public void StopAndDeleteAppPool()
+ {
+ _logger.LogInformation("Stopping application pool '{name}' and deleting application.", _applicationPool.Name);
+
+ if (_applicationPool != null)
+ {
+ _applicationPool.Stop();
+ }
+
+ // Remove the application from website.
+ if (_application != null)
+ {
+ _application = Website.Applications.Where(a => a.Path == _application.Path).FirstOrDefault();
+ Website.Applications.Remove(_application);
+ _serverManager.ApplicationPools.Remove(_serverManager.ApplicationPools[_applicationPool.Name]);
+ _serverManager.CommitChanges();
+ _logger.LogInformation("Successfully stopped application pool '{name}' and deleted application from IIS.", _applicationPool.Name);
+ }
+ }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Deployers/IISExpressDeployer.cs b/src/Microsoft.AspNet.Server.Testing/Deployers/IISExpressDeployer.cs
new file mode 100644
index 0000000000..30dc94bfdc
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Deployers/IISExpressDeployer.cs
@@ -0,0 +1,208 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading;
+using Microsoft.Framework.Logging;
+using Microsoft.Framework.Runtime;
+using Microsoft.Framework.Runtime.Infrastructure;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Deployment helper for IISExpress.
+ ///
+ public class IISExpressDeployer : ApplicationDeployer
+ {
+ private Process _hostProcess;
+
+ public IISExpressDeployer(DeploymentParameters deploymentParameters, ILogger logger)
+ : base(deploymentParameters, logger)
+ {
+ }
+
+ public override DeploymentResult Deploy()
+ {
+ // Start timer
+ StartTimer();
+
+ DeploymentParameters.DnxRuntime = PopulateChosenRuntimeInformation();
+
+ if (DeploymentParameters.PublishApplicationBeforeDeployment)
+ {
+ DnuPublish();
+ }
+
+ // Launch the host process.
+ var hostExitToken = StartIISExpress();
+
+ return new DeploymentResult
+ {
+ WebRootLocation = DeploymentParameters.ApplicationPath,
+ DeploymentParameters = DeploymentParameters,
+ // Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath.
+ ApplicationBaseUri = DeploymentParameters.ApplicationBaseUriHint,
+ HostShutdownToken = hostExitToken
+ };
+ }
+
+ private CancellationToken StartIISExpress()
+ {
+ if (!string.IsNullOrWhiteSpace(DeploymentParameters.ApplicationHostConfigTemplateContent))
+ {
+ // 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.
+
+ DeploymentParameters.ApplicationHostConfigTemplateContent =
+ DeploymentParameters.ApplicationHostConfigTemplateContent
+ .Replace("[ApplicationPhysicalPath]", Path.Combine(DeploymentParameters.ApplicationPath, "wwwroot"))
+ .Replace("[PORT]", new Uri(DeploymentParameters.ApplicationBaseUriHint).Port.ToString());
+
+ DeploymentParameters.ApplicationHostConfigLocation = Path.GetTempFileName();
+
+ File.WriteAllText(DeploymentParameters.ApplicationHostConfigLocation,
+ DeploymentParameters.ApplicationHostConfigTemplateContent.Replace("[ApplicationPhysicalPath]", DeploymentParameters.ApplicationPath));
+ }
+
+ if (!DeploymentParameters.PublishApplicationBeforeDeployment)
+ {
+ CopyAspNetLoader();
+ }
+
+ var webroot = DeploymentParameters.ApplicationPath;
+ if (!webroot.EndsWith("wwwroot"))
+ {
+ webroot = Path.Combine(webroot, "wwwroot");
+ }
+
+ var parameters = string.IsNullOrWhiteSpace(DeploymentParameters.ApplicationHostConfigLocation) ?
+ string.Format("/port:{0} /path:\"{1}\" /trace:error", new Uri(DeploymentParameters.ApplicationBaseUriHint).Port, webroot) :
+ string.Format("/site:{0} /config:{1} /trace:error", DeploymentParameters.SiteName, DeploymentParameters.ApplicationHostConfigLocation);
+
+ var iisExpressPath = GetIISExpressPath();
+
+ Logger.LogInformation("Executing command : {iisExpress} {args}", iisExpressPath, parameters);
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = iisExpressPath,
+ Arguments = parameters,
+ UseShellExecute = false,
+ CreateNoWindow = false
+ };
+
+ AddEnvironmentVariablesToProcess(startInfo);
+
+ // IIS express figures out the DNX from %PATH%.
+#if DNX451
+ SetEnvironmentVariable(startInfo.EnvironmentVariables, "PATH", ChosenRuntimePath + ";" + startInfo.EnvironmentVariables["PATH"]);
+ SetEnvironmentVariable(startInfo.EnvironmentVariables, "DNX_APPBASE", DeploymentParameters.ApplicationPath);
+#elif DNXCORE50
+ SetEnvironmentVariable(startInfo.Environment, "PATH", ChosenRuntimePath + ";" + startInfo.Environment["PATH"]);
+ SetEnvironmentVariable(startInfo.Environment, "DNX_APPBASE", DeploymentParameters.ApplicationPath);
+#endif
+
+ _hostProcess = Process.Start(startInfo);
+ _hostProcess.EnableRaisingEvents = true;
+ var hostExitTokenSource = new CancellationTokenSource();
+ _hostProcess.Exited += (sender, e) =>
+ {
+ TriggerHostShutdown(hostExitTokenSource);
+ };
+
+ 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 void CopyAspNetLoader()
+ {
+ var libraryManager = (ILibraryManager)CallContextServiceLocator.Locator.ServiceProvider.GetService(typeof(ILibraryManager));
+ var interopLibrary = libraryManager.GetLibraryInformation("Microsoft.AspNet.Loader.IIS.Interop");
+
+ if (interopLibrary == null)
+ {
+ throw new Exception(
+ string.Format("Include Microsoft.AspNet.Server.IIS package in your project.json to deploy in {0}.",
+ ServerType.IISExpress));
+ }
+
+ var aspNetLoaderSrcPath = Path.Combine(interopLibrary.Path, "tools", "AspNet.Loader.dll");
+ var aspNetLoaderDestPath = Path.Combine(DeploymentParameters.ApplicationPath, "wwwroot", "bin", "AspNet.Loader.dll");
+
+ // Create bin directory if it does not exist.
+ Directory.CreateDirectory(new DirectoryInfo(aspNetLoaderDestPath).Parent.FullName);
+
+ if (!File.Exists(aspNetLoaderDestPath))
+ {
+ try
+ {
+ File.Copy(aspNetLoaderSrcPath, aspNetLoaderDestPath);
+ }
+ catch (IOException)
+ {
+ // Ignore file already exists exception. Sometimes multiple tests might try
+ // doing the same and one of them wins.
+ }
+ }
+ }
+
+ private string GetIISExpressPath()
+ {
+ // Get path to program files
+ var iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles(x86)"), "IIS Express", "iisexpress.exe");
+
+ // Get path to 64 bit of IIS Express
+ if (DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64)
+ {
+ iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles"), "IIS Express", "iisexpress.exe");
+
+ // If process is 32 bit, the path points to x86. Replace path to point to x64
+ iisExpressPath = IntPtr.Size == 8 ? iisExpressPath : iisExpressPath.Replace(" (x86)", "");
+ }
+
+ if (!File.Exists(iisExpressPath))
+ {
+ throw new Exception("Unable to find IISExpress on the machine");
+ }
+
+ return iisExpressPath;
+ }
+
+ public override void Dispose()
+ {
+ ShutDownIfAnyHostProcess(_hostProcess);
+
+ if (!string.IsNullOrWhiteSpace(DeploymentParameters.ApplicationHostConfigLocation)
+ && File.Exists(DeploymentParameters.ApplicationHostConfigLocation))
+ {
+ // Delete the temp applicationHostConfig that we created.
+ try
+ {
+ File.Delete(DeploymentParameters.ApplicationHostConfigLocation);
+ }
+ catch (Exception exception)
+ {
+ // Ignore delete failures - just write a log.
+ Logger.LogWarning("Failed to delete '{config}'. Exception : {exception}", DeploymentParameters.ApplicationHostConfigLocation, exception.Message);
+ }
+ }
+
+ if (DeploymentParameters.PublishApplicationBeforeDeployment)
+ {
+ CleanPublishedOutput();
+ }
+
+ InvokeUserApplicationCleanup();
+
+ StopTimer();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Deployers/MonoDeployer.cs b/src/Microsoft.AspNet.Server.Testing/Deployers/MonoDeployer.cs
new file mode 100644
index 0000000000..12f099596f
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Deployers/MonoDeployer.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading;
+using Microsoft.Framework.Logging;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Deployer for Kestrel on Mono.
+ ///
+ public class MonoDeployer : ApplicationDeployer
+ {
+ private Process _hostProcess;
+
+ public MonoDeployer(DeploymentParameters deploymentParameters, ILogger logger)
+ : base(deploymentParameters, logger)
+ {
+ }
+
+ public override DeploymentResult Deploy()
+ {
+ // Start timer
+ StartTimer();
+
+ var path = Environment.GetEnvironmentVariable("PATH");
+ var runtimeBin = path.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).
+ Where(c => c.Contains("dnx-mono")).FirstOrDefault();
+
+ if (string.IsNullOrWhiteSpace(runtimeBin))
+ {
+ throw new Exception("Runtime not detected on the machine.");
+ }
+
+ if (DeploymentParameters.PublishApplicationBeforeDeployment)
+ {
+ // We use full path to runtime to pack.
+ DeploymentParameters.DnxRuntime = new DirectoryInfo(runtimeBin).Parent.FullName;
+ DnuPublish();
+ }
+
+ // Launch the host process.
+ var hostExitToken = StartMonoHost();
+
+ return new DeploymentResult
+ {
+ WebRootLocation = DeploymentParameters.ApplicationPath,
+ DeploymentParameters = DeploymentParameters,
+ ApplicationBaseUri = DeploymentParameters.ApplicationBaseUriHint,
+ HostShutdownToken = hostExitToken
+ };
+ }
+
+ private CancellationToken StartMonoHost()
+ {
+ if (DeploymentParameters.ServerType != ServerType.Kestrel)
+ {
+ throw new InvalidOperationException("kestrel is the only valid ServerType for Mono");
+ }
+
+ Logger.LogInformation("Executing command: dnx \"{appPath}\" kestrel --server.urls {url}",
+ DeploymentParameters.ApplicationPath, DeploymentParameters.ApplicationBaseUriHint);
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "dnx",
+ Arguments = string.Format("\"{0}\" kestrel --server.urls {1}", DeploymentParameters.ApplicationPath, DeploymentParameters.ApplicationBaseUriHint),
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardInput = true
+ };
+
+ _hostProcess = Process.Start(startInfo);
+ _hostProcess.EnableRaisingEvents = true;
+ var hostExitTokenSource = new CancellationTokenSource();
+ _hostProcess.Exited += (sender, e) =>
+ {
+ Logger.LogError("Host process {processName} exited with code {exitCode}.", startInfo.FileName, _hostProcess.ExitCode);
+ TriggerHostShutdown(hostExitTokenSource);
+ };
+
+ Logger.LogInformation("Started {0}. Process Id : {1}", _hostProcess.MainModule.FileName, _hostProcess.Id);
+
+ 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");
+ }
+
+ return hostExitTokenSource.Token;
+ }
+
+ public override void Dispose()
+ {
+ ShutDownIfAnyHostProcess(_hostProcess);
+
+ if (DeploymentParameters.PublishApplicationBeforeDeployment)
+ {
+ CleanPublishedOutput();
+ }
+
+ InvokeUserApplicationCleanup();
+
+ StopTimer();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Deployers/SelfHostDeployer.cs b/src/Microsoft.AspNet.Server.Testing/Deployers/SelfHostDeployer.cs
new file mode 100644
index 0000000000..12252c8829
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Deployers/SelfHostDeployer.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading;
+using Microsoft.Framework.Logging;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Deployer for WebListener and Kestrel.
+ ///
+ public class SelfHostDeployer : ApplicationDeployer
+ {
+ private Process _hostProcess;
+
+ public SelfHostDeployer(DeploymentParameters deploymentParameters, ILogger logger)
+ : base(deploymentParameters, logger)
+ {
+ }
+
+ public override DeploymentResult Deploy()
+ {
+ // Start timer
+ StartTimer();
+
+ DeploymentParameters.DnxRuntime = PopulateChosenRuntimeInformation();
+
+ if (DeploymentParameters.PublishApplicationBeforeDeployment)
+ {
+ DnuPublish();
+ }
+
+ // Launch the host process.
+ var hostExitToken = StartSelfHost();
+
+ return new DeploymentResult
+ {
+ WebRootLocation = DeploymentParameters.ApplicationPath,
+ DeploymentParameters = DeploymentParameters,
+ ApplicationBaseUri = DeploymentParameters.ApplicationBaseUriHint,
+ HostShutdownToken = hostExitToken
+ };
+ }
+
+ private CancellationToken StartSelfHost()
+ {
+ var commandName = DeploymentParameters.ServerType == ServerType.WebListener ? "web" : "kestrel";
+ Logger.LogInformation("Executing dnx.exe {appPath} {command} --server.urls {url}", DeploymentParameters.ApplicationPath, commandName, DeploymentParameters.ApplicationBaseUriHint);
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = Path.Combine(ChosenRuntimePath, "dnx.exe"),
+ Arguments = string.Format("\"{0}\" {1} --server.urls {2}", DeploymentParameters.ApplicationPath, commandName, DeploymentParameters.ApplicationBaseUriHint),
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ AddEnvironmentVariablesToProcess(startInfo);
+
+ _hostProcess = Process.Start(startInfo);
+ _hostProcess.EnableRaisingEvents = true;
+ var hostExitTokenSource = new CancellationTokenSource();
+ _hostProcess.Exited += (sender, e) =>
+ {
+ TriggerHostShutdown(hostExitTokenSource);
+ };
+
+ 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()
+ {
+ ShutDownIfAnyHostProcess(_hostProcess);
+
+ if (DeploymentParameters.PublishApplicationBeforeDeployment)
+ {
+ CleanPublishedOutput();
+ }
+
+ InvokeUserApplicationCleanup();
+
+ StopTimer();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/Microsoft.AspNet.Server.Testing.xproj b/src/Microsoft.AspNet.Server.Testing/Microsoft.AspNet.Server.Testing.xproj
new file mode 100644
index 0000000000..6d890ba4ec
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/Microsoft.AspNet.Server.Testing.xproj
@@ -0,0 +1,20 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 3da89347-6731-4366-80c4-548f24e8607b
+
+
+
+
+
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/project.json b/src/Microsoft.AspNet.Server.Testing/project.json
new file mode 100644
index 0000000000..c4abcae964
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/project.json
@@ -0,0 +1,31 @@
+{
+ "version": "1.0.0-*",
+ "description": "ASP.NET 5 helpers to deploy applications to IIS Express, IIS, WebListener and Kestrel for testing.",
+ "dependencies": {
+ "Microsoft.AspNet.Testing": "1.0.0-*",
+ "Microsoft.Framework.Logging.Interfaces": "1.0.0-*",
+ "Microsoft.Framework.Runtime.Interfaces": "1.0.0-*"
+ },
+ "frameworks": {
+ "dnx451": {
+ "dependencies": {
+ "Microsoft.Web.Administration": "7.0.0"
+ },
+ "frameworkAssemblies": {
+ "System.Net.Http": "",
+ "System.Xml": ""
+ }
+ },
+ "dnxcore50": {
+ "dependencies": {
+ "System.Diagnostics.Process": "4.0.0-beta-*",
+ "System.IO.FileSystem": "4.0.0-*",
+ "System.Net.Http": "4.0.0-beta-*",
+ "System.Runtime.Extensions": "4.0.10-beta-*",
+ "System.Text.RegularExpressions": "4.0.10-beta-*",
+ "System.Threading": "4.0.10-beta-*",
+ "System.Threading.Thread": "4.0.0-*"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/xunit/SkipIfCurrentRuntimeIsCoreClr.cs b/src/Microsoft.AspNet.Server.Testing/xunit/SkipIfCurrentRuntimeIsCoreClr.cs
new file mode 100644
index 0000000000..7c1d5340aa
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/xunit/SkipIfCurrentRuntimeIsCoreClr.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Testing.xunit;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Skips a test if the DNX used to run the test is CoreClr.
+ ///
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ public class SkipIfCurrentRuntimeIsCoreClrAttribute : Attribute, ITestCondition
+ {
+ public bool IsMet
+ {
+ get
+ {
+ return !Process.GetCurrentProcess().ProcessName.ToLower().Contains("coreclr");
+ }
+ }
+
+ public string SkipReason
+ {
+ get
+ {
+ return "Cannot run these test variations using CoreCLR DNX as helpers are not available on CoreCLR.";
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Server.Testing/xunit/SkipIfIISNativeVariationsNotEnabledAttribute.cs b/src/Microsoft.AspNet.Server.Testing/xunit/SkipIfIISNativeVariationsNotEnabledAttribute.cs
new file mode 100644
index 0000000000..541fa684bc
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/xunit/SkipIfIISNativeVariationsNotEnabledAttribute.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.Testing.xunit;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Skip test if IIS native module is not enabled. To enable setup native module
+ /// and set environment variable IIS_NATIVE_VARIATIONS_ENABLED=true for the test process.
+ ///
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ public class SkipIfIISNativeVariationsNotEnabledAttribute : Attribute, ITestCondition
+ {
+ public bool IsMet
+ {
+ get
+ {
+ return Environment.GetEnvironmentVariable("IIS_NATIVE_VARIATIONS_ENABLED") == "true";
+ }
+ }
+
+ public string SkipReason
+ {
+ get
+ {
+ return "Skipping Native module test since native module variations are not enabled. " +
+ "To run the test, setup the native module and set the environment variable IIS_NATIVE_VARIATIONS_ENABLED=true.";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/xunit/SkipIfIISVariationsNotEnabledAttribute.cs b/src/Microsoft.AspNet.Server.Testing/xunit/SkipIfIISVariationsNotEnabledAttribute.cs
new file mode 100644
index 0000000000..dbfcbc5014
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/xunit/SkipIfIISVariationsNotEnabledAttribute.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.Testing.xunit;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Skip test if IIS variations are not enabled. To enable set environment variable
+ /// IIS_VARIATIONS_ENABLED=true for the test process.
+ ///
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ public class SkipIfIISVariationsNotEnabledAttribute : Attribute, ITestCondition
+ {
+ public bool IsMet
+ {
+ get
+ {
+ return Environment.GetEnvironmentVariable("IIS_VARIATIONS_ENABLED") == "true";
+ }
+ }
+
+ public string SkipReason
+ {
+ get
+ {
+ return "Skipping IIS variation of tests. " +
+ "To run the IIS variations, setup IIS and set the environment variable IIS_VARIATIONS_ENABLED=true.";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Server.Testing/xunit/SkipOn32BitOSAttribute.cs b/src/Microsoft.AspNet.Server.Testing/xunit/SkipOn32BitOSAttribute.cs
new file mode 100644
index 0000000000..37be266e59
--- /dev/null
+++ b/src/Microsoft.AspNet.Server.Testing/xunit/SkipOn32BitOSAttribute.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Testing.xunit;
+
+namespace Microsoft.AspNet.Server.Testing
+{
+ ///
+ /// Skips a 64 bit test if the current Windows OS is 32-bit.
+ ///
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ public class SkipOn32BitOSAttribute : Attribute, ITestCondition
+ {
+ public bool IsMet
+ {
+ get
+ {
+ // Directory found only on 64-bit OS.
+ return Directory.Exists(Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"), "SysWOW64"));
+ }
+ }
+
+ public string SkipReason
+ {
+ get
+ {
+ return "Skipping the x64 test since Windows is 32-bit";
+ }
+ }
+ }
+}
\ No newline at end of file