SOS.UnitTests now builds and runs on Windows, Linux and OSX.
authorMike McLaughlin <mikem@microsoft.com>
Tue, 29 May 2018 23:27:05 +0000 (16:27 -0700)
committerMike McLaughlin <mikem@microsoft.com>
Fri, 29 Jun 2018 21:32:58 +0000 (14:32 -0700)
Added conditional to config files. Now any config value, Options or Option
can have a Condition="$(foo) == bar". Only == and != are supported.

Added Import config file support allow one config file to import
another. <Import ConfigFile="<name of file>" />

Upgraded to .NET Core cli version 2.1.300 (final release).

Don't use the dbgeng ioctl to get the DAC interface on Windows.

Move test logging to better named directory

211 files changed:
.vsts-dotnet-ci.yml
.vsts-dotnet.yml
Directory.Build.props
README.md
THIRD-PARTY-NOTICES.TXT [new file with mode: 0644]
diagnostics.sln
eng/Build-Native.cmd
eng/build-native.sh
eng/build.sh
eng/cibuild.sh
eng/docker-cibuild.sh
global.json
src/DebuggerTests/AcquireDotNetTestStep.cs [deleted file]
src/DebuggerTests/AssertX.cs [deleted file]
src/DebuggerTests/BaseDebuggeeCompiler.cs [deleted file]
src/DebuggerTests/CliDebuggeeCompiler.cs [deleted file]
src/DebuggerTests/ConsoleTestOutputHelper.cs [deleted file]
src/DebuggerTests/CsprojBuildDebuggeeTestStep.cs [deleted file]
src/DebuggerTests/DebuggeeCompiler.cs [deleted file]
src/DebuggerTests/DebuggerTests.csproj [deleted file]
src/DebuggerTests/DotNetBuildDebuggeeTestStep.cs [deleted file]
src/DebuggerTests/FileTestOutputHelper.cs [deleted file]
src/DebuggerTests/IProcessLogger.cs [deleted file]
src/DebuggerTests/IndentedTestOutputHelper.cs [deleted file]
src/DebuggerTests/MultiplexTestOutputHelper.cs [deleted file]
src/DebuggerTests/PrebuiltDebuggeeCompiler.cs [deleted file]
src/DebuggerTests/ProcessRunner.cs [deleted file]
src/DebuggerTests/TestConfiguration.cs [deleted file]
src/DebuggerTests/TestOutputProcessLogger.cs [deleted file]
src/DebuggerTests/TestRunner.cs [deleted file]
src/DebuggerTests/TestStep.cs [deleted file]
src/DebuggerTests/Xunit.Extensions/SkipTestException.cs [deleted file]
src/DebuggerTests/Xunit.Extensions/SkippableFactAttribute.cs [deleted file]
src/DebuggerTests/Xunit.Extensions/SkippableFactDiscoverer.cs [deleted file]
src/DebuggerTests/Xunit.Extensions/SkippableFactMessageBus.cs [deleted file]
src/DebuggerTests/Xunit.Extensions/SkippableFactTestCase.cs [deleted file]
src/DebuggerTests/Xunit.Extensions/SkippableTheoryAttribute.cs [deleted file]
src/DebuggerTests/Xunit.Extensions/SkippableTheoryDiscoverer.cs [deleted file]
src/DebuggerTests/Xunit.Extensions/SkippableTheoryTestCase.cs [deleted file]
src/DebuggerTests/Xunit.Extensions/license.txt [deleted file]
src/Microsoft.Diagnostic.TestHelpers/AcquireDotNetTestStep.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/AssertX.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/BaseDebuggeeCompiler.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/CliDebuggeeCompiler.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/ConsoleTestOutputHelper.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/CsprojBuildDebuggeeTestStep.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/DebuggeeCompiler.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/DotNetBuildDebuggeeTestStep.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/FileTestOutputHelper.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/IProcessLogger.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/IndentedTestOutputHelper.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Microsoft.Diagnostic.TestHelpers.csproj [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/MultiplexTestOutputHelper.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/PrebuiltDebuggeeCompiler.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/ProcessRunner.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/TestConfiguration.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/TestOutputProcessLogger.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/TestRunner.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/TestStep.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkipTestException.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactAttribute.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactDiscoverer.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactMessageBus.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactTestCase.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryAttribute.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryDiscoverer.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryTestCase.cs [new file with mode: 0644]
src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/license.txt [new file with mode: 0644]
src/SOS/CMakeLists.txt
src/SOS/NETCore/CMakeLists.txt [deleted file]
src/SOS/NETCore/SOS.NETCore.csproj [deleted file]
src/SOS/NETCore/SymbolReader.cs [deleted file]
src/SOS/SOS.NETCore/CMakeLists.txt [new file with mode: 0644]
src/SOS/SOS.NETCore/SOS.NETCore.csproj [new file with mode: 0644]
src/SOS/SOS.NETCore/SymbolReader.cs [new file with mode: 0644]
src/SOS/SOS.UnitTests/ConfigFiles/Debug/Debugger.Tests.Common.txt [new file with mode: 0644]
src/SOS/SOS.UnitTests/ConfigFiles/Release/Debugger.Tests.Common.txt [new file with mode: 0644]
src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt [new file with mode: 0644]
src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.cs [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.csproj [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.cs [deleted file]
src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.csproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/GCWhere/GCWhere.csproj [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/GCWhere/gcwhere.csproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/NestedExceptionTest.csproj [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/nestedexceptiontest.csproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/Overflow/Overflow.csproj
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.cs [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.csproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/TestClasses.cs [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/DateConverter.h [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.cpp [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.h [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.cpp [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.h [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/Windows.RSS.Utils.vcxproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.cpp [deleted file]
src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.h [deleted file]
src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/ReflectionTest.csproj [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/reflectiontest.csproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/SimpleThrow.csproj [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/simplethrow.csproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp.sln
src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs
src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.csproj [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.csproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.props [deleted file]
src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp_prebuild.targets [deleted file]
src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/SymbolTestDll.csproj [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.csproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.props [deleted file]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserLibrary.csproj [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserTask.cs [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException.sln
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.cs [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.csproj [new file with mode: 0644]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.csproj [deleted file]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.props [deleted file]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserTask.cs [deleted file]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.cs [deleted file]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.props [deleted file]
src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/tasknestedexception.csproj [deleted file]
src/SOS/SOS.UnitTests/SOS.UnitTests.csproj
src/SOS/SOS.UnitTests/SOS.cs
src/SOS/SOS.UnitTests/SOSRunner.cs
src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script
src/SOS/SOS.UnitTests/Scripts/StackTests.script
src/SOS/SOS.UnitTests/Scripts/WinRTAsync.script [deleted file]
src/SOS/Strike/CMakeLists.txt
src/SOS/Strike/datatarget.cpp
src/SOS/Strike/exts.cpp
src/SOS/Strike/util.cpp
src/SOS/Strike/xplat/dbgeng.h
src/SOS/TestDebuggee/Test.cs [deleted file]
src/SOS/TestDebuggee/TestDebuggee.csproj [deleted file]
src/SOS/lldbplugin.tests/README.md [new file with mode: 0644]
src/SOS/lldbplugin.tests/TestDebuggee/Test.cs [new file with mode: 0644]
src/SOS/lldbplugin.tests/TestDebuggee/TestDebuggee.csproj [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_bpmd_clear.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_bpmd_clearall.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_bpmd_methoddesc.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function_iloffset.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_bpmd_nofuturemodule_module_function.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_clrstack.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_clrthreads.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_clru.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dso.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dumpclass.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dumpheap.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dumpil.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dumplog.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dumpmd.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dumpmodule.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dumpmt.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dumpobj.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_dumpstack.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_eeheap.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_eestack.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_gcroot.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_histclear.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_histinit.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_histobj.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_histobjfind.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_histroot.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_ip2md.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_name2ee.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_pe.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_sos.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/t_cmd_soshelp.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/test_libsosplugin.py [new file with mode: 0644]
src/SOS/lldbplugin.tests/testsos.sh [new file with mode: 0755]
src/SOS/lldbplugin.tests/testutils.py [new file with mode: 0644]
src/SOS/tests/README.md [deleted file]
src/SOS/tests/t_cmd_bpmd_clear.py [deleted file]
src/SOS/tests/t_cmd_bpmd_clearall.py [deleted file]
src/SOS/tests/t_cmd_bpmd_methoddesc.py [deleted file]
src/SOS/tests/t_cmd_bpmd_module_function.py [deleted file]
src/SOS/tests/t_cmd_bpmd_module_function_iloffset.py [deleted file]
src/SOS/tests/t_cmd_bpmd_nofuturemodule_module_function.py [deleted file]
src/SOS/tests/t_cmd_clrstack.py [deleted file]
src/SOS/tests/t_cmd_clrthreads.py [deleted file]
src/SOS/tests/t_cmd_clru.py [deleted file]
src/SOS/tests/t_cmd_dso.py [deleted file]
src/SOS/tests/t_cmd_dumpclass.py [deleted file]
src/SOS/tests/t_cmd_dumpheap.py [deleted file]
src/SOS/tests/t_cmd_dumpil.py [deleted file]
src/SOS/tests/t_cmd_dumplog.py [deleted file]
src/SOS/tests/t_cmd_dumpmd.py [deleted file]
src/SOS/tests/t_cmd_dumpmodule.py [deleted file]
src/SOS/tests/t_cmd_dumpmt.py [deleted file]
src/SOS/tests/t_cmd_dumpobj.py [deleted file]
src/SOS/tests/t_cmd_dumpstack.py [deleted file]
src/SOS/tests/t_cmd_eeheap.py [deleted file]
src/SOS/tests/t_cmd_eestack.py [deleted file]
src/SOS/tests/t_cmd_gcroot.py [deleted file]
src/SOS/tests/t_cmd_histclear.py [deleted file]
src/SOS/tests/t_cmd_histinit.py [deleted file]
src/SOS/tests/t_cmd_histobj.py [deleted file]
src/SOS/tests/t_cmd_histobjfind.py [deleted file]
src/SOS/tests/t_cmd_histroot.py [deleted file]
src/SOS/tests/t_cmd_ip2md.py [deleted file]
src/SOS/tests/t_cmd_name2ee.py [deleted file]
src/SOS/tests/t_cmd_pe.py [deleted file]
src/SOS/tests/t_cmd_sos.py [deleted file]
src/SOS/tests/t_cmd_soshelp.py [deleted file]
src/SOS/tests/test_libsosplugin.py [deleted file]
src/SOS/tests/testsos.sh [deleted file]
src/SOS/tests/testutils.py [deleted file]
src/inc/bitvector.h [new file with mode: 0644]

index a00de37c9047c415d0a1a3f596a1ac262327d243..5c3ed1f1e8752a9a93e85a54184aaab737fa7847 100644 (file)
@@ -26,7 +26,7 @@ phases:
     phaseName: CentOS_7
     agentOs: Linux
     buildReason: IndividualCI
-    dockerImage: microsoft/dotnet-buildtools-prereqs:centos-7-d485f41-20173404063424
+    dockerImage: microsoft/dotnet-buildtools-prereqs:centos-7-c103199-20180628120549
     queue:
       name: DotNetCore-Linux
       demands:
index a184cfe29545683f4800ac6c61e03fde4448b659..565024a262025f642f4ec2aa9a5ef426194fe89f 100644 (file)
@@ -5,7 +5,7 @@ phases:
     agentOs: Windows_NT
     buildReason: Internal
     queue: 
-      name: DotNet-Build
+      name: DotNetCore-Build
       demands:
         - agent.os -equals Windows_NT
       parallel: 2
@@ -26,7 +26,7 @@ phases:
     phaseName: CentOS_7
     agentOs: Linux
     buildReason: Internal
-    dockerImage: microsoft/dotnet-buildtools-prereqs:centos-7-d485f41-20173404063424
+    dockerImage: microsoft/dotnet-buildtools-prereqs:centos-7-c103199-20180628120549
     queue:
       name: DotNet-Build
       demands:
@@ -49,7 +49,7 @@ phases:
     phaseName: Ubuntu_14_04
     agentOs: Linux
     buildReason: Internal
-    dockerImage: microsoft/dotnet-buildtools-prereqs:ubuntu-14.04-cross-0cd4667-20170319080304
+    dockerImage: microsoft/dotnet-buildtools-prereqs:ubuntu-18.04-c103199-20180628134610
     queue:
       name: DotNet-Build
       demands:
@@ -67,7 +67,7 @@ phases:
     agentOs: Darwin
     buildReason: Internal
     queue: 
-      name: DotNet-Build
+      name: DotNetCore-Build
       demands:
         - agent.os -equals Darwin
       parallel: 2
index e8896aefc2d22b9ef907bafd1626b52288316731..cad51789c9f2f37cc39faee683a4648bb292160e 100644 (file)
@@ -6,6 +6,7 @@
     <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>
index 73569f37682dee7451b61f56ee71e8e3e89fb824..aa0271c54af47b5300a716032922ec8d323b1b6e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ To build under Windows, run build.cmd from the root of the repository:
 [Lots of build spew]
 
 BUILD: Repo sucessfully built.
-BUILD: Product binaries are available at c:\git\diagnostics\artifacts\bin\Windows_NT.x64.Debug 
+BUILD: Product binaries are available at c:\git\diagnostics\artifacts\Debug\bin\Windows_NT.x64
 ```
 
 To build under Linux, MacOS, FreeBSD, or NetBSD, run build.sh from the root of the repository:
@@ -47,7 +47,7 @@ $ ./build.sh
 [Lots of build spew]
 
 BUILD: Repo sucessfully built.
-BUILD: Product binaries are available at /home/mikem/diagnostics/artifacts/bin/Linux.x64.Debug
+BUILD: Product binaries are available at /home/mikem/diagnostics/artifacts/Debug/bin/Linux.x64
 ```
 
 To test the resulting SOS (and plugin if xplat):
diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT
new file mode 100644 (file)
index 0000000..4a8002d
--- /dev/null
@@ -0,0 +1,251 @@
+.NET Core uses third-party libraries or other resources that may be
+distributed under licenses different than the .NET Core software.
+
+Attributions and license notices for test cases originally authored by
+third parties can be found in the respective test directories.
+
+In the event that we accidentally failed to list a required notice, please
+bring it to our attention. Post an issue or email us:
+
+           dotnet@microsoft.com
+
+The attached notices are provided for information only.
+
+License notice for RFC 3492
+---------------------------
+
+The punycode implementation is based on the sample code in RFC 3492
+        
+Copyright (C) The Internet Society (2003).  All Rights Reserved.
+
+This document and translations of it may be copied and furnished to
+others, and derivative works that comment on or otherwise explain it
+or assist in its implementation may be prepared, copied, published
+and distributed, in whole or in part, without restriction of any
+kind, provided that the above copyright notice and this paragraph are
+included on all such copies and derivative works.  However, this
+document itself may not be modified in any way, such as by removing
+the copyright notice or references to the Internet Society or other
+Internet organizations, except as needed for the purpose of
+developing Internet standards in which case the procedures for
+copyrights defined in the Internet Standards process must be
+followed, or as required to translate it into languages other than
+English.
+
+The limited permissions granted above are perpetual and will not be
+revoked by the Internet Society or its successors or assigns.
+
+This document and the information contained herein is provided on an
+"AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+License notice for Algorithm from Internet Draft document "UUIDs and GUIDs"
+---------------------------------------------------------------------------
+
+Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc.
+Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. &
+Digital Equipment Corporation, Maynard, Mass.
+To anyone who acknowledges that this file is provided "AS IS"
+without any express or implied warranty: permission to use, copy,
+modify, and distribute this file for any purpose is hereby
+granted without fee, provided that the above copyright notices and
+this notice appears in all source code copies, and that none of
+the names of Open Software Foundation, Inc., Hewlett-Packard
+Company, or Digital Equipment Corporation be used in advertising
+or publicity pertaining to distribution of the software without
+specific, written prior permission.  Neither Open Software
+Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital Equipment
+Corporation makes any representations about the suitability of
+this software for any purpose.
+
+Copyright(C) The Internet Society 1997. All Rights Reserved.
+
+This document and translations of it may be copied and furnished to others,
+and derivative works that comment on or otherwise explain it or assist in
+its implementation may be prepared, copied, published and distributed, in
+whole or in part, without restriction of any kind, provided that the above
+copyright notice and this paragraph are included on all such copies and
+derivative works.However, this document itself may not be modified in any
+way, such as by removing the copyright notice or references to the Internet
+Society or other Internet organizations, except as needed for the purpose of
+developing Internet standards in which case the procedures for copyrights
+defined in the Internet Standards process must be followed, or as required
+to translate it into languages other than English.
+
+The limited permissions granted above are perpetual and will not be revoked
+by the Internet Society or its successors or assigns.
+
+This document and the information contained herein is provided on an "AS IS"
+basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE
+DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY
+RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
+PARTICULAR PURPOSE.
+
+License notice for Algorithm from RFC 4122 - 
+A Universally Unique IDentifier (UUID) URN Namespace
+----------------------------------------------------
+
+Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc.
+Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. &
+Digital Equipment Corporation, Maynard, Mass.
+Copyright (c) 1998 Microsoft.
+To anyone who acknowledges that this file is provided "AS IS"
+without any express or implied warranty: permission to use, copy,
+modify, and distribute this file for any purpose is hereby
+granted without fee, provided that the above copyright notices and
+this notice appears in all source code copies, and that none of
+the names of Open Software Foundation, Inc., Hewlett-Packard
+Company, Microsoft, or Digital Equipment Corporation be used in
+advertising or publicity pertaining to distribution of the software
+without specific, written prior permission. Neither Open Software
+Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital
+Equipment Corporation makes any representations about the
+suitability of this software for any purpose."
+
+License notice for The LLVM Compiler Infrastructure
+---------------------------------------------------
+
+Developed by:
+
+    LLVM Team
+
+    University of Illinois at Urbana-Champaign
+
+    http://llvm.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimers.
+
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimers in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the names of the LLVM Team, University of Illinois at
+      Urbana-Champaign, nor the names of its contributors may be used to
+      endorse or promote products derived from this Software without specific
+      prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.
+
+License notice for Bit Twiddling Hacks
+--------------------------------------
+
+Bit Twiddling Hacks
+
+By Sean Eron Anderson
+seander@cs.stanford.edu
+
+Individually, the code snippets here are in the public domain (unless otherwise
+noted) â€” feel free to use them however you please. The aggregate collection and
+descriptions are Â© 1997-2005 Sean Eron Anderson. The code and descriptions are
+distributed in the hope that they will be useful, but WITHOUT ANY WARRANTY and
+without even the implied warranty of merchantability or fitness for a particular
+purpose. 
+
+License notice for Bob Jenkins
+------------------------------
+
+By Bob Jenkins, 1996.  bob_jenkins@burtleburtle.net.  You may use this
+code any way you wish, private, educational, or commercial.  It's free.
+
+License notice for Greg Parker
+------------------------------
+
+Greg Parker     gparker@cs.stanford.edu     December 2000
+This code is in the public domain and may be copied or modified without 
+permission. 
+
+License notice for libunwind8 based code
+----------------------------------------
+
+Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P.
+   Contributed by David Mosberger-Tang <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.
index 496cb563a7b18f936c0bbbea871547866ead5d94..f5adb8e0867e47488a7991c438c1f5100f2d9fb2 100644 (file)
@@ -3,48 +3,83 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
 VisualStudioVersion = 15.0.27004.2005
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.NETCore", "src\SOS\NETCore\SOS.NETCore.csproj", "{20513BA2-A156-4A17-4C70-5AC2DBD4F833}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.NETCore", "src\SOS\SOS.NETCore\SOS.NETCore.csproj", "{20513BA2-A156-4A17-4C70-5AC2DBD4F833}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestDebuggee", "src\SOS\TestDebuggee\TestDebuggee.csproj", "{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestDebuggee", "src\SOS\lldbplugin.tests\TestDebuggee\TestDebuggee.csproj", "{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DebuggerTests", "src\DebuggerTests\DebuggerTests.csproj", "{730C1201-1848-4F1E-8C1F-6316FB886C35}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostic.TestHelpers", "src\Microsoft.Diagnostic.TestHelpers\Microsoft.Diagnostic.TestHelpers.csproj", "{730C1201-1848-4F1E-8C1F-6316FB886C35}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SOS", "SOS", "{41638A4C-0DAF-47ED-A774-ECBBAC0315D7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.UnitTests", "src\SOS\SOS.UnitTests\SOS.UnitTests.csproj", "{1532DB3C-7DCD-45C6-B697-62B8378A16A2}"
 EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
                Debug|x64 = Debug|x64
+               Debug|x86 = Debug|x86
                Release|Any CPU = Release|Any CPU
                Release|x64 = Release|x64
+               Release|x86 = Release|x86
        EndGlobalSection
        GlobalSection(ProjectConfigurationPlatforms) = postSolution
                {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x64.ActiveCfg = Debug|Any CPU
                {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x64.Build.0 = Debug|Any CPU
+               {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x86.Build.0 = Debug|Any CPU
                {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.Build.0 = Release|Any CPU
                {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x64.ActiveCfg = Release|Any CPU
                {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x64.Build.0 = Release|Any CPU
+               {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x86.ActiveCfg = Release|Any CPU
+               {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x86.Build.0 = Release|Any CPU
                {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x64.ActiveCfg = Debug|Any CPU
                {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x64.Build.0 = Debug|Any CPU
+               {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x86.Build.0 = Debug|Any CPU
                {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|Any CPU.Build.0 = Release|Any CPU
                {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x64.ActiveCfg = Release|Any CPU
                {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x64.Build.0 = Release|Any CPU
+               {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x86.ActiveCfg = Release|Any CPU
+               {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x86.Build.0 = Release|Any CPU
                {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x64.ActiveCfg = Debug|Any CPU
                {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x64.Build.0 = Debug|Any CPU
+               {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x86.Build.0 = Debug|Any CPU
                {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|Any CPU.Build.0 = Release|Any CPU
                {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x64.ActiveCfg = Release|Any CPU
                {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x64.Build.0 = Release|Any CPU
+               {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x86.ActiveCfg = Release|Any CPU
+               {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x86.Build.0 = Release|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x64.Build.0 = Debug|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x86.Build.0 = Debug|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|Any CPU.Build.0 = Release|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x64.ActiveCfg = Release|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x64.Build.0 = Release|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x86.ActiveCfg = Release|Any CPU
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x86.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
        EndGlobalSection
+       GlobalSection(NestedProjects) = preSolution
+               {20513BA2-A156-4A17-4C70-5AC2DBD4F833} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
+               {6C43BE85-F8C3-4D76-8050-F25CE953A7FD} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
+               {1532DB3C-7DCD-45C6-B697-62B8378A16A2} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
+       EndGlobalSection
        GlobalSection(ExtensibilityGlobals) = postSolution
                SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
        EndGlobalSection
index ac57defbd8466975e491df82503b61bf5d5707fe..b2bf88b513aa84a00dabcca3674d3c5be6cff501 100644 (file)
@@ -34,9 +34,9 @@ set ghprbCommentBody=
 ::      __ProjectDir        -- default: directory of the dir.props file
 ::      __SourceDir         -- default: %__ProjectDir%\src\
 ::      __RootBinDir        -- default: %__ProjectDir%\artifacts\
-::      __BinDir            -- default: %__RootBinDir%\bin\%__BuildOS%.%__BuildArch.%__BuildType%\
 ::      __IntermediatesDir  -- default: %__RootBinDir%\obj\%__BuildOS%.%__BuildArch.%__BuildType%\
-::      __LogDir            -- default: %__RootBinDir%\log\%__BuildOS%.%__BuildArch.%__BuildType%\
+::      __BinDir            -- default: %__RootBinDir%\%__BuildType%\bin\%__BuildOS%.%__BuildArch\
+::      __LogDir            -- default: %__RootBinDir%\%__BuildType%\log\%__BuildOS%.%__BuildArch\
 ::
 :: Thus, these variables are not simply internal to this script!
 
@@ -58,9 +58,6 @@ set "__ProjectDir=%~dp0"
 if %__ProjectDir:~-1%==\ set "__ProjectDir=%__ProjectDir:~0,-1%"
 set "__ProjectDir=%__ProjectDir%\.."
 set "__SourceDir=%__ProjectDir%\src"
-set "__PackagesDir=%DotNetRestorePackagesPath%"
-if [%__PackagesDir%]==[] set "__PackagesDir=%__ProjectDir%\packages"
-set "__RootBinDir=%__ProjectDir%\artifacts"
 
 :: __UnprocessedBuildArgs are args that we pass to msbuild (e.g. /p:__BuildArch=x64)
 set "__args=%*"
@@ -118,17 +115,17 @@ if /i "%__BuildType%"=="debug" set __BuildType=Debug
 if /i "%__BuildType%"=="release" set __BuildType=Release
 
 :: Set the remaining variables based upon the determined build configuration
-set "__BinDir=%__RootBinDir%\bin\%__BuildOS%.%__BuildArch%.%__BuildType%"
+set "__RootBinDir=%__ProjectDir%\artifacts"
+set "__ConfigBinDir=%__RootBinDir%\%__BuildType%"
+set "__BinDir=%__ConfigBinDir%\bin\%__BuildOS%.%__BuildArch%"
+set "__LogDir=%__ConfigBinDir%\log\%__BuildOS%.%__BuildArch%"
 set "__IntermediatesDir=%__RootBinDir%\obj\%__BuildOS%.%__BuildArch%.%__BuildType%"
-set "__LogDir=%__RootBinDir%\log\%__BuildOS%.%__BuildArch%.%__BuildType%"
-if "%__NMakeMakefiles%"=="1" (set "__IntermediatesDir=%__RootBinDir%\nmakeobj\%__BuildOS%.%__BuildArch%.%__BuildType%")
+set "__PackagesBinDir=%__ConfigBinDir%\packages"
 
-set "__PackagesBinDir=%__BinDir%\.nuget"
 set "__CrossComponentBinDir=%__BinDir%"
 set "__CrossCompIntermediatesDir=%__IntermediatesDir%\crossgen"
-
 if NOT "%__CrossArch%" == "" set __CrossComponentBinDir=%__CrossComponentBinDir%\%__CrossArch%
-set "__CrossGenCoreLibLog=%__LogDir%\CrossgenCoreLib_%__BuildOS%__%__BuildArch%__%__BuildType%.log"
+set "__CrossGenCoreLibLog=%__LogDir%\CrossgenCoreLib_%__BuildOS%_%__BuildArch%.log"
 set "__CrossgenExe=%__CrossComponentBinDir%\crossgen.exe"
 
 :: Generate path to be set for CMAKE_INSTALL_PREFIX to contain forward slash
index 70f208ac9596a2d6f3661bd3bc45be0e9b7d5549..693f5821a3764402b8a7c0da9a784dd790d4685e 100755 (executable)
@@ -261,13 +261,17 @@ if [ "$__BuildType" == "debug" ]; then
 fi
 
 __RootBinDir=$__ProjectRoot/artifacts
-__IntermediatesDir="$__RootBinDir/obj/$__BuildOS.$__BuildArch.$__BuildType"
-__LogFileDir="$__RootBinDir/log/$__BuildOS.$__BuildArch.$__BuildType"
-__ExtraCmakeArgs="-DCLR_MANAGED_BINARY_DIR=$__RootBinDir/$__BuildType/bin"
+__ConfigBinDir=$__RootBinDir/$__BuildType
+__BinDir=$__ConfigBinDir/bin/$__BuildOS.$__BuildArch
+__LogDir=$__ConfigBinDir/log/$__BuildOS.$__BuildArch
+__IntermediatesDir=$__RootBinDir/obj/$__BuildOS.$__BuildArch.$__BuildType
+__ResultsDir=$__ConfigBinDir/TestResults
+__PackagesBinDir=$__ConfigBinDir/packages
+__ExtraCmakeArgs=-DCLR_MANAGED_BINARY_DIR=$__ConfigBinDir/bin
 
 # Specify path to be set for CMAKE_INSTALL_PREFIX.
 # This is where all built native libraries will copied to.
-export __CMakeBinDir="$__RootBinDir/bin/$__BuildOS.$__BuildArch.$__BuildType"
+export __CMakeBinDir="$__BinDir"
 
 # Set default clang version
 if [[ $__ClangMajorVersion == 0 && $__ClangMinorVersion == 0 ]]; then
@@ -286,7 +290,7 @@ if [[ "$__BuildArch" == "armel" ]]; then
 fi
 
 mkdir -p "$__IntermediatesDir"
-mkdir -p "$__LogFileDir"
+mkdir -p "$__LogDir"
 mkdir -p "$__CMakeBinDir"
 
 build_native()
@@ -304,7 +308,7 @@ build_native()
 
     pushd "$intermediatesForBuild"
     echo "Invoking \"$__ProjectRoot/eng/gen-buildsys-clang.sh\" \"$__ProjectRoot\" $__ClangMajorVersion $__ClangMinorVersion $platformArch $__BuildType $generator $extraCmakeArguments $__cmakeargs"
-    "$__ProjectRoot/eng/gen-buildsys-clang.sh" "$__ProjectRoot" $__ClangMajorVersion $__ClangMinorVersion $platformArch $__BuildType $generator "$extraCmakeArguments" "$__cmakeargs"
+    "$__ProjectRoot/eng/gen-buildsys-clang.sh" "$__ProjectRoot" $__ClangMajorVersion $__ClangMinorVersion $platformArch $__BuildType $generator "$extraCmakeArguments" "$__cmakeargs" | tee $__LogDir/cmake.log
     popd
 
     if [ ! -f "$intermediatesForBuild/$buildFile" ]; then
@@ -317,7 +321,7 @@ build_native()
 
     echo "Executing $buildTool install -j $__NumProc"
 
-    $buildTool install -j $__NumProc
+    $buildTool install -j $__NumProc | tee $__LogDir/make.log
     if [ $? != 0 ]; then
         echo "Failed to build."
         exit 1
@@ -449,6 +453,11 @@ if [ $__Test == 1 ]; then
         export GDB_PATH="$(which gdb 2> /dev/null)"
     fi
 
+    if [ "$__HostOS" == "Linux" ]; then
+        # This is needed on some distros like centos 7 so gdb generate-core-file creates a dump that works
+       echo 0x37 > /proc/self/coredump_filter
+    fi
+
     echo "lldb: '$LLDB_PATH' gdb: '$GDB_PATH'"
 
     # Run xunit SOS tests
@@ -463,7 +472,8 @@ if [ $__Test == 1 ]; then
         __Plugin=$__CMakeBinDir/libsosplugin.so
     fi
 
-    "$__ProjectRoot/src/SOS/tests/testsos.sh" "$__ProjectRoot" "$__Plugin" "$__RootBinDir/$__BuildType/bin" "$__LogFileDir"
+    # Run lldb python tests
+    "$__ProjectRoot/src/SOS/lldbplugin.tests/testsos.sh" "$__ProjectRoot" "$__Plugin" "$__ConfigBinDir/bin" "$__ResultsDir"
     if [ $? != 0 ]; then
         exit 1
     fi
index 660930fed12cbc7f8750bd3eabd830e6cb743130..73db5193676f428334352842fc5a304bdd19e920 100755 (executable)
@@ -17,7 +17,7 @@ done
 scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
 
 # remove the options that build.sh can't handle and pass it to build-native.sh
-__args="$(echo $@ | sed 's/--test//g;s/--clang[0-9]\.[0-9]//g;s/--fixdockerimage//g')"
+__args="$(echo $@ | sed 's/--test//g;s/--clang[0-9]\.[0-9]//g')"
 
 # build managed components
 "$scriptroot/common/build.sh" $__args
@@ -27,3 +27,6 @@ fi
 
 # build native components and test both
 "$scriptroot/build-native.sh" $@
+if [[ $? != 0 ]]; then
+    exit 1
+fi
index 83e8b0430de10b6a05b17d7f1b11c801091d903c..9519e111ec1b71c22cfd4ad357daa6e846c37cf4 100755 (executable)
@@ -38,3 +38,6 @@ if [ "$__osname" == "Linux" ]; then
 fi
 
 "$scriptroot/build.sh" --ci $@
+if [[ $? != 0 ]]; then
+    exit 1
+fi
index 8f0f21306756e51bc49db9885d022557d23bfce5..73c8fd9e895219987486f582f09a68e80e75ddea 100755 (executable)
@@ -64,7 +64,11 @@ $docker_bin exec $docker_id su -c "echo '%container_SUDO_user ALL=(ALL:ALL) NOPA
 
 echo "Execute cibuild.sh $args"
 $docker_bin exec --workdir=$source_directory --user $container_user_name $docker_id $source_directory/eng/cibuild.sh $args
+lasterrorcode=$?
 
 echo "Cleanup Docker Container/Network"
 $docker_bin container stop $docker_id
 $docker_bin network rm vsts_network_$docker_container_name
+
+exit $lasterrorcode
+
index ad8f2a5300bf0f95e48f9af838ce7aca79210b3e..d612380d1c5bbd8b65e033410f1700e3f7737162 100644 (file)
@@ -1,6 +1,6 @@
 {
   "sdk": {
-    "version": "2.1.300-rc1-008673"
+    "version": "2.1.300"
   },
   "msbuild-sdks": {
     "RoslynTools.RepoToolset": "1.0.0-beta2-62810-01"
diff --git a/src/DebuggerTests/AcquireDotNetTestStep.cs b/src/DebuggerTests/AcquireDotNetTestStep.cs
deleted file mode 100644 (file)
index 75f79b0..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-using System;
-using System.IO;
-using System.IO.Compression;
-using System.Net;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-{
-    /// <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();
-        }
-
-    }
-}
diff --git a/src/DebuggerTests/AssertX.cs b/src/DebuggerTests/AssertX.cs
deleted file mode 100644 (file)
index f64bee7..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-using Debugger.Tests;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-{
-    public static partial class AssertX
-    {
-        public static void DirectoryExists(string dirDescriptiveName, string dirPath, ITestOutputHelper output)
-        {
-            if (!Directory.Exists(dirPath))
-            {
-                string errorMessage = "Expected " + dirDescriptiveName + " to exist: " + dirPath;
-                output.WriteLine(errorMessage);
-                try
-                {
-                    string parentDir = dirPath;
-                    while (true)
-                    {
-                        if (Directory.Exists(Path.GetDirectoryName(parentDir)))
-                        {
-                            output.WriteLine("First parent directory that exists: " + Path.GetDirectoryName(parentDir));
-                            break;
-                        }
-                        if (Path.GetDirectoryName(parentDir) == parentDir)
-                        {
-                            output.WriteLine("Also unable to find any parent directory that exists");
-                            break;
-                        }
-                        parentDir = Path.GetDirectoryName(parentDir);
-                    }
-                }
-                catch (Exception e)
-                {
-                    output.WriteLine("Additional error while trying to diagnose missing directory:");
-                    output.WriteLine(e.GetType() + ": " + e.Message);
-                }
-                throw new DirectoryNotFoundException(errorMessage);
-            }
-        }
-
-        public static void FileExists(string fileDescriptiveName, string filePath, ITestOutputHelper output)
-        {
-            if (!File.Exists(filePath))
-            {
-                string errorMessage = "Expected " + fileDescriptiveName + " to exist: " + filePath;
-                output.WriteLine(errorMessage);
-                try
-                {
-                    string parentDir = filePath;
-                    while (true)
-                    {
-                        if (Directory.Exists(Path.GetDirectoryName(parentDir)))
-                        {
-                            output.WriteLine("First parent directory that exists: " + Path.GetDirectoryName(parentDir));
-                            break;
-                        }
-                        if (Path.GetDirectoryName(parentDir) == parentDir)
-                        {
-                            output.WriteLine("Also unable to find any parent directory that exists");
-                            break;
-                        }
-                        parentDir = Path.GetDirectoryName(parentDir);
-                    }
-                }
-                catch (Exception e)
-                {
-                    output.WriteLine("Additional error while trying to diagnose missing file:");
-                    output.WriteLine(e.GetType() + ": " + e.Message);
-                }
-                throw new FileNotFoundException(errorMessage);
-            }
-        }
-    }
-}
-
-
diff --git a/src/DebuggerTests/BaseDebuggeeCompiler.cs b/src/DebuggerTests/BaseDebuggeeCompiler.cs
deleted file mode 100644 (file)
index 3422c4a..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.IO.Compression;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml;
-using System.Xml.Linq;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-{
-    /// <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);
-    }
-}
diff --git a/src/DebuggerTests/CliDebuggeeCompiler.cs b/src/DebuggerTests/CliDebuggeeCompiler.cs
deleted file mode 100644 (file)
index 951f21b..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.IO.Compression;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml;
-using System.Xml.Linq;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-{
-    /// <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));
-        }
-    }
-}
diff --git a/src/DebuggerTests/ConsoleTestOutputHelper.cs b/src/DebuggerTests/ConsoleTestOutputHelper.cs
deleted file mode 100644 (file)
index f35dfc1..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-using System;
-using System.Text;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests
-{
-    public class ConsoleTestOutputHelper : ITestOutputHelper
-    {
-        public void WriteLine(string message)
-        {
-            Console.WriteLine(message);
-        }
-
-        public void WriteLine(string format, params object[] args)
-        {
-            Console.WriteLine(format, args);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/DebuggerTests/CsprojBuildDebuggeeTestStep.cs b/src/DebuggerTests/CsprojBuildDebuggeeTestStep.cs
deleted file mode 100644 (file)
index 5742187..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-{
-    /// <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);
-        }
-    }
-}
diff --git a/src/DebuggerTests/DebuggeeCompiler.cs b/src/DebuggerTests/DebuggeeCompiler.cs
deleted file mode 100644 (file)
index 2a9ab9a..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-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; }
-    }
-}
diff --git a/src/DebuggerTests/DebuggerTests.csproj b/src/DebuggerTests/DebuggerTests.csproj
deleted file mode 100644 (file)
index 93b3cb5..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<!-- 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>
diff --git a/src/DebuggerTests/DotNetBuildDebuggeeTestStep.cs b/src/DebuggerTests/DotNetBuildDebuggeeTestStep.cs
deleted file mode 100644 (file)
index df4f99b..0000000
+++ /dev/null
@@ -1,374 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-{
-    /// <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);
-    }
-}
diff --git a/src/DebuggerTests/FileTestOutputHelper.cs b/src/DebuggerTests/FileTestOutputHelper.cs
deleted file mode 100644 (file)
index 16f26bf..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests
-{
-    /// <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();
-            }
-        }
-    }
-}
diff --git a/src/DebuggerTests/IProcessLogger.cs b/src/DebuggerTests/IProcessLogger.cs
deleted file mode 100644 (file)
index 8213467..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace Debugger.Tests
-{
-    public enum ProcessStream
-    {
-        StandardIn = 0,
-        StandardOut = 1,
-        StandardError = 2,
-        MaxStreams = 3
-    }
-
-    public enum KillReason
-    {
-        TimedOut,
-        Unknown
-    }
-
-    public interface IProcessLogger
-    {
-        void ProcessExited(ProcessRunner runner);
-        void ProcessKilled(ProcessRunner runner, KillReason reason);
-        void ProcessStarted(ProcessRunner runner);
-        void Write(ProcessRunner runner, string data, ProcessStream stream);
-        void WriteLine(ProcessRunner runner, string data, ProcessStream stream);
-    }
-}
\ No newline at end of file
diff --git a/src/DebuggerTests/IndentedTestOutputHelper.cs b/src/DebuggerTests/IndentedTestOutputHelper.cs
deleted file mode 100644 (file)
index cd3390d..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests
-{
-    /// <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);
-        }
-    }
-}
diff --git a/src/DebuggerTests/MultiplexTestOutputHelper.cs b/src/DebuggerTests/MultiplexTestOutputHelper.cs
deleted file mode 100644 (file)
index d64a2c4..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using System.Text;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests
-{
-    public class MultiplexTestOutputHelper : ITestOutputHelper
-    {
-        readonly ITestOutputHelper[] _outputs;
-
-        public MultiplexTestOutputHelper(params ITestOutputHelper[] outputs)
-        {
-            _outputs = outputs;
-        }
-
-        public void WriteLine(string message)
-        {
-            foreach(ITestOutputHelper output in _outputs)
-            {
-                output.WriteLine(message);
-            }
-        }
-
-        public void WriteLine(string format, params object[] args)
-        {
-            foreach (ITestOutputHelper output in _outputs)
-            {
-                output.WriteLine(format, args);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/DebuggerTests/PrebuiltDebuggeeCompiler.cs b/src/DebuggerTests/PrebuiltDebuggeeCompiler.cs
deleted file mode 100644 (file)
index e7ea22a..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-{
-    public class PrebuiltDebuggeeCompiler : IDebuggeeCompiler
-    {
-        string _sourcePath;
-        string _binaryPath;
-        string _binaryExePath;
-
-        public PrebuiltDebuggeeCompiler(TestConfiguration config, string debuggeeName)
-        {
-            //we anticipate paths like this:
-            //Source:   <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
diff --git a/src/DebuggerTests/ProcessRunner.cs b/src/DebuggerTests/ProcessRunner.cs
deleted file mode 100644 (file)
index eff46ee..0000000
+++ /dev/null
@@ -1,466 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests
-{
-    /// <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);
-                }
-            }
-        }
-    }
-}
diff --git a/src/DebuggerTests/TestConfiguration.cs b/src/DebuggerTests/TestConfiguration.cs
deleted file mode 100644 (file)
index 594a2de..0000000
+++ /dev/null
@@ -1,611 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests
-{
-    public class TestRunConfiguration : IDisposable
-    {
-        public static TestRunConfiguration Instance
-        {
-            get { return _instance.Value; }
-        }
-
-        static Lazy<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;
-            }
-        }
-    }
-}
diff --git a/src/DebuggerTests/TestOutputProcessLogger.cs b/src/DebuggerTests/TestOutputProcessLogger.cs
deleted file mode 100644 (file)
index 5a35ddd..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Xml;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests
-{
-    public class TestOutputProcessLogger : IProcessLogger
-    {
-        string _timeFormat = "mm\\:ss\\.fff";
-        ITestOutputHelper _output;
-        StringBuilder[] _lineBuffers;
-
-        public TestOutputProcessLogger(ITestOutputHelper output)
-        {
-            _output = output;
-            _lineBuffers = new StringBuilder[(int)ProcessStream.MaxStreams];
-        }
-
-        public void ProcessStarted(ProcessRunner runner)
-        {
-            lock (this)
-            {
-                _output.WriteLine("Running Process: " + runner.ReplayCommand);
-                _output.WriteLine("Working Directory: " + runner.WorkingDirectory);
-                IEnumerable<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
diff --git a/src/DebuggerTests/TestRunner.cs b/src/DebuggerTests/TestRunner.cs
deleted file mode 100644 (file)
index 21d0c31..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-using Debugger.Tests.Build;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests
-{
-    public class TestRunner
-    {
-        /// <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;
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/src/DebuggerTests/TestStep.cs b/src/DebuggerTests/TestStep.cs
deleted file mode 100644 (file)
index 6f92871..0000000
+++ /dev/null
@@ -1,633 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Xml;
-using System.Xml.Linq;
-using Xunit.Abstractions;
-
-namespace Debugger.Tests.Build
-{
-    /// <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)
-        { }
-    }
-}
diff --git a/src/DebuggerTests/Xunit.Extensions/SkipTestException.cs b/src/DebuggerTests/Xunit.Extensions/SkipTestException.cs
deleted file mode 100644 (file)
index 21c5dbf..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-using System;
-
-namespace Xunit.Extensions
-{
-    public class SkipTestException : Exception
-    {
-        public SkipTestException(string reason)
-            : base(reason) { }
-    }
-}
diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableFactAttribute.cs b/src/DebuggerTests/Xunit.Extensions/SkippableFactAttribute.cs
deleted file mode 100644 (file)
index 6ee8889..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-using Xunit;
-using Xunit.Sdk;
-
-namespace Xunit.Extensions
-{
-    [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableFactDiscoverer", "Debugger.Tests")]
-    public class SkippableFactAttribute : FactAttribute { }
-}
diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableFactDiscoverer.cs b/src/DebuggerTests/Xunit.Extensions/SkippableFactDiscoverer.cs
deleted file mode 100644 (file)
index 0e3eccf..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Collections.Generic;
-using Xunit.Abstractions;
-using Xunit.Sdk;
-
-namespace Xunit.Extensions
-{
-    public class SkippableFactDiscoverer : IXunitTestCaseDiscoverer
-    {
-        readonly IMessageSink diagnosticMessageSink;
-
-        public SkippableFactDiscoverer(IMessageSink diagnosticMessageSink)
-        {
-            this.diagnosticMessageSink = diagnosticMessageSink;
-        }
-
-        public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
-        {
-            yield return new SkippableFactTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
-        }
-    }
-}
diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableFactMessageBus.cs b/src/DebuggerTests/Xunit.Extensions/SkippableFactMessageBus.cs
deleted file mode 100644 (file)
index 9a7fa30..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-using System.Linq;
-using Xunit.Abstractions;
-using Xunit.Sdk;
-
-namespace Xunit.Extensions
-{
-    public class SkippableFactMessageBus : IMessageBus
-    {
-        readonly IMessageBus innerBus;
-
-        public SkippableFactMessageBus(IMessageBus innerBus)
-        {
-            this.innerBus = innerBus;
-        }
-
-        public int DynamicallySkippedTestCount { get; private set; }
-
-        public void Dispose() { }
-
-        public bool QueueMessage(IMessageSinkMessage message)
-        {
-            var testFailed = message as ITestFailed;
-            if (testFailed != null)
-            {
-                var exceptionType = testFailed.ExceptionTypes.FirstOrDefault();
-                if (exceptionType == typeof(SkipTestException).FullName)
-                {
-                    DynamicallySkippedTestCount++;
-                    return innerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault()));
-                }
-            }
-
-            // Nothing we care about, send it on its way
-            return innerBus.QueueMessage(message);
-        }
-    }
-}
diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableFactTestCase.cs b/src/DebuggerTests/Xunit.Extensions/SkippableFactTestCase.cs
deleted file mode 100644 (file)
index 6e34cde..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-using Xunit.Sdk;
-
-namespace Xunit.Extensions
-{
-    public class SkippableFactTestCase : XunitTestCase
-    {
-        [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
-        public SkippableFactTestCase() { }
-
-        public SkippableFactTestCase(IMessageSink diagnosticMessageSink, Sdk.TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null)
-            : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments) { }
-
-        public override async Task<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;
-        }
-    }
-}
diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableTheoryAttribute.cs b/src/DebuggerTests/Xunit.Extensions/SkippableTheoryAttribute.cs
deleted file mode 100644 (file)
index fd98412..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-using Xunit;
-using Xunit.Sdk;
-
-namespace Xunit.Extensions
-{
-    [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableTheoryDiscoverer", "Debugger.Tests")]
-    public class SkippableTheoryAttribute : TheoryAttribute { }
-}
diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableTheoryDiscoverer.cs b/src/DebuggerTests/Xunit.Extensions/SkippableTheoryDiscoverer.cs
deleted file mode 100644 (file)
index 2640a02..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Xunit.Abstractions;
-using Xunit.Sdk;
-
-namespace Xunit.Extensions
-{
-    public class SkippableTheoryDiscoverer : IXunitTestCaseDiscoverer
-    {
-        readonly IMessageSink diagnosticMessageSink;
-        readonly TheoryDiscoverer theoryDiscoverer;
-
-        public SkippableTheoryDiscoverer(IMessageSink diagnosticMessageSink)
-        {
-            this.diagnosticMessageSink = diagnosticMessageSink;
-
-            theoryDiscoverer = new TheoryDiscoverer(diagnosticMessageSink);
-        }
-
-        public IEnumerable<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));
-        }
-    }
-}
diff --git a/src/DebuggerTests/Xunit.Extensions/SkippableTheoryTestCase.cs b/src/DebuggerTests/Xunit.Extensions/SkippableTheoryTestCase.cs
deleted file mode 100644 (file)
index a27012f..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Xunit.Abstractions;
-using Xunit.Sdk;
-
-namespace Xunit.Extensions
-{
-    public class SkippableTheoryTestCase : XunitTheoryTestCase
-    {
-        [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
-        public SkippableTheoryTestCase() { }
-
-        public SkippableTheoryTestCase(IMessageSink diagnosticMessageSink, Xunit.Sdk.TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod)
-            : base(diagnosticMessageSink, defaultMethodDisplay, testMethod) { }
-
-        public override async Task<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;
-        }
-    }
-}
diff --git a/src/DebuggerTests/Xunit.Extensions/license.txt b/src/DebuggerTests/Xunit.Extensions/license.txt
deleted file mode 100644 (file)
index 39b2d65..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-Source in this directory is derived source at https://github.com/xunit/samples.xunit. The repo provided the following license:
-
-Copyright 2014 Outercurve Foundation
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostic.TestHelpers/AcquireDotNetTestStep.cs b/src/Microsoft.Diagnostic.TestHelpers/AcquireDotNetTestStep.cs
new file mode 100644 (file)
index 0000000..e18cfb8
--- /dev/null
@@ -0,0 +1,221 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Net;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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();
+        }
+
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/AssertX.cs b/src/Microsoft.Diagnostic.TestHelpers/AssertX.cs
new file mode 100644 (file)
index 0000000..2672ca9
--- /dev/null
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    public static partial class AssertX
+    {
+        public static void DirectoryExists(string dirDescriptiveName, string dirPath, ITestOutputHelper output)
+        {
+            if (!Directory.Exists(dirPath))
+            {
+                string errorMessage = "Expected " + dirDescriptiveName + " to exist: " + dirPath;
+                output.WriteLine(errorMessage);
+                try
+                {
+                    string parentDir = dirPath;
+                    while (true)
+                    {
+                        if (Directory.Exists(Path.GetDirectoryName(parentDir)))
+                        {
+                            output.WriteLine("First parent directory that exists: " + Path.GetDirectoryName(parentDir));
+                            break;
+                        }
+                        if (Path.GetDirectoryName(parentDir) == parentDir)
+                        {
+                            output.WriteLine("Also unable to find any parent directory that exists");
+                            break;
+                        }
+                        parentDir = Path.GetDirectoryName(parentDir);
+                    }
+                }
+                catch (Exception e)
+                {
+                    output.WriteLine("Additional error while trying to diagnose missing directory:");
+                    output.WriteLine(e.GetType() + ": " + e.Message);
+                }
+                throw new DirectoryNotFoundException(errorMessage);
+            }
+        }
+
+        public static void FileExists(string fileDescriptiveName, string filePath, ITestOutputHelper output)
+        {
+            if (!File.Exists(filePath))
+            {
+                string errorMessage = "Expected " + fileDescriptiveName + " to exist: " + filePath;
+                output.WriteLine(errorMessage);
+                try
+                {
+                    string parentDir = filePath;
+                    while (true)
+                    {
+                        if (Directory.Exists(Path.GetDirectoryName(parentDir)))
+                        {
+                            output.WriteLine("First parent directory that exists: " + Path.GetDirectoryName(parentDir));
+                            break;
+                        }
+                        if (Path.GetDirectoryName(parentDir) == parentDir)
+                        {
+                            output.WriteLine("Also unable to find any parent directory that exists");
+                            break;
+                        }
+                        parentDir = Path.GetDirectoryName(parentDir);
+                    }
+                }
+                catch (Exception e)
+                {
+                    output.WriteLine("Additional error while trying to diagnose missing file:");
+                    output.WriteLine(e.GetType() + ": " + e.Message);
+                }
+                throw new FileNotFoundException(errorMessage);
+            }
+        }
+    }
+}
+
+
diff --git a/src/Microsoft.Diagnostic.TestHelpers/BaseDebuggeeCompiler.cs b/src/Microsoft.Diagnostic.TestHelpers/BaseDebuggeeCompiler.cs
new file mode 100644 (file)
index 0000000..ab67354
--- /dev/null
@@ -0,0 +1,233 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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);
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/CliDebuggeeCompiler.cs b/src/Microsoft.Diagnostic.TestHelpers/CliDebuggeeCompiler.cs
new file mode 100644 (file)
index 0000000..bd853f2
--- /dev/null
@@ -0,0 +1,80 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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));
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/ConsoleTestOutputHelper.cs b/src/Microsoft.Diagnostic.TestHelpers/ConsoleTestOutputHelper.cs
new file mode 100644 (file)
index 0000000..38b5af9
--- /dev/null
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    public class ConsoleTestOutputHelper : ITestOutputHelper
+    {
+        public void WriteLine(string message)
+        {
+            Console.WriteLine(message);
+        }
+
+        public void WriteLine(string format, params object[] args)
+        {
+            Console.WriteLine(format, args);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostic.TestHelpers/CsprojBuildDebuggeeTestStep.cs b/src/Microsoft.Diagnostic.TestHelpers/CsprojBuildDebuggeeTestStep.cs
new file mode 100644 (file)
index 0000000..58ce9db
--- /dev/null
@@ -0,0 +1,128 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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);
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/DebuggeeCompiler.cs b/src/Microsoft.Diagnostic.TestHelpers/DebuggeeCompiler.cs
new file mode 100644 (file)
index 0000000..725bca9
--- /dev/null
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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; }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/DotNetBuildDebuggeeTestStep.cs b/src/Microsoft.Diagnostic.TestHelpers/DotNetBuildDebuggeeTestStep.cs
new file mode 100644 (file)
index 0000000..a1030b7
--- /dev/null
@@ -0,0 +1,378 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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);
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/FileTestOutputHelper.cs b/src/Microsoft.Diagnostic.TestHelpers/FileTestOutputHelper.cs
new file mode 100644 (file)
index 0000000..58f7c18
--- /dev/null
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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();
+            }
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/IProcessLogger.cs b/src/Microsoft.Diagnostic.TestHelpers/IProcessLogger.cs
new file mode 100644 (file)
index 0000000..7097525
--- /dev/null
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    public enum ProcessStream
+    {
+        StandardIn = 0,
+        StandardOut = 1,
+        StandardError = 2,
+        MaxStreams = 3
+    }
+
+    public enum KillReason
+    {
+        TimedOut,
+        Unknown
+    }
+
+    public interface IProcessLogger
+    {
+        void ProcessExited(ProcessRunner runner);
+        void ProcessKilled(ProcessRunner runner, KillReason reason);
+        void ProcessStarted(ProcessRunner runner);
+        void Write(ProcessRunner runner, string data, ProcessStream stream);
+        void WriteLine(ProcessRunner runner, string data, ProcessStream stream);
+    }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostic.TestHelpers/IndentedTestOutputHelper.cs b/src/Microsoft.Diagnostic.TestHelpers/IndentedTestOutputHelper.cs
new file mode 100644 (file)
index 0000000..49f3a22
--- /dev/null
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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);
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Microsoft.Diagnostic.TestHelpers.csproj b/src/Microsoft.Diagnostic.TestHelpers/Microsoft.Diagnostic.TestHelpers.csproj
new file mode 100644 (file)
index 0000000..431e0d0
--- /dev/null
@@ -0,0 +1,21 @@
+<!-- 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>
diff --git a/src/Microsoft.Diagnostic.TestHelpers/MultiplexTestOutputHelper.cs b/src/Microsoft.Diagnostic.TestHelpers/MultiplexTestOutputHelper.cs
new file mode 100644 (file)
index 0000000..781f1d1
--- /dev/null
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    public class MultiplexTestOutputHelper : ITestOutputHelper
+    {
+        readonly ITestOutputHelper[] _outputs;
+
+        public MultiplexTestOutputHelper(params ITestOutputHelper[] outputs)
+        {
+            _outputs = outputs;
+        }
+
+        public void WriteLine(string message)
+        {
+            foreach(ITestOutputHelper output in _outputs)
+            {
+                output.WriteLine(message);
+            }
+        }
+
+        public void WriteLine(string format, params object[] args)
+        {
+            foreach (ITestOutputHelper output in _outputs)
+            {
+                output.WriteLine(format, args);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostic.TestHelpers/PrebuiltDebuggeeCompiler.cs b/src/Microsoft.Diagnostic.TestHelpers/PrebuiltDebuggeeCompiler.cs
new file mode 100644 (file)
index 0000000..6e0ae72
--- /dev/null
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    public class PrebuiltDebuggeeCompiler : IDebuggeeCompiler
+    {
+        string _sourcePath;
+        string _binaryPath;
+        string _binaryExePath;
+
+        public PrebuiltDebuggeeCompiler(TestConfiguration config, string debuggeeName)
+        {
+            //we anticipate paths like this:
+            //Source:   <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
diff --git a/src/Microsoft.Diagnostic.TestHelpers/ProcessRunner.cs b/src/Microsoft.Diagnostic.TestHelpers/ProcessRunner.cs
new file mode 100644 (file)
index 0000000..1e24048
--- /dev/null
@@ -0,0 +1,469 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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);
+                }
+            }
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/TestConfiguration.cs b/src/Microsoft.Diagnostic.TestHelpers/TestConfiguration.cs
new file mode 100644 (file)
index 0000000..8cc3b2a
--- /dev/null
@@ -0,0 +1,615 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Xml.Linq;
+using Xunit;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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; } }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/TestOutputProcessLogger.cs b/src/Microsoft.Diagnostic.TestHelpers/TestOutputProcessLogger.cs
new file mode 100644 (file)
index 0000000..177a7d7
--- /dev/null
@@ -0,0 +1,144 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    public class TestOutputProcessLogger : IProcessLogger
+    {
+        string _timeFormat = "mm\\:ss\\.fff";
+        ITestOutputHelper _output;
+        StringBuilder[] _lineBuffers;
+
+        public TestOutputProcessLogger(ITestOutputHelper output)
+        {
+            _output = output;
+            _lineBuffers = new StringBuilder[(int)ProcessStream.MaxStreams];
+        }
+
+        public void ProcessStarted(ProcessRunner runner)
+        {
+            lock (this)
+            {
+                _output.WriteLine("Running Process: " + runner.ReplayCommand);
+                _output.WriteLine("Working Directory: " + runner.WorkingDirectory);
+                IEnumerable<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
diff --git a/src/Microsoft.Diagnostic.TestHelpers/TestRunner.cs b/src/Microsoft.Diagnostic.TestHelpers/TestRunner.cs
new file mode 100644 (file)
index 0000000..f6ba3ee
--- /dev/null
@@ -0,0 +1,261 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    public class TestRunner
+    {
+        /// <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;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/TestStep.cs b/src/Microsoft.Diagnostic.TestHelpers/TestStep.cs
new file mode 100644 (file)
index 0000000..680646b
--- /dev/null
@@ -0,0 +1,636 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostic.TestHelpers
+{
+    /// <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)
+        { }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkipTestException.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkipTestException.cs
new file mode 100644 (file)
index 0000000..21c5dbf
--- /dev/null
@@ -0,0 +1,10 @@
+using System;
+
+namespace Xunit.Extensions
+{
+    public class SkipTestException : Exception
+    {
+        public SkipTestException(string reason)
+            : base(reason) { }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactAttribute.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactAttribute.cs
new file mode 100644 (file)
index 0000000..8421204
--- /dev/null
@@ -0,0 +1,8 @@
+using Xunit;
+using Xunit.Sdk;
+
+namespace Xunit.Extensions
+{
+    [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableFactDiscoverer", "Microsoft.Diagnostic.TestHelpers")]
+    public class SkippableFactAttribute : FactAttribute { }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactDiscoverer.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactDiscoverer.cs
new file mode 100644 (file)
index 0000000..0e3eccf
--- /dev/null
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Xunit.Extensions
+{
+    public class SkippableFactDiscoverer : IXunitTestCaseDiscoverer
+    {
+        readonly IMessageSink diagnosticMessageSink;
+
+        public SkippableFactDiscoverer(IMessageSink diagnosticMessageSink)
+        {
+            this.diagnosticMessageSink = diagnosticMessageSink;
+        }
+
+        public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
+        {
+            yield return new SkippableFactTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactMessageBus.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactMessageBus.cs
new file mode 100644 (file)
index 0000000..9a7fa30
--- /dev/null
@@ -0,0 +1,37 @@
+using System.Linq;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Xunit.Extensions
+{
+    public class SkippableFactMessageBus : IMessageBus
+    {
+        readonly IMessageBus innerBus;
+
+        public SkippableFactMessageBus(IMessageBus innerBus)
+        {
+            this.innerBus = innerBus;
+        }
+
+        public int DynamicallySkippedTestCount { get; private set; }
+
+        public void Dispose() { }
+
+        public bool QueueMessage(IMessageSinkMessage message)
+        {
+            var testFailed = message as ITestFailed;
+            if (testFailed != null)
+            {
+                var exceptionType = testFailed.ExceptionTypes.FirstOrDefault();
+                if (exceptionType == typeof(SkipTestException).FullName)
+                {
+                    DynamicallySkippedTestCount++;
+                    return innerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault()));
+                }
+            }
+
+            // Nothing we care about, send it on its way
+            return innerBus.QueueMessage(message);
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactTestCase.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableFactTestCase.cs
new file mode 100644 (file)
index 0000000..6e34cde
--- /dev/null
@@ -0,0 +1,34 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Xunit.Extensions
+{
+    public class SkippableFactTestCase : XunitTestCase
+    {
+        [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
+        public SkippableFactTestCase() { }
+
+        public SkippableFactTestCase(IMessageSink diagnosticMessageSink, Sdk.TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null)
+            : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments) { }
+
+        public override async Task<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;
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryAttribute.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryAttribute.cs
new file mode 100644 (file)
index 0000000..5496ea4
--- /dev/null
@@ -0,0 +1,7 @@
+using Xunit.Sdk;
+
+namespace Xunit.Extensions
+{
+    [XunitTestCaseDiscoverer("Xunit.Extensions.SkippableTheoryDiscoverer", "Microsoft.Diagnostic.TestHelpers")]
+    public class SkippableTheoryAttribute : TheoryAttribute { }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryDiscoverer.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryDiscoverer.cs
new file mode 100644 (file)
index 0000000..2640a02
--- /dev/null
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Xunit.Extensions
+{
+    public class SkippableTheoryDiscoverer : IXunitTestCaseDiscoverer
+    {
+        readonly IMessageSink diagnosticMessageSink;
+        readonly TheoryDiscoverer theoryDiscoverer;
+
+        public SkippableTheoryDiscoverer(IMessageSink diagnosticMessageSink)
+        {
+            this.diagnosticMessageSink = diagnosticMessageSink;
+
+            theoryDiscoverer = new TheoryDiscoverer(diagnosticMessageSink);
+        }
+
+        public IEnumerable<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));
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryTestCase.cs b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/SkippableTheoryTestCase.cs
new file mode 100644 (file)
index 0000000..a27012f
--- /dev/null
@@ -0,0 +1,35 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Xunit.Extensions
+{
+    public class SkippableTheoryTestCase : XunitTheoryTestCase
+    {
+        [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
+        public SkippableTheoryTestCase() { }
+
+        public SkippableTheoryTestCase(IMessageSink diagnosticMessageSink, Xunit.Sdk.TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod)
+            : base(diagnosticMessageSink, defaultMethodDisplay, testMethod) { }
+
+        public override async Task<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;
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/license.txt b/src/Microsoft.Diagnostic.TestHelpers/Xunit.Extensions/license.txt
new file mode 100644 (file)
index 0000000..39b2d65
--- /dev/null
@@ -0,0 +1,15 @@
+Source in this directory is derived source at https://github.com/xunit/samples.xunit. The repo provided the following license:
+
+Copyright 2014 Outercurve Foundation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
index 5837e033004302aafb816ade50527ba4440a5bf8..1530948cfefb3ee04d6438decbcfb532e9c15b05 100644 (file)
@@ -13,7 +13,7 @@ endif(WIN32)
 
 add_definitions(-D_SECURE_SCL=0)
 
-add_subdirectory(NETCore)
+add_subdirectory(SOS.NETCore)
 add_subdirectory(Strike)
 
 if(CLR_CMAKE_PLATFORM_UNIX)
diff --git a/src/SOS/NETCore/CMakeLists.txt b/src/SOS/NETCore/CMakeLists.txt
deleted file mode 100644 (file)
index 28ff7ea..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-project(SOS.NETCore)
-
-if(NOT ${CLR_MANAGED_BINARY_DIR} STREQUAL "")
-    set(MANAGED_BINDIR ${CLR_MANAGED_BINARY_DIR}/SOS.NETCore/netcoreapp1.0)
-
-    install(FILES ${MANAGED_BINDIR}/SOS.NETCore.dll DESTINATION .)
-    install(FILES ${MANAGED_BINDIR}/SOS.NETCore.pdb DESTINATION .)
-endif()
diff --git a/src/SOS/NETCore/SOS.NETCore.csproj b/src/SOS/NETCore/SOS.NETCore.csproj
deleted file mode 100644 (file)
index a88c47e..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<!-- 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>
diff --git a/src/SOS/NETCore/SymbolReader.cs b/src/SOS/NETCore/SymbolReader.cs
deleted file mode 100644 (file)
index 7a4bb52..0000000
+++ /dev/null
@@ -1,782 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Reflection.Metadata;
-using System.Reflection.Metadata.Ecma335;
-using System.Reflection.PortableExecutable;
-using System.Runtime.InteropServices;
-
-namespace SOS
-{
-    internal class SymbolReader
-    {
-        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
-        internal struct DebugInfo
-        {
-            public int lineNumber;
-            public int ilOffset;
-            public string fileName;
-        }
-
-        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
-        internal struct LocalVarInfo
-        {
-            public int startOffset;
-            public int endOffset;
-            public string name;
-        }
-
-        [StructLayout(LayoutKind.Sequential)]
-        internal struct MethodDebugInfo
-        {
-            public IntPtr points;
-            public int size;
-            public IntPtr locals;
-            public int localsSize;
-
-        }
-
-        /// <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;
-            }
-        }
-    }
-}
diff --git a/src/SOS/SOS.NETCore/CMakeLists.txt b/src/SOS/SOS.NETCore/CMakeLists.txt
new file mode 100644 (file)
index 0000000..28ff7ea
--- /dev/null
@@ -0,0 +1,8 @@
+project(SOS.NETCore)
+
+if(NOT ${CLR_MANAGED_BINARY_DIR} STREQUAL "")
+    set(MANAGED_BINDIR ${CLR_MANAGED_BINARY_DIR}/SOS.NETCore/netcoreapp1.0)
+
+    install(FILES ${MANAGED_BINDIR}/SOS.NETCore.dll DESTINATION .)
+    install(FILES ${MANAGED_BINDIR}/SOS.NETCore.pdb DESTINATION .)
+endif()
diff --git a/src/SOS/SOS.NETCore/SOS.NETCore.csproj b/src/SOS/SOS.NETCore/SOS.NETCore.csproj
new file mode 100644 (file)
index 0000000..a88c47e
--- /dev/null
@@ -0,0 +1,17 @@
+<!-- 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>
diff --git a/src/SOS/SOS.NETCore/SymbolReader.cs b/src/SOS/SOS.NETCore/SymbolReader.cs
new file mode 100644 (file)
index 0000000..7a4bb52
--- /dev/null
@@ -0,0 +1,782 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+using System.Runtime.InteropServices;
+
+namespace SOS
+{
+    internal class SymbolReader
+    {
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        internal struct DebugInfo
+        {
+            public int lineNumber;
+            public int ilOffset;
+            public string fileName;
+        }
+
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        internal struct LocalVarInfo
+        {
+            public int startOffset;
+            public int endOffset;
+            public string name;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        internal struct MethodDebugInfo
+        {
+            public IntPtr points;
+            public int size;
+            public IntPtr locals;
+            public int localsSize;
+
+        }
+
+        /// <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;
+            }
+        }
+    }
+}
diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Debug/Debugger.Tests.Common.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Debug/Debugger.Tests.Common.txt
new file mode 100644 (file)
index 0000000..2ccca66
--- /dev/null
@@ -0,0 +1,3 @@
+<Configuration>
+  <TargetConfiguration>Debug</TargetConfiguration>
+</Configuration>
diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Release/Debugger.Tests.Common.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Release/Debugger.Tests.Common.txt
new file mode 100644 (file)
index 0000000..c6bed20
--- /dev/null
@@ -0,0 +1,3 @@
+<Configuration>
+  <TargetConfiguration>Release</TargetConfiguration>
+</Configuration>
diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt
new file mode 100644 (file)
index 0000000..1e39521
--- /dev/null
@@ -0,0 +1,67 @@
+<!--
+  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>
diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt
new file mode 100644 (file)
index 0000000..edbcec6
--- /dev/null
@@ -0,0 +1,69 @@
+<!--
+  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>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.cs b/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.cs
new file mode 100644 (file)
index 0000000..bc7aaba
--- /dev/null
@@ -0,0 +1,77 @@
+using System;using System.Security;
+#if WINCORESYS
+[assembly: AllowPartiallyTrustedCallers]
+#endif
+public class C{
+       public static void DivideByZero(ref int v, ref int w)
+       {
+               int x;
+               int y = 1;
+
+               y--;
+               x = 2 / y;
+       }
+
+       public static void F3(ref int m, ref int n)
+       {
+               int a = 1;
+               int b = 2;
+               int c;
+
+               DivideByZero(ref a, ref b); // pass locals by ref to prevent enregistering
+
+               c = a + b;
+               Console.WriteLine("a + b = {0}", c);
+       }
+
+       public static void F2()
+       {
+               int p = 3;
+               int q = 4;
+               int r;
+
+               F3(ref p, ref q); // pass locals by ref to prevent enregistering
+
+               r = p * q;
+               Console.WriteLine("p * q = {0}", r);
+       }
+       
+       public static void F1()
+       {
+               try
+               {
+                       F2();
+               }
+               catch (DivideByZeroException)
+               {
+                       Console.WriteLine("F2 catch");
+               }
+       }
+
+       public static void Main(string[] args)
+       {
+               F1();
+               F2();
+       }
+
+
+    // This method should be called to pass SoS.DivZero test.
+    // We need to figure out how to call it from Main.
+       public static void R()
+       {
+               int m = 5;
+               int n = 6;
+               bool f = true;
+
+               // We want to test two types of Exception("Application Error Message")s.
+               // Divide by zero is a hardware fault, resulting
+               // no stack space movement.
+               // The two cases is a ">" versus ">=" test.
+               DivideByZero(ref m, ref n);
+               
+               if (f)  // this is always true, just to silence a compiler warning
+                       throw new ArgumentException("Application Error Message");
+               
+               Console.WriteLine("m - n = {0}", m-n);
+       }
+}
diff --git a/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.csproj b/src/SOS/SOS.UnitTests/Debuggees/DivZero/DivZero.csproj
new file mode 100644 (file)
index 0000000..d7cb5bd
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsPublishable>true</IsPublishable>
+  </PropertyGroup>
+</Project>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.cs b/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.cs
deleted file mode 100644 (file)
index bc7aaba..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;using System.Security;
-#if WINCORESYS
-[assembly: AllowPartiallyTrustedCallers]
-#endif
-public class C{
-       public static void DivideByZero(ref int v, ref int w)
-       {
-               int x;
-               int y = 1;
-
-               y--;
-               x = 2 / y;
-       }
-
-       public static void F3(ref int m, ref int n)
-       {
-               int a = 1;
-               int b = 2;
-               int c;
-
-               DivideByZero(ref a, ref b); // pass locals by ref to prevent enregistering
-
-               c = a + b;
-               Console.WriteLine("a + b = {0}", c);
-       }
-
-       public static void F2()
-       {
-               int p = 3;
-               int q = 4;
-               int r;
-
-               F3(ref p, ref q); // pass locals by ref to prevent enregistering
-
-               r = p * q;
-               Console.WriteLine("p * q = {0}", r);
-       }
-       
-       public static void F1()
-       {
-               try
-               {
-                       F2();
-               }
-               catch (DivideByZeroException)
-               {
-                       Console.WriteLine("F2 catch");
-               }
-       }
-
-       public static void Main(string[] args)
-       {
-               F1();
-               F2();
-       }
-
-
-    // This method should be called to pass SoS.DivZero test.
-    // We need to figure out how to call it from Main.
-       public static void R()
-       {
-               int m = 5;
-               int n = 6;
-               bool f = true;
-
-               // We want to test two types of Exception("Application Error Message")s.
-               // Divide by zero is a hardware fault, resulting
-               // no stack space movement.
-               // The two cases is a ">" versus ">=" test.
-               DivideByZero(ref m, ref n);
-               
-               if (f)  // this is always true, just to silence a compiler warning
-                       throw new ArgumentException("Application Error Message");
-               
-               Console.WriteLine("m - n = {0}", m-n);
-       }
-}
diff --git a/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.csproj b/src/SOS/SOS.UnitTests/Debuggees/DivZero/divzero.csproj
deleted file mode 100644 (file)
index df6bb3d..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/GCWhere/GCWhere.csproj b/src/SOS/SOS.UnitTests/Debuggees/GCWhere/GCWhere.csproj
new file mode 100644 (file)
index 0000000..d7cb5bd
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsPublishable>true</IsPublishable>
+  </PropertyGroup>
+</Project>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/GCWhere/gcwhere.csproj b/src/SOS/SOS.UnitTests/Debuggees/GCWhere/gcwhere.csproj
deleted file mode 100644 (file)
index 1e75061..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/NestedExceptionTest.csproj b/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/NestedExceptionTest.csproj
new file mode 100644 (file)
index 0000000..d7cb5bd
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsPublishable>true</IsPublishable>
+  </PropertyGroup>
+</Project>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/nestedexceptiontest.csproj b/src/SOS/SOS.UnitTests/Debuggees/NestedExceptionTest/nestedexceptiontest.csproj
deleted file mode 100644 (file)
index ad30c93..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?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>
index c292244ed18d9ebd50e496f80ef7461a708be835..d7cb5bd20cb2968b9f07212a7850eb93298babc9 100644 (file)
@@ -1,13 +1,8 @@
-<?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>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.cs b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.cs
deleted file mode 100644 (file)
index 0c45fc3..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-
-namespace RSSFeedTest
-{
-    public static class AsyncWinRTTest
-    {
-        public static void Main()
-        {
-            // Bad URL
-            string badURL =  "http://feeds.aljazeeraportal.net/AJE/live/1/200779101832373555?removehtml=1";
-            string goodURL = "http://feeds.aljazeeraportal.net/aje/live/1/200779101832373555?removehtml=1";
-            TestRSSFeed feed = new TestRSSFeed(goodURL);
-            Console.WriteLine("Throwing from asyncAction with Progress");
-            var theTask = feed.ThrowFromAsyncActionWithProgress();
-            theTask.Wait();
-            Console.WriteLine("The Program is exiting....");
-            return;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.csproj b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/RSSFeed.csproj
deleted file mode 100644 (file)
index bdb1b4c..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/TestClasses.cs b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/TestClasses.cs
deleted file mode 100644 (file)
index 3591f4c..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using Windows.RSS.Utils;
-using Windows.Foundation.Collections;
-
-namespace RSSFeedTest
-{
-       public class TestRSSFeed
-    {
-        public readonly string FeedUri;
-
-        private FeedItem[] m_feedItems;
-        private readonly FeedDataSource m_feedDataSource;
-
-        public TestRSSFeed(string feedURI)
-        {
-            FeedUri = feedURI;
-            m_feedDataSource = new FeedDataSource();
-        }
-
-        public async void Initialize()
-        {
-            var iasyncOp = m_feedDataSource.GetFeedAsync(FeedUri);
-
-            await iasyncOp.AsTask<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("&amp;", "&");
-            fixedDescription = fixedDescription.Replace("&lt;", "<");
-            fixedDescription = fixedDescription.Replace("&gt;", ">");
-
-            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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/DateConverter.h b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/DateConverter.h
deleted file mode 100644 (file)
index 27a6c6d..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-//*********************************************************
-//
-// Copyright (c) Microsoft. All rights reserved.
-// http://code.msdn.microsoft.com/windowsapps/Windows-Store-Simple-Blog-953302e8
-//*********************************************************
-
-//DateConverter.h
-
-#pragma once
-#include <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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.cpp b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.cpp
deleted file mode 100644 (file)
index 0879bd2..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-//*********************************************************
-//
-// Copyright (c) Microsoft. All rights reserved.
-// http://code.msdn.microsoft.com/windowsapps/Windows-Store-Simple-Blog-953302e8
-//*********************************************************
-
-#include "pch.h"
-#include "FeedData.h"
-
-using namespace std;
-using namespace Concurrency;
-using namespace Platform;
-using namespace Platform::Collections;
-using namespace Windows::Foundation;
-using namespace Windows::Web::Syndication;
-using namespace Windows::RSS::Utils;
-
-FeedDataSource::FeedDataSource()
-{
-       m_feeds = ref new Vector<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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.h b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedData.h
deleted file mode 100644 (file)
index db1ac07..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-//*********************************************************
-//
-// Copyright (c) Microsoft. All rights reserved.
-// http://code.msdn.microsoft.com/windowsapps/Windows-Store-Simple-Blog-953302e8
-//*********************************************************
-
-//feeddata.h
-
-#pragma once
-#include "pch.h"
-#include "FeedItem.h"
-
-namespace Windows
-{
-    namespace RSS
-    {
-        namespace Utils
-        {
-            // A FeedData object represents a feed that contains 
-            // one or more FeedItems. 
-            [Windows::UI::Xaml::Data::Bindable]
-            public ref class FeedData sealed
-            {
-            public:
-                FeedData(void)
-                {
-                    m_items = ref new Platform::Collections::Vector<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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.cpp b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.cpp
deleted file mode 100644 (file)
index 4e094f6..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-#include "pch.h"
-#include "FeedItem.h"
-
-using namespace Windows::RSS::Utils;
-
-using namespace Concurrency;
-using namespace Platform;
-using namespace Platform::Collections;
-using namespace Windows::Foundation;
-using namespace Windows::Web::Syndication;
-
-FeedItem::FeedItem(void)
-{
-       m_relatedArticles = ref new Vector<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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.h b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/FeedItem.h
deleted file mode 100644 (file)
index 78f529f..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-#pragma once
-
-namespace Windows
-{
-    namespace RSS
-    {
-        namespace Utils
-        {
-            // To be bindable, a class must be defined within a namespace
-            // and a bindable attribute needs to be applied.
-            // A FeedItem represents a single blog post.
-            [Windows::UI::Xaml::Data::Bindable]
-            public ref class FeedItem sealed
-            {
-            public:
-                FeedItem(void);
-
-                property Platform::String^ GUID;
-                property Windows::Foundation::DateTime PubDate;
-                property Windows::Foundation::Uri^ Link;
-
-                property Platform::String^ Title;
-
-                property Platform::String^ Summary;
-                property Platform::String^ Description;
-
-                // These are all URIs to the image associated to the article.
-                property Windows::Foundation::Uri^ AssetUri;
-                property Windows::Foundation::Uri^ ThumbnailUri;
-                property Windows::Foundation::Uri^ MainImageUri;
-
-                //property Windows::Web::Syndication::SyndicationItem^ RootItem;
-
-                property Windows::Foundation::Collections::IVector<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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/Windows.RSS.Utils.vcxproj b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/Windows.RSS.Utils.vcxproj
deleted file mode 100644 (file)
index bef88a8..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.cpp b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.cpp
deleted file mode 100644 (file)
index bcb5590..0000000
+++ /dev/null
@@ -1 +0,0 @@
-#include "pch.h"
diff --git a/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.h b/src/SOS/SOS.UnitTests/Debuggees/RSSFeed/Windows.RSS.Utils/pch.h
deleted file mode 100644 (file)
index 10fe677..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#pragma once
-
-#include <collection.h>
-#include <ppltasks.h>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/ReflectionTest.csproj b/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/ReflectionTest.csproj
new file mode 100644 (file)
index 0000000..d7cb5bd
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsPublishable>true</IsPublishable>
+  </PropertyGroup>
+</Project>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/reflectiontest.csproj b/src/SOS/SOS.UnitTests/Debuggees/ReflectionTest/reflectiontest.csproj
deleted file mode 100644 (file)
index 00f1da2..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?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>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/SimpleThrow.csproj b/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/SimpleThrow.csproj
new file mode 100644 (file)
index 0000000..d7cb5bd
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsPublishable>true</IsPublishable>
+  </PropertyGroup>
+</Project>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/simplethrow.csproj b/src/SOS/SOS.UnitTests/Debuggees/SimpleThrow/simplethrow.csproj
deleted file mode 100644 (file)
index a324613..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?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
index a9a17c128d39275dc85adf7bf01f470c785476bf..c0bc422cb1a3317a5d1a78c84af6483de7e72298 100644 (file)
@@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
 VisualStudioVersion = 15.0.26124.0
 MinimumVisualStudioVersion = 15.0.26124.0
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "symboltestapp", "symboltestapp\symboltestapp.csproj", "{EB291C94-C60E-4AE6-AEA0-B7EEF86F00AD}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymbolTestApp", "SymbolTestApp\SymbolTestApp.csproj", "{EB291C94-C60E-4AE6-AEA0-B7EEF86F00AD}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "symboltestdll", "symboltestdll\symboltestdll.csproj", "{53E66227-71BB-4300-9BE9-56CE570B28F8}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymbolTestDll", "SymbolTestDll\SymbolTestDll.csproj", "{53E66227-71BB-4300-9BE9-56CE570B28F8}"
 EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
index 107df730b6e1014ad078232235b2cc37ac53643b..289d91ec073f121c417941270058fd79339be2c7 100644 (file)
@@ -33,12 +33,12 @@ namespace SymbolTestApp
         static void Foo4(string dllPath)
         {
 #if FULL_CLR
-            byte[] dll = File.ReadAllBytes(Path.Combine(dllPath, @"symboltestdll.dll"));
-            byte[] pdb = File.ReadAllBytes(Path.Combine(dllPath, @"symboltestdll.pdb"));
+            byte[] dll = File.ReadAllBytes(Path.Combine(dllPath, @"SymbolTestDll.dll"));
+            byte[] pdb = File.ReadAllBytes(Path.Combine(dllPath, @"SymbolTestDll.pdb"));
             Assembly assembly = Assembly.Load(dll, pdb);
 #else
-            Stream dll = File.OpenRead(Path.Combine(dllPath, @"symboltestdll.dll"));
-            Stream pdb = File.OpenRead(Path.Combine(dllPath, @"symboltestdll.pdb"));
+            Stream dll = File.OpenRead(Path.Combine(dllPath, @"SymbolTestDll.dll"));
+            Stream pdb = File.OpenRead(Path.Combine(dllPath, @"SymbolTestDll.pdb"));
             Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(dll, pdb);
 #endif
             Type dllType = assembly.GetType("SymbolTestDll.TestClass");
diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.csproj b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/SymbolTestApp.csproj
new file mode 100644 (file)
index 0000000..bd62e04
--- /dev/null
@@ -0,0 +1,17 @@
+<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>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.csproj b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.csproj
deleted file mode 100644 (file)
index 15712dd..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.props b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp.props
deleted file mode 100644 (file)
index bb46703..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?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>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp_prebuild.targets b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestApp/symboltestapp_prebuild.targets
deleted file mode 100644 (file)
index a3d8eb6..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/SymbolTestDll.csproj b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/SymbolTestDll.csproj
new file mode 100644 (file)
index 0000000..cfa02ff
--- /dev/null
@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsPublishable>true</IsPublishable>
+    <DebugType>portable</DebugType>
+  </PropertyGroup>
+</Project>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.csproj b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.csproj
deleted file mode 100644 (file)
index 3b282da..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?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>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.props b/src/SOS/SOS.UnitTests/Debuggees/SymbolTestApp/SymbolTestDll/symboltestdll.props
deleted file mode 100644 (file)
index 1b69f9c..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserLibrary.csproj b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserLibrary.csproj
new file mode 100644 (file)
index 0000000..3e77879
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsPublishable>true</IsPublishable>
+  </PropertyGroup>
+</Project>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserTask.cs b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/RandomUserLibrary/RandomUserTask.cs
new file mode 100644 (file)
index 0000000..9560c13
--- /dev/null
@@ -0,0 +1,38 @@
+using System;
+using System.Threading.Tasks;
+
+namespace RandomTest
+{
+    public class RandomUserTask
+    {
+        public Task TryToDivideTask;
+
+        private readonly int m_startingNumber;
+
+        public RandomUserTask(int startingNumber)
+        {
+            m_startingNumber = startingNumber;
+            TryToDivideTask = Task.Factory.StartNew(() =>
+            {
+                try
+                {
+                    InnerException();
+                }
+                catch (Exception e)
+                {
+                    throw new FormatException("Bad format exception, outer.", e);
+                }
+            });
+        }
+
+        public void WaitTask()
+        {
+            TryToDivideTask.Wait();
+        }
+
+        public void InnerException()
+        {
+            throw new InvalidOperationException("This is an Inner InvalidOperationException.");
+        }
+    }
+}
index 5ac5625a89d56e8d48d9207db98bc3626e512c44..414fb881f9f552c4e7d79bc5402051b56e9019db 100644 (file)
@@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
 VisualStudioVersion = 15.0.26124.0
 MinimumVisualStudioVersion = 15.0.26124.0
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TaskNestedException", "tasknestedexception\TaskNestedException.csproj", "{B9B58649-CEA6-4EF5-A25E-916AE8E77917}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TaskNestedException", "TaskNestedException\TaskNestedException.csproj", "{B9B58649-CEA6-4EF5-A25E-916AE8E77917}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomUserLibrary", "randomuserlibrary\RandomUserLibrary.csproj", "{FADE7D7D-107F-42B0-ABD5-36A101CD8675}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomUserLibrary", "RandomUserLibrary\RandomUserLibrary.csproj", "{FADE7D7D-107F-42B0-ABD5-36A101CD8675}"
 EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.cs b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.cs
new file mode 100644 (file)
index 0000000..10b51af
--- /dev/null
@@ -0,0 +1,20 @@
+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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.csproj b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/TaskNestedException/TaskNestedException.csproj
new file mode 100644 (file)
index 0000000..8df8ab8
--- /dev/null
@@ -0,0 +1,16 @@
+<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>
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.csproj b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.csproj
deleted file mode 100644 (file)
index 9733e15..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.props b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserLibrary.props
deleted file mode 100644 (file)
index 1b69f9c..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserTask.cs b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/randomuserlibrary/RandomUserTask.cs
deleted file mode 100644 (file)
index 9560c13..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace RandomTest
-{
-    public class RandomUserTask
-    {
-        public Task TryToDivideTask;
-
-        private readonly int m_startingNumber;
-
-        public RandomUserTask(int startingNumber)
-        {
-            m_startingNumber = startingNumber;
-            TryToDivideTask = Task.Factory.StartNew(() =>
-            {
-                try
-                {
-                    InnerException();
-                }
-                catch (Exception e)
-                {
-                    throw new FormatException("Bad format exception, outer.", e);
-                }
-            });
-        }
-
-        public void WaitTask()
-        {
-            TryToDivideTask.Wait();
-        }
-
-        public void InnerException()
-        {
-            throw new InvalidOperationException("This is an Inner InvalidOperationException.");
-        }
-    }
-}
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.cs b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.cs
deleted file mode 100644 (file)
index 10b51af..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.props b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/TaskNestedException.props
deleted file mode 100644 (file)
index c6b0646..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?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
diff --git a/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/tasknestedexception.csproj b/src/SOS/SOS.UnitTests/Debuggees/TaskNestedException/tasknestedexception/tasknestedexception.csproj
deleted file mode 100644 (file)
index e25b827..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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
index 56fd90931b28b3d326761d1a4252084db56741c7..1171ef9f5ab5c9466bfa7d02fe726d2c1ab843ef 100644 (file)
@@ -3,6 +3,32 @@
   <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>
index 45932f52733b012863225b88cb7ea90c3020a560..b4ae3f95da1f0cbe437383d81357b19ffc176dac 100644 (file)
@@ -1,4 +1,8 @@
-using Debugger.Tests;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostic.TestHelpers;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -17,17 +21,11 @@ public class SOS
 
     ITestOutputHelper Output { get; set; }
 
-    public static IEnumerable<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");
         }
@@ -35,45 +33,38 @@ public class SOS
 
     private static bool IsCreateDumpConfig(TestConfiguration config)
     {
-        return config.DebuggeeDumpOutputRootDir != null;
+        return config.DebuggeeDumpOutputRootDir() != null;
     }
 
     private static bool IsOpenDumpConfig(TestConfiguration config)
     {
-        return config.DebuggeeDumpInputRootDir != null;
+        return config.DebuggeeDumpInputRootDir() != null;
     }
 
     private async Task CreateDump(TestConfiguration config, string testName, string debuggeeName, string debuggeeArguments)
     {
-        Directory.CreateDirectory(config.DebuggeeDumpOutputRootDir);
+        Directory.CreateDirectory(config.DebuggeeDumpOutputRootDir());
 
         using (SOSRunner runner = await SOSRunner.StartDebugger(config, Output, testName, debuggeeName, debuggeeArguments, loadDump: false, generateDump: true))
         {
             try
             {
                 await runner.LoadSosExtension();
-                await runner.ContinueExecution();
 
                 string command = null;
                 switch (runner.Debugger)
                 {
                     case SOSRunner.NativeDebugger.Cdb:
-                        if (config.TestProduct.Equals("desktop"))
-                        {
-                            // On desktop create triage dump
-                            command = ".dump /o /mshuRp %DUMP_NAME%";
-                        }
-                        else
-                        {
-                            // On .NET Core, create full dump
-                            command = ".dump /o /ma %DUMP_NAME%";
-                        }
+                        await runner.ContinueExecution();
+                        // On desktop create triage dump. On .NET Core, create full dump.
+                        command = config.TestProduct.Equals("desktop") ? ".dump /o /mshuRp %DUMP_NAME%" : ".dump /o /ma %DUMP_NAME%";
                         break;
                     case SOSRunner.NativeDebugger.Gdb:
                         command = "generate-core-file %DUMP_NAME%";
                         break;
                     case SOSRunner.NativeDebugger.Lldb:
-                        command = "sos CreateDump %DUMP_NAME%";
+                        await runner.ContinueExecution();
+                        command = OS.Kind == OSKind.OSX ? "process save-core %DUMP_NAME%" : "sos CreateDump %DUMP_NAME%";
                         break;
                     default:
                         throw new Exception(runner.Debugger.ToString() + " does not support creating dumps");
@@ -105,7 +96,7 @@ public class SOS
             await runner.RunScript(scriptName);
         }
 
-        // Against a crash dump
+        // Against a crash dump.
         if (IsCreateDumpConfig(config))
         {
             await CreateDump(config, testName, debuggeeName, debuggeeArguments);
@@ -120,21 +111,13 @@ public class SOS
         }
     }
 
-#if OSX_FAIL_WITH_BUG
-    [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")]
-#else
-    [SkippableTheory, MemberData("Configurations")]
-#endif
+    [SkippableTheory, MemberData(nameof(Configurations))]
     public async Task DivZero(TestConfiguration config)
     {
-        await RunTest(config, "DivZero", "SoS/DivZero.script");
+        await RunTest(config, "DivZero", "DivZero.script");
     }
 
-#if OSX_FAIL_WITH_BUG
-    [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")]
-#else
-    [SkippableTheory, MemberData("Configurations")]
-#endif
+    [SkippableTheory, MemberData(nameof(Configurations))]
     public async Task GCTests(TestConfiguration config)
     {
         const string testName = "SOS.GCTests";
@@ -144,97 +127,54 @@ public class SOS
         SkipIfArm(config);
         using (SOSRunner runner = await SOSRunner.StartDebugger(config, Output, testName, debuggeeName, debuggeeArguments: null))
         {
-            await runner.RunScript("SoS/GCTests.script");
+            await runner.RunScript("GCTests.script");
         }
     }
 
-#if OSX_FAIL_WITH_BUG
-    [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")]
-#else
-    [SkippableTheory, MemberData("Configurations")]
-#endif
+    [SkippableTheory, MemberData(nameof(Configurations))]
     public async Task Overflow(TestConfiguration config)
     {
-        await RunTest(config, "Overflow", "SoS/Overflow.script");
+        await RunTest(config, "Overflow", "Overflow.script");
     }
 
-#if OSX_FAIL_WITH_BUG
-    [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")]
-#else
-    [SkippableTheory, MemberData("Configurations")]
-#endif
+    [SkippableTheory, MemberData(nameof(Configurations))]
     public async Task Reflection(TestConfiguration config)
     {
-        await RunTest(config, "ReflectionTest", "SoS/Reflection.script");
+        await RunTest(config, "ReflectionTest", "Reflection.script");
     }
 
-#if OSX_FAIL_WITH_BUG
-    [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")]
-#else
-    [SkippableTheory, MemberData("Configurations")]
-#endif
+    [SkippableTheory, MemberData(nameof(Configurations))]
     public async Task SimpleThrow(TestConfiguration config)
     {
-        await RunTest(config, "SimpleThrow", "SoS/SimpleThrow.script");
+        await RunTest(config, "SimpleThrow", "SimpleThrow.script");
     }
 
-#if OSX_FAIL_WITH_BUG
-    [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")]
-#else
-    [SkippableTheory, MemberData("Configurations")]
-#endif
+    [SkippableTheory, MemberData(nameof(Configurations))]
     public async Task NestedExceptionTest(TestConfiguration config)
     {
-        await RunTest(config, "NestedExceptionTest", "SoS/NestedExceptionTest.script");
+        await RunTest(config, "NestedExceptionTest", "NestedExceptionTest.script");
     }
 
-#if OSX_FAIL_WITH_BUG
-    [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")]
-#else
-    [SkippableTheory, MemberData("Configurations")]
-#endif
+    [SkippableTheory, MemberData(nameof(Configurations))]
     public async Task TaskNestedException(TestConfiguration config)
     {
-        await RunTest(config, "TaskNestedException", "SoS/TaskNestedException.script");
+        await RunTest(config, "TaskNestedException", "TaskNestedException.script");
     }
 
-    [SkippableTheory(Skip = "Test issue: Test build system can't yet create the debuggee"), MemberData("Configurations")]
-    public async Task WinRTAsync(TestConfiguration config)
-    {
-        await RunTest(config, "RSSFeed", "SoS/WinRTAsync.script");
-    }
-
-#if OSX_FAIL_WITH_BUG
-    [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")]
-#else
-    [SkippableTheory, MemberData("Configurations")]
-#endif
+    [SkippableTheory, MemberData(nameof(Configurations))]
     public async Task StackTests(TestConfiguration config)
     {
-        if (config.BuildProjectRuntime == "linux-x64")
-        {
-            throw new SkipTestException("SOS StackTests are disabled on linux. Bug #584221");
-        }
-        await RunTest(config, "SOS.StackTests", "NestedExceptionTest", null, "SoS/StackTests.script");
+        await RunTest(config, "SOS.StackTests", "NestedExceptionTest", null, "StackTests.script");
     }
 
-#if OSX_FAIL_WITH_BUG
-    [SkippableTheory(Skip = "SOS tests not working for OS X"), MemberData("Configurations")]
-#else
-    [SkippableTheory, MemberData("Configurations")]
-#endif
+    [SkippableTheory, MemberData(nameof(Configurations))]
     public async Task StackAndOtherTests(TestConfiguration config)
     {
         SkipIfArm(config);
-        if (config.BuildProjectRuntime == "linux-x64")
-        {
-            throw new SkipTestException("SOS StackTests are disabled on linux. Bug #584221");
-        }
-
         foreach (TestConfiguration currentConfig in TestRunner.EnumeratePdbTypeConfigs(config))
         {
-            // This debuggee needs the directory of the exes/dlls to load the symboltestdll assembly.
-            await RunTest(currentConfig, "SOS.StackAndOtherTests", "symboltestapp", "%DEBUG_ROOT%", "SoS/StackAndOtherTests.script");
+            // This debuggee needs the directory of the exes/dlls to load the SymbolTestDll assembly.
+            await RunTest(currentConfig, "SOS.StackAndOtherTests", "SymbolTestApp", "%DEBUG_ROOT%", "StackAndOtherTests.script");
         }
     }
 }
index 5d3fe87a5518d7b00a1d397a76b9e600e4fd2993..1bf66d986f33b2c55f2f2ff588e18153d7a50535 100644 (file)
@@ -1,4 +1,8 @@
-using Debugger.Tests.Build;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostic.TestHelpers;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -9,788 +13,821 @@ using System.Threading;
 using System.Threading.Tasks;
 using Xunit.Abstractions;
 
-namespace Debugger.Tests
+public class SOSRunner : IDisposable
 {
-    public class SOSRunner : IDisposable
+    readonly TestConfiguration _config;
+    readonly TestRunner.OutputHelper _outputHelper;
+    readonly Dictionary<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"));
+    }
+}
index a3d4c0dc4e954bb5d0c375a012741be7bcd9beec..a05bb39303bd23f4497716691cff6b8e26ac85a0 100644 (file)
@@ -23,10 +23,10 @@ IFDEF:PROJECTK
 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)
@@ -59,6 +59,9 @@ VERIFY:.*\s+<HEXVAL>\s+<HEXVAL>\s+SymbolTestApp\.Program\.Foo1\(.*\)\s+\[(?i:.*[
 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
@@ -89,6 +92,8 @@ VERIFY:.*\s+<HEXVAL>\s+<HEXVAL>\s+\[DEFAULT\] Void SymbolTestApp\.Program\.Main\
 VERIFY:.*\s+Stack walk complete.\s+
 ENDIF:PROJECTK
 
+ENDIF:DESKTOP
+
 # Verify DumpStackObjects works
 IFDEF:PROJECTK
 SOSCOMMAND:DumpStackObjects
index 1c22d975925019e9a6eb019de176eb6ff462c87b..595dc9e9b687949ba8430d548557ef2048a7cab3 100644 (file)
@@ -61,6 +61,9 @@ VERIFY:\s+[r|e]ax=<HEXVAL>\s+[r|e]bx=<HEXVAL>\s+[r|e]cx=<HEXVAL>\s+
 ENDIF:64BIT
 ENDIF:PROJECTK
 
+# Disable ClrStack -i until coreclr issue #17259
+IFDEF:DESKTOP
+
 # 5) Verifying that ClrStack with the ICorDebug options works
 SOSCOMMAND:ClrStack -i
 VERIFY:.*\s+Dumping managed stack and managed variables using ICorDebug.\s+
@@ -85,6 +88,8 @@ VERIFY:.*\s+<HEXVAL>\s+<HEXVAL>\s+\[DEFAULT\] Void NestedExceptionTest\.Program\
 VERIFY:.*\s+Stack walk complete.\s+
 ENDIF:PROJECTK
 
+ENDIF:DESKTOP
+
 # 7) Verify DumpStackObjects works
 IFDEF:PROJECTK
 SOSCOMMAND:DumpStackObjects
@@ -105,7 +110,6 @@ VERIFY:.*\s+<HEXVAL>\s+<HEXVAL>\s+System\.InvalidOperationException\s+
 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+.*
@@ -122,4 +126,3 @@ VERIFY:.*\s+<HEXVAL>\s+<HEXVAL>\s+\(MethodDesc\s+<HEXVAL>\s+\+\s*0x<HEXVAL>\s+Ne
 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
diff --git a/src/SOS/SOS.UnitTests/Scripts/WinRTAsync.script b/src/SOS/SOS.UnitTests/Scripts/WinRTAsync.script
deleted file mode 100644 (file)
index 8b7bc68..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-# WinRT Async debugging scenario
-# 1) load the executable
-# 2) Run the executable and wait for it to crash
-# 3) Take a dump of the executable.
-# 4) Open the dump and compare the output
-# [EXPECTED OUTPUT (DESKTOP)]
-# EXPECTED OUTPUT:
-# Exception object: <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\]
index 1230d169b09f11253239bab0fe999df5eabc8cf7..de9a1b644d5845b4d689970c9fbd036905708b0a 100644 (file)
@@ -47,6 +47,7 @@ if(WIN32)
 
   set(SOS_SOURCES
     disasm.cpp
+    datatarget.cpp
     dllsext.cpp
     eeheap.cpp
     EventCallbacks.cpp
index fe90f0e825468de8d1258774ac092923c971df8c..fdf63b06628d59730b6c6fca5fd87d2046f68705 100644 (file)
@@ -74,7 +74,7 @@ DataTarget::GetMachineType(
     {
         return E_UNEXPECTED;
     }
-    return g_ExtControl->GetExecutingProcessorType(machine);
+    return g_ExtControl->GetExecutingProcessorType((PULONG)machine);
 }
 
 HRESULT STDMETHODCALLTYPE
@@ -107,6 +107,14 @@ DataTarget::GetImageBase(
     {
         return E_FAIL;
     }
+#ifndef FEATURE_PAL
+    // Remove the extension on Windows/dbgeng.
+    CHAR *lp = strrchr(lpstr, '.');
+    if (lp != nullptr)
+    {
+        *lp = '\0';
+    }
+#endif
     return g_ExtSymbols->GetModuleByModuleName(lpstr, 0, NULL, base);
 }
 
@@ -121,7 +129,7 @@ DataTarget::ReadVirtual(
     {
         return E_UNEXPECTED;
     }
-    return g_ExtData->ReadVirtual(address, (PVOID)buffer, request, done);
+    return g_ExtData->ReadVirtual(address, (PVOID)buffer, request, (PULONG)done);
 }
 
 HRESULT STDMETHODCALLTYPE
@@ -135,7 +143,7 @@ DataTarget::WriteVirtual(
     {
         return E_UNEXPECTED;
     }
-    return g_ExtData->WriteVirtual(address, (PVOID)buffer, request, done);
+    return g_ExtData->WriteVirtual(address, (PVOID)buffer, request, (PULONG)done);
 }
 
 HRESULT STDMETHODCALLTYPE
@@ -158,13 +166,13 @@ DataTarget::SetTLSValue(
 
 HRESULT STDMETHODCALLTYPE
 DataTarget::GetCurrentThreadID(
-    /* [out] */ ULONG32threadID)
+    /* [out] */ ULONG32 *threadID)
 {
     if (g_ExtSystem == NULL)
     {
         return E_UNEXPECTED;
     }
-    return g_ExtSystem->GetCurrentThreadSystemId(threadID);
+    return g_ExtSystem->GetCurrentThreadSystemId((PULONG)threadID);
 }
 
 HRESULT STDMETHODCALLTYPE
@@ -174,11 +182,52 @@ DataTarget::GetThreadContext(
     /* [in] */ ULONG32 contextSize,
     /* [out, size_is(contextSize)] */ PBYTE context)
 {
-    if (g_ExtSystem == NULL)
+#ifdef FEATURE_PAL
+    if (g_ExtServices == NULL)
     {
         return E_UNEXPECTED;
     }
-    return g_ExtSystem->GetThreadContextById(threadID, contextFlags, contextSize, context);
+    return g_ExtServices->GetThreadContextById(threadID, contextFlags, contextSize, context);
+#else
+    if (g_ExtSystem == NULL || g_ExtAdvanced3 == NULL)
+    {
+        return E_UNEXPECTED;
+    }
+    ULONG ulThreadIDOrig;
+    ULONG ulThreadIDRequested;
+    HRESULT hr;
+
+    hr = g_ExtSystem->GetCurrentThreadId(&ulThreadIDOrig);
+    if (FAILED(hr))
+    {
+       return hr;
+    }
+
+    hr = g_ExtSystem->GetThreadIdBySystemId(threadID, &ulThreadIDRequested);
+    if (FAILED(hr))
+    {
+       return hr;
+    }
+
+    hr = g_ExtSystem->SetCurrentThreadId(ulThreadIDRequested);
+    if (FAILED(hr))
+    {
+       return hr;
+    }
+
+    // Prepare context structure
+    ZeroMemory(context, contextSize);
+    ((CONTEXT*) context)->ContextFlags = contextFlags;
+
+    // Ok, do it!
+    hr = g_ExtAdvanced3->GetThreadContext((LPVOID) context, contextSize);
+
+    // This is cleanup; failure here doesn't mean GetThreadContext should fail
+    // (that's determined by hr).
+    g_ExtSystem->SetCurrentThreadId(ulThreadIDOrig);
+
+    return hr;
+#endif
 }
 
 HRESULT STDMETHODCALLTYPE
@@ -207,9 +256,13 @@ DataTarget::VirtualUnwind(
     /* [in] */ ULONG32 contextSize,
     /* [in, out, size_is(contextSize)] */ PBYTE context)
 {
+#ifdef FEATURE_PAL
     if (g_ExtServices == NULL)
     {
         return E_UNEXPECTED;
     }
     return g_ExtServices->VirtualUnwind(threadId, contextSize, context);
+#else
+    return E_NOTIMPL;
+#endif
 }
index 566ba35f194d7b4307dac360d871299ec9323591..15cb26ab0e53b59adb17dd4d3134f2e363416a73 100644 (file)
@@ -17,7 +17,6 @@
 //
 // globals
 //
-EXT_API_VERSION         ApiVersion = { (VER_PRODUCTVERSION_W >> 8), (VER_PRODUCTVERSION_W & 0xff), EXT_API_VERSION_NUMBER64, 0 };
 WINDBG_EXTENSION_APIS   ExtensionApis;
 
 ULONG PageSize;
@@ -201,7 +200,7 @@ DebugExtensionInitialize(PULONG Version, PULONG Flags)
     PDEBUG_CONTROL DebugControl;
     HRESULT Hr;
 
-    *Version = DEBUG_EXTENSION_VERSION(1, 0);
+    *Version = DEBUG_EXTENSION_VERSION(2, 0);
     *Flags = 0;
 
     if (g_Initialized)
index 416127ec1e2f6f09e878d83a48488cd7ce44159b..d03ec2e6e50ca1cf1655f7ef2b7569d339490bfc 100644 (file)
 #include <mscoree.h>
 #include <tchar.h>
 #include "debugshim.h"
-
-#ifdef FEATURE_PAL
 #include "datatarget.h"
-#endif // FEATURE_PAL
 #include "gcinfo.h"
 
 #ifndef STRESS_LOG
@@ -62,6 +59,8 @@ ResolveSequencePointDelegate SymbolReader::resolveSequencePointDelegate;
 GetLocalVariableName SymbolReader::getLocalVariableNameDelegate;
 GetLineByILOffsetDelegate SymbolReader::getLineByILOffsetDelegate;
 
+HRESULT GetCoreClrDirectory(std::string& coreClrDirectory);
+
 const char * const CorElementTypeName[ELEMENT_TYPE_MAX]=
 {
 #define TYPEINFO(e,ns,c,s,g,ia,ip,if,im,gv)    c,
@@ -4188,26 +4187,28 @@ void ResetGlobals(void)
 //
 HRESULT LoadClrDebugDll(void)
 {
-    HRESULT hr = S_OK;
-#ifdef FEATURE_PAL
     static IXCLRDataProcess* s_clrDataProcess = NULL;
+    HRESULT hr = S_OK;
+
     if (s_clrDataProcess == NULL)
     {
+#ifdef FEATURE_PAL
         int err = PAL_InitializeDLL();
-        if(err != 0)
+        if (err != 0)
         {
             return CORDBG_E_UNSUPPORTED;
         }
-        LPCSTR coreclrDirectory = g_ExtServices->GetCoreClrDirectory();
-        if (coreclrDirectory == NULL)
+#endif // FEATURE_PAL
+        std::string dacModulePath;
+        hr = GetCoreClrDirectory(dacModulePath);
+        if (FAILED(hr))
         {
-            return E_FAIL;
+            return hr;
         }
-        ArrayHolder<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;
@@ -4233,17 +4234,7 @@ HRESULT LoadClrDebugDll(void)
     g_clrData = s_clrDataProcess;
     g_clrData->AddRef();
     g_clrData->Flush();
-#else
-    WDBGEXTS_CLR_DATA_INTERFACE Query;
-
-    Query.Iid = &__uuidof(IXCLRDataProcess);
-    if (!Ioctl(IG_GET_CLR_DATA_INTERFACE, &Query, sizeof(Query)))
-    {
-        return E_FAIL;
-    }
 
-    g_clrData = (IXCLRDataProcess*)Query.Iface;
-#endif
     hr = g_clrData->QueryInterface(__uuidof(ISOSDacInterface), (void**)&g_sos);
     if (FAILED(hr))
     {
@@ -4676,16 +4667,15 @@ public:
         BYTE * context)
     {
 #ifdef FEATURE_PAL
-        if (g_ExtSystem == NULL)
+        if (g_ExtServices == NULL)
         {
             return E_UNEXPECTED;
         }
-        return g_ExtSystem->GetThreadContextById(dwThreadOSID, contextFlags, contextSize, context);
+        return g_ExtServices->GetThreadContextById(dwThreadOSID, contextFlags, contextSize, context);
 #else
         ULONG ulThreadIDOrig;
         ULONG ulThreadIDRequested;
         HRESULT hr;
-        HRESULT hrRet;
 
         hr = g_ExtSystem->GetCurrentThreadId(&ulThreadIDOrig);
         if (FAILED(hr))
@@ -4710,13 +4700,13 @@ public:
         ((CONTEXT*) context)->ContextFlags = contextFlags;
 
         // Ok, do it!
-        hrRet = g_ExtAdvanced3->GetThreadContext((LPVOID) context, contextSize);
+        hr = g_ExtAdvanced3->GetThreadContext((LPVOID) context, contextSize);
 
         // This is cleanup; failure here doesn't mean GetThreadContext should fail
-        // (that's determined by hrRet).
+        // (that's determined by hr).
         g_ExtSystem->SetCurrentThreadId(ulThreadIDOrig);
 
-        return hrRet;
+        return hr;
 #endif // FEATURE_PAL
     }
 
@@ -6421,49 +6411,59 @@ bool GetEntrypointExecutableAbsolutePath(std::string& entrypointExecutable)
 
 #endif // FEATURE_PAL
 
+HRESULT GetCoreClrDirectory(std::string& coreClrDirectory)
+{
+#ifdef FEATURE_PAL
+    LPCSTR directory = g_ExtServices->GetCoreClrDirectory();
+    if (directory == NULL)
+    {
+        ExtErr("Error: Runtime module (%s) not loaded yet", MAKEDLLNAME_A("coreclr"));
+        return E_FAIL;
+    }
+    if (!GetAbsolutePath(directory, coreClrDirectory))
+    {
+        ExtErr("Error: Failed to get coreclr absolute path\n");
+        return E_FAIL;
+    }
+#else
+    ULONG index;
+    HRESULT Status = g_ExtSymbols->GetModuleByModuleName(MAIN_CLR_MODULE_NAME_A, 0, &index, NULL);
+    if (FAILED(Status))
+    {
+        ExtErr("Error: Can't find coreclr module\n");
+        return Status;
+    }
+    ArrayHolder<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);
index 5dcf9572bbaf998a6885c8e9c844541fb29edd28..aa71451b4c6faaaee188dd90ec4dd1a9a3bbe8d0 100644 (file)
@@ -396,16 +396,6 @@ public:
         return m_lldbservices->GetThreadIdBySystemId(sysId, threadId);
     }
 
-    HRESULT 
-    GetThreadContextById(
-        /* in */ ULONG32 threadID,
-        /* in */ ULONG32 contextFlags,
-        /* in */ ULONG32 contextSize,
-        /* out */ PBYTE context)
-    {
-        return m_lldbservices->GetThreadContextById(threadID, contextFlags, contextSize, context);
-    }
-
     //----------------------------------------------------------------------------
     // IDebugRegisters
     //----------------------------------------------------------------------------
diff --git a/src/SOS/TestDebuggee/Test.cs b/src/SOS/TestDebuggee/Test.cs
deleted file mode 100644 (file)
index bd38297..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-
-class Test
-{
-    static void LikelyInlined()
-    {
-        Console.WriteLine("I would like to be inlined");
-    }
-
-    static void UnlikelyInlined()
-    {
-        Console.Write("I");
-        Console.Write(" ");
-        Console.Write("w");
-        Console.Write("o");
-        Console.Write("u");
-        Console.Write("l");
-        Console.Write("d");
-        Console.Write(" ");
-        Console.Write("n");
-        Console.Write("o");
-        Console.Write("t");
-        Console.Write(" ");
-        Console.Write("l");
-        Console.Write("i");
-        Console.Write("k");
-        Console.Write("e");
-        Console.Write(" ");
-        Console.Write("t");
-        Console.Write("o");
-        Console.Write(" ");
-        Console.Write("b");
-        Console.Write("e");
-        Console.Write(" ");
-        Console.Write("i");
-        Console.Write("n");
-        Console.Write("l");
-        Console.Write("i");
-        Console.Write("n");
-        Console.Write("e");
-        Console.Write("d");
-        Console.Write("\n");
-    }
-
-    static void ClrU()
-    {
-        Console.WriteLine("test dumpclass");
-    }
-
-    static void DumpClass()
-    {
-        Console.WriteLine("test dumpclass");
-    }
-
-    static void DumpIL()
-    {
-        Console.WriteLine("test dumpil");
-    }
-
-    static void DumpMD()
-    {
-        Console.WriteLine("test dumpmd");
-    }
-
-    static void DumpModule()
-    {
-        Console.WriteLine("test dumpmodule");
-    }
-
-    static void DumpObject()
-    {
-        Console.WriteLine("test dumpobject");
-    }
-
-    static void DumpStackObjects()
-    {
-        Console.WriteLine("test dso");
-    }
-
-    static void Name2EE()
-    {
-        Console.WriteLine("test name2ee");
-    }
-
-
-    static int Main()
-    {
-        DumpIL();
-        LikelyInlined();
-        UnlikelyInlined();
-
-        return 0;
-    }
-}
diff --git a/src/SOS/TestDebuggee/TestDebuggee.csproj b/src/SOS/TestDebuggee/TestDebuggee.csproj
deleted file mode 100644 (file)
index cb3144f..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<!-- 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>
diff --git a/src/SOS/lldbplugin.tests/README.md b/src/SOS/lldbplugin.tests/README.md
new file mode 100644 (file)
index 0000000..a4ad7dc
--- /dev/null
@@ -0,0 +1,44 @@
+Testing libsosplugin
+=====================================
+
+**Running tests**
+  
+The test.sh and testsos.sh scripts launches these tests and makes the following a lot easier.
+
+
+Make sure that python's lldb module is accessible. To run the tests manually, use the following command:
+  
+`python2 test_libsosplugin.py --lldb <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)
diff --git a/src/SOS/lldbplugin.tests/TestDebuggee/Test.cs b/src/SOS/lldbplugin.tests/TestDebuggee/Test.cs
new file mode 100644 (file)
index 0000000..bd38297
--- /dev/null
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+class Test
+{
+    static void LikelyInlined()
+    {
+        Console.WriteLine("I would like to be inlined");
+    }
+
+    static void UnlikelyInlined()
+    {
+        Console.Write("I");
+        Console.Write(" ");
+        Console.Write("w");
+        Console.Write("o");
+        Console.Write("u");
+        Console.Write("l");
+        Console.Write("d");
+        Console.Write(" ");
+        Console.Write("n");
+        Console.Write("o");
+        Console.Write("t");
+        Console.Write(" ");
+        Console.Write("l");
+        Console.Write("i");
+        Console.Write("k");
+        Console.Write("e");
+        Console.Write(" ");
+        Console.Write("t");
+        Console.Write("o");
+        Console.Write(" ");
+        Console.Write("b");
+        Console.Write("e");
+        Console.Write(" ");
+        Console.Write("i");
+        Console.Write("n");
+        Console.Write("l");
+        Console.Write("i");
+        Console.Write("n");
+        Console.Write("e");
+        Console.Write("d");
+        Console.Write("\n");
+    }
+
+    static void ClrU()
+    {
+        Console.WriteLine("test dumpclass");
+    }
+
+    static void DumpClass()
+    {
+        Console.WriteLine("test dumpclass");
+    }
+
+    static void DumpIL()
+    {
+        Console.WriteLine("test dumpil");
+    }
+
+    static void DumpMD()
+    {
+        Console.WriteLine("test dumpmd");
+    }
+
+    static void DumpModule()
+    {
+        Console.WriteLine("test dumpmodule");
+    }
+
+    static void DumpObject()
+    {
+        Console.WriteLine("test dumpobject");
+    }
+
+    static void DumpStackObjects()
+    {
+        Console.WriteLine("test dso");
+    }
+
+    static void Name2EE()
+    {
+        Console.WriteLine("test name2ee");
+    }
+
+
+    static int Main()
+    {
+        DumpIL();
+        LikelyInlined();
+        UnlikelyInlined();
+
+        return 0;
+    }
+}
diff --git a/src/SOS/lldbplugin.tests/TestDebuggee/TestDebuggee.csproj b/src/SOS/lldbplugin.tests/TestDebuggee/TestDebuggee.csproj
new file mode 100644 (file)
index 0000000..cb3144f
--- /dev/null
@@ -0,0 +1,9 @@
+<!-- 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>
diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_clear.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_clear.py
new file mode 100644 (file)
index 0000000..c80d130
--- /dev/null
@@ -0,0 +1,66 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+# bpmd -clear
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    # Set breakpoint
+
+    ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res)
+    out_msg = res.GetOutput()
+    err_msg = res.GetError()
+    print(out_msg)
+    print(err_msg)
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    # Output is not empty
+    # Should be at least 'Adding pending breakpoints...'
+    test.assertTrue(res.GetOutputSize() > 0)
+
+    # Error message is empty
+    test.assertTrue(res.GetErrorSize() == 0)
+
+    # Delete the first breakpoint
+
+    ci.HandleCommand("bpmd -clear 1", res)
+    out_msg = res.GetOutput()
+    err_msg = res.GetError()
+    print(out_msg)
+    print(err_msg)
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    match = re.search('Cleared', out_msg)
+    # Check for specific output
+    test.assertTrue(match)
+
+    # Error message is empty
+    test.assertTrue(res.GetErrorSize() == 0)
+
+    process.Continue()
+    # Process must be exited
+    test.assertEqual(process.GetState(), lldb.eStateStopped)
+
+    # The reason of this stop must be a breakpoint
+    test.assertEqual(process.GetSelectedThread().GetStopReason(),
+                     lldb.eStopReasonBreakpoint)
+
+    #
+
+    # Delete all breakpoints, continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_clearall.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_clearall.py
new file mode 100644 (file)
index 0000000..2c32185
--- /dev/null
@@ -0,0 +1,68 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+# bpmd -clearall
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    # Set breakpoint
+
+    ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res)
+    out_msg = res.GetOutput()
+    err_msg = res.GetError()
+    print(out_msg)
+    print(err_msg)
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    # Output is not empty
+    # Should be at least 'Adding pending breakpoints...'
+    test.assertTrue(res.GetOutputSize() > 0)
+
+    # Error message is empty
+    test.assertTrue(res.GetErrorSize() == 0)
+
+    # Delete all breakpoints
+
+    ci.HandleCommand("bpmd -clearall", res)
+    out_msg = res.GetOutput()
+    err_msg = res.GetError()
+    print(out_msg)
+    print(err_msg)
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    match = re.search('All pending breakpoints cleared.', out_msg)
+    # Check for specific output
+    test.assertTrue(match)
+
+    # Error message is empty
+    test.assertTrue(res.GetErrorSize() == 0)
+
+    process.Continue()
+    process.Continue()
+
+    # Process must be exited
+    test.assertEqual(process.GetState(), lldb.eStateExited)
+
+    # The reason of this stop must be a breakpoint
+    test.assertEqual(process.GetSelectedThread().GetStopReason(),
+                     lldb.eStopReasonNone)
+
+    #
+
+    # Delete all breakpoints, continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_methoddesc.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_methoddesc.py
new file mode 100644 (file)
index 0000000..f7d515a
--- /dev/null
@@ -0,0 +1,49 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+# bpmd -md <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)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function.py
new file mode 100644 (file)
index 0000000..471c580
--- /dev/null
@@ -0,0 +1,47 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+# bpmd <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)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function_iloffset.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_module_function_iloffset.py
new file mode 100644 (file)
index 0000000..51d5467
--- /dev/null
@@ -0,0 +1,47 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+# bpmd <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)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_bpmd_nofuturemodule_module_function.py b/src/SOS/lldbplugin.tests/t_cmd_bpmd_nofuturemodule_module_function.py
new file mode 100644 (file)
index 0000000..fc47f47
--- /dev/null
@@ -0,0 +1,51 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+# bpmd -nofuturemodule <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)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_clrstack.py b/src/SOS/lldbplugin.tests/t_cmd_clrstack.py
new file mode 100644 (file)
index 0000000..6812afb
--- /dev/null
@@ -0,0 +1,40 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("clrstack", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(res.GetOutputSize() > 0)
+
+    match = re.search('OS Thread Id', output)
+    # Specific string must be in the output
+    test.assertTrue(match)
+
+    match = re.search('Failed to start', output)
+    # Check if a fail was reported
+    test.assertFalse(match)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_clrthreads.py b/src/SOS/lldbplugin.tests/t_cmd_clrthreads.py
new file mode 100644 (file)
index 0000000..5e374dd
--- /dev/null
@@ -0,0 +1,35 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("clrthreads", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("ThreadCount:"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_clru.py b/src/SOS/lldbplugin.tests/t_cmd_clru.py
new file mode 100644 (file)
index 0000000..6f2971f
--- /dev/null
@@ -0,0 +1,50 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    match = re.search('JITTED Code Address:\s+([0-9a-fA-F]+)', output)
+    # Line matched
+    test.assertTrue(match)
+
+    groups = match.groups()
+    # Match has a single subgroup
+    test.assertEqual(len(groups), 1)
+
+    jit_addr = groups[0]
+    # Address must be a hex number
+    test.assertTrue(test.is_hexnum(jit_addr))
+
+    ci.HandleCommand("clru " + jit_addr, res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dso.py b/src/SOS/lldbplugin.tests/t_cmd_dso.py
new file mode 100644 (file)
index 0000000..fc67615
--- /dev/null
@@ -0,0 +1,35 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dso", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("SP/REG"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpclass.py b/src/SOS/lldbplugin.tests/t_cmd_dumpclass.py
new file mode 100644 (file)
index 0000000..8026122
--- /dev/null
@@ -0,0 +1,72 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output)
+    # Line matched
+    test.assertTrue(match)
+
+    groups = match.groups()
+    # Match has a single subgroup
+    test.assertEqual(len(groups), 1)
+
+    md_addr = groups[0]
+    # Address must be a hex number
+    test.assertTrue(test.is_hexnum(md_addr))
+
+    ci.HandleCommand("dumpmd " + md_addr, res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    match = re.search('Class:\s+([0-9a-fA-F]+)', output)
+    # Line matched
+    test.assertTrue(match)
+
+    groups = match.groups()
+    # Match has a single subgroup
+    test.assertEqual(len(groups), 1)
+
+    class_addr = groups[0]
+    # Address must be a hex number
+    test.assertTrue(test.is_hexnum(class_addr))
+
+    ci.HandleCommand("dumpmd " + class_addr, res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpheap.py b/src/SOS/lldbplugin.tests/t_cmd_dumpheap.py
new file mode 100644 (file)
index 0000000..0186435
--- /dev/null
@@ -0,0 +1,35 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dumpheap", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("Address"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpil.py b/src/SOS/lldbplugin.tests/t_cmd_dumpil.py
new file mode 100644 (file)
index 0000000..1ddd482
--- /dev/null
@@ -0,0 +1,42 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    md_addr = test.get_methoddesc(debugger, assembly, "Test.DumpIL")
+
+    ci.HandleCommand("dumpil " + md_addr, res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    insts = res.GetOutput()
+    print(insts)
+    # Function must have some instructions
+    test.assertTrue(len(insts) > 0)
+
+    match = re.search(r'IL_\w{4}:\sldstr.*test\sdumpil.*' +
+                      r'IL_\w{4}:\scall.*System\.Console::WriteLine.*' +
+                      r'IL_\w{4}:\sret',
+                      insts.replace('\n', ' '))
+    # Must have ldstr, call and ret instructions
+    test.assertTrue(match)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumplog.py b/src/SOS/lldbplugin.tests/t_cmd_dumplog.py
new file mode 100644 (file)
index 0000000..631beb4
--- /dev/null
@@ -0,0 +1,35 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dumplog", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find(" dump "), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpmd.py b/src/SOS/lldbplugin.tests/t_cmd_dumpmd.py
new file mode 100644 (file)
index 0000000..b2b020b
--- /dev/null
@@ -0,0 +1,50 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output)
+    # Line matched
+    test.assertTrue(match)
+
+    groups = match.groups()
+    # Match has a single subgroup
+    test.assertEqual(len(groups), 1)
+
+    md_addr = groups[0]
+    # Address must be a hex number
+    test.assertTrue(test.is_hexnum(md_addr))
+
+    ci.HandleCommand("dumpmd " + md_addr, res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpmodule.py b/src/SOS/lldbplugin.tests/t_cmd_dumpmodule.py
new file mode 100644 (file)
index 0000000..5f256b1
--- /dev/null
@@ -0,0 +1,50 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    match = re.search('Module:\s+([0-9a-fA-F]+)', output)
+    # Line matched
+    test.assertTrue(match)
+
+    groups = match.groups()
+    # Match has a single subgroup
+    test.assertEqual(len(groups), 1)
+
+    md_addr = groups[0]
+    # Address must be a hex number
+    test.assertTrue(test.is_hexnum(md_addr))
+
+    ci.HandleCommand("dumpmodule " + md_addr, res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpmt.py b/src/SOS/lldbplugin.tests/t_cmd_dumpmt.py
new file mode 100644 (file)
index 0000000..3640a4b
--- /dev/null
@@ -0,0 +1,72 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output)
+    # Line matched
+    test.assertTrue(match)
+
+    groups = match.groups()
+    # Match has a single subgroup
+    test.assertEqual(len(groups), 1)
+
+    md_addr = groups[0]
+    # Address must be a hex number
+    test.assertTrue(test.is_hexnum(md_addr))
+
+    ci.HandleCommand("dumpmd " + md_addr, res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    match = re.search('MethodTable:\s+([0-9a-fA-F]+)', output)
+    # Line matched
+    test.assertTrue(match)
+
+    groups = match.groups()
+    # Match has a single subgroup
+    test.assertEqual(len(groups), 1)
+
+    mt_addr = groups[0]
+    # Address must be a hex number
+    test.assertTrue(test.is_hexnum(mt_addr))
+
+    ci.HandleCommand("dumpmt " + mt_addr, res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpobj.py b/src/SOS/lldbplugin.tests/t_cmd_dumpobj.py
new file mode 100644 (file)
index 0000000..a6f2e39
--- /dev/null
@@ -0,0 +1,73 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dso", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Get all objects
+    objects = []
+    for line in output.split('\n'):
+        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
+        # Not all lines list objects
+        if match:
+            groups = match.groups()
+            # Match has exactly two subgroups
+            test.assertEqual(len(groups), 2)
+
+            obj_addr = groups[1]
+            # Address must be a hex number
+            test.assertTrue(test.is_hexnum(obj_addr))
+
+            objects.append(obj_addr)
+
+    # There must be at least one object
+    test.assertTrue(len(objects) > 0)
+
+    for obj in objects:
+        ci.HandleCommand("dumpobj " + obj, res)
+        print(res.GetOutput())
+        print(res.GetError())
+        # Interpreter must have this command and able to run it
+        test.assertTrue(res.Succeeded())
+
+        output = res.GetOutput()
+        # Output is not empty
+        test.assertTrue(len(output) > 0)
+
+        match = re.search('Name:\s+\S+', output)
+        test.assertTrue(match)
+        match = re.search('MethodTable:\s+[0-9a-fA-F]+', output)
+        test.assertTrue(match)
+        match = re.search('EEClass:\s+[0-9a-fA-F]+', output)
+        test.assertTrue(match)
+        match = re.search('Size:\s+[0-9a-fA-F]+', output)
+        test.assertTrue(match)
+        match = re.search('Fields:', output)
+        test.assertTrue(match)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_dumpstack.py b/src/SOS/lldbplugin.tests/t_cmd_dumpstack.py
new file mode 100644 (file)
index 0000000..24ae1a5
--- /dev/null
@@ -0,0 +1,35 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dumpstack", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("Test.Main()"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_eeheap.py b/src/SOS/lldbplugin.tests/t_cmd_eeheap.py
new file mode 100644 (file)
index 0000000..8c95964
--- /dev/null
@@ -0,0 +1,35 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("eeheap", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("Loader Heap"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_eestack.py b/src/SOS/lldbplugin.tests/t_cmd_eestack.py
new file mode 100644 (file)
index 0000000..8764bcc
--- /dev/null
@@ -0,0 +1,35 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("eestack", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("Current frame"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_gcroot.py b/src/SOS/lldbplugin.tests/t_cmd_gcroot.py
new file mode 100644 (file)
index 0000000..636578d
--- /dev/null
@@ -0,0 +1,65 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dso", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Get all objects
+    objects = []
+    for line in output.split('\n'):
+        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
+        # Not all lines list objects
+        if match:
+            groups = match.groups()
+            # Match has exactly two subgroups
+            test.assertEqual(len(groups), 2)
+
+            obj_addr = groups[1]
+            # Address must be a hex number
+            test.assertTrue(test.is_hexnum(obj_addr))
+
+            objects.append(obj_addr)
+
+    # There must be at least one object
+    test.assertTrue(len(objects) > 0)
+
+    for obj in objects:
+        ci.HandleCommand("gcroot " + obj, res)
+        print(res.GetOutput())
+        print(res.GetError())
+        # Interpreter must have this command and able to run it
+        test.assertTrue(res.Succeeded())
+
+        output = res.GetOutput()
+        # Output is not empty
+        test.assertTrue(len(output) > 0)
+
+        match = re.search('Found', output)
+        test.assertTrue(match)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_histclear.py b/src/SOS/lldbplugin.tests/t_cmd_histclear.py
new file mode 100644 (file)
index 0000000..b5206a7
--- /dev/null
@@ -0,0 +1,35 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("histclear", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("Completed successfully."), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_histinit.py b/src/SOS/lldbplugin.tests/t_cmd_histinit.py
new file mode 100644 (file)
index 0000000..d0b2e89
--- /dev/null
@@ -0,0 +1,35 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("histinit", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("STRESS LOG:"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_histobj.py b/src/SOS/lldbplugin.tests/t_cmd_histobj.py
new file mode 100644 (file)
index 0000000..402f042
--- /dev/null
@@ -0,0 +1,65 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dso", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Get all objects
+    objects = []
+    for line in output.split('\n'):
+        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
+        # Not all lines list objects
+        if match:
+            groups = match.groups()
+            # Match has exactly two subgroups
+            test.assertEqual(len(groups), 2)
+
+            obj_addr = groups[1]
+            # Address must be a hex number
+            test.assertTrue(test.is_hexnum(obj_addr))
+
+            objects.append(obj_addr)
+
+    # There must be at least one object
+    test.assertTrue(len(objects) > 0)
+
+    for obj in objects:
+        ci.HandleCommand("histobj " + obj, res)
+        print(res.GetOutput())
+        print(res.GetError())
+        # Interpreter must have this command and able to run it
+        test.assertTrue(res.Succeeded())
+
+        output = res.GetOutput()
+        # Output is not empty
+        test.assertTrue(len(output) > 0)
+
+        match = re.search('GCCount', output)
+        test.assertTrue(match)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_histobjfind.py b/src/SOS/lldbplugin.tests/t_cmd_histobjfind.py
new file mode 100644 (file)
index 0000000..6116399
--- /dev/null
@@ -0,0 +1,65 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dso", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Get all objects
+    objects = []
+    for line in output.split('\n'):
+        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
+        # Not all lines list objects
+        if match:
+            groups = match.groups()
+            # Match has exactly two subgroups
+            test.assertEqual(len(groups), 2)
+
+            obj_addr = groups[1]
+            # Address must be a hex number
+            test.assertTrue(test.is_hexnum(obj_addr))
+
+            objects.append(obj_addr)
+
+    # There must be at least one object
+    test.assertTrue(len(objects) > 0)
+
+    for obj in objects:
+        ci.HandleCommand("histobjfind " + obj, res)
+        print(res.GetOutput())
+        print(res.GetError())
+        # Interpreter must have this command and able to run it
+        test.assertTrue(res.Succeeded())
+
+        output = res.GetOutput()
+        # Output is not empty
+        test.assertTrue(len(output) > 0)
+
+        match = re.search('GCCount', output)
+        test.assertTrue(match)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_histroot.py b/src/SOS/lldbplugin.tests/t_cmd_histroot.py
new file mode 100644 (file)
index 0000000..e212b1f
--- /dev/null
@@ -0,0 +1,65 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dso", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Get all objects
+    objects = []
+    for line in output.split('\n'):
+        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
+        # Not all lines list objects
+        if match:
+            groups = match.groups()
+            # Match has exactly two subgroups
+            test.assertEqual(len(groups), 2)
+
+            obj_addr = groups[1]
+            # Address must be a hex number
+            test.assertTrue(test.is_hexnum(obj_addr))
+
+            objects.append(obj_addr)
+
+    # There must be at least one object
+    test.assertTrue(len(objects) > 0)
+
+    for obj in objects:
+        ci.HandleCommand("histroot " + obj, res)
+        print(res.GetOutput())
+        print(res.GetError())
+        # Interpreter must have this command and able to run it
+        test.assertTrue(res.Succeeded())
+
+        output = res.GetOutput()
+        # Output is not empty
+        test.assertTrue(len(output) > 0)
+
+        match = re.search('GCCount', output)
+        test.assertTrue(match)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_ip2md.py b/src/SOS/lldbplugin.tests/t_cmd_ip2md.py
new file mode 100644 (file)
index 0000000..78bebe9
--- /dev/null
@@ -0,0 +1,57 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    match = re.search('JITTED Code Address:\s+([0-9a-fA-F]+)', output)
+    # Line matched
+    test.assertTrue(match)
+
+    groups = match.groups()
+    # Match has a single subgroup
+    test.assertEqual(len(groups), 1)
+
+    jit_addr = groups[0]
+    # Address must be a hex number
+    test.assertTrue(test.is_hexnum(jit_addr))
+
+    ci.HandleCommand("ip2md " + jit_addr, res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("MethodDesc:"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_name2ee.py b/src/SOS/lldbplugin.tests/t_cmd_name2ee.py
new file mode 100644 (file)
index 0000000..71d4212
--- /dev/null
@@ -0,0 +1,50 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    match = re.search('Module:\s+[0-9a-fA-F]+', output)
+    test.assertTrue(match)
+    match = re.search('Assembly:\s+\S+', output)
+    test.assertTrue(match)
+    match = re.search('Token:\s+[0-9a-fA-F]+', output)
+    test.assertTrue(match)
+    match = re.search('MethodDesc:\s+[0-9a-fA-F]+', output)
+    test.assertTrue(match)
+    match = re.search('Name:\s+\S+', output)
+    test.assertTrue(match)
+
+    process.Continue()
+    # Process must exit
+    test.assertEqual(process.GetState(), lldb.eStateExited)
+
+    # Process must exit with zero code
+    test.assertEqual(process.GetExitStatus(), 0)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_pe.py b/src/SOS/lldbplugin.tests/t_cmd_pe.py
new file mode 100644 (file)
index 0000000..0a87014
--- /dev/null
@@ -0,0 +1,28 @@
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("dso", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_sos.py b/src/SOS/lldbplugin.tests/t_cmd_sos.py
new file mode 100644 (file)
index 0000000..b407491
--- /dev/null
@@ -0,0 +1,31 @@
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("sos", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("SOS"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/t_cmd_soshelp.py b/src/SOS/lldbplugin.tests/t_cmd_soshelp.py
new file mode 100644 (file)
index 0000000..8bb51da
--- /dev/null
@@ -0,0 +1,31 @@
+import lldb
+import re
+import testutils as test
+
+
+def runScenario(assembly, debugger, target):
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+    ci = debugger.GetCommandInterpreter()
+
+    # Run debugger, wait until libcoreclr is loaded,
+    # set breakpoint at Test.Main and stop there
+    test.stop_in_main(debugger, assembly)
+
+    ci.HandleCommand("soshelp", res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    test.assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    test.assertTrue(len(output) > 0)
+
+    # Specific string must be in the output
+    test.assertNotEqual(output.find("soshelp <functionname>"), -1)
+
+    # TODO: test other use cases
+
+    # Continue current process and checks its exit code
+    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/lldbplugin.tests/test_libsosplugin.py b/src/SOS/lldbplugin.tests/test_libsosplugin.py
new file mode 100644 (file)
index 0000000..1f7b827
--- /dev/null
@@ -0,0 +1,301 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+from __future__ import print_function
+import unittest
+import argparse
+import re
+import tempfile
+import subprocess
+import threading
+import os
+import sys
+import inspect
+
+lldb = ''
+logfiledir = ''
+host = ''
+plugin = ''
+assembly = ''
+fail_flag = ''
+fail_flag_lldb = ''
+summary_file = ''
+timeout = 0
+regex = ''
+repeat = 0
+
+tests_failed = False
+
+def runWithTimeout(cmd):
+    p = None
+
+    def run():
+        global p
+        p = subprocess.Popen(cmd, shell=True)
+        p.communicate()
+
+    thread = threading.Thread(target=run)
+    thread.start()
+
+    thread.join(timeout)
+    if thread.is_alive():
+        with open(summary_file, 'a+') as summary:
+            print('Timeout!', file=summary)
+        p.kill()
+        thread.join()
+
+
+class TestSosCommands(unittest.TestCase):
+
+    def do_test(self, command):
+        open(fail_flag, 'a').close()
+        try:
+            os.unlink(fail_flag_lldb)
+        except:
+            pass
+
+       logfile = os.path.join(logfiledir, command)
+
+        cmd = (('%s -b ' % lldb) +
+               ("-k \"script open('%s', 'a').close()\" " % fail_flag_lldb) +
+               ("-k 'quit' ") +
+               ("--no-lldbinit ") +
+               ("-o \"version\" ") +
+               ("-O \"plugin load %s \" " % plugin) +
+               ("-o \"script import testutils as test\" ") +
+               ("-o \"script test.fail_flag = '%s'\" " % fail_flag) +
+               ("-o \"script test.summary_file = '%s'\" " % summary_file) +
+               ("-o \"script test.run('%s', '%s')\" " % (assembly, command)) +
+               ("-o \"quit\" ") +
+               (" -- %s %s > %s.log 2>&1" % (host, assembly, logfile)))
+
+        runWithTimeout(cmd)
+        if not os.path.isfile(fail_flag):
+            tests_failed = True
+
+        if not os.path.isfile(fail_flag_lldb):
+            tests_failed = True
+        self.assertFalse(os.path.isfile(fail_flag))
+        self.assertFalse(os.path.isfile(fail_flag_lldb))
+
+        try:
+            os.unlink(fail_flag)
+        except:
+            pass
+        try:
+            os.unlink(fail_flag_lldb)
+        except:
+            pass
+
+    def t_cmd_bpmd_nofuturemodule_module_function(self):
+        self.do_test('t_cmd_bpmd_nofuturemodule_module_function')
+
+    def t_cmd_bpmd_module_function(self):
+        self.do_test('t_cmd_bpmd_module_function')
+
+    def t_cmd_bpmd_module_function_iloffset(self):
+        self.do_test('t_cmd_bpmd_module_function_iloffset')
+
+    def t_cmd_bpmd_methoddesc(self):
+        self.do_test('t_cmd_bpmd_methoddesc')
+
+    def t_cmd_bpmd_clear(self):
+        self.do_test('t_cmd_bpmd_clear')
+
+    def t_cmd_bpmd_clearall(self):
+        self.do_test('t_cmd_bpmd_clearall')
+
+    def t_cmd_clrstack(self):
+        self.do_test('t_cmd_clrstack')
+
+    def t_cmd_clrthreads(self):
+        self.do_test('t_cmd_clrthreads')
+
+    def t_cmd_clru(self):
+        self.do_test('t_cmd_clru')
+
+    def t_cmd_dumpclass(self):
+        self.do_test('t_cmd_dumpclass')
+
+    def t_cmd_dumpheap(self):
+        self.do_test('t_cmd_dumpheap')
+
+    def t_cmd_dumpil(self):
+        self.do_test('t_cmd_dumpil')
+
+    def t_cmd_dumplog(self):
+        self.do_test('t_cmd_dumplog')
+
+    def t_cmd_dumpmd(self):
+        self.do_test('t_cmd_dumpmd')
+
+    def t_cmd_dumpmodule(self):
+        self.do_test('t_cmd_dumpmodule')
+
+    def t_cmd_dumpmt(self):
+        self.do_test('t_cmd_dumpmt')
+
+    def t_cmd_dumpobj(self):
+        self.do_test('t_cmd_dumpobj')
+
+    def t_cmd_dumpstack(self):
+        self.do_test('t_cmd_dumpstack')
+
+    def t_cmd_dso(self):
+        self.do_test('t_cmd_dso')
+
+    def t_cmd_eeheap(self):
+        self.do_test('t_cmd_eeheap')
+
+    def t_cmd_eestack(self):
+        self.do_test('t_cmd_eestack')
+
+    def t_cmd_gcroot(self):
+        self.do_test('t_cmd_gcroot')
+
+    def t_cmd_ip2md(self):
+        self.do_test('t_cmd_ip2md')
+
+    def t_cmd_name2ee(self):
+        self.do_test('t_cmd_name2ee')
+
+    def t_cmd_pe(self):
+        self.do_test('t_cmd_pe')
+
+    def t_cmd_histclear(self):
+        self.do_test('t_cmd_histclear')
+
+    def t_cmd_histinit(self):
+        self.do_test('t_cmd_histinit')
+
+    def t_cmd_histobj(self):
+        self.do_test('t_cmd_histobj')
+
+    def t_cmd_histobjfind(self):
+        self.do_test('t_cmd_histobjfind')
+
+    def t_cmd_histroot(self):
+        self.do_test('t_cmd_histroot')
+
+    def t_cmd_sos(self):
+        self.do_test('t_cmd_sos')
+
+    def t_cmd_soshelp(self):
+        self.do_test('t_cmd_soshelp')
+
+
+def generate_report():
+    report = [{'name': 'TOTAL', True: 0, False: 0, 'completed': True}]
+    fail_messages = []
+
+    if not os.path.isfile(summary_file):
+        print('No summary file to process!')
+        return
+
+    with open(summary_file, 'r') as summary:
+        for line in summary:
+            if line.startswith('new_suite: '):
+                report.append({'name': line.split()[-1], True: 0, False: 0,
+                               'completed': False, 'timeout': False})
+            elif line.startswith('True'):
+                report[-1][True] += 1
+            elif line.startswith('False'):
+                report[-1][False] += 1
+            elif line.startswith('Completed!'):
+                report[-1]['completed'] = True
+            elif line.startswith('Timeout!'):
+                report[-1]['timeout'] = True
+            elif line.startswith('!!! '):
+                fail_messages.append(line.rstrip('\n'))
+
+    for suite in report[1:]:
+        report[0][True] += suite[True]
+        report[0][False] += suite[False]
+        report[0]['completed'] &= suite['completed']
+
+    for line in fail_messages:
+        print(line)
+
+    print()
+    print('=' * 79)
+    print('{:72} {:6}'.format('Test suite', 'Result'))
+    print('-' * 79)
+    for suite in report[1:]:
+        if suite['timeout']:
+            result = 'Timeout'
+        elif suite[False]:
+            result = 'Fail'
+        elif not suite['completed']:
+            result = 'Crash'
+        elif suite[True]:
+            result = 'Success'
+        else:
+            result = 'Please, report'
+        print('{:68} {:>10}'.format(suite['name'], result))
+    print('=' * 79)
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--lldb', default='lldb')
+    parser.add_argument('--host', default='.')
+    parser.add_argument('--plugin', default='.')
+    parser.add_argument('--logfiledir', default='.')
+    parser.add_argument('--assembly', default='Test.exe')
+    parser.add_argument('--timeout', default=90)
+    parser.add_argument('--regex', default='t_cmd_')
+    parser.add_argument('--repeat', default=1)
+    parser.add_argument('unittest_args', nargs='*')
+
+    args = parser.parse_args()
+
+    lldb = args.lldb
+    host = args.host
+    plugin = args.plugin
+    logfiledir = args.logfiledir
+    assembly = args.assembly
+    timeout = int(args.timeout)
+    regex = args.regex
+    repeat = int(args.repeat)
+    print("host: %s" % host)
+    print("lldb: %s" % lldb)
+    print("lldb plugin: %s" % plugin)
+    print("logfiledir: %s" % logfiledir)
+    print("assembly: %s" % assembly)
+    print("timeout: %i" % timeout)
+    print("regex: %s" % regex)
+    print("repeat: %i" % repeat)
+
+    if os.name != 'posix':
+        print('OS not supported')
+        exit(1)
+
+    fail_flag = os.path.join(logfiledir, 'fail_flag')
+    fail_flag_lldb = os.path.join(logfiledir, 'fail_flag.lldb')
+
+    print("fail_flag: %s" % fail_flag)
+    print("fail_flag_lldb: %s" % fail_flag_lldb)
+
+    summary_file = os.path.join(logfiledir, 'summary')
+    print("summary_file: %s" % summary_file)
+
+    try:
+        os.unlink(summary_file)
+    except:
+        pass
+
+    sys.argv[1:] = args.unittest_args
+    suite = unittest.TestSuite()
+    all_tests = inspect.getmembers(TestSosCommands, predicate=inspect.ismethod)
+    for (test_name, test_func) in all_tests:
+        if re.match(regex, test_name):
+            suite.addTest(TestSosCommands(test_name))
+    unittest.TextTestRunner(verbosity=1).run(suite)
+
+    generate_report()
+
+    if tests_failed:
+        exit(1)
+
diff --git a/src/SOS/lldbplugin.tests/testsos.sh b/src/SOS/lldbplugin.tests/testsos.sh
new file mode 100755 (executable)
index 0000000..d4f4f5f
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+__ProjectRoot="$1"
+__Plugin="$2"
+__ManagedBinDir="$3"
+__ResultsDir="$4"
+
+if [[ "$__ProjectRoot" = "" || "$__Plugin" = "" || "$__ManagedBinDir" = "" || "$__ResultsDir" = "" ]]; then
+    echo "Project root, plugin or log directory required"
+    exit 1
+fi
+
+__Host=$__ProjectRoot/.dotnet/dotnet
+__TestProgram=$__ManagedBinDir/TestDebuggee/netcoreapp2.0/TestDebuggee.dll
+
+# Turn on stress logging so the dumplog and histinit commands pass
+export COMPlus_LogFacility=0xffffffbf
+export COMPlus_LogLevel=6
+export COMPlus_StressLog=1
+export COMPlus_StressLogSize=65536
+
+if [[ ! -x "$LLDB_PATH" ]]; then
+    echo "LLDB_PATH doesn't exist or not executable"
+    exit 1
+fi
+
+__LogFileDir=$__ResultsDir/lldbplugin.tests_$(date +%Y_%m_%d_%H_%M_%S)
+mkdir -p $__LogFileDir
+
+cd $__ProjectRoot/src/SOS/lldbplugin.tests/
+rm -f StressLog.txt
+python $__ProjectRoot/src/SOS/lldbplugin.tests/test_libsosplugin.py --lldb $LLDB_PATH --host $__Host --plugin $__Plugin --logfiledir $__LogFileDir --assembly $__TestProgram
+
diff --git a/src/SOS/lldbplugin.tests/testutils.py b/src/SOS/lldbplugin.tests/testutils.py
new file mode 100644 (file)
index 0000000..7d7df27
--- /dev/null
@@ -0,0 +1,209 @@
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+# See the LICENSE file in the project root for more information.
+
+from __future__ import print_function
+import lldb
+import re
+import inspect
+import sys
+import os
+import importlib
+
+summary_file = ''
+fail_flag = ''
+
+failed = False
+
+def assertCommon(passed, fatal):
+    global failed
+    with open(summary_file, 'a+') as summary:
+        print(bool(passed), file=summary)
+        if (not passed):
+            failed = True
+            print('!!! test failed:', file=summary)
+            for s in inspect.stack()[2:]:
+                print("!!!  %s:%i" % (s[1], s[2]), file=summary)
+                print("!!! %s" % s[4][0], file=summary)
+                if re.match('\W*t_\w+\.py$', s[1]):
+                    break
+            print('!!! ', file=summary)
+
+            if fatal:
+                exit(1)
+
+def assertTrue(x, fatal=True):
+    passed = bool(x)
+    assertCommon(passed, fatal)
+
+
+def assertFalse(x, fatal=True):
+    passed = not bool(x)
+    assertCommon(passed, fatal)
+
+
+def assertEqual(x, y, fatal=True):
+    passed = (x == y)
+    if not passed:
+        print(str(x), ' != ', str(y))
+    assertCommon(passed, fatal)
+
+
+def assertNotEqual(x, y, fatal=True):
+    passed = (x != y)
+    if not passed:
+        print(str(x), ' == ', str(y))
+    assertCommon(passed, fatal)
+
+
+def checkResult(res):
+    global failed
+    if not res.Succeeded():
+        failed = True
+        print(res.GetOutput())
+        print(res.GetError())
+        exit(1)
+
+
+def is_hexnum(s):
+    try:
+        int(s, 16)
+        return True
+    except ValueError:
+        return False
+
+
+def exec_and_find(commandInterpreter, cmd, regexp):
+    res = lldb.SBCommandReturnObject()
+    commandInterpreter.HandleCommand(cmd, res)
+    checkResult(res)
+
+    expr = re.compile(regexp)
+    addr = None
+
+    print(res.GetOutput())
+    lines = res.GetOutput().splitlines()
+    for line in lines:
+        match = expr.match(line)
+        if match:
+            addr = match.group(1)
+            break
+
+    print("Found addr: " + str(addr))
+    return addr
+
+
+def stop_in_main(debugger, assembly):
+    ci = debugger.GetCommandInterpreter()
+    target = debugger.GetSelectedTarget()
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+
+    # Process must be stopped here while libcoreclr loading.
+    # This test usually fails on release version of coreclr
+    # since we depend on 'LoadLibraryExW' symbol present.
+    assertEqual(process.GetState(), lldb.eStateStopped)
+
+    # The reason of this stop must be a breakpoint
+    assertEqual(process.GetSelectedThread().GetStopReason(),
+                lldb.eStopReasonBreakpoint)
+
+    ci.HandleCommand("bpmd " + assembly + " Test.Main", res)
+    out_msg = res.GetOutput()
+    err_msg = res.GetError()
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    assertTrue(res.Succeeded())
+
+    # Output is not empty
+    # Should be at least 'Adding pending breakpoints...'
+    assertTrue(res.GetOutputSize() > 0)
+
+    # Error message is empty
+    assertTrue(res.GetErrorSize() == 0)
+
+    process.Continue()
+    # Process must be stopped here if bpmd works at all
+    assertEqual(process.GetState(), lldb.eStateStopped)
+
+    # The reason of this stop must be a breakpoint
+    assertEqual(process.GetSelectedThread().GetStopReason(),
+                lldb.eStopReasonBreakpoint)
+
+
+def exit_lldb(debugger, assembly):
+    ci = debugger.GetCommandInterpreter()
+    target = debugger.GetSelectedTarget()
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+
+    ci.HandleCommand("breakpoint delete --force", res)
+    out_msg = res.GetOutput()
+    err_msg = res.GetError()
+    print(out_msg)
+    print(err_msg)
+    # Interpreter must have this command and able to run it
+    # assertTrue(res.Succeeded())
+
+    process.Continue()
+    # Process must exit
+    assertEqual(process.GetState(), lldb.eStateExited)
+
+    # Process must exit with zero code
+    assertEqual(process.GetExitStatus(), 0)
+
+
+def get_methoddesc(debugger, assembly, funcname):
+    ci = debugger.GetCommandInterpreter()
+    target = debugger.GetSelectedTarget()
+    process = target.GetProcess()
+    res = lldb.SBCommandReturnObject()
+
+    ci.HandleCommand("name2ee %s %s" % (assembly, funcname), res)
+    print(res.GetOutput())
+    print(res.GetError())
+    # Interpreter must have this command and able to run it
+    assertTrue(res.Succeeded())
+
+    output = res.GetOutput()
+    # Output is not empty
+    assertTrue(len(output) > 0)
+
+    match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output)
+    # Line matched
+    assertTrue(match)
+
+    groups = match.groups()
+    # Match has a single subgroup
+    assertEqual(len(groups), 1)
+
+    md_addr = groups[0]
+    # Address must be a hex number
+    assertTrue(is_hexnum(md_addr))
+
+    return md_addr
+
+
+def run(assembly, module):
+    with open(summary_file, 'a+') as summary:
+        print('new_suite: %s' % module, file=summary)
+
+    debugger = lldb.debugger
+
+    debugger.SetAsync(False)
+    target = lldb.target
+
+    debugger.HandleCommand("breakpoint set --name coreclr_execute_assembly")
+    debugger.HandleCommand("process launch")
+
+    # run the scenario
+    print("starting scenario...")
+    i = importlib.import_module(module)
+    scenarioResult = i.runScenario(os.path.basename(assembly), debugger, target)
+
+    if (target.GetProcess().GetExitStatus() == 0) and not failed:
+        os.unlink(fail_flag)
+
+    with open(summary_file, 'a+') as summary:
+        print('Completed!', file=summary)
diff --git a/src/SOS/tests/README.md b/src/SOS/tests/README.md
deleted file mode 100644 (file)
index a4ad7dc..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-Testing libsosplugin
-=====================================
-
-**Running tests**
-  
-The test.sh and testsos.sh scripts launches these tests and makes the following a lot easier.
-
-
-Make sure that python's lldb module is accessible. To run the tests manually, use the following command:
-  
-`python2 test_libsosplugin.py --lldb <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)
diff --git a/src/SOS/tests/t_cmd_bpmd_clear.py b/src/SOS/tests/t_cmd_bpmd_clear.py
deleted file mode 100644 (file)
index c80d130..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-# bpmd -clear
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    # Set breakpoint
-
-    ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res)
-    out_msg = res.GetOutput()
-    err_msg = res.GetError()
-    print(out_msg)
-    print(err_msg)
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    # Output is not empty
-    # Should be at least 'Adding pending breakpoints...'
-    test.assertTrue(res.GetOutputSize() > 0)
-
-    # Error message is empty
-    test.assertTrue(res.GetErrorSize() == 0)
-
-    # Delete the first breakpoint
-
-    ci.HandleCommand("bpmd -clear 1", res)
-    out_msg = res.GetOutput()
-    err_msg = res.GetError()
-    print(out_msg)
-    print(err_msg)
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    match = re.search('Cleared', out_msg)
-    # Check for specific output
-    test.assertTrue(match)
-
-    # Error message is empty
-    test.assertTrue(res.GetErrorSize() == 0)
-
-    process.Continue()
-    # Process must be exited
-    test.assertEqual(process.GetState(), lldb.eStateStopped)
-
-    # The reason of this stop must be a breakpoint
-    test.assertEqual(process.GetSelectedThread().GetStopReason(),
-                     lldb.eStopReasonBreakpoint)
-
-    #
-
-    # Delete all breakpoints, continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_bpmd_clearall.py b/src/SOS/tests/t_cmd_bpmd_clearall.py
deleted file mode 100644 (file)
index 2c32185..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-# bpmd -clearall
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    # Set breakpoint
-
-    ci.HandleCommand("bpmd " + assembly + " Test.UnlikelyInlined", res)
-    out_msg = res.GetOutput()
-    err_msg = res.GetError()
-    print(out_msg)
-    print(err_msg)
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    # Output is not empty
-    # Should be at least 'Adding pending breakpoints...'
-    test.assertTrue(res.GetOutputSize() > 0)
-
-    # Error message is empty
-    test.assertTrue(res.GetErrorSize() == 0)
-
-    # Delete all breakpoints
-
-    ci.HandleCommand("bpmd -clearall", res)
-    out_msg = res.GetOutput()
-    err_msg = res.GetError()
-    print(out_msg)
-    print(err_msg)
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    match = re.search('All pending breakpoints cleared.', out_msg)
-    # Check for specific output
-    test.assertTrue(match)
-
-    # Error message is empty
-    test.assertTrue(res.GetErrorSize() == 0)
-
-    process.Continue()
-    process.Continue()
-
-    # Process must be exited
-    test.assertEqual(process.GetState(), lldb.eStateExited)
-
-    # The reason of this stop must be a breakpoint
-    test.assertEqual(process.GetSelectedThread().GetStopReason(),
-                     lldb.eStopReasonNone)
-
-    #
-
-    # Delete all breakpoints, continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_bpmd_methoddesc.py b/src/SOS/tests/t_cmd_bpmd_methoddesc.py
deleted file mode 100644 (file)
index f7d515a..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-# bpmd -md <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)
diff --git a/src/SOS/tests/t_cmd_bpmd_module_function.py b/src/SOS/tests/t_cmd_bpmd_module_function.py
deleted file mode 100644 (file)
index 471c580..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-# bpmd <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)
diff --git a/src/SOS/tests/t_cmd_bpmd_module_function_iloffset.py b/src/SOS/tests/t_cmd_bpmd_module_function_iloffset.py
deleted file mode 100644 (file)
index 51d5467..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-# bpmd <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)
diff --git a/src/SOS/tests/t_cmd_bpmd_nofuturemodule_module_function.py b/src/SOS/tests/t_cmd_bpmd_nofuturemodule_module_function.py
deleted file mode 100644 (file)
index fc47f47..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-# bpmd -nofuturemodule <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)
diff --git a/src/SOS/tests/t_cmd_clrstack.py b/src/SOS/tests/t_cmd_clrstack.py
deleted file mode 100644 (file)
index 6812afb..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("clrstack", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(res.GetOutputSize() > 0)
-
-    match = re.search('OS Thread Id', output)
-    # Specific string must be in the output
-    test.assertTrue(match)
-
-    match = re.search('Failed to start', output)
-    # Check if a fail was reported
-    test.assertFalse(match)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_clrthreads.py b/src/SOS/tests/t_cmd_clrthreads.py
deleted file mode 100644 (file)
index 5e374dd..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("clrthreads", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("ThreadCount:"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_clru.py b/src/SOS/tests/t_cmd_clru.py
deleted file mode 100644 (file)
index 6f2971f..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    match = re.search('JITTED Code Address:\s+([0-9a-fA-F]+)', output)
-    # Line matched
-    test.assertTrue(match)
-
-    groups = match.groups()
-    # Match has a single subgroup
-    test.assertEqual(len(groups), 1)
-
-    jit_addr = groups[0]
-    # Address must be a hex number
-    test.assertTrue(test.is_hexnum(jit_addr))
-
-    ci.HandleCommand("clru " + jit_addr, res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dso.py b/src/SOS/tests/t_cmd_dso.py
deleted file mode 100644 (file)
index fc67615..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dso", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("SP/REG"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dumpclass.py b/src/SOS/tests/t_cmd_dumpclass.py
deleted file mode 100644 (file)
index 8026122..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output)
-    # Line matched
-    test.assertTrue(match)
-
-    groups = match.groups()
-    # Match has a single subgroup
-    test.assertEqual(len(groups), 1)
-
-    md_addr = groups[0]
-    # Address must be a hex number
-    test.assertTrue(test.is_hexnum(md_addr))
-
-    ci.HandleCommand("dumpmd " + md_addr, res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    match = re.search('Class:\s+([0-9a-fA-F]+)', output)
-    # Line matched
-    test.assertTrue(match)
-
-    groups = match.groups()
-    # Match has a single subgroup
-    test.assertEqual(len(groups), 1)
-
-    class_addr = groups[0]
-    # Address must be a hex number
-    test.assertTrue(test.is_hexnum(class_addr))
-
-    ci.HandleCommand("dumpmd " + class_addr, res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dumpheap.py b/src/SOS/tests/t_cmd_dumpheap.py
deleted file mode 100644 (file)
index 0186435..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dumpheap", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("Address"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dumpil.py b/src/SOS/tests/t_cmd_dumpil.py
deleted file mode 100644 (file)
index 1ddd482..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    md_addr = test.get_methoddesc(debugger, assembly, "Test.DumpIL")
-
-    ci.HandleCommand("dumpil " + md_addr, res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    insts = res.GetOutput()
-    print(insts)
-    # Function must have some instructions
-    test.assertTrue(len(insts) > 0)
-
-    match = re.search(r'IL_\w{4}:\sldstr.*test\sdumpil.*' +
-                      r'IL_\w{4}:\scall.*System\.Console::WriteLine.*' +
-                      r'IL_\w{4}:\sret',
-                      insts.replace('\n', ' '))
-    # Must have ldstr, call and ret instructions
-    test.assertTrue(match)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dumplog.py b/src/SOS/tests/t_cmd_dumplog.py
deleted file mode 100644 (file)
index 631beb4..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dumplog", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find(" dump "), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dumpmd.py b/src/SOS/tests/t_cmd_dumpmd.py
deleted file mode 100644 (file)
index b2b020b..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output)
-    # Line matched
-    test.assertTrue(match)
-
-    groups = match.groups()
-    # Match has a single subgroup
-    test.assertEqual(len(groups), 1)
-
-    md_addr = groups[0]
-    # Address must be a hex number
-    test.assertTrue(test.is_hexnum(md_addr))
-
-    ci.HandleCommand("dumpmd " + md_addr, res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dumpmodule.py b/src/SOS/tests/t_cmd_dumpmodule.py
deleted file mode 100644 (file)
index 5f256b1..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    match = re.search('Module:\s+([0-9a-fA-F]+)', output)
-    # Line matched
-    test.assertTrue(match)
-
-    groups = match.groups()
-    # Match has a single subgroup
-    test.assertEqual(len(groups), 1)
-
-    md_addr = groups[0]
-    # Address must be a hex number
-    test.assertTrue(test.is_hexnum(md_addr))
-
-    ci.HandleCommand("dumpmodule " + md_addr, res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dumpmt.py b/src/SOS/tests/t_cmd_dumpmt.py
deleted file mode 100644 (file)
index 3640a4b..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output)
-    # Line matched
-    test.assertTrue(match)
-
-    groups = match.groups()
-    # Match has a single subgroup
-    test.assertEqual(len(groups), 1)
-
-    md_addr = groups[0]
-    # Address must be a hex number
-    test.assertTrue(test.is_hexnum(md_addr))
-
-    ci.HandleCommand("dumpmd " + md_addr, res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    match = re.search('MethodTable:\s+([0-9a-fA-F]+)', output)
-    # Line matched
-    test.assertTrue(match)
-
-    groups = match.groups()
-    # Match has a single subgroup
-    test.assertEqual(len(groups), 1)
-
-    mt_addr = groups[0]
-    # Address must be a hex number
-    test.assertTrue(test.is_hexnum(mt_addr))
-
-    ci.HandleCommand("dumpmt " + mt_addr, res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dumpobj.py b/src/SOS/tests/t_cmd_dumpobj.py
deleted file mode 100644 (file)
index a6f2e39..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dso", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Get all objects
-    objects = []
-    for line in output.split('\n'):
-        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
-        # Not all lines list objects
-        if match:
-            groups = match.groups()
-            # Match has exactly two subgroups
-            test.assertEqual(len(groups), 2)
-
-            obj_addr = groups[1]
-            # Address must be a hex number
-            test.assertTrue(test.is_hexnum(obj_addr))
-
-            objects.append(obj_addr)
-
-    # There must be at least one object
-    test.assertTrue(len(objects) > 0)
-
-    for obj in objects:
-        ci.HandleCommand("dumpobj " + obj, res)
-        print(res.GetOutput())
-        print(res.GetError())
-        # Interpreter must have this command and able to run it
-        test.assertTrue(res.Succeeded())
-
-        output = res.GetOutput()
-        # Output is not empty
-        test.assertTrue(len(output) > 0)
-
-        match = re.search('Name:\s+\S+', output)
-        test.assertTrue(match)
-        match = re.search('MethodTable:\s+[0-9a-fA-F]+', output)
-        test.assertTrue(match)
-        match = re.search('EEClass:\s+[0-9a-fA-F]+', output)
-        test.assertTrue(match)
-        match = re.search('Size:\s+[0-9a-fA-F]+', output)
-        test.assertTrue(match)
-        match = re.search('Fields:', output)
-        test.assertTrue(match)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_dumpstack.py b/src/SOS/tests/t_cmd_dumpstack.py
deleted file mode 100644 (file)
index 24ae1a5..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dumpstack", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("Test.Main()"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_eeheap.py b/src/SOS/tests/t_cmd_eeheap.py
deleted file mode 100644 (file)
index 8c95964..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("eeheap", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("Loader Heap"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_eestack.py b/src/SOS/tests/t_cmd_eestack.py
deleted file mode 100644 (file)
index 8764bcc..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("eestack", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("Current frame"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_gcroot.py b/src/SOS/tests/t_cmd_gcroot.py
deleted file mode 100644 (file)
index 636578d..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dso", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Get all objects
-    objects = []
-    for line in output.split('\n'):
-        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
-        # Not all lines list objects
-        if match:
-            groups = match.groups()
-            # Match has exactly two subgroups
-            test.assertEqual(len(groups), 2)
-
-            obj_addr = groups[1]
-            # Address must be a hex number
-            test.assertTrue(test.is_hexnum(obj_addr))
-
-            objects.append(obj_addr)
-
-    # There must be at least one object
-    test.assertTrue(len(objects) > 0)
-
-    for obj in objects:
-        ci.HandleCommand("gcroot " + obj, res)
-        print(res.GetOutput())
-        print(res.GetError())
-        # Interpreter must have this command and able to run it
-        test.assertTrue(res.Succeeded())
-
-        output = res.GetOutput()
-        # Output is not empty
-        test.assertTrue(len(output) > 0)
-
-        match = re.search('Found', output)
-        test.assertTrue(match)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_histclear.py b/src/SOS/tests/t_cmd_histclear.py
deleted file mode 100644 (file)
index b5206a7..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("histclear", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("Completed successfully."), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_histinit.py b/src/SOS/tests/t_cmd_histinit.py
deleted file mode 100644 (file)
index d0b2e89..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("histinit", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("STRESS LOG:"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_histobj.py b/src/SOS/tests/t_cmd_histobj.py
deleted file mode 100644 (file)
index 402f042..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dso", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Get all objects
-    objects = []
-    for line in output.split('\n'):
-        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
-        # Not all lines list objects
-        if match:
-            groups = match.groups()
-            # Match has exactly two subgroups
-            test.assertEqual(len(groups), 2)
-
-            obj_addr = groups[1]
-            # Address must be a hex number
-            test.assertTrue(test.is_hexnum(obj_addr))
-
-            objects.append(obj_addr)
-
-    # There must be at least one object
-    test.assertTrue(len(objects) > 0)
-
-    for obj in objects:
-        ci.HandleCommand("histobj " + obj, res)
-        print(res.GetOutput())
-        print(res.GetError())
-        # Interpreter must have this command and able to run it
-        test.assertTrue(res.Succeeded())
-
-        output = res.GetOutput()
-        # Output is not empty
-        test.assertTrue(len(output) > 0)
-
-        match = re.search('GCCount', output)
-        test.assertTrue(match)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_histobjfind.py b/src/SOS/tests/t_cmd_histobjfind.py
deleted file mode 100644 (file)
index 6116399..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dso", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Get all objects
-    objects = []
-    for line in output.split('\n'):
-        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
-        # Not all lines list objects
-        if match:
-            groups = match.groups()
-            # Match has exactly two subgroups
-            test.assertEqual(len(groups), 2)
-
-            obj_addr = groups[1]
-            # Address must be a hex number
-            test.assertTrue(test.is_hexnum(obj_addr))
-
-            objects.append(obj_addr)
-
-    # There must be at least one object
-    test.assertTrue(len(objects) > 0)
-
-    for obj in objects:
-        ci.HandleCommand("histobjfind " + obj, res)
-        print(res.GetOutput())
-        print(res.GetError())
-        # Interpreter must have this command and able to run it
-        test.assertTrue(res.Succeeded())
-
-        output = res.GetOutput()
-        # Output is not empty
-        test.assertTrue(len(output) > 0)
-
-        match = re.search('GCCount', output)
-        test.assertTrue(match)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_histroot.py b/src/SOS/tests/t_cmd_histroot.py
deleted file mode 100644 (file)
index e212b1f..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dso", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Get all objects
-    objects = []
-    for line in output.split('\n'):
-        match = re.match('([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s', line)
-        # Not all lines list objects
-        if match:
-            groups = match.groups()
-            # Match has exactly two subgroups
-            test.assertEqual(len(groups), 2)
-
-            obj_addr = groups[1]
-            # Address must be a hex number
-            test.assertTrue(test.is_hexnum(obj_addr))
-
-            objects.append(obj_addr)
-
-    # There must be at least one object
-    test.assertTrue(len(objects) > 0)
-
-    for obj in objects:
-        ci.HandleCommand("histroot " + obj, res)
-        print(res.GetOutput())
-        print(res.GetError())
-        # Interpreter must have this command and able to run it
-        test.assertTrue(res.Succeeded())
-
-        output = res.GetOutput()
-        # Output is not empty
-        test.assertTrue(len(output) > 0)
-
-        match = re.search('GCCount', output)
-        test.assertTrue(match)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_ip2md.py b/src/SOS/tests/t_cmd_ip2md.py
deleted file mode 100644 (file)
index 78bebe9..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    match = re.search('JITTED Code Address:\s+([0-9a-fA-F]+)', output)
-    # Line matched
-    test.assertTrue(match)
-
-    groups = match.groups()
-    # Match has a single subgroup
-    test.assertEqual(len(groups), 1)
-
-    jit_addr = groups[0]
-    # Address must be a hex number
-    test.assertTrue(test.is_hexnum(jit_addr))
-
-    ci.HandleCommand("ip2md " + jit_addr, res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("MethodDesc:"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_name2ee.py b/src/SOS/tests/t_cmd_name2ee.py
deleted file mode 100644 (file)
index 71d4212..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("name2ee " + assembly + " Test.Main", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    match = re.search('Module:\s+[0-9a-fA-F]+', output)
-    test.assertTrue(match)
-    match = re.search('Assembly:\s+\S+', output)
-    test.assertTrue(match)
-    match = re.search('Token:\s+[0-9a-fA-F]+', output)
-    test.assertTrue(match)
-    match = re.search('MethodDesc:\s+[0-9a-fA-F]+', output)
-    test.assertTrue(match)
-    match = re.search('Name:\s+\S+', output)
-    test.assertTrue(match)
-
-    process.Continue()
-    # Process must exit
-    test.assertEqual(process.GetState(), lldb.eStateExited)
-
-    # Process must exit with zero code
-    test.assertEqual(process.GetExitStatus(), 0)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_pe.py b/src/SOS/tests/t_cmd_pe.py
deleted file mode 100644 (file)
index 0a87014..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("dso", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_sos.py b/src/SOS/tests/t_cmd_sos.py
deleted file mode 100644 (file)
index b407491..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("sos", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("SOS"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/t_cmd_soshelp.py b/src/SOS/tests/t_cmd_soshelp.py
deleted file mode 100644 (file)
index 8bb51da..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-import lldb
-import re
-import testutils as test
-
-
-def runScenario(assembly, debugger, target):
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-    ci = debugger.GetCommandInterpreter()
-
-    # Run debugger, wait until libcoreclr is loaded,
-    # set breakpoint at Test.Main and stop there
-    test.stop_in_main(debugger, assembly)
-
-    ci.HandleCommand("soshelp", res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    test.assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    test.assertTrue(len(output) > 0)
-
-    # Specific string must be in the output
-    test.assertNotEqual(output.find("soshelp <functionname>"), -1)
-
-    # TODO: test other use cases
-
-    # Continue current process and checks its exit code
-    test.exit_lldb(debugger, assembly)
diff --git a/src/SOS/tests/test_libsosplugin.py b/src/SOS/tests/test_libsosplugin.py
deleted file mode 100755 (executable)
index 1f7b827..0000000
+++ /dev/null
@@ -1,301 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-from __future__ import print_function
-import unittest
-import argparse
-import re
-import tempfile
-import subprocess
-import threading
-import os
-import sys
-import inspect
-
-lldb = ''
-logfiledir = ''
-host = ''
-plugin = ''
-assembly = ''
-fail_flag = ''
-fail_flag_lldb = ''
-summary_file = ''
-timeout = 0
-regex = ''
-repeat = 0
-
-tests_failed = False
-
-def runWithTimeout(cmd):
-    p = None
-
-    def run():
-        global p
-        p = subprocess.Popen(cmd, shell=True)
-        p.communicate()
-
-    thread = threading.Thread(target=run)
-    thread.start()
-
-    thread.join(timeout)
-    if thread.is_alive():
-        with open(summary_file, 'a+') as summary:
-            print('Timeout!', file=summary)
-        p.kill()
-        thread.join()
-
-
-class TestSosCommands(unittest.TestCase):
-
-    def do_test(self, command):
-        open(fail_flag, 'a').close()
-        try:
-            os.unlink(fail_flag_lldb)
-        except:
-            pass
-
-       logfile = os.path.join(logfiledir, command)
-
-        cmd = (('%s -b ' % lldb) +
-               ("-k \"script open('%s', 'a').close()\" " % fail_flag_lldb) +
-               ("-k 'quit' ") +
-               ("--no-lldbinit ") +
-               ("-o \"version\" ") +
-               ("-O \"plugin load %s \" " % plugin) +
-               ("-o \"script import testutils as test\" ") +
-               ("-o \"script test.fail_flag = '%s'\" " % fail_flag) +
-               ("-o \"script test.summary_file = '%s'\" " % summary_file) +
-               ("-o \"script test.run('%s', '%s')\" " % (assembly, command)) +
-               ("-o \"quit\" ") +
-               (" -- %s %s > %s.log 2>&1" % (host, assembly, logfile)))
-
-        runWithTimeout(cmd)
-        if not os.path.isfile(fail_flag):
-            tests_failed = True
-
-        if not os.path.isfile(fail_flag_lldb):
-            tests_failed = True
-        self.assertFalse(os.path.isfile(fail_flag))
-        self.assertFalse(os.path.isfile(fail_flag_lldb))
-
-        try:
-            os.unlink(fail_flag)
-        except:
-            pass
-        try:
-            os.unlink(fail_flag_lldb)
-        except:
-            pass
-
-    def t_cmd_bpmd_nofuturemodule_module_function(self):
-        self.do_test('t_cmd_bpmd_nofuturemodule_module_function')
-
-    def t_cmd_bpmd_module_function(self):
-        self.do_test('t_cmd_bpmd_module_function')
-
-    def t_cmd_bpmd_module_function_iloffset(self):
-        self.do_test('t_cmd_bpmd_module_function_iloffset')
-
-    def t_cmd_bpmd_methoddesc(self):
-        self.do_test('t_cmd_bpmd_methoddesc')
-
-    def t_cmd_bpmd_clear(self):
-        self.do_test('t_cmd_bpmd_clear')
-
-    def t_cmd_bpmd_clearall(self):
-        self.do_test('t_cmd_bpmd_clearall')
-
-    def t_cmd_clrstack(self):
-        self.do_test('t_cmd_clrstack')
-
-    def t_cmd_clrthreads(self):
-        self.do_test('t_cmd_clrthreads')
-
-    def t_cmd_clru(self):
-        self.do_test('t_cmd_clru')
-
-    def t_cmd_dumpclass(self):
-        self.do_test('t_cmd_dumpclass')
-
-    def t_cmd_dumpheap(self):
-        self.do_test('t_cmd_dumpheap')
-
-    def t_cmd_dumpil(self):
-        self.do_test('t_cmd_dumpil')
-
-    def t_cmd_dumplog(self):
-        self.do_test('t_cmd_dumplog')
-
-    def t_cmd_dumpmd(self):
-        self.do_test('t_cmd_dumpmd')
-
-    def t_cmd_dumpmodule(self):
-        self.do_test('t_cmd_dumpmodule')
-
-    def t_cmd_dumpmt(self):
-        self.do_test('t_cmd_dumpmt')
-
-    def t_cmd_dumpobj(self):
-        self.do_test('t_cmd_dumpobj')
-
-    def t_cmd_dumpstack(self):
-        self.do_test('t_cmd_dumpstack')
-
-    def t_cmd_dso(self):
-        self.do_test('t_cmd_dso')
-
-    def t_cmd_eeheap(self):
-        self.do_test('t_cmd_eeheap')
-
-    def t_cmd_eestack(self):
-        self.do_test('t_cmd_eestack')
-
-    def t_cmd_gcroot(self):
-        self.do_test('t_cmd_gcroot')
-
-    def t_cmd_ip2md(self):
-        self.do_test('t_cmd_ip2md')
-
-    def t_cmd_name2ee(self):
-        self.do_test('t_cmd_name2ee')
-
-    def t_cmd_pe(self):
-        self.do_test('t_cmd_pe')
-
-    def t_cmd_histclear(self):
-        self.do_test('t_cmd_histclear')
-
-    def t_cmd_histinit(self):
-        self.do_test('t_cmd_histinit')
-
-    def t_cmd_histobj(self):
-        self.do_test('t_cmd_histobj')
-
-    def t_cmd_histobjfind(self):
-        self.do_test('t_cmd_histobjfind')
-
-    def t_cmd_histroot(self):
-        self.do_test('t_cmd_histroot')
-
-    def t_cmd_sos(self):
-        self.do_test('t_cmd_sos')
-
-    def t_cmd_soshelp(self):
-        self.do_test('t_cmd_soshelp')
-
-
-def generate_report():
-    report = [{'name': 'TOTAL', True: 0, False: 0, 'completed': True}]
-    fail_messages = []
-
-    if not os.path.isfile(summary_file):
-        print('No summary file to process!')
-        return
-
-    with open(summary_file, 'r') as summary:
-        for line in summary:
-            if line.startswith('new_suite: '):
-                report.append({'name': line.split()[-1], True: 0, False: 0,
-                               'completed': False, 'timeout': False})
-            elif line.startswith('True'):
-                report[-1][True] += 1
-            elif line.startswith('False'):
-                report[-1][False] += 1
-            elif line.startswith('Completed!'):
-                report[-1]['completed'] = True
-            elif line.startswith('Timeout!'):
-                report[-1]['timeout'] = True
-            elif line.startswith('!!! '):
-                fail_messages.append(line.rstrip('\n'))
-
-    for suite in report[1:]:
-        report[0][True] += suite[True]
-        report[0][False] += suite[False]
-        report[0]['completed'] &= suite['completed']
-
-    for line in fail_messages:
-        print(line)
-
-    print()
-    print('=' * 79)
-    print('{:72} {:6}'.format('Test suite', 'Result'))
-    print('-' * 79)
-    for suite in report[1:]:
-        if suite['timeout']:
-            result = 'Timeout'
-        elif suite[False]:
-            result = 'Fail'
-        elif not suite['completed']:
-            result = 'Crash'
-        elif suite[True]:
-            result = 'Success'
-        else:
-            result = 'Please, report'
-        print('{:68} {:>10}'.format(suite['name'], result))
-    print('=' * 79)
-
-
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--lldb', default='lldb')
-    parser.add_argument('--host', default='.')
-    parser.add_argument('--plugin', default='.')
-    parser.add_argument('--logfiledir', default='.')
-    parser.add_argument('--assembly', default='Test.exe')
-    parser.add_argument('--timeout', default=90)
-    parser.add_argument('--regex', default='t_cmd_')
-    parser.add_argument('--repeat', default=1)
-    parser.add_argument('unittest_args', nargs='*')
-
-    args = parser.parse_args()
-
-    lldb = args.lldb
-    host = args.host
-    plugin = args.plugin
-    logfiledir = args.logfiledir
-    assembly = args.assembly
-    timeout = int(args.timeout)
-    regex = args.regex
-    repeat = int(args.repeat)
-    print("host: %s" % host)
-    print("lldb: %s" % lldb)
-    print("lldb plugin: %s" % plugin)
-    print("logfiledir: %s" % logfiledir)
-    print("assembly: %s" % assembly)
-    print("timeout: %i" % timeout)
-    print("regex: %s" % regex)
-    print("repeat: %i" % repeat)
-
-    if os.name != 'posix':
-        print('OS not supported')
-        exit(1)
-
-    fail_flag = os.path.join(logfiledir, 'fail_flag')
-    fail_flag_lldb = os.path.join(logfiledir, 'fail_flag.lldb')
-
-    print("fail_flag: %s" % fail_flag)
-    print("fail_flag_lldb: %s" % fail_flag_lldb)
-
-    summary_file = os.path.join(logfiledir, 'summary')
-    print("summary_file: %s" % summary_file)
-
-    try:
-        os.unlink(summary_file)
-    except:
-        pass
-
-    sys.argv[1:] = args.unittest_args
-    suite = unittest.TestSuite()
-    all_tests = inspect.getmembers(TestSosCommands, predicate=inspect.ismethod)
-    for (test_name, test_func) in all_tests:
-        if re.match(regex, test_name):
-            suite.addTest(TestSosCommands(test_name))
-    unittest.TextTestRunner(verbosity=1).run(suite)
-
-    generate_report()
-
-    if tests_failed:
-        exit(1)
-
diff --git a/src/SOS/tests/testsos.sh b/src/SOS/tests/testsos.sh
deleted file mode 100755 (executable)
index e697ee3..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-__ProjectRoot="$1"
-__Plugin="$2"
-__ManagedBinDir="$3"
-__LogFileDir="$4"
-
-if [[ "$__ProjectRoot" = "" || "$__Plugin" = "" || "$__ManagedBinDir" = "" || "$__LogFileDir" = "" ]]; then
-    echo "Project root, plugin or log directory required"
-    exit 1
-fi
-
-__Host=$__ProjectRoot/.dotnet/dotnet
-__TestProgram=$__ManagedBinDir/TestDebuggee/netcoreapp2.0/TestDebuggee.dll
-
-# Turn on stress logging so the dumplog and histinit commands pass
-export COMPlus_LogFacility=0xffffffbf
-export COMPlus_LogLevel=6
-export COMPlus_StressLog=1
-export COMPlus_StressLogSize=65536
-
-if [[ ! -x "$LLDB_PATH" ]]; then
-    echo "LLDB_PATH doesn't exist or not executable"
-    exit 1
-fi
-
-cd $__ProjectRoot/src/SOS/tests/
-rm -f StressLog.txt
-python $__ProjectRoot/src/SOS/tests/test_libsosplugin.py --lldb $LLDB_PATH --host $__Host --plugin $__Plugin --logfiledir $__LogFileDir --assembly $__TestProgram
-if [ $? != 0 ]; then
-    cat $__LogFileDir/*.log
-    echo "LLDB python tests FAILED"
-    exit 1
-fi
-echo "LLDB python tests PASSED"
diff --git a/src/SOS/tests/testutils.py b/src/SOS/tests/testutils.py
deleted file mode 100644 (file)
index 7d7df27..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-# Licensed to the .NET Foundation under one or more agreements.
-# The .NET Foundation licenses this file to you under the MIT license.
-# See the LICENSE file in the project root for more information.
-
-from __future__ import print_function
-import lldb
-import re
-import inspect
-import sys
-import os
-import importlib
-
-summary_file = ''
-fail_flag = ''
-
-failed = False
-
-def assertCommon(passed, fatal):
-    global failed
-    with open(summary_file, 'a+') as summary:
-        print(bool(passed), file=summary)
-        if (not passed):
-            failed = True
-            print('!!! test failed:', file=summary)
-            for s in inspect.stack()[2:]:
-                print("!!!  %s:%i" % (s[1], s[2]), file=summary)
-                print("!!! %s" % s[4][0], file=summary)
-                if re.match('\W*t_\w+\.py$', s[1]):
-                    break
-            print('!!! ', file=summary)
-
-            if fatal:
-                exit(1)
-
-def assertTrue(x, fatal=True):
-    passed = bool(x)
-    assertCommon(passed, fatal)
-
-
-def assertFalse(x, fatal=True):
-    passed = not bool(x)
-    assertCommon(passed, fatal)
-
-
-def assertEqual(x, y, fatal=True):
-    passed = (x == y)
-    if not passed:
-        print(str(x), ' != ', str(y))
-    assertCommon(passed, fatal)
-
-
-def assertNotEqual(x, y, fatal=True):
-    passed = (x != y)
-    if not passed:
-        print(str(x), ' == ', str(y))
-    assertCommon(passed, fatal)
-
-
-def checkResult(res):
-    global failed
-    if not res.Succeeded():
-        failed = True
-        print(res.GetOutput())
-        print(res.GetError())
-        exit(1)
-
-
-def is_hexnum(s):
-    try:
-        int(s, 16)
-        return True
-    except ValueError:
-        return False
-
-
-def exec_and_find(commandInterpreter, cmd, regexp):
-    res = lldb.SBCommandReturnObject()
-    commandInterpreter.HandleCommand(cmd, res)
-    checkResult(res)
-
-    expr = re.compile(regexp)
-    addr = None
-
-    print(res.GetOutput())
-    lines = res.GetOutput().splitlines()
-    for line in lines:
-        match = expr.match(line)
-        if match:
-            addr = match.group(1)
-            break
-
-    print("Found addr: " + str(addr))
-    return addr
-
-
-def stop_in_main(debugger, assembly):
-    ci = debugger.GetCommandInterpreter()
-    target = debugger.GetSelectedTarget()
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-
-    # Process must be stopped here while libcoreclr loading.
-    # This test usually fails on release version of coreclr
-    # since we depend on 'LoadLibraryExW' symbol present.
-    assertEqual(process.GetState(), lldb.eStateStopped)
-
-    # The reason of this stop must be a breakpoint
-    assertEqual(process.GetSelectedThread().GetStopReason(),
-                lldb.eStopReasonBreakpoint)
-
-    ci.HandleCommand("bpmd " + assembly + " Test.Main", res)
-    out_msg = res.GetOutput()
-    err_msg = res.GetError()
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    assertTrue(res.Succeeded())
-
-    # Output is not empty
-    # Should be at least 'Adding pending breakpoints...'
-    assertTrue(res.GetOutputSize() > 0)
-
-    # Error message is empty
-    assertTrue(res.GetErrorSize() == 0)
-
-    process.Continue()
-    # Process must be stopped here if bpmd works at all
-    assertEqual(process.GetState(), lldb.eStateStopped)
-
-    # The reason of this stop must be a breakpoint
-    assertEqual(process.GetSelectedThread().GetStopReason(),
-                lldb.eStopReasonBreakpoint)
-
-
-def exit_lldb(debugger, assembly):
-    ci = debugger.GetCommandInterpreter()
-    target = debugger.GetSelectedTarget()
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-
-    ci.HandleCommand("breakpoint delete --force", res)
-    out_msg = res.GetOutput()
-    err_msg = res.GetError()
-    print(out_msg)
-    print(err_msg)
-    # Interpreter must have this command and able to run it
-    # assertTrue(res.Succeeded())
-
-    process.Continue()
-    # Process must exit
-    assertEqual(process.GetState(), lldb.eStateExited)
-
-    # Process must exit with zero code
-    assertEqual(process.GetExitStatus(), 0)
-
-
-def get_methoddesc(debugger, assembly, funcname):
-    ci = debugger.GetCommandInterpreter()
-    target = debugger.GetSelectedTarget()
-    process = target.GetProcess()
-    res = lldb.SBCommandReturnObject()
-
-    ci.HandleCommand("name2ee %s %s" % (assembly, funcname), res)
-    print(res.GetOutput())
-    print(res.GetError())
-    # Interpreter must have this command and able to run it
-    assertTrue(res.Succeeded())
-
-    output = res.GetOutput()
-    # Output is not empty
-    assertTrue(len(output) > 0)
-
-    match = re.search('MethodDesc:\s+([0-9a-fA-F]+)', output)
-    # Line matched
-    assertTrue(match)
-
-    groups = match.groups()
-    # Match has a single subgroup
-    assertEqual(len(groups), 1)
-
-    md_addr = groups[0]
-    # Address must be a hex number
-    assertTrue(is_hexnum(md_addr))
-
-    return md_addr
-
-
-def run(assembly, module):
-    with open(summary_file, 'a+') as summary:
-        print('new_suite: %s' % module, file=summary)
-
-    debugger = lldb.debugger
-
-    debugger.SetAsync(False)
-    target = lldb.target
-
-    debugger.HandleCommand("breakpoint set --name coreclr_execute_assembly")
-    debugger.HandleCommand("process launch")
-
-    # run the scenario
-    print("starting scenario...")
-    i = importlib.import_module(module)
-    scenarioResult = i.runScenario(os.path.basename(assembly), debugger, target)
-
-    if (target.GetProcess().GetExitStatus() == 0) and not failed:
-        os.unlink(fail_flag)
-
-    with open(summary_file, 'a+') as summary:
-        print('Completed!', file=summary)
diff --git a/src/inc/bitvector.h b/src/inc/bitvector.h
new file mode 100644 (file)
index 0000000..a4181db
--- /dev/null
@@ -0,0 +1,462 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+/***************************************************************************/
+/*                           BitVector.h                                   */
+/***************************************************************************/
+//  Routines to support a growable bitvector
+/***************************************************************************/
+
+#ifndef BITVECTOR_H
+#define BITVECTOR_H 1
+
+
+#ifndef LIMITED_METHOD_CONTRACT
+#define LIMITED_METHOD_CONTRACT
+#define UNDEF_LIMITED_METHOD_CONTRACT
+#endif
+
+#ifndef WRAPPER_NO_CONTRACT
+#define WRAPPER_NO_CONTRACT
+#define UNDEF_WRAPPER_NO_CONTRACT
+#endif
+
+#ifndef SUPPORTS_DAC
+#define SUPPORTS_DAC
+#define UNDEF_SUPPORTS_DAC
+#endif
+
+#ifndef _ASSERTE
+#define _ASSERTE(x)
+#define UNDEF_ASSERTE
+#endif
+
+#define USE_BITVECTOR 1
+#if USE_BITVECTOR
+
+/* The bitvector class is meant to be a drop in replacement for an integer
+   (that is you use it like an integer), however it grows as needed.
+
+   Features:
+       plug compatible with normal integers;
+       grows as needed
+       Optimized for the small case when the vector fits in machine word
+       Uses one machine word if vector fits in machine word (minus a bit)
+
+       Some caveates:
+           You should use mutator operators  &=, |= ... instead of the 
+           non-mutators whenever possible to avoid creating a temps
+
+           Specifically did NOT supply automatic coersions to
+           and from short types so that the programmer is aware of
+           when code was being injected on his behalf.  The upshot of this
+           is that you have to use the  BitVector() toUnsigned() to convert 
+*/
+
+/***************************************************************************/
+
+class BitVector {
+    // Set this to be unsigned char to do testing, should be UINT_PTR for real life
+
+    typedef UINT_PTR ChunkType;  // The size of integer type that the machine can operate on directly  
+//  typedef BYTE ChunkType;      // Use for testing
+
+    // Maximum number of bits in our bitvector
+#define MAX_PTRARG_OFS 1024
+
+    enum {
+        IS_BIG     = 1,                             // The low bit is used to discrimate m_val and m_vals
+        CHUNK_BITS = sizeof(ChunkType)*8,           // The number of bits that we can manipuate as a chunk
+        SMALL_BITS = CHUNK_BITS - 1,                // The number of bits we can fit in the small representation
+//      SMALL_BITS = 5,                             // TESTING ONLY: The number of bits we can fit in the small representation
+        VALS_COUNT = MAX_PTRARG_OFS / CHUNK_BITS,   // The number of ChunkType elements in the Vals array
+    };
+
+public:
+    BitVector()
+    { 
+        LIMITED_METHOD_CONTRACT;
+        SUPPORTS_DAC;
+
+        m_val = 0;
+    }
+
+    BOOL isBig() const
+    {
+        LIMITED_METHOD_CONTRACT;
+        SUPPORTS_DAC;
+
+        return ((m_val & IS_BIG) != 0);
+    }
+
+    void toBig()
+    {
+        LIMITED_METHOD_CONTRACT;
+        SUPPORTS_DAC;
+
+        if (!isBig())
+        {
+            doBigInit(smallBits());
+        }
+    }
+
+    explicit BitVector(ChunkType arg)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if (arg > MaxVal)
+        {
+            doBigInit(arg);
+        }
+        else 
+        {
+            m_val = ChunkType(arg << 1);
+        }
+    }
+
+    BitVector(ChunkType arg, UINT shift)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if ((arg > MaxVal) || (shift >= SMALL_BITS) || (arg > (MaxVal >> shift)))
+        {
+            doBigInit(arg);
+            doBigLeftShiftAssign(shift);
+        }
+        else
+        {
+            m_val = ChunkType(arg << (shift+1));
+        }
+    }
+
+#define CONSTRUCT_ptrArgTP(arg,shift)   BitVector((arg), (shift))
+
+    BitVector(const BitVector& arg)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if (arg.isBig())
+        {
+            doBigInit(arg);
+        }
+        else
+        {
+            m_val = arg.m_val;
+        }
+    }
+    
+    void operator <<=(unsigned shift)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if ((m_val == 0) || (shift == 0))     // Zero is a special case, don't need to do anything
+            return;
+
+        if (isBig() || (shift >= SMALL_BITS) || (m_val > (MaxVal >> (shift-1))))
+        {
+            doBigLeftShiftAssign(shift);
+        }
+        else 
+        {
+            m_val <<= shift;
+        }
+    }
+
+    void operator >>=(unsigned shift)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if (isBig())
+        {
+            doBigRightShiftAssign(shift);
+        }
+        else
+        {
+            m_val >>= shift;
+            m_val &= ~IS_BIG;  // clear the isBig bit if it got set
+        }
+    }
+    
+    void operator |=(const BitVector& arg)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if (((m_val | arg.m_val) & IS_BIG) != 0)
+        {
+            doBigOrAssign(arg);
+        }
+        else
+        {
+            m_val |= arg.m_val;
+        }
+    }
+
+    // Note that that is set difference, not subtration
+    void operator -=(const BitVector& arg)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if (((m_val | arg.m_val) & IS_BIG) != 0)
+        {
+            doBigDiffAssign(arg);
+        }
+        else
+        {
+            m_val &= ~arg.m_val;
+        }
+    }
+
+    void operator &=(const BitVector& arg)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if (((m_val | arg.m_val) & IS_BIG) != 0)
+        {
+            doBigAndAssign(arg);
+        }
+        else
+        {
+            m_val &= arg.m_val;
+        }
+    }
+
+    friend void setDiff(BitVector& target, const BitVector& arg)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        target -= arg;
+    }
+
+    friend BOOL intersect(const BitVector& arg1, const BitVector& arg2)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if (((arg1.m_val | arg2.m_val) & IS_BIG) != 0)
+        {
+            return arg1.doBigIntersect(arg2);
+        }
+        else
+        {
+            return ((arg1.m_val & arg2.m_val) != 0);
+        }
+    }
+    
+    BOOL operator ==(const BitVector& arg) const
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if ((m_val | arg.m_val) & IS_BIG)
+        {
+            return doBigEquals(arg);
+        }
+        else
+        {
+            return m_val == arg.m_val;
+        }
+    }
+
+    BOOL operator !=(const BitVector& arg) const
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        return !(*this == arg);
+    }
+
+    friend ChunkType toUnsigned(const BitVector& arg)
+    { 
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        if (arg.isBig())
+        {
+            return arg.m_vals.m_chunks[0];   // Note truncation
+        }
+        else 
+        {
+            return arg.smallBits();
+        }
+    }
+
+    // Note that we require the invariant that zero is always stored in the
+    // small form so that this works bitvector is zero iff (m_val == 0)
+    friend BOOL isZero(const BitVector& arg)
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        return arg.m_val == 0;
+    }
+
+    /* currently only used in asserts */
+    BitVector operator &(const BitVector& arg) const
+    {
+        WRAPPER_NO_CONTRACT;
+        SUPPORTS_DAC;
+
+        BitVector ret = *this;
+        ret &= arg;
+        return ret;
+    }
+
+    int  NumBits() const;
+
+private:
+
+    static const ChunkType MaxVal = ((ChunkType)1 << SMALL_BITS) - 1;    // Maximum value that can be stored in m_val
+
+    // This is the structure that we use when the bit vector overflows.  
+    // It is a simple vector.  
+    struct Vals {
+        unsigned m_encodedLength;         // An encoding of the current length of the 'm_chunks' array
+        ChunkType m_chunks[VALS_COUNT]; 
+
+        BOOL isBig() const
+        {
+            LIMITED_METHOD_CONTRACT;
+            SUPPORTS_DAC;
+
+            return ((m_encodedLength & IS_BIG) != 0);
+        }
+
+        unsigned GetLength() const
+        {
+            LIMITED_METHOD_CONTRACT;
+            SUPPORTS_DAC;
+            
+            if (isBig())
+            {
+                unsigned length = (m_encodedLength >> 1);
+                _ASSERTE(length > 0);
+                return length;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+
+        void SetLength(unsigned length)
+        {
+            LIMITED_METHOD_CONTRACT;
+            SUPPORTS_DAC;
+
+            _ASSERTE(length > 0);
+            _ASSERTE(length <= VALS_COUNT);
+
+            m_encodedLength  = (ChunkType) (length << 1);
+            m_encodedLength |= (ChunkType) IS_BIG;
+         }
+    };
+
+    //
+    // This is the instance data for the bitvector
+    //
+    // We discrimininate on this
+    union {
+        ChunkType m_val;     // if m_val bit 0 is false, then bits 1-N are the bit vector
+        Vals      m_vals;    // if m_val bit 1 is true, then use Vals
+    };
+
+
+    ChunkType smallBits() const
+    {
+        LIMITED_METHOD_CONTRACT;
+        SUPPORTS_DAC;
+
+        _ASSERTE(!isBig());
+        return (m_val >> 1);
+    }
+
+#ifdef STRIKE
+    void doBigInit(ChunkType arg) {}
+#else
+    void doBigInit(ChunkType arg);
+#endif
+    void doBigInit(const BitVector& arg);
+    void doBigLeftShiftAssign(unsigned arg);
+    void doBigRightShiftAssign(unsigned arg);
+    void doBigDiffAssign(const BitVector&);
+    void doBigAndAssign(const BitVector&);
+    void doBigOrAssign(const BitVector& arg);
+    BOOL doBigEquals(const BitVector&) const;
+    BOOL doBigIntersect(const BitVector&) const;
+};
+
+typedef BitVector ptrArgTP;
+
+#else // !USE_BITVECTOR
+
+typedef unsigned __int64 ptrArgTP;
+
+    // Maximum number of bits in our bitvector
+#define MAX_PTRARG_OFS (sizeof(ptrArgTP) * 8)
+
+#define CONSTRUCT_ptrArgTP(arg,shift)   (((ptrArgTP) (arg)) << (shift))
+
+inline BOOL isZero(const ptrArgTP& arg)
+{
+    LIMITED_METHOD_CONTRACT;
+    SUPPORTS_DAC;
+    return (arg == 0);
+}
+
+inline ptrArgTP toUnsigned(const ptrArgTP& arg)
+{
+    LIMITED_METHOD_CONTRACT;
+    SUPPORTS_DAC;
+    return arg;
+}
+
+inline void setDiff(ptrArgTP& target, const ptrArgTP& arg)
+{
+    LIMITED_METHOD_CONTRACT;
+    SUPPORTS_DAC;
+
+    target &= ~arg;
+}
+
+inline BOOL intersect(const ptrArgTP arg1, const ptrArgTP arg2)
+{
+    LIMITED_METHOD_CONTRACT;
+    SUPPORTS_DAC;
+
+    return ((arg1 & arg2) != 0);
+}
+
+#endif  // !USE_BITVECTOR
+
+#ifdef UNDEF_LIMITED_METHOD_CONTRACT
+#undef LIMITED_METHOD_CONTRACT
+#undef UNDEF_LIMITED_METHOD_CONTRACT
+#endif
+
+#ifdef UNDEF_WRAPPER_NO_CONTRACT
+#undef WRAPPER_NO_CONTRACT
+#undef UNDEF_WRAPPER_NO_CONTRACT
+#endif
+
+#ifdef UNDEF_SUPPORTS_DAC
+#undef SUPPORTS_DAC
+#undef UNDEF_SUPPORTS_DAC
+#endif
+
+#ifdef UNDEF_ASSERTE
+#undef _ASSERTE
+#undef UNDEF_ASSERTE
+#endif
+
+#endif // BITVECTOR_H