diff --git a/Arcade.sln b/Arcade.sln index 66b28cb2fa0..15d8c91ec3c 100644 --- a/Arcade.sln +++ b/Arcade.sln @@ -77,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Helix.Clie EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.SourceRewriter", "src\Microsoft.DotNet.SourceRewriter\Microsoft.DotNet.SourceRewriter.csproj", "{464D41D0-D1C4-4D3C-BD43-28A2B4260EFF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.XUnitConsoleRunner", "src\Microsoft.DotNet.XUnitConsoleRunner\src\Microsoft.DotNet.XUnitConsoleRunner.csproj", "{2D6BC91D-5D19-4611-B111-FA70C4E8E90D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -463,6 +465,18 @@ Global {464D41D0-D1C4-4D3C-BD43-28A2B4260EFF}.Release|x64.Build.0 = Release|Any CPU {464D41D0-D1C4-4D3C-BD43-28A2B4260EFF}.Release|x86.ActiveCfg = Release|Any CPU {464D41D0-D1C4-4D3C-BD43-28A2B4260EFF}.Release|x86.Build.0 = Release|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Debug|x64.ActiveCfg = Debug|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Debug|x64.Build.0 = Debug|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Debug|x86.ActiveCfg = Debug|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Debug|x86.Build.0 = Debug|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Release|Any CPU.Build.0 = Release|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Release|x64.ActiveCfg = Release|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Release|x64.Build.0 = Release|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Release|x86.ActiveCfg = Release|Any CPU + {2D6BC91D-5D19-4611-B111-FA70C4E8E90D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 859bd1a6d9c..a4979eedd4c 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -8,4 +8,24 @@ bring it to our attention. Post an issue or email us: The attached notices are provided for information only. -No notices are provided at this time. \ No newline at end of file +--------------------------------------- + +The code in src/Microsoft.DotNet.XUnitConsoleRunner/src/* was imported from: + https://github.com/xunit/xunit/tree/v2/src/xunit.console + +This set of code is covered by the following license: + + Copyright (c) .NET Foundation and Contributors + All Rights Reserved + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/eng/Versions.props b/eng/Versions.props index b76d704cabc..e5d0b517eb1 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -47,6 +47,7 @@ 4.4.0 8.5.0 2.4.1-pre.build.4059 + 2.0.3 2.4.1-pre.build.4059 2.2.0-beta.19067.6 1.0.0-beta.19060.8 diff --git a/src/Microsoft.DotNet.CoreFxTesting/build/core/Core.targets b/src/Microsoft.DotNet.CoreFxTesting/build/core/Core.targets index 85151919225..31a4d31ee2d 100644 --- a/src/Microsoft.DotNet.CoreFxTesting/build/core/Core.targets +++ b/src/Microsoft.DotNet.CoreFxTesting/build/core/Core.targets @@ -31,7 +31,7 @@ $(RunScriptHostDir)dotnet - true + true $(TestAssetsDir)xunit.runner.json testResults.xml - ValidateTargetOSTrait;$(GenerateRunScriptDependsOn);SetupILCTestProfile; + ValidateTargetOSTrait;$(GenerateRunScriptDependsOn); diff --git a/src/Microsoft.DotNet.CoreFxTesting/build/core/test/Test.targets b/src/Microsoft.DotNet.CoreFxTesting/build/core/test/Test.targets index c4b4542d26b..d15e01e81d6 100644 --- a/src/Microsoft.DotNet.CoreFxTesting/build/core/test/Test.targets +++ b/src/Microsoft.DotNet.CoreFxTesting/build/core/test/Test.targets @@ -111,42 +111,33 @@ xunit.console.dll - RemoteExecutorConsoleApp.exe - RemoteExecutorConsoleApp <_TestILCFolder>%RUNTIME_PATH%\TestILC ret <_UseSharedAssemblies Condition="'$(EnableMultiFileILCTests)' == 'true'">-useSharedAssemblies <_ILCWin32 Condition="'$(BuildingUAPAOTVertical)' != 'true'">-win32 + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/AppxManifest.xml b/src/Microsoft.DotNet.Uap.TestTools/AppxManifest.xml similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/AppxManifest.xml rename to src/Microsoft.DotNet.Uap.TestTools/AppxManifest.xml diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/LockScreenLogo.png b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/LockScreenLogo.png similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/LockScreenLogo.png rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/LockScreenLogo.png diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/SplashScreen.png b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/SplashScreen.png similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/SplashScreen.png rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/SplashScreen.png diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Square150x150Logo.png b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Square150x150Logo.png similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Square150x150Logo.png rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Square150x150Logo.png diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Square310x310Logo.png b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Square310x310Logo.png similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Square310x310Logo.png rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Square310x310Logo.png diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Square44x44Logo.png b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Square44x44Logo.png similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Square44x44Logo.png rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Square44x44Logo.png diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Square71x71Logo.png b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Square71x71Logo.png similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Square71x71Logo.png rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Square71x71Logo.png diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/StoreLogo.png b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/StoreLogo.png similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/StoreLogo.png rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/StoreLogo.png diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Wide310x150Logo.png b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Wide310x150Logo.png similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Assets/Wide310x150Logo.png rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Assets/Wide310x150Logo.png diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Microsoft.DotNet.XUnitRunnerUap.csproj b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.XUnitRunnerUap.csproj similarity index 74% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Microsoft.DotNet.XUnitRunnerUap.csproj rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.XUnitRunnerUap.csproj index f8a680dac51..6ee0c0f8fd3 100644 --- a/src/Microsoft.DotNet.XUnitRunnerUap/src/Microsoft.DotNet.XUnitRunnerUap.csproj +++ b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.XUnitRunnerUap.csproj @@ -125,12 +125,43 @@ PackageReference - - - + + CommandLine.cs + + + ConsoleRunner.cs + + + ParallelismOption.cs + + + Program.cs + + + RemoteExecutor.cs + + + Sinks\DiagnosticMessageSink.cs + + + Utility\Transform.cs + + + Utility\TransformFactory.cs + + + Utility\ConsoleRunnerLogger.cs + + + common\ConsoleHelper.cs + + + common\DictionaryExtensions.cs + + + common\Json.cs + - - @@ -138,6 +169,18 @@ + + HTML.xslt + + + JUnitXml.xslt + + + NUnitXml.xslt + + + xUnit1.xslt + @@ -149,8 +192,13 @@ + + 2.2.1 + runtime; build; native; contentfiles; analyzers + all + - 6.2.0-Preview1-26502-02 + 6.2.0-preview1-26926-04 2.4.1-pre.build.4059 diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Microsoft.DotNet.XUnitRunnerUap_TemporaryKey.pfx b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.XUnitRunnerUap_TemporaryKey.pfx similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Microsoft.DotNet.XUnitRunnerUap_TemporaryKey.pfx rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.XUnitRunnerUap_TemporaryKey.pfx diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Package.appxmanifest b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Package.appxmanifest similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Package.appxmanifest rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Package.appxmanifest diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Properties/AssemblyInfo.cs b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Properties/AssemblyInfo.cs rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Properties/Default.rd.xml b/src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Properties/Default.rd.xml similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/Properties/Default.rd.xml rename to src/Microsoft.DotNet.Uap.TestTools/Microsoft.DotNet.XUnitRunnerUap/Properties/Default.rd.xml diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/README.md b/src/Microsoft.DotNet.Uap.TestTools/README.md similarity index 79% rename from src/Microsoft.DotNet.XUnitRunnerUap/README.md rename to src/Microsoft.DotNet.Uap.TestTools/README.md index 1ba67eba023..275cdac1b89 100644 --- a/src/Microsoft.DotNet.XUnitRunnerUap/README.md +++ b/src/Microsoft.DotNet.Uap.TestTools/README.md @@ -12,7 +12,7 @@ Supported platforms: - ARM and ARM64 tools installed - Windows 10 >= v10.0.17134 (April 2018 Update) - To build and bundle the runner and launcher for deployment invoke the `buildAndUpdate.bat` script as following: `.\buildAndUpdate.bat src Launcher bin`. This will create an output directory (third parameter) which can be copied into the existing nuget package `Microsoft.DotNet.Uap.TestTools` for updates. + To build and bundle the runner and launcher for deployment invoke the `buildAndUpdate.bat` script as following: `.\buildAndUpdate.bat Microsoft.DotNet.XUnitRunnerUap WindowsStoreAppLauncher ..\Microsoft.DotNet.XUnitConsoleRunner\src bin\microsoft.dotnet.uap.testtools`. This will create an output directory (third parameter) which can be copied into the existing nuget package `Microsoft.DotNet.Uap.TestTools` for updates. ## Microsoft.DotNet.XUnitRunnerUap diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/App.h b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/App.h similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/App.h rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/App.h diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/AppxApp.cpp b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/AppxApp.cpp similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/AppxApp.cpp rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/AppxApp.cpp diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/AppxApp.h b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/AppxApp.h similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/AppxApp.h rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/AppxApp.h diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Args.h b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Args.h similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Args.h rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Args.h diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Stream.cpp b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Stream.cpp similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Stream.cpp rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Stream.cpp diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Stream.h b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Stream.h similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Stream.h rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Stream.h diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Support.cpp b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Support.cpp similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Support.cpp rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Support.cpp diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Support.h b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Support.h similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/Support.h rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/Support.h diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreApp.cpp b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreApp.cpp similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreApp.cpp rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreApp.cpp diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreApp.h b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreApp.h similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreApp.h rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreApp.h diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreApp.rc b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreApp.rc similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreApp.rc rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreApp.rc diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreAppLauncher.cpp b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreAppLauncher.cpp similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreAppLauncher.cpp rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreAppLauncher.cpp diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreAppLauncher.vcxproj b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreAppLauncher.vcxproj similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/WindowsStoreAppLauncher.vcxproj rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/WindowsStoreAppLauncher.vcxproj diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/stdafx.cpp b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/stdafx.cpp similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/stdafx.cpp rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/stdafx.cpp diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Launcher/stdafx.h b/src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/stdafx.h similarity index 100% rename from src/Microsoft.DotNet.XUnitRunnerUap/Launcher/stdafx.h rename to src/Microsoft.DotNet.Uap.TestTools/WindowsStoreAppLauncher/stdafx.h diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/buildAndUpdate.bat b/src/Microsoft.DotNet.Uap.TestTools/buildAndUpdate.bat similarity index 66% rename from src/Microsoft.DotNet.XUnitRunnerUap/buildAndUpdate.bat rename to src/Microsoft.DotNet.Uap.TestTools/buildAndUpdate.bat index 0ab44ea0296..a1945c71a06 100644 --- a/src/Microsoft.DotNet.XUnitRunnerUap/buildAndUpdate.bat +++ b/src/Microsoft.DotNet.Uap.TestTools/buildAndUpdate.bat @@ -2,7 +2,8 @@ setlocal echo off set RunnerSourceFolder=%1 set LauncherSourceFolder=%2 - set DestinationFolder=%3 + set XUnitConsoleRunnerFolder=%3 + set DestinationFolder=%4 call :LauncherMain x86 call :RunnerMain x86 @@ -16,6 +17,8 @@ setlocal call :LauncherMain arm64 call :RunnerMain arm64 + call :XUnitConsoleRunner + echo on goto :EOF @@ -26,7 +29,15 @@ setlocal msbuild /t:rebuild "%LauncherSourceFolder%\WindowsStoreAppLauncher.vcxproj" /p:Platform=%_platform% /p:Configuration=Release REM Copy the launcher executable - xcopy /y "%LauncherSourceFolder%\bin\%_platform%\Release\WindowsStoreAppLauncher.exe" "%DestinationFolder%\%_platform%\Launcher\" + xcopy /y "%LauncherSourceFolder%\bin\%_platform%\Release\WindowsStoreAppLauncher.exe" "%DestinationFolder%\Tools\%_platform%\Launcher\" + GOTO :EOF + +:XUnitConsoleRunner + REM Rebuild the runner + msbuild /t:rebuild "%XUnitConsoleRunnerFolder%\Microsoft.DotNet.XUnitConsoleRunner.csproj" /p:DefineConstants="WINDOWS_UWP" /p:OutputPath="bin\WINDOWS_UWP" + + REM Copy the runner executable + xcopy /y "%XUnitConsoleRunnerFolder%\bin\WINDOWS_UWP\*" "%DestinationFolder%\lib\netcoreapp2.0\" GOTO :EOF :RunnerMain @@ -42,12 +53,13 @@ setlocal IF "%_platform%" == "arm64" ( makeappx unpack /l /o /p "%RunnerSourceFolder%\bin\AppPackages\Microsoft.DotNet.XUnitRunnerUap_1.0.0.0_%_platform%_Debug_Test\Dependencies\%_platform%\Microsoft.VCLibs.%_platform%.14.00.appx" /d "%RunnerSourceFolder%\bin\unpack\Microsoft.VCLibs.%_platform%.Debug.14.00\%_platform%" ) ELSE ( - makeappx unpack /l /o /p "%RunnerSourceFolder%\bin\AppPackages\Microsoft.DotNet.XUnitRunnerUap_1.0.0.0_%_platform%_Debug_Test\Dependencies\%_platform%\Microsoft.NET.CoreRuntime.2.1.appx" /d "%RunnerSourceFolder%\bin\unpack\Microsoft.NET.CoreRuntime.2.1\%_platform%" + makeappx unpack /l /o /p "%RunnerSourceFolder%\bin\AppPackages\Microsoft.DotNet.XUnitRunnerUap_1.0.0.0_%_platform%_Debug_Test\Dependencies\%_platform%\Microsoft.NET.CoreRuntime.2.2.appx" /d "%RunnerSourceFolder%\bin\unpack\Microsoft.NET.CoreRuntime.2.2\%_platform%" + makeappx unpack /l /o /p "%RunnerSourceFolder%\bin\AppPackages\Microsoft.DotNet.XUnitRunnerUap_1.0.0.0_%_platform%_Debug_Test\Dependencies\%_platform%\Microsoft.NET.CoreFramework.Debug.2.2.appx" /d "%RunnerSourceFolder%\bin\unpack\Microsoft.NET.CoreFramework.Debug.2.2\%_platform%" makeappx unpack /l /o /p "%RunnerSourceFolder%\bin\AppPackages\Microsoft.DotNet.XUnitRunnerUap_1.0.0.0_%_platform%_Debug_Test\Dependencies\%_platform%\Microsoft.VCLibs.%_platform%.Debug.14.00.appx" /d "%RunnerSourceFolder%\bin\unpack\Microsoft.VCLibs.%_platform%.Debug.14.00\%_platform%" ) REM Copy the files we care about from the unpacked folder - call :RunnerCopy "%RunnerSourceFolder%\bin\unpack" "%DestinationFolder%\%_platform%\Runner" + call :RunnerCopy "%RunnerSourceFolder%\bin\unpack" "%DestinationFolder%\Tools\%_platform%\Runner" GOTO :EOF :RunnerCopy @@ -71,27 +83,24 @@ setlocal IF "%_platform%" == "arm64" ( xcopy /y %_mainSource%\clrcompression.dll %_dest%\ xcopy /y %_mainSource%\Microsoft.DotNet.XUnitRunnerUap.dll %_dest%\ - xcopy /y %_mainSource%\mrt100.dll %_dest%\ - xcopy /y %_mainSource%\mrt100_app.dll %_dest%\ - xcopy /y %_mainSource%\vcruntime140_app.dll %_dest%\ ) ELSE ( xcopy /y %_mainSource%\entrypoint\Microsoft.DotNet.XUnitRunnerUap.exe %_dest%\entrypoint\ xcopy /y %_mainSource%\Properties\Default.rd.xml %_dest%\Properties\ xcopy /y %_mainSource%\WinMetadata\Windows.winmd %_dest%\WinMetadata\ - xcopy /y %_mainSource%\System.ServiceModel.dll %_dest%\ - xcopy /y %_mainSource%\System.ServiceModel.Duplex.dll %_dest%\ - xcopy /y %_mainSource%\System.ServiceModel.Http.dll %_dest%\ - xcopy /y %_mainSource%\System.ServiceModel.NetTcp.dll %_dest%\ - xcopy /y %_mainSource%\System.ServiceModel.Primitives.dll %_dest%\ - xcopy /y %_mainSource%\System.ServiceModel.Security.dll %_dest%\ - xcopy /y %_mainSource%\System.ServiceModel.Web.dll %_dest%\ - xcopy /y %_mainSource%\System.ServiceProcess.dll %_dest%\ + xcopy /y "%_source%\Microsoft.NET.CoreFramework.Debug.2.2\%_platform%\System.ServiceModel.dll" %_dest%\ + xcopy /y "%_source%\Microsoft.NET.CoreFramework.Debug.2.2\%_platform%\System.ServiceModel.Duplex.dll" %_dest%\ + xcopy /y "%_source%\Microsoft.NET.CoreFramework.Debug.2.2\%_platform%\System.ServiceModel.Http.dll" %_dest%\ + xcopy /y "%_source%\Microsoft.NET.CoreFramework.Debug.2.2\%_platform%\System.ServiceModel.NetTcp.dll" %_dest%\ + xcopy /y "%_source%\Microsoft.NET.CoreFramework.Debug.2.2\%_platform%\System.ServiceModel.Primitives.dll" %_dest%\ + xcopy /y "%_source%\Microsoft.NET.CoreFramework.Debug.2.2\%_platform%\System.ServiceModel.Security.dll" %_dest%\ + xcopy /y "%_source%\Microsoft.NET.CoreFramework.Debug.2.2\%_platform%\System.ServiceModel.Web.dll" %_dest%\ + xcopy /y "%_source%\Microsoft.NET.CoreFramework.Debug.2.2\%_platform%\System.ServiceProcess.dll" %_dest%\ xcopy /y %_mainSource%\xunit.abstractions.dll %_dest%\ xcopy /y %_mainSource%\xunit.assert.dll %_dest%\ xcopy /y %_mainSource%\xunit.core.dll %_dest%\ xcopy /y %_mainSource%\xunit.execution.dotnet.dll %_dest%\ xcopy /y %_mainSource%\xunit.runner.utility.uwp10.dll %_dest%\ - xcopy /y "%_source%\Microsoft.NET.CoreRuntime.2.1\%_platform%\uwphost.dll" "%_dest%\" + xcopy /y "%_source%\Microsoft.NET.CoreRuntime.2.2\%_platform%\uwphost.dll" "%_dest%\" ) GOTO:EOF diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/README.md b/src/Microsoft.DotNet.XUnitConsoleRunner/README.md new file mode 100644 index 00000000000..6ba69c3167a --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/README.md @@ -0,0 +1,6 @@ +# Custom Version of the Xunit Console Runner + +## Origin/Attribution +This is a fork of the code in https://github.com/xunit/xunit for building the `Microsoft.DotNet.XUnitConsoleRunner` NuGet package. +The original authors of this code are Brad Wilson and Oren Novotny. See `../../THIRD-PARTY-NOTICES.TXT` for +the license for this code. \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/CommandLine.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/CommandLine.cs similarity index 68% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/CommandLine.cs rename to src/Microsoft.DotNet.XUnitConsoleRunner/src/CommandLine.cs index cb363b7c34c..3e17ad6991a 100644 --- a/src/Microsoft.DotNet.XUnitRunnerUap/src/CommandLine.cs +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/CommandLine.cs @@ -1,27 +1,16 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - using System; using System.Collections.Generic; using System.IO; -using Xunit; +using System.Linq; -namespace Microsoft.DotNet.XUnitRunnerUap +namespace Xunit.ConsoleClient { - internal class CommandLine + public class CommandLine { - public enum ParallelismOption - { - none, - collections, - assemblies, - all - } + readonly Stack arguments = new Stack(); + readonly List unknownOptions = new List(); - private readonly Stack arguments = new Stack(); - - private CommandLine(string[] args, Predicate fileExists = null) + protected CommandLine(string[] args, Predicate fileExists = null) { if (fileExists == null) fileExists = File.Exists; @@ -32,18 +21,28 @@ private CommandLine(string[] args, Predicate fileExists = null) Project = Parse(fileExists); } + public AppDomainSupport? AppDomains { get; protected set; } + public bool Debug { get; protected set; } public bool DiagnosticMessages { get; protected set; } + public bool InternalDiagnosticMessages { get; protected set; } + public bool FailSkips { get; protected set; } public int? MaxParallelThreads { get; set; } + public bool NoAutoReporters { get; protected set; } + public bool NoColor { get; protected set; } public bool NoLogo { get; protected set; } +#if DEBUG + public bool Pause { get; protected set; } +#endif + public XunitProject Project { get; protected set; } public bool? ParallelizeAssemblies { get; protected set; } @@ -52,13 +51,28 @@ private CommandLine(string[] args, Predicate fileExists = null) public bool Serialize { get; protected set; } - public bool Verbose { get; protected set; } + public bool StopOnFail { get; protected set; } public bool Wait { get; protected set; } - public static CommandLine Parse(params string[] args) + public IRunnerReporter ChooseReporter(IReadOnlyList reporters) { - return new CommandLine(args); + var result = default(IRunnerReporter); + + foreach (var unknownOption in unknownOptions) + { + var reporter = reporters.FirstOrDefault(r => r.RunnerSwitch == unknownOption) ?? throw new ArgumentException($"unknown option: -{unknownOption}"); + + if (result != null) + throw new ArgumentException("only one reporter is allowed"); + + result = reporter; + } + + if (!NoAutoReporters) + result = reporters.FirstOrDefault(r => r.IsEnvironmentallyEnabled) ?? result; + + return result ?? new DefaultRunnerReporterWithTypes(); } protected virtual string GetFullPath(string fileName) @@ -90,7 +104,10 @@ static bool IsConfigFile(string fileName) { return fileName.EndsWith(".config", StringComparison.OrdinalIgnoreCase) || fileName.EndsWith(".json", StringComparison.OrdinalIgnoreCase); - } + } + + public static CommandLine Parse(params string[] args) + => new CommandLine(args); protected XunitProject Parse(Predicate fileExists) { @@ -102,6 +119,10 @@ protected XunitProject Parse(Predicate fileExists) break; var assemblyFile = arguments.Pop(); + if (IsConfigFile(assemblyFile)) + throw new ArgumentException($"expecting assembly, got config file: {assemblyFile}"); + if (!fileExists(assemblyFile)) + throw new ArgumentException($"file not found: {assemblyFile}"); string configFile = null; if (arguments.Count > 0) @@ -118,9 +139,6 @@ protected XunitProject Parse(Predicate fileExists) assemblies.Add(Tuple.Create(assemblyFile, configFile)); } - if (assemblies.Count == 0) - throw new ArgumentException("must specify at least one assembly"); - var project = GetProjectFile(assemblies); while (arguments.Count > 0) @@ -143,11 +161,34 @@ protected XunitProject Parse(Predicate fileExists) GuardNoOptionValue(option); FailSkips = true; } + else if (optionName == "stoponfail") + { + GuardNoOptionValue(option); + StopOnFail = true; + } else if (optionName == "nocolor") { GuardNoOptionValue(option); NoColor = true; + TransformFactory.NoErrorColoring = NoColor; + } + else if (optionName == "noappdomain") // Here for historical reasons + { + GuardNoOptionValue(option); + AppDomains = AppDomainSupport.Denied; } + else if (optionName == "noautoreporters") + { + GuardNoOptionValue(option); + NoAutoReporters = true; + } +#if DEBUG + else if (optionName == "pause") + { + GuardNoOptionValue(option); + Pause = true; + } +#endif else if (optionName == "debug") { GuardNoOptionValue(option); @@ -158,11 +199,6 @@ protected XunitProject Parse(Predicate fileExists) GuardNoOptionValue(option); Serialize = true; } - else if (optionName == "verbose") - { - GuardNoOptionValue(option); - Verbose = true; - } else if (optionName == "wait") { GuardNoOptionValue(option); @@ -173,6 +209,34 @@ protected XunitProject Parse(Predicate fileExists) GuardNoOptionValue(option); DiagnosticMessages = true; } + else if (optionName == "internaldiagnostics") + { + GuardNoOptionValue(option); + InternalDiagnosticMessages = true; + } + else if (optionName == "appdomains") + { + if (option.Value == null) + throw new ArgumentException("missing argument for -appdomains"); + + switch (option.Value) + { + case "ifavailable": + AppDomains = AppDomainSupport.IfAvailable; + break; + + case "required": + break; + + case "denied": + AppDomains = AppDomainSupport.Denied; + break; + + default: + throw new ArgumentException("incorrect argument value for -appdomains (must be 'ifavailable', 'required', or 'denied')"); + + } + } else if (optionName == "maxthreads") { if (option.Value == null) @@ -202,8 +266,7 @@ protected XunitProject Parse(Predicate fileExists) if (option.Value == null) throw new ArgumentException("missing argument for -parallel"); - ParallelismOption parallelismOption; - if (!Enum.TryParse(option.Value, out parallelismOption)) + if (!Enum.TryParse(option.Value, out ParallelismOption parallelismOption)) throw new ArgumentException("incorrect argument value for -parallel"); switch (parallelismOption) @@ -229,6 +292,12 @@ protected XunitProject Parse(Predicate fileExists) break; } } + else if (optionName == "noshadow") + { + GuardNoOptionValue(option); + foreach (var assembly in project.Assemblies) + assembly.Configuration.ShadowCopy = false; + } else if (optionName == "trait") { if (option.Value == null) @@ -297,12 +366,24 @@ protected XunitProject Parse(Predicate fileExists) project.Filters.ExcludedNamespaces.Add(option.Value); } - else if (optionName == "xml") + else { - if (option.Value == null) - throw new ArgumentException($"missing filename for {option.Key}"); + // Might be a result output file... + if (TransformFactory.AvailableTransforms.Any(t => t.CommandLine.Equals(optionName, StringComparison.OrdinalIgnoreCase))) + { + if (option.Value == null) + throw new ArgumentException($"missing filename for {option.Key}"); + + EnsurePathExists(option.Value); - project.Output.Add(optionName, option.Value); + project.Output.Add(optionName, option.Value); + } + // ...or it might be a reporter (we won't know until later) + else + { + GuardNoOptionValue(option); + unknownOptions.Add(optionName); + } } } @@ -319,5 +400,15 @@ static KeyValuePair PopOption(Stack arguments) return new KeyValuePair(option, value); } + + static void EnsurePathExists(string path) + { + var directory = Path.GetDirectoryName(path); + + if (string.IsNullOrEmpty(directory)) + return; + + Directory.CreateDirectory(directory); + } } } diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/ConsoleRunner.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/ConsoleRunner.cs new file mode 100644 index 00000000000..c880cbc6267 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/ConsoleRunner.cs @@ -0,0 +1,452 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Xunit.ConsoleClient +{ + class ConsoleRunner + { + volatile bool cancel; + CommandLine commandLine; + readonly object consoleLock; + readonly ConcurrentDictionary completionMessages = new ConcurrentDictionary(); + bool failed; + IRunnerLogger logger; + IMessageSinkWithTypes reporterMessageHandler; + + public ConsoleRunner(object consoleLock) + { + this.consoleLock = consoleLock; + } + + public int EntryPoint(string[] args) + { +#if WINDOWS_UWP + // Handle RemoteExecutor + if (args.Length > 0 && args[0] == "remote") + { + return RemoteExecutor.Execute(args.Skip(1).ToArray()); + } +#endif + + commandLine = CommandLine.Parse(args); + + try + { + var reporters = GetAvailableRunnerReporters(); + + if (args.Length == 0 || args[0] == "-?" || args[0] == "/?" || args[0] == "-h" || args[0] == "--help") + { + PrintHeader(); + PrintUsage(reporters); + return 2; + } + + if (commandLine.Project.Assemblies.Count == 0) + throw new ArgumentException("must specify at least one assembly"); + + Console.CancelKeyPress += (sender, e) => + { + if (!cancel) + { + Console.WriteLine("Canceling... (Press Ctrl+C again to terminate)"); + cancel = true; + e.Cancel = true; + } + }; + + var defaultDirectory = Directory.GetCurrentDirectory(); + if (!defaultDirectory.EndsWith(new string(new[] { Path.DirectorySeparatorChar }), StringComparison.Ordinal)) + defaultDirectory += Path.DirectorySeparatorChar; + + var reporter = commandLine.ChooseReporter(reporters); + +#if DEBUG + if (commandLine.Pause) + { + Console.Write("Press any key to start execution..."); + Console.ReadKey(true); + Console.WriteLine(); + } +#endif + + if (commandLine.Debug) + Debugger.Launch(); + + logger = new ConsoleRunnerLogger(!commandLine.NoColor, consoleLock); + reporterMessageHandler = MessageSinkWithTypesAdapter.Wrap(reporter.CreateMessageHandler(logger)); + + if (!commandLine.NoLogo) + PrintHeader(); + + var failCount = RunProject(commandLine.Project, commandLine.Serialize, commandLine.ParallelizeAssemblies, + commandLine.ParallelizeTestCollections, commandLine.MaxParallelThreads, + commandLine.DiagnosticMessages, commandLine.NoColor, commandLine.AppDomains, + commandLine.FailSkips, commandLine.StopOnFail, commandLine.InternalDiagnosticMessages); + + if (cancel) + return -1073741510; // 0xC000013A: The application terminated as a result of a CTRL+C + + if (commandLine.Wait) + { + Console.WriteLine(); + Console.Write("Press any key to continue..."); + Console.ReadKey(); + Console.WriteLine(); + } + + return failCount > 0 ? 1 : 0; + } + catch (Exception ex) + { + if (!commandLine.NoColor) + ConsoleHelper.SetForegroundColor(ConsoleColor.Red); + + Console.WriteLine($"error: {ex.Message}"); + + if (commandLine.InternalDiagnosticMessages) + { + if (!commandLine.NoColor) + ConsoleHelper.SetForegroundColor(ConsoleColor.DarkGray); + + Console.WriteLine(ex.StackTrace); + } + + return ex is ArgumentException ? 3 : 4; + } + finally + { + if (!commandLine.NoColor) + ConsoleHelper.ResetColor(); + } + } + + List GetAvailableRunnerReporters() + { + var result = new List(); + + var runnerPath = Path.GetDirectoryName(typeof(Program).GetTypeInfo().Assembly.Location); + // Fix path on UWP .NET Native + if (string.IsNullOrEmpty(runnerPath)) + { + runnerPath = AppDomain.CurrentDomain.BaseDirectory; + } + + foreach (var dllFile in Directory.GetFiles(runnerPath, "*.dll").Select(f => Path.Combine(runnerPath, f))) + { + Type[] types = new Type[0]; + + try + { + var assembly = Assembly.Load(new AssemblyName(Path.GetFileNameWithoutExtension(dllFile))); + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + types = ex.Types; + } + catch + { + continue; + } + + foreach (var type in types) + { +#pragma warning disable CS0618 + if (type == null || type.GetTypeInfo().IsAbstract || type == typeof(DefaultRunnerReporter) || type == typeof(DefaultRunnerReporterWithTypes) || !type.GetInterfaces().Any(t => t == typeof(IRunnerReporter))) + continue; +#pragma warning restore CS0618 + var ctor = type.GetConstructor(new Type[0]); + if (ctor == null) + { + ConsoleHelper.SetForegroundColor(ConsoleColor.Yellow); + Console.WriteLine($"Type {type.FullName} in assembly {dllFile} appears to be a runner reporter, but does not have an empty constructor."); + ConsoleHelper.ResetColor(); + continue; + } + + result.Add((IRunnerReporter)ctor.Invoke(new object[0])); + } + } + + return result; + } + + void PrintHeader() + { + var platform = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; + + Console.WriteLine($"Microsoft.DotNet.XUnitConsoleRunner v2.5.0 ({IntPtr.Size * 8}-bit {platform})"); + } + + void PrintUsage(IReadOnlyList reporters) + { + var executableName = "dotnet xunit"; + + Console.WriteLine("Copyright (C) .NET Foundation."); + Console.WriteLine(); + Console.WriteLine($"usage: {executableName} [configFile] [assemblyFile [configFile]...] [options] [reporter] [resultFormat filename [...]]"); + Console.WriteLine(); + Console.WriteLine("Note: Configuration files must end in .json (XML is not supported on .NET Core)"); + Console.WriteLine(); + Console.WriteLine("Valid options:"); + Console.WriteLine(" -nologo : do not show the copyright message"); + Console.WriteLine(" -nocolor : do not output results with colors"); + Console.WriteLine(" -failskips : convert skipped tests into failures"); + Console.WriteLine(" -stoponfail : stop on first test failure"); + Console.WriteLine(" -parallel option : set parallelization based on option"); + Console.WriteLine(" : none - turn off all parallelization"); + Console.WriteLine(" : collections - only parallelize collections"); + Console.WriteLine(" : assemblies - only parallelize assemblies"); + Console.WriteLine(" : all - parallelize assemblies & collections"); + Console.WriteLine(" -maxthreads count : maximum thread count for collection parallelization"); + Console.WriteLine(" : default - run with default (1 thread per CPU thread)"); + Console.WriteLine(" : unlimited - run with unbounded thread count"); + Console.WriteLine(" : (number) - limit task thread pool size to 'count'"); + Console.WriteLine(" -wait : wait for input after completion"); + Console.WriteLine(" -diagnostics : enable diagnostics messages for all test assemblies"); + Console.WriteLine(" -internaldiagnostics : enable internal diagnostics messages for all test assemblies"); +#if DEBUG + Console.WriteLine(" -pause : pause before doing any work, to help attach a debugger"); +#endif + Console.WriteLine(" -debug : launch the debugger to debug the tests"); + Console.WriteLine(" -serialize : serialize all test cases (for diagnostic purposes only)"); + Console.WriteLine(" -trait \"name=value\" : only run tests with matching name/value traits"); + Console.WriteLine(" : if specified more than once, acts as an OR operation"); + Console.WriteLine(" -notrait \"name=value\" : do not run tests with matching name/value traits"); + Console.WriteLine(" : if specified more than once, acts as an AND operation"); + Console.WriteLine(" -method \"name\" : run a given test method (can be fully specified or use a wildcard;"); + Console.WriteLine(" : i.e., 'MyNamespace.MyClass.MyTestMethod' or '*.MyTestMethod')"); + Console.WriteLine(" : if specified more than once, acts as an OR operation"); + Console.WriteLine(" -nomethod \"name\" : do not run a given test method (can be fully specified or use a wildcard;"); + Console.WriteLine(" : i.e., 'MyNamespace.MyClass.MyTestMethod' or '*.MyTestMethod')"); + Console.WriteLine(" : if specified more than once, acts as an AND operation"); + Console.WriteLine(" -class \"name\" : run all methods in a given test class (should be fully"); + Console.WriteLine(" : specified; i.e., 'MyNamespace.MyClass')"); + Console.WriteLine(" : if specified more than once, acts as an OR operation"); + Console.WriteLine(" -noclass \"name\" : do not run any methods in a given test class (should be fully"); + Console.WriteLine(" : specified; i.e., 'MyNamespace.MyClass')"); + Console.WriteLine(" : if specified more than once, acts as an AND operation"); + Console.WriteLine(" -namespace \"name\" : run all methods in a given namespace (i.e.,"); + Console.WriteLine(" : 'MyNamespace.MySubNamespace')"); + Console.WriteLine(" : if specified more than once, acts as an OR operation"); + Console.WriteLine(" -nonamespace \"name\" : do not run any methods in a given namespace (i.e.,"); + Console.WriteLine(" : 'MyNamespace.MySubNamespace')"); + Console.WriteLine(" : if specified more than once, acts as an AND operation"); + Console.WriteLine(" -noautoreporters : do not allow reporters to be auto-enabled by environment"); + Console.WriteLine(" : (for example, auto-detecting TeamCity or AppVeyor)"); + Console.WriteLine(" -framework \"name\" : set the target framework"); + Console.WriteLine(); + + var switchableReporters = reporters.Where(r => !string.IsNullOrWhiteSpace(r.RunnerSwitch)).ToList(); + if (switchableReporters.Count > 0) + { + Console.WriteLine("Reporters: (optional, choose only one)"); + + foreach (var reporter in switchableReporters.OrderBy(r => r.RunnerSwitch)) + Console.WriteLine($" -{reporter.RunnerSwitch.ToLowerInvariant().PadRight(21)} : {reporter.Description}"); + + Console.WriteLine(); + } + + Console.WriteLine("Result formats: (optional, choose one or more)"); + TransformFactory.AvailableTransforms.ForEach( + transform => Console.WriteLine($" -{$"{transform.CommandLine} ".PadRight(21).Substring(0, 21)} : {transform.Description}") + ); + } + + int RunProject(XunitProject project, + bool serialize, + bool? parallelizeAssemblies, + bool? parallelizeTestCollections, + int? maxThreadCount, + bool diagnosticMessages, + bool noColor, + AppDomainSupport? appDomains, + bool failSkips, + bool stopOnFail, + bool internalDiagnosticMessages) + { + XElement assembliesElement = null; + var clockTime = Stopwatch.StartNew(); + var xmlTransformers = TransformFactory.GetXmlTransformers(project); + var needsXml = xmlTransformers.Count > 0; + + if (!parallelizeAssemblies.HasValue) + parallelizeAssemblies = project.All(assembly => assembly.Configuration.ParallelizeAssemblyOrDefault); + + if (needsXml) + assembliesElement = new XElement("assemblies"); + + var originalWorkingFolder = Directory.GetCurrentDirectory(); + + if (parallelizeAssemblies.GetValueOrDefault()) + { + var tasks = project.Assemblies.Select(assembly => Task.Run(() => ExecuteAssembly(consoleLock, assembly, serialize, needsXml, parallelizeTestCollections, maxThreadCount, diagnosticMessages, noColor, appDomains, failSkips, stopOnFail, project.Filters, internalDiagnosticMessages))); + var results = Task.WhenAll(tasks).GetAwaiter().GetResult(); + foreach (var assemblyElement in results.Where(result => result != null)) + assembliesElement.Add(assemblyElement); + } + else + { + foreach (var assembly in project.Assemblies) + { + var assemblyElement = ExecuteAssembly(consoleLock, assembly, serialize, needsXml, parallelizeTestCollections, maxThreadCount, diagnosticMessages, noColor, appDomains, failSkips, stopOnFail, project.Filters, internalDiagnosticMessages); + if (assemblyElement != null) + assembliesElement.Add(assemblyElement); + } + } + + clockTime.Stop(); + + if (assembliesElement != null) + assembliesElement.Add(new XAttribute("timestamp", DateTime.Now.ToString(CultureInfo.InvariantCulture))); + + if (completionMessages.Count > 0) + reporterMessageHandler.OnMessage(new TestExecutionSummary(clockTime.Elapsed, completionMessages.OrderBy(kvp => kvp.Key).ToList())); + + Directory.SetCurrentDirectory(originalWorkingFolder); + + xmlTransformers.ForEach(transformer => transformer(assembliesElement)); + + return failed ? 1 : completionMessages.Values.Sum(summary => summary.Failed); + } + + XElement ExecuteAssembly(object consoleLock, + XunitProjectAssembly assembly, + bool serialize, + bool needsXml, + bool? parallelizeTestCollections, + int? maxThreadCount, + bool diagnosticMessages, + bool noColor, + AppDomainSupport? appDomains, + bool failSkips, + bool stopOnFail, + XunitFilters filters, + bool internalDiagnosticMessages) + { + if (cancel) + return null; + + var assemblyElement = needsXml ? new XElement("assembly") : null; + + try + { + if (!ValidateFileExists(consoleLock, assembly.AssemblyFilename) || !ValidateFileExists(consoleLock, assembly.ConfigFilename)) + return null; + + // Turn off pre-enumeration of theories, since there is no theory selection UI in this runner + assembly.Configuration.PreEnumerateTheories = false; + assembly.Configuration.DiagnosticMessages |= diagnosticMessages; + assembly.Configuration.InternalDiagnosticMessages |= internalDiagnosticMessages; + + if (appDomains.HasValue) + assembly.Configuration.AppDomain = appDomains; + + // Setup discovery and execution options with command-line overrides + var discoveryOptions = TestFrameworkOptions.ForDiscovery(assembly.Configuration); + var executionOptions = TestFrameworkOptions.ForExecution(assembly.Configuration); + executionOptions.SetStopOnTestFail(stopOnFail); + if (maxThreadCount.HasValue) + executionOptions.SetMaxParallelThreads(maxThreadCount); + if (parallelizeTestCollections.HasValue) + executionOptions.SetDisableParallelization(!parallelizeTestCollections.GetValueOrDefault()); + + var assemblyDisplayName = Path.GetFileNameWithoutExtension(assembly.AssemblyFilename); + var diagnosticMessageSink = DiagnosticMessageSink.ForDiagnostics(consoleLock, assemblyDisplayName, assembly.Configuration.DiagnosticMessagesOrDefault, noColor); + var internalDiagnosticsMessageSink = DiagnosticMessageSink.ForInternalDiagnostics(consoleLock, assemblyDisplayName, assembly.Configuration.InternalDiagnosticMessagesOrDefault, noColor); + var appDomainSupport = assembly.Configuration.AppDomainOrDefault; + var shadowCopy = assembly.Configuration.ShadowCopyOrDefault; + var longRunningSeconds = assembly.Configuration.LongRunningTestSecondsOrDefault; + +#if !WINDOWS_UWP + using (AssemblyHelper.SubscribeResolveForAssembly(assembly.AssemblyFilename, internalDiagnosticsMessageSink)) +#endif + using (var controller = new XunitFrontController(appDomainSupport, assembly.AssemblyFilename, assembly.ConfigFilename, shadowCopy, diagnosticMessageSink: diagnosticMessageSink)) + using (var discoverySink = new TestDiscoverySink(() => cancel)) + { + // Discover & filter the tests + reporterMessageHandler.OnMessage(new TestAssemblyDiscoveryStarting(assembly, controller.CanUseAppDomains && appDomainSupport != AppDomainSupport.Denied, shadowCopy, discoveryOptions)); + + controller.Find(false, discoverySink, discoveryOptions); + discoverySink.Finished.WaitOne(); + + var testCasesDiscovered = discoverySink.TestCases.Count; + var filteredTestCases = discoverySink.TestCases.Where(filters.Filter).ToList(); + var testCasesToRun = filteredTestCases.Count; + + reporterMessageHandler.OnMessage(new TestAssemblyDiscoveryFinished(assembly, discoveryOptions, testCasesDiscovered, testCasesToRun)); + + // Run the filtered tests + if (testCasesToRun == 0) + completionMessages.TryAdd(Path.GetFileName(assembly.AssemblyFilename), new ExecutionSummary()); + else + { + if (serialize) + filteredTestCases = filteredTestCases.Select(controller.Serialize).Select(controller.Deserialize).ToList(); + + reporterMessageHandler.OnMessage(new TestAssemblyExecutionStarting(assembly, executionOptions)); + + IExecutionSink resultsSink = new DelegatingExecutionSummarySink(reporterMessageHandler, () => cancel, (path, summary) => completionMessages.TryAdd(path, summary)); + if (assemblyElement != null) + resultsSink = new DelegatingXmlCreationSink(resultsSink, assemblyElement); + if (longRunningSeconds > 0) + resultsSink = new DelegatingLongRunningTestDetectionSink(resultsSink, TimeSpan.FromSeconds(longRunningSeconds), MessageSinkWithTypesAdapter.Wrap(diagnosticMessageSink)); + if (failSkips) + resultsSink = new DelegatingFailSkipSink(resultsSink); + + controller.RunTests(filteredTestCases, resultsSink, executionOptions); + resultsSink.Finished.WaitOne(); + + reporterMessageHandler.OnMessage(new TestAssemblyExecutionFinished(assembly, executionOptions, resultsSink.ExecutionSummary)); + if (stopOnFail && resultsSink.ExecutionSummary.Failed != 0) + { + Console.WriteLine("Canceling due to test failure..."); + cancel = true; + } + } + } + } + catch (Exception ex) + { + failed = true; + + var e = ex; + while (e != null) + { + Console.WriteLine($"{e.GetType().FullName}: {e.Message}"); + + if (internalDiagnosticMessages) + Console.WriteLine(e.StackTrace); + + e = e.InnerException; + } + } + + return assemblyElement; + } + + bool ValidateFileExists(object consoleLock, string fileName) + { + if (string.IsNullOrWhiteSpace(fileName) || File.Exists(fileName)) + return true; + + lock (consoleLock) + { + ConsoleHelper.SetForegroundColor(ConsoleColor.Red); + Console.WriteLine($"File not found: {fileName}"); + ConsoleHelper.SetForegroundColor(ConsoleColor.Gray); + } + + return false; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/HTML.xslt b/src/Microsoft.DotNet.XUnitConsoleRunner/src/HTML.xslt new file mode 100644 index 00000000000..f73e26e6de2 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/HTML.xslt @@ -0,0 +1,240 @@ + + + + + + ]]> + + + xUnit.net Test Results + + + + +

+ Assemblies Run +

+ +

+ Summary +

+
+ Tests run: — + + Errors: , + + + Failures: , + + + Skipped: , + + Run time: s, + Finished: +
+ +
+

+ Errors +

+ +
+ +
+

+ Failed tests +

+ + + +
+ +
+

+ Collection failures +

+ + + +
+ +
+

+ Skipped tests +

+ + + +
+
+

+ All tests +

+
Click test class name to expand/collapse test details
+ + + +

+ + s + + + ToggleClass('class') + ToggleClass('class') + + + + + + +   +   + ( tests) + + +
+

+
+ + display: none; + + class + + + +
+
+ + +
+ + +
+ +
+
+ + +
+ altrow + + + s + + + + Skipped + + + + + + + + +   +
+ +
+
+ +
+
+ +
Output:
+
+
+ +
Traits:
+ + +
+
+
+
+ + + + + + + + + +

+ +
+ altrow +
+ +
+
+ +
+
+
+
+
+ + + +
+ + altrow + + + Test Assembly Cleanup + Test Collection Cleanup + Test Class Cleanup + Test Method Cleanup + Test Case Cleanup + Test Cleanup + Fatal Error + + () + +
+ +
+
+ +
+
+
+
+
+ +
\ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/JUnitXml.xslt b/src/Microsoft.DotNet.XUnitConsoleRunner/src/JUnitXml.xslt new file mode 100644 index 00000000000..ad02d9da4cd --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/JUnitXml.xslt @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj new file mode 100644 index 00000000000..2f4446cfbee --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj @@ -0,0 +1,30 @@ + + + + Microsoft.DotNet.XUnitConsoleRunner + xunit.console + true + Exe + Xunit.ConsoleClient + netcoreapp2.0 + true + 2.5.0 + latest + $(DefaultItemExcludes);common\AssemblyResolution\*.cs + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/NUnitXml.xslt b/src/Microsoft.DotNet.XUnitConsoleRunner/src/NUnitXml.xslt new file mode 100644 index 00000000000..76029f47b61 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/NUnitXml.xslt @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + True + + + Failure + Success + + + + + + + + + + + + + + + + + + Failure + Success + + + False + True + + + + + + + + + + + + + + + + + Failure + Success + + + False + True + + + + + + + + + + + + + + + + + + + + + + + + + False + True + + + Failure + Success + Skipped + + + + False + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/ParallelismOption.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/ParallelismOption.cs new file mode 100644 index 00000000000..9d4147ef05f --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/ParallelismOption.cs @@ -0,0 +1,10 @@ +namespace Xunit.ConsoleClient +{ + public enum ParallelismOption + { + none, + collections, + assemblies, + all + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Program.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Program.cs new file mode 100644 index 00000000000..47a1b41819b --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Linq; + +namespace Xunit.ConsoleClient +{ + public class Program + { + [STAThread] + public static int Main(string[] args) + { + // This code must not contain any references to code in any external assembly (or any code that references any code in any + // other assembly) until AFTER the creation of the AssemblyHelper. + var consoleLock = new object(); + var internalDiagnosticsMessageSink = DiagnosticMessageSink.ForInternalDiagnostics(consoleLock, args.Contains("-internaldiagnostics"), args.Contains("-nocolor")); + +#if !WINDOWS_UWP + using (AssemblyHelper.SubscribeResolveForAssembly(typeof(Program), internalDiagnosticsMessageSink)) +#endif + return new ConsoleRunner(consoleLock).EntryPoint(args); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/RemoteExecutor.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/RemoteExecutor.cs similarity index 90% rename from src/Microsoft.DotNet.XUnitRunnerUap/src/RemoteExecutor.cs rename to src/Microsoft.DotNet.XUnitConsoleRunner/src/RemoteExecutor.cs index 8ce6c0a1158..e47d337c157 100644 --- a/src/Microsoft.DotNet.XUnitRunnerUap/src/RemoteExecutor.cs +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/RemoteExecutor.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if WINDOWS_UWP + using System; using System.IO; using System.Reflection; @@ -10,7 +12,7 @@ using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; -namespace Microsoft.DotNet.XUnitRunnerUap +namespace Xunit.ConsoleClient { /// /// Provides an entry point in a new process that will load a specified method and invoke it. @@ -113,22 +115,6 @@ public static int Execute(string[] args) return exitCode; } - private static MethodInfo GetMethod(this Type type, string methodName) - { - Type t = type; - while (t != null) - { - TypeInfo ti = t.GetTypeInfo(); - MethodInfo mi = ti.GetDeclaredMethod(methodName); - if (mi != null) - { - return mi; - } - t = ti.BaseType; - } - return null; - } - private static T[] Subarray(this T[] arr, int offset, int count) { var newArr = new T[count]; @@ -137,3 +123,5 @@ private static T[] Subarray(this T[] arr, int offset, int count) } } } + +#endif diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Sinks/DiagnosticMessageSink.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Sinks/DiagnosticMessageSink.cs new file mode 100644 index 00000000000..c7432260237 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Sinks/DiagnosticMessageSink.cs @@ -0,0 +1,54 @@ +using System; +using Xunit.Abstractions; + +namespace Xunit.ConsoleClient +{ + public class DiagnosticMessageSink : MarshalByRefObject, IMessageSink + { + readonly string assemblyDisplayName; + readonly object consoleLock; + readonly ConsoleColor displayColor; + readonly bool noColor; + readonly bool showDiagnostics; + + DiagnosticMessageSink(object consoleLock, string assemblyDisplayName, bool showDiagnostics, bool noColor, ConsoleColor displayColor) + { + this.consoleLock = consoleLock; + this.assemblyDisplayName = assemblyDisplayName; + this.noColor = noColor; + this.displayColor = displayColor; + this.showDiagnostics = showDiagnostics; + } + + public static DiagnosticMessageSink ForDiagnostics(object consoleLock, string assemblyDisplayName, bool showDiagnostics, bool noColor) + => new DiagnosticMessageSink(consoleLock, assemblyDisplayName, showDiagnostics, noColor, ConsoleColor.Yellow); + + public static DiagnosticMessageSink ForInternalDiagnostics(object consoleLock, bool showDiagnostics, bool noColor) + => new DiagnosticMessageSink(consoleLock, null, showDiagnostics, noColor, ConsoleColor.DarkGray); + + public static DiagnosticMessageSink ForInternalDiagnostics(object consoleLock, string assemblyDisplayName, bool showDiagnostics, bool noColor) + => new DiagnosticMessageSink(consoleLock, assemblyDisplayName, showDiagnostics, noColor, ConsoleColor.DarkGray); + + public bool OnMessage(IMessageSinkMessage message) + { + if (showDiagnostics && message is IDiagnosticMessage diagnosticMessage) + { + lock (consoleLock) + { + if (!noColor) + ConsoleHelper.SetForegroundColor(displayColor); + + if (assemblyDisplayName != null) + Console.WriteLine($" {assemblyDisplayName}: {diagnosticMessage.Message}"); + else + Console.WriteLine($" {diagnosticMessage.Message}"); + + if (!noColor) + ConsoleHelper.ResetColor(); + } + } + + return true; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/ConsoleRunnerLogger.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/ConsoleRunnerLogger.cs new file mode 100644 index 00000000000..935269ef8a5 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/ConsoleRunnerLogger.cs @@ -0,0 +1,84 @@ +#if WINDOWS_UWP + +using System; + +namespace Xunit.ConsoleClient +{ + /// + /// An implementation of which logs messages + /// to and . + /// + public class ConsoleRunnerLogger : IRunnerLogger + { + readonly object lockObject; + readonly bool useColors; + + /// + /// Initializes a new instance of the class. + /// + /// A flag to indicate whether colors should be used when + /// logging messages. + public ConsoleRunnerLogger(bool useColors) : this(useColors, new object()) { } + + /// + /// Initializes a new instance of the class. + /// + /// A flag to indicate whether colors should be used when + /// logging messages. + /// The lock object used to prevent console clashes. + public ConsoleRunnerLogger(bool useColors, object lockObject) + { + this.useColors = useColors; + this.lockObject = lockObject; + } + + /// + public object LockObject => lockObject; + + /// + public void LogError(StackFrameInfo stackFrame, string message) + { + using (SetColor(ConsoleColor.Red)) + lock (LockObject) + Console.Error.WriteLine(message); + } + + /// + public void LogImportantMessage(StackFrameInfo stackFrame, string message) + { + using (SetColor(ConsoleColor.Gray)) + lock (LockObject) + Console.WriteLine(message); + } + + /// + public void LogMessage(StackFrameInfo stackFrame, string message) + { + using (SetColor(ConsoleColor.DarkGray)) + lock (LockObject) + Console.WriteLine(message); + } + + /// + public void LogWarning(StackFrameInfo stackFrame, string message) + { + using (SetColor(ConsoleColor.Yellow)) + lock (LockObject) + Console.WriteLine(message); + } + + IDisposable SetColor(ConsoleColor color) + => useColors ? new ColorRestorer(color) : null; + + class ColorRestorer : IDisposable + { + public ColorRestorer(ConsoleColor color) + => ConsoleHelper.SetForegroundColor(color); + + public void Dispose() + => ConsoleHelper.ResetColor(); + } + } +} + +#endif diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/Transform.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/Transform.cs new file mode 100644 index 00000000000..5b4541a0f8f --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/Transform.cs @@ -0,0 +1,12 @@ +using System; +using System.Xml.Linq; + +namespace Xunit.ConsoleClient +{ + public class Transform + { + public string CommandLine; + public string Description; + public Action OutputHandler; + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/TransformFactory.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/TransformFactory.cs new file mode 100644 index 00000000000..7a54089729f --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/TransformFactory.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; + +namespace Xunit.ConsoleClient +{ + public class TransformFactory + { + static readonly TransformFactory instance = new TransformFactory(); + + readonly Dictionary availableTransforms = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public static bool NoErrorColoring = false; + + protected TransformFactory() + { + availableTransforms.Add("xml", new Transform + { + CommandLine = "xml", + Description = "output results to xUnit.net v2 XML file", + OutputHandler = Handler_DirectWrite + }); + availableTransforms.Add("xmlv1", new Transform + { + CommandLine = "xmlv1", + Description = "output results to xUnit.net v1 XML file", + OutputHandler = (xml, outputFileName) => Handler_XslTransform("xmlv1", "xUnit1.xslt", xml, outputFileName) + }); + availableTransforms.Add("html", new Transform + { + CommandLine = "html", + Description = "output results to HTML file", + OutputHandler = (xml, outputFileName) => Handler_XslTransform("html", "HTML.xslt", xml, outputFileName) + }); + availableTransforms.Add("nunit", new Transform + { + CommandLine = "nunit", + Description = "output results to NUnit v2.5 XML file", + OutputHandler = (xml, outputFileName) => Handler_XslTransform("nunit", "NUnitXml.xslt", xml, outputFileName) + }); + availableTransforms.Add("junit", new Transform + { + CommandLine = "junit", + Description = "output results to JUnit XML file", + OutputHandler = (xml, outputFileName) => Handler_XslTransform("junit", "JUnitXml.xslt", xml, outputFileName) + }); + } + + public static List AvailableTransforms + => instance.availableTransforms.Values.ToList(); + + public static List> GetXmlTransformers(XunitProject project) + => project.Output + .Select(output => new Action(xml => instance.availableTransforms[output.Key].OutputHandler(xml, output.Value))) + .ToList(); + + static void Handler_DirectWrite(XElement xml, string outputFileName) + { + using (var stream = File.Create(outputFileName)) + xml.Save(stream); + } + + static void Handler_XslTransform(string key, string resourceName, XElement xml, string outputFileName) + { + var xmlTransform = new System.Xml.Xsl.XslCompiledTransform(); + + using (var writer = XmlWriter.Create(outputFileName, new XmlWriterSettings { Indent = true })) + using (var xsltStream = typeof(TransformFactory).GetTypeInfo().Assembly.GetManifestResourceStream($"Xunit.ConsoleClient.{resourceName}")) + using (var xsltReader = XmlReader.Create(xsltStream)) + using (var xmlReader = xml.CreateReader()) + { + xmlTransform.Load(xsltReader); + xmlTransform.Transform(xmlReader, writer); + } + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/build/xunit.runner.console.props b/src/Microsoft.DotNet.XUnitConsoleRunner/src/build/xunit.runner.console.props new file mode 100644 index 00000000000..868af20c2e4 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/build/xunit.runner.console.props @@ -0,0 +1,7 @@ + + + + $(MSBuildThisFileDirectory)..\tools\netcoreapp2.0\xunit.console.dll + + + diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/AssemblyHelper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/AssemblyHelper.cs new file mode 100644 index 00000000000..1c7893b3cbf --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/AssemblyHelper.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using Internal.Microsoft.Extensions.DependencyModel; +using Xunit.Abstractions; + +namespace Xunit +{ + /// + /// This class provides assistance with assembly resolution for missing assemblies. + /// + class AssemblyHelper : AssemblyLoadContext, IDisposable + { + readonly DependencyContextAssemblyCache assemblyCache; + readonly IMessageSink internalDiagnosticsMessageSink; + + /// + /// Initializes a new instance of the class. + /// + /// The path to the assembly + /// An optional message sink for use with internal diagnostics messages; + /// may pass null for no internal diagnostics messages + public AssemblyHelper(string assemblyFileName, IMessageSink internalDiagnosticsMessageSink) + { + this.internalDiagnosticsMessageSink = internalDiagnosticsMessageSink; + + if (!File.Exists(assemblyFileName)) + { + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_NetCoreApp..ctor] Assembly file not found: '{assemblyFileName}'")); + return; + } + + var assembly = LoadFromAssemblyPath(assemblyFileName); + if (assembly == null) + { + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_NetCoreApp..ctor] Assembly file could not be loaded: '{assemblyFileName}'")); + return; + } + + var dependencyContext = DependencyContext.Load(assembly); + if (dependencyContext == null) + { + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_NetCoreApp..ctor] Assembly file does not contain dependency manifest: '{assemblyFileName}'")); + return; + } + + var assemblyFolder = Path.GetDirectoryName(assemblyFileName); + assemblyCache = new DependencyContextAssemblyCache(assemblyFolder, dependencyContext, internalDiagnosticsMessageSink); + + Default.Resolving += OnResolving; + } + + /// + public void Dispose() + { + if (assemblyCache != null) + Default.Resolving -= OnResolving; + } + + /// + protected override Assembly Load(AssemblyName assemblyName) + => Default.LoadFromAssemblyName(assemblyName); + + /// + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + var result = default(IntPtr); + + if (assemblyCache != null) + result = assemblyCache.LoadUnmanagedLibrary(unmanagedDllName, path => LoadUnmanagedDllFromPath(path)); + + if (result == default) + result = base.LoadUnmanagedDll(unmanagedDllName); + + return result; + } + + Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) + => assemblyCache?.LoadManagedDll(name.Name, path => LoadFromAssemblyPath(path)); + + /// + /// Subscribes to the appropriate assembly resolution event, to provide automatic assembly resolution for + /// an assembly and any of its dependencies. Depending on the target platform, this may include the use + /// of the .deps.json file generated during the build process. + /// + /// An object which, when disposed, un-subscribes. + public static IDisposable SubscribeResolveForAssembly(string assemblyFileName, IMessageSink internalDiagnosticsMessageSink = null) + => new AssemblyHelper(assemblyFileName, internalDiagnosticsMessageSink); + + /// + /// Subscribes to the appropriate assembly resolution event, to provide automatic assembly resolution for + /// an assembly and any of its dependencies. Depending on the target platform, this may include the use + /// of the .deps.json file generated during the build process. + /// + /// An object which, when disposed, un-subscribes. + public static IDisposable SubscribeResolveForAssembly(Type typeInAssembly, IMessageSink internalDiagnosticsMessageSink = null) + => new AssemblyHelper(typeInAssembly.GetTypeInfo().Assembly.Location, internalDiagnosticsMessageSink); + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/DependencyContextAssemblyCache.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/DependencyContextAssemblyCache.cs new file mode 100644 index 00000000000..bc6d6068c2d --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/DependencyContextAssemblyCache.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Internal.Microsoft.DotNet.PlatformAbstractions; +using Internal.Microsoft.Extensions.DependencyModel; +using Xunit.Abstractions; +using RuntimeEnvironment = Internal.Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment; + +namespace Xunit +{ + class DependencyContextAssemblyCache + { + static readonly RuntimeFallbacks AnyAndBase = new RuntimeFallbacks("unknown", "any", "base"); + static readonly string[] ManagedAssemblyExtensions = { ".dll", ".exe" }; + static readonly Tuple ManagedAssemblyNotFound = new Tuple(null, null); + static readonly Regex RuntimeIdRegex = new Regex(@"(?[A-Za-z0-9]+)(\.(?[0-9\.]+))?(?\-[A-Za-z0-9]+)?(?\-[A-Za-z0-9]+)?"); + + readonly string assemblyFolder; + readonly XunitPackageCompilationAssemblyResolver assemblyResolver; + readonly string currentRuntimeIdentifier; + readonly DependencyContext dependencyContext; + readonly Lazy fallbackRuntimeIdentifier; + readonly IFileSystem fileSystem; + readonly IMessageSink internalDiagnosticsMessageSink; + readonly Dictionary managedAssemblyCache; + readonly Dictionary> managedAssemblyMap; + readonly Platform operatingSystemPlatform; + readonly string[] unmanagedDllFormats; + readonly Dictionary unmanagedAssemblyCache; + readonly Dictionary> unmanagedAssemblyMap; + + public DependencyContextAssemblyCache(string assemblyFolder, + DependencyContext dependencyContext, + IMessageSink internalDiagnosticsMessageSink, + Platform? operatingSystemPlatform = null, + string currentRuntimeIdentifier = null, + IFileSystem fileSystem = null) + { + this.assemblyFolder = assemblyFolder; + this.dependencyContext = dependencyContext; + this.internalDiagnosticsMessageSink = internalDiagnosticsMessageSink; + this.operatingSystemPlatform = operatingSystemPlatform ?? RuntimeEnvironment.OperatingSystemPlatform; + this.currentRuntimeIdentifier = currentRuntimeIdentifier ?? RuntimeEnvironment.GetRuntimeIdentifier(); + this.fileSystem = fileSystem ?? new FileSystemWrapper(); + + fallbackRuntimeIdentifier = new Lazy(() => GetFallbackRuntime(this.currentRuntimeIdentifier)); + assemblyResolver = new XunitPackageCompilationAssemblyResolver(internalDiagnosticsMessageSink, fileSystem); + + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache..ctor] Runtime graph: [{string.Join(",", dependencyContext.RuntimeGraph.Select(x => $"'{x.Runtime}'"))}]")); + + var compatibleRuntimes = GetCompatibleRuntimes(dependencyContext); + + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache..ctor] Compatible runtimes: [{string.Join(",", compatibleRuntimes.Select(x => $"'{x}'"))}]")); + + managedAssemblyCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + managedAssemblyMap = + dependencyContext.RuntimeLibraries + .Where(lib => lib.RuntimeAssemblyGroups?.Count > 0) + .Select(lib => compatibleRuntimes.Select(runtime => Tuple.Create(lib, lib.RuntimeAssemblyGroups.FirstOrDefault(libGroup => string.Equals(libGroup.Runtime, runtime)))) + .FirstOrDefault(tuple => tuple.Item2?.AssetPaths != null)) + .Where(tuple => tuple != null) + .SelectMany(tuple => tuple.Item2.AssetPaths.Where(x => x != null) + .Select(path => Tuple.Create(Path.GetFileNameWithoutExtension(path), Tuple.Create(tuple.Item1, tuple.Item2)))) + .ToDictionaryIgnoringDuplicateKeys(tuple => tuple.Item1, tuple => tuple.Item2, StringComparer.OrdinalIgnoreCase); + + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache..ctor] Managed assembly map: [{string.Join(",", managedAssemblyMap.Keys.Select(k => $"'{k}'").OrderBy(k => k, StringComparer.OrdinalIgnoreCase))}]")); + + unmanagedDllFormats = GetUnmanagedDllFormats().ToArray(); + unmanagedAssemblyCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + unmanagedAssemblyMap = + dependencyContext.RuntimeLibraries + .Select(lib => compatibleRuntimes.Select(runtime => Tuple.Create(lib, lib.NativeLibraryGroups.FirstOrDefault(libGroup => string.Equals(libGroup.Runtime, runtime)))) + .FirstOrDefault(tuple => tuple.Item2?.AssetPaths != null)) + .Where(tuple => tuple != null) + .SelectMany(tuple => tuple.Item2.AssetPaths.Where(x => x != null) + .Select(path => Tuple.Create(Path.GetFileName(path), Tuple.Create(tuple.Item1, tuple.Item2)))) + .ToDictionaryIgnoringDuplicateKeys(tuple => tuple.Item1, tuple => tuple.Item2, StringComparer.OrdinalIgnoreCase); + + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache..ctor] Unmanaged assembly map: [{string.Join(",", unmanagedAssemblyMap.Keys.Select(k => $"'{k}'").OrderBy(k => k, StringComparer.OrdinalIgnoreCase))}]")); + } + + List GetCompatibleRuntimes(DependencyContext dependencyContext) + { + var result = new List(GetFallbacks(dependencyContext.RuntimeGraph).Fallbacks); + result.Insert(0, fallbackRuntimeIdentifier.IsValueCreated ? fallbackRuntimeIdentifier.Value : currentRuntimeIdentifier); + result.Add(string.Empty); + return result; + } + + RuntimeFallbacks GetFallbacks(IReadOnlyList runtimeGraph) + => runtimeGraph.FirstOrDefault(x => string.Equals(x.Runtime, currentRuntimeIdentifier, StringComparison.OrdinalIgnoreCase)) + ?? runtimeGraph.FirstOrDefault(x => string.Equals(x.Runtime, fallbackRuntimeIdentifier.Value, StringComparison.OrdinalIgnoreCase)) + ?? AnyAndBase; + + // This mimics the behavior of https://github.com/dotnet/core-setup/blob/863047f3ca16bada3ffc82493d1dbad6e560b80a/src/corehost/common/pal.h#L53-L73 + string GetFallbackRuntime(string runtime) + { + var match = RuntimeIdRegex.Match(runtime); + var arch = match?.Groups?["arch"]?.Value; + var result = default(string); + + switch (operatingSystemPlatform) + { + case Platform.Windows: + result = "win10" + arch; + break; + + case Platform.Darwin: + result = "osx.10.12" + arch; + break; + + case Platform.Linux: + result = "linux" + arch; + break; + + default: + result = "unknown"; + break; + } + + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.GetFallbackRuntime] Could not find runtime '{runtime}', falling back to '{result}'")); + + return result; + } + + IEnumerable GetUnmanagedDllFormats() + { + yield return "{0}"; + + if (operatingSystemPlatform == Platform.Windows) + { + yield return "{0}.dll"; + } + else if (operatingSystemPlatform == Platform.Darwin) + { + yield return "lib{0}.dylib"; + yield return "{0}.dylib"; + } + else if (operatingSystemPlatform == Platform.Linux) + { + yield return "lib{0}.so"; + yield return "{0}.so"; + } + } + + public Assembly LoadManagedDll(string assemblyName, Func managedAssemblyLoader) + { + if (!managedAssemblyCache.TryGetValue(assemblyName, out var result)) + { + var tupleResult = ResolveManagedAssembly(assemblyName, managedAssemblyLoader); + var resolvedAssemblyPath = tupleResult.Item1; + result = tupleResult.Item2; + managedAssemblyCache[assemblyName] = result; + + if (internalDiagnosticsMessageSink != null) + { + if (result == null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadManagedDll] Resolution for '{assemblyName}' failed, passed down to next resolver")); + else + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadManagedDll] Resolved '{assemblyName}' to '{resolvedAssemblyPath}'")); + } + } + + return result; + } + + public IntPtr LoadUnmanagedLibrary(string unmanagedLibraryName, Func unmanagedAssemblyLoader) + { + var result = default(IntPtr); + var needDiagnostics = false; + + if (!unmanagedAssemblyCache.TryGetValue(unmanagedLibraryName, out var resolvedAssemblyPath)) + { + resolvedAssemblyPath = ResolveUnmanagedLibrary(unmanagedLibraryName); + unmanagedAssemblyCache[unmanagedLibraryName] = resolvedAssemblyPath; + needDiagnostics = true; + } + + if (resolvedAssemblyPath != null) + result = unmanagedAssemblyLoader(resolvedAssemblyPath); + + if (needDiagnostics && internalDiagnosticsMessageSink != null) + if (result != default) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadUnmanagedLibrary] Resolved '{unmanagedLibraryName}' to '{resolvedAssemblyPath}'")); + else + { + if (resolvedAssemblyPath != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadUnmanagedLibrary] Resolving '{unmanagedLibraryName}', found assembly path '{resolvedAssemblyPath}' but the assembly would not load")); + + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadUnmanagedLibrary] Resolution for '{unmanagedLibraryName}' failed, passed down to next resolver")); + } + + return result; + } + + Tuple ResolveManagedAssembly(string assemblyName, Func managedAssemblyLoader) + { + // Try to find dependency in the local folder + var assemblyPath = Path.Combine(Path.GetFullPath(assemblyFolder), assemblyName); + + foreach (var extension in ManagedAssemblyExtensions) + try + { + var resolvedAssemblyPath = assemblyPath + extension; + if (fileSystem.File.Exists(resolvedAssemblyPath)) + { + var assembly = managedAssemblyLoader(resolvedAssemblyPath); + if (assembly != null) + return Tuple.Create(resolvedAssemblyPath, assembly); + } + } + catch { } + + // Try to find dependency from .deps.json + if (managedAssemblyMap.TryGetValue(assemblyName, out var libraryTuple)) + { + var library = libraryTuple.Item1; + var assetGroup = libraryTuple.Item2; + var wrapper = new CompilationLibrary(library.Type, library.Name, library.Version, library.Hash, + assetGroup.AssetPaths, library.Dependencies, library.Serviceable, + library.Path, library.HashPath); + + var assemblies = new List(); + if (assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies)) + { + var resolvedAssemblyPath = assemblies.FirstOrDefault(a => string.Equals(assemblyName, Path.GetFileNameWithoutExtension(a), StringComparison.OrdinalIgnoreCase)); + if (resolvedAssemblyPath != null) + { + resolvedAssemblyPath = Path.GetFullPath(resolvedAssemblyPath); + + var assembly = managedAssemblyLoader(resolvedAssemblyPath); + if (assembly != null) + return Tuple.Create(resolvedAssemblyPath, assembly); + + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveManagedAssembly] Resolving '{assemblyName}', found assembly path '{resolvedAssemblyPath}' but the assembly would not load")); + } + else + { + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveManagedAssembly] Resolving '{assemblyName}', found a resolved path, but could not map a filename in [{string.Join(",", assemblies.OrderBy(k => k, StringComparer.OrdinalIgnoreCase).Select(k => $"'{k}'"))}]")); + } + } + else + { + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveManagedAssembly] Resolving '{assemblyName}', found in dependency map, but unable to resolve a path in [{string.Join(",", assetGroup.AssetPaths.OrderBy(k => k, StringComparer.OrdinalIgnoreCase).Select(k => $"'{k}'"))}]")); + } + } + + return ManagedAssemblyNotFound; + } + + public string ResolveUnmanagedLibrary(string unmanagedLibraryName) + { + foreach (var format in unmanagedDllFormats) + { + var formattedUnmanagedDllName = string.Format(format, unmanagedLibraryName); + + if (unmanagedAssemblyMap.TryGetValue(formattedUnmanagedDllName, out var libraryTuple)) + { + var library = libraryTuple.Item1; + var assetGroup = libraryTuple.Item2; + var wrapper = new CompilationLibrary(library.Type, library.Name, library.Version, library.Hash, assetGroup.AssetPaths, library.Dependencies, library.Serviceable); + + var assemblies = new List(); + if (assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies)) + { + var resolvedAssemblyPath = assemblies.FirstOrDefault(a => string.Equals(formattedUnmanagedDllName, Path.GetFileName(a), StringComparison.OrdinalIgnoreCase)); + if (resolvedAssemblyPath != null) + return Path.GetFullPath(resolvedAssemblyPath); + + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveUnmanagedLibrary] Found a resolved path, but could not map a filename in [{string.Join(",", assemblies.OrderBy(k => k, StringComparer.OrdinalIgnoreCase).Select(k => $"'{k}'"))}]")); + } + else + { + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveUnmanagedLibrary] Found in dependency map, but unable to resolve a path in [{string.Join(",", assetGroup.AssetPaths.OrderBy(k => k, StringComparer.OrdinalIgnoreCase).Select(k => $"'{k}'"))}]")); + } + } + } + + return null; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/ApplicationEnvironment.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/ApplicationEnvironment.cs new file mode 100644 index 00000000000..74ed0624652 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/ApplicationEnvironment.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +namespace Internal.Microsoft.DotNet.PlatformAbstractions +{ + internal static class ApplicationEnvironment + { + public static string ApplicationBasePath { get; } = GetApplicationBasePath(); + + private static string GetApplicationBasePath() + { + var basePath = AppContext.BaseDirectory; + return Path.GetFullPath(basePath); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/HashCodeCombiner.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/HashCodeCombiner.cs new file mode 100644 index 00000000000..a8290e3586d --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/HashCodeCombiner.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace Internal.Microsoft.DotNet.PlatformAbstractions +{ + internal struct HashCodeCombiner + { + private long _combinedHash64; + + public int CombinedHash + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return _combinedHash64.GetHashCode(); } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private HashCodeCombiner(long seed) + { + _combinedHash64 = seed; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int i) + { + _combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(string s) + { + var hashCode = (s != null) ? s.GetHashCode() : 0; + Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HashCodeCombiner Start() + { + return new HashCodeCombiner(0x1505L); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Darwin.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Darwin.cs new file mode 100644 index 00000000000..532d52b4325 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Darwin.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +namespace Internal.Microsoft.DotNet.PlatformAbstractions.Native +{ + internal static partial class NativeMethods + { + public static class Darwin + { + private const int CTL_KERN = 1; + private const int KERN_OSRELEASE = 2; + + public unsafe static string GetKernelRelease() + { + const uint BUFFER_LENGTH = 32; + + var name = stackalloc int[2]; + name[0] = CTL_KERN; + name[1] = KERN_OSRELEASE; + + var buf = stackalloc byte[(int)BUFFER_LENGTH]; + var len = stackalloc uint[1]; + *len = BUFFER_LENGTH; + + try + { + // If the buffer isn't big enough, it seems sysctl still returns 0 and just sets len to the + // necessary buffer size. This appears to be contrary to the man page, but it's easy to detect + // by simply checking len against the buffer length. + if (sysctl(name, 2, buf, len, IntPtr.Zero, 0) == 0 && *len < BUFFER_LENGTH) + { + return Marshal.PtrToStringAnsi((IntPtr)buf, (int)*len); + } + } + catch (Exception ex) + { + throw new PlatformNotSupportedException("Error reading Darwin Kernel Version", ex); + } + throw new PlatformNotSupportedException("Unknown error reading Darwin Kernel Version"); + } + + [DllImport("libc")] + private unsafe static extern int sysctl( + int* name, + uint namelen, + byte* oldp, + uint* oldlenp, + IntPtr newp, + uint newlen); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs new file mode 100644 index 00000000000..d9e26a5c129 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.InteropServices; + +namespace Internal.Microsoft.DotNet.PlatformAbstractions.Native +{ + internal static partial class NativeMethods + { + public static class Windows + { + [StructLayout(LayoutKind.Sequential)] + internal struct RTL_OSVERSIONINFOEX + { + internal uint dwOSVersionInfoSize; + internal uint dwMajorVersion; + internal uint dwMinorVersion; + internal uint dwBuildNumber; + internal uint dwPlatformId; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + internal string szCSDVersion; + } + + // This call avoids the shimming Windows does to report old versions + [DllImport("ntdll")] + private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation); + + internal static string RtlGetVersion() + { + RTL_OSVERSIONINFOEX osvi = new RTL_OSVERSIONINFOEX(); + osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi); + if (RtlGetVersion(out osvi) == 0) + { + return $"{osvi.dwMajorVersion}.{osvi.dwMinorVersion}.{osvi.dwBuildNumber}"; + } + else + { + return null; + } + } + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs new file mode 100644 index 00000000000..8fb2c3dc2c0 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs @@ -0,0 +1,159 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Internal.Microsoft.DotNet.PlatformAbstractions.Native +{ + internal static class PlatformApis + { + private class DistroInfo + { + public string Id; + public string VersionId; + } + + private static readonly Lazy _platform = new Lazy(DetermineOSPlatform); + private static readonly Lazy _distroInfo = new Lazy(LoadDistroInfo); + + public static string GetOSName() + { + switch (GetOSPlatform()) + { + case Platform.Windows: + return "Windows"; + case Platform.Linux: + return GetDistroId() ?? "Linux"; + case Platform.Darwin: + return "Mac OS X"; + default: + return "Unknown"; + } + } + + public static string GetOSVersion() + { + switch (GetOSPlatform()) + { + case Platform.Windows: + return NativeMethods.Windows.RtlGetVersion() ?? string.Empty; + case Platform.Linux: + return GetDistroVersionId() ?? string.Empty; + case Platform.Darwin: + return GetDarwinVersion() ?? string.Empty; + default: + return string.Empty; + } + } + + private static string GetDarwinVersion() + { + Version version; + var kernelRelease = NativeMethods.Darwin.GetKernelRelease(); + if (!Version.TryParse(kernelRelease, out version) || version.Major < 5) + { + // 10.0 covers all versions prior to Darwin 5 + // Similarly, if the version is not a valid version number, but we have still detected that it is Darwin, we just assume + // it is OS X 10.0 + return "10.0"; + } + else + { + // Mac OS X 10.1 mapped to Darwin 5.x, and the mapping continues that way + // So just subtract 4 from the Darwin version. + // https://en.wikipedia.org/wiki/Darwin_%28operating_system%29 + return $"10.{version.Major - 4}"; + } + } + + public static Platform GetOSPlatform() + { + return _platform.Value; + } + + private static string GetDistroId() + { + return _distroInfo.Value?.Id; + } + + private static string GetDistroVersionId() + { + return _distroInfo.Value?.VersionId; + } + + private static DistroInfo LoadDistroInfo() + { + // Sample os-release file: + // NAME="Ubuntu" + // VERSION = "14.04.3 LTS, Trusty Tahr" + // ID = ubuntu + // ID_LIKE = debian + // PRETTY_NAME = "Ubuntu 14.04.3 LTS" + // VERSION_ID = "14.04" + // HOME_URL = "http://www.ubuntu.com/" + // SUPPORT_URL = "http://help.ubuntu.com/" + // BUG_REPORT_URL = "http://bugs.launchpad.net/ubuntu/" + // We use ID and VERSION_ID + + if (File.Exists("/etc/os-release")) + { + var lines = File.ReadAllLines("/etc/os-release"); + var result = new DistroInfo(); + foreach (var line in lines) + { + if (line.StartsWith("ID=", StringComparison.Ordinal)) + { + result.Id = line.Substring(3).Trim('"', '\''); + } + else if (line.StartsWith("VERSION_ID=", StringComparison.Ordinal)) + { + result.VersionId = line.Substring(11).Trim('"', '\''); + } + } + + return NormalizeDistroInfo(result); + } + return null; + } + + // For some distros, we don't want to use the full version from VERSION_ID. One example is + // Red Hat Enterprise Linux, which includes a minor version in their VERSION_ID but minor + // versions are backwards compatable. + // + // In this case, we'll normalized RIDs like 'rhel.7.2' and 'rhel.7.3' to a generic + // 'rhel.7'. This brings RHEL in line with other distros like CentOS or Debian which + // don't put minor version numbers in their VERSION_ID fields because all minor versions + // are backwards compatible. + private static DistroInfo NormalizeDistroInfo(DistroInfo distroInfo) + { + // Handle if VersionId is null by just setting the index to -1. + int minorVersionNumberSeparatorIndex = distroInfo.VersionId?.IndexOf('.') ?? -1; + + if (distroInfo.Id == "rhel" && minorVersionNumberSeparatorIndex != -1) + { + distroInfo.VersionId = distroInfo.VersionId.Substring(0, minorVersionNumberSeparatorIndex); + } + + return distroInfo; + } + + private static Platform DetermineOSPlatform() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Platform.Windows; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Platform.Linux; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return Platform.Darwin; + } + return Platform.Unknown; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Platform.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Platform.cs new file mode 100644 index 00000000000..dba0bce892b --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Platform.cs @@ -0,0 +1,13 @@ +// Copyright(c) .NET Foundation and contributors.All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Internal.Microsoft.DotNet.PlatformAbstractions +{ + internal enum Platform + { + Unknown = 0, + Windows = 1, + Linux = 2, + Darwin = 3 + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/RuntimeEnvironment.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/RuntimeEnvironment.cs new file mode 100644 index 00000000000..a9849f5287f --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/RuntimeEnvironment.cs @@ -0,0 +1,105 @@ +// Copyright(c) .NET Foundation and contributors.All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Internal.Microsoft.DotNet.PlatformAbstractions.Native; + +namespace Internal.Microsoft.DotNet.PlatformAbstractions +{ + internal static class RuntimeEnvironment + { + private static readonly string OverrideEnvironmentVariableName = "DOTNET_RUNTIME_ID"; + + public static Platform OperatingSystemPlatform { get; } = PlatformApis.GetOSPlatform(); + + public static string OperatingSystemVersion { get; } = PlatformApis.GetOSVersion(); + + public static string OperatingSystem { get; } = PlatformApis.GetOSName(); + + public static string RuntimeArchitecture { get; } = GetArch(); + + private static string GetArch() + { + return IntPtr.Size == 8 ? "x64" : "x86"; + } + + public static string GetRuntimeIdentifier() + { + return + Environment.GetEnvironmentVariable(OverrideEnvironmentVariableName) ?? + (GetRIDOS() + GetRIDVersion() + GetRIDArch()); + } + + private static string GetRIDArch() + { + if (!string.IsNullOrEmpty(RuntimeArchitecture)) + { + return $"-{RuntimeArchitecture.ToLowerInvariant()}"; + } + return string.Empty; + } + + private static string GetRIDVersion() + { + // Windows RIDs do not separate OS name and version by "." due to legacy + // Others do, that's why we have the "." prefix on them below + switch (OperatingSystemPlatform) + { + case Platform.Windows: + return GetWindowsProductVersion(); + case Platform.Linux: + if (string.IsNullOrEmpty(OperatingSystemVersion)) + { + return string.Empty; + } + + return $".{OperatingSystemVersion}"; + case Platform.Darwin: + return $".{OperatingSystemVersion}"; + default: + return string.Empty; // Unknown Platform? Unknown Version! + } + } + + private static string GetWindowsProductVersion() + { + var ver = Version.Parse(OperatingSystemVersion); + if (ver.Major == 6) + { + if (ver.Minor == 1) + { + return "7"; + } + else if (ver.Minor == 2) + { + return "8"; + } + else if (ver.Minor == 3) + { + return "81"; + } + } + else if (ver.Major >= 10) + { + // Return the major version for use in RID computation without applying any cap. + return ver.Major.ToString(); + } + return string.Empty; // Unknown version + } + + private static string GetRIDOS() + { + switch (OperatingSystemPlatform) + { + case Platform.Windows: + return "win"; + case Platform.Linux: + return OperatingSystem.ToLowerInvariant(); + case Platform.Darwin: + return "osx"; + default: + return "unknown"; + } + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs new file mode 100644 index 00000000000..815b0aecfde --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Internal.Microsoft.Extensions.DependencyModel.Resolution; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class CompilationLibrary : Library + { + public CompilationLibrary(string type, + string name, + string version, + string hash, + IEnumerable assemblies, + IEnumerable dependencies, + bool serviceable) + : this(type, name, version, hash, assemblies, dependencies, serviceable, path: null, hashPath: null) + { + } + + public CompilationLibrary(string type, + string name, + string version, + string hash, + IEnumerable assemblies, + IEnumerable dependencies, + bool serviceable, + string path, + string hashPath) + : base(type, name, version, hash, dependencies, serviceable, path, hashPath) + { + if (assemblies == null) + { + throw new ArgumentNullException(nameof(assemblies)); + } + Assemblies = assemblies.ToArray(); + } + + public IReadOnlyList Assemblies { get; } + + internal static ICompilationAssemblyResolver DefaultResolver { get; } = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[] + { + new AppBaseCompilationAssemblyResolver(), + new ReferenceAssemblyPathResolver(), + new PackageCompilationAssemblyResolver() + }); + + private IEnumerable ResolveReferencePaths(ICompilationAssemblyResolver resolver, List assemblies) + { + if (!resolver.TryResolveAssemblyPaths(this, assemblies)) + { + throw new InvalidOperationException($"Cannot find compilation library location for package '{Name}'"); + } + return assemblies; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationOptions.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationOptions.cs new file mode 100644 index 00000000000..a517e80e2a1 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationOptions.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class CompilationOptions + { + public IReadOnlyList Defines { get; } + + public string LanguageVersion { get; } + + public string Platform { get; } + + public bool? AllowUnsafe { get; } + + public bool? WarningsAsErrors { get; } + + public bool? Optimize { get; } + + public string KeyFile { get; } + + public bool? DelaySign { get; } + + public bool? PublicSign { get; } + + public string DebugType { get; } + + public bool? EmitEntryPoint { get; } + + public bool? GenerateXmlDocumentation { get; } + + public static CompilationOptions Default { get; } = new CompilationOptions( + defines: Enumerable.Empty(), + languageVersion: null, + platform: null, + allowUnsafe: null, + warningsAsErrors: null, + optimize: null, + keyFile: null, + delaySign: null, + publicSign: null, + debugType: null, + emitEntryPoint: null, + generateXmlDocumentation: null); + + public CompilationOptions(IEnumerable defines, + string languageVersion, + string platform, + bool? allowUnsafe, + bool? warningsAsErrors, + bool? optimize, + string keyFile, + bool? delaySign, + bool? publicSign, + string debugType, + bool? emitEntryPoint, + bool? generateXmlDocumentation) + { + if (defines == null) + { + throw new ArgumentNullException(nameof(defines)); + } + Defines = defines.ToArray(); + LanguageVersion = languageVersion; + Platform = platform; + AllowUnsafe = allowUnsafe; + WarningsAsErrors = warningsAsErrors; + Optimize = optimize; + KeyFile = keyFile; + DelaySign = delaySign; + PublicSign = publicSign; + DebugType = debugType; + EmitEntryPoint = emitEntryPoint; + GenerateXmlDocumentation = generateXmlDocumentation; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Dependency.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Dependency.cs new file mode 100644 index 00000000000..795e2de48bc --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Dependency.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Internal.Microsoft.DotNet.PlatformAbstractions; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal struct Dependency + { + public Dependency(string name, string version) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(nameof(name)); + } + if (string.IsNullOrEmpty(version)) + { + throw new ArgumentException(nameof(version)); + } + Name = name; + Version = version; + } + + public string Name { get; } + public string Version { get; } + + public bool Equals(Dependency other) + { + return string.Equals(Name, other.Name) && string.Equals(Version, other.Version); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is Dependency && Equals((Dependency) obj); + } + + public override int GetHashCode() + { + var combiner = HashCodeCombiner.Start(); + combiner.Add(Name); + combiner.Add(Version); + return combiner.CombinedHash; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContext.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContext.cs new file mode 100644 index 00000000000..83b34ac3da7 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContext.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class DependencyContext + { + private static readonly Lazy _defaultContext = new Lazy(LoadDefault); + + public DependencyContext(TargetInfo target, + CompilationOptions compilationOptions, + IEnumerable compileLibraries, + IEnumerable runtimeLibraries, + IEnumerable runtimeGraph) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + if (compilationOptions == null) + { + throw new ArgumentNullException(nameof(compilationOptions)); + } + if (compileLibraries == null) + { + throw new ArgumentNullException(nameof(compileLibraries)); + } + if (runtimeLibraries == null) + { + throw new ArgumentNullException(nameof(runtimeLibraries)); + } + if (runtimeGraph == null) + { + throw new ArgumentNullException(nameof(runtimeGraph)); + } + + Target = target; + CompilationOptions = compilationOptions; + CompileLibraries = compileLibraries.ToArray(); + RuntimeLibraries = runtimeLibraries.ToArray(); + RuntimeGraph = runtimeGraph.ToArray(); + } + + public static DependencyContext Default => _defaultContext.Value; + + public TargetInfo Target { get; } + + public CompilationOptions CompilationOptions { get; } + + public IReadOnlyList CompileLibraries { get; } + + public IReadOnlyList RuntimeLibraries { get; } + + public IReadOnlyList RuntimeGraph { get; } + + public DependencyContext Merge(DependencyContext other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new DependencyContext( + Target, + CompilationOptions, + CompileLibraries.Union(other.CompileLibraries, new LibraryMergeEqualityComparer()), + RuntimeLibraries.Union(other.RuntimeLibraries, new LibraryMergeEqualityComparer()), + RuntimeGraph.Union(other.RuntimeGraph) + ); + } + + private static DependencyContext LoadDefault() + { + var entryAssembly = Assembly.GetEntryAssembly(); + if (entryAssembly == null) + { + return null; + } + + return Load(entryAssembly); + } + + public static DependencyContext Load(Assembly assembly) + { + return DependencyContextLoader.Default.Load(assembly); + } + + private class LibraryMergeEqualityComparer : IEqualityComparer where T : Library + { + public bool Equals(T x, T y) + { + return StringComparer.OrdinalIgnoreCase.Equals(x.Name, y.Name); + } + + public int GetHashCode(T obj) + { + return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name); + } + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs new file mode 100644 index 00000000000..051cedbb192 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs @@ -0,0 +1,525 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xunit; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class DependencyContextJsonReader : IDependencyContextReader + { + static readonly string[] EmptyStringArray = new string[0]; + + private readonly IDictionary _stringPool = new Dictionary(); + + public DependencyContext Read(Stream stream) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + using (var streamReader = new StreamReader(stream)) + { + return Read(streamReader); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _stringPool.Clear(); + } + } + + public void Dispose() + { + Dispose(true); + } + + private DependencyContext Read(TextReader reader) + { + // { + // "runtimeTarget": {...}, + // "compilationOptions": {...}, + // "targets": {...}, + // "libraries": {...}, + // "runtimes": {...} + // } + + var root = JsonDeserializer.Deserialize(reader) as JsonObject; + if (root == null) + return null; + + var runtime = string.Empty; + var framework = string.Empty; + var isPortable = true; + + ReadRuntimeTarget(root.ValueAsJsonObject(DependencyContextStrings.RuntimeTargetPropertyName), out var runtimeTargetName, out var runtimeSignature); + var compilationOptions = ReadCompilationOptions(root.ValueAsJsonObject(DependencyContextStrings.CompilationOptionsPropertName)); + var targets = ReadTargets(root.ValueAsJsonObject(DependencyContextStrings.TargetsPropertyName)); + var libraryStubs = ReadLibraries(root.ValueAsJsonObject(DependencyContextStrings.LibrariesPropertyName)); + var runtimeFallbacks = ReadRuntimes(root.ValueAsJsonObject(DependencyContextStrings.RuntimesPropertyName)); + + if (compilationOptions == null) + compilationOptions = CompilationOptions.Default; + + Target runtimeTarget = SelectRuntimeTarget(targets, runtimeTargetName); + runtimeTargetName = runtimeTarget?.Name; + + if (runtimeTargetName != null) + { + var seperatorIndex = runtimeTargetName.IndexOf(DependencyContextStrings.VersionSeperator); + if (seperatorIndex > -1 && seperatorIndex < runtimeTargetName.Length) + { + runtime = runtimeTargetName.Substring(seperatorIndex + 1); + framework = runtimeTargetName.Substring(0, seperatorIndex); + isPortable = false; + } + else + { + framework = runtimeTargetName; + } + } + + Target compileTarget = null; + + var ridlessTarget = targets.FirstOrDefault(t => !IsRuntimeTarget(t.Name)); + if (ridlessTarget != null) + { + compileTarget = ridlessTarget; + if (runtimeTarget == null) + { + runtimeTarget = compileTarget; + framework = ridlessTarget.Name; + } + } + + if (runtimeTarget == null) + throw new FormatException("No runtime target found"); + + return new DependencyContext( + new TargetInfo(framework, runtime, runtimeSignature, isPortable), + compilationOptions, + CreateLibraries(compileTarget?.Libraries, false, libraryStubs).Cast().ToArray(), + CreateLibraries(runtimeTarget.Libraries, true, libraryStubs).Cast().ToArray(), + runtimeFallbacks ?? Enumerable.Empty()); + } + + private Target SelectRuntimeTarget(List targets, string runtimeTargetName) + { + Target target; + + if (targets == null || targets.Count == 0) + throw new FormatException("Dependency file does not have 'targets' section"); + + if (!string.IsNullOrEmpty(runtimeTargetName)) + { + target = targets.FirstOrDefault(t => t.Name == runtimeTargetName); + if (target == null) + throw new FormatException($"Target with name {runtimeTargetName} not found"); + } + else + { + target = targets.FirstOrDefault(t => IsRuntimeTarget(t.Name)); + } + + return target; + } + + private bool IsRuntimeTarget(string name) + => name.Contains(DependencyContextStrings.VersionSeperator); + + private void ReadRuntimeTarget(JsonObject runtimeTargetJson, out string runtimeTargetName, out string runtimeSignature) + { + // { + // "name": ".NETCoreApp,Version=v1.0", + // "signature": "35bd60f1a92c048eea72ff8160ba07b616ebd0f6" + // } + + runtimeTargetName = runtimeTargetJson?.ValueAsString(DependencyContextStrings.RuntimeTargetNamePropertyName); + runtimeSignature = runtimeTargetJson?.ValueAsString(DependencyContextStrings.RuntimeTargetSignaturePropertyName); + } + + private CompilationOptions ReadCompilationOptions(JsonObject compilationOptionsJson) + { + // { + // "defines": ["","",...], + // "languageVersion": "...", + // "platform": "...", + // "allowUnsafe: "true|false", + // "warningsAsErrors": "true|false", + // "optimize": "true|false", + // "keyFile": "...", + // "delaySign": "true|false", + // "publicSign": "true|false", + // "debugType": "...", + // "emitEntryPoint: "true|false", + // "xmlDoc": "true|false" + // } + + if (compilationOptionsJson == null) + return null; + + var defines = compilationOptionsJson.ValueAsStringArray(DependencyContextStrings.DefinesPropertyName) ?? EmptyStringArray; + var languageVersion = compilationOptionsJson.ValueAsString(DependencyContextStrings.LanguageVersionPropertyName); + var platform = compilationOptionsJson.ValueAsString(DependencyContextStrings.PlatformPropertyName); + var allowUnsafe = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.AllowUnsafePropertyName); + var warningsAsErrors = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.WarningsAsErrorsPropertyName); + var optimize = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.OptimizePropertyName); + var keyFile = compilationOptionsJson.ValueAsString(DependencyContextStrings.KeyFilePropertyName); + var delaySign = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.DelaySignPropertyName); + var publicSign = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.PublicSignPropertyName); + var debugType = compilationOptionsJson.ValueAsString(DependencyContextStrings.DebugTypePropertyName); + var emitEntryPoint = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.EmitEntryPointPropertyName); + var generateXmlDocumentation = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.GenerateXmlDocumentationPropertyName); + + return new CompilationOptions(defines, languageVersion, platform, allowUnsafe, warningsAsErrors, optimize, keyFile, delaySign, publicSign, debugType, emitEntryPoint, generateXmlDocumentation); + } + + private List ReadTargets(JsonObject targetsJson) + { + // Object dictionary: string => object + + var targets = new List(); + + if (targetsJson != null) + foreach (var key in targetsJson.Keys) + targets.Add(ReadTarget(key, targetsJson.ValueAsJsonObject(key))); + + return targets; + } + + private Target ReadTarget(string targetName, JsonObject targetJson) + { + // Object dictionary: string => object + + var libraries = new List(); + + foreach (var key in targetJson.Keys) + libraries.Add(ReadTargetLibrary(key, targetJson.ValueAsJsonObject(key))); + + return new Target { Name = targetName, Libraries = libraries }; + } + + private TargetLibrary ReadTargetLibrary(string targetLibraryName, JsonObject targetLibraryJson) + { + // { + // "dependencies": {...}, + // "runtime": {...}, # Dictionary: name => {} + // "native": {...}, # Dictionary: name => {} + // "compile": {...}, # Dictionary: name => {} + // "runtime": {...}, + // "resources": {...}, + // "compileOnly": "true|false" + // } + + var dependencies = ReadTargetLibraryDependencies(targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.DependenciesPropertyName)); + var runtimes = targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.RuntimeAssembliesKey)?.Keys; + var natives = targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.NativeLibrariesKey)?.Keys; + var compilations = targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.CompileTimeAssembliesKey)?.Keys; + var runtimeTargets = ReadTargetLibraryRuntimeTargets(targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.RuntimeTargetsPropertyName)); + var resources = ReadTargetLibraryResources(targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.ResourceAssembliesPropertyName)); + var compileOnly = targetLibraryJson.ValueAsNullableBoolean(DependencyContextStrings.CompilationOnlyPropertyName); + + return new TargetLibrary + { + Name = targetLibraryName, + Dependencies = dependencies ?? Enumerable.Empty(), + Runtimes = runtimes?.ToList(), + Natives = natives?.ToList(), + Compilations = compilations?.ToList(), + RuntimeTargets = runtimeTargets, + Resources = resources, + CompileOnly = compileOnly + }; + } + + public IEnumerable ReadTargetLibraryDependencies(JsonObject targetLibraryDependenciesJson) + { + // Object dictionary: string => string + + var dependencies = new List(); + + if (targetLibraryDependenciesJson != null) + foreach (var key in targetLibraryDependenciesJson.Keys) + dependencies.Add(new Dependency(Pool(key), Pool(targetLibraryDependenciesJson.ValueAsString(key)))); + + return dependencies; + } + + private List ReadTargetLibraryRuntimeTargets(JsonObject targetLibraryRuntimeTargetsJson) + { + // Object dictionary: string => { "rid": "...", "assetType": "..." } + + var runtimeTargets = new List(); + + if (targetLibraryRuntimeTargetsJson != null) + { + foreach (var key in targetLibraryRuntimeTargetsJson.Keys) + { + var runtimeTargetJson = targetLibraryRuntimeTargetsJson.ValueAsJsonObject(key); + + runtimeTargets.Add(new RuntimeTargetEntryStub + { + Path = key, + Rid = Pool(runtimeTargetJson?.ValueAsString(DependencyContextStrings.RidPropertyName)), + Type = Pool(runtimeTargetJson?.ValueAsString(DependencyContextStrings.AssetTypePropertyName)) + }); + } + } + + return runtimeTargets; + } + + private List ReadTargetLibraryResources(JsonObject targetLibraryResourcesJson) + { + // Object dictionary: string => { "locale": "..." } + + var resources = new List(); + + if (targetLibraryResourcesJson != null) + { + foreach (var key in targetLibraryResourcesJson.Keys) + { + string locale = targetLibraryResourcesJson.ValueAsJsonObject(key)?.ValueAsString(DependencyContextStrings.LocalePropertyName); + + if (locale != null) + resources.Add(new ResourceAssembly(key, Pool(locale))); + } + } + + return resources; + } + + private Dictionary ReadLibraries(JsonObject librariesJson) + { + // Object dictionary: string => object + + var libraries = new Dictionary(); + + if (librariesJson != null) + foreach (var key in librariesJson.Keys) + libraries.Add(Pool(key), ReadLibrary(librariesJson.ValueAsJsonObject(key))); + + return libraries; + } + + private LibraryStub ReadLibrary(JsonObject libraryJson) + { + // { + // "sha512": "...", + // "type": "...", + // "serviceable: "true|false", + // "path": "...", + // "hashPath: "...", + // "runtimeStoreManifestName": "..." + // } + + string hash = libraryJson.ValueAsString(DependencyContextStrings.Sha512PropertyName); + string type = libraryJson.ValueAsString(DependencyContextStrings.TypePropertyName); + bool serviceable = libraryJson.ValueAsBoolean(DependencyContextStrings.ServiceablePropertyName); + string path = libraryJson.ValueAsString(DependencyContextStrings.PathPropertyName); + string hashPath = libraryJson.ValueAsString(DependencyContextStrings.HashPathPropertyName); + string runtimeStoreManifestName = libraryJson.ValueAsString(DependencyContextStrings.RuntimeStoreManifestPropertyName); + + return new LibraryStub() + { + Hash = hash, + Type = Pool(type), + Serviceable = serviceable, + Path = path, + HashPath = hashPath, + RuntimeStoreManifestName = runtimeStoreManifestName + }; + } + + private List ReadRuntimes(JsonObject runtimesJson) + { + // Object dictionary: string => ["...","...",...] + + var runtimeFallbacks = new List(); + + if (runtimesJson != null) + foreach (var key in runtimesJson.Keys) + runtimeFallbacks.Add(new RuntimeFallbacks(key, runtimesJson.ValueAsStringArray(key) ?? EmptyStringArray)); + + return runtimeFallbacks; + } + + private IEnumerable CreateLibraries(IEnumerable libraries, bool runtime, Dictionary libraryStubs) + { + if (libraries == null) + return Enumerable.Empty(); + + return libraries.Select(property => CreateLibrary(property, runtime, libraryStubs)) + .Where(library => library != null); + } + + private Library CreateLibrary(TargetLibrary targetLibrary, bool runtime, Dictionary libraryStubs) + { + var nameWithVersion = targetLibrary.Name; + LibraryStub stub; + + if (libraryStubs == null || !libraryStubs.TryGetValue(nameWithVersion, out stub)) + { + throw new InvalidOperationException($"Cannot find library information for {nameWithVersion}"); + } + + var seperatorPosition = nameWithVersion.IndexOf(DependencyContextStrings.VersionSeperator); + + var name = Pool(nameWithVersion.Substring(0, seperatorPosition)); + var version = Pool(nameWithVersion.Substring(seperatorPosition + 1)); + + if (runtime) + { + // Runtime section of this library was trimmed by type:platform + var isCompilationOnly = targetLibrary.CompileOnly; + if (isCompilationOnly == true) + { + return null; + } + + var runtimeAssemblyGroups = new List(); + var nativeLibraryGroups = new List(); + if (targetLibrary.RuntimeTargets != null) + { + foreach (var ridGroup in targetLibrary.RuntimeTargets.GroupBy(e => e.Rid)) + { + var groupRuntimeAssemblies = ridGroup + .Where(e => e.Type == DependencyContextStrings.RuntimeAssetType) + .Select(e => e.Path) + .ToArray(); + + if (groupRuntimeAssemblies.Any()) + { + runtimeAssemblyGroups.Add(new RuntimeAssetGroup( + ridGroup.Key, + groupRuntimeAssemblies.Where(a => Path.GetFileName(a) != "_._"))); + } + + var groupNativeLibraries = ridGroup + .Where(e => e.Type == DependencyContextStrings.NativeAssetType) + .Select(e => e.Path) + .ToArray(); + + if (groupNativeLibraries.Any()) + { + nativeLibraryGroups.Add(new RuntimeAssetGroup( + ridGroup.Key, + groupNativeLibraries.Where(a => Path.GetFileName(a) != "_._"))); + } + } + } + + if (targetLibrary.Runtimes != null && targetLibrary.Runtimes.Count > 0) + { + runtimeAssemblyGroups.Add(new RuntimeAssetGroup(string.Empty, targetLibrary.Runtimes)); + } + + if (targetLibrary.Natives != null && targetLibrary.Natives.Count > 0) + { + nativeLibraryGroups.Add(new RuntimeAssetGroup(string.Empty, targetLibrary.Natives)); + } + + return new RuntimeLibrary( + type: stub.Type, + name: name, + version: version, + hash: stub.Hash, + runtimeAssemblyGroups: runtimeAssemblyGroups, + nativeLibraryGroups: nativeLibraryGroups, + resourceAssemblies: targetLibrary.Resources ?? Enumerable.Empty(), + dependencies: targetLibrary.Dependencies, + serviceable: stub.Serviceable, + path: stub.Path, + hashPath: stub.HashPath, + runtimeStoreManifestName: stub.RuntimeStoreManifestName); + } + else + { + var assemblies = (targetLibrary.Compilations != null) ? targetLibrary.Compilations : Enumerable.Empty(); + return new CompilationLibrary( + stub.Type, + name, + version, + stub.Hash, + assemblies, + targetLibrary.Dependencies, + stub.Serviceable, + stub.Path, + stub.HashPath); + } + } + + private string Pool(string s) + { + if (s == null) + { + return null; + } + + string result; + if (!_stringPool.TryGetValue(s, out result)) + { + _stringPool[s] = s; + result = s; + } + return result; + } + + private class Target + { + public string Name; + + public IEnumerable Libraries; + } + + private struct TargetLibrary + { + public string Name; + + public IEnumerable Dependencies; + + public List Runtimes; + + public List Natives; + + public List Compilations; + + public List RuntimeTargets; + + public List Resources; + + public bool? CompileOnly; + } + + private struct RuntimeTargetEntryStub + { + public string Type; + + public string Path; + + public string Rid; + } + + private struct LibraryStub + { + public string Hash; + + public string Type; + + public bool Serviceable; + + public string Path; + + public string HashPath; + + public string RuntimeStoreManifestName; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextLoader.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextLoader.cs new file mode 100644 index 00000000000..d2ff1cccfc7 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextLoader.cs @@ -0,0 +1,128 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class DependencyContextLoader + { + private const string DepsJsonExtension = ".deps.json"; + + private readonly string _entryPointDepsLocation; + private readonly IEnumerable _nonEntryPointDepsPaths; + private readonly IFileSystem _fileSystem; + private readonly Func _jsonReaderFactory; + + public DependencyContextLoader() : this( + DependencyContextPaths.Current.Application, + DependencyContextPaths.Current.NonApplicationPaths, + FileSystemWrapper.Default, + () => new DependencyContextJsonReader()) + { + } + + internal DependencyContextLoader( + string entryPointDepsLocation, + IEnumerable nonEntryPointDepsPaths, + IFileSystem fileSystem, + Func jsonReaderFactory) + { + _entryPointDepsLocation = entryPointDepsLocation; + _nonEntryPointDepsPaths = nonEntryPointDepsPaths; + _fileSystem = fileSystem; + _jsonReaderFactory = jsonReaderFactory; + } + + public static DependencyContextLoader Default { get; } = new DependencyContextLoader(); + + private static bool IsEntryAssembly(Assembly assembly) + { + return assembly.Equals(Assembly.GetEntryAssembly()); + } + + private static Stream GetResourceStream(Assembly assembly, string name) + { + return assembly.GetManifestResourceStream(name); + } + + public DependencyContext Load(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + DependencyContext context = null; + using (var reader = _jsonReaderFactory()) + { + if (IsEntryAssembly(assembly)) + { + context = LoadEntryAssemblyContext(reader); + } + + if (context == null) + { + context = LoadAssemblyContext(assembly, reader); + } + + if (context != null) + { + foreach (var extraPath in _nonEntryPointDepsPaths) + { + var extraContext = LoadContext(reader, extraPath); + if (extraContext != null) + { + context = context.Merge(extraContext); + } + } + } + } + return context; + } + + private DependencyContext LoadEntryAssemblyContext(IDependencyContextReader reader) + { + return LoadContext(reader, _entryPointDepsLocation); + } + + private DependencyContext LoadContext(IDependencyContextReader reader, string location) + { + if (!string.IsNullOrEmpty(location)) + { + Debug.Assert(_fileSystem.File.Exists(location)); + using (var stream = _fileSystem.File.OpenRead(location)) + { + return reader.Read(stream); + } + } + return null; + } + + private DependencyContext LoadAssemblyContext(Assembly assembly, IDependencyContextReader reader) + { + using (var stream = GetResourceStream(assembly, assembly.GetName().Name + DepsJsonExtension)) + { + if (stream != null) + { + return reader.Read(stream); + } + } + + var depsJsonFile = Path.ChangeExtension(assembly.Location, DepsJsonExtension); + if (_fileSystem.File.Exists(depsJsonFile)) + { + using (var stream = _fileSystem.File.OpenRead(depsJsonFile)) + { + return reader.Read(stream); + } + } + + return null; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextPaths.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextPaths.cs new file mode 100644 index 00000000000..ec3311a68ac --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextPaths.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class DependencyContextPaths + { + private static readonly string DepsFilesProperty = "APP_CONTEXT_DEPS_FILES"; + private static readonly string FxDepsFileProperty = "FX_DEPS_FILE"; + + public static DependencyContextPaths Current { get; } = GetCurrent(); + + public string Application { get; } + + public string SharedRuntime { get; } + + public IEnumerable NonApplicationPaths { get; } + + public DependencyContextPaths( + string application, + string sharedRuntime, + IEnumerable nonApplicationPaths) + { + Application = application; + SharedRuntime = sharedRuntime; + NonApplicationPaths = nonApplicationPaths ?? Enumerable.Empty(); + } + + private static DependencyContextPaths GetCurrent() + { +#if NETCOREAPP + var deps = AppContext.GetData(DepsFilesProperty); + var fxDeps = AppContext.GetData(FxDepsFileProperty); +#else + var deps = AppDomain.CurrentDomain.GetData(DepsFilesProperty); + var fxDeps = AppDomain.CurrentDomain.GetData(FxDepsFileProperty); +#endif + return Create(deps as string, fxDeps as string); + } + + internal static DependencyContextPaths Create(string depsFiles, string sharedRuntime) + { + var files = depsFiles?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + var application = files != null && files.Length > 0 ? files[0] : null; + + var nonApplicationPaths = files? + .Skip(1) // the application path + .ToArray(); + + return new DependencyContextPaths( + application, + sharedRuntime, + nonApplicationPaths); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs new file mode 100644 index 00000000000..b5d877daae4 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class DependencyContextStrings + { + internal const char VersionSeperator = '/'; + + internal const string CompileTimeAssembliesKey = "compile"; + + internal const string RuntimeAssembliesKey = "runtime"; + + internal const string NativeLibrariesKey = "native"; + + internal const string RuntimeTargetPropertyName = "runtimeTarget"; + + internal const string LibrariesPropertyName = "libraries"; + + internal const string TargetsPropertyName = "targets"; + + internal const string DependenciesPropertyName = "dependencies"; + + internal const string Sha512PropertyName = "sha512"; + + internal const string PathPropertyName = "path"; + + internal const string HashPathPropertyName = "hashPath"; + + internal const string RuntimeStoreManifestPropertyName = "runtimeStoreManifestName"; + + internal const string TypePropertyName = "type"; + + internal const string ServiceablePropertyName = "serviceable"; + + internal const string CompilationOptionsPropertName = "compilationOptions"; + + internal const string DefinesPropertyName = "defines"; + + internal const string LanguageVersionPropertyName = "languageVersion"; + + internal const string PlatformPropertyName = "platform"; + + internal const string AllowUnsafePropertyName = "allowUnsafe"; + + internal const string WarningsAsErrorsPropertyName = "warningsAsErrors"; + + internal const string OptimizePropertyName = "optimize"; + + internal const string KeyFilePropertyName = "keyFile"; + + internal const string DelaySignPropertyName = "delaySign"; + + internal const string PublicSignPropertyName = "publicSign"; + + internal const string DebugTypePropertyName = "debugType"; + + internal const string EmitEntryPointPropertyName = "emitEntryPoint"; + + internal const string GenerateXmlDocumentationPropertyName = "xmlDoc"; + + internal const string PortablePropertyName = "portable"; + + internal const string RuntimeTargetNamePropertyName = "name"; + + internal const string RuntimeTargetSignaturePropertyName = "signature"; + + internal const string RuntimesPropertyName = "runtimes"; + + internal const string RuntimeTargetsPropertyName = "runtimeTargets"; + + internal const string RidPropertyName = "rid"; + + internal const string AssetTypePropertyName = "assetType"; + + internal const string RuntimeAssetType = "runtime"; + + internal const string NativeAssetType = "native"; + + internal const string ResourceAssembliesPropertyName = "resources"; + + internal const string LocalePropertyName = "locale"; + + internal const string CompilationOnlyPropertyName = "compileOnly"; + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DirectoryWrapper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DirectoryWrapper.cs new file mode 100644 index 00000000000..25cc2501e71 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DirectoryWrapper.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class DirectoryWrapper: IDirectory + { + public bool Exists(string path) + { + return Directory.Exists(path); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/EnvironmentWrapper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/EnvironmentWrapper.cs new file mode 100644 index 00000000000..827c6ea71f7 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/EnvironmentWrapper.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class EnvironmentWrapper : IEnvironment + { + public static IEnvironment Default = new EnvironmentWrapper(); + + public string GetEnvironmentVariable(string name) + { + return Environment.GetEnvironmentVariable(name); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileSystemWrapper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileSystemWrapper.cs new file mode 100644 index 00000000000..acb5055e65c --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileSystemWrapper.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class FileSystemWrapper : IFileSystem + { + public static IFileSystem Default { get; } = new FileSystemWrapper(); + + public IFile File { get; } = new FileWrapper(); + + public IDirectory Directory { get; } = new DirectoryWrapper(); + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileWrapper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileWrapper.cs new file mode 100644 index 00000000000..50015dd0dba --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileWrapper.cs @@ -0,0 +1,50 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class FileWrapper: IFile + { + public bool Exists(string path) + { + return File.Exists(path); + } + + public string ReadAllText(string path) + { + return File.ReadAllText(path); + } + + public Stream OpenRead(string path) + { + return File.OpenRead(path); + } + + public Stream OpenFile( + string path, + FileMode fileMode, + FileAccess fileAccess, + FileShare fileShare, + int bufferSize, + FileOptions fileOptions) + { + return new FileStream(path, fileMode, fileAccess, fileShare, bufferSize, fileOptions); + } + + public void CreateEmptyFile(string path) + { + try + { + var emptyFile = File.Create(path); + if (emptyFile != null) + { + emptyFile.Dispose(); + } + } + catch { } + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDependencyContextReader.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDependencyContextReader.cs new file mode 100644 index 00000000000..2d5662eb382 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDependencyContextReader.cs @@ -0,0 +1,10 @@ +using System; +using System.IO; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal interface IDependencyContextReader: IDisposable + { + DependencyContext Read(Stream stream); + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDirectory.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDirectory.cs new file mode 100644 index 00000000000..7ce126e0930 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDirectory.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal interface IDirectory + { + bool Exists(string path); + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IEnvironment.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IEnvironment.cs new file mode 100644 index 00000000000..7ed3470fc23 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IEnvironment.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal interface IEnvironment + { + string GetEnvironmentVariable(string name); + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFile.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFile.cs new file mode 100644 index 00000000000..88db2b7a49a --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFile.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal interface IFile + { + bool Exists(string path); + + string ReadAllText(string path); + + Stream OpenRead(string path); + + Stream OpenFile( + string path, + FileMode fileMode, + FileAccess fileAccess, + FileShare fileShare, + int bufferSize, + FileOptions fileOptions); + + void CreateEmptyFile(string path); + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFileSystem.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFileSystem.cs new file mode 100644 index 00000000000..9e01a30c59b --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFileSystem.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal interface IFileSystem + { + IFile File { get; } + IDirectory Directory { get; } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Library.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Library.cs new file mode 100644 index 00000000000..4e182f121b0 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Library.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using System.Collections.Generic; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class Library + { + public Library(string type, + string name, + string version, + string hash, + IEnumerable dependencies, + bool serviceable, + string path, + string hashPath) + : this(type, name, version, hash, dependencies, serviceable, path, hashPath, runtimeStoreManifestName: null) + { + } + public Library(string type, + string name, + string version, + string hash, + IEnumerable dependencies, + bool serviceable, + string path, + string hashPath, + string runtimeStoreManifestName = null) + { + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(nameof(type)); + } + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(nameof(name)); + } + if (string.IsNullOrEmpty(version)) + { + throw new ArgumentException(nameof(version)); + } + if (dependencies == null) + { + throw new ArgumentNullException(nameof(dependencies)); + } + Type = type; + Name = name; + Version = version; + Hash = hash; + Dependencies = dependencies.ToArray(); + Serviceable = serviceable; + Path = path; + HashPath = hashPath; + RuntimeStoreManifestName = runtimeStoreManifestName; + } + + public string Type { get; } + + public string Name { get; } + + public string Version { get; } + + public string Hash { get; } + + public IReadOnlyList Dependencies { get; } + + public bool Serviceable { get; } + + public string Path { get; } + + public string HashPath { get; } + + public string RuntimeStoreManifestName {get;} + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/AppBaseCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/AppBaseCompilationAssemblyResolver.cs new file mode 100644 index 00000000000..72c6c0955ac --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/AppBaseCompilationAssemblyResolver.cs @@ -0,0 +1,110 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using Internal.Microsoft.DotNet.PlatformAbstractions; + +namespace Internal.Microsoft.Extensions.DependencyModel.Resolution +{ + internal class AppBaseCompilationAssemblyResolver : ICompilationAssemblyResolver + { + private static string RefsDirectoryName = "refs"; + private readonly IFileSystem _fileSystem; + private readonly string _basePath; + private readonly DependencyContextPaths _dependencyContextPaths; + + public AppBaseCompilationAssemblyResolver() + : this(FileSystemWrapper.Default) + { + } + + internal AppBaseCompilationAssemblyResolver(IFileSystem fileSystem) + : this(fileSystem, ApplicationEnvironment.ApplicationBasePath, DependencyContextPaths.Current) + { + } + + internal AppBaseCompilationAssemblyResolver(IFileSystem fileSystem, string basePath, DependencyContextPaths dependencyContextPaths) + { + _fileSystem = fileSystem; + _basePath = basePath; + _dependencyContextPaths = dependencyContextPaths; + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + var isProject = string.Equals(library.Type, "project", StringComparison.OrdinalIgnoreCase) || + string.Equals(library.Type, "msbuildproject", StringComparison.OrdinalIgnoreCase); + + var isPackage = string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase); + var isReferenceAssembly = string.Equals(library.Type, "referenceassembly", StringComparison.OrdinalIgnoreCase); + if (!isProject && + !isPackage && + !isReferenceAssembly && + !string.Equals(library.Type, "reference", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var refsPath = Path.Combine(_basePath, RefsDirectoryName); + var isPublished = _fileSystem.Directory.Exists(refsPath); + + // Resolving reference assemblies requires refs folder to exist + if (isReferenceAssembly && !isPublished) + { + return false; + } + + var directories = new List() + { + _basePath + }; + + if (isPublished) + { + directories.Insert(0, refsPath); + } + + // Only packages can come from shared runtime + var sharedPath = _dependencyContextPaths.SharedRuntime; + if (isPublished && isPackage && !string.IsNullOrEmpty(sharedPath)) + { + var sharedDirectory = Path.GetDirectoryName(sharedPath); + var sharedRefs = Path.Combine(sharedDirectory, RefsDirectoryName); + if (_fileSystem.Directory.Exists(sharedRefs)) + { + directories.Add(sharedRefs); + } + directories.Add(sharedDirectory); + } + + var paths = new List(); + + foreach (var assembly in library.Assemblies) + { + bool resolved = false; + var assemblyFile = Path.GetFileName(assembly); + foreach (var directory in directories) + { + string fullName; + if (ResolverUtils.TryResolveAssemblyFile(_fileSystem, directory, assemblyFile, out fullName)) + { + paths.Add(fullName); + resolved = true; + break; + } + } + + if (!resolved) + { + return false; + } + } + + // only modify the assemblies parameter if we've resolved all files + assemblies?.AddRange(paths); + return true; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/CompositeCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/CompositeCompilationAssemblyResolver.cs new file mode 100644 index 00000000000..80011c384cc --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/CompositeCompilationAssemblyResolver.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +namespace Internal.Microsoft.Extensions.DependencyModel.Resolution +{ + internal class CompositeCompilationAssemblyResolver: ICompilationAssemblyResolver + { + private readonly ICompilationAssemblyResolver[] _resolvers; + + public CompositeCompilationAssemblyResolver(ICompilationAssemblyResolver[] resolvers) + { + _resolvers = resolvers; + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + foreach (var resolver in _resolvers) + { + if (resolver.TryResolveAssemblyPaths(library, assemblies)) + { + return true; + } + } + return false; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs new file mode 100644 index 00000000000..488e8aad9c4 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Internal.Microsoft.DotNet.PlatformAbstractions; + +namespace Internal.Microsoft.Extensions.DependencyModel.Resolution +{ + internal class DotNetReferenceAssembliesPathResolver + { + public static readonly string DotNetReferenceAssembliesPathEnv = "DOTNET_REFERENCE_ASSEMBLIES_PATH"; + + internal static string Resolve(IEnvironment envirnment, IFileSystem fileSystem) + { + var path = envirnment.GetEnvironmentVariable(DotNetReferenceAssembliesPathEnv); + if (!string.IsNullOrEmpty(path)) + { + return path; + } + + return GetDefaultDotNetReferenceAssembliesPath(fileSystem); + } + + private static string GetDefaultDotNetReferenceAssembliesPath(IFileSystem fileSystem) + { + var os = RuntimeEnvironment.OperatingSystemPlatform; + + if (os == Platform.Windows) + { + return null; + } + + if (os == Platform.Darwin && + fileSystem.Directory.Exists("/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/xbuild-frameworks")) + { + return "/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/xbuild-frameworks"; + } + + if (fileSystem.Directory.Exists("/usr/local/lib/mono/xbuild-frameworks")) + { + return "/usr/local/lib/mono/xbuild-frameworks"; + } + + if (fileSystem.Directory.Exists("/usr/lib/mono/xbuild-frameworks")) + { + return "/usr/lib/mono/xbuild-frameworks"; + } + + return null; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ICompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ICompilationAssemblyResolver.cs new file mode 100644 index 00000000000..d035cf300c5 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ICompilationAssemblyResolver.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +namespace Internal.Microsoft.Extensions.DependencyModel.Resolution +{ + internal interface ICompilationAssemblyResolver + { + bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies); + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs new file mode 100644 index 00000000000..e268fbee18f --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs @@ -0,0 +1,126 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using Internal.Microsoft.DotNet.PlatformAbstractions; + +namespace Internal.Microsoft.Extensions.DependencyModel.Resolution +{ + internal class PackageCompilationAssemblyResolver: ICompilationAssemblyResolver + { + private readonly IFileSystem _fileSystem; + private readonly string[] _nugetPackageDirectories; + + public PackageCompilationAssemblyResolver() + : this(EnvironmentWrapper.Default, FileSystemWrapper.Default) + { + } + + public PackageCompilationAssemblyResolver(string nugetPackageDirectory) + : this(FileSystemWrapper.Default, new string[] { nugetPackageDirectory }) + { + } + + internal PackageCompilationAssemblyResolver(IEnvironment environment, + IFileSystem fileSystem) + : this(fileSystem, GetDefaultProbeDirectories(environment)) + { + } + + internal PackageCompilationAssemblyResolver(IFileSystem fileSystem, string[] nugetPackageDirectories) + { + _fileSystem = fileSystem; + _nugetPackageDirectories = nugetPackageDirectories; + } + + private static string[] GetDefaultProbeDirectories(IEnvironment environment) => + GetDefaultProbeDirectories(RuntimeEnvironment.OperatingSystemPlatform, environment); + + internal static string[] GetDefaultProbeDirectories(Platform osPlatform, IEnvironment environment) + { + var probeDirectories = AppContext.GetData("PROBING_DIRECTORIES"); + + var listOfDirectories = probeDirectories as string; + + if (!string.IsNullOrEmpty(listOfDirectories)) + { + return listOfDirectories.Split(new char [] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries ); + } + + var packageDirectory = environment.GetEnvironmentVariable("NUGET_PACKAGES"); + + if (!string.IsNullOrEmpty(packageDirectory)) + { + return new string[] { packageDirectory }; + } + + string basePath; + if (osPlatform == Platform.Windows) + { + basePath = environment.GetEnvironmentVariable("USERPROFILE"); + } + else + { + basePath = environment.GetEnvironmentVariable("HOME"); + } + + if (string.IsNullOrEmpty(basePath)) + { + return new string[] { string.Empty }; + } + + return new string[] { Path.Combine(basePath, ".nuget", "packages") }; + + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + if (_nugetPackageDirectories == null || _nugetPackageDirectories.Length == 0 || + !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + foreach (var directory in _nugetPackageDirectories) + { + string packagePath; + + if (ResolverUtils.TryResolvePackagePath(_fileSystem, library, directory, out packagePath)) + { + IEnumerable fullPathsFromPackage; + if (TryResolveFromPackagePath(_fileSystem, library, packagePath, out fullPathsFromPackage)) + { + assemblies.AddRange(fullPathsFromPackage); + return true; + } + } + } + return false; + } + + private static bool TryResolveFromPackagePath(IFileSystem fileSystem, CompilationLibrary library, string basePath, out IEnumerable results) + { + var paths = new List(); + + foreach (var assembly in library.Assemblies) + { + string fullName; + if (!ResolverUtils.TryResolveAssemblyFile(fileSystem, basePath, assembly, out fullName)) + { + // if one of the files can't be found, skip this package path completely. + // there are package paths that don't include all of the "ref" assemblies + // (ex. ones created by 'dotnet store') + results = null; + return false; + } + + paths.Add(fullName); + } + + results = paths; + return true; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs new file mode 100644 index 00000000000..6aa6c1fa589 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs @@ -0,0 +1,140 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using Internal.Microsoft.DotNet.PlatformAbstractions; + +namespace Internal.Microsoft.Extensions.DependencyModel.Resolution +{ + internal class ReferenceAssemblyPathResolver: ICompilationAssemblyResolver + { + private readonly IFileSystem _fileSystem; + private readonly string _defaultReferenceAssembliesPath; + private readonly string[] _fallbackSearchPaths; + + public ReferenceAssemblyPathResolver() + : this(FileSystemWrapper.Default, EnvironmentWrapper.Default) + { + } + + public ReferenceAssemblyPathResolver(string defaultReferenceAssembliesPath, string[] fallbackSearchPaths) + : this(FileSystemWrapper.Default, defaultReferenceAssembliesPath, fallbackSearchPaths) + { + } + + internal ReferenceAssemblyPathResolver(IFileSystem fileSystem, IEnvironment environment) + : this(fileSystem, + GetDefaultReferenceAssembliesPath(fileSystem, RuntimeEnvironment.OperatingSystemPlatform, environment), + GetFallbackSearchPaths(fileSystem, RuntimeEnvironment.OperatingSystemPlatform, environment)) + { + } + + internal ReferenceAssemblyPathResolver(IFileSystem fileSystem, string defaultReferenceAssembliesPath, string[] fallbackSearchPaths) + { + _fileSystem = fileSystem; + _defaultReferenceAssembliesPath = defaultReferenceAssembliesPath; + _fallbackSearchPaths = fallbackSearchPaths; + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + if (!string.Equals(library.Type, "referenceassembly", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + foreach (var assembly in library.Assemblies) + { + string fullName; + if (!TryResolveReferenceAssembly(assembly, out fullName)) + { + throw new InvalidOperationException($"Cannot find reference assembly '{assembly}' file for package {library.Name}"); + } + assemblies.Add(fullName); + } + return true; + } + + private bool TryResolveReferenceAssembly(string path, out string fullPath) + { + fullPath = null; + + if (_defaultReferenceAssembliesPath != null) + { + var relativeToReferenceAssemblies = Path.Combine(_defaultReferenceAssembliesPath, path); + if (_fileSystem.File.Exists(relativeToReferenceAssemblies)) + { + fullPath = relativeToReferenceAssemblies; + return true; + } + } + + var name = Path.GetFileName(path); + foreach (var fallbackPath in _fallbackSearchPaths) + { + var fallbackFile = Path.Combine(fallbackPath, name); + if (_fileSystem.File.Exists(fallbackFile)) + { + fullPath = fallbackFile; + return true; + } + } + + return false; + } + + internal static string[] GetFallbackSearchPaths(IFileSystem fileSystem, Platform platform, IEnvironment environment) + { + if (platform != Platform.Windows) + { + return new string[0]; + } + + var net20Dir = Path.Combine(environment.GetEnvironmentVariable("WINDIR"), "Microsoft.NET", "Framework", "v2.0.50727"); + + if (!fileSystem.Directory.Exists(net20Dir)) + { + return new string[0]; + } + return new[] { net20Dir }; + } + + internal static string GetDefaultReferenceAssembliesPath(IFileSystem fileSystem, Platform platform, IEnvironment environment) + { + // Allow setting the reference assemblies path via an environment variable + var referenceAssembliesPath = DotNetReferenceAssembliesPathResolver.Resolve(environment, fileSystem); + if (!string.IsNullOrEmpty(referenceAssembliesPath)) + { + return referenceAssembliesPath; + } + + if (platform != Platform.Windows) + { + // There is no reference assemblies path outside of windows + // The environment variable can be used to specify one + return null; + } + + // References assemblies are in %ProgramFiles(x86)% on + // 64 bit machines + var programFiles = environment.GetEnvironmentVariable("ProgramFiles(x86)"); + + if (string.IsNullOrEmpty(programFiles)) + { + // On 32 bit machines they are in %ProgramFiles% + programFiles = environment.GetEnvironmentVariable("ProgramFiles"); + } + + if (string.IsNullOrEmpty(programFiles)) + { + // Reference assemblies aren't installed + return null; + } + + return Path.Combine( + programFiles, + "Reference Assemblies", "Microsoft", "Framework"); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ResolverUtils.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ResolverUtils.cs new file mode 100644 index 00000000000..e1002b4b64c --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ResolverUtils.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; + +namespace Internal.Microsoft.Extensions.DependencyModel.Resolution +{ + internal static class ResolverUtils + { + internal static bool TryResolvePackagePath(IFileSystem fileSystem, CompilationLibrary library, string basePath, out string packagePath) + { + var path = library.Path; + if (string.IsNullOrEmpty(path)) + { + path = Path.Combine(library.Name.ToLowerInvariant(), library.Version.ToLowerInvariant()); + } + + packagePath = Path.Combine(basePath, path); + + if (fileSystem.Directory.Exists(packagePath)) + { + return true; + } + return false; + } + + internal static bool TryResolveAssemblyFile(IFileSystem fileSystem, string basePath, string assemblyPath, out string fullName) + { + fullName = Path.GetFullPath(Path.Combine(basePath, assemblyPath)); + if (fileSystem.File.Exists(fullName)) + { + return true; + } + return false; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs new file mode 100644 index 00000000000..427f9670b57 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class ResourceAssembly + { + public ResourceAssembly(string path, string locale) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException(nameof(path)); + } + if (string.IsNullOrEmpty(locale)) + { + throw new ArgumentException(nameof(locale)); + } + Locale = locale; + Path = path; + } + + public string Locale { get; set; } + + public string Path { get; set; } + + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeAssetGroup.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeAssetGroup.cs new file mode 100644 index 00000000000..ba3ad90bb18 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeAssetGroup.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; +using System.Collections.Generic; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class RuntimeAssetGroup + { + public RuntimeAssetGroup(string runtime, params string[] assetPaths) : this(runtime, (IEnumerable)assetPaths) { } + + public RuntimeAssetGroup(string runtime, IEnumerable assetPaths) + { + Runtime = runtime; + AssetPaths = assetPaths.ToArray(); + } + + /// + /// The runtime ID associated with this group (may be empty if the group is runtime-agnostic) + /// + public string Runtime { get; } + + /// + /// Gets a list of assets provided in this runtime group + /// + public IReadOnlyList AssetPaths { get; } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs new file mode 100644 index 00000000000..acdee225b44 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class RuntimeFallbacks + { + public string Runtime { get; set; } + public IReadOnlyList Fallbacks { get; set; } + + public RuntimeFallbacks(string runtime, params string[] fallbacks) : this(runtime, (IEnumerable)fallbacks) { } + public RuntimeFallbacks(string runtime, IEnumerable fallbacks) + { + if (string.IsNullOrEmpty(runtime)) + { + throw new ArgumentException(nameof(runtime)); + } + if (fallbacks == null) + { + throw new ArgumentNullException(nameof(fallbacks)); + } + Runtime = runtime; + Fallbacks = fallbacks.ToArray(); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs new file mode 100644 index 00000000000..a875f871863 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class RuntimeLibrary : Library + { + public RuntimeLibrary(string type, + string name, + string version, + string hash, + IReadOnlyList runtimeAssemblyGroups, + IReadOnlyList nativeLibraryGroups, + IEnumerable resourceAssemblies, + IEnumerable dependencies, + bool serviceable, + string path, + string hashPath) + : this(type, + name, + version, + hash, + runtimeAssemblyGroups, + nativeLibraryGroups, + resourceAssemblies, + dependencies, + serviceable, + path, + hashPath, + runtimeStoreManifestName : null) + { + } + + public RuntimeLibrary(string type, + string name, + string version, + string hash, + IReadOnlyList runtimeAssemblyGroups, + IReadOnlyList nativeLibraryGroups, + IEnumerable resourceAssemblies, + IEnumerable dependencies, + bool serviceable, + string path, + string hashPath, + string runtimeStoreManifestName) + : base(type, + name, + version, + hash, + dependencies, + serviceable, + path, + hashPath, + runtimeStoreManifestName) + { + if (runtimeAssemblyGroups == null) + { + throw new ArgumentNullException(nameof(runtimeAssemblyGroups)); + } + if (nativeLibraryGroups == null) + { + throw new ArgumentNullException(nameof(nativeLibraryGroups)); + } + if (resourceAssemblies == null) + { + throw new ArgumentNullException(nameof(resourceAssemblies)); + } + RuntimeAssemblyGroups = runtimeAssemblyGroups; + ResourceAssemblies = resourceAssemblies.ToArray(); + NativeLibraryGroups = nativeLibraryGroups; + } + + public IReadOnlyList RuntimeAssemblyGroups { get; } + + public IReadOnlyList NativeLibraryGroups { get; } + + public IReadOnlyList ResourceAssemblies { get; } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/TargetInfo.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/TargetInfo.cs new file mode 100644 index 00000000000..980b5a2529c --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/TargetInfo.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Internal.Microsoft.Extensions.DependencyModel +{ + internal class TargetInfo + { + public TargetInfo(string framework, + string runtime, + string runtimeSignature, + bool isPortable) + { + if (string.IsNullOrEmpty(framework)) + { + throw new ArgumentException(nameof(framework)); + } + + Framework = framework; + Runtime = runtime; + RuntimeSignature = runtimeSignature; + IsPortable = isPortable; + } + + public string Framework { get; } + + public string Runtime { get; } + + public string RuntimeSignature { get; } + + public bool IsPortable { get; } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs new file mode 100644 index 00000000000..32974a0c190 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs @@ -0,0 +1,101 @@ +// Adapted from https://github.com/dotnet/core-setup/blob/652b680dff6b1afb9cd26cc3c2e883a664c209fd/src/managed/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Internal.Microsoft.DotNet.PlatformAbstractions; +using Internal.Microsoft.Extensions.DependencyModel; +using Internal.Microsoft.Extensions.DependencyModel.Resolution; +using Xunit.Abstractions; + +namespace Xunit +{ + class XunitPackageCompilationAssemblyResolver : ICompilationAssemblyResolver + { + readonly IFileSystem fileSystem; + readonly List nugetPackageDirectories; + + public XunitPackageCompilationAssemblyResolver(IMessageSink internalDiagnosticsMessageSink, + IFileSystem fileSystem = null) + { + nugetPackageDirectories = GetDefaultProbeDirectories(internalDiagnosticsMessageSink); + this.fileSystem = fileSystem ?? new FileSystemWrapper(); + } + + static List GetDefaultProbeDirectories(IMessageSink internalDiagnosticsMessageSink) => + GetDefaultProbeDirectories(RuntimeEnvironment.OperatingSystemPlatform, internalDiagnosticsMessageSink); + + static List GetDefaultProbeDirectories(Platform osPlatform, IMessageSink internalDiagnosticsMessageSink) + { + var results = default(List); + + var probeDirectories = AppContext.GetData("PROBING_DIRECTORIES") as string; + if (!string.IsNullOrEmpty(probeDirectories)) + results = probeDirectories.Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries).ToList(); + else + { + results = new List(); + + // Allow the user to override the default location of NuGet packages + var packageDirectory = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); + if (!string.IsNullOrEmpty(packageDirectory)) + results.Add(packageDirectory); + else + { + string basePath; + if (osPlatform == Platform.Windows) + basePath = Environment.GetEnvironmentVariable("USERPROFILE"); + else + basePath = Environment.GetEnvironmentVariable("HOME"); + + if (!string.IsNullOrEmpty(basePath)) + results.Add(Path.Combine(basePath, ".nuget", "packages")); + } + } + + if (internalDiagnosticsMessageSink != null) + internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[XunitPackageCompilationAssemblyResolver.GetDefaultProbeDirectories] returns: [{string.Join(",", results.Select(x => $"'{x}'"))}]")); + + return results; + } + + public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) + { + if (nugetPackageDirectories.Count == 0 || !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase)) + return false; + + foreach (var directory in nugetPackageDirectories) + if (ResolverUtils.TryResolvePackagePath(fileSystem, library, directory, out var packagePath)) + if (TryResolveFromPackagePath(library, packagePath, out var fullPathsFromPackage)) + { + assemblies.AddRange(fullPathsFromPackage); + return true; + } + + return false; + } + + bool TryResolveFromPackagePath(CompilationLibrary library, string basePath, out IEnumerable results) + { + var paths = new List(); + + foreach (var assembly in library.Assemblies) + { + if (!ResolverUtils.TryResolveAssemblyFile(fileSystem, basePath, assembly, out var fullName)) + { + // if one of the files can't be found, skip this package path completely. + // there are package paths that don't include all of the "ref" assemblies + // (ex. ones created by 'dotnet store') + results = null; + return false; + } + + paths.Add(fullName); + } + + results = paths; + return true; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/_DiagnosticMessage.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/_DiagnosticMessage.cs new file mode 100644 index 00000000000..693c93f99d9 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/_DiagnosticMessage.cs @@ -0,0 +1,19 @@ +using Xunit.Abstractions; + +namespace Xunit +{ + class _DiagnosticMessage : IDiagnosticMessage + { + /// + /// Initializes a new instance of the class. + /// + /// The message to send + public _DiagnosticMessage(string message) + { + Message = message; + } + + /// + public string Message { get; set; } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/ConsoleHelper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/ConsoleHelper.cs new file mode 100644 index 00000000000..6a47a2a2255 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/ConsoleHelper.cs @@ -0,0 +1,66 @@ +// This code was adapted from https://github.com/Microsoft/msbuild/blob/ab090d1255caa87e742cbdbc6d7fe904ecebd975/src/Build/Logging/BaseConsoleLogger.cs#L361-L401 +// Under the MIT license https://github.com/Microsoft/msbuild/blob/ab090d1255caa87e742cbdbc6d7fe904ecebd975/LICENSE + +using System; + +namespace Xunit +{ + internal static class ConsoleHelper + { + internal static Action ResetColor; + internal static Action SetForegroundColor; + + static ConsoleHelper() + { + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + { + ResetColor = ResetColorConsole; + SetForegroundColor = SetForegroundColorConsole; + } + else + { + ResetColor = ResetColorANSI; + SetForegroundColor = SetForegroundColorANSI; + } + } + + static void SetForegroundColorANSI(ConsoleColor c) + { + string colorString = "\x1b["; + switch (c) + { + case ConsoleColor.Black: colorString += "30"; break; + case ConsoleColor.DarkBlue: colorString += "34"; break; + case ConsoleColor.DarkGreen: colorString += "32"; break; + case ConsoleColor.DarkCyan: colorString += "36"; break; + case ConsoleColor.DarkRed: colorString += "31"; break; + case ConsoleColor.DarkMagenta: colorString += "35"; break; + case ConsoleColor.DarkYellow: colorString += "33"; break; + case ConsoleColor.Gray: colorString += "37"; break; + case ConsoleColor.DarkGray: colorString += "30;1"; break; + case ConsoleColor.Blue: colorString += "34;1"; break; + case ConsoleColor.Green: colorString += "32;1"; break; + case ConsoleColor.Cyan: colorString += "36;1"; break; + case ConsoleColor.Red: colorString += "31;1"; break; + case ConsoleColor.Magenta: colorString += "35;1"; break; + case ConsoleColor.Yellow: colorString += "33;1"; break; + case ConsoleColor.White: colorString += "37;1"; break; + default: colorString = ""; break; + } + if ("" != colorString) + { + colorString += "m"; + Console.Out.Write(colorString); + } + } + + static void SetForegroundColorConsole(ConsoleColor c) + => Console.ForegroundColor = c; + + static void ResetColorANSI() + => Console.Out.Write("\x1b[m"); + + static void ResetColorConsole() + => Console.ResetColor(); + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/DictionaryExtensions.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/DictionaryExtensions.cs new file mode 100644 index 00000000000..2dd72676b3f --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/DictionaryExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +static class DictionaryExtensions +{ + public static void Add(this IDictionary> dictionary, TKey key, TValue value) + { + dictionary.GetOrAdd(key).Add(value); + } + + public static TValue GetOrAdd(this IDictionary dictionary, TKey key) + where TValue : new() + { + return dictionary.GetOrAdd(key, () => new TValue()); + } + + public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func newValue) + { + TValue result; + + if (!dictionary.TryGetValue(key, out result)) + { + result = newValue(); + dictionary[key] = result; + } + + return result; + } + + public static Dictionary ToDictionaryIgnoringDuplicateKeys(this IEnumerable inputValues, + Func keySelector, + Func valueSelector, + IEqualityComparer comparer = null) + { + var result = new Dictionary(comparer); + + foreach (var inputValue in inputValues) + { + var key = keySelector(inputValue); + if (!result.ContainsKey(key)) + result.Add(key, valueSelector(inputValue)); + } + + return result; + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/Json.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/Json.cs new file mode 100644 index 00000000000..c31f0aac213 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/Json.cs @@ -0,0 +1,879 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace Xunit +{ + class JsonArray : JsonValue + { + readonly JsonValue[] _array; + + public JsonArray(JsonValue[] array, int line, int column) + : base(line, column) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + _array = array; + } + + public int Length + { + get { return _array.Length; } + } + + public JsonValue this[int index] + { + get { return _array[index]; } + } + } + + class JsonBoolean : JsonValue + { + public JsonBoolean(JsonToken token) + : base(token.Line, token.Column) + { + if (token.Type == JsonTokenType.True) + { + Value = true; + } + else if (token.Type == JsonTokenType.False) + { + Value = false; + } + else + { + throw new ArgumentException("Token value should be either True or False.", nameof(token)); + } + } + + public bool Value { get; private set; } + + public static implicit operator bool (JsonBoolean jsonBoolean) + { + return jsonBoolean.Value; + } + } + + class JsonBuffer + { + public const string ValueNull = "null"; + public const string ValueTrue = "true"; + public const string ValueFalse = "false"; + + StringBuilder _buffer = new StringBuilder(); + StringBuilder _codePointBuffer = new StringBuilder(4); + readonly TextReader _reader; + JsonToken _token; + int _line; + int _column; + + public JsonBuffer(TextReader reader) + { + _reader = reader; + _line = 1; + } + + public JsonToken Read() + { + int first; + while (true) + { + first = ReadNextChar(); + + if (first == -1) + { + _token.Type = JsonTokenType.EOF; + return _token; + } + else if (!IsWhitespace(first)) + { + break; + } + } + + _token.Value = ((char)first).ToString(); + _token.Line = _line; + _token.Column = _column; + + if (first == '{') + { + _token.Type = JsonTokenType.LeftCurlyBracket; + } + else if (first == '}') + { + _token.Type = JsonTokenType.RightCurlyBracket; + } + else if (first == '[') + { + _token.Type = JsonTokenType.LeftSquareBracket; + } + else if (first == ']') + { + _token.Type = JsonTokenType.RightSquareBracket; + } + else if (first == ':') + { + _token.Type = JsonTokenType.Colon; + } + else if (first == ',') + { + _token.Type = JsonTokenType.Comma; + } + else if (first == '"') + { + _token.Type = JsonTokenType.String; + _token.Value = ReadString(); + } + else if (first == 't') + { + ReadLiteral(ValueTrue); + _token.Type = JsonTokenType.True; + } + else if (first == 'f') + { + ReadLiteral(ValueFalse); + _token.Type = JsonTokenType.False; + } + else if (first == 'n') + { + ReadLiteral(ValueNull); + _token.Type = JsonTokenType.Null; + } + else if ((first >= '0' && first <= '9') || first == '-') + { + _token.Type = JsonTokenType.Number; + _token.Value = ReadNumber(first); + } + else + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_IllegalCharacter(first), + _token); + } + + // JsonToken is a value type + return _token; + } + + int ReadNextChar() + { + while (true) + { + var value = _reader.Read(); + _column++; + switch (value) + { + case -1: + // This is the end of file + return -1; + case '\n': + // This is a new line. Let the next loop read the first character of the following line. + // Set position ahead of next line + _column = 0; + _line++; + + continue; + case '\r': + break; + default: + // Returns the normal value + return value; + } + } + } + + string ReadNumber(int firstRead) + { +#if NET35 + _buffer = new StringBuilder(); +#else + _buffer.Clear(); +#endif + _buffer.Append((char)firstRead); + + while (true) + { + var next = _reader.Peek(); + + if ((next >= '0' && next <= '9') || + next == '.' || + next == 'e' || + next == 'E') + { + _buffer.Append((char)ReadNextChar()); + } + else + { + break; + } + } + + return _buffer.ToString(); + } + + void ReadLiteral(string literal) + { + for (int i = 1; i < literal.Length; ++i) + { + var next = _reader.Peek(); + if (next != literal[i]) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_UnrecognizedLiteral(literal), + _line, _column); + } + else + { + ReadNextChar(); + } + } + + var tail = _reader.Peek(); + if (tail != '}' && + tail != ']' && + tail != ',' && + tail != '\n' && + tail != -1 && + !IsWhitespace(tail)) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_IllegalTrailingCharacterAfterLiteral(tail, literal), + _line, _column); + } + } + + string ReadString() + { +#if NET35 + _buffer = new StringBuilder(); +#else + _buffer.Clear(); +#endif + var escaped = false; + + while (true) + { + var next = ReadNextChar(); + + if (next == -1 || next == '\n') + { + throw new JsonDeserializerException( + JsonDeserializerResource.JSON_OpenString, + _line, _column); + } + else if (escaped) + { + if ((next == '"') || (next == '\\') || (next == '/')) + { + _buffer.Append((char)next); + } + else if (next == 'b') + { + // '\b' backspace + _buffer.Append('\b'); + } + else if (next == 'f') + { + // '\f' form feed + _buffer.Append('\f'); + } + else if (next == 'n') + { + // '\n' line feed + _buffer.Append('\n'); + } + else if (next == 'r') + { + // '\r' carriage return + _buffer.Append('\r'); + } + else if (next == 't') + { + // '\t' tab + _buffer.Append('\t'); + } + else if (next == 'u') + { + // '\uXXXX' unicode + var unicodeLine = _line; + var unicodeColumn = _column; + +#if NET35 + _codePointBuffer = new StringBuilder(4); +#else + _codePointBuffer.Clear(); +#endif + for (int i = 0; i < 4; ++i) + { + next = ReadNextChar(); + if (next == -1) + { + throw new JsonDeserializerException( + JsonDeserializerResource.JSON_InvalidEnd, + unicodeLine, + unicodeColumn); + } + else + { + _codePointBuffer[i] = (char)next; + } + } + + try + { + var unicodeValue = int.Parse(_codePointBuffer.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + _buffer.Append((char)unicodeValue); + } + catch (FormatException ex) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidUnicode(_codePointBuffer.ToString()), + ex, + unicodeLine, + unicodeColumn); + } + } + else + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxNotExpected("character escape", "\\" + next), + _line, + _column); + } + + escaped = false; + } + else if (next == '\\') + { + escaped = true; + } + else if (next == '"') + { + break; + } + else + { + _buffer.Append((char)next); + } + } + + return _buffer.ToString(); + } + + static bool IsWhitespace(int value) + { + return value == ' ' || value == '\t' || value == '\r'; + } + } + + static class JsonDeserializer + { + public static JsonValue Deserialize(TextReader reader) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + var buffer = new JsonBuffer(reader); + + var result = DeserializeInternal(buffer.Read(), buffer); + + // There are still unprocessed char. The parsing is not finished. Error happened. + var nextToken = buffer.Read(); + if (nextToken.Type != JsonTokenType.EOF) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_UnfinishedJSON(nextToken.Value), + nextToken); + } + + return result; + } + + static JsonValue DeserializeInternal(JsonToken next, JsonBuffer buffer) + { + if (next.Type == JsonTokenType.EOF) + { + return null; + } + + if (next.Type == JsonTokenType.LeftSquareBracket) + { + return DeserializeArray(next, buffer); + } + + if (next.Type == JsonTokenType.LeftCurlyBracket) + { + return DeserializeObject(next, buffer); + } + + if (next.Type == JsonTokenType.String) + { + return new JsonString(next.Value, next.Line, next.Column); + } + + if (next.Type == JsonTokenType.True || next.Type == JsonTokenType.False) + { + return new JsonBoolean(next); + } + + if (next.Type == JsonTokenType.Null) + { + return new JsonNull(next.Line, next.Column); + } + + if (next.Type == JsonTokenType.Number) + { + return new JsonNumber(next); + } + + throw new JsonDeserializerException(JsonDeserializerResource.Format_InvalidTokenExpectation( + next.Value, "'{', '[', true, false, null, JSON string, JSON number, or the end of the file"), + next); + } + + static JsonArray DeserializeArray(JsonToken head, JsonBuffer buffer) + { + var list = new List(); + while (true) + { + var next = buffer.Read(); + if (next.Type == JsonTokenType.RightSquareBracket) + { + break; + } + + list.Add(DeserializeInternal(next, buffer)); + + next = buffer.Read(); + if (next.Type == JsonTokenType.EOF) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON array", ']', ','), + next); + } + else if (next.Type == JsonTokenType.RightSquareBracket) + { + break; + } + else if (next.Type != JsonTokenType.Comma) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON array", ','), + next); + } + } + + return new JsonArray(list.ToArray(), head.Line, head.Column); + } + + static JsonObject DeserializeObject(JsonToken head, JsonBuffer buffer) + { + var dictionary = new Dictionary(); + + // Loop through each JSON entry in the input object + while (true) + { + var next = buffer.Read(); + if (next.Type == JsonTokenType.EOF) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", '}'), + next); + } + + if (next.Type == JsonTokenType.Colon) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxNotExpected("JSON object", ':'), + next); + } + else if (next.Type == JsonTokenType.RightCurlyBracket) + { + break; + } + else + { + if (next.Type != JsonTokenType.String) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object member name", "JSON string"), + next); + } + + var memberName = next.Value; + if (dictionary.ContainsKey(memberName)) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_DuplicateObjectMemberName(memberName), + next); + } + + next = buffer.Read(); + if (next.Type != JsonTokenType.Colon) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", ':'), + next); + } + + dictionary[memberName] = DeserializeInternal(buffer.Read(), buffer); + + next = buffer.Read(); + if (next.Type == JsonTokenType.RightCurlyBracket) + { + break; + } + else if (next.Type != JsonTokenType.Comma) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", ',', '}'), + next); + } + } + } + + return new JsonObject(dictionary, head.Line, head.Column); + } + } + + class JsonDeserializerException : Exception + { + public JsonDeserializerException(string message, Exception innerException, int line, int column) + : base(message, innerException) + { + Line = line; + Column = column; + } + + public JsonDeserializerException(string message, int line, int column) + : base(message) + { + Line = line; + Column = column; + } + + public JsonDeserializerException(string message, JsonToken nextToken) + : base(message) + { + Line = nextToken.Line; + Column = nextToken.Column; + } + + public int Line { get; } + + public int Column { get; } + } + + static class JsonDeserializerResource + { + internal static string Format_IllegalCharacter(int value) + { + return $"Illegal character '{(char)value}' (Unicode hexadecimal {value:X4})."; + } + + internal static string Format_IllegalTrailingCharacterAfterLiteral(int value, string literal) + { + return $"Illegal character '{(char)value}' (Unicode hexadecimal {value:X4}) after the literal name '{literal}'."; + } + + internal static string Format_UnrecognizedLiteral(string literal) + { + return $"Invalid JSON literal. Expected literal '{literal}'."; + } + + internal static string Format_DuplicateObjectMemberName(string memberName) + { + return Format_InvalidSyntax("JSON object", $"Duplicate member name '{memberName}'"); + } + + internal static string Format_InvalidFloatNumberFormat(string raw) + { + return $"Invalid float number format: {raw}"; + } + + internal static string Format_FloatNumberOverflow(string raw) + { + return $"Float number overflow: {raw}"; + } + + internal static string Format_InvalidSyntax(string syntaxName, string issue) + { + return $"Invalid {syntaxName} syntax. {issue}."; + } + + internal static string Format_InvalidSyntaxNotExpected(string syntaxName, char unexpected) + { + return $"Invalid {syntaxName} syntax. Unexpected '{unexpected}'."; + } + + internal static string Format_InvalidSyntaxNotExpected(string syntaxName, string unexpected) + { + return $"Invalid {syntaxName} syntax. Unexpected {unexpected}."; + } + + internal static string Format_InvalidSyntaxExpectation(string syntaxName, char expectation) + { + return $"Invalid {syntaxName} syntax. Expected '{expectation}'."; + } + + internal static string Format_InvalidSyntaxExpectation(string syntaxName, string expectation) + { + return $"Invalid {syntaxName} syntax. Expected {expectation}."; + } + + internal static string Format_InvalidSyntaxExpectation(string syntaxName, char expectation1, char expectation2) + { + return $"Invalid {syntaxName} syntax. Expected '{expectation1}' or '{expectation2}'."; + } + + internal static string Format_InvalidTokenExpectation(string tokenValue, string expectation) + { + return $"Unexpected token '{tokenValue}'. Expected {expectation}."; + } + + internal static string Format_InvalidUnicode(string unicode) + { + return $"Invalid Unicode [{unicode}]"; + } + + internal static string Format_UnfinishedJSON(string nextTokenValue) + { + return $"Invalid JSON end. Unprocessed token {nextTokenValue}."; + } + + internal static string JSON_OpenString + { + get { return Format_InvalidSyntaxExpectation("JSON string", '\"'); } + } + + internal static string JSON_InvalidEnd + { + get { return "Invalid JSON. Unexpected end of file."; } + } + } + + class JsonNull : JsonValue + { + public JsonNull(int line, int column) + : base(line, column) + { + } + } + + class JsonNumber : JsonValue + { + readonly string _raw; + readonly double _double; + + public JsonNumber(JsonToken token) + : base(token.Line, token.Column) + { + try + { + _raw = token.Value; + _double = double.Parse(_raw, NumberStyles.Float); + } + catch (FormatException ex) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_InvalidFloatNumberFormat(_raw), + ex, + token.Line, + token.Column); + } + catch (OverflowException ex) + { + throw new JsonDeserializerException( + JsonDeserializerResource.Format_FloatNumberOverflow(_raw), + ex, + token.Line, + token.Column); + } + } + + public double Double + { + get { return _double; } + } + + public string Raw + { + get { return _raw; } + } + } + + class JsonObject : JsonValue + { + readonly IDictionary _data; + + public JsonObject(IDictionary data, int line, int column) + : base(line, column) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + _data = data; + } + + public ICollection Keys + { + get { return _data.Keys; } + } + + public JsonValue Value(string key) + { + JsonValue result; + if (!_data.TryGetValue(key, out result)) + { + result = null; + } + + return result; + } + + public JsonObject ValueAsJsonObject(string key) + { + return Value(key) as JsonObject; + } + + public JsonString ValueAsString(string key) + { + return Value(key) as JsonString; + } + + public int ValueAsInt(string key) + { + var number = Value(key) as JsonNumber; + if (number == null) + { + throw new FormatException(); + } + return Convert.ToInt32(number.Raw); + } + + public bool ValueAsBoolean(string key, bool defaultValue = false) + { + var boolVal = Value(key) as JsonBoolean; + if (boolVal != null) + { + return boolVal.Value; + } + + return defaultValue; + } + + public bool? ValueAsNullableBoolean(string key) + { + var boolVal = Value(key) as JsonBoolean; + if (boolVal != null) + { + return boolVal.Value; + } + + return null; + } + + public string[] ValueAsStringArray(string key) + { + var list = Value(key) as JsonArray; + if (list == null) + { + return null; + } + + var result = new string[list.Length]; + + for (int i = 0; i < list.Length; ++i) + { + var jsonString = list[i] as JsonString; + result[i] = jsonString?.ToString(); + } + + return result; + } + } + + class JsonString : JsonValue + { + readonly string _value; + + public JsonString(string value, int line, int column) + : base(line, column) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _value = value; + } + + public string Value + { + get { return _value; } + } + + public override string ToString() + { + return _value; + } + + public static implicit operator string (JsonString instance) + { + if (instance == null) + { + return null; + } + else + { + return instance.Value; + } + } + } + + struct JsonToken + { + public JsonTokenType Type; + public string Value; + public int Line; + public int Column; + } + + enum JsonTokenType + { + LeftCurlyBracket, // [ + LeftSquareBracket, // { + RightCurlyBracket, // ] + RightSquareBracket, // } + Colon, // : + Comma, // , + Null, + True, + False, + Number, + String, + EOF + } + + class JsonValue + { + public JsonValue(int line, int column) + { + Line = line; + Column = column; + } + + public int Line { get; } + + public int Column { get; } + } +} diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/xUnit1.xslt b/src/Microsoft.DotNet.XUnitConsoleRunner/src/xUnit1.xslt new file mode 100644 index 00000000000..a8425d36efb --- /dev/null +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/xUnit1.xslt @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.props b/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.props deleted file mode 100644 index 0c98d167d19..00000000000 --- a/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.props +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.targets b/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.targets deleted file mode 100644 index 0c98d167d19..00000000000 --- a/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.targets +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.Uap.TestTools.sln b/src/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.Uap.TestTools.sln deleted file mode 100644 index 8e39506cab5..00000000000 --- a/src/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.Uap.TestTools.sln +++ /dev/null @@ -1,65 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28119.50 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsStoreAppLauncher", "Launcher\WindowsStoreAppLauncher.vcxproj", "{177AA879-D233-4A16-8398-8BDF5D5E1ED8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.XUnitRunnerUap", "src\Microsoft.DotNet.XUnitRunnerUap.csproj", "{552DECA2-68D7-4506-86DE-E808D1E60FB6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM = Debug|ARM - Debug|ARM64 = Debug|ARM64 - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|ARM = Release|ARM - Release|ARM64 = Release|ARM64 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|ARM.ActiveCfg = Release|x64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|ARM.Build.0 = Release|x64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|ARM64.ActiveCfg = Release|ARM64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|ARM64.Build.0 = Release|ARM64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|x64.ActiveCfg = Release|x64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|x64.Build.0 = Release|x64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|x86.ActiveCfg = Release|Win32 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|x86.Build.0 = Release|Win32 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|ARM.ActiveCfg = Release|Win32 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|ARM64.ActiveCfg = Release|ARM64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|ARM64.Build.0 = Release|ARM64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|x64.ActiveCfg = Release|x64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|x64.Build.0 = Release|x64 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|x86.ActiveCfg = Release|Win32 - {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|x86.Build.0 = Release|Win32 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM.ActiveCfg = Debug|ARM - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM.Build.0 = Debug|ARM - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM.Deploy.0 = Debug|ARM - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM64.Build.0 = Debug|ARM64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x64.ActiveCfg = Debug|x64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x64.Build.0 = Debug|x64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x64.Deploy.0 = Debug|x64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x86.ActiveCfg = Debug|x86 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x86.Build.0 = Debug|x86 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x86.Deploy.0 = Debug|x86 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM.ActiveCfg = Release|ARM - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM.Build.0 = Release|ARM - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM.Deploy.0 = Release|ARM - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM64.ActiveCfg = Release|ARM64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM64.Build.0 = Release|ARM64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM64.Deploy.0 = Release|ARM64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x64.ActiveCfg = Release|x64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x64.Build.0 = Release|x64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x64.Deploy.0 = Release|x64 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x86.ActiveCfg = Release|x86 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x86.Build.0 = Release|x86 - {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x86.Deploy.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/DictionaryExtensions.cs b/src/Microsoft.DotNet.XUnitRunnerUap/src/DictionaryExtensions.cs deleted file mode 100644 index ebfdd06f581..00000000000 --- a/src/Microsoft.DotNet.XUnitRunnerUap/src/DictionaryExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; - -namespace Microsoft.DotNet.XUnitRunnerUap -{ - internal static class DictionaryExtensions - { - public static void Add(this IDictionary> dictionary, TKey key, TValue value) - { - dictionary.GetOrAdd(key).Add(value); - } - - public static TValue GetOrAdd(this IDictionary dictionary, TKey key) - where TValue : new() - { - return dictionary.GetOrAdd(key, () => new TValue()); - } - - public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func newValue) - { - if (!dictionary.TryGetValue(key, out TValue result)) - { - result = newValue(); - dictionary[key] = result; - } - - return result; - } - } -} diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Program.cs b/src/Microsoft.DotNet.XUnitRunnerUap/src/Program.cs deleted file mode 100644 index 6c0ada7b0af..00000000000 --- a/src/Microsoft.DotNet.XUnitRunnerUap/src/Program.cs +++ /dev/null @@ -1,240 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using System.Xml.Linq; -using Windows.Storage; -using Xunit; - -namespace Microsoft.DotNet.XUnitRunnerUap -{ - class Program - { - volatile static bool cancel = false; - - static int Main(string[] args) - { - // Handle RemoteExecutor - if (args.Length > 0 && args[0] == "remote") - { - return RemoteExecutor.Execute(args.Skip(1).ToArray()); - } - - if (args.Length == 0 || args[0] == "-?" || args[0] == "/?" || args[0] == "-h" || args[0] == "--help") - { - PrintHeader(); - PrintUsage(); - return 2; - } - - var commandLine = CommandLine.Parse(args); - - Console.CancelKeyPress += (sender, e) => - { - if (!cancel) - { - Console.WriteLine("Canceling... (Press Ctrl+C again to terminate)"); - cancel = true; - e.Cancel = true; - } - }; - - if (commandLine.Debug) - { - Debugger.Launch(); - } - - if (!commandLine.NoLogo) - PrintHeader(); - - var completionMessages = new ConcurrentDictionary(); - var assembliesElement = new XElement("assemblies"); - - int errorCount = 0; - int failCount = 0; - - foreach (var assembly in commandLine.Project.Assemblies) - { - if (cancel) - { - break; - } - - assembly.Configuration.PreEnumerateTheories = false; - assembly.Configuration.DiagnosticMessages |= commandLine.DiagnosticMessages; - assembly.Configuration.AppDomain = AppDomainSupport.Denied; - var discoveryOptions = TestFrameworkOptions.ForDiscovery(assembly.Configuration); - var executionOptions = TestFrameworkOptions.ForExecution(assembly.Configuration); - executionOptions.SetDisableParallelization(true); - - try - { - using (var xunit = new XunitFrontController(AppDomainSupport.Denied, assembly.AssemblyFilename, assembly.ConfigFilename, assembly.Configuration.ShadowCopyOrDefault)) - using (var discoveryVisitor = new TestDiscoveryVisitor()) - { - string assemblyName = Path.GetFileNameWithoutExtension(assembly.AssemblyFilename); - // Discover & filter the tests - Console.WriteLine($"Discovering: {assemblyName}"); - xunit.Find(false, discoveryVisitor, discoveryOptions); - discoveryVisitor.Finished.WaitOne(); - - var testCasesDiscovered = discoveryVisitor.TestCases.Count; - var filteredTestCases = discoveryVisitor.TestCases.Where(commandLine.Project.Filters.Filter).ToList(); - var testCasesToRun = filteredTestCases.Count; - - Console.WriteLine($"Discovered: {assemblyName}"); - - // Run the filtered tests - if (testCasesToRun == 0) - { - Console.WriteLine($"Info: {assemblyName} has no tests to run"); - } - else - { - if (commandLine.Serialize) - { - filteredTestCases = filteredTestCases.Select(xunit.Serialize).Select(xunit.Deserialize).ToList(); - } - - var assemblyElement = new XElement("assembly"); - - StandardUapVisitor resultsVisitor = new StandardUapVisitor(assemblyElement, () => cancel, completionMessages, commandLine.Verbose, commandLine.FailSkips); - - xunit.RunTests(filteredTestCases, resultsVisitor, executionOptions); - - resultsVisitor.Finished.WaitOne(); - - assembliesElement.Add(assemblyElement); - - // Set counters to determine the error code later. - errorCount += resultsVisitor.ExecutionSummary.Errors; - failCount += resultsVisitor.ExecutionSummary.Failed; - - Console.WriteLine($"{Path.GetFileNameWithoutExtension(assembly.AssemblyFilename)} Total: {resultsVisitor.ExecutionSummary.Total}, Errors: {resultsVisitor.ExecutionSummary.Errors}, Failed: {resultsVisitor.ExecutionSummary.Failed}, Skipped: {resultsVisitor.ExecutionSummary.Skipped}, Time: {resultsVisitor.ExecutionSummary.Time}"); - } - } - } - catch (Exception ex) - { - assembliesElement = new XElement("error"); - assembliesElement.Add(ex); - - if (!commandLine.NoColor) - Console.ForegroundColor = ConsoleColor.Red; - - Console.WriteLine($"error: {ex.Message}"); - - if (commandLine.DiagnosticMessages) - { - if (!commandLine.NoColor) - Console.ForegroundColor = ConsoleColor.DarkGray; - - Console.WriteLine(ex.StackTrace); - } - } - finally - { - if (!commandLine.NoColor) - Console.ResetColor(); - - WriteResults(Path.GetFileName(assembly.AssemblyFilename), assembliesElement).GetAwaiter().GetResult(); - } - } - - if (cancel) - return -1073741510; // 0xC000013A: The application terminated as a result of a CTRL+C - - if (commandLine.Wait) - { - Console.WriteLine(); - Console.Write("Press any key to continue..."); - Console.ReadKey(); - Console.WriteLine(); - } - - if (errorCount > 0 || failCount > 0) - return 1; - - return 0; - } - - static async Task WriteResults(string test, XElement data) - { - StorageFolder folder = await KnownFolders.DocumentsLibrary.CreateFolderAsync("TestResults", CreationCollisionOption.OpenIfExists); - StorageFile file = await folder.CreateFileAsync(test + ".xml", CreationCollisionOption.ReplaceExisting); - - using (var stream = await file.OpenStreamForWriteAsync()) - { - data.Save(stream); - stream.Flush(); - } - } - - private static void PrintHeader() - { - var platform = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; - var versionAttribute = typeof(Program).Assembly.GetCustomAttribute(); - - Console.WriteLine($"xUnit.net Console Runner v1.0.25 ({IntPtr.Size * 8}-bit {platform})"); - } - - private static void PrintUsage() - { - var executableName = "XUnitRunnerUap"; - - Console.WriteLine("Copyright (C) .NET Foundation."); - Console.WriteLine(); - Console.WriteLine($"usage: {executableName} [options]"); - Console.WriteLine(); - Console.WriteLine("Valid options:"); - Console.WriteLine(" -nologo : do not show the copyright message"); - Console.WriteLine(" -nocolor : do not output results with colors"); - Console.WriteLine(" -failskips : convert skipped tests into failures"); - Console.WriteLine(" -parallel option : set parallelization based on option"); - Console.WriteLine(" : none - turn off all parallelization"); - Console.WriteLine(" : collections - only parallelize collections"); - Console.WriteLine(" : assemblies - only parallelize assemblies"); - Console.WriteLine(" : all - parallelize assemblies & collections"); - Console.WriteLine(" -maxthreads count : maximum thread count for collection parallelization"); - Console.WriteLine(" : default - run with default (1 thread per CPU thread)"); - Console.WriteLine(" : unlimited - run with unbounded thread count"); - Console.WriteLine(" : (number) - limit task thread pool size to 'count'"); - Console.WriteLine(" -wait : wait for input after completion"); - Console.WriteLine(" -diagnostics : enable diagnostics messages for all test assemblies"); - Console.WriteLine(" -debug : launch the debugger to debug the tests"); - Console.WriteLine(" -serialize : serialize all test cases (for diagnostic purposes only)"); - Console.WriteLine(" -verbose : enable verbose messages (track start and finish time)"); - Console.WriteLine(" -trait \"name=value\" : only run tests with matching name/value traits"); - Console.WriteLine(" : if specified more than once, acts as an OR operation"); - Console.WriteLine(" -notrait \"name=value\" : do not run tests with matching name/value traits"); - Console.WriteLine(" : if specified more than once, acts as an AND operation"); - Console.WriteLine(" -method \"name\" : run a given test method (can be fully specified or use a wildcard;"); - Console.WriteLine(" : i.e., 'MyNamespace.MyClass.MyTestMethod' or '*.MyTestMethod')"); - Console.WriteLine(" : if specified more than once, acts as an OR operation"); - Console.WriteLine(" -nomethod \"name\" : do not run a given test method (can be fully specified or use a wildcard;"); - Console.WriteLine(" : i.e., 'MyNamespace.MyClass.MyTestMethod' or '*.MyTestMethod')"); - Console.WriteLine(" : if specified more than once, acts as an AND operation"); - Console.WriteLine(" -class \"name\" : run all methods in a given test class (should be fully"); - Console.WriteLine(" : specified; i.e., 'MyNamespace.MyClass')"); - Console.WriteLine(" : if specified more than once, acts as an OR operation"); - Console.WriteLine(" -noclass \"name\" : do not run any methods in a given test class (should be fully"); - Console.WriteLine(" : specified; i.e., 'MyNamespace.MyClass')"); - Console.WriteLine(" : if specified more than once, acts as an AND operation"); - Console.WriteLine(" -namespace \"name\" : run all methods in a given namespace (i.e.,"); - Console.WriteLine(" : 'MyNamespace.MySubNamespace')"); - Console.WriteLine(" : if specified more than once, acts as an OR operation"); - Console.WriteLine(" -nonamespace \"name\" : do not run any methods in a given namespace (i.e.,"); - Console.WriteLine(" : 'MyNamespace.MySubNamespace')"); - Console.WriteLine(" : if specified more than once, acts as an AND operation"); - Console.WriteLine(" -xml \"name\" : The xml test results file name"); - Console.WriteLine(); - } - } -} diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/StandardUapVisitor.cs b/src/Microsoft.DotNet.XUnitRunnerUap/src/StandardUapVisitor.cs deleted file mode 100644 index 5af2dd709eb..00000000000 --- a/src/Microsoft.DotNet.XUnitRunnerUap/src/StandardUapVisitor.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Concurrent; -using System.IO; -using System.Xml.Linq; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.DotNet.XUnitRunnerUap -{ - internal class StandardUapVisitor : XmlTestExecutionVisitor - { - private string _assemblyName; - private readonly ConcurrentDictionary _completionMessages; - private readonly bool _verbose; - private readonly bool _failSkips; - - public StandardUapVisitor(XElement assemblyElement, Func cancelThunk, - ConcurrentDictionary completionMessages, bool verbose, bool failSkips) - : base(assemblyElement, cancelThunk) - { - _completionMessages = completionMessages; - _verbose = verbose; - _failSkips = failSkips; - } - - public ExecutionSummary ExecutionSummary - { - get - { - if (_completionMessages.TryGetValue(_assemblyName, out ExecutionSummary summary)) - { - return summary; - } - - return new ExecutionSummary(); - } - } - - protected override bool Visit(ITestAssemblyStarting assemblyStarting) - { - _assemblyName = Path.GetFileNameWithoutExtension(assemblyStarting.TestAssembly.Assembly.AssemblyPath); - - Console.WriteLine($"Starting: {_assemblyName}"); - - return base.Visit(assemblyStarting); - } - - protected override bool Visit(ITestAssemblyFinished assemblyFinished) - { - // Base class does computation of results, so call it first. - var result = base.Visit(assemblyFinished); - - Console.WriteLine($"Finished: {_assemblyName}"); - - _completionMessages.TryAdd(_assemblyName, new ExecutionSummary - { - Total = assemblyFinished.TestsRun, - Failed = !_failSkips ? assemblyFinished.TestsFailed : assemblyFinished.TestsFailed + assemblyFinished.TestsSkipped, - Skipped = !_failSkips ? assemblyFinished.TestsSkipped : 0, - Time = assemblyFinished.ExecutionTime, - Errors = Errors - }); - - return result; - } - - protected override bool Visit(ITestFailed testFailed) - { - Console.WriteLine($" {XmlEscape(testFailed.Test.DisplayName)} [FAIL]"); - Console.WriteLine($" {ExceptionUtility.CombineMessages(testFailed).Replace(Environment.NewLine, Environment.NewLine + " ")}"); - - WriteStackTrace(ExceptionUtility.CombineStackTraces(testFailed)); - - return base.Visit(testFailed); - } - - protected override bool Visit(ITestPassed testPassed) - { - return base.Visit(testPassed); - } - - protected override bool Visit(ITestSkipped testSkipped) - { - if (_failSkips) - { - return Visit(new TestFailed(testSkipped.Test, 0M, "", new[] { "FAIL_SKIP" }, new[] { testSkipped.Reason }, new[] { "" }, new[] { -1 })); - } - - Console.WriteLine($" {XmlEscape(testSkipped.Test.DisplayName)} [SKIP]"); - Console.WriteLine($" {XmlEscape(testSkipped.Reason)}"); - - return base.Visit(testSkipped); - } - - protected override bool Visit(ITestStarting testStarting) - { - if (_verbose) - { - Console.WriteLine($" {XmlEscape(testStarting.Test.DisplayName)} [STARTING]"); - } - return base.Visit(testStarting); - } - - protected override bool Visit(ITestFinished testFinished) - { - if (_verbose) - { - Console.WriteLine($" {XmlEscape(testFinished.Test.DisplayName)} [FINISHED] Time: {testFinished.ExecutionTime}s"); - } - return base.Visit(testFinished); - } - - protected override bool Visit(IErrorMessage error) - { - WriteError("FATAL", error); - - return base.Visit(error); - } - - protected override bool Visit(ITestAssemblyCleanupFailure cleanupFailure) - { - WriteError($"Test Assembly Cleanup Failure ({cleanupFailure.TestAssembly.Assembly.AssemblyPath})", cleanupFailure); - - return base.Visit(cleanupFailure); - } - - protected override bool Visit(ITestCaseCleanupFailure cleanupFailure) - { - WriteError($"Test Case Cleanup Failure ({cleanupFailure.TestCase.DisplayName})", cleanupFailure); - - return base.Visit(cleanupFailure); - } - - protected override bool Visit(ITestClassCleanupFailure cleanupFailure) - { - WriteError($"Test Class Cleanup Failure ({cleanupFailure.TestClass.Class.Name})", cleanupFailure); - - return base.Visit(cleanupFailure); - } - - protected override bool Visit(ITestCollectionCleanupFailure cleanupFailure) - { - WriteError($"Test Collection Cleanup Failure ({cleanupFailure.TestCollection.DisplayName})", cleanupFailure); - - return base.Visit(cleanupFailure); - } - - protected override bool Visit(ITestCleanupFailure cleanupFailure) - { - WriteError($"Test Cleanup Failure ({cleanupFailure.Test.DisplayName})", cleanupFailure); - - return base.Visit(cleanupFailure); - } - - protected override bool Visit(ITestMethodCleanupFailure cleanupFailure) - { - WriteError($"Test Method Cleanup Failure ({cleanupFailure.TestMethod.Method.Name})", cleanupFailure); - - return base.Visit(cleanupFailure); - } - - protected void WriteError(string failureName, IFailureInformation failureInfo) - { - Console.WriteLine($" [{failureName}] {XmlEscape(failureInfo.ExceptionTypes[0])}"); - Console.WriteLine($" {XmlEscape(ExceptionUtility.CombineMessages(failureInfo))}"); - - WriteStackTrace(ExceptionUtility.CombineStackTraces(failureInfo)); - } - - void WriteStackTrace(string stackTrace) - { - if (string.IsNullOrWhiteSpace(stackTrace)) - return; - - Console.WriteLine(" Stack Trace:"); - - foreach (var stackFrame in stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None)) - { - Console.WriteLine($" {StackFrameTransformer.TransformFrame(stackFrame, Directory.GetCurrentDirectory())}"); - } - } - } -}