diff --git a/CHANGELOG.md b/CHANGELOG.md index 719ce0d281..f15f8897e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### BREAKING CHANGES - SentryOptions.IsEnvironmentUser now defaults to false on MAUI. The means the User.Name will no longer be set, by default, to the name of the device ([#4606](https://github.com/getsentry/sentry-dotnet/pull/4606)) +- Spans and Transactions now implement `IDispose` so that they can be used with `using` statements/declarations that will automatically finish the span with a status of OK when it passes out of scope, if it has not already been finished, to be consistent with `Activity` classes when using OpenTelemetry ([#4627](https://github.com/getsentry/sentry-dotnet/pull/4627)) +- SpanTracer and TransactionTracer are still public but these are now `sealed` (see also [#4627](https://github.com/getsentry/sentry-dotnet/pull/4627)) - Remove unnecessary files from SentryCocoaFramework before packing ([#4602](https://github.com/getsentry/sentry-dotnet/pull/4602)) ### Fixes diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs index dfc7723796..1a2605c0ee 100644 --- a/samples/Sentry.Samples.Console.Basic/Program.cs +++ b/samples/Sentry.Samples.Console.Basic/Program.cs @@ -63,7 +63,7 @@ // Always try to finish the transaction successfully. // Unhandled exceptions will fail the transaction automatically. -// Optionally, you can try/catch the exception, and call transaction.Finish(exception) on failure. +// Optionally, you can try/catch the exception and call transaction.Finish(exception) on failure. transaction.Finish(); async Task FirstFunction() @@ -79,7 +79,6 @@ async Task FirstFunction() async Task SecondFunction() { var span = transaction.StartChild("function", nameof(SecondFunction)); - try { // Simulate doing some work @@ -97,26 +96,25 @@ async Task SecondFunction() SentrySdk.Logger.LogError(static log => log.SetAttribute("method", nameof(SecondFunction)), "Error with message: {0}", exception.Message); } - - span.Finish(); + finally + { + span.Finish(); + } } async Task ThirdFunction() { - var span = transaction.StartChild("function", nameof(ThirdFunction)); - try - { - // Simulate doing some work - await Task.Delay(100); + // The `using` here ensures the span gets finished when we leave this method... This is unnecessary here, + // since the method always throws and the span will be finished automatically when the exception is captured, + // but this gives you another way to ensure spans are finished. + using var span = transaction.StartChild("function", nameof(ThirdFunction)); - SentrySdk.Logger.LogFatal(static log => log.SetAttribute("suppress", true), - "Crash imminent!"); + // Simulate doing some work + await Task.Delay(100); - // This is an example of an unhandled exception. It will be captured automatically. - throw new InvalidOperationException("Something happened that crashed the app!"); - } - finally - { - span.Finish(); - } + SentrySdk.Logger.LogFatal(static log => log.SetAttribute("suppress", true), + "Crash imminent!"); + + // This is an example of an unhandled exception. It will be captured automatically. + throw new InvalidOperationException("Something happened that crashed the app!"); } diff --git a/src/Sentry/ISpan.cs b/src/Sentry/ISpan.cs index 5cf7193a9c..8bcd06d609 100644 --- a/src/Sentry/ISpan.cs +++ b/src/Sentry/ISpan.cs @@ -5,7 +5,7 @@ namespace Sentry; /// /// SpanTracer interface /// -public interface ISpan : ISpanData +public interface ISpan : ISpanData, IDisposable { /// /// Span description. diff --git a/src/Sentry/Internal/NoOpSpan.cs b/src/Sentry/Internal/NoOpSpan.cs index d57aa34261..75cefe68b7 100644 --- a/src/Sentry/Internal/NoOpSpan.cs +++ b/src/Sentry/Internal/NoOpSpan.cs @@ -85,4 +85,8 @@ public void SetMeasurement(string name, Measurement measurement) } public string? Origin { get; set; } + + public void Dispose() + { + } } diff --git a/src/Sentry/SpanTracer.cs b/src/Sentry/SpanTracer.cs index ed51d48eaa..8304eca540 100644 --- a/src/Sentry/SpanTracer.cs +++ b/src/Sentry/SpanTracer.cs @@ -6,10 +6,11 @@ namespace Sentry; /// /// Transaction span tracer. /// -public class SpanTracer : IBaseTracer, ISpan +public sealed class SpanTracer : IBaseTracer, ISpan { private readonly IHub _hub; private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); + private string? _origin; private readonly Instrumenter _instrumenter = Instrumenter.Sentry; @@ -190,5 +191,13 @@ internal set _origin = value; } } - private string? _origin; + + /// + /// Automatically finishes the span at the end of a using block. This is a convenience method only. Disposing + /// is not required. + /// + public void Dispose() + { + Finish(); + } } diff --git a/src/Sentry/TransactionTracer.cs b/src/Sentry/TransactionTracer.cs index bdd829ff81..205585dd26 100644 --- a/src/Sentry/TransactionTracer.cs +++ b/src/Sentry/TransactionTracer.cs @@ -7,13 +7,14 @@ namespace Sentry; /// /// Transaction tracer. /// -public class TransactionTracer : IBaseTracer, ITransactionTracer +public sealed class TransactionTracer : IBaseTracer, ITransactionTracer { private readonly IHub _hub; private readonly SentryOptions? _options; private readonly Timer? _idleTimer; private long _cancelIdleTimeout; private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); + private int _hasFinished = 0; private readonly Instrumenter _instrumenter = Instrumenter.Sentry; @@ -361,6 +362,12 @@ public void Clear() /// public void Finish() { + // TODO: Replace with InterlockedBoolean once this has been merged into version6 + if (Interlocked.Exchange(ref _hasFinished, 1) == 1) + { + return; + } + _options?.LogDebug("Attempting to finish Transaction '{0}'.", SpanId); if (Interlocked.Exchange(ref _cancelIdleTimeout, 0) == 1) { @@ -431,4 +438,19 @@ private void ReleaseSpans() #endif _activeSpanTracker.Clear(); } + + /// + /// + /// Automatically finishes the transaction with a status of at the end of a + /// using block, if it has not already been finished. + /// + /// + /// This is the equivalent of calling when the transaction passes out of scope. + /// + /// + /// This is a convenience method only. Disposing is not required. + public void Dispose() + { + Finish(); + } } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index 406a853181..1f8c5f41ee 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -261,7 +261,7 @@ namespace Sentry { Sentry.SentryUser? Create(); } - public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext + public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable { new string? Description { get; set; } new string Operation { get; set; } @@ -291,7 +291,7 @@ namespace Sentry { string? Platform { get; set; } } - public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext + public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext, System.IDisposable { new bool? IsParentSampled { get; set; } new string Name { get; set; } @@ -1160,7 +1160,7 @@ namespace Sentry OutOfRange = 15, DataLoss = 16, } - public class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext + public sealed class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable { public SpanTracer(Sentry.IHub hub, Sentry.TransactionTracer transaction, Sentry.SpanId? parentSpanId, Sentry.SentryId traceId, string operation) { } public System.Collections.Generic.IReadOnlyDictionary Data { get; } @@ -1178,6 +1178,7 @@ namespace Sentry public Sentry.SpanStatus? Status { get; set; } public System.Collections.Generic.IReadOnlyDictionary Tags { get; } public Sentry.SentryId TraceId { get; } + public void Dispose() { } public void Finish() { } public void Finish(Sentry.SpanStatus status) { } public void Finish(System.Exception exception) { } @@ -1239,7 +1240,7 @@ namespace Sentry public System.Collections.Generic.IReadOnlyDictionary CustomSamplingContext { get; } public Sentry.ITransactionContext TransactionContext { get; } } - public class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext + public sealed class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext, System.IDisposable { public TransactionTracer(Sentry.IHub hub, Sentry.ITransactionContext context) { } public System.Collections.Generic.IReadOnlyCollection Breadcrumbs { get; } @@ -1275,6 +1276,7 @@ namespace Sentry public Sentry.SentryId TraceId { get; } public Sentry.SentryUser User { get; set; } public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void Dispose() { } public void Finish() { } public void Finish(Sentry.SpanStatus status) { } public void Finish(System.Exception exception) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 406a853181..1f8c5f41ee 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -261,7 +261,7 @@ namespace Sentry { Sentry.SentryUser? Create(); } - public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext + public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable { new string? Description { get; set; } new string Operation { get; set; } @@ -291,7 +291,7 @@ namespace Sentry { string? Platform { get; set; } } - public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext + public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext, System.IDisposable { new bool? IsParentSampled { get; set; } new string Name { get; set; } @@ -1160,7 +1160,7 @@ namespace Sentry OutOfRange = 15, DataLoss = 16, } - public class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext + public sealed class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable { public SpanTracer(Sentry.IHub hub, Sentry.TransactionTracer transaction, Sentry.SpanId? parentSpanId, Sentry.SentryId traceId, string operation) { } public System.Collections.Generic.IReadOnlyDictionary Data { get; } @@ -1178,6 +1178,7 @@ namespace Sentry public Sentry.SpanStatus? Status { get; set; } public System.Collections.Generic.IReadOnlyDictionary Tags { get; } public Sentry.SentryId TraceId { get; } + public void Dispose() { } public void Finish() { } public void Finish(Sentry.SpanStatus status) { } public void Finish(System.Exception exception) { } @@ -1239,7 +1240,7 @@ namespace Sentry public System.Collections.Generic.IReadOnlyDictionary CustomSamplingContext { get; } public Sentry.ITransactionContext TransactionContext { get; } } - public class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext + public sealed class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext, System.IDisposable { public TransactionTracer(Sentry.IHub hub, Sentry.ITransactionContext context) { } public System.Collections.Generic.IReadOnlyCollection Breadcrumbs { get; } @@ -1275,6 +1276,7 @@ namespace Sentry public Sentry.SentryId TraceId { get; } public Sentry.SentryUser User { get; set; } public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void Dispose() { } public void Finish() { } public void Finish(Sentry.SpanStatus status) { } public void Finish(System.Exception exception) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 406a853181..1f8c5f41ee 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -261,7 +261,7 @@ namespace Sentry { Sentry.SentryUser? Create(); } - public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext + public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable { new string? Description { get; set; } new string Operation { get; set; } @@ -291,7 +291,7 @@ namespace Sentry { string? Platform { get; set; } } - public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext + public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext, System.IDisposable { new bool? IsParentSampled { get; set; } new string Name { get; set; } @@ -1160,7 +1160,7 @@ namespace Sentry OutOfRange = 15, DataLoss = 16, } - public class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext + public sealed class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable { public SpanTracer(Sentry.IHub hub, Sentry.TransactionTracer transaction, Sentry.SpanId? parentSpanId, Sentry.SentryId traceId, string operation) { } public System.Collections.Generic.IReadOnlyDictionary Data { get; } @@ -1178,6 +1178,7 @@ namespace Sentry public Sentry.SpanStatus? Status { get; set; } public System.Collections.Generic.IReadOnlyDictionary Tags { get; } public Sentry.SentryId TraceId { get; } + public void Dispose() { } public void Finish() { } public void Finish(Sentry.SpanStatus status) { } public void Finish(System.Exception exception) { } @@ -1239,7 +1240,7 @@ namespace Sentry public System.Collections.Generic.IReadOnlyDictionary CustomSamplingContext { get; } public Sentry.ITransactionContext TransactionContext { get; } } - public class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext + public sealed class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext, System.IDisposable { public TransactionTracer(Sentry.IHub hub, Sentry.ITransactionContext context) { } public System.Collections.Generic.IReadOnlyCollection Breadcrumbs { get; } @@ -1275,6 +1276,7 @@ namespace Sentry public Sentry.SentryId TraceId { get; } public Sentry.SentryUser User { get; set; } public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void Dispose() { } public void Finish() { } public void Finish(Sentry.SpanStatus status) { } public void Finish(System.Exception exception) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index e2a02fc89a..9eeb00ffd1 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -249,7 +249,7 @@ namespace Sentry { Sentry.SentryUser? Create(); } - public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext + public interface ISpan : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable { new string? Description { get; set; } new string Operation { get; set; } @@ -279,7 +279,7 @@ namespace Sentry { string? Platform { get; set; } } - public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext + public interface ITransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.Protocol.ITraceContext, System.IDisposable { new bool? IsParentSampled { get; set; } new string Name { get; set; } @@ -1136,7 +1136,7 @@ namespace Sentry OutOfRange = 15, DataLoss = 16, } - public class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext + public sealed class SpanTracer : Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.Protocol.ITraceContext, System.IDisposable { public SpanTracer(Sentry.IHub hub, Sentry.TransactionTracer transaction, Sentry.SpanId? parentSpanId, Sentry.SentryId traceId, string operation) { } public System.Collections.Generic.IReadOnlyDictionary Data { get; } @@ -1154,6 +1154,7 @@ namespace Sentry public Sentry.SpanStatus? Status { get; set; } public System.Collections.Generic.IReadOnlyDictionary Tags { get; } public Sentry.SentryId TraceId { get; } + public void Dispose() { } public void Finish() { } public void Finish(Sentry.SpanStatus status) { } public void Finish(System.Exception exception) { } @@ -1215,7 +1216,7 @@ namespace Sentry public System.Collections.Generic.IReadOnlyDictionary CustomSamplingContext { get; } public Sentry.ITransactionContext TransactionContext { get; } } - public class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext + public sealed class TransactionTracer : Sentry.IEventLike, Sentry.IHasData, Sentry.IHasExtra, Sentry.IHasTags, Sentry.ISpan, Sentry.ISpanData, Sentry.ITransactionContext, Sentry.ITransactionData, Sentry.ITransactionTracer, Sentry.Protocol.ITraceContext, System.IDisposable { public TransactionTracer(Sentry.IHub hub, Sentry.ITransactionContext context) { } public System.Collections.Generic.IReadOnlyCollection Breadcrumbs { get; } @@ -1251,6 +1252,7 @@ namespace Sentry public Sentry.SentryId TraceId { get; } public Sentry.SentryUser User { get; set; } public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void Dispose() { } public void Finish() { } public void Finish(Sentry.SpanStatus status) { } public void Finish(System.Exception exception) { } diff --git a/test/Sentry.Tests/SpanTracerTests.cs b/test/Sentry.Tests/SpanTracerTests.cs index 59becf2740..9362ad160d 100644 --- a/test/Sentry.Tests/SpanTracerTests.cs +++ b/test/Sentry.Tests/SpanTracerTests.cs @@ -10,7 +10,7 @@ public async Task SetExtra_DataInserted_NoDataLoss() { // Arrange var hub = Substitute.For(); - var transaction = new SpanTracer(hub, null, null, SentryId.Empty, ""); + var spanTracer = new SpanTracer(hub, null, null, SentryId.Empty, ""); var evt = new ManualResetEvent(false); var ready = new ManualResetEvent(false); var counter = 0; @@ -27,7 +27,7 @@ public async Task SetExtra_DataInserted_NoDataLoss() for (var i = 0; i < amount; i++) { - transaction.SetExtra(Guid.NewGuid().ToString(), Guid.NewGuid()); + spanTracer.SetExtra(Guid.NewGuid().ToString(), Guid.NewGuid()); } })).ToList(); ready.WaitOne(); @@ -36,7 +36,38 @@ public async Task SetExtra_DataInserted_NoDataLoss() // Arrange // 4 tasks testing X amount should be the same amount as Extras. - Assert.Equal(4 * amount, transaction.Extra.Count); + Assert.Equal(4 * amount, spanTracer.Extra.Count); } } + + [Fact] + public void Dispose_TransactionIsUnfinished_FinishesTransaction() + { + // Arrange + var hub = Substitute.For(); + var spanTracer = new SpanTracer(hub, null, null, SentryId.Empty, ""); + + // Act + spanTracer.Dispose(); + + // Assert + spanTracer.IsFinished.Should().BeTrue(); + } + + [Fact] + public void Dispose_TransactionIsFinsished_DoesNothing() + { + // Arrange + var hub = Substitute.For(); + var spanTracer = new SpanTracer(hub, null, null, SentryId.Empty, ""); + spanTracer.Finish(); + var finishTimestamp = spanTracer.EndTimestamp; + + // Act + spanTracer.Dispose(); + + // Assert + spanTracer.IsFinished.Should().BeTrue(); + spanTracer.EndTimestamp.Should().Be(finishTimestamp); + } } diff --git a/test/Sentry.Tests/TransactionTracerTests.cs b/test/Sentry.Tests/TransactionTracerTests.cs new file mode 100644 index 0000000000..904bb13369 --- /dev/null +++ b/test/Sentry.Tests/TransactionTracerTests.cs @@ -0,0 +1,33 @@ +namespace Sentry.Tests; + +public class TransactionTracerTests +{ + [Fact] + public void Dispose_Unfinished_Finishes() + { + // Arrange + var hub = Substitute.For(); + var transaction = new TransactionTracer(hub, "op", "name"); + + // Act + transaction.Dispose(); + + // Assert + hub.Received(1).CaptureTransaction(Arg.Is(t => t.SpanId == transaction.SpanId)); + } + + [Fact] + public void Dispose_Finished_DoesNothing() + { + // Arrange + var hub = Substitute.For(); + var transaction = new TransactionTracer(hub, "op", "name"); + transaction.Finish(); + + // Act + transaction.Dispose(); + + // Assert + hub.Received(1).CaptureTransaction(Arg.Is(t => t.SpanId == transaction.SpanId)); + } +}