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