Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timer observation overhaul #74

Merged
merged 3 commits into from
Jan 10, 2017
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
16 changes: 8 additions & 8 deletions detox/ios/Detox.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
394767CF1DBF98D900D72256 /* ReactNativeSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 394767C61DBF98D900D72256 /* ReactNativeSupport.m */; };
394767D01DBF98D900D72256 /* WXJSDisplayLinkIdlingResource.h in Headers */ = {isa = PBXBuildFile; fileRef = 394767C71DBF98D900D72256 /* WXJSDisplayLinkIdlingResource.h */; };
394767D11DBF98D900D72256 /* WXJSDisplayLinkIdlingResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 394767C81DBF98D900D72256 /* WXJSDisplayLinkIdlingResource.m */; };
394767D21DBF98D900D72256 /* WXJSTimerCounterIdlingResource.h in Headers */ = {isa = PBXBuildFile; fileRef = 394767C91DBF98D900D72256 /* WXJSTimerCounterIdlingResource.h */; };
394767D31DBF98D900D72256 /* WXJSTimerCounterIdlingResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 394767CA1DBF98D900D72256 /* WXJSTimerCounterIdlingResource.m */; };
394767D21DBF98D900D72256 /* WXJSTimerObservationIdlingResource.h in Headers */ = {isa = PBXBuildFile; fileRef = 394767C91DBF98D900D72256 /* WXJSTimerObservationIdlingResource.h */; };
394767D31DBF98D900D72256 /* WXJSTimerObservationIdlingResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 394767CA1DBF98D900D72256 /* WXJSTimerObservationIdlingResource.m */; };
394767D41DBF98D900D72256 /* WXRunLoopIdlingResource.h in Headers */ = {isa = PBXBuildFile; fileRef = 394767CB1DBF98D900D72256 /* WXRunLoopIdlingResource.h */; };
394767D51DBF98D900D72256 /* WXRunLoopIdlingResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 394767CC1DBF98D900D72256 /* WXRunLoopIdlingResource.m */; };
394767F61DBF994200D72256 /* SocketRocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 394767E71DBF992400D72256 /* SocketRocket.framework */; };
Expand Down Expand Up @@ -156,8 +156,8 @@
394767C61DBF98D900D72256 /* ReactNativeSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReactNativeSupport.m; sourceTree = "<group>"; };
394767C71DBF98D900D72256 /* WXJSDisplayLinkIdlingResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXJSDisplayLinkIdlingResource.h; sourceTree = "<group>"; };
394767C81DBF98D900D72256 /* WXJSDisplayLinkIdlingResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXJSDisplayLinkIdlingResource.m; sourceTree = "<group>"; };
394767C91DBF98D900D72256 /* WXJSTimerCounterIdlingResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXJSTimerCounterIdlingResource.h; sourceTree = "<group>"; };
394767CA1DBF98D900D72256 /* WXJSTimerCounterIdlingResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXJSTimerCounterIdlingResource.m; sourceTree = "<group>"; };
394767C91DBF98D900D72256 /* WXJSTimerObservationIdlingResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXJSTimerObservationIdlingResource.h; sourceTree = "<group>"; };
394767CA1DBF98D900D72256 /* WXJSTimerObservationIdlingResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXJSTimerObservationIdlingResource.m; sourceTree = "<group>"; };
394767CB1DBF98D900D72256 /* WXRunLoopIdlingResource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXRunLoopIdlingResource.h; sourceTree = "<group>"; };
394767CC1DBF98D900D72256 /* WXRunLoopIdlingResource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXRunLoopIdlingResource.m; sourceTree = "<group>"; };
394767D71DBF991E00D72256 /* EarlGrey.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = EarlGrey.xcodeproj; path = EarlGrey/EarlGrey.xcodeproj; sourceTree = "<group>"; };
Expand Down Expand Up @@ -235,8 +235,8 @@
394767C61DBF98D900D72256 /* ReactNativeSupport.m */,
394767C71DBF98D900D72256 /* WXJSDisplayLinkIdlingResource.h */,
394767C81DBF98D900D72256 /* WXJSDisplayLinkIdlingResource.m */,
394767C91DBF98D900D72256 /* WXJSTimerCounterIdlingResource.h */,
394767CA1DBF98D900D72256 /* WXJSTimerCounterIdlingResource.m */,
394767C91DBF98D900D72256 /* WXJSTimerObservationIdlingResource.h */,
394767CA1DBF98D900D72256 /* WXJSTimerObservationIdlingResource.m */,
394767CB1DBF98D900D72256 /* WXRunLoopIdlingResource.h */,
394767CC1DBF98D900D72256 /* WXRunLoopIdlingResource.m */,
);
Expand Down Expand Up @@ -280,7 +280,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
394767D21DBF98D900D72256 /* WXJSTimerCounterIdlingResource.h in Headers */,
394767D21DBF98D900D72256 /* WXJSTimerObservationIdlingResource.h in Headers */,
394767CD1DBF98D900D72256 /* ReactNativeHeaders.h in Headers */,
394767D41DBF98D900D72256 /* WXRunLoopIdlingResource.h in Headers */,
394767BF1DBF98A700D72256 /* GREYCondition+Detox.h in Headers */,
Expand Down Expand Up @@ -456,7 +456,7 @@
394767D51DBF98D900D72256 /* WXRunLoopIdlingResource.m in Sources */,
394767CF1DBF98D900D72256 /* ReactNativeSupport.m in Sources */,
394767B51DBF987E00D72256 /* TestRunner.m in Sources */,
394767D31DBF98D900D72256 /* WXJSTimerCounterIdlingResource.m in Sources */,
394767D31DBF98D900D72256 /* WXJSTimerObservationIdlingResource.m in Sources */,
394767C01DBF98A700D72256 /* GREYCondition+Detox.m in Sources */,
394767D11DBF98D900D72256 /* WXJSDisplayLinkIdlingResource.m in Sources */,
394767B11DBF987E00D72256 /* MethodInvocation.m in Sources */,
Expand Down
12 changes: 6 additions & 6 deletions detox/ios/Detox/ReactNativeSupport.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

#import "WXRunLoopIdlingResource.h"
#import "WXJSDisplayLinkIdlingResource.h"
#import "WXJSTimerCounterIdlingResource.h"
#import "WXJSTimerObservationIdlingResource.h"

@import ObjectiveC;
@import Darwin;
Expand Down Expand Up @@ -80,12 +80,12 @@ void setupForTests()
orig_runRunLoopThread = (void(*)(id, SEL))method_getImplementation(m);
method_setImplementation(m, (IMP)swz_runRunLoopThread);

cls = NSClassFromString(@"RCTDisplayLink");
m = class_getInstanceMethod(cls, NSSelectorFromString(@"addToRunLoop:"));
orig_addToRunLoop = (void(*)(id, SEL, NSRunLoop*))method_getImplementation(m);
method_setImplementation(m, (IMP)swz_addToRunLoop);
// cls = NSClassFromString(@"RCTDisplayLink");
// m = class_getInstanceMethod(cls, NSSelectorFromString(@"addToRunLoop:"));
// orig_addToRunLoop = (void(*)(id, SEL, NSRunLoop*))method_getImplementation(m);
// method_setImplementation(m, (IMP)swz_addToRunLoop);

[[GREYUIThreadExecutor sharedInstance] registerIdlingResource:[WXJSTimerCounterIdlingResource new]];
[[GREYUIThreadExecutor sharedInstance] registerIdlingResource:[WXJSTimerObservationIdlingResource new]];
}

@implementation ReactNativeSupport
Expand Down
4 changes: 2 additions & 2 deletions detox/ios/Detox/TestFailureHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ - (void)handleException:(GREYFrameworkException *)exception details:(NSString *)
{
NSString *description = [NSString stringWithFormat:@"%@\n%@", [exception description], details];

NSLog(@"Detox Test Failed:\n%@", description);
NSLog(@"☣️ DETOX:: Test Failed:\n%@", description);

UIWindow* window = [[UIApplication sharedApplication] keyWindow];
NSLog(@"UI Hierarchy on test failure:\n%@", [GREYElementHierarchy hierarchyStringForElement:window]);
NSLog(@"☣️ DETOX:: UI Hierarchy on test failure:\n%@", [GREYElementHierarchy hierarchyStringForElement:window]);

if (self.delegate) [self.delegate onTestFailed:description];
}
Expand Down
2 changes: 2 additions & 0 deletions detox/ios/Detox/WXJSDisplayLinkIdlingResource.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ - (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink

- (BOOL)isIdleNow
{
NSLog(@"☣️ DETOX:: DisplayLinkIdleResource: %@", @(_displayLink.isPaused));

return _displayLink.isPaused;
}

Expand Down
14 changes: 0 additions & 14 deletions detox/ios/Detox/WXJSTimerCounterIdlingResource.h

This file was deleted.

85 changes: 0 additions & 85 deletions detox/ios/Detox/WXJSTimerCounterIdlingResource.m

This file was deleted.

16 changes: 16 additions & 0 deletions detox/ios/Detox/WXJSTimerObservationIdlingResource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// WXJSTimerObservationIdlingResource.h
// Detox
//
// Created by Leo Natan (Wix) on 14/10/2016.
// Copyright © 2016 Wix. All rights reserved.
//

@import Foundation;
#import <EarlGrey/EarlGrey.h>

@interface WXJSTimerObservationIdlingResource : NSObject <GREYIdlingResource>

- (void)setDurationThreshold:(NSTimeInterval)durationThreshold;

@end
175 changes: 175 additions & 0 deletions detox/ios/Detox/WXJSTimerObservationIdlingResource.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//
// WXJSTimerObservationIdlingResource.m
// Detox
//
// Created by Leo Natan (Wix) on 14/10/2016.
// Copyright © 2016 Wix. All rights reserved.
//

#import "WXJSTimerObservationIdlingResource.h"
@import ObjectiveC;

@interface _WXJSTimingObservationWrapper : NSObject @end
@implementation _WXJSTimingObservationWrapper
{
NSMutableArray<NSNumber*>* _observedTimers;
NSMutableDictionary* _timers;
}

- (instancetype)initWithTimers:(NSMutableDictionary*)timers
{
self = [super init];
if(self)
{
_timers = timers;
_observedTimers = [NSMutableArray new];
}

return self;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature* sig = [super methodSignatureForSelector:aSelector];

if(sig == nil)
{
sig = [_timers methodSignatureForSelector:aSelector];
}

return sig;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation invokeWithTarget:_timers];
}

- (void)addObservedTimer:(NSNumber*)observedNumber
{
[_observedTimers addObject:observedNumber];
}

- (NSUInteger)countOfObservedTimers
{
return _observedTimers.count;
}

- (void)removeObjectForKey:(NSNumber*)aKey
{
if([_observedTimers containsObject:aKey])
{
NSLog(@"☣️ DETOX:: Removing observed timer %@", aKey);
[_observedTimers removeObject:aKey];
}

[_timers removeObjectForKey:aKey];
}

@end

@implementation WXJSTimerObservationIdlingResource
{
NSMapTable<id, _WXJSTimingObservationWrapper*>* _observations;
dispatch_queue_t _timersObservationQueue;
NSTimeInterval _durationThreshold;
}

- (void)setDurationThreshold:(NSTimeInterval)durationThreshold
{
_durationThreshold = durationThreshold;
}

- (NSString*)failuireReasonForDuration:(NSTimeInterval)duration repeats:(BOOL)repeats
{
if(repeats == YES)
{
return @"repeats==true";
}
else if(duration > _durationThreshold)
{
return [NSString stringWithFormat:@"duration>%@", @(_durationThreshold)];
}

return @"";
}

- (instancetype)init
{
self = [super init];
if(self)
{
_timersObservationQueue = dispatch_queue_create("_timersCounterSerialQueue", NULL);
_observations = [NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory];
_durationThreshold = 1.5;

__weak __typeof(self) weakSelf = self;

Class cls = NSClassFromString(@"RCTTiming");
SEL createTimerSel = NSSelectorFromString(@"createTimer:duration:jsSchedulingTime:repeats:");
Method m = class_getInstanceMethod(cls, createTimerSel);

void (*orig_createTimer)(id, SEL, NSNumber*, NSTimeInterval, NSDate*, BOOL) = (void(*)(id, SEL, NSNumber*, NSTimeInterval, NSDate*, BOOL))method_getImplementation(m);
method_setImplementation(m, imp_implementationWithBlock(^(id _self, NSNumber* timerID, NSTimeInterval duration, NSDate* jsDate, BOOL repeats) {
__strong __typeof(weakSelf) strongSelf = weakSelf;

dispatch_sync(_timersObservationQueue, ^{
_WXJSTimingObservationWrapper* _observationWrapper = [strongSelf->_observations objectForKey:_self];

if(_observationWrapper == nil)
{
_observationWrapper = [[_WXJSTimingObservationWrapper alloc] initWithTimers:[_self valueForKey:@"_timers"]];
[_self setValue:_observationWrapper forKey:@"_timers"];
[_observations setObject:_observationWrapper forKey:_self];
}


if(duration <= _durationThreshold && repeats == NO)
{
NSLog(@"☣️ DETOX:: Observing timer: %@ d: %@ r: %@", timerID, @(duration), @(repeats));

[_observationWrapper addObservedTimer:timerID];
}
else
{
NSLog(@"☣️ DETOX:: Ignoring timer: %@ failure reason: \"%@\"", timerID, [self failuireReasonForDuration:duration repeats:repeats]);
}
});

orig_createTimer(_self, createTimerSel, timerID, duration, jsDate, repeats);
}));
}
return self;
}

- (BOOL)isIdleNow
{
__block BOOL rv = YES;

dispatch_sync(_timersObservationQueue, ^{
NSUInteger observedTimersCount = 0;

for(_WXJSTimingObservationWrapper* wrapper in _observations.objectEnumerator)
{
observedTimersCount += wrapper.countOfObservedTimers;
}

rv = observedTimersCount == 0;
});

NSLog(@"☣️ DETOX:: TimerIdleResource: %@", @(rv));

return rv;
}

- (NSString *)idlingResourceName
{
return NSStringFromClass([self class]);
}

- (NSString *)idlingResourceDescription
{
return [self idlingResourceName];
}

@end
Loading