Skip to content

Commit 6c73108

Browse files
LeoNatannoomorph
andcommitted
feat: Detox Instruments Integration (Phase 1) (#1165)
Co-authored-by: Yaroslav Serhieiev <yaroslavs@wix.com>
1 parent 34b5691 commit 6c73108

26 files changed

Lines changed: 586 additions & 54 deletions

detox/ios/Detox.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
39BCE599224BDBF000B0D357 /* ReactNativeSupportNoARC.m in Sources */ = {isa = PBXBuildFile; fileRef = 39BCE597224BDBF000B0D357 /* ReactNativeSupportNoARC.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
6666
39C3C3511DBF9A13008177E1 /* EarlGrey.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 394767DC1DBF991E00D72256 /* EarlGrey.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
6767
39C3C3531DBF9A19008177E1 /* SocketRocket.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 394767E91DBF992400D72256 /* SocketRocket.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
68+
39CE25AB22197F0900D78AA1 /* DetoxInstrumentsManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 39CE25A922197F0900D78AA1 /* DetoxInstrumentsManager.h */; };
69+
39CE25AC22197F0900D78AA1 /* DetoxInstrumentsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 39CE25AA22197F0900D78AA1 /* DetoxInstrumentsManager.m */; };
6870
39CEFCDB1E34E91B00A09124 /* DetoxUserNotificationDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CEFCDA1E34E91B00A09124 /* DetoxUserNotificationDispatcher.swift */; };
6971
39F642281FDD5EB100468FED /* DTXLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = 39F642201FDD5EB000468FED /* DTXLogging.h */; };
7072
39F642291FDD5EB100468FED /* DTXLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 39F642271FDD5EB000468FED /* DTXLogging.m */; };
@@ -240,6 +242,8 @@
240242
39A34C6F1E30F10D00BEBB59 /* DetoxAppDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetoxAppDelegateProxy.h; sourceTree = "<group>"; };
241243
39A34C701E30F10D00BEBB59 /* DetoxAppDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetoxAppDelegateProxy.m; sourceTree = "<group>"; };
242244
39AB2D30205ABBD90029CD1F /* DetoxUserActivityDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetoxUserActivityDispatcher.swift; sourceTree = "<group>"; };
245+
39CE25A922197F0900D78AA1 /* DetoxInstrumentsManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DetoxInstrumentsManager.h; sourceTree = "<group>"; };
246+
39CE25AA22197F0900D78AA1 /* DetoxInstrumentsManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DetoxInstrumentsManager.m; sourceTree = "<group>"; };
243247
39BCE596224BDBF000B0D357 /* ReactNativeSupportNoARC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactNativeSupportNoARC.h; sourceTree = "<group>"; };
244248
39BCE597224BDBF000B0D357 /* ReactNativeSupportNoARC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactNativeSupportNoARC.m; sourceTree = "<group>"; };
245249
39CEFCDA1E34E91B00A09124 /* DetoxUserNotificationDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetoxUserNotificationDispatcher.swift; sourceTree = "<group>"; };
@@ -382,6 +386,8 @@
382386
39F6422A1FDD5EEC00468FED /* Detox.pch */,
383387
399BF36621933F0C00F96D50 /* ExternalLogging.h */,
384388
399BF36721933F0C00F96D50 /* ExternalLogging.m */,
389+
39CE25A922197F0900D78AA1 /* DetoxInstrumentsManager.h */,
390+
39CE25AA22197F0900D78AA1 /* DetoxInstrumentsManager.m */,
385391
);
386392
path = Detox;
387393
sourceTree = "<group>";
@@ -487,6 +493,7 @@
487493
394767B21DBF987E00D72256 /* TestFailureHandler.h in Headers */,
488494
394767CE1DBF98D900D72256 /* ReactNativeSupport.h in Headers */,
489495
394767B61DBF987E00D72256 /* WebSocket.h in Headers */,
496+
39CE25AB22197F0900D78AA1 /* DetoxInstrumentsManager.h in Headers */,
490497
394767BE1DBF98A700D72256 /* EarlGreyExtensions.h in Headers */,
491498
394767B41DBF987E00D72256 /* TestRunner.h in Headers */,
492499
390D1CA01E3A4E38007F5F46 /* UNPushNotificationTrigger+PrivateHeaders.h in Headers */,
@@ -717,6 +724,7 @@
717724
398260E8220CC9530061E83E /* GREYActions+Detox.m in Sources */,
718725
39FFD9471FD730A600C97030 /* DetoxCrashHandler.mm in Sources */,
719726
46A6A63D1EF697BB00E3AA79 /* GREYConfiguration+Detox.m in Sources */,
727+
39CE25AC22197F0900D78AA1 /* DetoxInstrumentsManager.m in Sources */,
720728
);
721729
runOnlyForDeploymentPostprocessing = 0;
722730
};

detox/ios/Detox.xcodeproj/xcshareddata/xcschemes/Detox.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "0900"
3+
LastUpgradeVersion = "1020"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// DetoxInstrumentsManager.h
3+
// Detox
4+
//
5+
// Created by Leo Natan (Wix) on 2/17/19.
6+
// Copyright © 2019 Wix. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
@interface DetoxInstrumentsManager : NSObject
12+
13+
+ (NSURL*)defaultURLForTestName:(NSString*)testName;
14+
15+
- (void)startRecordingAtURL:(NSURL*)URL;
16+
- (void)continueRecordingAtURL:(NSURL*)URL;
17+
- (void)stopRecordingWithCompletionHandler:(void(^)(NSError* error))completionHandler;
18+
19+
@end
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
//
2+
// DetoxInstrumentsManager.m
3+
// Detox
4+
//
5+
// Created by Leo Natan (Wix) on 2/17/19.
6+
// Copyright © 2019 Wix. All rights reserved.
7+
//
8+
9+
#import "DetoxInstrumentsManager.h"
10+
#import "DTXLogging.h"
11+
#include <dlfcn.h>
12+
13+
DTX_CREATE_LOG_PREFIX(DetoxInstrumentsManager, @"🥶")
14+
15+
@interface NSObject ()
16+
17+
@property (class, nonatomic, strong, readonly) id defaultProfilingConfiguration;
18+
@property (nonatomic, readwrite) NSTimeInterval samplingInterval;
19+
@property (nonatomic, readwrite) BOOL recordNetwork;
20+
@property (nonatomic, readwrite) BOOL recordThreadInformation;
21+
@property (nonatomic, readwrite) BOOL collectStackTraces;
22+
@property (nonatomic, readwrite) BOOL symbolicateStackTraces;
23+
@property (atomic, assign, readonly, getter=isRecording) BOOL recording;
24+
@property (nonatomic, copy, null_resettable, readwrite) NSURL* recordingFileURL;
25+
26+
- (void)startProfilingWithConfiguration:(id)configuration;
27+
- (void)continueProfilingWithConfiguration:(id)configuration;
28+
- (void)stopProfilingWithCompletionHandler:(void(^ __nullable)(NSError* __nullable error))completionHandler;
29+
30+
@end
31+
32+
typedef NS_ENUM(NSUInteger, __DTXEventStatus) {
33+
__DTXEventStatusCompleted,
34+
__DTXEventStatusError,
35+
__DTXEventStatusCancelled
36+
};
37+
38+
static Class __DTXProfiler;
39+
static Class __DTXMutableProfilingConfiguration;
40+
41+
static void (*__DTXProfilerAddTag)(NSString* tag);
42+
static NSString* (*__DTXProfilerMarkEventIntervalBegin)(NSString* category, NSString* name, NSString* __nullable message);
43+
static void (*__DTXProfilerMarkEventIntervalEnd)(NSString* identifier, __DTXEventStatus eventStatus, NSString* __nullable endMessage);
44+
static void (*__DTXProfilerMarkEvent)(NSString* category, NSString* name, __DTXEventStatus eventStatus, NSString* __nullable startMessage);
45+
46+
@implementation DetoxInstrumentsManager
47+
{
48+
id _recorderInstance;
49+
}
50+
51+
+ (void)_loadProfiler
52+
{
53+
static dispatch_once_t onceToken;
54+
dispatch_once(&onceToken, ^{
55+
__DTXProfiler = NSClassFromString(@"DTXProfiler");
56+
57+
if(__DTXProfiler == NULL)
58+
{
59+
//The user has not linked the Profiler framework. Load it manually.
60+
61+
NSString* instrumentsPath = [NSUserDefaults.standardUserDefaults stringForKey:@"instrumentsPath"];
62+
if(instrumentsPath == nil)
63+
{
64+
instrumentsPath = @"/Applications/Detox Instruments.app";
65+
}
66+
67+
NSURL* bundleURL = [[NSURL fileURLWithPath:instrumentsPath] URLByAppendingPathComponent:@"/Contents/SharedSupport/ProfilerFramework/DTXProfiler.framework"];
68+
NSBundle* profilerBundle = [NSBundle bundleWithURL:bundleURL];
69+
70+
if(profilerBundle == nil)
71+
{
72+
dtx_log_error(@"Error loading Profiler framework bundle. Bundle not found at %@", bundleURL.path);
73+
return;
74+
}
75+
76+
NSError* error = nil;
77+
[profilerBundle loadAndReturnError:&error];
78+
79+
if(error != nil)
80+
{
81+
dtx_log_error(@"Error loading Profiler framework bundle: %@", error);
82+
return;
83+
}
84+
}
85+
86+
__DTXProfiler = NSClassFromString(@"DTXProfiler");
87+
if(__DTXProfiler == NULL)
88+
{
89+
dtx_log_error(@"DTXProfiler class not found—this should not have happened!");
90+
return;
91+
}
92+
93+
__DTXMutableProfilingConfiguration = NSClassFromString(@"DTXMutableProfilingConfiguration");
94+
if(__DTXMutableProfilingConfiguration == NULL)
95+
{
96+
dtx_log_error(@"DTXMutableProfilingConfiguration class not found—this should not have happened!");
97+
return;
98+
}
99+
100+
__DTXProfilerAddTag = dlsym(RTLD_DEFAULT, "DTXProfilerAddTag");
101+
__DTXProfilerMarkEventIntervalBegin = dlsym(RTLD_DEFAULT, "DTXProfilerMarkEventIntervalBegin");
102+
__DTXProfilerMarkEventIntervalEnd = dlsym(RTLD_DEFAULT, "DTXProfilerMarkEventIntervalEnd");
103+
__DTXProfilerMarkEvent = dlsym(RTLD_DEFAULT, "DTXProfilerMarkEvent");
104+
105+
if(__DTXProfilerAddTag == NULL || __DTXProfilerMarkEventIntervalBegin == NULL || __DTXProfilerMarkEventIntervalEnd == NULL || __DTXProfilerMarkEvent == NULL)
106+
{
107+
dtx_log_error(@"One or more DTXProfilerAPI functions are NULL—this should not have happened!");
108+
return;
109+
}
110+
});
111+
}
112+
113+
+ (NSString *)_sanitizeFileNameString:(NSString *)fileName
114+
{
115+
NSCharacterSet* illegalFileNameCharacters = [NSCharacterSet characterSetWithCharactersInString:@":/\\?%*|\"<>"];
116+
return [[fileName componentsSeparatedByCharactersInSet:illegalFileNameCharacters] componentsJoinedByString:@"_"];
117+
}
118+
119+
+ (NSURL*)defaultURLForTestName:(NSString*)testName
120+
{
121+
NSURL* documents = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
122+
NSURL* rv = [documents URLByAppendingPathComponent:[self _sanitizeFileNameString:testName]];
123+
124+
dtx_log_debug(@"Returning %@ as URL", rv.path);
125+
126+
return rv;
127+
}
128+
129+
- (instancetype)init
130+
{
131+
[DetoxInstrumentsManager _loadProfiler];
132+
133+
self = [super init];
134+
135+
if(self)
136+
{
137+
_recorderInstance = [__DTXProfiler new];
138+
}
139+
140+
return self;
141+
}
142+
143+
- (id)_configForDetoxRecordingWithURL:(NSURL*)URL
144+
{
145+
id config = [__DTXMutableProfilingConfiguration defaultProfilingConfiguration];
146+
[config setRecordingFileURL:URL];
147+
148+
//TODO: Finalize the actual config for Detox perf recording.
149+
[config setRecordNetwork:NO];
150+
[config setRecordThreadInformation:YES];
151+
[config setCollectStackTraces:YES];
152+
[config setSymbolicateStackTraces:YES];
153+
[config setSamplingInterval:0.1];
154+
155+
return config;
156+
}
157+
158+
- (void)startRecordingAtURL:(NSURL*)URL
159+
{
160+
[_recorderInstance startProfilingWithConfiguration:[self _configForDetoxRecordingWithURL:URL]];
161+
}
162+
163+
- (void)continueRecordingAtURL:(NSURL*)URL
164+
{
165+
[_recorderInstance continueProfilingWithConfiguration:[self _configForDetoxRecordingWithURL:URL]];
166+
}
167+
168+
- (void)stopRecordingWithCompletionHandler:(void(^)(NSError* error))completionHandler
169+
{
170+
if(_recorderInstance == nil || [_recorderInstance isRecording] == NO)
171+
{
172+
if(completionHandler != nil)
173+
{
174+
completionHandler(nil);
175+
}
176+
177+
return;
178+
}
179+
180+
[_recorderInstance stopProfilingWithCompletionHandler:completionHandler];
181+
}
182+
183+
@end

0 commit comments

Comments
 (0)