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:
agentOs: Windows_NT
buildReason: Internal
queue:
- name: DotNet-Build
+ name: DotNetCore-Build
demands:
- agent.os -equals Windows_NT
parallel: 2
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:
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:
agentOs: Darwin
buildReason: Internal
queue:
- name: DotNet-Build
+ name: DotNetCore-Build
demands:
- agent.os -equals Darwin
parallel: 2
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageLicenseUrl>http://go.microsoft.com/fwlink/?LinkId=529443</PackageLicenseUrl>
<PackageIconUrl>http://go.microsoft.com/fwlink/?LinkID=288859</PackageIconUrl>
+ <SourceLinkProvider>GitHub</SourceLinkProvider>
<IsPublishable>false</IsPublishable>
<NoPackageAnalysis>true</NoPackageAnalysis>
</PropertyGroup>
[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:
[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):
--- /dev/null
+.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 <davidm@hpl.hp.com>
+
+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.
# 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
:: __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!
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=%*"
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
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
fi
mkdir -p "$__IntermediatesDir"
-mkdir -p "$__LogFileDir"
+mkdir -p "$__LogDir"
mkdir -p "$__CMakeBinDir"
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
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
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
__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
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
# build native components and test both
"$scriptroot/build-native.sh" $@
+if [[ $? != 0 ]]; then
+ exit 1
+fi
fi
"$scriptroot/build.sh" --ci $@
+if [[ $? != 0 ]]; then
+ exit 1
+fi
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
+
{
"sdk": {
- "version": "2.1.300-rc1-008673"
+ "version": "2.1.300"
},
"msbuild-sdks": {
"RoslynTools.RepoToolset": "1.0.0-beta2-62810-01"
+++ /dev/null
-using System;
-using System.IO;
-using System.IO.Compression;
-using System.Net;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-{
- /// <summary>
- /// Acquires the CLI tools from a web endpoint, a local zip/tar.gz, or directly from a local path
- /// </summary>
- public class AcquireDotNetTestStep : TestStep
- {
- /// <summary>
- /// Create a new AcquireDotNetTestStep
- /// </summary>
- /// <param name="remoteDotNetZipPath">
- /// 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</param>
- /// <param name="localDotNetZipPath">
- /// 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</param>
- /// <param name="localDotNetTarPath">
- /// If localDotNetZipPath points to a .tar.gz, this path will be used to store the uncompressed .tar
- /// file. Otherwise this path is unused.</param>
- /// <param name="localDotNetZipExpandDirPath">
- /// If localDotNetZipPath is non-null, this path will be used to store the expanded version of the
- /// archive. Otherwise this path is unused.</param>
- /// <param name="localDotNetPath">
- /// 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.</param>
- /// <param name="cliToolsVersion">
- /// The version string for the CLI tools being downloaded
- /// </param>
- /// <param name="logFilePath">
- /// The path where an activity log for this test step should be written.
- /// </param>
- 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;
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- public string RemoteDotNetPath { get; private set; }
-
- /// <summary>
- /// 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.
- /// </summary>
- public string LocalDotNetZipPath { get; private set; }
-
- /// <summary>
- /// If localDotNetZipPath points to a .tar.gz, this path will be used to store the uncompressed .tar
- /// file. Otherwise null.
- /// </summary>
- public string LocalDotNetTarPath { get; private set; }
-
- /// <summary>
- /// If localDotNetZipPath is non-null, this path will be used to store the expanded version of the
- /// archive. Otherwise null.
- /// </summary>
- public string LocalDotNetZipExpandDirPath { get; private set; }
-
- /// <summary>
- /// The path to the dotnet binary when the test step is complete.
- /// </summary>
- public string LocalDotNetPath { get; private set; }
-
- /// <summary>
- /// The version string for the CLI tools
- /// </summary>
- 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();
- }
-
- }
-}
+++ /dev/null
-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);
- }
- }
- }
-}
-
-
+++ /dev/null
-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
-{
- /// <summary>
- /// This compiler acquires the CLI tools and uses them to build debuggees.
- /// </summary>
- /// <remarks>
- /// 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
- /// </remarks>
- public abstract class BaseDebuggeeCompiler : IDebuggeeCompiler
- {
- AcquireDotNetTestStep _acquireTask;
- DotNetBuildDebuggeeTestStep _buildDebuggeeTask;
-
- /// <summary>
- /// Creates a new BaseDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build debuggees via dotnet build.
- /// </summary>
- /// <param name="config">
- /// 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
- /// </param>
- /// <param name="debuggeeName">
- /// 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
- /// </param>
- public BaseDebuggeeCompiler(TestConfiguration config, string debuggeeName)
- {
- _acquireTask = ConfigureAcquireDotNetTask(config);
- _buildDebuggeeTask = ConfigureDotNetBuildDebuggeeTask(config, _acquireTask.LocalDotNetPath, _acquireTask.CliToolsVersion, debuggeeName);
- }
-
- async public Task<DebuggeeConfiguration> 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<string, string> GetNugetFeeds(TestConfiguration config)
- {
- Dictionary<string, string> nugetFeeds = new Dictionary<string, string>();
- 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 <key>=<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: <DebuggeeSourceRoot>/<DebuggeeName>
- //DebuggeeNativeLibDir: <DebuggeeNativeLibRoot>/<DebuggeeName>
- //DotNetRootBuildDir: <DebuggeeBuildRoot>
- //DebuggeeSolutionDir: <DebuggeeBuildRoot>/<DebuggeeName>
- //DebuggeeProjectDir: <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]
- //DebuggeeBinaryDir: <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]/bin/Debug/<framework>/[<runtime>]
- //DebuggeeBinaryDll: <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]/bin/Debug/<framework>/<DebuggeeName>.dll
- //DebuggeeBinaryExe: <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]/bin/Debug/<framework>/[<runtime>]/<DebuggeeName>.exe
- //LogPath: <DebuggeeBuildRoot>/<DebuggeeName>.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:
- //<DebuggeeBuildRoot>/MyApp/global.json
- //<DebuggeeBuildRoot>/MyApp/MyApp/project.json
- //<DebuggeeBuildRoot>/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);
- }
-}
+++ /dev/null
-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
-{
- /// <summary>
- /// This compiler acquires the CLI tools and uses them to build and optionally link debuggees via dotnet publish.
- /// </summary>
- public class CliDebuggeeCompiler : BaseDebuggeeCompiler
- {
- /// <summary>
- /// Creates a new CliDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build and optionally link debuggees via dotnet publish.
- /// <param name="config">
- /// LinkerPackageVersion If set, this version of the linker package will be used to link the debuggee during publish.
- /// </param>
- /// </summary>
- public CliDebuggeeCompiler(TestConfiguration config, string debuggeeName) : base(config, debuggeeName) {}
-
- private static Dictionary<string,string> GetBuildProperties(TestConfiguration config, string runtime)
- {
- Dictionary<string, string> buildProperties = new Dictionary<string, string>();
- 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));
- }
- }
-}
+++ /dev/null
-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
+++ /dev/null
-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
-{
- /// <summary>
- /// This test step builds debuggees using the dotnet tools with .csproj projects files.
- /// </summary>
- /// <remarks>
- /// Any <debugge name>.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.
- /// </remarks>
- public class CsprojBuildDebuggeeTestStep : DotNetBuildDebuggeeTestStep
- {
- /// <param name="buildProperties">
- /// A mapping from .csproj property strings to their values. These properties will be set when building the debuggee.
- /// </param>
- /// <param name="runtimeIdentifier">
- /// The runtime moniker to be built.
- /// </param>
- public CsprojBuildDebuggeeTestStep(string dotnetToolPath,
- string templateSolutionDirPath,
- string debuggeeNativeLibDirPath,
- Dictionary<string,string> buildProperties,
- string runtimeIdentifier,
- string linkerPackageVersion,
- string debuggeeName,
- string debuggeeSolutionDirPath,
- string debuggeeProjectDirPath,
- string debuggeeBinaryDirPath,
- string debuggeeBinaryDllPath,
- string debuggeeBinaryExePath,
- string nugetPackageCacheDirPath,
- Dictionary<string,string> 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;
- }
-
- /// <summary>
- /// A mapping from .csproj property strings to their values. These properties will be set when building the debuggee.
- /// </summary>
- public IDictionary<string,string> 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);
- }
- }
-}
+++ /dev/null
-using System;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-
-{
- /// <summary>
- /// 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.
- /// </summary>
- public static class DebuggeeCompiler
- {
- async public static Task<DebuggeeConfiguration> 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<DebuggeeConfiguration> 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; }
- }
-}
+++ /dev/null
-<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
-<Project Sdk="RoslynTools.RepoToolset">
- <PropertyGroup>
- <TargetFramework>netcoreapp2.0</TargetFramework>
- <AssemblyName>DebuggerTests</AssemblyName>
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
- <NoWarn>;1591;1701</NoWarn>
- <IsPackable>true</IsPackable>
- <Description>Debugger test support</Description>
- <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
- <PackageTags>Tests</PackageTags>
- </PropertyGroup>
-
- <ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
- <PackageReference Include="xunit" Version="2.3.1" />
- <PackageReference Include="xunit.abstractions" Version="2.0.1" />
- <PackageReference Include="xunit.runner.console" Version="2.3.1" />
- </ItemGroup>
-</Project>
+++ /dev/null
-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
-{
- /// <summary>
- /// This test step builds debuggees using the dotnet tools
- /// </summary>
- /// <remarks>
- /// 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
- /// </remarks>
- public abstract class DotNetBuildDebuggeeTestStep : TestStep
- {
- /// <summary>
- /// Create a new DotNetBuildDebuggeeTestStep.
- /// </summary>
- /// <param name="dotnetToolPath">
- /// The path to the dotnet executable
- /// </param>
- /// <param name="templateSolutionDirPath">
- /// 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.
- /// </param>
- /// <param name="debuggeeNativeLibDirPath">
- /// The path where the debuggee's native binary dependencies will be copied from.
- /// </param>
- /// <param name="debuggeeSolutionDirPath">
- /// The path where the debuggee solution will be created. For single project solutions this will be identical to
- /// the debuggee project directory.
- /// </param>
- /// <param name="debuggeeProjectDirPath">
- /// 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.
- /// </param>
- /// <param name="debuggeeBinaryDirPath">
- /// The directory path where the dotnet tool will place the compiled debuggee binaries.
- /// </param>
- /// <param name="debuggeeBinaryDllPath">
- /// The path where the dotnet tool will place the compiled debuggee assembly.
- /// </param>
- /// <param name="debuggeeBinaryExePath">
- /// The path to which the build will copy the debuggee binary dll with a .exe extension.
- /// </param>
- /// <param name="nugetPackageCacheDirPath">
- /// 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.
- /// </param>
- /// <param name="nugetFeeds">
- /// A mapping of nuget feed names to locations. These feeds will be used to restore debuggee
- /// nuget package dependencies.
- /// </param>
- /// <param name="logPath">
- /// The path where the build output will be logged
- /// </param>
- public DotNetBuildDebuggeeTestStep(string dotnetToolPath,
- string templateSolutionDirPath,
- string debuggeeNativeLibDirPath,
- string debuggeeSolutionDirPath,
- string debuggeeProjectDirPath,
- string debuggeeBinaryDirPath,
- string debuggeeBinaryDllPath,
- string debuggeeBinaryExePath,
- string nugetPackageCacheDirPath,
- Dictionary<string,string> 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");
- }
- }
-
- /// <summary>
- /// The path to the dotnet executable
- /// </summary>
- public string DotNetToolPath { get; private set; }
- /// <summary>
- /// 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.
- /// </summary>
- public string DebuggeeTemplateSolutionDirPath { get; private set; }
- /// <summary>
- /// The path where the debuggee's native binary dependencies will be copied from.
- /// </summary>
- public string DebuggeeNativeLibDirPath { get; private set; }
- /// <summary>
- /// The path where the debuggee solution will be created. For single project solutions this will be identical to
- /// the debuggee project directory.
- /// </summary>
- public string DebuggeeSolutionDirPath { get; private set; }
- /// <summary>
- /// 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.
- /// </summary>
- public string DebuggeeProjectDirPath { get; private set; }
- /// <summary>
- /// The directory path where the dotnet tool will place the compiled debuggee binaries.
- /// </summary>
- public string DebuggeeBinaryDirPath { get; private set; }
- /// <summary>
- /// The path where the dotnet tool will place the compiled debuggee assembly.
- /// </summary>
- public string DebuggeeBinaryDllPath { get; private set; }
- /// <summary>
- /// The path to which the build will copy the debuggee binary dll with a .exe extension.
- /// </summary>
- 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<string,string> 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("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
- sb.AppendLine("<configuration>");
- if(NugetFeeds != null && NugetFeeds.Count > 0)
- {
- sb.AppendLine(" <packageSources>");
- sb.AppendLine(" <clear />");
- foreach(KeyValuePair<string, string> kv in NugetFeeds)
- {
- sb.AppendLine(" <add key=\"" + kv.Key + "\" value=\"" + kv.Value + "\" />");
- }
- sb.AppendLine(" </packageSources>");
- sb.AppendLine(" <activePackageSource>");
- sb.AppendLine(" <add key=\"All\" value=\"(Aggregate source)\" />");
- sb.AppendLine(" </activePackageSource>");
- }
- sb.AppendLine("</configuration>");
-
- 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);
- }
-}
+++ /dev/null
-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
-{
- /// <summary>
- /// An ITestOutputHelper implementation that logs to a file
- /// </summary>
- 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();
- }
- }
- }
-}
+++ /dev/null
-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
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests
-{
- /// <summary>
- /// An implementation of ITestOutputHelper that adds one indent level to
- /// the start of each line
- /// </summary>
- 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);
- }
- }
-}
+++ /dev/null
-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
+++ /dev/null
-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: <DebuggeeSourceRoot>/<DebuggeeName>/[<DebuggeeName>]
- //Binaries: <DebuggeeBuildRoot>/<DebuggeeName>/
- _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<DebuggeeConfiguration> Execute(ITestOutputHelper output)
- {
- return Task.Factory.StartNew<DebuggeeConfiguration>(() => new DebuggeeConfiguration(_sourcePath, _binaryPath, _binaryExePath));
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-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
-{
- /// <summary>
- /// Executes a process and logs the output
- /// </summary>
- /// <remarks>
- /// 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.
- /// </remarks>
- 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<IProcessLogger> _loggers;
- Process _p;
- DateTime _startTime;
- TimeSpan _timeout;
- ITestOutputHelper _traceOutput;
- int? _expectedExitCode;
- TaskCompletionSource<Process> _waitForProcessStartTaskSource;
- Task<int> _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<IProcessLogger>();
- _timeout = TimeSpan.FromMinutes(10);
- _cancelSource = new CancellationTokenSource();
- _killReason = null;
- _waitForProcessStartTaskSource = new TaskCompletionSource<Process>();
- Task<Process> 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<string,string> EnvironmentVariables
- {
- get { lock (_lock) { return new Dictionary<string, string>(_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<int> Run()
- {
- Start();
- return WaitForExit();
- }
-
- public Task<int> 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<int> InternalWaitForExit(Task<Process> 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);
- }
- }
- }
- }
-}
+++ /dev/null
-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<TestRunConfiguration> _instance = new Lazy<TestRunConfiguration>(() => 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<string,string> initialConfig = new Dictionary<string, string>();
- initialConfig["Timestamp"] = GetTimeStampText();
- initialConfig["TempPath"] = Path.GetTempPath();
- initialConfig["WorkingDir"] = GetInitialWorkingDir();
-
- string SkipMdbgStep = Environment.GetEnvironmentVariable("SkipMdbgStep");
- initialConfig["SkipMdbgStep"] = SkipMdbgStep == null ? "false": SkipMdbgStep.ToLowerInvariant();
-
- Dictionary<string, string>[] configs = ParseConfigSettings(new Dictionary<string, string>[] { 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<string,string>[] ParseConfigSettings(Dictionary<string,string>[] templates, XElement node)
- {
- Dictionary<string, string>[] curTemplates = templates;
- foreach(XElement child in node.Elements())
- {
- curTemplates = ParseConfigSetting(curTemplates, child);
- }
- return curTemplates;
- }
-
- Dictionary<string, string>[] ParseConfigSetting(Dictionary<string, string>[] templates, XElement node)
- {
- if (node.Name == "Options")
- {
- List<Dictionary<string, string>> newTemplates = new List<Dictionary<string, string>>();
- foreach (XElement optionNode in node.Elements("Option"))
- {
- Dictionary<string, string>[] templateCopy = templates.Select(c => new Dictionary<string,string>(c)).ToArray();
- newTemplates.AddRange(ParseConfigSettings(templateCopy, optionNode));
- }
- return newTemplates.ToArray();
- }
- else
- {
- foreach(Dictionary<string, string> config in templates)
- {
- string resolveNodeValue = ResolveProperties(config, node.Value);
- config[node.Name.LocalName] = resolveNodeValue;
- }
- return templates;
- }
- }
-
- private string ResolveProperties(Dictionary<string, string> 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<string, string> 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<string, string> _settings;
-
- public TestConfiguration()
- {
- _settings = new Dictionary<string, string>();
- }
-
- public TestConfiguration(Dictionary<string,string> initialSettings)
- {
- _settings = new Dictionary<string, string>(initialSettings);
- }
-
- public IReadOnlyDictionary<string, string> AllSettings
- {
- get { return _settings; }
- }
-
- public TestConfiguration CloneWithNewDebugType(string pdbType)
- {
- Debug.Assert(!string.IsNullOrWhiteSpace(pdbType));
-
- var currentSettings = new Dictionary<string,string>(_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;
- }
- }
- }
-}
+++ /dev/null
-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<KeyValuePair<string,string>> 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
+++ /dev/null
-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
- {
- /// <summary>
- /// Run debuggee (without any debugger) and compare the console output to the regex specified.
- /// </summary>
- /// <param name="config">test config to use</param>
- /// <param name="output">output helper</param>
- /// <param name="testName">test case name</param>
- /// <param name="debuggeeName">debuggee name (no path)</param>
- /// <param name="outputRegex">regex to match on console (standard and error) output</param>
- /// <returns></returns>
- public static async Task<int> 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();
- }
- }
-
- /// <summary>
- /// Returns a test config for each PDB type supported by the product/platform.
- /// </summary>
- /// <param name="config">starting config</param>
- /// <returns>new configs for each supported PDB type</returns>
- public static IEnumerable<TestConfiguration> 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);
- }
- }
- }
-
- /// <summary>
- /// Returns an output helper for the specified config.
- /// </summary>
- /// <param name="config">test config</param>
- /// <param name="output">starting output helper</param>
- /// <param name="testName">test case name</param>
- /// <returns>new output helper</returns>
- 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;
- }
- }
- }
- }
- }
-}
+++ /dev/null
-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
-{
- /// <summary>
- /// 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
- /// </summary>
- 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<TestStepState> 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("</InitialStepState>");
- if(indexOfInitialStepStateElementEnd == -1)
- {
- return false;
- }
- int splitIndex = indexOfInitialStepStateElementEnd + "</InitialStepState>".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<TestStepRunState>(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)
- { }
- }
-}
+++ /dev/null
-using System;
-
-namespace Xunit.Extensions
-{
- public class SkipTestException : Exception
- {
- public SkipTestException(string reason)
- : base(reason) { }
- }
-}
+++ /dev/null
-using Xunit;
-using Xunit.Sdk;
-
-namespace Xunit.Extensions
-{
- [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableFactDiscoverer", "Debugger.Tests")]
- public class SkippableFactAttribute : FactAttribute { }
-}
+++ /dev/null
-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<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
- {
- yield return new SkippableFactTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
- }
- }
-}
+++ /dev/null
-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);
- }
- }
-}
+++ /dev/null
-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<RunSummary> 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;
- }
- }
-}
+++ /dev/null
-using Xunit;
-using Xunit.Sdk;
-
-namespace Xunit.Extensions
-{
- [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableTheoryDiscoverer", "Debugger.Tests")]
- public class SkippableTheoryAttribute : TheoryAttribute { }
-}
+++ /dev/null
-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<IXunitTestCase> 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));
- }
- }
-}
+++ /dev/null
-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<RunSummary> 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;
- }
- }
-}
+++ /dev/null
-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
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Acquires the CLI tools from a web endpoint, a local zip/tar.gz, or directly from a local path
+ /// </summary>
+ public class AcquireDotNetTestStep : TestStep
+ {
+ /// <summary>
+ /// Create a new AcquireDotNetTestStep
+ /// </summary>
+ /// <param name="remoteDotNetZipPath">
+ /// 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</param>
+ /// <param name="localDotNetZipPath">
+ /// 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</param>
+ /// <param name="localDotNetTarPath">
+ /// If localDotNetZipPath points to a .tar.gz, this path will be used to store the uncompressed .tar
+ /// file. Otherwise this path is unused.</param>
+ /// <param name="localDotNetZipExpandDirPath">
+ /// If localDotNetZipPath is non-null, this path will be used to store the expanded version of the
+ /// archive. Otherwise this path is unused.</param>
+ /// <param name="localDotNetPath">
+ /// 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.</param>
+ /// <param name="logFilePath">
+ /// The path where an activity log for this test step should be written.
+ /// </param>
+ ///
+ 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;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public string RemoteDotNetPath { get; private set; }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public string LocalDotNetZipPath { get; private set; }
+
+ /// <summary>
+ /// If localDotNetZipPath points to a .tar.gz, this path will be used to store the uncompressed .tar
+ /// file. Otherwise null.
+ /// </summary>
+ public string LocalDotNetTarPath { get; private set; }
+
+ /// <summary>
+ /// If localDotNetZipPath is non-null, this path will be used to store the expanded version of the
+ /// archive. Otherwise null.
+ /// </summary>
+ public string LocalDotNetZipExpandDirPath { get; private set; }
+
+ /// <summary>
+ /// The path to the dotnet binary when the test step is complete.
+ /// </summary>
+ public string LocalDotNetPath { get; private set; }
+
+ /// <summary>
+ /// Returns true, if there any actual work to do (like downloading, unziping or untaring).
+ /// </summary>
+ 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();
+ }
+
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+ }
+}
+
+
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// This compiler acquires the CLI tools and uses them to build debuggees.
+ /// </summary>
+ /// <remarks>
+ /// 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
+ /// </remarks>
+ public abstract class BaseDebuggeeCompiler : IDebuggeeCompiler
+ {
+ AcquireDotNetTestStep _acquireTask;
+ DotNetBuildDebuggeeTestStep _buildDebuggeeTask;
+
+ /// <summary>
+ /// Creates a new BaseDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build debuggees via dotnet build.
+ /// </summary>
+ /// <param name="config">
+ /// 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
+ /// </param>
+ /// <param name="debuggeeName">
+ /// 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
+ /// </param>
+ public BaseDebuggeeCompiler(TestConfiguration config, string debuggeeName)
+ {
+ _acquireTask = ConfigureAcquireDotNetTask(config);
+ _buildDebuggeeTask = ConfigureDotNetBuildDebuggeeTask(config, _acquireTask.LocalDotNetPath, config.CliVersion, debuggeeName);
+ }
+
+ async public Task<DebuggeeConfiguration> 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<string, string> GetNugetFeeds(TestConfiguration config)
+ {
+ Dictionary<string, string> nugetFeeds = new Dictionary<string, string>();
+ 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 <key>=<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: <DebuggeeSourceRoot>/<DebuggeeName>
+ //DebuggeeNativeLibDir: <DebuggeeNativeLibRoot>/<DebuggeeName>
+ //DotNetRootBuildDir: <DebuggeeBuildRoot>
+ //DebuggeeSolutionDir: <DebuggeeBuildRoot>/<DebuggeeName>
+ //DebuggeeProjectDir: <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]
+ //DebuggeeBinaryDir: <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]/bin/Debug/<framework>/[<runtime>]
+ //DebuggeeBinaryDll: <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]/bin/Debug/<framework>/<DebuggeeName>.dll
+ //DebuggeeBinaryExe: <DebuggeeBuildRoot>/<DebuggeeName>[/<DebuggeeName>]/bin/Debug/<framework>/[<runtime>]/<DebuggeeName>.exe
+ //LogPath: <DebuggeeBuildRoot>/<DebuggeeName>.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:
+ //<DebuggeeBuildRoot>/MyApp/global.json
+ //<DebuggeeBuildRoot>/MyApp/MyApp/project.json
+ //<DebuggeeBuildRoot>/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);
+ }
+}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// This compiler acquires the CLI tools and uses them to build and optionally link debuggees via dotnet publish.
+ /// </summary>
+ public class CliDebuggeeCompiler : BaseDebuggeeCompiler
+ {
+ /// <summary>
+ /// Creates a new CliDebuggeeCompiler. This compiler acquires the CLI tools and uses them to build and optionally link debuggees via dotnet publish.
+ /// <param name="config">
+ /// LinkerPackageVersion If set, this version of the linker package will be used to link the debuggee during publish.
+ /// </param>
+ /// </summary>
+ public CliDebuggeeCompiler(TestConfiguration config, string debuggeeName) : base(config, debuggeeName) {}
+
+ private static Dictionary<string,string> GetBuildProperties(TestConfiguration config, string runtimeIdentifier)
+ {
+ Dictionary<string, string> buildProperties = new Dictionary<string, string>();
+ 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));
+ }
+ }
+}
--- /dev/null
+// 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
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// This test step builds debuggees using the dotnet tools with .csproj projects files.
+ /// </summary>
+ /// <remarks>
+ /// Any <debugge name>.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.
+ /// </remarks>
+ public class CsprojBuildDebuggeeTestStep : DotNetBuildDebuggeeTestStep
+ {
+ /// <param name="buildProperties">
+ /// A mapping from .csproj property strings to their values. These properties will be set when building the debuggee.
+ /// </param>
+ /// <param name="runtimeIdentifier">
+ /// The runtime moniker to be built.
+ /// </param>
+ public CsprojBuildDebuggeeTestStep(string dotnetToolPath,
+ string templateSolutionDirPath,
+ string debuggeeNativeLibDirPath,
+ Dictionary<string,string> buildProperties,
+ string runtimeIdentifier,
+ string linkerPackageVersion,
+ string debuggeeName,
+ string debuggeeSolutionDirPath,
+ string debuggeeProjectDirPath,
+ string debuggeeBinaryDirPath,
+ string debuggeeBinaryDllPath,
+ string debuggeeBinaryExePath,
+ string nugetPackageCacheDirPath,
+ Dictionary<string,string> 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;
+ }
+
+ /// <summary>
+ /// A mapping from .csproj property strings to their values. These properties will be set when building the debuggee.
+ /// </summary>
+ public IDictionary<string,string> 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);
+ }
+ }
+}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public static class DebuggeeCompiler
+ {
+ async public static Task<DebuggeeConfiguration> 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<DebuggeeConfiguration> 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; }
+ }
+}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// This test step builds debuggees using the dotnet tools
+ /// </summary>
+ /// <remarks>
+ /// 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
+ /// </remarks>
+ public abstract class DotNetBuildDebuggeeTestStep : TestStep
+ {
+ /// <summary>
+ /// Create a new DotNetBuildDebuggeeTestStep.
+ /// </summary>
+ /// <param name="dotnetToolPath">
+ /// The path to the dotnet executable
+ /// </param>
+ /// <param name="templateSolutionDirPath">
+ /// 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.
+ /// </param>
+ /// <param name="debuggeeNativeLibDirPath">
+ /// The path where the debuggee's native binary dependencies will be copied from.
+ /// </param>
+ /// <param name="debuggeeSolutionDirPath">
+ /// The path where the debuggee solution will be created. For single project solutions this will be identical to
+ /// the debuggee project directory.
+ /// </param>
+ /// <param name="debuggeeProjectDirPath">
+ /// 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.
+ /// </param>
+ /// <param name="debuggeeBinaryDirPath">
+ /// The directory path where the dotnet tool will place the compiled debuggee binaries.
+ /// </param>
+ /// <param name="debuggeeBinaryDllPath">
+ /// The path where the dotnet tool will place the compiled debuggee assembly.
+ /// </param>
+ /// <param name="debuggeeBinaryExePath">
+ /// The path to which the build will copy the debuggee binary dll with a .exe extension.
+ /// </param>
+ /// <param name="nugetPackageCacheDirPath">
+ /// 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.
+ /// </param>
+ /// <param name="nugetFeeds">
+ /// A mapping of nuget feed names to locations. These feeds will be used to restore debuggee
+ /// nuget package dependencies.
+ /// </param>
+ /// <param name="logPath">
+ /// The path where the build output will be logged
+ /// </param>
+ public DotNetBuildDebuggeeTestStep(string dotnetToolPath,
+ string templateSolutionDirPath,
+ string debuggeeNativeLibDirPath,
+ string debuggeeSolutionDirPath,
+ string debuggeeProjectDirPath,
+ string debuggeeBinaryDirPath,
+ string debuggeeBinaryDllPath,
+ string debuggeeBinaryExePath,
+ string nugetPackageCacheDirPath,
+ Dictionary<string,string> 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");
+ }
+ }
+
+ /// <summary>
+ /// The path to the dotnet executable
+ /// </summary>
+ public string DotNetToolPath { get; private set; }
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public string DebuggeeTemplateSolutionDirPath { get; private set; }
+ /// <summary>
+ /// The path where the debuggee's native binary dependencies will be copied from.
+ /// </summary>
+ public string DebuggeeNativeLibDirPath { get; private set; }
+ /// <summary>
+ /// The path where the debuggee solution will be created. For single project solutions this will be identical to
+ /// the debuggee project directory.
+ /// </summary>
+ public string DebuggeeSolutionDirPath { get; private set; }
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public string DebuggeeProjectDirPath { get; private set; }
+ /// <summary>
+ /// The directory path where the dotnet tool will place the compiled debuggee binaries.
+ /// </summary>
+ public string DebuggeeBinaryDirPath { get; private set; }
+ /// <summary>
+ /// The path where the dotnet tool will place the compiled debuggee assembly.
+ /// </summary>
+ public string DebuggeeBinaryDllPath { get; private set; }
+ /// <summary>
+ /// The path to which the build will copy the debuggee binary dll with a .exe extension.
+ /// </summary>
+ 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<string,string> 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("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+ sb.AppendLine("<configuration>");
+ if(NugetFeeds != null && NugetFeeds.Count > 0)
+ {
+ sb.AppendLine(" <packageSources>");
+ sb.AppendLine(" <clear />");
+ foreach(KeyValuePair<string, string> kv in NugetFeeds)
+ {
+ sb.AppendLine(" <add key=\"" + kv.Key + "\" value=\"" + kv.Value + "\" />");
+ }
+ sb.AppendLine(" </packageSources>");
+ sb.AppendLine(" <activePackageSource>");
+ sb.AppendLine(" <add key=\"All\" value=\"(Aggregate source)\" />");
+ sb.AppendLine(" </activePackageSource>");
+ }
+ sb.AppendLine("</configuration>");
+
+ 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);
+ }
+}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// An ITestOutputHelper implementation that logs to a file
+ /// </summary>
+ 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();
+ }
+ }
+ }
+}
--- /dev/null
+// 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
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// An implementation of ITestOutputHelper that adds one indent level to
+ /// the start of each line
+ /// </summary>
+ 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);
+ }
+ }
+}
--- /dev/null
+<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
+<Project Sdk="RoslynTools.RepoToolset">
+ <PropertyGroup>
+ <TargetFramework>netcoreapp2.0</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <NoWarn>;1591;1701</NoWarn>
+ <IsPackable>true</IsPackable>
+ <Description>Diagnostic test support</Description>
+ <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
+ <PackageTags>tests</PackageTags>
+ <DefineConstants>$(DefineConstants);CORE_CLR</DefineConstants>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.0-dev" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
+ <PackageReference Include="xunit" Version="2.3.1" />
+ <PackageReference Include="xunit.abstractions" Version="2.0.1" />
+ <PackageReference Include="xunit.runner.console" Version="2.3.1" />
+ </ItemGroup>
+</Project>
--- /dev/null
+// 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
--- /dev/null
+// 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: <DebuggeeSourceRoot>/<DebuggeeName>/[<DebuggeeName>]
+ //Binaries: <DebuggeeBuildRoot>/<DebuggeeName>/
+ _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<DebuggeeConfiguration> Execute(ITestOutputHelper output)
+ {
+ return Task.Factory.StartNew<DebuggeeConfiguration>(() => new DebuggeeConfiguration(_sourcePath, _binaryPath, _binaryExePath));
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Executes a process and logs the output
+ /// </summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ 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<IProcessLogger> _loggers;
+ Process _p;
+ DateTime _startTime;
+ TimeSpan _timeout;
+ ITestOutputHelper _traceOutput;
+ int? _expectedExitCode;
+ TaskCompletionSource<Process> _waitForProcessStartTaskSource;
+ Task<int> _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<IProcessLogger>();
+ _timeout = TimeSpan.FromMinutes(10);
+ _cancelSource = new CancellationTokenSource();
+ _killReason = null;
+ _waitForProcessStartTaskSource = new TaskCompletionSource<Process>();
+ Task<Process> 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<string,string> EnvironmentVariables
+ {
+ get { lock (_lock) { return new Dictionary<string, string>(_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<int> Run()
+ {
+ Start();
+ return WaitForExit();
+ }
+
+ public Task<int> 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<int> InternalWaitForExit(Task<Process> 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);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Represents the all the test configurations for a test run.
+ /// </summary>
+ public class TestRunConfiguration : IDisposable
+ {
+ public static TestRunConfiguration Instance
+ {
+ get { return _instance.Value; }
+ }
+
+ static Lazy<TestRunConfiguration> _instance = new Lazy<TestRunConfiguration>(() => 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<TestConfiguration> 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<string, string> initialConfig = new Dictionary<string, string>
+ {
+ ["Timestamp"] = GetTimeStampText(),
+ ["TempPath"] = Path.GetTempPath(),
+ ["WorkingDir"] = GetInitialWorkingDir(),
+ ["OS"] = OS.Kind.ToString(),
+ ["TargetArchitecture"] = OS.TargetArchitecture.ToString().ToLowerInvariant(),
+ ["NuGetPackageCacheDir"] = nugetPackages
+ };
+ IEnumerable<Dictionary<string, string>> configs = ParseConfigFile(path, new Dictionary<string, string>[] { initialConfig });
+ Configurations = configs.Select(c => new TestConfiguration(c));
+ }
+
+ Dictionary<string, string>[] ParseConfigFile(string path, Dictionary<string, string>[] 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<string, string>[] ParseConfigSettings(Dictionary<string, string>[] templates, XElement node)
+ {
+ Dictionary<string, string>[] currentTemplates = templates;
+ foreach (XElement child in node.Elements())
+ {
+ currentTemplates = ParseConfigSetting(currentTemplates, child);
+ }
+ return currentTemplates;
+ }
+
+ Dictionary<string, string>[] ParseConfigSetting(Dictionary<string, string>[] 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<string, string> currentTemplate = templates.Last();
+
+ switch (node.Name.LocalName)
+ {
+ case "Options":
+ if (EvaluateConditional(currentTemplate, node))
+ {
+ List<Dictionary<string, string>> newTemplates = new List<Dictionary<string, string>>();
+ foreach (XElement optionNode in node.Elements("Option"))
+ {
+ if (EvaluateConditional(currentTemplate, optionNode))
+ {
+ IEnumerable<Dictionary<string, string>> templateCopy = templates.Select(c => new Dictionary<string, string>(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<string, string> 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<string, string> 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<string, string> 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<string, string> config, string propName)
+ {
+ return propName.Equals("WinDir", StringComparison.OrdinalIgnoreCase)
+ ? Path.GetFullPath(Environment.ExpandEnvironmentVariables("%WINDIR%"))
+ : config[propName] ?? "";
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ /// <summary>
+ /// Represents the current test configuration
+ /// </summary>
+ public class TestConfiguration
+ {
+ const string DebugTypeKey = "DebugType";
+ const string DebuggeeBuildRootKey = "DebuggeeBuildRoot";
+
+ internal static readonly string BaseDir = Path.GetFullPath(".");
+
+ private Dictionary<string, string> _settings;
+
+ public TestConfiguration()
+ {
+ _settings = new Dictionary<string, string>();
+ }
+
+ public TestConfiguration(Dictionary<string,string> initialSettings)
+ {
+ _settings = new Dictionary<string, string>(initialSettings);
+ }
+
+ public IReadOnlyDictionary<string, string> AllSettings
+ {
+ get { return _settings; }
+ }
+
+ public TestConfiguration CloneWithNewDebugType(string pdbType)
+ {
+ Debug.Assert(!string.IsNullOrWhiteSpace(pdbType));
+
+ var currentSettings = new Dictionary<string,string>(_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);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public string TargetArchitecture
+ {
+ get { return GetValue("TargetArchitecture").ToLowerInvariant(); }
+ }
+
+ /// <summary>
+ /// The product "projectk" (.NET Core) or "desktop".
+ /// </summary>
+ public string TestProduct
+ {
+ get { return GetValue("TestProduct").ToLowerInvariant(); }
+ }
+
+ /// <summary>
+ /// The test runner script directory
+ /// </summary>
+ public string ScriptRootDir
+ {
+ get { return MakeCanonicalPath(GetValue("ScriptRootDir")); }
+ }
+
+ /// <summary>
+ /// Working temporary directory.
+ /// </summary>
+ public string WorkingDir
+ {
+ get { return MakeCanonicalPath(GetValue("WorkingDir")); }
+ }
+
+ /// <summary>
+ /// The host program to run a .NET Core or null for desktop/no host.
+ /// </summary>
+ public string HostExe
+ {
+ get { return MakeCanonicalExePath(GetValue("HostExe")); }
+ }
+
+ /// <summary>
+ /// Arguments to the HostExe.
+ /// </summary>
+ public string HostArgs
+ {
+ get { return GetValue("HostArgs"); }
+ }
+
+ /// <summary>
+ /// Environment variables to pass to the target process (via the ProcessRunner).
+ /// </summary>
+ public string HostEnvVars
+ {
+ get { return GetValue("HostEnvVars"); }
+ }
+
+ /// <summary>
+ /// Add the host environment variables to the process runner.
+ /// </summary>
+ /// <param name="runner">process runner instance</param>
+ 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]);
+ }
+ }
+ }
+
+ /// <summary>
+ /// The directory to the runtime (coreclr.dll, etc.) symbols
+ /// </summary>
+ public string RuntimeSymbolsPath
+ {
+ get { return MakeCanonicalPath(GetValue("RuntimeSymbolsPath")); }
+ }
+
+ /// <summary>
+ /// The root of the debuggees (managed and native)
+ /// </summary>
+ public string DebuggeeRootDir
+ {
+ get { return MakeCanonicalPath(GetValue("DebuggeeRootDir")); }
+ }
+
+ /// <summary>
+ /// How the debuggees are built: "prebuilt" or "cli" (builds the debuggee during the test run with build and cli configuration).
+ /// </summary>
+ public string DebuggeeBuildProcess
+ {
+ get { return GetValue("DebuggeeBuildProcess")?.ToLowerInvariant(); }
+ }
+
+ /// <summary>
+ /// Debuggee sources and template project file will be retrieved from here: <DebuggeeSourceRoot>/<DebuggeeName>/[<DebuggeeName>]
+ /// </summary>
+ public string DebuggeeSourceRoot
+ {
+ get { return MakeCanonicalPath(GetValue("DebuggeeSourceRoot")); }
+ }
+
+ /// <summary>
+ /// Debuggee final sources/project file/binary outputs will be placed here: <DebuggeeBuildRoot>/<DebuggeeName>/
+ /// </summary>
+ public string DebuggeeBuildRoot
+ {
+ get { return MakeCanonicalPath(GetValue(DebuggeeBuildRootKey)); }
+ }
+
+ /// <summary>
+ /// Debuggee native binary dependencies will be retrieved from here.
+ /// </summary>
+ public string DebuggeeNativeLibRoot
+ {
+ get { return MakeCanonicalPath(GetValue("DebuggeeNativeLibRoot")); }
+ }
+
+ /// <summary>
+ /// The version of the Microsoft.NETCore.App package to reference when building the debuggee.
+ /// </summary>
+ public string BuildProjectMicrosoftNetCoreAppVersion
+ {
+ get { return GetValue("BuildProjectMicrosoftNetCoreAppVersion"); }
+ }
+
+ /// <summary>
+ /// The framework type/version used to build the debuggee like "netcoreapp2.0" or "netstandard1.0".
+ /// </summary>
+ public string BuildProjectFramework
+ {
+ get { return GetValue("BuildProjectFramework"); }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public string BuildProjectRuntime
+ {
+ get { return GetValue("BuildProjectRuntime"); }
+ }
+
+ /// <summary>
+ /// The type of PDB: "full" (Windows PDB) or "portable".
+ /// </summary>
+ public string DebugType
+ {
+ get { return GetValue(DebugTypeKey); }
+ }
+
+ /// <summary>
+ /// Either the local path to the dotnet cli to build or the URL of the runtime to download and install.
+ /// </summary>
+ public string CliPath
+ {
+ get { return MakeCanonicalPath(GetValue("CliPath")); }
+ }
+
+ /// <summary>
+ /// The local path to put the downloaded and decompressed runtime.
+ /// </summary>
+ public string CliCacheRoot
+ {
+ get { return MakeCanonicalPath(GetValue("CliCacheRoot")); }
+ }
+
+ /// <summary>
+ /// The version (i.e. 2.0.0) of the dotnet cli to use.
+ /// </summary>
+ public string CliVersion
+ {
+ get { return GetValue("CliVersion"); }
+ }
+
+ /// <summary>
+ /// The directory to cache the nuget packages on restore
+ /// </summary>
+ public string NuGetPackageCacheDir
+ {
+ get { return MakeCanonicalPath(GetValue("NuGetPackageCacheDir")); }
+ }
+
+ /// <summary>
+ /// The nuget package feeds separated by semicolons.
+ /// </summary>
+ public string NuGetPackageFeeds
+ {
+ get { return GetValue("NuGetPackageFeeds"); }
+ }
+
+ /// <summary>
+ /// If true, log the test output, etc. to the console.
+ /// </summary>
+ public bool LogToConsole
+ {
+ get { return bool.TryParse(GetValue("LogToConsole"), out bool b) && b; }
+ }
+
+ /// <summary>
+ /// The directory to put the test logs.
+ /// </summary>
+ public string LogDirPath
+ {
+ get { return MakeCanonicalPath(GetValue("LogDir")); }
+ }
+
+ /// <summary>
+ /// The "ILLink.Tasks" package version to reference or null.
+ /// </summary>
+ public string LinkerPackageVersion
+ {
+ get { return GetValue("LinkerPackageVersion"); }
+ }
+
+ /// <summary>
+ /// Returns the configuration value for the key or null.
+ /// </summary>
+ /// <param name="key">name of the configuration value</param>
+ /// <returns>configuration value or null</returns>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// The OS running
+ /// </summary>
+ public enum OSKind
+ {
+ Windows,
+ Linux,
+ OSX,
+ Unknown,
+ }
+
+ /// <summary>
+ /// The OS specific configuration
+ /// </summary>
+ 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
+ }
+
+ /// <summary>
+ /// The OS the tests are running.
+ /// </summary>
+ public static OSKind Kind { get; private set; }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public static Architecture TargetArchitecture { get { return System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture; } }
+ }
+}
--- /dev/null
+// 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<KeyValuePair<string,string>> 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
--- /dev/null
+// 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
+ {
+ /// <summary>
+ /// Run debuggee (without any debugger) and compare the console output to the regex specified.
+ /// </summary>
+ /// <param name="config">test config to use</param>
+ /// <param name="output">output helper</param>
+ /// <param name="testName">test case name</param>
+ /// <param name="debuggeeName">debuggee name (no path)</param>
+ /// <param name="outputRegex">regex to match on console (standard and error) output</param>
+ /// <returns></returns>
+ public static async Task<int> 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();
+ }
+ }
+
+ /// <summary>
+ /// Returns a test config for each PDB type supported by the product/platform.
+ /// </summary>
+ /// <param name="config">starting config</param>
+ /// <returns>new configs for each supported PDB type</returns>
+ public static IEnumerable<TestConfiguration> 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);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns an output helper for the specified config.
+ /// </summary>
+ /// <param name="config">test config</param>
+ /// <param name="output">starting output helper</param>
+ /// <param name="testName">test case name</param>
+ /// <returns>new output helper</returns>
+ 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;
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// 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
+ /// </summary>
+ 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<TestStepState> 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("</InitialStepState>");
+ if(indexOfInitialStepStateElementEnd == -1)
+ {
+ return false;
+ }
+ int splitIndex = indexOfInitialStepStateElementEnd + "</InitialStepState>".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<TestStepRunState>(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)
+ { }
+ }
+}
--- /dev/null
+using System;
+
+namespace Xunit.Extensions
+{
+ public class SkipTestException : Exception
+ {
+ public SkipTestException(string reason)
+ : base(reason) { }
+ }
+}
--- /dev/null
+using Xunit;
+using Xunit.Sdk;
+
+namespace Xunit.Extensions
+{
+ [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableFactDiscoverer", "Microsoft.Diagnostic.TestHelpers")]
+ public class SkippableFactAttribute : FactAttribute { }
+}
--- /dev/null
+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<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
+ {
+ yield return new SkippableFactTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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<RunSummary> 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;
+ }
+ }
+}
--- /dev/null
+using Xunit.Sdk;
+
+namespace Xunit.Extensions
+{
+ [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableTheoryDiscoverer", "Microsoft.Diagnostic.TestHelpers")]
+ public class SkippableTheoryAttribute : TheoryAttribute { }
+}
--- /dev/null
+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<IXunitTestCase> 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));
+ }
+ }
+}
--- /dev/null
+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<RunSummary> 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;
+ }
+ }
+}
--- /dev/null
+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
add_definitions(-D_SECURE_SCL=0)
-add_subdirectory(NETCore)
+add_subdirectory(SOS.NETCore)
add_subdirectory(Strike)
if(CLR_CMAKE_PLATFORM_UNIX)
+++ /dev/null
-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()
+++ /dev/null
-<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
-<Project Sdk="RoslynTools.RepoToolset">
- <PropertyGroup>
- <TargetFramework>netcoreapp1.0</TargetFramework>
- <AssemblyName>SOS.NETCore</AssemblyName>
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
- <NoWarn>;1591;1701</NoWarn>
- <IsPackable>true</IsPackable>
- <Description>Managed SOS Services</Description>
- <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
- <PackageTags>SOS</PackageTags>
- </PropertyGroup>
-
- <ItemGroup>
- <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
- </ItemGroup>
-</Project>
+++ /dev/null
-// 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;
-
- }
-
- /// <summary>
- /// Read memory callback
- /// </summary>
- /// <returns>number of bytes read or 0 for error</returns>
- 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();
- }
-
- /// <summary>
- /// Stream implementation to read debugger target memory for in-memory PDBs
- /// </summary>
- 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();
- }
- }
-
- /// <summary>
- /// Quick fix for Path.GetFileName which incorrectly handles Windows-style paths on Linux
- /// </summary>
- /// <param name="pathName"> File path to be processed </param>
- /// <returns>Last component of path</returns>
- private static string GetFileName(string pathName)
- {
- int pos = pathName.LastIndexOfAny(new char[] { '/', '\\'});
- if (pos < 0)
- return pathName;
- return pathName.Substring(pos + 1);
- }
-
- /// <summary>
- /// Checks availability of debugging information for given assembly.
- /// </summary>
- /// <param name="assemblyPath">
- /// File path of the assembly or null if the module is in-memory or dynamic (generated by Reflection.Emit)
- /// </param>
- /// <param name="isFileLayout">type of in-memory PE layout, if true, file based layout otherwise, loaded layout</param>
- /// <param name="loadedPeAddress">
- /// 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 <paramref name="inMemoryPdbAddress"/> and <paramref name="inMemoryPdbSize"/>).
- /// </param>
- /// <param name="loadedPeSize">loaded PE image size</param>
- /// <param name="inMemoryPdbAddress">in memory PDB address or zero</param>
- /// <param name="inMemoryPdbSize">in memory PDB size</param>
- /// <returns>Symbol reader handle or zero if error</returns>
- 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;
- }
-
- /// <summary>
- /// Cleanup and dispose of symbol reader handle
- /// </summary>
- /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
- internal static void Dispose(IntPtr symbolReaderHandle)
- {
- Debug.Assert(symbolReaderHandle != IntPtr.Zero);
- try
- {
- GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle);
- ((OpenedReader)gch.Target).Dispose();
- gch.Free();
- }
- catch
- {
- }
- }
-
- /// <summary>
- /// Returns method token and IL offset for given source line number.
- /// </summary>
- /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
- /// <param name="filePath">source file name and path</param>
- /// <param name="lineNumber">source line number</param>
- /// <param name="methodToken">method token return</param>
- /// <param name="ilOffset">IL offset return</param>
- /// <returns> true if information is available</returns>
- 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;
- }
-
- /// <summary>
- /// Returns source line number and source file name for given IL offset and method token.
- /// </summary>
- /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
- /// <param name="methodToken">method token</param>
- /// <param name="ilOffset">IL offset</param>
- /// <param name="lineNumber">source line number return</param>
- /// <param name="fileName">source file name return</param>
- /// <returns> true if information is available</returns>
- 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;
- }
-
- /// <summary>
- /// Helper method to return source line number and source file name for given IL offset and method token.
- /// </summary>
- /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
- /// <param name="methodToken">method token</param>
- /// <param name="ilOffset">IL offset</param>
- /// <param name="lineNumber">source line number return</param>
- /// <param name="fileName">source file name return</param>
- /// <returns> true if information is available</returns>
- 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;
- }
-
- /// <summary>
- /// Returns local variable name for given local index and IL offset.
- /// </summary>
- /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
- /// <param name="methodToken">method token</param>
- /// <param name="localIndex">local variable index</param>
- /// <param name="localVarName">local variable name return</param>
- /// <returns>true if name has been found</returns>
- 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;
- }
-
- /// <summary>
- /// Helper method to return local variable name for given local index and IL offset.
- /// </summary>
- /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
- /// <param name="methodToken">method token</param>
- /// <param name="localIndex">local variable index</param>
- /// <param name="localVarName">local variable name return</param>
- /// <returns>true if name has been found</returns>
- 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<LocalVarInfo> 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<LocalVarInfo>();
-
- 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;
-
- }
- /// <summary>
- /// Returns source name, line numbers and IL offsets for given method token.
- /// </summary>
- /// <param name="assemblyPath">file path of the assembly</param>
- /// <param name="methodToken">method token</param>
- /// <param name="debugInfo">structure with debug information return</param>
- /// <returns>true if information is available</returns>
- /// <remarks>used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs</remarks>
- internal static bool GetInfoForMethod(string assemblyPath, int methodToken, ref MethodDebugInfo debugInfo)
- {
- try
- {
- List<DebugInfo> points = null;
- List<LocalVarInfo> locals = null;
-
- if (!GetDebugInfoForMethod(assemblyPath, methodToken, out points))
- {
- return false;
- }
-
- if (!GetLocalsInfoForMethod(assemblyPath, methodToken, out locals))
- {
- return false;
- }
- var structSize = Marshal.SizeOf<DebugInfo>();
-
- 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<LocalVarInfo>();
-
- 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;
- }
-
- /// <summary>
- /// Helper method to return source name, line numbers and IL offsets for given method token.
- /// </summary>
- /// <param name="assemblyPath">file path of the assembly</param>
- /// <param name="methodToken">method token</param>
- /// <param name="points">list of debug information for each sequence point return</param>
- /// <returns>true if information is available</returns>
- /// <remarks>used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs</remarks>
- private static bool GetDebugInfoForMethod(string assemblyPath, int methodToken, out List<DebugInfo> 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<DebugInfo>();
- 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;
- }
-
- /// <summary>
- /// Returns the portable PDB reader for the assembly path
- /// </summary>
- /// <param name="assemblyPath">file path of the assembly or null if the module is in-memory or dynamic</param>
- /// <param name="isFileLayout">type of in-memory PE layout, if true, file based layout otherwise, loaded layout</param>
- /// <param name="peStream">optional in-memory PE stream</param>
- /// <param name="pdbStream">optional in-memory PDB stream</param>
- /// <returns>reader/provider wrapper instance</returns>
- /// <remarks>
- /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around.
- /// </remarks>
- 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;
- }
- }
- }
-}
--- /dev/null
+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()
--- /dev/null
+<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
+<Project Sdk="RoslynTools.RepoToolset">
+ <PropertyGroup>
+ <TargetFramework>netcoreapp1.0</TargetFramework>
+ <AssemblyName>SOS.NETCore</AssemblyName>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <NoWarn>;1591;1701</NoWarn>
+ <IsPackable>true</IsPackable>
+ <Description>Managed SOS Services</Description>
+ <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
+ <PackageTags>SOS</PackageTags>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
+ </ItemGroup>
+</Project>
--- /dev/null
+// 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;
+
+ }
+
+ /// <summary>
+ /// Read memory callback
+ /// </summary>
+ /// <returns>number of bytes read or 0 for error</returns>
+ 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();
+ }
+
+ /// <summary>
+ /// Stream implementation to read debugger target memory for in-memory PDBs
+ /// </summary>
+ 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();
+ }
+ }
+
+ /// <summary>
+ /// Quick fix for Path.GetFileName which incorrectly handles Windows-style paths on Linux
+ /// </summary>
+ /// <param name="pathName"> File path to be processed </param>
+ /// <returns>Last component of path</returns>
+ private static string GetFileName(string pathName)
+ {
+ int pos = pathName.LastIndexOfAny(new char[] { '/', '\\'});
+ if (pos < 0)
+ return pathName;
+ return pathName.Substring(pos + 1);
+ }
+
+ /// <summary>
+ /// Checks availability of debugging information for given assembly.
+ /// </summary>
+ /// <param name="assemblyPath">
+ /// File path of the assembly or null if the module is in-memory or dynamic (generated by Reflection.Emit)
+ /// </param>
+ /// <param name="isFileLayout">type of in-memory PE layout, if true, file based layout otherwise, loaded layout</param>
+ /// <param name="loadedPeAddress">
+ /// 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 <paramref name="inMemoryPdbAddress"/> and <paramref name="inMemoryPdbSize"/>).
+ /// </param>
+ /// <param name="loadedPeSize">loaded PE image size</param>
+ /// <param name="inMemoryPdbAddress">in memory PDB address or zero</param>
+ /// <param name="inMemoryPdbSize">in memory PDB size</param>
+ /// <returns>Symbol reader handle or zero if error</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Cleanup and dispose of symbol reader handle
+ /// </summary>
+ /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+ internal static void Dispose(IntPtr symbolReaderHandle)
+ {
+ Debug.Assert(symbolReaderHandle != IntPtr.Zero);
+ try
+ {
+ GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle);
+ ((OpenedReader)gch.Target).Dispose();
+ gch.Free();
+ }
+ catch
+ {
+ }
+ }
+
+ /// <summary>
+ /// Returns method token and IL offset for given source line number.
+ /// </summary>
+ /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+ /// <param name="filePath">source file name and path</param>
+ /// <param name="lineNumber">source line number</param>
+ /// <param name="methodToken">method token return</param>
+ /// <param name="ilOffset">IL offset return</param>
+ /// <returns> true if information is available</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Returns source line number and source file name for given IL offset and method token.
+ /// </summary>
+ /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+ /// <param name="methodToken">method token</param>
+ /// <param name="ilOffset">IL offset</param>
+ /// <param name="lineNumber">source line number return</param>
+ /// <param name="fileName">source file name return</param>
+ /// <returns> true if information is available</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Helper method to return source line number and source file name for given IL offset and method token.
+ /// </summary>
+ /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+ /// <param name="methodToken">method token</param>
+ /// <param name="ilOffset">IL offset</param>
+ /// <param name="lineNumber">source line number return</param>
+ /// <param name="fileName">source file name return</param>
+ /// <returns> true if information is available</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Returns local variable name for given local index and IL offset.
+ /// </summary>
+ /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+ /// <param name="methodToken">method token</param>
+ /// <param name="localIndex">local variable index</param>
+ /// <param name="localVarName">local variable name return</param>
+ /// <returns>true if name has been found</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Helper method to return local variable name for given local index and IL offset.
+ /// </summary>
+ /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+ /// <param name="methodToken">method token</param>
+ /// <param name="localIndex">local variable index</param>
+ /// <param name="localVarName">local variable name return</param>
+ /// <returns>true if name has been found</returns>
+ 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<LocalVarInfo> 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<LocalVarInfo>();
+
+ 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;
+
+ }
+ /// <summary>
+ /// Returns source name, line numbers and IL offsets for given method token.
+ /// </summary>
+ /// <param name="assemblyPath">file path of the assembly</param>
+ /// <param name="methodToken">method token</param>
+ /// <param name="debugInfo">structure with debug information return</param>
+ /// <returns>true if information is available</returns>
+ /// <remarks>used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs</remarks>
+ internal static bool GetInfoForMethod(string assemblyPath, int methodToken, ref MethodDebugInfo debugInfo)
+ {
+ try
+ {
+ List<DebugInfo> points = null;
+ List<LocalVarInfo> locals = null;
+
+ if (!GetDebugInfoForMethod(assemblyPath, methodToken, out points))
+ {
+ return false;
+ }
+
+ if (!GetLocalsInfoForMethod(assemblyPath, methodToken, out locals))
+ {
+ return false;
+ }
+ var structSize = Marshal.SizeOf<DebugInfo>();
+
+ 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<LocalVarInfo>();
+
+ 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;
+ }
+
+ /// <summary>
+ /// Helper method to return source name, line numbers and IL offsets for given method token.
+ /// </summary>
+ /// <param name="assemblyPath">file path of the assembly</param>
+ /// <param name="methodToken">method token</param>
+ /// <param name="points">list of debug information for each sequence point return</param>
+ /// <returns>true if information is available</returns>
+ /// <remarks>used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs</remarks>
+ private static bool GetDebugInfoForMethod(string assemblyPath, int methodToken, out List<DebugInfo> 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<DebugInfo>();
+ 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;
+ }
+
+ /// <summary>
+ /// Returns the portable PDB reader for the assembly path
+ /// </summary>
+ /// <param name="assemblyPath">file path of the assembly or null if the module is in-memory or dynamic</param>
+ /// <param name="isFileLayout">type of in-memory PE layout, if true, file based layout otherwise, loaded layout</param>
+ /// <param name="peStream">optional in-memory PE stream</param>
+ /// <param name="pdbStream">optional in-memory PDB stream</param>
+ /// <returns>reader/provider wrapper instance</returns>
+ /// <remarks>
+ /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around.
+ /// </remarks>
+ 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;
+ }
+ }
+ }
+}
--- /dev/null
+<Configuration>
+ <TargetConfiguration>Debug</TargetConfiguration>
+</Configuration>
--- /dev/null
+<Configuration>
+ <TargetConfiguration>Release</TargetConfiguration>
+</Configuration>
--- /dev/null
+<!--
+ The xunit tests in Debugger.Tests aren't truly unit tests - they depend on other stuff that may
+ not be in the same directory. This file configures the tests to find what they need. At the moment
+ this file is generated in a hardcoded way to support running from the bin directory on our git
+ based build, but once we understand the different environments the tests will need to run in we
+ can figure out the right build and deployment strategy for each. Hopefully this configuration offers
+ enough flexibility that the tests themselves don't need to change.
+-->
+
+<Configuration>
+ <Import ConfigFile="Debugger.Tests.Common.txt" />
+
+ <TestProduct>ProjectK</TestProduct>
+ <RepoRootDir>../../../../..</RepoRootDir>
+ <ScriptRootDir>$(RepoRootDir)/src/SOS/SOS.UnitTests/Scripts</ScriptRootDir>
+ <RootBinDir>$(RepoRootDir)/artifacts</RootBinDir>
+ <InstallDir>$(RootBinDir)/$(TargetConfiguration)/bin/$(OS).$(TargetArchitecture)</InstallDir>
+ <LogDir>$(RootBinDir)/$(TargetConfiguration)/TestResults/sos.unittests_$(Timestamp)</LogDir>
+ <DumpDir>$(RootBinDir)/$(TargetConfiguration)/tmp/dumps</DumpDir>
+
+ <DebuggeeSourceRoot>$(RepoRootDir)/src/SOS/SOS.UnitTests/Debuggees</DebuggeeSourceRoot>
+ <DebuggeeRootDir>$(RootBinDir)/Debuggees</DebuggeeRootDir>
+ <DebuggeeBuildRoot>$(DebuggeeRootDir)</DebuggeeBuildRoot>
+ <DebuggeeNativeLibRoot>$(DebuggeeBuildRoot)/native</DebuggeeNativeLibRoot>
+ <DebuggeeBuildProcess>cli</DebuggeeBuildProcess>
+
+ <BuildProjectMicrosoftNetCoreAppVersion>2.1.0</BuildProjectMicrosoftNetCoreAppVersion>
+ <BuildProjectFramework>netcoreapp2.1</BuildProjectFramework>
+ <CliPath>$(RepoRootDir)/.dotnet/dotnet</CliPath>
+
+ <NuGetPackageFeeds>
+ myget.org dotnet-core=https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
+ nuget.org=https://www.nuget.org/api/v2/
+ </NuGetPackageFeeds>
+
+ <!--
+ <CliVersion>2.0.0</CliVersion>
+ <CliPath Condition="$(OS) == Linux">https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$(CliVersion)/dotnet-sdk-$(CliVersion)-linux-$(TargetArchitecture).tar.gz</CliPath>
+ <CliPath Condition="$(OS) == OSX">https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$(CliVersion)/dotnet-sdk-$(CliVersion)-osx-$(TargetArchitecture).tar.gz</CliPath>
+ <CliCacheRoot>$(RootBinDir)/dotnet</CliCacheRoot>
+
+ <BuildProjectMicrosoftNetCoreAppVersion>2.0.0</BuildProjectMicrosoftNetCoreAppVersion>
+ <BuildProjectFramework>netcoreapp2.0</BuildProjectFramework>
+ <BuildProjectRuntime Condition="$(OS) == Linux">linux-$(TargetArchitecture)</BuildProjectRuntime>
+ <BuildProjectRuntime Condition="$(OS) == OSX">osx-$(TargetArchitecture)</BuildProjectRuntime>
+
+ <HostExe>$(CliCacheRoot)/dotnet</HostExe>
+ <RuntimeSymbolsPath>$(CliCacheRoot)/shared/Microsoft.NETCore.App/$(BuildProjectMicrosoftNetCoreAppVersion)</RuntimeSymbolsPath>
+ -->
+
+ <HostExe>$(RepoRootDir)/.dotnet/dotnet</HostExe>
+ <RuntimeSymbolsPath>$(RepoRootDir)/.dotnet/shared/Microsoft.NETCore.App/$(BuildProjectMicrosoftNetCoreAppVersion)</RuntimeSymbolsPath>
+ <LLDBHelperScript>$(ScriptRootDir)/lldbhelper.py</LLDBHelperScript>
+
+ <Options>
+ <Option Condition="$(OS) == Linux">
+ <SOSPath>$(InstallDir)/libsosplugin.so</SOSPath>
+ <DebuggeeDumpOutputRootDir>$(DumpDir)/$(TestProduct)</DebuggeeDumpOutputRootDir>
+ <DebuggeeDumpInputRootDir>$(DebuggeeDumpOutputRootDir)</DebuggeeDumpInputRootDir>
+ </Option>
+ <Option Condition="$(OS) == OSX">
+ <SOSPath>$(InstallDir)/libsosplugin.dylib</SOSPath>
+ <!-- Dump testing is disabled on macOS. gdb can't run processes because it needs to be codesigned and lldb on macOS's "process save-core" is too slow -->
+ </Option>
+ </Options>
+
+</Configuration>
--- /dev/null
+<!--
+ The xunit tests in Debugger.Tests aren't truly unit tests - they depend on other stuff that may
+ not be in the same directory. This file configures the tests to find what they need. At the moment
+ this file is generated in a hardcoded way to support running from the bin directory on our git
+ based build, but once we understand the different environments the tests will need to run in we
+ can figure out the right build and deployment strategy for each. Hopefully this configuration offers
+ enough flexibility that the tests themselves don't need to change.
+-->
+
+<Configuration>
+ <Import ConfigFile="Debugger.Tests.Common.txt" />
+
+ <RepoRootDir>..\..\..\..\..</RepoRootDir>
+ <ScriptRootDir>$(RepoRootDir)\src\SOS\SOS.UnitTests\Scripts</ScriptRootDir>
+ <RootBinDir>$(RepoRootDir)\artifacts</RootBinDir>
+
+ <InstallDir>$(RootBinDir)\$(TargetConfiguration)\bin\Windows_NT.$(TargetArchitecture)</InstallDir>
+ <LogDir>$(RootBinDir)\$(TargetConfiguration)\TestResults\sos.unittests_$(Timestamp)</LogDir>
+ <DumpDir>$(RootBinDir)\$(TargetConfiguration)\tmp\dumps</DumpDir>
+ <CDBPath>$(NuGetPackageCacheDir)\cdb-sos\1.1.0\runtimes\win-$(TargetArchitecture)\native\cdb.exe</CDBPath>
+
+ <DebuggeeSourceRoot>$(RepoRootDir)\src\SOS\SOS.UnitTests\Debuggees</DebuggeeSourceRoot>
+ <DebuggeeRootDir>$(RootBinDir)\Debuggees</DebuggeeRootDir>
+ <DebuggeeBuildRoot>$(DebuggeeRootDir)</DebuggeeBuildRoot>
+ <DebuggeeNativeLibRoot>$(DebuggeeBuildRoot)\native</DebuggeeNativeLibRoot>
+ <DebuggeeBuildProcess>cli</DebuggeeBuildProcess>
+
+ <BuildProjectMicrosoftNetCoreAppVersion>2.1.0</BuildProjectMicrosoftNetCoreAppVersion>
+ <BuildProjectFramework>netcoreapp2.1</BuildProjectFramework>
+ <CliPath>$(RepoRootDir)\.dotnet\dotnet.exe</CliPath>
+
+ <NuGetPackageFeeds>
+ myget.org dotnet-core=https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
+ nuget.org=https://www.nuget.org/api/v2/
+ </NuGetPackageFeeds>
+
+ <Options>
+ <Option>
+ <TestProduct>ProjectK</TestProduct>
+ <!--
+ <CliVersion>2.0.0</CliVersion>
+ <CliPath>https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$(CliVersion)/dotnet-sdk-$(CliVersion)-win-$(TargetArchitecture).zip</CliPath>
+ <CliCacheRoot>$(RootBinDir)\dotnet</CliCacheRoot>
+
+ <BuildProjectMicrosoftNetCoreAppVersion>2.0.0</BuildProjectMicrosoftNetCoreAppVersion>
+ <BuildProjectFramework>netcoreapp2.0</BuildProjectFramework>
+ <BuildProjectRuntime>win-$(TargetArchitecture)</BuildProjectRuntime>
+
+ <HostExe>$(CliCacheRoot)\dotnet.exe</HostExe>
+ <RuntimeSymbolsPath>$(CliCacheRoot)\shared\Microsoft.NETCore.App\$(BuildProjectMicrosoftNetCoreAppVersion)</RuntimeSymbolsPath>
+ -->
+ <HostExe>$(RepoRootDir)\.dotnet\dotnet.exe</HostExe>
+ <RuntimeSymbolsPath>$(RepoRootDir)\.dotnet\shared\Microsoft.NETCore.App\$(BuildProjectMicrosoftNetCoreAppVersion)</RuntimeSymbolsPath>
+ <SOSPath>$(InstallDir)\sos.dll</SOSPath>
+ </Option>
+ <!--
+ <Option Condition="$(TargetArchitecture) != arm64">
+ <TestProduct>Desktop</TestProduct>
+ <FrameworkDirPath Condition="$(TargetArchitecture) == x64">$(WinDir)\Microsoft.Net\Framework64\v4.0.30319\</FrameworkDirPath>
+ <FrameworkDirPath Condition="$(TargetArchitecture) != x64">$(WinDir)\Microsoft.Net\Framework\v4.0.30319\</FrameworkDirPath>
+ <RuntimeSymbolsPath>$(FrameworkDirPath)</RuntimeSymbolsPath>
+ <SOSPath>$(FrameworkDirPath)\sos.dll</SOSPath>
+ </Option>
+ -->
+ </Options>
+
+ <DebuggeeDumpOutputRootDir>$(DumpDir)\$(TestProduct)</DebuggeeDumpOutputRootDir>
+ <DebuggeeDumpInputRootDir>$(DebuggeeDumpOutputRootDir)</DebuggeeDumpInputRootDir>
+</Configuration>
--- /dev/null
+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);
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ </PropertyGroup>
+</Project>
+++ /dev/null
-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);
- }
-}
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>sedouard</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
- <ProjectGuid>{A2F6FA79-80DB-4EA5-A2E2-CC0BE785169F}</ProjectGuid>
- <SccProjectName>SAK</SccProjectName>
- <SccLocalPath>SAK</SccLocalPath>
- <SccAuxPath>SAK</SccAuxPath>
- <SccProvider>SAK</SccProvider>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"/>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"/>
- <ItemGroup>
- <Compile Include="divzero.cs" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ </PropertyGroup>
+</Project>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>sridhper</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <OutputType>exe</OutputType>
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-
- <CLRTestILCKeepIntermediates>true</CLRTestILCKeepIntermediates>
- <SccProjectName>SAK</SccProjectName>
- <SccLocalPath>SAK</SccLocalPath>
- <SccAuxPath>SAK</SccAuxPath>
- <SccProvider>SAK</SccProvider>
- <ProjectGuid>{0F0CA3AD-B37C-452E-BA91-678F71D6AFAA}</ProjectGuid>
- </PropertyGroup>
- <ItemGroup>
- <Compile Include="GCWhere.cs" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ </PropertyGroup>
+</Project>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>mikem</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <OutputType>exe</OutputType>
- </PropertyGroup>
- <ItemGroup>
- <Compile Include="NestedExceptionTest.cs" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>conniey</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <Configuration>Release</Configuration>
- </PropertyGroup>
- <ItemGroup>
- <Compile Include="Overflow.cs" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
\ No newline at end of file
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ </PropertyGroup>
+</Project>
+++ /dev/null
-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
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>conniey</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <OutputType>exe</OutputType>
- </PropertyGroup>
- <ItemGroup>
- <Compile Include="RSSFeed.cs" />
- <Compile Include="TestClasses.cs" />
- </ItemGroup>
- <ItemGroup>
- <CLRTestContractReference Include="System.Collections" />
- <CLRTestContractReference Include="System.Diagnostics.Debug" />
- <CLRTestContractReference Include="System.Threading" />
- <CLRTestContractReference Include="System.Threading.Tasks" />
- <CLRTestContractReference Include="System.Runtime.WindowsRuntime" />
- <CLRTestContractReference Include="System.Runtime.InteropServices.WindowsRuntime" />
- <CLRTestWinMDReference Include="Windows" />
- </ItemGroup>
- <ItemGroup>
- <CLRTestPInvokeServerReference Include="Windows.RSS.Utils\Windows.RSS.Utils.vcxproj" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
\ No newline at end of file
+++ /dev/null
-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<FeedData>()
- .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<FeedData> 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<string> vector = new TestObservableVector<string>();
- vector.Add(FeedUri);
- Console.WriteLine("Calling into winrt code...");
- await m_feedDataSource.GetFeedsAsync(vector);
- Console.WriteLine("Should not finish!");
- }
-
- /// <summary>
- /// 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. ;(
- /// </summary>
- private static string ParseFeedSummary(string description)
- {
- string fixedDescription = description.Replace("&", "&");
- fixedDescription = fixedDescription.Replace("<", "<");
- fixedDescription = fixedDescription.Replace(">", ">");
-
- return fixedDescription;
- }
- }
-
- public class TestObservableVector<T> : List<T>, IObservableVector<T>
- {
- public event VectorChangedEventHandler<T> VectorChanged;
-
- public TestObservableVector()
- {
- VectorChanged = delegate(IObservableVector<T> sender, IVectorChangedEventArgs e) { };
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-//*********************************************************
-//
-// Copyright (c) Microsoft. All rights reserved.
-// http://code.msdn.microsoft.com/windowsapps/Windows-Store-Simple-Blog-953302e8
-//*********************************************************
-
-//DateConverter.h
-
-#pragma once
-#include <string> //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<Windows::Foundation::DateTime>(value);
- auto param = safe_cast<Platform::String^>(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
+++ /dev/null
-//*********************************************************
-//
-// 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<FeedData^>();
-}
-
-// 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<FeedData^>^ FeedDataSource::GetFeedAsync(String^ uri)
-{
- return create_async([uri, this]()
- {
- //auto feedDataSource = safe_cast<FeedDataSource^>(
- // 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<FeedData^> 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<FeedData^> 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 <windows.h>.
- OutputDebugString(fd->Title->Data());
- OutputDebugString(L"\r\n");
- })
- .then([](task<void> 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<double>^ FeedDataSource::GetFeedsAsync(Windows::Foundation::Collections::IObservableVector<String^>^ feeds)
-{
- int first = 0;
- int last = 20;
- return create_async([feeds](progress_reporter<double> reporter)
- {
- throw ref new InvalidArgumentException("Some exception thrown from GetFeedsAsync.");
- });
-}
\ No newline at end of file
+++ /dev/null
-//*********************************************************
-//
-// 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<FeedItem^>();
- }
-
- // 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<FeedItem^>^ Items
- {
- Windows::Foundation::Collections::IVector<FeedItem^>^ get() { return m_items; }
- }
-
- property Platform::String^ Description;
- property Windows::Foundation::DateTime PubDate;
- property Platform::String^ Uri;
-
- private:
- Platform::Collections::Vector<FeedItem^>^ 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: <local:FeedDataSource x:Key="feedDataSource" />
- [Windows::UI::Xaml::Data::Bindable]
- public ref class FeedDataSource sealed
- {
- public:
- FeedDataSource();
- property Windows::Foundation::Collections::IObservableVector<FeedData^>^ Feeds
- {
- Windows::Foundation::Collections::IObservableVector<FeedData^>^ get()
- {
- return this->m_feeds;
- }
- }
- Windows::Foundation::IAsyncOperation<FeedData^>^ GetFeedAsync(Platform::String^ uri);
-
- Windows::Foundation::IAsyncActionWithProgress<double>^ GetFeedsAsync(Windows::Foundation::Collections::IObservableVector<Platform::String^>^ feeds);
-
- void RemoveFeed(Platform::String^ uri);
-
- private:
- Platform::Collections::Vector<FeedData^>^ m_feeds;
- std::map<Platform::String^, Concurrency::task_completion_event<FeedData^>> m_feedCompletionEvents;
-
- FeedData^ GetFeedData(Platform::String^ feedUri, Windows::Web::Syndication::SyndicationFeed^ feed);
- void AddFeed(Platform::String^ uri);
- };
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-#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<String^>();
- m_relatedLinks = ref new Vector<String^>();
-}
-
-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<String^>^ FeedItem::GetRelatedGuidsFromString(Platform::String^ guidString)
-{
- Vector<String^>^ vector = ref new Vector<String^>();
- const wchar_t* delimiter = L",";
- auto wcguid = guidString->Data();
-
- return vector;
-}
\ No newline at end of file
+++ /dev/null
-#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<Platform::String^>^ RelatedLinks
- {
- Windows::Foundation::Collections::IVector<Platform::String^>^ get() { return m_relatedLinks; }
- }
-
- property Windows::Foundation::Collections::IVector<Platform::String^>^ RelatedArticles
- {
- Windows::Foundation::Collections::IVector<Platform::String^>^ get() { return m_relatedArticles; }
- }
-
- static FeedItem^ ParseSyndicationItem(Windows::Web::Syndication::SyndicationItem^ feedItem);
- private:
- ~FeedItem(void);
- Platform::Collections::Vector<Platform::String^>^ m_relatedArticles;
- Platform::Collections::Vector<Platform::String^>^ m_relatedLinks;
-
- static Windows::Foundation::Uri^ GetUrlFromAttribute(Windows::Data::Xml::Dom::XmlNamedNodeMap^ attributes);
- static Platform::Collections::Vector<Platform::String^>^ GetRelatedGuidsFromString(Platform::String^ guidString);
- };
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), CLRTest.Common.props))\CLRTest.Common.props" />
- <PropertyGroup>
- <OutputName>Windows.RSS.Utils</OutputName>
- <CLRTestKind>Debuggee</CLRTestKind>
- <CLRTestLanguage>C++</CLRTestLanguage>
- <CLRTestOwner>conniey</CLRTestOwner>
- <ConfigurationType>DynamicLibrary</ConfigurationType>
- <CLRTestServerUsesExports>true</CLRTestServerUsesExports>
- <EnableWindowsRuntimeExtensions>true</EnableWindowsRuntimeExtensions>
- </PropertyGroup>
- <ItemGroup>
- <ClInclude Include="DateConverter.h" />
- <ClInclude Include="FeedData.h" />
- <ClInclude Include="FeedItem.h" />
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="FeedData.cpp" />
- <ClCompile Include="FeedItem.cpp" />
- </ItemGroup>
- <ItemGroup>
- <CLRTestServerExport Include="Windows.RSS.Utils.FeedDataSource"/>
- <CLRTestServerExport Include="Windows.RSS.Utils.FeedData"/>
- <CLRTestServerExport Include="Windows.RSS.Utils.FeedItem"/>
- </ItemGroup>
- <Import Project="$(CLRTestRoot)\CLRTest.targets" />
-</Project>
\ No newline at end of file
+++ /dev/null
-#include "pch.h"
+++ /dev/null
-#pragma once
-
-#include <collection.h>
-#include <ppltasks.h>
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ </PropertyGroup>
+</Project>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>conniey</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <OutputType>exe</OutputType>
- </PropertyGroup>
- <ItemGroup>
- <Compile Include="ReflectionTest.cs" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ </PropertyGroup>
+</Project>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>conniey</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <OutputType>exe</OutputType>
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
- </PropertyGroup>
- <ItemGroup>
- <Compile Include="SimpleThrow.cs" />
- <Compile Include="UserObject.cs" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
\ No newline at end of file
# 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
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");
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ <DebugType>portable</DebugType>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <AdditionalSourceCopy Include="..\SymbolTestApp.sln" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\SymbolTestDll\SymbolTestDll.csproj" />
+ </ItemGroup>
+</Project>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>mikem</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <DefineConstants>$(DefineConstants);FULL_CLR</DefineConstants>
- </PropertyGroup>
- <PropertyGroup Condition="'$(PdbKind)' != ''">
- <DebugType>$(PdbKind)</DebugType>
- <OutputPath>$(OutputPath)/../$(PdbKind)/symboltestapp/</OutputPath>
- </PropertyGroup>
- <Import Project="$(MSBuildThisFileDirectory)symboltestapp.props" />
- <ItemGroup>
- <Compile Include="SymbolTestApp.cs" />
- </ItemGroup>
- <ItemGroup>
- <AdditionalSourceCopy Include="..\SymbolTestApp.sln" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-
- <!-- In PreBuild scenarios, we need to output this project multiple times for each pdb type, handled by the below targets file -->
- <Import Project="symboltestapp_prebuild.targets" Condition="'$(CoreCLR)' == 'true' and '$(PdbKind)' == ''" />
-</Project>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup>
- <ProjectReference Include="..\symboltestdll\symboltestdll.csproj" />
- </ItemGroup>
-</Project>
+++ /dev/null
-<Project>
- <Target Name="Build">
-
- <!-- List of pdb types -->
- <ItemGroup>
- <PdbKind Include="portable" />
- <PdbKind Include="full" />
- </ItemGroup>
-
- <!-- Invoke the build with the specified pdb kind -->
- <MSBuild Projects="symboltestapp.csproj"
- Properties="Configuration=$(Configuration);Platform=$(Platform);OSGroup=$(OSGroup);SkipRestore=$(SkipRestore);CoreCLR=$(CoreCLR);PdbKind=%(PdbKind.Identity)" />
- </Target>
-
- <!-- Don't do any exe renaming when this is invoked, as there isn't anything to actually rename -->
- <Target Name="CopyDllToExe" />
-
-</Project>
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Library</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ <DebugType>portable</DebugType>
+ </PropertyGroup>
+</Project>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>mikem</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <DebugType Condition="'$(PdbKind)' != ''">$(PdbKind)</DebugType>
- <OutputPath Condition="'$(PdbKind)' != ''">$(OutputPath)/../$(PdbKind)/symboltestdll/</OutputPath>
- </PropertyGroup>
- <Import Project="$(MSBuildThisFileDirectory)symboltestdll.props" />
- <ItemGroup>
- <Compile Include="TestClass.cs" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <PropertyGroup>
- <OutputType>Library</OutputType>
- </PropertyGroup>
-</Project>
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Library</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ </PropertyGroup>
+</Project>
--- /dev/null
+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.");
+ }
+ }
+}
# 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
--- /dev/null
+using System;
+using System.Threading.Tasks;
+using RandomTest;
+
+namespace SosTests
+{
+ /// <summary>
+ /// This test creates an asynchronous task that results in an exception being thrown.
+ /// </summary>
+ class TaskException
+ {
+ static int Main()
+ {
+ RandomUserTask theTask = new RandomUserTask(100);
+ theTask.WaitTask();
+
+ return 0;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPublishable>true</IsPublishable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <AdditionalSourceCopy Include="..\TaskNestedException.sln" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\RandomUserLibrary\RandomUserLibrary.csproj" />
+ </ItemGroup>
+</Project>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>conniey</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- </PropertyGroup>
- <Import Project="$(MSBuildThisFileDirectory)RandomUserLibrary.props" />
- <ItemGroup>
- <Compile Include="RandomUserTask.cs" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <PropertyGroup>
- <OutputType>Library</OutputType>
- </PropertyGroup>
-</Project>
\ No newline at end of file
+++ /dev/null
-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.");
- }
- }
-}
+++ /dev/null
-using System;
-using System.Threading.Tasks;
-using RandomTest;
-
-namespace SosTests
-{
- /// <summary>
- /// This test creates an asynchronous task that results in an exception being thrown.
- /// </summary>
- class TaskException
- {
- static int Main()
- {
- RandomUserTask theTask = new RandomUserTask(100);
- theTask.WaitTask();
-
- return 0;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup>
- <ProjectReference Include="../RandomUserLibrary/RandomUserLibrary.csproj" />
- </ItemGroup>
-</Project>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
- <PropertyGroup>
- <CLRTestOwner>conniey</CLRTestOwner>
- <CLRTestKind>Debuggee</CLRTestKind>
- <OutputType>exe</OutputType>
- </PropertyGroup>
- <Import Project="$(MSBuildThisFileDirectory)TaskNestedException.props" />
- <ItemGroup>
- <Compile Include="TaskNestedException.cs" />
- </ItemGroup>
- <ItemGroup>
- <AdditionalSourceCopy Include="..\TaskNestedException.sln" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\randomuserlibrary\RandomUserLibrary.csproj" />
- </ItemGroup>
- <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
\ No newline at end of file
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<NoWarn>;1591;1701</NoWarn>
+ <DefineConstants>$(DefineConstants);CORE_CLR</DefineConstants>
</PropertyGroup>
+ <ItemGroup>
+ <Compile Remove="Debuggees\**" />
+ <EmbeddedResource Remove="Debuggees\**" />
+ <None Remove="Debuggees\**" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Content Include="ConfigFiles\Windows\Debugger.Tests.Config.txt" Condition="'$(OS)' == 'Windows_NT'">
+ <Link>Debugger.Tests.Config.txt</Link>
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
+ <Content Include="ConfigFiles\Unix\Debugger.Tests.Config.txt" Condition="$(OS) == 'Unix'">
+ <Link>Debugger.Tests.Config.txt</Link>
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
+ <Content Include="ConfigFiles\$(Configuration)\Debugger.Tests.Common.txt">
+ <Link>Debugger.Tests.Common.txt</Link>
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\Microsoft.Diagnostic.TestHelpers\Microsoft.Diagnostic.TestHelpers.csproj" />
+ <PackageReference Include="cdb-sos" Version="1.1.0" Condition="'$(OS)' == 'Windows_NT'" />
+ </ItemGroup>
</Project>
-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;
ITestOutputHelper Output { get; set; }
- public static IEnumerable<object[]> Configurations
- {
- get
- {
- return TestRunConfiguration.Instance.Configurations.Select(c => new[] { c });
- }
- }
+ public static IEnumerable<object[]> 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");
}
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");
await runner.RunScript(scriptName);
}
- // Against a crash dump
+ // Against a crash dump.
if (IsCreateDumpConfig(config))
{
await CreateDump(config, testName, debuggeeName, debuggeeArguments);
}
}
-#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";
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");
}
}
}
-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;
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<string, string> _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<string, string> _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<string, string> 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<SOSRunner> 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<string, string> 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<SOSRunner> 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<string> initialCommands = new List<string>();
+ 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<string> initialCommands = new List<string>();
- 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<string> enabledDefines = GetEnabledDefines();
- LogProcessingReproInfo(scriptFile, enabledDefines);
- string[] scriptLines = File.ReadAllLines(scriptFile);
- List<string> activeDefines = new List<string>();
- bool isActiveDefineRegionEnabled = IsActiveDefineRegionEnabled(activeDefines, enabledDefines);
- int i = 0;
- try
+ throw new Exception("Script file does not exist: " + scriptFile);
+ }
+ HashSet<string> enabledDefines = GetEnabledDefines();
+ LogProcessingReproInfo(scriptFile, enabledDefines);
+ string[] scriptLines = File.ReadAllLines(scriptFile);
+ Dictionary<string, bool> activeDefines = new Dictionary<string, bool>();
+ 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<string> commands = new List<string>();
- 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<string> RunSosCommand(string command)
+ public async Task LoadSosExtension()
+ {
+ List<string> commands = new List<string>();
+ 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<string> 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<string> RunCommand(string command)
+ public async Task<string> 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<string> 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<string> 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<string> 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 <PREVPOUT> more stuff
+ // The PREVPOUT tag will be replaced by whatever the last <POUT>
+ // tag matched in a previous command. See below for the POUT rules.
+ const string prevPoutTag = "<PREVPOUT>";
+ const string poutTag = "<POUT>";
+ 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<string> HandleCommand(string input)
+ // The POUT convention is to write a commnd like this:
+ // COMMAND: Some stuff <POUT>regex<POUT> 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 <PREVPOUT> more stuff
- // The PREVPOUT tag will be replaced by whatever the last <POUT>
- // tag matched in a previous command. See below for the POUT rules.
- const string prevPoutTag = "<PREVPOUT>";
- const string poutTag = "<POUT>";
- 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 <POUT>regex<POUT> 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<string> enabledDefines)
+ private void LogProcessingReproInfo(string scriptFile, HashSet<string> enabledDefines)
+ {
+ WriteLine(" STARTING SCRIPT: {0}", scriptFile);
+ foreach (KeyValuePair<string, string> kv in _variables)
{
- WriteLine(" STARTING SCRIPT: {0}", scriptFile);
- foreach (KeyValuePair<string, string> kv in _variables)
- {
- WriteLine(" " + kv.Key + " => " + kv.Value);
- }
- foreach (string define in enabledDefines)
- {
- WriteLine(" " + define);
- }
+ WriteLine(" " + kv.Key + " => " + kv.Value);
}
-
- private List<string> GetEnabledDefines()
+ foreach (string define in enabledDefines)
{
- List<string> defines = new List<string>();
- 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<string> activeDefines, List<string> enabledDefines)
+ private HashSet<string> GetEnabledDefines()
+ {
+ HashSet<string> defines = new HashSet<string>
{
- 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<string, string> GenerateVariables(TestConfiguration config, DebuggeeConfiguration debuggeeConfig, bool generateDump)
+ else
{
- Dictionary<string, string> vars = new Dictionary<string, string>();
- 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<string, bool> activeDefines, HashSet<string> enabledDefines)
+ {
+ foreach (KeyValuePair<string, bool> 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("<DEBUGGEE_EXE>", debuggeeExe.Replace(@"\", @"\\"));
- vars.Add("<DEBUG_ROOT>", 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("<SOURCE_PATH>", debuggeeConfig.SourcePath.Replace(@"\", @"\\"));
- vars.Add("<HEXVAL>", HexValueRegEx);
- vars.Add("<DECVAL>", DecValueRegEx);
+ private static Dictionary<string, string> GenerateVariables(TestConfiguration config, DebuggeeConfiguration debuggeeConfig, bool generateDump)
+ {
+ Dictionary<string, string> vars = new Dictionary<string, string>();
+ 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("<DEBUGGEE_EXE>", debuggeeExe.Replace(@"\", @"\\"));
+ vars.Add("<DEBUG_ROOT>", 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("<SOURCE_PATH>", debuggeeConfig.SourcePath.Replace(@"\", @"\\"));
+ vars.Add("<HEXVAL>", HexValueRegEx);
+ vars.Add("<DECVAL>", DecValueRegEx);
+
+ return vars;
+ }
+
+ private string ReplaceVariables(string input)
+ {
+ return ReplaceVariables(_variables, input);
+ }
- private string ReplaceVariables(string input)
+ private static string ReplaceVariables(Dictionary<string, string> vars, string input)
+ {
+ string output = input;
+ foreach (KeyValuePair<string,string> kv in vars)
{
- return ReplaceVariables(_variables, input);
+ output = output.Replace(kv.Key, kv.Value);
}
+ return output;
+ }
+
+ class ScriptLogger : TestOutputProcessLogger
+ {
+ readonly NativeDebugger _debugger;
+ readonly List<Task<string>> _taskQueue;
+ readonly StringBuilder _lastCommandOutput;
+ TaskCompletionSource<string> _taskSource;
+
+ public bool HasProcessExited { get; private set; }
- private static string ReplaceVariables(Dictionary<string, string> vars, string input)
+ public ScriptLogger(NativeDebugger debugger, ITestOutputHelper output)
+ : base(output)
{
- string output = input;
- foreach (KeyValuePair<string,string> kv in vars)
+ lock (this)
{
- output = output.Replace(kv.Key, kv.Value);
+ _debugger = debugger;
+ _lastCommandOutput = new StringBuilder();
+ _taskQueue = new List<Task<string>>();
+ AddTask();
}
- return output;
}
- class ScriptLogger : TestOutputProcessLogger
+ private void AddTask()
{
- readonly NativeDebugger _debugger;
- readonly List<Task<string>> _taskQueue;
- readonly StringBuilder _lastCommandOutput;
- TaskCompletionSource<string> _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<Task<string>>();
- AddTask();
- }
- }
+ _taskSource = new TaskCompletionSource<string>();
+ _taskQueue.Add(_taskSource.Task);
+ }
- private void AddTask()
+ public async Task<bool> WaitForCommandPrompt()
+ {
+ Task<string> currentTask = null;
+ lock (this)
{
- _taskSource = new TaskCompletionSource<string>();
- _taskQueue.Add(_taskSource.Task);
+ currentTask = _taskQueue[0];
+ _taskQueue.RemoveAt(0);
}
+ return (await currentTask) != null;
+ }
- public async Task<bool> WaitForCommandPrompt()
+ public Task<string> WaitForCommandOutput()
+ {
+ Task<string> currentTask = null;
+ lock (this)
{
- Task<string> currentTask = null;
- lock (this)
- {
- currentTask = _taskQueue[0];
- _taskQueue.RemoveAt(0);
- }
- return (await currentTask) != null;
+ currentTask = _taskQueue[0];
}
+ return currentTask;
+ }
- public Task<string> WaitForCommandOutput()
+ public string ProcessCommand(string command)
+ {
+ if (_debugger == NativeDebugger.Lldb)
{
- Task<string> 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 = "<END_COMMAND_OUTPUT>";
- 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 = "<END_COMMAND_OUTPUT>";
+ 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"));
+ }
+}
SOSCOMMAND:ClrStack -f
VERIFY:.*OS Thread Id:\s+0x<HEXVAL>\s+.*
VERIFY:\s+Child\s+SP\s+IP\s+Call Site\s+
-VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+symboltestapp\.(dll|exe)!SymbolTestApp\.Program\.Foo4\(System\.String\)\s+\+\s+<DECVAL>\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 46\]\s*
-VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+symboltestapp\.(dll|exe)!SymbolTestApp\.Program\.Foo2\(.*\)\s+\+\s+<DECVAL>\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 29\]\s*
-VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+symboltestapp\.(dll|exe)!SymbolTestApp\.Program\.Foo1\(.*\)\s+\+\s+<DECVAL>\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 24\]\s*
-VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+symboltestapp\.(dll|exe)!SymbolTestApp\.Program\.Main\(.*\)\s+\+\s+<DECVAL>\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 19\]\s*
+VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+SymbolTestApp\.(dll|exe)!SymbolTestApp\.Program\.Foo4\(System\.String\)\s+\+\s+<DECVAL>\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 46\]\s*
+VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+SymbolTestApp\.(dll|exe)!SymbolTestApp\.Program\.Foo2\(.*\)\s+\+\s+<DECVAL>\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 29\]\s*
+VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+SymbolTestApp\.(dll|exe)!SymbolTestApp\.Program\.Foo1\(.*\)\s+\+\s+<DECVAL>\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 24\]\s*
+VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+SymbolTestApp\.(dll|exe)!SymbolTestApp\.Program\.Main\(.*\)\s+\+\s+<DECVAL>\s+\[(?i:.*[\\|/]SymbolTestApp\.cs) @ 19\]\s*
ENDIF:PROJECTK
# Verify that ClrStack all option works (locals/params)
VERIFY:.*\s+<HEXVAL>\s+<HEXVAL>\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
VERIFY:.*\s+Stack walk complete.\s+
ENDIF:PROJECTK
+ENDIF:DESKTOP
+
# Verify DumpStackObjects works
IFDEF:PROJECTK
SOSCOMMAND:DumpStackObjects
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+
VERIFY:.*\s+Stack walk complete.\s+
ENDIF:PROJECTK
+ENDIF:DESKTOP
+
# 7) Verify DumpStackObjects works
IFDEF:PROJECTK
SOSCOMMAND:DumpStackObjects
VERIFY:.*\s+<HEXVAL>\s+<HEXVAL>\s+System\.String.*
ENDIF:PROJECTK
-IFDEF:DESKTOP
# 9) Verify DumpStack works
SOSCOMMAND:DumpStack
VERIFY:.*OS Thread Id:\s+0x<HEXVAL>\s+.*
SOSCOMMAND:EEStack
VERIFY:.*Child(-SP|EBP)\s+RetAddr\s+Caller, Callee\s+
VERIFY:.*\s+<HEXVAL>\s+<HEXVAL>\s+\(MethodDesc\s+<HEXVAL>\s+\+\s*0x<HEXVAL>\s+NestedExceptionTest\.Program\.Main\(System\.String\[\]\)\),\s+calling.*
-ENDIF:DESKTOP
+++ /dev/null
-# 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: <HEXVAL>
-# Exception type: System.AggregateException
-# Message: One or more errors occurred.
-# InnerException: System.ArgumentException, Use !PrintException <HEXVAL> to see more.
-# StackTrace (generated):
-# SP IP Function
-# <HEXVAL> <HEXVAL> mscorlib_ni!System.Threading.Tasks.Task.Wait(Int32, System.Threading.CancellationToken)+0xd551d5
-# <HEXVAL> <HEXVAL> mscorlib_ni!System.Threading.Tasks.Task.Wait()+0x11
-# <HEXVAL> <HEXVAL> RSSFeed!RSSFeedTest.AsyncWinRTTest.Main()+0xce
-
-# [EXPECTED OUTPUT (PROJECTN)]
-# Exception type: System.AggregateException
-# Message: <Invalid Object>
-# InnerException: System.ArgumentException, Use !PrintException (\d+) to see more.
-# StackTrace (generated):
-# IP Function
-# <HEXVAL> RSSFeed_<HEXVAL>!$11_System::Threading::Tasks::Task.ThrowIfExceptional+0x50
-# <HEXVAL> RSSFeed_<HEXVAL>!$11_System::Threading::Tasks::Task.Wait+0xd9
-# <HEXVAL> RSSFeed_<HEXVAL>!$11_System::Threading::Tasks::Task.Wait+0x38
-# <HEXVAL> RSSFeed_<HEXVAL>!$0_RSSFeedTest::AsyncWinRTTest.Main+0xb8
-# <HEXVAL> RSSFeed_<HEXVAL>!$0_RSSFeedTest::AsyncWinRTTest.{ILT$Main}+0xd
-# <HEXVAL> RSSFeed_<HEXVAL>!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+<HEXVAL>\s+
-VERIFY:Exception type:\s+System\.AggregateException\s+
-VERIFY:Message:\s+<Invalid Object>\s+
-VERIFY:InnerException:\s+System\.ArgumentException, Use !PrintException <HEXVAL> to see more\.\s+
-VERIFY:StackTrace \(generated\):\s+
-VERIFY:\s+SP\s+\s+IP\s+\s+Function\s+
-VERIFY:\s+<HEXVAL>\s+<HEXVAL>.+System(::|\.)Threading(::|\.)Tasks(::|\.)Task\.Wait(\(\))?\+0x<HEXVAL>\s+
-VERIFY:\s+<HEXVAL>\s+<HEXVAL>.+RSSFeedTest(::|\.)AsyncWinRTTest\.Main(\(\))?\+0x<HEXVAL>\s+
-
-# C) Verifying the inside ArgumentException from the WinRT component.
-SOSCOMMAND:PrintException -lines <POUT>InnerException:\s+System\.ArgumentException, Use !PrintException (<HEXVAL>) to see more<POUT>
-VERIFY:Exception type:\s+System\.ArgumentException\s+
-VERIFY:Message:\s+<Invalid Object>\s+
-VERIFY:InnerException:\s+<none>\s+
-VERIFY:StackTrace \(generated\):\s+
-VERIFY:\s+SP\s+IP\s+Function\s+
-VERIFY:<HEXVAL>\s+<HEXVAL>\s+.+RSSFeedTest(::|\.)TestRSSFeed(::|\+)<ThrowFromAsyncActionWithProgress>d__7\.MoveNext(\(\))?\+0x<HEXVAL>\s*\[.+debuggees\\RSSFeed\\TestClasses\.cs @ 57\]
set(SOS_SOURCES
disasm.cpp
+ datatarget.cpp
dllsext.cpp
eeheap.cpp
EventCallbacks.cpp
{
return E_UNEXPECTED;
}
- return g_ExtControl->GetExecutingProcessorType(machine);
+ return g_ExtControl->GetExecutingProcessorType((PULONG)machine);
}
HRESULT STDMETHODCALLTYPE
{
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);
}
{
return E_UNEXPECTED;
}
- return g_ExtData->ReadVirtual(address, (PVOID)buffer, request, done);
+ return g_ExtData->ReadVirtual(address, (PVOID)buffer, request, (PULONG)done);
}
HRESULT STDMETHODCALLTYPE
{
return E_UNEXPECTED;
}
- return g_ExtData->WriteVirtual(address, (PVOID)buffer, request, done);
+ return g_ExtData->WriteVirtual(address, (PVOID)buffer, request, (PULONG)done);
}
HRESULT STDMETHODCALLTYPE
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
/* [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
/* [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
}
//
// globals
//
-EXT_API_VERSION ApiVersion = { (VER_PRODUCTVERSION_W >> 8), (VER_PRODUCTVERSION_W & 0xff), EXT_API_VERSION_NUMBER64, 0 };
WINDBG_EXTENSION_APIS ExtensionApis;
ULONG PageSize;
PDEBUG_CONTROL DebugControl;
HRESULT Hr;
- *Version = DEBUG_EXTENSION_VERSION(1, 0);
+ *Version = DEBUG_EXTENSION_VERSION(2, 0);
*Flags = 0;
if (g_Initialized)
#include <mscoree.h>
#include <tchar.h>
#include "debugshim.h"
-
-#ifdef FEATURE_PAL
#include "datatarget.h"
-#endif // FEATURE_PAL
#include "gcinfo.h"
#ifndef STRESS_LOG
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,
//
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<char> 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;
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))
{
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))
((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
}
#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<char> 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<char> 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);
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
//----------------------------------------------------------------------------
+++ /dev/null
-// 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;
- }
-}
+++ /dev/null
-<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
-<Project Sdk="RoslynTools.RepoToolset">
- <PropertyGroup>
- <OutputType>Exe</OutputType>
- <TargetFramework>netcoreapp2.0</TargetFramework>
- <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
- <NoWarn>;1591;1701</NoWarn>
- </PropertyGroup>
-</Project>
--- /dev/null
+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 <path-to-lldb> --host <path-to-host> --plugin <path-to-sosplugin> --logfiledir <path-to-logdir> --assembly <path-to-testdebuggee>`
+
+- `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)
--- /dev/null
+// 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;
+ }
+}
--- /dev/null
+<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
+<Project Sdk="RoslynTools.RepoToolset">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.0</TargetFramework>
+ <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
+ <NoWarn>;1591;1701</NoWarn>
+ </PropertyGroup>
+</Project>
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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 <MethodDesc pointer>
+
+
+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)
--- /dev/null
+# 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 <module name> <managed function name>
+
+
+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)
--- /dev/null
+# 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 <module name> <managed function name> [<il offset>]
+
+
+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)
--- /dev/null
+# 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 <module name> <managed function name>
+
+
+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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+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)
--- /dev/null
+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)
--- /dev/null
+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 <functionname>"), -1)
+
+ # TODO: test other use cases
+
+ # Continue current process and checks its exit code
+ test.exit_lldb(debugger, assembly)
--- /dev/null
+# 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)
+
--- /dev/null
+#!/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
+
--- /dev/null
+# 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)
+++ /dev/null
-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 <path-to-lldb> --host <path-to-host> --plugin <path-to-sosplugin> --logfiledir <path-to-logdir> --assembly <path-to-testdebuggee>`
-
-- `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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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 <MethodDesc pointer>
-
-
-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)
+++ /dev/null
-# 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 <module name> <managed function name>
-
-
-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)
+++ /dev/null
-# 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 <module name> <managed function name> [<il offset>]
-
-
-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)
+++ /dev/null
-# 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 <module name> <managed function name>
-
-
-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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-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)
+++ /dev/null
-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)
+++ /dev/null
-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 <functionname>"), -1)
-
- # TODO: test other use cases
-
- # Continue current process and checks its exit code
- test.exit_lldb(debugger, assembly)
+++ /dev/null
-# 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)
-
+++ /dev/null
-#!/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"
+++ /dev/null
-# 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)
--- /dev/null
+// 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