From: Mike McLaughlin Date: Tue, 29 May 2018 23:27:05 +0000 (-0700) Subject: SOS.UnitTests now builds and runs on Windows, Linux and OSX. X-Git-Tag: submit/tizen/20190813.035844~88^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f27989b8bc676109f575ff4119ea0acfe3e39342;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git SOS.UnitTests now builds and runs on Windows, Linux and OSX. Added conditional to config files. Now any config value, Options or Option can have a Condition="$(foo) == bar". Only == and != are supported. Added Import config file support allow one config file to import another. Upgraded to .NET Core cli version 2.1.300 (final release). Don't use the dbgeng ioctl to get the DAC interface on Windows. Move test logging to better named directory --- diff --git a/.vsts-dotnet-ci.yml b/.vsts-dotnet-ci.yml index a00de37c9..5c3ed1f1e 100644 --- a/.vsts-dotnet-ci.yml +++ b/.vsts-dotnet-ci.yml @@ -26,7 +26,7 @@ phases: phaseName: CentOS_7 agentOs: Linux buildReason: IndividualCI - dockerImage: microsoft/dotnet-buildtools-prereqs:centos-7-d485f41-20173404063424 + dockerImage: microsoft/dotnet-buildtools-prereqs:centos-7-c103199-20180628120549 queue: name: DotNetCore-Linux demands: diff --git a/.vsts-dotnet.yml b/.vsts-dotnet.yml index a184cfe29..565024a26 100644 --- a/.vsts-dotnet.yml +++ b/.vsts-dotnet.yml @@ -5,7 +5,7 @@ phases: agentOs: Windows_NT buildReason: Internal queue: - name: DotNet-Build + name: DotNetCore-Build demands: - agent.os -equals Windows_NT parallel: 2 @@ -26,7 +26,7 @@ phases: phaseName: CentOS_7 agentOs: Linux buildReason: Internal - dockerImage: microsoft/dotnet-buildtools-prereqs:centos-7-d485f41-20173404063424 + dockerImage: microsoft/dotnet-buildtools-prereqs:centos-7-c103199-20180628120549 queue: name: DotNet-Build demands: @@ -49,7 +49,7 @@ phases: phaseName: Ubuntu_14_04 agentOs: Linux buildReason: Internal - dockerImage: microsoft/dotnet-buildtools-prereqs:ubuntu-14.04-cross-0cd4667-20170319080304 + dockerImage: microsoft/dotnet-buildtools-prereqs:ubuntu-18.04-c103199-20180628134610 queue: name: DotNet-Build demands: @@ -67,7 +67,7 @@ phases: agentOs: Darwin buildReason: Internal queue: - name: DotNet-Build + name: DotNetCore-Build demands: - agent.os -equals Darwin parallel: 2 diff --git a/Directory.Build.props b/Directory.Build.props index e8896aefc..cad51789c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,6 +6,7 @@ $(RepositoryUrl) http://go.microsoft.com/fwlink/?LinkId=529443 http://go.microsoft.com/fwlink/?LinkID=288859 + GitHub false true diff --git a/README.md b/README.md index 73569f376..aa0271c54 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ To build under Windows, run build.cmd from the root of the repository: [Lots of build spew] BUILD: Repo sucessfully built. -BUILD: Product binaries are available at c:\git\diagnostics\artifacts\bin\Windows_NT.x64.Debug +BUILD: Product binaries are available at c:\git\diagnostics\artifacts\Debug\bin\Windows_NT.x64 ``` To build under Linux, MacOS, FreeBSD, or NetBSD, run build.sh from the root of the repository: @@ -47,7 +47,7 @@ $ ./build.sh [Lots of build spew] BUILD: Repo sucessfully built. -BUILD: Product binaries are available at /home/mikem/diagnostics/artifacts/bin/Linux.x64.Debug +BUILD: Product binaries are available at /home/mikem/diagnostics/artifacts/Debug/bin/Linux.x64 ``` To test the resulting SOS (and plugin if xplat): diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT new file mode 100644 index 000000000..4a8002db2 --- /dev/null +++ b/THIRD-PARTY-NOTICES.TXT @@ -0,0 +1,251 @@ +.NET Core uses third-party libraries or other resources that may be +distributed under licenses different than the .NET Core software. + +Attributions and license notices for test cases originally authored by +third parties can be found in the respective test directories. + +In the event that we accidentally failed to list a required notice, please +bring it to our attention. Post an issue or email us: + + dotnet@microsoft.com + +The attached notices are provided for information only. + +License notice for RFC 3492 +--------------------------- + +The punycode implementation is based on the sample code in RFC 3492 + +Copyright (C) The Internet Society (2003). All Rights Reserved. + +This document and translations of it may be copied and furnished to +others, and derivative works that comment on or otherwise explain it +or assist in its implementation may be prepared, copied, published +and distributed, in whole or in part, without restriction of any +kind, provided that the above copyright notice and this paragraph are +included on all such copies and derivative works. However, this +document itself may not be modified in any way, such as by removing +the copyright notice or references to the Internet Society or other +Internet organizations, except as needed for the purpose of +developing Internet standards in which case the procedures for +copyrights defined in the Internet Standards process must be +followed, or as required to translate it into languages other than +English. + +The limited permissions granted above are perpetual and will not be +revoked by the Internet Society or its successors or assigns. + +This document and the information contained herein is provided on an +"AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING +TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION +HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF +MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +License notice for Algorithm from Internet Draft document "UUIDs and GUIDs" +--------------------------------------------------------------------------- + +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & +Digital Equipment Corporation, Maynard, Mass. +To anyone who acknowledges that this file is provided "AS IS" +without any express or implied warranty: permission to use, copy, +modify, and distribute this file for any purpose is hereby +granted without fee, provided that the above copyright notices and +this notice appears in all source code copies, and that none of +the names of Open Software Foundation, Inc., Hewlett-Packard +Company, or Digital Equipment Corporation be used in advertising +or publicity pertaining to distribution of the software without +specific, written prior permission. Neither Open Software +Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital Equipment +Corporation makes any representations about the suitability of +this software for any purpose. + +Copyright(C) The Internet Society 1997. All Rights Reserved. + +This document and translations of it may be copied and furnished to others, +and derivative works that comment on or otherwise explain it or assist in +its implementation may be prepared, copied, published and distributed, in +whole or in part, without restriction of any kind, provided that the above +copyright notice and this paragraph are included on all such copies and +derivative works.However, this document itself may not be modified in any +way, such as by removing the copyright notice or references to the Internet +Society or other Internet organizations, except as needed for the purpose of +developing Internet standards in which case the procedures for copyrights +defined in the Internet Standards process must be followed, or as required +to translate it into languages other than English. + +The limited permissions granted above are perpetual and will not be revoked +by the Internet Society or its successors or assigns. + +This document and the information contained herein is provided on an "AS IS" +basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE +DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY +RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A +PARTICULAR PURPOSE. + +License notice for Algorithm from RFC 4122 - +A Universally Unique IDentifier (UUID) URN Namespace +---------------------------------------------------- + +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & +Digital Equipment Corporation, Maynard, Mass. +Copyright (c) 1998 Microsoft. +To anyone who acknowledges that this file is provided "AS IS" +without any express or implied warranty: permission to use, copy, +modify, and distribute this file for any purpose is hereby +granted without fee, provided that the above copyright notices and +this notice appears in all source code copies, and that none of +the names of Open Software Foundation, Inc., Hewlett-Packard +Company, Microsoft, or Digital Equipment Corporation be used in +advertising or publicity pertaining to distribution of the software +without specific, written prior permission. Neither Open Software +Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital +Equipment Corporation makes any representations about the +suitability of this software for any purpose." + +License notice for The LLVM Compiler Infrastructure +--------------------------------------------------- + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +License notice for Bit Twiddling Hacks +-------------------------------------- + +Bit Twiddling Hacks + +By Sean Eron Anderson +seander@cs.stanford.edu + +Individually, the code snippets here are in the public domain (unless otherwise +noted) — feel free to use them however you please. The aggregate collection and +descriptions are © 1997-2005 Sean Eron Anderson. The code and descriptions are +distributed in the hope that they will be useful, but WITHOUT ANY WARRANTY and +without even the implied warranty of merchantability or fitness for a particular +purpose. + +License notice for Bob Jenkins +------------------------------ + +By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this +code any way you wish, private, educational, or commercial. It's free. + +License notice for Greg Parker +------------------------------ + +Greg Parker gparker@cs.stanford.edu December 2000 +This code is in the public domain and may be copied or modified without +permission. + +License notice for libunwind8 based code +---------------------------------------- + +Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P. + Contributed by David Mosberger-Tang + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +License notice for the Printing Floating-Point Numbers +/****************************************************************************** + Copyright (c) 2014 Ryan Juckett + http://www.ryanjuckett.com/ + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +******************************************************************************/ + +License notice for xxHash +------------------------- + +xxHash Library +Copyright (c) 2012-2014, Yann Collet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/diagnostics.sln b/diagnostics.sln index 496cb563a..f5adb8e08 100644 --- a/diagnostics.sln +++ b/diagnostics.sln @@ -3,48 +3,83 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2005 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.NETCore", "src\SOS\NETCore\SOS.NETCore.csproj", "{20513BA2-A156-4A17-4C70-5AC2DBD4F833}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.NETCore", "src\SOS\SOS.NETCore\SOS.NETCore.csproj", "{20513BA2-A156-4A17-4C70-5AC2DBD4F833}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestDebuggee", "src\SOS\TestDebuggee\TestDebuggee.csproj", "{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestDebuggee", "src\SOS\lldbplugin.tests\TestDebuggee\TestDebuggee.csproj", "{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DebuggerTests", "src\DebuggerTests\DebuggerTests.csproj", "{730C1201-1848-4F1E-8C1F-6316FB886C35}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostic.TestHelpers", "src\Microsoft.Diagnostic.TestHelpers\Microsoft.Diagnostic.TestHelpers.csproj", "{730C1201-1848-4F1E-8C1F-6316FB886C35}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SOS", "SOS", "{41638A4C-0DAF-47ED-A774-ECBBAC0315D7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.UnitTests", "src\SOS\SOS.UnitTests\SOS.UnitTests.csproj", "{1532DB3C-7DCD-45C6-B697-62B8378A16A2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.Build.0 = Debug|Any CPU {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x64.ActiveCfg = Debug|Any CPU {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x64.Build.0 = Debug|Any CPU + {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x86.ActiveCfg = Debug|Any CPU + {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x86.Build.0 = Debug|Any CPU {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.ActiveCfg = Release|Any CPU {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.Build.0 = Release|Any CPU {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x64.ActiveCfg = Release|Any CPU {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x64.Build.0 = Release|Any CPU + {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x86.ActiveCfg = Release|Any CPU + {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x86.Build.0 = Release|Any CPU {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x64.ActiveCfg = Debug|Any CPU {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x64.Build.0 = Debug|Any CPU + {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x86.ActiveCfg = Debug|Any CPU + {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x86.Build.0 = Debug|Any CPU {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|Any CPU.Build.0 = Release|Any CPU {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x64.ActiveCfg = Release|Any CPU {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x64.Build.0 = Release|Any CPU + {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x86.ActiveCfg = Release|Any CPU + {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x86.Build.0 = Release|Any CPU {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|Any CPU.Build.0 = Debug|Any CPU {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x64.ActiveCfg = Debug|Any CPU {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x64.Build.0 = Debug|Any CPU + {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x86.ActiveCfg = Debug|Any CPU + {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x86.Build.0 = Debug|Any CPU {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|Any CPU.ActiveCfg = Release|Any CPU {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|Any CPU.Build.0 = Release|Any CPU {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x64.ActiveCfg = Release|Any CPU {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x64.Build.0 = Release|Any CPU + {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x86.ActiveCfg = Release|Any CPU + {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x86.Build.0 = Release|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x64.ActiveCfg = Debug|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x64.Build.0 = Debug|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x86.ActiveCfg = Debug|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x86.Build.0 = Debug|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|Any CPU.Build.0 = Release|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x64.ActiveCfg = Release|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x64.Build.0 = Release|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x86.ActiveCfg = Release|Any CPU + {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {20513BA2-A156-4A17-4C70-5AC2DBD4F833} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7} + {6C43BE85-F8C3-4D76-8050-F25CE953A7FD} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7} + {1532DB3C-7DCD-45C6-B697-62B8378A16A2} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0} EndGlobalSection diff --git a/eng/Build-Native.cmd b/eng/Build-Native.cmd index ac57defbd..b2bf88b51 100644 --- a/eng/Build-Native.cmd +++ b/eng/Build-Native.cmd @@ -34,9 +34,9 @@ set ghprbCommentBody= :: __ProjectDir -- default: directory of the dir.props file :: __SourceDir -- default: %__ProjectDir%\src\ :: __RootBinDir -- default: %__ProjectDir%\artifacts\ -:: __BinDir -- default: %__RootBinDir%\bin\%__BuildOS%.%__BuildArch.%__BuildType%\ :: __IntermediatesDir -- default: %__RootBinDir%\obj\%__BuildOS%.%__BuildArch.%__BuildType%\ -:: __LogDir -- default: %__RootBinDir%\log\%__BuildOS%.%__BuildArch.%__BuildType%\ +:: __BinDir -- default: %__RootBinDir%\%__BuildType%\bin\%__BuildOS%.%__BuildArch\ +:: __LogDir -- default: %__RootBinDir%\%__BuildType%\log\%__BuildOS%.%__BuildArch\ :: :: Thus, these variables are not simply internal to this script! @@ -58,9 +58,6 @@ set "__ProjectDir=%~dp0" if %__ProjectDir:~-1%==\ set "__ProjectDir=%__ProjectDir:~0,-1%" set "__ProjectDir=%__ProjectDir%\.." set "__SourceDir=%__ProjectDir%\src" -set "__PackagesDir=%DotNetRestorePackagesPath%" -if [%__PackagesDir%]==[] set "__PackagesDir=%__ProjectDir%\packages" -set "__RootBinDir=%__ProjectDir%\artifacts" :: __UnprocessedBuildArgs are args that we pass to msbuild (e.g. /p:__BuildArch=x64) set "__args=%*" @@ -118,17 +115,17 @@ if /i "%__BuildType%"=="debug" set __BuildType=Debug if /i "%__BuildType%"=="release" set __BuildType=Release :: Set the remaining variables based upon the determined build configuration -set "__BinDir=%__RootBinDir%\bin\%__BuildOS%.%__BuildArch%.%__BuildType%" +set "__RootBinDir=%__ProjectDir%\artifacts" +set "__ConfigBinDir=%__RootBinDir%\%__BuildType%" +set "__BinDir=%__ConfigBinDir%\bin\%__BuildOS%.%__BuildArch%" +set "__LogDir=%__ConfigBinDir%\log\%__BuildOS%.%__BuildArch%" set "__IntermediatesDir=%__RootBinDir%\obj\%__BuildOS%.%__BuildArch%.%__BuildType%" -set "__LogDir=%__RootBinDir%\log\%__BuildOS%.%__BuildArch%.%__BuildType%" -if "%__NMakeMakefiles%"=="1" (set "__IntermediatesDir=%__RootBinDir%\nmakeobj\%__BuildOS%.%__BuildArch%.%__BuildType%") +set "__PackagesBinDir=%__ConfigBinDir%\packages" -set "__PackagesBinDir=%__BinDir%\.nuget" set "__CrossComponentBinDir=%__BinDir%" set "__CrossCompIntermediatesDir=%__IntermediatesDir%\crossgen" - if NOT "%__CrossArch%" == "" set __CrossComponentBinDir=%__CrossComponentBinDir%\%__CrossArch% -set "__CrossGenCoreLibLog=%__LogDir%\CrossgenCoreLib_%__BuildOS%__%__BuildArch%__%__BuildType%.log" +set "__CrossGenCoreLibLog=%__LogDir%\CrossgenCoreLib_%__BuildOS%_%__BuildArch%.log" set "__CrossgenExe=%__CrossComponentBinDir%\crossgen.exe" :: Generate path to be set for CMAKE_INSTALL_PREFIX to contain forward slash diff --git a/eng/build-native.sh b/eng/build-native.sh index 70f208ac9..693f5821a 100755 --- a/eng/build-native.sh +++ b/eng/build-native.sh @@ -261,13 +261,17 @@ if [ "$__BuildType" == "debug" ]; then fi __RootBinDir=$__ProjectRoot/artifacts -__IntermediatesDir="$__RootBinDir/obj/$__BuildOS.$__BuildArch.$__BuildType" -__LogFileDir="$__RootBinDir/log/$__BuildOS.$__BuildArch.$__BuildType" -__ExtraCmakeArgs="-DCLR_MANAGED_BINARY_DIR=$__RootBinDir/$__BuildType/bin" +__ConfigBinDir=$__RootBinDir/$__BuildType +__BinDir=$__ConfigBinDir/bin/$__BuildOS.$__BuildArch +__LogDir=$__ConfigBinDir/log/$__BuildOS.$__BuildArch +__IntermediatesDir=$__RootBinDir/obj/$__BuildOS.$__BuildArch.$__BuildType +__ResultsDir=$__ConfigBinDir/TestResults +__PackagesBinDir=$__ConfigBinDir/packages +__ExtraCmakeArgs=-DCLR_MANAGED_BINARY_DIR=$__ConfigBinDir/bin # Specify path to be set for CMAKE_INSTALL_PREFIX. # This is where all built native libraries will copied to. -export __CMakeBinDir="$__RootBinDir/bin/$__BuildOS.$__BuildArch.$__BuildType" +export __CMakeBinDir="$__BinDir" # Set default clang version if [[ $__ClangMajorVersion == 0 && $__ClangMinorVersion == 0 ]]; then @@ -286,7 +290,7 @@ if [[ "$__BuildArch" == "armel" ]]; then fi mkdir -p "$__IntermediatesDir" -mkdir -p "$__LogFileDir" +mkdir -p "$__LogDir" mkdir -p "$__CMakeBinDir" build_native() @@ -304,7 +308,7 @@ build_native() pushd "$intermediatesForBuild" echo "Invoking \"$__ProjectRoot/eng/gen-buildsys-clang.sh\" \"$__ProjectRoot\" $__ClangMajorVersion $__ClangMinorVersion $platformArch $__BuildType $generator $extraCmakeArguments $__cmakeargs" - "$__ProjectRoot/eng/gen-buildsys-clang.sh" "$__ProjectRoot" $__ClangMajorVersion $__ClangMinorVersion $platformArch $__BuildType $generator "$extraCmakeArguments" "$__cmakeargs" + "$__ProjectRoot/eng/gen-buildsys-clang.sh" "$__ProjectRoot" $__ClangMajorVersion $__ClangMinorVersion $platformArch $__BuildType $generator "$extraCmakeArguments" "$__cmakeargs" | tee $__LogDir/cmake.log popd if [ ! -f "$intermediatesForBuild/$buildFile" ]; then @@ -317,7 +321,7 @@ build_native() echo "Executing $buildTool install -j $__NumProc" - $buildTool install -j $__NumProc + $buildTool install -j $__NumProc | tee $__LogDir/make.log if [ $? != 0 ]; then echo "Failed to build." exit 1 @@ -449,6 +453,11 @@ if [ $__Test == 1 ]; then export GDB_PATH="$(which gdb 2> /dev/null)" fi + if [ "$__HostOS" == "Linux" ]; then + # This is needed on some distros like centos 7 so gdb generate-core-file creates a dump that works + echo 0x37 > /proc/self/coredump_filter + fi + echo "lldb: '$LLDB_PATH' gdb: '$GDB_PATH'" # Run xunit SOS tests @@ -463,7 +472,8 @@ if [ $__Test == 1 ]; then __Plugin=$__CMakeBinDir/libsosplugin.so fi - "$__ProjectRoot/src/SOS/tests/testsos.sh" "$__ProjectRoot" "$__Plugin" "$__RootBinDir/$__BuildType/bin" "$__LogFileDir" + # Run lldb python tests + "$__ProjectRoot/src/SOS/lldbplugin.tests/testsos.sh" "$__ProjectRoot" "$__Plugin" "$__ConfigBinDir/bin" "$__ResultsDir" if [ $? != 0 ]; then exit 1 fi diff --git a/eng/build.sh b/eng/build.sh index 660930fed..73db51936 100755 --- a/eng/build.sh +++ b/eng/build.sh @@ -17,7 +17,7 @@ done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" # remove the options that build.sh can't handle and pass it to build-native.sh -__args="$(echo $@ | sed 's/--test//g;s/--clang[0-9]\.[0-9]//g;s/--fixdockerimage//g')" +__args="$(echo $@ | sed 's/--test//g;s/--clang[0-9]\.[0-9]//g')" # build managed components "$scriptroot/common/build.sh" $__args @@ -27,3 +27,6 @@ fi # build native components and test both "$scriptroot/build-native.sh" $@ +if [[ $? != 0 ]]; then + exit 1 +fi diff --git a/eng/cibuild.sh b/eng/cibuild.sh index 83e8b0430..9519e111e 100755 --- a/eng/cibuild.sh +++ b/eng/cibuild.sh @@ -38,3 +38,6 @@ if [ "$__osname" == "Linux" ]; then fi "$scriptroot/build.sh" --ci $@ +if [[ $? != 0 ]]; then + exit 1 +fi diff --git a/eng/docker-cibuild.sh b/eng/docker-cibuild.sh index 8f0f21306..73c8fd9e8 100755 --- a/eng/docker-cibuild.sh +++ b/eng/docker-cibuild.sh @@ -64,7 +64,11 @@ $docker_bin exec $docker_id su -c "echo '%container_SUDO_user ALL=(ALL:ALL) NOPA echo "Execute cibuild.sh $args" $docker_bin exec --workdir=$source_directory --user $container_user_name $docker_id $source_directory/eng/cibuild.sh $args +lasterrorcode=$? echo "Cleanup Docker Container/Network" $docker_bin container stop $docker_id $docker_bin network rm vsts_network_$docker_container_name + +exit $lasterrorcode + diff --git a/global.json b/global.json index ad8f2a530..d612380d1 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "2.1.300-rc1-008673" + "version": "2.1.300" }, "msbuild-sdks": { "RoslynTools.RepoToolset": "1.0.0-beta2-62810-01" diff --git a/src/DebuggerTests/AcquireDotNetTestStep.cs b/src/DebuggerTests/AcquireDotNetTestStep.cs deleted file mode 100644 index 75f79b0c6..000000000 --- a/src/DebuggerTests/AcquireDotNetTestStep.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.IO; -using System.IO.Compression; -using System.Net; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Debugger.Tests.Build -{ - /// - /// Acquires the CLI tools from a web endpoint, a local zip/tar.gz, or directly from a local path - /// - public class AcquireDotNetTestStep : TestStep - { - /// - /// Create a new AcquireDotNetTestStep - /// - /// - /// If non-null, the CLI tools will be downloaded from this web endpoint. - /// The path should use an http or https scheme and the remote file should be in .zip or .tar.gz format. - /// localDotNetZipPath must also be non-null to indicate where the downloaded archive will be cached - /// - /// If non-null, the location of a .zip or .tar.gz compressed folder containing the CLI tools. This - /// must be a local file system or network file system path. - /// localDotNetZipExpandDirPath must also be non-null to indicate where the expanded folder will be - /// stored. - /// localDotNetTarPath must be non-null if localDotNetZip points to a .tar.gz format archive, in order - /// to indicate where the .tar file will be cached - /// - /// If localDotNetZipPath points to a .tar.gz, this path will be used to store the uncompressed .tar - /// file. Otherwise this path is unused. - /// - /// If localDotNetZipPath is non-null, this path will be used to store the expanded version of the - /// archive. Otherwise this path is unused. - /// - /// The path to the dotnet binary. When the CLI tools are being acquired from a compressed archive - /// this will presumably be a path inside the localDotNetZipExpandDirPath directory, otherwise - /// it can be any local file system path where the dotnet binary can be found. - /// - /// The version string for the CLI tools being downloaded - /// - /// - /// The path where an activity log for this test step should be written. - /// - public AcquireDotNetTestStep(string remoteDotNetZipPath, - string localDotNetZipPath, - string localDotNetTarPath, - string localDotNetZipExpandDirPath, - string localDotNetPath, - string cliToolsVersion, - string logFilePath) - : base(logFilePath, "Acquire DotNet Tools") - { - RemoteDotNetPath = remoteDotNetZipPath; - LocalDotNetZipPath = localDotNetZipPath; - if(localDotNetZipPath != null && localDotNetZipPath.EndsWith(".tar.gz")) - { - LocalDotNetTarPath = localDotNetTarPath; - } - if(localDotNetZipPath != null) - { - LocalDotNetZipExpandDirPath = localDotNetZipExpandDirPath; - } - LocalDotNetPath = localDotNetPath; - CliToolsVersion = cliToolsVersion; - } - - /// - /// If non-null, the CLI tools will be downloaded from this web endpoint. - /// The path should use an http or https scheme and the remote file should be in .zip or .tar.gz format. - /// - public string RemoteDotNetPath { get; private set; } - - /// - /// If non-null, the location of a .zip or .tar.gz compressed folder containing the CLI tools. This - /// is a local file system or network file system path. - /// - public string LocalDotNetZipPath { get; private set; } - - /// - /// If localDotNetZipPath points to a .tar.gz, this path will be used to store the uncompressed .tar - /// file. Otherwise null. - /// - public string LocalDotNetTarPath { get; private set; } - - /// - /// If localDotNetZipPath is non-null, this path will be used to store the expanded version of the - /// archive. Otherwise null. - /// - public string LocalDotNetZipExpandDirPath { get; private set; } - - /// - /// The path to the dotnet binary when the test step is complete. - /// - public string LocalDotNetPath { get; private set; } - - /// - /// The version string for the CLI tools - /// - public string CliToolsVersion { get; private set; } - - async protected override Task DoWork(ITestOutputHelper output) - { - if (RemoteDotNetPath != null) - { - await DownloadFile(RemoteDotNetPath, LocalDotNetZipPath, output); - } - if (LocalDotNetZipPath != null) - { - if (LocalDotNetZipPath.EndsWith(".zip")) - { - await Unzip(LocalDotNetZipPath, LocalDotNetZipExpandDirPath, output); - } - else if(LocalDotNetZipPath.EndsWith(".tar.gz")) - { - await UnGZip(LocalDotNetZipPath, LocalDotNetTarPath, output); - await Untar(LocalDotNetTarPath, LocalDotNetZipExpandDirPath, output); - } - else - { - output.WriteLine("Unsupported compression format: " + LocalDotNetZipPath); - throw new NotSupportedException("Unsupported compression format: " + LocalDotNetZipPath); - } - } - output.WriteLine("Dotnet path: " + LocalDotNetPath); - if (!File.Exists(LocalDotNetPath)) - { - throw new FileNotFoundException(LocalDotNetPath + " not found"); - } - } - - async static Task DownloadFile(string remotePath, string localPath, ITestOutputHelper output) - { - output.WriteLine("Downloading: " + remotePath + " -> " + localPath); - Directory.CreateDirectory(Path.GetDirectoryName(localPath)); - WebRequest request = HttpWebRequest.Create(remotePath); - WebResponse response = await request.GetResponseAsync(); - using (FileStream localZipStream = File.OpenWrite(localPath)) - { - // TODO: restore the CopyToAsync code after System.Net.Http.dll is - // updated to a newer version. The current old version has a bug - // where the copy never finished. - // await response.GetResponseStream().CopyToAsync(localZipStream); - byte[] buffer = new byte[16 * 1024]; - long bytesLeft = response.ContentLength; - - while (bytesLeft > 0) - { - int read = response.GetResponseStream().Read(buffer, 0, buffer.Length); - if (read == 0) - break; - localZipStream.Write(buffer, 0, read); - bytesLeft -= read; - } - output.WriteLine("Downloading finished"); - } - } - - async static Task UnGZip(string gzipPath, string expandedFilePath, ITestOutputHelper output) - { - output.WriteLine("Unziping: " + gzipPath + " -> " + expandedFilePath); - using (FileStream gzipStream = File.OpenRead(gzipPath)) - { - using (GZipStream expandedStream = new GZipStream(gzipStream, CompressionMode.Decompress)) - { - using (FileStream targetFileStream = File.OpenWrite(expandedFilePath)) - { - await expandedStream.CopyToAsync(targetFileStream); - } - } - } - } - - async static Task Unzip(string zipPath, string expandedDirPath, ITestOutputHelper output) - { - output.WriteLine("Unziping: " + zipPath + " -> " + expandedDirPath); - using (FileStream zipStream = File.OpenRead(zipPath)) - { - ZipArchive zip = new ZipArchive(zipStream); - foreach (ZipArchiveEntry entry in zip.Entries) - { - string extractedFilePath = Path.Combine(expandedDirPath, entry.FullName); - Directory.CreateDirectory(Path.GetDirectoryName(extractedFilePath)); - using (Stream zipFileStream = entry.Open()) - { - using (FileStream extractedFileStream = File.OpenWrite(extractedFilePath)) - { - await zipFileStream.CopyToAsync(extractedFileStream); - } - } - } - } - } - - async static Task Untar(string tarPath, string expandedDirPath, ITestOutputHelper output) - { - Directory.CreateDirectory(expandedDirPath); - string tarToolPath = null; - if (OS.Kind == OSKind.Linux) - { - tarToolPath = "/bin/tar"; - } - else if (OS.Kind == OSKind.OSX) - { - tarToolPath = "/usr/bin/tar"; - } - else - { - throw new NotSupportedException("Unknown where this OS stores the tar executable"); - } - - await new ProcessRunner(tarToolPath, "-xf " + tarPath). - WithWorkingDirectory(expandedDirPath). - WithLog(output). - WithExpectedExitCode(0). - Run(); - } - - } -} diff --git a/src/DebuggerTests/AssertX.cs b/src/DebuggerTests/AssertX.cs deleted file mode 100644 index f64bee7d4..000000000 --- a/src/DebuggerTests/AssertX.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Debugger.Tests; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Xunit; -using Xunit.Abstractions; - -namespace Debugger.Tests.Build -{ - public static partial class AssertX - { - public static void DirectoryExists(string dirDescriptiveName, string dirPath, ITestOutputHelper output) - { - if (!Directory.Exists(dirPath)) - { - string errorMessage = "Expected " + dirDescriptiveName + " to exist: " + dirPath; - output.WriteLine(errorMessage); - try - { - string parentDir = dirPath; - while (true) - { - if (Directory.Exists(Path.GetDirectoryName(parentDir))) - { - output.WriteLine("First parent directory that exists: " + Path.GetDirectoryName(parentDir)); - break; - } - if (Path.GetDirectoryName(parentDir) == parentDir) - { - output.WriteLine("Also unable to find any parent directory that exists"); - break; - } - parentDir = Path.GetDirectoryName(parentDir); - } - } - catch (Exception e) - { - output.WriteLine("Additional error while trying to diagnose missing directory:"); - output.WriteLine(e.GetType() + ": " + e.Message); - } - throw new DirectoryNotFoundException(errorMessage); - } - } - - public static void FileExists(string fileDescriptiveName, string filePath, ITestOutputHelper output) - { - if (!File.Exists(filePath)) - { - string errorMessage = "Expected " + fileDescriptiveName + " to exist: " + filePath; - output.WriteLine(errorMessage); - try - { - string parentDir = filePath; - while (true) - { - if (Directory.Exists(Path.GetDirectoryName(parentDir))) - { - output.WriteLine("First parent directory that exists: " + Path.GetDirectoryName(parentDir)); - break; - } - if (Path.GetDirectoryName(parentDir) == parentDir) - { - output.WriteLine("Also unable to find any parent directory that exists"); - break; - } - parentDir = Path.GetDirectoryName(parentDir); - } - } - catch (Exception e) - { - output.WriteLine("Additional error while trying to diagnose missing file:"); - output.WriteLine(e.GetType() + ": " + e.Message); - } - throw new FileNotFoundException(errorMessage); - } - } - } -} - - diff --git a/src/DebuggerTests/BaseDebuggeeCompiler.cs b/src/DebuggerTests/BaseDebuggeeCompiler.cs deleted file mode 100644 index 3422c4a49..000000000 --- a/src/DebuggerTests/BaseDebuggeeCompiler.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; -using Xunit; -using Xunit.Abstractions; - -namespace Debugger.Tests.Build -{ - /// - /// This compiler acquires the CLI tools and uses them to build debuggees. - /// - /// - /// The build process consists of the following steps: - /// 1. Acquire the CLI tools from the CliPath. This generally involves downloading them from the web and unpacking them. - /// 2. Create a source directory with the conventions that dotnet expects by copying it from the DebuggeeSourceRoot. Any project.template.json - /// file that is found will be specialized by replacing macros with specific contents. This lets us decide the runtime and dependency versions - /// at test execution time. - /// 3. Run dotnet restore in the newly created source directory - /// 4. Run dotnet build in the same directory - /// 5. Rename the built debuggee dll to an exe (so that it conforms to some historical conventions our tests expect) - /// 6. Copy any native dependencies from the DebuggeeNativeLibRoot to the debuggee output directory - /// - public abstract class BaseDebuggeeCompiler : IDebuggeeCompiler - { - AcquireDotNetTestStep _acquireTask; - DotNetBuildDebuggeeTestStep _buildDebuggeeTask; - - /// - /// Creates a new BaseDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build debuggees via dotnet build. - /// - /// - /// The test configuration that will be used to configure the build. The following configuration options should be set in the config: - /// CliPath The location to get the CLI tools from, either as a .zip/.tar.gz at a web endpoint, a .zip/.tar.gz - /// at a local filesystem path, or the dotnet binary at a local filesystem path - /// WorkingDir Temporary storage for CLI tools compressed archives downloaded from the internet will be stored here - /// CliCacheRoot The final CLI tools will be expanded and cached here - /// DebuggeeSourceRoot Debuggee sources and template project file will be retrieved from here - /// DebuggeeNativeLibRoot Debuggee native binary dependencies will be retrieved from here - /// DebuggeeBuildRoot Debuggee final sources/project file/binary outputs will be placed here - /// BuildProjectRuntime The runtime moniker to be built - /// BuildProjectMicrosoftNETCoreAppVersion The nuget package version of Microsoft.NETCore.App package to build against for debuggees that references this library - /// NugetPackageCacheDir The directory where NuGet packages are cached during restore - /// NugetFeeds The set of nuget feeds that are used to search for packages during restore - /// - /// - /// The name of the debuggee to be built, from which various build file paths are constructed. Before build it is assumed that: - /// Debuggee sources are located at config.DebuggeeSourceRoot/debuggeeName/ - /// Debuggee native dependencies are located at config.DebuggeeNativeLibRoot/debuggeeName/ - /// - /// After the build: - /// Debuggee build outputs will be created at config.DebuggeeNativeLibRoot/debuggeeName/ - /// A log of the build is stored at config.DebuggeeNativeLibRoot/debuggeeName.txt - /// - public BaseDebuggeeCompiler(TestConfiguration config, string debuggeeName) - { - _acquireTask = ConfigureAcquireDotNetTask(config); - _buildDebuggeeTask = ConfigureDotNetBuildDebuggeeTask(config, _acquireTask.LocalDotNetPath, _acquireTask.CliToolsVersion, debuggeeName); - } - - async public Task Execute(ITestOutputHelper output) - { - await _acquireTask.Execute(output); - await _buildDebuggeeTask.Execute(output); - return new DebuggeeConfiguration(_buildDebuggeeTask.DebuggeeProjectDirPath, - _buildDebuggeeTask.DebuggeeBinaryDirPath, - _buildDebuggeeTask.DebuggeeBinaryExePath); - } - - public static AcquireDotNetTestStep ConfigureAcquireDotNetTask(TestConfiguration config) - { - string remoteCliZipPath = null; - string localCliZipPath = null; - string localCliTarPath = null; - string localCliExpandedDirPath = null; - - string dotNetPath = config.CliPath; - if (dotNetPath.StartsWith("http:") || dotNetPath.StartsWith("https:")) - { - remoteCliZipPath = dotNetPath; - dotNetPath = Path.Combine(config.WorkingDir, "dotnet_zip", Path.GetFileName(remoteCliZipPath)); - } - if (dotNetPath.EndsWith(".zip") || dotNetPath.EndsWith(".tar.gz")) - { - localCliZipPath = dotNetPath; - string cliVersionDirName = null; - if (dotNetPath.EndsWith(".tar.gz")) - { - localCliTarPath = localCliZipPath.Substring(0, dotNetPath.Length - 3); - cliVersionDirName = Path.GetFileNameWithoutExtension(localCliTarPath); - } - else - { - cliVersionDirName = Path.GetFileNameWithoutExtension(localCliZipPath); - } - - localCliExpandedDirPath = Path.Combine(config.CliCacheRoot, cliVersionDirName); - - if (cliVersionDirName.Contains("win")) - { - dotNetPath = Path.Combine(localCliExpandedDirPath, "dotnet.exe"); - } - else - { - dotNetPath = Path.Combine(localCliExpandedDirPath, "dotnet"); - } - } - string acquireLogDir = Path.GetDirectoryName(Path.GetDirectoryName(dotNetPath)); - string acquireLogPath = Path.Combine(acquireLogDir, Path.GetDirectoryName(dotNetPath) + ".acquisition_log.txt"); - return new AcquireDotNetTestStep(remoteCliZipPath, - localCliZipPath, - localCliTarPath, - localCliExpandedDirPath, - dotNetPath, - config.CliVersion, - acquireLogPath); - } - - - protected static string GetInitialSourceDirPath(TestConfiguration config, string debuggeeName) - { - return Path.Combine(config.DebuggeeSourceRoot, debuggeeName); - } - protected static string GetDebuggeeNativeLibDirPath(TestConfiguration config, string debuggeeName) - { - return Path.Combine(config.DebuggeeNativeLibRoot, debuggeeName); - } - protected static string GetDebuggeeSolutionDirPath(string dotNetRootBuildDirPath, string debuggeeName) - { - return Path.Combine(dotNetRootBuildDirPath, debuggeeName); - } - protected static string GetDotNetRootBuildDirPath(TestConfiguration config) - { - return config.DebuggeeBuildRoot; - } - protected static string GetDebuggeeProjectDirPath(string debuggeeSolutionDirPath, string initialSourceDirPath, string debuggeeName) - { - string debuggeeProjectDirPath = debuggeeSolutionDirPath; - if (Directory.Exists(Path.Combine(initialSourceDirPath, debuggeeName))) - { - debuggeeProjectDirPath = Path.Combine(debuggeeSolutionDirPath, debuggeeName); - } - return debuggeeProjectDirPath; - } - protected virtual string GetDebuggeeBinaryDirPath(string debuggeeProjectDirPath, string framework, string runtime) - { - string debuggeeBinaryDirPath = null; - if (runtime != null) - { - debuggeeBinaryDirPath = Path.Combine(debuggeeProjectDirPath, "bin", "Debug", framework, runtime); - } - else - { - debuggeeBinaryDirPath = Path.Combine(debuggeeProjectDirPath, "bin", "Debug", framework); - } - return debuggeeBinaryDirPath; - } - protected static string GetDebuggeeBinaryDllPath(string debuggeeBinaryDirPath, string debuggeeName) - { - return Path.Combine(debuggeeBinaryDirPath, debuggeeName + ".dll"); - } - protected static string GetDebuggeeBinaryExePath(string debuggeeBinaryDirPath, string debuggeeName) - { - return Path.Combine(debuggeeBinaryDirPath, debuggeeName + ".exe"); - } - protected static string GetLogPath(TestConfiguration config, string debuggeeName) - { - return Path.Combine(GetDotNetRootBuildDirPath(config), debuggeeName + ".txt"); - } - protected static Dictionary GetNugetFeeds(TestConfiguration config) - { - Dictionary nugetFeeds = new Dictionary(); - if(!string.IsNullOrWhiteSpace(config.NuGetPackageFeeds)) - { - string[] feeds = config.NuGetPackageFeeds.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - foreach(string feed in feeds) - { - string[] feedParts = feed.Trim().Split('='); - if(feedParts.Length != 2) - { - throw new Exception("Expected feed \'" + feed + "\' to value = format"); - } - nugetFeeds.Add(feedParts[0], feedParts[1]); - } - } - return nugetFeeds; - } - protected static string GetRuntime(TestConfiguration config) - { - return config.BuildProjectRuntime; - } - protected abstract string GetFramework(TestConfiguration config); - - //we anticipate source paths like this: - //InitialSource: / - //DebuggeeNativeLibDir: / - //DotNetRootBuildDir: - //DebuggeeSolutionDir: / - //DebuggeeProjectDir: /[/] - //DebuggeeBinaryDir: /[/]/bin/Debug//[] - //DebuggeeBinaryDll: /[/]/bin/Debug//.dll - //DebuggeeBinaryExe: /[/]/bin/Debug//[]/.exe - //LogPath: /.txt - - // As seen above the project directory has two forms. In most cases it is identical with the solution - // directory for solutions that only build one managed binary. For a few cases where we need to build - // multiple binaries the project directory is nested one additional level. Siblings of the project directory - // are used for the referenced assemblies' project directories. For example: - ///MyApp/global.json - ///MyApp/MyApp/project.json - ///MyApp/MyHelperLib/project.json - - // some combinations of dotnet + project.json seem to produce a runtime directory after the framework and some don't. - // I don't yet understand what exact factors drive this choice though I assume it has to do with shared runtime support. - // The logic works for the current default configuration but may not correctly handle others. - // - // When the runtime directory is present it will have a native host exe in it that has been renamed to the debugee - // name. It also has a managed dll in it which functions as a managed exe when renamed. - // When the runtime directory is missing, the framework directory will have a managed dll in it that functions if it - // is renamed to an exe. I'm sure that renaming isn't the intended usage, but it works and produces less churn - // in our tests for the moment. - public abstract DotNetBuildDebuggeeTestStep ConfigureDotNetBuildDebuggeeTask(TestConfiguration config, string dotNetPath, string cliToolsVersion, string debuggeeName); - } -} diff --git a/src/DebuggerTests/CliDebuggeeCompiler.cs b/src/DebuggerTests/CliDebuggeeCompiler.cs deleted file mode 100644 index 951f21b08..000000000 --- a/src/DebuggerTests/CliDebuggeeCompiler.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; -using Xunit; -using Xunit.Abstractions; - -namespace Debugger.Tests.Build -{ - /// - /// This compiler acquires the CLI tools and uses them to build and optionally link debuggees via dotnet publish. - /// - public class CliDebuggeeCompiler : BaseDebuggeeCompiler - { - /// - /// Creates a new CliDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build and optionally link debuggees via dotnet publish. - /// - /// LinkerPackageVersion If set, this version of the linker package will be used to link the debuggee during publish. - /// - /// - public CliDebuggeeCompiler(TestConfiguration config, string debuggeeName) : base(config, debuggeeName) {} - - private static Dictionary GetBuildProperties(TestConfiguration config, string runtime) - { - Dictionary buildProperties = new Dictionary(); - buildProperties.Add("RuntimeFrameworkVersion", config.BuildProjectMicrosoftNetCoreAppVersion); - buildProperties.Add("RuntimeIdentifier", runtime); - string debugType = config.DebugType; - if (debugType == null) - { - // The default PDB type is portable - debugType = "portable"; - } - buildProperties.Add("DebugType", debugType); - return buildProperties; - } - - protected override string GetFramework(TestConfiguration config) - { - return config.BuildProjectFramework ?? "netcoreapp2.0"; - } - - protected override string GetDebuggeeBinaryDirPath(string debuggeeProjectDirPath, string framework, string runtime) - { - string debuggeeBinaryDirPath = base.GetDebuggeeBinaryDirPath(debuggeeProjectDirPath, framework, runtime); - debuggeeBinaryDirPath = Path.Combine(debuggeeBinaryDirPath, "publish"); - return debuggeeBinaryDirPath; - } - - public override DotNetBuildDebuggeeTestStep ConfigureDotNetBuildDebuggeeTask(TestConfiguration config, string dotNetPath, string cliToolsVersion, string debuggeeName) - { - string runtime = GetRuntime(config); - string initialSourceDirPath = GetInitialSourceDirPath(config, debuggeeName); - string dotNetRootBuildDirPath = GetDotNetRootBuildDirPath(config); - string debuggeeSolutionDirPath = GetDebuggeeSolutionDirPath(dotNetRootBuildDirPath, debuggeeName); - string debuggeeProjectDirPath = GetDebuggeeProjectDirPath(debuggeeSolutionDirPath, initialSourceDirPath, debuggeeName); - string debuggeeBinaryDirPath = GetDebuggeeBinaryDirPath(debuggeeProjectDirPath, GetFramework(config), runtime); - string debuggeeBinaryDllPath = GetDebuggeeBinaryDllPath(debuggeeBinaryDirPath, debuggeeName); - string debuggeeBinaryExePath = GetDebuggeeBinaryExePath(debuggeeBinaryDirPath, debuggeeName); - return new CsprojBuildDebuggeeTestStep(dotNetPath, - initialSourceDirPath, - GetDebuggeeNativeLibDirPath(config, debuggeeName), - GetBuildProperties(config, runtime), - runtime, - config.LinkerPackageVersion, - debuggeeName, - debuggeeSolutionDirPath, - debuggeeProjectDirPath, - debuggeeBinaryDirPath, - debuggeeBinaryDllPath, - debuggeeBinaryExePath, - config.NuGetPackageCacheDir, - GetNugetFeeds(config), - GetLogPath(config, debuggeeName)); - } - } -} diff --git a/src/DebuggerTests/ConsoleTestOutputHelper.cs b/src/DebuggerTests/ConsoleTestOutputHelper.cs deleted file mode 100644 index f35dfc1fb..000000000 --- a/src/DebuggerTests/ConsoleTestOutputHelper.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Text; -using Xunit.Abstractions; - -namespace Debugger.Tests -{ - public class ConsoleTestOutputHelper : ITestOutputHelper - { - public void WriteLine(string message) - { - Console.WriteLine(message); - } - - public void WriteLine(string format, params object[] args) - { - Console.WriteLine(format, args); - } - } -} \ No newline at end of file diff --git a/src/DebuggerTests/CsprojBuildDebuggeeTestStep.cs b/src/DebuggerTests/CsprojBuildDebuggeeTestStep.cs deleted file mode 100644 index 5742187db..000000000 --- a/src/DebuggerTests/CsprojBuildDebuggeeTestStep.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; -using Xunit.Abstractions; - -namespace Debugger.Tests.Build -{ - /// - /// This test step builds debuggees using the dotnet tools with .csproj projects files. - /// - /// - /// Any .csproj file that is found will be specialized by adding a linker package reference. - /// This lets us decide the runtime and dependency versions at test execution time. - /// - public class CsprojBuildDebuggeeTestStep : DotNetBuildDebuggeeTestStep - { - /// - /// A mapping from .csproj property strings to their values. These properties will be set when building the debuggee. - /// - /// - /// The runtime moniker to be built. - /// - public CsprojBuildDebuggeeTestStep(string dotnetToolPath, - string templateSolutionDirPath, - string debuggeeNativeLibDirPath, - Dictionary buildProperties, - string runtimeIdentifier, - string linkerPackageVersion, - string debuggeeName, - string debuggeeSolutionDirPath, - string debuggeeProjectDirPath, - string debuggeeBinaryDirPath, - string debuggeeBinaryDllPath, - string debuggeeBinaryExePath, - string nugetPackageCacheDirPath, - Dictionary nugetFeeds, - string logPath) : - base(dotnetToolPath, - templateSolutionDirPath, - debuggeeNativeLibDirPath, - debuggeeSolutionDirPath, - debuggeeProjectDirPath, - debuggeeBinaryDirPath, - debuggeeBinaryDllPath, - debuggeeBinaryExePath, - nugetPackageCacheDirPath, - nugetFeeds, - logPath) - { - BuildProperties = buildProperties; - RuntimeIdentifier = runtimeIdentifier; - DebuggeeName = debuggeeName; - ProjectTemplateFileName = debuggeeName + ".csproj"; - LinkerPackageVersion = linkerPackageVersion; - } - - /// - /// A mapping from .csproj property strings to their values. These properties will be set when building the debuggee. - /// - public IDictionary BuildProperties { get; } - public string RuntimeIdentifier { get; } - public string DebuggeeName { get; } - public string LinkerPackageVersion { get; } - public override string ProjectTemplateFileName { get; } - - protected override async Task Restore(ITestOutputHelper output) - { - string extraArgs = null; - if (RuntimeIdentifier != null) - { - extraArgs = " --runtime " + RuntimeIdentifier; - } - await Restore(extraArgs, output); - } - - protected override async Task Build(ITestOutputHelper output) - { - string publishArgs = "publish"; - foreach (var prop in BuildProperties) - { - publishArgs += $" /p:{prop.Key}={prop.Value}"; - } - await Build(publishArgs, output); - } - - protected override void ExpandProjectTemplate(string filePath, string destDirPath, ITestOutputHelper output) - { - ConvertCsprojTemplate(filePath, Path.Combine(destDirPath, DebuggeeName + ".csproj"), output); - } - - private void ConvertCsprojTemplate(string csprojTemplatePath, string csprojOutPath, ITestOutputHelper output) - { - var xdoc = XDocument.Load(csprojTemplatePath); - var ns = xdoc.Root.GetDefaultNamespace(); - if (LinkerPackageVersion != null) - { - AddLinkerPackageReference(xdoc, ns, LinkerPackageVersion, output); - } - using (var fs = new FileStream(csprojOutPath, FileMode.Create)) - { - xdoc.Save(fs); - } - } - - private static void AddLinkerPackageReference(XDocument xdoc, XNamespace ns, string linkerPackageVersion, ITestOutputHelper output) - { - xdoc.Root.Add(new XElement(ns + "ItemGroup", - new XElement(ns + "PackageReference", - new XAttribute("Include", "ILLink.Tasks"), - new XAttribute("Version", linkerPackageVersion)))); - } - - protected override void AssertDebuggeeAssetsFileExists(ITestOutputHelper output) - { - AssertX.FileExists("debuggee project.assets.json", Path.Combine(DebuggeeProjectDirPath, "obj", "project.assets.json"), output); - } - - protected override void AssertDebuggeeProjectFileExists(ITestOutputHelper output) - { - AssertX.FileExists("debuggee csproj", Path.Combine(DebuggeeProjectDirPath, DebuggeeName + ".csproj"), output); - } - } -} diff --git a/src/DebuggerTests/DebuggeeCompiler.cs b/src/DebuggerTests/DebuggeeCompiler.cs deleted file mode 100644 index 2a9ab9a4a..000000000 --- a/src/DebuggerTests/DebuggeeCompiler.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Debugger.Tests.Build - -{ - /// - /// DebugeeCompiler is responsible for finding and/or producing the source and binaries of a given debuggee. - /// The steps it takes to do this depend on the TestConfiguration. - /// - public static class DebuggeeCompiler - { - async public static Task Execute(TestConfiguration config, string debuggeeName, ITestOutputHelper output) - { - IDebuggeeCompiler compiler = null; - if (config.DebuggeeBuildProcess == "prebuilt") - { - compiler = new PrebuiltDebuggeeCompiler(config, debuggeeName); - } - else if (config.DebuggeeBuildProcess == "cli") - { - compiler = new CliDebuggeeCompiler(config, debuggeeName); - } - else - { - throw new Exception("Invalid DebuggeeBuildProcess configuration value. Expected 'prebuilt', actual \'" + config.DebuggeeBuildProcess + "\'"); - } - - return await compiler.Execute(output); - } - } - - public interface IDebuggeeCompiler - { - Task Execute(ITestOutputHelper output); - } - - public class DebuggeeConfiguration - { - public DebuggeeConfiguration(string sourcePath, string binaryDirPath, string binaryExePath) - { - SourcePath = sourcePath; - BinaryDirPath = binaryDirPath; - BinaryExePath = binaryExePath; - } - public string SourcePath { get; private set; } - public string BinaryDirPath { get; private set; } - public string BinaryExePath { get; private set; } - } -} diff --git a/src/DebuggerTests/DebuggerTests.csproj b/src/DebuggerTests/DebuggerTests.csproj deleted file mode 100644 index 93b3cb5da..000000000 --- a/src/DebuggerTests/DebuggerTests.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - netcoreapp2.0 - DebuggerTests - true - ;1591;1701 - true - Debugger test support - $(Description) - Tests - - - - - - - - - - diff --git a/src/DebuggerTests/DotNetBuildDebuggeeTestStep.cs b/src/DebuggerTests/DotNetBuildDebuggeeTestStep.cs deleted file mode 100644 index df4f99be4..000000000 --- a/src/DebuggerTests/DotNetBuildDebuggeeTestStep.cs +++ /dev/null @@ -1,374 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Debugger.Tests.Build -{ - /// - /// This test step builds debuggees using the dotnet tools - /// - /// - /// The build process consists of the following steps: - /// 1. Create a source directory with the conventions that dotnet expects by copying it from the DebuggeeSourceRoot. Any template project - /// file that is found will be specialized by the implementation class. - /// 2. Run dotnet restore in the newly created source directory - /// 3. Run dotnet build in the same directory - /// 4. Rename the built debuggee dll to an exe (so that it conforms to some historical conventions our tests expect) - /// 5. Copy any native dependencies from the DebuggeeNativeLibRoot to the debuggee output directory - /// - public abstract class DotNetBuildDebuggeeTestStep : TestStep - { - /// - /// Create a new DotNetBuildDebuggeeTestStep. - /// - /// - /// The path to the dotnet executable - /// - /// - /// The path to the template solution source. This will be copied into the final solution source directory - /// under debuggeeSolutionDirPath and any project.template.json files it contains will be specialized. - /// - /// - /// The path where the debuggee's native binary dependencies will be copied from. - /// - /// - /// The path where the debuggee solution will be created. For single project solutions this will be identical to - /// the debuggee project directory. - /// - /// - /// The path where the primary debuggee executable project directory will be created. For single project solutions this - /// will be identical to the debuggee solution directory. - /// - /// - /// The directory path where the dotnet tool will place the compiled debuggee binaries. - /// - /// - /// The path where the dotnet tool will place the compiled debuggee assembly. - /// - /// - /// The path to which the build will copy the debuggee binary dll with a .exe extension. - /// - /// - /// The path to the NuGet package cache. If null, no value for this setting will be placed in the - /// NuGet.config file and dotnet will need to read it from other ambient NuGet.config files or infer - /// a default cache. - /// - /// - /// A mapping of nuget feed names to locations. These feeds will be used to restore debuggee - /// nuget package dependencies. - /// - /// - /// The path where the build output will be logged - /// - public DotNetBuildDebuggeeTestStep(string dotnetToolPath, - string templateSolutionDirPath, - string debuggeeNativeLibDirPath, - string debuggeeSolutionDirPath, - string debuggeeProjectDirPath, - string debuggeeBinaryDirPath, - string debuggeeBinaryDllPath, - string debuggeeBinaryExePath, - string nugetPackageCacheDirPath, - Dictionary nugetFeeds, - string logPath) : - base(logPath, "Build Debuggee") - { - DotNetToolPath = dotnetToolPath; - DebuggeeTemplateSolutionDirPath = templateSolutionDirPath; - DebuggeeNativeLibDirPath = debuggeeNativeLibDirPath; - DebuggeeSolutionDirPath = debuggeeSolutionDirPath; - DebuggeeProjectDirPath = debuggeeProjectDirPath; - DebuggeeBinaryDirPath = debuggeeBinaryDirPath; - DebuggeeBinaryDllPath = debuggeeBinaryDllPath; - DebuggeeBinaryExePath = debuggeeBinaryExePath; - NuGetPackageCacheDirPath = nugetPackageCacheDirPath; - NugetFeeds = nugetFeeds; - if(NugetFeeds != null && NugetFeeds.Count > 0) - { - NuGetConfigPath = Path.Combine(DebuggeeSolutionDirPath, "NuGet.config"); - } - } - - /// - /// The path to the dotnet executable - /// - public string DotNetToolPath { get; private set; } - /// - /// The path to the template solution source. This will be copied into the final solution source directory - /// under debuggeeSolutionDirPath and any project.template.json files it contains will be specialized. - /// - public string DebuggeeTemplateSolutionDirPath { get; private set; } - /// - /// The path where the debuggee's native binary dependencies will be copied from. - /// - public string DebuggeeNativeLibDirPath { get; private set; } - /// - /// The path where the debuggee solution will be created. For single project solutions this will be identical to - /// the debuggee project directory. - /// - public string DebuggeeSolutionDirPath { get; private set; } - /// - /// The path where the primary debuggee executable project directory will be created. For single project solutions this - /// will be identical to the debuggee solution directory. - /// - public string DebuggeeProjectDirPath { get; private set; } - /// - /// The directory path where the dotnet tool will place the compiled debuggee binaries. - /// - public string DebuggeeBinaryDirPath { get; private set; } - /// - /// The path where the dotnet tool will place the compiled debuggee assembly. - /// - public string DebuggeeBinaryDllPath { get; private set; } - /// - /// The path to which the build will copy the debuggee binary dll with a .exe extension. - /// - public string DebuggeeBinaryExePath { get; private set; } - /// The path to the NuGet package cache. If null, no value for this setting will be placed in the - /// NuGet.config file and dotnet will need to read it from other ambient NuGet.config files or infer - /// a default cache. - public string NuGetPackageCacheDirPath { get; private set; } - public string NuGetConfigPath { get; private set; } - public IDictionary NugetFeeds { get; private set; } - public abstract string ProjectTemplateFileName { get; } - - async protected override Task DoWork(ITestOutputHelper output) - { - PrepareProjectSolution(output); - await Restore(output); - await Build(output); - RenameDebuggeeDllToExe(output); - CopyNativeDependencies(output); - } - - void PrepareProjectSolution(ITestOutputHelper output) - { - AssertDebuggeeSolutionTemplateDirExists(output); - - output.WriteLine("Creating Solution Source Directory"); - output.WriteLine("{"); - IndentedTestOutputHelper indentedOutput = new IndentedTestOutputHelper(output); - CopySourceDirectory(DebuggeeTemplateSolutionDirPath, DebuggeeSolutionDirPath, indentedOutput); - CreateNuGetConfig(indentedOutput); - output.WriteLine("}"); - output.WriteLine(""); - - AssertDebuggeeSolutionDirExists(output); - AssertDebuggeeProjectDirExists(output); - AssertDebuggeeProjectFileExists(output); - } - - SemaphoreSlim _dotnetRestoreLock = new SemaphoreSlim(1); - - protected async Task Restore(string extraArgs, ITestOutputHelper output) - { - AssertDebuggeeSolutionDirExists(output); - AssertDebuggeeProjectDirExists(output); - AssertDebuggeeProjectFileExists(output); - - string args = "restore"; - if (NuGetConfigPath != null) - { - args += " --configfile " + NuGetConfigPath; - } - if (NuGetPackageCacheDirPath != null) - { - args += " --packages \"" + NuGetPackageCacheDirPath + "\""; - } - if (extraArgs != null) - { - args += extraArgs; - } - ProcessRunner runner = new ProcessRunner(DotNetToolPath, args). - WithWorkingDirectory(DebuggeeSolutionDirPath). - WithLog(output). - WithTimeout(TimeSpan.FromMinutes(10)). // restore can be painfully slow - WithExpectedExitCode(0); - - if (OS.Kind != OSKind.Windows && Environment.GetEnvironmentVariable("HOME") == null) - { - output.WriteLine("Detected HOME environment variable doesn't exist. This will trigger a bug in dotnet restore."); - output.WriteLine("See: https://github.com/NuGet/Home/issues/2960"); - output.WriteLine("Test will workaround this by manually setting a HOME value"); - output.WriteLine(""); - runner = runner.WithEnvironmentVariable("HOME", DebuggeeSolutionDirPath); - } - - //workaround for https://github.com/dotnet/cli/issues/3868 - await _dotnetRestoreLock.WaitAsync(); - try - { - await runner.Run(); - } - finally - { - _dotnetRestoreLock.Release(); - } - - AssertDebuggeeAssetsFileExists(output); - } - - protected virtual async Task Restore(ITestOutputHelper output) - { - await Restore(null, output); - } - - void RenameDebuggeeDllToExe(ITestOutputHelper output) - { - if(DebuggeeBinaryDllPath == null) - { - return; - } - AssertDebuggeeDllExists(output); - - output.WriteLine("Copying: " + DebuggeeBinaryDllPath + " -> " + DebuggeeBinaryExePath); - File.Copy(DebuggeeBinaryDllPath, DebuggeeBinaryExePath, true); - - AssertDebuggeeExeExists(output); - } - - protected async Task Build(string dotnetArgs, ITestOutputHelper output) - { - AssertDebuggeeSolutionDirExists(output); - AssertDebuggeeProjectFileExists(output); - AssertDebuggeeAssetsFileExists(output); - - ProcessRunner runner = new ProcessRunner(DotNetToolPath, dotnetArgs). - WithWorkingDirectory(DebuggeeProjectDirPath). - WithLog(output). - WithTimeout(TimeSpan.FromMinutes(10)). // a mac CI build of the modules debuggee is painfully slow :( - WithExpectedExitCode(0); - - if (OS.Kind != OSKind.Windows && Environment.GetEnvironmentVariable("HOME") == null) - { - output.WriteLine("Detected HOME environment variable doesn't exist. This will trigger a bug in dotnet build."); - output.WriteLine("See: https://github.com/NuGet/Home/issues/2960"); - output.WriteLine("Test will workaround this by manually setting a HOME value"); - output.WriteLine(""); - runner = runner.WithEnvironmentVariable("HOME", DebuggeeSolutionDirPath); - } - if(NuGetPackageCacheDirPath != null) - { - //dotnet restore helpfully documents its --packages argument in the help text, but - //NUGET_PACKAGES was undocumented as far as I noticed. If this stops working we can also - //auto-generate a global.json with a "packages" setting, but this was more expedient. - runner = runner.WithEnvironmentVariable("NUGET_PACKAGES", NuGetPackageCacheDirPath); - } - - await runner.Run(); - - if (DebuggeeBinaryDllPath != null) - { - AssertDebuggeeDllExists(output); - } - else - { - AssertDebuggeeExeExists(output); - } - } - - protected virtual async Task Build(ITestOutputHelper output) - { - await Build("build", output); - } - - void CopyNativeDependencies(ITestOutputHelper output) - { - if(Directory.Exists(DebuggeeNativeLibDirPath)) - { - foreach(string filePath in Directory.EnumerateFiles(DebuggeeNativeLibDirPath)) - { - string targetPath = Path.Combine(DebuggeeBinaryDirPath, Path.GetFileName(filePath)); - output.WriteLine("Copying: " + filePath + " -> " + targetPath); - File.Copy(filePath, targetPath); - } - } - } - - private void CopySourceDirectory(string sourceDirPath, string destDirPath, ITestOutputHelper output) - { - output.WriteLine("Copying: " + sourceDirPath + " -> " + destDirPath); - Directory.CreateDirectory(destDirPath); - foreach(string dirPath in Directory.EnumerateDirectories(sourceDirPath)) - { - CopySourceDirectory(dirPath, Path.Combine(destDirPath, Path.GetFileName(dirPath)), output); - } - foreach (string filePath in Directory.EnumerateFiles(sourceDirPath)) - { - string fileName = Path.GetFileName(filePath); - if (fileName == ProjectTemplateFileName) - { - ExpandProjectTemplate(filePath, destDirPath, output); - } - else - { - File.Copy(filePath, Path.Combine(destDirPath, Path.GetFileName(filePath)), true); - } - } - } - - protected abstract void ExpandProjectTemplate(string filePath, string destDirPath, ITestOutputHelper output); - - protected void CreateNuGetConfig(ITestOutputHelper output) - { - if (NuGetConfigPath == null) - { - return; - } - string nugetConfigPath = Path.Combine(DebuggeeSolutionDirPath, "NuGet.config"); - StringBuilder sb = new StringBuilder(); - sb.AppendLine(""); - sb.AppendLine(""); - if(NugetFeeds != null && NugetFeeds.Count > 0) - { - sb.AppendLine(" "); - sb.AppendLine(" "); - foreach(KeyValuePair kv in NugetFeeds) - { - sb.AppendLine(" "); - } - sb.AppendLine(" "); - sb.AppendLine(" "); - sb.AppendLine(" "); - sb.AppendLine(" "); - } - sb.AppendLine(""); - - output.WriteLine("Creating: " + NuGetConfigPath); - File.WriteAllText(NuGetConfigPath, sb.ToString()); - } - - protected void AssertDebuggeeSolutionTemplateDirExists(ITestOutputHelper output) - { - AssertX.DirectoryExists("debuggee solution template directory", DebuggeeTemplateSolutionDirPath, output); - } - - protected void AssertDebuggeeProjectDirExists(ITestOutputHelper output) - { - AssertX.DirectoryExists("debuggee project directory", DebuggeeProjectDirPath, output); - } - - protected void AssertDebuggeeSolutionDirExists(ITestOutputHelper output) - { - AssertX.DirectoryExists("debuggee solution directory", DebuggeeSolutionDirPath, output); - } - - protected void AssertDebuggeeDllExists(ITestOutputHelper output) - { - AssertX.FileExists("debuggee dll", DebuggeeBinaryDllPath, output); - } - - protected void AssertDebuggeeExeExists(ITestOutputHelper output) - { - AssertX.FileExists("debuggee exe", DebuggeeBinaryExePath, output); - } - - protected abstract void AssertDebuggeeAssetsFileExists(ITestOutputHelper output); - - protected abstract void AssertDebuggeeProjectFileExists(ITestOutputHelper output); - } -} diff --git a/src/DebuggerTests/FileTestOutputHelper.cs b/src/DebuggerTests/FileTestOutputHelper.cs deleted file mode 100644 index 16f26bf59..000000000 --- a/src/DebuggerTests/FileTestOutputHelper.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Debugger.Tests -{ - /// - /// An ITestOutputHelper implementation that logs to a file - /// - class FileTestOutputHelper : ITestOutputHelper, IDisposable - { - readonly StreamWriter _logWriter; - readonly object _lock; - - public FileTestOutputHelper(string logFilePath, FileMode fileMode = FileMode.Create) - { - Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); - FileStream fs = new FileStream(logFilePath, fileMode); - _logWriter = new StreamWriter(fs); - _logWriter.AutoFlush = true; - _lock = new object(); - } - - public void WriteLine(string message) - { - lock (_lock) - { - _logWriter.WriteLine(message); - } - } - - public void WriteLine(string format, params object[] args) - { - lock (_lock) - { - _logWriter.WriteLine(format, args); - } - } - - public void Dispose() - { - lock (_lock) - { - _logWriter.Dispose(); - } - } - } -} diff --git a/src/DebuggerTests/IProcessLogger.cs b/src/DebuggerTests/IProcessLogger.cs deleted file mode 100644 index 821346700..000000000 --- a/src/DebuggerTests/IProcessLogger.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Debugger.Tests -{ - public enum ProcessStream - { - StandardIn = 0, - StandardOut = 1, - StandardError = 2, - MaxStreams = 3 - } - - public enum KillReason - { - TimedOut, - Unknown - } - - public interface IProcessLogger - { - void ProcessExited(ProcessRunner runner); - void ProcessKilled(ProcessRunner runner, KillReason reason); - void ProcessStarted(ProcessRunner runner); - void Write(ProcessRunner runner, string data, ProcessStream stream); - void WriteLine(ProcessRunner runner, string data, ProcessStream stream); - } -} \ No newline at end of file diff --git a/src/DebuggerTests/IndentedTestOutputHelper.cs b/src/DebuggerTests/IndentedTestOutputHelper.cs deleted file mode 100644 index cd3390db8..000000000 --- a/src/DebuggerTests/IndentedTestOutputHelper.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Debugger.Tests -{ - /// - /// An implementation of ITestOutputHelper that adds one indent level to - /// the start of each line - /// - public class IndentedTestOutputHelper : ITestOutputHelper - { - readonly string _indentText; - readonly ITestOutputHelper _output; - - public IndentedTestOutputHelper(ITestOutputHelper innerOutput, string indentText = " ") - { - _output = innerOutput; - _indentText = indentText; - } - - public void WriteLine(string message) - { - _output.WriteLine(_indentText + message); - } - - public void WriteLine(string format, params object[] args) - { - _output.WriteLine(_indentText + format, args); - } - } -} diff --git a/src/DebuggerTests/MultiplexTestOutputHelper.cs b/src/DebuggerTests/MultiplexTestOutputHelper.cs deleted file mode 100644 index d64a2c49d..000000000 --- a/src/DebuggerTests/MultiplexTestOutputHelper.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Text; -using Xunit.Abstractions; - -namespace Debugger.Tests -{ - public class MultiplexTestOutputHelper : ITestOutputHelper - { - readonly ITestOutputHelper[] _outputs; - - public MultiplexTestOutputHelper(params ITestOutputHelper[] outputs) - { - _outputs = outputs; - } - - public void WriteLine(string message) - { - foreach(ITestOutputHelper output in _outputs) - { - output.WriteLine(message); - } - } - - public void WriteLine(string format, params object[] args) - { - foreach (ITestOutputHelper output in _outputs) - { - output.WriteLine(format, args); - } - } - } -} \ No newline at end of file diff --git a/src/DebuggerTests/PrebuiltDebuggeeCompiler.cs b/src/DebuggerTests/PrebuiltDebuggeeCompiler.cs deleted file mode 100644 index e7ea22a7b..000000000 --- a/src/DebuggerTests/PrebuiltDebuggeeCompiler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -namespace Debugger.Tests.Build -{ - public class PrebuiltDebuggeeCompiler : IDebuggeeCompiler - { - string _sourcePath; - string _binaryPath; - string _binaryExePath; - - public PrebuiltDebuggeeCompiler(TestConfiguration config, string debuggeeName) - { - //we anticipate paths like this: - //Source: //[] - //Binaries: // - _sourcePath = Path.Combine(config.DebuggeeSourceRoot, debuggeeName); - if (Directory.Exists(Path.Combine(_sourcePath, debuggeeName))) - { - _sourcePath = Path.Combine(_sourcePath, debuggeeName); - } - - _binaryPath = Path.Combine(config.DebuggeeBuildRoot, debuggeeName); - _binaryExePath = Path.Combine(_binaryPath, debuggeeName); - _binaryExePath += ".exe"; - } - - public Task Execute(ITestOutputHelper output) - { - return Task.Factory.StartNew(() => new DebuggeeConfiguration(_sourcePath, _binaryPath, _binaryExePath)); - } - } -} \ No newline at end of file diff --git a/src/DebuggerTests/ProcessRunner.cs b/src/DebuggerTests/ProcessRunner.cs deleted file mode 100644 index eff46eefe..000000000 --- a/src/DebuggerTests/ProcessRunner.cs +++ /dev/null @@ -1,466 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Debugger.Tests -{ - /// - /// Executes a process and logs the output - /// - /// - /// The intended lifecycle is: - /// a) Create a new ProcessRunner - /// b) Use the various WithXXX methods to modify the configuration of the process to launch - /// c) await RunAsync() to start the process and wait for it to terminate. Configuration - /// changes are no longer possible - /// d) While waiting for RunAsync(), optionally call Kill() one or more times. This will expedite - /// the termination of the process but there is no guarantee the process is terminated by - /// the time Kill() returns. - /// - /// Although the entire API of this type has been designed to be thread-safe, its typical that - /// only calls to Kill() and property getters invoked within the logging callbacks will be called - /// asynchronously. - /// - public class ProcessRunner - { - // All of the locals might accessed from multiple threads and need to read/written under - // the _lock. We also use the lock to synchronize property access on the process object. - // - // Be careful not to cause deadlocks by calling the logging callbacks with the lock held. - // The logger has its own lock and it will hold that lock when it calls into property getters - // on this type. - object _lock = new object(); - - List _loggers; - Process _p; - DateTime _startTime; - TimeSpan _timeout; - ITestOutputHelper _traceOutput; - int? _expectedExitCode; - TaskCompletionSource _waitForProcessStartTaskSource; - Task _waitForExitTask; - Task _timeoutProcessTask; - Task _readStdOutTask; - Task _readStdErrTask; - CancellationTokenSource _cancelSource; - private string _replayCommand; - private KillReason? _killReason; - - public ProcessRunner(string exePath, string arguments, string replayCommand = null) - { - ProcessStartInfo psi = new ProcessStartInfo(); - psi.FileName = exePath; - psi.Arguments = arguments; - psi.UseShellExecute = false; - psi.RedirectStandardInput = true; - psi.RedirectStandardOutput = true; - psi.RedirectStandardError = true; - psi.CreateNoWindow = true; - - lock (_lock) - { - _p = new Process(); - _p.StartInfo = psi; - _p.EnableRaisingEvents = false; - _loggers = new List(); - _timeout = TimeSpan.FromMinutes(10); - _cancelSource = new CancellationTokenSource(); - _killReason = null; - _waitForProcessStartTaskSource = new TaskCompletionSource(); - Task startTask = _waitForProcessStartTaskSource.Task; - - // unfortunately we can't use the default Process stream reading because it only returns full lines and we have scenarios - // that need to receive the output before the newline character is written - _readStdOutTask = startTask.ContinueWith(t => - { - ReadStreamToLoggers(_p.StandardOutput, ProcessStream.StandardOut, _cancelSource.Token); - }, - _cancelSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default); - - _readStdErrTask = startTask.ContinueWith(t => - { - ReadStreamToLoggers(_p.StandardError, ProcessStream.StandardError, _cancelSource.Token); - }, - _cancelSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default); - - _timeoutProcessTask = startTask.ContinueWith(t => - { - Task.Delay(_timeout, _cancelSource.Token).ContinueWith(t2 => Kill(KillReason.TimedOut), TaskContinuationOptions.NotOnCanceled); - }, - _cancelSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default); - - _waitForExitTask = InternalWaitForExit(startTask, _readStdOutTask, _readStdErrTask); - - if (replayCommand == null) - { - _replayCommand = ExePath + " " + Arguments; - } - else - { - _replayCommand = replayCommand; - } - } - } - - public string ReplayCommand - { - get { lock (_lock) { return _replayCommand; } } - } - - public ProcessRunner WithEnvironmentVariable(string key, string value) - { - lock (_lock) - { - _p.StartInfo.Environment[key] = value; - } - return this; - } - - public ProcessRunner WithWorkingDirectory(string workingDirectory) - { - lock (_lock) - { - _p.StartInfo.WorkingDirectory = workingDirectory; - } - return this; - } - - public ProcessRunner WithLog(IProcessLogger logger) - { - lock (_lock) - { - _loggers.Add(logger); - } - return this; - } - - public ProcessRunner WithLog(ITestOutputHelper output) - { - lock (_lock) - { - _loggers.Add(new TestOutputProcessLogger(output)); - } - return this; - } - - public ProcessRunner WithDiagnosticTracing(ITestOutputHelper traceOutput) - { - lock (_lock) - { - _traceOutput = new ConsoleTestOutputHelper(traceOutput); - } - return this; - } - - public IProcessLogger[] Loggers - { - get { lock (_lock) { return _loggers.ToArray(); } } - } - - public ProcessRunner WithTimeout(TimeSpan timeout) - { - lock (_lock) - { - _timeout = timeout; - } - return this; - } - - public ProcessRunner WithExpectedExitCode(int expectedExitCode) - { - lock (_lock) - { - _expectedExitCode = expectedExitCode; - } - return this; - } - - public string ExePath - { - get { lock (_lock) { return _p.StartInfo.FileName; } } - } - - public string Arguments - { - get { lock (_lock) { return _p.StartInfo.Arguments; } } - } - - public string WorkingDirectory - { - get { lock (_lock) { return _p.StartInfo.WorkingDirectory; } } - } - - public int ProcessId - { - get { lock (_lock) { return _p.Id; } } - } - - public Dictionary EnvironmentVariables - { - get { lock (_lock) { return new Dictionary(_p.StartInfo.Environment); } } - } - - public bool IsStarted - { - get { lock (_lock) { return _waitForProcessStartTaskSource.Task.IsCompleted; } } - } - - public DateTime StartTime - { - get { lock (_lock) { return _startTime; } } - } - - public int ExitCode - { - get { lock (_lock) { return _p.ExitCode; } } - } - - public void StandardInputWriteLine(string line) - { - IProcessLogger[] loggers = null; - StreamWriter inputStream = null; - lock (_lock) - { - loggers = _loggers.ToArray(); - inputStream = _p.StandardInput; - } - foreach (IProcessLogger logger in loggers) - { - logger.WriteLine(this, line, ProcessStream.StandardIn); - } - inputStream.WriteLine(line); - } - - public Task Run() - { - Start(); - return WaitForExit(); - } - - public Task WaitForExit() - { - lock (_lock) - { - return _waitForExitTask; - } - } - - public ProcessRunner Start() - { - Process p = null; - lock (_lock) - { - p = _p; - } - // this is safe to call on multiple threads, it only launches the process once - bool started = p.Start(); - - IProcessLogger[] loggers = null; - lock (_lock) - { - // only the first thread to get here will initialize this state - if (!_waitForProcessStartTaskSource.Task.IsCompleted) - { - loggers = _loggers.ToArray(); - _startTime = DateTime.Now; - _waitForProcessStartTaskSource.SetResult(_p); - } - } - - // only the first thread that entered the lock above will run this - if (loggers != null) - { - foreach (IProcessLogger logger in loggers) - { - logger.ProcessStarted(this); - } - } - - return this; - } - - private void ReadStreamToLoggers(StreamReader reader, ProcessStream stream, CancellationToken cancelToken) - { - IProcessLogger[] loggers = Loggers; - - // for the best efficiency we want to read in chunks, but if the underlying stream isn't - // going to timeout partial reads then we have to fall back to reading one character at a time - int readChunkSize = 1; - if (reader.BaseStream.CanTimeout) - { - readChunkSize = 1000; - } - - char[] buffer = new char[readChunkSize]; - bool lastCharWasCarriageReturn = false; - do - { - int charsRead = 0; - int lastStartLine = 0; - charsRead = reader.ReadBlock(buffer, 0, readChunkSize); - - // this lock keeps the standard out/error streams from being intermixed - lock (loggers) - { - for (int i = 0; i < charsRead; i++) - { - // eat the \n after a \r, if any - bool isNewLine = buffer[i] == '\n'; - bool isCarriageReturn = buffer[i] == '\r'; - if (lastCharWasCarriageReturn && isNewLine) - { - lastStartLine++; - lastCharWasCarriageReturn = false; - continue; - } - lastCharWasCarriageReturn = isCarriageReturn; - if (isCarriageReturn || isNewLine) - { - string line = new string(buffer, lastStartLine, i - lastStartLine); - lastStartLine = i + 1; - foreach (IProcessLogger logger in loggers) - { - logger.WriteLine(this, line, stream); - } - } - } - - // flush any fractional line - if (charsRead > lastStartLine) - { - string line = new string(buffer, lastStartLine, charsRead - lastStartLine); - foreach (IProcessLogger logger in loggers) - { - logger.Write(this, line, stream); - } - } - } - } - while (!reader.EndOfStream && !cancelToken.IsCancellationRequested); - } - - public void Kill(KillReason reason = KillReason.Unknown) - { - IProcessLogger[] loggers = null; - Process p = null; - lock (_lock) - { - if (_waitForExitTask.IsCompleted) - { - return; - } - if (_killReason.HasValue) - { - return; - } - _killReason = reason; - if (!_p.HasExited) - { - p = _p; - } - - loggers = _loggers.ToArray(); - _cancelSource.Cancel(); - } - - if (p != null) - { - // its possible the process could exit just after we check so - // we still have to handle the InvalidOperationException that - // can be thrown. - try - { - p.Kill(); - } - catch (InvalidOperationException) { } - } - - foreach (IProcessLogger logger in loggers) - { - logger.ProcessKilled(this, reason); - } - } - - private async Task InternalWaitForExit(Task startProcessTask, Task stdOutTask, Task stdErrTask) - { - DebugTrace("starting InternalWaitForExit"); - Process p = await startProcessTask; - DebugTrace("InternalWaitForExit {0} '{1}'", p.Id, _replayCommand); - - Task processExit = Task.Factory.StartNew(() => - { - DebugTrace("starting Process.WaitForExit {0}", p.Id); - p.WaitForExit(); - DebugTrace("ending Process.WaitForExit {0}", p.Id); - }, - TaskCreationOptions.LongRunning); - - DebugTrace("awaiting process {0} exit, stdOut, and stdErr", p.Id); - await Task.WhenAll(processExit, stdOutTask, stdErrTask); - DebugTrace("await process {0} exit, stdOut, and stdErr complete", p.Id); - - foreach (IProcessLogger logger in Loggers) - { - logger.ProcessExited(this); - } - - lock (_lock) - { - if (_expectedExitCode.HasValue && p.ExitCode != _expectedExitCode.Value) - { - throw new Exception("Process returned exit code " + p.ExitCode + ", expected " + _expectedExitCode.Value + Environment.NewLine + - "Command Line: " + ReplayCommand + Environment.NewLine + - "Working Directory: " + WorkingDirectory); - } - DebugTrace("InternalWaitForExit {0} returning {1}", p.Id, p.ExitCode); - return p.ExitCode; - } - } - - private void DebugTrace(string format, params object[] args) - { - lock (_lock) - { - if (_traceOutput != null) - { - string message = string.Format(format, args); - _traceOutput.WriteLine("TRACE: {0}", message); - } - } - } - - class ConsoleTestOutputHelper : ITestOutputHelper - { - readonly ITestOutputHelper _output; - - public ConsoleTestOutputHelper(ITestOutputHelper output) - { - _output = output; - } - - public void WriteLine(string message) - { - Console.WriteLine(message); - if (_output != null) - { - _output.WriteLine(message); - } - - } - - public void WriteLine(string format, params object[] args) - { - Console.WriteLine(format, args); - if (_output != null) - { - _output.WriteLine(format, args); - } - } - } - } -} diff --git a/src/DebuggerTests/TestConfiguration.cs b/src/DebuggerTests/TestConfiguration.cs deleted file mode 100644 index 594a2dea6..000000000 --- a/src/DebuggerTests/TestConfiguration.cs +++ /dev/null @@ -1,611 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; -using Xunit; -using Xunit.Abstractions; - -namespace Debugger.Tests -{ - public class TestRunConfiguration : IDisposable - { - public static TestRunConfiguration Instance - { - get { return _instance.Value; } - } - - static Lazy _instance = new Lazy(() => ParseDefaultConfigFile()); - static string BaseDir = Path.GetFullPath("."); - - static TestRunConfiguration ParseDefaultConfigFile() - { - string configFilePath = Path.Combine(BaseDir, "Debugger.Tests.Config.txt"); - TestRunConfiguration testRunConfig = new TestRunConfiguration(); - testRunConfig.ParseConfigFile(configFilePath); - return testRunConfig; - } - - DateTime _timestamp = DateTime.Now; - - public TestConfiguration[] Configurations { get; private set; } - - void ParseConfigFile(string path) - { - XDocument doc = XDocument.Load(path); - XElement elem = doc.Root; - Assert.Equal("Configuration", elem.Name); - Dictionary initialConfig = new Dictionary(); - initialConfig["Timestamp"] = GetTimeStampText(); - initialConfig["TempPath"] = Path.GetTempPath(); - initialConfig["WorkingDir"] = GetInitialWorkingDir(); - - string SkipMdbgStep = Environment.GetEnvironmentVariable("SkipMdbgStep"); - initialConfig["SkipMdbgStep"] = SkipMdbgStep == null ? "false": SkipMdbgStep.ToLowerInvariant(); - - Dictionary[] configs = ParseConfigSettings(new Dictionary[] { initialConfig }, elem); - Configurations = configs.Select(c => new TestConfiguration(c)).ToArray(); - } - - string GetTimeStampText() - { - return _timestamp.ToString("yyyy\\_MM\\_dd\\_hh\\_mm\\_ss\\_ffff"); - } - - string GetInitialWorkingDir() - { - return Path.Combine(Path.GetTempPath(), "TestRun_" + GetTimeStampText()); - } - - Dictionary[] ParseConfigSettings(Dictionary[] templates, XElement node) - { - Dictionary[] curTemplates = templates; - foreach(XElement child in node.Elements()) - { - curTemplates = ParseConfigSetting(curTemplates, child); - } - return curTemplates; - } - - Dictionary[] ParseConfigSetting(Dictionary[] templates, XElement node) - { - if (node.Name == "Options") - { - List> newTemplates = new List>(); - foreach (XElement optionNode in node.Elements("Option")) - { - Dictionary[] templateCopy = templates.Select(c => new Dictionary(c)).ToArray(); - newTemplates.AddRange(ParseConfigSettings(templateCopy, optionNode)); - } - return newTemplates.ToArray(); - } - else - { - foreach(Dictionary config in templates) - { - string resolveNodeValue = ResolveProperties(config, node.Value); - config[node.Name.LocalName] = resolveNodeValue; - } - return templates; - } - } - - private string ResolveProperties(Dictionary config, string rawNodeValue) - { - StringBuilder resolvedValue = new StringBuilder(); - for(int i = 0; i < rawNodeValue.Length; ) - { - int propStartIndex = rawNodeValue.IndexOf("$(", i); - if (propStartIndex == -1) - { - if (i != rawNodeValue.Length) - { - resolvedValue.Append(rawNodeValue.Substring(i)); - } - break; - } - else - { - int propEndIndex = rawNodeValue.IndexOf(")", propStartIndex+1); - Assert.NotEqual(-1, propEndIndex); - if (propStartIndex != i) - { - resolvedValue.Append(rawNodeValue.Substring(i, propStartIndex - i)); - } - resolvedValue.Append(ResolveProperty(config, rawNodeValue.Substring(propStartIndex+2, propEndIndex - propStartIndex-2))); - i = propEndIndex + 1; - } - } - - return resolvedValue.ToString(); - } - - private string ResolveProperty(Dictionary config, string propName) - { - if (propName.Equals("WinDir", StringComparison.OrdinalIgnoreCase)) - { - return Path.GetFullPath(Environment.ExpandEnvironmentVariables("%windir%")); - } - string val = config[propName]; - return val == null ? "" : val; - } - - public void Dispose() - { - } - } - - - public class TestConfiguration - { - const string DebugTypeKey = "DebugType"; - const string DebuggeeBuildRootKey = "DebuggeeBuildRoot"; - - static string BaseDir = Path.GetFullPath("."); - - private Dictionary _settings; - - public TestConfiguration() - { - _settings = new Dictionary(); - } - - public TestConfiguration(Dictionary initialSettings) - { - _settings = new Dictionary(initialSettings); - } - - public IReadOnlyDictionary AllSettings - { - get { return _settings; } - } - - public TestConfiguration CloneWithNewDebugType(string pdbType) - { - Debug.Assert(!string.IsNullOrWhiteSpace(pdbType)); - - var currentSettings = new Dictionary(_settings); - - // Set or replace if the pdb debug type - currentSettings[DebugTypeKey] = pdbType; - - // The debuggee build root must exist. Append the pdb type to make it unique. - currentSettings[DebuggeeBuildRootKey] = Path.Combine(currentSettings[DebuggeeBuildRootKey], pdbType); - - return new TestConfiguration(currentSettings); - } - - public string ScriptRootDir - { - get - { - return MakeCanonicalPath(Get("ScriptRootDir")); - } - } - - public string MDbgDir - { - get - { - return MakeCanonicalPath(Get("MDbgDir")); - } - } - - public string OrangeDir - { - get - { - return MakeCanonicalPath(Get("OrangeDir")); - } - } - - public string TestProduct - { - get - { - return Get("TestProduct").ToLowerInvariant(); - } - } - - public string HostExe - { - get - { - return MakeCanonicalExePath(Get("HostExe")); - } - } - - public string HostArgs - { - get - { - return Get("HostArgs"); - } - } - - public string HostEnvVars - { - get - { - return Get("HostEnvVars"); - } - } - - public string RuntimeSymbolsPath - { - get - { - return MakeCanonicalPath(Get("RuntimeSymbolsPath")); - } - } - - public string DbgShim - { - get - { - return MakeCanonicalPath(Get("DbgShim")); - } - } - - public string DebuggeeRootDir - { - get - { - return MakeCanonicalPath(Get("DebuggeeRootDir")); - } - } - - public string AdditionalMDbgStartupCommands - { - get - { - return Get("AdditionalMDbgStartupCommands"); - } - } - - public string DebuggeeDumpInputRootDir - { - get - { - return MakeCanonicalPath(Get("DebuggeeDumpInputRootDir")); - } - } - - public string DebuggeeDumpOutputRootDir - { - get - { - return MakeCanonicalPath(Get("DebuggeeDumpOutputRootDir")); - } - } - - public string WorkingDir - { - get - { - return MakeCanonicalPath(Get("WorkingDir")); - } - } - - public string DebuggeeBuildProcess - { - get - { - string debuggeeBuildProcess = Get("DebuggeeBuildProcess"); - return debuggeeBuildProcess == null ? null : debuggeeBuildProcess.ToLowerInvariant(); - } - } - - public string DebuggeeSourceRoot - { - get - { - return MakeCanonicalPath(Get("DebuggeeSourceRoot")); - } - } - - public string DebuggeeBuildRoot - { - get - { - return MakeCanonicalPath(Get(DebuggeeBuildRootKey)); - } - } - - public string DebuggeeNativeLibRoot - { - get - { - return MakeCanonicalPath(Get("DebuggeeNativeLibRoot")); - } - } - - public string BuildProjectMicrosoftNetCoreAppVersion - { - get - { - return Get("BuildProjectMicrosoftNetCoreAppVersion"); - } - } - - public string BuildProjectFramework - { - get - { - return Get("BuildProjectFramework"); - } - } - - public string BuildProjectRuntime - { - get - { - return Get("BuildProjectRuntime"); - } - } - - public string DebugType - { - get - { - return Get(DebugTypeKey); - } - } - - public string CliPath - { - get - { - return MakeCanonicalPath(Get("CliPath")); - } - } - - public string CliCacheRoot - { - get - { - return MakeCanonicalPath(Get("CliCacheRoot")); - } - } - - public string CliVersion - { - get - { - return Get("CliVersion"); - } - } - - public string NuGetPackageCacheDir - { - get - { - return MakeCanonicalPath(Get("NuGetPackageCacheDir")); - } - } - - public string NuGetPackageFeeds - { - get - { - return Get("NuGetPackageFeeds"); - } - } - - public string TestRoot - { - get - { - return MakeCanonicalPath(Get("TestRoot")); - } - } - - public string CDBPath - { - get - { - return MakeCanonicalExePath(Get("CDBPath")); - } - } - - public string LLDBPath - { - get - { - return MakeCanonicalPath(Get("LLDBPath")); - } - } - - public string LLDBHelperScript - { - get - { - return MakeCanonicalPath(Get("LLDBHelperScript")); - } - } - - public string GDBPath - { - get - { - return MakeCanonicalPath(Get("GDBPath")); - } - } - - public string SOSPath - { - get - { - return MakeCanonicalPath(Get("SOSPath")); - } - } - - public string TargetArchitecture - { - get - { - return Get("TargetArchitecture").ToLowerInvariant(); - } - } - public bool SkipMdbgStep - { - get - { - return Get("SkipMdbgStep") == "true"; - } - } - - public string MDbgExe - { - get { return Path.Combine(MDbgDir, "Mdbg.exe"); } - } - - public string MDbgExtensionDir - { - get { return MDbgDir; } - } - - public string OrangeExe - { - get { return Path.Combine(OrangeDir, "Orange.exe"); } - } - - public bool WaitForDebuggerAttach - { - get - { - bool b; - return bool.TryParse(Get("WaitForDebuggerAttach"), out b) && b; - } - } - - public bool LogToConsole - { - get - { - bool b; - return bool.TryParse(Get("LogToConsole"), out b) && b; - } - } - - public string LogDirPath - { - get - { - return MakeCanonicalPath(Get("LogDir")); - } - } - - public string LinkerPackageVersion - { - get - { - return Get("LinkerPackageVersion"); - } - } - - private string Get(string key) - { - // unlike dictionary it is OK to ask for non-existant keys - // if the key doesn't exist the result is null - string settingValue = null; - _settings.TryGetValue(key, out settingValue); - return settingValue; - } - - private string MakeCanonicalExePath(string maybeRelativePath) - { - if (string.IsNullOrWhiteSpace(maybeRelativePath)) - { - return null; - } - string maybeRelativePathWithExtension = maybeRelativePath; - if (OS.Kind == OSKind.Windows && !maybeRelativePath.EndsWith(".exe")) - { - maybeRelativePathWithExtension = maybeRelativePath + ".exe"; - } - return MakeCanonicalPath(maybeRelativePathWithExtension); - } - - private string MakeCanonicalPath(string maybeRelativePath) - { - return MakeCanonicalPath(BaseDir, maybeRelativePath); - } - - private string MakeCanonicalPath(string baseDir, string maybeRelativePath) - { - if (string.IsNullOrWhiteSpace(maybeRelativePath)) - { - return null; - } - // we will assume any path referencing an http endpoint is canonical already - if(maybeRelativePath.StartsWith("http:") || - maybeRelativePath.StartsWith("https:")) - { - return maybeRelativePath; - } - string path = Path.IsPathRooted(maybeRelativePath) ? maybeRelativePath : Path.Combine(baseDir, maybeRelativePath); - path = Path.GetFullPath(path); - return OS.Kind != OSKind.Windows ? path.Replace('\\', '/') : path; - } - - public override string ToString() - { - return TestProduct + "." + DebuggeeBuildProcess; - } - } - - public enum OSKind - { - Windows, - Linux, - OSX, - FreeBSD, - Unknown, - } - - public static class OS - { - private static OSKind _kind; - - static OS() - { -#if CORE_CLR // Only core build can run on different OSes - if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - _kind = OSKind.Linux; - } - else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - _kind = OSKind.OSX; - } - else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - _kind = OSKind.Windows; - } - else - { - // Default to Unknown - _kind = OSKind.Unknown; - } - -#else // For everything else there's Windows - _kind = OSKind.Windows; -#endif - } - - public static OSKind Kind - { - get - { - return _kind; - } - } - } -} diff --git a/src/DebuggerTests/TestOutputProcessLogger.cs b/src/DebuggerTests/TestOutputProcessLogger.cs deleted file mode 100644 index 5a35ddd96..000000000 --- a/src/DebuggerTests/TestOutputProcessLogger.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml; -using Xunit.Abstractions; - -namespace Debugger.Tests -{ - public class TestOutputProcessLogger : IProcessLogger - { - string _timeFormat = "mm\\:ss\\.fff"; - ITestOutputHelper _output; - StringBuilder[] _lineBuffers; - - public TestOutputProcessLogger(ITestOutputHelper output) - { - _output = output; - _lineBuffers = new StringBuilder[(int)ProcessStream.MaxStreams]; - } - - public void ProcessStarted(ProcessRunner runner) - { - lock (this) - { - _output.WriteLine("Running Process: " + runner.ReplayCommand); - _output.WriteLine("Working Directory: " + runner.WorkingDirectory); - IEnumerable> additionalEnvVars = - runner.EnvironmentVariables.Where(kv => Environment.GetEnvironmentVariable(kv.Key) != kv.Value); - - if(additionalEnvVars.Any()) - { - _output.WriteLine("Additional Environment Variables: " + - string.Join(", ", additionalEnvVars.Select(kv => kv.Key + "=" + kv.Value))); - } - _output.WriteLine("{"); - } - } - - public virtual void Write(ProcessRunner runner, string data, ProcessStream stream) - { - lock (this) - { - AppendToLineBuffer(runner, stream, data); - } - } - - public virtual void WriteLine(ProcessRunner runner, string data, ProcessStream stream) - { - lock (this) - { - StringBuilder lineBuffer = AppendToLineBuffer(runner, stream, data); - //Ensure all output is written even if it isn't a full line before we log input - if (stream == ProcessStream.StandardIn) - { - FlushOutput(); - } - _output.WriteLine(lineBuffer.ToString()); - _lineBuffers[(int)stream] = null; - } - } - - public virtual void ProcessExited(ProcessRunner runner) - { - lock (this) - { - TimeSpan offset = runner.StartTime - DateTime.Now; - _output.WriteLine("}"); - _output.WriteLine("Exit code: " + runner.ExitCode + " ( " + offset.ToString(_timeFormat) + " elapsed)"); - _output.WriteLine(""); - } - } - - public void ProcessKilled(ProcessRunner runner, KillReason reason) - { - lock (this) - { - TimeSpan offset = runner.StartTime - DateTime.Now; - string reasonText = ""; - if (reason == KillReason.TimedOut) - { - reasonText = "Process timed out"; - } - else if (reason == KillReason.Unknown) - { - reasonText = "Kill() was called"; - } - _output.WriteLine(" Killing process: " + offset.ToString(_timeFormat) + ": " + reasonText); - } - } - - protected void FlushOutput() - { - if (_lineBuffers[(int)ProcessStream.StandardOut] != null) - { - _output.WriteLine(_lineBuffers[(int)ProcessStream.StandardOut].ToString()); - _lineBuffers[(int)ProcessStream.StandardOut] = null; - } - if (_lineBuffers[(int)ProcessStream.StandardError] != null) - { - _output.WriteLine(_lineBuffers[(int)ProcessStream.StandardError].ToString()); - _lineBuffers[(int)ProcessStream.StandardError] = null; - } - } - - private StringBuilder AppendToLineBuffer(ProcessRunner runner, ProcessStream stream, string data) - { - StringBuilder lineBuffer = _lineBuffers[(int)stream]; - if (lineBuffer == null) - { - TimeSpan offset = runner.StartTime - DateTime.Now; - lineBuffer = new StringBuilder(); - lineBuffer.Append(" "); - if (stream == ProcessStream.StandardError) - { - lineBuffer.Append("STDERROR: "); - } - else if (stream == ProcessStream.StandardIn) - { - lineBuffer.Append("STDIN: "); - } - lineBuffer.Append(offset.ToString(_timeFormat)); - lineBuffer.Append(": "); - _lineBuffers[(int)stream] = lineBuffer; - } - - // xunit has a bug where a non-printable character isn't properly escaped when - // it is written into the xml results which ultimately results in - // the xml being improperly truncated. For example MDbg has a test case that prints - // \0 and dotnet tools print \u001B to colorize their console output. - foreach(char c in data) - { - if(!char.IsControl(c)) - { - lineBuffer.Append(c); - } - } - return lineBuffer; - } - } -} \ No newline at end of file diff --git a/src/DebuggerTests/TestRunner.cs b/src/DebuggerTests/TestRunner.cs deleted file mode 100644 index 21d0c3111..000000000 --- a/src/DebuggerTests/TestRunner.cs +++ /dev/null @@ -1,258 +0,0 @@ -using Debugger.Tests.Build; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Debugger.Tests -{ - public class TestRunner - { - /// - /// Run debuggee (without any debugger) and compare the console output to the regex specified. - /// - /// test config to use - /// output helper - /// test case name - /// debuggee name (no path) - /// regex to match on console (standard and error) output - /// - public static async Task Run(TestConfiguration config, ITestOutputHelper output, string testName, string debuggeeName, string outputRegex) - { - OutputHelper outputHelper = null; - try - { - // Setup the logging from the options in the config file - outputHelper = ConfigureLogging(config, output, testName); - - // Restore and build the debuggee. The debuggee name is lower cased because the - // source directory name has been lowercased by the build system. - DebuggeeConfiguration debuggeeConfig = await DebuggeeCompiler.Execute(config, debuggeeName.ToLowerInvariant(), outputHelper); - - outputHelper.WriteLine("Starting {0}", testName); - outputHelper.WriteLine("{"); - - // Get the full debuggee launch command line (includes the host if required) - string exePath = debuggeeConfig.BinaryExePath; - string arguments = debuggeeConfig.BinaryDirPath; - if (!string.IsNullOrWhiteSpace(config.HostExe)) - { - exePath = config.HostExe; - arguments = Environment.ExpandEnvironmentVariables(string.Format("{0} {1} {2}", config.HostArgs, debuggeeConfig.BinaryExePath, debuggeeConfig.BinaryDirPath)); - } - - TestLogger testLogger = new TestLogger(outputHelper.IndentedOutput); - ProcessRunner processRunner = new ProcessRunner(exePath, arguments). - WithLog(testLogger). - WithTimeout(TimeSpan.FromMinutes(5)); - - processRunner.Start(); - - // Wait for the debuggee to finish before getting the debuggee output - int exitCode = await processRunner.WaitForExit(); - - string debuggeeStandardOutput = testLogger.GetStandardOutput(); - string debuggeeStandardError = testLogger.GetStandardError(); - - // The debuggee output is all the stdout first and then all the stderr output last - string debuggeeOutput = debuggeeStandardOutput + debuggeeStandardError; - if (string.IsNullOrEmpty(debuggeeOutput)) - { - throw new Exception("No debuggee output"); - } - // Remove any CR's in the match string because this assembly is built on Windows (with CRs) and - // ran on Linux/OS X (without CRs). - outputRegex = outputRegex.Replace("\r", ""); - - // Now match the debuggee output and regex match string - if (!new Regex(outputRegex, RegexOptions.Multiline).IsMatch(debuggeeOutput)) - { - throw new Exception(string.Format("\nDebuggee output:\n\n'{0}'\n\nDid not match the expression:\n\n'{1}'", debuggeeOutput, outputRegex)); - } - - return exitCode; - } - catch (Exception ex) - { - // Log the exception - outputHelper?.WriteLine(ex.ToString()); - throw; - } - finally - { - outputHelper?.WriteLine("}"); - outputHelper?.Dispose(); - } - } - - /// - /// Returns a test config for each PDB type supported by the product/platform. - /// - /// starting config - /// new configs for each supported PDB type - public static IEnumerable EnumeratePdbTypeConfigs(TestConfiguration config) - { - // Enabled after an official dotnet cli supports "embedded" PDBs - issue #244 - // string[] pdbTypes = { "portable", "embedded" }; - string[] pdbTypes = { "portable" }; - - if (OS.Kind == OSKind.Windows) - { - if (config.TestProduct.Equals("projectk")) - { - // Enabled after an official dotnet cli supports "embedded" PDBs - issue #244 - // pdbTypes = new string[] { "portable", "full", "embedded" }; - pdbTypes = new string[] { "portable", "full" }; - } - else - { - // Don't change the config on the desktop/projectn projects - pdbTypes = new string[] { "" }; - } - } - - foreach (string pdbType in pdbTypes) - { - if (string.IsNullOrWhiteSpace(pdbType)) - { - yield return config; - } - else - { - yield return config.CloneWithNewDebugType(pdbType); - } - } - } - - /// - /// Returns an output helper for the specified config. - /// - /// test config - /// starting output helper - /// test case name - /// new output helper - internal static TestRunner.OutputHelper ConfigureLogging(TestConfiguration config, ITestOutputHelper output, string testName) - { - FileTestOutputHelper fileLogger = null; - ConsoleTestOutputHelper consoleLogger = null; - if (!string.IsNullOrEmpty(config.LogDirPath)) - { - string logFileName = testName + "." + config.ToString() + ".txt"; - string logPath = Path.Combine(config.LogDirPath, logFileName); - fileLogger = new FileTestOutputHelper(logPath, FileMode.Append); - } - if (config.LogToConsole) - { - consoleLogger = new ConsoleTestOutputHelper(); - } - return new TestRunner.OutputHelper(output, fileLogger, consoleLogger); - } - - internal class OutputHelper : ITestOutputHelper, IDisposable - { - readonly ITestOutputHelper _output; - readonly FileTestOutputHelper _fileLogger; - readonly ConsoleTestOutputHelper _consoleLogger; - - public readonly ITestOutputHelper IndentedOutput; - - public OutputHelper(ITestOutputHelper output, FileTestOutputHelper fileLogger, ConsoleTestOutputHelper consoleLogger) - { - _output = output; - _fileLogger = fileLogger; - _consoleLogger = consoleLogger; - IndentedOutput = new IndentedTestOutputHelper(this); - } - - public void WriteLine(string message) - { - _output.WriteLine(message); - _fileLogger?.WriteLine(message); - _consoleLogger?.WriteLine(message); - } - - public void WriteLine(string format, params object[] args) - { - _output.WriteLine(format, args); - _fileLogger?.WriteLine(format, args); - _consoleLogger?.WriteLine(format, args); - } - - public void Dispose() - { - _fileLogger?.Dispose(); - } - } - - class TestLogger : TestOutputProcessLogger - { - readonly StringBuilder _standardOutput; - readonly StringBuilder _standardError; - - public TestLogger(ITestOutputHelper output) - : base(output) - { - lock (this) - { - _standardOutput = new StringBuilder(); - _standardError = new StringBuilder(); - } - } - - public string GetStandardOutput() - { - lock (this) - { - return _standardOutput.ToString(); - } - } - - public string GetStandardError() - { - lock (this) - { - return _standardError.ToString(); - } - } - - public override void Write(ProcessRunner runner, string data, ProcessStream stream) - { - lock (this) - { - base.Write(runner, data, stream); - switch (stream) - { - case ProcessStream.StandardOut: - _standardOutput.Append(data); - break; - - case ProcessStream.StandardError: - _standardError.Append(data); - break; - } - } - } - - public override void WriteLine(ProcessRunner runner, string data, ProcessStream stream) - { - lock (this) - { - base.WriteLine(runner, data, stream); - switch (stream) - { - case ProcessStream.StandardOut: - _standardOutput.AppendLine(data); - break; - - case ProcessStream.StandardError: - _standardError.AppendLine(data); - break; - } - } - } - } - } -} diff --git a/src/DebuggerTests/TestStep.cs b/src/DebuggerTests/TestStep.cs deleted file mode 100644 index 6f928717f..000000000 --- a/src/DebuggerTests/TestStep.cs +++ /dev/null @@ -1,633 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; -using Xunit.Abstractions; - -namespace Debugger.Tests.Build -{ - /// - /// An incremental atomic unit of work in the process of running a test. A test - /// can consist of multiple processes running across different machines at - /// different times. The TestStep supports: - /// 1) coordination between test processes to ensure each step runs only once - /// 2) disk based persistance so that later steps in different processes can - /// reload the state of earlier steps - /// 3) Pretty printing logs - /// 4) TODO: Dependency analysis to determine if the cached output of a previous step - /// execution is still valid - /// - public class TestStep - { - string _logFilePath; - string _stateFilePath; - TimeSpan _timeout; - - public TestStep(string logFilePath, string friendlyName) - { - _logFilePath = logFilePath; - _stateFilePath = Path.ChangeExtension(_logFilePath, "state.txt"); - _timeout = TimeSpan.FromMinutes(20); - FriendlyName = friendlyName; - } - - public string FriendlyName { get; private set; } - - async public Task Execute(ITestOutputHelper output) - { - // if this step is in progress on another thread, wait for it - TestStepState stepState = await AcquireStepStateLock(output); - - //if this thread wins the race we do the work on this thread, otherwise - //we log the winner's saved output - if (stepState.RunState != TestStepRunState.InProgress) - { - LogHeader(stepState, true, output); - LogPreviousResults(stepState, output); - LogFooter(stepState, output); - ThrowExceptionIfFaulted(stepState); - } - else - { - await UncachedExecute(stepState, output); - } - } - - protected virtual Task DoWork(ITestOutputHelper output) - { - output.WriteLine("Overload the default DoWork implementation in order to run useful work"); - return Task.Delay(0); - } - - private async Task UncachedExecute(TestStepState stepState, ITestOutputHelper output) - { - using (FileTestOutputHelper stepLog = new FileTestOutputHelper(_logFilePath)) - { - try - { - LogHeader(stepState, false, output); - MultiplexTestOutputHelper mux = new MultiplexTestOutputHelper(new IndentedTestOutputHelper(output), stepLog); - await DoWork(mux); - stepState = stepState.Complete(); - } - catch (Exception e) - { - stepState = stepState.Fault(e.Message, e.StackTrace); - } - finally - { - LogFooter(stepState, output); - await WriteFinalStepState(stepState, output); - ThrowExceptionIfFaulted(stepState); - } - } - } - - private bool TryWriteInitialStepState(TestStepState state, ITestOutputHelper output) - { - // To ensure the file is atomically updated we write the contents to a temporary - // file, then move it to the final location - try - { - string tempPath = Path.GetTempFileName(); - try - { - File.WriteAllText(tempPath, state.SerializeInitialState()); - Directory.CreateDirectory(Path.GetDirectoryName(_stateFilePath)); - File.Move(tempPath, _stateFilePath); - return true; - } - finally - { - File.Delete(tempPath); - } - - } - catch (IOException ex) - { - output.WriteLine("Exception writing state file {0} {1}", _stateFilePath, ex.ToString()); - return false; - } - } - - private bool TryOpenExistingStepStateFile(out TestStepState stepState, ITestOutputHelper output) - { - stepState = null; - try - { - if (!Directory.Exists(Path.GetDirectoryName(_stateFilePath))) - { - return false; - } - bool result = TestStepState.TryParse(File.ReadAllText(_stateFilePath), out stepState); - if (!result) - { - output.WriteLine("TryParse failed on opening existing state file {0}", _stateFilePath); - } - return result; - } - catch (IOException ex) - { - output.WriteLine("Exception opening existing state file {0} {1}", _stateFilePath, ex.ToString()); - return false; - } - } - - async private Task WriteFinalStepState(TestStepState stepState, ITestOutputHelper output) - { - const int NumberOfRetries = 5; - FileStream stepStateStream = null; - - // Retry few times because the state file may be open temporarily by another thread or process. - for (int retries = 0; retries < NumberOfRetries; retries++) - { - try - { - stepStateStream = File.Open(_stateFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); - break; - } - catch (IOException ex) - { - output.WriteLine("WriteFinalStepState exception {0} retry #{1}", ex.ToString(), retries); - if (retries >= (NumberOfRetries - 1)) - { - throw; - } - } - } - - using (stepStateStream) - { - stepStateStream.Seek(0, SeekOrigin.End); - StreamWriter writer = new StreamWriter(stepStateStream); - await writer.WriteAsync(Environment.NewLine + stepState.SerializeFinalState()); - await writer.FlushAsync(); - } - } - - private void LogHeader(TestStepState stepState, bool cached, ITestOutputHelper output) - { - string cachedText = cached ? " (CACHED)" : ""; - output.WriteLine("[" + stepState.StartTime + "] " + FriendlyName + cachedText); - output.WriteLine("Process: " + stepState.ProcessName + "(ID: 0x" + stepState.ProcessID.ToString("x") + ") on " + stepState.Machine); - output.WriteLine("{"); - } - - private void LogFooter(TestStepState stepState, ITestOutputHelper output) - { - output.WriteLine("}"); - string elapsedTime = null; - if (stepState.RunState == TestStepRunState.InProgress) - { - output.WriteLine(FriendlyName + " Not Complete"); - output.WriteLine(stepState.ErrorMessage); - } - else - { - elapsedTime = (stepState.CompleteTime.Value - stepState.StartTime).ToString("mm\\:ss\\.fff"); - } - if (stepState.RunState == TestStepRunState.Complete) - { - output.WriteLine(FriendlyName + " Complete (" + elapsedTime + " elapsed)"); - } - else if (stepState.RunState == TestStepRunState.Faulted) - { - output.WriteLine(FriendlyName + " Faulted (" + elapsedTime + " elapsed)"); - output.WriteLine(stepState.ErrorMessage); - output.WriteLine(stepState.ErrorStackTrace); - } - output.WriteLine(""); - output.WriteLine(""); - } - - private async Task AcquireStepStateLock(ITestOutputHelper output) - { - TestStepState initialStepState = new TestStepState(); - - bool stepStateFileExists = false; - while (true) - { - TestStepState openedStepState = null; - stepStateFileExists = File.Exists(_stateFilePath); - if (!stepStateFileExists && TryWriteInitialStepState(initialStepState, output)) - { - // this thread gets to do the work, persist the initial lock state - return initialStepState; - } - - if (stepStateFileExists && TryOpenExistingStepStateFile(out openedStepState, output)) - { - if (!ShouldReuseCachedStepState(openedStepState)) - { - try - { - File.Delete(_stateFilePath); - continue; - } - catch (IOException ex) - { - output.WriteLine("Exception deleting state file {0} {1}", _stateFilePath, ex.ToString()); - } - } - else if (openedStepState.RunState != TestStepRunState.InProgress) - { - // we can reuse the work and it is finished - stop waiting and return it - return openedStepState; - } - } - - // If we get here we are either: - // a) Waiting for some other thread (potentially in another process) to complete the work - // b) Waiting for a hopefully transient IO issue to resolve so that we can determine whether or not the work has already been claimed - // - // If we wait for too long in either case we will eventually timeout. - ThrowExceptionForIncompleteWorkIfNeeded(initialStepState, openedStepState, stepStateFileExists, output); - await Task.Delay(TimeSpan.FromSeconds(1)); - } - } - - private void ThrowExceptionForIncompleteWorkIfNeeded(TestStepState initialStepState, TestStepState openedStepState, bool stepStateFileExists, ITestOutputHelper output) - { - bool timeout = (DateTimeOffset.Now - initialStepState.StartTime > _timeout); - bool notFinishable = openedStepState != null && - ShouldReuseCachedStepState(openedStepState) && - openedStepState.RunState == TestStepRunState.InProgress && - !IsOpenedStateChangeable(openedStepState); - if (timeout || notFinishable) - { - TestStepState currentState = openedStepState != null ? openedStepState : initialStepState; - LogHeader(currentState, true, output); - StringBuilder errorMessage = new StringBuilder(); - if (timeout) - { - errorMessage.Append("Timeout after " + _timeout + ". "); - } - if (!stepStateFileExists) - { - errorMessage.Append("Unable to create file:" + Environment.NewLine + - _stateFilePath); - } - else if (openedStepState == null) - { - errorMessage.AppendLine("Unable to parse file:" + Environment.NewLine + - _stateFilePath); - } - else - { - // these error cases should have a valid previous log we can restore - Debug.Assert(currentState == openedStepState); - LogPreviousResults(currentState, output); - - errorMessage.AppendLine("This step was not marked complete in: " + Environment.NewLine + - _stateFilePath); - - if (!IsPreviousMachineSame(openedStepState)) - { - errorMessage.AppendLine("The current machine (" + Environment.MachineName + ") differs from the one which ran the step originally (" + currentState.Machine + ")." + Environment.NewLine + - "Perhaps the original process (ID: 0x" + currentState.ProcessID.ToString("x") + ") executing the work exited unexpectedly or the file was" + Environment.NewLine + - "copied to this machine before the work was complete?"); - } - else if (IsPreviousMachineSame(openedStepState) && !IsPreviousProcessRunning(openedStepState)) - { - errorMessage.AppendLine("As of " + DateTimeOffset.Now + " the process executing this step (ID: 0x" + currentState.ProcessID.ToString("x") + ")" + Environment.NewLine + - "is no longer running. Perhaps it was killed or exited unexpectedly?"); - } - else if (openedStepState.ProcessID != Process.GetCurrentProcess().Id) - { - errorMessage.AppendLine("As of " + DateTimeOffset.Now + " the process executing this step (ID: 0x" + currentState.ProcessID.ToString("x") + ")" + Environment.NewLine + - "is still running. The process may be hung or running more slowly than expected?"); - } - else - { - errorMessage.AppendLine("As of " + DateTimeOffset.Now + " this step should still be running on some other thread in this process (ID: 0x" + currentState.ProcessID.ToString("x") + ")" + Environment.NewLine + - "Perhaps the work has deadlocked or is running more slowly than expected?"); - } - - string reuseMessage = GetReuseStepStateReason(openedStepState); - if (reuseMessage == null) - { - reuseMessage = "Deleting the file to retry the test step was attempted automatically, but failed."; - } - else - { - reuseMessage = "Deleting the file to retry the test step was not attempted automatically because " + reuseMessage + "."; - } - errorMessage.Append(reuseMessage); - } - currentState = currentState.Incomplete(errorMessage.ToString()); - LogFooter(currentState, output); - if (timeout) - { - throw new TestStepException("Timeout waiting for " + FriendlyName + " step to complete." + Environment.NewLine + errorMessage.ToString()); - } - else - { - throw new TestStepException(FriendlyName + " step can not be completed." + Environment.NewLine + errorMessage.ToString()); - } - } - } - - private static bool ShouldReuseCachedStepState(TestStepState openedStepState) - { - return (GetReuseStepStateReason(openedStepState) != null); - } - - private static string GetReuseStepStateReason(TestStepState openedStepState) - { - //This heuristic may need to change, in some cases it is probably too eager to - //reuse past results when we wanted to retest something. - - if (openedStepState.RunState == TestStepRunState.Complete) - { - return "succesful steps are always reused"; - } - else if(!IsPreviousMachineSame(openedStepState)) - { - return "steps on run on other machines are always reused, regardless of success"; - } - else if(IsPreviousProcessRunning(openedStepState)) - { - return "steps run in currently executing processes are always reused, regardless of success"; - } - else - { - return null; - } - } - - private static bool IsPreviousMachineSame(TestStepState openedStepState) - { - return Environment.MachineName == openedStepState.Machine; - } - - private static bool IsPreviousProcessRunning(TestStepState openedStepState) - { - Debug.Assert(IsPreviousMachineSame(openedStepState)); - return (Process.GetProcesses().Any(p => p.Id == openedStepState.ProcessID && p.ProcessName == openedStepState.ProcessName)); - } - - private static bool IsOpenedStateChangeable(TestStepState openedStepState) - { - return (openedStepState.RunState == TestStepRunState.InProgress && - IsPreviousMachineSame(openedStepState) && - IsPreviousProcessRunning(openedStepState)); - } - - private void LogPreviousResults(TestStepState cachedTaskState, ITestOutputHelper output) - { - ITestOutputHelper indentedOutput = new IndentedTestOutputHelper(output); - try - { - string[] lines = File.ReadAllLines(_logFilePath); - foreach (string line in lines) - { - indentedOutput.WriteLine(line); - } - } - catch (IOException e) - { - string errorMessage = "Error accessing task log file: " + _logFilePath + Environment.NewLine + - e.GetType().FullName + ": " + e.Message; - indentedOutput.WriteLine(errorMessage); - } - } - - private void ThrowExceptionIfFaulted(TestStepState cachedStepState) - { - if(cachedStepState.RunState == TestStepRunState.Faulted) - { - throw new TestStepException(FriendlyName, cachedStepState.ErrorMessage, cachedStepState.ErrorStackTrace); - } - } - - enum TestStepRunState - { - InProgress, - Complete, - Faulted - } - - class TestStepState - { - public TestStepState() - { - RunState = TestStepRunState.InProgress; - Machine = Environment.MachineName; - ProcessID = Process.GetCurrentProcess().Id; - ProcessName = Process.GetCurrentProcess().ProcessName; - StartTime = DateTimeOffset.Now; - } - public TestStepState(TestStepRunState runState, - string machine, - int pid, - string processName, - DateTimeOffset startTime, - DateTimeOffset? completeTime, - string errorMessage, - string errorStackTrace) - { - RunState = runState; - Machine = machine; - ProcessID = pid; - ProcessName = processName; - StartTime = startTime; - CompleteTime = completeTime; - ErrorMessage = errorMessage; - ErrorStackTrace = errorStackTrace; - } - public TestStepRunState RunState { get; private set; } - public string Machine { get; private set; } - public int ProcessID { get; private set; } - public string ProcessName { get; private set; } - public string ErrorMessage { get; private set; } - public string ErrorStackTrace { get; private set; } - public DateTimeOffset StartTime { get; private set; } - public DateTimeOffset? CompleteTime { get; private set; } - - public TestStepState Incomplete(string errorMessage) - { - return WithFinalState(TestStepRunState.InProgress, null, errorMessage, null); - } - - public TestStepState Fault(string errorMessage, string errorStackTrace) - { - return WithFinalState(TestStepRunState.Faulted, DateTimeOffset.Now, errorMessage, errorStackTrace); - } - - public TestStepState Complete() - { - return WithFinalState(TestStepRunState.Complete, DateTimeOffset.Now, null, null); - } - - TestStepState WithFinalState(TestStepRunState runState, DateTimeOffset? taskCompleteTime, string errorMessage, string errorStackTrace) - { - return new TestStepState(runState, Machine, ProcessID, ProcessName, StartTime, taskCompleteTime, errorMessage, errorStackTrace); - } - - public string SerializeInitialState() - { - XElement initState = new XElement("InitialStepState", - new XElement("Machine", Machine), - new XElement("ProcessID", "0x" + ProcessID.ToString("x")), - new XElement("ProcessName", ProcessName), - new XElement("StartTime", StartTime) - ); - return initState.ToString(); - } - - public string SerializeFinalState() - { - XElement finalState = new XElement("FinalStepState", - new XElement("RunState", RunState) - ); - if (CompleteTime != null) - { - finalState.Add(new XElement("CompleteTime", CompleteTime.Value)); - } - if (ErrorMessage != null) - { - finalState.Add(new XElement("ErrorMessage", ErrorMessage)); - } - if (ErrorStackTrace != null) - { - finalState.Add(new XElement("ErrorStackTrace", ErrorStackTrace)); - } - return finalState.ToString(); - } - - public static bool TryParse(string text, out TestStepState parsedState) - { - parsedState = null; - try - { - // The XmlReader is not happy with two root nodes so we crudely split them. - int indexOfInitialStepStateElementEnd = text.IndexOf(""); - if(indexOfInitialStepStateElementEnd == -1) - { - return false; - } - int splitIndex = indexOfInitialStepStateElementEnd + "".Length; - string initialStepStateText = text.Substring(0, splitIndex); - string finalStepStateText = text.Substring(splitIndex); - - XElement initialStepStateElement = XElement.Parse(initialStepStateText); - if (initialStepStateElement == null || initialStepStateElement.Name != "InitialStepState") - { - return false; - } - XElement machineElement = initialStepStateElement.Element("Machine"); - if (machineElement == null || string.IsNullOrWhiteSpace(machineElement.Value)) - { - return false; - } - string machine = machineElement.Value; - XElement processIDElement = initialStepStateElement.Element("ProcessID"); - int processID; - if (processIDElement == null || - !processIDElement.Value.StartsWith("0x")) - { - return false; - } - string processIdNumberText = processIDElement.Value.Substring("0x".Length); - if (!int.TryParse(processIdNumberText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out processID)) - { - return false; - } - string processName = null; - XElement processNameElement = initialStepStateElement.Element("ProcessName"); - if (processNameElement != null) - { - processName = processNameElement.Value; - } - DateTimeOffset startTime; - XElement startTimeElement = initialStepStateElement.Element("StartTime"); - if (startTimeElement == null || !DateTimeOffset.TryParse(startTimeElement.Value, out startTime)) - { - return false; - } - parsedState = new TestStepState(TestStepRunState.InProgress, machine, processID, processName, startTime, null, null, null); - TryParseFinalState(finalStepStateText, ref parsedState); - return true; - } - catch (XmlException) - { - return false; - } - } - - private static void TryParseFinalState(string text, ref TestStepState taskState) - { - // If there are errors reading the final state portion of the stream we need to treat it - // as if the stream had terminated at the end of the InitialTaskState node. - // This covers a small window of time when appending the FinalTaskState node is in progress. - // - if(string.IsNullOrWhiteSpace(text)) - { - return; - } - try - { - XElement finalTaskStateElement = XElement.Parse(text); - if (finalTaskStateElement == null || finalTaskStateElement.Name != "FinalStepState") - { - return; - } - XElement runStateElement = finalTaskStateElement.Element("RunState"); - TestStepRunState runState; - if (runStateElement == null || !Enum.TryParse(runStateElement.Value, out runState)) - { - return; - } - DateTimeOffset? completeTime = null; - XElement completeTimeElement = finalTaskStateElement.Element("CompleteTime"); - if (completeTimeElement != null) - { - DateTimeOffset tempCompleteTime; - if (!DateTimeOffset.TryParse(completeTimeElement.Value, out tempCompleteTime)) - { - return; - } - else - { - completeTime = tempCompleteTime; - } - } - XElement errorMessageElement = finalTaskStateElement.Element("ErrorMessage"); - string errorMessage = null; - if (errorMessageElement != null) - { - errorMessage = errorMessageElement.Value; - } - XElement errorStackTraceElement = finalTaskStateElement.Element("ErrorStackTrace"); - string errorStackTrace = null; - if (errorStackTraceElement != null) - { - errorStackTrace = errorStackTraceElement.Value; - } - - taskState = taskState.WithFinalState(runState, completeTime, errorMessage, errorStackTrace); - } - catch (XmlException) { } - } - } - } - - public class TestStepException : Exception - { - public TestStepException(string errorMessage) : - base(errorMessage) - { } - - public TestStepException(string stepName, string errorMessage, string stackTrace) : - base("The " + stepName + " test step failed." + Environment.NewLine + - "Original Error: " + errorMessage + Environment.NewLine + - stackTrace) - { } - } -} diff --git a/src/DebuggerTests/Xunit.Extensions/SkipTestException.cs b/src/DebuggerTests/Xunit.Extensions/SkipTestException.cs deleted file mode 100644 index 21c5dbf6e..000000000 --- a/src/DebuggerTests/Xunit.Extensions/SkipTestException.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Xunit.Extensions -{ - public class SkipTestException : Exception - { - public SkipTestException(string reason) - : base(reason) { } - } -} diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableFactAttribute.cs b/src/DebuggerTests/Xunit.Extensions/SkippableFactAttribute.cs deleted file mode 100644 index 6ee8889cc..000000000 --- a/src/DebuggerTests/Xunit.Extensions/SkippableFactAttribute.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Xunit; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableFactDiscoverer", "Debugger.Tests")] - public class SkippableFactAttribute : FactAttribute { } -} diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableFactDiscoverer.cs b/src/DebuggerTests/Xunit.Extensions/SkippableFactDiscoverer.cs deleted file mode 100644 index 0e3eccfd0..000000000 --- a/src/DebuggerTests/Xunit.Extensions/SkippableFactDiscoverer.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class SkippableFactDiscoverer : IXunitTestCaseDiscoverer - { - readonly IMessageSink diagnosticMessageSink; - - public SkippableFactDiscoverer(IMessageSink diagnosticMessageSink) - { - this.diagnosticMessageSink = diagnosticMessageSink; - } - - public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) - { - yield return new SkippableFactTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod); - } - } -} diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableFactMessageBus.cs b/src/DebuggerTests/Xunit.Extensions/SkippableFactMessageBus.cs deleted file mode 100644 index 9a7fa3074..000000000 --- a/src/DebuggerTests/Xunit.Extensions/SkippableFactMessageBus.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class SkippableFactMessageBus : IMessageBus - { - readonly IMessageBus innerBus; - - public SkippableFactMessageBus(IMessageBus innerBus) - { - this.innerBus = innerBus; - } - - public int DynamicallySkippedTestCount { get; private set; } - - public void Dispose() { } - - public bool QueueMessage(IMessageSinkMessage message) - { - var testFailed = message as ITestFailed; - if (testFailed != null) - { - var exceptionType = testFailed.ExceptionTypes.FirstOrDefault(); - if (exceptionType == typeof(SkipTestException).FullName) - { - DynamicallySkippedTestCount++; - return innerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault())); - } - } - - // Nothing we care about, send it on its way - return innerBus.QueueMessage(message); - } - } -} diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableFactTestCase.cs b/src/DebuggerTests/Xunit.Extensions/SkippableFactTestCase.cs deleted file mode 100644 index 6e34cdec3..000000000 --- a/src/DebuggerTests/Xunit.Extensions/SkippableFactTestCase.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class SkippableFactTestCase : XunitTestCase - { - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public SkippableFactTestCase() { } - - public SkippableFactTestCase(IMessageSink diagnosticMessageSink, Sdk.TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null) - : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments) { } - - public override async Task RunAsync(IMessageSink diagnosticMessageSink, - IMessageBus messageBus, - object[] constructorArguments, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - { - var skipMessageBus = new SkippableFactMessageBus(messageBus); - var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource); - if (skipMessageBus.DynamicallySkippedTestCount > 0) - { - result.Failed -= skipMessageBus.DynamicallySkippedTestCount; - result.Skipped += skipMessageBus.DynamicallySkippedTestCount; - } - - return result; - } - } -} diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableTheoryAttribute.cs b/src/DebuggerTests/Xunit.Extensions/SkippableTheoryAttribute.cs deleted file mode 100644 index fd984128a..000000000 --- a/src/DebuggerTests/Xunit.Extensions/SkippableTheoryAttribute.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Xunit; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableTheoryDiscoverer", "Debugger.Tests")] - public class SkippableTheoryAttribute : TheoryAttribute { } -} diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableTheoryDiscoverer.cs b/src/DebuggerTests/Xunit.Extensions/SkippableTheoryDiscoverer.cs deleted file mode 100644 index 2640a021b..000000000 --- a/src/DebuggerTests/Xunit.Extensions/SkippableTheoryDiscoverer.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class SkippableTheoryDiscoverer : IXunitTestCaseDiscoverer - { - readonly IMessageSink diagnosticMessageSink; - readonly TheoryDiscoverer theoryDiscoverer; - - public SkippableTheoryDiscoverer(IMessageSink diagnosticMessageSink) - { - this.diagnosticMessageSink = diagnosticMessageSink; - - theoryDiscoverer = new TheoryDiscoverer(diagnosticMessageSink); - } - - public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) - { - var defaultMethodDisplay = discoveryOptions.MethodDisplayOrDefault(); - - // Unlike fact discovery, the underlying algorithm for theories is complex, so we let the theory discoverer - // do its work, and do a little on-the-fly conversion into our own test cases. - return theoryDiscoverer.Discover(discoveryOptions, testMethod, factAttribute) - .Select(testCase => testCase is XunitTheoryTestCase - ? (IXunitTestCase)new SkippableTheoryTestCase(diagnosticMessageSink, defaultMethodDisplay, testCase.TestMethod) - : new SkippableFactTestCase(diagnosticMessageSink, defaultMethodDisplay, testCase.TestMethod, testCase.TestMethodArguments)); - } - } -} diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableTheoryTestCase.cs b/src/DebuggerTests/Xunit.Extensions/SkippableTheoryTestCase.cs deleted file mode 100644 index a27012f6c..000000000 --- a/src/DebuggerTests/Xunit.Extensions/SkippableTheoryTestCase.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class SkippableTheoryTestCase : XunitTheoryTestCase - { - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public SkippableTheoryTestCase() { } - - public SkippableTheoryTestCase(IMessageSink diagnosticMessageSink, Xunit.Sdk.TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod) - : base(diagnosticMessageSink, defaultMethodDisplay, testMethod) { } - - public override async Task RunAsync(IMessageSink diagnosticMessageSink, - IMessageBus messageBus, - object[] constructorArguments, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - { - // Duplicated code from SkippableFactTestCase. I'm sure we could find a way to de-dup with some thought. - var skipMessageBus = new SkippableFactMessageBus(messageBus); - var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource); - if (skipMessageBus.DynamicallySkippedTestCount > 0) - { - result.Failed -= skipMessageBus.DynamicallySkippedTestCount; - result.Skipped += skipMessageBus.DynamicallySkippedTestCount; - } - - return result; - } - } -} diff --git a/src/DebuggerTests/Xunit.Extensions/license.txt b/src/DebuggerTests/Xunit.Extensions/license.txt deleted file mode 100644 index 39b2d6592..000000000 --- a/src/DebuggerTests/Xunit.Extensions/license.txt +++ /dev/null @@ -1,15 +0,0 @@ -Source in this directory is derived source at https://github.com/xunit/samples.xunit. The repo provided the following license: - -Copyright 2014 Outercurve Foundation - -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/src/Microsoft.Diagnostic.TestHelpers/AcquireDotNetTestStep.cs b/src/Microsoft.Diagnostic.TestHelpers/AcquireDotNetTestStep.cs new file mode 100644 index 000000000..e18cfb85b --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/AcquireDotNetTestStep.cs @@ -0,0 +1,221 @@ +// 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.IO; +using System.IO.Compression; +using System.Net; +using System.Threading.Tasks; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// Acquires the CLI tools from a web endpoint, a local zip/tar.gz, or directly from a local path + /// + public class AcquireDotNetTestStep : TestStep + { + /// + /// Create a new AcquireDotNetTestStep + /// + /// + /// If non-null, the CLI tools will be downloaded from this web endpoint. + /// The path should use an http or https scheme and the remote file should be in .zip or .tar.gz format. + /// localDotNetZipPath must also be non-null to indicate where the downloaded archive will be cached + /// + /// If non-null, the location of a .zip or .tar.gz compressed folder containing the CLI tools. This + /// must be a local file system or network file system path. + /// localDotNetZipExpandDirPath must also be non-null to indicate where the expanded folder will be + /// stored. + /// localDotNetTarPath must be non-null if localDotNetZip points to a .tar.gz format archive, in order + /// to indicate where the .tar file will be cached + /// + /// If localDotNetZipPath points to a .tar.gz, this path will be used to store the uncompressed .tar + /// file. Otherwise this path is unused. + /// + /// If localDotNetZipPath is non-null, this path will be used to store the expanded version of the + /// archive. Otherwise this path is unused. + /// + /// The path to the dotnet binary. When the CLI tools are being acquired from a compressed archive + /// this will presumably be a path inside the localDotNetZipExpandDirPath directory, otherwise + /// it can be any local file system path where the dotnet binary can be found. + /// + /// The path where an activity log for this test step should be written. + /// + /// + public AcquireDotNetTestStep( + string remoteDotNetZipPath, + string localDotNetZipPath, + string localDotNetTarPath, + string localDotNetZipExpandDirPath, + string localDotNetPath, + string logFilePath) + : base(logFilePath, "Acquire DotNet Tools") + { + RemoteDotNetPath = remoteDotNetZipPath; + LocalDotNetZipPath = localDotNetZipPath; + if (localDotNetZipPath != null && localDotNetZipPath.EndsWith(".tar.gz")) + { + LocalDotNetTarPath = localDotNetTarPath; + } + if (localDotNetZipPath != null) + { + LocalDotNetZipExpandDirPath = localDotNetZipExpandDirPath; + } + LocalDotNetPath = localDotNetPath; + } + + /// + /// If non-null, the CLI tools will be downloaded from this web endpoint. + /// The path should use an http or https scheme and the remote file should be in .zip or .tar.gz format. + /// + public string RemoteDotNetPath { get; private set; } + + /// + /// If non-null, the location of a .zip or .tar.gz compressed folder containing the CLI tools. This + /// is a local file system or network file system path. + /// + public string LocalDotNetZipPath { get; private set; } + + /// + /// If localDotNetZipPath points to a .tar.gz, this path will be used to store the uncompressed .tar + /// file. Otherwise null. + /// + public string LocalDotNetTarPath { get; private set; } + + /// + /// If localDotNetZipPath is non-null, this path will be used to store the expanded version of the + /// archive. Otherwise null. + /// + public string LocalDotNetZipExpandDirPath { get; private set; } + + /// + /// The path to the dotnet binary when the test step is complete. + /// + public string LocalDotNetPath { get; private set; } + + /// + /// Returns true, if there any actual work to do (like downloading, unziping or untaring). + /// + public bool AnyWorkToDo { get { return RemoteDotNetPath != null || LocalDotNetZipPath != null; } } + + async protected override Task DoWork(ITestOutputHelper output) + { + if (RemoteDotNetPath != null) + { + await DownloadFile(RemoteDotNetPath, LocalDotNetZipPath, output); + } + if (LocalDotNetZipPath != null) + { + if (LocalDotNetZipPath.EndsWith(".zip")) + { + await Unzip(LocalDotNetZipPath, LocalDotNetZipExpandDirPath, output); + } + else if(LocalDotNetZipPath.EndsWith(".tar.gz")) + { + await UnGZip(LocalDotNetZipPath, LocalDotNetTarPath, output); + await Untar(LocalDotNetTarPath, LocalDotNetZipExpandDirPath, output); + } + else + { + output.WriteLine("Unsupported compression format: " + LocalDotNetZipPath); + throw new NotSupportedException("Unsupported compression format: " + LocalDotNetZipPath); + } + } + output.WriteLine("Dotnet path: " + LocalDotNetPath); + if (!File.Exists(LocalDotNetPath)) + { + throw new FileNotFoundException(LocalDotNetPath + " not found"); + } + } + + async static Task DownloadFile(string remotePath, string localPath, ITestOutputHelper output) + { + output.WriteLine("Downloading: " + remotePath + " -> " + localPath); + Directory.CreateDirectory(Path.GetDirectoryName(localPath)); + WebRequest request = HttpWebRequest.Create(remotePath); + WebResponse response = await request.GetResponseAsync(); + using (FileStream localZipStream = File.OpenWrite(localPath)) + { + // TODO: restore the CopyToAsync code after System.Net.Http.dll is + // updated to a newer version. The current old version has a bug + // where the copy never finished. + // await response.GetResponseStream().CopyToAsync(localZipStream); + byte[] buffer = new byte[16 * 1024]; + long bytesLeft = response.ContentLength; + + while (bytesLeft > 0) + { + int read = response.GetResponseStream().Read(buffer, 0, buffer.Length); + if (read == 0) + break; + localZipStream.Write(buffer, 0, read); + bytesLeft -= read; + } + output.WriteLine("Downloading finished"); + } + } + + async static Task UnGZip(string gzipPath, string expandedFilePath, ITestOutputHelper output) + { + output.WriteLine("Unziping: " + gzipPath + " -> " + expandedFilePath); + using (FileStream gzipStream = File.OpenRead(gzipPath)) + { + using (GZipStream expandedStream = new GZipStream(gzipStream, CompressionMode.Decompress)) + { + using (FileStream targetFileStream = File.OpenWrite(expandedFilePath)) + { + await expandedStream.CopyToAsync(targetFileStream); + } + } + } + } + + async static Task Unzip(string zipPath, string expandedDirPath, ITestOutputHelper output) + { + output.WriteLine("Unziping: " + zipPath + " -> " + expandedDirPath); + using (FileStream zipStream = File.OpenRead(zipPath)) + { + ZipArchive zip = new ZipArchive(zipStream); + foreach (ZipArchiveEntry entry in zip.Entries) + { + string extractedFilePath = Path.Combine(expandedDirPath, entry.FullName); + Directory.CreateDirectory(Path.GetDirectoryName(extractedFilePath)); + using (Stream zipFileStream = entry.Open()) + { + using (FileStream extractedFileStream = File.OpenWrite(extractedFilePath)) + { + await zipFileStream.CopyToAsync(extractedFileStream); + } + } + } + } + } + + async static Task Untar(string tarPath, string expandedDirPath, ITestOutputHelper output) + { + Directory.CreateDirectory(expandedDirPath); + string tarToolPath = null; + if (OS.Kind == OSKind.Linux) + { + tarToolPath = "/bin/tar"; + } + else if (OS.Kind == OSKind.OSX) + { + tarToolPath = "/usr/bin/tar"; + } + else + { + throw new NotSupportedException("Unknown where this OS stores the tar executable"); + } + + await new ProcessRunner(tarToolPath, "-xf " + tarPath). + WithWorkingDirectory(expandedDirPath). + WithLog(output). + WithExpectedExitCode(0). + Run(); + } + + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/AssertX.cs b/src/Microsoft.Diagnostic.TestHelpers/AssertX.cs new file mode 100644 index 000000000..2672ca9b7 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/AssertX.cs @@ -0,0 +1,81 @@ +// 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.IO; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + public static partial class AssertX + { + public static void DirectoryExists(string dirDescriptiveName, string dirPath, ITestOutputHelper output) + { + if (!Directory.Exists(dirPath)) + { + string errorMessage = "Expected " + dirDescriptiveName + " to exist: " + dirPath; + output.WriteLine(errorMessage); + try + { + string parentDir = dirPath; + while (true) + { + if (Directory.Exists(Path.GetDirectoryName(parentDir))) + { + output.WriteLine("First parent directory that exists: " + Path.GetDirectoryName(parentDir)); + break; + } + if (Path.GetDirectoryName(parentDir) == parentDir) + { + output.WriteLine("Also unable to find any parent directory that exists"); + break; + } + parentDir = Path.GetDirectoryName(parentDir); + } + } + catch (Exception e) + { + output.WriteLine("Additional error while trying to diagnose missing directory:"); + output.WriteLine(e.GetType() + ": " + e.Message); + } + throw new DirectoryNotFoundException(errorMessage); + } + } + + public static void FileExists(string fileDescriptiveName, string filePath, ITestOutputHelper output) + { + if (!File.Exists(filePath)) + { + string errorMessage = "Expected " + fileDescriptiveName + " to exist: " + filePath; + output.WriteLine(errorMessage); + try + { + string parentDir = filePath; + while (true) + { + if (Directory.Exists(Path.GetDirectoryName(parentDir))) + { + output.WriteLine("First parent directory that exists: " + Path.GetDirectoryName(parentDir)); + break; + } + if (Path.GetDirectoryName(parentDir) == parentDir) + { + output.WriteLine("Also unable to find any parent directory that exists"); + break; + } + parentDir = Path.GetDirectoryName(parentDir); + } + } + catch (Exception e) + { + output.WriteLine("Additional error while trying to diagnose missing file:"); + output.WriteLine(e.GetType() + ": " + e.Message); + } + throw new FileNotFoundException(errorMessage); + } + } + } +} + + diff --git a/src/Microsoft.Diagnostic.TestHelpers/BaseDebuggeeCompiler.cs b/src/Microsoft.Diagnostic.TestHelpers/BaseDebuggeeCompiler.cs new file mode 100644 index 000000000..ab67354d3 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/BaseDebuggeeCompiler.cs @@ -0,0 +1,233 @@ +// 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 System.Threading.Tasks; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// This compiler acquires the CLI tools and uses them to build debuggees. + /// + /// + /// The build process consists of the following steps: + /// 1. Acquire the CLI tools from the CliPath. This generally involves downloading them from the web and unpacking them. + /// 2. Create a source directory with the conventions that dotnet expects by copying it from the DebuggeeSourceRoot. Any project.template.json + /// file that is found will be specialized by replacing macros with specific contents. This lets us decide the runtime and dependency versions + /// at test execution time. + /// 3. Run dotnet restore in the newly created source directory + /// 4. Run dotnet build in the same directory + /// 5. Rename the built debuggee dll to an exe (so that it conforms to some historical conventions our tests expect) + /// 6. Copy any native dependencies from the DebuggeeNativeLibRoot to the debuggee output directory + /// + public abstract class BaseDebuggeeCompiler : IDebuggeeCompiler + { + AcquireDotNetTestStep _acquireTask; + DotNetBuildDebuggeeTestStep _buildDebuggeeTask; + + /// + /// Creates a new BaseDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build debuggees via dotnet build. + /// + /// + /// The test configuration that will be used to configure the build. The following configuration options should be set in the config: + /// CliPath The location to get the CLI tools from, either as a .zip/.tar.gz at a web endpoint, a .zip/.tar.gz + /// at a local filesystem path, or the dotnet binary at a local filesystem path + /// WorkingDir Temporary storage for CLI tools compressed archives downloaded from the internet will be stored here + /// CliCacheRoot The final CLI tools will be expanded and cached here + /// DebuggeeSourceRoot Debuggee sources and template project file will be retrieved from here + /// DebuggeeNativeLibRoot Debuggee native binary dependencies will be retrieved from here + /// DebuggeeBuildRoot Debuggee final sources/project file/binary outputs will be placed here + /// BuildProjectRuntime The runtime moniker to be built + /// BuildProjectMicrosoftNETCoreAppVersion The nuget package version of Microsoft.NETCore.App package to build against for debuggees that references this library + /// NugetPackageCacheDir The directory where NuGet packages are cached during restore + /// NugetFeeds The set of nuget feeds that are used to search for packages during restore + /// + /// + /// The name of the debuggee to be built, from which various build file paths are constructed. Before build it is assumed that: + /// Debuggee sources are located at config.DebuggeeSourceRoot/debuggeeName/ + /// Debuggee native dependencies are located at config.DebuggeeNativeLibRoot/debuggeeName/ + /// + /// After the build: + /// Debuggee build outputs will be created at config.DebuggeeNativeLibRoot/debuggeeName/ + /// A log of the build is stored at config.DebuggeeNativeLibRoot/debuggeeName.txt + /// + public BaseDebuggeeCompiler(TestConfiguration config, string debuggeeName) + { + _acquireTask = ConfigureAcquireDotNetTask(config); + _buildDebuggeeTask = ConfigureDotNetBuildDebuggeeTask(config, _acquireTask.LocalDotNetPath, config.CliVersion, debuggeeName); + } + + async public Task Execute(ITestOutputHelper output) + { + if (_acquireTask.AnyWorkToDo) + { + await _acquireTask.Execute(output); + } + await _buildDebuggeeTask.Execute(output); + return new DebuggeeConfiguration(_buildDebuggeeTask.DebuggeeProjectDirPath, + _buildDebuggeeTask.DebuggeeBinaryDirPath, + _buildDebuggeeTask.DebuggeeBinaryExePath ?? _buildDebuggeeTask.DebuggeeBinaryDllPath); + } + + public static AcquireDotNetTestStep ConfigureAcquireDotNetTask(TestConfiguration config) + { + string remoteCliZipPath = null; + string localCliZipPath = null; + string localCliTarPath = null; + string localCliExpandedDirPath = null; + + string dotNetPath = config.CliPath; + if (dotNetPath.StartsWith("http:") || dotNetPath.StartsWith("https:")) + { + remoteCliZipPath = dotNetPath; + dotNetPath = Path.Combine(config.WorkingDir, "dotnet_zip", Path.GetFileName(remoteCliZipPath)); + } + if (dotNetPath.EndsWith(".zip") || dotNetPath.EndsWith(".tar.gz")) + { + localCliZipPath = dotNetPath; + string cliVersionDirName = null; + if (dotNetPath.EndsWith(".tar.gz")) + { + localCliTarPath = localCliZipPath.Substring(0, dotNetPath.Length - 3); + cliVersionDirName = Path.GetFileNameWithoutExtension(localCliTarPath); + } + else + { + cliVersionDirName = Path.GetFileNameWithoutExtension(localCliZipPath); + } + + localCliExpandedDirPath = Path.Combine(config.CliCacheRoot, cliVersionDirName); + dotNetPath = Path.Combine(localCliExpandedDirPath, OS.Kind == OSKind.Windows ? "dotnet.exe" : "dotnet"); + } + string acquireLogDir = Path.GetDirectoryName(Path.GetDirectoryName(dotNetPath)); + string acquireLogPath = Path.Combine(acquireLogDir, Path.GetDirectoryName(dotNetPath) + ".acquisition_log.txt"); + return new AcquireDotNetTestStep( + remoteCliZipPath, + localCliZipPath, + localCliTarPath, + localCliExpandedDirPath, + dotNetPath, + acquireLogPath); + } + + + protected static string GetInitialSourceDirPath(TestConfiguration config, string debuggeeName) + { + return Path.Combine(config.DebuggeeSourceRoot, debuggeeName); + } + + protected static string GetDebuggeeNativeLibDirPath(TestConfiguration config, string debuggeeName) + { + return Path.Combine(config.DebuggeeNativeLibRoot, debuggeeName); + } + + protected static string GetDebuggeeSolutionDirPath(string dotNetRootBuildDirPath, string debuggeeName) + { + return Path.Combine(dotNetRootBuildDirPath, debuggeeName); + } + + protected static string GetDotNetRootBuildDirPath(TestConfiguration config) + { + return config.DebuggeeBuildRoot; + } + + protected static string GetDebuggeeProjectDirPath(string debuggeeSolutionDirPath, string initialSourceDirPath, string debuggeeName) + { + string debuggeeProjectDirPath = debuggeeSolutionDirPath; + if (Directory.Exists(Path.Combine(initialSourceDirPath, debuggeeName))) + { + debuggeeProjectDirPath = Path.Combine(debuggeeSolutionDirPath, debuggeeName); + } + return debuggeeProjectDirPath; + } + + protected virtual string GetDebuggeeBinaryDirPath(string debuggeeProjectDirPath, string framework, string runtime) + { + string debuggeeBinaryDirPath = null; + if (runtime != null) + { + debuggeeBinaryDirPath = Path.Combine(debuggeeProjectDirPath, "bin", "Debug", framework, runtime); + } + else + { + debuggeeBinaryDirPath = Path.Combine(debuggeeProjectDirPath, "bin", "Debug", framework); + } + return debuggeeBinaryDirPath; + } + + protected static string GetDebuggeeBinaryDllPath(string debuggeeBinaryDirPath, string debuggeeName) + { + return Path.Combine(debuggeeBinaryDirPath, debuggeeName + ".dll"); + } + + protected static string GetDebuggeeBinaryExePath(string debuggeeBinaryDirPath, string debuggeeName) + { + return Path.Combine(debuggeeBinaryDirPath, debuggeeName + ".exe"); + } + + protected static string GetLogPath(TestConfiguration config, string debuggeeName) + { + return Path.Combine(GetDotNetRootBuildDirPath(config), debuggeeName + ".txt"); + } + + protected static Dictionary GetNugetFeeds(TestConfiguration config) + { + Dictionary nugetFeeds = new Dictionary(); + if(!string.IsNullOrWhiteSpace(config.NuGetPackageFeeds)) + { + string[] feeds = config.NuGetPackageFeeds.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach(string feed in feeds) + { + string[] feedParts = feed.Trim().Split('='); + if(feedParts.Length != 2) + { + throw new Exception("Expected feed \'" + feed + "\' to value = format"); + } + nugetFeeds.Add(feedParts[0], feedParts[1]); + } + } + return nugetFeeds; + } + + protected static string GetRuntime(TestConfiguration config) + { + return config.BuildProjectRuntime; + } + + protected abstract string GetFramework(TestConfiguration config); + + //we anticipate source paths like this: + //InitialSource: / + //DebuggeeNativeLibDir: / + //DotNetRootBuildDir: + //DebuggeeSolutionDir: / + //DebuggeeProjectDir: /[/] + //DebuggeeBinaryDir: /[/]/bin/Debug//[] + //DebuggeeBinaryDll: /[/]/bin/Debug//.dll + //DebuggeeBinaryExe: /[/]/bin/Debug//[]/.exe + //LogPath: /.txt + + // As seen above the project directory has two forms. In most cases it is identical with the solution + // directory for solutions that only build one managed binary. For a few cases where we need to build + // multiple binaries the project directory is nested one additional level. Siblings of the project directory + // are used for the referenced assemblies' project directories. For example: + ///MyApp/global.json + ///MyApp/MyApp/project.json + ///MyApp/MyHelperLib/project.json + + // some combinations of dotnet + project.json seem to produce a runtime directory after the framework and some don't. + // I don't yet understand what exact factors drive this choice though I assume it has to do with shared runtime support. + // The logic works for the current default configuration but may not correctly handle others. + // + // When the runtime directory is present it will have a native host exe in it that has been renamed to the debugee + // name. It also has a managed dll in it which functions as a managed exe when renamed. + // When the runtime directory is missing, the framework directory will have a managed dll in it that functions if it + // is renamed to an exe. I'm sure that renaming isn't the intended usage, but it works and produces less churn + // in our tests for the moment. + public abstract DotNetBuildDebuggeeTestStep ConfigureDotNetBuildDebuggeeTask(TestConfiguration config, string dotNetPath, string cliToolsVersion, string debuggeeName); + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/CliDebuggeeCompiler.cs b/src/Microsoft.Diagnostic.TestHelpers/CliDebuggeeCompiler.cs new file mode 100644 index 000000000..bd853f296 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/CliDebuggeeCompiler.cs @@ -0,0 +1,80 @@ +// 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.Collections.Generic; +using System.IO; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// This compiler acquires the CLI tools and uses them to build and optionally link debuggees via dotnet publish. + /// + public class CliDebuggeeCompiler : BaseDebuggeeCompiler + { + /// + /// Creates a new CliDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build and optionally link debuggees via dotnet publish. + /// + /// LinkerPackageVersion If set, this version of the linker package will be used to link the debuggee during publish. + /// + /// + public CliDebuggeeCompiler(TestConfiguration config, string debuggeeName) : base(config, debuggeeName) {} + + private static Dictionary GetBuildProperties(TestConfiguration config, string runtimeIdentifier) + { + Dictionary buildProperties = new Dictionary(); + buildProperties.Add("RuntimeFrameworkVersion", config.BuildProjectMicrosoftNetCoreAppVersion); + if (runtimeIdentifier != null) + { + buildProperties.Add("RuntimeIdentifier", runtimeIdentifier); + } + string debugType = config.DebugType; + if (debugType == null) + { + // The default PDB type is portable + debugType = "portable"; + } + buildProperties.Add("DebugType", debugType); + return buildProperties; + } + + protected override string GetFramework(TestConfiguration config) + { + return config.BuildProjectFramework ?? "netcoreapp2.0"; + } + + protected override string GetDebuggeeBinaryDirPath(string debuggeeProjectDirPath, string framework, string runtimeIdentifier) + { + string debuggeeBinaryDirPath = base.GetDebuggeeBinaryDirPath(debuggeeProjectDirPath, framework, runtimeIdentifier); + debuggeeBinaryDirPath = Path.Combine(debuggeeBinaryDirPath, "publish"); + return debuggeeBinaryDirPath; + } + + public override DotNetBuildDebuggeeTestStep ConfigureDotNetBuildDebuggeeTask(TestConfiguration config, string dotNetPath, string cliToolsVersion, string debuggeeName) + { + string runtimeIdentifier = GetRuntime(config); + string initialSourceDirPath = GetInitialSourceDirPath(config, debuggeeName); + string dotNetRootBuildDirPath = GetDotNetRootBuildDirPath(config); + string debuggeeSolutionDirPath = GetDebuggeeSolutionDirPath(dotNetRootBuildDirPath, debuggeeName); + string debuggeeProjectDirPath = GetDebuggeeProjectDirPath(debuggeeSolutionDirPath, initialSourceDirPath, debuggeeName); + string debuggeeBinaryDirPath = GetDebuggeeBinaryDirPath(debuggeeProjectDirPath, GetFramework(config), runtimeIdentifier); + string debuggeeBinaryDllPath = GetDebuggeeBinaryDllPath(debuggeeBinaryDirPath, debuggeeName); + string debuggeeBinaryExePath = runtimeIdentifier != null ? GetDebuggeeBinaryExePath(debuggeeBinaryDirPath, debuggeeName) : null; + return new CsprojBuildDebuggeeTestStep(dotNetPath, + initialSourceDirPath, + GetDebuggeeNativeLibDirPath(config, debuggeeName), + GetBuildProperties(config, runtimeIdentifier), + runtimeIdentifier, + config.LinkerPackageVersion, + debuggeeName, + debuggeeSolutionDirPath, + debuggeeProjectDirPath, + debuggeeBinaryDirPath, + debuggeeBinaryDllPath, + debuggeeBinaryExePath, + config.NuGetPackageCacheDir, + GetNugetFeeds(config), + GetLogPath(config, debuggeeName)); + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/ConsoleTestOutputHelper.cs b/src/Microsoft.Diagnostic.TestHelpers/ConsoleTestOutputHelper.cs new file mode 100644 index 000000000..38b5af98b --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/ConsoleTestOutputHelper.cs @@ -0,0 +1,22 @@ +// 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 Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + public class ConsoleTestOutputHelper : ITestOutputHelper + { + public void WriteLine(string message) + { + Console.WriteLine(message); + } + + public void WriteLine(string format, params object[] args) + { + Console.WriteLine(format, args); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Diagnostic.TestHelpers/CsprojBuildDebuggeeTestStep.cs b/src/Microsoft.Diagnostic.TestHelpers/CsprojBuildDebuggeeTestStep.cs new file mode 100644 index 000000000..58ce9db61 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/CsprojBuildDebuggeeTestStep.cs @@ -0,0 +1,128 @@ +// 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.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Xml.Linq; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// This test step builds debuggees using the dotnet tools with .csproj projects files. + /// + /// + /// Any .csproj file that is found will be specialized by adding a linker package reference. + /// This lets us decide the runtime and dependency versions at test execution time. + /// + public class CsprojBuildDebuggeeTestStep : DotNetBuildDebuggeeTestStep + { + /// + /// A mapping from .csproj property strings to their values. These properties will be set when building the debuggee. + /// + /// + /// The runtime moniker to be built. + /// + public CsprojBuildDebuggeeTestStep(string dotnetToolPath, + string templateSolutionDirPath, + string debuggeeNativeLibDirPath, + Dictionary buildProperties, + string runtimeIdentifier, + string linkerPackageVersion, + string debuggeeName, + string debuggeeSolutionDirPath, + string debuggeeProjectDirPath, + string debuggeeBinaryDirPath, + string debuggeeBinaryDllPath, + string debuggeeBinaryExePath, + string nugetPackageCacheDirPath, + Dictionary nugetFeeds, + string logPath) : + base(dotnetToolPath, + templateSolutionDirPath, + debuggeeNativeLibDirPath, + debuggeeSolutionDirPath, + debuggeeProjectDirPath, + debuggeeBinaryDirPath, + debuggeeBinaryDllPath, + debuggeeBinaryExePath, + nugetPackageCacheDirPath, + nugetFeeds, + logPath) + { + BuildProperties = buildProperties; + RuntimeIdentifier = runtimeIdentifier; + DebuggeeName = debuggeeName; + ProjectTemplateFileName = debuggeeName + ".csproj"; + LinkerPackageVersion = linkerPackageVersion; + } + + /// + /// A mapping from .csproj property strings to their values. These properties will be set when building the debuggee. + /// + public IDictionary BuildProperties { get; } + public string RuntimeIdentifier { get; } + public string DebuggeeName { get; } + public string LinkerPackageVersion { get; } + public override string ProjectTemplateFileName { get; } + + protected override async Task Restore(ITestOutputHelper output) + { + string extraArgs = null; + if (RuntimeIdentifier != null) + { + extraArgs = " --runtime " + RuntimeIdentifier; + } + await Restore(extraArgs, output); + } + + protected override async Task Build(ITestOutputHelper output) + { + string publishArgs = "publish"; + foreach (var prop in BuildProperties) + { + publishArgs += $" /p:{prop.Key}={prop.Value}"; + } + await Build(publishArgs, output); + } + + protected override void ExpandProjectTemplate(string filePath, string destDirPath, ITestOutputHelper output) + { + ConvertCsprojTemplate(filePath, Path.Combine(destDirPath, DebuggeeName + ".csproj"), output); + } + + private void ConvertCsprojTemplate(string csprojTemplatePath, string csprojOutPath, ITestOutputHelper output) + { + var xdoc = XDocument.Load(csprojTemplatePath); + var ns = xdoc.Root.GetDefaultNamespace(); + if (LinkerPackageVersion != null) + { + AddLinkerPackageReference(xdoc, ns, LinkerPackageVersion, output); + } + using (var fs = new FileStream(csprojOutPath, FileMode.Create)) + { + xdoc.Save(fs); + } + } + + private static void AddLinkerPackageReference(XDocument xdoc, XNamespace ns, string linkerPackageVersion, ITestOutputHelper output) + { + xdoc.Root.Add(new XElement(ns + "ItemGroup", + new XElement(ns + "PackageReference", + new XAttribute("Include", "ILLink.Tasks"), + new XAttribute("Version", linkerPackageVersion)))); + } + + protected override void AssertDebuggeeAssetsFileExists(ITestOutputHelper output) + { + AssertX.FileExists("debuggee project.assets.json", Path.Combine(DebuggeeProjectDirPath, "obj", "project.assets.json"), output); + } + + protected override void AssertDebuggeeProjectFileExists(ITestOutputHelper output) + { + AssertX.FileExists("debuggee csproj", Path.Combine(DebuggeeProjectDirPath, DebuggeeName + ".csproj"), output); + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/DebuggeeCompiler.cs b/src/Microsoft.Diagnostic.TestHelpers/DebuggeeCompiler.cs new file mode 100644 index 000000000..725bca9f3 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/DebuggeeCompiler.cs @@ -0,0 +1,54 @@ +// 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.Threading.Tasks; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// DebugeeCompiler is responsible for finding and/or producing the source and binaries of a given debuggee. + /// The steps it takes to do this depend on the TestConfiguration. + /// + public static class DebuggeeCompiler + { + async public static Task Execute(TestConfiguration config, string debuggeeName, ITestOutputHelper output) + { + IDebuggeeCompiler compiler = null; + if (config.DebuggeeBuildProcess == "prebuilt") + { + compiler = new PrebuiltDebuggeeCompiler(config, debuggeeName); + } + else if (config.DebuggeeBuildProcess == "cli") + { + compiler = new CliDebuggeeCompiler(config, debuggeeName); + } + else + { + throw new Exception("Invalid DebuggeeBuildProcess configuration value. Expected 'prebuilt', actual \'" + config.DebuggeeBuildProcess + "\'"); + } + + return await compiler.Execute(output); + } + } + + public interface IDebuggeeCompiler + { + Task Execute(ITestOutputHelper output); + } + + public class DebuggeeConfiguration + { + public DebuggeeConfiguration(string sourcePath, string binaryDirPath, string binaryExePath) + { + SourcePath = sourcePath; + BinaryDirPath = binaryDirPath; + BinaryExePath = binaryExePath; + } + public string SourcePath { get; private set; } + public string BinaryDirPath { get; private set; } + public string BinaryExePath { get; private set; } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/DotNetBuildDebuggeeTestStep.cs b/src/Microsoft.Diagnostic.TestHelpers/DotNetBuildDebuggeeTestStep.cs new file mode 100644 index 000000000..a1030b770 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/DotNetBuildDebuggeeTestStep.cs @@ -0,0 +1,378 @@ +// 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 System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// This test step builds debuggees using the dotnet tools + /// + /// + /// The build process consists of the following steps: + /// 1. Create a source directory with the conventions that dotnet expects by copying it from the DebuggeeSourceRoot. Any template project + /// file that is found will be specialized by the implementation class. + /// 2. Run dotnet restore in the newly created source directory + /// 3. Run dotnet build in the same directory + /// 4. Rename the built debuggee dll to an exe (so that it conforms to some historical conventions our tests expect) + /// 5. Copy any native dependencies from the DebuggeeNativeLibRoot to the debuggee output directory + /// + public abstract class DotNetBuildDebuggeeTestStep : TestStep + { + /// + /// Create a new DotNetBuildDebuggeeTestStep. + /// + /// + /// The path to the dotnet executable + /// + /// + /// The path to the template solution source. This will be copied into the final solution source directory + /// under debuggeeSolutionDirPath and any project.template.json files it contains will be specialized. + /// + /// + /// The path where the debuggee's native binary dependencies will be copied from. + /// + /// + /// The path where the debuggee solution will be created. For single project solutions this will be identical to + /// the debuggee project directory. + /// + /// + /// The path where the primary debuggee executable project directory will be created. For single project solutions this + /// will be identical to the debuggee solution directory. + /// + /// + /// The directory path where the dotnet tool will place the compiled debuggee binaries. + /// + /// + /// The path where the dotnet tool will place the compiled debuggee assembly. + /// + /// + /// The path to which the build will copy the debuggee binary dll with a .exe extension. + /// + /// + /// The path to the NuGet package cache. If null, no value for this setting will be placed in the + /// NuGet.config file and dotnet will need to read it from other ambient NuGet.config files or infer + /// a default cache. + /// + /// + /// A mapping of nuget feed names to locations. These feeds will be used to restore debuggee + /// nuget package dependencies. + /// + /// + /// The path where the build output will be logged + /// + public DotNetBuildDebuggeeTestStep(string dotnetToolPath, + string templateSolutionDirPath, + string debuggeeNativeLibDirPath, + string debuggeeSolutionDirPath, + string debuggeeProjectDirPath, + string debuggeeBinaryDirPath, + string debuggeeBinaryDllPath, + string debuggeeBinaryExePath, + string nugetPackageCacheDirPath, + Dictionary nugetFeeds, + string logPath) : + base(logPath, "Build Debuggee") + { + DotNetToolPath = dotnetToolPath; + DebuggeeTemplateSolutionDirPath = templateSolutionDirPath; + DebuggeeNativeLibDirPath = debuggeeNativeLibDirPath; + DebuggeeSolutionDirPath = debuggeeSolutionDirPath; + DebuggeeProjectDirPath = debuggeeProjectDirPath; + DebuggeeBinaryDirPath = debuggeeBinaryDirPath; + DebuggeeBinaryDllPath = debuggeeBinaryDllPath; + DebuggeeBinaryExePath = debuggeeBinaryExePath; + NuGetPackageCacheDirPath = nugetPackageCacheDirPath; + NugetFeeds = nugetFeeds; + if(NugetFeeds != null && NugetFeeds.Count > 0) + { + NuGetConfigPath = Path.Combine(DebuggeeSolutionDirPath, "NuGet.config"); + } + } + + /// + /// The path to the dotnet executable + /// + public string DotNetToolPath { get; private set; } + /// + /// The path to the template solution source. This will be copied into the final solution source directory + /// under debuggeeSolutionDirPath and any project.template.json files it contains will be specialized. + /// + public string DebuggeeTemplateSolutionDirPath { get; private set; } + /// + /// The path where the debuggee's native binary dependencies will be copied from. + /// + public string DebuggeeNativeLibDirPath { get; private set; } + /// + /// The path where the debuggee solution will be created. For single project solutions this will be identical to + /// the debuggee project directory. + /// + public string DebuggeeSolutionDirPath { get; private set; } + /// + /// The path where the primary debuggee executable project directory will be created. For single project solutions this + /// will be identical to the debuggee solution directory. + /// + public string DebuggeeProjectDirPath { get; private set; } + /// + /// The directory path where the dotnet tool will place the compiled debuggee binaries. + /// + public string DebuggeeBinaryDirPath { get; private set; } + /// + /// The path where the dotnet tool will place the compiled debuggee assembly. + /// + public string DebuggeeBinaryDllPath { get; private set; } + /// + /// The path to which the build will copy the debuggee binary dll with a .exe extension. + /// + public string DebuggeeBinaryExePath { get; private set; } + /// The path to the NuGet package cache. If null, no value for this setting will be placed in the + /// NuGet.config file and dotnet will need to read it from other ambient NuGet.config files or infer + /// a default cache. + public string NuGetPackageCacheDirPath { get; private set; } + public string NuGetConfigPath { get; private set; } + public IDictionary NugetFeeds { get; private set; } + public abstract string ProjectTemplateFileName { get; } + + async protected override Task DoWork(ITestOutputHelper output) + { + PrepareProjectSolution(output); + await Restore(output); + await Build(output); + RenameDebuggeeDllToExe(output); + CopyNativeDependencies(output); + } + + void PrepareProjectSolution(ITestOutputHelper output) + { + AssertDebuggeeSolutionTemplateDirExists(output); + + output.WriteLine("Creating Solution Source Directory"); + output.WriteLine("{"); + IndentedTestOutputHelper indentedOutput = new IndentedTestOutputHelper(output); + CopySourceDirectory(DebuggeeTemplateSolutionDirPath, DebuggeeSolutionDirPath, indentedOutput); + CreateNuGetConfig(indentedOutput); + output.WriteLine("}"); + output.WriteLine(""); + + AssertDebuggeeSolutionDirExists(output); + AssertDebuggeeProjectDirExists(output); + AssertDebuggeeProjectFileExists(output); + } + + SemaphoreSlim _dotnetRestoreLock = new SemaphoreSlim(1); + + protected async Task Restore(string extraArgs, ITestOutputHelper output) + { + AssertDebuggeeSolutionDirExists(output); + AssertDebuggeeProjectDirExists(output); + AssertDebuggeeProjectFileExists(output); + + string args = "restore"; + if (NuGetConfigPath != null) + { + args += " --configfile " + NuGetConfigPath; + } + if (NuGetPackageCacheDirPath != null) + { + args += " --packages \"" + NuGetPackageCacheDirPath + "\""; + } + if (extraArgs != null) + { + args += extraArgs; + } + ProcessRunner runner = new ProcessRunner(DotNetToolPath, args). + WithWorkingDirectory(DebuggeeSolutionDirPath). + WithLog(output). + WithTimeout(TimeSpan.FromMinutes(10)). // restore can be painfully slow + WithExpectedExitCode(0); + + if (OS.Kind != OSKind.Windows && Environment.GetEnvironmentVariable("HOME") == null) + { + output.WriteLine("Detected HOME environment variable doesn't exist. This will trigger a bug in dotnet restore."); + output.WriteLine("See: https://github.com/NuGet/Home/issues/2960"); + output.WriteLine("Test will workaround this by manually setting a HOME value"); + output.WriteLine(""); + runner = runner.WithEnvironmentVariable("HOME", DebuggeeSolutionDirPath); + } + + //workaround for https://github.com/dotnet/cli/issues/3868 + await _dotnetRestoreLock.WaitAsync(); + try + { + await runner.Run(); + } + finally + { + _dotnetRestoreLock.Release(); + } + + AssertDebuggeeAssetsFileExists(output); + } + + protected virtual async Task Restore(ITestOutputHelper output) + { + await Restore(null, output); + } + + void RenameDebuggeeDllToExe(ITestOutputHelper output) + { + if (DebuggeeBinaryDllPath == null || DebuggeeBinaryExePath == null) + { + return; + } + AssertDebuggeeDllExists(output); + + output.WriteLine("Copying: " + DebuggeeBinaryDllPath + " -> " + DebuggeeBinaryExePath); + File.Copy(DebuggeeBinaryDllPath, DebuggeeBinaryExePath, true); + + AssertDebuggeeExeExists(output); + } + + protected async Task Build(string dotnetArgs, ITestOutputHelper output) + { + AssertDebuggeeSolutionDirExists(output); + AssertDebuggeeProjectFileExists(output); + AssertDebuggeeAssetsFileExists(output); + + ProcessRunner runner = new ProcessRunner(DotNetToolPath, dotnetArgs). + WithWorkingDirectory(DebuggeeProjectDirPath). + WithLog(output). + WithTimeout(TimeSpan.FromMinutes(10)). // a mac CI build of the modules debuggee is painfully slow :( + WithExpectedExitCode(0); + + if (OS.Kind != OSKind.Windows && Environment.GetEnvironmentVariable("HOME") == null) + { + output.WriteLine("Detected HOME environment variable doesn't exist. This will trigger a bug in dotnet build."); + output.WriteLine("See: https://github.com/NuGet/Home/issues/2960"); + output.WriteLine("Test will workaround this by manually setting a HOME value"); + output.WriteLine(""); + runner = runner.WithEnvironmentVariable("HOME", DebuggeeSolutionDirPath); + } + if (NuGetPackageCacheDirPath != null) + { + //dotnet restore helpfully documents its --packages argument in the help text, but + //NUGET_PACKAGES was undocumented as far as I noticed. If this stops working we can also + //auto-generate a global.json with a "packages" setting, but this was more expedient. + runner = runner.WithEnvironmentVariable("NUGET_PACKAGES", NuGetPackageCacheDirPath); + } + + await runner.Run(); + + if (DebuggeeBinaryDllPath != null) + { + AssertDebuggeeDllExists(output); + } + else + { + AssertDebuggeeExeExists(output); + } + } + + protected virtual async Task Build(ITestOutputHelper output) + { + await Build("build", output); + } + + void CopyNativeDependencies(ITestOutputHelper output) + { + if (Directory.Exists(DebuggeeNativeLibDirPath)) + { + foreach (string filePath in Directory.EnumerateFiles(DebuggeeNativeLibDirPath)) + { + string targetPath = Path.Combine(DebuggeeBinaryDirPath, Path.GetFileName(filePath)); + output.WriteLine("Copying: " + filePath + " -> " + targetPath); + File.Copy(filePath, targetPath); + } + } + } + + private void CopySourceDirectory(string sourceDirPath, string destDirPath, ITestOutputHelper output) + { + output.WriteLine("Copying: " + sourceDirPath + " -> " + destDirPath); + Directory.CreateDirectory(destDirPath); + foreach(string dirPath in Directory.EnumerateDirectories(sourceDirPath)) + { + CopySourceDirectory(dirPath, Path.Combine(destDirPath, Path.GetFileName(dirPath)), output); + } + foreach (string filePath in Directory.EnumerateFiles(sourceDirPath)) + { + string fileName = Path.GetFileName(filePath); + if (fileName == ProjectTemplateFileName) + { + ExpandProjectTemplate(filePath, destDirPath, output); + } + else + { + File.Copy(filePath, Path.Combine(destDirPath, Path.GetFileName(filePath)), true); + } + } + } + + protected abstract void ExpandProjectTemplate(string filePath, string destDirPath, ITestOutputHelper output); + + protected void CreateNuGetConfig(ITestOutputHelper output) + { + if (NuGetConfigPath == null) + { + return; + } + string nugetConfigPath = Path.Combine(DebuggeeSolutionDirPath, "NuGet.config"); + StringBuilder sb = new StringBuilder(); + sb.AppendLine(""); + sb.AppendLine(""); + if(NugetFeeds != null && NugetFeeds.Count > 0) + { + sb.AppendLine(" "); + sb.AppendLine(" "); + foreach(KeyValuePair kv in NugetFeeds) + { + sb.AppendLine(" "); + } + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.AppendLine(" "); + } + sb.AppendLine(""); + + output.WriteLine("Creating: " + NuGetConfigPath); + File.WriteAllText(NuGetConfigPath, sb.ToString()); + } + + protected void AssertDebuggeeSolutionTemplateDirExists(ITestOutputHelper output) + { + AssertX.DirectoryExists("debuggee solution template directory", DebuggeeTemplateSolutionDirPath, output); + } + + protected void AssertDebuggeeProjectDirExists(ITestOutputHelper output) + { + AssertX.DirectoryExists("debuggee project directory", DebuggeeProjectDirPath, output); + } + + protected void AssertDebuggeeSolutionDirExists(ITestOutputHelper output) + { + AssertX.DirectoryExists("debuggee solution directory", DebuggeeSolutionDirPath, output); + } + + protected void AssertDebuggeeDllExists(ITestOutputHelper output) + { + AssertX.FileExists("debuggee dll", DebuggeeBinaryDllPath, output); + } + + protected void AssertDebuggeeExeExists(ITestOutputHelper output) + { + AssertX.FileExists("debuggee exe", DebuggeeBinaryExePath, output); + } + + protected abstract void AssertDebuggeeAssetsFileExists(ITestOutputHelper output); + + protected abstract void AssertDebuggeeProjectFileExists(ITestOutputHelper output); + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/FileTestOutputHelper.cs b/src/Microsoft.Diagnostic.TestHelpers/FileTestOutputHelper.cs new file mode 100644 index 000000000..58f7c182f --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/FileTestOutputHelper.cs @@ -0,0 +1,52 @@ +// 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.IO; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// An ITestOutputHelper implementation that logs to a file + /// + public class FileTestOutputHelper : ITestOutputHelper, IDisposable + { + readonly StreamWriter _logWriter; + readonly object _lock; + + public FileTestOutputHelper(string logFilePath, FileMode fileMode = FileMode.Create) + { + Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); + FileStream fs = new FileStream(logFilePath, fileMode); + _logWriter = new StreamWriter(fs); + _logWriter.AutoFlush = true; + _lock = new object(); + } + + public void WriteLine(string message) + { + lock (_lock) + { + _logWriter.WriteLine(message); + } + } + + public void WriteLine(string format, params object[] args) + { + lock (_lock) + { + _logWriter.WriteLine(format, args); + } + } + + public void Dispose() + { + lock (_lock) + { + _logWriter.Dispose(); + } + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/IProcessLogger.cs b/src/Microsoft.Diagnostic.TestHelpers/IProcessLogger.cs new file mode 100644 index 000000000..709752578 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/IProcessLogger.cs @@ -0,0 +1,29 @@ +// 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. + +namespace Microsoft.Diagnostic.TestHelpers +{ + public enum ProcessStream + { + StandardIn = 0, + StandardOut = 1, + StandardError = 2, + MaxStreams = 3 + } + + public enum KillReason + { + TimedOut, + Unknown + } + + public interface IProcessLogger + { + void ProcessExited(ProcessRunner runner); + void ProcessKilled(ProcessRunner runner, KillReason reason); + void ProcessStarted(ProcessRunner runner); + void Write(ProcessRunner runner, string data, ProcessStream stream); + void WriteLine(ProcessRunner runner, string data, ProcessStream stream); + } +} \ No newline at end of file diff --git a/src/Microsoft.Diagnostic.TestHelpers/IndentedTestOutputHelper.cs b/src/Microsoft.Diagnostic.TestHelpers/IndentedTestOutputHelper.cs new file mode 100644 index 000000000..49f3a225c --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/IndentedTestOutputHelper.cs @@ -0,0 +1,34 @@ +// 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 Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// An implementation of ITestOutputHelper that adds one indent level to + /// the start of each line + /// + public class IndentedTestOutputHelper : ITestOutputHelper + { + readonly string _indentText; + readonly ITestOutputHelper _output; + + public IndentedTestOutputHelper(ITestOutputHelper innerOutput, string indentText = " ") + { + _output = innerOutput; + _indentText = indentText; + } + + public void WriteLine(string message) + { + _output.WriteLine(_indentText + message); + } + + public void WriteLine(string format, params object[] args) + { + _output.WriteLine(_indentText + format, args); + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Microsoft.Diagnostic.TestHelpers.csproj b/src/Microsoft.Diagnostic.TestHelpers/Microsoft.Diagnostic.TestHelpers.csproj new file mode 100644 index 000000000..431e0d0ab --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Microsoft.Diagnostic.TestHelpers.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.0 + true + ;1591;1701 + true + Diagnostic test support + $(Description) + tests + $(DefineConstants);CORE_CLR + + + + + + + + + + diff --git a/src/Microsoft.Diagnostic.TestHelpers/MultiplexTestOutputHelper.cs b/src/Microsoft.Diagnostic.TestHelpers/MultiplexTestOutputHelper.cs new file mode 100644 index 000000000..781f1d15b --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/MultiplexTestOutputHelper.cs @@ -0,0 +1,34 @@ +// 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 Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + public class MultiplexTestOutputHelper : ITestOutputHelper + { + readonly ITestOutputHelper[] _outputs; + + public MultiplexTestOutputHelper(params ITestOutputHelper[] outputs) + { + _outputs = outputs; + } + + public void WriteLine(string message) + { + foreach(ITestOutputHelper output in _outputs) + { + output.WriteLine(message); + } + } + + public void WriteLine(string format, params object[] args) + { + foreach (ITestOutputHelper output in _outputs) + { + output.WriteLine(format, args); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Diagnostic.TestHelpers/PrebuiltDebuggeeCompiler.cs b/src/Microsoft.Diagnostic.TestHelpers/PrebuiltDebuggeeCompiler.cs new file mode 100644 index 000000000..6e0ae72b8 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/PrebuiltDebuggeeCompiler.cs @@ -0,0 +1,38 @@ +// 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.IO; +using System.Threading.Tasks; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + public class PrebuiltDebuggeeCompiler : IDebuggeeCompiler + { + string _sourcePath; + string _binaryPath; + string _binaryExePath; + + public PrebuiltDebuggeeCompiler(TestConfiguration config, string debuggeeName) + { + //we anticipate paths like this: + //Source: //[] + //Binaries: // + _sourcePath = Path.Combine(config.DebuggeeSourceRoot, debuggeeName); + if (Directory.Exists(Path.Combine(_sourcePath, debuggeeName))) + { + _sourcePath = Path.Combine(_sourcePath, debuggeeName); + } + + _binaryPath = Path.Combine(config.DebuggeeBuildRoot, debuggeeName); + _binaryExePath = Path.Combine(_binaryPath, debuggeeName); + _binaryExePath += ".exe"; + } + + public Task Execute(ITestOutputHelper output) + { + return Task.Factory.StartNew(() => new DebuggeeConfiguration(_sourcePath, _binaryPath, _binaryExePath)); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Diagnostic.TestHelpers/ProcessRunner.cs b/src/Microsoft.Diagnostic.TestHelpers/ProcessRunner.cs new file mode 100644 index 000000000..1e2404880 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/ProcessRunner.cs @@ -0,0 +1,469 @@ +// 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.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// Executes a process and logs the output + /// + /// + /// The intended lifecycle is: + /// a) Create a new ProcessRunner + /// b) Use the various WithXXX methods to modify the configuration of the process to launch + /// c) await RunAsync() to start the process and wait for it to terminate. Configuration + /// changes are no longer possible + /// d) While waiting for RunAsync(), optionally call Kill() one or more times. This will expedite + /// the termination of the process but there is no guarantee the process is terminated by + /// the time Kill() returns. + /// + /// Although the entire API of this type has been designed to be thread-safe, its typical that + /// only calls to Kill() and property getters invoked within the logging callbacks will be called + /// asynchronously. + /// + public class ProcessRunner + { + // All of the locals might accessed from multiple threads and need to read/written under + // the _lock. We also use the lock to synchronize property access on the process object. + // + // Be careful not to cause deadlocks by calling the logging callbacks with the lock held. + // The logger has its own lock and it will hold that lock when it calls into property getters + // on this type. + object _lock = new object(); + + List _loggers; + Process _p; + DateTime _startTime; + TimeSpan _timeout; + ITestOutputHelper _traceOutput; + int? _expectedExitCode; + TaskCompletionSource _waitForProcessStartTaskSource; + Task _waitForExitTask; + Task _timeoutProcessTask; + Task _readStdOutTask; + Task _readStdErrTask; + CancellationTokenSource _cancelSource; + private string _replayCommand; + private KillReason? _killReason; + + public ProcessRunner(string exePath, string arguments, string replayCommand = null) + { + ProcessStartInfo psi = new ProcessStartInfo(); + psi.FileName = exePath; + psi.Arguments = arguments; + psi.UseShellExecute = false; + psi.RedirectStandardInput = true; + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + psi.CreateNoWindow = true; + + lock (_lock) + { + _p = new Process(); + _p.StartInfo = psi; + _p.EnableRaisingEvents = false; + _loggers = new List(); + _timeout = TimeSpan.FromMinutes(10); + _cancelSource = new CancellationTokenSource(); + _killReason = null; + _waitForProcessStartTaskSource = new TaskCompletionSource(); + Task startTask = _waitForProcessStartTaskSource.Task; + + // unfortunately we can't use the default Process stream reading because it only returns full lines and we have scenarios + // that need to receive the output before the newline character is written + _readStdOutTask = startTask.ContinueWith(t => + { + ReadStreamToLoggers(_p.StandardOutput, ProcessStream.StandardOut, _cancelSource.Token); + }, + _cancelSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default); + + _readStdErrTask = startTask.ContinueWith(t => + { + ReadStreamToLoggers(_p.StandardError, ProcessStream.StandardError, _cancelSource.Token); + }, + _cancelSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default); + + _timeoutProcessTask = startTask.ContinueWith(t => + { + Task.Delay(_timeout, _cancelSource.Token).ContinueWith(t2 => Kill(KillReason.TimedOut), TaskContinuationOptions.NotOnCanceled); + }, + _cancelSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default); + + _waitForExitTask = InternalWaitForExit(startTask, _readStdOutTask, _readStdErrTask); + + if (replayCommand == null) + { + _replayCommand = ExePath + " " + Arguments; + } + else + { + _replayCommand = replayCommand; + } + } + } + + public string ReplayCommand + { + get { lock (_lock) { return _replayCommand; } } + } + + public ProcessRunner WithEnvironmentVariable(string key, string value) + { + lock (_lock) + { + _p.StartInfo.Environment[key] = value; + } + return this; + } + + public ProcessRunner WithWorkingDirectory(string workingDirectory) + { + lock (_lock) + { + _p.StartInfo.WorkingDirectory = workingDirectory; + } + return this; + } + + public ProcessRunner WithLog(IProcessLogger logger) + { + lock (_lock) + { + _loggers.Add(logger); + } + return this; + } + + public ProcessRunner WithLog(ITestOutputHelper output) + { + lock (_lock) + { + _loggers.Add(new TestOutputProcessLogger(output)); + } + return this; + } + + public ProcessRunner WithDiagnosticTracing(ITestOutputHelper traceOutput) + { + lock (_lock) + { + _traceOutput = new ConsoleTestOutputHelper(traceOutput); + } + return this; + } + + public IProcessLogger[] Loggers + { + get { lock (_lock) { return _loggers.ToArray(); } } + } + + public ProcessRunner WithTimeout(TimeSpan timeout) + { + lock (_lock) + { + _timeout = timeout; + } + return this; + } + + public ProcessRunner WithExpectedExitCode(int expectedExitCode) + { + lock (_lock) + { + _expectedExitCode = expectedExitCode; + } + return this; + } + + public string ExePath + { + get { lock (_lock) { return _p.StartInfo.FileName; } } + } + + public string Arguments + { + get { lock (_lock) { return _p.StartInfo.Arguments; } } + } + + public string WorkingDirectory + { + get { lock (_lock) { return _p.StartInfo.WorkingDirectory; } } + } + + public int ProcessId + { + get { lock (_lock) { return _p.Id; } } + } + + public Dictionary EnvironmentVariables + { + get { lock (_lock) { return new Dictionary(_p.StartInfo.Environment); } } + } + + public bool IsStarted + { + get { lock (_lock) { return _waitForProcessStartTaskSource.Task.IsCompleted; } } + } + + public DateTime StartTime + { + get { lock (_lock) { return _startTime; } } + } + + public int ExitCode + { + get { lock (_lock) { return _p.ExitCode; } } + } + + public void StandardInputWriteLine(string line) + { + IProcessLogger[] loggers = null; + StreamWriter inputStream = null; + lock (_lock) + { + loggers = _loggers.ToArray(); + inputStream = _p.StandardInput; + } + foreach (IProcessLogger logger in loggers) + { + logger.WriteLine(this, line, ProcessStream.StandardIn); + } + inputStream.WriteLine(line); + } + + public Task Run() + { + Start(); + return WaitForExit(); + } + + public Task WaitForExit() + { + lock (_lock) + { + return _waitForExitTask; + } + } + + public ProcessRunner Start() + { + Process p = null; + lock (_lock) + { + p = _p; + } + // this is safe to call on multiple threads, it only launches the process once + bool started = p.Start(); + + IProcessLogger[] loggers = null; + lock (_lock) + { + // only the first thread to get here will initialize this state + if (!_waitForProcessStartTaskSource.Task.IsCompleted) + { + loggers = _loggers.ToArray(); + _startTime = DateTime.Now; + _waitForProcessStartTaskSource.SetResult(_p); + } + } + + // only the first thread that entered the lock above will run this + if (loggers != null) + { + foreach (IProcessLogger logger in loggers) + { + logger.ProcessStarted(this); + } + } + + return this; + } + + private void ReadStreamToLoggers(StreamReader reader, ProcessStream stream, CancellationToken cancelToken) + { + IProcessLogger[] loggers = Loggers; + + // for the best efficiency we want to read in chunks, but if the underlying stream isn't + // going to timeout partial reads then we have to fall back to reading one character at a time + int readChunkSize = 1; + if (reader.BaseStream.CanTimeout) + { + readChunkSize = 1000; + } + + char[] buffer = new char[readChunkSize]; + bool lastCharWasCarriageReturn = false; + do + { + int charsRead = 0; + int lastStartLine = 0; + charsRead = reader.ReadBlock(buffer, 0, readChunkSize); + + // this lock keeps the standard out/error streams from being intermixed + lock (loggers) + { + for (int i = 0; i < charsRead; i++) + { + // eat the \n after a \r, if any + bool isNewLine = buffer[i] == '\n'; + bool isCarriageReturn = buffer[i] == '\r'; + if (lastCharWasCarriageReturn && isNewLine) + { + lastStartLine++; + lastCharWasCarriageReturn = false; + continue; + } + lastCharWasCarriageReturn = isCarriageReturn; + if (isCarriageReturn || isNewLine) + { + string line = new string(buffer, lastStartLine, i - lastStartLine); + lastStartLine = i + 1; + foreach (IProcessLogger logger in loggers) + { + logger.WriteLine(this, line, stream); + } + } + } + + // flush any fractional line + if (charsRead > lastStartLine) + { + string line = new string(buffer, lastStartLine, charsRead - lastStartLine); + foreach (IProcessLogger logger in loggers) + { + logger.Write(this, line, stream); + } + } + } + } + while (!reader.EndOfStream && !cancelToken.IsCancellationRequested); + } + + public void Kill(KillReason reason = KillReason.Unknown) + { + IProcessLogger[] loggers = null; + Process p = null; + lock (_lock) + { + if (_waitForExitTask.IsCompleted) + { + return; + } + if (_killReason.HasValue) + { + return; + } + _killReason = reason; + if (!_p.HasExited) + { + p = _p; + } + + loggers = _loggers.ToArray(); + _cancelSource.Cancel(); + } + + if (p != null) + { + // its possible the process could exit just after we check so + // we still have to handle the InvalidOperationException that + // can be thrown. + try + { + p.Kill(); + } + catch (InvalidOperationException) { } + } + + foreach (IProcessLogger logger in loggers) + { + logger.ProcessKilled(this, reason); + } + } + + private async Task InternalWaitForExit(Task startProcessTask, Task stdOutTask, Task stdErrTask) + { + DebugTrace("starting InternalWaitForExit"); + Process p = await startProcessTask; + DebugTrace("InternalWaitForExit {0} '{1}'", p.Id, _replayCommand); + + Task processExit = Task.Factory.StartNew(() => + { + DebugTrace("starting Process.WaitForExit {0}", p.Id); + p.WaitForExit(); + DebugTrace("ending Process.WaitForExit {0}", p.Id); + }, + TaskCreationOptions.LongRunning); + + DebugTrace("awaiting process {0} exit, stdOut, and stdErr", p.Id); + await Task.WhenAll(processExit, stdOutTask, stdErrTask); + DebugTrace("await process {0} exit, stdOut, and stdErr complete", p.Id); + + foreach (IProcessLogger logger in Loggers) + { + logger.ProcessExited(this); + } + + lock (_lock) + { + if (_expectedExitCode.HasValue && p.ExitCode != _expectedExitCode.Value) + { + throw new Exception("Process returned exit code " + p.ExitCode + ", expected " + _expectedExitCode.Value + Environment.NewLine + + "Command Line: " + ReplayCommand + Environment.NewLine + + "Working Directory: " + WorkingDirectory); + } + DebugTrace("InternalWaitForExit {0} returning {1}", p.Id, p.ExitCode); + return p.ExitCode; + } + } + + private void DebugTrace(string format, params object[] args) + { + lock (_lock) + { + if (_traceOutput != null) + { + string message = string.Format(format, args); + _traceOutput.WriteLine("TRACE: {0}", message); + } + } + } + + class ConsoleTestOutputHelper : ITestOutputHelper + { + readonly ITestOutputHelper _output; + + public ConsoleTestOutputHelper(ITestOutputHelper output) + { + _output = output; + } + + public void WriteLine(string message) + { + Console.WriteLine(message); + if (_output != null) + { + _output.WriteLine(message); + } + + } + + public void WriteLine(string format, params object[] args) + { + Console.WriteLine(format, args); + if (_output != null) + { + _output.WriteLine(format, args); + } + } + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/TestConfiguration.cs b/src/Microsoft.Diagnostic.TestHelpers/TestConfiguration.cs new file mode 100644 index 000000000..8cc3b2ace --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/TestConfiguration.cs @@ -0,0 +1,615 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Xml.Linq; +using Xunit; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// Represents the all the test configurations for a test run. + /// + public class TestRunConfiguration : IDisposable + { + public static TestRunConfiguration Instance + { + get { return _instance.Value; } + } + + static Lazy _instance = new Lazy(() => ParseDefaultConfigFile()); + + static TestRunConfiguration ParseDefaultConfigFile() + { + string configFilePath = Path.Combine(TestConfiguration.BaseDir, "Debugger.Tests.Config.txt"); + TestRunConfiguration testRunConfig = new TestRunConfiguration(); + testRunConfig.ParseConfigFile(configFilePath); + return testRunConfig; + } + + DateTime _timestamp = DateTime.Now; + + public IEnumerable Configurations { get; private set; } + + void ParseConfigFile(string path) + { + string nugetPackages = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); + if (nugetPackages == null) + { + // If not already set, the RoslynTools RepoToolSet scripts/build system sets + // NUGET_PACKAGES to the UserProfile or HOME nuget cache directories if building + // locally (for speed) or to the repo root/.packages in CI builds (to isolate + // global machine dependences). + // + // This emulates that logic so the VS Test Explorer can still run the tests for + // config files that don't set the NugetPackagesCacheDir value (like the SOS unit + // tests). + string nugetPackagesRoot = null; + if (OS.Kind == OSKind.Windows) + { + nugetPackagesRoot = Environment.GetEnvironmentVariable("UserProfile"); + } + else if (OS.Kind == OSKind.Linux || OS.Kind == OSKind.OSX) + { + nugetPackagesRoot = Environment.GetEnvironmentVariable("HOME"); + } + if (nugetPackagesRoot != null) + { + nugetPackages = Path.Combine(nugetPackagesRoot, ".nuget", "packages"); + } + } + // The TargetArchitecture and NuGetPackageCacheDir can still be overridden + // in a config file. This is just setting the default. The other values can + // also // be overriden but it is not recommended. + Dictionary initialConfig = new Dictionary + { + ["Timestamp"] = GetTimeStampText(), + ["TempPath"] = Path.GetTempPath(), + ["WorkingDir"] = GetInitialWorkingDir(), + ["OS"] = OS.Kind.ToString(), + ["TargetArchitecture"] = OS.TargetArchitecture.ToString().ToLowerInvariant(), + ["NuGetPackageCacheDir"] = nugetPackages + }; + IEnumerable> configs = ParseConfigFile(path, new Dictionary[] { initialConfig }); + Configurations = configs.Select(c => new TestConfiguration(c)); + } + + Dictionary[] ParseConfigFile(string path, Dictionary[] templates) + { + XDocument doc = XDocument.Load(path); + XElement elem = doc.Root; + Assert.Equal("Configuration", elem.Name); + return ParseConfigSettings(templates, elem); + } + + string GetTimeStampText() + { + return _timestamp.ToString("yyyy\\_MM\\_dd\\_hh\\_mm\\_ss\\_ffff"); + } + + string GetInitialWorkingDir() + { + return Path.Combine(Path.GetTempPath(), "TestRun_" + GetTimeStampText()); + } + + Dictionary[] ParseConfigSettings(Dictionary[] templates, XElement node) + { + Dictionary[] currentTemplates = templates; + foreach (XElement child in node.Elements()) + { + currentTemplates = ParseConfigSetting(currentTemplates, child); + } + return currentTemplates; + } + + Dictionary[] ParseConfigSetting(Dictionary[] templates, XElement node) + { + // As long as the templates are added at the end of the list, the "current" + // config for this section is the last one in the array. + Dictionary currentTemplate = templates.Last(); + + switch (node.Name.LocalName) + { + case "Options": + if (EvaluateConditional(currentTemplate, node)) + { + List> newTemplates = new List>(); + foreach (XElement optionNode in node.Elements("Option")) + { + if (EvaluateConditional(currentTemplate, optionNode)) + { + IEnumerable> templateCopy = templates.Select(c => new Dictionary(c)); + newTemplates.AddRange(ParseConfigSettings(templateCopy.ToArray(), optionNode)); + } + } + if (newTemplates.Count > 0) + { + return newTemplates.ToArray(); + } + } + break; + + case "Import": + if (EvaluateConditional(currentTemplate, node)) + { + foreach (XAttribute attr in node.Attributes("ConfigFile")) + { + string file = Path.Combine(TestConfiguration.BaseDir, attr.Value); + templates = ParseConfigFile(file, templates); + } + } + break; + + default: + foreach (Dictionary config in templates) + { + // This checks the condition on an individual config value + if (EvaluateConditional(config, node)) + { + string resolveNodeValue = ResolveProperties(config, node.Value); + config[node.Name.LocalName] = resolveNodeValue; + } + } + break; + } + return templates; + } + + bool EvaluateConditional(Dictionary config, XElement node) + { + foreach (XAttribute attr in node.Attributes("Condition")) + { + string conditionText = attr.Value; + + // Only equals and not equals are supported + string[] parts = conditionText.Split("=="); + bool equal; + + if (parts.Length == 2) + { + equal = true; + } + else + { + parts = conditionText.Split("!="); + if (parts.Length != 2) + { + throw new ArgumentException("Invalid Condition attribute {0}", attr.Value); + } + equal = false; + } + + // Resolve any config values in the condition + string leftValue = ResolveProperties(config, parts[0]).Trim(); + string rightValue = ResolveProperties(config, parts[1]).Trim(); + + // Now do the simple string comparsion of the left/right sides of the condition + return equal ? leftValue == rightValue : leftValue != rightValue; + } + return true; + } + + private string ResolveProperties(Dictionary config, string rawNodeValue) + { + StringBuilder resolvedValue = new StringBuilder(); + for(int i = 0; i < rawNodeValue.Length; ) + { + int propStartIndex = rawNodeValue.IndexOf("$(", i); + if (propStartIndex == -1) + { + if (i != rawNodeValue.Length) + { + resolvedValue.Append(rawNodeValue.Substring(i)); + } + break; + } + else + { + int propEndIndex = rawNodeValue.IndexOf(")", propStartIndex+1); + Assert.NotEqual(-1, propEndIndex); + if (propStartIndex != i) + { + resolvedValue.Append(rawNodeValue.Substring(i, propStartIndex - i)); + } + resolvedValue.Append(ResolveProperty(config, rawNodeValue.Substring(propStartIndex+2, propEndIndex - propStartIndex-2))); + i = propEndIndex + 1; + } + } + + return resolvedValue.ToString(); + } + + private string ResolveProperty(Dictionary config, string propName) + { + return propName.Equals("WinDir", StringComparison.OrdinalIgnoreCase) + ? Path.GetFullPath(Environment.ExpandEnvironmentVariables("%WINDIR%")) + : config[propName] ?? ""; + } + + public void Dispose() + { + } + } + + /// + /// Represents the current test configuration + /// + public class TestConfiguration + { + const string DebugTypeKey = "DebugType"; + const string DebuggeeBuildRootKey = "DebuggeeBuildRoot"; + + internal static readonly string BaseDir = Path.GetFullPath("."); + + private Dictionary _settings; + + public TestConfiguration() + { + _settings = new Dictionary(); + } + + public TestConfiguration(Dictionary initialSettings) + { + _settings = new Dictionary(initialSettings); + } + + public IReadOnlyDictionary AllSettings + { + get { return _settings; } + } + + public TestConfiguration CloneWithNewDebugType(string pdbType) + { + Debug.Assert(!string.IsNullOrWhiteSpace(pdbType)); + + var currentSettings = new Dictionary(_settings); + + // Set or replace if the pdb debug type + currentSettings[DebugTypeKey] = pdbType; + + // The debuggee build root must exist. Append the pdb type to make it unique. + currentSettings[DebuggeeBuildRootKey] = Path.Combine(currentSettings[DebuggeeBuildRootKey], pdbType); + + return new TestConfiguration(currentSettings); + } + + /// + /// The target architecture (x64, x86, arm, arm64) to build and run. If the config + /// file doesn't have an TargetArchitecture property, then the current running + /// architecture is used. + /// + public string TargetArchitecture + { + get { return GetValue("TargetArchitecture").ToLowerInvariant(); } + } + + /// + /// The product "projectk" (.NET Core) or "desktop". + /// + public string TestProduct + { + get { return GetValue("TestProduct").ToLowerInvariant(); } + } + + /// + /// The test runner script directory + /// + public string ScriptRootDir + { + get { return MakeCanonicalPath(GetValue("ScriptRootDir")); } + } + + /// + /// Working temporary directory. + /// + public string WorkingDir + { + get { return MakeCanonicalPath(GetValue("WorkingDir")); } + } + + /// + /// The host program to run a .NET Core or null for desktop/no host. + /// + public string HostExe + { + get { return MakeCanonicalExePath(GetValue("HostExe")); } + } + + /// + /// Arguments to the HostExe. + /// + public string HostArgs + { + get { return GetValue("HostArgs"); } + } + + /// + /// Environment variables to pass to the target process (via the ProcessRunner). + /// + public string HostEnvVars + { + get { return GetValue("HostEnvVars"); } + } + + /// + /// Add the host environment variables to the process runner. + /// + /// process runner instance + public void AddHostEnvVars(ProcessRunner runner) + { + if (HostEnvVars != null) + { + string[] vars = HostEnvVars.Split(';'); + foreach (string var in vars) + { + if (string.IsNullOrEmpty(var)) + { + continue; + } + string[] parts = var.Split('='); + runner = runner.WithEnvironmentVariable(parts[0], parts[1]); + } + } + } + + /// + /// The directory to the runtime (coreclr.dll, etc.) symbols + /// + public string RuntimeSymbolsPath + { + get { return MakeCanonicalPath(GetValue("RuntimeSymbolsPath")); } + } + + /// + /// The root of the debuggees (managed and native) + /// + public string DebuggeeRootDir + { + get { return MakeCanonicalPath(GetValue("DebuggeeRootDir")); } + } + + /// + /// How the debuggees are built: "prebuilt" or "cli" (builds the debuggee during the test run with build and cli configuration). + /// + public string DebuggeeBuildProcess + { + get { return GetValue("DebuggeeBuildProcess")?.ToLowerInvariant(); } + } + + /// + /// Debuggee sources and template project file will be retrieved from here: //[] + /// + public string DebuggeeSourceRoot + { + get { return MakeCanonicalPath(GetValue("DebuggeeSourceRoot")); } + } + + /// + /// Debuggee final sources/project file/binary outputs will be placed here: // + /// + public string DebuggeeBuildRoot + { + get { return MakeCanonicalPath(GetValue(DebuggeeBuildRootKey)); } + } + + /// + /// Debuggee native binary dependencies will be retrieved from here. + /// + public string DebuggeeNativeLibRoot + { + get { return MakeCanonicalPath(GetValue("DebuggeeNativeLibRoot")); } + } + + /// + /// The version of the Microsoft.NETCore.App package to reference when building the debuggee. + /// + public string BuildProjectMicrosoftNetCoreAppVersion + { + get { return GetValue("BuildProjectMicrosoftNetCoreAppVersion"); } + } + + /// + /// The framework type/version used to build the debuggee like "netcoreapp2.0" or "netstandard1.0". + /// + public string BuildProjectFramework + { + get { return GetValue("BuildProjectFramework"); } + } + + /// + /// Optional runtime identifier (RID) like "linux-x64" or "win-x86". If set, causes the debuggee to + /// be built a as "standalone" dotnet cli project where the runtime is copied to the debuggee build + /// root. + /// + public string BuildProjectRuntime + { + get { return GetValue("BuildProjectRuntime"); } + } + + /// + /// The type of PDB: "full" (Windows PDB) or "portable". + /// + public string DebugType + { + get { return GetValue(DebugTypeKey); } + } + + /// + /// Either the local path to the dotnet cli to build or the URL of the runtime to download and install. + /// + public string CliPath + { + get { return MakeCanonicalPath(GetValue("CliPath")); } + } + + /// + /// The local path to put the downloaded and decompressed runtime. + /// + public string CliCacheRoot + { + get { return MakeCanonicalPath(GetValue("CliCacheRoot")); } + } + + /// + /// The version (i.e. 2.0.0) of the dotnet cli to use. + /// + public string CliVersion + { + get { return GetValue("CliVersion"); } + } + + /// + /// The directory to cache the nuget packages on restore + /// + public string NuGetPackageCacheDir + { + get { return MakeCanonicalPath(GetValue("NuGetPackageCacheDir")); } + } + + /// + /// The nuget package feeds separated by semicolons. + /// + public string NuGetPackageFeeds + { + get { return GetValue("NuGetPackageFeeds"); } + } + + /// + /// If true, log the test output, etc. to the console. + /// + public bool LogToConsole + { + get { return bool.TryParse(GetValue("LogToConsole"), out bool b) && b; } + } + + /// + /// The directory to put the test logs. + /// + public string LogDirPath + { + get { return MakeCanonicalPath(GetValue("LogDir")); } + } + + /// + /// The "ILLink.Tasks" package version to reference or null. + /// + public string LinkerPackageVersion + { + get { return GetValue("LinkerPackageVersion"); } + } + + /// + /// Returns the configuration value for the key or null. + /// + /// name of the configuration value + /// configuration value or null + public string GetValue(string key) + { + // unlike dictionary it is OK to ask for non-existant keys + // if the key doesn't exist the result is null + _settings.TryGetValue(key, out string settingValue); + return settingValue; + } + + public static string MakeCanonicalExePath(string maybeRelativePath) + { + if (string.IsNullOrWhiteSpace(maybeRelativePath)) + { + return null; + } + string maybeRelativePathWithExtension = maybeRelativePath; + if (OS.Kind == OSKind.Windows && !maybeRelativePath.EndsWith(".exe")) + { + maybeRelativePathWithExtension = maybeRelativePath + ".exe"; + } + return MakeCanonicalPath(maybeRelativePathWithExtension); + } + + public static string MakeCanonicalPath(string maybeRelativePath) + { + return MakeCanonicalPath(BaseDir, maybeRelativePath); + } + + public static string MakeCanonicalPath(string baseDir, string maybeRelativePath) + { + if (string.IsNullOrWhiteSpace(maybeRelativePath)) + { + return null; + } + // we will assume any path referencing an http endpoint is canonical already + if(maybeRelativePath.StartsWith("http:") || + maybeRelativePath.StartsWith("https:")) + { + return maybeRelativePath; + } + string path = Path.IsPathRooted(maybeRelativePath) ? maybeRelativePath : Path.Combine(baseDir, maybeRelativePath); + path = Path.GetFullPath(path); + return OS.Kind != OSKind.Windows ? path.Replace('\\', '/') : path; + } + + public override string ToString() + { + return TestProduct + "." + DebuggeeBuildProcess; + } + } + + /// + /// The OS running + /// + public enum OSKind + { + Windows, + Linux, + OSX, + Unknown, + } + + /// + /// The OS specific configuration + /// + public static class OS + { + static OS() + { +#if CORE_CLR // Only core build can run on different OSes + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Kind = OSKind.Linux; + } + else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Kind = OSKind.OSX; + } + else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Kind = OSKind.Windows; + } + else + { + // Default to Unknown + Kind = OSKind.Unknown; + } + +#else // For everything else there's Windows + _kind = OSKind.Windows; +#endif + } + + /// + /// The OS the tests are running. + /// + public static OSKind Kind { get; private set; } + + /// + /// The architecture the tests are running. We are assuming that the test runner, the debugger and the debugger's target are all the same architecture. + /// + public static Architecture TargetArchitecture { get { return System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture; } } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/TestOutputProcessLogger.cs b/src/Microsoft.Diagnostic.TestHelpers/TestOutputProcessLogger.cs new file mode 100644 index 000000000..177a7d79d --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/TestOutputProcessLogger.cs @@ -0,0 +1,144 @@ +// 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.Linq; +using System.Text; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + public class TestOutputProcessLogger : IProcessLogger + { + string _timeFormat = "mm\\:ss\\.fff"; + ITestOutputHelper _output; + StringBuilder[] _lineBuffers; + + public TestOutputProcessLogger(ITestOutputHelper output) + { + _output = output; + _lineBuffers = new StringBuilder[(int)ProcessStream.MaxStreams]; + } + + public void ProcessStarted(ProcessRunner runner) + { + lock (this) + { + _output.WriteLine("Running Process: " + runner.ReplayCommand); + _output.WriteLine("Working Directory: " + runner.WorkingDirectory); + IEnumerable> additionalEnvVars = + runner.EnvironmentVariables.Where(kv => Environment.GetEnvironmentVariable(kv.Key) != kv.Value); + + if(additionalEnvVars.Any()) + { + _output.WriteLine("Additional Environment Variables: " + + string.Join(", ", additionalEnvVars.Select(kv => kv.Key + "=" + kv.Value))); + } + _output.WriteLine("{"); + } + } + + public virtual void Write(ProcessRunner runner, string data, ProcessStream stream) + { + lock (this) + { + AppendToLineBuffer(runner, stream, data); + } + } + + public virtual void WriteLine(ProcessRunner runner, string data, ProcessStream stream) + { + lock (this) + { + StringBuilder lineBuffer = AppendToLineBuffer(runner, stream, data); + //Ensure all output is written even if it isn't a full line before we log input + if (stream == ProcessStream.StandardIn) + { + FlushOutput(); + } + _output.WriteLine(lineBuffer.ToString()); + _lineBuffers[(int)stream] = null; + } + } + + public virtual void ProcessExited(ProcessRunner runner) + { + lock (this) + { + TimeSpan offset = runner.StartTime - DateTime.Now; + _output.WriteLine("}"); + _output.WriteLine("Exit code: " + runner.ExitCode + " ( " + offset.ToString(_timeFormat) + " elapsed)"); + _output.WriteLine(""); + } + } + + public void ProcessKilled(ProcessRunner runner, KillReason reason) + { + lock (this) + { + TimeSpan offset = runner.StartTime - DateTime.Now; + string reasonText = ""; + if (reason == KillReason.TimedOut) + { + reasonText = "Process timed out"; + } + else if (reason == KillReason.Unknown) + { + reasonText = "Kill() was called"; + } + _output.WriteLine(" Killing process: " + offset.ToString(_timeFormat) + ": " + reasonText); + } + } + + protected void FlushOutput() + { + if (_lineBuffers[(int)ProcessStream.StandardOut] != null) + { + _output.WriteLine(_lineBuffers[(int)ProcessStream.StandardOut].ToString()); + _lineBuffers[(int)ProcessStream.StandardOut] = null; + } + if (_lineBuffers[(int)ProcessStream.StandardError] != null) + { + _output.WriteLine(_lineBuffers[(int)ProcessStream.StandardError].ToString()); + _lineBuffers[(int)ProcessStream.StandardError] = null; + } + } + + private StringBuilder AppendToLineBuffer(ProcessRunner runner, ProcessStream stream, string data) + { + StringBuilder lineBuffer = _lineBuffers[(int)stream]; + if (lineBuffer == null) + { + TimeSpan offset = runner.StartTime - DateTime.Now; + lineBuffer = new StringBuilder(); + lineBuffer.Append(" "); + if (stream == ProcessStream.StandardError) + { + lineBuffer.Append("STDERROR: "); + } + else if (stream == ProcessStream.StandardIn) + { + lineBuffer.Append("STDIN: "); + } + lineBuffer.Append(offset.ToString(_timeFormat)); + lineBuffer.Append(": "); + _lineBuffers[(int)stream] = lineBuffer; + } + + // xunit has a bug where a non-printable character isn't properly escaped when + // it is written into the xml results which ultimately results in + // the xml being improperly truncated. For example MDbg has a test case that prints + // \0 and dotnet tools print \u001B to colorize their console output. + foreach(char c in data) + { + if(!char.IsControl(c)) + { + lineBuffer.Append(c); + } + } + return lineBuffer; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Diagnostic.TestHelpers/TestRunner.cs b/src/Microsoft.Diagnostic.TestHelpers/TestRunner.cs new file mode 100644 index 000000000..f6ba3eedb --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/TestRunner.cs @@ -0,0 +1,261 @@ +// 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 System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + public class TestRunner + { + /// + /// Run debuggee (without any debugger) and compare the console output to the regex specified. + /// + /// test config to use + /// output helper + /// test case name + /// debuggee name (no path) + /// regex to match on console (standard and error) output + /// + public static async Task Run(TestConfiguration config, ITestOutputHelper output, string testName, string debuggeeName, string outputRegex) + { + OutputHelper outputHelper = null; + try + { + // Setup the logging from the options in the config file + outputHelper = ConfigureLogging(config, output, testName); + + // Restore and build the debuggee. The debuggee name is lower cased because the + // source directory name has been lowercased by the build system. + DebuggeeConfiguration debuggeeConfig = await DebuggeeCompiler.Execute(config, debuggeeName.ToLowerInvariant(), outputHelper); + + outputHelper.WriteLine("Starting {0}", testName); + outputHelper.WriteLine("{"); + + // Get the full debuggee launch command line (includes the host if required) + string exePath = debuggeeConfig.BinaryExePath; + string arguments = debuggeeConfig.BinaryDirPath; + if (!string.IsNullOrWhiteSpace(config.HostExe)) + { + exePath = config.HostExe; + arguments = Environment.ExpandEnvironmentVariables(string.Format("{0} {1} {2}", config.HostArgs, debuggeeConfig.BinaryExePath, debuggeeConfig.BinaryDirPath)); + } + + TestLogger testLogger = new TestLogger(outputHelper.IndentedOutput); + ProcessRunner processRunner = new ProcessRunner(exePath, arguments). + WithLog(testLogger). + WithTimeout(TimeSpan.FromMinutes(5)); + + processRunner.Start(); + + // Wait for the debuggee to finish before getting the debuggee output + int exitCode = await processRunner.WaitForExit(); + + string debuggeeStandardOutput = testLogger.GetStandardOutput(); + string debuggeeStandardError = testLogger.GetStandardError(); + + // The debuggee output is all the stdout first and then all the stderr output last + string debuggeeOutput = debuggeeStandardOutput + debuggeeStandardError; + if (string.IsNullOrEmpty(debuggeeOutput)) + { + throw new Exception("No debuggee output"); + } + // Remove any CR's in the match string because this assembly is built on Windows (with CRs) and + // ran on Linux/OS X (without CRs). + outputRegex = outputRegex.Replace("\r", ""); + + // Now match the debuggee output and regex match string + if (!new Regex(outputRegex, RegexOptions.Multiline).IsMatch(debuggeeOutput)) + { + throw new Exception(string.Format("\nDebuggee output:\n\n'{0}'\n\nDid not match the expression:\n\n'{1}'", debuggeeOutput, outputRegex)); + } + + return exitCode; + } + catch (Exception ex) + { + // Log the exception + outputHelper?.WriteLine(ex.ToString()); + throw; + } + finally + { + outputHelper?.WriteLine("}"); + outputHelper?.Dispose(); + } + } + + /// + /// Returns a test config for each PDB type supported by the product/platform. + /// + /// starting config + /// new configs for each supported PDB type + public static IEnumerable EnumeratePdbTypeConfigs(TestConfiguration config) + { + // Enabled after an official dotnet cli supports "embedded" PDBs - issue #244 + // string[] pdbTypes = { "portable", "embedded" }; + string[] pdbTypes = { "portable" }; + + if (OS.Kind == OSKind.Windows) + { + if (config.TestProduct.Equals("projectk")) + { + // Enabled after an official dotnet cli supports "embedded" PDBs - issue #244 + // pdbTypes = new string[] { "portable", "full", "embedded" }; + pdbTypes = new string[] { "portable", "full" }; + } + else + { + // Don't change the config on the desktop/projectn projects + pdbTypes = new string[] { "" }; + } + } + + foreach (string pdbType in pdbTypes) + { + if (string.IsNullOrWhiteSpace(pdbType)) + { + yield return config; + } + else + { + yield return config.CloneWithNewDebugType(pdbType); + } + } + } + + /// + /// Returns an output helper for the specified config. + /// + /// test config + /// starting output helper + /// test case name + /// new output helper + public static TestRunner.OutputHelper ConfigureLogging(TestConfiguration config, ITestOutputHelper output, string testName) + { + FileTestOutputHelper fileLogger = null; + ConsoleTestOutputHelper consoleLogger = null; + if (!string.IsNullOrEmpty(config.LogDirPath)) + { + string logFileName = testName + "." + config.ToString() + ".txt"; + string logPath = Path.Combine(config.LogDirPath, logFileName); + fileLogger = new FileTestOutputHelper(logPath, FileMode.Append); + } + if (config.LogToConsole) + { + consoleLogger = new ConsoleTestOutputHelper(); + } + return new TestRunner.OutputHelper(output, fileLogger, consoleLogger); + } + + public class OutputHelper : ITestOutputHelper, IDisposable + { + readonly ITestOutputHelper _output; + readonly FileTestOutputHelper _fileLogger; + readonly ConsoleTestOutputHelper _consoleLogger; + + public readonly ITestOutputHelper IndentedOutput; + + public OutputHelper(ITestOutputHelper output, FileTestOutputHelper fileLogger, ConsoleTestOutputHelper consoleLogger) + { + _output = output; + _fileLogger = fileLogger; + _consoleLogger = consoleLogger; + IndentedOutput = new IndentedTestOutputHelper(this); + } + + public void WriteLine(string message) + { + _output.WriteLine(message); + _fileLogger?.WriteLine(message); + _consoleLogger?.WriteLine(message); + } + + public void WriteLine(string format, params object[] args) + { + _output.WriteLine(format, args); + _fileLogger?.WriteLine(format, args); + _consoleLogger?.WriteLine(format, args); + } + + public void Dispose() + { + _fileLogger?.Dispose(); + } + } + + class TestLogger : TestOutputProcessLogger + { + readonly StringBuilder _standardOutput; + readonly StringBuilder _standardError; + + public TestLogger(ITestOutputHelper output) + : base(output) + { + lock (this) + { + _standardOutput = new StringBuilder(); + _standardError = new StringBuilder(); + } + } + + public string GetStandardOutput() + { + lock (this) + { + return _standardOutput.ToString(); + } + } + + public string GetStandardError() + { + lock (this) + { + return _standardError.ToString(); + } + } + + public override void Write(ProcessRunner runner, string data, ProcessStream stream) + { + lock (this) + { + base.Write(runner, data, stream); + switch (stream) + { + case ProcessStream.StandardOut: + _standardOutput.Append(data); + break; + + case ProcessStream.StandardError: + _standardError.Append(data); + break; + } + } + } + + public override void WriteLine(ProcessRunner runner, string data, ProcessStream stream) + { + lock (this) + { + base.WriteLine(runner, data, stream); + switch (stream) + { + case ProcessStream.StandardOut: + _standardOutput.AppendLine(data); + break; + + case ProcessStream.StandardError: + _standardError.AppendLine(data); + break; + } + } + } + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/TestStep.cs b/src/Microsoft.Diagnostic.TestHelpers/TestStep.cs new file mode 100644 index 000000000..680646b9c --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/TestStep.cs @@ -0,0 +1,636 @@ +// 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.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using Xunit.Abstractions; + +namespace Microsoft.Diagnostic.TestHelpers +{ + /// + /// An incremental atomic unit of work in the process of running a test. A test + /// can consist of multiple processes running across different machines at + /// different times. The TestStep supports: + /// 1) coordination between test processes to ensure each step runs only once + /// 2) disk based persistance so that later steps in different processes can + /// reload the state of earlier steps + /// 3) Pretty printing logs + /// 4) TODO: Dependency analysis to determine if the cached output of a previous step + /// execution is still valid + /// + public class TestStep + { + string _logFilePath; + string _stateFilePath; + TimeSpan _timeout; + + public TestStep(string logFilePath, string friendlyName) + { + _logFilePath = logFilePath; + _stateFilePath = Path.ChangeExtension(_logFilePath, "state.txt"); + _timeout = TimeSpan.FromMinutes(20); + FriendlyName = friendlyName; + } + + public string FriendlyName { get; private set; } + + async public Task Execute(ITestOutputHelper output) + { + // if this step is in progress on another thread, wait for it + TestStepState stepState = await AcquireStepStateLock(output); + + //if this thread wins the race we do the work on this thread, otherwise + //we log the winner's saved output + if (stepState.RunState != TestStepRunState.InProgress) + { + LogHeader(stepState, true, output); + LogPreviousResults(stepState, output); + LogFooter(stepState, output); + ThrowExceptionIfFaulted(stepState); + } + else + { + await UncachedExecute(stepState, output); + } + } + + protected virtual Task DoWork(ITestOutputHelper output) + { + output.WriteLine("Overload the default DoWork implementation in order to run useful work"); + return Task.Delay(0); + } + + private async Task UncachedExecute(TestStepState stepState, ITestOutputHelper output) + { + using (FileTestOutputHelper stepLog = new FileTestOutputHelper(_logFilePath)) + { + try + { + LogHeader(stepState, false, output); + MultiplexTestOutputHelper mux = new MultiplexTestOutputHelper(new IndentedTestOutputHelper(output), stepLog); + await DoWork(mux); + stepState = stepState.Complete(); + } + catch (Exception e) + { + stepState = stepState.Fault(e.Message, e.StackTrace); + } + finally + { + LogFooter(stepState, output); + await WriteFinalStepState(stepState, output); + ThrowExceptionIfFaulted(stepState); + } + } + } + + private bool TryWriteInitialStepState(TestStepState state, ITestOutputHelper output) + { + // To ensure the file is atomically updated we write the contents to a temporary + // file, then move it to the final location + try + { + string tempPath = Path.GetTempFileName(); + try + { + File.WriteAllText(tempPath, state.SerializeInitialState()); + Directory.CreateDirectory(Path.GetDirectoryName(_stateFilePath)); + File.Move(tempPath, _stateFilePath); + return true; + } + finally + { + File.Delete(tempPath); + } + + } + catch (IOException ex) + { + output.WriteLine("Exception writing state file {0} {1}", _stateFilePath, ex.ToString()); + return false; + } + } + + private bool TryOpenExistingStepStateFile(out TestStepState stepState, ITestOutputHelper output) + { + stepState = null; + try + { + if (!Directory.Exists(Path.GetDirectoryName(_stateFilePath))) + { + return false; + } + bool result = TestStepState.TryParse(File.ReadAllText(_stateFilePath), out stepState); + if (!result) + { + output.WriteLine("TryParse failed on opening existing state file {0}", _stateFilePath); + } + return result; + } + catch (IOException ex) + { + output.WriteLine("Exception opening existing state file {0} {1}", _stateFilePath, ex.ToString()); + return false; + } + } + + async private Task WriteFinalStepState(TestStepState stepState, ITestOutputHelper output) + { + const int NumberOfRetries = 5; + FileStream stepStateStream = null; + + // Retry few times because the state file may be open temporarily by another thread or process. + for (int retries = 0; retries < NumberOfRetries; retries++) + { + try + { + stepStateStream = File.Open(_stateFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); + break; + } + catch (IOException ex) + { + output.WriteLine("WriteFinalStepState exception {0} retry #{1}", ex.ToString(), retries); + if (retries >= (NumberOfRetries - 1)) + { + throw; + } + } + } + + using (stepStateStream) + { + stepStateStream.Seek(0, SeekOrigin.End); + StreamWriter writer = new StreamWriter(stepStateStream); + await writer.WriteAsync(Environment.NewLine + stepState.SerializeFinalState()); + await writer.FlushAsync(); + } + } + + private void LogHeader(TestStepState stepState, bool cached, ITestOutputHelper output) + { + string cachedText = cached ? " (CACHED)" : ""; + output.WriteLine("[" + stepState.StartTime + "] " + FriendlyName + cachedText); + output.WriteLine("Process: " + stepState.ProcessName + "(ID: 0x" + stepState.ProcessID.ToString("x") + ") on " + stepState.Machine); + output.WriteLine("{"); + } + + private void LogFooter(TestStepState stepState, ITestOutputHelper output) + { + output.WriteLine("}"); + string elapsedTime = null; + if (stepState.RunState == TestStepRunState.InProgress) + { + output.WriteLine(FriendlyName + " Not Complete"); + output.WriteLine(stepState.ErrorMessage); + } + else + { + elapsedTime = (stepState.CompleteTime.Value - stepState.StartTime).ToString("mm\\:ss\\.fff"); + } + if (stepState.RunState == TestStepRunState.Complete) + { + output.WriteLine(FriendlyName + " Complete (" + elapsedTime + " elapsed)"); + } + else if (stepState.RunState == TestStepRunState.Faulted) + { + output.WriteLine(FriendlyName + " Faulted (" + elapsedTime + " elapsed)"); + output.WriteLine(stepState.ErrorMessage); + output.WriteLine(stepState.ErrorStackTrace); + } + output.WriteLine(""); + output.WriteLine(""); + } + + private async Task AcquireStepStateLock(ITestOutputHelper output) + { + TestStepState initialStepState = new TestStepState(); + + bool stepStateFileExists = false; + while (true) + { + TestStepState openedStepState = null; + stepStateFileExists = File.Exists(_stateFilePath); + if (!stepStateFileExists && TryWriteInitialStepState(initialStepState, output)) + { + // this thread gets to do the work, persist the initial lock state + return initialStepState; + } + + if (stepStateFileExists && TryOpenExistingStepStateFile(out openedStepState, output)) + { + if (!ShouldReuseCachedStepState(openedStepState)) + { + try + { + File.Delete(_stateFilePath); + continue; + } + catch (IOException ex) + { + output.WriteLine("Exception deleting state file {0} {1}", _stateFilePath, ex.ToString()); + } + } + else if (openedStepState.RunState != TestStepRunState.InProgress) + { + // we can reuse the work and it is finished - stop waiting and return it + return openedStepState; + } + } + + // If we get here we are either: + // a) Waiting for some other thread (potentially in another process) to complete the work + // b) Waiting for a hopefully transient IO issue to resolve so that we can determine whether or not the work has already been claimed + // + // If we wait for too long in either case we will eventually timeout. + ThrowExceptionForIncompleteWorkIfNeeded(initialStepState, openedStepState, stepStateFileExists, output); + await Task.Delay(TimeSpan.FromSeconds(1)); + } + } + + private void ThrowExceptionForIncompleteWorkIfNeeded(TestStepState initialStepState, TestStepState openedStepState, bool stepStateFileExists, ITestOutputHelper output) + { + bool timeout = (DateTimeOffset.Now - initialStepState.StartTime > _timeout); + bool notFinishable = openedStepState != null && + ShouldReuseCachedStepState(openedStepState) && + openedStepState.RunState == TestStepRunState.InProgress && + !IsOpenedStateChangeable(openedStepState); + if (timeout || notFinishable) + { + TestStepState currentState = openedStepState != null ? openedStepState : initialStepState; + LogHeader(currentState, true, output); + StringBuilder errorMessage = new StringBuilder(); + if (timeout) + { + errorMessage.Append("Timeout after " + _timeout + ". "); + } + if (!stepStateFileExists) + { + errorMessage.Append("Unable to create file:" + Environment.NewLine + + _stateFilePath); + } + else if (openedStepState == null) + { + errorMessage.AppendLine("Unable to parse file:" + Environment.NewLine + + _stateFilePath); + } + else + { + // these error cases should have a valid previous log we can restore + Debug.Assert(currentState == openedStepState); + LogPreviousResults(currentState, output); + + errorMessage.AppendLine("This step was not marked complete in: " + Environment.NewLine + + _stateFilePath); + + if (!IsPreviousMachineSame(openedStepState)) + { + errorMessage.AppendLine("The current machine (" + Environment.MachineName + ") differs from the one which ran the step originally (" + currentState.Machine + ")." + Environment.NewLine + + "Perhaps the original process (ID: 0x" + currentState.ProcessID.ToString("x") + ") executing the work exited unexpectedly or the file was" + Environment.NewLine + + "copied to this machine before the work was complete?"); + } + else if (IsPreviousMachineSame(openedStepState) && !IsPreviousProcessRunning(openedStepState)) + { + errorMessage.AppendLine("As of " + DateTimeOffset.Now + " the process executing this step (ID: 0x" + currentState.ProcessID.ToString("x") + ")" + Environment.NewLine + + "is no longer running. Perhaps it was killed or exited unexpectedly?"); + } + else if (openedStepState.ProcessID != Process.GetCurrentProcess().Id) + { + errorMessage.AppendLine("As of " + DateTimeOffset.Now + " the process executing this step (ID: 0x" + currentState.ProcessID.ToString("x") + ")" + Environment.NewLine + + "is still running. The process may be hung or running more slowly than expected?"); + } + else + { + errorMessage.AppendLine("As of " + DateTimeOffset.Now + " this step should still be running on some other thread in this process (ID: 0x" + currentState.ProcessID.ToString("x") + ")" + Environment.NewLine + + "Perhaps the work has deadlocked or is running more slowly than expected?"); + } + + string reuseMessage = GetReuseStepStateReason(openedStepState); + if (reuseMessage == null) + { + reuseMessage = "Deleting the file to retry the test step was attempted automatically, but failed."; + } + else + { + reuseMessage = "Deleting the file to retry the test step was not attempted automatically because " + reuseMessage + "."; + } + errorMessage.Append(reuseMessage); + } + currentState = currentState.Incomplete(errorMessage.ToString()); + LogFooter(currentState, output); + if (timeout) + { + throw new TestStepException("Timeout waiting for " + FriendlyName + " step to complete." + Environment.NewLine + errorMessage.ToString()); + } + else + { + throw new TestStepException(FriendlyName + " step can not be completed." + Environment.NewLine + errorMessage.ToString()); + } + } + } + + private static bool ShouldReuseCachedStepState(TestStepState openedStepState) + { + return (GetReuseStepStateReason(openedStepState) != null); + } + + private static string GetReuseStepStateReason(TestStepState openedStepState) + { + //This heuristic may need to change, in some cases it is probably too eager to + //reuse past results when we wanted to retest something. + + if (openedStepState.RunState == TestStepRunState.Complete) + { + return "succesful steps are always reused"; + } + else if(!IsPreviousMachineSame(openedStepState)) + { + return "steps on run on other machines are always reused, regardless of success"; + } + else if(IsPreviousProcessRunning(openedStepState)) + { + return "steps run in currently executing processes are always reused, regardless of success"; + } + else + { + return null; + } + } + + private static bool IsPreviousMachineSame(TestStepState openedStepState) + { + return Environment.MachineName == openedStepState.Machine; + } + + private static bool IsPreviousProcessRunning(TestStepState openedStepState) + { + Debug.Assert(IsPreviousMachineSame(openedStepState)); + return (Process.GetProcesses().Any(p => p.Id == openedStepState.ProcessID && p.ProcessName == openedStepState.ProcessName)); + } + + private static bool IsOpenedStateChangeable(TestStepState openedStepState) + { + return (openedStepState.RunState == TestStepRunState.InProgress && + IsPreviousMachineSame(openedStepState) && + IsPreviousProcessRunning(openedStepState)); + } + + private void LogPreviousResults(TestStepState cachedTaskState, ITestOutputHelper output) + { + ITestOutputHelper indentedOutput = new IndentedTestOutputHelper(output); + try + { + string[] lines = File.ReadAllLines(_logFilePath); + foreach (string line in lines) + { + indentedOutput.WriteLine(line); + } + } + catch (IOException e) + { + string errorMessage = "Error accessing task log file: " + _logFilePath + Environment.NewLine + + e.GetType().FullName + ": " + e.Message; + indentedOutput.WriteLine(errorMessage); + } + } + + private void ThrowExceptionIfFaulted(TestStepState cachedStepState) + { + if(cachedStepState.RunState == TestStepRunState.Faulted) + { + throw new TestStepException(FriendlyName, cachedStepState.ErrorMessage, cachedStepState.ErrorStackTrace); + } + } + + enum TestStepRunState + { + InProgress, + Complete, + Faulted + } + + class TestStepState + { + public TestStepState() + { + RunState = TestStepRunState.InProgress; + Machine = Environment.MachineName; + ProcessID = Process.GetCurrentProcess().Id; + ProcessName = Process.GetCurrentProcess().ProcessName; + StartTime = DateTimeOffset.Now; + } + public TestStepState(TestStepRunState runState, + string machine, + int pid, + string processName, + DateTimeOffset startTime, + DateTimeOffset? completeTime, + string errorMessage, + string errorStackTrace) + { + RunState = runState; + Machine = machine; + ProcessID = pid; + ProcessName = processName; + StartTime = startTime; + CompleteTime = completeTime; + ErrorMessage = errorMessage; + ErrorStackTrace = errorStackTrace; + } + public TestStepRunState RunState { get; private set; } + public string Machine { get; private set; } + public int ProcessID { get; private set; } + public string ProcessName { get; private set; } + public string ErrorMessage { get; private set; } + public string ErrorStackTrace { get; private set; } + public DateTimeOffset StartTime { get; private set; } + public DateTimeOffset? CompleteTime { get; private set; } + + public TestStepState Incomplete(string errorMessage) + { + return WithFinalState(TestStepRunState.InProgress, null, errorMessage, null); + } + + public TestStepState Fault(string errorMessage, string errorStackTrace) + { + return WithFinalState(TestStepRunState.Faulted, DateTimeOffset.Now, errorMessage, errorStackTrace); + } + + public TestStepState Complete() + { + return WithFinalState(TestStepRunState.Complete, DateTimeOffset.Now, null, null); + } + + TestStepState WithFinalState(TestStepRunState runState, DateTimeOffset? taskCompleteTime, string errorMessage, string errorStackTrace) + { + return new TestStepState(runState, Machine, ProcessID, ProcessName, StartTime, taskCompleteTime, errorMessage, errorStackTrace); + } + + public string SerializeInitialState() + { + XElement initState = new XElement("InitialStepState", + new XElement("Machine", Machine), + new XElement("ProcessID", "0x" + ProcessID.ToString("x")), + new XElement("ProcessName", ProcessName), + new XElement("StartTime", StartTime) + ); + return initState.ToString(); + } + + public string SerializeFinalState() + { + XElement finalState = new XElement("FinalStepState", + new XElement("RunState", RunState) + ); + if (CompleteTime != null) + { + finalState.Add(new XElement("CompleteTime", CompleteTime.Value)); + } + if (ErrorMessage != null) + { + finalState.Add(new XElement("ErrorMessage", ErrorMessage)); + } + if (ErrorStackTrace != null) + { + finalState.Add(new XElement("ErrorStackTrace", ErrorStackTrace)); + } + return finalState.ToString(); + } + + public static bool TryParse(string text, out TestStepState parsedState) + { + parsedState = null; + try + { + // The XmlReader is not happy with two root nodes so we crudely split them. + int indexOfInitialStepStateElementEnd = text.IndexOf(""); + if(indexOfInitialStepStateElementEnd == -1) + { + return false; + } + int splitIndex = indexOfInitialStepStateElementEnd + "".Length; + string initialStepStateText = text.Substring(0, splitIndex); + string finalStepStateText = text.Substring(splitIndex); + + XElement initialStepStateElement = XElement.Parse(initialStepStateText); + if (initialStepStateElement == null || initialStepStateElement.Name != "InitialStepState") + { + return false; + } + XElement machineElement = initialStepStateElement.Element("Machine"); + if (machineElement == null || string.IsNullOrWhiteSpace(machineElement.Value)) + { + return false; + } + string machine = machineElement.Value; + XElement processIDElement = initialStepStateElement.Element("ProcessID"); + int processID; + if (processIDElement == null || + !processIDElement.Value.StartsWith("0x")) + { + return false; + } + string processIdNumberText = processIDElement.Value.Substring("0x".Length); + if (!int.TryParse(processIdNumberText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out processID)) + { + return false; + } + string processName = null; + XElement processNameElement = initialStepStateElement.Element("ProcessName"); + if (processNameElement != null) + { + processName = processNameElement.Value; + } + DateTimeOffset startTime; + XElement startTimeElement = initialStepStateElement.Element("StartTime"); + if (startTimeElement == null || !DateTimeOffset.TryParse(startTimeElement.Value, out startTime)) + { + return false; + } + parsedState = new TestStepState(TestStepRunState.InProgress, machine, processID, processName, startTime, null, null, null); + TryParseFinalState(finalStepStateText, ref parsedState); + return true; + } + catch (XmlException) + { + return false; + } + } + + private static void TryParseFinalState(string text, ref TestStepState taskState) + { + // If there are errors reading the final state portion of the stream we need to treat it + // as if the stream had terminated at the end of the InitialTaskState node. + // This covers a small window of time when appending the FinalTaskState node is in progress. + // + if(string.IsNullOrWhiteSpace(text)) + { + return; + } + try + { + XElement finalTaskStateElement = XElement.Parse(text); + if (finalTaskStateElement == null || finalTaskStateElement.Name != "FinalStepState") + { + return; + } + XElement runStateElement = finalTaskStateElement.Element("RunState"); + TestStepRunState runState; + if (runStateElement == null || !Enum.TryParse(runStateElement.Value, out runState)) + { + return; + } + DateTimeOffset? completeTime = null; + XElement completeTimeElement = finalTaskStateElement.Element("CompleteTime"); + if (completeTimeElement != null) + { + DateTimeOffset tempCompleteTime; + if (!DateTimeOffset.TryParse(completeTimeElement.Value, out tempCompleteTime)) + { + return; + } + else + { + completeTime = tempCompleteTime; + } + } + XElement errorMessageElement = finalTaskStateElement.Element("ErrorMessage"); + string errorMessage = null; + if (errorMessageElement != null) + { + errorMessage = errorMessageElement.Value; + } + XElement errorStackTraceElement = finalTaskStateElement.Element("ErrorStackTrace"); + string errorStackTrace = null; + if (errorStackTraceElement != null) + { + errorStackTrace = errorStackTraceElement.Value; + } + + taskState = taskState.WithFinalState(runState, completeTime, errorMessage, errorStackTrace); + } + catch (XmlException) { } + } + } + } + + public class TestStepException : Exception + { + public TestStepException(string errorMessage) : + base(errorMessage) + { } + + public TestStepException(string stepName, string errorMessage, string stackTrace) : + base("The " + stepName + " test step failed." + Environment.NewLine + + "Original Error: " + errorMessage + Environment.NewLine + + stackTrace) + { } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkipTestException.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkipTestException.cs new file mode 100644 index 000000000..21c5dbf6e --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkipTestException.cs @@ -0,0 +1,10 @@ +using System; + +namespace Xunit.Extensions +{ + public class SkipTestException : Exception + { + public SkipTestException(string reason) + : base(reason) { } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactAttribute.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactAttribute.cs new file mode 100644 index 000000000..8421204f6 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactAttribute.cs @@ -0,0 +1,8 @@ +using Xunit; +using Xunit.Sdk; + +namespace Xunit.Extensions +{ + [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableFactDiscoverer", "Microsoft.Diagnostic.TestHelpers")] + public class SkippableFactAttribute : FactAttribute { } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactDiscoverer.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactDiscoverer.cs new file mode 100644 index 000000000..0e3eccfd0 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactDiscoverer.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Xunit.Extensions +{ + public class SkippableFactDiscoverer : IXunitTestCaseDiscoverer + { + readonly IMessageSink diagnosticMessageSink; + + public SkippableFactDiscoverer(IMessageSink diagnosticMessageSink) + { + this.diagnosticMessageSink = diagnosticMessageSink; + } + + public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + { + yield return new SkippableFactTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod); + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactMessageBus.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactMessageBus.cs new file mode 100644 index 000000000..9a7fa3074 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactMessageBus.cs @@ -0,0 +1,37 @@ +using System.Linq; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Xunit.Extensions +{ + public class SkippableFactMessageBus : IMessageBus + { + readonly IMessageBus innerBus; + + public SkippableFactMessageBus(IMessageBus innerBus) + { + this.innerBus = innerBus; + } + + public int DynamicallySkippedTestCount { get; private set; } + + public void Dispose() { } + + public bool QueueMessage(IMessageSinkMessage message) + { + var testFailed = message as ITestFailed; + if (testFailed != null) + { + var exceptionType = testFailed.ExceptionTypes.FirstOrDefault(); + if (exceptionType == typeof(SkipTestException).FullName) + { + DynamicallySkippedTestCount++; + return innerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault())); + } + } + + // Nothing we care about, send it on its way + return innerBus.QueueMessage(message); + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactTestCase.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactTestCase.cs new file mode 100644 index 000000000..6e34cdec3 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactTestCase.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Xunit.Extensions +{ + public class SkippableFactTestCase : XunitTestCase + { + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public SkippableFactTestCase() { } + + public SkippableFactTestCase(IMessageSink diagnosticMessageSink, Sdk.TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null) + : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments) { } + + public override async Task RunAsync(IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + object[] constructorArguments, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + { + var skipMessageBus = new SkippableFactMessageBus(messageBus); + var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource); + if (skipMessageBus.DynamicallySkippedTestCount > 0) + { + result.Failed -= skipMessageBus.DynamicallySkippedTestCount; + result.Skipped += skipMessageBus.DynamicallySkippedTestCount; + } + + return result; + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryAttribute.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryAttribute.cs new file mode 100644 index 000000000..5496ea45b --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryAttribute.cs @@ -0,0 +1,7 @@ +using Xunit.Sdk; + +namespace Xunit.Extensions +{ + [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableTheoryDiscoverer", "Microsoft.Diagnostic.TestHelpers")] + public class SkippableTheoryAttribute : TheoryAttribute { } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryDiscoverer.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryDiscoverer.cs new file mode 100644 index 000000000..2640a021b --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryDiscoverer.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Xunit.Extensions +{ + public class SkippableTheoryDiscoverer : IXunitTestCaseDiscoverer + { + readonly IMessageSink diagnosticMessageSink; + readonly TheoryDiscoverer theoryDiscoverer; + + public SkippableTheoryDiscoverer(IMessageSink diagnosticMessageSink) + { + this.diagnosticMessageSink = diagnosticMessageSink; + + theoryDiscoverer = new TheoryDiscoverer(diagnosticMessageSink); + } + + public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + { + var defaultMethodDisplay = discoveryOptions.MethodDisplayOrDefault(); + + // Unlike fact discovery, the underlying algorithm for theories is complex, so we let the theory discoverer + // do its work, and do a little on-the-fly conversion into our own test cases. + return theoryDiscoverer.Discover(discoveryOptions, testMethod, factAttribute) + .Select(testCase => testCase is XunitTheoryTestCase + ? (IXunitTestCase)new SkippableTheoryTestCase(diagnosticMessageSink, defaultMethodDisplay, testCase.TestMethod) + : new SkippableFactTestCase(diagnosticMessageSink, defaultMethodDisplay, testCase.TestMethod, testCase.TestMethodArguments)); + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryTestCase.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryTestCase.cs new file mode 100644 index 000000000..a27012f6c --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryTestCase.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Xunit.Extensions +{ + public class SkippableTheoryTestCase : XunitTheoryTestCase + { + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public SkippableTheoryTestCase() { } + + public SkippableTheoryTestCase(IMessageSink diagnosticMessageSink, Xunit.Sdk.TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod) + : base(diagnosticMessageSink, defaultMethodDisplay, testMethod) { } + + public override async Task RunAsync(IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + object[] constructorArguments, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + { + // Duplicated code from SkippableFactTestCase. I'm sure we could find a way to de-dup with some thought. + var skipMessageBus = new SkippableFactMessageBus(messageBus); + var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource); + if (skipMessageBus.DynamicallySkippedTestCount > 0) + { + result.Failed -= skipMessageBus.DynamicallySkippedTestCount; + result.Skipped += skipMessageBus.DynamicallySkippedTestCount; + } + + return result; + } + } +} diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/license.txt b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/license.txt new file mode 100644 index 000000000..39b2d6592 --- /dev/null +++ b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/license.txt @@ -0,0 +1,15 @@ +Source in this directory is derived source at https://github.com/xunit/samples.xunit. The repo provided the following license: + +Copyright 2014 Outercurve Foundation + +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/src/SOS/CMakeLists.txt b/src/SOS/CMakeLists.txt index 5837e0330..1530948cf 100644 --- a/src/SOS/CMakeLists.txt +++ b/src/SOS/CMakeLists.txt @@ -13,7 +13,7 @@ endif(WIN32) add_definitions(-D_SECURE_SCL=0) -add_subdirectory(NETCore) +add_subdirectory(SOS.NETCore) add_subdirectory(Strike) if(CLR_CMAKE_PLATFORM_UNIX) diff --git a/src/SOS/NETCore/CMakeLists.txt b/src/SOS/NETCore/CMakeLists.txt deleted file mode 100644 index 28ff7ea70..000000000 --- a/src/SOS/NETCore/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -project(SOS.NETCore) - -if(NOT ${CLR_MANAGED_BINARY_DIR} STREQUAL "") - set(MANAGED_BINDIR ${CLR_MANAGED_BINARY_DIR}/SOS.NETCore/netcoreapp1.0) - - install(FILES ${MANAGED_BINDIR}/SOS.NETCore.dll DESTINATION .) - install(FILES ${MANAGED_BINDIR}/SOS.NETCore.pdb DESTINATION .) -endif() diff --git a/src/SOS/NETCore/SOS.NETCore.csproj b/src/SOS/NETCore/SOS.NETCore.csproj deleted file mode 100644 index a88c47e8b..000000000 --- a/src/SOS/NETCore/SOS.NETCore.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - netcoreapp1.0 - SOS.NETCore - true - ;1591;1701 - true - Managed SOS Services - $(Description) - SOS - - - - - - diff --git a/src/SOS/NETCore/SymbolReader.cs b/src/SOS/NETCore/SymbolReader.cs deleted file mode 100644 index 7a4bb5210..000000000 --- a/src/SOS/NETCore/SymbolReader.cs +++ /dev/null @@ -1,782 +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; -using System.Diagnostics; -using System.IO; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Reflection.PortableExecutable; -using System.Runtime.InteropServices; - -namespace SOS -{ - internal class SymbolReader - { - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct DebugInfo - { - public int lineNumber; - public int ilOffset; - public string fileName; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct LocalVarInfo - { - public int startOffset; - public int endOffset; - public string name; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct MethodDebugInfo - { - public IntPtr points; - public int size; - public IntPtr locals; - public int localsSize; - - } - - /// - /// Read memory callback - /// - /// number of bytes read or 0 for error - internal unsafe delegate int ReadMemoryDelegate(ulong address, byte* buffer, int count); - - private sealed class OpenedReader : IDisposable - { - public readonly MetadataReaderProvider Provider; - public readonly MetadataReader Reader; - - public OpenedReader(MetadataReaderProvider provider, MetadataReader reader) - { - Debug.Assert(provider != null); - Debug.Assert(reader != null); - - Provider = provider; - Reader = reader; - } - - public void Dispose() => Provider.Dispose(); - } - - /// - /// Stream implementation to read debugger target memory for in-memory PDBs - /// - private class TargetStream : Stream - { - readonly ulong _address; - readonly ReadMemoryDelegate _readMemory; - - public override long Position { get; set; } - public override long Length { get; } - public override bool CanSeek { get { return true; } } - public override bool CanRead { get { return true; } } - public override bool CanWrite { get { return false; } } - - public TargetStream(ulong address, int size, ReadMemoryDelegate readMemory) - : base() - { - _address = address; - _readMemory = readMemory; - Length = size; - Position = 0; - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (Position + count > Length) - { - throw new ArgumentOutOfRangeException(); - } - unsafe - { - fixed (byte* p = &buffer[offset]) - { - int read = _readMemory(_address + (ulong)Position, p, count); - Position += read; - return read; - } - } - } - - public override long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - Position = offset; - break; - case SeekOrigin.End: - Position = Length + offset; - break; - case SeekOrigin.Current: - Position += offset; - break; - } - return Position; - } - - public override void Flush() - { - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - } - - /// - /// Quick fix for Path.GetFileName which incorrectly handles Windows-style paths on Linux - /// - /// File path to be processed - /// Last component of path - private static string GetFileName(string pathName) - { - int pos = pathName.LastIndexOfAny(new char[] { '/', '\\'}); - if (pos < 0) - return pathName; - return pathName.Substring(pos + 1); - } - - /// - /// Checks availability of debugging information for given assembly. - /// - /// - /// File path of the assembly or null if the module is in-memory or dynamic (generated by Reflection.Emit) - /// - /// type of in-memory PE layout, if true, file based layout otherwise, loaded layout - /// - /// Loaded PE image address or zero if the module is dynamic (generated by Reflection.Emit). - /// Dynamic modules have their PDBs (if any) generated to an in-memory stream - /// (pointed to by and ). - /// - /// loaded PE image size - /// in memory PDB address or zero - /// in memory PDB size - /// Symbol reader handle or zero if error - internal static IntPtr LoadSymbolsForModule(string assemblyPath, bool isFileLayout, ulong loadedPeAddress, int loadedPeSize, - ulong inMemoryPdbAddress, int inMemoryPdbSize, ReadMemoryDelegate readMemory) - { - try - { - TargetStream peStream = null; - if (assemblyPath == null && loadedPeAddress != 0) - { - peStream = new TargetStream(loadedPeAddress, loadedPeSize, readMemory); - } - TargetStream pdbStream = null; - if (inMemoryPdbAddress != 0) - { - pdbStream = new TargetStream(inMemoryPdbAddress, inMemoryPdbSize, readMemory); - } - OpenedReader openedReader = GetReader(assemblyPath, isFileLayout, peStream, pdbStream); - if (openedReader != null) - { - GCHandle gch = GCHandle.Alloc(openedReader); - return GCHandle.ToIntPtr(gch); - } - } - catch - { - } - return IntPtr.Zero; - } - - /// - /// Cleanup and dispose of symbol reader handle - /// - /// symbol reader handle returned by LoadSymbolsForModule - internal static void Dispose(IntPtr symbolReaderHandle) - { - Debug.Assert(symbolReaderHandle != IntPtr.Zero); - try - { - GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); - ((OpenedReader)gch.Target).Dispose(); - gch.Free(); - } - catch - { - } - } - - /// - /// Returns method token and IL offset for given source line number. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// source file name and path - /// source line number - /// method token return - /// IL offset return - /// true if information is available - internal static bool ResolveSequencePoint(IntPtr symbolReaderHandle, string filePath, int lineNumber, out int methodToken, out int ilOffset) - { - Debug.Assert(symbolReaderHandle != IntPtr.Zero); - methodToken = 0; - ilOffset = 0; - - GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); - MetadataReader reader = ((OpenedReader)gch.Target).Reader; - - try - { - string fileName = GetFileName(filePath); - foreach (MethodDebugInformationHandle methodDebugInformationHandle in reader.MethodDebugInformation) - { - MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugInformationHandle); - SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); - foreach (SequencePoint point in sequencePoints) - { - string sourceName = reader.GetString(reader.GetDocument(point.Document).Name); - if (point.StartLine == lineNumber && GetFileName(sourceName) == fileName) - { - methodToken = MetadataTokens.GetToken(methodDebugInformationHandle.ToDefinitionHandle()); - ilOffset = point.Offset; - return true; - } - } - } - } - catch - { - } - return false; - } - - /// - /// Returns source line number and source file name for given IL offset and method token. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// method token - /// IL offset - /// source line number return - /// source file name return - /// true if information is available - internal static bool GetLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out IntPtr fileName) - { - lineNumber = 0; - fileName = IntPtr.Zero; - - string sourceFileName = null; - - if (!GetSourceLineByILOffset(symbolReaderHandle, methodToken, ilOffset, out lineNumber, out sourceFileName)) - { - return false; - } - fileName = Marshal.StringToBSTR(sourceFileName); - sourceFileName = null; - return true; - } - - /// - /// Helper method to return source line number and source file name for given IL offset and method token. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// method token - /// IL offset - /// source line number return - /// source file name return - /// true if information is available - private static bool GetSourceLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out string fileName) - { - Debug.Assert(symbolReaderHandle != IntPtr.Zero); - lineNumber = 0; - fileName = null; - - GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); - MetadataReader reader = ((OpenedReader)gch.Target).Reader; - - try - { - Handle handle = MetadataTokens.Handle(methodToken); - if (handle.Kind != HandleKind.MethodDefinition) - return false; - - MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); - if (methodDebugHandle.IsNil) - return false; - - MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugHandle); - SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); - - SequencePoint nearestPoint = sequencePoints.GetEnumerator().Current; - foreach (SequencePoint point in sequencePoints) - { - if (point.Offset < ilOffset) - { - nearestPoint = point; - } - else - { - if (point.Offset == ilOffset) - nearestPoint = point; - - if (nearestPoint.StartLine == 0 || nearestPoint.StartLine == SequencePoint.HiddenLine) - return false; - - lineNumber = nearestPoint.StartLine; - fileName = reader.GetString(reader.GetDocument(nearestPoint.Document).Name); - return true; - } - } - } - catch - { - } - return false; - } - - /// - /// Returns local variable name for given local index and IL offset. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// method token - /// local variable index - /// local variable name return - /// true if name has been found - internal static bool GetLocalVariableName(IntPtr symbolReaderHandle, int methodToken, int localIndex, out IntPtr localVarName) - { - localVarName = IntPtr.Zero; - - string localVar = null; - if (!GetLocalVariableByIndex(symbolReaderHandle, methodToken, localIndex, out localVar)) - return false; - - localVarName = Marshal.StringToBSTR(localVar); - localVar = null; - return true; - } - - /// - /// Helper method to return local variable name for given local index and IL offset. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// method token - /// local variable index - /// local variable name return - /// true if name has been found - internal static bool GetLocalVariableByIndex(IntPtr symbolReaderHandle, int methodToken, int localIndex, out string localVarName) - { - Debug.Assert(symbolReaderHandle != IntPtr.Zero); - localVarName = null; - - GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); - MetadataReader reader = ((OpenedReader)gch.Target).Reader; - - try - { - Handle handle = MetadataTokens.Handle(methodToken); - if (handle.Kind != HandleKind.MethodDefinition) - return false; - - MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); - LocalScopeHandleCollection localScopes = reader.GetLocalScopes(methodDebugHandle); - foreach (LocalScopeHandle scopeHandle in localScopes) - { - LocalScope scope = reader.GetLocalScope(scopeHandle); - LocalVariableHandleCollection localVars = scope.GetLocalVariables(); - foreach (LocalVariableHandle varHandle in localVars) - { - LocalVariable localVar = reader.GetLocalVariable(varHandle); - if (localVar.Index == localIndex) - { - if (localVar.Attributes == LocalVariableAttributes.DebuggerHidden) - return false; - - localVarName = reader.GetString(localVar.Name); - return true; - } - } - } - } - catch - { - } - return false; - } - internal static bool GetLocalsInfoForMethod(string assemblyPath, int methodToken, out List locals) - { - locals = null; - - OpenedReader openedReader = GetReader(assemblyPath, isFileLayout: true, peStream: null, pdbStream: null); - if (openedReader == null) - return false; - - using (openedReader) - { - try - { - Handle handle = MetadataTokens.Handle(methodToken); - if (handle.Kind != HandleKind.MethodDefinition) - return false; - - locals = new List(); - - MethodDebugInformationHandle methodDebugHandle = - ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); - LocalScopeHandleCollection localScopes = openedReader.Reader.GetLocalScopes(methodDebugHandle); - foreach (LocalScopeHandle scopeHandle in localScopes) - { - LocalScope scope = openedReader.Reader.GetLocalScope(scopeHandle); - LocalVariableHandleCollection localVars = scope.GetLocalVariables(); - foreach (LocalVariableHandle varHandle in localVars) - { - LocalVariable localVar = openedReader.Reader.GetLocalVariable(varHandle); - if (localVar.Attributes == LocalVariableAttributes.DebuggerHidden) - continue; - LocalVarInfo info = new LocalVarInfo(); - info.startOffset = scope.StartOffset; - info.endOffset = scope.EndOffset; - info.name = openedReader.Reader.GetString(localVar.Name); - locals.Add(info); - } - } - } - catch - { - return false; - } - } - return true; - - } - /// - /// Returns source name, line numbers and IL offsets for given method token. - /// - /// file path of the assembly - /// method token - /// structure with debug information return - /// true if information is available - /// used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs - internal static bool GetInfoForMethod(string assemblyPath, int methodToken, ref MethodDebugInfo debugInfo) - { - try - { - List points = null; - List locals = null; - - if (!GetDebugInfoForMethod(assemblyPath, methodToken, out points)) - { - return false; - } - - if (!GetLocalsInfoForMethod(assemblyPath, methodToken, out locals)) - { - return false; - } - var structSize = Marshal.SizeOf(); - - debugInfo.size = points.Count; - var ptr = debugInfo.points; - - foreach (var info in points) - { - Marshal.StructureToPtr(info, ptr, false); - ptr = (IntPtr)(ptr.ToInt64() + structSize); - } - - structSize = Marshal.SizeOf(); - - debugInfo.localsSize = locals.Count; - ptr = debugInfo.locals; - - foreach (var info in locals) - { - Marshal.StructureToPtr(info, ptr, false); - ptr = (IntPtr)(ptr.ToInt64() + structSize); - } - - return true; - } - catch - { - } - return false; - } - - /// - /// Helper method to return source name, line numbers and IL offsets for given method token. - /// - /// file path of the assembly - /// method token - /// list of debug information for each sequence point return - /// true if information is available - /// used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs - private static bool GetDebugInfoForMethod(string assemblyPath, int methodToken, out List points) - { - points = null; - - OpenedReader openedReader = GetReader(assemblyPath, isFileLayout: true, peStream: null, pdbStream: null); - if (openedReader == null) - return false; - - using (openedReader) - { - try - { - Handle handle = MetadataTokens.Handle(methodToken); - if (handle.Kind != HandleKind.MethodDefinition) - return false; - - points = new List(); - MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); - MethodDebugInformation methodDebugInfo = openedReader.Reader.GetMethodDebugInformation(methodDebugHandle); - SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); - - foreach (SequencePoint point in sequencePoints) - { - - DebugInfo debugInfo = new DebugInfo(); - debugInfo.lineNumber = point.StartLine; - debugInfo.fileName = openedReader.Reader.GetString(openedReader.Reader.GetDocument(point.Document).Name); - debugInfo.ilOffset = point.Offset; - points.Add(debugInfo); - } - } - catch - { - return false; - } - } - return true; - } - - /// - /// Returns the portable PDB reader for the assembly path - /// - /// file path of the assembly or null if the module is in-memory or dynamic - /// type of in-memory PE layout, if true, file based layout otherwise, loaded layout - /// optional in-memory PE stream - /// optional in-memory PDB stream - /// reader/provider wrapper instance - /// - /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around. - /// - private static OpenedReader GetReader(string assemblyPath, bool isFileLayout, Stream peStream, Stream pdbStream) - { - return (pdbStream != null) ? TryOpenReaderForInMemoryPdb(pdbStream) : TryOpenReaderFromAssembly(assemblyPath, isFileLayout, peStream); - } - - private static OpenedReader TryOpenReaderForInMemoryPdb(Stream pdbStream) - { - Debug.Assert(pdbStream != null); - - byte[] buffer = new byte[sizeof(uint)]; - if (pdbStream.Read(buffer, 0, sizeof(uint)) != sizeof(uint)) - { - return null; - } - uint signature = BitConverter.ToUInt32(buffer, 0); - - // quick check to avoid throwing exceptions below in common cases: - const uint ManagedMetadataSignature = 0x424A5342; - if (signature != ManagedMetadataSignature) - { - // not a Portable PDB - return null; - } - - OpenedReader result = null; - MetadataReaderProvider provider = null; - try - { - pdbStream.Position = 0; - provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); - result = new OpenedReader(provider, provider.GetMetadataReader()); - } - catch (Exception e) when (e is BadImageFormatException || e is IOException) - { - return null; - } - finally - { - if (result == null) - { - provider?.Dispose(); - } - } - - return result; - } - - private static OpenedReader TryOpenReaderFromAssembly(string assemblyPath, bool isFileLayout, Stream peStream) - { - if (assemblyPath == null && peStream == null) - return null; - - PEStreamOptions options = isFileLayout ? PEStreamOptions.Default : PEStreamOptions.IsLoadedImage; - if (peStream == null) - { - peStream = TryOpenFile(assemblyPath); - if (peStream == null) - return null; - - options = PEStreamOptions.Default; - } - - try - { - using (var peReader = new PEReader(peStream, options)) - { - DebugDirectoryEntry codeViewEntry, embeddedPdbEntry; - ReadPortableDebugTableEntries(peReader, out codeViewEntry, out embeddedPdbEntry); - - // First try .pdb file specified in CodeView data (we prefer .pdb file on disk over embedded PDB - // since embedded PDB needs decompression which is less efficient than memory-mapping the file). - if (codeViewEntry.DataSize != 0) - { - var result = TryOpenReaderFromCodeView(peReader, codeViewEntry, assemblyPath); - if (result != null) - { - return result; - } - } - - // if it failed try Embedded Portable PDB (if available): - if (embeddedPdbEntry.DataSize != 0) - { - return TryOpenReaderFromEmbeddedPdb(peReader, embeddedPdbEntry); - } - } - } - catch (Exception e) when (e is BadImageFormatException || e is IOException) - { - // nop - } - - return null; - } - - private static void ReadPortableDebugTableEntries(PEReader peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry embeddedPdbEntry) - { - // See spec: https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PE-COFF.md - - codeViewEntry = default(DebugDirectoryEntry); - embeddedPdbEntry = default(DebugDirectoryEntry); - - foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) - { - if (entry.Type == DebugDirectoryEntryType.CodeView) - { - const ushort PortableCodeViewVersionMagic = 0x504d; - if (entry.MinorVersion != PortableCodeViewVersionMagic) - { - continue; - } - - codeViewEntry = entry; - } - else if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb) - { - embeddedPdbEntry = entry; - } - } - } - - private static OpenedReader TryOpenReaderFromCodeView(PEReader peReader, DebugDirectoryEntry codeViewEntry, string assemblyPath) - { - OpenedReader result = null; - MetadataReaderProvider provider = null; - try - { - var data = peReader.ReadCodeViewDebugDirectoryData(codeViewEntry); - - string pdbPath = data.Path; - if (assemblyPath != null) - { - try - { - pdbPath = Path.Combine(Path.GetDirectoryName(assemblyPath), GetFileName(pdbPath)); - } - catch - { - // invalid characters in CodeView path - return null; - } - } - - var pdbStream = TryOpenFile(pdbPath); - if (pdbStream == null) - { - return null; - } - - provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); - var reader = provider.GetMetadataReader(); - - // Validate that the PDB matches the assembly version - if (data.Age == 1 && new BlobContentId(reader.DebugMetadataHeader.Id) == new BlobContentId(data.Guid, codeViewEntry.Stamp)) - { - result = new OpenedReader(provider, reader); - } - } - catch (Exception e) when (e is BadImageFormatException || e is IOException) - { - return null; - } - finally - { - if (result == null) - { - provider?.Dispose(); - } - } - - return result; - } - - private static OpenedReader TryOpenReaderFromEmbeddedPdb(PEReader peReader, DebugDirectoryEntry embeddedPdbEntry) - { - OpenedReader result = null; - MetadataReaderProvider provider = null; - - try - { - // TODO: We might want to cache this provider globally (across stack traces), - // since decompressing embedded PDB takes some time. - provider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry); - result = new OpenedReader(provider, provider.GetMetadataReader()); - } - catch (Exception e) when (e is BadImageFormatException || e is IOException) - { - return null; - } - finally - { - if (result == null) - { - provider?.Dispose(); - } - } - - return result; - } - - private static Stream TryOpenFile(string path) - { - if (!File.Exists(path)) - { - return null; - } - try - { - return File.OpenRead(path); - } - catch - { - return null; - } - } - } -} diff --git a/src/SOS/SOS.NETCore/CMakeLists.txt b/src/SOS/SOS.NETCore/CMakeLists.txt new file mode 100644 index 000000000..28ff7ea70 --- /dev/null +++ b/src/SOS/SOS.NETCore/CMakeLists.txt @@ -0,0 +1,8 @@ +project(SOS.NETCore) + +if(NOT ${CLR_MANAGED_BINARY_DIR} STREQUAL "") + set(MANAGED_BINDIR ${CLR_MANAGED_BINARY_DIR}/SOS.NETCore/netcoreapp1.0) + + install(FILES ${MANAGED_BINDIR}/SOS.NETCore.dll DESTINATION .) + install(FILES ${MANAGED_BINDIR}/SOS.NETCore.pdb DESTINATION .) +endif() diff --git a/src/SOS/SOS.NETCore/SOS.NETCore.csproj b/src/SOS/SOS.NETCore/SOS.NETCore.csproj new file mode 100644 index 000000000..a88c47e8b --- /dev/null +++ b/src/SOS/SOS.NETCore/SOS.NETCore.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp1.0 + SOS.NETCore + true + ;1591;1701 + true + Managed SOS Services + $(Description) + SOS + + + + + + diff --git a/src/SOS/SOS.NETCore/SymbolReader.cs b/src/SOS/SOS.NETCore/SymbolReader.cs new file mode 100644 index 000000000..7a4bb5210 --- /dev/null +++ b/src/SOS/SOS.NETCore/SymbolReader.cs @@ -0,0 +1,782 @@ +// 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.Diagnostics; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using System.Runtime.InteropServices; + +namespace SOS +{ + internal class SymbolReader + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct DebugInfo + { + public int lineNumber; + public int ilOffset; + public string fileName; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct LocalVarInfo + { + public int startOffset; + public int endOffset; + public string name; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MethodDebugInfo + { + public IntPtr points; + public int size; + public IntPtr locals; + public int localsSize; + + } + + /// + /// Read memory callback + /// + /// number of bytes read or 0 for error + internal unsafe delegate int ReadMemoryDelegate(ulong address, byte* buffer, int count); + + private sealed class OpenedReader : IDisposable + { + public readonly MetadataReaderProvider Provider; + public readonly MetadataReader Reader; + + public OpenedReader(MetadataReaderProvider provider, MetadataReader reader) + { + Debug.Assert(provider != null); + Debug.Assert(reader != null); + + Provider = provider; + Reader = reader; + } + + public void Dispose() => Provider.Dispose(); + } + + /// + /// Stream implementation to read debugger target memory for in-memory PDBs + /// + private class TargetStream : Stream + { + readonly ulong _address; + readonly ReadMemoryDelegate _readMemory; + + public override long Position { get; set; } + public override long Length { get; } + public override bool CanSeek { get { return true; } } + public override bool CanRead { get { return true; } } + public override bool CanWrite { get { return false; } } + + public TargetStream(ulong address, int size, ReadMemoryDelegate readMemory) + : base() + { + _address = address; + _readMemory = readMemory; + Length = size; + Position = 0; + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (Position + count > Length) + { + throw new ArgumentOutOfRangeException(); + } + unsafe + { + fixed (byte* p = &buffer[offset]) + { + int read = _readMemory(_address + (ulong)Position, p, count); + Position += read; + return read; + } + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + Position = offset; + break; + case SeekOrigin.End: + Position = Length + offset; + break; + case SeekOrigin.Current: + Position += offset; + break; + } + return Position; + } + + public override void Flush() + { + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } + + /// + /// Quick fix for Path.GetFileName which incorrectly handles Windows-style paths on Linux + /// + /// File path to be processed + /// Last component of path + private static string GetFileName(string pathName) + { + int pos = pathName.LastIndexOfAny(new char[] { '/', '\\'}); + if (pos < 0) + return pathName; + return pathName.Substring(pos + 1); + } + + /// + /// Checks availability of debugging information for given assembly. + /// + /// + /// File path of the assembly or null if the module is in-memory or dynamic (generated by Reflection.Emit) + /// + /// type of in-memory PE layout, if true, file based layout otherwise, loaded layout + /// + /// Loaded PE image address or zero if the module is dynamic (generated by Reflection.Emit). + /// Dynamic modules have their PDBs (if any) generated to an in-memory stream + /// (pointed to by and ). + /// + /// loaded PE image size + /// in memory PDB address or zero + /// in memory PDB size + /// Symbol reader handle or zero if error + internal static IntPtr LoadSymbolsForModule(string assemblyPath, bool isFileLayout, ulong loadedPeAddress, int loadedPeSize, + ulong inMemoryPdbAddress, int inMemoryPdbSize, ReadMemoryDelegate readMemory) + { + try + { + TargetStream peStream = null; + if (assemblyPath == null && loadedPeAddress != 0) + { + peStream = new TargetStream(loadedPeAddress, loadedPeSize, readMemory); + } + TargetStream pdbStream = null; + if (inMemoryPdbAddress != 0) + { + pdbStream = new TargetStream(inMemoryPdbAddress, inMemoryPdbSize, readMemory); + } + OpenedReader openedReader = GetReader(assemblyPath, isFileLayout, peStream, pdbStream); + if (openedReader != null) + { + GCHandle gch = GCHandle.Alloc(openedReader); + return GCHandle.ToIntPtr(gch); + } + } + catch + { + } + return IntPtr.Zero; + } + + /// + /// Cleanup and dispose of symbol reader handle + /// + /// symbol reader handle returned by LoadSymbolsForModule + internal static void Dispose(IntPtr symbolReaderHandle) + { + Debug.Assert(symbolReaderHandle != IntPtr.Zero); + try + { + GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); + ((OpenedReader)gch.Target).Dispose(); + gch.Free(); + } + catch + { + } + } + + /// + /// Returns method token and IL offset for given source line number. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// source file name and path + /// source line number + /// method token return + /// IL offset return + /// true if information is available + internal static bool ResolveSequencePoint(IntPtr symbolReaderHandle, string filePath, int lineNumber, out int methodToken, out int ilOffset) + { + Debug.Assert(symbolReaderHandle != IntPtr.Zero); + methodToken = 0; + ilOffset = 0; + + GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); + MetadataReader reader = ((OpenedReader)gch.Target).Reader; + + try + { + string fileName = GetFileName(filePath); + foreach (MethodDebugInformationHandle methodDebugInformationHandle in reader.MethodDebugInformation) + { + MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugInformationHandle); + SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); + foreach (SequencePoint point in sequencePoints) + { + string sourceName = reader.GetString(reader.GetDocument(point.Document).Name); + if (point.StartLine == lineNumber && GetFileName(sourceName) == fileName) + { + methodToken = MetadataTokens.GetToken(methodDebugInformationHandle.ToDefinitionHandle()); + ilOffset = point.Offset; + return true; + } + } + } + } + catch + { + } + return false; + } + + /// + /// Returns source line number and source file name for given IL offset and method token. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// method token + /// IL offset + /// source line number return + /// source file name return + /// true if information is available + internal static bool GetLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out IntPtr fileName) + { + lineNumber = 0; + fileName = IntPtr.Zero; + + string sourceFileName = null; + + if (!GetSourceLineByILOffset(symbolReaderHandle, methodToken, ilOffset, out lineNumber, out sourceFileName)) + { + return false; + } + fileName = Marshal.StringToBSTR(sourceFileName); + sourceFileName = null; + return true; + } + + /// + /// Helper method to return source line number and source file name for given IL offset and method token. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// method token + /// IL offset + /// source line number return + /// source file name return + /// true if information is available + private static bool GetSourceLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out string fileName) + { + Debug.Assert(symbolReaderHandle != IntPtr.Zero); + lineNumber = 0; + fileName = null; + + GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); + MetadataReader reader = ((OpenedReader)gch.Target).Reader; + + try + { + Handle handle = MetadataTokens.Handle(methodToken); + if (handle.Kind != HandleKind.MethodDefinition) + return false; + + MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); + if (methodDebugHandle.IsNil) + return false; + + MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugHandle); + SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); + + SequencePoint nearestPoint = sequencePoints.GetEnumerator().Current; + foreach (SequencePoint point in sequencePoints) + { + if (point.Offset < ilOffset) + { + nearestPoint = point; + } + else + { + if (point.Offset == ilOffset) + nearestPoint = point; + + if (nearestPoint.StartLine == 0 || nearestPoint.StartLine == SequencePoint.HiddenLine) + return false; + + lineNumber = nearestPoint.StartLine; + fileName = reader.GetString(reader.GetDocument(nearestPoint.Document).Name); + return true; + } + } + } + catch + { + } + return false; + } + + /// + /// Returns local variable name for given local index and IL offset. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// method token + /// local variable index + /// local variable name return + /// true if name has been found + internal static bool GetLocalVariableName(IntPtr symbolReaderHandle, int methodToken, int localIndex, out IntPtr localVarName) + { + localVarName = IntPtr.Zero; + + string localVar = null; + if (!GetLocalVariableByIndex(symbolReaderHandle, methodToken, localIndex, out localVar)) + return false; + + localVarName = Marshal.StringToBSTR(localVar); + localVar = null; + return true; + } + + /// + /// Helper method to return local variable name for given local index and IL offset. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// method token + /// local variable index + /// local variable name return + /// true if name has been found + internal static bool GetLocalVariableByIndex(IntPtr symbolReaderHandle, int methodToken, int localIndex, out string localVarName) + { + Debug.Assert(symbolReaderHandle != IntPtr.Zero); + localVarName = null; + + GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); + MetadataReader reader = ((OpenedReader)gch.Target).Reader; + + try + { + Handle handle = MetadataTokens.Handle(methodToken); + if (handle.Kind != HandleKind.MethodDefinition) + return false; + + MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); + LocalScopeHandleCollection localScopes = reader.GetLocalScopes(methodDebugHandle); + foreach (LocalScopeHandle scopeHandle in localScopes) + { + LocalScope scope = reader.GetLocalScope(scopeHandle); + LocalVariableHandleCollection localVars = scope.GetLocalVariables(); + foreach (LocalVariableHandle varHandle in localVars) + { + LocalVariable localVar = reader.GetLocalVariable(varHandle); + if (localVar.Index == localIndex) + { + if (localVar.Attributes == LocalVariableAttributes.DebuggerHidden) + return false; + + localVarName = reader.GetString(localVar.Name); + return true; + } + } + } + } + catch + { + } + return false; + } + internal static bool GetLocalsInfoForMethod(string assemblyPath, int methodToken, out List locals) + { + locals = null; + + OpenedReader openedReader = GetReader(assemblyPath, isFileLayout: true, peStream: null, pdbStream: null); + if (openedReader == null) + return false; + + using (openedReader) + { + try + { + Handle handle = MetadataTokens.Handle(methodToken); + if (handle.Kind != HandleKind.MethodDefinition) + return false; + + locals = new List(); + + MethodDebugInformationHandle methodDebugHandle = + ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); + LocalScopeHandleCollection localScopes = openedReader.Reader.GetLocalScopes(methodDebugHandle); + foreach (LocalScopeHandle scopeHandle in localScopes) + { + LocalScope scope = openedReader.Reader.GetLocalScope(scopeHandle); + LocalVariableHandleCollection localVars = scope.GetLocalVariables(); + foreach (LocalVariableHandle varHandle in localVars) + { + LocalVariable localVar = openedReader.Reader.GetLocalVariable(varHandle); + if (localVar.Attributes == LocalVariableAttributes.DebuggerHidden) + continue; + LocalVarInfo info = new LocalVarInfo(); + info.startOffset = scope.StartOffset; + info.endOffset = scope.EndOffset; + info.name = openedReader.Reader.GetString(localVar.Name); + locals.Add(info); + } + } + } + catch + { + return false; + } + } + return true; + + } + /// + /// Returns source name, line numbers and IL offsets for given method token. + /// + /// file path of the assembly + /// method token + /// structure with debug information return + /// true if information is available + /// used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs + internal static bool GetInfoForMethod(string assemblyPath, int methodToken, ref MethodDebugInfo debugInfo) + { + try + { + List points = null; + List locals = null; + + if (!GetDebugInfoForMethod(assemblyPath, methodToken, out points)) + { + return false; + } + + if (!GetLocalsInfoForMethod(assemblyPath, methodToken, out locals)) + { + return false; + } + var structSize = Marshal.SizeOf(); + + debugInfo.size = points.Count; + var ptr = debugInfo.points; + + foreach (var info in points) + { + Marshal.StructureToPtr(info, ptr, false); + ptr = (IntPtr)(ptr.ToInt64() + structSize); + } + + structSize = Marshal.SizeOf(); + + debugInfo.localsSize = locals.Count; + ptr = debugInfo.locals; + + foreach (var info in locals) + { + Marshal.StructureToPtr(info, ptr, false); + ptr = (IntPtr)(ptr.ToInt64() + structSize); + } + + return true; + } + catch + { + } + return false; + } + + /// + /// Helper method to return source name, line numbers and IL offsets for given method token. + /// + /// file path of the assembly + /// method token + /// list of debug information for each sequence point return + /// true if information is available + /// used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs + private static bool GetDebugInfoForMethod(string assemblyPath, int methodToken, out List points) + { + points = null; + + OpenedReader openedReader = GetReader(assemblyPath, isFileLayout: true, peStream: null, pdbStream: null); + if (openedReader == null) + return false; + + using (openedReader) + { + try + { + Handle handle = MetadataTokens.Handle(methodToken); + if (handle.Kind != HandleKind.MethodDefinition) + return false; + + points = new List(); + MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); + MethodDebugInformation methodDebugInfo = openedReader.Reader.GetMethodDebugInformation(methodDebugHandle); + SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); + + foreach (SequencePoint point in sequencePoints) + { + + DebugInfo debugInfo = new DebugInfo(); + debugInfo.lineNumber = point.StartLine; + debugInfo.fileName = openedReader.Reader.GetString(openedReader.Reader.GetDocument(point.Document).Name); + debugInfo.ilOffset = point.Offset; + points.Add(debugInfo); + } + } + catch + { + return false; + } + } + return true; + } + + /// + /// Returns the portable PDB reader for the assembly path + /// + /// file path of the assembly or null if the module is in-memory or dynamic + /// type of in-memory PE layout, if true, file based layout otherwise, loaded layout + /// optional in-memory PE stream + /// optional in-memory PDB stream + /// reader/provider wrapper instance + /// + /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around. + /// + private static OpenedReader GetReader(string assemblyPath, bool isFileLayout, Stream peStream, Stream pdbStream) + { + return (pdbStream != null) ? TryOpenReaderForInMemoryPdb(pdbStream) : TryOpenReaderFromAssembly(assemblyPath, isFileLayout, peStream); + } + + private static OpenedReader TryOpenReaderForInMemoryPdb(Stream pdbStream) + { + Debug.Assert(pdbStream != null); + + byte[] buffer = new byte[sizeof(uint)]; + if (pdbStream.Read(buffer, 0, sizeof(uint)) != sizeof(uint)) + { + return null; + } + uint signature = BitConverter.ToUInt32(buffer, 0); + + // quick check to avoid throwing exceptions below in common cases: + const uint ManagedMetadataSignature = 0x424A5342; + if (signature != ManagedMetadataSignature) + { + // not a Portable PDB + return null; + } + + OpenedReader result = null; + MetadataReaderProvider provider = null; + try + { + pdbStream.Position = 0; + provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); + result = new OpenedReader(provider, provider.GetMetadataReader()); + } + catch (Exception e) when (e is BadImageFormatException || e is IOException) + { + return null; + } + finally + { + if (result == null) + { + provider?.Dispose(); + } + } + + return result; + } + + private static OpenedReader TryOpenReaderFromAssembly(string assemblyPath, bool isFileLayout, Stream peStream) + { + if (assemblyPath == null && peStream == null) + return null; + + PEStreamOptions options = isFileLayout ? PEStreamOptions.Default : PEStreamOptions.IsLoadedImage; + if (peStream == null) + { + peStream = TryOpenFile(assemblyPath); + if (peStream == null) + return null; + + options = PEStreamOptions.Default; + } + + try + { + using (var peReader = new PEReader(peStream, options)) + { + DebugDirectoryEntry codeViewEntry, embeddedPdbEntry; + ReadPortableDebugTableEntries(peReader, out codeViewEntry, out embeddedPdbEntry); + + // First try .pdb file specified in CodeView data (we prefer .pdb file on disk over embedded PDB + // since embedded PDB needs decompression which is less efficient than memory-mapping the file). + if (codeViewEntry.DataSize != 0) + { + var result = TryOpenReaderFromCodeView(peReader, codeViewEntry, assemblyPath); + if (result != null) + { + return result; + } + } + + // if it failed try Embedded Portable PDB (if available): + if (embeddedPdbEntry.DataSize != 0) + { + return TryOpenReaderFromEmbeddedPdb(peReader, embeddedPdbEntry); + } + } + } + catch (Exception e) when (e is BadImageFormatException || e is IOException) + { + // nop + } + + return null; + } + + private static void ReadPortableDebugTableEntries(PEReader peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry embeddedPdbEntry) + { + // See spec: https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PE-COFF.md + + codeViewEntry = default(DebugDirectoryEntry); + embeddedPdbEntry = default(DebugDirectoryEntry); + + foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) + { + if (entry.Type == DebugDirectoryEntryType.CodeView) + { + const ushort PortableCodeViewVersionMagic = 0x504d; + if (entry.MinorVersion != PortableCodeViewVersionMagic) + { + continue; + } + + codeViewEntry = entry; + } + else if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb) + { + embeddedPdbEntry = entry; + } + } + } + + private static OpenedReader TryOpenReaderFromCodeView(PEReader peReader, DebugDirectoryEntry codeViewEntry, string assemblyPath) + { + OpenedReader result = null; + MetadataReaderProvider provider = null; + try + { + var data = peReader.ReadCodeViewDebugDirectoryData(codeViewEntry); + + string pdbPath = data.Path; + if (assemblyPath != null) + { + try + { + pdbPath = Path.Combine(Path.GetDirectoryName(assemblyPath), GetFileName(pdbPath)); + } + catch + { + // invalid characters in CodeView path + return null; + } + } + + var pdbStream = TryOpenFile(pdbPath); + if (pdbStream == null) + { + return null; + } + + provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); + var reader = provider.GetMetadataReader(); + + // Validate that the PDB matches the assembly version + if (data.Age == 1 && new BlobContentId(reader.DebugMetadataHeader.Id) == new BlobContentId(data.Guid, codeViewEntry.Stamp)) + { + result = new OpenedReader(provider, reader); + } + } + catch (Exception e) when (e is BadImageFormatException || e is IOException) + { + return null; + } + finally + { + if (result == null) + { + provider?.Dispose(); + } + } + + return result; + } + + private static OpenedReader TryOpenReaderFromEmbeddedPdb(PEReader peReader, DebugDirectoryEntry embeddedPdbEntry) + { + OpenedReader result = null; + MetadataReaderProvider provider = null; + + try + { + // TODO: We might want to cache this provider globally (across stack traces), + // since decompressing embedded PDB takes some time. + provider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry); + result = new OpenedReader(provider, provider.GetMetadataReader()); + } + catch (Exception e) when (e is BadImageFormatException || e is IOException) + { + return null; + } + finally + { + if (result == null) + { + provider?.Dispose(); + } + } + + return result; + } + + private static Stream TryOpenFile(string path) + { + if (!File.Exists(path)) + { + return null; + } + try + { + return File.OpenRead(path); + } + catch + { + return null; + } + } + } +} diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Debug/Debugger.Tests.Common.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Debug/Debugger.Tests.Common.txt new file mode 100644 index 000000000..2ccca66bc --- /dev/null +++ b/src/SOS/SOS.UnitTests/ConfigFiles/Debug/Debugger.Tests.Common.txt @@ -0,0 +1,3 @@ + + Debug + diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Release/Debugger.Tests.Common.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Release/Debugger.Tests.Common.txt new file mode 100644 index 000000000..c6bed20a4 --- /dev/null +++ b/src/SOS/SOS.UnitTests/ConfigFiles/Release/Debugger.Tests.Common.txt @@ -0,0 +1,3 @@ + + Release + diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt new file mode 100644 index 000000000..1e3952177 --- /dev/null +++ b/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt @@ -0,0 +1,67 @@ + + + + + + ProjectK + ../../../../.. + $(RepoRootDir)/src/SOS/SOS.UnitTests/Scripts + $(RepoRootDir)/artifacts + $(RootBinDir)/$(TargetConfiguration)/bin/$(OS).$(TargetArchitecture) + $(RootBinDir)/$(TargetConfiguration)/TestResults/sos.unittests_$(Timestamp) + $(RootBinDir)/$(TargetConfiguration)/tmp/dumps + + $(RepoRootDir)/src/SOS/SOS.UnitTests/Debuggees + $(RootBinDir)/Debuggees + $(DebuggeeRootDir) + $(DebuggeeBuildRoot)/native + cli + + 2.1.0 + netcoreapp2.1 + $(RepoRootDir)/.dotnet/dotnet + + + myget.org dotnet-core=https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; + nuget.org=https://www.nuget.org/api/v2/ + + + + + $(RepoRootDir)/.dotnet/dotnet + $(RepoRootDir)/.dotnet/shared/Microsoft.NETCore.App/$(BuildProjectMicrosoftNetCoreAppVersion) + $(ScriptRootDir)/lldbhelper.py + + + + + + + diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt new file mode 100644 index 000000000..edbcec6f8 --- /dev/null +++ b/src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt @@ -0,0 +1,69 @@ + + + + + + ..\..\..\..\.. + $(RepoRootDir)\src\SOS\SOS.UnitTests\Scripts + $(RepoRootDir)\artifacts + + $(RootBinDir)\$(TargetConfiguration)\bin\Windows_NT.$(TargetArchitecture) + $(RootBinDir)\$(TargetConfiguration)\TestResults\sos.unittests_$(Timestamp) + $(RootBinDir)\$(TargetConfiguration)\tmp\dumps + $(NuGetPackageCacheDir)\cdb-sos\1.1.0\runtimes\win-$(TargetArchitecture)\native\cdb.exe + + $(RepoRootDir)\src\SOS\SOS.UnitTests\Debuggees + $(RootBinDir)\Debuggees + $(DebuggeeRootDir) + $(DebuggeeBuildRoot)\native + cli + + 2.1.0 + netcoreapp2.1 + $(RepoRootDir)\.dotnet\dotnet.exe + + + myget.org dotnet-core=https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; + nuget.org=https://www.nuget.org/api/v2/ + + + + + + + + $(DumpDir)\$(TestProduct) + $(DebuggeeDumpOutputRootDir) + diff --git a/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.cs b/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.cs new file mode 100644 index 000000000..bc7aaba9c --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.cs @@ -0,0 +1,77 @@ +using System;using System.Security; +#if WINCORESYS +[assembly: AllowPartiallyTrustedCallers] +#endif +public class C{ + public static void DivideByZero(ref int v, ref int w) + { + int x; + int y = 1; + + y--; + x = 2 / y; + } + + public static void F3(ref int m, ref int n) + { + int a = 1; + int b = 2; + int c; + + DivideByZero(ref a, ref b); // pass locals by ref to prevent enregistering + + c = a + b; + Console.WriteLine("a + b = {0}", c); + } + + public static void F2() + { + int p = 3; + int q = 4; + int r; + + F3(ref p, ref q); // pass locals by ref to prevent enregistering + + r = p * q; + Console.WriteLine("p * q = {0}", r); + } + + public static void F1() + { + try + { + F2(); + } + catch (DivideByZeroException) + { + Console.WriteLine("F2 catch"); + } + } + + public static void Main(string[] args) + { + F1(); + F2(); + } + + + // This method should be called to pass SoS.DivZero test. + // We need to figure out how to call it from Main. + public static void R() + { + int m = 5; + int n = 6; + bool f = true; + + // We want to test two types of Exception("Application Error Message")s. + // Divide by zero is a hardware fault, resulting + // no stack space movement. + // The two cases is a ">" versus ">=" test. + DivideByZero(ref m, ref n); + + if (f) // this is always true, just to silence a compiler warning + throw new ArgumentException("Application Error Message"); + + Console.WriteLine("m - n = {0}", m-n); + } +} diff --git a/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.csproj b/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.csproj new file mode 100644 index 000000000..d7cb5bd20 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.csproj @@ -0,0 +1,8 @@ + + + Exe + netcoreapp2.1 + true + true + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.cs b/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.cs deleted file mode 100644 index bc7aaba9c..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System;using System.Security; -#if WINCORESYS -[assembly: AllowPartiallyTrustedCallers] -#endif -public class C{ - public static void DivideByZero(ref int v, ref int w) - { - int x; - int y = 1; - - y--; - x = 2 / y; - } - - public static void F3(ref int m, ref int n) - { - int a = 1; - int b = 2; - int c; - - DivideByZero(ref a, ref b); // pass locals by ref to prevent enregistering - - c = a + b; - Console.WriteLine("a + b = {0}", c); - } - - public static void F2() - { - int p = 3; - int q = 4; - int r; - - F3(ref p, ref q); // pass locals by ref to prevent enregistering - - r = p * q; - Console.WriteLine("p * q = {0}", r); - } - - public static void F1() - { - try - { - F2(); - } - catch (DivideByZeroException) - { - Console.WriteLine("F2 catch"); - } - } - - public static void Main(string[] args) - { - F1(); - F2(); - } - - - // This method should be called to pass SoS.DivZero test. - // We need to figure out how to call it from Main. - public static void R() - { - int m = 5; - int n = 6; - bool f = true; - - // We want to test two types of Exception("Application Error Message")s. - // Divide by zero is a hardware fault, resulting - // no stack space movement. - // The two cases is a ">" versus ">=" test. - DivideByZero(ref m, ref n); - - if (f) // this is always true, just to silence a compiler warning - throw new ArgumentException("Application Error Message"); - - Console.WriteLine("m - n = {0}", m-n); - } -} diff --git a/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.csproj b/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.csproj deleted file mode 100644 index df6bb3dd5..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - - sedouard - Debuggee - true - {A2F6FA79-80DB-4EA5-A2E2-CC0BE785169F} - SAK - SAK - SAK - SAK - - - - - - - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/GCWhere/GCWhere.csproj b/src/SOS/SOS.UnitTests/Debuggees/GCWhere/GCWhere.csproj new file mode 100644 index 000000000..d7cb5bd20 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/GCWhere/GCWhere.csproj @@ -0,0 +1,8 @@ + + + Exe + netcoreapp2.1 + true + true + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/GCWhere/gcwhere.csproj b/src/SOS/SOS.UnitTests/Debuggees/GCWhere/gcwhere.csproj deleted file mode 100644 index 1e75061dc..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/GCWhere/gcwhere.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - sridhper - Debuggee - exe - true - - true - SAK - SAK - SAK - SAK - {0F0CA3AD-B37C-452E-BA91-678F71D6AFAA} - - - - - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/NestedExceptionTest.csproj b/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/NestedExceptionTest.csproj new file mode 100644 index 000000000..d7cb5bd20 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/NestedExceptionTest.csproj @@ -0,0 +1,8 @@ + + + Exe + netcoreapp2.1 + true + true + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/nestedexceptiontest.csproj b/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/nestedexceptiontest.csproj deleted file mode 100644 index ad30c93be..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/nestedexceptiontest.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - - mikem - Debuggee - exe - - - - - - diff --git a/src/SOS/SOS.UnitTests/Debuggees/Overflow/Overflow.csproj b/src/SOS/SOS.UnitTests/Debuggees/Overflow/Overflow.csproj index c292244ed..d7cb5bd20 100644 --- a/src/SOS/SOS.UnitTests/Debuggees/Overflow/Overflow.csproj +++ b/src/SOS/SOS.UnitTests/Debuggees/Overflow/Overflow.csproj @@ -1,13 +1,8 @@ - - - - - conniey - Debuggee - Release - - - - - - \ No newline at end of file + + + Exe + netcoreapp2.1 + true + true + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.cs b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.cs deleted file mode 100644 index 0c45fc3c4..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace RSSFeedTest -{ - public static class AsyncWinRTTest - { - public static void Main() - { - // Bad URL - string badURL = "http://feeds.aljazeeraportal.net/AJE/live/1/200779101832373555?removehtml=1"; - string goodURL = "http://feeds.aljazeeraportal.net/aje/live/1/200779101832373555?removehtml=1"; - TestRSSFeed feed = new TestRSSFeed(goodURL); - Console.WriteLine("Throwing from asyncAction with Progress"); - var theTask = feed.ThrowFromAsyncActionWithProgress(); - theTask.Wait(); - Console.WriteLine("The Program is exiting...."); - return; - } - } -} \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.csproj b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.csproj deleted file mode 100644 index bdb1b4caa..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - - conniey - Debuggee - exe - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/TestClasses.cs b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/TestClasses.cs deleted file mode 100644 index 3591f4c60..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/TestClasses.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Windows.RSS.Utils; -using Windows.Foundation.Collections; - -namespace RSSFeedTest -{ - public class TestRSSFeed - { - public readonly string FeedUri; - - private FeedItem[] m_feedItems; - private readonly FeedDataSource m_feedDataSource; - - public TestRSSFeed(string feedURI) - { - FeedUri = feedURI; - m_feedDataSource = new FeedDataSource(); - } - - public async void Initialize() - { - var iasyncOp = m_feedDataSource.GetFeedAsync(FeedUri); - - await iasyncOp.AsTask() - .ContinueWith(t => - { - FeedData rssFeed = t.Result; - - for (int i = 0; i < rssFeed.Items.Count; i++) - { - var item = rssFeed.Items[i]; - string newSummary = ParseFeedSummary(item.Summary); - item.Summary = newSummary; - } - - return rssFeed; - }) - .ContinueWith((Task t, object _) => - { - var rssFeed = t.Result; - m_feedItems = new FeedItem[rssFeed.Items.Count]; - for (int i = 0; i < rssFeed.Items.Count; i++) - { - var item = rssFeed.Items[i]; - m_feedItems[i] = item; - } - }, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext()); - } - - public async Task ThrowFromAsyncActionWithProgress() - { - TestObservableVector vector = new TestObservableVector(); - vector.Add(FeedUri); - Console.WriteLine("Calling into winrt code..."); - await m_feedDataSource.GetFeedsAsync(vector); - Console.WriteLine("Should not finish!"); - } - - /// - /// This removes all of those HTML escaped characters and the image URL - /// at the beginning of the RSS feed description. - /// I hacked this into C# because it is easier than writing the parsing - /// code into the winRT component. ;( - /// - private static string ParseFeedSummary(string description) - { - string fixedDescription = description.Replace("&", "&"); - fixedDescription = fixedDescription.Replace("<", "<"); - fixedDescription = fixedDescription.Replace(">", ">"); - - return fixedDescription; - } - } - - public class TestObservableVector : List, IObservableVector - { - public event VectorChangedEventHandler VectorChanged; - - public TestObservableVector() - { - VectorChanged = delegate(IObservableVector sender, IVectorChangedEventArgs e) { }; - } - } -} \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/DateConverter.h b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/DateConverter.h deleted file mode 100644 index 27a6c6d0f..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/DateConverter.h +++ /dev/null @@ -1,77 +0,0 @@ -//********************************************************* -// -// Copyright (c) Microsoft. All rights reserved. -// http://code.msdn.microsoft.com/windowsapps/Windows-Store-Simple-Blog-953302e8 -//********************************************************* - -//DateConverter.h - -#pragma once -#include //for wcscmp - -namespace Windows -{ - namespace RSS - { - namespace Utils - { - public ref class DateConverter sealed : public Windows::UI::Xaml::Data::IValueConverter - { - public: - virtual Platform::Object^ Convert(Platform::Object^ value, - Windows::UI::Xaml::Interop::TypeName targetType, - Platform::Object^ parameter, - Platform::String^ language) - { - if (value == nullptr) - { - throw ref new Platform::InvalidArgumentException(); - } - auto dt = safe_cast(value); - auto param = safe_cast(parameter); - Platform::String^ result; - if (param == nullptr) - { - auto dtf = - Windows::Globalization::DateTimeFormatting::DateTimeFormatter::ShortDate::get(); - result = dtf->Format(dt); - } - else if (wcscmp(param->Data(), L"month") == 0) - { - auto month = - ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{month.abbreviated(3)}"); - result = month->Format(dt); - } - else if (wcscmp(param->Data(), L"day") == 0) - { - auto month = - ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{day.integer(2)}"); - result = month->Format(dt); - } - else if (wcscmp(param->Data(), L"year") == 0) - { - auto month = - ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{year.full}"); - result = month->Format(dt); - } - else - { - // We don't handle other format types currently. - throw ref new Platform::InvalidArgumentException(); - } - - return result; - } - - virtual Platform::Object^ ConvertBack(Platform::Object^ value, - Windows::UI::Xaml::Interop::TypeName targetType, - Platform::Object^ parameter, - Platform::String^ language) - { - // Not needed in Windows. Left as an exercise. - throw ref new Platform::NotImplementedException(); - } - }; - } - } -} \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.cpp b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.cpp deleted file mode 100644 index 0879bd257..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.cpp +++ /dev/null @@ -1,150 +0,0 @@ -//********************************************************* -// -// Copyright (c) Microsoft. All rights reserved. -// http://code.msdn.microsoft.com/windowsapps/Windows-Store-Simple-Blog-953302e8 -//********************************************************* - -#include "pch.h" -#include "FeedData.h" - -using namespace std; -using namespace Concurrency; -using namespace Platform; -using namespace Platform::Collections; -using namespace Windows::Foundation; -using namespace Windows::Web::Syndication; -using namespace Windows::RSS::Utils; - -FeedDataSource::FeedDataSource() -{ - m_feeds = ref new Vector(); -} - -// We use this method to get the proper FeedData object when resuming -// from shutdown. We need to wait for this data to be populated before -// we attempt to restore page state. Note the use of task_completion_event -// which doesn't block the UI thread. -IAsyncOperation^ FeedDataSource::GetFeedAsync(String^ uri) -{ - return create_async([uri, this]() - { - //auto feedDataSource = safe_cast( - // App::Current->Resources->Lookup("feedDataSource")); - auto iterator = this->m_feedCompletionEvents.find(uri); - if (iterator == this->m_feedCompletionEvents.end()) - this->AddFeed(uri); - - // Does not block the UI thread. - auto f = this->m_feedCompletionEvents[uri]; - - // In the callers we continue from this task after the event is - // set in InitDataSource and we know we have a FeedData^. - task t = create_task(f); - return t; - }); -} - -void FeedDataSource::RemoveFeed(String^ uri) -{ - int num = m_feedCompletionEvents.erase(uri); - String^ debug = L"Removed: " + num.ToString(); - OutputDebugString(debug->Data()); - - int index = -1; - for (auto feed : m_feeds) - { - if (feed->Uri->Equals(uri)) - break; - - index++; - } - if (index == -1) - OutputDebugString(L"COULD NOT Find the feed to remove!"); - else - m_feeds->RemoveAt(index); -} - -void FeedDataSource::AddFeed(String^ uri) { - task_completion_event taskCompletionEvent; - m_feedCompletionEvents.insert(make_pair(uri, taskCompletionEvent)); - - SyndicationClient^ client = ref new SyndicationClient(); - auto feedUri = ref new Uri(uri); - - create_task(client->RetrieveFeedAsync(feedUri)) - .then([this, uri](SyndicationFeed^ feed) -> FeedData^ - { - return GetFeedData(uri, feed); - }, concurrency::task_continuation_context::use_arbitrary()) - .then([this](FeedData^ fd) - { - m_feeds->Append(fd); - m_feedCompletionEvents[fd->Uri].set(fd); - - // Write to VS output window in debug mode only. Requires . - OutputDebugString(fd->Title->Data()); - OutputDebugString(L"\r\n"); - }) - .then([](task t) - { - // The last continuation serves as an error handler. - try - { - t.get(); - } - // SyndicationClient throws Platform::InvalidArgumentException - // if a URL contains illegal characters. - // We catch this exception for demonstration purposes only. - // In the current design of this app, an illegal - // character can only be introduced by a coding error - // and should not be caught. If we modify the app to allow - // the user to manually add a new url, then we need to catch - // the exception. - catch (Platform::InvalidArgumentException^ e) - { - // For example purposes we just output error to console. - // In a real world app that allowed the user to enter - // a url manually, you could prompt them to try again. - OutputDebugString(e->Message->Data()); - } - }); //end task chain -} - -FeedData^ FeedDataSource::GetFeedData(String^ feedUri, SyndicationFeed^ feed) -{ - FeedData^ feedData = ref new FeedData(); - - // Knowing this makes it easier to map completion_events - // when we resume from termination. - feedData->Uri = feedUri; - - // Get the title of the feed (not the individual posts). - feedData->Title = feed->Title->Text; - - if (feed->Subtitle->Text != nullptr) - { - feedData->Description = feed->Subtitle->Text; - } - // Use the date of the latest post as the last updated date. - feedData->PubDate = feed->Items->GetAt(0)->PublishedDate; - // Construct a FeedItem object for each post in the feed - // using a range-based for loop. Preferable to a - // C-style for loop, or std::for_each. - for (auto item : feed->Items) - { - auto feedItem = FeedItem::ParseSyndicationItem(item); - feedData->Items->Append(feedItem); - }; - - return feedData; -} - -IAsyncActionWithProgress^ FeedDataSource::GetFeedsAsync(Windows::Foundation::Collections::IObservableVector^ feeds) -{ - int first = 0; - int last = 20; - return create_async([feeds](progress_reporter reporter) - { - throw ref new InvalidArgumentException("Some exception thrown from GetFeedsAsync."); - }); -} \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.h b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.h deleted file mode 100644 index db1ac07b1..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.h +++ /dev/null @@ -1,79 +0,0 @@ -//********************************************************* -// -// Copyright (c) Microsoft. All rights reserved. -// http://code.msdn.microsoft.com/windowsapps/Windows-Store-Simple-Blog-953302e8 -//********************************************************* - -//feeddata.h - -#pragma once -#include "pch.h" -#include "FeedItem.h" - -namespace Windows -{ - namespace RSS - { - namespace Utils - { - // A FeedData object represents a feed that contains - // one or more FeedItems. - [Windows::UI::Xaml::Data::Bindable] - public ref class FeedData sealed - { - public: - FeedData(void) - { - m_items = ref new Platform::Collections::Vector(); - } - - // The public members must be Windows Runtime types so that - // the XAML controls can bind to them from a separate .winmd. - property Platform::String^ Title; - property Windows::Foundation::Collections::IVector^ Items - { - Windows::Foundation::Collections::IVector^ get() { return m_items; } - } - - property Platform::String^ Description; - property Windows::Foundation::DateTime PubDate; - property Platform::String^ Uri; - - private: - Platform::Collections::Vector^ m_items; - ~FeedData(void){} - }; - - // A FeedDataSource represents a collection of FeedData objects - // and provides the methods to download the source data from which - // FeedData and FeedItem objects are constructed. This class is - // instantiated at startup by this declaration in the - // ResourceDictionary in app.xaml: - [Windows::UI::Xaml::Data::Bindable] - public ref class FeedDataSource sealed - { - public: - FeedDataSource(); - property Windows::Foundation::Collections::IObservableVector^ Feeds - { - Windows::Foundation::Collections::IObservableVector^ get() - { - return this->m_feeds; - } - } - Windows::Foundation::IAsyncOperation^ GetFeedAsync(Platform::String^ uri); - - Windows::Foundation::IAsyncActionWithProgress^ GetFeedsAsync(Windows::Foundation::Collections::IObservableVector^ feeds); - - void RemoveFeed(Platform::String^ uri); - - private: - Platform::Collections::Vector^ m_feeds; - std::map> m_feedCompletionEvents; - - FeedData^ GetFeedData(Platform::String^ feedUri, Windows::Web::Syndication::SyndicationFeed^ feed); - void AddFeed(Platform::String^ uri); - }; - } - } -} \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.cpp b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.cpp deleted file mode 100644 index 4e094f6e2..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "pch.h" -#include "FeedItem.h" - -using namespace Windows::RSS::Utils; - -using namespace Concurrency; -using namespace Platform; -using namespace Platform::Collections; -using namespace Windows::Foundation; -using namespace Windows::Web::Syndication; - -FeedItem::FeedItem(void) -{ - m_relatedArticles = ref new Vector(); - m_relatedLinks = ref new Vector(); -} - -FeedItem::~FeedItem(void) -{ - -} - -FeedItem^ FeedItem::ParseSyndicationItem(Windows::Web::Syndication::SyndicationItem^ rssItem) -{ - FeedItem^ item = ref new FeedItem(); - item->GUID = rssItem->Id; - item->Title = rssItem->Title->Text; - item->Summary = rssItem->Summary->Text; - item->PubDate = rssItem->PublishedDate; - - //item->RootItem = rssItem; - - if (rssItem->Links->Size > 0) - { - item->Link = ref new Uri(rssItem->Links->GetAt(0)->NodeValue); - } - - auto xmlDocument = rssItem->GetXmlDocument(SyndicationFormat::Rss20); - //auto xml = xmlDocument->GetXml(); - for (auto node : xmlDocument->DocumentElement->ChildNodes) - { - auto name = node->NodeName; - auto value = node->NodeValue; - - if (name->Equals("asset")) - item->AssetUri = ref new Uri(value->ToString()); - else if (name->Equals("mainimage")) - item->MainImageUri = FeedItem::GetUrlFromAttribute(node->Attributes); - else if (name->Equals("thumbnail")) - item->ThumbnailUri = FeedItem::GetUrlFromAttribute(node->Attributes); - } - - return item; -} - -Uri^ FeedItem::GetUrlFromAttribute(Windows::Data::Xml::Dom::XmlNamedNodeMap^ attributes) -{ - String^ url = nullptr; - for (auto attr : attributes) - { - if (attr->NodeName->Equals("url")) - { - url = attr->NodeValue->ToString(); - break; - } - } - - return url != nullptr - ? ref new Uri(url) - : nullptr; -} - -Vector^ FeedItem::GetRelatedGuidsFromString(Platform::String^ guidString) -{ - Vector^ vector = ref new Vector(); - const wchar_t* delimiter = L","; - auto wcguid = guidString->Data(); - - return vector; -} \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.h b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.h deleted file mode 100644 index 78f529f97..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -namespace Windows -{ - namespace RSS - { - namespace Utils - { - // To be bindable, a class must be defined within a namespace - // and a bindable attribute needs to be applied. - // A FeedItem represents a single blog post. - [Windows::UI::Xaml::Data::Bindable] - public ref class FeedItem sealed - { - public: - FeedItem(void); - - property Platform::String^ GUID; - property Windows::Foundation::DateTime PubDate; - property Windows::Foundation::Uri^ Link; - - property Platform::String^ Title; - - property Platform::String^ Summary; - property Platform::String^ Description; - - // These are all URIs to the image associated to the article. - property Windows::Foundation::Uri^ AssetUri; - property Windows::Foundation::Uri^ ThumbnailUri; - property Windows::Foundation::Uri^ MainImageUri; - - //property Windows::Web::Syndication::SyndicationItem^ RootItem; - - property Windows::Foundation::Collections::IVector^ RelatedLinks - { - Windows::Foundation::Collections::IVector^ get() { return m_relatedLinks; } - } - - property Windows::Foundation::Collections::IVector^ RelatedArticles - { - Windows::Foundation::Collections::IVector^ get() { return m_relatedArticles; } - } - - static FeedItem^ ParseSyndicationItem(Windows::Web::Syndication::SyndicationItem^ feedItem); - private: - ~FeedItem(void); - Platform::Collections::Vector^ m_relatedArticles; - Platform::Collections::Vector^ m_relatedLinks; - - static Windows::Foundation::Uri^ GetUrlFromAttribute(Windows::Data::Xml::Dom::XmlNamedNodeMap^ attributes); - static Platform::Collections::Vector^ GetRelatedGuidsFromString(Platform::String^ guidString); - }; - } - } -} \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/Windows.RSS.Utils.vcxproj b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/Windows.RSS.Utils.vcxproj deleted file mode 100644 index bef88a8cb..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/Windows.RSS.Utils.vcxproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - - Windows.RSS.Utils - Debuggee - C++ - conniey - DynamicLibrary - true - true - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.cpp b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.cpp deleted file mode 100644 index bcb5590be..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.h b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.h deleted file mode 100644 index 10fe677c7..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#include -#include diff --git a/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/ReflectionTest.csproj b/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/ReflectionTest.csproj new file mode 100644 index 000000000..d7cb5bd20 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/ReflectionTest.csproj @@ -0,0 +1,8 @@ + + + Exe + netcoreapp2.1 + true + true + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/reflectiontest.csproj b/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/reflectiontest.csproj deleted file mode 100644 index 00f1da2d9..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/reflectiontest.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - - conniey - Debuggee - exe - - - - - - diff --git a/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/SimpleThrow.csproj b/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/SimpleThrow.csproj new file mode 100644 index 000000000..d7cb5bd20 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/SimpleThrow.csproj @@ -0,0 +1,8 @@ + + + Exe + netcoreapp2.1 + true + true + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/simplethrow.csproj b/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/simplethrow.csproj deleted file mode 100644 index a32461328..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/simplethrow.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - - conniey - Debuggee - exe - true - - - - - - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp.sln b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp.sln index a9a17c128..c0bc422cb 100644 --- a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp.sln +++ b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "symboltestapp", "symboltestapp\symboltestapp.csproj", "{EB291C94-C60E-4AE6-AEA0-B7EEF86F00AD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymbolTestApp", "SymbolTestApp\SymbolTestApp.csproj", "{EB291C94-C60E-4AE6-AEA0-B7EEF86F00AD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "symboltestdll", "symboltestdll\symboltestdll.csproj", "{53E66227-71BB-4300-9BE9-56CE570B28F8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymbolTestDll", "SymbolTestDll\SymbolTestDll.csproj", "{53E66227-71BB-4300-9BE9-56CE570B28F8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs index 107df730b..289d91ec0 100644 --- a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs +++ b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @@ -33,12 +33,12 @@ namespace SymbolTestApp static void Foo4(string dllPath) { #if FULL_CLR - byte[] dll = File.ReadAllBytes(Path.Combine(dllPath, @"symboltestdll.dll")); - byte[] pdb = File.ReadAllBytes(Path.Combine(dllPath, @"symboltestdll.pdb")); + byte[] dll = File.ReadAllBytes(Path.Combine(dllPath, @"SymbolTestDll.dll")); + byte[] pdb = File.ReadAllBytes(Path.Combine(dllPath, @"SymbolTestDll.pdb")); Assembly assembly = Assembly.Load(dll, pdb); #else - Stream dll = File.OpenRead(Path.Combine(dllPath, @"symboltestdll.dll")); - Stream pdb = File.OpenRead(Path.Combine(dllPath, @"symboltestdll.pdb")); + Stream dll = File.OpenRead(Path.Combine(dllPath, @"SymbolTestDll.dll")); + Stream pdb = File.OpenRead(Path.Combine(dllPath, @"SymbolTestDll.pdb")); Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(dll, pdb); #endif Type dllType = assembly.GetType("SymbolTestDll.TestClass"); diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.csproj b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.csproj new file mode 100644 index 000000000..bd62e0428 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.csproj @@ -0,0 +1,17 @@ + + + Exe + netcoreapp2.1 + true + true + portable + + + + + + + + + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.csproj b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.csproj deleted file mode 100644 index 15712dda7..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - mikem - Debuggee - $(DefineConstants);FULL_CLR - - - $(PdbKind) - $(OutputPath)/../$(PdbKind)/symboltestapp/ - - - - - - - - - - - - - diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.props b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.props deleted file mode 100644 index bb467031e..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp_prebuild.targets b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp_prebuild.targets deleted file mode 100644 index a3d8eb63d..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp_prebuild.targets +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/SymbolTestDll.csproj b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/SymbolTestDll.csproj new file mode 100644 index 000000000..cfa02ff5d --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/SymbolTestDll.csproj @@ -0,0 +1,9 @@ + + + Library + netcoreapp2.1 + true + true + portable + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.csproj b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.csproj deleted file mode 100644 index 3b282daf0..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - - mikem - Debuggee - $(PdbKind) - $(OutputPath)/../$(PdbKind)/symboltestdll/ - - - - - - - diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.props b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.props deleted file mode 100644 index 1b69f9ca7..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - Library - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserLibrary.csproj b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserLibrary.csproj new file mode 100644 index 000000000..3e77879a2 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserLibrary.csproj @@ -0,0 +1,8 @@ + + + Library + netcoreapp2.1 + true + true + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserTask.cs b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserTask.cs new file mode 100644 index 000000000..9560c131c --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserTask.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading.Tasks; + +namespace RandomTest +{ + public class RandomUserTask + { + public Task TryToDivideTask; + + private readonly int m_startingNumber; + + public RandomUserTask(int startingNumber) + { + m_startingNumber = startingNumber; + TryToDivideTask = Task.Factory.StartNew(() => + { + try + { + InnerException(); + } + catch (Exception e) + { + throw new FormatException("Bad format exception, outer.", e); + } + }); + } + + public void WaitTask() + { + TryToDivideTask.Wait(); + } + + public void InnerException() + { + throw new InvalidOperationException("This is an Inner InvalidOperationException."); + } + } +} diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException.sln b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException.sln index 5ac5625a8..414fb881f 100644 --- a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException.sln +++ b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TaskNestedException", "tasknestedexception\TaskNestedException.csproj", "{B9B58649-CEA6-4EF5-A25E-916AE8E77917}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TaskNestedException", "TaskNestedException\TaskNestedException.csproj", "{B9B58649-CEA6-4EF5-A25E-916AE8E77917}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomUserLibrary", "randomuserlibrary\RandomUserLibrary.csproj", "{FADE7D7D-107F-42B0-ABD5-36A101CD8675}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomUserLibrary", "RandomUserLibrary\RandomUserLibrary.csproj", "{FADE7D7D-107F-42B0-ABD5-36A101CD8675}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.cs b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.cs new file mode 100644 index 000000000..10b51afbb --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using RandomTest; + +namespace SosTests +{ + /// + /// This test creates an asynchronous task that results in an exception being thrown. + /// + class TaskException + { + static int Main() + { + RandomUserTask theTask = new RandomUserTask(100); + theTask.WaitTask(); + + return 0; + } + } +} \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.csproj b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.csproj new file mode 100644 index 000000000..8df8ab838 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.csproj @@ -0,0 +1,16 @@ + + + Exe + netcoreapp2.1 + true + true + + + + + + + + + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.csproj b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.csproj deleted file mode 100644 index 9733e1536..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - - conniey - Debuggee - - - - - - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.props b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.props deleted file mode 100644 index 1b69f9ca7..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - Library - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserTask.cs b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserTask.cs deleted file mode 100644 index 9560c131c..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserTask.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace RandomTest -{ - public class RandomUserTask - { - public Task TryToDivideTask; - - private readonly int m_startingNumber; - - public RandomUserTask(int startingNumber) - { - m_startingNumber = startingNumber; - TryToDivideTask = Task.Factory.StartNew(() => - { - try - { - InnerException(); - } - catch (Exception e) - { - throw new FormatException("Bad format exception, outer.", e); - } - }); - } - - public void WaitTask() - { - TryToDivideTask.Wait(); - } - - public void InnerException() - { - throw new InvalidOperationException("This is an Inner InvalidOperationException."); - } - } -} diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.cs b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.cs deleted file mode 100644 index 10b51afbb..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Threading.Tasks; -using RandomTest; - -namespace SosTests -{ - /// - /// This test creates an asynchronous task that results in an exception being thrown. - /// - class TaskException - { - static int Main() - { - RandomUserTask theTask = new RandomUserTask(100); - theTask.WaitTask(); - - return 0; - } - } -} \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.props b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.props deleted file mode 100644 index c6b064684..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/tasknestedexception.csproj b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/tasknestedexception.csproj deleted file mode 100644 index e25b82775..000000000 --- a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/tasknestedexception.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - - conniey - Debuggee - exe - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj b/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj index 56fd90931..1171ef9f5 100644 --- a/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj +++ b/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj @@ -3,6 +3,32 @@ netcoreapp2.0 ;1591;1701 + $(DefineConstants);CORE_CLR + + + + + + + + + Debugger.Tests.Config.txt + Always + + + Debugger.Tests.Config.txt + Always + + + Debugger.Tests.Common.txt + Always + + + + + + + diff --git a/src/SOS/SOS.UnitTests/SOS.cs b/src/SOS/SOS.UnitTests/SOS.cs index 45932f527..b4ae3f95d 100644 --- a/src/SOS/SOS.UnitTests/SOS.cs +++ b/src/SOS/SOS.UnitTests/SOS.cs @@ -1,4 +1,8 @@ -using Debugger.Tests; +// 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 Microsoft.Diagnostic.TestHelpers; using System; using System.Collections.Generic; using System.IO; @@ -17,17 +21,11 @@ public class SOS ITestOutputHelper Output { get; set; } - public static IEnumerable Configurations - { - get - { - return TestRunConfiguration.Instance.Configurations.Select(c => new[] { c }); - } - } + public static IEnumerable Configurations => TestRunConfiguration.Instance.Configurations.Select(c => new[] { c }); private void SkipIfArm(TestConfiguration config) { - if (config.BuildProjectRuntime == "linux-arm" || config.BuildProjectRuntime == "linux-arm64" || config.BuildProjectRuntime == "win-arm" || config.BuildProjectRuntime == "win7-arm64") + if (config.TargetArchitecture == "arm" || config.TargetArchitecture == "arm64") { throw new SkipTestException("SOS does not support ARM architectures"); } @@ -35,45 +33,38 @@ public class SOS private static bool IsCreateDumpConfig(TestConfiguration config) { - return config.DebuggeeDumpOutputRootDir != null; + return config.DebuggeeDumpOutputRootDir() != null; } private static bool IsOpenDumpConfig(TestConfiguration config) { - return config.DebuggeeDumpInputRootDir != null; + return config.DebuggeeDumpInputRootDir() != null; } private async Task CreateDump(TestConfiguration config, string testName, string debuggeeName, string debuggeeArguments) { - Directory.CreateDirectory(config.DebuggeeDumpOutputRootDir); + Directory.CreateDirectory(config.DebuggeeDumpOutputRootDir()); using (SOSRunner runner = await SOSRunner.StartDebugger(config, Output, testName, debuggeeName, debuggeeArguments, loadDump: false, generateDump: true)) { try { await runner.LoadSosExtension(); - await runner.ContinueExecution(); string command = null; switch (runner.Debugger) { case SOSRunner.NativeDebugger.Cdb: - if (config.TestProduct.Equals("desktop")) - { - // On desktop create triage dump - command = ".dump /o /mshuRp %DUMP_NAME%"; - } - else - { - // On .NET Core, create full dump - command = ".dump /o /ma %DUMP_NAME%"; - } + await runner.ContinueExecution(); + // On desktop create triage dump. On .NET Core, create full dump. + command = config.TestProduct.Equals("desktop") ? ".dump /o /mshuRp %DUMP_NAME%" : ".dump /o /ma %DUMP_NAME%"; break; case SOSRunner.NativeDebugger.Gdb: command = "generate-core-file %DUMP_NAME%"; break; case SOSRunner.NativeDebugger.Lldb: - command = "sos CreateDump %DUMP_NAME%"; + await runner.ContinueExecution(); + command = OS.Kind == OSKind.OSX ? "process save-core %DUMP_NAME%" : "sos CreateDump %DUMP_NAME%"; break; default: throw new Exception(runner.Debugger.ToString() + " does not support creating dumps"); @@ -105,7 +96,7 @@ public class SOS await runner.RunScript(scriptName); } - // Against a crash dump + // Against a crash dump. if (IsCreateDumpConfig(config)) { await CreateDump(config, testName, debuggeeName, debuggeeArguments); @@ -120,21 +111,13 @@ public class SOS } } -#if OSX_FAIL_WITH_BUG - [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")] -#else - [SkippableTheory, MemberData("Configurations")] -#endif + [SkippableTheory, MemberData(nameof(Configurations))] public async Task DivZero(TestConfiguration config) { - await RunTest(config, "DivZero", "SoS/DivZero.script"); + await RunTest(config, "DivZero", "DivZero.script"); } -#if OSX_FAIL_WITH_BUG - [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")] -#else - [SkippableTheory, MemberData("Configurations")] -#endif + [SkippableTheory, MemberData(nameof(Configurations))] public async Task GCTests(TestConfiguration config) { const string testName = "SOS.GCTests"; @@ -144,97 +127,54 @@ public class SOS SkipIfArm(config); using (SOSRunner runner = await SOSRunner.StartDebugger(config, Output, testName, debuggeeName, debuggeeArguments: null)) { - await runner.RunScript("SoS/GCTests.script"); + await runner.RunScript("GCTests.script"); } } -#if OSX_FAIL_WITH_BUG - [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")] -#else - [SkippableTheory, MemberData("Configurations")] -#endif + [SkippableTheory, MemberData(nameof(Configurations))] public async Task Overflow(TestConfiguration config) { - await RunTest(config, "Overflow", "SoS/Overflow.script"); + await RunTest(config, "Overflow", "Overflow.script"); } -#if OSX_FAIL_WITH_BUG - [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")] -#else - [SkippableTheory, MemberData("Configurations")] -#endif + [SkippableTheory, MemberData(nameof(Configurations))] public async Task Reflection(TestConfiguration config) { - await RunTest(config, "ReflectionTest", "SoS/Reflection.script"); + await RunTest(config, "ReflectionTest", "Reflection.script"); } -#if OSX_FAIL_WITH_BUG - [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")] -#else - [SkippableTheory, MemberData("Configurations")] -#endif + [SkippableTheory, MemberData(nameof(Configurations))] public async Task SimpleThrow(TestConfiguration config) { - await RunTest(config, "SimpleThrow", "SoS/SimpleThrow.script"); + await RunTest(config, "SimpleThrow", "SimpleThrow.script"); } -#if OSX_FAIL_WITH_BUG - [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")] -#else - [SkippableTheory, MemberData("Configurations")] -#endif + [SkippableTheory, MemberData(nameof(Configurations))] public async Task NestedExceptionTest(TestConfiguration config) { - await RunTest(config, "NestedExceptionTest", "SoS/NestedExceptionTest.script"); + await RunTest(config, "NestedExceptionTest", "NestedExceptionTest.script"); } -#if OSX_FAIL_WITH_BUG - [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")] -#else - [SkippableTheory, MemberData("Configurations")] -#endif + [SkippableTheory, MemberData(nameof(Configurations))] public async Task TaskNestedException(TestConfiguration config) { - await RunTest(config, "TaskNestedException", "SoS/TaskNestedException.script"); + await RunTest(config, "TaskNestedException", "TaskNestedException.script"); } - [SkippableTheory(Skip = "Test issue: Test build system can't yet create the debuggee"), MemberData("Configurations")] - public async Task WinRTAsync(TestConfiguration config) - { - await RunTest(config, "RSSFeed", "SoS/WinRTAsync.script"); - } - -#if OSX_FAIL_WITH_BUG - [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")] -#else - [SkippableTheory, MemberData("Configurations")] -#endif + [SkippableTheory, MemberData(nameof(Configurations))] public async Task StackTests(TestConfiguration config) { - if (config.BuildProjectRuntime == "linux-x64") - { - throw new SkipTestException("SOS StackTests are disabled on linux. Bug #584221"); - } - await RunTest(config, "SOS.StackTests", "NestedExceptionTest", null, "SoS/StackTests.script"); + await RunTest(config, "SOS.StackTests", "NestedExceptionTest", null, "StackTests.script"); } -#if OSX_FAIL_WITH_BUG - [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")] -#else - [SkippableTheory, MemberData("Configurations")] -#endif + [SkippableTheory, MemberData(nameof(Configurations))] public async Task StackAndOtherTests(TestConfiguration config) { SkipIfArm(config); - if (config.BuildProjectRuntime == "linux-x64") - { - throw new SkipTestException("SOS StackTests are disabled on linux. Bug #584221"); - } - foreach (TestConfiguration currentConfig in TestRunner.EnumeratePdbTypeConfigs(config)) { - // This debuggee needs the directory of the exes/dlls to load the symboltestdll assembly. - await RunTest(currentConfig, "SOS.StackAndOtherTests", "symboltestapp", "%DEBUG_ROOT%", "SoS/StackAndOtherTests.script"); + // This debuggee needs the directory of the exes/dlls to load the SymbolTestDll assembly. + await RunTest(currentConfig, "SOS.StackAndOtherTests", "SymbolTestApp", "%DEBUG_ROOT%", "StackAndOtherTests.script"); } } } diff --git a/src/SOS/SOS.UnitTests/SOSRunner.cs b/src/SOS/SOS.UnitTests/SOSRunner.cs index 5d3fe87a5..1bf66d986 100644 --- a/src/SOS/SOS.UnitTests/SOSRunner.cs +++ b/src/SOS/SOS.UnitTests/SOSRunner.cs @@ -1,4 +1,8 @@ -using Debugger.Tests.Build; +// 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 Microsoft.Diagnostic.TestHelpers; using System; using System.Collections.Generic; using System.IO; @@ -9,788 +13,821 @@ using System.Threading; using System.Threading.Tasks; using Xunit.Abstractions; -namespace Debugger.Tests +public class SOSRunner : IDisposable { - public class SOSRunner : IDisposable + readonly TestConfiguration _config; + readonly TestRunner.OutputHelper _outputHelper; + readonly Dictionary _variables; + readonly ScriptLogger _scriptLogger; + readonly ProcessRunner _processRunner; + readonly bool _isDump; + + string _lastCommandOutput; + string _previousCommandCapture; + + public enum NativeDebugger { - readonly TestConfiguration _config; - readonly TestRunner.OutputHelper _outputHelper; - readonly Dictionary _variables; - readonly ScriptLogger _scriptLogger; - readonly ProcessRunner _processRunner; - readonly bool _isDump; + Unknown, + Cdb, + Lldb, + Gdb + } - string _lastCommandOutput; - string _previousCommandCapture; + public const string HexValueRegEx = "[A-Fa-f0-9]+(`[A-Fa-f0-9]+)?"; + public const string DecValueRegEx = "[0-9]+(`[0-9]+)?"; - public enum NativeDebugger - { - Unknown, - Cdb, - Lldb, - Gdb - } + public NativeDebugger Debugger { get; private set; } - public const string HexValueRegEx = "[A-Fa-f0-9]+(`[A-Fa-f0-9]+)?"; - public const string DecValueRegEx = "[0-9]+(`[0-9]+)?"; + public string DebuggerToString + { + get { return Debugger.ToString().ToUpperInvariant(); } + } - public NativeDebugger Debugger { get; private set; } + private SOSRunner(NativeDebugger debugger, TestConfiguration config, TestRunner.OutputHelper outputHelper, + Dictionary variables, ScriptLogger scriptLogger, ProcessRunner processRunner, bool isDump) + { + Debugger = debugger; + _config = config; + _outputHelper = outputHelper; + _variables = variables; + _scriptLogger = scriptLogger; + _processRunner = processRunner; + _isDump = isDump; + } - public string DebuggerToString - { - get { return Debugger.ToString().ToUpperInvariant(); } - } + public static async Task StartDebugger(TestConfiguration config, ITestOutputHelper output, + string testName, string debuggeeName, string debuggeeArguments, bool loadDump = false, bool generateDump = false) + { + TestRunner.OutputHelper outputHelper = null; + SOSRunner sosRunner = null; - private static int s_setExecuteOnDebuggers = 0; + // Figure out which native debugger to use + NativeDebugger debugger = GetNativeDebuggerToUse(config, generateDump); - private SOSRunner(NativeDebugger debugger, TestConfiguration config, TestRunner.OutputHelper outputHelper, - Dictionary variables, ScriptLogger scriptLogger, ProcessRunner processRunner, bool isDump) + try { - Debugger = debugger; - _config = config; - _outputHelper = outputHelper; - _variables = variables; - _scriptLogger = scriptLogger; - _processRunner = processRunner; - _isDump = isDump; - } + // Setup the logging from the options in the config file + outputHelper = TestRunner.ConfigureLogging(config, output, testName); - public static async Task StartDebugger(TestConfiguration config, ITestOutputHelper output, - string testName, string debuggeeName, string debuggeeArguments, bool loadDump = false, bool generateDump = false) - { - TestRunner.OutputHelper outputHelper = null; - SOSRunner sosRunner = null; + // Restore and build the debuggee. + DebuggeeConfiguration debuggeeConfig = await DebuggeeCompiler.Execute(config, debuggeeName, outputHelper); - // Figure out which native debugger to use - NativeDebugger debugger = GetNativeDebuggerToUse(generateDump); + outputHelper.WriteLine("SOSRunner processing {0}", testName); + outputHelper.WriteLine("{"); - try - { - // Setup the logging from the options in the config file - outputHelper = TestRunner.ConfigureLogging(config, output, testName); + var variables = GenerateVariables(config, debuggeeConfig, generateDump); + var scriptLogger = new ScriptLogger(debugger, outputHelper.IndentedOutput); - // Restore and build the debuggee. The debuggee name is lower cased because the - // source directory name has been lowercased by the build system. - DebuggeeConfiguration debuggeeConfig = await DebuggeeCompiler.Execute(config, debuggeeName.ToLowerInvariant(), outputHelper); + // Get the full debuggee launch command line (includes the host if required) + var debuggeeCommandLine = new StringBuilder(); + if (!string.IsNullOrWhiteSpace(config.HostExe)) + { + debuggeeCommandLine.Append(config.HostExe); + debuggeeCommandLine.Append(" "); + if (!string.IsNullOrWhiteSpace(config.HostArgs)) + { + debuggeeCommandLine.Append(config.HostArgs); + debuggeeCommandLine.Append(" "); + } + } + debuggeeCommandLine.Append(debuggeeConfig.BinaryExePath); + if (!string.IsNullOrWhiteSpace(debuggeeArguments)) + { + debuggeeCommandLine.Append(" "); + debuggeeCommandLine.Append(debuggeeArguments); + } - outputHelper.WriteLine("SOSRunner processing {0}", testName); - outputHelper.WriteLine("{"); + // Get the native debugger path + string debuggerPath = GetNativeDebuggerPath(debugger, config); + if (string.IsNullOrWhiteSpace(debuggerPath) || !File.Exists(debuggerPath)) + { + throw new Exception("Native debugger path not set or does not exist: " + debuggerPath); + } - var variables = GenerateVariables(config, debuggeeConfig, generateDump); - var scriptLogger = new ScriptLogger(debugger, outputHelper.IndentedOutput); + // Get the debugger arguments and commands to run initially + List initialCommands = new List(); + string arguments = null; - // Get the full debuggee launch command line (includes the host if required) - var debuggeeCommandLine = new StringBuilder(); - if (!string.IsNullOrWhiteSpace(config.HostExe)) - { - debuggeeCommandLine.Append(config.HostExe); - debuggeeCommandLine.Append(" "); - if (!string.IsNullOrWhiteSpace(config.HostArgs)) + switch (debugger) + { + case NativeDebugger.Cdb: + initialCommands.Add(".sympath %DEBUG_ROOT%"); + initialCommands.Add(".extpath " + Path.GetDirectoryName(config.SOSPath())); + if (loadDump) { - debuggeeCommandLine.Append(config.HostArgs); - debuggeeCommandLine.Append(" "); + arguments = "-z %DUMP_NAME%"; } - } - debuggeeCommandLine.Append(debuggeeConfig.BinaryExePath); - if (!string.IsNullOrWhiteSpace(debuggeeArguments)) - { - debuggeeCommandLine.Append(" "); - debuggeeCommandLine.Append(debuggeeArguments); - } - - // Get the native debugger path - string debuggerPath = GetNativeDebuggerPath(debugger, config); - if (string.IsNullOrWhiteSpace(debuggerPath) || !File.Exists(debuggerPath)) - { - throw new Exception("Native debugger path not set or does not exist: " + debuggerPath); - } + else + { + arguments = "-Gsins " + debuggeeCommandLine; - // Get the debugger arguments and commands to run initially - List initialCommands = new List(); - string arguments = null; + // disable stopping on integer divide-by-zero and integer overflow exceptions + initialCommands.Add("sxd dz"); + initialCommands.Add("sxd iov"); + } + // Add the path to runtime so cdb/sos can find mscordbi. + string runtimeSymbolsPath = config.RuntimeSymbolsPath; + if (runtimeSymbolsPath != null) + { + initialCommands.Add(".sympath+ " + runtimeSymbolsPath); + } + // Turn off warnings that can happen in the middle of a command's output + initialCommands.Add(".outmask- 4"); + break; + case NativeDebugger.Lldb: + // Get the lldb python script file path necessary to capture the output of commands + // by printing a prompt after all the command output is printed. + string lldbHelperScript = config.LLDBHelperScript(); + if (string.IsNullOrWhiteSpace(lldbHelperScript) || !File.Exists(lldbHelperScript)) + { + throw new Exception("LLDB helper script path not set or does not exist: " + lldbHelperScript); + } + arguments = string.Format(@"--no-lldbinit -o ""settings set interpreter.prompt-on-quit false"" -o ""command script import {0}""", lldbHelperScript); - switch (debugger) - { - case NativeDebugger.Cdb: - if (loadDump) - { - arguments = "-z %DUMP_NAME%"; - initialCommands.Add(".sympath %DEBUG_ROOT%;srv*"); - } - else - { - arguments = "-Gsins " + debuggeeCommandLine; - initialCommands.Add(".sympath %DEBUG_ROOT%;srv*"); - // disable stopping on integer divide-by-zero and integer overflow exceptions - initialCommands.Add("sxd dz"); - initialCommands.Add("sxd iov"); - } - // Add the path to runtime so cdb/sos can find mscordbi. - string runtimeSymbolsPath = config.RuntimeSymbolsPath; - if (runtimeSymbolsPath != null) - { - initialCommands.Add(".sympath+ " + runtimeSymbolsPath); - } - // Turn off warnings that can happen in the middle of a command's output - initialCommands.Add(".outmask- 4"); - break; - case NativeDebugger.Lldb: - // Get the lldb python script file path necessary to capture the output of commands - // by printing a prompt after all the command output is printed. - string lldbHelperScript = config.LLDBHelperScript; - if (string.IsNullOrWhiteSpace(lldbHelperScript) || !File.Exists(lldbHelperScript)) - { - throw new Exception("LLDB helper script path not set or does not exist: " + lldbHelperScript); - } - arguments = string.Format(@"--no-lldbinit -o ""settings set interpreter.prompt-on-quit false"" -o ""command script import {0}""", lldbHelperScript); + initialCommands.Add("version"); - // Load the dump or launch the debuggee process - if (loadDump) - { - initialCommands.Add(string.Format(@"target create --core ""%DUMP_NAME%"" ""{0}""", config.HostExe)); - } - else + // Load the dump or launch the debuggee process + if (loadDump) + { + initialCommands.Add(string.Format(@"target create --core ""%DUMP_NAME%"" ""{0}""", config.HostExe)); + } + else + { + initialCommands.Add(string.Format(@"target create ""{0}""", config.HostExe)); + if (!string.IsNullOrWhiteSpace(config.HostArgs)) { - initialCommands.Add(string.Format(@"target create ""{0}""", config.HostExe)); - if (!string.IsNullOrWhiteSpace(config.HostArgs)) - { - initialCommands.Add(string.Format(@"settings append target.run-args ""{0}""", ReplaceVariables(variables, config.HostArgs))); - } - initialCommands.Add(string.Format(@"settings append target.run-args ""{0}""", debuggeeConfig.BinaryExePath)); - if (!string.IsNullOrWhiteSpace(debuggeeArguments)) - { - initialCommands.Add(string.Format(@"settings append target.run-args ""{0}""", ReplaceVariables(variables, debuggeeArguments))); - } - initialCommands.Add("process launch -s"); - initialCommands.Add("process handle -s false -n false -p true SIGFPE"); - initialCommands.Add("process handle -s false -n false -p true SIGSEGV"); - initialCommands.Add("process handle -s true -n true -p true SIGABRT"); + initialCommands.Add(string.Format(@"settings append target.run-args ""{0}""", ReplaceVariables(variables, config.HostArgs))); } - break; - case NativeDebugger.Gdb: - if (loadDump) + initialCommands.Add(string.Format(@"settings append target.run-args ""{0}""", debuggeeConfig.BinaryExePath)); + if (!string.IsNullOrWhiteSpace(debuggeeArguments)) { - throw new Exception("GDB not meant for loading core dumps"); + initialCommands.Add(string.Format(@"settings append target.run-args ""{0}""", ReplaceVariables(variables, debuggeeArguments))); } - arguments = "--args " + debuggeeCommandLine; - initialCommands.Add("handle SIGFPE nostop noprint"); - initialCommands.Add("handle SIGSEGV nostop noprint"); - initialCommands.Add("handle SIGABRT stop print"); - initialCommands.Add("start"); - break; - } - - if (OS.Kind != OSKind.Windows) - { - if (Interlocked.Exchange(ref s_setExecuteOnDebuggers, 1) == 0) + initialCommands.Add("process launch -s"); + initialCommands.Add("process handle -s false -n false -p true SIGFPE"); + initialCommands.Add("process handle -s false -n false -p true SIGSEGV"); + initialCommands.Add("process handle -s true -n true -p true SIGABRT"); + } + break; + case NativeDebugger.Gdb: + if (loadDump) { - // The binaries from the lldb and gdb packages don't have the execute bit set - // so set it now first time the SOS tests are run. - var sl = new ScriptLogger(debugger, outputHelper.IndentedOutput); - - // Will also set execute on gdb when the gdb package is ready. - ProcessRunner pr = new ProcessRunner( - "/bin/bash", - string.Format(@"-c ""/bin/chmod +x {0}/* {0}/../lib/*""", - Path.GetDirectoryName(config.LLDBPath))). - WithLog(sl); - - pr.Start(); - - await pr.WaitForExit(); + throw new Exception("GDB not meant for loading core dumps"); } - } + arguments = "--args " + debuggeeCommandLine; + initialCommands.Add("handle SIGFPE nostop noprint"); + initialCommands.Add("handle SIGSEGV nostop noprint"); + initialCommands.Add("handle SIGABRT stop print"); + initialCommands.Add("set startup-with-shell off"); + initialCommands.Add("run"); + break; + } - // Create the native debugger process running - ProcessRunner processRunner = new ProcessRunner(debuggerPath, ReplaceVariables(variables, arguments)). - WithLog(scriptLogger). - WithTimeout(TimeSpan.FromMinutes(5)); + // Create the native debugger process running + ProcessRunner processRunner = new ProcessRunner(debuggerPath, ReplaceVariables(variables, arguments)). + WithLog(scriptLogger). + WithTimeout(TimeSpan.FromMinutes(5)); - // Create the sos runner instance - sosRunner = new SOSRunner(debugger, config, outputHelper, variables, scriptLogger, processRunner, loadDump); + // Create the sos runner instance + sosRunner = new SOSRunner(debugger, config, outputHelper, variables, scriptLogger, processRunner, loadDump); - // Start the native debugger - processRunner.Start(); + // Start the native debugger + processRunner.Start(); - // Execute the initial debugger commands - await sosRunner.RunCommands(initialCommands); + // Execute the initial debugger commands + await sosRunner.RunCommands(initialCommands); - return sosRunner; - } - catch (Exception ex) - { - // Log the exception - outputHelper?.WriteLine(ex.ToString()); + return sosRunner; + } + catch (Exception ex) + { + // Log the exception + outputHelper?.WriteLine(ex.ToString()); - // The runner needs to kill the process and dispose of the file logger - sosRunner?.Dispose(); + // The runner needs to kill the process and dispose of the file logger + sosRunner?.Dispose(); - // The file logging output helper needs to be disposed to close the file - outputHelper?.Dispose(); - throw; - } + // The file logging output helper needs to be disposed to close the file + outputHelper?.Dispose(); + throw; } + } - public async Task RunScript(string scriptRelativePath) + public async Task RunScript(string scriptRelativePath) + { + string scriptFile = Path.Combine(_config.ScriptRootDir, scriptRelativePath); + if (!File.Exists(scriptFile)) { - string scriptFile = Path.Combine(_config.ScriptRootDir, scriptRelativePath); - if (!File.Exists(scriptFile)) - { - throw new Exception("Script file does not exist: " + scriptFile); - } - List enabledDefines = GetEnabledDefines(); - LogProcessingReproInfo(scriptFile, enabledDefines); - string[] scriptLines = File.ReadAllLines(scriptFile); - List activeDefines = new List(); - bool isActiveDefineRegionEnabled = IsActiveDefineRegionEnabled(activeDefines, enabledDefines); - int i = 0; - try + throw new Exception("Script file does not exist: " + scriptFile); + } + HashSet enabledDefines = GetEnabledDefines(); + LogProcessingReproInfo(scriptFile, enabledDefines); + string[] scriptLines = File.ReadAllLines(scriptFile); + Dictionary activeDefines = new Dictionary(); + bool isActiveDefineRegionEnabled = IsActiveDefineRegionEnabled(activeDefines, enabledDefines); + int i = 0; + try + { + for (; i < scriptLines.Length; i++) { - for (; i < scriptLines.Length; i++) + string line = scriptLines[i].TrimStart(); + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) { - string line = scriptLines[i].TrimStart(); - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) - { - continue; - } - else if (line.StartsWith("IFDEF:")) - { - string define = line.Substring("IFDEF:".Length); - activeDefines.Add(define); - isActiveDefineRegionEnabled = IsActiveDefineRegionEnabled(activeDefines, enabledDefines); - } - else if (line.StartsWith("ENDIF:")) - { - string define = line.Substring("ENDIF:".Length); - if (!activeDefines.Last().Equals(define)) - { - throw new Exception("Mismatched IFDEF/ENDIF. IFDEF: " + activeDefines.Last() + " ENDIF: " + define); - } - activeDefines.Remove(define); - isActiveDefineRegionEnabled = IsActiveDefineRegionEnabled(activeDefines, enabledDefines); - } - else if (!isActiveDefineRegionEnabled) - { - continue; - } - else if (line.StartsWith("LOADSOS")) - { - await LoadSosExtension(); - } - else if (line.StartsWith("CONTINUE")) - { - await ContinueExecution(); - } - else if (line.StartsWith("SOSCOMMAND:")) - { - string input = line.Substring("SOSCOMMAND:".Length).TrimStart(); - await RunSosCommand(input); - } - else if (line.StartsWith("COMMAND:")) - { - string input = line.Substring("COMMAND:".Length).TrimStart(); - await RunCommand(input); - } - else if (line.StartsWith("VERIFY:")) - { - string verifyLine = line.Substring("VERIFY:".Length); - VerifyOutput(verifyLine); - } - else + continue; + } + else if (line.StartsWith("IFDEF:")) + { + string define = line.Substring("IFDEF:".Length); + activeDefines.Add(define, true); + isActiveDefineRegionEnabled = IsActiveDefineRegionEnabled(activeDefines, enabledDefines); + } + else if (line.StartsWith("!IFDEF:")) + { + string define = line.Substring("!IFDEF:".Length); + activeDefines.Add(define, false); + isActiveDefineRegionEnabled = IsActiveDefineRegionEnabled(activeDefines, enabledDefines); + } + else if (line.StartsWith("ENDIF:")) + { + string define = line.Substring("ENDIF:".Length); + if (!activeDefines.Last().Key.Equals(define)) { - continue; + throw new Exception("Mismatched IFDEF/ENDIF. IFDEF: " + activeDefines.Last().Key + " ENDIF: " + define); } + activeDefines.Remove(define); + isActiveDefineRegionEnabled = IsActiveDefineRegionEnabled(activeDefines, enabledDefines); } - - if (activeDefines.Count != 0) + else if (!isActiveDefineRegionEnabled) { - throw new Exception("Error unbalanced IFDEFs. " + activeDefines[0] + " has no ENDIF."); + WriteLine(" SKIPPING: {0}", line); + continue; } - - await QuitDebugger(); - } - catch (Exception e) - { - WriteLine("SOSRunner error at " + scriptFile + ":" + (i + 1)); - WriteLine("Excerpt from " + scriptFile + ":"); - for (int j = Math.Max(0, i - 2); j < Math.Min(i + 3, scriptLines.Length); j++) + else if (line.StartsWith("LOADSOS")) + { + await LoadSosExtension(); + } + else if (line.StartsWith("CONTINUE")) + { + await ContinueExecution(); + } + else if (line.StartsWith("SOSCOMMAND:")) + { + string input = line.Substring("SOSCOMMAND:".Length).TrimStart(); + await RunSosCommand(input); + } + else if (line.StartsWith("COMMAND:")) + { + string input = line.Substring("COMMAND:".Length).TrimStart(); + await RunCommand(input); + } + else if (line.StartsWith("VERIFY:")) { - WriteLine((j + 1).ToString().PadLeft(5) + " " + scriptLines[j]); + string verifyLine = line.Substring("VERIFY:".Length); + VerifyOutput(verifyLine); + } + else + { + continue; } - WriteLine(e.ToString()); - throw; } - } - public async Task LoadSosExtension() - { - List commands = new List(); - switch (Debugger) + if (activeDefines.Count != 0) { - case NativeDebugger.Cdb: - commands.Add(".load " + _config.SOSPath); - commands.Add(".lines; .reload"); - break; - case NativeDebugger.Lldb: - commands.Add("plugin load " + _config.SOSPath); - if (_isDump) - { - // lldb doesn't load dump with the initial thread set to one with - // the exception. This SOS command looks for a thread with a managed - // exception and set the current thread to it. - commands.Add("clrthreads -managedexception"); - } - break; - default: - throw new Exception(DebuggerToString + " cannot load sos extension"); + throw new Exception("Error unbalanced IFDEFs. " + activeDefines.First().Key + " has no ENDIF."); } - await RunCommands(commands); - } - public async Task ContinueExecution() + await QuitDebugger(); + } + catch (Exception e) { - string command = null; - switch (Debugger) + WriteLine("SOSRunner error at " + scriptFile + ":" + (i + 1)); + WriteLine("Excerpt from " + scriptFile + ":"); + for (int j = Math.Max(0, i - 2); j < Math.Min(i + 3, scriptLines.Length); j++) { - case NativeDebugger.Cdb: - command = "g"; - break; - case NativeDebugger.Lldb: - command = "process continue"; - break; - case NativeDebugger.Gdb: - command = "continue"; - break; + WriteLine((j + 1).ToString().PadLeft(5) + " " + scriptLines[j]); } - await RunCommand(command); + WriteLine(e.ToString()); + throw; } + } - public async Task RunSosCommand(string command) + public async Task LoadSosExtension() + { + List commands = new List(); + switch (Debugger) { - switch (Debugger) - { - case NativeDebugger.Cdb: - command = "!" + command; - break; - case NativeDebugger.Lldb: - command = "sos " + command; - break; - default: - throw new Exception(DebuggerToString + " cannot execute sos command"); - } - return await RunCommand(command); + case NativeDebugger.Cdb: + commands.Add(".load " + _config.SOSPath()); + commands.Add(".lines; .reload"); + break; + case NativeDebugger.Lldb: + commands.Add("plugin load " + _config.SOSPath()); + if (_isDump) + { + // lldb doesn't load dump with the initial thread set to one with + // the exception. This SOS command looks for a thread with a managed + // exception and set the current thread to it. + commands.Add("clrthreads -managedexception"); + } + break; + case NativeDebugger.Gdb: + break; + default: + throw new Exception(DebuggerToString + " cannot load sos extension"); } + await RunCommands(commands); + } - public async Task RunCommands(IEnumerable commands) + public async Task ContinueExecution() + { + string command = null; + switch (Debugger) { - foreach (string command in commands) - { - await RunCommand(command); - } + case NativeDebugger.Cdb: + command = "g"; + break; + case NativeDebugger.Lldb: + command = "process continue"; + break; + case NativeDebugger.Gdb: + command = "continue"; + break; } + await RunCommand(command); + } - public async Task RunCommand(string command) + public async Task RunSosCommand(string command) + { + switch (Debugger) { - if (string.IsNullOrWhiteSpace(command)) - { - throw new Exception("Debugger command empty or null"); - } - return await HandleCommand(command); + case NativeDebugger.Cdb: + command = "!" + command; + break; + case NativeDebugger.Lldb: + command = "sos " + command; + break; + default: + throw new Exception(DebuggerToString + " cannot execute sos command"); } + return await RunCommand(command); + } - public async Task QuitDebugger() + public async Task RunCommands(IEnumerable commands) + { + foreach (string command in commands) { - if (await _scriptLogger.WaitForCommandPrompt()) - { - string command = null; - switch (Debugger) - { - case NativeDebugger.Cdb: - case NativeDebugger.Gdb: - command = "q"; - break; - case NativeDebugger.Lldb: - command = "quit"; - break; - } - _processRunner.StandardInputWriteLine(command); - if (await _scriptLogger.WaitForCommandPrompt()) - { - throw new Exception(DebuggerToString + " did not exit after quit command"); - } - } - await _processRunner.WaitForExit(); + await RunCommand(command); } + } - public void VerifyOutput(string verifyLine) + public async Task RunCommand(string command) + { + if (string.IsNullOrWhiteSpace(command)) { - string regex = ReplaceVariables(verifyLine.TrimStart()); + throw new Exception("Debugger command empty or null"); + } + return await HandleCommand(command); + } - if (_lastCommandOutput == null) + public async Task QuitDebugger() + { + if (await _scriptLogger.WaitForCommandPrompt()) + { + string command = null; + switch (Debugger) { - throw new Exception("VerifyOutput: no last command output or debugger exited unexpectedly: " + regex); + case NativeDebugger.Cdb: + case NativeDebugger.Gdb: + command = "q"; + break; + case NativeDebugger.Lldb: + command = "quit"; + break; } - if (!new Regex(regex, RegexOptions.Multiline).IsMatch(_lastCommandOutput)) + _processRunner.StandardInputWriteLine(command); + if (await _scriptLogger.WaitForCommandPrompt()) { - throw new Exception("Debugger output did not match the expression: " + regex); + throw new Exception(DebuggerToString + " did not exit after quit command"); } } + await _processRunner.WaitForExit(); + } + + public void VerifyOutput(string verifyLine) + { + string regex = ReplaceVariables(verifyLine.TrimStart()); - public static string GenerateDumpFileName(TestConfiguration config, string debuggeeName, bool generateDump) + if (_lastCommandOutput == null) { - string dumpRoot = generateDump ? config.DebuggeeDumpOutputRootDir : config.DebuggeeDumpInputRootDir; - if (dumpRoot != null) - { - return Path.Combine(dumpRoot, Path.GetFileNameWithoutExtension(debuggeeName) + ".dmp"); - } - return null; + throw new Exception("VerifyOutput: no last command output or debugger exited unexpectedly: " + regex); + } + if (!new Regex(regex, RegexOptions.Multiline).IsMatch(_lastCommandOutput)) + { + throw new Exception("Debugger output did not match the expression: " + regex); } + } - public void WriteLine(string message) + public static string GenerateDumpFileName(TestConfiguration config, string debuggeeName, bool generateDump) + { + string dumpRoot = generateDump ? config.DebuggeeDumpOutputRootDir() : config.DebuggeeDumpInputRootDir(); + if (dumpRoot != null) { - _outputHelper.IndentedOutput.WriteLine(message); + return Path.Combine(dumpRoot, Path.GetFileNameWithoutExtension(debuggeeName) + ".dmp"); } + return null; + } + + public void WriteLine(string message) + { + _outputHelper.IndentedOutput.WriteLine(message); + } + + public void WriteLine(string format, params object[] args) + { + _outputHelper.IndentedOutput.WriteLine(format, args); + } - public void WriteLine(string format, params object[] args) + public void Dispose() + { + if (!_scriptLogger.HasProcessExited) { - _outputHelper.IndentedOutput.WriteLine(format, args); + _processRunner.Kill(); } + _processRunner.WaitForExit().GetAwaiter().GetResult(); - public void Dispose() + _outputHelper.WriteLine("}"); + _outputHelper.Dispose(); + } + + private static NativeDebugger GetNativeDebuggerToUse(TestConfiguration config, bool generateDump) + { + switch (OS.Kind) { - if (!_scriptLogger.HasProcessExited) - { - _processRunner.Kill(); - } - _processRunner.WaitForExit().GetAwaiter().GetResult(); + case OSKind.Windows: + return NativeDebugger.Cdb; + + case OSKind.Linux: + case OSKind.OSX: + return generateDump ? (config.GenerateDumpWithLLDB() ? NativeDebugger.Lldb : NativeDebugger.Gdb) : NativeDebugger.Lldb; - _outputHelper.WriteLine("}"); - _outputHelper.Dispose(); + default: + throw new Exception(OS.Kind.ToString() + " not supported"); } + } - private static NativeDebugger GetNativeDebuggerToUse(bool generateDump) + private static string GetNativeDebuggerPath(NativeDebugger debugger, TestConfiguration config) + { + switch (debugger) { - switch (OS.Kind) - { - case OSKind.Windows: - return NativeDebugger.Cdb; + case NativeDebugger.Cdb: + return config.CDBPath(); - case OSKind.Linux: - case OSKind.FreeBSD: - return NativeDebugger.Lldb; + case NativeDebugger.Lldb: + return config.LLDBPath(); - case OSKind.OSX: - if (generateDump) - { - return NativeDebugger.Gdb; - } - else - { - return NativeDebugger.Lldb; - } + case NativeDebugger.Gdb: + return config.GDBPath(); + } - default: - throw new Exception(OS.Kind.ToString() + " not supported"); - } + return null; + } + + private async Task HandleCommand(string input) + { + if (!await _scriptLogger.WaitForCommandPrompt()) + { + throw new Exception(string.Format("{0} exited unexpectedly executing '{1}'", DebuggerToString, input)); } - private static string GetNativeDebuggerPath(NativeDebugger debugger, TestConfiguration config) + // The PREVPOUT convention is to write a command like this: + // COMMAND: Some stuff more stuff + // The PREVPOUT tag will be replaced by whatever the last + // tag matched in a previous command. See below for the POUT rules. + const string prevPoutTag = ""; + const string poutTag = ""; + if (input.Contains(prevPoutTag)) { - switch (debugger) + if (_previousCommandCapture == null) { - case NativeDebugger.Cdb: - return config.CDBPath; - - case NativeDebugger.Lldb: - return config.LLDBPath; - - case NativeDebugger.Gdb: - return config.GDBPath; + throw new Exception(prevPoutTag + " in a COMMAND input requires a previous command with a " + poutTag + " that matched something"); } - - return null; + input = input.Replace(prevPoutTag, _previousCommandCapture); } - private async Task HandleCommand(string input) + // The POUT convention is to write a commnd like this: + // COMMAND: Some stuff regex more stuff + // The regular expression identified by the POUT tags is applied to last command's output + // and then the 1st capture group is substituted into this command in place of the POUT tagged region + int firstPOUT = input.IndexOf(poutTag); + if (firstPOUT != -1) { - if (!await _scriptLogger.WaitForCommandPrompt()) + int secondPOUT = input.IndexOf(poutTag, firstPOUT + poutTag.Length); + if (secondPOUT == -1) { - throw new Exception(string.Format("{0} exited unexpectedly executing '{1}'", DebuggerToString, input)); + throw new Exception("SOS script is missing closing " + poutTag + " tag"); } - - // The PREVPOUT convention is to write a command like this: - // COMMAND: Some stuff more stuff - // The PREVPOUT tag will be replaced by whatever the last - // tag matched in a previous command. See below for the POUT rules. - const string prevPoutTag = ""; - const string poutTag = ""; - if (input.Contains(prevPoutTag)) + else { - if (_previousCommandCapture == null) + if (_lastCommandOutput == null) { - throw new Exception(prevPoutTag + " in a COMMAND input requires a previous command with a " + poutTag + " that matched something"); + throw new Exception(poutTag + " can't be used when there is no previous command output"); } - input = input.Replace(prevPoutTag, _previousCommandCapture); - } - - // The POUT convention is to write a commnd like this: - // COMMAND: Some stuff regex more stuff - // The regular expression identified by the POUT tags is applied to last command's output - // and then the 1st capture group is substituted into this command in place of the POUT tagged region - int firstPOUT = input.IndexOf(poutTag); - if (firstPOUT != -1) - { - int secondPOUT = input.IndexOf(poutTag, firstPOUT + poutTag.Length); - if (secondPOUT == -1) + int startRegexIndex = firstPOUT + poutTag.Length; + string poutRegex = input.Substring(startRegexIndex, secondPOUT - startRegexIndex); + Match m = Regex.Match(_lastCommandOutput, ReplaceVariables(poutRegex), RegexOptions.Multiline); + if (!m.Success) { - throw new Exception("SOS script is missing closing " + poutTag + " tag"); + throw new Exception("The previous command output did not match the " + poutTag + " expression: " + poutRegex); } - else + if (m.Groups.Count <= 1) { - if (_lastCommandOutput == null) - { - throw new Exception(poutTag + " can't be used when there is no previous command output"); - } - int startRegexIndex = firstPOUT + poutTag.Length; - string poutRegex = input.Substring(startRegexIndex, secondPOUT - startRegexIndex); - Match m = Regex.Match(_lastCommandOutput, ReplaceVariables(poutRegex), RegexOptions.Multiline); - if (!m.Success) - { - throw new Exception("The previous command output did not match the " + poutTag + " expression: " + poutRegex); - } - if (m.Groups.Count <= 1) - { - throw new Exception("The " + poutTag + " regular expression must have a capture group"); - } - string poutMatchResult = m.Groups[1].Value; - _previousCommandCapture = poutMatchResult; - input = input.Substring(0, firstPOUT) + poutMatchResult + input.Substring(secondPOUT + poutTag.Length); + throw new Exception("The " + poutTag + " regular expression must have a capture group"); } + string poutMatchResult = m.Groups[1].Value; + _previousCommandCapture = poutMatchResult; + input = input.Substring(0, firstPOUT) + poutMatchResult + input.Substring(secondPOUT + poutTag.Length); } - - _processRunner.StandardInputWriteLine(_scriptLogger.ProcessCommand(ReplaceVariables(input))); - _lastCommandOutput = await _scriptLogger.WaitForCommandOutput(); - return _lastCommandOutput; } + + _processRunner.StandardInputWriteLine(_scriptLogger.ProcessCommand(ReplaceVariables(input))); + _lastCommandOutput = await _scriptLogger.WaitForCommandOutput(); + return _lastCommandOutput; + } - private void LogProcessingReproInfo(string scriptFile, List enabledDefines) + private void LogProcessingReproInfo(string scriptFile, HashSet enabledDefines) + { + WriteLine(" STARTING SCRIPT: {0}", scriptFile); + foreach (KeyValuePair kv in _variables) { - WriteLine(" STARTING SCRIPT: {0}", scriptFile); - foreach (KeyValuePair kv in _variables) - { - WriteLine(" " + kv.Key + " => " + kv.Value); - } - foreach (string define in enabledDefines) - { - WriteLine(" " + define); - } + WriteLine(" " + kv.Key + " => " + kv.Value); } - - private List GetEnabledDefines() + foreach (string define in enabledDefines) { - List defines = new List(); - defines.Add(OS.Kind.ToString().ToUpperInvariant()); - defines.Add(DebuggerToString); - defines.Add(_config.TestProduct.ToUpperInvariant()); - if (_isDump) - { - defines.Add("DUMP"); - } - else - { - defines.Add("LIVE"); - } - if (_config.TargetArchitecture.Equals("x86")) - { - defines.Add("32BIT"); - } - else if (_config.TargetArchitecture.Equals("x64") || _config.TargetArchitecture.Equals("arm64")) - { - defines.Add("64BIT"); - } - else - { - throw new NotSupportedException("TargetArchitecture " + _config.TargetArchitecture + " not supported"); - } - return defines; + WriteLine(" " + define); } + } - private bool IsActiveDefineRegionEnabled(List activeDefines, List enabledDefines) + private HashSet GetEnabledDefines() + { + HashSet defines = new HashSet { - foreach (string activeDefine in activeDefines) - { - if (!enabledDefines.Contains(activeDefine)) - { - return false; - } - } - return true; + DebuggerToString, + OS.Kind.ToString().ToUpperInvariant(), + _config.TestProduct.ToUpperInvariant(), + _config.TargetArchitecture.ToLowerInvariant() + }; + if (_isDump) + { + defines.Add("DUMP"); } - - private static Dictionary GenerateVariables(TestConfiguration config, DebuggeeConfiguration debuggeeConfig, bool generateDump) + else { - Dictionary vars = new Dictionary(); - string debuggeeExe = debuggeeConfig.BinaryExePath; - string dumpFileName = GenerateDumpFileName(config, Path.GetFileNameWithoutExtension(debuggeeExe), generateDump); + defines.Add("LIVE"); + } + if (_config.TargetArchitecture.Equals("x86") || _config.TargetArchitecture.Equals("arm")) + { + defines.Add("32BIT"); + } + else if (_config.TargetArchitecture.Equals("x64") || _config.TargetArchitecture.Equals("arm64")) + { + defines.Add("64BIT"); + } + else + { + throw new NotSupportedException("TargetArchitecture " + _config.TargetArchitecture + " not supported"); + } + return defines; + } - vars.Add("%DEBUGGEE_EXE%", debuggeeExe); - if (dumpFileName != null) + private bool IsActiveDefineRegionEnabled(Dictionary activeDefines, HashSet enabledDefines) + { + foreach (KeyValuePair activeDefine in activeDefines) + { + // If Value is true, then it should be defined. If false, then it should not be defined. + if (enabledDefines.Contains(activeDefine.Key) != activeDefine.Value) { - vars.Add("%DUMP_NAME%", dumpFileName); + return false; } - vars.Add("%DEBUG_ROOT%", debuggeeConfig.BinaryDirPath); - vars.Add("%SOS_PATH%", config.SOSPath); + } + return true; + } - // Can be used in an RegEx expression - vars.Add("", debuggeeExe.Replace(@"\", @"\\")); - vars.Add("", debuggeeConfig.BinaryDirPath.Replace(@"\", @"\\")); - // On the desktop, the debuggee source is copied to this path but not built from this - // path so this regex won't work for the desktop. - vars.Add("", debuggeeConfig.SourcePath.Replace(@"\", @"\\")); - vars.Add("", HexValueRegEx); - vars.Add("", DecValueRegEx); + private static Dictionary GenerateVariables(TestConfiguration config, DebuggeeConfiguration debuggeeConfig, bool generateDump) + { + Dictionary vars = new Dictionary(); + string debuggeeExe = debuggeeConfig.BinaryExePath; + string dumpFileName = GenerateDumpFileName(config, Path.GetFileNameWithoutExtension(debuggeeExe), generateDump); - return vars; + vars.Add("%DEBUGGEE_EXE%", debuggeeExe); + if (dumpFileName != null) + { + vars.Add("%DUMP_NAME%", dumpFileName); } + vars.Add("%DEBUG_ROOT%", debuggeeConfig.BinaryDirPath); + vars.Add("%SOS_PATH%", config.SOSPath()); + + // Can be used in an RegEx expression + vars.Add("", debuggeeExe.Replace(@"\", @"\\")); + vars.Add("", debuggeeConfig.BinaryDirPath.Replace(@"\", @"\\")); + // On the desktop, the debuggee source is copied to this path but not built from this + // path so this regex won't work for the desktop. + vars.Add("", debuggeeConfig.SourcePath.Replace(@"\", @"\\")); + vars.Add("", HexValueRegEx); + vars.Add("", DecValueRegEx); + + return vars; + } + + private string ReplaceVariables(string input) + { + return ReplaceVariables(_variables, input); + } - private string ReplaceVariables(string input) + private static string ReplaceVariables(Dictionary vars, string input) + { + string output = input; + foreach (KeyValuePair kv in vars) { - return ReplaceVariables(_variables, input); + output = output.Replace(kv.Key, kv.Value); } + return output; + } + + class ScriptLogger : TestOutputProcessLogger + { + readonly NativeDebugger _debugger; + readonly List> _taskQueue; + readonly StringBuilder _lastCommandOutput; + TaskCompletionSource _taskSource; + + public bool HasProcessExited { get; private set; } - private static string ReplaceVariables(Dictionary vars, string input) + public ScriptLogger(NativeDebugger debugger, ITestOutputHelper output) + : base(output) { - string output = input; - foreach (KeyValuePair kv in vars) + lock (this) { - output = output.Replace(kv.Key, kv.Value); + _debugger = debugger; + _lastCommandOutput = new StringBuilder(); + _taskQueue = new List>(); + AddTask(); } - return output; } - class ScriptLogger : TestOutputProcessLogger + private void AddTask() { - readonly NativeDebugger _debugger; - readonly List> _taskQueue; - readonly StringBuilder _lastCommandOutput; - TaskCompletionSource _taskSource; - - public bool HasProcessExited { get; private set; } - - public ScriptLogger(NativeDebugger debugger, ITestOutputHelper output) - : base(output) - { - lock (this) - { - _debugger = debugger; - _lastCommandOutput = new StringBuilder(); - _taskQueue = new List>(); - AddTask(); - } - } + _taskSource = new TaskCompletionSource(); + _taskQueue.Add(_taskSource.Task); + } - private void AddTask() + public async Task WaitForCommandPrompt() + { + Task currentTask = null; + lock (this) { - _taskSource = new TaskCompletionSource(); - _taskQueue.Add(_taskSource.Task); + currentTask = _taskQueue[0]; + _taskQueue.RemoveAt(0); } + return (await currentTask) != null; + } - public async Task WaitForCommandPrompt() + public Task WaitForCommandOutput() + { + Task currentTask = null; + lock (this) { - Task currentTask = null; - lock (this) - { - currentTask = _taskQueue[0]; - _taskQueue.RemoveAt(0); - } - return (await currentTask) != null; + currentTask = _taskQueue[0]; } + return currentTask; + } - public Task WaitForCommandOutput() + public string ProcessCommand(string command) + { + if (_debugger == NativeDebugger.Lldb) { - Task currentTask = null; - lock (this) - { - currentTask = _taskQueue[0]; - } - return currentTask; + command = string.Format("runcommand {0}", command); } + return command; + } - public string ProcessCommand(string command) + public override void Write(ProcessRunner runner, string data, ProcessStream stream) + { + lock (this) { - if (_debugger == NativeDebugger.Lldb) + base.Write(runner, data, stream); + if (stream == ProcessStream.StandardOut) { - command = string.Format("runcommand {0}", command); - } - return command; - } + _lastCommandOutput.Append(data); + string lastCommandOutput = _lastCommandOutput.ToString(); - public override void Write(ProcessRunner runner, string data, ProcessStream stream) - { - lock (this) - { - base.Write(runner, data, stream); - if (stream == ProcessStream.StandardOut) + string prompt; + switch (_debugger) { - _lastCommandOutput.Append(data); - string lastCommandOutput = _lastCommandOutput.ToString(); - - string prompt; - switch (_debugger) - { - case NativeDebugger.Cdb: - // Some commands like DumpStack have ===> or -> in the output that looks - // like the cdb prompt. Using a regex here to better match the cdb prompt - // is way to slow. - if (lastCommandOutput.EndsWith("=> ") || lastCommandOutput.EndsWith("-> ")) - { - return; - } - prompt = "> "; - break; - case NativeDebugger.Lldb: - prompt = ""; - break; - case NativeDebugger.Gdb: - prompt = "(gdb) "; - break; - default: - throw new Exception("Debugger prompt not supported"); - } + case NativeDebugger.Cdb: + // Some commands like DumpStack have ===> or -> in the output that looks + // like the cdb prompt. Using a regex here to better match the cdb prompt + // is way to slow. + if (lastCommandOutput.EndsWith("=> ") || lastCommandOutput.EndsWith("-> ")) + { + return; + } + prompt = "> "; + break; + case NativeDebugger.Lldb: + prompt = ""; + break; + case NativeDebugger.Gdb: + prompt = "(gdb) "; + break; + default: + throw new Exception("Debugger prompt not supported"); + } - if (lastCommandOutput.EndsWith(prompt)) - { - FlushOutput(); - _taskSource.TrySetResult(lastCommandOutput); - _lastCommandOutput.Clear(); - AddTask(); - } + if (lastCommandOutput.EndsWith(prompt)) + { + FlushOutput(); + _taskSource.TrySetResult(lastCommandOutput); + _lastCommandOutput.Clear(); + AddTask(); } } } + } - public override void WriteLine(ProcessRunner runner, string data, ProcessStream stream) + public override void WriteLine(ProcessRunner runner, string data, ProcessStream stream) + { + lock (this) { - lock (this) + base.WriteLine(runner, data, stream); + if (stream == ProcessStream.StandardOut) { - base.WriteLine(runner, data, stream); - if (stream == ProcessStream.StandardOut) - { - _lastCommandOutput.AppendLine(data); - } + _lastCommandOutput.AppendLine(data); } } + } - public override void ProcessExited(ProcessRunner runner) + public override void ProcessExited(ProcessRunner runner) + { + lock (this) { - lock (this) - { - base.ProcessExited(runner); - FlushOutput(); - HasProcessExited = true; - _taskSource.TrySetResult(null); - } + base.ProcessExited(runner); + FlushOutput(); + HasProcessExited = true; + _taskSource.TrySetResult(null); } } } } + +public static class TestConfigurationExtensions +{ + public static string CDBPath(this TestConfiguration config) + { + return TestConfiguration.MakeCanonicalExePath(config.GetValue("CDBPath")); + } + + public static string LLDBHelperScript(this TestConfiguration config) + { + return TestConfiguration.MakeCanonicalPath(config.GetValue("LLDBHelperScript")); + } + + public static string LLDBPath(this TestConfiguration config) + { + string lldbPath = config.GetValue("LLDBPath"); + if(string.IsNullOrEmpty(lldbPath)) + { + lldbPath = Environment.GetEnvironmentVariable("LLDB_PATH"); + } + return TestConfiguration.MakeCanonicalPath(lldbPath); + } + + public static string GDBPath(this TestConfiguration config) + { + string gdbPath = config.GetValue("GDBPath"); + if(string.IsNullOrEmpty(gdbPath)) + { + gdbPath = Environment.GetEnvironmentVariable("GDB_PATH"); + } + return TestConfiguration.MakeCanonicalPath(gdbPath); + } + + public static string SOSPath(this TestConfiguration config) + { + return TestConfiguration.MakeCanonicalPath(config.GetValue("SOSPath")); + } + + public static bool GenerateDumpWithLLDB(this TestConfiguration config) + { + return config.GetValue("GenerateDumpWithLLDB")?.ToLowerInvariant() == "true"; + } + + public static string DebuggeeDumpInputRootDir(this TestConfiguration config) + { + return TestConfiguration.MakeCanonicalPath(config.GetValue("DebuggeeDumpInputRootDir")); + } + + public static string DebuggeeDumpOutputRootDir(this TestConfiguration config) + { + return TestConfiguration.MakeCanonicalPath(config.GetValue("DebuggeeDumpOutputRootDir")); + } +} diff --git a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script index a3d4c0dc4..a05bb3930 100644 --- a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script @@ -23,10 +23,10 @@ IFDEF:PROJECTK SOSCOMMAND:ClrStack -f VERIFY:.*OS Thread Id:\s+0x\s+.* VERIFY:\s+Child\s+SP\s+IP\s+Call Site\s+ -VERIFY:\s+\s+\s+symboltestapp\.(dll|exe)!SymbolTestApp\.Program\.Foo4\(System\.String\)\s+\+\s+\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 46\]\s* -VERIFY:\s+\s+\s+symboltestapp\.(dll|exe)!SymbolTestApp\.Program\.Foo2\(.*\)\s+\+\s+\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 29\]\s* -VERIFY:\s+\s+\s+symboltestapp\.(dll|exe)!SymbolTestApp\.Program\.Foo1\(.*\)\s+\+\s+\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 24\]\s* -VERIFY:\s+\s+\s+symboltestapp\.(dll|exe)!SymbolTestApp\.Program\.Main\(.*\)\s+\+\s+\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 19\]\s* +VERIFY:\s+\s+\s+SymbolTestApp\.(dll|exe)!SymbolTestApp\.Program\.Foo4\(System\.String\)\s+\+\s+\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 46\]\s* +VERIFY:\s+\s+\s+SymbolTestApp\.(dll|exe)!SymbolTestApp\.Program\.Foo2\(.*\)\s+\+\s+\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 29\]\s* +VERIFY:\s+\s+\s+SymbolTestApp\.(dll|exe)!SymbolTestApp\.Program\.Foo1\(.*\)\s+\+\s+\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 24\]\s* +VERIFY:\s+\s+\s+SymbolTestApp\.(dll|exe)!SymbolTestApp\.Program\.Main\(.*\)\s+\+\s+\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 19\]\s* ENDIF:PROJECTK # Verify that ClrStack all option works (locals/params) @@ -59,6 +59,9 @@ VERIFY:.*\s+\s+\s+SymbolTestApp\.Program\.Foo1\(.*\)\s+\[(?i:.*[ VERIFY:.*\s+\s+\s+SymbolTestApp\.Program\.Main\(.*\)\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 19\]\s* ENDIF:PROJECTK +# Disable ClrStack -i until coreclr issue #17259 +IFDEF:DESKTOP + # Verify that ClrStack with the ICorDebug options works SOSCOMMAND:ClrStack -i IFDEF:PROJECTK @@ -89,6 +92,8 @@ VERIFY:.*\s+\s+\s+\[DEFAULT\] Void SymbolTestApp\.Program\.Main\ VERIFY:.*\s+Stack walk complete.\s+ ENDIF:PROJECTK +ENDIF:DESKTOP + # Verify DumpStackObjects works IFDEF:PROJECTK SOSCOMMAND:DumpStackObjects diff --git a/src/SOS/SOS.UnitTests/Scripts/StackTests.script b/src/SOS/SOS.UnitTests/Scripts/StackTests.script index 1c22d9759..595dc9e9b 100644 --- a/src/SOS/SOS.UnitTests/Scripts/StackTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/StackTests.script @@ -61,6 +61,9 @@ VERIFY:\s+[r|e]ax=\s+[r|e]bx=\s+[r|e]cx=\s+ ENDIF:64BIT ENDIF:PROJECTK +# Disable ClrStack -i until coreclr issue #17259 +IFDEF:DESKTOP + # 5) Verifying that ClrStack with the ICorDebug options works SOSCOMMAND:ClrStack -i VERIFY:.*\s+Dumping managed stack and managed variables using ICorDebug.\s+ @@ -85,6 +88,8 @@ VERIFY:.*\s+\s+\s+\[DEFAULT\] Void NestedExceptionTest\.Program\ VERIFY:.*\s+Stack walk complete.\s+ ENDIF:PROJECTK +ENDIF:DESKTOP + # 7) Verify DumpStackObjects works IFDEF:PROJECTK SOSCOMMAND:DumpStackObjects @@ -105,7 +110,6 @@ VERIFY:.*\s+\s+\s+System\.InvalidOperationException\s+ VERIFY:.*\s+\s+\s+System\.String.* ENDIF:PROJECTK -IFDEF:DESKTOP # 9) Verify DumpStack works SOSCOMMAND:DumpStack VERIFY:.*OS Thread Id:\s+0x\s+.* @@ -122,4 +126,3 @@ VERIFY:.*\s+\s+\s+\(MethodDesc\s+\s+\+\s*0x\s+Ne SOSCOMMAND:EEStack VERIFY:.*Child(-SP|EBP)\s+RetAddr\s+Caller, Callee\s+ VERIFY:.*\s+\s+\s+\(MethodDesc\s+\s+\+\s*0x\s+NestedExceptionTest\.Program\.Main\(System\.String\[\]\)\),\s+calling.* -ENDIF:DESKTOP diff --git a/src/SOS/SOS.UnitTests/Scripts/WinRTAsync.script b/src/SOS/SOS.UnitTests/Scripts/WinRTAsync.script deleted file mode 100644 index 8b7bc68b3..000000000 --- a/src/SOS/SOS.UnitTests/Scripts/WinRTAsync.script +++ /dev/null @@ -1,56 +0,0 @@ -# WinRT Async debugging scenario -# 1) load the executable -# 2) Run the executable and wait for it to crash -# 3) Take a dump of the executable. -# 4) Open the dump and compare the output -# [EXPECTED OUTPUT (DESKTOP)] -# EXPECTED OUTPUT: -# Exception object: -# Exception type: System.AggregateException -# Message: One or more errors occurred. -# InnerException: System.ArgumentException, Use !PrintException to see more. -# StackTrace (generated): -# SP IP Function -# mscorlib_ni!System.Threading.Tasks.Task.Wait(Int32, System.Threading.CancellationToken)+0xd551d5 -# mscorlib_ni!System.Threading.Tasks.Task.Wait()+0x11 -# RSSFeed!RSSFeedTest.AsyncWinRTTest.Main()+0xce - -# [EXPECTED OUTPUT (PROJECTN)] -# Exception type: System.AggregateException -# Message: -# InnerException: System.ArgumentException, Use !PrintException (\d+) to see more. -# StackTrace (generated): -# IP Function -# RSSFeed_!$11_System::Threading::Tasks::Task.ThrowIfExceptional+0x50 -# RSSFeed_!$11_System::Threading::Tasks::Task.Wait+0xd9 -# RSSFeed_!$11_System::Threading::Tasks::Task.Wait+0x38 -# RSSFeed_!$0_RSSFeedTest::AsyncWinRTTest.Main+0xb8 -# RSSFeed_!$0_RSSFeedTest::AsyncWinRTTest.{ILT$Main}+0xd -# RSSFeed_!RHBinder__ShimExeMain+0x20 -# HResult: 80131500 - -LOADSOS - -IFDEF:LIVE -CONTINUE -ENDIF:LIVE - -# B) Verifying that PrintException gives us the right exception in the format above. -SOSCOMMAND:PrintException -VERIFY:Exception object:\s+\s+ -VERIFY:Exception type:\s+System\.AggregateException\s+ -VERIFY:Message:\s+\s+ -VERIFY:InnerException:\s+System\.ArgumentException, Use !PrintException to see more\.\s+ -VERIFY:StackTrace \(generated\):\s+ -VERIFY:\s+SP\s+\s+IP\s+\s+Function\s+ -VERIFY:\s+\s+.+System(::|\.)Threading(::|\.)Tasks(::|\.)Task\.Wait(\(\))?\+0x\s+ -VERIFY:\s+\s+.+RSSFeedTest(::|\.)AsyncWinRTTest\.Main(\(\))?\+0x\s+ - -# C) Verifying the inside ArgumentException from the WinRT component. -SOSCOMMAND:PrintException -lines InnerException:\s+System\.ArgumentException, Use !PrintException () to see more -VERIFY:Exception type:\s+System\.ArgumentException\s+ -VERIFY:Message:\s+\s+ -VERIFY:InnerException:\s+\s+ -VERIFY:StackTrace \(generated\):\s+ -VERIFY:\s+SP\s+IP\s+Function\s+ -VERIFY:\s+\s+.+RSSFeedTest(::|\.)TestRSSFeed(::|\+)d__7\.MoveNext(\(\))?\+0x\s*\[.+debuggees\\RSSFeed\\TestClasses\.cs @ 57\] diff --git a/src/SOS/Strike/CMakeLists.txt b/src/SOS/Strike/CMakeLists.txt index 1230d169b..de9a1b644 100644 --- a/src/SOS/Strike/CMakeLists.txt +++ b/src/SOS/Strike/CMakeLists.txt @@ -47,6 +47,7 @@ if(WIN32) set(SOS_SOURCES disasm.cpp + datatarget.cpp dllsext.cpp eeheap.cpp EventCallbacks.cpp diff --git a/src/SOS/Strike/datatarget.cpp b/src/SOS/Strike/datatarget.cpp index fe90f0e82..fdf63b066 100644 --- a/src/SOS/Strike/datatarget.cpp +++ b/src/SOS/Strike/datatarget.cpp @@ -74,7 +74,7 @@ DataTarget::GetMachineType( { return E_UNEXPECTED; } - return g_ExtControl->GetExecutingProcessorType(machine); + return g_ExtControl->GetExecutingProcessorType((PULONG)machine); } HRESULT STDMETHODCALLTYPE @@ -107,6 +107,14 @@ DataTarget::GetImageBase( { return E_FAIL; } +#ifndef FEATURE_PAL + // Remove the extension on Windows/dbgeng. + CHAR *lp = strrchr(lpstr, '.'); + if (lp != nullptr) + { + *lp = '\0'; + } +#endif return g_ExtSymbols->GetModuleByModuleName(lpstr, 0, NULL, base); } @@ -121,7 +129,7 @@ DataTarget::ReadVirtual( { return E_UNEXPECTED; } - return g_ExtData->ReadVirtual(address, (PVOID)buffer, request, done); + return g_ExtData->ReadVirtual(address, (PVOID)buffer, request, (PULONG)done); } HRESULT STDMETHODCALLTYPE @@ -135,7 +143,7 @@ DataTarget::WriteVirtual( { return E_UNEXPECTED; } - return g_ExtData->WriteVirtual(address, (PVOID)buffer, request, done); + return g_ExtData->WriteVirtual(address, (PVOID)buffer, request, (PULONG)done); } HRESULT STDMETHODCALLTYPE @@ -158,13 +166,13 @@ DataTarget::SetTLSValue( HRESULT STDMETHODCALLTYPE DataTarget::GetCurrentThreadID( - /* [out] */ ULONG32* threadID) + /* [out] */ ULONG32 *threadID) { if (g_ExtSystem == NULL) { return E_UNEXPECTED; } - return g_ExtSystem->GetCurrentThreadSystemId(threadID); + return g_ExtSystem->GetCurrentThreadSystemId((PULONG)threadID); } HRESULT STDMETHODCALLTYPE @@ -174,11 +182,52 @@ DataTarget::GetThreadContext( /* [in] */ ULONG32 contextSize, /* [out, size_is(contextSize)] */ PBYTE context) { - if (g_ExtSystem == NULL) +#ifdef FEATURE_PAL + if (g_ExtServices == NULL) { return E_UNEXPECTED; } - return g_ExtSystem->GetThreadContextById(threadID, contextFlags, contextSize, context); + return g_ExtServices->GetThreadContextById(threadID, contextFlags, contextSize, context); +#else + if (g_ExtSystem == NULL || g_ExtAdvanced3 == NULL) + { + return E_UNEXPECTED; + } + ULONG ulThreadIDOrig; + ULONG ulThreadIDRequested; + HRESULT hr; + + hr = g_ExtSystem->GetCurrentThreadId(&ulThreadIDOrig); + if (FAILED(hr)) + { + return hr; + } + + hr = g_ExtSystem->GetThreadIdBySystemId(threadID, &ulThreadIDRequested); + if (FAILED(hr)) + { + return hr; + } + + hr = g_ExtSystem->SetCurrentThreadId(ulThreadIDRequested); + if (FAILED(hr)) + { + return hr; + } + + // Prepare context structure + ZeroMemory(context, contextSize); + ((CONTEXT*) context)->ContextFlags = contextFlags; + + // Ok, do it! + hr = g_ExtAdvanced3->GetThreadContext((LPVOID) context, contextSize); + + // This is cleanup; failure here doesn't mean GetThreadContext should fail + // (that's determined by hr). + g_ExtSystem->SetCurrentThreadId(ulThreadIDOrig); + + return hr; +#endif } HRESULT STDMETHODCALLTYPE @@ -207,9 +256,13 @@ DataTarget::VirtualUnwind( /* [in] */ ULONG32 contextSize, /* [in, out, size_is(contextSize)] */ PBYTE context) { +#ifdef FEATURE_PAL if (g_ExtServices == NULL) { return E_UNEXPECTED; } return g_ExtServices->VirtualUnwind(threadId, contextSize, context); +#else + return E_NOTIMPL; +#endif } diff --git a/src/SOS/Strike/exts.cpp b/src/SOS/Strike/exts.cpp index 566ba35f1..15cb26ab0 100644 --- a/src/SOS/Strike/exts.cpp +++ b/src/SOS/Strike/exts.cpp @@ -17,7 +17,6 @@ // // globals // -EXT_API_VERSION ApiVersion = { (VER_PRODUCTVERSION_W >> 8), (VER_PRODUCTVERSION_W & 0xff), EXT_API_VERSION_NUMBER64, 0 }; WINDBG_EXTENSION_APIS ExtensionApis; ULONG PageSize; @@ -201,7 +200,7 @@ DebugExtensionInitialize(PULONG Version, PULONG Flags) PDEBUG_CONTROL DebugControl; HRESULT Hr; - *Version = DEBUG_EXTENSION_VERSION(1, 0); + *Version = DEBUG_EXTENSION_VERSION(2, 0); *Flags = 0; if (g_Initialized) diff --git a/src/SOS/Strike/util.cpp b/src/SOS/Strike/util.cpp index 416127ec1..d03ec2e6e 100644 --- a/src/SOS/Strike/util.cpp +++ b/src/SOS/Strike/util.cpp @@ -25,10 +25,7 @@ #include #include #include "debugshim.h" - -#ifdef FEATURE_PAL #include "datatarget.h" -#endif // FEATURE_PAL #include "gcinfo.h" #ifndef STRESS_LOG @@ -62,6 +59,8 @@ ResolveSequencePointDelegate SymbolReader::resolveSequencePointDelegate; GetLocalVariableName SymbolReader::getLocalVariableNameDelegate; GetLineByILOffsetDelegate SymbolReader::getLineByILOffsetDelegate; +HRESULT GetCoreClrDirectory(std::string& coreClrDirectory); + const char * const CorElementTypeName[ELEMENT_TYPE_MAX]= { #define TYPEINFO(e,ns,c,s,g,ia,ip,if,im,gv) c, @@ -4188,26 +4187,28 @@ void ResetGlobals(void) // HRESULT LoadClrDebugDll(void) { - HRESULT hr = S_OK; -#ifdef FEATURE_PAL static IXCLRDataProcess* s_clrDataProcess = NULL; + HRESULT hr = S_OK; + if (s_clrDataProcess == NULL) { +#ifdef FEATURE_PAL int err = PAL_InitializeDLL(); - if(err != 0) + if (err != 0) { return CORDBG_E_UNSUPPORTED; } - LPCSTR coreclrDirectory = g_ExtServices->GetCoreClrDirectory(); - if (coreclrDirectory == NULL) +#endif // FEATURE_PAL + std::string dacModulePath; + hr = GetCoreClrDirectory(dacModulePath); + if (FAILED(hr)) { - return E_FAIL; + return hr; } - ArrayHolder dacModulePath = new char[MAX_LONGPATH + 1]; - strcpy_s(dacModulePath, MAX_LONGPATH, coreclrDirectory); - strcat_s(dacModulePath, MAX_LONGPATH, MAKEDLLNAME_A("mscordaccore")); + dacModulePath.append(DIRECTORY_SEPARATOR_STR_A); + dacModulePath.append(MAKEDLLNAME_A("mscordaccore")); - HMODULE hdac = LoadLibraryA(dacModulePath); + HMODULE hdac = LoadLibraryA(dacModulePath.c_str()); if (hdac == NULL) { return CORDBG_E_MISSING_DEBUGGER_EXPORTS; @@ -4233,17 +4234,7 @@ HRESULT LoadClrDebugDll(void) g_clrData = s_clrDataProcess; g_clrData->AddRef(); g_clrData->Flush(); -#else - WDBGEXTS_CLR_DATA_INTERFACE Query; - - Query.Iid = &__uuidof(IXCLRDataProcess); - if (!Ioctl(IG_GET_CLR_DATA_INTERFACE, &Query, sizeof(Query))) - { - return E_FAIL; - } - g_clrData = (IXCLRDataProcess*)Query.Iface; -#endif hr = g_clrData->QueryInterface(__uuidof(ISOSDacInterface), (void**)&g_sos); if (FAILED(hr)) { @@ -4676,16 +4667,15 @@ public: BYTE * context) { #ifdef FEATURE_PAL - if (g_ExtSystem == NULL) + if (g_ExtServices == NULL) { return E_UNEXPECTED; } - return g_ExtSystem->GetThreadContextById(dwThreadOSID, contextFlags, contextSize, context); + return g_ExtServices->GetThreadContextById(dwThreadOSID, contextFlags, contextSize, context); #else ULONG ulThreadIDOrig; ULONG ulThreadIDRequested; HRESULT hr; - HRESULT hrRet; hr = g_ExtSystem->GetCurrentThreadId(&ulThreadIDOrig); if (FAILED(hr)) @@ -4710,13 +4700,13 @@ public: ((CONTEXT*) context)->ContextFlags = contextFlags; // Ok, do it! - hrRet = g_ExtAdvanced3->GetThreadContext((LPVOID) context, contextSize); + hr = g_ExtAdvanced3->GetThreadContext((LPVOID) context, contextSize); // This is cleanup; failure here doesn't mean GetThreadContext should fail - // (that's determined by hrRet). + // (that's determined by hr). g_ExtSystem->SetCurrentThreadId(ulThreadIDOrig); - return hrRet; + return hr; #endif // FEATURE_PAL } @@ -6421,49 +6411,59 @@ bool GetEntrypointExecutableAbsolutePath(std::string& entrypointExecutable) #endif // FEATURE_PAL +HRESULT GetCoreClrDirectory(std::string& coreClrDirectory) +{ +#ifdef FEATURE_PAL + LPCSTR directory = g_ExtServices->GetCoreClrDirectory(); + if (directory == NULL) + { + ExtErr("Error: Runtime module (%s) not loaded yet", MAKEDLLNAME_A("coreclr")); + return E_FAIL; + } + if (!GetAbsolutePath(directory, coreClrDirectory)) + { + ExtErr("Error: Failed to get coreclr absolute path\n"); + return E_FAIL; + } +#else + ULONG index; + HRESULT Status = g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_MODULE_NAME_A, 0, &index, NULL); + if (FAILED(Status)) + { + ExtErr("Error: Can't find coreclr module\n"); + return Status; + } + ArrayHolder szModuleName = new char[MAX_LONGPATH + 1]; + Status = g_ExtSymbols->GetModuleNames(index, 0, szModuleName, MAX_LONGPATH, NULL, NULL, 0, NULL, NULL, 0, NULL); + if (FAILED(Status)) + { + ExtErr("Error: Failed to get coreclr module name\n"); + return Status; + } + coreClrDirectory = szModuleName; + + // Parse off the module name to get just the path + size_t lastSlash = coreClrDirectory.rfind(DIRECTORY_SEPARATOR_CHAR_A); + if (lastSlash == std::string::npos) + { + ExtErr("Error: Failed to parse coreclr module name\n"); + return E_FAIL; + } + coreClrDirectory.assign(coreClrDirectory, 0, lastSlash); +#endif + return S_OK; +} + HRESULT GetHostRuntime(std::string& coreClrPath, std::string& hostRuntimeDirectory) { // If the hosting runtime isn't already set, use the runtime we are debugging if (g_hostRuntimeDirectory == nullptr) { -#ifdef FEATURE_PAL - LPCSTR coreClrDirectory = g_ExtServices->GetCoreClrDirectory(); - if (coreClrDirectory == NULL) - { - ExtErr("Error: Runtime module (%s) not loaded yet", MAKEDLLNAME_A("coreclr")); - return E_FAIL; - } - if (!GetAbsolutePath(coreClrDirectory, hostRuntimeDirectory)) - { - ExtErr("Error: Failed to get coreclr absolute path\n"); - return E_FAIL; - } -#else - ULONG index; - HRESULT Status = g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_MODULE_NAME_A, 0, &index, NULL); - if (FAILED(Status)) - { - ExtErr("Error: Can't find coreclr module\n"); - return Status; - } - ArrayHolder szModuleName = new char[MAX_LONGPATH + 1]; - Status = g_ExtSymbols->GetModuleNames(index, 0, szModuleName, MAX_LONGPATH, NULL, NULL, 0, NULL, NULL, 0, NULL); - if (FAILED(Status)) - { - ExtErr("Error: Failed to get coreclr module name\n"); - return Status; - } - coreClrPath = szModuleName; - - // Parse off the module name to get just the path - size_t lastSlash = coreClrPath.rfind(DIRECTORY_SEPARATOR_CHAR_A); - if (lastSlash == std::string::npos) + HRESULT hr = GetCoreClrDirectory(hostRuntimeDirectory); + if (FAILED(hr)) { - ExtErr("Error: Failed to parse coreclr module name\n"); - return E_FAIL; + return hr; } - hostRuntimeDirectory.assign(coreClrPath, 0, lastSlash); -#endif g_hostRuntimeDirectory = _strdup(hostRuntimeDirectory.c_str()); } hostRuntimeDirectory.assign(g_hostRuntimeDirectory); diff --git a/src/SOS/Strike/xplat/dbgeng.h b/src/SOS/Strike/xplat/dbgeng.h index 5dcf9572b..aa71451b4 100644 --- a/src/SOS/Strike/xplat/dbgeng.h +++ b/src/SOS/Strike/xplat/dbgeng.h @@ -396,16 +396,6 @@ public: return m_lldbservices->GetThreadIdBySystemId(sysId, threadId); } - HRESULT - GetThreadContextById( - /* in */ ULONG32 threadID, - /* in */ ULONG32 contextFlags, - /* in */ ULONG32 contextSize, - /* out */ PBYTE context) - { - return m_lldbservices->GetThreadContextById(threadID, contextFlags, contextSize, context); - } - //---------------------------------------------------------------------------- // IDebugRegisters //---------------------------------------------------------------------------- diff --git a/src/SOS/TestDebuggee/Test.cs b/src/SOS/TestDebuggee/Test.cs deleted file mode 100644 index bd3829739..000000000 --- a/src/SOS/TestDebuggee/Test.cs +++ /dev/null @@ -1,98 +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; - -class Test -{ - static void LikelyInlined() - { - Console.WriteLine("I would like to be inlined"); - } - - static void UnlikelyInlined() - { - Console.Write("I"); - Console.Write(" "); - Console.Write("w"); - Console.Write("o"); - Console.Write("u"); - Console.Write("l"); - Console.Write("d"); - Console.Write(" "); - Console.Write("n"); - Console.Write("o"); - Console.Write("t"); - Console.Write(" "); - Console.Write("l"); - Console.Write("i"); - Console.Write("k"); - Console.Write("e"); - Console.Write(" "); - Console.Write("t"); - Console.Write("o"); - Console.Write(" "); - Console.Write("b"); - Console.Write("e"); - Console.Write(" "); - Console.Write("i"); - Console.Write("n"); - Console.Write("l"); - Console.Write("i"); - Console.Write("n"); - Console.Write("e"); - Console.Write("d"); - Console.Write("\n"); - } - - static void ClrU() - { - Console.WriteLine("test dumpclass"); - } - - static void DumpClass() - { - Console.WriteLine("test dumpclass"); - } - - static void DumpIL() - { - Console.WriteLine("test dumpil"); - } - - static void DumpMD() - { - Console.WriteLine("test dumpmd"); - } - - static void DumpModule() - { - Console.WriteLine("test dumpmodule"); - } - - static void DumpObject() - { - Console.WriteLine("test dumpobject"); - } - - static void DumpStackObjects() - { - Console.WriteLine("test dso"); - } - - static void Name2EE() - { - Console.WriteLine("test name2ee"); - } - - - static int Main() - { - DumpIL(); - LikelyInlined(); - UnlikelyInlined(); - - return 0; - } -} diff --git a/src/SOS/TestDebuggee/TestDebuggee.csproj b/src/SOS/TestDebuggee/TestDebuggee.csproj deleted file mode 100644 index cb3144fc2..000000000 --- a/src/SOS/TestDebuggee/TestDebuggee.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - Exe - netcoreapp2.0 - true - ;1591;1701 - - diff --git a/src/SOS/lldbplugin.tests/README.md b/src/SOS/lldbplugin.tests/README.md new file mode 100644 index 000000000..a4ad7dce9 --- /dev/null +++ b/src/SOS/lldbplugin.tests/README.md @@ -0,0 +1,44 @@ +Testing libsosplugin +===================================== + +**Running tests** + +The test.sh and testsos.sh scripts launches these tests and makes the following a lot easier. + + +Make sure that python's lldb module is accessible. To run the tests manually, use the following command: + +`python2 test_libsosplugin.py --lldb --host --plugin --logfiledir --assembly ` + +- `lldb` is a path to `lldb` to run +- `host` is a path to .NET Core host like `corerun` or `dotnet` +- `plugin` is the path to the lldb sos plugin +- `logfiledir` is the path to put the log files +- `assembly` is a compiled test assembly (e.g. TestDebuggee.dll) +- `timeout` is a deadline for a single test (in seconds) +- `regex` is a regular expression matching tests to run +- `repeat` is a number of passes for each test + +Log files for both failed and passed tests are `*.log` and `*.log.2` for standard output and error correspondingly. + + +**Writing tests** +Tests start with the `TestSosCommands` class defined in `test_libsosplugin.py`. To add a test to the suite, start with implementing a new method inside this class whose name begins with `test_`. Most new commands will require only one line of code in this method: `self.do_test("scenarioname")`. This command will launch a new `lldb` instance, which in turn will call the `runScenario` method from `scenarioname` module. `scenarioname` is the name of the python module that will be running the scenario inside `lldb` (found in `tests` folder alongside with `test_libsosplugin.py` and named `scenarioname.py`). +An example of a scenario looks like this: + + import lldb + def runScenario(assemblyName, debugger, target): + process = target.GetProcess() + + # do some work + + process.Continue() + return True + + `runScenario` method does all the work related to running the scenario: setting breakpoints, running SOS commands and examining their output. It should return a boolean value indicating a success or a failure. +***Note:*** `testutils.py` defines some useful commands that can be reused in many scenarios. + + +**Useful links** +[Python scripting in LLDB](http://lldb.llvm.org/python-reference.html) +[Python unittest framework](https://docs.python.org/2.7/library/unittest.html) diff --git a/src/SOS/lldbplugin.tests/TestDebuggee/Test.cs b/src/SOS/lldbplugin.tests/TestDebuggee/Test.cs new file mode 100644 index 000000000..bd3829739 --- /dev/null +++ b/src/SOS/lldbplugin.tests/TestDebuggee/Test.cs @@ -0,0 +1,98 @@ +// 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; + +class Test +{ + static void LikelyInlined() + { + Console.WriteLine("I would like to be inlined"); + } + + static void UnlikelyInlined() + { + Console.Write("I"); + Console.Write(" "); + Console.Write("w"); + Console.Write("o"); + Console.Write("u"); + Console.Write("l"); + Console.Write("d"); + Console.Write(" "); + Console.Write("n"); + Console.Write("o"); + Console.Write("t"); + Console.Write(" "); + Console.Write("l"); + Console.Write("i"); + Console.Write("k"); + Console.Write("e"); + Console.Write(" "); + Console.Write("t"); + Console.Write("o"); + Console.Write(" "); + Console.Write("b"); + Console.Write("e"); + Console.Write(" "); + Console.Write("i"); + Console.Write("n"); + Console.Write("l"); + Console.Write("i"); + Console.Write("n"); + Console.Write("e"); + Console.Write("d"); + Console.Write("\n"); + } + + static void ClrU() + { + Console.WriteLine("test dumpclass"); + } + + static void DumpClass() + { + Console.WriteLine("test dumpclass"); + } + + static void DumpIL() + { + Console.WriteLine("test dumpil"); + } + + static void DumpMD() + { + Console.WriteLine("test dumpmd"); + } + + static void DumpModule() + { + Console.WriteLine("test dumpmodule"); + } + + static void DumpObject() + { + Console.WriteLine("test dumpobject"); + } + + static void DumpStackObjects() + { + Console.WriteLine("test dso"); + } + + static void Name2EE() + { + Console.WriteLine("test name2ee"); + } + + + static int Main() + { + DumpIL(); + LikelyInlined(); + UnlikelyInlined(); + + return 0; + } +} diff --git a/src/SOS/lldbplugin.tests/TestDebuggee/TestDebuggee.csproj b/src/SOS/lldbplugin.tests/TestDebuggee/TestDebuggee.csproj new file mode 100644 index 000000000..cb3144fc2 --- /dev/null +++ b/src/SOS/lldbplugin.tests/TestDebuggee/TestDebuggee.csproj @@ -0,0 +1,9 @@ + + + + Exe + netcoreapp2.0 + true + ;1591;1701 + + diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_clear.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_clear.py new file mode 100644 index 000000000..c80d130fc --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_bpmd_clear.py @@ -0,0 +1,66 @@ +# 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. + +import lldb +import re +import testutils as test + +# bpmd -clear + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + # Set breakpoint + + ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(out_msg) + print(err_msg) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # Output is not empty + # Should be at least 'Adding pending breakpoints...' + test.assertTrue(res.GetOutputSize() > 0) + + # Error message is empty + test.assertTrue(res.GetErrorSize() == 0) + + # Delete the first breakpoint + + ci.HandleCommand("bpmd -clear 1", res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(out_msg) + print(err_msg) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + match = re.search('Cleared', out_msg) + # Check for specific output + test.assertTrue(match) + + # Error message is empty + test.assertTrue(res.GetErrorSize() == 0) + + process.Continue() + # Process must be exited + test.assertEqual(process.GetState(), lldb.eStateStopped) + + # The reason of this stop must be a breakpoint + test.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonBreakpoint) + + # + + # Delete all breakpoints, continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_clearall.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_clearall.py new file mode 100644 index 000000000..2c3218505 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_bpmd_clearall.py @@ -0,0 +1,68 @@ +# 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. + +import lldb +import re +import testutils as test + +# bpmd -clearall + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + # Set breakpoint + + ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(out_msg) + print(err_msg) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # Output is not empty + # Should be at least 'Adding pending breakpoints...' + test.assertTrue(res.GetOutputSize() > 0) + + # Error message is empty + test.assertTrue(res.GetErrorSize() == 0) + + # Delete all breakpoints + + ci.HandleCommand("bpmd -clearall", res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(out_msg) + print(err_msg) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + match = re.search('All pending breakpoints cleared.', out_msg) + # Check for specific output + test.assertTrue(match) + + # Error message is empty + test.assertTrue(res.GetErrorSize() == 0) + + process.Continue() + process.Continue() + + # Process must be exited + test.assertEqual(process.GetState(), lldb.eStateExited) + + # The reason of this stop must be a breakpoint + test.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonNone) + + # + + # Delete all breakpoints, continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_methoddesc.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_methoddesc.py new file mode 100644 index 000000000..f7d515ab6 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_bpmd_methoddesc.py @@ -0,0 +1,49 @@ +# 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. + +import lldb +import re +import testutils as test + +# bpmd -md + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + md_addr = test.get_methoddesc(debugger, assembly, "Test.UnlikelyInlined") + + ci.HandleCommand("bpmd -md %s" % md_addr, res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(out_msg) + print(err_msg) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # Output is not empty + # Should be at least 'Adding pending breakpoints...' + test.assertTrue(res.GetOutputSize() > 0) + + # Error message is empty + test.assertTrue(res.GetErrorSize() == 0) + + process.Continue() + # Process must be stopped at UnlinkelyInlined + test.assertEqual(process.GetState(), lldb.eStateStopped) + + # The reason of this stop must be a breakpoint + test.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonBreakpoint) + + # + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function.py new file mode 100644 index 000000000..471c58040 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function.py @@ -0,0 +1,47 @@ +# 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. + +import lldb +import re +import testutils as test + +# bpmd + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(out_msg) + print(err_msg) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # Output is not empty + # Should be at least 'Adding pending breakpoints...' + test.assertTrue(res.GetOutputSize() > 0) + + # Error message is empty + test.assertTrue(res.GetErrorSize() == 0) + + process.Continue() + # Process must be stopped at UnlinkelyInlined + test.assertEqual(process.GetState(), lldb.eStateStopped) + + # The reason of this stop must be a breakpoint + test.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonBreakpoint) + + # + + # Delete all breakpoints, continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function_iloffset.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function_iloffset.py new file mode 100644 index 000000000..51d546772 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function_iloffset.py @@ -0,0 +1,47 @@ +# 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. + +import lldb +import re +import testutils as test + +# bpmd [] + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined 66", res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(out_msg) + print(err_msg) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # Output is not empty + # Should be at least 'Adding pending breakpoints...' + test.assertTrue(res.GetOutputSize() > 0) + + # Error message is empty + test.assertTrue(res.GetErrorSize() == 0) + + process.Continue() + # Process must be stopped at UnlinkelyInlined + test.assertEqual(process.GetState(), lldb.eStateStopped) + + # The reason of this stop must be a breakpoint + test.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonBreakpoint) + + # + + # Delete all breakpoints, continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_nofuturemodule_module_function.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_nofuturemodule_module_function.py new file mode 100644 index 000000000..fc47f474b --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_bpmd_nofuturemodule_module_function.py @@ -0,0 +1,51 @@ +# 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. + +import lldb +import re +import testutils as test + +# bpmd -nofuturemodule + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Process must be stopped here while libcoreclr loading. + # This test usually fails on release version of coreclr + # since we depend on 'LoadLibraryExW' symbol present. + test.assertEqual(process.GetState(), lldb.eStateStopped) + + # The reason of this stop must be a breakpoint + test.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonBreakpoint) + + ci.HandleCommand("bpmd -nofuturemodule " + assembly + " Test.Main", res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(out_msg) + print(err_msg) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # Output is empty + test.assertTrue(res.GetOutputSize() == 0) + + # Error message is empty + test.assertTrue(res.GetErrorSize() == 0) + + process.Continue() + # Process must be exited because of -nofuturemodule flag + test.assertEqual(process.GetState(), lldb.eStateExited) + + # The reason of this stop must be a breakpoint + test.assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonNone) + + # + + # Delete all breakpoints, continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_clrstack.py b/src/SOS/lldbplugin.tests/t_cmd_clrstack.py new file mode 100644 index 000000000..6812afb0c --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_clrstack.py @@ -0,0 +1,40 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("clrstack", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(res.GetOutputSize() > 0) + + match = re.search('OS Thread Id', output) + # Specific string must be in the output + test.assertTrue(match) + + match = re.search('Failed to start', output) + # Check if a fail was reported + test.assertFalse(match) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_clrthreads.py b/src/SOS/lldbplugin.tests/t_cmd_clrthreads.py new file mode 100644 index 000000000..5e374dd06 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_clrthreads.py @@ -0,0 +1,35 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("clrthreads", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("ThreadCount:"), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_clru.py b/src/SOS/lldbplugin.tests/t_cmd_clru.py new file mode 100644 index 000000000..6f2971f61 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_clru.py @@ -0,0 +1,50 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("name2ee " + assembly + " Test.Main", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('JITTED Code Address:\s+([0-9a-fA-F]+)', output) + # Line matched + test.assertTrue(match) + + groups = match.groups() + # Match has a single subgroup + test.assertEqual(len(groups), 1) + + jit_addr = groups[0] + # Address must be a hex number + test.assertTrue(test.is_hexnum(jit_addr)) + + ci.HandleCommand("clru " + jit_addr, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dso.py b/src/SOS/lldbplugin.tests/t_cmd_dso.py new file mode 100644 index 000000000..fc6761559 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dso.py @@ -0,0 +1,35 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dso", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("SP/REG"), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpclass.py b/src/SOS/lldbplugin.tests/t_cmd_dumpclass.py new file mode 100644 index 000000000..8026122d6 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dumpclass.py @@ -0,0 +1,72 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("name2ee " + assembly + " Test.Main", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output) + # Line matched + test.assertTrue(match) + + groups = match.groups() + # Match has a single subgroup + test.assertEqual(len(groups), 1) + + md_addr = groups[0] + # Address must be a hex number + test.assertTrue(test.is_hexnum(md_addr)) + + ci.HandleCommand("dumpmd " + md_addr, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('Class:\s+([0-9a-fA-F]+)', output) + # Line matched + test.assertTrue(match) + + groups = match.groups() + # Match has a single subgroup + test.assertEqual(len(groups), 1) + + class_addr = groups[0] + # Address must be a hex number + test.assertTrue(test.is_hexnum(class_addr)) + + ci.HandleCommand("dumpmd " + class_addr, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpheap.py b/src/SOS/lldbplugin.tests/t_cmd_dumpheap.py new file mode 100644 index 000000000..018643566 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dumpheap.py @@ -0,0 +1,35 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dumpheap", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("Address"), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpil.py b/src/SOS/lldbplugin.tests/t_cmd_dumpil.py new file mode 100644 index 000000000..1ddd482b0 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dumpil.py @@ -0,0 +1,42 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + md_addr = test.get_methoddesc(debugger, assembly, "Test.DumpIL") + + ci.HandleCommand("dumpil " + md_addr, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + insts = res.GetOutput() + print(insts) + # Function must have some instructions + test.assertTrue(len(insts) > 0) + + match = re.search(r'IL_\w{4}:\sldstr.*test\sdumpil.*' + + r'IL_\w{4}:\scall.*System\.Console::WriteLine.*' + + r'IL_\w{4}:\sret', + insts.replace('\n', ' ')) + # Must have ldstr, call and ret instructions + test.assertTrue(match) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumplog.py b/src/SOS/lldbplugin.tests/t_cmd_dumplog.py new file mode 100644 index 000000000..631beb48a --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dumplog.py @@ -0,0 +1,35 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dumplog", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find(" dump "), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpmd.py b/src/SOS/lldbplugin.tests/t_cmd_dumpmd.py new file mode 100644 index 000000000..b2b020b4d --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dumpmd.py @@ -0,0 +1,50 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("name2ee " + assembly + " Test.Main", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output) + # Line matched + test.assertTrue(match) + + groups = match.groups() + # Match has a single subgroup + test.assertEqual(len(groups), 1) + + md_addr = groups[0] + # Address must be a hex number + test.assertTrue(test.is_hexnum(md_addr)) + + ci.HandleCommand("dumpmd " + md_addr, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpmodule.py b/src/SOS/lldbplugin.tests/t_cmd_dumpmodule.py new file mode 100644 index 000000000..5f256b12f --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dumpmodule.py @@ -0,0 +1,50 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("name2ee " + assembly + " Test.Main", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('Module:\s+([0-9a-fA-F]+)', output) + # Line matched + test.assertTrue(match) + + groups = match.groups() + # Match has a single subgroup + test.assertEqual(len(groups), 1) + + md_addr = groups[0] + # Address must be a hex number + test.assertTrue(test.is_hexnum(md_addr)) + + ci.HandleCommand("dumpmodule " + md_addr, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpmt.py b/src/SOS/lldbplugin.tests/t_cmd_dumpmt.py new file mode 100644 index 000000000..3640a4b55 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dumpmt.py @@ -0,0 +1,72 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("name2ee " + assembly + " Test.Main", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output) + # Line matched + test.assertTrue(match) + + groups = match.groups() + # Match has a single subgroup + test.assertEqual(len(groups), 1) + + md_addr = groups[0] + # Address must be a hex number + test.assertTrue(test.is_hexnum(md_addr)) + + ci.HandleCommand("dumpmd " + md_addr, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('MethodTable:\s+([0-9a-fA-F]+)', output) + # Line matched + test.assertTrue(match) + + groups = match.groups() + # Match has a single subgroup + test.assertEqual(len(groups), 1) + + mt_addr = groups[0] + # Address must be a hex number + test.assertTrue(test.is_hexnum(mt_addr)) + + ci.HandleCommand("dumpmt " + mt_addr, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpobj.py b/src/SOS/lldbplugin.tests/t_cmd_dumpobj.py new file mode 100644 index 000000000..a6f2e3934 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dumpobj.py @@ -0,0 +1,73 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dso", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Get all objects + objects = [] + for line in output.split('\n'): + match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) + # Not all lines list objects + if match: + groups = match.groups() + # Match has exactly two subgroups + test.assertEqual(len(groups), 2) + + obj_addr = groups[1] + # Address must be a hex number + test.assertTrue(test.is_hexnum(obj_addr)) + + objects.append(obj_addr) + + # There must be at least one object + test.assertTrue(len(objects) > 0) + + for obj in objects: + ci.HandleCommand("dumpobj " + obj, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('Name:\s+\S+', output) + test.assertTrue(match) + match = re.search('MethodTable:\s+[0-9a-fA-F]+', output) + test.assertTrue(match) + match = re.search('EEClass:\s+[0-9a-fA-F]+', output) + test.assertTrue(match) + match = re.search('Size:\s+[0-9a-fA-F]+', output) + test.assertTrue(match) + match = re.search('Fields:', output) + test.assertTrue(match) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpstack.py b/src/SOS/lldbplugin.tests/t_cmd_dumpstack.py new file mode 100644 index 000000000..24ae1a5bd --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_dumpstack.py @@ -0,0 +1,35 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dumpstack", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("Test.Main()"), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_eeheap.py b/src/SOS/lldbplugin.tests/t_cmd_eeheap.py new file mode 100644 index 000000000..8c9596467 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_eeheap.py @@ -0,0 +1,35 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("eeheap", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("Loader Heap"), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_eestack.py b/src/SOS/lldbplugin.tests/t_cmd_eestack.py new file mode 100644 index 000000000..8764bccf4 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_eestack.py @@ -0,0 +1,35 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("eestack", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("Current frame"), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_gcroot.py b/src/SOS/lldbplugin.tests/t_cmd_gcroot.py new file mode 100644 index 000000000..636578d69 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_gcroot.py @@ -0,0 +1,65 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dso", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Get all objects + objects = [] + for line in output.split('\n'): + match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) + # Not all lines list objects + if match: + groups = match.groups() + # Match has exactly two subgroups + test.assertEqual(len(groups), 2) + + obj_addr = groups[1] + # Address must be a hex number + test.assertTrue(test.is_hexnum(obj_addr)) + + objects.append(obj_addr) + + # There must be at least one object + test.assertTrue(len(objects) > 0) + + for obj in objects: + ci.HandleCommand("gcroot " + obj, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('Found', output) + test.assertTrue(match) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_histclear.py b/src/SOS/lldbplugin.tests/t_cmd_histclear.py new file mode 100644 index 000000000..b5206a7cd --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_histclear.py @@ -0,0 +1,35 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("histclear", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("Completed successfully."), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_histinit.py b/src/SOS/lldbplugin.tests/t_cmd_histinit.py new file mode 100644 index 000000000..d0b2e8929 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_histinit.py @@ -0,0 +1,35 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("histinit", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("STRESS LOG:"), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_histobj.py b/src/SOS/lldbplugin.tests/t_cmd_histobj.py new file mode 100644 index 000000000..402f04214 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_histobj.py @@ -0,0 +1,65 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dso", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Get all objects + objects = [] + for line in output.split('\n'): + match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) + # Not all lines list objects + if match: + groups = match.groups() + # Match has exactly two subgroups + test.assertEqual(len(groups), 2) + + obj_addr = groups[1] + # Address must be a hex number + test.assertTrue(test.is_hexnum(obj_addr)) + + objects.append(obj_addr) + + # There must be at least one object + test.assertTrue(len(objects) > 0) + + for obj in objects: + ci.HandleCommand("histobj " + obj, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('GCCount', output) + test.assertTrue(match) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_histobjfind.py b/src/SOS/lldbplugin.tests/t_cmd_histobjfind.py new file mode 100644 index 000000000..611639978 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_histobjfind.py @@ -0,0 +1,65 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dso", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Get all objects + objects = [] + for line in output.split('\n'): + match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) + # Not all lines list objects + if match: + groups = match.groups() + # Match has exactly two subgroups + test.assertEqual(len(groups), 2) + + obj_addr = groups[1] + # Address must be a hex number + test.assertTrue(test.is_hexnum(obj_addr)) + + objects.append(obj_addr) + + # There must be at least one object + test.assertTrue(len(objects) > 0) + + for obj in objects: + ci.HandleCommand("histobjfind " + obj, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('GCCount', output) + test.assertTrue(match) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_histroot.py b/src/SOS/lldbplugin.tests/t_cmd_histroot.py new file mode 100644 index 000000000..e212b1f08 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_histroot.py @@ -0,0 +1,65 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dso", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Get all objects + objects = [] + for line in output.split('\n'): + match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) + # Not all lines list objects + if match: + groups = match.groups() + # Match has exactly two subgroups + test.assertEqual(len(groups), 2) + + obj_addr = groups[1] + # Address must be a hex number + test.assertTrue(test.is_hexnum(obj_addr)) + + objects.append(obj_addr) + + # There must be at least one object + test.assertTrue(len(objects) > 0) + + for obj in objects: + ci.HandleCommand("histroot " + obj, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('GCCount', output) + test.assertTrue(match) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_ip2md.py b/src/SOS/lldbplugin.tests/t_cmd_ip2md.py new file mode 100644 index 000000000..78bebe95f --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_ip2md.py @@ -0,0 +1,57 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("name2ee " + assembly + " Test.Main", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('JITTED Code Address:\s+([0-9a-fA-F]+)', output) + # Line matched + test.assertTrue(match) + + groups = match.groups() + # Match has a single subgroup + test.assertEqual(len(groups), 1) + + jit_addr = groups[0] + # Address must be a hex number + test.assertTrue(test.is_hexnum(jit_addr)) + + ci.HandleCommand("ip2md " + jit_addr, res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("MethodDesc:"), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_name2ee.py b/src/SOS/lldbplugin.tests/t_cmd_name2ee.py new file mode 100644 index 000000000..71d421251 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_name2ee.py @@ -0,0 +1,50 @@ +# 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. + +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("name2ee " + assembly + " Test.Main", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + match = re.search('Module:\s+[0-9a-fA-F]+', output) + test.assertTrue(match) + match = re.search('Assembly:\s+\S+', output) + test.assertTrue(match) + match = re.search('Token:\s+[0-9a-fA-F]+', output) + test.assertTrue(match) + match = re.search('MethodDesc:\s+[0-9a-fA-F]+', output) + test.assertTrue(match) + match = re.search('Name:\s+\S+', output) + test.assertTrue(match) + + process.Continue() + # Process must exit + test.assertEqual(process.GetState(), lldb.eStateExited) + + # Process must exit with zero code + test.assertEqual(process.GetExitStatus(), 0) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_pe.py b/src/SOS/lldbplugin.tests/t_cmd_pe.py new file mode 100644 index 000000000..0a8701493 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_pe.py @@ -0,0 +1,28 @@ +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("dso", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_sos.py b/src/SOS/lldbplugin.tests/t_cmd_sos.py new file mode 100644 index 000000000..b407491d7 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_sos.py @@ -0,0 +1,31 @@ +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("sos", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("SOS"), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/t_cmd_soshelp.py b/src/SOS/lldbplugin.tests/t_cmd_soshelp.py new file mode 100644 index 000000000..8bb51dad5 --- /dev/null +++ b/src/SOS/lldbplugin.tests/t_cmd_soshelp.py @@ -0,0 +1,31 @@ +import lldb +import re +import testutils as test + + +def runScenario(assembly, debugger, target): + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + ci = debugger.GetCommandInterpreter() + + # Run debugger, wait until libcoreclr is loaded, + # set breakpoint at Test.Main and stop there + test.stop_in_main(debugger, assembly) + + ci.HandleCommand("soshelp", res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + test.assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + test.assertTrue(len(output) > 0) + + # Specific string must be in the output + test.assertNotEqual(output.find("soshelp "), -1) + + # TODO: test other use cases + + # Continue current process and checks its exit code + test.exit_lldb(debugger, assembly) diff --git a/src/SOS/lldbplugin.tests/test_libsosplugin.py b/src/SOS/lldbplugin.tests/test_libsosplugin.py new file mode 100644 index 000000000..1f7b8272d --- /dev/null +++ b/src/SOS/lldbplugin.tests/test_libsosplugin.py @@ -0,0 +1,301 @@ +# 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. + +from __future__ import print_function +import unittest +import argparse +import re +import tempfile +import subprocess +import threading +import os +import sys +import inspect + +lldb = '' +logfiledir = '' +host = '' +plugin = '' +assembly = '' +fail_flag = '' +fail_flag_lldb = '' +summary_file = '' +timeout = 0 +regex = '' +repeat = 0 + +tests_failed = False + +def runWithTimeout(cmd): + p = None + + def run(): + global p + p = subprocess.Popen(cmd, shell=True) + p.communicate() + + thread = threading.Thread(target=run) + thread.start() + + thread.join(timeout) + if thread.is_alive(): + with open(summary_file, 'a+') as summary: + print('Timeout!', file=summary) + p.kill() + thread.join() + + +class TestSosCommands(unittest.TestCase): + + def do_test(self, command): + open(fail_flag, 'a').close() + try: + os.unlink(fail_flag_lldb) + except: + pass + + logfile = os.path.join(logfiledir, command) + + cmd = (('%s -b ' % lldb) + + ("-k \"script open('%s', 'a').close()\" " % fail_flag_lldb) + + ("-k 'quit' ") + + ("--no-lldbinit ") + + ("-o \"version\" ") + + ("-O \"plugin load %s \" " % plugin) + + ("-o \"script import testutils as test\" ") + + ("-o \"script test.fail_flag = '%s'\" " % fail_flag) + + ("-o \"script test.summary_file = '%s'\" " % summary_file) + + ("-o \"script test.run('%s', '%s')\" " % (assembly, command)) + + ("-o \"quit\" ") + + (" -- %s %s > %s.log 2>&1" % (host, assembly, logfile))) + + runWithTimeout(cmd) + if not os.path.isfile(fail_flag): + tests_failed = True + + if not os.path.isfile(fail_flag_lldb): + tests_failed = True + + self.assertFalse(os.path.isfile(fail_flag)) + self.assertFalse(os.path.isfile(fail_flag_lldb)) + + try: + os.unlink(fail_flag) + except: + pass + try: + os.unlink(fail_flag_lldb) + except: + pass + + def t_cmd_bpmd_nofuturemodule_module_function(self): + self.do_test('t_cmd_bpmd_nofuturemodule_module_function') + + def t_cmd_bpmd_module_function(self): + self.do_test('t_cmd_bpmd_module_function') + + def t_cmd_bpmd_module_function_iloffset(self): + self.do_test('t_cmd_bpmd_module_function_iloffset') + + def t_cmd_bpmd_methoddesc(self): + self.do_test('t_cmd_bpmd_methoddesc') + + def t_cmd_bpmd_clear(self): + self.do_test('t_cmd_bpmd_clear') + + def t_cmd_bpmd_clearall(self): + self.do_test('t_cmd_bpmd_clearall') + + def t_cmd_clrstack(self): + self.do_test('t_cmd_clrstack') + + def t_cmd_clrthreads(self): + self.do_test('t_cmd_clrthreads') + + def t_cmd_clru(self): + self.do_test('t_cmd_clru') + + def t_cmd_dumpclass(self): + self.do_test('t_cmd_dumpclass') + + def t_cmd_dumpheap(self): + self.do_test('t_cmd_dumpheap') + + def t_cmd_dumpil(self): + self.do_test('t_cmd_dumpil') + + def t_cmd_dumplog(self): + self.do_test('t_cmd_dumplog') + + def t_cmd_dumpmd(self): + self.do_test('t_cmd_dumpmd') + + def t_cmd_dumpmodule(self): + self.do_test('t_cmd_dumpmodule') + + def t_cmd_dumpmt(self): + self.do_test('t_cmd_dumpmt') + + def t_cmd_dumpobj(self): + self.do_test('t_cmd_dumpobj') + + def t_cmd_dumpstack(self): + self.do_test('t_cmd_dumpstack') + + def t_cmd_dso(self): + self.do_test('t_cmd_dso') + + def t_cmd_eeheap(self): + self.do_test('t_cmd_eeheap') + + def t_cmd_eestack(self): + self.do_test('t_cmd_eestack') + + def t_cmd_gcroot(self): + self.do_test('t_cmd_gcroot') + + def t_cmd_ip2md(self): + self.do_test('t_cmd_ip2md') + + def t_cmd_name2ee(self): + self.do_test('t_cmd_name2ee') + + def t_cmd_pe(self): + self.do_test('t_cmd_pe') + + def t_cmd_histclear(self): + self.do_test('t_cmd_histclear') + + def t_cmd_histinit(self): + self.do_test('t_cmd_histinit') + + def t_cmd_histobj(self): + self.do_test('t_cmd_histobj') + + def t_cmd_histobjfind(self): + self.do_test('t_cmd_histobjfind') + + def t_cmd_histroot(self): + self.do_test('t_cmd_histroot') + + def t_cmd_sos(self): + self.do_test('t_cmd_sos') + + def t_cmd_soshelp(self): + self.do_test('t_cmd_soshelp') + + +def generate_report(): + report = [{'name': 'TOTAL', True: 0, False: 0, 'completed': True}] + fail_messages = [] + + if not os.path.isfile(summary_file): + print('No summary file to process!') + return + + with open(summary_file, 'r') as summary: + for line in summary: + if line.startswith('new_suite: '): + report.append({'name': line.split()[-1], True: 0, False: 0, + 'completed': False, 'timeout': False}) + elif line.startswith('True'): + report[-1][True] += 1 + elif line.startswith('False'): + report[-1][False] += 1 + elif line.startswith('Completed!'): + report[-1]['completed'] = True + elif line.startswith('Timeout!'): + report[-1]['timeout'] = True + elif line.startswith('!!! '): + fail_messages.append(line.rstrip('\n')) + + for suite in report[1:]: + report[0][True] += suite[True] + report[0][False] += suite[False] + report[0]['completed'] &= suite['completed'] + + for line in fail_messages: + print(line) + + print() + print('=' * 79) + print('{:72} {:6}'.format('Test suite', 'Result')) + print('-' * 79) + for suite in report[1:]: + if suite['timeout']: + result = 'Timeout' + elif suite[False]: + result = 'Fail' + elif not suite['completed']: + result = 'Crash' + elif suite[True]: + result = 'Success' + else: + result = 'Please, report' + print('{:68} {:>10}'.format(suite['name'], result)) + print('=' * 79) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--lldb', default='lldb') + parser.add_argument('--host', default='.') + parser.add_argument('--plugin', default='.') + parser.add_argument('--logfiledir', default='.') + parser.add_argument('--assembly', default='Test.exe') + parser.add_argument('--timeout', default=90) + parser.add_argument('--regex', default='t_cmd_') + parser.add_argument('--repeat', default=1) + parser.add_argument('unittest_args', nargs='*') + + args = parser.parse_args() + + lldb = args.lldb + host = args.host + plugin = args.plugin + logfiledir = args.logfiledir + assembly = args.assembly + timeout = int(args.timeout) + regex = args.regex + repeat = int(args.repeat) + print("host: %s" % host) + print("lldb: %s" % lldb) + print("lldb plugin: %s" % plugin) + print("logfiledir: %s" % logfiledir) + print("assembly: %s" % assembly) + print("timeout: %i" % timeout) + print("regex: %s" % regex) + print("repeat: %i" % repeat) + + if os.name != 'posix': + print('OS not supported') + exit(1) + + fail_flag = os.path.join(logfiledir, 'fail_flag') + fail_flag_lldb = os.path.join(logfiledir, 'fail_flag.lldb') + + print("fail_flag: %s" % fail_flag) + print("fail_flag_lldb: %s" % fail_flag_lldb) + + summary_file = os.path.join(logfiledir, 'summary') + print("summary_file: %s" % summary_file) + + try: + os.unlink(summary_file) + except: + pass + + sys.argv[1:] = args.unittest_args + suite = unittest.TestSuite() + all_tests = inspect.getmembers(TestSosCommands, predicate=inspect.ismethod) + for (test_name, test_func) in all_tests: + if re.match(regex, test_name): + suite.addTest(TestSosCommands(test_name)) + unittest.TextTestRunner(verbosity=1).run(suite) + + generate_report() + + if tests_failed: + exit(1) + diff --git a/src/SOS/lldbplugin.tests/testsos.sh b/src/SOS/lldbplugin.tests/testsos.sh new file mode 100755 index 000000000..d4f4f5fde --- /dev/null +++ b/src/SOS/lldbplugin.tests/testsos.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +__ProjectRoot="$1" +__Plugin="$2" +__ManagedBinDir="$3" +__ResultsDir="$4" + +if [[ "$__ProjectRoot" = "" || "$__Plugin" = "" || "$__ManagedBinDir" = "" || "$__ResultsDir" = "" ]]; then + echo "Project root, plugin or log directory required" + exit 1 +fi + +__Host=$__ProjectRoot/.dotnet/dotnet +__TestProgram=$__ManagedBinDir/TestDebuggee/netcoreapp2.0/TestDebuggee.dll + +# Turn on stress logging so the dumplog and histinit commands pass +export COMPlus_LogFacility=0xffffffbf +export COMPlus_LogLevel=6 +export COMPlus_StressLog=1 +export COMPlus_StressLogSize=65536 + +if [[ ! -x "$LLDB_PATH" ]]; then + echo "LLDB_PATH doesn't exist or not executable" + exit 1 +fi + +__LogFileDir=$__ResultsDir/lldbplugin.tests_$(date +%Y_%m_%d_%H_%M_%S) +mkdir -p $__LogFileDir + +cd $__ProjectRoot/src/SOS/lldbplugin.tests/ +rm -f StressLog.txt +python $__ProjectRoot/src/SOS/lldbplugin.tests/test_libsosplugin.py --lldb $LLDB_PATH --host $__Host --plugin $__Plugin --logfiledir $__LogFileDir --assembly $__TestProgram + diff --git a/src/SOS/lldbplugin.tests/testutils.py b/src/SOS/lldbplugin.tests/testutils.py new file mode 100644 index 000000000..7d7df276a --- /dev/null +++ b/src/SOS/lldbplugin.tests/testutils.py @@ -0,0 +1,209 @@ +# 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. + +from __future__ import print_function +import lldb +import re +import inspect +import sys +import os +import importlib + +summary_file = '' +fail_flag = '' + +failed = False + +def assertCommon(passed, fatal): + global failed + with open(summary_file, 'a+') as summary: + print(bool(passed), file=summary) + if (not passed): + failed = True + print('!!! test failed:', file=summary) + for s in inspect.stack()[2:]: + print("!!! %s:%i" % (s[1], s[2]), file=summary) + print("!!! %s" % s[4][0], file=summary) + if re.match('\W*t_\w+\.py$', s[1]): + break + print('!!! ', file=summary) + + if fatal: + exit(1) + +def assertTrue(x, fatal=True): + passed = bool(x) + assertCommon(passed, fatal) + + +def assertFalse(x, fatal=True): + passed = not bool(x) + assertCommon(passed, fatal) + + +def assertEqual(x, y, fatal=True): + passed = (x == y) + if not passed: + print(str(x), ' != ', str(y)) + assertCommon(passed, fatal) + + +def assertNotEqual(x, y, fatal=True): + passed = (x != y) + if not passed: + print(str(x), ' == ', str(y)) + assertCommon(passed, fatal) + + +def checkResult(res): + global failed + if not res.Succeeded(): + failed = True + print(res.GetOutput()) + print(res.GetError()) + exit(1) + + +def is_hexnum(s): + try: + int(s, 16) + return True + except ValueError: + return False + + +def exec_and_find(commandInterpreter, cmd, regexp): + res = lldb.SBCommandReturnObject() + commandInterpreter.HandleCommand(cmd, res) + checkResult(res) + + expr = re.compile(regexp) + addr = None + + print(res.GetOutput()) + lines = res.GetOutput().splitlines() + for line in lines: + match = expr.match(line) + if match: + addr = match.group(1) + break + + print("Found addr: " + str(addr)) + return addr + + +def stop_in_main(debugger, assembly): + ci = debugger.GetCommandInterpreter() + target = debugger.GetSelectedTarget() + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + + # Process must be stopped here while libcoreclr loading. + # This test usually fails on release version of coreclr + # since we depend on 'LoadLibraryExW' symbol present. + assertEqual(process.GetState(), lldb.eStateStopped) + + # The reason of this stop must be a breakpoint + assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonBreakpoint) + + ci.HandleCommand("bpmd " + assembly + " Test.Main", res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + assertTrue(res.Succeeded()) + + # Output is not empty + # Should be at least 'Adding pending breakpoints...' + assertTrue(res.GetOutputSize() > 0) + + # Error message is empty + assertTrue(res.GetErrorSize() == 0) + + process.Continue() + # Process must be stopped here if bpmd works at all + assertEqual(process.GetState(), lldb.eStateStopped) + + # The reason of this stop must be a breakpoint + assertEqual(process.GetSelectedThread().GetStopReason(), + lldb.eStopReasonBreakpoint) + + +def exit_lldb(debugger, assembly): + ci = debugger.GetCommandInterpreter() + target = debugger.GetSelectedTarget() + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + + ci.HandleCommand("breakpoint delete --force", res) + out_msg = res.GetOutput() + err_msg = res.GetError() + print(out_msg) + print(err_msg) + # Interpreter must have this command and able to run it + # assertTrue(res.Succeeded()) + + process.Continue() + # Process must exit + assertEqual(process.GetState(), lldb.eStateExited) + + # Process must exit with zero code + assertEqual(process.GetExitStatus(), 0) + + +def get_methoddesc(debugger, assembly, funcname): + ci = debugger.GetCommandInterpreter() + target = debugger.GetSelectedTarget() + process = target.GetProcess() + res = lldb.SBCommandReturnObject() + + ci.HandleCommand("name2ee %s %s" % (assembly, funcname), res) + print(res.GetOutput()) + print(res.GetError()) + # Interpreter must have this command and able to run it + assertTrue(res.Succeeded()) + + output = res.GetOutput() + # Output is not empty + assertTrue(len(output) > 0) + + match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output) + # Line matched + assertTrue(match) + + groups = match.groups() + # Match has a single subgroup + assertEqual(len(groups), 1) + + md_addr = groups[0] + # Address must be a hex number + assertTrue(is_hexnum(md_addr)) + + return md_addr + + +def run(assembly, module): + with open(summary_file, 'a+') as summary: + print('new_suite: %s' % module, file=summary) + + debugger = lldb.debugger + + debugger.SetAsync(False) + target = lldb.target + + debugger.HandleCommand("breakpoint set --name coreclr_execute_assembly") + debugger.HandleCommand("process launch") + + # run the scenario + print("starting scenario...") + i = importlib.import_module(module) + scenarioResult = i.runScenario(os.path.basename(assembly), debugger, target) + + if (target.GetProcess().GetExitStatus() == 0) and not failed: + os.unlink(fail_flag) + + with open(summary_file, 'a+') as summary: + print('Completed!', file=summary) diff --git a/src/SOS/tests/README.md b/src/SOS/tests/README.md deleted file mode 100644 index a4ad7dce9..000000000 --- a/src/SOS/tests/README.md +++ /dev/null @@ -1,44 +0,0 @@ -Testing libsosplugin -===================================== - -**Running tests** - -The test.sh and testsos.sh scripts launches these tests and makes the following a lot easier. - - -Make sure that python's lldb module is accessible. To run the tests manually, use the following command: - -`python2 test_libsosplugin.py --lldb --host --plugin --logfiledir --assembly ` - -- `lldb` is a path to `lldb` to run -- `host` is a path to .NET Core host like `corerun` or `dotnet` -- `plugin` is the path to the lldb sos plugin -- `logfiledir` is the path to put the log files -- `assembly` is a compiled test assembly (e.g. TestDebuggee.dll) -- `timeout` is a deadline for a single test (in seconds) -- `regex` is a regular expression matching tests to run -- `repeat` is a number of passes for each test - -Log files for both failed and passed tests are `*.log` and `*.log.2` for standard output and error correspondingly. - - -**Writing tests** -Tests start with the `TestSosCommands` class defined in `test_libsosplugin.py`. To add a test to the suite, start with implementing a new method inside this class whose name begins with `test_`. Most new commands will require only one line of code in this method: `self.do_test("scenarioname")`. This command will launch a new `lldb` instance, which in turn will call the `runScenario` method from `scenarioname` module. `scenarioname` is the name of the python module that will be running the scenario inside `lldb` (found in `tests` folder alongside with `test_libsosplugin.py` and named `scenarioname.py`). -An example of a scenario looks like this: - - import lldb - def runScenario(assemblyName, debugger, target): - process = target.GetProcess() - - # do some work - - process.Continue() - return True - - `runScenario` method does all the work related to running the scenario: setting breakpoints, running SOS commands and examining their output. It should return a boolean value indicating a success or a failure. -***Note:*** `testutils.py` defines some useful commands that can be reused in many scenarios. - - -**Useful links** -[Python scripting in LLDB](http://lldb.llvm.org/python-reference.html) -[Python unittest framework](https://docs.python.org/2.7/library/unittest.html) diff --git a/src/SOS/tests/t_cmd_bpmd_clear.py b/src/SOS/tests/t_cmd_bpmd_clear.py deleted file mode 100644 index c80d130fc..000000000 --- a/src/SOS/tests/t_cmd_bpmd_clear.py +++ /dev/null @@ -1,66 +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. - -import lldb -import re -import testutils as test - -# bpmd -clear - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - # Set breakpoint - - ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(out_msg) - print(err_msg) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # Output is not empty - # Should be at least 'Adding pending breakpoints...' - test.assertTrue(res.GetOutputSize() > 0) - - # Error message is empty - test.assertTrue(res.GetErrorSize() == 0) - - # Delete the first breakpoint - - ci.HandleCommand("bpmd -clear 1", res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(out_msg) - print(err_msg) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - match = re.search('Cleared', out_msg) - # Check for specific output - test.assertTrue(match) - - # Error message is empty - test.assertTrue(res.GetErrorSize() == 0) - - process.Continue() - # Process must be exited - test.assertEqual(process.GetState(), lldb.eStateStopped) - - # The reason of this stop must be a breakpoint - test.assertEqual(process.GetSelectedThread().GetStopReason(), - lldb.eStopReasonBreakpoint) - - # - - # Delete all breakpoints, continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_bpmd_clearall.py b/src/SOS/tests/t_cmd_bpmd_clearall.py deleted file mode 100644 index 2c3218505..000000000 --- a/src/SOS/tests/t_cmd_bpmd_clearall.py +++ /dev/null @@ -1,68 +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. - -import lldb -import re -import testutils as test - -# bpmd -clearall - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - # Set breakpoint - - ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(out_msg) - print(err_msg) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # Output is not empty - # Should be at least 'Adding pending breakpoints...' - test.assertTrue(res.GetOutputSize() > 0) - - # Error message is empty - test.assertTrue(res.GetErrorSize() == 0) - - # Delete all breakpoints - - ci.HandleCommand("bpmd -clearall", res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(out_msg) - print(err_msg) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - match = re.search('All pending breakpoints cleared.', out_msg) - # Check for specific output - test.assertTrue(match) - - # Error message is empty - test.assertTrue(res.GetErrorSize() == 0) - - process.Continue() - process.Continue() - - # Process must be exited - test.assertEqual(process.GetState(), lldb.eStateExited) - - # The reason of this stop must be a breakpoint - test.assertEqual(process.GetSelectedThread().GetStopReason(), - lldb.eStopReasonNone) - - # - - # Delete all breakpoints, continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_bpmd_methoddesc.py b/src/SOS/tests/t_cmd_bpmd_methoddesc.py deleted file mode 100644 index f7d515ab6..000000000 --- a/src/SOS/tests/t_cmd_bpmd_methoddesc.py +++ /dev/null @@ -1,49 +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. - -import lldb -import re -import testutils as test - -# bpmd -md - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - md_addr = test.get_methoddesc(debugger, assembly, "Test.UnlikelyInlined") - - ci.HandleCommand("bpmd -md %s" % md_addr, res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(out_msg) - print(err_msg) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # Output is not empty - # Should be at least 'Adding pending breakpoints...' - test.assertTrue(res.GetOutputSize() > 0) - - # Error message is empty - test.assertTrue(res.GetErrorSize() == 0) - - process.Continue() - # Process must be stopped at UnlinkelyInlined - test.assertEqual(process.GetState(), lldb.eStateStopped) - - # The reason of this stop must be a breakpoint - test.assertEqual(process.GetSelectedThread().GetStopReason(), - lldb.eStopReasonBreakpoint) - - # - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_bpmd_module_function.py b/src/SOS/tests/t_cmd_bpmd_module_function.py deleted file mode 100644 index 471c58040..000000000 --- a/src/SOS/tests/t_cmd_bpmd_module_function.py +++ /dev/null @@ -1,47 +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. - -import lldb -import re -import testutils as test - -# bpmd - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(out_msg) - print(err_msg) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # Output is not empty - # Should be at least 'Adding pending breakpoints...' - test.assertTrue(res.GetOutputSize() > 0) - - # Error message is empty - test.assertTrue(res.GetErrorSize() == 0) - - process.Continue() - # Process must be stopped at UnlinkelyInlined - test.assertEqual(process.GetState(), lldb.eStateStopped) - - # The reason of this stop must be a breakpoint - test.assertEqual(process.GetSelectedThread().GetStopReason(), - lldb.eStopReasonBreakpoint) - - # - - # Delete all breakpoints, continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_bpmd_module_function_iloffset.py b/src/SOS/tests/t_cmd_bpmd_module_function_iloffset.py deleted file mode 100644 index 51d546772..000000000 --- a/src/SOS/tests/t_cmd_bpmd_module_function_iloffset.py +++ /dev/null @@ -1,47 +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. - -import lldb -import re -import testutils as test - -# bpmd [] - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined 66", res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(out_msg) - print(err_msg) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # Output is not empty - # Should be at least 'Adding pending breakpoints...' - test.assertTrue(res.GetOutputSize() > 0) - - # Error message is empty - test.assertTrue(res.GetErrorSize() == 0) - - process.Continue() - # Process must be stopped at UnlinkelyInlined - test.assertEqual(process.GetState(), lldb.eStateStopped) - - # The reason of this stop must be a breakpoint - test.assertEqual(process.GetSelectedThread().GetStopReason(), - lldb.eStopReasonBreakpoint) - - # - - # Delete all breakpoints, continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_bpmd_nofuturemodule_module_function.py b/src/SOS/tests/t_cmd_bpmd_nofuturemodule_module_function.py deleted file mode 100644 index fc47f474b..000000000 --- a/src/SOS/tests/t_cmd_bpmd_nofuturemodule_module_function.py +++ /dev/null @@ -1,51 +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. - -import lldb -import re -import testutils as test - -# bpmd -nofuturemodule - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Process must be stopped here while libcoreclr loading. - # This test usually fails on release version of coreclr - # since we depend on 'LoadLibraryExW' symbol present. - test.assertEqual(process.GetState(), lldb.eStateStopped) - - # The reason of this stop must be a breakpoint - test.assertEqual(process.GetSelectedThread().GetStopReason(), - lldb.eStopReasonBreakpoint) - - ci.HandleCommand("bpmd -nofuturemodule " + assembly + " Test.Main", res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(out_msg) - print(err_msg) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # Output is empty - test.assertTrue(res.GetOutputSize() == 0) - - # Error message is empty - test.assertTrue(res.GetErrorSize() == 0) - - process.Continue() - # Process must be exited because of -nofuturemodule flag - test.assertEqual(process.GetState(), lldb.eStateExited) - - # The reason of this stop must be a breakpoint - test.assertEqual(process.GetSelectedThread().GetStopReason(), - lldb.eStopReasonNone) - - # - - # Delete all breakpoints, continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_clrstack.py b/src/SOS/tests/t_cmd_clrstack.py deleted file mode 100644 index 6812afb0c..000000000 --- a/src/SOS/tests/t_cmd_clrstack.py +++ /dev/null @@ -1,40 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("clrstack", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(res.GetOutputSize() > 0) - - match = re.search('OS Thread Id', output) - # Specific string must be in the output - test.assertTrue(match) - - match = re.search('Failed to start', output) - # Check if a fail was reported - test.assertFalse(match) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_clrthreads.py b/src/SOS/tests/t_cmd_clrthreads.py deleted file mode 100644 index 5e374dd06..000000000 --- a/src/SOS/tests/t_cmd_clrthreads.py +++ /dev/null @@ -1,35 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("clrthreads", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("ThreadCount:"), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_clru.py b/src/SOS/tests/t_cmd_clru.py deleted file mode 100644 index 6f2971f61..000000000 --- a/src/SOS/tests/t_cmd_clru.py +++ /dev/null @@ -1,50 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("name2ee " + assembly + " Test.Main", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('JITTED Code Address:\s+([0-9a-fA-F]+)', output) - # Line matched - test.assertTrue(match) - - groups = match.groups() - # Match has a single subgroup - test.assertEqual(len(groups), 1) - - jit_addr = groups[0] - # Address must be a hex number - test.assertTrue(test.is_hexnum(jit_addr)) - - ci.HandleCommand("clru " + jit_addr, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dso.py b/src/SOS/tests/t_cmd_dso.py deleted file mode 100644 index fc6761559..000000000 --- a/src/SOS/tests/t_cmd_dso.py +++ /dev/null @@ -1,35 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dso", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("SP/REG"), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dumpclass.py b/src/SOS/tests/t_cmd_dumpclass.py deleted file mode 100644 index 8026122d6..000000000 --- a/src/SOS/tests/t_cmd_dumpclass.py +++ /dev/null @@ -1,72 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("name2ee " + assembly + " Test.Main", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output) - # Line matched - test.assertTrue(match) - - groups = match.groups() - # Match has a single subgroup - test.assertEqual(len(groups), 1) - - md_addr = groups[0] - # Address must be a hex number - test.assertTrue(test.is_hexnum(md_addr)) - - ci.HandleCommand("dumpmd " + md_addr, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('Class:\s+([0-9a-fA-F]+)', output) - # Line matched - test.assertTrue(match) - - groups = match.groups() - # Match has a single subgroup - test.assertEqual(len(groups), 1) - - class_addr = groups[0] - # Address must be a hex number - test.assertTrue(test.is_hexnum(class_addr)) - - ci.HandleCommand("dumpmd " + class_addr, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dumpheap.py b/src/SOS/tests/t_cmd_dumpheap.py deleted file mode 100644 index 018643566..000000000 --- a/src/SOS/tests/t_cmd_dumpheap.py +++ /dev/null @@ -1,35 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dumpheap", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("Address"), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dumpil.py b/src/SOS/tests/t_cmd_dumpil.py deleted file mode 100644 index 1ddd482b0..000000000 --- a/src/SOS/tests/t_cmd_dumpil.py +++ /dev/null @@ -1,42 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - md_addr = test.get_methoddesc(debugger, assembly, "Test.DumpIL") - - ci.HandleCommand("dumpil " + md_addr, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - insts = res.GetOutput() - print(insts) - # Function must have some instructions - test.assertTrue(len(insts) > 0) - - match = re.search(r'IL_\w{4}:\sldstr.*test\sdumpil.*' + - r'IL_\w{4}:\scall.*System\.Console::WriteLine.*' + - r'IL_\w{4}:\sret', - insts.replace('\n', ' ')) - # Must have ldstr, call and ret instructions - test.assertTrue(match) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dumplog.py b/src/SOS/tests/t_cmd_dumplog.py deleted file mode 100644 index 631beb48a..000000000 --- a/src/SOS/tests/t_cmd_dumplog.py +++ /dev/null @@ -1,35 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dumplog", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find(" dump "), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dumpmd.py b/src/SOS/tests/t_cmd_dumpmd.py deleted file mode 100644 index b2b020b4d..000000000 --- a/src/SOS/tests/t_cmd_dumpmd.py +++ /dev/null @@ -1,50 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("name2ee " + assembly + " Test.Main", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output) - # Line matched - test.assertTrue(match) - - groups = match.groups() - # Match has a single subgroup - test.assertEqual(len(groups), 1) - - md_addr = groups[0] - # Address must be a hex number - test.assertTrue(test.is_hexnum(md_addr)) - - ci.HandleCommand("dumpmd " + md_addr, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dumpmodule.py b/src/SOS/tests/t_cmd_dumpmodule.py deleted file mode 100644 index 5f256b12f..000000000 --- a/src/SOS/tests/t_cmd_dumpmodule.py +++ /dev/null @@ -1,50 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("name2ee " + assembly + " Test.Main", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('Module:\s+([0-9a-fA-F]+)', output) - # Line matched - test.assertTrue(match) - - groups = match.groups() - # Match has a single subgroup - test.assertEqual(len(groups), 1) - - md_addr = groups[0] - # Address must be a hex number - test.assertTrue(test.is_hexnum(md_addr)) - - ci.HandleCommand("dumpmodule " + md_addr, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dumpmt.py b/src/SOS/tests/t_cmd_dumpmt.py deleted file mode 100644 index 3640a4b55..000000000 --- a/src/SOS/tests/t_cmd_dumpmt.py +++ /dev/null @@ -1,72 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("name2ee " + assembly + " Test.Main", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output) - # Line matched - test.assertTrue(match) - - groups = match.groups() - # Match has a single subgroup - test.assertEqual(len(groups), 1) - - md_addr = groups[0] - # Address must be a hex number - test.assertTrue(test.is_hexnum(md_addr)) - - ci.HandleCommand("dumpmd " + md_addr, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('MethodTable:\s+([0-9a-fA-F]+)', output) - # Line matched - test.assertTrue(match) - - groups = match.groups() - # Match has a single subgroup - test.assertEqual(len(groups), 1) - - mt_addr = groups[0] - # Address must be a hex number - test.assertTrue(test.is_hexnum(mt_addr)) - - ci.HandleCommand("dumpmt " + mt_addr, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dumpobj.py b/src/SOS/tests/t_cmd_dumpobj.py deleted file mode 100644 index a6f2e3934..000000000 --- a/src/SOS/tests/t_cmd_dumpobj.py +++ /dev/null @@ -1,73 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dso", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Get all objects - objects = [] - for line in output.split('\n'): - match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) - # Not all lines list objects - if match: - groups = match.groups() - # Match has exactly two subgroups - test.assertEqual(len(groups), 2) - - obj_addr = groups[1] - # Address must be a hex number - test.assertTrue(test.is_hexnum(obj_addr)) - - objects.append(obj_addr) - - # There must be at least one object - test.assertTrue(len(objects) > 0) - - for obj in objects: - ci.HandleCommand("dumpobj " + obj, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('Name:\s+\S+', output) - test.assertTrue(match) - match = re.search('MethodTable:\s+[0-9a-fA-F]+', output) - test.assertTrue(match) - match = re.search('EEClass:\s+[0-9a-fA-F]+', output) - test.assertTrue(match) - match = re.search('Size:\s+[0-9a-fA-F]+', output) - test.assertTrue(match) - match = re.search('Fields:', output) - test.assertTrue(match) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_dumpstack.py b/src/SOS/tests/t_cmd_dumpstack.py deleted file mode 100644 index 24ae1a5bd..000000000 --- a/src/SOS/tests/t_cmd_dumpstack.py +++ /dev/null @@ -1,35 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dumpstack", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("Test.Main()"), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_eeheap.py b/src/SOS/tests/t_cmd_eeheap.py deleted file mode 100644 index 8c9596467..000000000 --- a/src/SOS/tests/t_cmd_eeheap.py +++ /dev/null @@ -1,35 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("eeheap", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("Loader Heap"), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_eestack.py b/src/SOS/tests/t_cmd_eestack.py deleted file mode 100644 index 8764bccf4..000000000 --- a/src/SOS/tests/t_cmd_eestack.py +++ /dev/null @@ -1,35 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("eestack", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("Current frame"), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_gcroot.py b/src/SOS/tests/t_cmd_gcroot.py deleted file mode 100644 index 636578d69..000000000 --- a/src/SOS/tests/t_cmd_gcroot.py +++ /dev/null @@ -1,65 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dso", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Get all objects - objects = [] - for line in output.split('\n'): - match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) - # Not all lines list objects - if match: - groups = match.groups() - # Match has exactly two subgroups - test.assertEqual(len(groups), 2) - - obj_addr = groups[1] - # Address must be a hex number - test.assertTrue(test.is_hexnum(obj_addr)) - - objects.append(obj_addr) - - # There must be at least one object - test.assertTrue(len(objects) > 0) - - for obj in objects: - ci.HandleCommand("gcroot " + obj, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('Found', output) - test.assertTrue(match) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_histclear.py b/src/SOS/tests/t_cmd_histclear.py deleted file mode 100644 index b5206a7cd..000000000 --- a/src/SOS/tests/t_cmd_histclear.py +++ /dev/null @@ -1,35 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("histclear", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("Completed successfully."), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_histinit.py b/src/SOS/tests/t_cmd_histinit.py deleted file mode 100644 index d0b2e8929..000000000 --- a/src/SOS/tests/t_cmd_histinit.py +++ /dev/null @@ -1,35 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("histinit", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("STRESS LOG:"), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_histobj.py b/src/SOS/tests/t_cmd_histobj.py deleted file mode 100644 index 402f04214..000000000 --- a/src/SOS/tests/t_cmd_histobj.py +++ /dev/null @@ -1,65 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dso", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Get all objects - objects = [] - for line in output.split('\n'): - match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) - # Not all lines list objects - if match: - groups = match.groups() - # Match has exactly two subgroups - test.assertEqual(len(groups), 2) - - obj_addr = groups[1] - # Address must be a hex number - test.assertTrue(test.is_hexnum(obj_addr)) - - objects.append(obj_addr) - - # There must be at least one object - test.assertTrue(len(objects) > 0) - - for obj in objects: - ci.HandleCommand("histobj " + obj, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('GCCount', output) - test.assertTrue(match) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_histobjfind.py b/src/SOS/tests/t_cmd_histobjfind.py deleted file mode 100644 index 611639978..000000000 --- a/src/SOS/tests/t_cmd_histobjfind.py +++ /dev/null @@ -1,65 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dso", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Get all objects - objects = [] - for line in output.split('\n'): - match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) - # Not all lines list objects - if match: - groups = match.groups() - # Match has exactly two subgroups - test.assertEqual(len(groups), 2) - - obj_addr = groups[1] - # Address must be a hex number - test.assertTrue(test.is_hexnum(obj_addr)) - - objects.append(obj_addr) - - # There must be at least one object - test.assertTrue(len(objects) > 0) - - for obj in objects: - ci.HandleCommand("histobjfind " + obj, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('GCCount', output) - test.assertTrue(match) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_histroot.py b/src/SOS/tests/t_cmd_histroot.py deleted file mode 100644 index e212b1f08..000000000 --- a/src/SOS/tests/t_cmd_histroot.py +++ /dev/null @@ -1,65 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dso", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Get all objects - objects = [] - for line in output.split('\n'): - match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line) - # Not all lines list objects - if match: - groups = match.groups() - # Match has exactly two subgroups - test.assertEqual(len(groups), 2) - - obj_addr = groups[1] - # Address must be a hex number - test.assertTrue(test.is_hexnum(obj_addr)) - - objects.append(obj_addr) - - # There must be at least one object - test.assertTrue(len(objects) > 0) - - for obj in objects: - ci.HandleCommand("histroot " + obj, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('GCCount', output) - test.assertTrue(match) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_ip2md.py b/src/SOS/tests/t_cmd_ip2md.py deleted file mode 100644 index 78bebe95f..000000000 --- a/src/SOS/tests/t_cmd_ip2md.py +++ /dev/null @@ -1,57 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("name2ee " + assembly + " Test.Main", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('JITTED Code Address:\s+([0-9a-fA-F]+)', output) - # Line matched - test.assertTrue(match) - - groups = match.groups() - # Match has a single subgroup - test.assertEqual(len(groups), 1) - - jit_addr = groups[0] - # Address must be a hex number - test.assertTrue(test.is_hexnum(jit_addr)) - - ci.HandleCommand("ip2md " + jit_addr, res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("MethodDesc:"), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_name2ee.py b/src/SOS/tests/t_cmd_name2ee.py deleted file mode 100644 index 71d421251..000000000 --- a/src/SOS/tests/t_cmd_name2ee.py +++ /dev/null @@ -1,50 +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. - -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("name2ee " + assembly + " Test.Main", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - match = re.search('Module:\s+[0-9a-fA-F]+', output) - test.assertTrue(match) - match = re.search('Assembly:\s+\S+', output) - test.assertTrue(match) - match = re.search('Token:\s+[0-9a-fA-F]+', output) - test.assertTrue(match) - match = re.search('MethodDesc:\s+[0-9a-fA-F]+', output) - test.assertTrue(match) - match = re.search('Name:\s+\S+', output) - test.assertTrue(match) - - process.Continue() - # Process must exit - test.assertEqual(process.GetState(), lldb.eStateExited) - - # Process must exit with zero code - test.assertEqual(process.GetExitStatus(), 0) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_pe.py b/src/SOS/tests/t_cmd_pe.py deleted file mode 100644 index 0a8701493..000000000 --- a/src/SOS/tests/t_cmd_pe.py +++ /dev/null @@ -1,28 +0,0 @@ -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("dso", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_sos.py b/src/SOS/tests/t_cmd_sos.py deleted file mode 100644 index b407491d7..000000000 --- a/src/SOS/tests/t_cmd_sos.py +++ /dev/null @@ -1,31 +0,0 @@ -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("sos", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("SOS"), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/t_cmd_soshelp.py b/src/SOS/tests/t_cmd_soshelp.py deleted file mode 100644 index 8bb51dad5..000000000 --- a/src/SOS/tests/t_cmd_soshelp.py +++ /dev/null @@ -1,31 +0,0 @@ -import lldb -import re -import testutils as test - - -def runScenario(assembly, debugger, target): - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - ci = debugger.GetCommandInterpreter() - - # Run debugger, wait until libcoreclr is loaded, - # set breakpoint at Test.Main and stop there - test.stop_in_main(debugger, assembly) - - ci.HandleCommand("soshelp", res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - test.assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - test.assertTrue(len(output) > 0) - - # Specific string must be in the output - test.assertNotEqual(output.find("soshelp "), -1) - - # TODO: test other use cases - - # Continue current process and checks its exit code - test.exit_lldb(debugger, assembly) diff --git a/src/SOS/tests/test_libsosplugin.py b/src/SOS/tests/test_libsosplugin.py deleted file mode 100755 index 1f7b8272d..000000000 --- a/src/SOS/tests/test_libsosplugin.py +++ /dev/null @@ -1,301 +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. - -from __future__ import print_function -import unittest -import argparse -import re -import tempfile -import subprocess -import threading -import os -import sys -import inspect - -lldb = '' -logfiledir = '' -host = '' -plugin = '' -assembly = '' -fail_flag = '' -fail_flag_lldb = '' -summary_file = '' -timeout = 0 -regex = '' -repeat = 0 - -tests_failed = False - -def runWithTimeout(cmd): - p = None - - def run(): - global p - p = subprocess.Popen(cmd, shell=True) - p.communicate() - - thread = threading.Thread(target=run) - thread.start() - - thread.join(timeout) - if thread.is_alive(): - with open(summary_file, 'a+') as summary: - print('Timeout!', file=summary) - p.kill() - thread.join() - - -class TestSosCommands(unittest.TestCase): - - def do_test(self, command): - open(fail_flag, 'a').close() - try: - os.unlink(fail_flag_lldb) - except: - pass - - logfile = os.path.join(logfiledir, command) - - cmd = (('%s -b ' % lldb) + - ("-k \"script open('%s', 'a').close()\" " % fail_flag_lldb) + - ("-k 'quit' ") + - ("--no-lldbinit ") + - ("-o \"version\" ") + - ("-O \"plugin load %s \" " % plugin) + - ("-o \"script import testutils as test\" ") + - ("-o \"script test.fail_flag = '%s'\" " % fail_flag) + - ("-o \"script test.summary_file = '%s'\" " % summary_file) + - ("-o \"script test.run('%s', '%s')\" " % (assembly, command)) + - ("-o \"quit\" ") + - (" -- %s %s > %s.log 2>&1" % (host, assembly, logfile))) - - runWithTimeout(cmd) - if not os.path.isfile(fail_flag): - tests_failed = True - - if not os.path.isfile(fail_flag_lldb): - tests_failed = True - - self.assertFalse(os.path.isfile(fail_flag)) - self.assertFalse(os.path.isfile(fail_flag_lldb)) - - try: - os.unlink(fail_flag) - except: - pass - try: - os.unlink(fail_flag_lldb) - except: - pass - - def t_cmd_bpmd_nofuturemodule_module_function(self): - self.do_test('t_cmd_bpmd_nofuturemodule_module_function') - - def t_cmd_bpmd_module_function(self): - self.do_test('t_cmd_bpmd_module_function') - - def t_cmd_bpmd_module_function_iloffset(self): - self.do_test('t_cmd_bpmd_module_function_iloffset') - - def t_cmd_bpmd_methoddesc(self): - self.do_test('t_cmd_bpmd_methoddesc') - - def t_cmd_bpmd_clear(self): - self.do_test('t_cmd_bpmd_clear') - - def t_cmd_bpmd_clearall(self): - self.do_test('t_cmd_bpmd_clearall') - - def t_cmd_clrstack(self): - self.do_test('t_cmd_clrstack') - - def t_cmd_clrthreads(self): - self.do_test('t_cmd_clrthreads') - - def t_cmd_clru(self): - self.do_test('t_cmd_clru') - - def t_cmd_dumpclass(self): - self.do_test('t_cmd_dumpclass') - - def t_cmd_dumpheap(self): - self.do_test('t_cmd_dumpheap') - - def t_cmd_dumpil(self): - self.do_test('t_cmd_dumpil') - - def t_cmd_dumplog(self): - self.do_test('t_cmd_dumplog') - - def t_cmd_dumpmd(self): - self.do_test('t_cmd_dumpmd') - - def t_cmd_dumpmodule(self): - self.do_test('t_cmd_dumpmodule') - - def t_cmd_dumpmt(self): - self.do_test('t_cmd_dumpmt') - - def t_cmd_dumpobj(self): - self.do_test('t_cmd_dumpobj') - - def t_cmd_dumpstack(self): - self.do_test('t_cmd_dumpstack') - - def t_cmd_dso(self): - self.do_test('t_cmd_dso') - - def t_cmd_eeheap(self): - self.do_test('t_cmd_eeheap') - - def t_cmd_eestack(self): - self.do_test('t_cmd_eestack') - - def t_cmd_gcroot(self): - self.do_test('t_cmd_gcroot') - - def t_cmd_ip2md(self): - self.do_test('t_cmd_ip2md') - - def t_cmd_name2ee(self): - self.do_test('t_cmd_name2ee') - - def t_cmd_pe(self): - self.do_test('t_cmd_pe') - - def t_cmd_histclear(self): - self.do_test('t_cmd_histclear') - - def t_cmd_histinit(self): - self.do_test('t_cmd_histinit') - - def t_cmd_histobj(self): - self.do_test('t_cmd_histobj') - - def t_cmd_histobjfind(self): - self.do_test('t_cmd_histobjfind') - - def t_cmd_histroot(self): - self.do_test('t_cmd_histroot') - - def t_cmd_sos(self): - self.do_test('t_cmd_sos') - - def t_cmd_soshelp(self): - self.do_test('t_cmd_soshelp') - - -def generate_report(): - report = [{'name': 'TOTAL', True: 0, False: 0, 'completed': True}] - fail_messages = [] - - if not os.path.isfile(summary_file): - print('No summary file to process!') - return - - with open(summary_file, 'r') as summary: - for line in summary: - if line.startswith('new_suite: '): - report.append({'name': line.split()[-1], True: 0, False: 0, - 'completed': False, 'timeout': False}) - elif line.startswith('True'): - report[-1][True] += 1 - elif line.startswith('False'): - report[-1][False] += 1 - elif line.startswith('Completed!'): - report[-1]['completed'] = True - elif line.startswith('Timeout!'): - report[-1]['timeout'] = True - elif line.startswith('!!! '): - fail_messages.append(line.rstrip('\n')) - - for suite in report[1:]: - report[0][True] += suite[True] - report[0][False] += suite[False] - report[0]['completed'] &= suite['completed'] - - for line in fail_messages: - print(line) - - print() - print('=' * 79) - print('{:72} {:6}'.format('Test suite', 'Result')) - print('-' * 79) - for suite in report[1:]: - if suite['timeout']: - result = 'Timeout' - elif suite[False]: - result = 'Fail' - elif not suite['completed']: - result = 'Crash' - elif suite[True]: - result = 'Success' - else: - result = 'Please, report' - print('{:68} {:>10}'.format(suite['name'], result)) - print('=' * 79) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('--lldb', default='lldb') - parser.add_argument('--host', default='.') - parser.add_argument('--plugin', default='.') - parser.add_argument('--logfiledir', default='.') - parser.add_argument('--assembly', default='Test.exe') - parser.add_argument('--timeout', default=90) - parser.add_argument('--regex', default='t_cmd_') - parser.add_argument('--repeat', default=1) - parser.add_argument('unittest_args', nargs='*') - - args = parser.parse_args() - - lldb = args.lldb - host = args.host - plugin = args.plugin - logfiledir = args.logfiledir - assembly = args.assembly - timeout = int(args.timeout) - regex = args.regex - repeat = int(args.repeat) - print("host: %s" % host) - print("lldb: %s" % lldb) - print("lldb plugin: %s" % plugin) - print("logfiledir: %s" % logfiledir) - print("assembly: %s" % assembly) - print("timeout: %i" % timeout) - print("regex: %s" % regex) - print("repeat: %i" % repeat) - - if os.name != 'posix': - print('OS not supported') - exit(1) - - fail_flag = os.path.join(logfiledir, 'fail_flag') - fail_flag_lldb = os.path.join(logfiledir, 'fail_flag.lldb') - - print("fail_flag: %s" % fail_flag) - print("fail_flag_lldb: %s" % fail_flag_lldb) - - summary_file = os.path.join(logfiledir, 'summary') - print("summary_file: %s" % summary_file) - - try: - os.unlink(summary_file) - except: - pass - - sys.argv[1:] = args.unittest_args - suite = unittest.TestSuite() - all_tests = inspect.getmembers(TestSosCommands, predicate=inspect.ismethod) - for (test_name, test_func) in all_tests: - if re.match(regex, test_name): - suite.addTest(TestSosCommands(test_name)) - unittest.TextTestRunner(verbosity=1).run(suite) - - generate_report() - - if tests_failed: - exit(1) - diff --git a/src/SOS/tests/testsos.sh b/src/SOS/tests/testsos.sh deleted file mode 100755 index e697ee399..000000000 --- a/src/SOS/tests/testsos.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -__ProjectRoot="$1" -__Plugin="$2" -__ManagedBinDir="$3" -__LogFileDir="$4" - -if [[ "$__ProjectRoot" = "" || "$__Plugin" = "" || "$__ManagedBinDir" = "" || "$__LogFileDir" = "" ]]; then - echo "Project root, plugin or log directory required" - exit 1 -fi - -__Host=$__ProjectRoot/.dotnet/dotnet -__TestProgram=$__ManagedBinDir/TestDebuggee/netcoreapp2.0/TestDebuggee.dll - -# Turn on stress logging so the dumplog and histinit commands pass -export COMPlus_LogFacility=0xffffffbf -export COMPlus_LogLevel=6 -export COMPlus_StressLog=1 -export COMPlus_StressLogSize=65536 - -if [[ ! -x "$LLDB_PATH" ]]; then - echo "LLDB_PATH doesn't exist or not executable" - exit 1 -fi - -cd $__ProjectRoot/src/SOS/tests/ -rm -f StressLog.txt -python $__ProjectRoot/src/SOS/tests/test_libsosplugin.py --lldb $LLDB_PATH --host $__Host --plugin $__Plugin --logfiledir $__LogFileDir --assembly $__TestProgram -if [ $? != 0 ]; then - cat $__LogFileDir/*.log - echo "LLDB python tests FAILED" - exit 1 -fi -echo "LLDB python tests PASSED" diff --git a/src/SOS/tests/testutils.py b/src/SOS/tests/testutils.py deleted file mode 100644 index 7d7df276a..000000000 --- a/src/SOS/tests/testutils.py +++ /dev/null @@ -1,209 +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. - -from __future__ import print_function -import lldb -import re -import inspect -import sys -import os -import importlib - -summary_file = '' -fail_flag = '' - -failed = False - -def assertCommon(passed, fatal): - global failed - with open(summary_file, 'a+') as summary: - print(bool(passed), file=summary) - if (not passed): - failed = True - print('!!! test failed:', file=summary) - for s in inspect.stack()[2:]: - print("!!! %s:%i" % (s[1], s[2]), file=summary) - print("!!! %s" % s[4][0], file=summary) - if re.match('\W*t_\w+\.py$', s[1]): - break - print('!!! ', file=summary) - - if fatal: - exit(1) - -def assertTrue(x, fatal=True): - passed = bool(x) - assertCommon(passed, fatal) - - -def assertFalse(x, fatal=True): - passed = not bool(x) - assertCommon(passed, fatal) - - -def assertEqual(x, y, fatal=True): - passed = (x == y) - if not passed: - print(str(x), ' != ', str(y)) - assertCommon(passed, fatal) - - -def assertNotEqual(x, y, fatal=True): - passed = (x != y) - if not passed: - print(str(x), ' == ', str(y)) - assertCommon(passed, fatal) - - -def checkResult(res): - global failed - if not res.Succeeded(): - failed = True - print(res.GetOutput()) - print(res.GetError()) - exit(1) - - -def is_hexnum(s): - try: - int(s, 16) - return True - except ValueError: - return False - - -def exec_and_find(commandInterpreter, cmd, regexp): - res = lldb.SBCommandReturnObject() - commandInterpreter.HandleCommand(cmd, res) - checkResult(res) - - expr = re.compile(regexp) - addr = None - - print(res.GetOutput()) - lines = res.GetOutput().splitlines() - for line in lines: - match = expr.match(line) - if match: - addr = match.group(1) - break - - print("Found addr: " + str(addr)) - return addr - - -def stop_in_main(debugger, assembly): - ci = debugger.GetCommandInterpreter() - target = debugger.GetSelectedTarget() - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - - # Process must be stopped here while libcoreclr loading. - # This test usually fails on release version of coreclr - # since we depend on 'LoadLibraryExW' symbol present. - assertEqual(process.GetState(), lldb.eStateStopped) - - # The reason of this stop must be a breakpoint - assertEqual(process.GetSelectedThread().GetStopReason(), - lldb.eStopReasonBreakpoint) - - ci.HandleCommand("bpmd " + assembly + " Test.Main", res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - assertTrue(res.Succeeded()) - - # Output is not empty - # Should be at least 'Adding pending breakpoints...' - assertTrue(res.GetOutputSize() > 0) - - # Error message is empty - assertTrue(res.GetErrorSize() == 0) - - process.Continue() - # Process must be stopped here if bpmd works at all - assertEqual(process.GetState(), lldb.eStateStopped) - - # The reason of this stop must be a breakpoint - assertEqual(process.GetSelectedThread().GetStopReason(), - lldb.eStopReasonBreakpoint) - - -def exit_lldb(debugger, assembly): - ci = debugger.GetCommandInterpreter() - target = debugger.GetSelectedTarget() - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - - ci.HandleCommand("breakpoint delete --force", res) - out_msg = res.GetOutput() - err_msg = res.GetError() - print(out_msg) - print(err_msg) - # Interpreter must have this command and able to run it - # assertTrue(res.Succeeded()) - - process.Continue() - # Process must exit - assertEqual(process.GetState(), lldb.eStateExited) - - # Process must exit with zero code - assertEqual(process.GetExitStatus(), 0) - - -def get_methoddesc(debugger, assembly, funcname): - ci = debugger.GetCommandInterpreter() - target = debugger.GetSelectedTarget() - process = target.GetProcess() - res = lldb.SBCommandReturnObject() - - ci.HandleCommand("name2ee %s %s" % (assembly, funcname), res) - print(res.GetOutput()) - print(res.GetError()) - # Interpreter must have this command and able to run it - assertTrue(res.Succeeded()) - - output = res.GetOutput() - # Output is not empty - assertTrue(len(output) > 0) - - match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output) - # Line matched - assertTrue(match) - - groups = match.groups() - # Match has a single subgroup - assertEqual(len(groups), 1) - - md_addr = groups[0] - # Address must be a hex number - assertTrue(is_hexnum(md_addr)) - - return md_addr - - -def run(assembly, module): - with open(summary_file, 'a+') as summary: - print('new_suite: %s' % module, file=summary) - - debugger = lldb.debugger - - debugger.SetAsync(False) - target = lldb.target - - debugger.HandleCommand("breakpoint set --name coreclr_execute_assembly") - debugger.HandleCommand("process launch") - - # run the scenario - print("starting scenario...") - i = importlib.import_module(module) - scenarioResult = i.runScenario(os.path.basename(assembly), debugger, target) - - if (target.GetProcess().GetExitStatus() == 0) and not failed: - os.unlink(fail_flag) - - with open(summary_file, 'a+') as summary: - print('Completed!', file=summary) diff --git a/src/inc/bitvector.h b/src/inc/bitvector.h new file mode 100644 index 000000000..a4181dbb8 --- /dev/null +++ b/src/inc/bitvector.h @@ -0,0 +1,462 @@ +// 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. + + +/***************************************************************************/ +/* BitVector.h */ +/***************************************************************************/ +// Routines to support a growable bitvector +/***************************************************************************/ + +#ifndef BITVECTOR_H +#define BITVECTOR_H 1 + + +#ifndef LIMITED_METHOD_CONTRACT +#define LIMITED_METHOD_CONTRACT +#define UNDEF_LIMITED_METHOD_CONTRACT +#endif + +#ifndef WRAPPER_NO_CONTRACT +#define WRAPPER_NO_CONTRACT +#define UNDEF_WRAPPER_NO_CONTRACT +#endif + +#ifndef SUPPORTS_DAC +#define SUPPORTS_DAC +#define UNDEF_SUPPORTS_DAC +#endif + +#ifndef _ASSERTE +#define _ASSERTE(x) +#define UNDEF_ASSERTE +#endif + +#define USE_BITVECTOR 1 +#if USE_BITVECTOR + +/* The bitvector class is meant to be a drop in replacement for an integer + (that is you use it like an integer), however it grows as needed. + + Features: + plug compatible with normal integers; + grows as needed + Optimized for the small case when the vector fits in machine word + Uses one machine word if vector fits in machine word (minus a bit) + + Some caveates: + You should use mutator operators &=, |= ... instead of the + non-mutators whenever possible to avoid creating a temps + + Specifically did NOT supply automatic coersions to + and from short types so that the programmer is aware of + when code was being injected on his behalf. The upshot of this + is that you have to use the BitVector() toUnsigned() to convert +*/ + +/***************************************************************************/ + +class BitVector { + // Set this to be unsigned char to do testing, should be UINT_PTR for real life + + typedef UINT_PTR ChunkType; // The size of integer type that the machine can operate on directly +// typedef BYTE ChunkType; // Use for testing + + // Maximum number of bits in our bitvector +#define MAX_PTRARG_OFS 1024 + + enum { + IS_BIG = 1, // The low bit is used to discrimate m_val and m_vals + CHUNK_BITS = sizeof(ChunkType)*8, // The number of bits that we can manipuate as a chunk + SMALL_BITS = CHUNK_BITS - 1, // The number of bits we can fit in the small representation +// SMALL_BITS = 5, // TESTING ONLY: The number of bits we can fit in the small representation + VALS_COUNT = MAX_PTRARG_OFS / CHUNK_BITS, // The number of ChunkType elements in the Vals array + }; + +public: + BitVector() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + m_val = 0; + } + + BOOL isBig() const + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + return ((m_val & IS_BIG) != 0); + } + + void toBig() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + if (!isBig()) + { + doBigInit(smallBits()); + } + } + + explicit BitVector(ChunkType arg) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (arg > MaxVal) + { + doBigInit(arg); + } + else + { + m_val = ChunkType(arg << 1); + } + } + + BitVector(ChunkType arg, UINT shift) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if ((arg > MaxVal) || (shift >= SMALL_BITS) || (arg > (MaxVal >> shift))) + { + doBigInit(arg); + doBigLeftShiftAssign(shift); + } + else + { + m_val = ChunkType(arg << (shift+1)); + } + } + +#define CONSTRUCT_ptrArgTP(arg,shift) BitVector((arg), (shift)) + + BitVector(const BitVector& arg) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (arg.isBig()) + { + doBigInit(arg); + } + else + { + m_val = arg.m_val; + } + } + + void operator <<=(unsigned shift) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if ((m_val == 0) || (shift == 0)) // Zero is a special case, don't need to do anything + return; + + if (isBig() || (shift >= SMALL_BITS) || (m_val > (MaxVal >> (shift-1)))) + { + doBigLeftShiftAssign(shift); + } + else + { + m_val <<= shift; + } + } + + void operator >>=(unsigned shift) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (isBig()) + { + doBigRightShiftAssign(shift); + } + else + { + m_val >>= shift; + m_val &= ~IS_BIG; // clear the isBig bit if it got set + } + } + + void operator |=(const BitVector& arg) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (((m_val | arg.m_val) & IS_BIG) != 0) + { + doBigOrAssign(arg); + } + else + { + m_val |= arg.m_val; + } + } + + // Note that that is set difference, not subtration + void operator -=(const BitVector& arg) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (((m_val | arg.m_val) & IS_BIG) != 0) + { + doBigDiffAssign(arg); + } + else + { + m_val &= ~arg.m_val; + } + } + + void operator &=(const BitVector& arg) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (((m_val | arg.m_val) & IS_BIG) != 0) + { + doBigAndAssign(arg); + } + else + { + m_val &= arg.m_val; + } + } + + friend void setDiff(BitVector& target, const BitVector& arg) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + target -= arg; + } + + friend BOOL intersect(const BitVector& arg1, const BitVector& arg2) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (((arg1.m_val | arg2.m_val) & IS_BIG) != 0) + { + return arg1.doBigIntersect(arg2); + } + else + { + return ((arg1.m_val & arg2.m_val) != 0); + } + } + + BOOL operator ==(const BitVector& arg) const + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if ((m_val | arg.m_val) & IS_BIG) + { + return doBigEquals(arg); + } + else + { + return m_val == arg.m_val; + } + } + + BOOL operator !=(const BitVector& arg) const + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + return !(*this == arg); + } + + friend ChunkType toUnsigned(const BitVector& arg) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (arg.isBig()) + { + return arg.m_vals.m_chunks[0]; // Note truncation + } + else + { + return arg.smallBits(); + } + } + + // Note that we require the invariant that zero is always stored in the + // small form so that this works bitvector is zero iff (m_val == 0) + friend BOOL isZero(const BitVector& arg) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + return arg.m_val == 0; + } + + /* currently only used in asserts */ + BitVector operator &(const BitVector& arg) const + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + BitVector ret = *this; + ret &= arg; + return ret; + } + + int NumBits() const; + +private: + + static const ChunkType MaxVal = ((ChunkType)1 << SMALL_BITS) - 1; // Maximum value that can be stored in m_val + + // This is the structure that we use when the bit vector overflows. + // It is a simple vector. + struct Vals { + unsigned m_encodedLength; // An encoding of the current length of the 'm_chunks' array + ChunkType m_chunks[VALS_COUNT]; + + BOOL isBig() const + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + return ((m_encodedLength & IS_BIG) != 0); + } + + unsigned GetLength() const + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + if (isBig()) + { + unsigned length = (m_encodedLength >> 1); + _ASSERTE(length > 0); + return length; + } + else + { + return 0; + } + } + + void SetLength(unsigned length) + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + _ASSERTE(length > 0); + _ASSERTE(length <= VALS_COUNT); + + m_encodedLength = (ChunkType) (length << 1); + m_encodedLength |= (ChunkType) IS_BIG; + } + }; + + // + // This is the instance data for the bitvector + // + // We discrimininate on this + union { + ChunkType m_val; // if m_val bit 0 is false, then bits 1-N are the bit vector + Vals m_vals; // if m_val bit 1 is true, then use Vals + }; + + + ChunkType smallBits() const + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + _ASSERTE(!isBig()); + return (m_val >> 1); + } + +#ifdef STRIKE + void doBigInit(ChunkType arg) {} +#else + void doBigInit(ChunkType arg); +#endif + void doBigInit(const BitVector& arg); + void doBigLeftShiftAssign(unsigned arg); + void doBigRightShiftAssign(unsigned arg); + void doBigDiffAssign(const BitVector&); + void doBigAndAssign(const BitVector&); + void doBigOrAssign(const BitVector& arg); + BOOL doBigEquals(const BitVector&) const; + BOOL doBigIntersect(const BitVector&) const; +}; + +typedef BitVector ptrArgTP; + +#else // !USE_BITVECTOR + +typedef unsigned __int64 ptrArgTP; + + // Maximum number of bits in our bitvector +#define MAX_PTRARG_OFS (sizeof(ptrArgTP) * 8) + +#define CONSTRUCT_ptrArgTP(arg,shift) (((ptrArgTP) (arg)) << (shift)) + +inline BOOL isZero(const ptrArgTP& arg) +{ + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + return (arg == 0); +} + +inline ptrArgTP toUnsigned(const ptrArgTP& arg) +{ + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + return arg; +} + +inline void setDiff(ptrArgTP& target, const ptrArgTP& arg) +{ + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + target &= ~arg; +} + +inline BOOL intersect(const ptrArgTP arg1, const ptrArgTP arg2) +{ + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + return ((arg1 & arg2) != 0); +} + +#endif // !USE_BITVECTOR + +#ifdef UNDEF_LIMITED_METHOD_CONTRACT +#undef LIMITED_METHOD_CONTRACT +#undef UNDEF_LIMITED_METHOD_CONTRACT +#endif + +#ifdef UNDEF_WRAPPER_NO_CONTRACT +#undef WRAPPER_NO_CONTRACT +#undef UNDEF_WRAPPER_NO_CONTRACT +#endif + +#ifdef UNDEF_SUPPORTS_DAC +#undef SUPPORTS_DAC +#undef UNDEF_SUPPORTS_DAC +#endif + +#ifdef UNDEF_ASSERTE +#undef _ASSERTE +#undef UNDEF_ASSERTE +#endif + +#endif // BITVECTOR_H