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

test runtime lifecycle callback #37897

Closed
wants to merge 7 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,33 @@

#import <XCTest/XCTest.h>

#import <RCTTestUtils/RCTMemoryUtils.h>
#import <RCTTestUtils/ShimRCTInstance.h>
#import <React/RCTLog.h>
#import <React/RCTMockDef.h>
#import <ReactCommon/RCTHermesInstance.h>
#import <ReactCommon/RCTHost.h>
#import <ReactCommon/RCTInstance.h>
#import <ReactCommon/RCTTurboModuleManager.h>

#import <OCMock/OCMock.h>

RCT_MOCK_REF(RCTHost, _RCTLogNativeInternal);

RCTLogLevel gLogLevel;
int gLogCalledTimes = 0;
NSString *gLogMessage = nil;
static void RCTLogNativeInternalMock(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...)
{
gLogLevel = level;
gLogCalledTimes++;

va_list args;
va_start(args, format);
gLogMessage = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
}

@interface RCTHostTests : XCTestCase
@end

Expand All @@ -29,6 +48,8 @@ - (void)setUp
{
[super setUp];

RCTAutoReleasePoolPush();

shimmedRCTInstance = [ShimRCTInstance new];

_mockHostDelegate = OCMProtocolMock(@protocol(RCTHostDelegate));
Expand All @@ -42,16 +63,96 @@ - (void)setUp

- (void)tearDown
{
RCTAutoReleasePoolPop();

_subject = nil;
XCTAssertEqual(RCTGetRetainCount(_subject), 0);

_mockHostDelegate = nil;
XCTAssertEqual(RCTGetRetainCount(_mockHostDelegate), 0);

[shimmedRCTInstance reset];
gLogCalledTimes = 0;
gLogMessage = nil;

[super tearDown];
}

- (void)testStart
{
RCT_MOCK_SET(RCTHost, _RCTLogNativeInternal, RCTLogNativeInternalMock);

XCTAssertEqual(shimmedRCTInstance.initCount, 0);
[_subject start];
OCMVerify(OCMTimes(1), [_mockHostDelegate hostDidStart:_subject]);
XCTAssertEqual(shimmedRCTInstance.initCount, 1);
XCTAssertEqual(gLogCalledTimes, 0);

XCTAssertEqual(shimmedRCTInstance.invalidateCount, 0);
[_subject start];
XCTAssertEqual(shimmedRCTInstance.initCount, 2);
XCTAssertEqual(shimmedRCTInstance.invalidateCount, 1);
OCMVerify(OCMTimes(2), [_mockHostDelegate hostDidStart:_subject]);
XCTAssertEqual(gLogLevel, RCTLogLevelWarning);
XCTAssertEqual(gLogCalledTimes, 1);
XCTAssertEqualObjects(
gLogMessage,
@"RCTHost should not be creating a new instance if one already exists. This implies there is a bug with how/when this method is being called.");

RCT_MOCK_RESET(RCTHost, _RCTLogNativeInternal);
}

- (void)testCallFunctionOnJSModule
{
[_subject start];

NSArray *args = @[ @"hi", @(5), @(NO) ];
[_subject callFunctionOnJSModule:@"jsModule" method:@"method" args:args];

XCTAssertEqualObjects(shimmedRCTInstance.jsModuleName, @"jsModule");
XCTAssertEqualObjects(shimmedRCTInstance.method, @"method");
XCTAssertEqualObjects(shimmedRCTInstance.args, args);
}

- (void)testDidReceiveErrorStack
{
id<RCTInstanceDelegate> instanceDelegate = (id<RCTInstanceDelegate>)_subject;

NSMutableArray<NSDictionary<NSString *, id> *> *stack = [NSMutableArray array];

NSMutableDictionary<NSString *, id> *stackFrame0 = [NSMutableDictionary dictionary];
stackFrame0[@"linenumber"] = @(3);
stackFrame0[@"column"] = @(4);
stackFrame0[@"methodname"] = @"method1";
stackFrame0[@"file"] = @"file1.js";
[stack addObject:stackFrame0];

NSMutableDictionary<NSString *, id> *stackFrame1 = [NSMutableDictionary dictionary];
stackFrame0[@"linenumber"] = @(63);
stackFrame0[@"column"] = @(44);
stackFrame0[@"methodname"] = @"method2";
stackFrame0[@"file"] = @"file2.js";
[stack addObject:stackFrame1];

[instanceDelegate instance:[OCMArg any] didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES];

OCMVerify(
OCMTimes(1),
[_mockHostDelegate host:_subject didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES]);
}

- (void)testDidInitializeRuntime
{
id<RCTHostRuntimeDelegate> mockRuntimeDelegate = OCMProtocolMock(@protocol(RCTHostRuntimeDelegate));
_subject.runtimeDelegate = mockRuntimeDelegate;

auto hermesRuntime = facebook::hermes::makeHermesRuntime();
facebook::jsi::Runtime *rt = hermesRuntime.get();

id<RCTInstanceDelegate> instanceDelegate = (id<RCTInstanceDelegate>)_subject;
[instanceDelegate instance:[OCMArg any] didInitializeRuntime:*rt];

OCMVerify(OCMTimes(1), [mockRuntimeDelegate host:_subject didInitializeRuntime:*rt]);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
#import <React/RCTFabricSurface.h>
#import <React/RCTJSThread.h>
#import <React/RCTLog.h>
#import <React/RCTMockDef.h>
#import <React/RCTPerformanceLogger.h>
#import <React/RCTReloadCommand.h>

RCT_MOCK_DEF(RCTHost, _RCTLogNativeInternal);
#define _RCTLogNativeInternal RCT_MOCK_USE(RCTHost, _RCTLogNativeInternal)

using namespace facebook::react;

@interface RCTHost () <RCTReloadListener, RCTInstanceDelegate>
Expand Down Expand Up @@ -230,28 +234,13 @@ - (void)dealloc

#pragma mark - RCTInstanceDelegate

- (void)instance:(RCTInstance *)instance didReceiveErrorMap:(facebook::react::MapBuffer)errorMap
- (void)instance:(RCTInstance *)instance
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
message:(NSString *)message
exceptionId:(NSUInteger)exceptionId
isFatal:(BOOL)isFatal
{
NSString *message = [NSString stringWithCString:errorMap.getString(JSErrorHandlerKey::kErrorMessage).c_str()
encoding:[NSString defaultCStringEncoding]];
std::vector<facebook::react::MapBuffer> frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames);
NSMutableArray<NSDictionary<NSString *, id> *> *stack = [NSMutableArray new];
for (facebook::react::MapBuffer const &mapBuffer : frames) {
NSDictionary *frame = @{
@"file" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameFileName).c_str()
encoding:[NSString defaultCStringEncoding]],
@"methodName" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameMethodName).c_str()
encoding:[NSString defaultCStringEncoding]],
@"lineNumber" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameLineNumber)],
@"column" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameColumnNumber)],
};
[stack addObject:frame];
}
[_hostDelegate host:self
didReceiveJSErrorStack:stack
message:message
exceptionId:errorMap.getInt(JSErrorHandlerKey::kExceptionId)
isFatal:errorMap.getBool(JSErrorHandlerKey::kIsFatal)];
[_hostDelegate host:self didReceiveJSErrorStack:stack message:message exceptionId:exceptionId isFatal:isFatal];
}

- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ FB_RUNTIME_PROTOCOL

@protocol RCTInstanceDelegate <RCTContextContainerHandling>

- (void)instance:(RCTInstance *)instance didReceiveErrorMap:(facebook::react::MapBuffer)errorMap;
- (void)instance:(RCTInstance *)instance
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
message:(NSString *)message
exceptionId:(NSUInteger)exceptionId
isFatal:(BOOL)isFatal;

- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime;

@end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,26 @@ - (void)_notifyEventDispatcherObserversOfEvent_DEPRECATED:(NSNotification *)noti

- (void)_handleJSErrorMap:(facebook::react::MapBuffer)errorMap
{
[_delegate instance:self didReceiveErrorMap:std::move(errorMap)];
NSString *message = [NSString stringWithCString:errorMap.getString(JSErrorHandlerKey::kErrorMessage).c_str()
encoding:[NSString defaultCStringEncoding]];
std::vector<facebook::react::MapBuffer> frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames);
NSMutableArray<NSDictionary<NSString *, id> *> *stack = [NSMutableArray new];
for (facebook::react::MapBuffer const &mapBuffer : frames) {
NSDictionary *frame = @{
@"file" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameFileName).c_str()
encoding:[NSString defaultCStringEncoding]],
@"methodName" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameMethodName).c_str()
encoding:[NSString defaultCStringEncoding]],
@"lineNumber" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameLineNumber)],
@"column" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameColumnNumber)],
};
[stack addObject:frame];
}
[_delegate instance:self
didReceiveJSErrorStack:stack
message:message
exceptionId:errorMap.getInt(JSErrorHandlerKey::kExceptionId)
isFatal:errorMap.getBool(JSErrorHandlerKey::kIsFatal)];
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import <Foundation/Foundation.h>

#import <React/RCTDefines.h>

RCT_EXTERN_C_BEGIN

int RCTGetRetainCount(id _Nullable object);

void RCTAutoReleasePoolPush(void);
void RCTAutoReleasePoolPop(void);

RCT_EXTERN_C_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import "RCTMemoryUtils.h"

int RCTGetRetainCount(id _Nullable object)
{
return object != nil ? CFGetRetainCount((__bridge CFTypeRef)object) - 1 : 0;
}

OBJC_EXPORT
void *objc_autoreleasePoolPush(void) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);

OBJC_EXPORT
void objc_autoreleasePoolPop(void *context) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);

static NSString *const kAutoreleasePoolContextStackKey = @"autorelease_pool_context_stack";

void RCTAutoReleasePoolPush(void)
{
assert([NSThread isMainThread]);
NSMutableDictionary *dictionary = [[NSThread currentThread] threadDictionary];
void *context = objc_autoreleasePoolPush();
NSMutableArray<NSValue *> *contextStack = dictionary[kAutoreleasePoolContextStackKey];
if (!contextStack) {
contextStack = [NSMutableArray array];
dictionary[kAutoreleasePoolContextStackKey] = contextStack;
}
[contextStack addObject:[NSValue valueWithPointer:context]];
}

void RCTAutoReleasePoolPop(void)
{
assert([NSThread isMainThread]);
NSMutableDictionary *dictionary = [[NSThread currentThread] threadDictionary];
NSMutableArray<NSValue *> *contextStack = dictionary[kAutoreleasePoolContextStackKey];
assert(contextStack.count > 0);
NSValue *lastContext = contextStack.lastObject;
[contextStack removeLastObject];
objc_autoreleasePoolPop(lastContext.pointerValue);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@

@interface ShimRCTInstance : NSObject

@property (assign) int initCount;
@property int initCount;
@property int invalidateCount;

@property NSString *jsModuleName;
@property NSString *method;
@property NSArray *args;

- (void)reset;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ - (instancetype)init
[ShimRCTInstance class],
@selector(initWithDelegate:
jsEngineInstance:bundleManager:turboModuleManagerDelegate:onInitialBundleLoad:moduleRegistry:));
RCTSwizzleInstanceSelector([RCTInstance class], [ShimRCTInstance class], @selector(invalidate));
RCTSwizzleInstanceSelector(
[RCTInstance class], [ShimRCTInstance class], @selector(callFunctionOnJSModule:method:args:));
weakShim = self;
}
return self;
Expand All @@ -36,7 +39,11 @@ - (void)reset
[ShimRCTInstance class],
@selector(initWithDelegate:
jsEngineInstance:bundleManager:turboModuleManagerDelegate:onInitialBundleLoad:moduleRegistry:));
RCTSwizzleInstanceSelector([RCTInstance class], [ShimRCTInstance class], @selector(invalidate));
RCTSwizzleInstanceSelector(
[RCTInstance class], [ShimRCTInstance class], @selector(callFunctionOnJSModule:method:args:));
_initCount = 0;
_invalidateCount = 0;
}

- (instancetype)initWithDelegate:(id<RCTInstanceDelegate>)delegate
Expand All @@ -50,4 +57,16 @@ - (instancetype)initWithDelegate:(id<RCTInstanceDelegate>)delegate
return self;
}

- (void)invalidate
{
weakShim.invalidateCount++;
}

- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args
{
weakShim.jsModuleName = moduleName;
weakShim.method = method;
weakShim.args = [args copy];
}

@end