Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not crash on Debug.Assert #2335

Merged
merged 35 commits into from
Feb 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
afd1066
Fixing acceptance tests
nohwnd Feb 18, 2020
feb9822
wip
nohwnd Feb 18, 2020
fde83d2
check packages path
nohwnd Feb 18, 2020
035524f
Use the same path in vs and console
nohwnd Feb 18, 2020
1c3bf90
Cause stack overflow faster
nohwnd Feb 18, 2020
3059f68
add tests
nohwnd Feb 19, 2020
0f8ea66
Add trace listener to prevent the process from crash
nohwnd Feb 19, 2020
9a3cc8d
add test to make sure the exception type is public
nohwnd Feb 20, 2020
870e5f1
Remove hardcoded dependency version
nohwnd Feb 20, 2020
07b87c1
Fix process counting
nohwnd Feb 20, 2020
7707776
Fix process acceptance tests
nohwnd Feb 20, 2020
0451b4b
use NETCOREAPP instead of NET451
nohwnd Feb 20, 2020
9eb5cca
Fixing build
nohwnd Feb 20, 2020
ccbd375
Fix params
nohwnd Feb 21, 2020
4461f19
Specify dotnet_root
nohwnd Feb 21, 2020
973a715
Install x86 runtime
nohwnd Feb 21, 2020
b5b998d
fff
nohwnd Feb 21, 2020
b0757be
bb
nohwnd Feb 21, 2020
3a63812
Set the env for tests as well
nohwnd Feb 21, 2020
bb8e3a9
fix namespace
nohwnd Feb 21, 2020
7e936ff
Fix var
nohwnd Feb 21, 2020
62eabc4
revert build
nohwnd Feb 21, 2020
b175d21
Fix tests in parallel
nohwnd Feb 21, 2020
c8246b2
Fix debug test tests
nohwnd Feb 21, 2020
3486d9b
plat tests
nohwnd Feb 21, 2020
bb585e1
Make Debug assertion internal
nohwnd Feb 24, 2020
288da7d
Fix feedback
nohwnd Feb 24, 2020
d56c4ee
only override fail
nohwnd Feb 24, 2020
a8b90d4
Add few unit tests
nohwnd Feb 24, 2020
52cb5fe
Add unit tests for trace listener
nohwnd Feb 24, 2020
0a849ae
Merge branch 'master' into debug-assert
nohwnd Feb 24, 2020
6f88c2b
Clean up
nohwnd Feb 24, 2020
c9c3160
Define DEBUG in Release
nohwnd Feb 24, 2020
4795f91
fix assert tests
nohwnd Feb 24, 2020
872717a
Fix debugassert
nohwnd Feb 24, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/build/TestPlatform.Dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@
<Import Project="$(DotNetPackageVersionPropsPath)" Condition="'$(DotNetPackageVersionPropsPath)' != ''" />

</Project>

17 changes: 17 additions & 0 deletions src/testhost.x86/DebugAssertException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.VisualStudio.TestPlatform.TestHost
{
using System;

internal sealed class DebugAssertException : Exception
{
public DebugAssertException(string message, string stackTrace) : base(message)
{
StackTrace = stackTrace;
}

public override string StackTrace { get; }
}
}
46 changes: 32 additions & 14 deletions src/testhost.x86/DefaultEngineInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ public void Invoke(IDictionary<string, string> argsDictionary)
#endif
}

#if NETCOREAPP
TestHostTraceListener.Setup();
#endif

this.SetParentProcessExitCallback(argsDictionary);

this.requestHandler.ConnectionInfo =
Expand All @@ -92,7 +96,8 @@ public void Invoke(IDictionary<string, string> argsDictionary)
// Initialize Communication with vstest.console
this.requestHandler.InitializeCommunication();

// Initialize DataCollection Communication if data collection port is provided.
// skipping because 0 is the default value, and also the value the the callers use when they
// call with the parameter specified, but without providing an actual port
var dcPort = CommandLineArgumentsHelper.GetIntArgFromDict(argsDictionary, DataCollectionPortArgument);
if (dcPort > 0)
{
Expand Down Expand Up @@ -176,22 +181,35 @@ private void ConnectToDatacollector(int dcPort)
private void SetParentProcessExitCallback(IDictionary<string, string> argsDictionary)
{
// Attach to exit of parent process
var parentProcessId = CommandLineArgumentsHelper.GetIntArgFromDict(argsDictionary, ParentProcessIdArgument);
EqtTrace.Info("DefaultEngineInvoker.SetParentProcessExitCallback: Monitoring parent process with id: '{0}'",
parentProcessId);
var hasParentProcessArgument = CommandLineArgumentsHelper.TryGetIntArgFromDict(argsDictionary, ParentProcessIdArgument, out var parentProcessId);

// In remote scenario we cannot monitor parent process, so we expect user to pass parentProcessId as -1
if (parentProcessId != -1)
if (!hasParentProcessArgument)
{
this.processHelper.SetExitCallback(
parentProcessId,
(obj) =>
{
EqtTrace.Info("DefaultEngineInvoker.SetParentProcessExitCallback: ParentProcess '{0}' Exited.",
parentProcessId);
new PlatformEnvironment().Exit(1);
});
throw new ArgumentException($"Argument {ParentProcessIdArgument} was not specified.");
}

EqtTrace.Info("DefaultEngineInvoker.SetParentProcessExitCallback: Monitoring parent process with id: '{0}'", parentProcessId);

if (parentProcessId == -1)
{
// In remote scenario we cannot monitor parent process, so we expect user to pass parentProcessId as -1
return;
}

if (parentProcessId == 0)
{
//TODO: should there be a warning / error in this case, on windows and linux we are most likely not started by this PID 0, because it's Idle process on Windows, and Swapper on Linux, and similarly in docker
// Trying to attach to 0 will cause access denied error on Windows
}

this.processHelper.SetExitCallback(
parentProcessId,
(obj) =>
{
EqtTrace.Info("DefaultEngineInvoker.SetParentProcessExitCallback: ParentProcess '{0}' Exited.",
parentProcessId);
new PlatformEnvironment().Exit(1);
});
}

private static TestHostConnectionInfo GetConnectionInfo(IDictionary<string, string> argsDictionary)
Expand Down
104 changes: 104 additions & 0 deletions src/testhost.x86/TestHostTraceListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


namespace Microsoft.VisualStudio.TestPlatform.TestHost
{
#if NETCOREAPP
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

internal class TestHostTraceListener : DefaultTraceListener
{
public static void Setup()
{
EqtTrace.Info("Setting up debug trace listener.");
// in the majority of cases there will be only a single DefaultTraceListener in this collection
// and we will replace that with our listener, in case there are listeners of different types we keep
// them as is
for (var i = 0; i < Trace.Listeners.Count; i++)
{
var listener = Trace.Listeners[i];
if (listener is DefaultTraceListener)
{
EqtTrace.Verbose($"TestPlatformTraceListener.Setup: Replacing listener {0} with { nameof(TestHostTraceListener) }.", Trace.Listeners[i]);
Trace.Listeners[i] = new TestHostTraceListener();
}
}

EqtTrace.Verbose("TestPlatformTraceListener.Setup: Added test platform trace listener.");

#if NETCOREAPP2_1
try
{
// workaround for netcoreapp2.1 where the trace listener api is not called when
// Debug.Assert fails. This method is internal, but the class is on purpose keeping the
// callback settable so tests can set the callback
var field = typeof(Debug).GetField("s_ShowDialog", BindingFlags.Static | BindingFlags.NonPublic);
var value = field.GetValue(null);
field.SetValue(null, (Action<string, string, string, string>)ShowDialog);
nohwnd marked this conversation as resolved.
Show resolved Hide resolved
}
catch (Exception ex)
{
EqtTrace.Error("TestPlatformTraceListener.Setup: Failed to replace inner callback to ShowDialog in Debug.Assert. Calls to Debug.Assert with crash the test host process. {0}", ex);
}
#endif
}

public override void Fail(string message)
{
throw GetException(message);
nohwnd marked this conversation as resolved.
Show resolved Hide resolved
}

public override void Fail(string message, string detailMessage)
{
throw GetException((message + Environment.NewLine + detailMessage));
}

public static void ShowDialog(string stackTrace, string message, string detailMessage, string _)
{
var text = !string.IsNullOrEmpty(message)
? !string.IsNullOrEmpty(detailMessage)
? (message + Environment.NewLine + detailMessage)
: message
: null;
throw GetException(text);
}

private static DebugAssertException GetException(string message)
{
var debugTypes = new Type[] { typeof(Debug), typeof(Trace) };
var stack = new StackTrace(true);

var debugMethodFound = false;
var frameCount = 0;
MethodBase method = null;
foreach (var f in stack.GetFrames())
{
var m = f.GetMethod();
var declaringType = m?.DeclaringType;
if (!debugMethodFound && (declaringType == typeof(Debug) || declaringType == typeof(Trace)))
{
method = m;
debugMethodFound = true;
}

if (debugMethodFound)
{
frameCount++;
}
}

var stackTrace = string.Join(Environment.NewLine, stack.ToString().Split(Environment.NewLine).TakeLast(frameCount));
var methodName = method != null ? $"{method.DeclaringType.Name}.{method.Name}" : "<method>";
var wholeMessage = $"Method {methodName} failed with '{message}', and was translated to { typeof(DebugAssertException).FullName } to avoid terminating the process hosting the test.";

return new DebugAssertException(wholeMessage, stackTrace);
nohwnd marked this conversation as resolved.
Show resolved Hide resolved
}
}

#endif
}
2 changes: 2 additions & 0 deletions src/testhost/testhost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\testhost.x86\DebugAssertException.cs" Link="DebugAssertException.cs" />
<Compile Include="..\testhost.x86\Program.cs;..\testhost.x86\DefaultEngineInvoker.cs;..\testhost.x86\IEngineInvoker.cs;..\testhost.x86\AppDomainEngineInvoker.cs;..\testhost.x86\Friends.cs;..\testhost.x86\UnitTestClient.cs" />
<Compile Include="..\testhost.x86\TestHostTraceListener.cs" Link="TestHostTraceListener.cs" />
<None Include="app.config" />
</ItemGroup>
<ItemGroup>
Expand Down
31 changes: 31 additions & 0 deletions test/Microsoft.TestPlatform.AcceptanceTests/DebugAssertTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.TestPlatform.AcceptanceTests
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

[TestClass]
public class DebugAssertTests : AcceptanceTestBase
{
[TestMethod]
// this is core only, there is nothing we can do about Debug.Assert crashing the process on framework
[NetCoreTargetFrameworkDataSource(useDesktopRunner: false)]
public void RunningTestWithAFailingDebugAssertDoesNotCrashTheHostingProcess(RunnerInfo runnerInfo)
{
// when debugging this test in case it starts failing, be aware that the default behavior of Debug.Assert
// is to not crash the process when we are running in debug, and debugger is attached
AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo);

var assemblyPath = this.BuildMultipleAssemblyPath("CrashingOnDebugAssertTestProject.dll").Trim('\"');
var arguments = PrepareArguments(assemblyPath, null, null, this.FrameworkArgValue, runnerInfo.InIsolationValue);
this.InvokeVsTest(arguments);

// this will have failed tests when our trace listener works and crash the testhost process when it does not
// because crashing processes is what a failed Debug.Assert does by default, unless you have a debugger attached
this.ValidateSummaryStatus(passedTestsCount: 4, failedTestsCount: 4, 0);
StringAssert.Contains(this.StdOut, "threw exception: Microsoft.VisualStudio.TestPlatform.TestHost.DebugAssertException: Method Debug.Assert failed");
}
}
}
Binary file not shown.
62 changes: 62 additions & 0 deletions test/TestAssets/CrashingOnDebugAssertTestProject/DebugTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Diagnostics;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CrashingOnDebugAssertTestProject
{
// Release profile in this project needs to be the same as
// Debug profile, to define TRACE and DEBUG constants and don't
// optimize the code, otherwise we will not run Debug.Assert in
// our acceptance tests on build server, because that is built as Release
[TestClass]
public class DebugTests
{
[TestMethod]
public void DebugAssertFailsTheTest()
{
Debug.Assert(false);
}

[TestMethod]
public void DebugFailFailsTheTest()
{
Debug.Fail("fail");
}

[TestMethod]
public void TraceAssertFailsTheTest()
{
Trace.Assert(false);
}

[TestMethod]
public void TraceFailFailsThetest()
{
Trace.Fail("fail");
}

[TestMethod]
public void TraceWriteDoesNotFailTheTest()
{
Trace.Write("hello");
}

[TestMethod]
public void TraceWriteLineDoesNotFailTheTest()
{
Trace.WriteLine("hello");
}

[TestMethod]
public void DebugWriteDoesNotFailTheTest()
{
Debug.Write("hello");
}

[TestMethod]
public void DebugWriteLineDoesNotFailTheTest()
{
Debug.WriteLine("hello");
}
}
}
14 changes: 14 additions & 0 deletions test/TestAssets/TestAssets.sln/TestAssets.sln
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvironmentVariablesTestPro
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectFileRunSettingsTestProject", "..\ProjectFileRunSettingsTestProject\ProjectFileRunSettingsTestProject.csproj", "{A0113409-56D3-4060-BD94-A4BA4739712D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CrashingOnDebugAssertTestProject", "..\CrashingOnDebugAssertTestProject\CrashingOnDebugAssertTestProject.csproj", "{7A04F7AC-09E4-426C-A599-110DFA693200}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DCD7C4DA-B8CC-46D0-AA21-1340DD1EB5ED}"
ProjectSection(SolutionItems) = preProject
..\NuGet.config = ..\NuGet.config
Expand Down Expand Up @@ -364,6 +366,18 @@ Global
{A0113409-56D3-4060-BD94-A4BA4739712D}.Release|x64.Build.0 = Release|Any CPU
{A0113409-56D3-4060-BD94-A4BA4739712D}.Release|x86.ActiveCfg = Release|Any CPU
{A0113409-56D3-4060-BD94-A4BA4739712D}.Release|x86.Build.0 = Release|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Debug|x64.ActiveCfg = Debug|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Debug|x64.Build.0 = Debug|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Debug|x86.ActiveCfg = Debug|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Debug|x86.Build.0 = Debug|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Release|Any CPU.Build.0 = Release|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Release|x64.ActiveCfg = Release|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Release|x64.Build.0 = Release|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Release|x86.ActiveCfg = Release|Any CPU
{7A04F7AC-09E4-426C-A599-110DFA693200}.Release|x86.Build.0 = Release|Any CPU
{1FF723F6-3A09-41F6-B85C-C4BE9C4374F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FF723F6-3A09-41F6-B85C-C4BE9C4374F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FF723F6-3A09-41F6-B85C-C4BE9C4374F0}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand Down
4 changes: 0 additions & 4 deletions test/coverlet.collector/coverlet.collector.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\scripts\key.snk" Link="key.snk" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.TestPlatform.ObjectModel\Microsoft.TestPlatform.ObjectModel.csproj" />
</ItemGroup>
Expand Down
Loading