From 88750f84838fa909d3ee9418b7409233c1b11f25 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 10 Feb 2025 09:31:27 -0900 Subject: [PATCH 1/8] impr(profiling): increase buffer length from 10s to 60s --- Sources/Sentry/Profiling/SentryProfilerDefines.h | 2 +- scripts/.clang-format-version | 2 +- scripts/.swiftlint-version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Sentry/Profiling/SentryProfilerDefines.h b/Sources/Sentry/Profiling/SentryProfilerDefines.h index d06b1539855..e561ee8a0e1 100644 --- a/Sources/Sentry/Profiling/SentryProfilerDefines.h +++ b/Sources/Sentry/Profiling/SentryProfilerDefines.h @@ -15,7 +15,7 @@ typedef NS_ENUM(NSUInteger, SentryProfilerTruncationReason) { SentryProfilerTruncationReasonAppMovedToBackground, }; -static NSTimeInterval kSentryProfilerChunkExpirationInterval = 10; +static NSTimeInterval kSentryProfilerChunkExpirationInterval = 60; static NSTimeInterval kSentryProfilerTimeoutInterval = 30; NS_ASSUME_NONNULL_BEGIN diff --git a/scripts/.clang-format-version b/scripts/.clang-format-version index 87c0f53ffeb..d370346ab01 100644 --- a/scripts/.clang-format-version +++ b/scripts/.clang-format-version @@ -1 +1 @@ -19.1.6 +19.1.7 diff --git a/scripts/.swiftlint-version b/scripts/.swiftlint-version index d139b327db8..31cbc49ac73 100644 --- a/scripts/.swiftlint-version +++ b/scripts/.swiftlint-version @@ -1 +1 @@ -0.57.1 +0.58.2 From e9fbe6b627a0ac31673a397e8ef01e78b4c6de65 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 10 Feb 2025 09:46:18 -0900 Subject: [PATCH 2/8] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 320181a9a99..327f9d1874b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Add `sample_rand` to baggage (#4751) - Add timeIntervalSince1970 to log messages (#4781) - Add `waitForFullDisplay` to `sentryTrace` view modifier (#4797) +- Increase continuous profiling buffer size to 60 seconds (#4826) ### Fixes From 00a1a3a57c0e780e364f9605233d75be1000f022 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 11 Feb 2025 15:51:13 -0900 Subject: [PATCH 3/8] add test --- .../TestSentryNSTimerFactory.swift | 89 ++++++++++++++----- .../SentryContinuousProfilerTests.swift | 37 ++++++-- .../SentryProfileTestFixture.swift | 6 +- .../SentryTraceProfilerTests.swift | 4 +- .../SentryTimeToDisplayTrackerTest.swift | 6 +- .../Transaction/SentryTracerTests.swift | 34 +++---- 6 files changed, 124 insertions(+), 52 deletions(-) diff --git a/SentryTestUtils/TestSentryNSTimerFactory.swift b/SentryTestUtils/TestSentryNSTimerFactory.swift index 7e9fe92f2c2..0f3813adf94 100644 --- a/SentryTestUtils/TestSentryNSTimerFactory.swift +++ b/SentryTestUtils/TestSentryNSTimerFactory.swift @@ -1,52 +1,101 @@ import Foundation -import Sentry +@testable import Sentry // We must not subclass NSTimer, see https://developer.apple.com/documentation/foundation/nstimer#1770465. // Therefore we return a NSTimer instance here with TimeInterval.infinity. public class TestSentryNSTimerFactory: SentryNSTimerFactory { public struct Overrides { - private var _timer: Timer? - - public var timer: Timer { - get { - _timer ?? Timer() - } + private var _timer: Timer? + private var _interval: TimeInterval? + + public var timer: Timer { + get { + _timer ?? Timer() + } set(newValue) { _timer = newValue } - } - + } + var block: ((Timer) -> Void)? - + + var interval: TimeInterval { + get { + _interval ?? TimeInterval.infinity + } + set { + _interval = newValue + } + } + + var lastFireDate: Date + struct InvocationInfo { var target: NSObject var selector: Selector } var invocationInfo: InvocationInfo? + + init(timer: Timer, interval: TimeInterval? = nil, block: ((Timer) -> Void)? = nil, lastFireDate: Date, invocationInfo: InvocationInfo? = nil) { + self._timer = timer + self._interval = interval + self.block = block + self.lastFireDate = lastFireDate + self.invocationInfo = invocationInfo + } } + + public var overrides: Overrides? + + private var currentDateProvider: SentryCurrentDateProvider + + public init(currentDateProvider: SentryCurrentDateProvider) { + self.currentDateProvider = currentDateProvider + super.init() + } +} - public var overrides = Overrides() - - public override func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { +// MARK: Superclass overrides +public extension TestSentryNSTimerFactory { + override func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval.infinity, repeats: repeats, block: block) - overrides.timer = timer - overrides.block = block + overrides = Overrides(timer: timer, interval: interval, block: block, lastFireDate: currentDateProvider.date()) return timer } - - public override func scheduledTimer(withTimeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool) -> Timer { + + override func scheduledTimer(withTimeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool) -> Timer { let timer = Timer.scheduledTimer(timeInterval: ti, target: aTarget, selector: aSelector, userInfo: userInfo, repeats: yesOrNo) //swiftlint:disable force_cast - overrides.invocationInfo = Overrides.InvocationInfo(target: aTarget as! NSObject, selector: aSelector) + let invocationInfo = Overrides.InvocationInfo(target: aTarget as! NSObject, selector: aSelector) //swiftlint:enable force_cast + overrides = Overrides(timer: timer, interval: ti, lastFireDate: currentDateProvider.date(), invocationInfo: invocationInfo) return timer } +} + +// MARK: Extensions +public extension TestSentryNSTimerFactory { + enum TestTimerError: Error { + case timerNotInitialized + } + + // check the current time against the last fire time and interval, and if enough time has elapsed, execute any block/invocation registered with the timer + func check() throws { + guard let overrides = overrides else { throw TestTimerError.timerNotInitialized } + let currentDate = currentDateProvider.date() + if currentDate.timeIntervalSince(overrides.lastFireDate) >= overrides.interval { + try fire() + } + } - public func fire() { + // immediately execute any block/invocation registered with the timer + func fire() throws { + guard var overrides = overrides else { throw TestTimerError.timerNotInitialized } + overrides.lastFireDate = currentDateProvider.date() if let block = overrides.block { block(overrides.timer) } else if let invocationInfo = overrides.invocationInfo { - try! Invocation(target: invocationInfo.target, selector: invocationInfo.selector).invoke() + try Invocation(target: invocationInfo.target, selector: invocationInfo.selector).invoke() } } } diff --git a/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift b/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift index 2a14874b5e7..f435c803474 100644 --- a/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift +++ b/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift @@ -94,12 +94,12 @@ final class SentryContinuousProfilerTests: XCTestCase { XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) } - func testClosingSDKStopsProfile() { + func testClosingSDKStopsProfile() throws { XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling()) SentryContinuousProfiler.start() XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) SentrySDK.close() - assertContinuousProfileStoppage() + try assertContinuousProfileStoppage() } func testStartingAPerformanceTransactionDoesNotStartProfiler() throws { @@ -119,14 +119,14 @@ final class SentryContinuousProfilerTests: XCTestCase { // assert that the first chunk was sent fixture.currentDateProvider.advanceBy(interval: kSentryProfilerChunkExpirationInterval) - fixture.timeoutTimerFactory.fire() + try fixture.timeoutTimerFactory.check() let envelope = try XCTUnwrap(self.fixture.client?.captureEnvelopeInvocations.last) let profileItem = try XCTUnwrap(envelope.items.first) XCTAssertEqual("profile_chunk", profileItem.header.type) // assert that the profiler doesn't stop until after the next timer period elapses SentryContinuousProfiler.stop() - assertContinuousProfileStoppage() + try assertContinuousProfileStoppage() // check that the last full chunk was sent let lastEnvelope = try XCTUnwrap(self.fixture.client?.captureEnvelopeInvocations.last) @@ -136,6 +136,27 @@ final class SentryContinuousProfilerTests: XCTestCase { // check that two chunks were sent in total XCTAssertEqual(2, self.fixture.client?.captureEnvelopeInvocations.count) } + + func testChunkSerializationAfterBufferInterval() throws { + SentryContinuousProfiler.start() + XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) + + // Advance time by the buffer interval to trigger chunk serialization + fixture.currentDateProvider.advanceBy(interval: 60) + try fixture.timeoutTimerFactory.check() + + // Check that a chunk was serialized and sent + let envelope = try XCTUnwrap(self.fixture.client?.captureEnvelopeInvocations.last) + let profileItem = try XCTUnwrap(envelope.items.first) + XCTAssertEqual("profile_chunk", profileItem.header.type) + + // Ensure the profiler is still running + XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) + + // Stop the profiler + SentryContinuousProfiler.stop() + try assertContinuousProfileStoppage() + } } private extension SentryContinuousProfilerTests { @@ -159,7 +180,7 @@ private extension SentryContinuousProfilerTests { try fixture.gatherMockedContinuousProfileMetrics() try addMockSamples(mockAddresses: expectedAddresses) fixture.currentDateProvider.advanceBy(interval: 1) - fixture.timeoutTimerFactory.fire() + try fixture.timeoutTimerFactory.check() XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) try assertValidData(expectedEnvironment: expectedEnvironment, expectedAddresses: expectedAddresses, countMetricsReadingAtProfileStart: countMetricsReadingAtProfileStart) #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) @@ -174,13 +195,13 @@ private extension SentryContinuousProfilerTests { XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) SentryContinuousProfiler.stop() - assertContinuousProfileStoppage() + try assertContinuousProfileStoppage() } - func assertContinuousProfileStoppage() { + func assertContinuousProfileStoppage() throws { XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) fixture.currentDateProvider.advance(by: kSentryProfilerTimeoutInterval) - fixture.timeoutTimerFactory.fire() + try fixture.timeoutTimerFactory.check() XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling()) } diff --git a/Tests/SentryProfilerTests/SentryProfileTestFixture.swift b/Tests/SentryProfilerTests/SentryProfileTestFixture.swift index fddd4b98e46..9d70983dc33 100644 --- a/Tests/SentryProfilerTests/SentryProfileTestFixture.swift +++ b/Tests/SentryProfilerTests/SentryProfileTestFixture.swift @@ -28,7 +28,7 @@ class SentryProfileTestFixture { let processInfoWrapper = TestSentryNSProcessInfoWrapper() let dispatchFactory = TestDispatchFactory() var metricTimerFactory: TestDispatchSourceWrapper? - let timeoutTimerFactory = TestSentryNSTimerFactory() + var timeoutTimerFactory: TestSentryNSTimerFactory let dispatchQueueWrapper = TestSentryDispatchQueueWrapper() let notificationCenter = TestNSNotificationCenterWrapper() @@ -46,8 +46,10 @@ class SentryProfileTestFixture { SentryDependencyContainer.sharedInstance().systemWrapper = systemWrapper SentryDependencyContainer.sharedInstance().processInfoWrapper = processInfoWrapper SentryDependencyContainer.sharedInstance().dispatchFactory = dispatchFactory - SentryDependencyContainer.sharedInstance().timerFactory = timeoutTimerFactory SentryDependencyContainer.sharedInstance().notificationCenterWrapper = notificationCenter + + timeoutTimerFactory = TestSentryNSTimerFactory(currentDateProvider: self.currentDateProvider) + SentryDependencyContainer.sharedInstance().timerFactory = timeoutTimerFactory let image = DebugMeta() image.name = "sentrytest" diff --git a/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift b/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift index 291709da2e2..1bba7f57c5f 100644 --- a/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift +++ b/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift @@ -138,7 +138,7 @@ class SentryTraceProfilerTests: XCTestCase { // time out profiler for span A fixture.currentDateProvider.advanceBy(nanoseconds: 30.toNanoSeconds()) - fixture.timeoutTimerFactory.fire() + try fixture.timeoutTimerFactory.fire() fixture.currentDateProvider.advanceBy(nanoseconds: 0.5.toNanoSeconds()) @@ -405,7 +405,7 @@ private extension SentryTraceProfilerTests { try addMockSamples() fixture.currentDateProvider.advance(by: 31) if shouldTimeOut { - self.fixture.timeoutTimerFactory.fire() + try self.fixture.timeoutTimerFactory.fire() } let exp = expectation(description: "finished span") diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift index 2a1ced7a329..010bd2020a2 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift @@ -9,7 +9,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { private class Fixture { let dateProvider: TestCurrentDateProvider = TestCurrentDateProvider() - let timerFactory = TestSentryNSTimerFactory() + lazy var timerFactory = TestSentryNSTimerFactory(currentDateProvider: dateProvider) var displayLinkWrapper = TestDisplayLinkWrapper() var framesTracker: SentryFramesTracker @@ -280,7 +280,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 11)) // Timeout for tracer times out - fixture.timerFactory.fire() + try fixture.timerFactory.fire() fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 12)) sut.reportFullyDisplayed() @@ -330,7 +330,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 11)) // Timeout for tracer times out - fixture.timerFactory.fire() + try fixture.timerFactory.fire() fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 12)) sut.reportFullyDisplayed() diff --git a/Tests/SentryTests/Transaction/SentryTracerTests.swift b/Tests/SentryTests/Transaction/SentryTracerTests.swift index 9352209ff35..1322cbd69af 100644 --- a/Tests/SentryTests/Transaction/SentryTracerTests.swift +++ b/Tests/SentryTests/Transaction/SentryTracerTests.swift @@ -28,7 +28,7 @@ class SentryTracerTests: XCTestCase { let scope: Scope let dispatchQueue = TestSentryDispatchQueueWrapper() let debugImageProvider = TestDebugImageProvider() - let timerFactory = TestSentryNSTimerFactory() + lazy var timerFactory = TestSentryNSTimerFactory(currentDateProvider: currentDateProvider) let transactionName = "Some Transaction" let transactionOperation = "ui.load" @@ -244,7 +244,7 @@ class SentryTracerTests: XCTestCase { } } - func testDeadlineTimer_FinishesTransactionAndChildren() { + func testDeadlineTimer_FinishesTransactionAndChildren() throws { fixture.dispatchQueue.blockBeforeMainBlock = { true } let sut = fixture.getSut() @@ -254,7 +254,7 @@ class SentryTracerTests: XCTestCase { child3.finish() - fixture.timerFactory.fire() + try fixture.timerFactory.fire() assertOneTransactionCaptured(sut) @@ -264,13 +264,13 @@ class SentryTracerTests: XCTestCase { XCTAssertEqual(child3.status, .ok) } - func testDeadlineTimer_StartedAndCancelledOnMainThread() { + func testDeadlineTimer_StartedAndCancelledOnMainThread() throws { fixture.dispatchQueue.blockBeforeMainBlock = { true } let sut = fixture.getSut() let child1 = sut.startChild(operation: fixture.transactionOperation) - fixture.timerFactory.fire() + try fixture.timerFactory.fire() XCTAssertEqual(sut.status, .deadlineExceeded) XCTAssertEqual(child1.status, .deadlineExceeded) @@ -315,7 +315,7 @@ class SentryTracerTests: XCTestCase { XCTAssertNil(weakSut, "sut was not deallocated") - fixture.timerFactory.fire() + try fixture.timerFactory.fire() let invalidateTimerBlock = fixture.dispatchQueue.blockOnMainInvocations.last if invalidateTimerBlock != nil { @@ -326,20 +326,20 @@ class SentryTracerTests: XCTestCase { XCTAssertTrue(timer?.isValid ?? false) } - func testDeadlineTimer_WhenCancelling_IsInvalidated() { + func testDeadlineTimer_WhenCancelling_IsInvalidated() throws { fixture.dispatchQueue.blockBeforeMainBlock = { true } let sut = fixture.getSut() let timer: Timer? = Dynamic(sut).deadlineTimer _ = sut.startChild(operation: fixture.transactionOperation) - fixture.timerFactory.fire() + try fixture.timerFactory.fire() XCTAssertNil(Dynamic(sut).deadlineTimer.asObject, "DeadlineTimer should be nil.") XCTAssertFalse(timer?.isValid ?? true) } - func testDeadlineTimer_FiresAfterTracerDeallocated() { + func testDeadlineTimer_FiresAfterTracerDeallocated() throws { fixture.dispatchQueue.blockBeforeMainBlock = { true } // Added internal function so the tracer gets deallocated after executing this function. @@ -348,7 +348,7 @@ class SentryTracerTests: XCTestCase { } startTracer() - fixture.timerFactory.fire() + try fixture.timerFactory.fire() } func testDeadlineTimerForManualTransaction_NoWorkQueuedOnMainQueue() { @@ -378,7 +378,7 @@ class SentryTracerTests: XCTestCase { XCTAssertEqual(1, debugImageProvider.getDebugImagesFromCacheForFramesInvocations.count, "Tracer must retrieve debug images from cache.") } - func testDeadlineTimer_OnlyForAutoTransactions() { + func testDeadlineTimer_OnlyForAutoTransactions() throws { let sut = fixture.getSut(idleTimeout: fixture.idleTimeout) let child1 = sut.startChild(operation: fixture.transactionOperation) let child2 = sut.startChild(operation: fixture.transactionOperation) @@ -386,7 +386,7 @@ class SentryTracerTests: XCTestCase { child3.finish() - fixture.timerFactory.fire() + try fixture.timerFactory.fire() XCTAssertEqual(sut.status, .undefined) XCTAssertEqual(child1.status, .undefined) @@ -398,7 +398,7 @@ class SentryTracerTests: XCTestCase { let sut = fixture.getSut() sut.finish() - XCTAssertFalse(fixture.timerFactory.overrides.timer.isValid) + XCTAssertFalse(try XCTUnwrap(fixture.timerFactory.overrides).timer.isValid) } func testDeadlineTimer_MultipleSpansFinishedInParallel() { @@ -412,10 +412,10 @@ class SentryTracerTests: XCTestCase { SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = fixture.dispatchQueue } - func testFinish_CheckDefaultStatus() { + func testFinish_CheckDefaultStatus() throws { let sut = fixture.getSut() sut.finish() - fixture.timerFactory.fire() + try fixture.timerFactory.fire() XCTAssertEqual(sut.status, .ok) } @@ -1295,11 +1295,11 @@ class SentryTracerTests: XCTestCase { } #endif - func testFinishShouldBeCalled_Timeout_NotCaptured() { + func testFinishShouldBeCalled_Timeout_NotCaptured() throws { fixture.dispatchQueue.blockBeforeMainBlock = { true } let sut = fixture.getSut(finishMustBeCalled: true) - fixture.timerFactory.fire() + try fixture.timerFactory.fire() assertTransactionNotCaptured(sut) } From 40162a5293cbc2d9d1f468b7d90c5ab8ef7ed6fd Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 11 Feb 2025 16:37:29 -0900 Subject: [PATCH 4/8] fix tests --- .../SentryProfilerTests/SentryContinuousProfilerTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift b/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift index f435c803474..00226d96efb 100644 --- a/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift +++ b/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift @@ -118,7 +118,7 @@ final class SentryContinuousProfilerTests: XCTestCase { XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) // assert that the first chunk was sent - fixture.currentDateProvider.advanceBy(interval: kSentryProfilerChunkExpirationInterval) + fixture.currentDateProvider.advanceBy(interval: 60) try fixture.timeoutTimerFactory.check() let envelope = try XCTUnwrap(self.fixture.client?.captureEnvelopeInvocations.last) let profileItem = try XCTUnwrap(envelope.items.first) @@ -163,7 +163,7 @@ private extension SentryContinuousProfilerTests { func addMockSamples(mockAddresses: [NSNumber]) throws { let mockThreadMetadata = SentryProfileTestFixture.ThreadMetadata(id: 1, priority: 2, name: "main") let state = try XCTUnwrap(SentryContinuousProfiler.profiler()?.state) - for _ in 0.. Date: Tue, 11 Feb 2025 17:15:24 -0900 Subject: [PATCH 5/8] maybe fix tracer tests? --- SentryTestUtils/TestSentryNSTimerFactory.swift | 13 ++----------- .../SentryTests/Transaction/SentryTracerTests.swift | 8 +++----- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/SentryTestUtils/TestSentryNSTimerFactory.swift b/SentryTestUtils/TestSentryNSTimerFactory.swift index 0f3813adf94..59e7d231bf2 100644 --- a/SentryTestUtils/TestSentryNSTimerFactory.swift +++ b/SentryTestUtils/TestSentryNSTimerFactory.swift @@ -5,18 +5,9 @@ import Foundation // Therefore we return a NSTimer instance here with TimeInterval.infinity. public class TestSentryNSTimerFactory: SentryNSTimerFactory { public struct Overrides { - private var _timer: Timer? private var _interval: TimeInterval? - public var timer: Timer { - get { - _timer ?? Timer() - } - set(newValue) { - _timer = newValue - } - } - + public var timer: Timer var block: ((Timer) -> Void)? var interval: TimeInterval { @@ -37,7 +28,7 @@ public class TestSentryNSTimerFactory: SentryNSTimerFactory { var invocationInfo: InvocationInfo? init(timer: Timer, interval: TimeInterval? = nil, block: ((Timer) -> Void)? = nil, lastFireDate: Date, invocationInfo: InvocationInfo? = nil) { - self._timer = timer + self.timer = timer self._interval = interval self.block = block self.lastFireDate = lastFireDate diff --git a/Tests/SentryTests/Transaction/SentryTracerTests.swift b/Tests/SentryTests/Transaction/SentryTracerTests.swift index 1322cbd69af..5faf1aabe6e 100644 --- a/Tests/SentryTests/Transaction/SentryTracerTests.swift +++ b/Tests/SentryTests/Transaction/SentryTracerTests.swift @@ -52,8 +52,6 @@ class SentryTracerTests: XCTestCase { #endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) init() { - dispatchQueue.blockBeforeMainBlock = { false } - SentryDependencyContainer.sharedInstance().dateProvider = currentDateProvider SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = dispatchQueue @@ -388,9 +386,9 @@ class SentryTracerTests: XCTestCase { try fixture.timerFactory.fire() - XCTAssertEqual(sut.status, .undefined) - XCTAssertEqual(child1.status, .undefined) - XCTAssertEqual(child2.status, .undefined) + XCTAssertEqual(sut.status, .deadlineExceeded) + XCTAssertEqual(child1.status, .deadlineExceeded) + XCTAssertEqual(child2.status, .deadlineExceeded) XCTAssertEqual(child3.status, .ok) } From e36a8ce8a3c052772566884d9e8bc48b02c1214a Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 12 Feb 2025 11:46:34 +0100 Subject: [PATCH 6/8] fix tracer tests --- .../TestSentryNSTimerFactory.swift | 4 ++++ .../Transaction/SentryTracerTests.swift | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/SentryTestUtils/TestSentryNSTimerFactory.swift b/SentryTestUtils/TestSentryNSTimerFactory.swift index 59e7d231bf2..5cfc4ec2420 100644 --- a/SentryTestUtils/TestSentryNSTimerFactory.swift +++ b/SentryTestUtils/TestSentryNSTimerFactory.swift @@ -89,4 +89,8 @@ public extension TestSentryNSTimerFactory { try Invocation(target: invocationInfo.target, selector: invocationInfo.selector).invoke() } } + + func isTimerInitialized() -> Bool { + return overrides != nil + } } diff --git a/Tests/SentryTests/Transaction/SentryTracerTests.swift b/Tests/SentryTests/Transaction/SentryTracerTests.swift index 5faf1aabe6e..b2bf0b56ffa 100644 --- a/Tests/SentryTests/Transaction/SentryTracerTests.swift +++ b/Tests/SentryTests/Transaction/SentryTracerTests.swift @@ -376,7 +376,7 @@ class SentryTracerTests: XCTestCase { XCTAssertEqual(1, debugImageProvider.getDebugImagesFromCacheForFramesInvocations.count, "Tracer must retrieve debug images from cache.") } - func testDeadlineTimer_OnlyForAutoTransactions() throws { + func testDeadlineTimer_ForAutoTransaction_FinishesChildSpans() throws { let sut = fixture.getSut(idleTimeout: fixture.idleTimeout) let child1 = sut.startChild(operation: fixture.transactionOperation) let child2 = sut.startChild(operation: fixture.transactionOperation) @@ -384,6 +384,7 @@ class SentryTracerTests: XCTestCase { child3.finish() + XCTAssertTrue(fixture.timerFactory.isTimerInitialized()) try fixture.timerFactory.fire() XCTAssertEqual(sut.status, .deadlineExceeded) @@ -391,6 +392,22 @@ class SentryTracerTests: XCTestCase { XCTAssertEqual(child2.status, .deadlineExceeded) XCTAssertEqual(child3.status, .ok) } + + func testDeadlineTimer_ForManualTransactions_DoesNotFinishChildSpans() throws { + let sut = fixture.getSut(waitForChildren: false) + let child1 = sut.startChild(operation: fixture.transactionOperation) + let child2 = sut.startChild(operation: fixture.transactionOperation) + let child3 = sut.startChild(operation: fixture.transactionOperation) + + child3.finish() + + XCTAssertFalse(fixture.timerFactory.isTimerInitialized()) + + XCTAssertEqual(sut.status, .undefined) + XCTAssertEqual(child1.status, .undefined) + XCTAssertEqual(child2.status, .undefined) + XCTAssertEqual(child3.status, .ok) + } func testDeadlineTimer_Finish_Cancels_Timer() { let sut = fixture.getSut() From 3ab75ee75a65c6810ccb94e2f3a82a2c72df4c6b Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 12 Feb 2025 11:50:28 +0100 Subject: [PATCH 7/8] remove blockBeforeMainBlock for tracertests --- Tests/SentryTests/Transaction/SentryTracerTests.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Tests/SentryTests/Transaction/SentryTracerTests.swift b/Tests/SentryTests/Transaction/SentryTracerTests.swift index b2bf0b56ffa..ddbab64e33f 100644 --- a/Tests/SentryTests/Transaction/SentryTracerTests.swift +++ b/Tests/SentryTests/Transaction/SentryTracerTests.swift @@ -243,7 +243,6 @@ class SentryTracerTests: XCTestCase { } func testDeadlineTimer_FinishesTransactionAndChildren() throws { - fixture.dispatchQueue.blockBeforeMainBlock = { true } let sut = fixture.getSut() let child1 = sut.startChild(operation: fixture.transactionOperation) @@ -263,8 +262,6 @@ class SentryTracerTests: XCTestCase { } func testDeadlineTimer_StartedAndCancelledOnMainThread() throws { - fixture.dispatchQueue.blockBeforeMainBlock = { true } - let sut = fixture.getSut() let child1 = sut.startChild(operation: fixture.transactionOperation) @@ -325,8 +322,6 @@ class SentryTracerTests: XCTestCase { } func testDeadlineTimer_WhenCancelling_IsInvalidated() throws { - fixture.dispatchQueue.blockBeforeMainBlock = { true } - let sut = fixture.getSut() let timer: Timer? = Dynamic(sut).deadlineTimer _ = sut.startChild(operation: fixture.transactionOperation) @@ -338,8 +333,6 @@ class SentryTracerTests: XCTestCase { } func testDeadlineTimer_FiresAfterTracerDeallocated() throws { - fixture.dispatchQueue.blockBeforeMainBlock = { true } - // Added internal function so the tracer gets deallocated after executing this function. func startTracer() { _ = fixture.getSut() @@ -1311,8 +1304,6 @@ class SentryTracerTests: XCTestCase { #endif func testFinishShouldBeCalled_Timeout_NotCaptured() throws { - fixture.dispatchQueue.blockBeforeMainBlock = { true } - let sut = fixture.getSut(finishMustBeCalled: true) try fixture.timerFactory.fire() assertTransactionNotCaptured(sut) @@ -1360,8 +1351,6 @@ class SentryTracerTests: XCTestCase { } func testFinishForCrash_DoesNotCancelDeadlineTimer() throws { - fixture.dispatchQueue.blockBeforeMainBlock = { true } - let sut = fixture.getSut() _ = sut.startChild(operation: fixture.transactionOperation) let timer = try XCTUnwrap(Dynamic(sut).deadlineTimer.asObject as? Timer) From 141ad1b38c8b0756450c403c4fd22a5682b8c049 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 12 Feb 2025 14:47:27 -0900 Subject: [PATCH 8/8] add constant tests --- .../SentryProfilerTests/SentryContinuousProfilerTests.swift | 6 +++++- Tests/SentryProfilerTests/SentryTraceProfilerTests.swift | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift b/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift index 00226d96efb..ea2f7d5e713 100644 --- a/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift +++ b/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift @@ -23,7 +23,11 @@ final class SentryContinuousProfilerTests: XCTestCase { super.tearDown() clearTestState() } - + + func testSentryProfilerChunkExpirationInterval() { + XCTAssertEqual(60, kSentryProfilerChunkExpirationInterval) + } + func testStartingAndStoppingContinuousProfiler() throws { try performContinuousProfilingTest() } diff --git a/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift b/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift index 1bba7f57c5f..3a1277feb6c 100644 --- a/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift +++ b/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift @@ -23,6 +23,10 @@ class SentryTraceProfilerTests: XCTestCase { clearTestState() } + func testSentryProfilerTimoutInterval() { + XCTAssertEqual(30, kSentryProfilerTimeoutInterval) + } + func testMetricProfiler() throws { let span = try fixture.newTransaction() try addMockSamples()