Skip to content

Commit d72784d

Browse files
authored
chore: Add tests for SentryCrashContext (#6373)
* chore: Add tests for SentryCrashContext * Wait for threads to be finished before ending the test * Wait for thread to end with an expectation * Bump timeout values
1 parent f8687d1 commit d72784d

File tree

5 files changed

+147
-1
lines changed

5 files changed

+147
-1
lines changed

Sentry.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@
10161016
F4DCC9E52E4AACE0008ECE45 /* SentrySDKSettings+Equality.m in Sources */ = {isa = PBXBuildFile; fileRef = F4DCC9E42E4AACE0008ECE45 /* SentrySDKSettings+Equality.m */; };
10171017
F4E1E9812E8C2B150007B080 /* SentryDateUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E1E9802E8C2B150007B080 /* SentryDateUtil.swift */; };
10181018
F4E3DCCB2E1579240093CB80 /* SentryScopePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E3DCCA2E1579240093CB80 /* SentryScopePersistentStore.swift */; };
1019+
F4EF69232E95ABE800B6B46A /* SentryCrashMachineContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F4EF69222E95ABE800B6B46A /* SentryCrashMachineContextTests.m */; };
10191020
F4FE9DBD2E621F100014FED5 /* SentryRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FE9DBC2E621F100014FED5 /* SentryRandom.swift */; };
10201021
F4FE9DFD2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FE9DFB2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift */; };
10211022
F4FE9DFE2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FE9DFC2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift */; };
@@ -2358,6 +2359,7 @@
23582359
F4DCC9E42E4AACE0008ECE45 /* SentrySDKSettings+Equality.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentrySDKSettings+Equality.m"; sourceTree = "<group>"; };
23592360
F4E1E9802E8C2B150007B080 /* SentryDateUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDateUtil.swift; sourceTree = "<group>"; };
23602361
F4E3DCCA2E1579240093CB80 /* SentryScopePersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryScopePersistentStore.swift; sourceTree = "<group>"; };
2362+
F4EF69222E95ABE800B6B46A /* SentryCrashMachineContextTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashMachineContextTests.m; sourceTree = "<group>"; };
23612363
F4FE9DBC2E621F100014FED5 /* SentryRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRandom.swift; sourceTree = "<group>"; };
23622364
F4FE9DFB2E622CD70014FED5 /* SentryDefaultObjCRuntimeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDefaultObjCRuntimeWrapper.swift; sourceTree = "<group>"; };
23632365
F4FE9DFC2E622CD70014FED5 /* SentryObjCRuntimeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryObjCRuntimeWrapper.swift; sourceTree = "<group>"; };
@@ -3315,6 +3317,7 @@
33153317
621655652DB12A8900810504 /* SentryCrashMach-OTests.m */,
33163318
628B45332DB8D5E700934391 /* SentryCrashCxaThrowSwapper_Tests.mm */,
33173319
629258542DAF91940049388F /* SentryCrashStackCursorSelfThreadTests.swift */,
3320+
F4EF69222E95ABE800B6B46A /* SentryCrashMachineContextTests.m */,
33183321
);
33193322
path = SentryCrash;
33203323
sourceTree = "<group>";
@@ -6203,6 +6206,7 @@
62036206
F4AACD612E01ACE800DDDD1E /* SentryCrashDynamicLinkerTests.m in Sources */,
62046207
7B6438A726A70DDB000D0F65 /* UIViewControllerSentryTests.swift in Sources */,
62056208
D8BC28D62C00C6DF0054DA4D /* StringExtensionsTests.swift in Sources */,
6209+
F4EF69232E95ABE800B6B46A /* SentryCrashMachineContextTests.m in Sources */,
62066210
15E0A8F0240F638200F044E3 /* SentrySerializationNilTests.m in Sources */,
62076211
0A2D8D8728992260008720F6 /* SentryBaseIntegrationTests.swift in Sources */,
62086212
7B6D135C27F4605D00331ED2 /* TestEnvelopeRateLimitDelegate.swift in Sources */,

Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext_Apple.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ extern "C" {
4343
# define STRUCT_MCONTEXT_L _STRUCT_MCONTEXT
4444
#endif
4545

46+
#define SENTRY_CRASH_MAX_THREADS 100
47+
4648
typedef struct SentryCrashMachineContext {
4749
thread_t thisThread;
48-
thread_t allThreads[100];
50+
thread_t allThreads[SENTRY_CRASH_MAX_THREADS];
4951
int threadCount;
5052
bool isCrashedContext;
5153
bool isCurrentThread;
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#import <XCTest/XCTest.h>
2+
3+
#import "SentryCrashMachineContext.h"
4+
#import "SentryCrashMachineContext_Apple.h"
5+
#import "TestThread.h"
6+
#import <mach/mach.h>
7+
8+
@interface SentryCrashMachineContextTests : XCTestCase
9+
@end
10+
11+
@implementation SentryCrashMachineContextTests
12+
13+
- (void)testGetContextForThread_NonCrashedContext_DoesNotPopulateThreadList
14+
{
15+
// Create a test thread
16+
NSObject *notificationObject = [[NSObject alloc] init];
17+
TestThread *thread = [[TestThread alloc] init];
18+
thread.notificationObject = notificationObject;
19+
20+
XCTestExpectation *exp = [self expectationWithDescription:@"thread started"];
21+
[NSNotificationCenter.defaultCenter
22+
addObserverForName:@"io.sentry.test.TestThread.main"
23+
object:notificationObject
24+
queue:nil
25+
usingBlock:^(NSNotification *_Nonnull __unused notification) {
26+
[NSNotificationCenter.defaultCenter
27+
removeObserver:self
28+
name:@"io.sentry.test.TestThread.main"
29+
object:notificationObject];
30+
[exp fulfill];
31+
}];
32+
33+
[thread start];
34+
[self waitForExpectationsWithTimeout:5 handler:nil];
35+
36+
kern_return_t kr;
37+
kr = thread_suspend(thread.thread);
38+
XCTAssertTrue(kr == KERN_SUCCESS, @"Thread suspension failed");
39+
40+
// Get context for a non-crashed thread
41+
SentryCrashMC_NEW_CONTEXT(machineContext);
42+
bool result = sentrycrashmc_getContextForThread(thread.thread, machineContext, NO);
43+
44+
XCTAssertTrue(result, @"Failed to get context for thread");
45+
XCTAssertFalse(
46+
sentrycrashmc_isCrashedContext(machineContext), @"Should not be marked as crashed context");
47+
48+
// For non-crashed contexts, thread list should not be populated (will be 0)
49+
int threadCount = sentrycrashmc_getThreadCount(machineContext);
50+
XCTAssertEqual(
51+
threadCount, 0, @"Thread count should be 0 for non-crashed context, got %d", threadCount);
52+
53+
thread_resume(thread.thread);
54+
XCTestExpectation *expectation =
55+
[[XCTestExpectation alloc] initWithDescription:@"Wait for thread to cancel"];
56+
thread.endExpectation = expectation;
57+
[thread cancel];
58+
[self waitForExpectations:@[ expectation ] timeout:5];
59+
}
60+
61+
- (void)testGetContextForThread_WithManyThreads
62+
{
63+
NSInteger numberOfThreads = 10;
64+
NSMutableArray<TestThread *> *threads = [NSMutableArray arrayWithCapacity:numberOfThreads];
65+
NSMutableArray<XCTestExpectation *> *expectations =
66+
[NSMutableArray arrayWithCapacity:numberOfThreads];
67+
68+
for (int i = 0; i < numberOfThreads; i++) {
69+
NSObject *notificationObject = [[NSObject alloc] init];
70+
TestThread *thread = [[TestThread alloc] init];
71+
thread.notificationObject = notificationObject;
72+
73+
XCTestExpectation *exp =
74+
[self expectationWithDescription:[NSString stringWithFormat:@"thread %d started", i]];
75+
[expectations addObject:exp];
76+
77+
[NSNotificationCenter.defaultCenter
78+
addObserverForName:@"io.sentry.test.TestThread.main"
79+
object:notificationObject
80+
queue:nil
81+
usingBlock:^(NSNotification *_Nonnull __unused notification) {
82+
[NSNotificationCenter.defaultCenter
83+
removeObserver:self
84+
name:@"io.sentry.test.TestThread.main"
85+
object:notificationObject];
86+
[exp fulfill];
87+
}];
88+
89+
[threads addObject:thread];
90+
[thread start];
91+
}
92+
93+
[self waitForExpectations:expectations timeout:5];
94+
95+
// Suspend the first thread and get its context
96+
TestThread *firstThread = threads[0];
97+
kern_return_t kr = thread_suspend(firstThread.thread);
98+
XCTAssertTrue(kr == KERN_SUCCESS, @"Thread suspension failed");
99+
100+
// Get context for the crashed thread
101+
SentryCrashMC_NEW_CONTEXT(machineContext);
102+
bool result = sentrycrashmc_getContextForThread(firstThread.thread, machineContext, YES);
103+
104+
XCTAssertTrue(result, @"Failed to get context for thread");
105+
106+
// Verify that thread list includes all our test threads
107+
int threadCount = sentrycrashmc_getThreadCount(machineContext);
108+
XCTAssertTrue(
109+
threadCount >= 10, @"Thread count should include all test threads, got %d", threadCount);
110+
XCTAssertTrue(threadCount <= SENTRY_CRASH_MAX_THREADS,
111+
@"Thread count should not exceed maximum of SENTRY_CRASH_MAX_THREADS, got %d", threadCount);
112+
113+
// Verify that all our threads are in the list
114+
for (TestThread *thread in threads) {
115+
int threadIndex = sentrycrashmc_indexOfThread(machineContext, thread.thread);
116+
XCTAssertTrue(threadIndex >= 0, @"Thread should be found in the list");
117+
}
118+
119+
// Clean up
120+
thread_resume(firstThread.thread);
121+
NSMutableArray<XCTestExpectation *> *finishExpectations =
122+
[NSMutableArray arrayWithCapacity:threads.count];
123+
for (TestThread *thread in threads) {
124+
thread.endExpectation =
125+
[[XCTestExpectation alloc] initWithDescription:@"Wait for thread to cancel"];
126+
[finishExpectations addObject:thread.endExpectation];
127+
[thread cancel];
128+
}
129+
130+
// Wait for all threads to finish (up to 10 seconds)
131+
[self waitForExpectations:finishExpectations timeout:10];
132+
}
133+
134+
@end

Tests/SentryTests/SentryCrash/TestThread.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
//
2727

2828
#import <Foundation/Foundation.h>
29+
#import <XCTest/XCTestExpectation.h>
2930
#import <mach/mach_types.h>
3031

3132
@interface TestThread : NSThread
3233

3334
@property (nonatomic, readwrite, assign) thread_t thread;
3435
@property (nonatomic, strong) NSObject *notificationObject;
36+
@property (nonatomic, strong) XCTestExpectation *endExpectation;
3537

3638
@end

Tests/SentryTests/SentryCrash/TestThread.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ - (void)main SENTRY_DISABLE_THREAD_SANITIZER("Known data race to fix")
4141
while (!self.isCancelled) {
4242
[[self class] sleepForTimeInterval:0.1];
4343
}
44+
45+
if (self.endExpectation) {
46+
[self.endExpectation fulfill];
47+
}
4448
}
4549

4650
@end

0 commit comments

Comments
 (0)