-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NUnit CallTarget integration (#1131)
* CallTarget NUnit Integration * Changes based in the reviews. * Changes from reviews * Update integrations * Fix CIApp compability by removing the Analyzed flag in the span. * update integrations.json
- Loading branch information
1 parent
69cb554
commit 86ddfe3
Showing
9 changed files
with
495 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
src/Datadog.Trace.ClrProfiler.Managed/AutoInstrumentation/NUnit/IMethodInfo.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using System.Reflection; | ||
|
||
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.NUnit | ||
{ | ||
/// <summary> | ||
/// DuckTyping interface for NUnit.Framework.Interfaces.IMethodInfo | ||
/// </summary> | ||
public interface IMethodInfo | ||
{ | ||
/// <summary> | ||
/// Gets the MethodInfo for this method. | ||
/// </summary> | ||
MethodInfo MethodInfo { get; } | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
src/Datadog.Trace.ClrProfiler.Managed/AutoInstrumentation/NUnit/IPropertyBag.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
|
||
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.NUnit | ||
{ | ||
/// <summary> | ||
/// DuckTyping interface for NUnit.Framework.Interfaces.IPropertyBag | ||
/// </summary> | ||
public interface IPropertyBag | ||
{ | ||
/// <summary> | ||
/// Gets a collection containing all the keys in the property set | ||
/// </summary> | ||
ICollection<string> Keys { get; } | ||
|
||
/// <summary> | ||
/// Gets or sets the list of values for a particular key | ||
/// </summary> | ||
/// <param name="key">The key for which the values are to be retrieved</param> | ||
IList this[string key] { get; } | ||
|
||
/// <summary> | ||
/// Gets a single value for a key, using the first | ||
/// one if multiple values are present and returning | ||
/// null if the value is not found. | ||
/// </summary> | ||
/// <param name="key">the key for which the values are to be retrieved</param> | ||
/// <returns>First value of the list for the key; otherwise null.</returns> | ||
object Get(string key); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
src/Datadog.Trace.ClrProfiler.Managed/AutoInstrumentation/NUnit/ITest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.NUnit | ||
{ | ||
/// <summary> | ||
/// DuckTyping interface for NUnit.Framework.Internal.Test | ||
/// </summary> | ||
public interface ITest | ||
{ | ||
/// <summary> | ||
/// Gets a MethodInfo for the method implementing this test. | ||
/// Returns null if the test is not implemented as a method. | ||
/// </summary> | ||
IMethodInfo Method { get; } | ||
|
||
/// <summary> | ||
/// Gets the arguments to use in creating the test or empty array if none required. | ||
/// </summary> | ||
object[] Arguments { get; } | ||
|
||
/// <summary> | ||
/// Gets the properties for this test | ||
/// </summary> | ||
IPropertyBag Properties { get; } | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/Datadog.Trace.ClrProfiler.Managed/AutoInstrumentation/NUnit/ITestExecutionContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.NUnit | ||
{ | ||
/// <summary> | ||
/// DuckTyping interface for NUnit.Framework.Internal.TestExecutionContext | ||
/// </summary> | ||
public interface ITestExecutionContext | ||
{ | ||
/// <summary> | ||
/// Gets the current test | ||
/// </summary> | ||
ITest CurrentTest { get; } | ||
} | ||
} |
145 changes: 145 additions & 0 deletions
145
src/Datadog.Trace.ClrProfiler.Managed/AutoInstrumentation/NUnit/NUnitIntegration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using Datadog.Trace.Ci; | ||
using Datadog.Trace.ExtensionMethods; | ||
|
||
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.NUnit | ||
{ | ||
internal static class NUnitIntegration | ||
{ | ||
static NUnitIntegration() | ||
{ | ||
// Preload environment variables. | ||
CIEnvironmentValues.DecorateSpan(null); | ||
} | ||
|
||
internal static Scope CreateScope<TContext>(TContext executionContext, Type targetType) | ||
where TContext : ITestExecutionContext | ||
{ | ||
ITest currentTest = executionContext.CurrentTest; | ||
MethodInfo testMethod = currentTest.Method.MethodInfo; | ||
object[] testMethodArguments = currentTest.Arguments; | ||
IPropertyBag testMethodProperties = currentTest.Properties; | ||
|
||
if (testMethod == null) | ||
{ | ||
return null; | ||
} | ||
|
||
string testFramework = "NUnit " + targetType?.Assembly?.GetName().Version; | ||
string testSuite = testMethod.DeclaringType?.FullName; | ||
string testName = testMethod.Name; | ||
string skipReason = null; | ||
|
||
Tracer tracer = Tracer.Instance; | ||
Scope scope = tracer.StartActive("nunit.test"); | ||
Span span = scope.Span; | ||
|
||
span.Type = SpanTypes.Test; | ||
span.SetTraceSamplingPriority(SamplingPriority.AutoKeep); | ||
span.ResourceName = $"{testSuite}.{testName}"; | ||
span.SetTag(TestTags.Suite, testSuite); | ||
span.SetTag(TestTags.Name, testName); | ||
span.SetTag(TestTags.Framework, testFramework); | ||
span.SetTag(TestTags.Type, TestTags.TypeTest); | ||
CIEnvironmentValues.DecorateSpan(span); | ||
|
||
var framework = FrameworkDescription.Instance; | ||
|
||
span.SetTag(CommonTags.RuntimeName, framework.Name); | ||
span.SetTag(CommonTags.RuntimeOSArchitecture, framework.OSArchitecture); | ||
span.SetTag(CommonTags.RuntimeOSPlatform, framework.OSPlatform); | ||
span.SetTag(CommonTags.RuntimeProcessArchitecture, framework.ProcessArchitecture); | ||
span.SetTag(CommonTags.RuntimeVersion, framework.ProductVersion); | ||
|
||
// Get test parameters | ||
ParameterInfo[] methodParameters = testMethod.GetParameters(); | ||
if (methodParameters?.Length > 0) | ||
{ | ||
for (int i = 0; i < methodParameters.Length; i++) | ||
{ | ||
if (testMethodArguments != null && i < testMethodArguments.Length) | ||
{ | ||
span.SetTag($"{TestTags.Arguments}.{methodParameters[i].Name}", testMethodArguments[i]?.ToString() ?? "(null)"); | ||
} | ||
else | ||
{ | ||
span.SetTag($"{TestTags.Arguments}.{methodParameters[i].Name}", "(default)"); | ||
} | ||
} | ||
} | ||
|
||
// Get traits | ||
if (testMethodProperties != null) | ||
{ | ||
skipReason = (string)testMethodProperties.Get("_SKIPREASON"); | ||
foreach (var key in testMethodProperties.Keys) | ||
{ | ||
if (key == "_SKIPREASON") | ||
{ | ||
continue; | ||
} | ||
|
||
IList value = testMethodProperties[key]; | ||
if (value != null) | ||
{ | ||
List<string> lstValues = new List<string>(); | ||
foreach (object valObj in value) | ||
{ | ||
if (valObj is null) | ||
{ | ||
continue; | ||
} | ||
|
||
lstValues.Add(valObj.ToString()); | ||
} | ||
|
||
span.SetTag($"{TestTags.Traits}.{key}", string.Join(", ", lstValues)); | ||
} | ||
else | ||
{ | ||
span.SetTag($"{TestTags.Traits}.{key}", "(null)"); | ||
} | ||
} | ||
} | ||
|
||
if (skipReason != null) | ||
{ | ||
span.SetTag(TestTags.Status, TestTags.StatusSkip); | ||
span.SetTag(TestTags.SkipReason, skipReason); | ||
span.Finish(TimeSpan.Zero); | ||
scope.Dispose(); | ||
scope = null; | ||
} | ||
|
||
span.ResetStartTime(); | ||
return scope; | ||
} | ||
|
||
internal static void FinishScope(Scope scope, Exception ex) | ||
{ | ||
// unwrap the generic NUnitException | ||
if (ex != null && ex.GetType().FullName == "NUnit.Framework.Internal.NUnitException") | ||
{ | ||
ex = ex.InnerException; | ||
} | ||
|
||
switch (ex?.GetType().FullName) | ||
{ | ||
case "NUnit.Framework.SuccessException": | ||
scope.Span.SetTag(TestTags.Status, TestTags.StatusPass); | ||
break; | ||
case "NUnit.Framework.IgnoreException": | ||
scope.Span.SetTag(TestTags.Status, TestTags.StatusSkip); | ||
break; | ||
default: | ||
scope.Span.SetException(ex); | ||
scope.Span.SetTag(TestTags.Status, TestTags.StatusFail); | ||
break; | ||
} | ||
} | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
...Trace.ClrProfiler.Managed/AutoInstrumentation/NUnit/NUnitSkipCommandExecuteIntegration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
using System; | ||
using Datadog.Trace.ClrProfiler.CallTarget; | ||
|
||
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.NUnit | ||
{ | ||
/// <summary> | ||
/// NUnit.Framework.Internal.Commands.SkipCommand.Execute() calltarget instrumentation | ||
/// </summary> | ||
[InstrumentMethod( | ||
Assembly = "nunit.framework", | ||
Type = "NUnit.Framework.Internal.Commands.SkipCommand", | ||
Method = "Execute", | ||
ReturnTypeName = "NUnit.Framework.Internal.TestResult", | ||
ParametersTypesNames = new[] { "NUnit.Framework.Internal.TestExecutionContext" }, | ||
MinimumVersion = "3.0.0", | ||
MaximumVersion = "3.*.*", | ||
IntegrationName = IntegrationName)] | ||
public class NUnitSkipCommandExecuteIntegration | ||
{ | ||
private const string IntegrationName = "NUnit"; | ||
|
||
/// <summary> | ||
/// OnMethodBegin callback | ||
/// </summary> | ||
/// <typeparam name="TTarget">Type of the target</typeparam> | ||
/// <typeparam name="TContext">ExecutionContext type</typeparam> | ||
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param> | ||
/// <param name="executionContext">Execution context instance</param> | ||
/// <returns>Calltarget state value</returns> | ||
public static CallTargetState OnMethodBegin<TTarget, TContext>(TTarget instance, TContext executionContext) | ||
where TContext : ITestExecutionContext | ||
{ | ||
if (!Tracer.Instance.Settings.IsIntegrationEnabled(IntegrationName)) | ||
{ | ||
return CallTargetState.GetDefault(); | ||
} | ||
|
||
return new CallTargetState(NUnitIntegration.CreateScope(executionContext, typeof(TTarget))); | ||
} | ||
|
||
/// <summary> | ||
/// OnMethodEnd callback | ||
/// </summary> | ||
/// <typeparam name="TTarget">Type of the target</typeparam> | ||
/// <typeparam name="TResult">TestResult type</typeparam> | ||
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param> | ||
/// <param name="returnValue">Original method return value</param> | ||
/// <param name="exception">Exception instance in case the original code threw an exception.</param> | ||
/// <param name="state">Calltarget state value</param> | ||
/// <returns>Return value of the method</returns> | ||
public static CallTargetReturn<TResult> OnMethodEnd<TTarget, TResult>(TTarget instance, TResult returnValue, Exception exception, CallTargetState state) | ||
{ | ||
Scope scope = (Scope)state.State; | ||
if (scope != null) | ||
{ | ||
NUnitIntegration.FinishScope(scope, exception); | ||
scope.Dispose(); | ||
} | ||
|
||
return new CallTargetReturn<TResult>(returnValue); | ||
} | ||
} | ||
} |
Oops, something went wrong.