diff --git a/src/Logging/Logging.Testing/src/CollectDumpAttribute.cs b/src/Logging/Logging.Testing/src/CollectDumpAttribute.cs
new file mode 100644
index 0000000000..8a6aa84bac
--- /dev/null
+++ b/src/Logging/Logging.Testing/src/CollectDumpAttribute.cs
@@ -0,0 +1,18 @@
+// Copyright(c) .NET Foundation.All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.Extensions.Logging.Testing
+{
+ ///
+ /// Capture the memory dump upon test failure.
+ ///
+ ///
+ /// This currently only works in Windows environments
+ ///
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+ public class CollectDumpAttribute : Attribute
+ {
+ }
+}
diff --git a/src/Logging/Logging.Testing/src/DumpCollector/DumpCollector.Windows.cs b/src/Logging/Logging.Testing/src/DumpCollector/DumpCollector.Windows.cs
new file mode 100644
index 0000000000..8d4168c20c
--- /dev/null
+++ b/src/Logging/Logging.Testing/src/DumpCollector/DumpCollector.Windows.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.Extensions.Logging.Testing
+{
+ public static partial class DumpCollector
+ {
+ private static class Windows
+ {
+ internal static void Collect(Process process, string outputFile)
+ {
+ // Open the file for writing
+ using (var stream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
+ {
+ // Dump the process!
+ var exceptionInfo = new NativeMethods.MINIDUMP_EXCEPTION_INFORMATION();
+ if (!NativeMethods.MiniDumpWriteDump(process.Handle, (uint)process.Id, stream.SafeFileHandle, NativeMethods.MINIDUMP_TYPE.MiniDumpWithFullMemory, ref exceptionInfo, IntPtr.Zero, IntPtr.Zero))
+ {
+ var err = Marshal.GetHRForLastWin32Error();
+ Marshal.ThrowExceptionForHR(err);
+ }
+ }
+ }
+
+ private static class NativeMethods
+ {
+ [DllImport("Dbghelp.dll")]
+ public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId, SafeFileHandle hFile, MINIDUMP_TYPE DumpType, ref MINIDUMP_EXCEPTION_INFORMATION ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam);
+
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
+ public struct MINIDUMP_EXCEPTION_INFORMATION
+ {
+ public uint ThreadId;
+ public IntPtr ExceptionPointers;
+ public int ClientPointers;
+ }
+
+ [Flags]
+ public enum MINIDUMP_TYPE : uint
+ {
+ MiniDumpNormal = 0,
+ MiniDumpWithDataSegs = 1 << 0,
+ MiniDumpWithFullMemory = 1 << 1,
+ MiniDumpWithHandleData = 1 << 2,
+ MiniDumpFilterMemory = 1 << 3,
+ MiniDumpScanMemory = 1 << 4,
+ MiniDumpWithUnloadedModules = 1 << 5,
+ MiniDumpWithIndirectlyReferencedMemory = 1 << 6,
+ MiniDumpFilterModulePaths = 1 << 7,
+ MiniDumpWithProcessThreadData = 1 << 8,
+ MiniDumpWithPrivateReadWriteMemory = 1 << 9,
+ MiniDumpWithoutOptionalData = 1 << 10,
+ MiniDumpWithFullMemoryInfo = 1 << 11,
+ MiniDumpWithThreadInfo = 1 << 12,
+ MiniDumpWithCodeSegs = 1 << 13,
+ MiniDumpWithoutAuxiliaryState = 1 << 14,
+ MiniDumpWithFullAuxiliaryState = 1 << 15,
+ MiniDumpWithPrivateWriteCopyMemory = 1 << 16,
+ MiniDumpIgnoreInaccessibleMemory = 1 << 17,
+ MiniDumpWithTokenInformation = 1 << 18,
+ MiniDumpWithModuleHeaders = 1 << 19,
+ MiniDumpFilterTriage = 1 << 20,
+ MiniDumpWithAvxXStateContext = 1 << 21,
+ MiniDumpWithIptTrace = 1 << 22,
+ MiniDumpValidTypeFlags = (-1) ^ ((~1) << 22)
+ }
+ }
+ }
+ }
+}
diff --git a/src/Logging/Logging.Testing/src/DumpCollector/DumpCollector.cs b/src/Logging/Logging.Testing/src/DumpCollector/DumpCollector.cs
new file mode 100644
index 0000000000..67043ed827
--- /dev/null
+++ b/src/Logging/Logging.Testing/src/DumpCollector/DumpCollector.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Extensions.Logging.Testing
+{
+ public static partial class DumpCollector
+ {
+ public static void Collect(Process process, string fileName)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ Windows.Collect(process, fileName);
+ }
+ // No implementations yet for macOS and Linux
+ }
+ }
+}
diff --git a/src/Logging/Logging.Testing/src/Xunit/LoggedTestInvoker.cs b/src/Logging/Logging.Testing/src/Xunit/LoggedTestInvoker.cs
index 708db37105..788c38b306 100644
--- a/src/Logging/Logging.Testing/src/Xunit/LoggedTestInvoker.cs
+++ b/src/Logging/Logging.Testing/src/Xunit/LoggedTestInvoker.cs
@@ -1,8 +1,10 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
@@ -15,6 +17,7 @@ namespace Microsoft.Extensions.Logging.Testing
{
private readonly ITestOutputHelper _output;
private readonly RetryContext _retryContext;
+ private readonly bool _collectDumpOnFailure;
public LoggedTestInvoker(
ITest test,
@@ -27,11 +30,13 @@ namespace Microsoft.Extensions.Logging.Testing
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource,
ITestOutputHelper output,
- RetryContext retryContext)
+ RetryContext retryContext,
+ bool collectDumpOnFailure)
: base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, beforeAfterAttributes, aggregator, cancellationTokenSource)
{
_output = output;
_retryContext = retryContext;
+ _collectDumpOnFailure = collectDumpOnFailure;
}
protected override object CreateTestClass()
@@ -63,5 +68,25 @@ namespace Microsoft.Extensions.Logging.Testing
return testClass;
}
+
+ protected override object CallTestMethod(object testClassInstance)
+ {
+ try
+ {
+ return base.CallTestMethod(testClassInstance);
+ }
+ catch
+ {
+ if (_collectDumpOnFailure && testClassInstance is LoggedTestBase loggedTestBase)
+ {
+ var path = Path.Combine(loggedTestBase.ResolvedLogOutputDirectory, loggedTestBase.ResolvedTestMethodName + ".dmp");
+ var process = Process.GetCurrentProcess();
+
+ DumpCollector.Collect(process, path);
+ }
+
+ throw;
+ }
+ }
}
}
diff --git a/src/Logging/Logging.Testing/src/Xunit/LoggedTestRunner.cs b/src/Logging/Logging.Testing/src/Xunit/LoggedTestRunner.cs
index 7dc847a113..11537eb679 100644
--- a/src/Logging/Logging.Testing/src/Xunit/LoggedTestRunner.cs
+++ b/src/Logging/Logging.Testing/src/Xunit/LoggedTestRunner.cs
@@ -50,9 +50,11 @@ namespace Microsoft.Extensions.Logging.Testing
private async Task InvokeTestMethodAsync(ExceptionAggregator aggregator, ITestOutputHelper output)
{
var retryAttribute = GetRetryAttribute(TestMethod);
+ var collectDump = TestMethod.GetCustomAttribute() != null;
+
if (!typeof(LoggedTestBase).IsAssignableFrom(TestClass) || retryAttribute == null)
{
- return await new LoggedTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource, output, null).RunAsync();
+ return await new LoggedTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource, output, null, collectDump).RunAsync();
}
var retryPredicateMethodName = retryAttribute.RetryPredicateName;
@@ -75,7 +77,7 @@ namespace Microsoft.Extensions.Logging.Testing
};
var retryAggregator = new ExceptionAggregator();
- var loggedTestInvoker = new LoggedTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, retryAggregator, CancellationTokenSource, output, retryContext);
+ var loggedTestInvoker = new LoggedTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, retryAggregator, CancellationTokenSource, output, retryContext, collectDump);
var totalTime = 0.0M;
do
@@ -95,7 +97,6 @@ namespace Microsoft.Extensions.Logging.Testing
return totalTime;
}
-
private RetryTestAttribute GetRetryAttribute(MethodInfo methodInfo)
{
var os = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? OperatingSystems.MacOSX