Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ import Foundation
}
#endif // (os(iOS) || os(tvOS)) && !SENTRY_NO_UI_FRAMEWORK

if options.featureFlagsUsed {
features.append("featureFlags")
}

return features
}
// swiftlint:enable cyclomatic_complexity function_body_length
Expand Down
14 changes: 14 additions & 0 deletions Sources/Swift/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,20 @@
/// Use this callback to drop or modify a metric before the SDK sends it to Sentry. Return nil to
/// drop the metric.
public var beforeSendMetric: ((SentryMetric) -> SentryMetric?)?

// MARK: - Internal

// Feature flag usage is only known after one of its APIs is used, so track it for inclusion in
// the SDK metadata built afterward.
private let _featureFlagsUsed = SentryMutex(false)

func markFeatureFlagsUsed() {
_featureFlagsUsed.withLock { $0 = true }
}

var featureFlagsUsed: Bool {
_featureFlagsUsed.withLock { $0 }
}
}

extension NSNumber {
Expand Down
1 change: 1 addition & 0 deletions Sources/Swift/Scope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ internal import _SentryPrivate
// Feature flag APIs live in this file so the public Scope API has a clear home.
extension Scope {
@nonobjc public func addFeatureFlag(name: String, result: Bool) {
SentryDependencyContainer.sharedInstance().startOptions?.markFeatureFlagsUsed()
addFeatureFlagInternal(name: name, result: result)
}

Expand Down
1 change: 1 addition & 0 deletions Sources/Swift/Span.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extension Span {
guard let span = self as? SentrySpanInternal else {
return
}
SentryDependencyContainer.sharedInstance().startOptions?.markFeatureFlagsUsed()
span.addFeatureFlagInternal(name: name, result: result)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@testable import Sentry
import XCTest

final class SentryOptionsFeatureFlagsUsageTests: XCTestCase {

func testFeatureFlagsUsed_whenInitialized_shouldBeFalse() {
XCTAssertFalse(Options().featureFlagsUsed)
}

func testMarkFeatureFlagsUsed_whenCalled_shouldSetFeatureFlagsUsed() {
let sut = Options()

sut.markFeatureFlagsUsed()

XCTAssertTrue(sut.featureFlagsUsed)
}

func testFeatureFlagsUsed_whenReadAndWrittenConcurrently_shouldRemainTrue() {
let sut = Options()
let queue = DispatchQueue(label: "SentryOptionsFeatureFlagsUsageTests", attributes: .concurrent)
let completed = expectation(description: "Concurrent feature flag usage access completed")
completed.expectedFulfillmentCount = 200
completed.assertForOverFulfill = true

for _ in 0..<100 {
queue.async {
sut.markFeatureFlagsUsed()
completed.fulfill()
}
queue.async {
_ = sut.featureFlagsUsed
completed.fulfill()
}
}

wait(for: [completed], timeout: 5)
XCTAssertTrue(sut.featureFlagsUsed)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,14 @@ final class SentryEnabledFeaturesBuilderTests: XCTestCase {
throw XCTSkip("Test not supported on this platform")
#endif
}

func testGetEnabledFeatures_whenFeatureFlagsUsed_shouldIncludeFeatureFlags() {
let options = Options()
options.markFeatureFlagsUsed()

let features = SentryEnabledFeaturesBuilder.getEnabledFeatures(options: options)

XCTAssertTrue(features.contains("featureFlags"))
}

}
13 changes: 13 additions & 0 deletions Tests/SentryTests/Protocol/SentryEnvelopeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ class SentryEnvelopeTests: XCTestCase {
func testInitSentryEnvelopeHeader_DefaultSdkInfoIsSet() {
XCTAssertEqual(defaultSdkInfo, SentryEnvelopeHeader(id: nil).sdkInfo)
}

func testInitSentryEnvelopeHeader_whenFeatureFlagAdded_shouldIncludeFeatureFlagSdkFeature() throws {
let options = Options()
options.dsn = TestConstants.dsnAsString(username: "SentryEnvelopeTests")
SentrySDK.start(options: options)
let featuresBeforeUse = try XCTUnwrap(SentryEnvelopeHeader(id: nil).sdkInfo?.features)
XCTAssertFalse(featuresBeforeUse.contains("featureFlags"))

SentrySDK.addFeatureFlag(name: "checkout", result: true)

let featuresAfterUse = try XCTUnwrap(SentryEnvelopeHeader(id: nil).sdkInfo?.features)
XCTAssertTrue(featuresAfterUse.contains("featureFlags"))
}

func testInitSentryEnvelopeHeader_IdAndSkInfoNil() {
let allNil = SentryEnvelopeHeader(id: nil, sdkInfo: nil, traceContext: nil)
Expand Down
12 changes: 11 additions & 1 deletion Tests/SentryTests/SentryClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2178,7 +2178,17 @@ final class SentryClientTests: XCTestCase {
let features = try XCTUnwrap(actual.sdk?["features"] as? [String])
XCTAssertTrue(features.contains("captureFailedRequests"))
}


func testCaptureEvent_whenFeatureFlagNotUsed_shouldOmitFeatureFlagSdkFeature() throws {
let sut = fixture.getSut()

sut.capture(message: "message")

let actual = try lastSentEvent()
let features = try XCTUnwrap(actual.sdk?["features"] as? [String])
XCTAssertFalse(features.contains("featureFlags"))
}

func testFileManagerCantBeInit() throws {
try SentryFileManager.prepareInitError()
defer {
Expand Down
9 changes: 9 additions & 0 deletions Tests/SentryTests/SentryHubTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,15 @@ class SentryHubTests: XCTestCase {
XCTAssertEqual(values.element(at: 0)?["result"] as? Bool, true)
}

func testAddFeatureFlag_shouldMarkSdkOptions() {
SentrySDK.setStart(with: fixture.options)
let hub = SentryHub(client: SentryClient(helper: fixture.client), andScope: Scope())

hub.addFeatureFlag(name: "checkout", result: true)

XCTAssertTrue(fixture.options.featureFlagsUsed)
}

func testBreadcrumbOverDefaultLimit() {
let hub = fixture.getSut(withMaxBreadcrumbs: 200)

Expand Down
14 changes: 14 additions & 0 deletions Tests/SentryTests/SentrySDKTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ class SentrySDKTests: XCTestCase {
let breadcrumbs = Dynamic(SentrySDKInternal.currentHub().scope).breadcrumbArray as [Breadcrumb]?
XCTAssertEqual(0, breadcrumbs?.count)
}

func testStart_whenInitialScopeAddsFeatureFlag_shouldMarkOptions() {
let options = Options.noIntegrations()
options.dsn = Self.dsnAsString
options.initialScope = { scope in
scope.addFeatureFlag(name: "checkout", result: true)
return scope
}

SentrySDK.start(options: options)

XCTAssertTrue(options.featureFlagsUsed)
}

func testStartWithConfigureOptions() {
SentrySDK.start { options in
Expand Down Expand Up @@ -544,6 +557,7 @@ class SentrySDKTests: XCTestCase {
XCTAssertEqual(values.count, 1)
XCTAssertEqual(values.element(at: 0)?["flag"] as? String, "checkout")
XCTAssertEqual(values.element(at: 0)?["result"] as? Bool, true)
XCTAssertTrue(fixture.options.featureFlagsUsed)
}

/// When events don't have debug meta the backend can't symbolicate the stack trace of events.
Expand Down
Loading