From 09d3b32fe570e330c1bd91b297e7cf9aad17b239 Mon Sep 17 00:00:00 2001 From: "Chris Ross (ASP.NET)" Date: Fri, 18 May 2018 16:39:14 -0700 Subject: [PATCH] Enable x86 testing #949 --- .../Common/DeploymentParameters.cs | 5 -- .../Common/DotNetCommands.cs | 71 ++++++++++++++++ .../Deployers/ApplicationDeployer.cs | 5 -- .../Deployers/IISExpressDeployer.cs | 40 ++++++--- .../Deployers/NginxDeployer.cs | 7 ++ .../Deployers/SelfHostDeployer.cs | 81 +++++++++++++------ .../TestMatrix.cs | 29 ++++++- .../TestVariant.cs | 8 +- .../xunit/SkipOn32BitOSAttribute.cs | 23 ++---- 9 files changed, 204 insertions(+), 65 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DeploymentParameters.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DeploymentParameters.cs index 538cd5ffe3..a687a542b3 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DeploymentParameters.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DeploymentParameters.cs @@ -65,11 +65,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting throw new DirectoryNotFoundException(string.Format("Application path {0} does not exist.", applicationPath)); } - if (runtimeArchitecture == RuntimeArchitecture.x86 && runtimeFlavor == RuntimeFlavor.CoreClr) - { - throw new NotSupportedException("32 bit deployment is not yet supported for CoreCLR. Don't remove the tests, just disable them for now."); - } - ApplicationPath = applicationPath; ApplicationName = new DirectoryInfo(ApplicationPath).Name; ServerType = serverType; diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs new file mode 100644 index 0000000000..38f84d5044 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Common/DotNetCommands.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common +{ + internal static class DotNetCommands + { + private const string _dotnetFolderName = ".dotnet"; + + internal static string DotNetHome { get; } = GetDotNetHome(); + + // Compare to https://github.com/aspnet/BuildTools/blob/314c98e4533217a841ff9767bb38e144eb6c93e4/tools/KoreBuild.Console/Commands/CommandContext.cs#L76 + private static string GetDotNetHome() + { + var dotnetHome = Environment.GetEnvironmentVariable("DOTNET_HOME"); + var userProfile = Environment.GetEnvironmentVariable("USERPROFILE"); + var home = Environment.GetEnvironmentVariable("HOME"); + + var result = Path.Combine(Directory.GetCurrentDirectory(), _dotnetFolderName); + if (!string.IsNullOrEmpty(dotnetHome)) + { + result = dotnetHome; + } + else if (!string.IsNullOrEmpty(userProfile)) + { + result = Path.Combine(userProfile, _dotnetFolderName); + } + else if (!string.IsNullOrEmpty(home)) + { + result = home; + } + + return result; + } + + internal static string GetDotNetInstallDir(RuntimeArchitecture arch) + { + var dotnetDir = DotNetHome; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + dotnetDir = Path.Combine(dotnetDir, arch.ToString()); + } + + return dotnetDir; + } + + internal static string GetDotNetExecutable(RuntimeArchitecture arch) + { + var dotnetDir = GetDotNetInstallDir(arch); + + var dotnetFile = "dotnet"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + dotnetFile += ".exe"; + } + + return Path.Combine(dotnetDir, dotnetFile); + } + + internal static bool IsRunningX86OnX64(RuntimeArchitecture arch) + { + return (RuntimeInformation.OSArchitecture == Architecture.X64 || RuntimeInformation.OSArchitecture == Architecture.Arm64) + && arch == RuntimeArchitecture.x86; + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployer.cs index f475c65d81..ddaac89711 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployer.cs @@ -47,11 +47,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting DeploymentParameters.RuntimeFlavor = GetRuntimeFlavor(DeploymentParameters.TargetFramework); } - if (DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86 && DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr) - { - throw new NotSupportedException("32 bit deployment is not yet supported for CoreCLR. Don't remove the tests, just disable them for now."); - } - if (string.IsNullOrEmpty(DeploymentParameters.ApplicationPath)) { throw new ArgumentException("ApplicationPath cannot be null."); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs index 8a7439809e..bdb70d4287 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/IISExpressDeployer.cs @@ -35,14 +35,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting { } - public bool Is64BitHost - { - get - { - return Environment.Is64BitOperatingSystem; - } - } - public override async Task DeployAsync() { using (Logger.BeginScope("Deployment")) @@ -128,6 +120,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting } ModifyHandlerSectionInWebConfig(key: "modules", value: DeploymentParameters.AncmVersion.ToString()); + ModifyDotNetExePathInWebConfig(); var parameters = string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigLocation) ? string.Format("/port:{0} /path:\"{1}\" /trace:error", uri.Port, contentRoot) : @@ -228,7 +221,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting { if (serverConfig.Contains(replaceFlag)) { - var ancmFile = Path.Combine(contentRoot, Is64BitHost ? $@"x64\{dllName}" : $@"x86\{dllName}"); + var arch = DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x64 ? $@"x64\{dllName}" : $@"x86\{dllName}"; + var ancmFile = Path.Combine(contentRoot, arch); if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile))) { ancmFile = Path.Combine(contentRoot, dllName); @@ -246,8 +240,14 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting private string GetIISExpressPath() { + var programFiles = "Program Files"; + if (DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) + { + programFiles = "Program Files (x86)"; + } + // Get path to program files - var iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("SystemDrive") + "\\", "Program Files", "IIS Express", "iisexpress.exe"); + var iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("SystemDrive") + "\\", programFiles, "IIS Express", "iisexpress.exe"); if (!File.Exists(iisExpressPath)) { @@ -297,8 +297,24 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting } } - // Transforms the web.config file to include the hostingModel="inprocess" element - // and adds the server type = Microsoft.AspNetServer.IIS such that Kestrel isn't added again in ServerTests + private void ModifyDotNetExePathInWebConfig() + { + // We assume the x64 dotnet.exe is on the path so we need to provide an absolute path for x86 scenarios. + // Only do it for scenarios that rely on dotnet.exe (Core, portable, etc.). + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr + && DeploymentParameters.ApplicationType == ApplicationType.Portable + && DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) + { + var executableName = DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture); + if (!File.Exists(executableName)) + { + throw new Exception($"Unable to find '{executableName}'.'"); + } + ModifyAspNetCoreSectionInWebConfig("processPath", executableName); + } + } + + // Transforms the web.config file to set attributes like hostingModel="inprocess" element private void ModifyAspNetCoreSectionInWebConfig(string key, string value) { var webConfigFile = $"{DeploymentParameters.PublishedApplicationRootPath}/web.config"; diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/NginxDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/NginxDeployer.cs index 7341624d41..655630f08a 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/NginxDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/NginxDeployer.cs @@ -35,6 +35,13 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting var redirectUri = TestUriHelper.BuildTestUri(ServerType.Nginx); + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr + && DeploymentParameters.ApplicationType == ApplicationType.Standalone) + { + // Publish is required to get the correct files in the output directory + DeploymentParameters.PublishApplicationBeforeDeployment = true; + } + if (DeploymentParameters.PublishApplicationBeforeDeployment) { DotnetPublish(); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/SelfHostDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/SelfHostDeployer.cs index e9ee52ac3d..8a9f6d6a4e 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/SelfHostDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/SelfHostDeployer.cs @@ -36,6 +36,20 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting // Start timer StartTimer(); + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr + && DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86) + { + // Publish is required to rebuild for the right bitness + DeploymentParameters.PublishApplicationBeforeDeployment = true; + } + + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr + && DeploymentParameters.ApplicationType == ApplicationType.Standalone) + { + // Publish is required to get the correct files in the output directory + DeploymentParameters.PublishApplicationBeforeDeployment = true; + } + if (DeploymentParameters.PublishApplicationBeforeDeployment) { DotnetPublish(); @@ -65,37 +79,42 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting { using (Logger.BeginScope("StartSelfHost")) { - string executableName; - string executableArgs = string.Empty; - string workingDirectory = string.Empty; + var executableName = string.Empty; + var executableArgs = string.Empty; + var workingDirectory = string.Empty; + var executableExtension = DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" + : (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""); + if (DeploymentParameters.PublishApplicationBeforeDeployment) { workingDirectory = DeploymentParameters.PublishedApplicationRootPath; - var executableExtension = - DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? ".exe" : - DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" : ""; - var executable = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, DeploymentParameters.ApplicationName + executableExtension); - - if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable) - { - executableName = "dotnet"; - executableArgs = executable; - } - else - { - executableName = executable; - } } else { - workingDirectory = DeploymentParameters.ApplicationPath; - var targetFramework = DeploymentParameters.TargetFramework ?? (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? Tfm.Net461 : Tfm.NetCoreApp22); - executableName = DotnetCommandName; - executableArgs = $"run --no-build -c {DeploymentParameters.Configuration} --framework {targetFramework} {DotnetArgumentSeparator}"; + // Core+Standalone always publishes. This must be Clr+Standalone or Core+Portable. + // Run from the pre-built bin/{config}/{tfm} directory. + var targetFramework = DeploymentParameters.TargetFramework + ?? (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? Tfm.Net461 : Tfm.NetCoreApp22); + workingDirectory = Path.Combine(DeploymentParameters.ApplicationPath, "bin", DeploymentParameters.Configuration, targetFramework); + // CurrentDirectory will point to bin/{config}/{tfm}, but the config and static files aren't copied, point to the app base instead. + DeploymentParameters.EnvironmentVariables["ASPNETCORE_CONTENTROOT"] = DeploymentParameters.ApplicationPath; } - executableArgs += $" --urls {hintUrl} " - + $" --server {(DeploymentParameters.ServerType == ServerType.HttpSys ? "Microsoft.AspNetCore.Server.HttpSys" : "Microsoft.AspNetCore.Server.Kestrel")}"; + var executable = Path.Combine(workingDirectory, DeploymentParameters.ApplicationName + executableExtension); + + if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable) + { + executableName = GetDotNetExeForArchitecture(); + executableArgs = executable; + } + else + { + executableName = executable; + } + + var server = DeploymentParameters.ServerType == ServerType.HttpSys + ? "Microsoft.AspNetCore.Server.HttpSys" : "Microsoft.AspNetCore.Server.Kestrel"; + executableArgs += $" --urls {hintUrl} --server {server}"; Logger.LogInformation($"Executing {executableName} {executableArgs}"); @@ -175,6 +194,22 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting } } + private string GetDotNetExeForArchitecture() + { + var executableName = DotnetCommandName; + // We expect x64 dotnet.exe to be on the path but we have to go searching for the x86 version. + if (DotNetCommands.IsRunningX86OnX64(DeploymentParameters.RuntimeArchitecture)) + { + executableName = DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture); + if (!File.Exists(executableName)) + { + throw new Exception($"Unable to find '{executableName}'.'"); + } + } + + return executableName; + } + public override void Dispose() { using (Logger.BeginScope("SelfHost.Dispose")) diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestMatrix.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestMatrix.cs index 857bb143d9..76785d20c7 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestMatrix.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestMatrix.cs @@ -246,9 +246,15 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting { foreach (var arch in Architectures) { + if (!IsArchitectureSupportedOnServer(arch, server)) + { + continue; + } + var archSkip = skip ?? SkipIfArchitectureNotSupportedOnCurrentSystem(arch); + if (server == ServerType.IISExpress) { - VaryByAncmVersion(variants, server, tfm, type, arch, skip); + VaryByAncmVersion(variants, server, tfm, type, arch, archSkip); } else { @@ -258,12 +264,31 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting Tfm = tfm, ApplicationType = type, Architecture = arch, - Skip = skip, + Skip = archSkip, }); } } } + private string SkipIfArchitectureNotSupportedOnCurrentSystem(RuntimeArchitecture arch) + { + if (arch == RuntimeArchitecture.x64) + { + // Can't run x64 on a x86 OS. + return (RuntimeInformation.OSArchitecture == Architecture.Arm || RuntimeInformation.OSArchitecture == Architecture.X86) + ? $"Cannot run {arch} on your current system." : null; + } + + // No x86 runtimes available on MacOS or Linux. + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? null : $"No {arch} available for non-Windows systems."; + } + + private bool IsArchitectureSupportedOnServer(RuntimeArchitecture arch, ServerType server) + { + // No x86 Mac/Linux runtime, don't generate a test variation that will always be skipped. + return !(arch == RuntimeArchitecture.x86 && ServerType.Nginx == server); + } + private void VaryByAncmVersion(IList variants, ServerType server, string tfm, ApplicationType type, RuntimeArchitecture arch, string skip) { foreach (var version in AncmVersions) diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestVariant.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestVariant.cs index e9b9cda607..76220d7fef 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestVariant.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/TestVariant.cs @@ -21,7 +21,13 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting public override string ToString() { // For debug and test explorer view - return $"Server: {Server}, TFM: {Tfm}, Type: {ApplicationType}, Host: {HostingModel}, ANCM: {AncmVersion}, Arch: {Architecture}"; + var description = $"Server: {Server}, TFM: {Tfm}, Type: {ApplicationType}, Arch: {Architecture}"; + if (Server == ServerType.IISExpress) + { + var version = AncmVersion == AncmVersion.AspNetCoreModule ? "V1" : "V2"; + description += $", ANCM: {version}, Host: {HostingModel}"; + } + return description; } public void Serialize(IXunitSerializationInfo info) diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting/xunit/SkipOn32BitOSAttribute.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting/xunit/SkipOn32BitOSAttribute.cs index 554cc63eda..066eb729c6 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting/xunit/SkipOn32BitOSAttribute.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting/xunit/SkipOn32BitOSAttribute.cs @@ -2,32 +2,21 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; +using System.Runtime.InteropServices; using Microsoft.AspNetCore.Testing.xunit; namespace Microsoft.AspNetCore.Server.IntegrationTesting { /// - /// Skips a 64 bit test if the current Windows OS is 32-bit. + /// Skips a 64 bit test if the current 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 bool IsMet => + RuntimeInformation.OSArchitecture == Architecture.Arm64 + || RuntimeInformation.OSArchitecture == Architecture.X64; - public string SkipReason - { - get - { - return "Skipping the x64 test since Windows is 32-bit"; - } - } + public string SkipReason => "Skipping the x64 test since Windows is 32-bit"; } } \ No newline at end of file