Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Don't capture replays for events dropped in `beforeSend` (#5916)
- Fix linking with SentrySwiftUI on Xcode 26 for visionOS (#5823)
- Structured Logging: Logger called before `SentrySDK.start` becomes unusable (#5984)
- Fix missing view hierachy when enabling `attachScreenshot` too (#5989)

### Improvements

Expand Down
9 changes: 8 additions & 1 deletion Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,14 @@ - (void)removeAttachmentProcessor:(id<SentryClientAttachmentProcessor>)attachmen
NSArray<SentryAttachment *> *processedAttachments = attachments;

for (id<SentryClientAttachmentProcessor> attachmentProcessor in self.attachmentProcessors) {
processedAttachments = [attachmentProcessor processAttachments:attachments forEvent:event];
// Keep chaining the processed attachments so each processor works on the output of the
// previous one. This is necessary so each processor can add and remove attachments.
//
// Important: This means the order of adding processors matters and relies on the
// initialization order of the integrations. At this point in time the attachment processors
// are only adding attachments, therefore we can ignore this restriction for now.
processedAttachments = [attachmentProcessor processAttachments:processedAttachments
forEvent:event];
}

return processedAttachments;
Expand Down
6 changes: 2 additions & 4 deletions Sources/Sentry/SentryScreenshotIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,11 @@ - (void)uninstall
return attachments;
}

NSMutableArray *result = [NSMutableArray arrayWithArray:attachments];

NSArray<NSData *> *screenshot =
[SentryDependencyContainer.sharedInstance.screenshot appScreenshotDatasFromMainThread];

NSMutableArray *result =
[NSMutableArray arrayWithCapacity:attachments.count + screenshot.count];
[result addObjectsFromArray:attachments];

for (int i = 0; i < screenshot.count; i++) {
NSString *name
= i == 0 ? @"screenshot.png" : [NSString stringWithFormat:@"screenshot-%i.png", i + 1];
Expand Down
90 changes: 80 additions & 10 deletions Tests/SentryTests/SentryClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import XCTest
// We are aware that the client has a lot of logic and we should maybe
// move some of it to other classes.
@available(*, deprecated, message: "This is deprecated because SentryOptions integrations is deprecated")
class SentryClientTest: XCTestCase {
class SentryClientTests: XCTestCase {

private static let dsn = TestConstants.dsnAsString(username: "SentryClientTest")

Expand Down Expand Up @@ -56,7 +56,7 @@ class SentryClientTest: XCTestCase {
user.ipAddress = "127.0.0.1"

let options = Options()
options.dsn = SentryClientTest.dsn
options.dsn = SentryClientTests.dsn
fileManager = try XCTUnwrap(SentryFileManager(options: options, dispatchQueueWrapper: TestSentryDispatchQueueWrapper()))

transaction = Transaction(trace: trace, children: [])
Expand All @@ -81,7 +81,7 @@ class SentryClientTest: XCTestCase {
var client: SentryClient!
do {
let options = try SentryOptionsInternal.initWithDict([
"dsn": SentryClientTest.dsn
"dsn": SentryClientTests.dsn
])
options.removeAllIntegrations()
configureOptions(options)
Expand Down Expand Up @@ -167,7 +167,7 @@ class SentryClientTest: XCTestCase {
SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = fixture.dispatchQueue

let options = Options()
options.dsn = SentryClientTest.dsn
options.dsn = SentryClientTests.dsn
// We have to put our cache into a subfolder of the default path, because on macOS we can't delete the default cache folder
options.cacheDirectoryPath = "\(options.cacheDirectoryPath)/cache"
_ = SentryClient(options: options)
Expand Down Expand Up @@ -475,6 +475,74 @@ class SentryClientTest: XCTestCase {
XCTAssertEqual(sentAttachments.count, 1)
XCTAssertEqual(extraAttachment, sentAttachments.first)
}

func test_AttachmentProcessors_Chained_Additive() {
// -- Arrange --
let sut = fixture.getSut()
let event = Event()
let att1 = Attachment(data: Data("one".utf8), filename: "AttachmentOne")
let att2 = Attachment(data: Data("two".utf8), filename: "AttachmentTwo")

let p1 = TestAttachmentProcessor { atts, _ in
var out = atts
out.append(att1)
return out
}

let p2 = TestAttachmentProcessor { atts, _ in
var out = atts
out.append(att2)
return out
}

// Order matters; second sees the output of the first.
sut.add(p1)
sut.add(p2)

// -- Act --
sut.capture(event: event)

// -- Assert --
let sentAttachments = fixture.transportAdapter.sendEventWithTraceStateInvocations.first?.attachments ?? []
XCTAssertEqual(sentAttachments.count, 2)
XCTAssertEqual(sentAttachments.element(at: 0), att1)
XCTAssertEqual(sentAttachments.element(at: 1), att2)
}

func test_AttachmentProcessors_Chained_RemovalThenAdd() {
// -- Arrange --
let sut = fixture.getSut()
let event = Event()

// Start with one attachment from the scope so the removal has an effect.
let initial = Attachment(data: Data("init".utf8), filename: "Initial")
let scope = Scope()
scope.addAttachment(initial)

// First processor removes everything.
let remover = TestAttachmentProcessor { _, _ in
return []
}

// Second processor appends a new one; should not see the removed initial.
let added = Attachment(data: Data("new".utf8), filename: "AddedAfterRemoval")
let adder = TestAttachmentProcessor { atts, _ in
var out = atts
out.append(added)
return out
}

sut.add(remover)
sut.add(adder)

// -- Act --
sut.capture(event: event, scope: scope)

// -- Assert --
let sentAttachments = fixture.transportAdapter.sendEventWithTraceStateInvocations.first?.attachments ?? []
XCTAssertEqual(sentAttachments.count, 1)
XCTAssertEqual(sentAttachments.first, added)
}

func testCaptureEventWithDsnSetAfterwards() {
let event = Event()
Expand All @@ -483,7 +551,7 @@ class SentryClientTest: XCTestCase {
options.dsn = nil
})

sut.options.dsn = SentryClientTest.dsn
sut.options.dsn = SentryClientTests.dsn

let eventId = sut.capture(event: event)
eventId.assertIsNotEmpty()
Expand Down Expand Up @@ -1737,7 +1805,7 @@ class SentryClientTest: XCTestCase {
SentryFileManager.prepareInitError()

let options = Options()
options.dsn = SentryClientTest.dsn
options.dsn = SentryClientTests.dsn
let client = SentryClient(options: options, dispatchQueue: TestSentryDispatchQueueWrapper(), deleteOldEnvelopeItems: false)

XCTAssertNil(client)
Expand Down Expand Up @@ -2180,8 +2248,8 @@ class SentryClientTest: XCTestCase {
XCTAssertEqual(0, fixture.transport.sentEnvelopes.count)
}

#if os(macOS)
func testCaptureSentryWrappedException() throws {
#if os(macOS)
let exception = NSException(name: NSExceptionName("exception"), reason: "reason", userInfo: nil)
// If we don't raise the exception, it won't have the callStack data
let raisedException = ExceptionCatcher.try {
Expand All @@ -2202,20 +2270,22 @@ class SentryClientTest: XCTestCase {
// Make sure the stacktrace is not empty
XCTAssertGreaterThan(actual.threads?[0].stacktrace?.frames.count ?? 0, 1)
// We will need to update it if the test class / module changes
let testMangledName = "$s11SentryTests0A10ClientTestC011testCaptureA16WrappedExceptionyyKF"
let testMangledName = "$s11SentryTests0a6ClientB0C011testCaptureA16WrappedExceptionyyKF"
let frameWithTestFunction = actual.threads?[0].stacktrace?.frames.first { frame in
frame.function == testMangledName
}
XCTAssertNotNil(frameWithTestFunction, "Mangled name for testCaptureSentryWrappedException not found in stacktrace")

// Last frame should always be `__exceptionPreprocess`
XCTAssertEqual(actual.threads?[0].stacktrace?.frames.last?.function, "__exceptionPreprocess")
#else
throw XCTSkip("Test is disabled for this OS version")
#endif // os(macOS)
}
#endif // os(macOS)
}

@available(*, deprecated, message: "This is deprecated because SentryOptions integrations is deprecated")
private extension SentryClientTest {
private extension SentryClientTests {
private func givenEventWithDebugMeta() -> Event {
let event = Event(level: SentryLevel.fatal)
let debugMeta = DebugMeta()
Expand Down
Loading