Skip to content

Commit

Permalink
Add ARM64 .NET Framework testhost (#3370)
Browse files Browse the repository at this point in the history
* Fix naming and packaging

* Revert

* Fixing warnings

* Add more comments, use string builder

* Apply suggestions from code review

Co-authored-by: Amaury Levé <amaury.leve@gmail.com>

Co-authored-by: Amaury Levé <amaury.leve@gmail.com>
  • Loading branch information
nohwnd and Evangelink authored Feb 16, 2022
1 parent f67ad49 commit 5532174
Show file tree
Hide file tree
Showing 12 changed files with 900 additions and 513 deletions.
16 changes: 16 additions & 0 deletions TestPlatform.sln
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest1", "playground\MSTes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AttachmentProcessorDataCollector", "test\TestAssets\AttachmentProcessorDataCollector\AttachmentProcessorDataCollector.csproj", "{B6AF6BCD-64C6-4F4E-ABCA-C8AA2AA66B7B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testhost.arm64", "src\testhost.arm64\testhost.arm64.csproj", "{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{10b6ade1-f808-4612-801d-4452f5b52242}*SharedItemsImports = 5
src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{186069fe-e1e8-4de1-bea4-0ff1484d22d1}*SharedItemsImports = 5
src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{27dfbd04-64b2-4f1b-82b2-006620cca6f8}*SharedItemsImports = 5
src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{2c7ce1f8-e73e-4987-8023-b5a0ebac86e8}*SharedItemsImports = 5
src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{71cb42ff-e750-4a3b-9c3a-ac938853cc89}*SharedItemsImports = 5
Expand Down Expand Up @@ -880,6 +883,18 @@ Global
{B6AF6BCD-64C6-4F4E-ABCA-C8AA2AA66B7B}.Release|x64.Build.0 = Release|Any CPU
{B6AF6BCD-64C6-4F4E-ABCA-C8AA2AA66B7B}.Release|x86.ActiveCfg = Release|Any CPU
{B6AF6BCD-64C6-4F4E-ABCA-C8AA2AA66B7B}.Release|x86.Build.0 = Release|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Debug|x64.ActiveCfg = Debug|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Debug|x64.Build.0 = Debug|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Debug|x86.ActiveCfg = Debug|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Debug|x86.Build.0 = Debug|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Release|Any CPU.Build.0 = Release|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Release|x64.ActiveCfg = Release|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Release|x64.Build.0 = Release|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Release|x86.ActiveCfg = Release|Any CPU
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -955,6 +970,7 @@ Global
{545A88D3-1AE2-4D39-9B7C-C691768AD17F} = {6CE2F530-582B-4695-A209-41065E103426}
{57A61A09-10AD-44BE-8DF4-A6FD108F7DF7} = {6CE2F530-582B-4695-A209-41065E103426}
{B6AF6BCD-64C6-4F4E-ABCA-C8AA2AA66B7B} = {D9A30E32-D466-4EC5-B4F2-62E17562279B}
{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0541B30C-FF51-4E28-B172-83F5F3934BCD}
Expand Down
28 changes: 27 additions & 1 deletion scripts/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ $TPB_Configuration = $Configuration
$TPB_TargetRuntime = $TargetRuntime
$TPB_X64_Runtime = "win7-x64"
$TPB_X86_Runtime = "win7-x86"
$TPB_ARM64_Runtime = "win10-arm64"

# Version suffix is empty for RTM release
$TPB_Version = if ($VersionSuffix -ne '') { $Version + "-" + $VersionSuffix } else { $Version }
Expand Down Expand Up @@ -206,19 +207,24 @@ function Publish-Package
$packageProject = Join-Path $env:TP_PACKAGE_PROJ_DIR "package\package.csproj"
$testHostProject = Join-Path $env:TP_ROOT_DIR "src\testhost\testhost.csproj"
$testHostx86Project = Join-Path $env:TP_ROOT_DIR "src\testhost.x86\testhost.x86.csproj"
$testHostarm64Project = Join-Path $env:TP_ROOT_DIR "src\testhost.arm64\testhost.arm64.csproj"

$testhostFullPackageDir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFramework451\$TPB_TargetRuntime")
$testhostCore20PackageDir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore20")
$testhostCore20PackageX64Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore20\$TPB_X64_Runtime")
$testhostCore20PackageX86Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore20\$TPB_X86_Runtime")
$testhostCore20PackageARM64Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore20\$TPB_ARM64_Runtime")
$testhostCore20PackageTempX64Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\publishTemp\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore20\$TPB_X64_Runtime")
$testhostCore20PackageTempX86Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\publishTemp\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore20\$TPB_X86_Runtime")
$testhostCore20PackageTempARM64Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\publishTemp\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore20\$TPB_ARM64_Runtime")

$testhostCore10PackageDir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore10")
$testhostCore10PackageX64Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore10\$TPB_X64_Runtime")
$testhostCore10PackageX86Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore10\$TPB_X86_Runtime")
$testhostCore10PackageARM64Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore10\$TPB_ARM64_Runtime")
$testhostCore10PackageTempX64Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\publishTemp\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore10\$TPB_X64_Runtime")
$testhostCore10PackageTempX86Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\publishTemp\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore10\$TPB_X86_Runtime")
$testhostCore10PackageTempARM64Dir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\publishTemp\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkCore10\$TPB_ARM64_Runtime")

$testhostUapPackageDir = $(Join-Path $env:TP_OUT_DIR "$TPB_Configuration\Microsoft.TestPlatform.TestHost\$TPB_TargetFrameworkUap100")
$vstestConsoleProject = Join-Path $env:TP_ROOT_DIR "src\vstest.console\vstest.console.csproj"
Expand Down Expand Up @@ -258,6 +264,11 @@ function Publish-Package
Publish-PackageWithRuntimeInternal $testHostx86Project $TPB_TargetFrameworkCore20 $TPB_X86_Runtime false $testhostCore20PackageTempX86Dir
Publish-PackageWithRuntimeInternal $testHostx86Project $TPB_TargetFrameworkCore10 $TPB_X86_Runtime true $testhostCore10PackageTempX86Dir

Write-Log "Package: Publish testhost.arm64\testhost.arm64.csproj"
Publish-PackageInternal $testHostarm64Project $TPB_TargetFramework451 $testhostFullPackageDir
Publish-PackageWithRuntimeInternal $testHostarm64Project $TPB_TargetFrameworkCore20 $TPB_ARM64_Runtime false $testhostCore20PackageTempARM64Dir
Publish-PackageWithRuntimeInternal $testHostarm64Project $TPB_TargetFrameworkCore10 $TPB_ARM64_Runtime true $testhostCore10PackageTempARM64Dir

# Copy the .NET multitarget testhost exes to destination folder (except for net451 which is the default)
foreach ($tfm in "net452;net46;net461;net462;net47;net471;net472;net48" -split ";") {
Copy-Item "$(Split-Path $testHostProject)\bin\$TPB_Configuration\$tfm\$TPB_X64_Runtime\testhost.$tfm.exe" $testhostFullPackageDir\testhost.$tfm.exe -Force
Expand All @@ -272,7 +283,14 @@ function Publish-Package
Copy-Item "$(Split-Path $testHostx86Project)\bin\$TPB_Configuration\$tfm\$TPB_X86_Runtime\testhost.$tfm.x86.exe.config" $testhostFullPackageDir\testhost.$tfm.x86.exe.config -Force
}

# Copy the .NET core x86 and x64 testhost exes from tempPublish to required folder
# Copy the .NET multitarget testhost.arm64 exes to destination folder (except for net451 which is the default)
foreach ($tfm in "net452;net46;net461;net462;net47;net471;net472;net48" -split ";") {
Copy-Item "$(Split-Path $testHostarm64Project)\bin\$TPB_Configuration\$tfm\$TPB_ARM64_Runtime\testhost.$tfm.arm64.exe" $testhostFullPackageDir\testhost.$tfm.arm64.exe -Force
Copy-Item "$(Split-Path $testHostarm64Project)\bin\$TPB_Configuration\$tfm\$TPB_ARM64_Runtime\testhost.$tfm.arm64.pdb" $testhostFullPackageDir\testhost.$tfm.arm64.pdb -Force
Copy-Item "$(Split-Path $testHostarm64Project)\bin\$TPB_Configuration\$tfm\$TPB_ARM64_Runtime\testhost.$tfm.arm64.exe.config" $testhostFullPackageDir\testhost.$tfm.arm64.exe.config -Force
}

# Copy the .NET core x86, x64 and arm64 testhost exes from tempPublish to required folder
New-Item -ItemType directory -Path $testhostCore20PackageX64Dir -Force | Out-Null
Copy-Item $testhostCore20PackageTempX64Dir\testhost* $testhostCore20PackageX64Dir -Force -Recurse
Copy-Item $testhostCore20PackageTempX64Dir\Microsoft.TestPlatform.PlatformAbstractions.dll $testhostCore20PackageX64Dir -Force
Expand All @@ -281,6 +299,10 @@ function Publish-Package
Copy-Item $testhostCore20PackageTempX86Dir\testhost.x86* $testhostCore20PackageX86Dir -Force -Recurse
Copy-Item $testhostCore20PackageTempX86Dir\Microsoft.TestPlatform.PlatformAbstractions.dll $testhostCore20PackageX86Dir -Force

New-Item -ItemType directory -Path $testhostCore20PackageARM64Dir -Force | Out-Null
Copy-Item $testhostCore20PackageTempARM64Dir\testhost.arm64* $testhostCore20PackageARM64Dir -Force -Recurse
Copy-Item $testhostCore20PackageTempARM64Dir\Microsoft.TestPlatform.PlatformAbstractions.dll $testhostCore20PackageARM64Dir -Force

New-Item -ItemType directory -Path $testhostCore10PackageX64Dir -Force | Out-Null
Copy-Item $testhostCore10PackageTempX64Dir\testhost* $testhostCore10PackageX64Dir -Force -Recurse
Copy-Item $testhostCore20PackageTempX64Dir\Microsoft.TestPlatform.PlatformAbstractions.dll $testhostCore10PackageX64Dir -Force
Expand All @@ -289,6 +311,10 @@ function Publish-Package
Copy-Item $testhostCore10PackageTempX86Dir\testhost.x86* $testhostCore10PackageX86Dir -Force -Recurse
Copy-Item $testhostCore10PackageTempX86Dir\Microsoft.TestPlatform.PlatformAbstractions.dll $testhostCore10PackageX86Dir -Force

New-Item -ItemType directory -Path $testhostCore10PackageARM64Dir -Force | Out-Null
Copy-Item $testhostCore10PackageTempARM64Dir\testhost.arm64* $testhostCore10PackageARM64Dir -Force -Recurse
Copy-Item $testhostCore10PackageTempARM64Dir\Microsoft.TestPlatform.PlatformAbstractions.dll $testhostCore10PackageARM64Dir -Force

# Copy over the Full CLR built testhost package assemblies to the Core CLR and Full CLR package folder.
$coreCLRFull_Dir = "TestHost"
$fullDestDir = Join-Path $coreCLR20PackageDir $coreCLRFull_Dir
Expand Down
6 changes: 3 additions & 3 deletions scripts/verify-nupkgs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ function Verify-Nuget-Packages($packageDirectory, $version)
$expectedNumOfFiles = @{
"Microsoft.CodeCoverage" = 53;
"Microsoft.NET.Test.Sdk" = 27;
"Microsoft.TestPlatform" = 590;
"Microsoft.TestPlatform" = 608;
"Microsoft.TestPlatform.Build" = 21;
"Microsoft.TestPlatform.CLI" = 405;
"Microsoft.TestPlatform.CLI" = 423;
"Microsoft.TestPlatform.Extensions.TrxLogger" = 35;
"Microsoft.TestPlatform.ObjectModel" = 238;
"Microsoft.TestPlatform.AdapterUtilities" = 62;
"Microsoft.TestPlatform.Portable" = 604;
"Microsoft.TestPlatform.Portable" = 640;
"Microsoft.TestPlatform.TestHost" = 214;
"Microsoft.TestPlatform.TranslationLayer" = 123;
"Microsoft.TestPlatform.Internal.Uwp" = 86;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting;
using Utilities;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
using System.Collections.Immutable;

/// <summary>
/// The default test host launcher for the engine.
Expand All @@ -42,13 +43,14 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting;
[FriendlyName(DefaultTestHostFriendlyName)]
public class DefaultTestHostManager : ITestRuntimeProvider2
{
private const string X64TestHostProcessName = "testhost{0}.exe";
private const string X86TestHostProcessName = "testhost{0}.x86.exe";

private const string DefaultTestHostUri = "HostProvider://DefaultTestHost";
private const string DefaultTestHostFriendlyName = "DefaultTestHost";
private const string TestAdapterEndsWithPattern = @"TestAdapter.dll";

// Any version (older or newer) that is not in this list will use the default testhost.exe that is built using net451.
// TODO: Add net481 when it is published, if it uses a new moniker.
private static readonly ImmutableArray<string> SupportedTargetFrameworks = ImmutableArray.Create("net452", "net46", "net461", "net462", "net47", "net471", "net472", "net48");

private Architecture _architecture;
private Framework _targetFramework;
private readonly IProcessHelper _processHelper;
Expand Down Expand Up @@ -133,24 +135,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
IDictionary<string, string> environmentVariables,
TestRunnerConnectionInfo connectionInfo)
{
string testHostProcessName;
if (_targetFramework.Name.StartsWith(".NETFramework,Version=v"))
{
var targetFrameworkMoniker = "net" + _targetFramework.Name.Replace(".NETFramework,Version=v", string.Empty).Replace(".", string.Empty);

// Net451 or older will use the default testhost.exe that is compiled against net451.
var isSupportedNetTarget = new[] { "net452", "net46", "net461", "net462", "net47", "net471", "net472", "net48" }.Contains(targetFrameworkMoniker);
var targetFrameworkSuffix = isSupportedNetTarget ? $".{targetFrameworkMoniker}" : string.Empty;

// Default test host manager supports shared test sources
testHostProcessName = string.Format(_architecture == Architecture.X86 ? X86TestHostProcessName : X64TestHostProcessName, targetFrameworkSuffix);
}
else
{
// This path is probably happening only in our tests, because otherwise we are first running CanExecuteCurrentRunConfiguration
// which would disqualify anything that is not netframework.
testHostProcessName = string.Format(_architecture == Architecture.X86 ? X86TestHostProcessName : X64TestHostProcessName, string.Empty);
}
string testHostProcessName = GetTestHostName(_architecture, _targetFramework, _processHelper.GetCurrentProcessArchitecture());

var currentWorkingDirectory = Path.Combine(Path.GetDirectoryName(typeof(DefaultTestHostManager).GetTypeInfo().Assembly.Location), "..//");
var argumentsString = " " + connectionInfo.ToCommandLineOptions();
Expand All @@ -160,7 +145,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(

if (!File.Exists(testhostProcessPath))
{
// "TestHost" is the name of the folder which contain Full CLR built testhost package assemblies.
// "TestHost" is the name of the folder which contain Full CLR built testhost package assemblies, in dotnet SDK.
testHostProcessName = Path.Combine("TestHost", testHostProcessName);
testhostProcessPath = Path.Combine(currentWorkingDirectory, testHostProcessName);
}
Expand Down Expand Up @@ -197,6 +182,75 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
};
}

private string GetTestHostName(Architecture architecture, Framework targetFramework, PlatformArchitecture processArchitecture)
{
// We ship multiple executables for testhost that follow this naming schema:
// testhost<.tfm><.architecture>.exe
// e.g.: testhost.net472.x86.exe -> 32-bit testhost for .NET Framework 4.7.2
//
// The tfm is omitted for .NET Framework 4.5.1 testhost.
// testhost.x86.exe -> 32-bit testhost for .NET Framework 4.5.1
//
// The architecture is omitted for 64-bit (x64) testhost.
// testhost.net472.exe -> 64-bit testhost for .NET Framework 4.7.2
// testhost.exe -> 64-bit testhost for .NET Framework 4.5.1
//
// These omissions are done for backwards compatibility because originally there were
// only testhost.exe and testhost.x86.exe, both built against .NET Framework 4.5.1.

StringBuilder testHostProcessName = new("testhost");

if (targetFramework.Name.StartsWith(".NETFramework,Version=v"))
{
// Transform target framework name into moniker.
// e.g. ".NETFramework,Version=v4.7.2" -> "net472".
var targetFrameworkMoniker = "net" + targetFramework.Name.Replace(".NETFramework,Version=v", string.Empty).Replace(".", string.Empty);

var isSupportedTargetFramework = SupportedTargetFrameworks.Contains(targetFrameworkMoniker);
if (isSupportedTargetFramework)
{
testHostProcessName.Append('.').Append(targetFrameworkMoniker);
}
else
{
// The .NET Framework 4.5.1 testhost that does not have moniker in the name is used as fallback.
}
}

var processArchitectureAsArchitecture = processArchitecture switch
{
PlatformArchitecture.X86 => Architecture.X86,
PlatformArchitecture.X64 => Architecture.X64,
PlatformArchitecture.ARM => Architecture.ARM,
PlatformArchitecture.ARM64 => Architecture.ARM64,
PlatformArchitecture.S390x => Architecture.S390x,
_ => throw new NotSupportedException(),
};

// Default architecture, or AnyCPU architecture will use the architecture of the current process,
// so when you run from 32-bit vstest.console, or from 32-bit dotnet test, you will get 32-bit testhost
// as the preferred testhost.
var actualArchitecture = architecture is Architecture.Default or Architecture.AnyCPU
? processArchitectureAsArchitecture
: architecture;

if (actualArchitecture != Architecture.X64)
{
// Append .<architecture> to the name, such as .x86. It is possible that we are not shipping the
// executable for the architecture with VS, and that will fail later with file not found exception,
// which is okay.
testHostProcessName.Append('.').Append(architecture.ToString().ToLowerInvariant());
}
else
{
// 64-bit (x64) executable, uses no architecture suffix in the name.
// E.g.: testhost.exe or testhost.net472.exe
}

testHostProcessName.Append(".exe");
return testHostProcessName.ToString();
}

/// <inheritdoc/>
public IEnumerable<string> GetTestPlatformExtensions(IEnumerable<string> sources, IEnumerable<string> extensions)
{
Expand Down
Loading

0 comments on commit 5532174

Please sign in to comment.